Files
sfc/cli/src/gsc_cli.erl
T
2026-06-02 16:51:05 -07:00

277 lines
6.8 KiB
Erlang

%%% @doc
%%% GSC CLI: explorer/harness for sfc iteration
%%% @end
-module(gsc_cli).
-vsn("0.1.0").
-author("Peter Harpending <peterharpending@qpq.swiss>").
-copyright("Peter Harpending <peterharpending@qpq.swiss>").
-license("GPL-3.0-only").
-export([
tokens/1,
so_tokens/1,
gso_tokens/1
]).
-export([start/1]).
-include("$gsc_include/gsc.hrl").
-include("ansi.hrl").
do_help() ->
io:format("~ts", [help_screen()]).
help_screen() ->
["you can't help people who refuse to help themsleves\n"].
-spec start(ArgV) -> ok
when ArgV :: [string()].
start([]) ->
do_eshell(),
ok;
start(["shell"]) ->
do_eshell(),
ok;
start(["eshell"]) ->
do_eshell(),
ok;
start(ArgV) ->
%io:format("ArgV: ~p~n", [ArgV]),
do(ArgV),
zx:silent_stop().
do(["list"]) ->
do_tlist();
do(["list", "tests"]) ->
do_tlist();
do(["test"]) ->
do_tests();
do(["test" | Tests]) ->
do_tests(Tests);
do(["tests"]) ->
do_tests();
do(["run", "tests"]) ->
do_tests();
do(["tokenizers_agree", Foo]) ->
io:format("~p~n", [tokenizers_agree(Foo)]);
% slowly phasing out shitty names like lctokens
% tokens = native sfc token representation
do(["tokens", Foo]) -> do_tokens(Foo);
do(["color_tokens", Foo]) -> do_color_tokens(Foo);
do(["ctokens", Foo]) -> do_color_tokens(Foo);
do(["colour_tokens" | _]) -> do_doi();
% so_tokens = so_scan tokens
do(["so", "tokens", Foo]) -> do_so_tokens(Foo);
do(["so_tokens", Foo]) -> do_so_tokens(Foo);
% gso_tokens = our mockery
do(["gso", "tokens", Foo]) -> do_gso_tokens(Foo);
do(["gso_tokens", Foo]) -> do_gso_tokens(Foo);
% print source file to screen with token boundaries highlighted
% script utility
do(["rmm", Foo]) ->
do_rmm(Foo);
do(Args) ->
io:format("bad args: ~p~n", [Args]),
do_help().
do_doi() ->
FP = zx:get_home() ++ "/priv/doi.txt",
page_file(FP).
% thank you chatgpt
% os:cmd didnt do nuffin because that's for running
% stuff in the background and capturing the output, not
% for taking over the screen
page_file(FilePath) ->
Less = os:find_executable("less"),
case Less of
false -> cat_file(FilePath);
_ -> less_file(Less, FilePath)
end.
cat_file(FilePath) ->
{ok, Bytes} = file:read_file(FilePath),
io:format("~ts", [Bytes]).
less_file(Less, FilePath) ->
Port = open_port({spawn_executable, Less},
[{args, [FilePath]},
nouse_stdio, exit_status]),
receive
{Port, {exit_status, 0}} ->
ok;
{Port, {exit_status, N}} ->
error({less_exit_status, N});
{'EXIT', Port, Reason} ->
error(Reason)
end.
do_tests() ->
io:format("TestModules = ~p~n", [test_mods()]),
do_runall_tests().
do_runall_tests() ->
lists:foreach(fun run_mod_main/1, test_mods()).
do_tests(List) ->
lists:foreach(fun run_test/1, List).
% n
run_test(TestName) ->
% we have two candidate atoms
C1 = list_to_atom(TestName),
C2 = list_to_atom("gsc_test_" ++ TestName),
KnownMods = test_mods(),
IsC1 = lists:member(C1, KnownMods),
IsC2 = lists:member(C2, KnownMods),
if
IsC1 -> rmm(C1);
IsC2 -> rmm(C2);
true -> error({no_such_test, TestName})
end.
rmm(X) -> run_mod_main(X).
% KnownTests = test_mods(),
% TestMods = ensure_all_known([], List, KnownTests),
% lists:foreach(fun run_mod_main/1, TestMods).
%ensure_all_known(Acc, [], _) ->
% lists:sort(Acc);
%ensure_all_known(Acc, [T | Ts], Knowns) ->
% case lists:member(T, Knowns) of
%
% end.
test_mods() ->
known_modules_with_prefix("gsc_test").
known_modules_with_prefix(Pfx) ->
ModsZipBeamsZipLoaded = code:all_available(),
kmp(Pfx, ModsZipBeamsZipLoaded, []).
kmp(_Pfx, [], Acc) ->
lists:sort(Acc);
kmp(Pfx, [{ModStr, _BeamPath, _Loaded} | Rest], Acc) ->
case lists:prefix(Pfx, ModStr) of
false -> kmp(Pfx, Rest, Acc);
true -> kmp(Pfx, Rest, [list_to_atom(ModStr) | Acc])
end.
run_mod_main(Mod) ->
io:format("========================================\n"
"~p:main()\n"
"========================================\n",
[Mod]),
try
Mod:main()
catch
Err:ErrType:Trace ->
io:format("~p: ~p~n", [Err, ErrType]),
io:format("Trace:~n~p~n", [Trace])
end.
do_tlist() ->
lists:foreach(
fun(ModName) ->
io:format("~s~n", [ModName])
end,
test_mods()
).
-spec do_eshell() -> ok.
% @doc start an erlang shell
do_eshell() ->
io:format("Welcome to the GSC shell!~n", []),
case shell:start_interactive() of
ok -> ok;
{error, already_started} -> ok;
{error, Reason} -> error(Reason)
end.
tokenizers_agree(File) ->
gso_tokens(File) =:= so_tokens(File).
do_tokens(FilePath) ->
[io:format("~p~n", [Tk]) || Tk <- tokens(FilePath)].
do_so_tokens(FilePath) ->
[io:format("~p~n", [Tk]) || Tk <- so_tokens(FilePath)].
do_gso_tokens(FilePath) ->
[io:format("~p~n", [Tk]) || Tk <- gso_tokens(FilePath)].
% rmm = run module:main() with our context loaded
% useful for prototyping
do_rmm(FilePath) ->
case compile:file(FilePath) of
{ok, Mod} -> Mod:main();
Error -> error(Error)
end.
so_tokens(FilePath) ->
{ok, FileBytes} = file:read_file(FilePath),
FileStr = unicode:characters_to_nfc_list(FileBytes),
{ok, Tokens} = so_scan:scan(FileStr),
Tokens.
gso_tokens(FilePath) ->
{ok, FileBytes} = file:read_file(FilePath),
FileStr = unicode:characters_to_nfc_list(FileBytes),
{ok, Tokens} = gso_scan:scan(FileStr),
Tokens.
tokens(FilePath) ->
{ok, Tokens} = gsc:tokens_from_file(FilePath),
Tokens.
do_color_tokens(File) ->
case gsc:tokens_from_file(File) of
{ok, Tokens} ->
ColorizedSrcStr = colorize_tokens(chunk_color_wheel(), Tokens, ""),
Full = [?ANSI_INVERT, ColorizedSrcStr, ?ANSI_UNINVERT],
io:format("~s", [Full]);
Error ->
io:format("~p~n", [Error])
end.
chunk_color_wheel() ->
%[yellow, blue].
[red, green, yellow, blue, magenta, cyan].
colorize_tokens(Wheel, [T | Ts], Acc) ->
{Color, NewWheel} = rotate(Wheel),
NewAcc = [Acc, colorize_token_str(Color, T)],
colorize_tokens(NewWheel, Ts, NewAcc);
colorize_tokens(_, [], Acc) ->
Acc.
rotate([A | Rest]) ->
{A, Rest ++ [A]}.
colorize_token_str(Color, #tk{str = Str}) ->
{Pfx, Sfx} = color_fixes(Color),
[Pfx, Str, Sfx].
color_fixes(red) -> {?ANSI_FG_RED, ?ANSI_FG_RESET};
color_fixes(green) -> {?ANSI_FG_GREEN, ?ANSI_FG_RESET};
color_fixes(yellow) -> {?ANSI_FG_YELLOW, ?ANSI_FG_RESET};
color_fixes(blue) -> {?ANSI_FG_BLUE, ?ANSI_FG_RESET};
color_fixes(magenta) -> {?ANSI_FG_MAGENTA, ?ANSI_FG_RESET};
color_fixes(cyan) -> {?ANSI_FG_CYAN, ?ANSI_FG_RESET}.