man-ng 0.1.0__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.
man_ng-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: man-ng
3
+ Version: 0.1.0
4
+ Summary: Manual Pages Next Generation - advanced, interactive terminal CLI
5
+ Author: Ahmed Sufyan
6
+ Requires-Python: >=3.7
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pyperclip>=1.8.2
9
+
10
+ # man-ng (mang)
11
+
12
+ Interactive manual pages alternative.
13
+
14
+ ## Features
15
+ - Command discovery
16
+ - Structured examples
17
+ - Dynamic environment variable substitution
18
+ - Interactive terminal buffer execution
19
+
20
+ ## Example `nmap.md`
21
+ ```
22
+ 1. Ping sweep a subnet
23
+ sudo nmap -sn <ip_range>
24
+ ```
25
+
26
+ Run `mang nmap` to list commands.
27
+ Run `mang nmap -g 1 ip_range=192.168.1.1` to evaluate and inject.
man_ng-0.1.0/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # man-ng (mang)
2
+
3
+ Interactive manual pages alternative.
4
+
5
+ ## Features
6
+ - Command discovery
7
+ - Structured examples
8
+ - Dynamic environment variable substitution
9
+ - Interactive terminal buffer execution
10
+
11
+ ## Example `nmap.md`
12
+ ```
13
+ 1. Ping sweep a subnet
14
+ sudo nmap -sn <ip_range>
15
+ ```
16
+
17
+ Run `mang nmap` to list commands.
18
+ Run `mang nmap -g 1 ip_range=192.168.1.1` to evaluate and inject.
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: man-ng
3
+ Version: 0.1.0
4
+ Summary: Manual Pages Next Generation - advanced, interactive terminal CLI
5
+ Author: Ahmed Sufyan
6
+ Requires-Python: >=3.7
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pyperclip>=1.8.2
9
+
10
+ # man-ng (mang)
11
+
12
+ Interactive manual pages alternative.
13
+
14
+ ## Features
15
+ - Command discovery
16
+ - Structured examples
17
+ - Dynamic environment variable substitution
18
+ - Interactive terminal buffer execution
19
+
20
+ ## Example `nmap.md`
21
+ ```
22
+ 1. Ping sweep a subnet
23
+ sudo nmap -sn <ip_range>
24
+ ```
25
+
26
+ Run `mang nmap` to list commands.
27
+ Run `mang nmap -g 1 ip_range=192.168.1.1` to evaluate and inject.
@@ -0,0 +1,13 @@
1
+ README.md
2
+ mang.py
3
+ pyproject.toml
4
+ man_ng.egg-info/PKG-INFO
5
+ man_ng.egg-info/SOURCES.txt
6
+ man_ng.egg-info/dependency_links.txt
7
+ man_ng.egg-info/entry_points.txt
8
+ man_ng.egg-info/requires.txt
9
+ man_ng.egg-info/top_level.txt
10
+ src/__init__.py
11
+ src/env.py
12
+ src/injector.py
13
+ src/parser.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mang = mang:main
@@ -0,0 +1 @@
1
+ pyperclip>=1.8.2
@@ -0,0 +1,2 @@
1
+ mang
2
+ src
man_ng-0.1.0/mang.py ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import os
4
+ import sys
5
+ import re
6
+ from src.parser import parse_markdown
7
+ from src.env import load_env, save_env, evaluate_value
8
+ from src.injector import push_to_prompt
9
+
10
+ def get_pages_dirs():
11
+ dirs = [
12
+ os.curdir,
13
+ os.path.join(os.curdir, "pages"),
14
+ os.path.expanduser("~/.man-ng/pages"),
15
+ "/usr/local/share/mang/pages",
16
+ "/usr/share/mang/pages"
17
+ ]
18
+ return dirs
19
+
20
+ def find_file(tool_name):
21
+ filename = f"{tool_name}.md"
22
+ for d in get_pages_dirs():
23
+ path = os.path.join(d, filename)
24
+ if os.path.exists(path):
25
+ return path
26
+ return None
27
+
28
+ def main():
29
+ parser = argparse.ArgumentParser(description="man-ng (mang): interactive manual pages")
30
+ parser.add_argument("tool", help="Tool name to lookup (e.g. nmap), 'set', or '--set'", nargs="?")
31
+ parser.add_argument("-g", "--get", type=int, help="Fetch specific command number")
32
+ parser.add_argument("--set", help="Save a variable to ~/.man-ng (e.g. ip=1.1.1.1)", metavar="KEY=VALUE")
33
+ parser.add_argument("--dry", action="store_true", help="Print the command without injecting")
34
+ parser.add_argument("--explain", action="store_true", help="Print explanations for the command")
35
+
36
+ args, overrides = parser.parse_known_args()
37
+
38
+ # Handle explicit --set flag
39
+ if args.set:
40
+ if "=" not in args.set:
41
+ print("Error: --set requires format key=value", file=sys.stderr)
42
+ sys.exit(1)
43
+ key, value = map(str.strip, args.set.split("=", 1))
44
+ save_env(key, value)
45
+ print(f"Saved {key}={value} to ~/.man-ng")
46
+ sys.exit(0)
47
+
48
+ if not args.tool:
49
+ parser.print_help()
50
+ sys.exit(0)
51
+
52
+ # Handle `mang set k=v` or `mang --set k=v` where tool='set'/'--set'
53
+ if args.tool in ("set", "--set"):
54
+ if not overrides:
55
+ print("Error: 'set' requires format key=value", file=sys.stderr)
56
+ sys.exit(1)
57
+ k_v = overrides[0]
58
+ if "=" not in k_v:
59
+ print("Error: 'set' requires format key=value", file=sys.stderr)
60
+ sys.exit(1)
61
+ key, value = map(str.strip, k_v.split("=", 1))
62
+ save_env(key, value)
63
+ print(f"Saved {key}={value} to ~/.man-ng")
64
+ sys.exit(0)
65
+
66
+ # Note: If no tool file is found, we handle it below
67
+ filepath = find_file(args.tool)
68
+ if not filepath:
69
+ print(f"Error: Command list for '{args.tool}' not found in any standard directory.", file=sys.stderr)
70
+ sys.exit(1)
71
+
72
+ commands = parse_markdown(filepath)
73
+ if not commands:
74
+ print(f"No commands found in {filepath}", file=sys.stderr)
75
+ sys.exit(1)
76
+
77
+ if args.get:
78
+ cmd_info = next((c for c in commands if c["id"] == str(args.get)), None)
79
+ if not cmd_info:
80
+ print(f"Error: command number {args.get} not found in {args.tool}.", file=sys.stderr)
81
+ sys.exit(1)
82
+
83
+ final_cmd = cmd_info["command"]
84
+ env_vars = load_env()
85
+
86
+ # Parse inline overrides
87
+ for override in overrides:
88
+ if "=" in override:
89
+ key, value = override.split("=", 1)
90
+ env_vars[key.strip()] = value.strip()
91
+
92
+ def repl(match):
93
+ key = match.group(1)
94
+ if key in env_vars:
95
+ # evaluate value (can be dynamic e.g. $(whoami))
96
+ val = env_vars[key]
97
+ return evaluate_value(val)
98
+ return match.group(0) # remain unchanged if not found
99
+
100
+ final_cmd = re.sub(r'<([^>]+)>', repl, final_cmd)
101
+
102
+ if args.explain:
103
+ print(f"Tool: {args.tool}")
104
+ print(f"Description: {cmd_info['description']}")
105
+ print(f"Variables applied: {', '.join(env_vars.keys()) if env_vars else 'None'}\n")
106
+
107
+ if args.dry:
108
+ print("Dry run. Final command:")
109
+ print(final_cmd)
110
+ else:
111
+ push_to_prompt(final_cmd)
112
+ else:
113
+ # List all commands
114
+ print(f"--- man-ng: {args.tool} ---")
115
+ for c in commands:
116
+ print(f"{c['id']}. {c['description']}")
117
+ display_cmd = c['command']
118
+ print(f" {display_cmd}\n")
119
+
120
+ if __name__ == "__main__":
121
+ main()
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "man-ng"
7
+ version = "0.1.0"
8
+ description = "Manual Pages Next Generation - advanced, interactive terminal CLI"
9
+ authors = [{ name="Ahmed Sufyan" }]
10
+ readme = "README.md"
11
+ requires-python = ">=3.7"
12
+ dependencies = [
13
+ "pyperclip>=1.8.2"
14
+ ]
15
+
16
+ [tool.setuptools]
17
+ py-modules = ["mang"]
18
+ packages = ["src"]
19
+
20
+ [project.scripts]
21
+ mang = "mang:main"
man_ng-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,40 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+
5
+ ENV_FILE = os.path.expanduser("~/.man-ng")
6
+
7
+ def load_env():
8
+ env_vars = {}
9
+ if not os.path.exists(ENV_FILE):
10
+ return env_vars
11
+
12
+ with open(ENV_FILE, 'r', encoding='utf-8') as f:
13
+ for line in f:
14
+ line = line.strip()
15
+ if not line or line.startswith('#'):
16
+ continue
17
+ if '=' in line:
18
+ key, value = line.split('=', 1)
19
+ env_vars[key.strip()] = value.strip()
20
+ return env_vars
21
+
22
+ def save_env(key, value):
23
+ env_vars = load_env()
24
+ env_vars[key] = value
25
+ with open(ENV_FILE, 'w', encoding='utf-8') as f:
26
+ for k, v in env_vars.items():
27
+ f.write(f"{k}={v}\n")
28
+
29
+ def evaluate_value(value):
30
+ # Check if value is dynamic, e.g. $(whoami)
31
+ match = re.match(r'^\$\((.*)\)$', value)
32
+ if match:
33
+ cmd = match.group(1)
34
+ try:
35
+ # Evaluate using shell
36
+ result = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT, text=True)
37
+ return result.strip()
38
+ except subprocess.CalledProcessError as e:
39
+ return f"<Error: {e}>"
40
+ return value
@@ -0,0 +1,25 @@
1
+ import sys
2
+ import platform
3
+
4
+ def push_to_prompt(command):
5
+ # Try using Linux specific ioctl first
6
+ if platform.system() != 'Windows':
7
+ try:
8
+ import fcntl
9
+ import termios
10
+ for char in command:
11
+ fcntl.ioctl(sys.stdin, termios.TIOCSTI, char)
12
+ print() # Print newline so the prompt redraws nicely
13
+ return True
14
+ except Exception:
15
+ pass # Fallback
16
+
17
+ # For Windows or if TIOCSTI fails
18
+ print(f"\n[mang] Final Command Ready:\n\n {command}\n")
19
+ try:
20
+ import pyperclip
21
+ pyperclip.copy(command)
22
+ print("[mang] (Command copied to clipboard! You can paste it now.)")
23
+ except ImportError:
24
+ pass
25
+ return False
@@ -0,0 +1,39 @@
1
+ import re
2
+
3
+ def parse_markdown(filepath):
4
+ """
5
+ Parses a markdown file and returns a list of dictionaries:
6
+ [
7
+ {"id": "1", "description": "Desc", "command": "cmd"},
8
+ ...
9
+ ]
10
+ """
11
+ commands = []
12
+ current_cmd = None
13
+
14
+ with open(filepath, 'r', encoding='utf-8') as f:
15
+ for line in f:
16
+ match = re.match(r'^(\d+)\.\s+(.*)', line)
17
+ if match:
18
+ if current_cmd:
19
+ current_cmd["command"] = current_cmd["command"].strip()
20
+ commands.append(current_cmd)
21
+ current_cmd = {
22
+ "id": match.group(1),
23
+ "description": match.group(2).strip(),
24
+ "command": ""
25
+ }
26
+ elif current_cmd:
27
+ # Capture everything else as command until the next number
28
+ if line.strip() != "":
29
+ # Add space if appending to existing command
30
+ if current_cmd["command"]:
31
+ current_cmd["command"] += " " + line.strip()
32
+ else:
33
+ current_cmd["command"] = line.strip()
34
+
35
+ if current_cmd:
36
+ current_cmd["command"] = current_cmd["command"].strip()
37
+ commands.append(current_cmd)
38
+
39
+ return commands