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 +27 -0
- man_ng-0.1.0/README.md +18 -0
- man_ng-0.1.0/man_ng.egg-info/PKG-INFO +27 -0
- man_ng-0.1.0/man_ng.egg-info/SOURCES.txt +13 -0
- man_ng-0.1.0/man_ng.egg-info/dependency_links.txt +1 -0
- man_ng-0.1.0/man_ng.egg-info/entry_points.txt +2 -0
- man_ng-0.1.0/man_ng.egg-info/requires.txt +1 -0
- man_ng-0.1.0/man_ng.egg-info/top_level.txt +2 -0
- man_ng-0.1.0/mang.py +121 -0
- man_ng-0.1.0/pyproject.toml +21 -0
- man_ng-0.1.0/setup.cfg +4 -0
- man_ng-0.1.0/src/__init__.py +0 -0
- man_ng-0.1.0/src/env.py +40 -0
- man_ng-0.1.0/src/injector.py +25 -0
- man_ng-0.1.0/src/parser.py +39 -0
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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyperclip>=1.8.2
|
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
|
File without changes
|
man_ng-0.1.0/src/env.py
ADDED
|
@@ -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
|