Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion phabfive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def init_logging(log_level):

if isinstance(_log_level, str):
print(
"CRITICAL: Undefined log-level set, please use any of the defined log levels inside Python logging module"
"CRITICAL - Undefined log-level set, please use any of the defined log levels inside Python logging module",
file=sys.stderr,
)
sys.exit(1)

Expand All @@ -30,6 +31,7 @@ def init_logging(log_level):

logging_conf = {
"version": 1,
"disable_existing_loggers": False,
"root": {
"level": log_level,
"handlers": ["console"],
Expand Down
128 changes: 104 additions & 24 deletions phabfive/cli.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# -*- coding: utf-8 -*-

# python std lib
import logging
import re
import sys

# 3rd party imports
from docopt import DocoptExit, Option, docopt, extras

# phabfive imports
from phabfive.constants import MONOGRAMS

log = logging.getLogger(__name__)

base_args = """
Usage:
phabfive [options] <command> [<args> ...]
Expand All @@ -28,6 +34,9 @@

Options:
--log-level=LEVEL Set loglevel [default: INFO]
--format=FORMAT Output format: rich (default), tree, or strict [default: rich]
--ascii=WHEN Use ASCII instead of Unicode (always/auto/never) [default: auto]
--hyperlink=WHEN Enable terminal hyperlinks (always/auto/never) [default: auto]
-h, --help Show this help message and exit
-V, --version Display the version number and exit
"""
Expand Down Expand Up @@ -254,9 +263,6 @@
# Using YAML templates
phabfive maniphest search --with templates/task-search/tasks-resolved-but-not-in-done.yaml
phabfive maniphest search --with templates/task-search/search-template.yaml --tag Override-Project

# Requires at least one filter (text, tag, date, column, priority, or status)
phabfive maniphest search # ERROR: not specific enough
"""


Expand Down Expand Up @@ -285,8 +291,6 @@ def parse_cli():

argv = [cli_args["<command>"]] + cli_args["<args>"]

from phabfive.constants import MONOGRAMS

patterns = re.compile("^(?:" + "|".join(MONOGRAMS.values()) + ")")

# First check for monogram shortcuts, i.e. invocation with `phabfive K123`
Expand Down Expand Up @@ -384,10 +388,40 @@ def run(cli_args, sub_args):
# Local imports required due to logging limitation
from phabfive import diffusion, maniphest, passphrase, paste, repl, user
from phabfive.constants import REPO_STATUS_CHOICES
from phabfive.core import Phabfive
from phabfive.exceptions import PhabfiveException
from phabfive.maniphest_transitions import parse_transition_patterns
from phabfive.priority_transitions import parse_priority_patterns

# Validate and process output options
valid_modes = ("always", "auto", "never")
valid_formats = ("rich", "tree", "strict")
output_format = cli_args.get("--format", "rich")
ascii_when = cli_args.get("--ascii", "never")
hyperlink_when = cli_args.get("--hyperlink", "never")

if output_format not in valid_formats:
sys.exit(f"ERROR - --format must be one of: {', '.join(valid_formats)}")
if ascii_when not in valid_modes:
sys.exit(f"ERROR - --ascii must be one of: {', '.join(valid_modes)}")
if hyperlink_when not in valid_modes:
sys.exit(f"ERROR - --hyperlink must be one of: {', '.join(valid_modes)}")

# Check mutual exclusivity
if ascii_when == "always" and hyperlink_when == "always":
sys.exit("ERROR - --ascii=always and --hyperlink=always are mutually exclusive")
if output_format == "strict" and hyperlink_when == "always":
sys.exit(
"ERROR - --format=strict and --hyperlink=always are mutually exclusive"
)

# Set output formatting options
Phabfive.set_output_options(
ascii_when=ascii_when,
hyperlink_when=hyperlink_when,
output_format=output_format,
)

retcode = 0

try:
Expand Down Expand Up @@ -527,16 +561,18 @@ def run(cli_args, sub_args):
return retcode
else:
# Create a single search config from CLI parameters
search_configs = [{
'search': {},
'title': 'Command Line Search',
'description': None
}]
search_configs = [
{
"search": {},
"title": "Command Line Search",
"description": None,
}
]

# Helper function to get value with CLI override priority
def get_param(cli_key, yaml_params, yaml_key=None, default=None):
if yaml_key is None:
yaml_key = cli_key.lstrip('-')
yaml_key = cli_key.lstrip("-")

# CLI takes precedence over YAML
cli_value = sub_args.get(cli_key)
Expand All @@ -548,13 +584,16 @@ def get_param(cli_key, yaml_params, yaml_key=None, default=None):

# Execute each search configuration
for i, config in enumerate(search_configs):
yaml_params = config['search']
yaml_params = config["search"]

# Print search header if multiple searches or if title/description provided
if len(search_configs) > 1 or config['title'] != 'Command Line Search':
if (
len(search_configs) > 1
or config["title"] != "Command Line Search"
):
print(f"\n{'=' * 60}")
print(f"🔍 {config['title']}")
if config['description']:
if config["description"]:
print(f"📝 {config['description']}")
print(f"{'=' * 60}")

Expand All @@ -563,7 +602,9 @@ def get_param(cli_key, yaml_params, yaml_key=None, default=None):
column_pattern = get_param("--column", yaml_params, "column")
if column_pattern:
try:
transition_patterns = parse_transition_patterns(column_pattern)
transition_patterns = parse_transition_patterns(
column_pattern
)
except Exception as e:
print(
f"ERROR: Invalid column filter pattern in {config['title']}: {e}",
Expand All @@ -576,7 +617,9 @@ def get_param(cli_key, yaml_params, yaml_key=None, default=None):
priority_pattern = get_param("--priority", yaml_params, "priority")
if priority_pattern:
try:
priority_patterns = parse_priority_patterns(priority_pattern)
priority_patterns = parse_priority_patterns(
priority_pattern
)
except Exception as e:
print(
f"ERROR: Invalid priority filter pattern in {config['title']}: {e}",
Expand All @@ -590,8 +633,10 @@ def get_param(cli_key, yaml_params, yaml_key=None, default=None):
if status_pattern:
try:
# Parse status patterns with API-fetched status ordering
status_patterns = maniphest_app.parse_status_patterns_with_api(
status_pattern
status_patterns = (
maniphest_app.parse_status_patterns_with_api(
status_pattern
)
)
except Exception as e:
print(
Expand All @@ -602,12 +647,37 @@ def get_param(cli_key, yaml_params, yaml_key=None, default=None):
return retcode

# Get other parameters with CLI override priority
show_history = get_param("--show-history", yaml_params, "show-history", False)
show_metadata = get_param("--show-metadata", yaml_params, "show-metadata", False)
show_history = get_param(
"--show-history", yaml_params, "show-history", False
)
show_metadata = get_param(
"--show-metadata", yaml_params, "show-metadata", False
)
text_query = get_param("<text_query>", yaml_params, "text_query")
tag = get_param("--tag", yaml_params, "tag")
created_after = get_param("--created-after", yaml_params, "created-after")
updated_after = get_param("--updated-after", yaml_params, "updated-after")
created_after = get_param(
"--created-after", yaml_params, "created-after"
)
updated_after = get_param(
"--updated-after", yaml_params, "updated-after"
)

# Check if any search criteria provided, show usage if not
has_criteria = any(
[
text_query,
tag,
created_after,
updated_after,
transition_patterns,
priority_patterns,
status_patterns,
]
)
if not has_criteria:
print("Usage:")
print(" phabfive maniphest search [<text_query>] [options]")
return retcode

maniphest_app.task_search(
text_query=text_query,
Expand Down Expand Up @@ -666,7 +736,17 @@ def get_param(cli_key, yaml_params, yaml_key=None, default=None):

if sub_args.get("show"):
# Use new unified task_show() method
task_id = int(sub_args["<ticket_id>"][1:])
ticket_id = sub_args["<ticket_id>"]

# Validate ticket ID format using MONOGRAMS pattern
maniphest_pattern = f"^{MONOGRAMS['maniphest']}$"
if not re.match(maniphest_pattern, ticket_id):
log.critical(
f"Invalid task ID '{ticket_id}'. Expected format: T123"
)
return 1

task_id = int(ticket_id[1:])

# Handle flags
show_history = sub_args.get("--show-history", False)
Expand All @@ -681,7 +761,7 @@ def get_param(cli_key, yaml_params, yaml_key=None, default=None):
)
except PhabfiveException as e:
# Catch all types of phabricator base exceptions
print(f"CRITICAL :: {str(e)}", file=sys.stderr)
log.critical(str(e))
retcode = 1

return retcode
Expand Down
Loading