extended firefly import options, cleaned up language usage in code, new README in english, changed license references

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Reindl David (IT-PTR-CEN2-SL10)
2026-05-04 00:23:02 +02:00
co-authored by Copilot
parent 90dc37cafc
commit a6ed05a38a
15 changed files with 2064 additions and 1208 deletions
+180 -39
View File
@@ -18,7 +18,7 @@ use UbsCsvTransformer\ConfigurationLoader;
use UbsCsvTransformer\FireflyImporter;
// ============================================================================
// CLI-Argument-Verarbeitung
// CLI argument processing
// ============================================================================
$argc = $_SERVER['argc'] ?? 0;
@@ -29,10 +29,10 @@ if ($argc < 2) {
exit(0);
}
// Debug-Modus aktivierbar
// Debug mode can be enabled
$debug = in_array('--debug', $argv) || in_array('-d', $argv);
// Extrahiere Kommando
// Extract command
$command = $argv[1];
try {
@@ -54,11 +54,26 @@ try {
// ============================================================================
/**
* Zeige Hilfe und Verwendungsanleitung
* Returns true when the active shell locale is German (de_*)
*/
function isGermanLocale(): bool
{
foreach (['LANG', 'LC_ALL', 'LC_MESSAGES', 'LANGUAGE'] as $var) {
$val = getenv($var);
if ($val !== false && $val !== '') {
return str_starts_with(strtolower($val), 'de');
}
}
return false;
}
/**
* Show help and usage instructions
*/
function showHelp(): void
{
echo <<<'HELP'
if (isGermanLocale()) {
echo <<<'HELP_DE'
╔════════════════════════════════════════════════════════════════════════════╗
║ Firefly Import Preprocessor - Kommandozeilen-Tool ║
║ ║
@@ -170,26 +185,150 @@ KONFIGURATION:
DOKUMENTATION:
Siehe README.md und UBS_Transformer_Guide.md für vollständige Dokumentation
Siehe README.md für vollständige Dokumentation
LIZENZ:
MIT License
GPL 3
HELP;
HELP_DE;
return;
}
echo <<<'HELP_EN'
╔════════════════════════════════════════════════════════════════════════════╗
║ Firefly Import Preprocessor - Command Line Tool ║
║ ║
║ A lightweight PHP 8 tool for transforming UBS E-Banking exports ║
║ into a Firefly III compatible format. ║
╚════════════════════════════════════════════════════════════════════════════╝
USAGE:
transformer [command] [options]
COMMANDS:
test [input] [config] [options]
Tests the transformation with a limited number of rows
Options:
--rows=N Process only N rows (default: 10)
--output=FILE, -o Also write result to file
Example:
transformer test ubs-export.csv config.json --rows=5
transformer test ubs-export.csv config.json -o test-output.csv
transform [input] [config] [options]
Transforms a complete CSV file
Options:
--output=FILE, -o Output path (default: input-transformed.csv)
--no-import Do not automatically import into Firefly III
Example:
transformer transform ubs-export.csv config.json
transformer transform ubs-export.csv config.json -o import.csv
validate [config] [options]
Validates the configuration file
Options:
--strict Strict validation (recommended)
Example:
transformer validate config.json
transformer validate config.json --strict
auto-import [config] [options]
Monitors source directory and processes new files
Options:
--watch Continuous monitoring (daemon mode)
--interval=SEC Check interval in seconds (default: 60)
--dry-run Show what would be done (no actual processing)
Example:
transformer auto-import config.json
transformer auto-import config.json --watch --interval=30
help, -h, --help
Show this help
GLOBAL OPTIONS:
--debug, -d Enable debug mode (detailed output)
INSTALLATION:
1. PHP 8.1+ must be installed
php --version
2. Autoloader setup (choose one):
Option A: With Composer (recommended)
composer install
Option B: Manual - files in directory structure:
ff-imp-preprocessor/
├── bin/transformer.php
├── src/*.php
└── config/config.json
3. Make executable:
chmod +x bin/transformer.php
4. Adjust configuration:
cp config/config.example.json config/config.json
nano config/config.json
EXAMPLES:
# Test transformation (first 5 rows)
./bin/transformer test data/ubs-export.csv config/config.json --rows=5
# Full transformation
./bin/transformer transform data/ubs-export.csv config/config.json \
--output=output/firefly-import.csv
# Validate configuration
./bin/transformer validate config/config.json --strict
# Start auto-import with monitoring
./bin/transformer auto-import config/config.json --watch
# Process only next file
./bin/transformer auto-import config/config.json
CONFIGURATION:
The config.json must have the following structure:
{
"metadata": { "extractionRules": {...} },
"csvStructure": { "delimiter": ";", ... },
"columnTransformations": { ... },
"fireflyImport": { "apiUrl": "...", "apiKey": "..." },
"directories": {
"source": "./import/source",
"output": "./import/output",
"archive": "./import/archive",
"error": "./import/error"
}
}
DOCUMENTATION:
See README.md for full documentation
LICENSE:
GPL 3
HELP_EN;
}
/**
* Expandiert ~ zu absolutem Home-Verzeichnis und löst relative Pfade auf
* Expands ~ to absolute home directory and resolves relative paths
*/
function expandPath(string $path): string
{
if (str_starts_with($path, '~/') || $path === '~') {
$home = getenv('HOME') ?: posix_getpwuid(posix_getuid())['dir'];
$path = $home . substr($path, 1);
$homeEnv = getenv('HOME');
$pwInfo = posix_getpwuid(posix_getuid());
$home = $homeEnv !== false && $homeEnv !== '' ? $homeEnv : ($pwInfo !== false ? $pwInfo['dir'] : '~');
$path = $home . substr($path, 1);
}
// Relative Pfade gegen cwd auflösen (ohne realpath, damit nicht-existierende Dirs erlaubt sind)
// Resolve relative paths against cwd (without realpath, so non-existent dirs are allowed)
if (!str_starts_with($path, '/')) {
$path = getcwd() . '/' . $path;
}
@@ -198,7 +337,7 @@ function expandPath(string $path): string
}
/**
* Parse CLI-Optionen in assoziatives Array
* Parses CLI options into an associative array
*/
function parseOptions(array $argv, int $startIndex = 0): array
{
@@ -217,9 +356,9 @@ function parseOptions(array $argv, int $startIndex = 0): array
}
/**
* Teste Transformation mit begrenzter Zeilenzahl
* Tests transformation with a limited number of rows
*/
function handleTest($argc, $argv): void
function handleTest(int $argc, array $argv): void
{
if ($argc < 4) {
throw new Exception("Usage: transformer test [input-file] [config-file] [options]");
@@ -234,10 +373,10 @@ function handleTest($argc, $argv): void
$outputFile = $options['output'] ?? $options['o'] ?? null;
if (!file_exists($inputFile)) {
throw new Exception("Input-Datei nicht gefunden: $inputFile");
throw new Exception("Input file not found: $inputFile");
}
if (!file_exists($configFile)) {
throw new Exception("Konfigurationsdatei nicht gefunden: $configFile");
throw new Exception("Configuration file not found: $configFile");
}
echo "\n📊 TEST-MODUS: Verarbeite max. $maxRows Zeilen\n";
@@ -292,9 +431,9 @@ function handleTest($argc, $argv): void
}
/**
* Transformiere komplette CSV-Datei
* Transforms a complete CSV file
*/
function handleTransform($argc, $argv): void
function handleTransform(int $argc, array $argv): void
{
if ($argc < 4) {
throw new Exception("Usage: transformer transform [input-file] [config-file] [options]");
@@ -308,10 +447,10 @@ function handleTransform($argc, $argv): void
$outputFile = $options['output'] ?? $options['o'] ?? null;
if (!file_exists($inputFile)) {
throw new Exception("Input-Datei nicht gefunden: $inputFile");
throw new Exception("Input file not found: $inputFile");
}
if (!file_exists($configFile)) {
throw new Exception("Konfigurationsdatei nicht gefunden: $configFile");
throw new Exception("Configuration file not found: $configFile");
}
echo "\n🚀 TRANSFORMATION STARTEN\n";
@@ -320,7 +459,7 @@ function handleTransform($argc, $argv): void
$configLoader = new ConfigurationLoader($configFile);
$configLoader->load();
// --output überschreibt Zielverzeichnis und Dateiname aus der Konfiguration
// --output overrides target directory and filename from configuration
if ($outputFile !== null) {
$outputFile = expandPath($outputFile);
$configLoader->set('directories.output', dirname($outputFile));
@@ -338,9 +477,9 @@ function handleTransform($argc, $argv): void
}
/**
* Validiere Konfigurationsdatei
* Validates the configuration file
*/
function handleValidate($argc, $argv): void
function handleValidate(int $argc, array $argv): void
{
if ($argc < 3) {
throw new Exception("Usage: transformer validate [config-file] [options]");
@@ -351,7 +490,7 @@ function handleValidate($argc, $argv): void
$strict = isset($options['strict']);
if (!file_exists($configFile)) {
throw new Exception("Konfigurationsdatei nicht gefunden: $configFile");
throw new Exception("Configuration file not found: $configFile");
}
echo "\n✔️ KONFIGURATION VALIDIEREN\n";
@@ -362,7 +501,7 @@ function handleValidate($argc, $argv): void
try {
$config = $configLoader->load();
// Basis-Validierung
// Basic validation
echo "✅ JSON-Format valide\n";
$required = ['metadata', 'csvStructure', 'columnTransformations'];
@@ -377,7 +516,7 @@ function handleValidate($argc, $argv): void
}
}
// Firefly-Validierung
// Firefly validation
if (isset($config['fireflyImport'])) {
echo "✅ Firefly III Konfiguration vorhanden\n";
if (empty($config['fireflyImport']['apiUrl'])) {
@@ -396,7 +535,7 @@ function handleValidate($argc, $argv): void
echo "⚠️ Firefly III Konfiguration nicht vorhanden (optional)\n";
}
// Verzeichnisse-Validierung
// Directory validation
if (isset($config['directories'])) {
echo "✅ Verzeichnisse konfiguriert\n";
$dirs = ['source', 'output', 'archive', 'error'];
@@ -416,14 +555,14 @@ function handleValidate($argc, $argv): void
echo "\n⚠️ Konfiguration hat Warnungen aber ist funktional\n\n";
}
} catch (Exception $e) {
throw new Exception("Validierungsfehler: " . $e->getMessage());
throw new Exception("Validation error: " . $e->getMessage());
}
}
/**
* Auto-Import mit Verzeichnis-Überwachung
* Auto-import with directory monitoring
*/
function handleAutoImport($argc, $argv): void
function handleAutoImport(int $argc, array $argv): void
{
if ($argc < 3) {
throw new Exception("Usage: transformer auto-import [config-file] [options]");
@@ -434,7 +573,7 @@ function handleAutoImport($argc, $argv): void
$debug = isset($options['debug']) || isset($options['d']);
if (!file_exists($configFile)) {
throw new Exception("Konfigurationsdatei nicht gefunden: $configFile");
throw new Exception("Configuration file not found: $configFile");
}
$configLoader = new ConfigurationLoader($configFile);
@@ -448,7 +587,7 @@ function handleAutoImport($argc, $argv): void
$watch = isset($options['watch']);
$interval = isset($options['interval']) ? (int)$options['interval'] : 60;
// Verzeichnisse erstellen
// Create directories
foreach ([$sourceDir, $outputDir, $archiveDir, $errorDir] as $dir) {
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
@@ -476,7 +615,9 @@ function handleAutoImport($argc, $argv): void
if ($watch) {
echo "⏳ Drücke Ctrl+C zum Beenden.\n\n";
while (true) {
$running = true;
/** @phpstan-ignore while.alwaysTrue (intentional infinite loop — terminated only via Ctrl+C / SIGINT) */
while ($running) {
processImportDirectory($sourceDir, $outputDir, $archiveDir, $errorDir, $config, $configFile, $dryRun, $debug);
sleep($interval);
}
@@ -486,9 +627,9 @@ function handleAutoImport($argc, $argv): void
}
/**
* Verarbeite Verzeichnis mit CSV-Dateien
* Processes directory containing CSV files
*/
function processImportDirectory($sourceDir, $outputDir, $archiveDir, $errorDir, $config, $configFile, $dryRun = false, $debug = false): void
function processImportDirectory(string $sourceDir, string $outputDir, string $archiveDir, string $errorDir, array $config, string $configFile, bool $dryRun = false, bool $debug = false): void
{
if (!is_dir($sourceDir)) {
return;
@@ -516,10 +657,10 @@ function processImportDirectory($sourceDir, $outputDir, $archiveDir, $errorDir,
$result = $engine->transform($file);
$outputFile = $result['outputFile'] ?? $outputFile;
// Archiviere Original-Datei
// Archive original file
$archiveFile = $archiveDir . '/' . $basename;
if (!rename($file, $archiveFile)) {
throw new Exception("Konnte nicht archivieren");
throw new Exception("Could not archive file");
}
// Firefly Import
@@ -534,7 +675,7 @@ function processImportDirectory($sourceDir, $outputDir, $archiveDir, $errorDir,
echo "❌ " . $e->getMessage() . "\n";
if (!$dryRun) {
// Verschiebe zu Error-Verzeichnis
// Move to error directory
$errorFile = $errorDir . '/' . $basename;
@rename($file, $errorFile);
}