Skip to content

Commit c8f78e3

Browse files
committed
feat: add usage samples and improve documentation for dotted-path usage
1 parent d4ba139 commit c8f78e3

File tree

4 files changed

+93
-63
lines changed

4 files changed

+93
-63
lines changed

bin/usage-samples

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#! /usr/bin/env bash
2+
3+
# Don't exit on error
4+
set +e
5+
6+
## this files just collects all possible outputs for regular and errors so
7+
## that we can test the output of the program
8+
9+
# Function to indent each line of input by 4 spaces
10+
function indent() {
11+
sed 's/^/ /'
12+
}
13+
14+
function run_command() {
15+
local description="$2"
16+
echo ""
17+
echo -e "\033[1m$description: \033[0m"
18+
echo ""
19+
eval "$1" | indent
20+
echo ""
21+
}
22+
23+
run_command "dotcat" "no arguments, quick usage"
24+
25+
run_command "dotcat --help" "--help"
26+
27+
run_command "dotcat help" "help"
28+
29+
run_command "dotcat pyproject.toml project.authors" "correct usage, both file exists and keys is found"
30+
31+
run_command "dotcat pyproject.toml" "missing dotted-path"
32+
33+
run_command "dotcat project.authors" "missing file"
34+
35+
run_command "dotcat nothere.toml project.authors" "no such file"
36+
37+
run_command "dotcat pyproject.toml project.nothere" "missing dotted-path"
38+
39+
# Reset exit behavior
40+
set -e

src/dotcat/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
from .dotcat import run, main
1+
from .dotcat import run, main, USAGE, HELP
2+
3+
__all__ = ["run", "main", "USAGE", "HELP"]

src/dotcat/dotcat.py

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
This script reads values, including nested values, from structured data files (JSON, YAML, TOML, INI).
44
55
Usage:
6-
dotcat <file> <dot_separated_key>
6+
dotcat <file> <dotted-path>
77
88
Example:
99
dotcat config.json python.editor.tabSize
@@ -75,14 +75,27 @@ def red(text: str) -> str:
7575

7676
USAGE = f"""
7777
{bold('dotcat')}
78+
Read values from structured data files (JSON, YAML, TOML, INI)
79+
80+
Usage: dotcat <file> <dotted-path>
81+
82+
<file> The input file (JSON, YAML, TOML, INI).
83+
<dotted-path> The dotted path to the desired data (e.g., project.authors).
84+
85+
See `dotcat --help` for more information.
86+
"""
87+
88+
HELP = f"""
89+
{bold('dotcat')}
7890
Read values, including nested values, from structured data files (JSON, YAML, TOML, INI)
7991
8092
{bold('USAGE:')}
81-
dotcat <file> <dot_separated_key>
93+
dotcat <file> <dotted-path>
8294
83-
{bold('EXAMPLE:')}
84-
dotcat config.json python.editor.tabSize
85-
dotcat somefile.toml a.b.c
95+
{bold('EXAMPLES:')}
96+
dotcat config.json python.editor.tabSize
97+
dotcat somefile.toml a.b.c
98+
dotcat package.json dependencies.react
8699
"""
87100

88101
######################################################################
@@ -299,8 +312,8 @@ def from_attr_chain(data: Dict[str, Any], lookup_chain: str) -> Any:
299312
Accesses a nested dictionary value with an attribute chain encoded by a dot-separated string.
300313
301314
Args:
302-
adict: The dictionary to access.
303-
lookup_path: The dot-separated string representing the nested keys.
315+
data: The dictionary to access.
316+
lookup_chain: The dotted-path string representing the nested keys.
304317
305318
Returns:
306319
The value at the specified nested key, or None if the key doesn't exist.
@@ -329,26 +342,31 @@ def from_attr_chain(data: Dict[str, Any], lookup_chain: str) -> Any:
329342

