Download app/tars_shell.py from OpenTARS/openTARS: direct link, hf CLI and curl.
- Browser
- Download file 24.1 kB
-
https://huggingface.co/OpenTARS/openTARS/resolve/main/app/tars_shell.py
- Command line
-
hf download hf://OpenTARS/openTARS/app/tars_shell.py
-
curl -L -o tars_shell.py https://huggingface.co/OpenTARS/openTARS/resolve/main/app/tars_shell.py
24.1 kB
| """5.2 (F5): um mini-shell em Python para os comandos de bash que o Gargantua aprendeu, onde não há bash (Windows) ou | |
| onde o comando não existe (macOS: `free`, `gio trash`). Só biblioteca padrão (e psutil para o `free`). | |
| Os dados de treino (20/10, 2.408 chamadas de execute_terminal) são quase todos destas formas, e é o que este arquivo cobre: | |
| echo/printf 'texto' > arquivo (e >>) mkdir -p pasta && echo ... > pasta/arq ls -1 pasta | wc -l | |
| mv a b rm -rf pasta touch arq free -h cat arq wc -l arq find pasta -name x | |
| com `~`, `${HOME}`, `$HOME`, `$(xdg-user-dir DOCUMENTS)`, aspas e espaços escapados (`Área\\ de\\ Trabalho`). | |
| A saída imita a do GNU (é o que o modelo viu no treino). As regras de comando perigoso continuam valendo antes daqui, | |
| porque leem o mesmo texto de bash. | |
| suportado(comando) -> bool todo comando da linha está aqui | |
| executar(comando, cwd=None) -> {"sucesso", "codigo", "stdout", "stderr", "mensagem"} (o formato do executar_terminal) | |
| """ | |
| import datetime as dt | |
| import fnmatch | |
| import glob | |
| import os | |
| import re | |
| import shutil | |
| from pathlib import Path | |
| import tars_plataforma as plataforma | |
| OPERADORES = ("&&", "||", ";", "|", ">>", ">", "2>&1", "2>") | |
| PASTAS_XDG = {"DESKTOP": "Desktop", "DOCUMENTS": "Documents", "DOWNLOAD": "Downloads", "MUSIC": "Music", | |
| "PICTURES": "Pictures", "VIDEOS": "Videos"} | |
| class ErroDeShell(Exception): | |
| pass | |
| # ------------------------------------------------------------ | |
| # Leitura da linha: palavras (com o que estava entre aspas) e operadores | |
| # ------------------------------------------------------------ | |
| class Palavra(str): | |
| """Uma palavra já sem aspas; `glob` diz se tinha * ou ? fora de aspas (só essas se expandem).""" | |
| glob = False | |
| def _variavel(nome): | |
| if nome == "HOME": | |
| return str(plataforma.pastas()["casa"]) | |
| if nome == "USER": | |
| return plataforma.pastas()["casa"].name | |
| return os.environ.get(nome, "") | |
| def _substituir_comando(texto): | |
| """Só o que aparece no treino: $(xdg-user-dir NOME) e $(pwd).""" | |
| m = re.fullmatch(r"\s*xdg-user-dir\s+([A-Z]+)\s*", texto) | |
| if m: | |
| return str(pasta_do_usuario(m.group(1))) | |
| if texto.strip() == "pwd": | |
| return os.getcwd() | |
| raise ErroDeShell(f"command substitution not supported: $({texto})") | |
| def pasta_do_usuario(chave): | |
| """Documents, Desktop, Downloads...: no Linux pelo user-dirs.dirs; fora dele, a pasta de nome em inglês.""" | |
| casa = plataforma.pastas()["casa"] | |
| if plataforma.sistema() == "linux": | |
| try: | |
| config = Path(os.environ.get("XDG_CONFIG_HOME") or casa / ".config") | |
| texto = (config / "user-dirs.dirs").read_text(encoding="utf-8") | |
| m = re.search(rf'^XDG_{chave}_DIR="(.+)"', texto, re.M) | |
| if m: | |
| return Path(m.group(1).replace("$HOME", str(casa))) | |
| except OSError: | |
| pass | |
| return casa / PASTAS_XDG.get(chave, chave.title()) | |
| def tokens(linha): | |
| """Lista de Palavra e de operadores (str). Levanta ErroDeShell com aspas abertas.""" | |
| saida, atual, tem, i, n = [], [], False, 0, len(linha) | |
| glob_fora = False | |
| def fechar(): | |
| nonlocal atual, tem, glob_fora | |
| if tem: | |
| p = Palavra("".join(atual)) | |
| p.glob = glob_fora | |
| saida.append(p) | |
| atual, tem, glob_fora = [], False, False | |
| while i < n: | |
| c = linha[i] | |
| if c in " \t\n": | |
| fechar() | |
| i += 1 | |
| continue | |
| op = next((o for o in OPERADORES if linha.startswith(o, i)), None) | |
| if op and not (op == "2>" and tem) and not (op == "2>&1" and tem): | |
| if op in (">", ">>", "2>") and tem and atual == ["2"]: | |
| atual, tem = [], False | |
| op = "2>" | |
| fechar() | |
| saida.append(op) | |
| i += len(op) | |
| continue | |
| if c == "\\" and i + 1 < n: | |
| atual.append(linha[i + 1]) | |
| tem = True | |
| i += 2 | |
| continue | |
| if c == "'": | |
| fim = linha.find("'", i + 1) | |
| if fim < 0: | |
| raise ErroDeShell("unterminated single quote") | |
| atual.append(linha[i + 1:fim]) | |
| tem = True | |
| i = fim + 1 | |
| continue | |
| if c == '"': | |
| i += 1 | |
| while i < n and linha[i] != '"': | |
| if linha[i] == "\\" and i + 1 < n and linha[i + 1] in '"\\$`': | |
| atual.append(linha[i + 1]) | |
| i += 2 | |
| continue | |
| if linha[i] == "$": | |
| texto, i = _ler_dolar(linha, i) | |
| atual.append(texto) | |
| continue | |
| atual.append(linha[i]) | |
| i += 1 | |
| if i >= n: | |
| raise ErroDeShell("unterminated double quote") | |
| tem = True | |
| i += 1 | |
| continue | |
| if c == "$": | |
| texto, i = _ler_dolar(linha, i) | |
| atual.append(texto) | |
| tem = True | |
| continue | |
| if c == "~" and not tem and (i + 1 == n or linha[i + 1] in "/ \t;|&>"): | |
| atual.append(str(plataforma.pastas()["casa"])) | |
| tem = True | |
| i += 1 | |
| continue | |
| if c in "*?[": | |
| glob_fora = True | |
| if c in "`<(){}" and not (c in "{}" and tem): | |
| raise ErroDeShell(f"unsupported shell syntax: {c}") | |
| atual.append(c) | |
| tem = True | |
| i += 1 | |
| fechar() | |
| return saida | |
| def _ler_dolar(linha, i): | |
| """$VAR, ${VAR} ou $(comando) a partir de linha[i] == '$'. Devolve (texto, nova posição).""" | |
| if linha.startswith("$(", i): | |
| fim = linha.find(")", i) | |
| if fim < 0: | |
| raise ErroDeShell("unterminated $(") | |
| return _substituir_comando(linha[i + 2:fim]), fim + 1 | |
| if linha.startswith("${", i): | |
| fim = linha.find("}", i) | |
| if fim < 0: | |
| raise ErroDeShell("unterminated ${") | |
| return _variavel(linha[i + 2:fim]), fim + 1 | |
| m = re.match(r"\$([A-Za-z_]\w*)", linha[i:]) | |
| if m: | |
| return _variavel(m.group(1)), i + m.end() | |
| return "$", i + 1 | |
| def _expandir(palavra, cwd): | |
| if not palavra.glob: | |
| return [str(palavra)] | |
| caminho = palavra if os.path.isabs(palavra) else os.path.join(cwd, palavra) | |
| achados = sorted(glob.glob(caminho), key=str.lower) | |
| if not achados: | |
| return [str(palavra)] | |
| return achados if os.path.isabs(palavra) else [os.path.relpath(a, cwd) for a in achados] | |
| def _windows_caminhos(linha): | |
| r"""C:\Users\sam\x fora de aspas viraria C:Userssamx no bash; no Windows, as barras viram /.""" | |
| if plataforma.sistema() != "windows": | |
| return linha | |
| return re.sub(r"(?<![\w\\])([A-Za-z]:\\[^\s\"';|&<>]*)", lambda m: m.group(1).replace("\\", "/"), linha) | |
| # ------------------------------------------------------------ | |
| # A linha em comandos: [(sequência, [pipeline, ...]), ...]; pipeline = [(argv, redirecionamentos)] | |
| # ------------------------------------------------------------ | |
| def _analisar(linha, cwd): | |
| lista, pipeline, argv, redir, ligacao = [], [], [], [], ";" | |
| toks = tokens(_windows_caminhos(linha)) | |
| i = 0 | |
| while i < len(toks): | |
| t = toks[i] | |
| if isinstance(t, Palavra): | |
| argv.extend(_expandir(t, cwd)) | |
| elif t in (">", ">>", "2>"): | |
| if i + 1 >= len(toks) or not isinstance(toks[i + 1], Palavra): | |
| raise ErroDeShell(f"syntax error near {t}") | |
| redir.append((t, str(toks[i + 1]))) | |
| i += 1 | |
| elif t == "2>&1": | |
| redir.append(("2>&1", None)) | |
| elif t == "|": | |
| pipeline.append((argv, redir)) | |
| argv, redir = [], [] | |
| else: # && || ; | |
| pipeline.append((argv, redir)) | |
| lista.append((ligacao, pipeline)) | |
| pipeline, argv, redir, ligacao = [], [], [], t | |
| i += 1 | |
| if argv or redir or pipeline: | |
| pipeline.append((argv, redir)) | |
| lista.append((ligacao, pipeline)) | |
| for _lig, pipe in lista: | |
| for argv, _r in pipe: | |
| if not argv: | |
| raise ErroDeShell("syntax error: empty command") | |
| return lista | |
| def suportado(comando): | |
| try: | |
| return all(argv[0] in COMANDOS for _l, pipe in _analisar(comando, os.getcwd()) for argv, _r in pipe) | |
| except ErroDeShell: | |
| return False | |
| # ------------------------------------------------------------ | |
| # Os comandos: cada um recebe (args, entrada, ctx) e devolve (código, saída, erro) | |
| # ------------------------------------------------------------ | |
| class Contexto: | |
| def __init__(self, cwd): | |
| self.cwd = cwd | |
| def caminho(self, p): | |
| p = os.path.expanduser(p) if p.startswith("~") else p | |
| return Path(p if os.path.isabs(p) else os.path.join(self.cwd, p)) | |
| def _opcoes(args, conhecidas=""): | |
| """Separa -abc (letras) dos operandos; '--' encerra as opções.""" | |
| letras, resto, fim = set(), [], False | |
| for a in args: | |
| if not fim and a == "--": | |
| fim = True | |
| elif not fim and a.startswith("-") and len(a) > 1: | |
| letras.update(a[1:] if not a.startswith("--") else [a]) | |
| else: | |
| resto.append(a) | |
| return letras, resto | |
| def c_echo(args, entrada, ctx): | |
| n = bool(args) and args[0] == "-n" | |
| e = bool(args) and args[0] in ("-e", "-ne", "-en") | |
| if args and args[0] in ("-n", "-e", "-ne", "-en"): | |
| n = "n" in args[0] | |
| args = args[1:] | |
| texto = " ".join(args) | |
| if e: | |
| texto = texto.encode("utf-8").decode("unicode_escape").encode("latin-1").decode("utf-8", "replace") | |
| return 0, texto + ("" if n else "\n"), "" | |
| def c_printf(args, entrada, ctx): | |
| if not args: | |
| return 1, "", "printf: usage: printf format [arguments]\n" | |
| formato, valores = args[0], list(args[1:]) | |
| formato = formato.replace("\\n", "\n").replace("\\t", "\t") | |
| saida = re.sub(r"%[sd]", lambda _m: valores.pop(0) if valores else "", formato).replace("%%", "%") | |
| return 0, saida, "" | |
| def c_mkdir(args, entrada, ctx): | |
| letras, pastas = _opcoes(args) | |
| erros, codigo = "", 0 | |
| for p in pastas: | |
| try: | |
| ctx.caminho(p).mkdir(parents="p" in letras, exist_ok="p" in letras) | |
| except FileExistsError: | |
| erros, codigo = erros + f"mkdir: cannot create directory '{p}': File exists\n", 1 | |
| except FileNotFoundError: | |
| erros, codigo = erros + f"mkdir: cannot create directory '{p}': No such file or directory\n", 1 | |
| return codigo, "", erros | |
| def c_touch(args, entrada, ctx): | |
| _l, arquivos = _opcoes(args) | |
| for a in arquivos: | |
| try: | |
| ctx.caminho(a).touch() | |
| except OSError as e: | |
| return 1, "", f"touch: cannot touch '{a}': {e.strerror}\n" | |
| return 0, "", "" | |
| def c_cat(args, entrada, ctx): | |
| _l, arquivos = _opcoes(args) | |
| if not arquivos: | |
| return 0, entrada, "" | |
| saida = [] | |
| for a in arquivos: | |
| try: | |
| saida.append(ctx.caminho(a).read_text(encoding="utf-8", errors="replace")) | |
| except IsADirectoryError: | |
| return 1, "".join(saida), f"cat: {a}: Is a directory\n" | |
| except OSError: | |
| return 1, "".join(saida), f"cat: {a}: No such file or directory\n" | |
| return 0, "".join(saida), "" | |
| def c_ls(args, entrada, ctx): | |
| letras, alvos = _opcoes(args) | |
| alvos = alvos or ["."] | |
| saida, erros, codigo = [], "", 0 | |
| for alvo in alvos: | |
| p = ctx.caminho(alvo) | |
| if not p.exists(): | |
| erros, codigo = erros + f"ls: cannot access '{alvo}': No such file or directory\n", 2 | |
| continue | |
| nomes = [p] if p.is_file() else sorted(p.iterdir(), key=lambda x: x.name.lower().lstrip(".")) | |
| if p.is_dir() and not ({"a", "A"} & letras): | |
| nomes = [x for x in nomes if not x.name.startswith(".")] | |
| if len(alvos) > 1 and p.is_dir(): | |
| saida.append(f"{alvo}:") | |
| for x in nomes: | |
| nome = alvo if x is p else x.name | |
| if "l" in letras: | |
| info = x.stat() | |
| tipo = "d" if x.is_dir() else "-" | |
| data = dt.datetime.fromtimestamp(info.st_mtime).strftime("%b %d %H:%M") | |
| tamanho = _humano(info.st_size) if "h" in letras else str(info.st_size) | |
| saida.append(f"{tipo}rw-r--r-- 1 {plataforma.pastas()['casa'].name} {tamanho:>6} {data} {nome}") | |
| else: | |
| saida.append(nome) | |
| return codigo, "".join(s + "\n" for s in saida), erros | |
| def c_mv(args, entrada, ctx): | |
| _l, ops = _opcoes(args) | |
| if len(ops) < 2: | |
| return 1, "", "mv: missing destination file operand\n" | |
| destino = ctx.caminho(ops[-1]) | |
| for origem in ops[:-1]: | |
| o = ctx.caminho(origem) | |
| if not o.exists(): | |
| return 1, "", f"mv: cannot stat '{origem}': No such file or directory\n" | |
| alvo = destino / o.name if destino.is_dir() else destino | |
| shutil.move(str(o), str(alvo)) | |
| return 0, "", "" | |
| def c_cp(args, entrada, ctx): | |
| letras, ops = _opcoes(args) | |
| if len(ops) < 2: | |
| return 1, "", "cp: missing destination file operand\n" | |
| destino = ctx.caminho(ops[-1]) | |
| for origem in ops[:-1]: | |
| o = ctx.caminho(origem) | |
| alvo = destino / o.name if destino.is_dir() else destino | |
| if o.is_dir(): | |
| if not ({"r", "R", "a"} & letras): | |
| return 1, "", f"cp: -r not specified; omitting directory '{origem}'\n" | |
| shutil.copytree(o, alvo, dirs_exist_ok=True) | |
| elif o.exists(): | |
| shutil.copy2(o, alvo) | |
| else: | |
| return 1, "", f"cp: cannot stat '{origem}': No such file or directory\n" | |
| return 0, "", "" | |
| def c_rm(args, entrada, ctx): | |
| letras, alvos = _opcoes(args) | |
| for a in alvos: | |
| p = ctx.caminho(a) | |
| if not p.exists() and not p.is_symlink(): | |
| if "f" not in letras: | |
| return 1, "", f"rm: cannot remove '{a}': No such file or directory\n" | |
| continue | |
| if p.is_dir() and not p.is_symlink(): | |
| if not ({"r", "R"} & letras): | |
| return 1, "", f"rm: cannot remove '{a}': Is a directory\n" | |
| shutil.rmtree(p) | |
| else: | |
| p.unlink() | |
| return 0, "", "" | |
| def c_rmdir(args, entrada, ctx): | |
| _l, alvos = _opcoes(args) | |
| for a in alvos: | |
| try: | |
| ctx.caminho(a).rmdir() | |
| except OSError: | |
| return 1, "", f"rmdir: failed to remove '{a}': Directory not empty\n" | |
| return 0, "", "" | |
| def _contar(texto): | |
| return texto.count("\n"), len(texto.split()), len(texto.encode("utf-8")) | |
| def c_wc(args, entrada, ctx): | |
| letras, arquivos = _opcoes(args) | |
| quais = [i for i, l in enumerate("lwc") if l in letras] or [0, 1, 2] | |
| linhas = [] | |
| if not arquivos: | |
| valores = _contar(entrada) | |
| return 0, " ".join(str(valores[i]) for i in quais) + "\n" if len(quais) == 1 else \ | |
| " ".join(f"{valores[i]:>7}" for i in quais) + "\n", "" | |
| totais = [0, 0, 0] | |
| for a in arquivos: | |
| try: | |
| valores = _contar(ctx.caminho(a).read_text(encoding="utf-8", errors="replace")) | |
| except OSError: | |
| return 1, "", f"wc: {a}: No such file or directory\n" | |
| totais = [x + y for x, y in zip(totais, valores)] | |
| linhas.append(([valores[i] for i in quais], a)) | |
| if len(arquivos) > 1: | |
| linhas.append(([totais[i] for i in quais], "total")) | |
| # o GNU alinha pela quantidade de dígitos do total de bytes quando há mais de um arquivo ou de uma coluna | |
| largura = len(str(max(totais[2], 1))) if len(linhas) > 1 or len(quais) > 1 else 0 | |
| return 0, "".join(" ".join(f"{v:>{largura}}" for v in vs) + f" {nome}\n" for vs, nome in linhas), "" | |
| def _humano(n): | |
| for unidade in ("B", "Ki", "Mi", "Gi", "Ti"): | |
| if n < 1024 or unidade == "Ti": | |
| if unidade == "B": | |
| return f"{n}B" | |
| return f"{n:.1f}{unidade}" if n < 10 else f"{int(n)}{unidade}" # o GNU corta, não arredonda | |
| n /= 1024 | |
| def c_free(args, entrada, ctx): | |
| import psutil | |
| letras, _r = _opcoes(args) | |
| ram, swap = psutil.virtual_memory(), psutil.swap_memory() | |
| if "h" in letras: | |
| f = _humano | |
| else: | |
| div = 1024 ** (3 if "g" in letras else 2 if "m" in letras else 1) | |
| f = lambda b: str(int(b // div)) # noqa: E731 | |
| compartilhado = getattr(ram, "shared", 0) | |
| cache = getattr(ram, "buffers", 0) + getattr(ram, "cached", 0) | |
| usado = ram.total - ram.available | |
| linhas = [f"{'':8}{'total':>12}{'used':>12}{'free':>12}{'shared':>12}{'buff/cache':>12}{'available':>12}", | |
| f"{'Mem:':8}{f(ram.total):>12}{f(usado):>12}{f(ram.free):>12}{f(compartilhado):>12}{f(cache):>12}{f(ram.available):>12}", | |
| f"{'Swap:':8}{f(swap.total):>12}{f(swap.used):>12}{f(swap.free):>12}"] | |
| return 0, "\n".join(linhas) + "\n", "" | |
| def c_find(args, entrada, ctx): | |
| if not args or args[0].startswith("-"): | |
| args = ["."] + list(args) | |
| raiz, resto = args[0], args[1:] | |
| padrao, tipo, sem_caixa = None, None, False | |
| i = 0 | |
| while i < len(resto): | |
| if resto[i] in ("-name", "-iname") and i + 1 < len(resto): | |
| padrao, sem_caixa = resto[i + 1], resto[i] == "-iname" | |
| i += 2 | |
| elif resto[i] == "-type" and i + 1 < len(resto): | |
| tipo = resto[i + 1] | |
| i += 2 | |
| elif resto[i] in ("-maxdepth",) and i + 1 < len(resto): | |
| i += 2 | |
| else: | |
| return 1, "", f"find: unknown predicate '{resto[i]}'\n" | |
| base = ctx.caminho(raiz) | |
| if not base.exists(): | |
| return 1, "", f"find: '{raiz}': No such file or directory\n" | |
| achados = [] | |
| for pasta, dirs, arqs in os.walk(base): | |
| dirs.sort(key=str.lower) | |
| for nome in [*dirs, *sorted(arqs, key=str.lower)]: | |
| completo = os.path.join(pasta, nome) | |
| if tipo == "f" and not os.path.isfile(completo) or tipo == "d" and not os.path.isdir(completo): | |
| continue | |
| if padrao and not (fnmatch.fnmatch(nome.lower(), padrao.lower()) if sem_caixa else fnmatch.fnmatchcase(nome, padrao)): | |
| continue | |
| achados.append(completo if os.path.isabs(raiz) or raiz.startswith("~") else | |
| os.path.join(raiz, os.path.relpath(completo, base))) | |
| return 0, "".join(a + "\n" for a in achados), "" | |
| def c_head_tail(nome): | |
| def comando(args, entrada, ctx): | |
| n, arquivos, i = 10, [], 0 | |
| while i < len(args): | |
| if args[i] == "-n" and i + 1 < len(args): | |
| n, i = int(args[i + 1]), i + 2 | |
| elif re.fullmatch(r"-\d+", args[i]): | |
| n, i = int(args[i][1:]), i + 1 | |
| else: | |
| arquivos.append(args[i]) | |
| i += 1 | |
| texto = entrada | |
| if arquivos: | |
| codigo, texto, erro = c_cat(arquivos, "", ctx) | |
| if codigo: | |
| return codigo, "", erro.replace("cat:", f"{nome}:") | |
| linhas = texto.splitlines(keepends=True) | |
| return 0, "".join(linhas[:n] if nome == "head" else linhas[-n:] if n else []), "" | |
| return comando | |
| def c_grep(args, entrada, ctx): | |
| letras, ops = _opcoes(args) | |
| if not ops: | |
| return 2, "", "grep: missing pattern\n" | |
| padrao = re.compile(ops[0], re.I if "i" in letras else 0) | |
| fontes = [(None, entrada)] if len(ops) == 1 else [] | |
| for a in ops[1:]: | |
| codigo, texto, erro = c_cat([a], "", ctx) | |
| if codigo: | |
| return 2, "", erro.replace("cat:", "grep:") | |
| fontes.append((a, texto)) | |
| saida, achou = [], 0 | |
| for nome, texto in fontes: | |
| for k, linha in enumerate(texto.splitlines(), 1): | |
| if bool(padrao.search(linha)) != ("v" in letras): | |
| achou += 1 | |
| prefixo = (f"{nome}:" if nome and len(fontes) > 1 else "") + (f"{k}:" if "n" in letras else "") | |
| saida.append(prefixo + linha) | |
| if "c" in letras: | |
| return (0 if achou else 1), f"{achou}\n", "" | |
| return (0 if achou else 1), "".join(s + "\n" for s in saida), "" | |
| def c_sort(args, entrada, ctx): | |
| letras, _o = _opcoes(args) | |
| linhas = sorted(entrada.splitlines(), key=str.lower, reverse="r" in letras) | |
| return 0, "".join(l + "\n" for l in linhas), "" | |
| def c_pwd(args, entrada, ctx): | |
| return 0, ctx.cwd + "\n", "" | |
| def c_cd(args, entrada, ctx): | |
| destino = ctx.caminho(args[0]) if args else plataforma.pastas()["casa"] | |
| if not destino.is_dir(): | |
| return 1, "", f"cd: {args[0] if args else ''}: No such file or directory\n" | |
| ctx.cwd = str(destino) | |
| return 0, "", "" | |
| def c_whoami(args, entrada, ctx): | |
| return 0, plataforma.pastas()["casa"].name + "\n", "" | |
| def c_date(args, entrada, ctx): | |
| return 0, dt.datetime.now().astimezone().strftime("%a %b %d %H:%M:%S %Z %Y") + "\n", "" | |
| def c_gio(args, entrada, ctx): | |
| """gio trash (o que o prompt ensina para apagar) e gio open.""" | |
| if args[:1] == ["trash"]: | |
| for a in [x for x in args[1:] if x != "--"]: | |
| try: | |
| plataforma.lixeira(str(ctx.caminho(a))) | |
| except OSError as e: | |
| return 1, "", f"gio: {a}: {e}\n" | |
| return 0, "", "" | |
| if args[:1] == ["open"] and len(args) > 1: | |
| plataforma.abrir(str(ctx.caminho(args[1])) if not re.match(r"\w+://", args[1]) else args[1]) | |
| return 0, "", "" | |
| return 1, "", "gio: only 'gio trash' and 'gio open' are available here\n" | |
| def c_xdg_open(args, entrada, ctx): | |
| if not args: | |
| return 1, "", "xdg-open: missing argument\n" | |
| plataforma.abrir(args[0] if re.match(r"\w+://", args[0]) else str(ctx.caminho(args[0]))) | |
| return 0, "", "" | |
| def c_true(args, entrada, ctx): | |
| return 0, "", "" | |
| COMANDOS = {"echo": c_echo, "printf": c_printf, "mkdir": c_mkdir, "touch": c_touch, "cat": c_cat, "ls": c_ls, "mv": c_mv, | |
| "cp": c_cp, "rm": c_rm, "rmdir": c_rmdir, "wc": c_wc, "free": c_free, "find": c_find, "head": c_head_tail("head"), | |
| "tail": c_head_tail("tail"), "grep": c_grep, "sort": c_sort, "pwd": c_pwd, "cd": c_cd, "whoami": c_whoami, | |
| "date": c_date, "gio": c_gio, "xdg-open": c_xdg_open, "true": c_true} | |
| # ------------------------------------------------------------ | |
| # Rodar | |
| # ------------------------------------------------------------ | |
| def _rodar_pipeline(pipe, ctx): | |
| entrada, codigo, erros = "", 0, "" | |
| for argv, redir in pipe: | |
| try: | |
| codigo, saida, erro = COMANDOS[argv[0]](argv[1:], entrada, ctx) | |
| except KeyError: | |
| codigo, saida, erro = 127, "", f"{argv[0]}: command not found\n" | |
| except OSError as e: | |
| codigo, saida, erro = 1, "", f"{argv[0]}: {e.strerror or e}\n" | |
| for tipo, alvo in redir: | |
| if tipo == "2>&1": | |
| saida, erro = saida + erro, "" | |
| elif tipo == "2>": | |
| erro = "" if alvo in ("/dev/null", "nul", "NUL") else erro | |
| elif alvo in ("/dev/null", "nul", "NUL"): | |
| saida = "" | |
| else: | |
| with open(ctx.caminho(alvo), "a" if tipo == ">>" else "w", encoding="utf-8", newline="\n") as f: | |
| f.write(saida) | |
| saida = "" | |
| erros += erro | |
| entrada = saida | |
| return codigo, entrada, erros | |
| def executar(comando, cwd=None): | |
| """Roda a linha e devolve o mesmo dicionário que o executar_terminal do núcleo.""" | |
| ctx = Contexto(cwd or os.getcwd()) | |
| try: | |
| lista = _analisar(comando, ctx.cwd) | |
| except ErroDeShell as e: | |
| return {"sucesso": False, "codigo": 2, "stdout": "", "stderr": f"{e}\n", "mensagem": "Command finished with an error."} | |
| codigo, saidas, erros = 0, [], "" | |
| for ligacao, pipe in lista: | |
| if (ligacao == "&&" and codigo != 0) or (ligacao == "||" and codigo == 0): | |
| continue | |
| codigo, saida, erro = _rodar_pipeline(pipe, ctx) | |
| saidas.append(saida) | |
| erros += erro | |
| return {"sucesso": codigo == 0, "codigo": codigo, "stdout": "".join(saidas), "stderr": erros, | |
| "mensagem": "Command finished." if codigo == 0 else "Command finished with an error."} | |