From 36333211ca1a1930cb9349349ecc29efe36c9337 Mon Sep 17 00:00:00 2001 From: Shinsuke Sugaya Date: Thu, 5 Mar 2026 18:08:02 +0900 Subject: [PATCH] refactor(commands): simplify command modules and add fessctl skill Refactor all command modules to reduce boilerplate and improve consistency. Expand utils.py with shared helpers, update test coverage accordingly, and add a Claude skill file for fessctl to enable AI-assisted management. Co-Authored-By: Claude Sonnet 4.6 --- .claude/skills/fessctl/SKILL.md | 172 ++++++++++++++++++++++ pyproject.toml | 1 - src/fessctl/cli.py | 17 +-- src/fessctl/commands/accesstoken.py | 115 ++++----------- src/fessctl/commands/badword.py | 107 +++++--------- src/fessctl/commands/boostdoc.py | 106 +++++-------- src/fessctl/commands/crawlinginfo.py | 76 ++++------ src/fessctl/commands/dataconfig.py | 121 +++++---------- src/fessctl/commands/duplicatehost.py | 135 ++++++----------- src/fessctl/commands/elevateword.py | 124 ++++++---------- src/fessctl/commands/fileauth.py | 118 ++++++--------- src/fessctl/commands/fileconfig.py | 156 +++++++------------- src/fessctl/commands/group.py | 175 ++++++++++------------ src/fessctl/commands/joblog.py | 91 +++++------- src/fessctl/commands/keymatch.py | 117 ++++++--------- src/fessctl/commands/labeltype.py | 115 +++++---------- src/fessctl/commands/pathmap.py | 112 +++++--------- src/fessctl/commands/relatedcontent.py | 110 +++++--------- src/fessctl/commands/relatedquery.py | 109 +++++--------- src/fessctl/commands/reqheader.py | 116 +++++---------- src/fessctl/commands/role.py | 113 +++++--------- src/fessctl/commands/scheduler.py | 140 ++++++------------ src/fessctl/commands/user.py | 132 +++++++---------- src/fessctl/commands/webauth.py | 120 +++++---------- src/fessctl/commands/webconfig.py | 154 ++++++------------- src/fessctl/utils.py | 73 ++++++++- tests/unit/test_utils.py | 196 +++++++++++++++++++++---- uv.lock | 2 - 28 files changed, 1333 insertions(+), 1790 deletions(-) create mode 100644 .claude/skills/fessctl/SKILL.md diff --git a/.claude/skills/fessctl/SKILL.md b/.claude/skills/fessctl/SKILL.md new file mode 100644 index 0000000..b3d604f --- /dev/null +++ b/.claude/skills/fessctl/SKILL.md @@ -0,0 +1,172 @@ +--- +name: fessctl +description: Manage Fess search engine via fessctl CLI. Use for CRUD operations on web configs, file configs, data configs, schedulers, users, roles, groups, and more. +--- + +## Overview + +fessctl is a CLI tool for managing Fess search engine via the admin API. It supports 22 resource types with standard CRUD operations. + +### Prerequisites + +- Python 3.13+ +- fessctl installed (`pip install fessctl` or `uv pip install fessctl`) +- Fess server running with API access enabled + +## Environment Setup + +```bash +export FESS_ENDPOINT="http://localhost:8080" # Fess server URL +export FESS_ACCESS_TOKEN="your-access-token" # API access token +export FESS_VERSION="v1" # API version (default: v1) +``` + +## Output Formats + +- `text` (default) - Markdown tables and structured output, AI-friendly +- `json` - Raw JSON from API +- `yaml` - YAML formatted output + +Use `-o json` for programmatic parsing, `-o text` for human/AI readable output. + +## Command Reference + +| Resource | Commands | Description | +| --- | --- | --- | +| accesstoken | create, update, delete, get, list | API access tokens | +| badword | create, update, delete, get, list | Bad words for suggest | +| boostdoc | create, update, delete, get, list | Document boost rules | +| crawlinginfo | delete, get, list | Crawling session info | +| dataconfig | create, update, delete, get, list | Data store configs | +| duplicatehost | create, update, delete, get, list | Duplicate host mappings | +| elevateword | create, update, delete, get, list | Promoted search words | +| fileauth | create, update, delete, get, list | File auth credentials | +| fileconfig | create, update, delete, get, list | File crawl configs | +| group | create, update, delete, get, getbyname, list | User groups | +| joblog | delete, get, list | Job execution logs | +| keymatch | create, update, delete, get, list | Key match rules | +| labeltype | create, update, delete, get, list | Label types | +| pathmap | create, update, delete, get, list | Path mappings | +| relatedcontent | create, update, delete, get, list | Related content | +| relatedquery | create, update, delete, get, list | Related queries | +| reqheader | create, update, delete, get, list | Request headers | +| role | create, update, delete, get, getbyname, list | User roles | +| scheduler | create, update, delete, get, list, start, stop | Job schedulers | +| user | create, update, delete, get, getbyname, list | Users | +| webauth | create, update, delete, get, list | Web auth credentials | +| webconfig | create, update, delete, get, list | Web crawl configs | + +## Common Workflows + +### Health Check + +```bash +fessctl ping +``` + +### Web Crawl Setup + +```bash +# 1. Create web config +fessctl webconfig create --name "My Site" --url "https://example.com" -o json + +# 2. Find the default crawler scheduler +fessctl scheduler list + +# 3. Start crawling +fessctl scheduler start + +# 4. Monitor job logs +fessctl joblog list +``` + +### User Management + +```bash +# Create role +fessctl role create "editor" + +# Create group +fessctl group create "team-a" + +# Create user with role and group +fessctl user create "john" "password123" --role "editor" --group "team-a" + +# Look up user by name +fessctl user getbyname "john" +``` + +### File Crawl Setup + +```bash +# Create file config +fessctl fileconfig create --name "Docs" --path "/data/docs" + +# Add file auth if needed +fessctl fileauth create --username "user" --file-config-id --password "pass" +``` + +## Response Structure + +- `response.status` == 0 means success +- `response.id` contains the resource ID on create +- `response.setting` contains single resource data (get) +- `response.settings` contains resource list (list) +- Exception: crawlinginfo and joblog use `response.log` / `response.logs` + +## Important Patterns + +- **Update**: internally does GET then PUT (merges existing data) +- **List pagination**: `--page` (default 1) and `--size` (default 100) +- **Permissions**: use `--permission "{role}guest"` format +- **Multi-value fields**: repeat the option (e.g., `--url "http://a" --url "http://b"`) + +## Complete Examples + +```bash +# Create a web config with multiple URLs and labels +fessctl webconfig create \ + --name "Corporate Site" \ + --url "https://www.example.com" \ + --url "https://blog.example.com" \ + --excluded-url "(?i).*(css|js|jpeg|jpg|gif|png)" \ + --depth 3 \ + --max-access-count 10000 \ + --num-of-thread 3 \ + --interval-time 5000 \ + -o json + +# Update a web config +fessctl webconfig update --name "Updated Name" --depth 5 + +# Delete a web config +fessctl webconfig delete + +# Get details +fessctl webconfig get + +# List with pagination +fessctl webconfig list --page 1 --size 50 + +# Create a scheduled job +fessctl scheduler create \ + --name "Nightly Crawl" \ + --target "all" \ + --script-type "groovy" \ + --cron-expression "0 0 2 * * ?" \ + --script-data "return container.getComponent(\"crawlJob\").execute();" + +# Create a boost rule +fessctl boostdoc create \ + --url-expr "https://important.example.com/.*" \ + --boost-expr "100" \ + --sort-order 1 + +# Create a key match +fessctl keymatch create \ + --term "FAQ" \ + --query "title:FAQ" \ + --max-size 3 \ + --boost 10.0 \ + --version-no 1 +``` diff --git a/pyproject.toml b/pyproject.toml index 77c1bd8..e2c29f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ requires-python = ">=3.13" dependencies = [ "httpx==0.28.1", "pyyaml==6.0.2", - "rich==14.0.0", "typer[all]==0.16.0", ] diff --git a/src/fessctl/cli.py b/src/fessctl/cli.py index 77a8db6..f84facc 100644 --- a/src/fessctl/cli.py +++ b/src/fessctl/cli.py @@ -5,6 +5,7 @@ from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings +from fessctl.utils import format_result_markdown, output_error from fessctl.commands.accesstoken import accesstoken_app # from fessctl.commands.backup import backup_app from fessctl.commands.badword import badword_app @@ -97,25 +98,17 @@ def ping( typer.echo(yaml.dump(result)) else: if status == "green" and not timed_out: - typer.secho( - "Fess server is healthy (status: green).", fg=typer.colors.GREEN - ) + typer.echo(format_result_markdown(True, "Fess server is healthy (status: green).", "Server", "ping")) elif status == "yellow": - typer.secho( - f"Fess server status: {status} (timed_out: {timed_out})", - fg=typer.colors.YELLOW, - ) + typer.echo(format_result_markdown(True, f"Fess server status: {status} (timed_out: {timed_out})", "Server", "ping")) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Fess server status: {status} (timed_out: {timed_out}) {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Fess server status: {status} (timed_out: {timed_out}) {message}", "Server", "ping")) raise typer.Exit(code=1) except typer.Exit: raise except Exception as e: - typer.secho(str(e), fg=typer.colors.RED) + output_error(output, e, "Server", "ping") raise typer.Exit(code=1) diff --git a/src/fessctl/commands/accesstoken.py b/src/fessctl/commands/accesstoken.py index 3ee1b11..1c3c2cc 100644 --- a/src/fessctl/commands/accesstoken.py +++ b/src/fessctl/commands/accesstoken.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 accesstoken_app = typer.Typer() @@ -66,16 +64,10 @@ def create_accesstoken( else: if status == 0: accesstoken_id = result.get("response", {}).get("id", "") - typer.secho( - f"AccessToken '{accesstoken_id}' created successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"AccessToken '{accesstoken_id}' created successfully.", "AccessToken", "create", accesstoken_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "AccessToken", "create")) raise typer.Exit(code=status) @@ -113,10 +105,7 @@ def update_accesstoken( if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"AccessToken with ID '{accesstoken_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"AccessToken with ID '{accesstoken_id}' not found. {message}", "AccessToken", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -145,16 +134,10 @@ def update_accesstoken( else: if status == 0: accesstoken_id = result.get("response", {}).get("id", "") - typer.secho( - f"AccessToken '{accesstoken_id}' updated successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"AccessToken '{accesstoken_id}' updated successfully.", "AccessToken", "update", accesstoken_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update AccessToken. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to update AccessToken. {message} Status code: {status}", "AccessToken", "update")) raise typer.Exit(code=status) @@ -178,16 +161,10 @@ def delete_accesstoken( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"AccessToken '{accesstoken_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"AccessToken '{accesstoken_id}' deleted successfully.", "AccessToken", "delete", accesstoken_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete AccessToken. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete AccessToken. {message} Status code: {status}", "AccessToken", "delete")) raise typer.Exit(code=status) @@ -212,41 +189,27 @@ def get_accesstoken( else: if status == 0: accesstoken = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"AccessToken Details: {accesstoken.get('name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # Output each field according to the latest AccessToken schema - table.add_row("id", str(accesstoken.get("id", "-"))) - table.add_row("updated_by", str( - accesstoken.get("updated_by", "-"))) - table.add_row( - "updated_time", to_utc_iso8601(accesstoken.get("updated_time")) - ) - table.add_row("version_no", str( - accesstoken.get("version_no", "-"))) - table.add_row("name", str(accesstoken.get("name", "-"))) - table.add_row("token", str(accesstoken.get("token", "-"))) - table.add_row("permissions", str( - accesstoken.get("permissions", "-"))) - table.add_row("parameter_name", str( - accesstoken.get("parameter_name", "-"))) - table.add_row("expires", str(accesstoken.get("expires", "-"))) - table.add_row("created_by", str( - accesstoken.get("created_by", "-"))) - table.add_row( - "created_time", to_utc_iso8601(accesstoken.get("created_time")) - ) - - console.print(table) + typer.echo(format_detail_markdown( + f"AccessToken Details: {accesstoken.get('name', '-')}", + accesstoken, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("name", "name"), + ("token", "token"), + ("permissions", "permissions"), + ("parameter_name", "parameter_name"), + ("expires", "expires"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve AccessToken. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve AccessToken. {message} Status code: {status}", "AccessToken", "get")) raise typer.Exit(code=status) @@ -273,26 +236,12 @@ def list_accesstokens( if status == 0: accesstokens = result.get("response", {}).get("settings", []) if not accesstokens: - typer.secho("No AccessTokens found.", fg=typer.colors.YELLOW) + typer.echo("No AccessTokens found.") else: - console = Console() - table = Table(title="AccessTokens") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan", no_wrap=True) - table.add_column("EXPIRES", style="cyan", no_wrap=True) - table.add_column("PERMISSIONS", style="cyan", no_wrap=False) - for accesstoken in accesstokens: - table.add_row( - accesstoken.get("id", "-"), - accesstoken.get("name", "-"), - accesstoken.get("expires", "-"), - accesstoken.get("permissions", "-"), - ) - console.print(table) + typer.echo(format_list_markdown("AccessTokens", accesstokens, [ + ("ID", "id"), ("NAME", "name"), ("EXPIRES", "expires"), ("PERMISSIONS", "permissions"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list AccessTokens. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list AccessTokens. {message} Status code: {status}", "AccessToken", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/badword.py b/src/fessctl/commands/badword.py index 84f2c9f..a0446b1 100644 --- a/src/fessctl/commands/badword.py +++ b/src/fessctl/commands/badword.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 badword_app = typer.Typer() @@ -50,12 +48,10 @@ def create_badword( else: if status == 0: badword_id = result.get("response", {}).get("id", "") - typer.secho( - f"BadWord '{badword_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"BadWord '{badword_id}' created successfully.", "BadWord", "create", badword_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create BadWord. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create BadWord. {message} Status code: {status}", "BadWord", "create")) raise typer.Exit(code=status) @@ -80,10 +76,7 @@ def update_badword( result = client.get_badword(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"BadWord with ID '{config_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"BadWord with ID '{config_id}' not found. {message}", "BadWord", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -103,16 +96,10 @@ def update_badword( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"BadWord '{config_id}' updated successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"BadWord '{config_id}' updated successfully.", "BadWord", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update BadWord. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to update BadWord. {message} Status code: {status}", "BadWord", "update")) raise typer.Exit(code=status) @@ -135,16 +122,10 @@ def delete_badword( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"BadWord '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"BadWord '{config_id}' deleted successfully.", "BadWord", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete BadWord. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete BadWord. {message} Status code: {status}", "BadWord", "delete")) raise typer.Exit(code=status) @@ -168,31 +149,24 @@ def get_badword( else: if status == 0: badword = result.get("response", {}).get("setting", {}) - console = Console() - table = Table(title=f"BadWord Details: {badword.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # Correct output fields based on API schema - table.add_row("id", str(badword.get("id", "-"))) - table.add_row("updated_by", str(badword.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - badword.get("updated_time"))) - table.add_row("version_no", str(badword.get("version_no", "-"))) - table.add_row("crud_mode", str(badword.get("crud_mode", "-"))) - table.add_row("suggest_word", str( - badword.get("suggest_word", "-"))) - table.add_row("created_by", str(badword.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - badword.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"BadWord Details: {badword.get('id', '-')}", + badword, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("suggest_word", "suggest_word"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve BadWord. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve BadWord. {message} Status code: {status}", "BadWord", "get")) raise typer.Exit(code=status) @@ -218,31 +192,20 @@ def list_badwords( if status == 0: badwords = result.get("response", {}).get("settings", []) if not badwords: - typer.secho("No BadWords found.", fg=typer.colors.YELLOW) + typer.echo("No BadWords found.") else: - console = Console() - table = Table(title="BadWords") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("SUGGEST_WORD", style="cyan") - table.add_column("UPDATED_BY", style="cyan") - table.add_column("UPDATED_TIME", style="cyan") - table.add_column("VERSION_NO", style="cyan") - - for badword in badwords: - table.add_row( - badword.get("id", "-"), - badword.get("suggest_word", "-"), - badword.get("updated_by", "-"), - to_utc_iso8601(badword.get("updated_time")), - str(badword.get("version_no", "-")), - ) - console.print(table) + display_items = [] + for item in badwords: + d = dict(item) + d["updated_time_display"] = to_utc_iso8601(item.get("updated_time")) + display_items.append(d) + typer.echo(format_list_markdown("BadWords", display_items, [ + ("ID", "id"), ("SUGGEST_WORD", "suggest_word"), ("UPDATED_BY", "updated_by"), + ("UPDATED_TIME", "updated_time_display"), ("VERSION_NO", "version_no"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list BadWords. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list BadWords. {message} Status code: {status}", "BadWord", "list")) raise typer.Exit(code=status) # TODO upload diff --git a/src/fessctl/commands/boostdoc.py b/src/fessctl/commands/boostdoc.py index c91be8a..cb2b9f3 100644 --- a/src/fessctl/commands/boostdoc.py +++ b/src/fessctl/commands/boostdoc.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 boostdoc_app = typer.Typer() @@ -54,12 +52,10 @@ def create_boostdoc( else: if status == 0: boostdoc_id = result.get("response", {}).get("id", "") - typer.secho( - f"BoostDoc '{boostdoc_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"BoostDoc '{boostdoc_id}' created successfully.", "BoostDoc", "create", boostdoc_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create BoostDoc. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create BoostDoc. {message} Status code: {status}", "BoostDoc", "create")) raise typer.Exit(code=status) @@ -88,8 +84,7 @@ def update_boostdoc( result = client.get_boostdoc(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"BoostDoc with ID '{config_id}' not found. {message}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"BoostDoc with ID '{config_id}' not found. {message}", "BoostDoc", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -113,12 +108,10 @@ def update_boostdoc( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"BoostDoc '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"BoostDoc '{config_id}' updated successfully.", "BoostDoc", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update BoostDoc. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to update BoostDoc. {message} Status code: {status}", "BoostDoc", "update")) raise typer.Exit(code=status) @@ -141,16 +134,10 @@ def delete_boostdoc( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"BoostDoc '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"BoostDoc '{config_id}' deleted successfully.", "BoostDoc", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete BoostDoc. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete BoostDoc. {message} Status code: {status}", "BoostDoc", "delete")) raise typer.Exit(code=status) @@ -174,31 +161,26 @@ def get_boostdoc( else: if status == 0: boostdoc = result.get("response", {}).get("setting", {}) - console = Console() - table = Table(title=f"BoostDoc Details: {boostdoc.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("id", str(boostdoc.get("id", "-"))) - table.add_row("url_expr", str(boostdoc.get("url_expr", "-"))) - table.add_row("boost_expr", str(boostdoc.get("boost_expr", "-"))) - table.add_row("sort_order", str(boostdoc.get("sort_order", "-"))) - table.add_row("version_no", str(boostdoc.get("version_no", "-"))) - table.add_row("crud_mode", str(boostdoc.get("crud_mode", "-"))) - table.add_row("updated_by", str(boostdoc.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - boostdoc.get("updated_time"))) - table.add_row("created_by", str(boostdoc.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - boostdoc.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"BoostDoc Details: {boostdoc.get('id', '-')}", + boostdoc, + [ + ("id", "id"), + ("url_expr", "url_expr"), + ("boost_expr", "boost_expr"), + ("sort_order", "sort_order"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve BoostDoc. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve BoostDoc. {message} Status code: {status}", "BoostDoc", "get")) raise typer.Exit(code=status) @@ -224,31 +206,19 @@ def list_boostdocs( if status == 0: boostdocs = result.get("response", {}).get("settings", []) if not boostdocs: - typer.secho("No BoostDocs found.", fg=typer.colors.YELLOW) + typer.echo("No BoostDocs found.") else: - console = Console() - table = Table(title="BoostDocs") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("URL_EXPR", style="magenta") - table.add_column("BOOST_EXPR", style="green") - table.add_column("SORT_ORDER", style="yellow") - table.add_column("UPDATED_BY", style="blue") - table.add_column("UPDATED_TIME", style="white") - - for boostdoc in boostdocs: - table.add_row( - boostdoc.get("id", "-"), - boostdoc.get("url_expr", "-"), - boostdoc.get("boost_expr", "-"), - str(boostdoc.get("sort_order", "-")), - boostdoc.get("updated_by", "-"), - to_utc_iso8601(boostdoc.get("updated_time")), - ) - console.print(table) + display_items = [] + for item in boostdocs: + d = dict(item) + d["updated_time_display"] = to_utc_iso8601(item.get("updated_time")) + display_items.append(d) + typer.echo(format_list_markdown("BoostDocs", display_items, [ + ("ID", "id"), ("URL_EXPR", "url_expr"), ("BOOST_EXPR", "boost_expr"), + ("SORT_ORDER", "sort_order"), ("UPDATED_BY", "updated_by"), + ("UPDATED_TIME", "updated_time_display"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list BoostDocs. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list BoostDocs. {message} Status code: {status}", "BoostDoc", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/crawlinginfo.py b/src/fessctl/commands/crawlinginfo.py index 3ec760c..b6fab65 100644 --- a/src/fessctl/commands/crawlinginfo.py +++ b/src/fessctl/commands/crawlinginfo.py @@ -2,12 +2,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 crawlinginfo_app = typer.Typer() @@ -32,16 +30,10 @@ def delete_crawlinginfo( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"CrawlingInfo '{crawlinginfo_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"CrawlingInfo '{crawlinginfo_id}' deleted successfully.", "CrawlingInfo", "delete", crawlinginfo_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete CrawlingInfo. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete CrawlingInfo. {message} Status code: {status}", "CrawlingInfo", "delete")) raise typer.Exit(code=status) @@ -66,30 +58,19 @@ def get_crawlinginfo( else: if status == 0: crawlinginfo = result.get("response", {}).get("log", {}) - console = Console() - table = Table( - title=f"CrawlingInfo Details: {crawlinginfo.get('job_name', '-')}" - ) - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # output all fields according to new schema - table.add_row("id", str(crawlinginfo.get("id", "-"))) - table.add_row("session_id", str( - crawlinginfo.get("session_id", "-"))) - table.add_row( - "created_time", to_utc_iso8601( - crawlinginfo.get("created_time")) - ) - # TODO add missing fields - - console.print(table) + typer.echo(format_detail_markdown( + f"CrawlingInfo Details: {crawlinginfo.get('job_name', '-')}", + crawlinginfo, + [ + ("id", "id"), + ("session_id", "session_id"), + ("created_time", "created_time"), + ], + transforms={"created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve CrawlingInfo. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve CrawlingInfo. {message} Status code: {status}", "CrawlingInfo", "get")) raise typer.Exit(code=status) @@ -115,24 +96,19 @@ def list_crawlinginfos( if status == 0: crawlinginfos = result.get("response", {}).get("logs", []) if not crawlinginfos: - typer.secho("No CrawlingInfos found.", fg=typer.colors.YELLOW) + typer.echo("No CrawlingInfos found.") else: - console = Console() - table = Table(title="CrawlingInfos") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("SESSION ID", style="cyan", no_wrap=True) - table.add_column("CREATED TIME", style="cyan", no_wrap=True) - for crawlinginfo in crawlinginfos: - table.add_row( - crawlinginfo.get("id", "-"), - crawlinginfo.get("session_id", "-"), - to_utc_iso8601(crawlinginfo.get("created_time")), - ) - console.print(table) + display_items = [] + for item in crawlinginfos: + d = dict(item) + d["created_time_display"] = to_utc_iso8601(item.get("created_time")) + display_items.append(d) + typer.echo(format_list_markdown("CrawlingInfos", display_items, [ + ("ID", "id"), + ("SESSION ID", "session_id"), + ("CREATED TIME", "created_time_display"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list CrawlingInfos. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list CrawlingInfos. {message} Status code: {status}", "CrawlingInfo", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/dataconfig.py b/src/fessctl/commands/dataconfig.py index 0d05e88..69647e1 100644 --- a/src/fessctl/commands/dataconfig.py +++ b/src/fessctl/commands/dataconfig.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 dataconfig_app = typer.Typer() @@ -71,14 +69,10 @@ def create_dataconfig( else: if status == 0: config_id = result.get("response", {}).get("id", "") - typer.secho( - f"DataConfig '{config_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"DataConfig '{config_id}' created successfully.", "DataConfig", "create", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "DataConfig", "create")) raise typer.Exit(code=status) @@ -120,10 +114,7 @@ def update_dataconfig( if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"DataConfig with ID '{config_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"DataConfig with ID '{config_id}' not found. {message}", "DataConfig", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -161,14 +152,10 @@ def update_dataconfig( else: if status == 0: config_id = result.get("response", {}).get("id", "") - typer.secho( - f"DataConfig '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"DataConfig '{config_id}' updated successfully.", "DataConfig", "update", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "DataConfig", "update")) raise typer.Exit(code=status) @@ -191,16 +178,10 @@ def delete_dataconfig( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"DataConfig '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"DataConfig '{config_id}' deleted successfully.", "DataConfig", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete DataConfig. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete DataConfig. {message} Status code: {status}", "DataConfig", "delete")) raise typer.Exit(code=status) @@ -224,45 +205,33 @@ def get_dataconfig( else: if status == 0: dataconfig = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"DataConfig Details: {dataconfig.get('name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("id", str(dataconfig.get("id", "-"))) - table.add_row("updated_by", str(dataconfig.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - dataconfig.get("updated_time"))) - table.add_row("version_no", str(dataconfig.get("version_no", "-"))) - table.add_row("crud_mode", str(dataconfig.get("crud_mode", "-"))) - table.add_row("name", str(dataconfig.get("name", "-"))) - table.add_row("description", str( - dataconfig.get("description", "-"))) - table.add_row("handler_name", str( - dataconfig.get("handler_name", "-"))) - table.add_row("handler_parameter", str( - dataconfig.get("handler_parameter", "-"))) - table.add_row("handler_script", str( - dataconfig.get("handler_script", "-"))) - table.add_row("boost", str(dataconfig.get("boost", "-"))) - table.add_row("available", str(dataconfig.get("available", "-"))) - table.add_row("permissions", str( - dataconfig.get("permissions", "-"))) - table.add_row("virtual_hosts", str( - dataconfig.get("virtual_hosts", "-"))) - table.add_row("sort_order", str(dataconfig.get("sort_order", "-"))) - table.add_row("created_by", str(dataconfig.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - dataconfig.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"DataConfig Details: {dataconfig.get('name', '-')}", + dataconfig, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("name", "name"), + ("description", "description"), + ("handler_name", "handler_name"), + ("handler_parameter", "handler_parameter"), + ("handler_script", "handler_script"), + ("boost", "boost"), + ("available", "available"), + ("permissions", "permissions"), + ("virtual_hosts", "virtual_hosts"), + ("sort_order", "sort_order"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve DataConfig. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve DataConfig. {message} Status code: {status}", "DataConfig", "get")) raise typer.Exit(code=status) @@ -288,26 +257,12 @@ def list_dataconfigs( if status == 0: dataconfigs = result.get("response", {}).get("settings", []) if not dataconfigs: - typer.secho("No DataConfigs found.", fg=typer.colors.YELLOW) + typer.echo("No DataConfigs found.") else: - console = Console() - table = Table(title="DataConfigs") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan", no_wrap=True) - table.add_column("AVAILABLE", style="cyan", no_wrap=True) - table.add_column("SORT ORDER", style="cyan", no_wrap=True) - for dataconfig in dataconfigs: - table.add_row( - dataconfig.get("id", "-"), - dataconfig.get("name", "-"), - dataconfig.get("available", "-"), - str(dataconfig.get("sort_order", "-")), - ) - console.print(table) + typer.echo(format_list_markdown("DataConfigs", dataconfigs, [ + ("ID", "id"), ("NAME", "name"), ("AVAILABLE", "available"), ("SORT ORDER", "sort_order"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list DataConfigs. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list DataConfigs. {message} Status code: {status}", "DataConfig", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/duplicatehost.py b/src/fessctl/commands/duplicatehost.py index 4f678c6..e8a1a3f 100644 --- a/src/fessctl/commands/duplicatehost.py +++ b/src/fessctl/commands/duplicatehost.py @@ -4,12 +4,15 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import ( + format_detail_markdown, + format_list_markdown, + format_result_markdown, + to_utc_iso8601, +) duplicatehost_app = typer.Typer() @@ -58,15 +61,10 @@ def create_duplicatehost( else: if status == 0: dup_id = result.get("response", {}).get("id", "") - typer.secho( - f"DuplicateHost '{dup_id}' created successfully.", fg=typer.colors.GREEN - ) + typer.echo(format_result_markdown(True, f"DuplicateHost '{dup_id}' created successfully.", "DuplicateHost", "create", dup_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create DuplicateHost. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to create DuplicateHost. {message} Status code: {status}", "DuplicateHost", "create")) raise typer.Exit(code=status) @@ -99,10 +97,7 @@ def update_duplicatehost( result = client.get_duplicatehost(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"DuplicateHost with ID '{config_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"DuplicateHost with ID '{config_id}' not found. {message}", "DuplicateHost", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -126,16 +121,10 @@ def update_duplicatehost( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"DuplicateHost '{config_id}' updated successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"DuplicateHost '{config_id}' updated successfully.", "DuplicateHost", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update DuplicateHost. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to update DuplicateHost. {message} Status code: {status}", "DuplicateHost", "update")) raise typer.Exit(code=status) @@ -159,16 +148,10 @@ def delete_duplicatehost( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"DuplicateHost '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"DuplicateHost '{config_id}' deleted successfully.", "DuplicateHost", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete DuplicateHost. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete DuplicateHost. {message} Status code: {status}", "DuplicateHost", "delete")) raise typer.Exit(code=status) @@ -193,47 +176,26 @@ def get_duplicatehost( else: if status == 0: duplicatehost = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"DuplicateHost Details: {duplicatehost.get('id', '-')}" - ) - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # 正しい public name ベースのフィールドのみを表示 - table.add_row("id", str(duplicatehost.get("id", "-"))) - table.add_row("regular_name", str( - duplicatehost.get("regular_name", "-"))) - table.add_row( - "duplicate_host_name", - str(duplicatehost.get("duplicate_host_name", "-")), - ) - table.add_row("sort_order", str( - duplicatehost.get("sort_order", "-"))) - table.add_row("version_no", str( - duplicatehost.get("version_no", "-"))) - table.add_row("crud_mode", str( - duplicatehost.get("crud_mode", "-"))) - table.add_row("updated_by", str( - duplicatehost.get("updated_by", "-"))) - table.add_row( - "updated_time", to_utc_iso8601( - duplicatehost.get("updated_time")) - ) - table.add_row("created_by", str( - duplicatehost.get("created_by", "-"))) - table.add_row( - "created_time", to_utc_iso8601( - duplicatehost.get("created_time")) - ) - - console.print(table) + typer.echo(format_detail_markdown( + f"DuplicateHost Details: {duplicatehost.get('id', '-')}", + duplicatehost, + [ + ("id", "id"), + ("regular_name", "regular_name"), + ("duplicate_host_name", "duplicate_host_name"), + ("sort_order", "sort_order"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve DuplicateHost. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve DuplicateHost. {message} Status code: {status}", "DuplicateHost", "get")) raise typer.Exit(code=status) @@ -260,31 +222,20 @@ def list_duplicatehosts( if status == 0: duplicatehosts = result.get("response", {}).get("settings", []) if not duplicatehosts: - typer.secho("No DuplicateHosts found.", fg=typer.colors.YELLOW) + typer.echo("No DuplicateHosts found.") else: - console = Console() - table = Table(title="DuplicateHosts") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("REGULAR NAME", style="magenta") - table.add_column("DUPLICATE HOST NAME", style="green") - table.add_column("SORT ORDER", style="yellow") - table.add_column("UPDATED BY", style="blue") - table.add_column("UPDATED TIME", style="white") - + display_items = [] for dh in duplicatehosts: - table.add_row( - dh.get("id", "-"), - dh.get("regular_name", "-"), - dh.get("duplicate_host_name", "-"), - str(dh.get("sort_order", "-")), - dh.get("updated_by", "-"), - to_utc_iso8601(dh.get("updated_time")), - ) - console.print(table) + d = dict(dh) + d["updated_time_display"] = to_utc_iso8601(dh.get("updated_time")) + display_items.append(d) + typer.echo(format_list_markdown("DuplicateHosts", display_items, [ + ("ID", "id"), ("REGULAR NAME", "regular_name"), + ("DUPLICATE HOST NAME", "duplicate_host_name"), + ("SORT ORDER", "sort_order"), ("UPDATED BY", "updated_by"), + ("UPDATED TIME", "updated_time_display"), + ])) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list DuplicateHosts. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list DuplicateHosts. {message} Status code: {status}", "DuplicateHost", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/elevateword.py b/src/fessctl/commands/elevateword.py index 8216065..bcd245e 100644 --- a/src/fessctl/commands/elevateword.py +++ b/src/fessctl/commands/elevateword.py @@ -4,12 +4,15 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import ( + format_detail_markdown, + format_list_markdown, + format_result_markdown, + to_utc_iso8601, +) elevateword_app = typer.Typer() @@ -67,12 +70,10 @@ def create_elevateword( else: if status == 0: eid = result.get("response", {}).get("id", "") - typer.secho( - f"ElevateWord '{eid}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"ElevateWord '{eid}' created successfully.", "ElevateWord", "create", eid)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create ElevateWord. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create ElevateWord. {message} Status code: {status}", "ElevateWord", "create")) raise typer.Exit(code=status) @@ -107,8 +108,7 @@ def update_elevateword( result = client.get_elevateword(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"ElevateWord with ID '{config_id}' not found. {message}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"ElevateWord with ID '{config_id}' not found. {message}", "ElevateWord", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -140,12 +140,10 @@ def update_elevateword( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"ElevateWord '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"ElevateWord '{config_id}' updated successfully.", "ElevateWord", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update ElevateWord. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to update ElevateWord. {message} Status code: {status}", "ElevateWord", "update")) raise typer.Exit(code=status) @@ -168,16 +166,10 @@ def delete_elevateword( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"ElevateWord '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"ElevateWord '{config_id}' deleted successfully.", "ElevateWord", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete ElevateWord. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete ElevateWord. {message} Status code: {status}", "ElevateWord", "delete")) raise typer.Exit(code=status) @@ -201,43 +193,29 @@ def get_elevateword( else: if status == 0: elevateword = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"ElevateWord Details: {elevateword.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # 正しい public name に基づいたフィールドのみ表示 - table.add_row("id", str(elevateword.get("id", "-"))) - table.add_row("suggest_word", str( - elevateword.get("suggest_word", "-"))) - table.add_row("boost", str(elevateword.get("boost", "-"))) - table.add_row("version_no", str( - elevateword.get("version_no", "-"))) - table.add_row("label_type_ids", ", ".join( - elevateword.get("label_type_ids", []))) - table.add_row("reading", str(elevateword.get("reading", "-"))) - table.add_row("target_label", str( - elevateword.get("target_label", "-"))) - table.add_row("permissions", str( - elevateword.get("permissions", "-"))) - table.add_row("crud_mode", str(elevateword.get("crud_mode", "-"))) - table.add_row("created_by", str( - elevateword.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - elevateword.get("created_time"))) - table.add_row("updated_by", str( - elevateword.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - elevateword.get("updated_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"ElevateWord Details: {elevateword.get('id', '-')}", + elevateword, + [ + ("id", "id"), + ("suggest_word", "suggest_word"), + ("boost", "boost"), + ("version_no", "version_no"), + ("label_type_ids", "label_type_ids"), + ("reading", "reading"), + ("target_label", "target_label"), + ("permissions", "permissions"), + ("crud_mode", "crud_mode"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ], + transforms={"created_time": to_utc_iso8601, "updated_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve ElevateWord. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve ElevateWord. {message} Status code: {status}", "ElevateWord", "get")) raise typer.Exit(code=1) @@ -263,31 +241,19 @@ def list_elevatewords( if status == 0: elevatewords = result.get("response", {}).get("settings", []) if not elevatewords: - typer.secho("No ElevateWords found.", fg=typer.colors.YELLOW) + typer.echo("No ElevateWords found.") else: - console = Console() - table = Table(title="ElevateWords") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("SUGGEST WORD", style="green") - table.add_column("BOOST", style="magenta") - table.add_column("VERSION NO", style="yellow") - table.add_column("UPDATED BY", style="blue") - table.add_column("UPDATED TIME", style="white") - + display_items = [] for ew in elevatewords: - table.add_row( - ew.get("id", "-"), - ew.get("suggest_word", "-"), - str(ew.get("boost", "-")), - str(ew.get("version_no", "-")), - ew.get("updated_by", "-"), - to_utc_iso8601(ew.get("updated_time")), - ) - console.print(table) + d = dict(ew) + d["updated_time_display"] = to_utc_iso8601(ew.get("updated_time")) + display_items.append(d) + typer.echo(format_list_markdown("ElevateWords", display_items, [ + ("ID", "id"), ("SUGGEST WORD", "suggest_word"), + ("BOOST", "boost"), ("VERSION NO", "version_no"), + ("UPDATED BY", "updated_by"), ("UPDATED TIME", "updated_time_display"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list ElevateWords. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list ElevateWords. {message} Status code: {status}", "ElevateWord", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/fileauth.py b/src/fessctl/commands/fileauth.py index fc67616..a91a2af 100644 --- a/src/fessctl/commands/fileauth.py +++ b/src/fessctl/commands/fileauth.py @@ -4,12 +4,16 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import ( + format_detail_markdown, + format_list_markdown, + format_result_markdown, + output_error, + to_utc_iso8601, +) fileauth_app = typer.Typer() @@ -72,12 +76,10 @@ def create_fileauth( else: if status == 0: fileauth_id = result.get("response", {}).get("id", "") - typer.secho( - f"FileAuth '{fileauth_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"FileAuth '{fileauth_id}' created successfully.", "FileAuth", "create", fileauth_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create FileAuth. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create FileAuth. {message} Status code: {status}", "FileAuth", "create")) raise typer.Exit(code=status) @@ -113,8 +115,7 @@ def update_fileauth( result = client.get_fileauth(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"FileAuth with ID '{config_id}' not found. {message}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"FileAuth with ID '{config_id}' not found. {message}", "FileAuth", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -145,12 +146,10 @@ def update_fileauth( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"FileAuth '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"FileAuth '{config_id}' updated successfully.", "FileAuth", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update FileAuth. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to update FileAuth. {message} Status code: {status}", "FileAuth", "update")) raise typer.Exit(code=status) @@ -173,16 +172,10 @@ def delete_fileauth( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"FileAuth '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"FileAuth '{config_id}' deleted successfully.", "FileAuth", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete FileAuth. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete FileAuth. {message} Status code: {status}", "FileAuth", "delete")) raise typer.Exit(code=status) @@ -207,43 +200,35 @@ def get_fileauth( else: if status == 0: fileauth = result.get("response", {}).get("setting", {}) - console = Console() - table = Table(title=f"FileAuth Details: {fileauth.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # Output fields (FileAuth public name fields only) - table.add_row("id", str(fileauth.get("id", "-"))) - table.add_row("updated_by", str(fileauth.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - fileauth.get("updated_time"))) - table.add_row("version_no", str(fileauth.get("version_no", "-"))) - table.add_row("crud_mode", str(fileauth.get("crud_mode", "-"))) - table.add_row("hostname", str(fileauth.get("hostname", "-"))) - table.add_row("port", str(fileauth.get("port", "-"))) - table.add_row("protocol_scheme", str( - fileauth.get("protocol_scheme", "-"))) - table.add_row("username", str(fileauth.get("username", "-"))) - table.add_row("password", str(fileauth.get("password", "-"))) - table.add_row("parameters", str(fileauth.get("parameters", "-"))) - table.add_row("file_config_id", str( - fileauth.get("file_config_id", "-"))) - table.add_row("created_by", str(fileauth.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - fileauth.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"FileAuth Details: {fileauth.get('id', '-')}", + fileauth, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("hostname", "hostname"), + ("port", "port"), + ("protocol_scheme", "protocol_scheme"), + ("username", "username"), + ("password", "password"), + ("parameters", "parameters"), + ("file_config_id", "file_config_id"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve FileAuth. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve FileAuth. {message} Status code: {status}", "FileAuth", "get")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error retrieving fileauth: {e}", fg=typer.colors.RED) + output_error(output, e, "FileAuth", "get") raise typer.Exit(code=1) @@ -269,29 +254,14 @@ def list_fileauths( if status == 0: fileauths = result.get("response", {}).get("settings", []) if not fileauths: - typer.secho("No FileAuths found.", fg=typer.colors.YELLOW) + typer.echo("No FileAuths found.") else: - console = Console() - table = Table(title="FileAuths") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("USERNAME", style="cyan") - table.add_column("HOSTNAME", style="cyan") - table.add_column("PORT", style="cyan") - table.add_column("FILE_CONFIG ID", style="cyan") - - for fileauth in fileauths: - table.add_row( - fileauth.get("id", "-"), - fileauth.get("username", "-"), - fileauth.get("hostname", "-"), - str(fileauth.get("port", "-")), - fileauth.get("file_config_id", "-"), - ) - console.print(table) + typer.echo(format_list_markdown("FileAuths", fileauths, [ + ("ID", "id"), ("USERNAME", "username"), + ("HOSTNAME", "hostname"), ("PORT", "port"), + ("FILE_CONFIG ID", "file_config_id"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list FileAuths. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list FileAuths. {message} Status code: {status}", "FileAuth", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/fileconfig.py b/src/fessctl/commands/fileconfig.py index 9528567..13631f6 100644 --- a/src/fessctl/commands/fileconfig.py +++ b/src/fessctl/commands/fileconfig.py @@ -4,12 +4,16 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import ( + format_detail_markdown, + format_list_markdown, + format_result_markdown, + output_error, + to_utc_iso8601, +) fileconfig_app = typer.Typer() @@ -106,14 +110,10 @@ def create_fileconfig( else: if status == 0: config_id = result.get("response", {}).get("id", "") - typer.secho( - f"FileConfig '{config_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"FileConfig '{config_id}' created successfully.", "FileConfig", "create", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "FileConfig", "create")) raise typer.Exit(code=status) @@ -184,10 +184,7 @@ def update_fileconfig( result = client.get_fileconfig(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"FileConfig with ID '{config_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"FileConfig with ID '{config_id}' not found. {message}", "FileConfig", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -241,14 +238,10 @@ def update_fileconfig( else: if status == 0: config_id = result.get("response", {}).get("id", "") - typer.secho( - f"FileConfig '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"FileConfig '{config_id}' updated successfully.", "FileConfig", "update", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "FileConfig", "update")) raise typer.Exit(code=status) @@ -271,16 +264,10 @@ def delete_fileconfig( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"FileConfig '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"FileConfig '{config_id}' deleted successfully.", "FileConfig", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete FileConfig. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete FileConfig. {message} Status code: {status}", "FileConfig", "delete")) raise typer.Exit(code=status) @@ -306,75 +293,46 @@ def get_fileconfig( else: if status == 0: fileconfig = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"FileConfig Details: {fileconfig.get('name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # Output all public fields - table.add_row("id", str(fileconfig.get("id", "-"))) - table.add_row("updated_by", str(fileconfig.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - fileconfig.get("updated_time"))) - table.add_row("version_no", str(fileconfig.get("version_no", "-"))) - table.add_row( - "label_type_ids", ", ".join( - fileconfig.get("label_type_ids", [])) - ) - table.add_row("crud_mode", str(fileconfig.get("crud_mode", "-"))) - table.add_row("name", str(fileconfig.get("name", "-"))) - table.add_row("description", str( - fileconfig.get("description", "-"))) - table.add_row("paths", str(fileconfig.get("paths", "-"))) - table.add_row("included_paths", str( - fileconfig.get("included_paths", "-"))) - table.add_row("excluded_paths", str( - fileconfig.get("excluded_paths", "-"))) - table.add_row( - "included_doc_paths", str( - fileconfig.get("included_doc_paths", "-")) - ) - table.add_row( - "excluded_doc_paths", str( - fileconfig.get("excluded_doc_paths", "-")) - ) - table.add_row( - "config_parameter", str( - fileconfig.get("config_parameter", "-")) - ) - table.add_row("depth", str(fileconfig.get("depth", "-"))) - table.add_row( - "max_access_count", str( - fileconfig.get("max_access_count", "-")) - ) - table.add_row("num_of_thread", str( - fileconfig.get("num_of_thread", "-"))) - table.add_row("interval_time", str( - fileconfig.get("interval_time", "-"))) - table.add_row("boost", str(fileconfig.get("boost", "-"))) - table.add_row("available", str(fileconfig.get("available", "-"))) - table.add_row("permissions", str( - fileconfig.get("permissions", "-"))) - table.add_row("virtual_hosts", str( - fileconfig.get("virtual_hosts", "-"))) - table.add_row("sort_order", str(fileconfig.get("sort_order", "-"))) - table.add_row("created_by", str(fileconfig.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - fileconfig.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"FileConfig Details: {fileconfig.get('name', '-')}", + fileconfig, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("label_type_ids", "label_type_ids"), + ("crud_mode", "crud_mode"), + ("name", "name"), + ("description", "description"), + ("paths", "paths"), + ("included_paths", "included_paths"), + ("excluded_paths", "excluded_paths"), + ("included_doc_paths", "included_doc_paths"), + ("excluded_doc_paths", "excluded_doc_paths"), + ("config_parameter", "config_parameter"), + ("depth", "depth"), + ("max_access_count", "max_access_count"), + ("num_of_thread", "num_of_thread"), + ("interval_time", "interval_time"), + ("boost", "boost"), + ("available", "available"), + ("permissions", "permissions"), + ("virtual_hosts", "virtual_hosts"), + ("sort_order", "sort_order"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve FileConfig. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve FileConfig. {message} Status code: {status}", "FileConfig", "get")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error retrieving fileconfig: {e}", fg=typer.colors.RED) + output_error(output, e, "FileConfig", "get") raise typer.Exit(code=1) @@ -400,20 +358,12 @@ def list_fileconfigs( if status == 0: configs = result.get("response", {}).get("settings", []) if not configs: - typer.secho("No FileConfigs found.", fg=typer.colors.YELLOW) + typer.echo("No FileConfigs found.") else: - console = Console() - table = Table(title="FileConfigs") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("Name", style="cyan", no_wrap=True) - for config in configs: - table.add_row(config.get("id", "-"), - config.get("name", "-")) - console.print(table) + typer.echo(format_list_markdown("FileConfigs", configs, [ + ("ID", "id"), ("Name", "name"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list FileConfigs. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list FileConfigs. {message} Status code: {status}", "FileConfig", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/group.py b/src/fessctl/commands/group.py index a1674c4..a3cb775 100644 --- a/src/fessctl/commands/group.py +++ b/src/fessctl/commands/group.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import encode_to_urlsafe_base64 +from fessctl.utils import encode_to_urlsafe_base64, format_detail_markdown, format_list_markdown, format_result_markdown, output_error # Create a Typer sub-application for group commands @@ -54,21 +52,15 @@ def create_group( else: if status == 0: group_id = result.get("response", {}).get("id", None) - typer.secho( - f"Group '{name}' created successfully with ID: {group_id}.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"Group '{name}' created successfully with ID: {group_id}.", "Group", "create", group_id or "")) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create group. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to create group. {message} Status code: {status}", "Group", "create")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error creating group: {e}", fg=typer.colors.RED) + output_error(output, e, "Group", "create") raise typer.Exit(code=1) @@ -93,51 +85,48 @@ def update_group( """ client = FessAPIClient(Settings()) - result = client.get_group(group_id=group_id) - if result.get("response", {}).get("status", 1) != 0: - message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Group with ID '{group_id}' not found. {message}", - fg=typer.colors.RED, - ) - raise typer.Exit(code=1) + try: + result = client.get_group(group_id=group_id) + if result.get("response", {}).get("status", 1) != 0: + message: str = result.get("response", {}).get("message", "") + typer.echo(format_result_markdown(False, f"Group with ID '{group_id}' not found. {message}", "Group", "update")) + raise typer.Exit(code=1) - config = result.get("response", {}).get("setting", {}) - config["crud_mode"] = 2 - config["updated_by"] = updated_by - config["updated_time"] = updated_time + config = result.get("response", {}).get("setting", {}) + config["crud_mode"] = 2 + config["updated_by"] = updated_by + config["updated_time"] = updated_time - if attributes is not None: - attr_dict = {} - for attr in attributes: - if "=" not in attr: - typer.secho( - f"Invalid attribute format: {attr}", fg=typer.colors.RED) - raise typer.Exit(code=1) - key, value = attr.split("=", 1) - attr_dict[key.strip()] = value.strip() - config["attributes"] = attr_dict + if attributes is not None: + attr_dict = {} + for attr in attributes: + if "=" not in attr: + typer.secho( + f"Invalid attribute format: {attr}", fg=typer.colors.RED) + raise typer.Exit(code=1) + key, value = attr.split("=", 1) + attr_dict[key.strip()] = value.strip() + config["attributes"] = attr_dict - result = client.update_group(config) - status = result.get("response", {}).get("status", 1) + result = client.update_group(config) + status = result.get("response", {}).get("status", 1) - if output == "json": - typer.echo(json.dumps(result, indent=2)) - elif output == "yaml": - typer.echo(yaml.dump(result)) - else: - if status == 0: - typer.secho( - f"Group '{group_id}' updated successfully.", - fg=typer.colors.GREEN, - ) + if output == "json": + typer.echo(json.dumps(result, indent=2)) + elif output == "yaml": + typer.echo(yaml.dump(result)) else: - message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update group. {message} Status code: {status}", - fg=typer.colors.RED, - ) - raise typer.Exit(code=status) + if status == 0: + typer.echo(format_result_markdown(True, f"Group '{group_id}' updated successfully.", "Group", "update", group_id)) + else: + message = result.get("response", {}).get("message", "") + typer.echo(format_result_markdown(False, f"Failed to update group. {message} Status code: {status}", "Group", "update")) + raise typer.Exit(code=status) + except typer.Exit: + raise + except Exception as e: + output_error(output, e, "Group", "update") + raise typer.Exit(code=1) @group_app.command("delete") @@ -162,21 +151,15 @@ def delete_group( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"Group with ID '{group_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"Group with ID '{group_id}' deleted successfully.", "Group", "delete", group_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete group. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete group. {message} Status code: {status}", "Group", "delete")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error deleting group: {e}", fg=typer.colors.RED) + output_error(output, e, "Group", "delete") raise typer.Exit(code=1) @@ -213,34 +196,32 @@ def get_group( else: if status == 0: group = result.get("response", {}).get("setting", {}) - console = Console() - table = Table(title=f"Group Details: {group.get('name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("ID", group.get("id", "-")) - table.add_row("Name", group.get("name", "-")) attributes = group.get("attributes", {}) attr_str = ( "\n".join(f"{k}={v}" for k, v in attributes.items()) if attributes else "-" ) - table.add_row("Attributes", attr_str) - table.add_row("Version", str(group.get("version_no", "-"))) - - console.print(table) + data = dict(group) + data["attributes_display"] = attr_str + typer.echo(format_detail_markdown( + f"Group Details: {group.get('name', '-')}", + data, + [ + ("ID", "id"), + ("Name", "name"), + ("Attributes", "attributes_display"), + ("Version", "version_no"), + ], + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve group. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve group. {message} Status code: {status}", "Group", "get")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error retrieving group: {e}", fg=typer.colors.RED) + output_error(output, e, "Group", "get") raise typer.Exit(code=1) @@ -269,36 +250,28 @@ def list_groups( if status == 0: groups = result.get("response", {}).get("settings", []) if not groups: - typer.secho("No groups found.", fg=typer.colors.YELLOW) + typer.echo("No groups found.") else: - console = Console() - table = Table(title="Groups") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan", no_wrap=True) - table.add_column("ATTRIBUTES", style="cyan", no_wrap=False) - table.add_column("VERSION", style="cyan", no_wrap=True) - for group in groups: - table.add_row( - *[ - group.get("id", "-"), - group.get("name", "-"), - "\n".join( - f"{k}={v}" - for k, v in group.get("attributes", {}).items() - ), - str(group.get("version_no", "-")), - ] + display_items = [] + for item in groups: + d = dict(item) + d["attributes_display"] = "\n".join( + f"{k}={v}" + for k, v in item.get("attributes", {}).items() ) - console.print(table) + display_items.append(d) + typer.echo(format_list_markdown("Groups", display_items, [ + ("ID", "id"), + ("NAME", "name"), + ("ATTRIBUTES", "attributes_display"), + ("VERSION", "version_no"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list groups. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list groups. {message} Status code: {status}", "Group", "list")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error listing groups: {e}", fg=typer.colors.RED) + output_error(output, e, "Group", "list") raise typer.Exit(code=1) diff --git a/src/fessctl/commands/joblog.py b/src/fessctl/commands/joblog.py index d5bccbb..8cacf73 100644 --- a/src/fessctl/commands/joblog.py +++ b/src/fessctl/commands/joblog.py @@ -2,12 +2,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 joblog_app = typer.Typer() @@ -31,16 +29,10 @@ def delete_joblog( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"JobLog '{joblog_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"JobLog '{joblog_id}' deleted successfully.", "JobLog", "delete", joblog_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete JobLog. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete JobLog. {message} Status code: {status}", "JobLog", "delete")) raise typer.Exit(code=status) @@ -64,32 +56,25 @@ def get_joblog( else: if status == 0: joblog = result.get("response", {}).get("log", {}) - console = Console() - table = Table( - title=f"JobLog Details: {joblog.get('job_name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # output all fields according to new schema - table.add_row("id", str(joblog.get("id", "-"))) - table.add_row("job_name", str(joblog.get("job_name", "-"))) - table.add_row("job_status", str(joblog.get("job_status", "-"))) - table.add_row("target", str(joblog.get("target", "-"))) - table.add_row("script_type", str(joblog.get("script_type", "-"))) - table.add_row("script_data", str(joblog.get("script_data", "-"))) - table.add_row("script_result", str( - joblog.get("script_result", "-"))) - table.add_row("start_time", to_utc_iso8601( - joblog.get("start_time"))) - table.add_row("end_time", to_utc_iso8601(joblog.get("end_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"JobLog Details: {joblog.get('job_name', '-')}", + joblog, + [ + ("id", "id"), + ("job_name", "job_name"), + ("job_status", "job_status"), + ("target", "target"), + ("script_type", "script_type"), + ("script_data", "script_data"), + ("script_result", "script_result"), + ("start_time", "start_time"), + ("end_time", "end_time"), + ], + transforms={"start_time": to_utc_iso8601, "end_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve JobLog. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve JobLog. {message} Status code: {status}", "JobLog", "get")) raise typer.Exit(code=status) @@ -114,28 +99,22 @@ def list_joblogs( if status == 0: joblogs = result.get("response", {}).get("logs", []) if not joblogs: - typer.secho("No JobLogs found.", fg=typer.colors.YELLOW) + typer.echo("No JobLogs found.") else: - console = Console() - table = Table(title="JobLogs") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan", no_wrap=True) - table.add_column("STATUS", style="cyan", no_wrap=True) - table.add_column("START TIME", style="cyan", no_wrap=True) - table.add_column("END TIME", style="cyan", no_wrap=True) - for joblog in joblogs: - table.add_row( - joblog.get("id", "-"), - joblog.get("job_name", "-"), - joblog.get("job_status", "-"), - to_utc_iso8601(joblog.get("start_time")), - to_utc_iso8601(joblog.get("end_time")), - ) - console.print(table) + display_items = [] + for item in joblogs: + d = dict(item) + d["start_time_display"] = to_utc_iso8601(item.get("start_time")) + d["end_time_display"] = to_utc_iso8601(item.get("end_time")) + display_items.append(d) + typer.echo(format_list_markdown("JobLogs", display_items, [ + ("ID", "id"), + ("NAME", "job_name"), + ("STATUS", "job_status"), + ("START TIME", "start_time_display"), + ("END TIME", "end_time_display"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list JobLogs. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list JobLogs. {message} Status code: {status}", "JobLog", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/keymatch.py b/src/fessctl/commands/keymatch.py index 945f74f..fb232b5 100644 --- a/src/fessctl/commands/keymatch.py +++ b/src/fessctl/commands/keymatch.py @@ -4,12 +4,16 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import ( + format_detail_markdown, + format_list_markdown, + format_result_markdown, + output_error, + to_utc_iso8601, +) keymatch_app = typer.Typer() @@ -62,12 +66,10 @@ def create_keymatch( else: if status == 0: keymatch_id = result.get("response", {}).get("id", "-") - typer.secho( - f"KeyMatch '{keymatch_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"KeyMatch '{keymatch_id}' created successfully.", "KeyMatch", "create", keymatch_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create KeyMatch. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create KeyMatch. {message} Status code: {status}", "KeyMatch", "create")) raise typer.Exit(code=status) @@ -101,8 +103,7 @@ def update_keymatch( result = client.get_keymatch(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"KeyMatch with ID '{config_id}' not found. {message}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"KeyMatch with ID '{config_id}' not found. {message}", "KeyMatch", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -132,12 +133,10 @@ def update_keymatch( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"KeyMatch '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"KeyMatch '{config_id}' updated successfully.", "KeyMatch", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update KeyMatch. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to update KeyMatch. {message} Status code: {status}", "KeyMatch", "update")) raise typer.Exit(code=status) @@ -160,16 +159,10 @@ def delete_keymatch( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"KeyMatch '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"KeyMatch '{config_id}' deleted successfully.", "KeyMatch", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete KeyMatch. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete KeyMatch. {message} Status code: {status}", "KeyMatch", "delete")) raise typer.Exit(code=status) @@ -194,37 +187,33 @@ def get_keymatch( else: if status == 0: keymatch = result.get("response", {}).get("setting", {}) - console = Console() - table = Table(title=f"KeyMatch Details: {keymatch.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("id", str(keymatch.get("id", "-"))) - table.add_row("term", str(keymatch.get("term", "-"))) - table.add_row("query", str(keymatch.get("query", "-"))) - table.add_row("max_size", str(keymatch.get("max_size", "-"))) - table.add_row("boost", str(keymatch.get("boost", "-"))) - table.add_row("virtual_host", str( - keymatch.get("virtual_host", "-"))) - table.add_row("version_no", str(keymatch.get("version_no", "-"))) - table.add_row("crud_mode", str(keymatch.get("crud_mode", "-"))) - table.add_row("created_by", str(keymatch.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - keymatch.get("created_time"))) - table.add_row("updated_by", str(keymatch.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - keymatch.get("updated_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"KeyMatch Details: {keymatch.get('id', '-')}", + keymatch, + [ + ("id", "id"), + ("term", "term"), + ("query", "query"), + ("max_size", "max_size"), + ("boost", "boost"), + ("virtual_host", "virtual_host"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ], + transforms={"created_time": to_utc_iso8601, "updated_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve KeyMatch. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to retrieve KeyMatch. {message} Status code: {status}", "KeyMatch", "get")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error retrieving keymatch: {e}", fg=typer.colors.RED) + output_error(output, e, "KeyMatch", "get") raise typer.Exit(code=1) @@ -250,33 +239,19 @@ def list_keymatchs( if status == 0: keymatchs = result.get("response", {}).get("settings", []) if not keymatchs: - typer.secho("No KeyMatchs found.", fg=typer.colors.YELLOW) + typer.echo("No KeyMatchs found.") else: - console = Console() - table = Table(title="KeyMatchs") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("TERM", style="green") - table.add_column("QUERY", style="magenta") - table.add_column("BOOST", style="yellow") - table.add_column("MAX SIZE", style="blue") - table.add_column("UPDATED BY", style="white") - table.add_column("UPDATED TIME", style="white") - + display_items = [] for km in keymatchs: - table.add_row( - km.get("id", "-"), - km.get("term", "-"), - km.get("query", "-"), - str(km.get("boost", "-")), - str(km.get("max_size", "-")), - km.get("updated_by", "-"), - to_utc_iso8601(km.get("updated_time")), - ) - console.print(table) + d = dict(km) + d["updated_time_display"] = to_utc_iso8601(km.get("updated_time")) + display_items.append(d) + typer.echo(format_list_markdown("KeyMatchs", display_items, [ + ("ID", "id"), ("TERM", "term"), ("QUERY", "query"), + ("BOOST", "boost"), ("MAX SIZE", "max_size"), + ("UPDATED BY", "updated_by"), ("UPDATED TIME", "updated_time_display"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list KeyMatchs. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list KeyMatchs. {message} Status code: {status}", "KeyMatch", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/labeltype.py b/src/fessctl/commands/labeltype.py index 4d1c094..fc5b2ab 100644 --- a/src/fessctl/commands/labeltype.py +++ b/src/fessctl/commands/labeltype.py @@ -4,12 +4,15 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import ( + format_detail_markdown, + format_list_markdown, + format_result_markdown, + to_utc_iso8601, +) labeltype_app = typer.Typer() @@ -71,16 +74,10 @@ def create_labeltype( else: if status == 0: labeltype_id = result.get("response", {}).get("id", "") - typer.secho( - f"LabelType '{labeltype_id}' created successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"LabelType '{labeltype_id}' created successfully.", "LabelType", "create", labeltype_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create LabelType. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to create LabelType. {message} Status code: {status}", "LabelType", "create")) raise typer.Exit(code=status) @@ -117,8 +114,7 @@ def update_labeltype( result = client.get_labeltype(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"LabelType with ID '{config_id}' not found. {message}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"LabelType with ID '{config_id}' not found. {message}", "LabelType", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -152,12 +148,10 @@ def update_labeltype( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"LabelType '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"LabelType '{config_id}' updated successfully.", "LabelType", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update LabelType. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to update LabelType. {message} Status code: {status}", "LabelType", "update")) raise typer.Exit(code=status) @@ -180,16 +174,10 @@ def delete_labeltype( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"LabelType '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"LabelType '{config_id}' deleted successfully.", "LabelType", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete LabelType. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete LabelType. {message} Status code: {status}", "LabelType", "delete")) raise typer.Exit(code=status) @@ -213,40 +201,30 @@ def get_labeltype( else: if status == 0: labeltype = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"LabelType Details: {labeltype.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("id", str(labeltype.get("id", "-"))) - table.add_row("name", str(labeltype.get("name", "-"))) - table.add_row("value", str(labeltype.get("value", "-"))) - table.add_row("version_no", str(labeltype.get("version_no", "-"))) - table.add_row("sort_order", str(labeltype.get("sort_order", "-"))) - table.add_row("included_paths", labeltype.get( - "included_paths", "").replace("\n", ", ")) - table.add_row("excluded_paths", labeltype.get( - "excluded_paths", "").replace("\n", ", ")) - table.add_row("permissions", labeltype.get( - "permissions", "").replace("\n", ", ")) - table.add_row("virtual_host", str( - labeltype.get("virtual_host", "-"))) - table.add_row("crud_mode", str(labeltype.get("crud_mode", "-"))) - table.add_row("created_by", str(labeltype.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - labeltype.get("created_time"))) - table.add_row("updated_by", str(labeltype.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - labeltype.get("updated_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"LabelType Details: {labeltype.get('id', '-')}", + labeltype, + [ + ("id", "id"), + ("name", "name"), + ("value", "value"), + ("version_no", "version_no"), + ("sort_order", "sort_order"), + ("included_paths", "included_paths"), + ("excluded_paths", "excluded_paths"), + ("permissions", "permissions"), + ("virtual_host", "virtual_host"), + ("crud_mode", "crud_mode"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ], + transforms={"created_time": to_utc_iso8601, "updated_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve LabelType. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve LabelType. {message} Status code: {status}", "LabelType", "get")) raise typer.Exit(code=status) @@ -272,25 +250,12 @@ def list_labeltypes( if status == 0: labeltypes = result.get("response", {}).get("settings", []) if not labeltypes: - typer.secho("No LabelTypes found.", fg=typer.colors.YELLOW) + typer.echo("No LabelTypes found.") else: - console = Console() - table = Table(title="LabelTypes") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="green") - table.add_column("VALUE", style="magenta") - - for lt in labeltypes: - table.add_row( - lt.get("id", "-"), - lt.get("name", "-"), - lt.get("value", "-"), - ) - console.print(table) + typer.echo(format_list_markdown("LabelTypes", labeltypes, [ + ("ID", "id"), ("NAME", "name"), ("VALUE", "value"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list LabelTypes. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list LabelTypes. {message} Status code: {status}", "LabelType", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/pathmap.py b/src/fessctl/commands/pathmap.py index 199c346..bc28df3 100644 --- a/src/fessctl/commands/pathmap.py +++ b/src/fessctl/commands/pathmap.py @@ -4,12 +4,15 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import ( + format_detail_markdown, + format_list_markdown, + format_result_markdown, + to_utc_iso8601, +) pathmap_app = typer.Typer() @@ -61,12 +64,10 @@ def create_path( else: if status == 0: path_id = result.get("response", {}).get("id", "") - typer.secho( - f"Path Mapping '{path_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"Path Mapping '{path_id}' created successfully.", "PathMap", "create", path_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create Path Mapping. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create Path Mapping. {message} Status code: {status}", "PathMap", "create")) raise typer.Exit(code=status) @@ -98,8 +99,7 @@ def update_pathmap( result = client.get_pathmap(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"PathMap with ID '{config_id}' not found. {message}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"PathMap with ID '{config_id}' not found. {message}", "PathMap", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -127,12 +127,10 @@ def update_pathmap( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"PathMap '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"PathMap '{config_id}' updated successfully.", "PathMap", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update PathMap. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to update PathMap. {message} Status code: {status}", "PathMap", "update")) raise typer.Exit(code=status) @@ -155,16 +153,10 @@ def delete_pathmap( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"PathMap '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"PathMap '{config_id}' deleted successfully.", "PathMap", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete PathMap. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete PathMap. {message} Status code: {status}", "PathMap", "delete")) raise typer.Exit(code=status) @@ -188,35 +180,28 @@ def get_pathmap( else: if status == 0: pathmap = result.get("response", {}).get("setting", {}) - console = Console() - table = Table(title=f"PathMap Details: {pathmap.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # 正しい PathMap パラメータ出力 - table.add_row("id", str(pathmap.get("id", "-"))) - table.add_row("regex", str(pathmap.get("regex", "-"))) - table.add_row("replacement", str(pathmap.get("replacement", "-"))) - table.add_row("process_type", str( - pathmap.get("process_type", "-"))) - table.add_row("sort_order", str(pathmap.get("sort_order", "-"))) - table.add_row("user_agent", str(pathmap.get("user_agent", "-"))) - table.add_row("version_no", str(pathmap.get("version_no", "-"))) - table.add_row("crud_mode", str(pathmap.get("crud_mode", "-"))) - table.add_row("updated_by", str(pathmap.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - pathmap.get("updated_time"))) - table.add_row("created_by", str(pathmap.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - pathmap.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"PathMap Details: {pathmap.get('id', '-')}", + pathmap, + [ + ("id", "id"), + ("regex", "regex"), + ("replacement", "replacement"), + ("process_type", "process_type"), + ("sort_order", "sort_order"), + ("user_agent", "user_agent"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve PathMap. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve PathMap. {message} Status code: {status}", "PathMap", "get")) raise typer.Exit(code=status) @@ -242,31 +227,14 @@ def list_pathmaps( if status == 0: pathmaps = result.get("response", {}).get("settings", []) if not pathmaps: - typer.secho("No PathMaps found.", fg=typer.colors.YELLOW) + typer.echo("No PathMaps found.") else: - console = Console() - table = Table(title="PathMaps") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("REGEX", style="magenta") - table.add_column("REPLACEMENT", style="green") - table.add_column("PROCESS TYPE", style="blue") - table.add_column("SORT ORDER", justify="right") - table.add_column("USER AGENT", style="yellow") - - for pathmap in pathmaps: - table.add_row( - pathmap.get("id", "-"), - pathmap.get("regex", "-"), - pathmap.get("replacement", "-"), - pathmap.get("process_type", "-"), - str(pathmap.get("sort_order", "-")), - pathmap.get("user_agent", "-"), - ) - console.print(table) + typer.echo(format_list_markdown("PathMaps", pathmaps, [ + ("ID", "id"), ("REGEX", "regex"), + ("REPLACEMENT", "replacement"), ("PROCESS TYPE", "process_type"), + ("SORT ORDER", "sort_order"), ("USER AGENT", "user_agent"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list PathMaps. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list PathMaps. {message} Status code: {status}", "PathMap", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/relatedcontent.py b/src/fessctl/commands/relatedcontent.py index 6dc0c30..b251603 100644 --- a/src/fessctl/commands/relatedcontent.py +++ b/src/fessctl/commands/relatedcontent.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 relatedcontent_app = typer.Typer() @@ -54,12 +52,10 @@ def create_relatedcontent( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho("RelatedContent created successfully.", - fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, "RelatedContent created successfully.", "RelatedContent", "create")) else: message: str = result.get("response", {}).get("message", "") - typer.secho(f"Failed to create RelatedContent. {message} Status code: {status}", - fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create RelatedContent. {message} Status code: {status}", "RelatedContent", "create")) raise typer.Exit(code=status) @@ -88,8 +84,7 @@ def update_relatedcontent( result = client.get_relatedcontent(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"RelatedContent with ID '{config_id}' not found. {message}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"RelatedContent with ID '{config_id}' not found. {message}", "RelatedContent", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -116,12 +111,10 @@ def update_relatedcontent( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"RelatedContent '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"RelatedContent '{config_id}' updated successfully.", "RelatedContent", "update", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update RelatedContent. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to update RelatedContent. {message} Status code: {status}", "RelatedContent", "update")) raise typer.Exit(code=status) @@ -144,16 +137,10 @@ def delete_relatedcontent( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"RelatedContent '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"RelatedContent '{config_id}' deleted successfully.", "RelatedContent", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete RelatedContent. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete RelatedContent. {message} Status code: {status}", "RelatedContent", "delete")) raise typer.Exit(code=status) @@ -177,40 +164,27 @@ def get_relatedcontent( else: if status == 0: relatedcontent = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"RelatedContent Details: {relatedcontent.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # Output only valid fields for RelatedContent - table.add_row("id", str(relatedcontent.get("id", "-"))) - table.add_row("term", str(relatedcontent.get("term", "-"))) - table.add_row("content", str(relatedcontent.get("content", "-"))) - table.add_row("sort_order", str( - relatedcontent.get("sort_order", "-"))) - table.add_row("virtual_host", str( - relatedcontent.get("virtual_host", "-"))) - table.add_row("version_no", str( - relatedcontent.get("version_no", "-"))) - table.add_row("crud_mode", str( - relatedcontent.get("crud_mode", "-"))) - table.add_row("updated_by", str( - relatedcontent.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - relatedcontent.get("updated_time"))) - table.add_row("created_by", str( - relatedcontent.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - relatedcontent.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"RelatedContent Details: {relatedcontent.get('id', '-')}", + relatedcontent, + [ + ("id", "id"), + ("term", "term"), + ("content", "content"), + ("sort_order", "sort_order"), + ("virtual_host", "virtual_host"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve RelatedContent. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve RelatedContent. {message} Status code: {status}", "RelatedContent", "get")) raise typer.Exit(code=status) @@ -236,28 +210,16 @@ def list_relatedcontents( if status == 0: relatedcontents = result.get("response", {}).get("settings", []) if not relatedcontents: - typer.secho("No RelatedContents found.", - fg=typer.colors.YELLOW) + typer.echo("No RelatedContents found.") else: - console = Console() - table = Table(title="RelatedContents") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("TERM", style="magenta") - table.add_column("CONTENT", style="magenta") - table.add_column("VIRTUAL HOST", style="green") - table.add_column("SORT ORDER", style="yellow") - - for rc in relatedcontents: - table.add_row( - rc.get("id", "-"), - rc.get("term", "-"), - rc.get("content", "-"), - rc.get("virtual_host", "-"), - str(rc.get("sort_order", "-")), - ) - console.print(table) + typer.echo(format_list_markdown("RelatedContents", relatedcontents, [ + ("ID", "id"), + ("TERM", "term"), + ("CONTENT", "content"), + ("VIRTUAL HOST", "virtual_host"), + ("SORT ORDER", "sort_order"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list RelatedContents. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to list RelatedContents. {message} Status code: {status}", "RelatedContent", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/relatedquery.py b/src/fessctl/commands/relatedquery.py index 4988a6c..b79d008 100644 --- a/src/fessctl/commands/relatedquery.py +++ b/src/fessctl/commands/relatedquery.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 relatedquery_app = typer.Typer() @@ -57,12 +55,10 @@ def create_relatedquery( else: if status == 0: relatedquery_id = result.get("response", {}).get("id", "") - typer.secho( - f"RelatedQuery '{relatedquery_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"RelatedQuery '{relatedquery_id}' created successfully.", "RelatedQuery", "create", relatedquery_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create RelatedQuery. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create RelatedQuery. {message} Status code: {status}", "RelatedQuery", "create")) raise typer.Exit(code=status) @@ -90,9 +86,7 @@ def update_relatedquery( if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"RelatedQuery with ID '{config_id}' not found. {message}", - fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"RelatedQuery with ID '{config_id}' not found. {message}", "RelatedQuery", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -117,14 +111,10 @@ def update_relatedquery( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"RelatedQuery '{config_id}' updated successfully.", - fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"RelatedQuery '{config_id}' updated successfully.", "RelatedQuery", "update", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update RelatedQuery. {message} Status code: {status}", - fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to update RelatedQuery. {message} Status code: {status}", "RelatedQuery", "update")) raise typer.Exit(code=status) @@ -147,16 +137,10 @@ def delete_relatedquery( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"RelatedQuery '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"RelatedQuery '{config_id}' deleted successfully.", "RelatedQuery", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete RelatedQuery. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete RelatedQuery. {message} Status code: {status}", "RelatedQuery", "delete")) raise typer.Exit(code=status) @@ -180,36 +164,26 @@ def get_relatedquery( else: if status == 0: relatedquery = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"RelatedQuery Details: {relatedquery.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("id", str(relatedquery.get("id", "-"))) - table.add_row("term", str(relatedquery.get("term", "-"))) - table.add_row("queries", str(relatedquery.get("queries", "-"))) - table.add_row("virtual_host", str( - relatedquery.get("virtual_host", "-"))) - table.add_row("version_no", str( - relatedquery.get("version_no", "-"))) - table.add_row("crud_mode", str(relatedquery.get("crud_mode", "-"))) - table.add_row("updated_by", str( - relatedquery.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - relatedquery.get("updated_time"))) - table.add_row("created_by", str( - relatedquery.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - relatedquery.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"RelatedQuery Details: {relatedquery.get('id', '-')}", + relatedquery, + [ + ("id", "id"), + ("term", "term"), + ("queries", "queries"), + ("virtual_host", "virtual_host"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve RelatedQuery. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve RelatedQuery. {message} Status code: {status}", "RelatedQuery", "get")) raise typer.Exit(code=status) @@ -235,29 +209,16 @@ def list_relatedqueries( if status == 0: relatedqueries = result.get("response", {}).get("settings", []) if len(relatedqueries) == 0: - typer.secho("No RelatedQueries found.", - fg=typer.colors.YELLOW) + typer.echo("No RelatedQueries found.") else: - console = Console() - table = Table(title="RelatedQueries") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("TERM", style="magenta") - table.add_column("QUERIES", style="green") - table.add_column("VIRTUAL HOST", style="blue") - table.add_column("VERSION", style="yellow") - - for rq in relatedqueries: - table.add_row( - rq.get("id", "-"), - rq.get("term", "-"), - rq.get("queries", "-"), - rq.get("virtual_host", "-"), - str(rq.get("version_no", "-")), - ) - console.print(table) + typer.echo(format_list_markdown("RelatedQueries", relatedqueries, [ + ("ID", "id"), + ("TERM", "term"), + ("QUERIES", "queries"), + ("VIRTUAL HOST", "virtual_host"), + ("VERSION", "version_no"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list RelatedQueries. {message} Status code: {status}", - fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to list RelatedQueries. {message} Status code: {status}", "RelatedQuery", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/reqheader.py b/src/fessctl/commands/reqheader.py index 5e50c20..594ce18 100644 --- a/src/fessctl/commands/reqheader.py +++ b/src/fessctl/commands/reqheader.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 reqheader_app = typer.Typer() @@ -55,14 +53,10 @@ def create_reqheader( else: if status == 0: reqheader_id = result.get("response", {}).get("id", "") - typer.secho( - f"ReqHeader '{reqheader_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"ReqHeader '{reqheader_id}' created successfully.", "ReqHeader", "create", reqheader_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create ReqHeader. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to create ReqHeader. {message} Status code: {status}", "ReqHeader", "create")) raise typer.Exit(code=status) @@ -91,8 +85,7 @@ def update_reqheader( result = client.get_reqheader(reqheader_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"ReqHeader '{reqheader_id}' not found. {message}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"ReqHeader '{reqheader_id}' not found. {message}", "ReqHeader", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -116,14 +109,10 @@ def update_reqheader( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"ReqHeader '{reqheader_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"ReqHeader '{reqheader_id}' updated successfully.", "ReqHeader", "update", reqheader_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update ReqHeader. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to update ReqHeader. {message} Status code: {status}", "ReqHeader", "update")) raise typer.Exit(code=status) @@ -146,16 +135,10 @@ def delete_reqheader( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"ReqHeader '{reqheader_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"ReqHeader '{reqheader_id}' deleted successfully.", "ReqHeader", "delete", reqheader_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete ReqHeader. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete ReqHeader. {message} Status code: {status}", "ReqHeader", "delete")) raise typer.Exit(code=status) @@ -179,40 +162,31 @@ def get_reqheader( else: if status == 0: reqheader = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"ReqHeader Details: {reqheader.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # Output fields (ReqHeader public name fields only) - table.add_row("id", str(reqheader.get("id", "-"))) - table.add_row("updated_by", str(reqheader.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - reqheader.get("updated_time"))) - table.add_row("version_no", str(reqheader.get("version_no", "-"))) - table.add_row("crud_mode", str(reqheader.get("crud_mode", "-"))) - table.add_row("hostname", str(reqheader.get("hostname", "-"))) - table.add_row("port", str(reqheader.get("port", "-"))) - table.add_row("auth_realm", str(reqheader.get("auth_realm", "-"))) - table.add_row("protocol_scheme", str( - reqheader.get("protocol_scheme", "-"))) - table.add_row("username", str(reqheader.get("username", "-"))) - table.add_row("password", str(reqheader.get("password", "-"))) - table.add_row("parameters", str(reqheader.get("parameters", "-"))) - table.add_row("web_config_id", str( - reqheader.get("web_config_id", "-"))) - table.add_row("created_by", str(reqheader.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - reqheader.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"ReqHeader Details: {reqheader.get('id', '-')}", + reqheader, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("hostname", "hostname"), + ("port", "port"), + ("auth_realm", "auth_realm"), + ("protocol_scheme", "protocol_scheme"), + ("username", "username"), + ("password", "password"), + ("parameters", "parameters"), + ("web_config_id", "web_config_id"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve ReqHeader. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve ReqHeader. {message} Status code: {status}", "ReqHeader", "get")) raise typer.Exit(code=status) @@ -239,27 +213,15 @@ def list_reqheaders( if status == 0: reqheaders = result.get("response", {}).get("settings", []) if not reqheaders: - typer.secho("No ReqHeaders found.", fg=typer.colors.YELLOW) + typer.echo("No ReqHeaders found.") else: - console = Console() - table = Table(title="ReqHeaders") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan") - table.add_column("VALUE", style="cyan") - table.add_column("WEB_CONFIG_ID", style="cyan") - - for rh in reqheaders: - table.add_row( - rh.get("id", "-"), - rh.get("name", "-"), - rh.get("value", "-"), - rh.get("web_config_id", "-"), - ) - console.print(table) + typer.echo(format_list_markdown("ReqHeaders", reqheaders, [ + ("ID", "id"), + ("NAME", "name"), + ("VALUE", "value"), + ("WEB_CONFIG_ID", "web_config_id"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list ReqHeaders. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list ReqHeaders. {message} Status code: {status}", "ReqHeader", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/role.py b/src/fessctl/commands/role.py index de6892a..909010b 100644 --- a/src/fessctl/commands/role.py +++ b/src/fessctl/commands/role.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import encode_to_urlsafe_base64 +from fessctl.utils import encode_to_urlsafe_base64, format_detail_markdown, format_list_markdown, format_result_markdown, output_error # Create a Typer sub-application for role commands role_app = typer.Typer() @@ -53,21 +51,15 @@ def create_role( else: if status == 0: role_id = result.get("response", {}).get("id", None) - typer.secho( - f"Role '{name}' created successfully with ID: {role_id}.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"Role '{name}' created successfully with ID: {role_id}.", "Role", "create", role_id or "")) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create role. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to create role. {message} Status code: {status}", "Role", "create")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error creating role: {e}", fg=typer.colors.RED) + output_error(output, e, "Role", "create") raise typer.Exit(code=1) @@ -95,10 +87,7 @@ def update_role( result = client.get_role(role_id=role_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Role with ID '{role_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Role with ID '{role_id}' not found. {message}", "Role", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -126,16 +115,10 @@ def update_role( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"Role '{role_id}' updated successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"Role '{role_id}' updated successfully.", "Role", "update", role_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update role. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to update role. {message} Status code: {status}", "Role", "update")) raise typer.Exit(code=status) @@ -161,21 +144,15 @@ def delete_role( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"Role with ID '{role_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"Role with ID '{role_id}' deleted successfully.", "Role", "delete", role_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete role. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete role. {message} Status code: {status}", "Role", "delete")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error deleting role: {e}", fg=typer.colors.RED) + output_error(output, e, "Role", "delete") raise typer.Exit(code=1) @@ -212,34 +189,32 @@ def get_role( else: if status == 0: role = result.get("response", {}).get("setting", {}) - console = Console() - table = Table(title=f"Role Details: {role.get('name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("ID", role.get("id", "-")) - table.add_row("Name", role.get("name", "-")) attributes = role.get("attributes", {}) attr_str = ( "\n".join(f"{k}={v}" for k, v in attributes.items()) if attributes else "-" ) - table.add_row("Attributes", attr_str) - table.add_row("Version", str(role.get("version_no", "-"))) - - console.print(table) + data = dict(role) + data["attributes_display"] = attr_str + typer.echo(format_detail_markdown( + f"Role Details: {role.get('name', '-')}", + data, + [ + ("ID", "id"), + ("Name", "name"), + ("Attributes", "attributes_display"), + ("Version", "version_no"), + ], + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve role. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve role. {message} Status code: {status}", "Role", "get")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error retrieving role: {e}", fg=typer.colors.RED) + output_error(output, e, "Role", "get") raise typer.Exit(code=1) @@ -268,36 +243,28 @@ def list_roles( if status == 0: roles = result.get("response", {}).get("settings", []) if not roles: - typer.secho("No roles found.", fg=typer.colors.YELLOW) + typer.echo("No roles found.") else: - console = Console() - table = Table(title="Roles") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan", no_wrap=True) - table.add_column("ATTRIBUTES", style="cyan", no_wrap=False) - table.add_column("VERSION", style="cyan", no_wrap=True) - for role in roles: - table.add_row( - *[ - role.get("id", "-"), - role.get("name", "-"), - "\n".join( - f"{k}={v}" - for k, v in role.get("attributes", {}).items() - ), - str(role.get("version_no", "-")), - ] + display_items = [] + for item in roles: + d = dict(item) + d["attributes_display"] = "\n".join( + f"{k}={v}" + for k, v in item.get("attributes", {}).items() ) - console.print(table) + display_items.append(d) + typer.echo(format_list_markdown("Roles", display_items, [ + ("ID", "id"), + ("NAME", "name"), + ("ATTRIBUTES", "attributes_display"), + ("VERSION", "version_no"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list roles. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list roles. {message} Status code: {status}", "Role", "list")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error listing roles: {e}", fg=typer.colors.RED) + output_error(output, e, "Role", "list") raise typer.Exit(code=1) diff --git a/src/fessctl/commands/scheduler.py b/src/fessctl/commands/scheduler.py index 0dcbc35..855644b 100644 --- a/src/fessctl/commands/scheduler.py +++ b/src/fessctl/commands/scheduler.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 scheduler_app = typer.Typer() @@ -65,14 +63,10 @@ def create_scheduler( else: if status == 0: scheduler_id = result.get("response", {}).get("id", "") - typer.secho( - f"Scheduler '{scheduler_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"Scheduler '{scheduler_id}' created successfully.", "Scheduler", "create", scheduler_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "Scheduler", "create")) raise typer.Exit(code=status) @@ -113,10 +107,7 @@ def update_scheduler( if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Scheduler with ID '{scheduler_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Scheduler with ID '{scheduler_id}' not found. {message}", "Scheduler", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -152,14 +143,10 @@ def update_scheduler( else: if status == 0: scheduler_id = result.get("response", {}).get("id", "") - typer.secho( - f"Scheduler '{scheduler_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"Scheduler '{scheduler_id}' updated successfully.", "Scheduler", "update", scheduler_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "Scheduler", "update")) raise typer.Exit(code=status) @@ -182,16 +169,10 @@ def delete_scheduler( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"Scheduler '{scheduler_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"Scheduler '{scheduler_id}' deleted successfully.", "Scheduler", "delete", scheduler_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete Scheduler. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete Scheduler. {message} Status code: {status}", "Scheduler", "delete")) raise typer.Exit(code=status) @@ -215,42 +196,32 @@ def get_scheduler( else: if status == 0: scheduler = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"Scheduler Details: {scheduler.get('name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("id", str(scheduler.get("id", "-"))) - table.add_row("updated_by", str(scheduler.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - scheduler.get("updated_time"))) - table.add_row("version_no", str(scheduler.get("version_no", "-"))) - table.add_row("crud_mode", str(scheduler.get("crud_mode", "-"))) - table.add_row("name", str(scheduler.get("name", "-"))) - table.add_row("target", str(scheduler.get("target", "-"))) - table.add_row("cron_expression", str( - scheduler.get("cron_expression", "-"))) - table.add_row("script_type", str( - scheduler.get("script_type", "-"))) - table.add_row("script_data", str( - scheduler.get("script_data", "-"))) - table.add_row("crawler", str(scheduler.get("crawler", "-"))) - table.add_row("job_logging", str( - scheduler.get("job_logging", "-"))) - table.add_row("available", str(scheduler.get("available", "-"))) - table.add_row("sort_order", str(scheduler.get("sort_order", "-"))) - table.add_row("created_by", str(scheduler.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - scheduler.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"Scheduler Details: {scheduler.get('name', '-')}", + scheduler, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("name", "name"), + ("target", "target"), + ("cron_expression", "cron_expression"), + ("script_type", "script_type"), + ("script_data", "script_data"), + ("crawler", "crawler"), + ("job_logging", "job_logging"), + ("available", "available"), + ("sort_order", "sort_order"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve Scheduler. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve Scheduler. {message} Status code: {status}", "Scheduler", "get")) raise typer.Exit(code=status) @@ -276,30 +247,15 @@ def list_schedulers( if status == 0: schedulers = result.get("response", {}).get("settings", []) if not schedulers: - typer.secho("No Schedulers found.", fg=typer.colors.YELLOW) + typer.echo("No Schedulers found.") else: - console = Console() - table = Table(title="Schedulers") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan", no_wrap=True) - table.add_column("AVAILABLE", style="cyan", no_wrap=True) - table.add_column("TARGET", style="cyan", no_wrap=True) - table.add_column("CRON", style="cyan", no_wrap=True) - for scheduler in schedulers: - table.add_row( - scheduler.get("id", "-"), - scheduler.get("name", "-"), - scheduler.get("available", "-"), - scheduler.get("target", "-"), - scheduler.get("cron_expression", "-"), - ) - console.print(table) + typer.echo(format_list_markdown("Schedulers", schedulers, [ + ("ID", "id"), ("NAME", "name"), ("AVAILABLE", "available"), + ("TARGET", "target"), ("CRON", "cron_expression"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list Schedulers. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list Schedulers. {message} Status code: {status}", "Scheduler", "list")) raise typer.Exit(code=status) @@ -322,16 +278,10 @@ def start_scheduler( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"Scheduler '{scheduler_id}' started successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"Scheduler '{scheduler_id}' started successfully.", "Scheduler", "start", scheduler_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to start Scheduler. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to start Scheduler. {message} Status code: {status}", "Scheduler", "start")) raise typer.Exit(code=status) @@ -354,14 +304,8 @@ def stop_scheduler( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"Scheduler '{scheduler_id}' stopped successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"Scheduler '{scheduler_id}' stopped successfully.", "Scheduler", "stop", scheduler_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to stop Scheduler. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to stop Scheduler. {message} Status code: {status}", "Scheduler", "stop")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/user.py b/src/fessctl/commands/user.py index f861dcb..5205518 100644 --- a/src/fessctl/commands/user.py +++ b/src/fessctl/commands/user.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import encode_to_urlsafe_base64 +from fessctl.utils import encode_to_urlsafe_base64, format_detail_markdown, format_list_markdown, format_result_markdown, output_error # Create a Typer sub-application for role commands user_app = typer.Typer() @@ -69,27 +67,19 @@ def create_user( if output == "json": typer.echo(json.dumps(result, indent=2)) elif output == "yaml": - import yaml - typer.echo(yaml.dump(result)) else: if status == 0: user_id = result.get("response", {}).get("id", None) - typer.secho( - f"User '{name}' created successfully with ID: {user_id}.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"User '{name}' created successfully with ID: {user_id}.", "User", "create", user_id or "")) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create user. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to create user. {message} Status code: {status}", "User", "create")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error creating user: {e}", fg=typer.colors.RED) + output_error(output, e, "User", "create") raise typer.Exit(code=1) @@ -137,9 +127,7 @@ def update_user( status = result.get("response", {}).get("status", 1) if status != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"User with ID '{user_id}' not found. {message}", fg=typer.colors.RED - ) + typer.echo(format_result_markdown(False, f"User with ID '{user_id}' not found. {message}", "User", "update")) raise typer.Exit(code=1) # 2) Merge existing setting into config @@ -185,14 +173,10 @@ def update_user( typer.echo(yaml.dump(update_resp)) else: if status == 0: - typer.secho( - f"User '{user_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"User '{user_id}' updated successfully.", "User", "update", user_id)) else: message: str = update_resp.get("response", {}).get("message", "") - typer.secho( - f"Failed to update user. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to update user. {message} Status code: {status}", "User", "update")) raise typer.Exit(code=status) @@ -217,21 +201,15 @@ def delete_user( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"User with ID '{user_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"User with ID '{user_id}' deleted successfully.", "User", "delete", user_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete user. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete user. {message} Status code: {status}", "User", "delete")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error deleting user: {e}", fg=typer.colors.RED) + output_error(output, e, "User", "delete") raise typer.Exit(code=1) @@ -267,31 +245,34 @@ def get_user( else: if status == 0: user_info = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"User Details: {user_info.get('name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("ID", user_info.get("id", "-")) - table.add_row("Name", user_info.get("name", "-")) - table.add_row("Roles", "\n".join(user_info.get("roles", []))) - table.add_row("Groups", "\n".join(user_info.get("groups", []))) - table.add_row("Attributes", "\n".join( - user_info.get("attributes", []))) - table.add_row("Version", str(user_info.get("version_no", "-"))) - console.print(table) + data = dict(user_info) + data["roles_display"] = "\n".join(user_info.get("roles", [])) + data["groups_display"] = "\n".join(user_info.get("groups", [])) + attrs = user_info.get("attributes", {}) + if isinstance(attrs, dict): + data["attributes_display"] = "\n".join(f"{k}={v}" for k, v in attrs.items()) + else: + data["attributes_display"] = "\n".join(str(a) for a in attrs) + typer.echo(format_detail_markdown( + f"User Details: {user_info.get('name', '-')}", + data, + [ + ("ID", "id"), + ("Name", "name"), + ("Roles", "roles_display"), + ("Groups", "groups_display"), + ("Attributes", "attributes_display"), + ("Version", "version_no"), + ], + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve user. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve user. {message} Status code: {status}", "User", "get")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho(f"Error retrieving user: {e}", fg=typer.colors.RED) + output_error(output, e, "User", "get") raise typer.Exit(code=1) @@ -320,41 +301,32 @@ def list_users( if status == 0: users = result.get("response", {}).get("settings", []) if not users: - typer.secho( - "No web authentication users found.", fg=typer.colors.YELLOW - ) + typer.echo("No web authentication users found.") else: - console = Console() - table = Table(title="Web Authentication Users") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan", no_wrap=True) - table.add_column("ROLES", style="cyan", no_wrap=False) - table.add_column("GROUPS", style="cyan", no_wrap=False) - table.add_column("ATTRIBUTES", style="cyan", no_wrap=False) - table.add_column("VERSION", style="cyan", no_wrap=True) - for user in users: - table.add_row( - user.get("id", "-"), - user.get("name", "-"), - "\n".join(user.get("roles", [])), - "\n".join(user.get("groups", [])), - "\n".join( - f"{k}={v}" - for k, v in user.get("attributes", {}).items() - ), - str(user.get("version_no", "-")), + display_items = [] + for item in users: + d = dict(item) + d["roles_display"] = "\n".join(item.get("roles", [])) + d["groups_display"] = "\n".join(item.get("groups", [])) + d["attributes_display"] = "\n".join( + f"{k}={v}" + for k, v in item.get("attributes", {}).items() ) - console.print(table) + display_items.append(d) + typer.echo(format_list_markdown("Web Authentication Users", display_items, [ + ("ID", "id"), + ("NAME", "name"), + ("ROLES", "roles_display"), + ("GROUPS", "groups_display"), + ("ATTRIBUTES", "attributes_display"), + ("VERSION", "version_no"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list web authentication users. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list web authentication users. {message} Status code: {status}", "User", "list")) raise typer.Exit(code=status) except typer.Exit: raise except Exception as e: - typer.secho( - f"Error listing web authentication users: {e}", fg=typer.colors.RED) + output_error(output, e, "User", "list") raise typer.Exit(code=1) diff --git a/src/fessctl/commands/webauth.py b/src/fessctl/commands/webauth.py index 26f55e3..cd8dc18 100644 --- a/src/fessctl/commands/webauth.py +++ b/src/fessctl/commands/webauth.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 webauth_app = typer.Typer() @@ -77,12 +75,10 @@ def create_webauth( else: if status == 0: webauth_id = result.get("response", {}).get("id", "") - typer.secho( - f"WebAuth '{webauth_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"WebAuth '{webauth_id}' created successfully.", "WebAuth", "create", webauth_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to create WebAuth. {message} Status code: {status}", fg=typer.colors.RED) + typer.echo(format_result_markdown(False, f"Failed to create WebAuth. {message} Status code: {status}", "WebAuth", "create")) raise typer.Exit(code=status) @@ -120,10 +116,7 @@ def update_webauth( result = client.get_webauth(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"WebAuth with ID '{config_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"WebAuth with ID '{config_id}' not found. {message}", "WebAuth", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -156,16 +149,10 @@ def update_webauth( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"WebAuth '{config_id}' updated successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"WebAuth '{config_id}' updated successfully.", "WebAuth", "update", config_id)) else: message = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to update WebAuth. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to update WebAuth. {message} Status code: {status}", "WebAuth", "update")) raise typer.Exit(code=status) @@ -188,16 +175,10 @@ def delete_webauth( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"WebAuth '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"WebAuth '{config_id}' deleted successfully.", "WebAuth", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete WebAuth. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete WebAuth. {message} Status code: {status}", "WebAuth", "delete")) raise typer.Exit(code=status) @@ -221,39 +202,31 @@ def get_webauth( else: if status == 0: webauth = result.get("response", {}).get("setting", {}) - console = Console() - table = Table(title=f"WebAuth Details: {webauth.get('id', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # Output fields (WebAuth public name fields only) - table.add_row("id", str(webauth.get("id", "-"))) - table.add_row("updated_by", str(webauth.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - webauth.get("updated_time"))) - table.add_row("version_no", str(webauth.get("version_no", "-"))) - table.add_row("crud_mode", str(webauth.get("crud_mode", "-"))) - table.add_row("hostname", str(webauth.get("hostname", "-"))) - table.add_row("port", str(webauth.get("port", "-"))) - table.add_row("auth_realm", str(webauth.get("auth_realm", "-"))) - table.add_row("protocol_scheme", str( - webauth.get("protocol_scheme", "-"))) - table.add_row("username", str(webauth.get("username", "-"))) - table.add_row("password", str(webauth.get("password", "-"))) - table.add_row("parameters", str(webauth.get("parameters", "-"))) - table.add_row("web_config_id", str( - webauth.get("web_config_id", "-"))) - table.add_row("created_by", str(webauth.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - webauth.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"WebAuth Details: {webauth.get('id', '-')}", + webauth, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("crud_mode", "crud_mode"), + ("hostname", "hostname"), + ("port", "port"), + ("auth_realm", "auth_realm"), + ("protocol_scheme", "protocol_scheme"), + ("username", "username"), + ("password", "password"), + ("parameters", "parameters"), + ("web_config_id", "web_config_id"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve WebAuth. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve WebAuth. {message} Status code: {status}", "WebAuth", "get")) raise typer.Exit(code=status) @@ -279,29 +252,16 @@ def list_webauths( if status == 0: webauths = result.get("response", {}).get("settings", []) if not webauths: - typer.secho("No WebAuths found.", fg=typer.colors.YELLOW) + typer.echo("No WebAuths found.") else: - console = Console() - table = Table(title="WebAuths") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("USERNAME", style="cyan") - table.add_column("HOSTNAME", style="cyan") - table.add_column("PORT", style="cyan") - table.add_column("WEB_CONFIG ID", style="cyan") - - for webauth in webauths: - table.add_row( - webauth.get("id", "-"), - webauth.get("username", "-"), - webauth.get("hostname", "-"), - str(webauth.get("port", "-")), - webauth.get("web_config_id", "-"), - ) - console.print(table) + typer.echo(format_list_markdown("WebAuths", webauths, [ + ("ID", "id"), + ("USERNAME", "username"), + ("HOSTNAME", "hostname"), + ("PORT", "port"), + ("WEB_CONFIG ID", "web_config_id"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list WebAuths. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list WebAuths. {message} Status code: {status}", "WebAuth", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/commands/webconfig.py b/src/fessctl/commands/webconfig.py index 99e928b..59f66d9 100644 --- a/src/fessctl/commands/webconfig.py +++ b/src/fessctl/commands/webconfig.py @@ -4,12 +4,10 @@ import typer import yaml -from rich.console import Console -from rich.table import Table from fessctl.api.client import FessAPIClient from fessctl.config.settings import Settings -from fessctl.utils import to_utc_iso8601 +from fessctl.utils import format_detail_markdown, format_list_markdown, format_result_markdown, to_utc_iso8601 webconfig_app = typer.Typer() @@ -114,14 +112,10 @@ def create_webconfig( else: if status == 0: config_id = result.get("response", {}).get("id", "") - typer.secho( - f"WebConfig '{config_id}' created successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"WebConfig '{config_id}' created successfully.", "WebConfig", "create", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "WebConfig", "create")) raise typer.Exit(code=status) @@ -197,10 +191,7 @@ def update_webconfig( result = client.get_webconfig(config_id) if result.get("response", {}).get("status", 1) != 0: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"WebConfig with ID '{config_id}' not found. {message}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"WebConfig with ID '{config_id}' not found. {message}", "WebConfig", "update")) raise typer.Exit(code=1) config = result.get("response", {}).get("setting", {}) @@ -256,14 +247,10 @@ def update_webconfig( else: if status == 0: config_id = result.get("response", {}).get("id", "") - typer.secho( - f"WebConfig '{config_id}' updated successfully.", fg=typer.colors.GREEN) + typer.echo(format_result_markdown(True, f"WebConfig '{config_id}' updated successfully.", "WebConfig", "update", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Operation failed. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Operation failed. {message} Status code: {status}", "WebConfig", "update")) raise typer.Exit(code=status) @@ -286,16 +273,10 @@ def delete_webconfig( typer.echo(yaml.dump(result)) else: if status == 0: - typer.secho( - f"WebConfig '{config_id}' deleted successfully.", - fg=typer.colors.GREEN, - ) + typer.echo(format_result_markdown(True, f"WebConfig '{config_id}' deleted successfully.", "WebConfig", "delete", config_id)) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to delete WebConfig. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to delete WebConfig. {message} Status code: {status}", "WebConfig", "delete")) raise typer.Exit(code=status) @@ -319,69 +300,42 @@ def get_webconfig( else: if status == 0: webconfig = result.get("response", {}).get("setting", {}) - console = Console() - table = Table( - title=f"WebConfig Details: {webconfig.get('name', '-')}") - table.add_column("Field", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - # Output all fields (public names) - table.add_row("id", str(webconfig.get("id", "-"))) - table.add_row("updated_by", str(webconfig.get("updated_by", "-"))) - table.add_row("updated_time", to_utc_iso8601( - webconfig.get("updated_time"))) - table.add_row("version_no", str(webconfig.get("version_no", "-"))) - table.add_row( - "label_type_ids", "\n".join( - webconfig.get("label_type_ids", [])) - ) - table.add_row("crud_mode", str(webconfig.get("crud_mode", "-"))) - table.add_row("name", str(webconfig.get("name", "-"))) - table.add_row("description", str( - webconfig.get("description", "-"))) - table.add_row("urls", str(webconfig.get("urls", "-"))) - table.add_row("included_urls", str( - webconfig.get("included_urls", "-"))) - table.add_row("excluded_urls", str( - webconfig.get("excluded_urls", "-"))) - table.add_row( - "included_doc_urls", str( - webconfig.get("included_doc_urls", "-")) - ) - table.add_row( - "excluded_doc_urls", str( - webconfig.get("excluded_doc_urls", "-")) - ) - table.add_row( - "config_parameter", str(webconfig.get("config_parameter", "-")) - ) - table.add_row("depth", str(webconfig.get("depth", "-"))) - table.add_row( - "max_access_count", str(webconfig.get("max_access_count", "-")) - ) - table.add_row("user_agent", str(webconfig.get("user_agent", "-"))) - table.add_row("num_of_thread", str( - webconfig.get("num_of_thread", "-"))) - table.add_row("interval_time", str( - webconfig.get("interval_time", "-"))) - table.add_row("boost", str(webconfig.get("boost", "-"))) - table.add_row("available", str(webconfig.get("available", "-"))) - table.add_row("permissions", str( - webconfig.get("permissions", "-"))) - table.add_row("virtual_hosts", str( - webconfig.get("virtual_hosts", "-"))) - table.add_row("sort_order", str(webconfig.get("sort_order", "-"))) - table.add_row("created_by", str(webconfig.get("created_by", "-"))) - table.add_row("created_time", to_utc_iso8601( - webconfig.get("created_time"))) - - console.print(table) + typer.echo(format_detail_markdown( + f"WebConfig Details: {webconfig.get('name', '-')}", + webconfig, + [ + ("id", "id"), + ("updated_by", "updated_by"), + ("updated_time", "updated_time"), + ("version_no", "version_no"), + ("label_type_ids", "label_type_ids"), + ("crud_mode", "crud_mode"), + ("name", "name"), + ("description", "description"), + ("urls", "urls"), + ("included_urls", "included_urls"), + ("excluded_urls", "excluded_urls"), + ("included_doc_urls", "included_doc_urls"), + ("excluded_doc_urls", "excluded_doc_urls"), + ("config_parameter", "config_parameter"), + ("depth", "depth"), + ("max_access_count", "max_access_count"), + ("user_agent", "user_agent"), + ("num_of_thread", "num_of_thread"), + ("interval_time", "interval_time"), + ("boost", "boost"), + ("available", "available"), + ("permissions", "permissions"), + ("virtual_hosts", "virtual_hosts"), + ("sort_order", "sort_order"), + ("created_by", "created_by"), + ("created_time", "created_time"), + ], + transforms={"updated_time": to_utc_iso8601, "created_time": to_utc_iso8601}, + )) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to retrieve WebConfig. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to retrieve WebConfig. {message} Status code: {status}", "WebConfig", "get")) raise typer.Exit(code=status) @@ -406,26 +360,12 @@ def list_webconfigs( if status == 0: webconfigs = result.get("response", {}).get("settings", []) if not webconfigs: - typer.secho("No WebConfigs found.", fg=typer.colors.YELLOW) + typer.echo("No WebConfigs found.") else: - console = Console() - table = Table(title="WebConfigs") - table.add_column("ID", style="cyan", no_wrap=True) - table.add_column("NAME", style="cyan", no_wrap=True) - table.add_column("AVAILABLE", style="cyan", no_wrap=True) - table.add_column("SORT ORDER", style="cyan", no_wrap=True) - for webconfig in webconfigs: - table.add_row( - webconfig.get("id", "-"), - webconfig.get("name", "-"), - webconfig.get("available", "-"), - str(webconfig.get("sort_order", "-")), - ) - console.print(table) + typer.echo(format_list_markdown("WebConfigs", webconfigs, [ + ("ID", "id"), ("NAME", "name"), ("AVAILABLE", "available"), ("SORT ORDER", "sort_order"), + ])) else: message: str = result.get("response", {}).get("message", "") - typer.secho( - f"Failed to list WebConfigs. {message} Status code: {status}", - fg=typer.colors.RED, - ) + typer.echo(format_result_markdown(False, f"Failed to list WebConfigs. {message} Status code: {status}", "WebConfig", "list")) raise typer.Exit(code=status) diff --git a/src/fessctl/utils.py b/src/fessctl/utils.py index 5db241c..d273a5e 100644 --- a/src/fessctl/utils.py +++ b/src/fessctl/utils.py @@ -1,7 +1,11 @@ import base64 +import json from datetime import datetime, timezone from typing import Optional +import typer +import yaml + def to_utc_iso8601(epoch_millis: Optional[int | str]) -> str: if epoch_millis is None: @@ -17,4 +21,71 @@ def encode_to_urlsafe_base64(text: str) -> str: """ byte_data = text.encode('utf-8') encoded_bytes = base64.urlsafe_b64encode(byte_data) - return encoded_bytes.decode('utf-8') \ No newline at end of file + return encoded_bytes.decode('utf-8') + + +def _escape_cell(value) -> str: + if value is None: + return "-" + s = str(value) + s = s.replace("|", "\\|") + s = s.replace("\n", "
") + return s + + +def format_list_markdown(title: str, items: list[dict], columns: list[tuple[str, str]]) -> str: + lines = [f"## {title}", ""] + headers = [col[0] for col in columns] + lines.append("| " + " | ".join(headers) + " |") + lines.append("| " + " | ".join("---" for _ in columns) + " |") + for item in items: + row = [] + for _, key in columns: + row.append(_escape_cell(item.get(key))) + lines.append("| " + " | ".join(row) + " |") + return "\n".join(lines) + + +def format_detail_markdown(title: str, data: dict, fields: list[tuple[str, str]], + transforms: dict[str, callable] | None = None) -> str: + lines = [f"## {title}", ""] + lines.append("| Field | Value |") + lines.append("| --- | --- |") + for display_name, dict_key in fields: + value = data.get(dict_key) + if transforms and dict_key in transforms: + value = transforms[dict_key](value) + lines.append(f"| {_escape_cell(display_name)} | {_escape_cell(value)} |") + return "\n".join(lines) + + +def format_result_markdown(success: bool, message: str, resource_type: str, + action: str, resource_id: str = "") -> str: + status = "success" if success else "error" + lines = ["## Result", ""] + lines.append(f"- **status**: {status}") + lines.append(f"- **action**: {action}") + lines.append(f"- **resource_type**: {resource_type}") + if resource_id: + lines.append(f"- **id**: {resource_id}") + lines.append(f"- **message**: {message}") + return "\n".join(lines) + + +def output_error(output: str, error: Exception, resource_type: str, action: str): + if output == "json": + typer.echo(json.dumps({ + "status": "error", + "resource_type": resource_type, + "action": action, + "message": str(error), + }, indent=2)) + elif output == "yaml": + typer.echo(yaml.dump({ + "status": "error", + "resource_type": resource_type, + "action": action, + "message": str(error), + })) + else: + typer.echo(format_result_markdown(False, str(error), resource_type, action)) \ No newline at end of file diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index b4f1f1a..818afe1 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -3,57 +3,52 @@ """ import pytest -from fessctl.utils import to_utc_iso8601, encode_to_urlsafe_base64 +from fessctl.utils import ( + to_utc_iso8601, + encode_to_urlsafe_base64, + format_list_markdown, + format_detail_markdown, + format_result_markdown, + output_error, +) class TestToUtcIso8601: """Tests for the to_utc_iso8601 function.""" def test_with_valid_epoch_millis(self): - """Test conversion of valid epoch milliseconds.""" - # 2024-01-15 12:30:45 UTC = 1705321845000 ms epoch_millis = 1705321845000 result = to_utc_iso8601(epoch_millis) assert result == "2024-01-15T12:30:45Z" def test_with_zero_epoch(self): - """Test conversion of epoch 0 (1970-01-01T00:00:00Z).""" epoch_millis = 0 result = to_utc_iso8601(epoch_millis) assert result == "1970-01-01T00:00:00Z" def test_with_none_returns_dash(self): - """Test that None input returns '-'.""" result = to_utc_iso8601(None) assert result == "-" def test_with_string_epoch(self): - """Test conversion of epoch as string (should be converted to int).""" epoch_millis_str = "1705321845000" result = to_utc_iso8601(epoch_millis_str) assert result == "2024-01-15T12:30:45Z" def test_with_integer_epoch(self): - """Test conversion with integer type.""" - # 2000-01-01T00:00:00Z = 946684800000 ms epoch_millis = 946684800000 result = to_utc_iso8601(epoch_millis) assert result == "2000-01-01T00:00:00Z" def test_iso8601_format_ends_with_z(self): - """Test that output ends with Z indicating UTC timezone.""" result = to_utc_iso8601(1705321845000) assert result.endswith("Z") def test_iso8601_format_has_t_separator(self): - """Test that output has T separator between date and time.""" result = to_utc_iso8601(1705321845000) assert "T" in result def test_with_large_epoch(self): - """Test with a large epoch value (future date).""" - # Use a known epoch and verify format is correct - # 2539424200000 ms converts to 2050-06-21T11:36:40Z epoch_millis = 2539424200000 result = to_utc_iso8601(epoch_millis) assert result == "2050-06-21T11:36:40Z" @@ -63,63 +58,47 @@ class TestEncodeToUrlsafeBase64: """Tests for the encode_to_urlsafe_base64 function.""" def test_basic_string(self): - """Test encoding of a basic ASCII string.""" text = "hello" result = encode_to_urlsafe_base64(text) assert result == "aGVsbG8=" def test_empty_string(self): - """Test encoding of an empty string.""" text = "" result = encode_to_urlsafe_base64(text) assert result == "" def test_string_with_special_chars(self): - """Test encoding of a string with special characters.""" text = "hello+world/test" result = encode_to_urlsafe_base64(text) - # URL-safe base64 should not contain + or / - assert "+" not in result or result.count("+") == 0 - # Verify it can be decoded back import base64 decoded = base64.urlsafe_b64decode(result).decode('utf-8') assert decoded == text def test_unicode_string(self): - """Test encoding of a Unicode string.""" - text = "こんにちは" # Japanese "hello" + text = "こんにちは" result = encode_to_urlsafe_base64(text) - # Verify it can be decoded back import base64 decoded = base64.urlsafe_b64decode(result).decode('utf-8') assert decoded == text def test_string_with_spaces(self): - """Test encoding of a string with spaces.""" text = "hello world" result = encode_to_urlsafe_base64(text) assert result == "aGVsbG8gd29ybGQ=" def test_urlsafe_no_standard_base64_chars(self): - """Test that URL-safe encoding uses - and _ instead of + and /.""" - # A string that would produce + and / in standard base64 text = "subjects?_d" result = encode_to_urlsafe_base64(text) - # URL-safe base64 uses - instead of + and _ instead of / - # Standard base64 would give: c3ViamVjdHM/X2Q= - # URL-safe base64 should not have standard unsafe chars in the middle import base64 decoded = base64.urlsafe_b64decode(result).decode('utf-8') assert decoded == text def test_numeric_string(self): - """Test encoding of a numeric string.""" text = "12345" result = encode_to_urlsafe_base64(text) assert result == "MTIzNDU=" def test_long_string(self): - """Test encoding of a longer string.""" text = "The quick brown fox jumps over the lazy dog" result = encode_to_urlsafe_base64(text) import base64 @@ -127,7 +106,6 @@ def test_long_string(self): assert decoded == text def test_string_with_newlines(self): - """Test encoding of a string with newlines.""" text = "line1\nline2\nline3" result = encode_to_urlsafe_base64(text) import base64 @@ -135,9 +113,163 @@ def test_string_with_newlines(self): assert decoded == text def test_email_address(self): - """Test encoding of an email address (common use case).""" text = "user@example.com" result = encode_to_urlsafe_base64(text) import base64 decoded = base64.urlsafe_b64decode(result).decode('utf-8') assert decoded == text + + +class TestFormatListMarkdown: + """Tests for the format_list_markdown function.""" + + def test_empty_list(self): + result = format_list_markdown("Items", [], [("ID", "id"), ("NAME", "name")]) + assert "## Items" in result + assert "| ID | NAME |" in result + # No data rows + lines = result.strip().split("\n") + assert len(lines) == 4 # title, blank, header, separator + + def test_single_item(self): + items = [{"id": "abc123", "name": "Test"}] + result = format_list_markdown("Items", items, [("ID", "id"), ("NAME", "name")]) + assert "| abc123 | Test |" in result + + def test_multiple_items(self): + items = [ + {"id": "1", "name": "First"}, + {"id": "2", "name": "Second"}, + {"id": "3", "name": "Third"}, + ] + result = format_list_markdown("Items", items, [("ID", "id"), ("NAME", "name")]) + assert "| 1 | First |" in result + assert "| 2 | Second |" in result + assert "| 3 | Third |" in result + + def test_pipe_escape(self): + items = [{"id": "1", "name": "a|b"}] + result = format_list_markdown("Items", items, [("ID", "id"), ("NAME", "name")]) + assert "a\\|b" in result + + def test_none_values(self): + items = [{"id": "1"}] + result = format_list_markdown("Items", items, [("ID", "id"), ("NAME", "name")]) + assert "| 1 | - |" in result + + def test_newlines_in_values(self): + items = [{"id": "1", "name": "line1\nline2"}] + result = format_list_markdown("Items", items, [("ID", "id"), ("NAME", "name")]) + assert "line1
line2" in result + + +class TestFormatDetailMarkdown: + """Tests for the format_detail_markdown function.""" + + def test_basic_fields(self): + data = {"id": "abc123", "name": "Test Config"} + fields = [("id", "id"), ("name", "name")] + result = format_detail_markdown("Details", data, fields) + assert "## Details" in result + assert "| Field | Value |" in result + assert "| id | abc123 |" in result + assert "| name | Test Config |" in result + + def test_with_transforms(self): + data = {"id": "abc123", "updated_time": 1705321845000} + fields = [("id", "id"), ("updated_time", "updated_time")] + transforms = {"updated_time": to_utc_iso8601} + result = format_detail_markdown("Details", data, fields, transforms=transforms) + assert "2024-01-15T12:30:45Z" in result + + def test_missing_fields(self): + data = {"id": "abc123"} + fields = [("id", "id"), ("name", "name")] + result = format_detail_markdown("Details", data, fields) + assert "| name | - |" in result + + +class TestFormatResultMarkdown: + """Tests for the format_result_markdown function.""" + + def test_success(self): + result = format_result_markdown(True, "Created successfully.", "WebConfig", "create", "abc123") + assert "## Result" in result + assert "**status**: success" in result + assert "**action**: create" in result + assert "**resource_type**: WebConfig" in result + assert "**id**: abc123" in result + assert "**message**: Created successfully." in result + + def test_failure(self): + result = format_result_markdown(False, "Not found.", "WebConfig", "get") + assert "**status**: error" in result + + def test_with_resource_id(self): + result = format_result_markdown(True, "OK", "WebConfig", "create", "id123") + assert "**id**: id123" in result + + def test_without_resource_id(self): + result = format_result_markdown(True, "OK", "WebConfig", "delete") + assert "**id**" not in result + + +class TestEscapeCellEmptyString: + """Tests that empty string values are preserved (not replaced with '-').""" + + def test_empty_string_in_list(self): + items = [{"id": "1", "name": ""}] + result = format_list_markdown("Items", items, [("ID", "id"), ("NAME", "name")]) + assert "| 1 | |" in result + + def test_empty_string_in_detail(self): + data = {"id": "abc", "description": ""} + fields = [("id", "id"), ("description", "description")] + result = format_detail_markdown("Details", data, fields) + assert "| description | |" in result + + def test_none_still_shows_dash(self): + items = [{"id": "1", "name": None}] + result = format_list_markdown("Items", items, [("ID", "id"), ("NAME", "name")]) + assert "| 1 | - |" in result + + +class TestOutputError: + """Tests for the output_error helper.""" + + def test_json_output(self, capsys): + import json as json_mod + try: + output_error("json", ValueError("test error"), "WebConfig", "create") + except SystemExit: + pass + captured = capsys.readouterr() + data = json_mod.loads(captured.out) + assert data["status"] == "error" + assert data["resource_type"] == "WebConfig" + assert data["action"] == "create" + assert data["message"] == "test error" + + def test_yaml_output(self, capsys): + import yaml as yaml_mod + try: + output_error("yaml", RuntimeError("fail"), "Group", "delete") + except SystemExit: + pass + captured = capsys.readouterr() + data = yaml_mod.safe_load(captured.out) + assert data["status"] == "error" + assert data["resource_type"] == "Group" + assert data["action"] == "delete" + assert data["message"] == "fail" + + def test_text_output(self, capsys): + try: + output_error("text", Exception("boom"), "User", "get") + except SystemExit: + pass + captured = capsys.readouterr() + assert "**status**: error" in captured.out + assert "**resource_type**: User" in captured.out + assert "**action**: get" in captured.out + assert "**message**: boom" in captured.out diff --git a/uv.lock b/uv.lock index 024cadf..e04ccb2 100644 --- a/uv.lock +++ b/uv.lock @@ -107,7 +107,6 @@ source = { editable = "." } dependencies = [ { name = "httpx" }, { name = "pyyaml" }, - { name = "rich" }, { name = "typer" }, ] @@ -124,7 +123,6 @@ requires-dist = [ { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.1.0" }, { name = "pyyaml", specifier = "==6.0.2" }, { name = "requests", marker = "extra == 'dev'", specifier = ">=2.28.0" }, - { name = "rich", specifier = "==14.0.0" }, { name = "testcontainers", marker = "extra == 'dev'", specifier = ">=3.9.0" }, { name = "typer", extras = ["all"], specifier = "==0.16.0" }, ]