diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c9de045e..7f5c08264 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: restore-keys: | ${{ runner.os }}-m2 - name: Test with Maven - run: ./mvnw clean package -B -Dmaven.test.skip=false -pl fesod-common,fesod-shaded,fesod-sheet + run: ./mvnw clean package -B -Dmaven.test.skip=false -pl fesod-common,fesod-shaded,fesod-sheet,fesod-cli - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: (!cancelled()) diff --git a/fesod-cli/pom.xml b/fesod-cli/pom.xml new file mode 100644 index 000000000..642997ee3 --- /dev/null +++ b/fesod-cli/pom.xml @@ -0,0 +1,175 @@ + + + + 4.0.0 + + + org.apache.fesod + fesod-parent + ${revision} + + + fesod-cli + jar + Apache Fesod CLI Tool + Command-line interface for Apache Fesod spreadsheet processing + + + 4.7.5 + 2.2 + org.apache.fesod.cli.FesodCli + + + + + + org.apache.fesod + fesod-sheet + ${project.version} + + + + + info.picocli + picocli + ${picocli.version} + + + + + com.alibaba.fastjson2 + fastjson2 + compile + + + + + org.yaml + snakeyaml + + + + + org.apache.commons + commons-csv + + + + + ch.qos.logback + logback-classic + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + info.picocli + picocli-codegen + ${picocli.version} + + + org.projectlombok + lombok + ${lombok.version} + + + + -Aproject=${project.groupId}/${project.artifactId} + + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + false + + + ${mainClass} + + ${project.name} + ${project.version} + Apache Software Foundation + true + + + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.6.0 + + + src/assembly/bin.xml + + posix + + + + make-assembly + package + + single + + + + + + + diff --git a/fesod-cli/src/assembly/bin.xml b/fesod-cli/src/assembly/bin.xml new file mode 100644 index 000000000..6c0d5c73a --- /dev/null +++ b/fesod-cli/src/assembly/bin.xml @@ -0,0 +1,84 @@ + + + + bin + + tar.gz + zip + + true + fesod-cli-${project.version} + + + + + ${project.build.directory} + lib + + fesod-cli-${project.version}.jar + + + + + + ${project.basedir}/src/main/scripts + bin + + fesod-cli + fesod-cli.bat + + 755 + + + + + ${project.basedir}/src/main/resources + conf + + logback.xml + default-config.yaml + + + + + + ${project.basedir}/.. + . + + LICENSE + NOTICE + DISCLAIMER + + + + + + ${project.basedir}/../dist/licenses + licenses + + **/* + + + + diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/FesodCli.java b/fesod-cli/src/main/java/org/apache/fesod/cli/FesodCli.java new file mode 100644 index 000000000..62d9619ff --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/FesodCli.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli; + +import org.apache.fesod.cli.commands.ConvertCommand; +import org.apache.fesod.cli.commands.InfoCommand; +import org.apache.fesod.cli.commands.ReadCommand; +import org.apache.fesod.cli.commands.VersionCommand; +import org.apache.fesod.cli.commands.WriteCommand; +import org.apache.fesod.cli.exception.CliException; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.Spec; + +@Command( + name = "fesod-cli", + mixinStandardHelpOptions = true, + version = {"Apache Fesod CLI", "Java Runtime: ${java.version}", "OS: ${os.name} ${os.arch}"}, + description = "Fast and Easy spreadsheet processing from the command line", + subcommands = { + ReadCommand.class, + WriteCommand.class, + ConvertCommand.class, + InfoCommand.class, + VersionCommand.class, + CommandLine.HelpCommand.class + }, + usageHelpAutoWidth = true, + footer = { + "", + "Examples:", + " fesod-cli read data.xlsx --format json", + " fesod-cli convert input.xls output.xlsx", + " fesod-cli convert input.xlsx output.csv --sheet 0", + " fesod-cli convert input.xlsx output.xlsx --sheet-name \"Sales\"", + " fesod-cli info data.xlsx", + "", + "Documentation: https://fesod.apache.org/docs/cli", + "Report bugs: https://github.com/apache/fesod/issues" + }) +public class FesodCli implements Runnable { + + @Spec + CommandSpec spec; + + @Option( + names = {"--verbose", "-v"}, + description = "Enable verbose logging") + private boolean verbose; + + @Override + public void run() { + // Default: show help when no command specified + spec.commandLine().usage(spec.commandLine().getOut()); + } + + public static void main(String[] args) { + int exitCode = new CommandLine(new FesodCli()) + .setExecutionExceptionHandler((ex, cmd, parseResult) -> { + cmd.getErr().println(cmd.getColorScheme().errorText("Error: " + ex.getMessage())); + + if (ex instanceof CliException) { + CliException cliEx = (CliException) ex; + if (cliEx.getCause() != null && parseResult.hasMatchedOption("--verbose")) { + cliEx.printStackTrace(cmd.getErr()); + } + return cliEx.getExitCode(); + } else { + if (parseResult.hasMatchedOption("--verbose")) { + ex.printStackTrace(cmd.getErr()); + } + return 1; + } + }) + .execute(args); + + System.exit(exitCode); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/commands/BaseCommand.java b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/BaseCommand.java new file mode 100644 index 000000000..d4ebc7b8b --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/BaseCommand.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import java.io.PrintWriter; +import java.nio.file.Paths; +import org.apache.fesod.cli.config.CliConfig; +import org.apache.fesod.cli.config.ConfigLoader; +import org.apache.fesod.cli.core.DocumentProcessor; +import org.apache.fesod.cli.core.ModuleRegistry; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.Spec; + +/** + * Base class for all CLI commands + */ +public abstract class BaseCommand implements Runnable { + + @Spec + protected CommandSpec spec; + + @Option( + names = {"--config", "-c"}, + description = "Configuration file path (default: ~/.fesod/config.yaml)", + paramLabel = "") + protected String configFile; + + @Option( + names = {"--module", "-m"}, + description = "Document module: sheet (default: sheet)", + defaultValue = "sheet") + protected String module; + + protected CliConfig config; + protected DocumentProcessor processor; + + /** + * Get the output print writer for this command + */ + protected PrintWriter getOut() { + return spec.commandLine().getOut(); + } + + protected void initialize() { + ConfigLoader loader = new ConfigLoader(); + if (configFile != null) { + config = loader.loadFromFile(Paths.get(configFile)); + } else { + config = loader.loadDefault(); + } + + processor = ModuleRegistry.getProcessor(module); + } + + @Override + public void run() { + initialize(); + execute(); + } + + protected abstract void execute(); +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/commands/ConvertCommand.java b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/ConvertCommand.java new file mode 100644 index 000000000..1cdba375a --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/ConvertCommand.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +/** + * Convert command implementation + */ +@Command( + name = "convert", + description = "Convert spreadsheet between different formats", + mixinStandardHelpOptions = true) +public class ConvertCommand extends BaseCommand { + + @Parameters(index = "0", description = "Input file path", paramLabel = "") + private String inputFile; + + @Parameters(index = "1", description = "Output file path", paramLabel = "") + private String outputFile; + + @Option( + names = {"-s", "--sheet"}, + description = "Sheet index (0-based) to convert. If not specified, all sheets will be converted.") + private Integer sheetIndex; + + @Option( + names = {"-n", "--sheet-name"}, + description = "Sheet name to convert") + private String sheetName; + + @Option( + names = {"-a", "--all"}, + description = "Convert all sheets (default if no sheet is specified)") + private Boolean convertAll; + + @Override + protected void execute() { + Path input = Paths.get(inputFile); + Path output = Paths.get(outputFile); + + Map options = new HashMap(); + options.put("sheetIndex", sheetIndex); + options.put("sheetName", sheetName); + options.put("convertAll", convertAll); + + processor.convert(input, output, options); + + String sheetInfo = ""; + if (sheetIndex != null) { + sheetInfo = " (sheet " + sheetIndex + ")"; + } else if (sheetName != null) { + sheetInfo = " (sheet '" + sheetName + "')"; + } else { + sheetInfo = " (all sheets)"; + } + + getOut().println("✓ Conversion completed" + sheetInfo + ": " + inputFile + " → " + outputFile); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/commands/InfoCommand.java b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/InfoCommand.java new file mode 100644 index 000000000..7b37189ab --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/InfoCommand.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import org.apache.fesod.cli.formatters.FormatterFactory; +import org.apache.fesod.cli.formatters.OutputFormatter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Parameters; + +/** + * Info command implementation + */ +@Command(name = "info", description = "Display spreadsheet file information", mixinStandardHelpOptions = true) +public class InfoCommand extends BaseCommand { + + @Parameters(index = "0", description = "Input file path", paramLabel = "") + private String inputFile; + + @Override + protected void execute() { + Path input = Paths.get(inputFile); + + Map info = processor.getInfo(input); + + OutputFormatter formatter = FormatterFactory.getFormatter("json"); + String output = formatter.format(info); + + getOut().println(output); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/commands/ReadCommand.java b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/ReadCommand.java new file mode 100644 index 000000000..a5dfc8ff6 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/ReadCommand.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import org.apache.fesod.cli.formatters.FormatterFactory; +import org.apache.fesod.cli.formatters.OutputFormatter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +/** + * Read command implementation + */ +@Command( + name = "read", + description = "Read spreadsheet data and output in specified format", + mixinStandardHelpOptions = true) +public class ReadCommand extends BaseCommand { + + @Parameters(index = "0", description = "Input file path", paramLabel = "") + private String inputFile; + + @Option( + names = {"--format", "-f"}, + description = "Output format: json, csv (default: json)", + defaultValue = "json") + private String format; + + @Option( + names = {"--sheet", "-s"}, + description = "Sheet name or index (default: 0)", + paramLabel = "") + private String sheet; + + @Option( + names = {"--output", "-o"}, + description = "Output file path (default: stdout)", + paramLabel = "") + private String outputFile; + + @Option( + names = {"--all"}, + description = "Read all sheets") + private boolean readAll; + + @Override + protected void execute() { + Path input = Paths.get(inputFile); + + Map options = new HashMap(); + + if (sheet != null) { + try { + int sheetIndex = Integer.parseInt(sheet); + options.put("sheetIndex", sheetIndex); + } catch (NumberFormatException e) { + options.put("sheetName", sheet); + } + } + + options.put("readAll", readAll); + + Map data = processor.read(input, options); + + OutputFormatter formatter = FormatterFactory.getFormatter(format); + String output = formatter.format(data); + + if (outputFile != null) { + formatter.writeToFile(output, Paths.get(outputFile)); + getOut().println("✓ Output written to: " + outputFile); + } else { + getOut().println(output); + } + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/commands/VersionCommand.java b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/VersionCommand.java new file mode 100644 index 000000000..ade10b41b --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/VersionCommand.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import java.io.PrintWriter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; + +/** + * Version command implementation + */ +@Command(name = "version", description = "Display version information", mixinStandardHelpOptions = true) +public class VersionCommand implements Runnable { + + @Spec + CommandSpec spec; + + @Override + public void run() { + PrintWriter out = spec.commandLine().getOut(); + out.println("Apache Fesod CLI"); + out.println("Java Version: " + System.getProperty("java.version")); + out.println("OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch")); + out.println(); + out.println("Copyright © 2025 The Apache Software Foundation"); + out.println("Licensed under the Apache License 2.0"); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/commands/WriteCommand.java b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/WriteCommand.java new file mode 100644 index 000000000..83ae9328e --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/commands/WriteCommand.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import com.alibaba.fastjson2.JSON; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +/** + * Write command implementation + */ +@Command(name = "write", description = "Write data from JSON/CSV to spreadsheet", mixinStandardHelpOptions = true) +public class WriteCommand extends BaseCommand { + + @Parameters(index = "0", description = "Input data file (JSON/CSV)", paramLabel = "") + private String inputFile; + + @Parameters(index = "1", description = "Output spreadsheet file", paramLabel = "") + private String outputFile; + + @Option( + names = {"--input-format"}, + description = "Input data format: json, csv (default: json)", + defaultValue = "json") + private String inputFormat; + + @Option( + names = {"--sheet-name"}, + description = "Sheet name (default: Sheet1)", + defaultValue = "Sheet1") + private String sheetName; + + @Override + protected void execute() { + try { + Path input = Paths.get(inputFile); + Path output = Paths.get(outputFile); + + if (!Files.exists(input)) { + throw new RuntimeException("Input file does not exist: " + inputFile); + } + if (!Files.isRegularFile(input)) { + throw new RuntimeException("Input path is not a regular file: " + inputFile); + } + if (!Files.isReadable(input)) { + throw new RuntimeException("Input file is not readable: " + inputFile); + } + String content = new String(Files.readAllBytes(input), "UTF-8"); + Map data = new HashMap(); + + if ("json".equalsIgnoreCase(inputFormat)) { + data.put("data", JSON.parse(content)); + } else { + throw new UnsupportedOperationException("CSV input format not yet implemented"); + } + + Map options = new HashMap(); + options.put("sheetName", sheetName); + + processor.write(data, output, options); + + getOut().println("✓ Data written to: " + outputFile); + + } catch (Exception e) { + throw new RuntimeException("Failed to write data: " + e.getMessage(), e); + } + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/config/CliConfig.java b/fesod-cli/src/main/java/org/apache/fesod/cli/config/CliConfig.java new file mode 100644 index 000000000..171f1f394 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/config/CliConfig.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.config; + +/** + * CLI configuration + */ +public class CliConfig { + + private DefaultsConfig defaults = new DefaultsConfig(); + private ReadConfig read = new ReadConfig(); + private WriteConfig write = new WriteConfig(); + + public DefaultsConfig getDefaults() { + return defaults; + } + + public void setDefaults(DefaultsConfig defaults) { + this.defaults = defaults; + } + + public ReadConfig getRead() { + return read; + } + + public void setRead(ReadConfig read) { + this.read = read; + } + + public WriteConfig getWrite() { + return write; + } + + public void setWrite(WriteConfig write) { + this.write = write; + } + + public static class DefaultsConfig { + private String outputFormat = "json"; + private String encoding = "UTF-8"; + + public String getOutputFormat() { + return outputFormat; + } + + public void setOutputFormat(String outputFormat) { + this.outputFormat = outputFormat; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + } + + public static class ReadConfig { + private boolean autoTrim = true; + private boolean ignoreEmptyRows = true; + + public boolean isAutoTrim() { + return autoTrim; + } + + public void setAutoTrim(boolean autoTrim) { + this.autoTrim = autoTrim; + } + + public boolean isIgnoreEmptyRows() { + return ignoreEmptyRows; + } + + public void setIgnoreEmptyRows(boolean ignoreEmptyRows) { + this.ignoreEmptyRows = ignoreEmptyRows; + } + } + + public static class WriteConfig { + private boolean autoCreateDirectories = true; + + public boolean isAutoCreateDirectories() { + return autoCreateDirectories; + } + + public void setAutoCreateDirectories(boolean autoCreateDirectories) { + this.autoCreateDirectories = autoCreateDirectories; + } + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/config/ConfigLoader.java b/fesod-cli/src/main/java/org/apache/fesod/cli/config/ConfigLoader.java new file mode 100644 index 000000000..8231c076c --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/config/ConfigLoader.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.config; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.yaml.snakeyaml.Yaml; + +/** + * Configuration loader + */ +public class ConfigLoader { + + private static final String USER_CONFIG_PATH = System.getProperty("user.home") + "/.fesod/config.yaml"; + + public CliConfig loadDefault() { + // 1. Try user home config (~/.fesod/config.yaml) + Path userConfig = Paths.get(USER_CONFIG_PATH); + if (Files.exists(userConfig)) { + System.out.println("Loading config from: " + userConfig); + return loadFromFile(userConfig); + } + + // 2. Try FESOD_HOME/conf/default-config.yaml + String fesodHome = System.getenv("FESOD_HOME"); + if (fesodHome != null && !fesodHome.isEmpty()) { + Path installConfig = Paths.get(fesodHome, "conf", "default-config.yaml"); + if (Files.exists(installConfig)) { + System.out.println("Loading config from: " + installConfig); + return loadFromFile(installConfig); + } + } + + // 3. Try relative path: conf/default-config.yaml + Path relativeConfig = Paths.get("conf", "default-config.yaml"); + if (Files.exists(relativeConfig)) { + System.out.println("Loading config from: " + relativeConfig.toAbsolutePath()); + return loadFromFile(relativeConfig); + } + + // 4. Fallback to embedded config in JAR + System.out.println("Loading default config from JAR"); + return createDefaultConfig(); + } + + public CliConfig loadFromFile(Path configPath) { + try { + try (InputStream is = Files.newInputStream(configPath)) { + Yaml yaml = new Yaml(); + CliConfig config = yaml.loadAs(is, CliConfig.class); + ConfigValidator.validate(config); + return config; + } + + } catch (Exception e) { + System.err.println("Warning: Failed to load config from " + configPath + ", using defaults"); + return createDefaultConfig(); + } + } + + private CliConfig createDefaultConfig() { + + try (InputStream is = getClass().getResourceAsStream("/default-config.yaml")) { + if (is != null) { + Yaml yaml = new Yaml(); + return yaml.loadAs(is, CliConfig.class); + } + } catch (Exception e) { + // fallback + } + return new CliConfig(); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/config/ConfigValidator.java b/fesod-cli/src/main/java/org/apache/fesod/cli/config/ConfigValidator.java new file mode 100644 index 000000000..773984abb --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/config/ConfigValidator.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.config; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; +import org.apache.fesod.cli.exception.ConfigurationException; + +/** + * Configuration validator + */ +public class ConfigValidator { + + private static final List SUPPORTED_OUTPUT_FORMATS = Arrays.asList("json", "csv", "xml"); + private static final List SUPPORTED_ENCODINGS = + Arrays.asList("UTF-8", "UTF-16", "ISO-8859-1", "GBK", "GB2312"); + + public static void validate(CliConfig config) { + if (config == null) { + throw new ConfigurationException("Configuration cannot be null"); + } + + validateDefaults(config.getDefaults()); + validateRead(config.getRead()); + validateWrite(config.getWrite()); + } + + private static void validateDefaults(CliConfig.DefaultsConfig defaults) { + if (defaults == null) { + return; // defaults are optional + } + + if (defaults.getOutputFormat() != null) { + if (!SUPPORTED_OUTPUT_FORMATS.contains(defaults.getOutputFormat().toLowerCase())) { + throw new ConfigurationException("Unsupported output format: " + defaults.getOutputFormat() + + ". Supported formats: " + String.join(", ", SUPPORTED_OUTPUT_FORMATS)); + } + } + + if (defaults.getEncoding() != null) { + try { + Charset.forName(defaults.getEncoding()); + } catch (Exception e) { + throw new ConfigurationException("Unsupported encoding: " + defaults.getEncoding() + + ". Supported encodings: " + String.join(", ", SUPPORTED_ENCODINGS)); + } + } + } + + private static void validateRead(CliConfig.ReadConfig read) { + if (read == null) { + return; // read config is optional + } + + // No specific validation needed for read config currently + // Can add validation for boolean fields if needed + } + + private static void validateWrite(CliConfig.WriteConfig write) { + if (write == null) { + return; // write config is optional + } + + // No specific validation needed for write config currently + // Can add validation for boolean fields if needed + } + + /** + * Validate configuration file path + */ + public static void validateConfigFile(String configFile) { + if (configFile == null || configFile.trim().isEmpty()) { + return; // null or empty is acceptable (will use defaults) + } + + if (!configFile.endsWith(".yaml") && !configFile.endsWith(".yml")) { + throw new ConfigurationException("Configuration file must be a YAML file (.yaml or .yml)"); + } + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/core/DocumentProcessor.java b/fesod-cli/src/main/java/org/apache/fesod/cli/core/DocumentProcessor.java new file mode 100644 index 000000000..7e6c831aa --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/core/DocumentProcessor.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.core; + +import java.nio.file.Path; +import java.util.Map; + +/** + * Document processor abstraction for different document types + */ +public interface DocumentProcessor { + + /** + * Read document and return data + */ + Map read(Path inputPath, Map options); + + /** + * Write data to document + */ + void write(Map data, Path outputPath, Map options); + + /** + * Convert document format + */ + void convert(Path inputPath, Path outputPath, Map options); + + /** + * Get document information + */ + Map getInfo(Path inputPath); + + /** + * Get supported module name + */ + String getModuleName(); +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/core/ModuleRegistry.java b/fesod-cli/src/main/java/org/apache/fesod/cli/core/ModuleRegistry.java new file mode 100644 index 000000000..393c3be59 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/core/ModuleRegistry.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.core; + +import java.util.HashMap; +import java.util.Map; +import org.apache.fesod.cli.core.sheet.SheetProcessor; +import org.apache.fesod.cli.exception.CliException; + +/** + * Module registry for managing document processors + */ +public class ModuleRegistry { + + private static final Map PROCESSORS = new HashMap(); + + static { + // Register sheet processor + registerProcessor(new SheetProcessor()); + } + + public static void registerProcessor(DocumentProcessor processor) { + PROCESSORS.put(processor.getModuleName(), processor); + } + + public static DocumentProcessor getProcessor(String moduleName) { + DocumentProcessor processor = PROCESSORS.get(moduleName); + if (processor == null) { + throw new CliException("Unsupported module: " + moduleName + ". Available modules: " + + String.join(", ", PROCESSORS.keySet())); + } + return processor; + } + + public static String[] getModuleNames() { + return PROCESSORS.keySet().toArray(new String[0]); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetConverter.java b/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetConverter.java new file mode 100644 index 000000000..e0ecff010 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetConverter.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.core.sheet; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.fesod.sheet.ExcelReader; +import org.apache.fesod.sheet.ExcelWriter; +import org.apache.fesod.sheet.FesodSheet; +import org.apache.fesod.sheet.read.metadata.ReadSheet; +import org.apache.fesod.sheet.write.metadata.WriteSheet; + +/** + * Sheet format converter + */ +public class SheetConverter { + + public void convert(Path inputPath, Path outputPath, Map options) { + Integer sheetIndex = (Integer) options.get("sheetIndex"); + String sheetName = (String) options.get("sheetName"); + Boolean convertAll = (Boolean) options.get("convertAll"); + + // Default to convert all sheets if no specific sheet is specified + if (convertAll == null && sheetIndex == null && sheetName == null) { + convertAll = true; + } + + if (convertAll != null && convertAll) { + convertAllSheets(inputPath, outputPath); + } else if (sheetIndex != null) { + convertSingleSheetByIndex(inputPath, outputPath, sheetIndex); + } else if (sheetName != null) { + convertSingleSheetByName(inputPath, outputPath, sheetName); + } else { + // Fallback to first sheet only + convertSingleSheetByIndex(inputPath, outputPath, 0); + } + } + + private void convertAllSheets(Path inputPath, Path outputPath) { + try (ExcelReader reader = + FesodSheet.read(inputPath.toFile()).headRowNumber(0).build(); + ExcelWriter writer = FesodSheet.write(outputPath.toFile()).build()) { + + List sheets = reader.excelExecutor().sheetList(); + + for (int i = 0; i < sheets.size(); i++) { + ReadSheet readSheet = sheets.get(i); + + // Read data from this sheet + List> result = FesodSheet.read(inputPath.toFile()) + .headRowNumber(0) + .sheet(readSheet.getSheetNo()) + .doReadSync(); + + // Convert to list format + List> sheetData = convertToListData(result); + + // Write to output with same sheet name + WriteSheet writeSheet = + FesodSheet.writerSheet(i, readSheet.getSheetName()).build(); + writer.write(sheetData, writeSheet); + } + } + } + + private void convertSingleSheetByIndex(Path inputPath, Path outputPath, int sheetIndex) { + List> result = FesodSheet.read(inputPath.toFile()) + .headRowNumber(0) + .sheet(sheetIndex) + .doReadSync(); + + List> sheetData = convertToListData(result); + + FesodSheet.write(outputPath.toFile()).sheet("Sheet1").doWrite(sheetData); + } + + private void convertSingleSheetByName(Path inputPath, Path outputPath, String sheetName) { + List> result = FesodSheet.read(inputPath.toFile()) + .headRowNumber(0) + .sheet(sheetName) + .doReadSync(); + + List> sheetData = convertToListData(result); + + FesodSheet.write(outputPath.toFile()).sheet(sheetName).doWrite(sheetData); + } + + private List> convertToListData(List> result) { + List> data = new ArrayList<>(); + for (Map rowData : result) { + List row = new ArrayList<>(); + for (String value : rowData.values()) { + row.add(value); + } + data.add(row); + } + return data; + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetProcessor.java b/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetProcessor.java new file mode 100644 index 000000000..1cb8ef832 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetProcessor.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.core.sheet; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.apache.fesod.cli.core.DocumentProcessor; +import org.apache.fesod.cli.exception.FileProcessException; +import org.apache.fesod.sheet.ExcelReader; +import org.apache.fesod.sheet.FesodSheet; +import org.apache.fesod.sheet.read.metadata.ReadSheet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Sheet document processor implementation + */ +public class SheetProcessor implements DocumentProcessor { + + private static final Logger log = LoggerFactory.getLogger(SheetProcessor.class); + + private final SheetReader reader; + private final SheetWriter writer; + private final SheetConverter converter; + + public SheetProcessor() { + this.reader = new SheetReader(); + this.writer = new SheetWriter(); + this.converter = new SheetConverter(); + } + + @Override + public Map read(Path inputPath, Map options) { + log.info("Reading spreadsheet from: {}", inputPath); + + try { + Integer sheetIndex = (Integer) options.get("sheetIndex"); + String sheetName = (String) options.get("sheetName"); + Boolean readAll = (Boolean) options.get("readAll"); + if (readAll == null) { + readAll = false; + } + + return reader.read(inputPath, sheetIndex, sheetName, readAll); + + } catch (Exception e) { + throw new FileProcessException("Failed to read spreadsheet: " + e.getMessage(), e); + } + } + + @Override + public void write(Map data, Path outputPath, Map options) { + log.info("Writing spreadsheet to: {}", outputPath); + + try { + String sheetName = (String) options.get("sheetName"); + if (sheetName == null) { + sheetName = "Sheet1"; + } + writer.write(data, outputPath, sheetName, options); + + } catch (Exception e) { + throw new FileProcessException("Failed to write spreadsheet: " + e.getMessage(), e); + } + } + + @Override + public void convert(Path inputPath, Path outputPath, Map options) { + log.info("Converting {} to {}", inputPath, outputPath); + + try { + converter.convert(inputPath, outputPath, options); + + } catch (Exception e) { + throw new FileProcessException("Failed to convert spreadsheet: " + e.getMessage(), e); + } + } + + @Override + public Map getInfo(Path inputPath) { + log.info("Getting info for: {}", inputPath); + + try { + Map info = new LinkedHashMap(); + List sheets; + + try (ExcelReader excelReader = FesodSheet.read(inputPath.toFile()).build()) { + sheets = excelReader.excelExecutor().sheetList(); + } + + info.put("file", inputPath.toString()); + info.put("fileSize", inputPath.toFile().length()); + info.put("sheetCount", sheets.size()); + + List> sheetInfoList = new ArrayList>(); + for (ReadSheet sheet : sheets) { + Map sheetInfo = new LinkedHashMap(); + sheetInfo.put("index", sheet.getSheetNo()); + sheetInfo.put("name", sheet.getSheetName()); + sheetInfo.put("hidden", sheet.isHidden()); + sheetInfo.put("rowCount", sheet.getNumRows()); + sheetInfoList.add(sheetInfo); + } + + info.put("sheets", sheetInfoList); + return info; + + } catch (Exception e) { + throw new FileProcessException("Failed to get spreadsheet info: " + e.getMessage(), e); + } + } + + @Override + public String getModuleName() { + return "sheet"; + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetReader.java b/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetReader.java new file mode 100644 index 000000000..3c6104020 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetReader.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.core.sheet; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.apache.fesod.sheet.ExcelReader; +import org.apache.fesod.sheet.FesodSheet; +import org.apache.fesod.sheet.read.metadata.ReadSheet; + +/** + * Sheet reader implementation + */ +public class SheetReader { + + public Map read(Path inputPath, Integer sheetIndex, String sheetName, Boolean readAll) { + Map result = new LinkedHashMap(); + + if (readAll) { + result.put("sheets", readAllSheets(inputPath)); + } else if (sheetName != null) { + result.put("data", readSheetByName(inputPath, sheetName)); + } else { + int index = sheetIndex != null ? sheetIndex : 0; + result.put("data", readSheetByIndex(inputPath, index)); + } + + return result; + } + + private List> readAllSheets(Path inputPath) { + List> allSheets = new ArrayList>(); + + try (ExcelReader excelReader = FesodSheet.read(inputPath.toFile()).build()) { + List sheets = excelReader.excelExecutor().sheetList(); + for (ReadSheet sheet : sheets) { + Map sheetData = new LinkedHashMap(); + sheetData.put("name", sheet.getSheetName()); + sheetData.put("index", sheet.getSheetNo()); + sheetData.put("rows", readSheetByIndex(inputPath, sheet.getSheetNo())); + allSheets.add(sheetData); + } + } + return allSheets; + } + + private JSONArray readSheetByIndex(Path inputPath, int sheetIndex) { + List> result = FesodSheet.read(inputPath.toFile()) + .headRowNumber(0) + .sheet(sheetIndex) + .doReadSync(); + return convertToJsonArray(result); + } + + private JSONArray readSheetByName(Path inputPath, String sheetName) { + List> result = FesodSheet.read(inputPath.toFile()) + .headRowNumber(0) + .sheet(sheetName) + .doReadSync(); + return convertToJsonArray(result); + } + + private JSONArray convertToJsonArray(List> data) { + JSONArray jsonArray = new JSONArray(); + for (Map rowData : data) { + JSONObject row = new JSONObject(new LinkedHashMap()); + for (Map.Entry entry : rowData.entrySet()) { + row.put("col_" + entry.getKey(), entry.getValue()); + } + jsonArray.add(row); + } + return jsonArray; + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetWriter.java b/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetWriter.java new file mode 100644 index 000000000..45fce7317 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/core/sheet/SheetWriter.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.core.sheet; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.fesod.sheet.FesodSheet; + +/** + * Sheet writer implementation + */ +public class SheetWriter { + + public void write(Map data, Path outputPath, String sheetName, Map options) { + Object dataObj = data.get("data"); + + if (dataObj instanceof String) { + dataObj = JSON.parse((String) dataObj); + } + + List> rows = new ArrayList>(); + + if (dataObj instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) dataObj; + + for (int i = 0; i < jsonArray.size(); i++) { + Object item = jsonArray.get(i); + + if (item instanceof JSONObject) { + JSONObject jsonObj = (JSONObject) item; + List row = new ArrayList(); + for (Object val : jsonObj.values()) { + row.add(val != null ? val.toString() : ""); + } + rows.add(row); + } else if (item instanceof List) { + List list = (List) item; + List row = new ArrayList(); + for (Object val : list) { + row.add(val != null ? val.toString() : ""); + } + rows.add(row); + } + } + } + + FesodSheet.write(outputPath.toFile()).sheet(sheetName).doWrite(rows); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/exception/CliException.java b/fesod-cli/src/main/java/org/apache/fesod/cli/exception/CliException.java new file mode 100644 index 000000000..daeb1de49 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/exception/CliException.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.exception; + +/** + * Base CLI exception + */ +public class CliException extends RuntimeException { + + private final int exitCode; + + public CliException(String message) { + this(message, 1); + } + + public CliException(String message, int exitCode) { + super(message); + this.exitCode = exitCode; + } + + public CliException(String message, Throwable cause) { + this(message, cause, 1); + } + + public CliException(String message, Throwable cause, int exitCode) { + super(message, cause); + this.exitCode = exitCode; + } + + public int getExitCode() { + return exitCode; + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/exception/ConfigurationException.java b/fesod-cli/src/main/java/org/apache/fesod/cli/exception/ConfigurationException.java new file mode 100644 index 000000000..f13153b68 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/exception/ConfigurationException.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.exception; + +/** + * Configuration exception + */ +public class ConfigurationException extends CliException { + + public ConfigurationException(String message) { + super(message, 3); + } + + public ConfigurationException(String message, Throwable cause) { + super(message, cause, 3); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/exception/FileProcessException.java b/fesod-cli/src/main/java/org/apache/fesod/cli/exception/FileProcessException.java new file mode 100644 index 000000000..1bacb0283 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/exception/FileProcessException.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.exception; + +/** + * File processing exception + */ +public class FileProcessException extends CliException { + + public FileProcessException(String message) { + super(message, 2); + } + + public FileProcessException(String message, Throwable cause) { + super(message, cause, 2); + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/CsvFormatter.java b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/CsvFormatter.java new file mode 100644 index 000000000..60e136076 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/CsvFormatter.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.formatters; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +/** + * CSV formatter implementation + */ +public class CsvFormatter implements OutputFormatter { + + @Override + public String format(Map data) { + try { + StringWriter out = new StringWriter(); + CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT); + + Object dataObj = data.get("data"); + + if (dataObj instanceof JSONArray) { + JSONArray array = (JSONArray) dataObj; + + if (array.size() > 0) { + // Get headers from the first JSONObject + Object firstItem = array.get(0); + if (firstItem instanceof JSONObject) { + JSONObject firstObj = (JSONObject) firstItem; + List headers = new ArrayList<>(firstObj.keySet()); + + // Print header row + printer.printRecord(headers); + + // Print data rows + for (int i = 0; i < array.size(); i++) { + Object item = array.get(i); + + if (item instanceof JSONObject) { + JSONObject obj = (JSONObject) item; + // Print values in the same order as headers + List values = new ArrayList<>(); + for (String header : headers) { + values.add(obj.get(header)); + } + printer.printRecord(values); + } + } + } + } + } + + printer.close(); + return out.toString(); + + } catch (IOException e) { + throw new RuntimeException("Failed to format as CSV: " + e.getMessage(), e); + } + } + + @Override + public void writeToFile(String content, Path outputPath) { + try { + Files.write(outputPath, content.getBytes("UTF-8")); + } catch (IOException e) { + throw new RuntimeException("Failed to write CSV to file: " + e.getMessage(), e); + } + } + + @Override + public String getFormatType() { + return "csv"; + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/FormatterFactory.java b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/FormatterFactory.java new file mode 100644 index 000000000..a40e8967e --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/FormatterFactory.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.formatters; + +import java.util.HashMap; +import java.util.Map; +import org.apache.fesod.cli.exception.CliException; + +/** + * Formatter factory + */ +public class FormatterFactory { + + private static final Map FORMATTERS = new HashMap(); + + static { + registerFormatter(new JsonFormatter()); + registerFormatter(new CsvFormatter()); + registerFormatter(new XmlFormatter()); + } + + public static void registerFormatter(OutputFormatter formatter) { + FORMATTERS.put(formatter.getFormatType().toLowerCase(), formatter); + } + + public static OutputFormatter getFormatter(String formatType) { + OutputFormatter formatter = FORMATTERS.get(formatType.toLowerCase()); + if (formatter == null) { + throw new CliException("Unsupported format: " + formatType); + } + return formatter; + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/JsonFormatter.java b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/JsonFormatter.java new file mode 100644 index 000000000..72947d13c --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/JsonFormatter.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.formatters; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +/** + * JSON formatter implementation + */ +public class JsonFormatter implements OutputFormatter { + + @Override + public String format(Map data) { + return JSON.toJSONString(data, JSONWriter.Feature.PrettyFormat); + } + + @Override + public void writeToFile(String content, Path outputPath) { + try { + Files.write(outputPath, content.getBytes("UTF-8")); + } catch (IOException e) { + throw new RuntimeException("Failed to write JSON to file: " + e.getMessage(), e); + } + } + + @Override + public String getFormatType() { + return "json"; + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/OutputFormatter.java b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/OutputFormatter.java new file mode 100644 index 000000000..c91b20277 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/OutputFormatter.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.formatters; + +import java.nio.file.Path; +import java.util.Map; + +/** + * Output formatter interface + */ +public interface OutputFormatter { + String format(Map data); + + void writeToFile(String content, Path outputPath); + + String getFormatType(); +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/XmlFormatter.java b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/XmlFormatter.java new file mode 100644 index 000000000..9107320c8 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/formatters/XmlFormatter.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.formatters; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +/** + * XML formatter implementation + */ +public class XmlFormatter implements OutputFormatter { + + @Override + public String format(Map data) { + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + org.w3c.dom.Document doc = docBuilder.newDocument(); + + // Create root element + org.w3c.dom.Element rootElement = doc.createElement("fesod-result"); + doc.appendChild(rootElement); + + // Convert data to XML + convertToXml(doc, rootElement, data); + + // Transform to string + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(doc), new StreamResult(writer)); + + return writer.toString(); + + } catch (ParserConfigurationException | TransformerException e) { + throw new RuntimeException("Failed to format as XML: " + e.getMessage(), e); + } + } + + private void convertToXml(org.w3c.dom.Document doc, org.w3c.dom.Element parent, Map data) { + for (Map.Entry entry : data.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + + org.w3c.dom.Element element = doc.createElement(key.replaceAll("[^a-zA-Z0-9]", "_")); + + if (value instanceof Map) { + convertToXml(doc, element, (Map) value); + } else if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + for (int i = 0; i < array.size(); i++) { + Object item = array.get(i); + org.w3c.dom.Element itemElement = doc.createElement("item"); + + if (item instanceof JSONObject) { + convertToXml(doc, itemElement, (JSONObject) item); + } else { + itemElement.setTextContent(item != null ? item.toString() : ""); + } + + element.appendChild(itemElement); + } + } else { + element.setTextContent(value != null ? value.toString() : ""); + } + + parent.appendChild(element); + } + } + + private void convertToXml(org.w3c.dom.Document doc, org.w3c.dom.Element parent, JSONObject jsonObject) { + for (String key : jsonObject.keySet()) { + Object value = jsonObject.get(key); + org.w3c.dom.Element element = doc.createElement(key.replaceAll("[^a-zA-Z0-9]", "_")); + element.setTextContent(value != null ? value.toString() : ""); + parent.appendChild(element); + } + } + + @Override + public void writeToFile(String content, Path outputPath) { + try { + Files.write(outputPath, content.getBytes("UTF-8")); + } catch (IOException e) { + throw new RuntimeException("Failed to write XML to file: " + e.getMessage(), e); + } + } + + @Override + public String getFormatType() { + return "xml"; + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/utils/FileUtils.java b/fesod-cli/src/main/java/org/apache/fesod/cli/utils/FileUtils.java new file mode 100644 index 000000000..57c533a96 --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/utils/FileUtils.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * File utility class + */ +public class FileUtils { + + private static final Logger log = LoggerFactory.getLogger(FileUtils.class); + + /** + * Check if file exists and is readable + */ + public static boolean isReadableFile(String filePath) { + if (filePath == null || filePath.trim().isEmpty()) { + return false; + } + + Path path = Paths.get(filePath); + return Files.exists(path) && Files.isReadable(path) && Files.isRegularFile(path); + } + + /** + * Check if directory exists and is writable + */ + public static boolean isWritableDirectory(String dirPath) { + if (dirPath == null || dirPath.trim().isEmpty()) { + return false; + } + + Path path = Paths.get(dirPath); + return Files.exists(path) && Files.isDirectory(path) && Files.isWritable(path); + } + + /** + * Create parent directories if they don't exist + */ + public static void createParentDirectories(Path filePath) throws IOException { + if (filePath.getParent() != null) { + Files.createDirectories(filePath.getParent()); + log.debug("Created parent directories for: {}", filePath); + } + } + + /** + * Get file extension + */ + public static String getFileExtension(String fileName) { + if (fileName == null || fileName.isEmpty()) { + return ""; + } + + int lastDotIndex = fileName.lastIndexOf('.'); + if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) { + return fileName.substring(lastDotIndex + 1).toLowerCase(); + } + + return ""; + } + + /** + * Check if file has Excel extension + */ + public static boolean isExcelFile(String filePath) { + String extension = getFileExtension(filePath); + return "xlsx".equals(extension) || "xls".equals(extension) || "csv".equals(extension); + } + + /** + * Get file size in human readable format + */ + public static String getHumanReadableFileSize(long bytes) { + if (bytes < 1024) { + return bytes + " B"; + } + int exp = (int) (Math.log(bytes) / Math.log(1024)); + String pre = "KMGTPE".charAt(exp - 1) + ""; + return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre); + } + + /** + * Validate output file path + */ + public static void validateOutputFile(String outputPath) { + if (outputPath == null || outputPath.trim().isEmpty()) { + return; // stdout is acceptable + } + + Path path = Paths.get(outputPath); + if (Files.exists(path) && !Files.isRegularFile(path)) { + throw new IllegalArgumentException("Output path is not a regular file: " + outputPath); + } + + // Check if parent directory is writable + if (path.getParent() != null && !isWritableDirectory(path.getParent().toString())) { + throw new IllegalArgumentException("Output directory is not writable: " + path.getParent()); + } + } +} diff --git a/fesod-cli/src/main/java/org/apache/fesod/cli/utils/LogUtils.java b/fesod-cli/src/main/java/org/apache/fesod/cli/utils/LogUtils.java new file mode 100644 index 000000000..39782939f --- /dev/null +++ b/fesod-cli/src/main/java/org/apache/fesod/cli/utils/LogUtils.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Logging utility class + */ +public class LogUtils { + + private static final Logger logger = LoggerFactory.getLogger(LogUtils.class); + + /** + * Log command execution start + */ + public static void logCommandStart(String commandName, String... args) { + if (logger.isInfoEnabled()) { + StringBuilder sb = new StringBuilder(); + sb.append("Executing command: ").append(commandName); + if (args.length > 0) { + sb.append(" with args: "); + for (int i = 0; i < args.length; i++) { + if (i > 0) sb.append(", "); + sb.append("'").append(args[i]).append("'"); + } + } + logger.info(sb.toString()); + } + } + + /** + * Log command execution end + */ + public static void logCommandEnd(String commandName, long startTime) { + long duration = System.currentTimeMillis() - startTime; + logger.info("Command '{}' completed in {} ms", commandName, duration); + } + + /** + * Log command execution error + */ + public static void logCommandError(String commandName, Exception e) { + logger.error("Command '{}' failed: {}", commandName, e.getMessage(), e); + } + + /** + * Log file operation + */ + public static void logFileOperation(String operation, String filePath) { + logger.debug("{} file: {}", operation, filePath); + } + + /** + * Log file operation with size + */ + public static void logFileOperation(String operation, String filePath, long fileSize) { + logger.debug("{} file: {} (size: {})", operation, filePath, FileUtils.getHumanReadableFileSize(fileSize)); + } + + /** + * Log configuration loading + */ + public static void logConfigLoading(String configPath) { + if (configPath != null) { + logger.debug("Loading configuration from: {}", configPath); + } else { + logger.debug("Using default configuration"); + } + } + + /** + * Log module registration + */ + public static void logModuleRegistration(String moduleName, String className) { + logger.debug("Registered module '{}' with class '{}'", moduleName, className); + } + + /** + * Log processor initialization + */ + public static void logProcessorInit(String processorType, String moduleName) { + logger.debug("Initialized {} processor for module '{}'", processorType, moduleName); + } + + /** + * Get a logger for a specific class + */ + public static Logger getLogger(Class clazz) { + return LoggerFactory.getLogger(clazz); + } + + /** + * Get a logger for a specific name + */ + public static Logger getLogger(String name) { + return LoggerFactory.getLogger(name); + } +} diff --git a/fesod-cli/src/main/resources/default-config.yaml b/fesod-cli/src/main/resources/default-config.yaml new file mode 100644 index 000000000..86018cf2f --- /dev/null +++ b/fesod-cli/src/main/resources/default-config.yaml @@ -0,0 +1,52 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Apache Fesod CLI Default Configuration +# This file contains default settings for the CLI tool +# Copy this file to ~/.fesod/config.yaml and modify as needed + +# Default settings +defaults: + # Default output format for read operations + # Supported formats: json, csv, xml + outputFormat: json + + # Default character encoding + # Supported encodings: UTF-8, UTF-16, ISO-8859-1, GBK, GB2312 + encoding: UTF-8 + +# Read operation settings +read: + # Whether to automatically trim whitespace from cell values + autoTrim: true + + # Whether to ignore empty rows when reading + ignoreEmptyRows: true + +# Write operation settings +write: + # Whether to automatically create parent directories for output files + autoCreateDirectories: true + +# Advanced settings (uncomment to modify) +# logging: +# level: INFO +# file: fesod-cli.log + +# performance: +# bufferSize: 8192 +# maxMemory: 512MB diff --git a/fesod-cli/src/main/resources/logback.xml b/fesod-cli/src/main/resources/logback.xml new file mode 100644 index 000000000..74dbb6e5c --- /dev/null +++ b/fesod-cli/src/main/resources/logback.xml @@ -0,0 +1,34 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + + + diff --git a/fesod-cli/src/main/scripts/fesod-cli b/fesod-cli/src/main/scripts/fesod-cli new file mode 100755 index 000000000..2e48e00f5 --- /dev/null +++ b/fesod-cli/src/main/scripts/fesod-cli @@ -0,0 +1,144 @@ +#!/usr/bin/env bash + +############################################################################## +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +############################################################################## + +############################################################################## +# Apache Fesod CLI Launcher Script +# Supports: JDK 8+ +# Platforms: Linux, macOS, Unix +############################################################################## + +set -e + +# 脚本所在目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Fesod CLI 主目录 +if [ -z "$FESOD_HOME" ]; then + FESOD_HOME="$(dirname "$SCRIPT_DIR")" + export FESOD_HOME +fi + +# 构建 CLASSPATH +# Fesod modules +CLASSPATH="$FESOD_HOME/lib/*" +# Third-party dependencies +CLASSPATH="$CLASSPATH:$FESOD_HOME/lib/ext/*" +# Configuration directory +CLASSPATH="$CLASSPATH:$FESOD_HOME/conf" + +# 检查 lib 目录是否存在 +if [ ! -d "$FESOD_HOME/lib" ]; then + echo "Error: Cannot find lib directory at $FESOD_HOME/lib" + exit 1 +fi + +# 查找 Java +find_java() { + # 1. 检查 JAVA_HOME + if [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/java" ]; then + echo "$JAVA_HOME/bin/java" + return 0 + fi + + # 2. 检查 PATH + if command -v java &> /dev/null; then + echo "java" + return 0 + fi + + # 3. macOS 特定查找 + if [ "$(uname)" = "Darwin" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME_CANDIDATE=$(/usr/libexec/java_home 2>/dev/null) + if [ -n "$JAVA_HOME_CANDIDATE" ] && [ -x "$JAVA_HOME_CANDIDATE/bin/java" ]; then + echo "$JAVA_HOME_CANDIDATE/bin/java" + return 0 + fi + fi + fi + + # 4. Linux 常见路径 + for candidate in \ + /usr/lib/jvm/java-8-openjdk-amd64/bin/java \ + /usr/lib/jvm/java-11-openjdk-amd64/bin/java \ + /usr/lib/jvm/default-java/bin/java + do + if [ -x "$candidate" ]; then + echo "$candidate" + return 0 + fi + done + + return 1 +} + +JAVA_CMD=$(find_java) + +# 验证 Java 可用性 +if [ -z "$JAVA_CMD" ]; then + echo "Error: Java is not installed or not in PATH" + echo "" + echo "Please install Java 8 or higher:" + echo " - Ubuntu/Debian: sudo apt-get install openjdk-8-jdk" + echo " - CentOS/RHEL: sudo yum install java-1.8.0-openjdk" + echo " - macOS: brew install openjdk@8" + echo " - Or download: https://adoptium.net/" + echo "" + echo "Or set JAVA_HOME environment variable:" + echo " export JAVA_HOME=/path/to/jdk" + exit 1 +fi + +# 检查 Java 版本 +check_java_version() { + local java_cmd=$1 + local version_output=$("$java_cmd" -version 2>&1) + local version=$(echo "$version_output" | head -n 1 | awk -F '"' '{print $2}') + + # 提取主版本号 + local major_version=$(echo "$version" | awk -F. '{print $1}') + if [ "$major_version" -eq 1 ]; then + major_version=$(echo "$version" | awk -F. '{print $2}') + fi + + if [ "$major_version" -lt 8 ]; then + echo "Error: Java 8 or higher is required" + echo "Current Java version: $version" + echo "Java command: $java_cmd" + exit 1 + fi +} + +check_java_version "$JAVA_CMD" + +# JVM 参数 +JAVA_OPTS="${FESOD_JAVA_OPTS:--Xms128m -Xmx1g}" + +# 日志配置 +if [ -f "$FESOD_HOME/conf/logback.xml" ]; then + JAVA_OPTS="$JAVA_OPTS -Dlogback.configurationFile=$FESOD_HOME/conf/logback.xml" +fi + +# 字符编码 +JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8" + +# 执行命令 +exec "$JAVA_CMD" $JAVA_OPTS -cp "$CLASSPATH" org.apache.fesod.cli.FesodCli "$@" diff --git a/fesod-cli/src/main/scripts/fesod-cli.bat b/fesod-cli/src/main/scripts/fesod-cli.bat new file mode 100644 index 000000000..a9664a65d --- /dev/null +++ b/fesod-cli/src/main/scripts/fesod-cli.bat @@ -0,0 +1,131 @@ +@echo off +setlocal enabledelayedexpansion + +REM ============================================================================ +REM Licensed to the Apache Software Foundation (ASF) under one +REM or more contributor license agreements. See the NOTICE file +REM distributed with this work for additional information +REM regarding copyright ownership. The ASF licenses this file +REM to you under the Apache License, Version 2.0 (the +REM "License"); you may not use this file except in compliance +REM with the License. You may obtain a copy of the License at +REM +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, +REM software distributed under the License is distributed on an +REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +REM KIND, either express or implied. See the License for the +REM specific language governing permissions and limitations +REM under the License. +REM ============================================================================ + +REM ============================================================================ +REM Apache Fesod CLI Launcher Script for Windows +REM Supports: JDK 8+ +REM ============================================================================ + +REM 脚本所在目录 +set SCRIPT_DIR=%~dp0 + +REM Fesod CLI 主目录 +if not defined FESOD_HOME ( + set FESOD_HOME=%SCRIPT_DIR%.. +) + +REM 构建 CLASSPATH +REM Fesod modules +set CLASSPATH=%FESOD_HOME%\lib\* +REM Third-party dependencies +set CLASSPATH=%CLASSPATH%;%FESOD_HOME%\lib\ext\* +REM Configuration directory +set CLASSPATH=%CLASSPATH%;%FESOD_HOME%\conf + +REM 检查 lib 目录是否存在 +if not exist "%FESOD_HOME%\lib" ( + echo Error: Cannot find lib directory at %FESOD_HOME%\lib + exit /b 1 +) + +REM 查找 Java +set JAVA_CMD= + +REM 1. 检查 JAVA_HOME +if defined JAVA_HOME ( + if exist "%JAVA_HOME%\bin\java.exe" ( + set JAVA_CMD=%JAVA_HOME%\bin\java.exe + goto :java_found + ) +) + +REM 2. 检查 PATH +where java >nul 2>nul +if %ERRORLEVEL% equ 0 ( + set JAVA_CMD=java + goto :java_found +) + +REM 3. 检查常见安装路径 +for %%d in ( + "C:\Program Files\Java\jdk-8" + "C:\Program Files\Java\jdk1.8.0_*" + "C:\Program Files\Java\jdk-11" + "C:\Program Files\OpenJDK\jdk-8" + "C:\Program Files\Eclipse Adoptium\jdk-8*" +) do ( + if exist "%%~d\bin\java.exe" ( + set JAVA_CMD=%%~d\bin\java.exe + goto :java_found + ) +) + +:java_not_found +echo Error: Java is not installed or not in PATH +echo. +echo Please install Java 8 or higher: +echo - Download from: https://adoptium.net/ +echo - Or install via Chocolatey: choco install openjdk8 +echo. +echo Or set JAVA_HOME environment variable: +echo set JAVA_HOME=C:\Path\To\JDK +exit /b 1 + +:java_found + +REM 检查 Java 版本 +for /f "tokens=3" %%g in ('"%JAVA_CMD%" -version 2^>^&1 ^| findstr /i "version"') do ( + set JAVA_VERSION=%%g +) +set JAVA_VERSION=%JAVA_VERSION:"=% + +REM 提取主版本号 +for /f "delims=." %%a in ("%JAVA_VERSION%") do set JAVA_MAJOR=%%a +if "%JAVA_MAJOR%" equ "1" ( + for /f "tokens=2 delims=." %%a in ("%JAVA_VERSION%") do set JAVA_MAJOR=%%a +) + +if %JAVA_MAJOR% lss 8 ( + echo Error: Java 8 or higher is required + echo Current Java version: %JAVA_VERSION% + echo Java command: %JAVA_CMD% + exit /b 1 +) + +REM JVM 参数 +if not defined FESOD_JAVA_OPTS ( + set JAVA_OPTS=-Xms128m -Xmx1g +) else ( + set JAVA_OPTS=%FESOD_JAVA_OPTS% +) + +REM 日志配置 +if exist "%FESOD_HOME%\conf\logback.xml" ( + set JAVA_OPTS=%JAVA_OPTS% -Dlogback.configurationFile=%FESOD_HOME%\conf\logback.xml +) + +REM 字符编码 +set JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8 + +REM 执行命令 +"%JAVA_CMD%" %JAVA_OPTS% -cp "%CLASSPATH%" org.apache.fesod.cli.FesodCli %* +exit /b %ERRORLEVEL% diff --git a/fesod-cli/src/test/java/org/apache/fesod/cli/commands/ConvertCommandTest.java b/fesod-cli/src/test/java/org/apache/fesod/cli/commands/ConvertCommandTest.java new file mode 100644 index 000000000..94b033c8e --- /dev/null +++ b/fesod-cli/src/test/java/org/apache/fesod/cli/commands/ConvertCommandTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import static org.junit.jupiter.api.Assertions.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; + +/** + * Test for ConvertCommand + */ +class ConvertCommandTest { + + @TempDir + Path tempDir; + + private CommandLine cmd; + + @BeforeEach + void setUp() { + // Create command instance + ConvertCommand convertCommand = new ConvertCommand(); + cmd = new CommandLine(convertCommand); + } + + @Test + void testConvertCommandHelp() { + String[] args = {"--help"}; + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute(args); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Convert spreadsheet between different formats")); + } + + @Test + void testConvertCommandWithMissingArguments() { + String[] args = {"input.xlsx"}; + + int exitCode = cmd.execute(args); + assertEquals(2, exitCode); // Missing required argument + } + + @Test + void testConvertCommandWithNonExistentInput() { + String[] args = {"nonexistent.xlsx", "output.xlsx"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail due to missing input file + } + + @Test + void testConvertCommandWithSameInputOutput() { + String[] args = {"same.xlsx", "same.xlsx"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail + } +} diff --git a/fesod-cli/src/test/java/org/apache/fesod/cli/commands/InfoCommandTest.java b/fesod-cli/src/test/java/org/apache/fesod/cli/commands/InfoCommandTest.java new file mode 100644 index 000000000..39c0e8451 --- /dev/null +++ b/fesod-cli/src/test/java/org/apache/fesod/cli/commands/InfoCommandTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import static org.junit.jupiter.api.Assertions.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; + +/** + * Test for InfoCommand + */ +class InfoCommandTest { + + @TempDir + Path tempDir; + + private CommandLine cmd; + + @BeforeEach + void setUp() { + // Create command instance + InfoCommand infoCommand = new InfoCommand(); + cmd = new CommandLine(infoCommand); + } + + @Test + void testInfoCommandHelp() { + String[] args = {"--help"}; + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute(args); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Display spreadsheet file information")); + } + + @Test + void testInfoCommandWithMissingArguments() { + String[] args = {}; + + int exitCode = cmd.execute(args); + assertEquals(2, exitCode); // Missing required argument + } + + @Test + void testInfoCommandWithNonExistentFile() { + String[] args = {"nonexistent.xlsx"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail due to missing file + } + + @Test + void testInfoCommandWithInvalidFile() { + String[] args = {tempDir.toString()}; // Pass directory instead of file + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail + } +} diff --git a/fesod-cli/src/test/java/org/apache/fesod/cli/commands/ReadCommandTest.java b/fesod-cli/src/test/java/org/apache/fesod/cli/commands/ReadCommandTest.java new file mode 100644 index 000000000..440723abe --- /dev/null +++ b/fesod-cli/src/test/java/org/apache/fesod/cli/commands/ReadCommandTest.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.commands; + +import static org.junit.jupiter.api.Assertions.*; +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; + +/** + * Test for ReadCommand + */ +class ReadCommandTest { + + @TempDir + Path tempDir; + + private File testExcelFile; + private CommandLine cmd; + + @BeforeEach + void setUp() throws Exception { + // Create a simple test Excel file + testExcelFile = tempDir.resolve("test.xlsx").toFile(); + + // Create command instance + ReadCommand readCommand = new ReadCommand(); + cmd = new CommandLine(readCommand); + } + + @Test + void testReadCommandWithNonExistentFile() { + // Test with non-existent file + String[] args = {"nonexistent.xlsx"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail + } + + @Test + void testReadCommandHelp() { + String[] args = {"--help"}; + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute(args); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Read spreadsheet data")); + assertTrue(output.contains("--format")); + assertTrue(output.contains("--sheet")); + } + + @Test + void testReadCommandWithInvalidFormat() { + String[] args = {"test.xlsx", "--format", "invalid"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail due to unsupported format + } + + @Test + void testReadCommandWithJsonFormat() { + String[] args = {"test.xlsx", "--format", "json"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail because file doesn't exist, but command parsing should work + } + + @Test + void testReadCommandWithSheetIndex() { + String[] args = {"test.xlsx", "--sheet", "0", "--format", "json"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail because file doesn't exist + } + + @Test + void testReadCommandWithSheetName() { + String[] args = {"test.xlsx", "--sheet", "Sheet1", "--format", "json"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail because file doesn't exist + } + + @Test + void testReadCommandWithAllSheets() { + String[] args = {"test.xlsx", "--all", "--format", "json"}; + + int exitCode = cmd.execute(args); + assertEquals(1, exitCode); // Should fail because file doesn't exist + } +} diff --git a/fesod-cli/src/test/java/org/apache/fesod/cli/core/sheet/SheetProcessorTest.java b/fesod-cli/src/test/java/org/apache/fesod/cli/core/sheet/SheetProcessorTest.java new file mode 100644 index 000000000..7f48a534e --- /dev/null +++ b/fesod-cli/src/test/java/org/apache/fesod/cli/core/sheet/SheetProcessorTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.core.sheet; + +import static org.junit.jupiter.api.Assertions.*; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import org.apache.fesod.cli.exception.FileProcessException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * Test for SheetProcessor + */ +class SheetProcessorTest { + + @TempDir + Path tempDir; + + private SheetProcessor processor; + + @BeforeEach + void setUp() { + processor = new SheetProcessor(); + } + + @Test + void testGetModuleName() { + assertEquals("sheet", processor.getModuleName()); + } + + @Test + void testReadWithNonExistentFile() { + Path nonExistentFile = tempDir.resolve("nonexistent.xlsx"); + Map options = new HashMap<>(); + + assertThrows(FileProcessException.class, () -> { + processor.read(nonExistentFile, options); + }); + } + + @Test + void testWriteWithNullData() { + Path outputFile = tempDir.resolve("output.xlsx"); + Map options = new HashMap<>(); + options.put("sheetName", "TestSheet"); + + assertThrows(FileProcessException.class, () -> { + processor.write(null, outputFile, options); + }); + } + + @Test + void testConvertWithNonExistentFile() { + Path inputFile = tempDir.resolve("nonexistent.xlsx"); + Path outputFile = tempDir.resolve("output.xlsx"); + Map options = new HashMap<>(); + + assertThrows(FileProcessException.class, () -> { + processor.convert(inputFile, outputFile, options); + }); + } + + @Test + void testGetInfoWithNonExistentFile() { + Path nonExistentFile = tempDir.resolve("nonexistent.xlsx"); + + assertThrows(FileProcessException.class, () -> { + processor.getInfo(nonExistentFile); + }); + } + + @Test + void testReadWithSheetIndexOption() { + Path nonExistentFile = tempDir.resolve("test.xlsx"); + Map options = new HashMap<>(); + options.put("sheetIndex", 0); + + assertThrows(FileProcessException.class, () -> { + processor.read(nonExistentFile, options); + }); + } + + @Test + void testReadWithSheetNameOption() { + Path nonExistentFile = tempDir.resolve("test.xlsx"); + Map options = new HashMap<>(); + options.put("sheetName", "Sheet1"); + + assertThrows(FileProcessException.class, () -> { + processor.read(nonExistentFile, options); + }); + } + + @Test + void testReadAllSheetsOption() { + Path nonExistentFile = tempDir.resolve("test.xlsx"); + Map options = new HashMap<>(); + options.put("readAll", true); + + assertThrows(FileProcessException.class, () -> { + processor.read(nonExistentFile, options); + }); + } +} diff --git a/fesod-cli/src/test/java/org/apache/fesod/cli/integration/CliIntegrationTest.java b/fesod-cli/src/test/java/org/apache/fesod/cli/integration/CliIntegrationTest.java new file mode 100644 index 000000000..d1ae1f91f --- /dev/null +++ b/fesod-cli/src/test/java/org/apache/fesod/cli/integration/CliIntegrationTest.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.cli.integration; + +import static org.junit.jupiter.api.Assertions.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Path; +import org.apache.fesod.cli.FesodCli; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; + +/** + * Integration tests for CLI commands + */ +class CliIntegrationTest { + + @TempDir + Path tempDir; + + @Test + void testCliHelp() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute("--help"); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("read")); + assertTrue(output.contains("write")); + assertTrue(output.contains("convert")); + assertTrue(output.contains("info")); + assertTrue(output.contains("version")); + } + + @Test + void testCliVersion() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute("version"); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Apache Fesod CLI")); + assertTrue(output.contains("Java Version")); + assertTrue(output.contains("OS:")); + } + + @Test + void testCliWithNoArgs() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute(); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("read")); + assertTrue(output.contains("write")); + assertTrue(output.contains("convert")); + } + + @Test + void testCliWithVerboseFlag() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + // Test with verbose flag (should still show help) + int exitCode = cmd.execute("--verbose"); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Usage: fesod-cli [-hvV] [COMMAND]")); + } + + @Test + void testReadCommandHelp() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute("read", "--help"); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Read spreadsheet data")); + assertTrue(output.contains("--format")); + assertTrue(output.contains("--sheet")); + assertTrue(output.contains("--output")); + assertTrue(output.contains("--all")); + } + + @Test + void testWriteCommandHelp() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute("write", "--help"); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Write data from JSON/CSV to spreadsheet")); + assertTrue(output.contains("--input-format")); + assertTrue(output.contains("--sheet-name")); + } + + @Test + void testConvertCommandHelp() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute("convert", "--help"); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Convert spreadsheet between different formats")); + } + + @Test + void testInfoCommandHelp() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setOut(pw); + + int exitCode = cmd.execute("info", "--help"); + assertEquals(0, exitCode); + + String output = sw.toString(); + assertTrue(output.contains("Display spreadsheet file information")); + } + + @Test + void testInvalidCommand() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + int exitCode = cmd.execute("invalid-command"); + assertEquals(2, exitCode); // Invalid command should return exit code 2 + } + + @Test + void testCliErrorHandling() { + FesodCli cli = new FesodCli(); + CommandLine cmd = new CommandLine(cli); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + cmd.setErr(pw); + + // Test with invalid arguments + int exitCode = cmd.execute("read", "nonexistent.xlsx"); + assertEquals(1, exitCode); + + String errorOutput = sw.toString(); + assertTrue(errorOutput.contains("FileProcessException")); // Should contain error message + } +} diff --git a/fesod-examples/fesod-sheet-examples/pom.xml b/fesod-examples/fesod-sheet-examples/pom.xml index 24c6b151b..97932a82b 100644 --- a/fesod-examples/fesod-sheet-examples/pom.xml +++ b/fesod-examples/fesod-sheet-examples/pom.xml @@ -44,5 +44,10 @@ spring-boot-starter-web test + + com.alibaba.fastjson2 + fastjson2 + test + diff --git a/fesod-sheet/pom.xml b/fesod-sheet/pom.xml index 5c26670ef..1ddd1c50d 100644 --- a/fesod-sheet/pom.xml +++ b/fesod-sheet/pom.xml @@ -72,5 +72,11 @@ + + + com.alibaba.fastjson2 + fastjson2 + test + diff --git a/pom.xml b/pom.xml index 5b9d407b4..99ac6e8ff 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,7 @@ fesod-shaded fesod-examples fesod-sheet + fesod-cli @@ -74,6 +75,7 @@ 1.18.42 5.3.39 2.0.60 + 2.2 2.7.18 1.7.36 1.7.36 @@ -187,6 +189,11 @@ fastjson2 ${fastjson2.version} + + org.yaml + snakeyaml + ${snakeyaml.version} + org.slf4j slf4j-simple diff --git a/website/docs/cli/cli.md b/website/docs/cli/cli.md new file mode 100644 index 000000000..08bce5073 --- /dev/null +++ b/website/docs/cli/cli.md @@ -0,0 +1,448 @@ +--- +id: 'cli' +title: 'CLI Tool' +sidebar_label: 'CLI Tool' +--- + +# Fesod CLI + +Apache Fesod CLI is a command-line tool for processing Excel spreadsheets. It allows you to read, write, convert, and inspect spreadsheet files directly from the terminal. + +## Features + +- **Read**: Extract data from Excel files and output in JSON or CSV format +- **Write**: Create Excel files from JSON data +- **Convert**: Convert between different spreadsheet formats (XLS ↔ XLSX) with support for multiple sheets +- **Info**: Display detailed information about spreadsheet files + +## Installation + +### Download + +Download the latest release from the [Fesod Releases](https://github.com/apache/fesod/releases) page. + +```bash +# Extract the distribution +tar -xzf apache-fesod-2.0.0-bin.tar.gz +cd apache-fesod-2.0.0-bin +``` + +### Directory Structure + +```text +apache-fesod-2.0.0-bin/ +├── bin/ # Executable scripts +│ ├── fesod-cli # Unix/Linux/macOS launcher +│ └── fesod-cli.bat # Windows launcher +├── lib/ # Fesod modules +├── lib/ext/ # Third-party dependencies +├── conf/ # Configuration files +└── licenses/ # License files +``` + +### Requirements + +- **Java 8** or higher +- Supported operating systems: Linux, macOS, Windows + +### Verify Installation + +```bash +# Unix/Linux/macOS +./bin/fesod-cli --version + +# Windows +bin\fesod-cli.bat --version +``` + +## Quick Start + +### Read Excel File + +```bash +# Output as JSON (default) +fesod-cli read data.xlsx + +# Output as CSV +fesod-cli read data.xlsx --format csv + +# Read specific sheet +fesod-cli read data.xlsx --sheet "Sales Data" + +# Save output to file +fesod-cli read data.xlsx --output result.json +``` + +### Convert File Format + +```bash +# Convert XLS to XLSX (all sheets) +fesod-cli convert legacy.xls modern.xlsx + +# Convert XLSX to XLS (all sheets) +fesod-cli convert data.xlsx data.xls + +# Convert specific sheet only +fesod-cli convert data.xlsx output.xlsx --sheet 0 +``` + +### Display File Information + +```bash +fesod-cli info data.xlsx +``` + +Example output: + +```json +{ + "fileName": "data.xlsx", + "fileSize": 15360, + "format": "XLSX", + "sheets": [ + { + "name": "Sheet1", + "rows": 100, + "columns": 5 + } + ] +} +``` + +### Write Data to Excel + +```bash +# Create Excel from JSON data +fesod-cli write data.json output.xlsx + +# Specify sheet name +fesod-cli write data.json output.xlsx --sheet-name "Report" +``` + +## Commands Reference + +### Global Options + +| Option | Description | +|--------|-------------| +| `--help`, `-h` | Show help message | +| `--version`, `-V` | Show version information | +| `--verbose`, `-v` | Enable verbose logging | + +### read + +Read spreadsheet data and output in specified format. + +```bash +fesod-cli read [options] +``` + +**Arguments:** + +| Argument | Description | +|----------|-------------| +| `` | Input file path (required) | + +**Options:** + +| Option | Description | Default | +|--------|-------------|---------| +| `--format`, `-f` | Output format: `json`, `csv` | `json` | +| `--sheet`, `-s` | Sheet name or index | `0` (first sheet) | +| `--output`, `-o` | Output file path | stdout | +| `--all` | Read all sheets | `false` | + +**Examples:** + +```bash +# Read first sheet as JSON +fesod-cli read sales.xlsx + +# Read specific sheet by name +fesod-cli read sales.xlsx --sheet "Q1 Report" + +# Read specific sheet by index (0-based) +fesod-cli read sales.xlsx --sheet 2 + +# Read all sheets +fesod-cli read sales.xlsx --all + +# Export as CSV (includes column headers) +fesod-cli read sales.xlsx --format csv --output sales.csv +``` + +:::info CSV Format +When using `--format csv`, the output includes column headers extracted from the first row of data, making it easier to understand the CSV structure. +::: + +### write + +Write data from JSON to spreadsheet. + +```bash +fesod-cli write [options] +``` + +**Arguments:** + +| Argument | Description | +|----------|-------------| +| `` | Input data file (JSON) | +| `` | Output spreadsheet file | + +**Options:** + +| Option | Description | Default | +|--------|-------------|---------| +| `--input-format` | Input data format: `json` | `json` | +| `--sheet-name` | Sheet name | `Sheet1` | + +**Examples:** + +```bash +# Create Excel from JSON +fesod-cli write data.json report.xlsx + +# Custom sheet name +fesod-cli write data.json report.xlsx --sheet-name "Monthly Report" +``` + +**JSON Input Format:** + +```json +[ + {"name": "Alice", "age": 30, "city": "New York"}, + {"name": "Bob", "age": 25, "city": "Los Angeles"}, + {"name": "Charlie", "age": 35, "city": "Chicago"} +] +``` + +### convert + +Convert spreadsheet between different formats. + +```bash +fesod-cli convert [options] +``` + +**Arguments:** + +| Argument | Description | +|----------|-------------| +| `` | Input file path | +| `` | Output file path | + +**Options:** + +| Option | Description | Default | +|--------|-------------|---------| +| `--sheet`, `-s` | Sheet index (0-based) to convert | All sheets | +| `--sheet-name`, `-n` | Sheet name to convert | All sheets | +| `--all`, `-a` | Convert all sheets (explicit) | `true` | + +**Supported Conversions:** + +| From | To | +|------|-----| +| `.xls` | `.xlsx` | +| `.xlsx` | `.xls` | +| `.xlsx` | `.csv` | +| `.xls` | `.csv` | + +**Examples:** + +```bash +# Convert all sheets (default behavior) +fesod-cli convert legacy.xls modern.xlsx + +# Convert only the first sheet (index 0) +fesod-cli convert data.xlsx data.xls --sheet 0 + +# Convert specific sheet by name +fesod-cli convert data.xlsx output.xlsx --sheet-name "Sales" + +# Excel to CSV (first sheet only) +fesod-cli convert data.xlsx data.csv --sheet 0 + +# Convert all sheets explicitly +fesod-cli convert multi-sheet.xlsx output.xlsx --all +``` + +:::tip +By default, the `convert` command converts **all sheets** from the input file to preserve data integrity. If you only need a specific sheet, use the `--sheet` or `--sheet-name` option. +::: + +### info + +Display spreadsheet file information. + +```bash +fesod-cli info +``` + +**Arguments:** + +| Argument | Description | +|----------|-------------| +| `` | Input file path | + +**Examples:** + +```bash +fesod-cli info report.xlsx +``` + +### version + +Display version information. + +```bash +fesod-cli version +``` + +### help + +Display help information. + +```bash +# General help +fesod-cli help + +# Command-specific help +fesod-cli help read +fesod-cli read --help +``` + +## Configuration + +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `FESOD_HOME` | Installation directory | Auto-detected | +| `JAVA_HOME` | Java installation path | Auto-detected | +| `FESOD_JAVA_OPTS` | JVM options | `-Xms128m -Xmx1g` | + +### Examples + +```bash +# Increase memory for large files +export FESOD_JAVA_OPTS="-Xms256m -Xmx4g" +fesod-cli read large-file.xlsx + +# Set Java home +export JAVA_HOME=/usr/lib/jvm/java-11 +fesod-cli --version +``` + +### Configuration File + +Configuration file location: `conf/default-config.yaml` + +```yaml +# Default output format +output: + format: json + encoding: UTF-8 + +# Read options +read: + defaultSheet: 0 + includeHeaders: true + +# Write options +write: + defaultSheetName: Sheet1 +``` + +## Troubleshooting + +### Common Errors + +#### "Java is not installed or not in PATH" + +**Solution:** Install Java 8 or higher, or set the `JAVA_HOME` environment variable. + +```bash +# Linux/macOS +export JAVA_HOME=/path/to/jdk + +# Windows +set JAVA_HOME=C:\Path\To\JDK +``` + +#### "Cannot find lib directory" + +**Solution:** Set the `FESOD_HOME` environment variable. + +```bash +export FESOD_HOME=/path/to/apache-fesod-2.0.0-bin +``` + +#### "OutOfMemoryError" when processing large files + +**Solution:** Increase the heap size. + +```bash +export FESOD_JAVA_OPTS="-Xms512m -Xmx4g" +fesod-cli read large-file.xlsx +``` + +#### Script execution error on Linux: "No such file or directory" + +**Cause:** Windows line endings (CRLF) in the script file. + +**Solution:** + +```bash +# Convert to Unix line endings +dos2unix bin/fesod-cli +# Or +sed -i 's/\r$//' bin/fesod-cli +``` + +## Examples + +### Batch Processing + +```bash +#!/bin/bash +# Convert all XLS files in a directory to XLSX + +for file in *.xls; do + output="${file%.xls}.xlsx" + fesod-cli convert "$file" "$output" + echo "Converted: $file -> $output" +done +``` + +### Pipeline with jq + +```bash +# Extract specific fields using jq +fesod-cli read data.xlsx | jq '.[] | {name, email}' + +# Count rows +fesod-cli read data.xlsx | jq 'length' + +# Filter by condition +fesod-cli read data.xlsx | jq '[.[] | select(.age > 30)]' +``` + +### Export Multiple Sheets + +```bash +# Read all sheets and save to separate files +fesod-cli read multi-sheet.xlsx --all | \ + jq -c 'to_entries[]' | \ + while read sheet; do + name=$(echo "$sheet" | jq -r '.key') + echo "$sheet" | jq '.value' > "${name}.json" + done +``` + +## See Also + +- [Fesod Sheet API Documentation](/docs/sheet/read/simple) +- [Migration Guide](/docs/migration/from-fastexcel) +- [GitHub Repository](https://github.com/apache/fesod) diff --git a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current.json b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current.json index b6c742d86..bc6004180 100644 --- a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current.json +++ b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current.json @@ -7,6 +7,10 @@ "message": "快速开始", "description": "The label for category quickstart in sidebar docs" }, + "sidebar.docs.category.fesod-cli": { + "message": "命令行工具", + "description": "The label for category fesod-cli in sidebar docs" + }, "sidebar.docs.category.read": { "message": "读取", "description": "The label for category read in sidebar docs" diff --git a/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/cli/cli.md b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/cli/cli.md new file mode 100644 index 000000000..99b298b67 --- /dev/null +++ b/website/i18n/zh-cn/docusaurus-plugin-content-docs/current/cli/cli.md @@ -0,0 +1,448 @@ +--- +id: 'cli' +title: '命令行工具' +sidebar_label: '命令行工具' +--- + +# Fesod CLI 命令行工具 + +Apache Fesod CLI 是一个用于处理 Excel 电子表格的命令行工具。它可以让你直接在终端中读取、写入、转换和查看电子表格文件。 + +## 功能特性 + +- **读取 (read)**:从 Excel 文件中提取数据,输出为 JSON 或 CSV 格式 +- **写入 (write)**:从 JSON 数据创建 Excel 文件 +- **转换 (convert)**:在不同电子表格格式之间转换 (XLS ↔ XLSX),支持多工作表转换 +- **信息 (info)**:显示电子表格文件的详细信息 + +## 安装 + +### 下载 + +从 [Fesod 发布页面](https://github.com/apache/fesod/releases) 下载最新版本。 + +```bash +# 解压分发包 +tar -xzf apache-fesod-2.0.0-bin.tar.gz +cd apache-fesod-2.0.0-bin +``` + +### 目录结构 + +```text +apache-fesod-2.0.0-bin/ +├── bin/ # 可执行脚本 +│ ├── fesod-cli # Unix/Linux/macOS 启动脚本 +│ └── fesod-cli.bat # Windows 启动脚本 +├── lib/ # Fesod 模块 +├── lib/ext/ # 第三方依赖 +├── conf/ # 配置文件 +└── licenses/ # 许可证文件 +``` + +### 系统要求 + +- **Java 8** 或更高版本 +- 支持的操作系统:Linux、macOS、Windows + +### 验证安装 + +```bash +# Unix/Linux/macOS +./bin/fesod-cli --version + +# Windows +bin\fesod-cli.bat --version +``` + +## 快速开始 + +### 读取 Excel 文件 + +```bash +# 输出为 JSON(默认) +fesod-cli read data.xlsx + +# 输出为 CSV +fesod-cli read data.xlsx --format csv + +# 读取指定工作表 +fesod-cli read data.xlsx --sheet "销售数据" + +# 保存输出到文件 +fesod-cli read data.xlsx --output result.json +``` + +### 转换文件格式 + +```bash +# XLS 转 XLSX(所有工作表) +fesod-cli convert legacy.xls modern.xlsx + +# XLSX 转 XLS(所有工作表) +fesod-cli convert data.xlsx data.xls + +# 仅转换指定工作表 +fesod-cli convert data.xlsx output.xlsx --sheet 0 +``` + +### 显示文件信息 + +```bash +fesod-cli info data.xlsx +``` + +示例输出: + +```json +{ + "fileName": "data.xlsx", + "fileSize": 15360, + "format": "XLSX", + "sheets": [ + { + "name": "Sheet1", + "rows": 100, + "columns": 5 + } + ] +} +``` + +### 写入数据到 Excel + +```bash +# 从 JSON 数据创建 Excel +fesod-cli write data.json output.xlsx + +# 指定工作表名称 +fesod-cli write data.json output.xlsx --sheet-name "报表" +``` + +## 命令参考 + +### 全局选项 + +| 选项 | 描述 | +|-----|------| +| `--help`, `-h` | 显示帮助信息 | +| `--version`, `-V` | 显示版本信息 | +| `--verbose`, `-v` | 启用详细日志 | + +### read 命令 + +读取电子表格数据并以指定格式输出。 + +```bash +fesod-cli read [options] +``` + +**参数:** + +| 参数 | 描述 | +|-----|------| +| `` | 输入文件路径(必需) | + +**选项:** + +| 选项 | 描述 | 默认值 | +|-----|------|-------| +| `--format`, `-f` | 输出格式:`json`, `csv` | `json` | +| `--sheet`, `-s` | 工作表名称或索引 | `0`(第一个工作表) | +| `--output`, `-o` | 输出文件路径 | 标准输出 | +| `--all` | 读取所有工作表 | `false` | + +**示例:** + +```bash +# 读取第一个工作表为 JSON +fesod-cli read sales.xlsx + +# 按名称读取指定工作表 +fesod-cli read sales.xlsx --sheet "第一季度报表" + +# 按索引读取指定工作表(从 0 开始) +fesod-cli read sales.xlsx --sheet 2 + +# 读取所有工作表 +fesod-cli read sales.xlsx --all + +# 导出为 CSV(包含列标题) +fesod-cli read sales.xlsx --format csv --output sales.csv +``` + +:::info CSV 格式 +当使用 `--format csv` 时,输出会包含从数据第一行提取的列标题,让 CSV 结构更易于理解。 +::: + +### write 命令 + +从 JSON 数据写入电子表格。 + +```bash +fesod-cli write [options] +``` + +**参数:** + +| 参数 | 描述 | +|-----|------| +| `` | 输入数据文件(JSON) | +| `` | 输出电子表格文件 | + +**选项:** + +| 选项 | 描述 | 默认值 | +|-----|------|-------| +| `--input-format` | 输入数据格式:`json` | `json` | +| `--sheet-name` | 工作表名称 | `Sheet1` | + +**示例:** + +```bash +# 从 JSON 创建 Excel +fesod-cli write data.json report.xlsx + +# 自定义工作表名称 +fesod-cli write data.json report.xlsx --sheet-name "月度报表" +``` + +**JSON 输入格式:** + +```json +[ + {"姓名": "张三", "年龄": 30, "城市": "北京"}, + {"姓名": "李四", "年龄": 25, "城市": "上海"}, + {"姓名": "王五", "年龄": 35, "城市": "广州"} +] +``` + +### convert 命令 + +在不同电子表格格式之间转换。 + +```bash +fesod-cli convert [options] +``` + +**参数:** + +| 参数 | 描述 | +|-----|------| +| `` | 输入文件路径 | +| `` | 输出文件路径 | + +**选项:** + +| 选项 | 描述 | 默认值 | +|-----|------|-------| +| `--sheet`, `-s` | 要转换的工作表索引(从 0 开始) | 所有工作表 | +| `--sheet-name`, `-n` | 要转换的工作表名称 | 所有工作表 | +| `--all`, `-a` | 转换所有工作表(显式) | `true` | + +**支持的转换:** + +| 源格式 | 目标格式 | +|-------|---------| +| `.xls` | `.xlsx` | +| `.xlsx` | `.xls` | +| `.xlsx` | `.csv` | +| `.xls` | `.csv` | + +**示例:** + +```bash +# 转换所有工作表(默认行为) +fesod-cli convert legacy.xls modern.xlsx + +# 仅转换第一个工作表(索引 0) +fesod-cli convert data.xlsx data.xls --sheet 0 + +# 按名称转换指定工作表 +fesod-cli convert data.xlsx output.xlsx --sheet-name "销售数据" + +# Excel 转 CSV(仅第一个工作表) +fesod-cli convert data.xlsx data.csv --sheet 0 + +# 显式转换所有工作表 +fesod-cli convert multi-sheet.xlsx output.xlsx --all +``` + +:::tip 提示 +默认情况下,`convert` 命令会转换输入文件的**所有工作表**以保持数据完整性。如果只需要特定工作表,请使用 `--sheet` 或 `--sheet-name` 选项。 +::: + +### info 命令 + +显示电子表格文件信息。 + +```bash +fesod-cli info +``` + +**参数:** + +| 参数 | 描述 | +|-----|------| +| `` | 输入文件路径 | + +**示例:** + +```bash +fesod-cli info report.xlsx +``` + +### version 命令 + +显示版本信息。 + +```bash +fesod-cli version +``` + +### help 命令 + +显示帮助信息。 + +```bash +# 通用帮助 +fesod-cli help + +# 命令特定帮助 +fesod-cli help read +fesod-cli read --help +``` + +## 配置 + +### 环境变量 + +| 变量 | 描述 | 默认值 | +|-----|------|-------| +| `FESOD_HOME` | 安装目录 | 自动检测 | +| `JAVA_HOME` | Java 安装路径 | 自动检测 | +| `FESOD_JAVA_OPTS` | JVM 选项 | `-Xms128m -Xmx1g` | + +### 示例 + +```bash +# 增加内存以处理大文件 +export FESOD_JAVA_OPTS="-Xms256m -Xmx4g" +fesod-cli read large-file.xlsx + +# 设置 Java 路径 +export JAVA_HOME=/usr/lib/jvm/java-11 +fesod-cli --version +``` + +### 配置文件 + +配置文件位置:`conf/default-config.yaml` + +```yaml +# 默认输出格式 +output: + format: json + encoding: UTF-8 + +# 读取选项 +read: + defaultSheet: 0 + includeHeaders: true + +# 写入选项 +write: + defaultSheetName: Sheet1 +``` + +## 故障排除 + +### 常见错误 + +#### "Java is not installed or not in PATH" + +**解决方案:** 安装 Java 8 或更高版本,或设置 `JAVA_HOME` 环境变量。 + +```bash +# Linux/macOS +export JAVA_HOME=/path/to/jdk + +# Windows +set JAVA_HOME=C:\Path\To\JDK +``` + +#### "Cannot find lib directory" + +**解决方案:** 设置 `FESOD_HOME` 环境变量。 + +```bash +export FESOD_HOME=/path/to/apache-fesod-2.0.0-bin +``` + +#### 处理大文件时出现 "OutOfMemoryError" + +**解决方案:** 增加堆内存大小。 + +```bash +export FESOD_JAVA_OPTS="-Xms512m -Xmx4g" +fesod-cli read large-file.xlsx +``` + +#### Linux 上脚本执行错误:"没有那个文件或目录" + +**原因:** 脚本文件包含 Windows 行尾符(CRLF)。 + +**解决方案:** + +```bash +# 转换为 Unix 行尾符 +dos2unix bin/fesod-cli +# 或者 +sed -i 's/\r$//' bin/fesod-cli +``` + +## 使用示例 + +### 批量处理 + +```bash +#!/bin/bash +# 将目录中所有 XLS 文件转换为 XLSX + +for file in *.xls; do + output="${file%.xls}.xlsx" + fesod-cli convert "$file" "$output" + echo "已转换: $file -> $output" +done +``` + +### 配合 jq 使用 + +```bash +# 使用 jq 提取特定字段 +fesod-cli read data.xlsx | jq '.[] | {name, email}' + +# 统计行数 +fesod-cli read data.xlsx | jq 'length' + +# 按条件筛选 +fesod-cli read data.xlsx | jq '[.[] | select(.age > 30)]' +``` + +### 导出多个工作表 + +```bash +# 读取所有工作表并保存到单独文件 +fesod-cli read multi-sheet.xlsx --all | \ + jq -c 'to_entries[]' | \ + while read sheet; do + name=$(echo "$sheet" | jq -r '.key') + echo "$sheet" | jq '.value' > "${name}.json" + done +``` + +## 相关链接 + +- [Fesod Sheet API 文档](/docs/sheet/read/simple) +- [迁移指南](/docs/migration/from-fastexcel) +- [GitHub 仓库](https://github.com/apache/fesod) diff --git a/website/sidebars.js b/website/sidebars.js index 5d3ef97b1..333d5ec18 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -44,6 +44,12 @@ const sidebars = { 'quickstart/guide', 'quickstart/simple-example' ] + }, { + type: 'category', + label: 'fesod-cli', + items: [ + 'cli/cli' + ] }, { type: 'category', label: 'fesod-sheet',