%%% @doc %%% GSC CLI: explorer/harness for sfc iteration %%% @end -module(gsc_cli). -vsn("0.1.0"). -author("Peter Harpending "). -copyright("Peter Harpending "). -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}.