oil-cli-tool 1.0.0__py3-none-any.whl
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.
- oil_cli_tool-1.0.0.dist-info/METADATA +13 -0
- oil_cli_tool-1.0.0.dist-info/RECORD +9 -0
- oil_cli_tool-1.0.0.dist-info/WHEEL +5 -0
- oil_cli_tool-1.0.0.dist-info/entry_points.txt +2 -0
- oil_cli_tool-1.0.0.dist-info/top_level.txt +1 -0
- oil_pkg/__init__.py +0 -0
- oil_pkg/cli.py +151 -0
- oil_pkg/core.py +72 -0
- oil_pkg/ui.py +78 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oil-cli-tool
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A tool for slick shell scripts.
|
|
5
|
+
Author: Your Name
|
|
6
|
+
Requires-Dist: typer
|
|
7
|
+
Requires-Dist: inquirer
|
|
8
|
+
Requires-Dist: yaspin
|
|
9
|
+
Requires-Dist: tabulate
|
|
10
|
+
Requires-Dist: pyfiglet
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: requires-dist
|
|
13
|
+
Dynamic: summary
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
oil_pkg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
oil_pkg/cli.py,sha256=91m8bcgFBTciqj5_YdH2hEya7PGNVVY31LyoYAqKsS0,4482
|
|
3
|
+
oil_pkg/core.py,sha256=MBTpxEnAVV2SjP9bUOz52NRa-rtlIKV7C_o00kN2PWw,2212
|
|
4
|
+
oil_pkg/ui.py,sha256=6BFFBf8TlmPZxLl2aji21aB1poyWfcdk17_Ll4l6GRs,2192
|
|
5
|
+
oil_cli_tool-1.0.0.dist-info/METADATA,sha256=TNY6N1aC_W57hm62llDrBwZA_yPTsdOYclzd74npDuM,286
|
|
6
|
+
oil_cli_tool-1.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
7
|
+
oil_cli_tool-1.0.0.dist-info/entry_points.txt,sha256=ja3FkpHXN3OMGZRTU4r2gbvW1bMhLUv_fBVQziEe_A4,40
|
|
8
|
+
oil_cli_tool-1.0.0.dist-info/top_level.txt,sha256=52UOC32rDJxVohGl3K4W9nArk6qM9HpBdiWCaAT_I4s,8
|
|
9
|
+
oil_cli_tool-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
oil_pkg
|
oil_pkg/__init__.py
ADDED
|
File without changes
|
oil_pkg/cli.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
import typer
|
|
3
|
+
from oil_pkg.core import oil_soak, oil_spill, oil_rag, oil_read_csv, oil_join, get_piped_data
|
|
4
|
+
from oil_pkg.ui import oil_choose, oil_input, oil_spin, oil_splat, oil_confirm, oil_table, oil_write, oil_filter
|
|
5
|
+
|
|
6
|
+
app = typer.Typer(
|
|
7
|
+
help="Oil: A tool for slick shell scripts.",
|
|
8
|
+
add_completion=False
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
@app.callback(invoke_without_command=True)
|
|
12
|
+
def main(ctx: typer.Context):
|
|
13
|
+
"""
|
|
14
|
+
Oil: A tool for slick shell scripts.
|
|
15
|
+
"""
|
|
16
|
+
if ctx.invoked_subcommand is None:
|
|
17
|
+
typer.echo(ctx.get_help())
|
|
18
|
+
raise typer.Exit()
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def choose(message: str, options: List[str] = typer.Argument(...)):
|
|
22
|
+
"""Allows the user to select an option from a list."""
|
|
23
|
+
# List[str] automatically collects all positional arguments into a list [cite: 54, 75]
|
|
24
|
+
result = oil_choose(message, options)
|
|
25
|
+
typer.echo(result)
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
def input(message: str):
|
|
29
|
+
"""Allows the user to input a string."""
|
|
30
|
+
result = oil_input(message)
|
|
31
|
+
typer.echo(result)
|
|
32
|
+
|
|
33
|
+
@app.command()
|
|
34
|
+
def soak(file_path: str, data: Optional[str] = typer.Argument(None)):
|
|
35
|
+
"""Soaks up data and saves it to a persistent file."""
|
|
36
|
+
# If no data is provided as an argument, attempt to read from stdin (pipe)
|
|
37
|
+
if data is None:
|
|
38
|
+
piped_lines = get_piped_data()
|
|
39
|
+
data = "\n".join(piped_lines) if piped_lines else ""
|
|
40
|
+
|
|
41
|
+
# Save the data to the user-specified file_path
|
|
42
|
+
oil_soak(file_path, {"cli_input": data})
|
|
43
|
+
typer.echo(f"✨ Successfully soaked data into {file_path}")
|
|
44
|
+
|
|
45
|
+
@app.command()
|
|
46
|
+
def spill(file_path: str):
|
|
47
|
+
"""Spills (reads) data from a persistent file and prints it."""
|
|
48
|
+
result = oil_spill(file_path)
|
|
49
|
+
typer.echo(result)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@app.command()
|
|
53
|
+
def rag(file_path: str):
|
|
54
|
+
"""Wipes a persistent state file clean."""
|
|
55
|
+
success = oil_rag(file_path)
|
|
56
|
+
if success:
|
|
57
|
+
typer.echo(f"✨ Wiped clean: {file_path}")
|
|
58
|
+
else:
|
|
59
|
+
typer.echo(f"Nothing to wipe: {file_path} does not exist.")
|
|
60
|
+
|
|
61
|
+
@app.command()
|
|
62
|
+
def spin(message: str, seconds: int = typer.Argument(2)):
|
|
63
|
+
"""Displays a loading spinner."""
|
|
64
|
+
oil_spin(message, seconds)
|
|
65
|
+
|
|
66
|
+
@app.command()
|
|
67
|
+
def splat(text: str, font: str = "standard"):
|
|
68
|
+
"""Generates ASCII art (splat) from text."""
|
|
69
|
+
# Use the function name defined in ui.py (oil_splat)
|
|
70
|
+
typer.echo(oil_splat(text, font=font))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.command()
|
|
74
|
+
def confirm(message: str):
|
|
75
|
+
"""Ask a user to confirm an action."""
|
|
76
|
+
if oil_confirm(message):
|
|
77
|
+
typer.echo("Confirmed.")
|
|
78
|
+
else:
|
|
79
|
+
typer.echo("Action cancelled.")
|
|
80
|
+
raise typer.Exit(code=1)
|
|
81
|
+
|
|
82
|
+
@app.command()
|
|
83
|
+
def rag(file_path: str):
|
|
84
|
+
"""Wipes a persistent state file clean."""
|
|
85
|
+
# Add the confirmation step
|
|
86
|
+
if not oil_confirm(f"Are you sure you want to wipe {file_path}?"):
|
|
87
|
+
typer.echo("Wipe aborted.")
|
|
88
|
+
raise typer.Exit()
|
|
89
|
+
|
|
90
|
+
success = oil_rag(file_path)
|
|
91
|
+
if success:
|
|
92
|
+
typer.echo(f"✨ Wiped clean: {file_path}")
|
|
93
|
+
else:
|
|
94
|
+
typer.echo(f"Nothing to wipe: {file_path} does not exist.")
|
|
95
|
+
|
|
96
|
+
@app.command()
|
|
97
|
+
def table(file_path: str):
|
|
98
|
+
"""Render a table of data from a file."""
|
|
99
|
+
# Assuming your spill function returns a list of lists or similar data structure
|
|
100
|
+
from oil.core import oil_spill
|
|
101
|
+
data = oil_spill(file_path)
|
|
102
|
+
typer.echo(oil_table(data))
|
|
103
|
+
|
|
104
|
+
@app.command()
|
|
105
|
+
def table(file_path: str):
|
|
106
|
+
"""Render a table of data from a file."""
|
|
107
|
+
data = oil_read_csv(file_path)
|
|
108
|
+
# The first row is headers, the rest is data
|
|
109
|
+
typer.echo(oil_table(data, headers="firstrow"))
|
|
110
|
+
|
|
111
|
+
@app.command()
|
|
112
|
+
def join(file1: str, file2: str, mode: str = "vertical"):
|
|
113
|
+
"""Join contents of two files."""
|
|
114
|
+
with open(file1, 'r') as f1, open(file2, 'r') as f2:
|
|
115
|
+
typer.echo(oil_join(f1.read(), f2.read(), mode))
|
|
116
|
+
|
|
117
|
+
@app.command()
|
|
118
|
+
def write(output_file: str = None):
|
|
119
|
+
"""Prompt for long-form text using the system editor."""
|
|
120
|
+
content = oil_write()
|
|
121
|
+
if output_file:
|
|
122
|
+
with open(output_file, 'w') as f:
|
|
123
|
+
f.write(content)
|
|
124
|
+
typer.echo(f"✨ Content saved to {output_file}")
|
|
125
|
+
else:
|
|
126
|
+
typer.echo(content)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.command()
|
|
131
|
+
def filter(options: str = None):
|
|
132
|
+
"""Filter input from a pipe or a comma-separated list."""
|
|
133
|
+
data = []
|
|
134
|
+
|
|
135
|
+
if options:
|
|
136
|
+
data = options.split(',')
|
|
137
|
+
else:
|
|
138
|
+
data = get_piped_data()
|
|
139
|
+
|
|
140
|
+
if not data:
|
|
141
|
+
typer.echo("No data provided to filter.")
|
|
142
|
+
raise typer.Exit()
|
|
143
|
+
|
|
144
|
+
choice = oil_filter(data)
|
|
145
|
+
typer.echo(choice)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
app()
|
|
151
|
+
|
oil_pkg/core.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import csv
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def oil_soak(file_path, data):
|
|
9
|
+
"""Saves data to a user-defined file path."""
|
|
10
|
+
# Get the directory part of the path
|
|
11
|
+
directory = os.path.dirname(file_path)
|
|
12
|
+
|
|
13
|
+
# Only try to create the directory if it's not the current directory
|
|
14
|
+
if directory:
|
|
15
|
+
os.makedirs(directory, exist_ok=True)
|
|
16
|
+
|
|
17
|
+
with open(file_path, 'w') as f:
|
|
18
|
+
json.dump(data, f, indent=4)
|
|
19
|
+
|
|
20
|
+
def oil_spill(file_path):
|
|
21
|
+
"""Reads data from a user-defined file path."""
|
|
22
|
+
try:
|
|
23
|
+
with open(file_path, 'r') as f:
|
|
24
|
+
return json.load(f)
|
|
25
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
26
|
+
# Return an empty dict if the file is missing or corrupted
|
|
27
|
+
return {}
|
|
28
|
+
|
|
29
|
+
def oil_rag(file_path):
|
|
30
|
+
"""Wipes the persistent file clean (deletes it)."""
|
|
31
|
+
if os.path.exists(file_path):
|
|
32
|
+
os.remove(file_path)
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
def oil_read_csv(file_path: str):
|
|
37
|
+
with open(file_path, mode='r', newline='') as f:
|
|
38
|
+
reader = csv.reader(f)
|
|
39
|
+
return list(reader)
|
|
40
|
+
|
|
41
|
+
def oil_join(content1: str, content2: str, mode: str = "vertical"):
|
|
42
|
+
if mode == "vertical":
|
|
43
|
+
return f"{content1}\n{content2}"
|
|
44
|
+
elif mode == "horizontal":
|
|
45
|
+
lines1 = content1.splitlines()
|
|
46
|
+
lines2 = content2.splitlines()
|
|
47
|
+
# Ensure equal height for side-by-side
|
|
48
|
+
max_height = max(len(lines1), len(lines2))
|
|
49
|
+
lines1 += [""] * (max_height - len(lines1))
|
|
50
|
+
lines2 += [""] * (max_height - len(lines2))
|
|
51
|
+
|
|
52
|
+
joined = [f"{l1:<30} {l2}" for l1, l2 in zip(lines1, lines2)]
|
|
53
|
+
return "\n".join(joined)
|
|
54
|
+
return content1
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_piped_data() -> list:
|
|
58
|
+
"""Reads lines from stdin, then reconnects stdin to the terminal."""
|
|
59
|
+
if not sys.stdin.isatty():
|
|
60
|
+
# 1. Read the data coming from the pipe
|
|
61
|
+
data = sys.stdin.read().splitlines()
|
|
62
|
+
|
|
63
|
+
# 2. Reconnect standard input to the controlling terminal
|
|
64
|
+
# This allows interactive prompts (like inquirer) to read keyboard input again
|
|
65
|
+
try:
|
|
66
|
+
sys.stdin = open('/dev/tty')
|
|
67
|
+
except OSError:
|
|
68
|
+
pass # Fallback if /dev/tty isn't available
|
|
69
|
+
|
|
70
|
+
return data
|
|
71
|
+
return []
|
|
72
|
+
|
oil_pkg/ui.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import inquirer
|
|
2
|
+
import typer
|
|
3
|
+
import time
|
|
4
|
+
import pyfiglet
|
|
5
|
+
from yaspin import yaspin
|
|
6
|
+
from tabulate import tabulate
|
|
7
|
+
import click
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
def oil_choose(message, options):
|
|
11
|
+
"""Renders a pure UI menu from a list."""
|
|
12
|
+
questions = [
|
|
13
|
+
inquirer.List('choice', message=message, choices=options)
|
|
14
|
+
]
|
|
15
|
+
answers = inquirer.prompt(questions)
|
|
16
|
+
|
|
17
|
+
# Handle cancellation (Ctrl+C)
|
|
18
|
+
if answers is None:
|
|
19
|
+
return "Cancelled by user"
|
|
20
|
+
|
|
21
|
+
return answers['choice']
|
|
22
|
+
|
|
23
|
+
def oil_input(message):
|
|
24
|
+
"""Captures pure UI input."""
|
|
25
|
+
return typer.prompt(message)
|
|
26
|
+
|
|
27
|
+
def oil_spin(message, seconds=2):
|
|
28
|
+
"""Pure UI: Displays a loading spinner for a set amount of time."""
|
|
29
|
+
with yaspin(text=message, color="cyan") as spinner:
|
|
30
|
+
time.sleep(seconds)
|
|
31
|
+
spinner.ok("✔")
|
|
32
|
+
|
|
33
|
+
def oil_splat(text, font="standard"):
|
|
34
|
+
"""Pure UI: Converts string into ASCII art."""
|
|
35
|
+
return pyfiglet.figlet_format(text, font=font)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def oil_confirm(message: str) -> bool:
|
|
40
|
+
questions = [
|
|
41
|
+
inquirer.Confirm('confirmed', message=message, default=False),
|
|
42
|
+
]
|
|
43
|
+
answers = inquirer.prompt(questions)
|
|
44
|
+
return answers['confirmed'] #
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
|
|
48
|
+
def oil_table(data, headers="firstrow"):
|
|
49
|
+
return tabulate(data, headers=headers, tablefmt="grid")
|
|
50
|
+
|
|
51
|
+
def oil_write(text: str = "") -> str:
|
|
52
|
+
"""Opens the user's default editor to write/edit text."""
|
|
53
|
+
# click.edit() is the standard way to handle terminal editors
|
|
54
|
+
edited_text = click.edit(text)
|
|
55
|
+
return edited_text if edited_text is not None else ""
|
|
56
|
+
|
|
57
|
+
def oil_filter(options: list) -> str:
|
|
58
|
+
"""Provides an interactive fuzzy-selection interface."""
|
|
59
|
+
questions = [
|
|
60
|
+
inquirer.List('choice',
|
|
61
|
+
message="Select an option",
|
|
62
|
+
choices=options,
|
|
63
|
+
carousel=True),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
# --- THE OS-LEVEL FILE DESCRIPTOR SWAP ---
|
|
67
|
+
original_stdout_fd = os.dup(1)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
tty_fd = os.open('/dev/tty', os.O_WRONLY)
|
|
71
|
+
os.dup2(tty_fd, 1)
|
|
72
|
+
answers = inquirer.prompt(questions)
|
|
73
|
+
os.close(tty_fd)
|
|
74
|
+
finally:
|
|
75
|
+
os.dup2(original_stdout_fd, 1)
|
|
76
|
+
os.close(original_stdout_fd)
|
|
77
|
+
|
|
78
|
+
return answers['choice'] if answers else ""
|