commit
92147b4180
16 changed files with 690 additions and 0 deletions
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
# Tool |
||||
|
||||
Python 实现的命令行聚合工具 |
||||
|
||||
```bash |
||||
git clone https://github.com/OhYee/tools.git |
||||
BIN=$(pwd)/tools/bin |
||||
export PATH=$PATH:$BIN |
||||
|
||||
echo "Add export PATH=\$PATH:$BIN to your .bashrc" |
||||
``` |
||||
|
||||
将子命令放入 addon 目录,添加自己的功能插件(使用 `.local.py` 结尾实现私有化插件) |
||||
|
||||
## 插件列表 |
||||
|
||||
- colorful: 带颜色替换的 `tail` |
||||
- store: 存储变量到硬盘中 |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
import os |
||||
from .base import addons |
||||
|
||||
addon_dir = os.path.split(os.path.realpath(__file__))[0] |
||||
|
||||
for _, _, files in os.walk(addon_dir): |
||||
for file in files: |
||||
if file.endswith(".py") and file != "base.py" and file != "__init__.py": |
||||
exec("from .%s import *" % file[:-3]) |
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
# -*- coding:utf-8 -*- |
||||
|
||||
class Addon: |
||||
def __init__(self): |
||||
pass |
||||
|
||||
def name(self): |
||||
return ["BaseAddon"] |
||||
|
||||
def description(self): |
||||
return "no help" |
||||
|
||||
def run(self, params): |
||||
print(params) |
||||
|
||||
def __repr__(self): |
||||
return "Addon[%s] %s" % (self.name(), self.description()) |
||||
|
||||
|
||||
addons = [] |
@ -0,0 +1,210 @@
@@ -0,0 +1,210 @@
|
||||
# -*- coding:utf-8 -*- |
||||
|
||||
from .base import * |
||||
from utils import * |
||||
|
||||
import sys |
||||
import re |
||||
|
||||
i18n = { |
||||
"zh_CN": { |
||||
"description": "高亮日志输出(ERROR 等字符)", |
||||
"regex_help": "高亮正则表达式 regexp[:color][:font style])", |
||||
"number_help": "只显示前 N 行", |
||||
"tail_help": "只显示最后 N 行", |
||||
"follow_help": "保持从文件读入", |
||||
"color_help": "支持的颜色", |
||||
"style_help": "支持的样式", |
||||
}, |
||||
"en_US": { |
||||
"description": "highlight output", |
||||
"regex_help": "regex pattern for highlight. regexp[:color][:font style])", |
||||
"number_help": "only show top N lines", |
||||
"tail_help": "only show last N lines", |
||||
"follow_help": "keep read from file", |
||||
"color_help": "color options", |
||||
"style_help": "style options", |
||||
}, |
||||
} |
||||
|
||||
|
||||
class Colorful(Addon): |
||||
def __init__(self): |
||||
debug("Init Colorful", ctx) |
||||
self.lang = I18n(i18n) |
||||
|
||||
def name(self): |
||||
return ["c", "colorful"] |
||||
|
||||
def description(self): |
||||
return self.lang.get("description") |
||||
|
||||
def init(self): |
||||
self.colors = { |
||||
"": "", |
||||
"black": "\033[30m", |
||||
"r": "\033[31m", |
||||
"red": "\033[31m", |
||||
"g": "\033[32m", |
||||
"green": "\033[32m", |
||||
"y": "\033[33m", |
||||
"yellow": "\033[33m", |
||||
"b": "\033[34m", |
||||
"blue": "\033[34m", |
||||
"m": "\033[35m", |
||||
"magenta": "\033[35m", |
||||
"c": "\033[36m", |
||||
"cyan": "\033[36m", |
||||
"w": "\033[37m", |
||||
"white": "\033[37m", |
||||
} |
||||
self.colorsRegexp = "^(?:%s){0,1}$" % "|".join( |
||||
["(?:%s)" % k for k in self.colors.keys() if k != ""] |
||||
) |
||||
debug(self.colorsRegexp) |
||||
self.styles = { |
||||
"": "", |
||||
"b": "\033[1m", |
||||
"bold": "\033[1m", |
||||
"w": "\033[2m", |
||||
"weak": "\033[2m", |
||||
"i": "\033[3m", |
||||
"italics": "\033[3m", |
||||
"u": "\033[4m", |
||||
"underline": "\033[4m", |
||||
"blink": "\033[5m", |
||||
"r": "\033[7m", |
||||
"reverse": "\033[7m", |
||||
"h": "\033[8m", |
||||
"hidden": "\033[8m", |
||||
"d": "\033[9m", |
||||
"delete": "\033[9m", |
||||
} |
||||
self.stylesRegexp = "^(?:%s){0,1}$" % "|".join( |
||||
["(?:%s)" % k for k in self.styles.keys() if k != ""] |
||||
) |
||||
debug(self.stylesRegexp) |
||||
|
||||
def run(self, params): |
||||
flags = Flags("o builtin [command] [options]", params) |
||||
flags.addFlag( |
||||
["-r", "--regex"], |
||||
self.lang.get("regex_help"), |
||||
True, |
||||
) |
||||
flags.addFlag( |
||||
["-n", "--number"], |
||||
self.lang.get("number_help"), |
||||
True, |
||||
) |
||||
flags.addFlag( |
||||
["-t", "--tail"], |
||||
self.lang.get("tail_help"), |
||||
True, |
||||
) |
||||
flags.addFlag( |
||||
["-f", "--follow"], |
||||
self.lang.get("follow_help"), |
||||
) |
||||
|
||||
debug(flags.parse()) |
||||
|
||||
self.init() |
||||
|
||||
if ctx.help: |
||||
# flags.showHelp() |
||||
print("\n".join( |
||||
[ |
||||
self.description(), |
||||
"", |
||||
"%s: %s" % ( |
||||
self.lang.get("color_help"), |
||||
"|".join(self.colors.keys()), |
||||
), |
||||
"%s: %s" % ( |
||||
self.lang.get("style_help"), |
||||
"|".join(self.styles.keys()), |
||||
), |
||||
] |
||||
)) |
||||
exit(0) |
||||
|
||||
regex = flags.list("-r") |
||||
replaceFunc = [] |
||||
for r in regex: |
||||
reg = "" |
||||
color = "" |
||||
style = "" |
||||
|
||||
if r.endswith("::"): |
||||
reg = r[: -2] |
||||
else: |
||||
lst = r.split(":") |
||||
if len(lst) > 1: |
||||
color = self.parse(self.colorsRegexp, lst[-2]) |
||||
style = self.parse(self.stylesRegexp, lst[-1]) |
||||
if color != None and style != None: |
||||
lst = lst[: -2] |
||||
else: |
||||
color = self.parse(self.colorsRegexp, lst[-1]) |
||||
style = "" |
||||
if color != None: |
||||
lst = lst[: -1] |
||||
reg = ":".join(lst) |
||||
|
||||
debug(r, "=>", "(", reg, "|", color, "|", style, ")") |
||||
|
||||
replaceFunc.append( |
||||
lambda x: re.sub( |
||||
reg, |
||||
lambda text: "%s%s%s%s" % ( |
||||
self.colors.get(color, ""), |
||||
self.styles.get(style, ""), |
||||
text.group(0), |
||||
"\033[0m", |
||||
), |
||||
x, |
||||
) |
||||
) |
||||
|
||||
head = flags.int("-n", -1) |
||||
tail = flags.int("-t", -1) |
||||
follow = flags.bool("-f") |
||||
file = flags.string("_") |
||||
if file == "": |
||||
debug("read from stdin") |
||||
for line in sys.stdin: |
||||
text = line |
||||
for r in replaceFunc: |
||||
text = r(text) |
||||
print(text, end="") |
||||
else: |
||||
debug("read from file", file) |
||||
with open(file, "r") as f: |
||||
lines = f.readlines() |
||||
if head != -1: |
||||
lines = lines[: min(head, len(lines))] |
||||
if tail != -1: |
||||
lines = lines[max(-len(lines), -tail):] |
||||
for line in lines: |
||||
text = line |
||||
for r in replaceFunc: |
||||
text = r(text) |
||||
print(text, end="") |
||||
while follow: |
||||
try: |
||||
text = f.readline() |
||||
except: |
||||
break |
||||
for r in replaceFunc: |
||||
text = r(text) |
||||
print(text, end="") |
||||
|
||||
def parse(self, repl, text): |
||||
return text if re.match(repl, text) != None else None |
||||
|
||||
def get(self, params): |
||||
pass |
||||
|
||||
|
||||
addons.append(Colorful) |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
# -*- coding:utf-8 -*- |
||||
|
||||
from .base import * |
||||
from utils import * |
||||
|
||||
import sys |
||||
|
||||
|
||||
i18n = { |
||||
"zh_CN": { |
||||
"description": "持久化存储键值对数据", |
||||
"write_help": "存储键值数据", |
||||
"read_help": "读取键值数据", |
||||
|
||||
}, |
||||
"en_US": { |
||||
"description": "store key-value in disk", |
||||
"write_help": "write key-value", |
||||
"read_help": "read key-value", |
||||
}, |
||||
} |
||||
|
||||
|
||||
class Store(Addon): |
||||
def __init__(self): |
||||
debug(ctx) |
||||
self.lang = I18n(i18n) |
||||
|
||||
def name(self): |
||||
return ["s", "store"] |
||||
|
||||
def description(self): |
||||
return self.lang.get("description") |
||||
|
||||
def run(self, params): |
||||
if ctx.help: |
||||
print( |
||||
"o store {write|read} {key} [value]\n " + |
||||
self.description() |
||||
) |
||||
exit(0) |
||||
|
||||
if len(params) < 1: |
||||
print("error: missing command, want write or read") |
||||
exit(1) |
||||
cmd = params[0] |
||||
|
||||
if len(params) < 2: |
||||
print("error: missing key") |
||||
exit(1) |
||||
key = params[1] |
||||
|
||||
if cmd in ["write", "w"]: |
||||
if len(params) < 3: |
||||
print("error: missing value") |
||||
exit(1) |
||||
value = params[2] |
||||
kvStore(key, value) |
||||
elif cmd in ["read", "r"]: |
||||
print(kvLoad(key, "")) |
||||
else: |
||||
print("Unknown command " + cmd) |
||||
|
||||
|
||||
addons.append(Store) |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash |
||||
|
||||
export SHELL_FOLDER=$(cd "$(dirname "$(realpath "$0")")";pwd) |
||||
|
||||
python $SHELL_FOLDER/../main.py $@ |
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
# -*- coding:utf-8 -*- |
||||
|
||||
from addon import addons |
||||
from utils import * |
||||
import os |
||||
|
||||
i18n = { |
||||
"zh_CN": { |
||||
"show_help": "显示帮助", |
||||
"debug_mode": "测试模式", |
||||
}, |
||||
"en_US": { |
||||
"show_help": "show help", |
||||
"debug_mode": "debug mode", |
||||
}, |
||||
} |
||||
|
||||
if __name__ == "__main__": |
||||
lang = I18n(i18n) |
||||
|
||||
flags = Flags("o [command] [options]") |
||||
flags.addFlag(["-h", "--help"], lang.get("show_help")) |
||||
flags.addFlag(["-d", "--debug"], lang.get("debug_mode")) |
||||
|
||||
ctx.debug = flags.bool("-d") |
||||
ctx.help = flags.bool("-h") |
||||
ctx.root = os.path.split(os.path.realpath(__file__))[0] |
||||
|
||||
debug(flags.parse()) |
||||
debug(ctx) |
||||
|
||||
commands = [] |
||||
for addon in addons: |
||||
add = addon() |
||||
|
||||
temp = add.name() |
||||
if type(temp) != list: |
||||
temp = [temp] |
||||
|
||||
names = [] |
||||
for name in temp: |
||||
debug(name, type(name)) |
||||
if type(name) == str: |
||||
names.append(name.replace(" ", "").lower()) |
||||
commands.append( |
||||
( |
||||
add, |
||||
names, |
||||
add.description() |
||||
) |
||||
) |
||||
|
||||
debug("addon loaded:", commands) |
||||
|
||||
rargs = flags.restArgs() |
||||
command = rargs[0] if len(rargs) > 0 else "" |
||||
if command != "": |
||||
for cmd in commands: |
||||
if command in cmd[1]: |
||||
debug("run %s %s" % (command, cmd[1])) |
||||
cmd[0].run(rargs[1:]) |
||||
exit(0) |
||||
print("Unknown command: %s" % command) |
||||
|
||||
print("\n".join([ |
||||
flags.help(), |
||||
"", |
||||
"commands:", |
||||
align([ |
||||
(",".join(cmd[1]), cmd[2]) |
||||
for cmd in commands |
||||
]) |
||||
])) |
||||
exit(0) |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
# -*- coding:utf-8 -*- |
||||
|
||||
from .context import ctx |
||||
from .debug import debug |
||||
from .flags import Flags, align |
||||
from .i18n import I18n |
||||
from .store import kvLoad, kvStore |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
# -*- coding:utf-8 -*- |
||||
|
||||
class _ctx(): |
||||
def __init__(self): |
||||
self.debug = False |
||||
self.help = False |
||||
self.ctx = "." |
||||
|
||||
def __str__(self): |
||||
return "ctx[%s]" % ", ".join( |
||||
["%s=%s" % (arg, getattr(self, arg)) |
||||
for arg in dir(self) |
||||
if not callable(getattr(self, arg)) and not (len(arg) > 1 and arg[0] == "_") |
||||
] |
||||
) |
||||
|
||||
|
||||
ctx = _ctx() |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
# -*- coding:utf-8 -*- |
||||
|
||||
from .context import ctx |
||||
|
||||
|
||||
def debug(*args): |
||||
if ctx.debug: |
||||
print(" ".join(map(str, args))) |
@ -0,0 +1,216 @@
@@ -0,0 +1,216 @@
|
||||
# -*- coding:utf-8 -*- |
||||
|
||||
""" |
||||
Python2/3 兼容,输入参数处理 |
||||
""" |
||||
|
||||
import sys |
||||
|
||||
|
||||
class Flags: |
||||
""" |
||||
Flags 解析命令行参数类参数 |
||||
""" |
||||
|
||||
def __init__(self, usage="", argv=None): |
||||
""" |
||||
- usage: 最前面的帮助文档 |
||||
- argv: 参数列表 list(str) |
||||
""" |
||||
self.argv = sys.argv[1:] if argv == None else argv |
||||
|
||||
self.flags = [] |
||||
self.flagMap = {} |
||||
self.usage = usage |
||||
self.shortOpt = {} |
||||
|
||||
self.ret = {} |
||||
self.parsed = False |
||||
|
||||
def addFlag(self, flag="", help="", hasValue=False): |
||||
""" |
||||
添加一个参数选项 |
||||
- flag: 用于识别的选项,如 ["-h", "--help"] |
||||
- help: 对应的帮助说明 |
||||
- hasValue: 是否要将后一个参数作为当前参数的值 |
||||
""" |
||||
if type(flag) != list: |
||||
flag = [flag] |
||||
|
||||
fid = len(self.flags) |
||||
temp = [] |
||||
for f in flag: |
||||
if type(f) == str and f != "": |
||||
temp.append(f) |
||||
self.flagMap[f] = fid |
||||
if len(f) == 2 and f[0] == "-": |
||||
self.shortOpt[f[1]] = fid |
||||
self.flags.append({ |
||||
"flag": temp, |
||||
"help": help, |
||||
"hasValue": hasValue, |
||||
"id": fid, |
||||
}) |
||||
|
||||
def parse(self): |
||||
""" |
||||
解析参数 |
||||
""" |
||||
if self.parsed: |
||||
return self.ret |
||||
|
||||
argv = self.argv |
||||
restArgv = [] |
||||
ret = {} |
||||
skip = False |
||||
|
||||
for idx, arg in enumerate(argv): |
||||
if skip: |
||||
skip = False |
||||
continue |
||||
|
||||
if arg in self.flagMap: |
||||
# 如果存在对应选项,则进行解析 |
||||
fid = self.flagMap[arg] |
||||
flag = self.flags[fid] |
||||
value = True |
||||
if flag.get("hasValue", False) and idx + 1 < len(argv): |
||||
# 如果存在值,则将下一个参数跳过 |
||||
value = argv[idx + 1] |
||||
skip = True |
||||
if fid not in ret: |
||||
ret[fid] = [] |
||||
ret[fid].append(value) |
||||
elif len(arg) > 2 and arg[0] == "-" and arg[1] != "-": |
||||
# 如果是形如 -it 的选项,则尝试将其解析为 -i 和 -t |
||||
args = list(arg[1:]) |
||||
fids = [ |
||||
self.flagMap.get("-%s" % arg, -1) |
||||
for arg in args |
||||
] |
||||
if len([fid for fid in fids if fid == -1]) > 0: |
||||
restArgv.append(arg) |
||||
else: |
||||
for fid in fids: |
||||
ret[fid].append(True) |
||||
else: |
||||
# 未知选项 |
||||
restArgv.append(arg) |
||||
|
||||
for key, value in ret.items(): |
||||
flag = self.flags[key] |
||||
for f in flag.get("flag", []): |
||||
self.ret[f] = value |
||||
self.ret["_"] = restArgv |
||||
self.parsed = True |
||||
return self.ret |
||||
|
||||
def help(self): |
||||
""" |
||||
返回帮助文档 |
||||
""" |
||||
line = [ |
||||
( |
||||
" ".join(flag.get("flag", [])) + |
||||
(" [value]" if flag.get("hasValue", False) else ""), |
||||
flag["help"], |
||||
) |
||||
for flag in self.flags |
||||
] |
||||
|
||||
result = [ |
||||
sys.argv[0] if len(self.usage) == 0 else self.usage, |
||||
"", |
||||
"options:", |
||||
align(line) |
||||
] |
||||
|
||||
return "\n".join(result) |
||||
|
||||
def showHelp(self): |
||||
""" |
||||
显示帮助文档并退出 |
||||
""" |
||||
print(self.help()) |
||||
exit(0) |
||||
|
||||
def list(self, key): |
||||
""" |
||||
返回指定选项列表 |
||||
""" |
||||
self.parse() |
||||
return self.ret.get(key, []) |
||||
|
||||
def count(self, key): |
||||
""" |
||||
返回指定选项出现的次数 |
||||
""" |
||||
return len(self.list(key)) |
||||
|
||||
def bool(self, key): |
||||
""" |
||||
返回是否出现过指定选项 |
||||
""" |
||||
return self.count(key) > 0 |
||||
|
||||
def string(self, key): |
||||
""" |
||||
返回指定选项的值(第一个) |
||||
""" |
||||
lst = self.list(key) |
||||
return lst[0] if len(lst) > 0 else "" |
||||
|
||||
def int(self, key, default=0): |
||||
""" |
||||
返回指定选项的值(第一个),并转换为整数 |
||||
""" |
||||
try: |
||||
n = int(self.string(key)) |
||||
except: |
||||
n = default |
||||
return n |
||||
|
||||
def restArgs(self): |
||||
""" |
||||
返回解析失败的参数 |
||||
""" |
||||
return self.list("_") |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
flags = Flags( |
||||
"%s -v -v -v -b -vb -f 1.txt -f 2.txt lovelive super star" % sys.argv[0] |
||||
) |
||||
flags.addFlag( |
||||
["-h", "--help"], |
||||
"show help", |
||||
) |
||||
flags.addFlag( |
||||
["-v", "--verbose"], |
||||
"verbose", |
||||
) |
||||
flags.addFlag( |
||||
["-f", "--file"], |
||||
"open files", |
||||
True |
||||
) |
||||
flags.addFlag( |
||||
["-b", "--base64"], |
||||
"base64", |
||||
) |
||||
|
||||
print(flags.parse()) |
||||
flags.showHelp() |
||||
print("never print") |
||||
|
||||
|
||||
def align(text, tab=4): |
||||
if len(text) == 0: |
||||
return "" |
||||
tab = tab*" " |
||||
maxLen = max([len(x[0]) for x in text]) |
||||
result = [ |
||||
tab + x[0].ljust(maxLen) + tab + " ".join(x[1:]) |
||||
for x in text |
||||
] |
||||
return "\n".join(result) |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
import os |
||||
|
||||
|
||||
class I18n: |
||||
def __init__(self, text_list, default_language=None): |
||||
if default_language == None: |
||||
default_language = os.environ.get( |
||||
'LANG', "en_US" |
||||
).split(".")[0] |
||||
self.default_language = default_language |
||||
self.texts = text_list |
||||
|
||||
def getLanguage(self, language=None): |
||||
if language == None: |
||||
language = self.default_language |
||||
return self.texts.get(self.default_language, {}) |
||||
|
||||
def get(self, key, default_text="", language=None): |
||||
return self.getLanguage(language).get(key, default_text) |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
from .context import ctx |
||||
|
||||
import os |
||||
|
||||
|
||||
def kvStore(key, value): |
||||
with open(os.path.join(ctx.root, 'store', key), "w") as f: |
||||
f.write(value) |
||||
|
||||
|
||||
def kvLoad(key, default=""): |
||||
filepath = os.path.join(ctx.root, 'store', key) |
||||
if os.path.exists(filepath): |
||||
with open(filepath, "r") as f: |
||||
return f.read() |
||||
else: |
||||
return default |
Loading…
Reference in new issue