330343
def parse_args(args: List[str]) -> Tuple[str, str, str, bool]:
331344
"""
332-
Returns the filename, lookup chain, output format, and check_install flag.
345+
Returns the filename, dotted-path, output format, and check_install flag.
333346
334347
Args:
335348
args: The list of command-line arguments.
336349
337350
Returns:
338-
The filename, lookup chain, output format, and check_install flag.
351+
The filename, dotted-path, output format, and check_install flag.
339352
"""
340353
# Handle help commands
341-
if args is None or len(args) == 0 or args == ["help"] or args == ["--help"]:
342-
print(USAGE)
354+
if args is None or len(args) == 0:
355+
print(HELP) # Show help for no arguments
356+
sys.exit(0)
357+
358+
# Handle explicit help requests
359+
if "help" in args or "-h" in args or "--help" in args:
360+
print(HELP) # Show help for help requests
343361
sys.exit(0)
344362

345363
parser = argparse.ArgumentParser(add_help=False)
346364
parser.add_argument("file", type=str, nargs="?", help="The file to read from")
347365
parser.add_argument(
348-
"dot_separated_key",
366+
"dotted_path",
349367
type=str,
350368
nargs="?",
351-
help="The dot-separated key to look up",
369+
help="The dotted-path to look up",
352370
)
353371
parser.add_argument(
354372
"--output",
@@ -365,15 +383,15 @@ def parse_args(args: List[str]) -> Tuple[str, str, str, bool]:
365383
parsed_args = parser.parse_args(args)
366384
return (
367385
parsed_args.file,
368-
parsed_args.dot_separated_key,
386+
parsed_args.dotted_path,
369387
parsed_args.output,
370388
parsed_args.check_install,
371389
)
372390

373391

374392
def is_likely_dot_path(arg: str) -> bool:
375393
"""
376-
Determines if an argument is likely a dot path rather than a file path.
394+
Determines if an argument is likely a dotted-path rather than a file path.
377395
378396
Args:
379397
arg: The argument to check.
@@ -402,8 +420,8 @@ def run(args: List[str] = None) -> None:
402420
check_install()
403421
return
404422

405-
# Special case: If we have only one argument and it looks like a dot path,
406-
# treat it as the dot path rather than the file
423+
# Special case: If we have only one argument and it looks like a dotted-path,
424+
# treat it as the dotted-path rather than the file
407425
if filename is not None and lookup_chain is None and len(args) == 1:
408426
if is_likely_dot_path(filename):
409427
# Swap the arguments
@@ -414,33 +432,33 @@ def run(args: List[str] = None) -> None:
414432
# Handle cases where one of the required arguments is missing
415433
if lookup_chain is None or filename is None:
416434
if filename is not None and lookup_chain is None:
417-
# Case 1: File is provided but dot pattern is missing
435+
# Case 1: File is provided but dotted-path is missing
418436
try:
419437
if os.path.exists(filename):
420-
# File exists, but dot pattern is missing
438+
# File exists, but dotted-path is missing
421439
print(
422-
f"{red('Dot')} path required. {red('Which')} value do you want me to look up in {filename}?"
440+
f"Dotted-path required. Which value do you want me to look up in {filename}?"
423441
)
424-
print(f"\n$dotcat {filename} {red('<pattern>')}")
442+
print(f"\n$dotcat {filename} {red('<dotted-path>')}")
425443
sys.exit(2) # Invalid usage
426444
except Exception:
427445
# If there's any error checking the file, fall back to general usage message
428446
pass
429447
elif filename is None and lookup_chain is not None:
430-
# Case 2: Dot pattern is provided but file is missing
431-
# Check if the argument looks like a dot path (contains dots)
448+
# Case 2: Dotted-path is provided but file is missing
449+
# Check if the argument looks like a dotted-path (contains dots)
432450
if "." in lookup_chain:
433-
# It looks like a dot path, so assume the file is missing
451+
# It looks like a dotted-path, so assume the file is missing
434452
print(
435-
f"{red('File')} path required. {red('Which')} file contains the value at {lookup_chain}?"
453+
f"File path required. Which file contains the value at {lookup_chain}?"
436454
)
437455
print(f"\n$dotcat {red('<file>')} {lookup_chain}")
438456
sys.exit(2) # Invalid usage
439457
# Otherwise, it might be a file without an extension or something else,
440458
# so fall back to the general usage message
441459

442460
# General usage message for other cases
443-
print(USAGE)
461+
print(USAGE) # Display usage for invalid arguments
444462
sys.exit(2) # Invalid usage
445463

446464
# gets the parsed data

tests/test_exec.py

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from io import StringIO
22
import sys
3-
from dotcat import run
3+
from dotcat import run, HELP
44

55
import pytest
66

@@ -19,17 +19,7 @@ def test_no_arguments():
1919
with pytest.raises(SystemExit) as pytest_wrapped_e:
2020
run(test_args)
2121
sys.stdout = sys.__stdout__
22-
expected_output = """
23-
dotcat
24-
Read values, including nested values, from structured data files (JSON, YAML, TOML, INI)
25-
26-
USAGE:
27-
dotcat <file> <dot_separated_key>
28-
29-
EXAMPLE:
30-
dotcat config.json python.editor.tabSize
31-
dotcat somefile.toml a.b.c
32-
""".strip()
22+
expected_output = remove_ansi_escape_sequences(HELP.strip())
3323
actual_output = remove_ansi_escape_sequences(captured_output.getvalue().strip())
3424
assert actual_output == expected_output
3525
assert pytest_wrapped_e.type == SystemExit
@@ -125,9 +115,9 @@ def test_file_without_dot_pattern():
125115
sys.stdout = sys.__stdout__
126116
actual_output = remove_ansi_escape_sequences(captured_output.getvalue().strip())
127117
# Check that the output contains the specific file name and guidance
128-
assert "Dot path required" in actual_output
118+
assert "Dotted-path required" in actual_output
129119
assert "tests/fixtures/test.json" in actual_output
130-
assert "<pattern>" in actual_output
120+
assert "<dotted-path>" in actual_output
131121
assert pytest_wrapped_e.type == SystemExit
132122
assert pytest_wrapped_e.value.code == 2
133123

@@ -154,17 +144,7 @@ def test_help_command():
154144
with pytest.raises(SystemExit) as pytest_wrapped_e:
155145
run(test_args)
156146
sys.stdout = sys.__stdout__
157-
expected_output = """
158-
dotcat
159-
Read values, including nested values, from structured data files (JSON, YAML, TOML, INI)
160-
161-
USAGE:
162-
dotcat <file> <dot_separated_key>
163-
164-
EXAMPLE:
165-
dotcat config.json python.editor.tabSize
166-
dotcat somefile.toml a.b.c
167-
""".strip()
147+
expected_output = remove_ansi_escape_sequences(HELP.strip())
168148
actual_output = remove_ansi_escape_sequences(captured_output.getvalue().strip())
169149
assert actual_output == expected_output
170150
assert pytest_wrapped_e.type == SystemExit
@@ -178,17 +158,7 @@ def test_help_flag():
178158
with pytest.raises(SystemExit) as pytest_wrapped_e:
179159
run(test_args)
180160
sys.stdout = sys.__stdout__
181-
expected_output = """
182-
dotcat
183-
Read values, including nested values, from structured data files (JSON, YAML, TOML, INI)
184-
185-
USAGE:
186-
dotcat <file> <dot_separated_key>
187-
188-
EXAMPLE:
189-
dotcat config.json python.editor.tabSize
190-
dotcat somefile.toml a.b.c
191-
""".strip()
161+
expected_output = remove_ansi_escape_sequences(HELP.strip())
192162
actual_output = remove_ansi_escape_sequences(captured_output.getvalue().strip())
193163
assert actual_output == expected_output
194164
assert pytest_wrapped_e.type == SystemExit

0 commit comments

Comments
 (0)