Browse Source

initial

master
OhYee 1 year ago
commit
92147b4180
  1. 1
      .gitignore
  2. 18
      README.md
  3. 1
      addon/.gitignore
  4. 9
      addon/__init__.py
  5. 20
      addon/base.py
  6. 210
      addon/colorful.py
  7. 65
      addon/store.py
  8. 5
      bin/o
  9. 74
      main.py
  10. 2
      store/.gitignore
  11. 7
      utils/__init__.py
  12. 18
      utils/context.py
  13. 8
      utils/debug.py
  14. 216
      utils/flags.py
  15. 19
      utils/i18n.py
  16. 17
      utils/store.py

1
.gitignore vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
*.pyc

18
README.md

@ -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: 存储变量到硬盘中

1
addon/.gitignore vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
*.local.py

9
addon/__init__.py

@ -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])

20
addon/base.py

@ -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 = []

210
addon/colorful.py

@ -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)

65
addon/store.py

@ -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)

5
bin/o

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
#!/bin/bash
export SHELL_FOLDER=$(cd "$(dirname "$(realpath "$0")")";pwd)
python $SHELL_FOLDER/../main.py $@

74
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)

2
store/.gitignore vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
*
!.gitignore

7
utils/__init__.py

@ -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

18
utils/context.py

@ -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()

8
utils/debug.py

@ -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)))

216
utils/flags.py

@ -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)

19
utils/i18n.py

@ -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)

17
utils/store.py

@ -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…
Cancel
Save