review: fix bookmark folders CLI option handling
This commit is contained in:
@@ -10,7 +10,7 @@ import yaml
|
|||||||
|
|
||||||
from twitter_cli.cli import cli
|
from twitter_cli.cli import cli
|
||||||
from twitter_cli.formatter import article_to_markdown, print_tweet_table
|
from twitter_cli.formatter import article_to_markdown, print_tweet_table
|
||||||
from twitter_cli.models import Author, Metrics, Tweet, UserProfile
|
from twitter_cli.models import Author, BookmarkFolder, Metrics, Tweet, UserProfile
|
||||||
from twitter_cli.serialization import tweets_to_json
|
from twitter_cli.serialization import tweets_to_json
|
||||||
|
|
||||||
|
|
||||||
@@ -254,6 +254,93 @@ def test_cli_bookmark_alias_works(monkeypatch) -> None:
|
|||||||
assert calls == ["123"]
|
assert calls == ["123"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_bookmarks_folders_inherits_parent_options(monkeypatch) -> None:
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def fake_folder_timeline(
|
||||||
|
folder_id: str,
|
||||||
|
max_count: int | None,
|
||||||
|
since: str | None,
|
||||||
|
as_json: bool,
|
||||||
|
as_yaml: bool,
|
||||||
|
output_file: str | None,
|
||||||
|
do_filter: bool,
|
||||||
|
compact: bool,
|
||||||
|
full_text: bool,
|
||||||
|
) -> None:
|
||||||
|
calls.append(
|
||||||
|
(
|
||||||
|
folder_id,
|
||||||
|
max_count,
|
||||||
|
since,
|
||||||
|
as_json,
|
||||||
|
as_yaml,
|
||||||
|
output_file,
|
||||||
|
do_filter,
|
||||||
|
compact,
|
||||||
|
full_text,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr("twitter_cli.cli._run_bookmark_folder_timeline", fake_folder_timeline)
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
["bookmarks", "--json", "--full-text", "-n", "7", "-o", "root.json", "--filter", "folders", "123"],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert calls == [("123", 7, None, True, False, "root.json", True, False, True)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_bookmarks_folders_list_inherits_parent_output_options(monkeypatch) -> None:
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def fake_list_bookmark_folders(
|
||||||
|
as_json: bool,
|
||||||
|
as_yaml: bool,
|
||||||
|
compact: bool,
|
||||||
|
output_file: str | None,
|
||||||
|
) -> None:
|
||||||
|
calls.append((as_json, as_yaml, compact, output_file))
|
||||||
|
|
||||||
|
monkeypatch.setattr("twitter_cli.cli._run_list_bookmark_folders", fake_list_bookmark_folders)
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
result = runner.invoke(cli, ["bookmarks", "--json", "-o", "folders.json", "folders"])
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert calls == [(True, False, False, "folders.json")]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_bookmarks_folders_list_writes_output_file(monkeypatch, tmp_path) -> None:
|
||||||
|
class FakeClient:
|
||||||
|
def fetch_bookmark_folders(self) -> list[BookmarkFolder]:
|
||||||
|
return [BookmarkFolder(id="f1", name="Reading"), BookmarkFolder(id="f2", name="Research")]
|
||||||
|
|
||||||
|
monkeypatch.setattr("twitter_cli.cli._get_client", lambda config=None, quiet=False: FakeClient())
|
||||||
|
monkeypatch.setattr("twitter_cli.cli.load_config", lambda: {})
|
||||||
|
output_path = tmp_path / "folders.json"
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
["bookmarks", "folders", "--json", "--output", str(output_path)],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
payload = json.loads(result.output)
|
||||||
|
assert payload["ok"] is True
|
||||||
|
assert payload["data"][0]["id"] == "f1"
|
||||||
|
|
||||||
|
saved = json.loads(output_path.read_text(encoding="utf-8"))
|
||||||
|
assert saved == [
|
||||||
|
{"id": "f1", "name": "Reading"},
|
||||||
|
{"id": "f2", "name": "Research"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_cli_whoami_command(monkeypatch) -> None:
|
def test_cli_whoami_command(monkeypatch) -> None:
|
||||||
from twitter_cli.models import UserProfile
|
from twitter_cli.models import UserProfile
|
||||||
|
|
||||||
|
|||||||
@@ -370,6 +370,25 @@ def _run_bookmarks_command(max_count, as_json, as_yaml, output_file, do_filter,
|
|||||||
_run_guarded(_run)
|
_run_guarded(_run)
|
||||||
|
|
||||||
|
|
||||||
|
def _inherit_option(ctx, name, value):
|
||||||
|
# type: (click.Context, str, Any) -> Any
|
||||||
|
"""Allow parent group options to flow into subcommands when omitted locally."""
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
parent = getattr(ctx, "parent", None)
|
||||||
|
if parent is None:
|
||||||
|
return value
|
||||||
|
return parent.params.get(name)
|
||||||
|
|
||||||
|
|
||||||
|
def _inherit_flag(ctx, name, value):
|
||||||
|
# type: (click.Context, str, bool) -> bool
|
||||||
|
parent = getattr(ctx, "parent", None)
|
||||||
|
if parent is None:
|
||||||
|
return value
|
||||||
|
return bool(value or parent.params.get(name, False))
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option(
|
@click.option(
|
||||||
"--type",
|
"--type",
|
||||||
@@ -489,9 +508,10 @@ def bookmarks(ctx, max_count, as_json, as_yaml, output_file, do_filter, full_tex
|
|||||||
@structured_output_options
|
@structured_output_options
|
||||||
@click.option("--output", "-o", "output_file", type=str, default=None, help="Save tweets to JSON file.")
|
@click.option("--output", "-o", "output_file", type=str, default=None, help="Save tweets to JSON file.")
|
||||||
@click.option("--filter", "do_filter", is_flag=True, help="Enable score-based filtering.")
|
@click.option("--filter", "do_filter", is_flag=True, help="Enable score-based filtering.")
|
||||||
|
@click.option("--full-text", is_flag=True, help="Show full tweet text in table output.")
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def bookmarks_folders(ctx, folder_id, max_count, since, as_json, as_yaml, output_file, do_filter):
|
def bookmarks_folders(ctx, folder_id, max_count, since, as_json, as_yaml, output_file, do_filter, full_text):
|
||||||
# type: (Any, Optional[str], Optional[int], Optional[str], bool, bool, Optional[str], bool) -> None
|
# type: (Any, Optional[str], Optional[int], Optional[str], bool, bool, Optional[str], bool, bool) -> None
|
||||||
"""List bookmark folders, or fetch tweets from a folder.
|
"""List bookmark folders, or fetch tweets from a folder.
|
||||||
|
|
||||||
\b
|
\b
|
||||||
@@ -502,17 +522,23 @@ def bookmarks_folders(ctx, folder_id, max_count, since, as_json, as_yaml, output
|
|||||||
twitter bookmarks folders <id> --since 2026-01-01
|
twitter bookmarks folders <id> --since 2026-01-01
|
||||||
"""
|
"""
|
||||||
compact = ctx.obj.get("compact", False)
|
compact = ctx.obj.get("compact", False)
|
||||||
|
max_count = _inherit_option(ctx, "max_count", max_count)
|
||||||
|
as_json = _inherit_flag(ctx, "as_json", as_json)
|
||||||
|
as_yaml = _inherit_flag(ctx, "as_yaml", as_yaml)
|
||||||
|
output_file = _inherit_option(ctx, "output_file", output_file)
|
||||||
|
do_filter = _inherit_flag(ctx, "do_filter", do_filter)
|
||||||
|
full_text = _inherit_flag(ctx, "full_text", full_text)
|
||||||
|
|
||||||
if folder_id is None:
|
if folder_id is None:
|
||||||
_run_list_bookmark_folders(as_json, as_yaml, compact)
|
_run_list_bookmark_folders(as_json, as_yaml, compact, output_file)
|
||||||
else:
|
else:
|
||||||
_run_bookmark_folder_timeline(
|
_run_bookmark_folder_timeline(
|
||||||
folder_id, max_count, since, as_json, as_yaml, output_file, do_filter, compact,
|
folder_id, max_count, since, as_json, as_yaml, output_file, do_filter, compact, full_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _run_list_bookmark_folders(as_json, as_yaml, compact):
|
def _run_list_bookmark_folders(as_json, as_yaml, compact, output_file=None):
|
||||||
# type: (bool, bool, bool) -> None
|
# type: (bool, bool, bool, Optional[str]) -> None
|
||||||
config = load_config()
|
config = load_config()
|
||||||
rich_output = use_rich_output(as_json=as_json, as_yaml=as_yaml, compact=compact)
|
rich_output = use_rich_output(as_json=as_json, as_yaml=as_yaml, compact=compact)
|
||||||
|
|
||||||
@@ -527,6 +553,12 @@ def _run_list_bookmark_folders(as_json, as_yaml, compact):
|
|||||||
from .serialization import bookmark_folders_to_data
|
from .serialization import bookmark_folders_to_data
|
||||||
data = bookmark_folders_to_data(folders)
|
data = bookmark_folders_to_data(folders)
|
||||||
|
|
||||||
|
if output_file:
|
||||||
|
import json as _json
|
||||||
|
Path(output_file).write_text(_json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
if rich_output:
|
||||||
|
console.print("💾 Saved to %s\n" % output_file)
|
||||||
|
|
||||||
if compact:
|
if compact:
|
||||||
import json as _json
|
import json as _json
|
||||||
click.echo(_json.dumps(data, ensure_ascii=False, indent=2))
|
click.echo(_json.dumps(data, ensure_ascii=False, indent=2))
|
||||||
@@ -561,7 +593,6 @@ def _parse_since_date(since_str):
|
|||||||
def _filter_tweets_since(tweets, since_str):
|
def _filter_tweets_since(tweets, since_str):
|
||||||
# type: (List[Tweet], str) -> List[Tweet]
|
# type: (List[Tweet], str) -> List[Tweet]
|
||||||
"""Filter tweets to only those created after the given date."""
|
"""Filter tweets to only those created after the given date."""
|
||||||
from datetime import datetime, timezone
|
|
||||||
from email.utils import parsedate_to_datetime
|
from email.utils import parsedate_to_datetime
|
||||||
cutoff = _parse_since_date(since_str)
|
cutoff = _parse_since_date(since_str)
|
||||||
filtered = []
|
filtered = []
|
||||||
@@ -577,8 +608,8 @@ def _filter_tweets_since(tweets, since_str):
|
|||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
def _run_bookmark_folder_timeline(folder_id, max_count, since, as_json, as_yaml, output_file, do_filter, compact):
|
def _run_bookmark_folder_timeline(folder_id, max_count, since, as_json, as_yaml, output_file, do_filter, compact, full_text=False):
|
||||||
# type: (str, Optional[int], Optional[str], bool, bool, Optional[str], bool, bool) -> None
|
# type: (str, Optional[int], Optional[str], bool, bool, Optional[str], bool, bool, bool) -> None
|
||||||
config = load_config()
|
config = load_config()
|
||||||
|
|
||||||
def _run():
|
def _run():
|
||||||
@@ -601,6 +632,7 @@ def _run_bookmark_folder_timeline(folder_id, max_count, since, as_json, as_yaml,
|
|||||||
do_filter,
|
do_filter,
|
||||||
config,
|
config,
|
||||||
compact=compact,
|
compact=compact,
|
||||||
|
full_text=full_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
_run_guarded(_run)
|
_run_guarded(_run)
|
||||||
|
|||||||
Reference in New Issue
Block a user