using GisConverter.Lib.Converters;
using GisConverter.Lib.Factories;
using GisConverter.Lib.Licensing;
using GisConverter.Lib.Logging;
using GisConverter.Lib.Models;
namespace GisConverter.ConsoleApp
{
///
/// Entry point and CLI dispatch for the GisConverter console application.
///
///
/// Responsibilities
/// - Exposes the single top-level command gis_convert which routes an input GIS artifact to a target format converter.
/// - Performs host-level concerns only: argument parsing, basic validation, logging initialization, license application
/// and converter selection. Conversion work is delegated to and .
///
/// Design notes
/// - The class intentionally keeps behavior deterministic and minimal because tests invoke
/// directly and assert on return codes and console output.
/// - Exit codes (see ) are used to communicate outcome to callers and automation.
/// - Keep user-facing messages concise and stable; tests and automation rely on predictable tokens and exit codes.
///
public class Program
{
// Exit codes for the CLI
private enum ExitCode : int
{
Success = 0, // Successful execution
AppError = 1, // invalid args, wrong command
ConversionFailed = 2, // converter returned Failure or null
Unexpected = 3, // unexpected exception
}
private const int MIN_REQUIRED_ARGS = 5;
private const int LOG_ARGS_START_INDEX = 5;
///
/// Process entry point. Sets up basic host concerns and delegates to .
///
/// Command-line arguments supplied by the caller.
///
/// - Enables UTF-8 console output so international characters and emoji render correctly.
/// - Uses in Debug builds to provide convenient developer scenarios.
/// - Calls and returns its numeric exit code; this method is intentionally minimal
/// and non-throwing so test harnesses and hosts can rely on the returned value.
///
private static int Main(string[] args)
{
// Enable Unicode (UTF-8) output for emoji and international characters
Console.OutputEncoding = System.Text.Encoding.UTF8;
args = EnsureDebugArgs(args);
return Run(args);
}
///
/// Returns debug default arguments when running under a debugger and the caller supplied insufficient args.
///
/// Original CLI arguments. May be null.
///
/// The original when populated; otherwise a curated set of debug arguments selected via
/// the environment variable GISCONVERTER_DEBUG_TARGET. In non-DEBUG builds the original args are returned.
///
///
/// - Compiled only in DEBUG builds; this helper injects developer-friendly scenarios and must not affect Release/CI.
/// - Use GISCONVERTER_DEBUG_TARGET to select a scenario (examples: Shapefile1, Csv1, Gml1).
/// - Keep debug defaults out of production paths to avoid surprising behavior in CI or consumers.
///
private static string[] EnsureDebugArgs(string[] args)
{
#if DEBUG
if (args != null && args.Length >= 6)
return args;
// Allow selecting a debug scenario via environment variable.
// Supported values: Shapefile (default), Csv, Gml.
var debugTarget = Environment.GetEnvironmentVariable("GISCONVERTER_DEBUG_TARGET");
switch (debugTarget.Trim().ToLowerInvariant())
{
// Shapefile test cases
case "shapefile1":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Shapefile\Input\ShapeFiles.7z",
"Shapefile",
@"D:\GisConverter\Tests\Shapefile\Output\Shapefile1",
@"D:\GisConverter\Tests\Shapefile\Temp\Shapefile1",
"Log",
@"D:\GisConverter\Tests\Shapefile\Log\shapefile_log1.txt"
};
case "shapefile2":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Shapefile\Input\Vector.7z",
"Shapefile",
@"D:\GisConverter\Tests\Shapefile\Output\Shapefile2",
@"D:\GisConverter\Tests\Shapefile\Temp\Shapefile2",
"Log",
@"D:\GisConverter\Tests\Shapefile\Log\shapefile_log2.txt"
};
case "shapefile3":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Shapefile\Input\לא סטטוטורי - גבול מחוז מאוחד.7z",
"Shapefile",
@"D:\GisConverter\Tests\Shapefile\Output\Shapefile3",
@"D:\GisConverter\Tests\Shapefile\Temp\Shapefile3",
"Log",
@"D:\GisConverter\Tests\Shapefile\Log\shapefile_log3.txt"
};
case "shapefile4":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Shapefile\Input\שכבות מידע (Arc View).7z",
"Shapefile",
@"D:\GisConverter\Tests\Shapefile\Output\Shapefile4",
@"D:\GisConverter\Tests\Shapefile\Temp\Shapefile4",
"Log",
@"D:\GisConverter\Tests\Shapefile\Log\shapefile_log4.txt"
};
// Csv test cases
case "csv1":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Csv\Input\features.7z",
"Csv",
@"D:\GisConverter\Tests\Csv\Output\Csv1",
@"D:\GisConverter\Tests\Csv\Temp\Csv1",
"Log",
@"D:\GisConverter\Tests\Csv\Log\csv_log1.txt"
};
case "csv2":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Csv\Input\features.csv",
"Csv",
@"D:\GisConverter\Tests\Csv\Output\Csv2",
@"D:\GisConverter\Tests\Csv\Temp\Csv2",
"Log",
@"D:\GisConverter\Tests\Csv\Log\csv_log2.txt"
};
case "csv3":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Csv\Input\features.zip",
"Csv",
@"D:\GisConverter\Tests\Csv\Output\Csv3",
@"D:\GisConverter\Tests\Csv\Temp\Csv3",
"Log",
@"D:\GisConverter\Tests\Csv\Log\csv_log3.txt"
};
// Gml test cases
case "gml1":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Gml\Input\gml_with_attribute_collection_schema.7z",
"Gml",
@"D:\GisConverter\Tests\Gml\Output\Gml1",
@"D:\GisConverter\Tests\Gml\Temp\Gml1",
"Log",
@"D:\GisConverter\Tests\Gml\Log\gml_log1.txt"
};
case "gml2":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Gml\Input\gml_with_attribute_collection_schema.zip",
"Gml",
@"D:\GisConverter\Tests\Gml\Output\Gml2",
@"D:\GisConverter\Tests\Gml\Temp\Gml2",
"Log",
@"D:\GisConverter\Tests\Gml\Log\gml_log2.txt"
};
case "gml3":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Gml\Input\gml_without_attribute_collection_schema.7z",
"Gml",
@"D:\GisConverter\Tests\Gml\Output\Gml3",
@"D:\GisConverter\Tests\Gml\Temp\Gml3",
"Log",
@"D:\GisConverter\Tests\Gml\Log\gml_log3.txt"
};
case "gml4":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Gml\Input\gml_without_attribute_collection_schema.gml",
"Gml",
@"D:\GisConverter\Tests\Gml\Output\Gml4",
@"D:\GisConverter\Tests\Gml\Temp\Gml4",
"Log",
@"D:\GisConverter\Tests\Gml\Log\gml_log4.txt"
};
case "gml5":
return new[]
{
"gis_convert",
@"D:\GisConverter\Tests\Gml\Input\gml_without_attribute_collection_schema.zip",
"Gml",
@"D:\GisConverter\Tests\Gml\Output\Gml5",
@"D:\GisConverter\Tests\Gml\Temp\Gml5",
"Log",
@"D:\GisConverter\Tests\Gml\Log\gml_log5.txt"
};
default:
return args ?? Array.Empty();
}
#else
return args ?? Array.Empty();
#endif
}
///
/// Main dispatcher for command-line operations.
///
/// Command-line arguments passed to the application.
/// Integer exit code describing result (see ).
///
/// - Recognizes top-level help flags and the single supported command gis_convert.
/// - Applies licensing via before performing other work;
/// when licensing fails a friendly error is written to stderr and the method returns an application error.
/// - Catches unexpected exceptions and returns while printing the exception message
/// for diagnostics. The method itself avoids throwing to keep callers (tests, CI) deterministic.
///
public static int Run(string[] args)
{
// Apply license FIRST before doing anything else
if (!AsposeLicenseManager.ApplyLicense())
{
Console.Error.WriteLine("⚠️ Failed to apply aspose license.");
Console.Error.WriteLine("the program cannot continue without a valid license.");
return (int)ExitCode.AppError;
}
if (args.Length == 0 || args[0] == "help" || args[0] == "--help" || args[0] == "-h")
{
ShowHelp();
return (int)ExitCode.Success;
}
if (args[0] == "--help-formats")
{
ShowHelpFormats();
return (int)ExitCode.Success;
}
// Extract the command from the first argument.
string command = args[0].ToLowerInvariant();
try
{
if (!args[0].Equals("gis_convert", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"❌ Unknown command: {args[0]}\n");
ShowHelp();
return (int)ExitCode.AppError;
}
return RunGisConverter(args);
}
catch (Exception ex)
{
Console.WriteLine("❌ Error:");
Console.WriteLine(ex.Message);
return (int)ExitCode.Unexpected;
}
}
///
/// Executes the gis_convert command and returns an exit code.
///
/// Command-line arguments where:
/// args[1] = input path, args[2] = target format, args[3] = output folder, args[4] = temp folder,
/// optional args starting at index 5 may contain logging options.
/// Exit code representing the command outcome.
///
/// Responsibilities and behavior:
/// - Validates argument count and prints usage when insufficient arguments are supplied.
/// - Parses optional logging arguments via and initializes logging
/// with (best-effort — logging failures do not abort conversion).
/// - Resolves an appropriate converter using and
/// invokes its method.
/// - Maps converter outcomes to stable exit codes:
/// - on success,
/// - when the converter reports failure or returns null,
/// - for validation/usage/detection issues.
/// - Defensive: catches unexpected exceptions from converters and reports them while returning .
///
/// Notes for tests and consumers:
/// - Tests may rely on specific console tokens and exit codes; prefer case-insensitive substring checks when asserting messages.
/// - The method intentionally writes concise, human-readable diagnostics to stdout/stderr to aid developers and CI diagnostics.
///
private static int RunGisConverter(string[] args)
{
// Require at least: command (gis_convert), gis input file path, gis target format option, output folder path, out temp folder.
if (args.Length < MIN_REQUIRED_ARGS)
{
Console.WriteLine("Usage: gis_convert