bejw 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bejw-0.2.2 → bejw-0.2.4}/PKG-INFO +6 -2
- {bejw-0.2.2 → bejw-0.2.4}/README.md +5 -1
- {bejw-0.2.2 → bejw-0.2.4}/bejw/main.py +47 -8
- {bejw-0.2.2 → bejw-0.2.4}/bejw/models.py +34 -2
- bejw-0.2.4/bejw/render.py +105 -0
- {bejw-0.2.2 → bejw-0.2.4}/bejw.egg-info/PKG-INFO +6 -2
- {bejw-0.2.2 → bejw-0.2.4}/bejw.egg-info/SOURCES.txt +2 -0
- {bejw-0.2.2 → bejw-0.2.4}/pyproject.toml +1 -1
- bejw-0.2.4/tests/test_cli_list_formats.py +83 -0
- bejw-0.2.4/tests/test_cli_read.py +72 -0
- {bejw-0.2.2 → bejw-0.2.4}/tests/test_models.py +6 -5
- bejw-0.2.2/bejw/render.py +0 -29
- {bejw-0.2.2 → bejw-0.2.4}/LICENSE +0 -0
- {bejw-0.2.2 → bejw-0.2.4}/bejw/__init__.py +0 -0
- {bejw-0.2.2 → bejw-0.2.4}/bejw/storage.py +0 -0
- {bejw-0.2.2 → bejw-0.2.4}/bejw.egg-info/dependency_links.txt +0 -0
- {bejw-0.2.2 → bejw-0.2.4}/bejw.egg-info/entry_points.txt +0 -0
- {bejw-0.2.2 → bejw-0.2.4}/bejw.egg-info/requires.txt +0 -0
- {bejw-0.2.2 → bejw-0.2.4}/bejw.egg-info/top_level.txt +0 -0
- {bejw-0.2.2 → bejw-0.2.4}/setup.cfg +0 -0
- {bejw-0.2.2 → bejw-0.2.4}/tests/test_storage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bejw
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A capped reading list for links that shimmer
|
|
5
5
|
Author: menisadi
|
|
6
6
|
License: MIT License
|
|
@@ -53,11 +53,15 @@ python main.py --help
|
|
|
53
53
|
python main.py init --capacity 10
|
|
54
54
|
python main.py add "https://example.com" "Example"
|
|
55
55
|
python main.py list
|
|
56
|
-
python main.py
|
|
56
|
+
python main.py list --format tsv --show-ids | fzf
|
|
57
|
+
python main.py list --format jsonl | jq .
|
|
58
|
+
python main.py remove <number>
|
|
57
59
|
python main.py capacity 5
|
|
58
60
|
python main.py clear
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
`list` output includes a 1-based number column that matches the `remove` command.
|
|
64
|
+
|
|
61
65
|
## Storage
|
|
62
66
|
|
|
63
67
|
By default, data is stored in `~/.bejw/links.json`.
|
|
@@ -15,11 +15,15 @@ python main.py --help
|
|
|
15
15
|
python main.py init --capacity 10
|
|
16
16
|
python main.py add "https://example.com" "Example"
|
|
17
17
|
python main.py list
|
|
18
|
-
python main.py
|
|
18
|
+
python main.py list --format tsv --show-ids | fzf
|
|
19
|
+
python main.py list --format jsonl | jq .
|
|
20
|
+
python main.py remove <number>
|
|
19
21
|
python main.py capacity 5
|
|
20
22
|
python main.py clear
|
|
21
23
|
```
|
|
22
24
|
|
|
25
|
+
`list` output includes a 1-based number column that matches the `remove` command.
|
|
26
|
+
|
|
23
27
|
## Storage
|
|
24
28
|
|
|
25
29
|
By default, data is stored in `~/.bejw/links.json`.
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
bejw: A capped reading list for links that shimmer
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import webbrowser
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
import typer
|
|
8
9
|
|
|
9
10
|
from .models import CapacityError, ReadingList
|
|
10
|
-
from .render import render_links
|
|
11
|
+
from .render import OutputFormat, render_links
|
|
11
12
|
from .storage import load, save
|
|
12
13
|
from rich import print
|
|
13
14
|
|
|
@@ -50,25 +51,63 @@ def add(url: str, title: str, file_path: str = DEFAULT_FILE_PATH) -> None:
|
|
|
50
51
|
)
|
|
51
52
|
raise typer.Exit(code=1)
|
|
52
53
|
save(reading_list, file_path)
|
|
53
|
-
|
|
54
|
+
ordered = reading_list.ordered_links()
|
|
55
|
+
number = next(
|
|
56
|
+
(index for index, item in enumerate(ordered, start=1) if item.id == link.id),
|
|
57
|
+
len(ordered),
|
|
58
|
+
)
|
|
59
|
+
typer.echo(f"Added #{number}")
|
|
54
60
|
|
|
55
61
|
|
|
56
62
|
@app.command()
|
|
57
|
-
def remove(
|
|
58
|
-
"""Remove a link from the reading list by
|
|
63
|
+
def remove(number: int, file_path: str = DEFAULT_FILE_PATH) -> None:
|
|
64
|
+
"""Remove a link from the reading list by number."""
|
|
59
65
|
reading_list = load(file_path)
|
|
60
|
-
removed = reading_list.
|
|
66
|
+
removed = reading_list.remove_by_number(number)
|
|
61
67
|
save(reading_list, file_path)
|
|
62
68
|
if not removed:
|
|
63
|
-
typer.echo("No link found with that
|
|
69
|
+
typer.echo("No link found with that number.")
|
|
70
|
+
raise typer.Exit(code=1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.command()
|
|
74
|
+
def read(number: int, file_path: str = DEFAULT_FILE_PATH) -> None:
|
|
75
|
+
"""Open a link from the reading list by number."""
|
|
76
|
+
reading_list = load(file_path)
|
|
77
|
+
ordered = reading_list.ordered_links()
|
|
78
|
+
if number < 1 or number > len(ordered):
|
|
79
|
+
typer.echo("No link found with that number.")
|
|
64
80
|
raise typer.Exit(code=1)
|
|
65
81
|
|
|
82
|
+
url = ordered[number - 1].url
|
|
83
|
+
webbrowser.open(url)
|
|
84
|
+
typer.echo(f"Opened #{number}: {url}")
|
|
85
|
+
|
|
66
86
|
|
|
67
87
|
@app.command()
|
|
68
|
-
def list(
|
|
88
|
+
def list(
|
|
89
|
+
file_path: str = DEFAULT_FILE_PATH,
|
|
90
|
+
show_ids: bool = False,
|
|
91
|
+
output_format: OutputFormat = typer.Option(
|
|
92
|
+
OutputFormat.TABLE,
|
|
93
|
+
"--format",
|
|
94
|
+
"-f",
|
|
95
|
+
help="Output format: table, tsv, csv, or jsonl.",
|
|
96
|
+
),
|
|
97
|
+
no_header: bool = typer.Option(
|
|
98
|
+
False,
|
|
99
|
+
"--no-header",
|
|
100
|
+
help="Omit the header row for tsv and csv output.",
|
|
101
|
+
),
|
|
102
|
+
) -> None:
|
|
69
103
|
"""Display the reading list."""
|
|
70
104
|
reading_list = load(file_path)
|
|
71
|
-
render_links(
|
|
105
|
+
render_links(
|
|
106
|
+
reading_list,
|
|
107
|
+
show_ids=show_ids,
|
|
108
|
+
output_format=output_format,
|
|
109
|
+
include_header=not no_header,
|
|
110
|
+
)
|
|
72
111
|
|
|
73
112
|
|
|
74
113
|
@app.command()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
4
5
|
from typing import Iterable
|
|
5
6
|
from uuid import uuid4
|
|
6
7
|
|
|
@@ -17,10 +18,22 @@ class Link:
|
|
|
17
18
|
id: str
|
|
18
19
|
url: str
|
|
19
20
|
title: str
|
|
21
|
+
created_at: str
|
|
20
22
|
|
|
21
23
|
@staticmethod
|
|
22
24
|
def create(url: str, title: str) -> "Link":
|
|
23
|
-
|
|
25
|
+
created_at = datetime.now(timezone.utc).isoformat()
|
|
26
|
+
return Link(id=str(uuid4()), url=url, title=title, created_at=created_at)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _created_at_key(link: Link) -> datetime:
|
|
30
|
+
try:
|
|
31
|
+
created_at = datetime.fromisoformat(link.created_at)
|
|
32
|
+
except ValueError:
|
|
33
|
+
return datetime.min.replace(tzinfo=timezone.utc)
|
|
34
|
+
if created_at.tzinfo is None:
|
|
35
|
+
return created_at.replace(tzinfo=timezone.utc)
|
|
36
|
+
return created_at
|
|
24
37
|
|
|
25
38
|
|
|
26
39
|
class ReadingList:
|
|
@@ -35,11 +48,21 @@ class ReadingList:
|
|
|
35
48
|
self.links.append(link)
|
|
36
49
|
return link
|
|
37
50
|
|
|
51
|
+
def ordered_links(self) -> list[Link]:
|
|
52
|
+
return sorted(self.links, key=_created_at_key)
|
|
53
|
+
|
|
38
54
|
def remove_link(self, link_id: str) -> bool:
|
|
39
55
|
before = len(self.links)
|
|
40
56
|
self.links = [link for link in self.links if link.id != link_id]
|
|
41
57
|
return len(self.links) < before
|
|
42
58
|
|
|
59
|
+
def remove_by_number(self, number: int) -> bool:
|
|
60
|
+
ordered = self.ordered_links()
|
|
61
|
+
if number < 1 or number > len(ordered):
|
|
62
|
+
return False
|
|
63
|
+
target_id = ordered[number - 1].id
|
|
64
|
+
return self.remove_link(target_id)
|
|
65
|
+
|
|
43
66
|
def clear_links(self) -> None:
|
|
44
67
|
self.links = []
|
|
45
68
|
|
|
@@ -51,6 +74,15 @@ class ReadingList:
|
|
|
51
74
|
|
|
52
75
|
@staticmethod
|
|
53
76
|
def from_dict(data: dict) -> "ReadingList":
|
|
54
|
-
|
|
77
|
+
raw_links = data.get("links", [])
|
|
78
|
+
base_time = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
|
79
|
+
links = []
|
|
80
|
+
for index, item in enumerate(raw_links):
|
|
81
|
+
if "created_at" not in item:
|
|
82
|
+
item = {
|
|
83
|
+
**item,
|
|
84
|
+
"created_at": (base_time + timedelta(seconds=index)).isoformat(),
|
|
85
|
+
}
|
|
86
|
+
links.append(Link(**item))
|
|
55
87
|
capacity = data.get("capacity", 10)
|
|
56
88
|
return ReadingList(capacity=capacity, links=links)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.table import Table, box
|
|
8
|
+
|
|
9
|
+
from .models import ReadingList
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OutputFormat(StrEnum):
|
|
15
|
+
TABLE = "table"
|
|
16
|
+
TSV = "tsv"
|
|
17
|
+
CSV = "csv"
|
|
18
|
+
JSONL = "jsonl"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _link_headers(show_ids: bool) -> list[str]:
|
|
22
|
+
headers = ["no"]
|
|
23
|
+
if show_ids:
|
|
24
|
+
headers.append("id")
|
|
25
|
+
headers.extend(["title", "url"])
|
|
26
|
+
return headers
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _link_values(reading_list: ReadingList, show_ids: bool) -> list[list[str]]:
|
|
30
|
+
rows: list[list[str]] = []
|
|
31
|
+
ordered = reading_list.ordered_links()
|
|
32
|
+
for index, link in enumerate(ordered, start=1):
|
|
33
|
+
row: list[str] = [str(index)]
|
|
34
|
+
if show_ids:
|
|
35
|
+
row.append(link.id)
|
|
36
|
+
row.append(link.title)
|
|
37
|
+
row.append(link.url)
|
|
38
|
+
rows.append(row)
|
|
39
|
+
return rows
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _render_delimited(
|
|
43
|
+
reading_list: ReadingList, show_ids: bool, include_header: bool, delimiter: str
|
|
44
|
+
) -> str:
|
|
45
|
+
output = io.StringIO()
|
|
46
|
+
writer = csv.writer(output, delimiter=delimiter, lineterminator="\n")
|
|
47
|
+
|
|
48
|
+
if include_header:
|
|
49
|
+
writer.writerow(_link_headers(show_ids))
|
|
50
|
+
writer.writerows(_link_values(reading_list, show_ids))
|
|
51
|
+
return output.getvalue()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _render_jsonl(reading_list: ReadingList, show_ids: bool) -> str:
|
|
55
|
+
"""Render the reading list as JSONL (JSON Lines) format."""
|
|
56
|
+
lines = []
|
|
57
|
+
ordered = reading_list.ordered_links()
|
|
58
|
+
for index, link in enumerate(ordered, start=1):
|
|
59
|
+
payload = {"number": index, "title": link.title, "url": link.url}
|
|
60
|
+
if show_ids:
|
|
61
|
+
payload["id"] = link.id
|
|
62
|
+
lines.append(json.dumps(payload, ensure_ascii=False))
|
|
63
|
+
# Join the lines. Add a final newline at the end if there are any lines
|
|
64
|
+
return "\n".join(lines) + ("\n" if lines else "")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def render_links(
|
|
68
|
+
reading_list: ReadingList,
|
|
69
|
+
show_ids: bool = False,
|
|
70
|
+
output_format: OutputFormat = OutputFormat.TABLE,
|
|
71
|
+
include_header: bool = True,
|
|
72
|
+
) -> None:
|
|
73
|
+
if output_format == OutputFormat.TSV:
|
|
74
|
+
console.file.write(_render_delimited(reading_list, show_ids, include_header, "\t"))
|
|
75
|
+
return
|
|
76
|
+
if output_format == OutputFormat.CSV:
|
|
77
|
+
console.file.write(_render_delimited(reading_list, show_ids, include_header, ","))
|
|
78
|
+
return
|
|
79
|
+
if output_format == OutputFormat.JSONL:
|
|
80
|
+
console.file.write(_render_jsonl(reading_list, show_ids))
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
# If non of the above formats were selected, rendering a table
|
|
84
|
+
table = Table(
|
|
85
|
+
title="Bejeweled Reading List",
|
|
86
|
+
box=box.SIMPLE,
|
|
87
|
+
title_justify="left",
|
|
88
|
+
title_style="bold red",
|
|
89
|
+
)
|
|
90
|
+
table.add_column("No.", style="cyan", no_wrap=True)
|
|
91
|
+
if show_ids:
|
|
92
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
93
|
+
table.add_column("Title", style="magenta")
|
|
94
|
+
table.add_column("URL", style="green")
|
|
95
|
+
|
|
96
|
+
ordered = reading_list.ordered_links()
|
|
97
|
+
for index, link in enumerate(ordered, start=1):
|
|
98
|
+
row = [str(index)]
|
|
99
|
+
if show_ids:
|
|
100
|
+
row.append(str(link.id))
|
|
101
|
+
row.append(link.title)
|
|
102
|
+
row.append(link.url)
|
|
103
|
+
table.add_row(*row)
|
|
104
|
+
|
|
105
|
+
console.print(table)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bejw
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A capped reading list for links that shimmer
|
|
5
5
|
Author: menisadi
|
|
6
6
|
License: MIT License
|
|
@@ -53,11 +53,15 @@ python main.py --help
|
|
|
53
53
|
python main.py init --capacity 10
|
|
54
54
|
python main.py add "https://example.com" "Example"
|
|
55
55
|
python main.py list
|
|
56
|
-
python main.py
|
|
56
|
+
python main.py list --format tsv --show-ids | fzf
|
|
57
|
+
python main.py list --format jsonl | jq .
|
|
58
|
+
python main.py remove <number>
|
|
57
59
|
python main.py capacity 5
|
|
58
60
|
python main.py clear
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
`list` output includes a 1-based number column that matches the `remove` command.
|
|
64
|
+
|
|
61
65
|
## Storage
|
|
62
66
|
|
|
63
67
|
By default, data is stored in `~/.bejw/links.json`.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from typer.testing import CliRunner
|
|
4
|
+
|
|
5
|
+
from bejw.main import app
|
|
6
|
+
from bejw.models import Link, ReadingList
|
|
7
|
+
from bejw.storage import save
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
runner = CliRunner()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _seed_reading_list(file_path: Path) -> None:
|
|
14
|
+
reading_list = ReadingList(
|
|
15
|
+
capacity=10,
|
|
16
|
+
links=[
|
|
17
|
+
Link(
|
|
18
|
+
id="id-1",
|
|
19
|
+
title="Example One",
|
|
20
|
+
url="https://example.com/1",
|
|
21
|
+
created_at="2024-01-01T00:00:00+00:00",
|
|
22
|
+
),
|
|
23
|
+
Link(
|
|
24
|
+
id="id-2",
|
|
25
|
+
title="Example Two",
|
|
26
|
+
url="https://example.com/2",
|
|
27
|
+
created_at="2024-01-02T00:00:00+00:00",
|
|
28
|
+
),
|
|
29
|
+
],
|
|
30
|
+
)
|
|
31
|
+
save(reading_list, str(file_path))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_list_tsv_with_header_and_ids(tmp_path: Path) -> None:
|
|
35
|
+
file_path = tmp_path / "links.json"
|
|
36
|
+
_seed_reading_list(file_path)
|
|
37
|
+
|
|
38
|
+
result = runner.invoke(
|
|
39
|
+
app,
|
|
40
|
+
["list", "--file-path", str(file_path), "--show-ids", "--format", "tsv"],
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
assert result.exit_code == 0
|
|
44
|
+
assert (
|
|
45
|
+
result.stdout
|
|
46
|
+
== "no\tid\ttitle\turl\n"
|
|
47
|
+
"1\tid-1\tExample One\thttps://example.com/1\n"
|
|
48
|
+
"2\tid-2\tExample Two\thttps://example.com/2\n"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_list_csv_without_header(tmp_path: Path) -> None:
|
|
53
|
+
file_path = tmp_path / "links.json"
|
|
54
|
+
_seed_reading_list(file_path)
|
|
55
|
+
|
|
56
|
+
result = runner.invoke(
|
|
57
|
+
app,
|
|
58
|
+
["list", "--file-path", str(file_path), "--format", "csv", "--no-header"],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
assert result.exit_code == 0
|
|
62
|
+
assert (
|
|
63
|
+
result.stdout
|
|
64
|
+
== "1,Example One,https://example.com/1\n"
|
|
65
|
+
"2,Example Two,https://example.com/2\n"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_list_jsonl_with_ids(tmp_path: Path) -> None:
|
|
70
|
+
file_path = tmp_path / "links.json"
|
|
71
|
+
_seed_reading_list(file_path)
|
|
72
|
+
|
|
73
|
+
result = runner.invoke(
|
|
74
|
+
app,
|
|
75
|
+
["list", "--file-path", str(file_path), "--show-ids", "--format", "jsonl"],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert result.exit_code == 0
|
|
79
|
+
assert (
|
|
80
|
+
result.stdout
|
|
81
|
+
== '{"number": 1, "title": "Example One", "url": "https://example.com/1", "id": "id-1"}\n'
|
|
82
|
+
'{"number": 2, "title": "Example Two", "url": "https://example.com/2", "id": "id-2"}\n'
|
|
83
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import webbrowser
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from typer.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from bejw.main import app
|
|
7
|
+
from bejw.models import Link, ReadingList
|
|
8
|
+
from bejw.storage import save
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
runner = CliRunner()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _seed_reading_list(file_path: Path) -> None:
|
|
15
|
+
reading_list = ReadingList(
|
|
16
|
+
capacity=10,
|
|
17
|
+
links=[
|
|
18
|
+
Link(
|
|
19
|
+
id="id-1",
|
|
20
|
+
title="Example One",
|
|
21
|
+
url="https://example.com/1",
|
|
22
|
+
created_at="2024-01-01T00:00:00+00:00",
|
|
23
|
+
),
|
|
24
|
+
Link(
|
|
25
|
+
id="id-2",
|
|
26
|
+
title="Example Two",
|
|
27
|
+
url="https://example.com/2",
|
|
28
|
+
created_at="2024-01-02T00:00:00+00:00",
|
|
29
|
+
),
|
|
30
|
+
],
|
|
31
|
+
)
|
|
32
|
+
save(reading_list, str(file_path))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_read_opens_selected_link_and_prints_confirmation(
|
|
36
|
+
tmp_path: Path, monkeypatch
|
|
37
|
+
) -> None:
|
|
38
|
+
file_path = tmp_path / "links.json"
|
|
39
|
+
_seed_reading_list(file_path)
|
|
40
|
+
opened: list[str] = []
|
|
41
|
+
|
|
42
|
+
def _fake_open(url: str, *_args, **_kwargs) -> bool:
|
|
43
|
+
opened.append(url)
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
monkeypatch.setattr(webbrowser, "open", _fake_open)
|
|
47
|
+
|
|
48
|
+
result = runner.invoke(app, ["read", "2", "--file-path", str(file_path)])
|
|
49
|
+
|
|
50
|
+
assert result.exit_code == 0
|
|
51
|
+
assert opened == ["https://example.com/2"]
|
|
52
|
+
assert result.stdout == "Opened #2: https://example.com/2\n"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_read_returns_error_when_number_is_missing(tmp_path: Path) -> None:
|
|
56
|
+
file_path = tmp_path / "links.json"
|
|
57
|
+
_seed_reading_list(file_path)
|
|
58
|
+
|
|
59
|
+
result = runner.invoke(app, ["read", "99", "--file-path", str(file_path)])
|
|
60
|
+
|
|
61
|
+
assert result.exit_code == 1
|
|
62
|
+
assert result.stdout == "No link found with that number.\n"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_read_returns_error_for_empty_list(tmp_path: Path) -> None:
|
|
66
|
+
file_path = tmp_path / "links.json"
|
|
67
|
+
save(ReadingList(capacity=10), str(file_path))
|
|
68
|
+
|
|
69
|
+
result = runner.invoke(app, ["read", "1", "--file-path", str(file_path)])
|
|
70
|
+
|
|
71
|
+
assert result.exit_code == 1
|
|
72
|
+
assert result.stdout == "No link found with that number.\n"
|
|
@@ -11,6 +11,7 @@ def test_add_link_increases_size_and_returns_link() -> None:
|
|
|
11
11
|
assert isinstance(link, Link)
|
|
12
12
|
assert link.url == "https://example.com"
|
|
13
13
|
assert link.title == "Example"
|
|
14
|
+
assert link.created_at
|
|
14
15
|
assert len(reading_list.links) == 1
|
|
15
16
|
|
|
16
17
|
|
|
@@ -22,20 +23,20 @@ def test_add_link_raises_when_capacity_reached() -> None:
|
|
|
22
23
|
reading_list.add_link("https://second.com", "Second")
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
def
|
|
26
|
+
def test_remove_by_number_returns_true_when_found() -> None:
|
|
26
27
|
reading_list = ReadingList()
|
|
27
|
-
|
|
28
|
+
reading_list.add_link("https://example.com", "Example")
|
|
28
29
|
|
|
29
|
-
removed = reading_list.
|
|
30
|
+
removed = reading_list.remove_by_number(1)
|
|
30
31
|
|
|
31
32
|
assert removed is True
|
|
32
33
|
assert reading_list.links == []
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
def
|
|
36
|
+
def test_remove_by_number_returns_false_when_missing() -> None:
|
|
36
37
|
reading_list = ReadingList()
|
|
37
38
|
|
|
38
|
-
removed = reading_list.
|
|
39
|
+
removed = reading_list.remove_by_number(2)
|
|
39
40
|
|
|
40
41
|
assert removed is False
|
|
41
42
|
|
bejw-0.2.2/bejw/render.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from rich.console import Console
|
|
2
|
-
from rich.table import Table, box
|
|
3
|
-
|
|
4
|
-
from .models import ReadingList
|
|
5
|
-
|
|
6
|
-
console = Console()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def render_links(reading_list: ReadingList, show_ids: bool = False) -> None:
|
|
10
|
-
table = Table(
|
|
11
|
-
title="Bejeweled Reading List",
|
|
12
|
-
box=box.SIMPLE,
|
|
13
|
-
title_justify="left",
|
|
14
|
-
title_style="bold red",
|
|
15
|
-
)
|
|
16
|
-
if show_ids:
|
|
17
|
-
table.add_column("ID", style="cyan", no_wrap=True)
|
|
18
|
-
table.add_column("Title", style="magenta")
|
|
19
|
-
table.add_column("URL", style="green")
|
|
20
|
-
|
|
21
|
-
for link in reading_list.links:
|
|
22
|
-
row = []
|
|
23
|
-
if show_ids:
|
|
24
|
-
row.append(str(link.id))
|
|
25
|
-
row.append(link.title)
|
|
26
|
-
row.append(link.url)
|
|
27
|
-
table.add_row(*row)
|
|
28
|
-
|
|
29
|
-
console.print(table)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|