Make CLI independent of working directory

This commit is contained in:
thePR0M3TH3AN
2025-06-19 20:50:49 -04:00
parent c822600b3f
commit 517955e98a
2 changed files with 33 additions and 29 deletions

View File

@@ -120,7 +120,7 @@ environments such as **MSYS2** or **Git Bash** can also be used, but they must
provide the same command-line utilities. provide the same command-line utilities.
## Generating a Flyer ## Generating a Flyer
Run the CLI from the repository root: Run the CLI from any directory:
```bash ```bash
# interactive prompts # interactive prompts

View File

@@ -6,9 +6,13 @@ import shutil
import subprocess import subprocess
import sys import sys
import datetime import datetime
from pathlib import Path
from InquirerPy import prompt, inquirer from InquirerPy import prompt, inquirer
from rich.console import Console from rich.console import Console
# project root
ROOT = Path(__file__).resolve().parent.parent
def require_cmd(cmd: str): def require_cmd(cmd: str):
if shutil.which(cmd) is None: if shutil.which(cmd) is None:
@@ -106,7 +110,7 @@ def update_from_pdf(config_path: str, pdf_path: str):
tmpdir = tempfile.mkdtemp() tmpdir = tempfile.mkdtemp()
os.makedirs(os.path.join(tmpdir, 'from_client'), exist_ok=True) os.makedirs(os.path.join(tmpdir, 'from_client'), exist_ok=True)
shutil.copy(pdf_path, os.path.join(tmpdir, 'from_client', 'submission_form.pdf')) shutil.copy(pdf_path, os.path.join(tmpdir, 'from_client', 'submission_form.pdf'))
shutil.copy('templates/blank/extract_form_fields.sh', tmpdir) shutil.copy(ROOT / 'templates' / 'blank' / 'extract_form_fields.sh', tmpdir)
shutil.copy(config_path, os.path.join(tmpdir, 'config.json')) shutil.copy(config_path, os.path.join(tmpdir, 'config.json'))
run(['bash', 'extract_form_fields.sh'], cwd=tmpdir) run(['bash', 'extract_form_fields.sh'], cwd=tmpdir)
shutil.copy(os.path.join(tmpdir, 'config.json'), config_path) shutil.copy(os.path.join(tmpdir, 'config.json'), config_path)
@@ -116,39 +120,39 @@ def update_from_pdf(config_path: str, pdf_path: str):
def copy_template(name: str) -> str: def copy_template(name: str) -> str:
"""Copy a template directory into dist/ with a datestamped folder.""" """Copy a template directory into dist/ with a datestamped folder."""
date = datetime.date.today().strftime('%Y%m%d') date = datetime.date.today().strftime('%Y%m%d')
src = os.path.join('templates', name) src = ROOT / 'templates' / name
if not os.path.isdir(src): if not src.is_dir():
print(f"Template {name} not found", file=sys.stderr) print(f"Template {name} not found", file=sys.stderr)
sys.exit(1) sys.exit(1)
dest = os.path.join('dist', f"{name}-{date}") dest = ROOT / 'dist' / f"{name}-{date}"
os.makedirs('dist', exist_ok=True) os.makedirs(ROOT / 'dist', exist_ok=True)
shutil.copytree(src, dest, dirs_exist_ok=True) shutil.copytree(src, dest, dirs_exist_ok=True)
print(f"Template copied to {dest}") print(f"Template copied to {dest}")
return dest return str(dest)
def build_assets(config_path: str, pdf_path: str | None = None): def build_assets(config_path: str, pdf_path: str | None = None):
# generate QR codes # generate QR codes
run(['bash', 'generate_qr.sh', config_path], cwd='src') run(['bash', 'generate_qr.sh', config_path], cwd=ROOT / 'src')
# obfuscate html # obfuscate html
run(['bash', 'obfuscate_index.sh', config_path], cwd='src') run(['bash', 'obfuscate_index.sh', config_path], cwd=ROOT / 'src')
run(['bash', 'obfuscate_nostr.sh', config_path], cwd='src') run(['bash', 'obfuscate_nostr.sh', config_path], cwd=ROOT / 'src')
data = load_config(config_path) data = load_config(config_path)
with open('src/index.html', 'r') as fh: with open(ROOT / 'src/index.html', 'r') as fh:
html = fh.read() html = fh.read()
pattern = r'<p class="binary" id="binary-message">.*?</p>' pattern = r'<p class="binary" id="binary-message">.*?</p>'
repl = f'<p class="binary" id="binary-message">{data.get("binary_message", "")}</p>' repl = f'<p class="binary" id="binary-message">{data.get("binary_message", "")}</p>'
html = re.sub(pattern, repl, html, flags=re.S) html = re.sub(pattern, repl, html, flags=re.S)
with open('src/index.html', 'w') as fh: with open(ROOT / 'src/index.html', 'w') as fh:
fh.write(html) fh.write(html)
subdomain = data['subdomain'] subdomain = data['subdomain']
dest = os.path.join('host', subdomain) dest = ROOT / 'host' / subdomain
os.makedirs(os.path.join(dest, 'from_client'), exist_ok=True) os.makedirs(dest / 'from_client', exist_ok=True)
shutil.copy(config_path, os.path.join(dest, 'config.json')) shutil.copy(config_path, dest / 'config.json')
for fname in ['index.html', 'nostr.html', 'qrcode-content.png', 'qrcode-tear-offs.png', 'example.pdf', 'submission_form.pdf']: for fname in ['index.html', 'nostr.html', 'qrcode-content.png', 'qrcode-tear-offs.png', 'example.pdf', 'submission_form.pdf']:
shutil.copy(os.path.join('src', fname), dest) shutil.copy(ROOT / 'src' / fname, dest)
if pdf_path: if pdf_path:
shutil.copy(pdf_path, os.path.join(dest, 'from_client', 'submission_form.pdf')) shutil.copy(pdf_path, dest / 'from_client' / 'submission_form.pdf')
print(f"Flyer files created under {dest}") print(f"Flyer files created under {dest}")
@@ -156,12 +160,12 @@ def serve(config_path: str):
if not require_cmd('onionshare-cli'): if not require_cmd('onionshare-cli'):
sys.exit(1) sys.exit(1)
subdomain = load_config(config_path)['subdomain'] subdomain = load_config(config_path)['subdomain']
dir_path = os.path.abspath(os.path.join('host', subdomain)) dir_path = ROOT / 'host' / subdomain
if not os.path.isdir(dir_path): if not dir_path.is_dir():
print(f"Directory {dir_path} not found", file=sys.stderr) print(f"Directory {dir_path} not found", file=sys.stderr)
sys.exit(1) sys.exit(1)
logfile = os.path.join(dir_path, 'onionshare.log') logfile = dir_path / 'onionshare.log'
proc = subprocess.Popen(['onionshare-cli', '--website', '--public', '--persistent', f'{dir_path}/.onionshare-session', dir_path], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT) proc = subprocess.Popen(['onionshare-cli', '--website', '--public', '--persistent', f'{dir_path}/.onionshare-session', str(dir_path)], stdout=open(logfile, 'w'), stderr=subprocess.STDOUT)
try: try:
import time import time
import re as _re import re as _re
@@ -181,12 +185,12 @@ def serve(config_path: str):
onion_url = m.group(0) onion_url = m.group(0)
print(f"Onion URL: {onion_url}") print(f"Onion URL: {onion_url}")
# update config # update config
data = load_config(os.path.join(dir_path, 'config.json')) data = load_config(dir_path / 'config.json')
data['url'] = onion_url data['url'] = onion_url
data['tear_off_link'] = onion_url data['tear_off_link'] = onion_url
save_config(data, os.path.join(dir_path, 'config.json')) save_config(data, dir_path / 'config.json')
# regenerate assets # regenerate assets
build_assets(os.path.join(dir_path, 'config.json')) build_assets(dir_path / 'config.json')
print(f"OnionShare running (PID {proc.pid}). See {logfile} for details.") print(f"OnionShare running (PID {proc.pid}). See {logfile} for details.")
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
@@ -194,22 +198,22 @@ def serve(config_path: str):
def import_configs(): def import_configs():
import glob import glob
files = sorted(glob.glob('imports/*.json')) files = sorted(glob.glob(str(ROOT / 'imports' / '*.json')))
if not files: if not files:
print('No JSON files found in imports') print('No JSON files found in imports')
return return
for json_file in files: for json_file in files:
print(f'Processing {json_file}') print(f'Processing {json_file}')
dest_config = 'src/config.json' dest_config = ROOT / 'src' / 'config.json'
shutil.copy(json_file, dest_config) shutil.copy(json_file, dest_config)
subdomain = load_config(json_file)['subdomain'] subdomain = load_config(json_file)['subdomain']
shutil.rmtree(os.path.join('host', subdomain), ignore_errors=True) shutil.rmtree(ROOT / 'host' / subdomain, ignore_errors=True)
build_assets(dest_config) build_assets(dest_config)
def main(argv=None): def main(argv=None):
parser = argparse.ArgumentParser(prog='voxvera') parser = argparse.ArgumentParser(prog='voxvera')
parser.add_argument('--config', default='src/config.json', help='Path to config.json') parser.add_argument('--config', default=str(ROOT / 'src' / 'config.json'), help='Path to config.json')
sub = parser.add_subparsers(dest='command') sub = parser.add_subparsers(dest='command')
p_init = sub.add_parser('init', help='Update configuration interactively or from PDF') p_init = sub.add_parser('init', help='Update configuration interactively or from PDF')
@@ -225,7 +229,7 @@ def main(argv=None):
sub.add_parser('quickstart', help='Init, build and serve in sequence') sub.add_parser('quickstart', help='Init, build and serve in sequence')
args = parser.parse_args(argv) args = parser.parse_args(argv)
config_path = os.path.abspath(args.config) config_path = Path(args.config).resolve()
if args.command == 'init': if args.command == 'init':
if args.template: if args.template: