<commit_before>-module(shellbeam).

-include("magicbeam.hrl").

-export([test/0]).
-export([behaviour_info/1]).
-export([start_shell/2, start/0, spawn_shell/2, spawn_shell/0]).

test() ->
    application:start(sasl),
    application:start(magicbeam).

behaviour_info(callbacks) ->

    [
     {commands,0}
    ].

start() ->
    application:start(sasl), application:start(crypto), application:start(ssh), application:start(magicbeam),
    timer:sleep(2000),
    user_drv:start(['tty_sl -c -e', {shellbeam, spawn_shell, [[magicbeam_shell], list_to_atom(node()) ++ "OTP 4 LYFE"]}]).

spawn_shell() ->
    spawn_shell(
      magicbeam_util:appenv(shellbeam_modules, [magicbeam_shell]),
      magicbeam_util:appenv(shellbeam_prompt, "shellz")
     ).

spawn_shell(Modules, Prompt) ->
    spawn(fun() -> start_shell(Modules, Prompt) end).

start_shell(Modules, Prompt) when is_list(Modules), is_list(Prompt) ->
    io:format("Magicbeam Shell v~s~n", [erlang:system_info(version)]),
    handle_shell(0, scan_modules(Modules), Prompt).

handle_shell(I, Commands, Prompt) ->
    case io:get_line(colour(green, Prompt) ++ " " ++ colour(red, integer_to_list(I)) ++ " > ") of
        eof -> ok;
        {error, _} = E -> error_out("Unable to get_line -> ~p", [E]), handle_shell(I, Commands, Prompt);
        D when is_list(D) ->
            case string:tokens(string:strip(D, right, $\n), " ") of
                [] ->
                    handle_shell(I, Commands, Prompt);
                T when is_list(T) ->
                    case process_tokens(Commands, T) of
                        {processed, F, A} ->
                            normal_out(F, A),
                            handle_shell(I + 1, Commands, Prompt);
                        syntax ->
                            error_out("Syntax Error.~n" ++ p_syntax(Commands), []),
                            handle_shell(I + 1, Commands, Prompt);
                        {error, F, A} ->
                            error_out(F, A),
                            handle_shell(I + 1, Commands, Prompt);
                        exit ->
                            ok;
                        {subshell, M, P} ->
                            ok = handle_shell(0, scan_modules(M), P),
                            handle_shell(I + 1, Commands, Prompt)
                    end
            end
    end.


scan_modules(Modules) -> scan_modules(Modules, []).
scan_modules([], Commands) ->
    Commands;
scan_modules([Module | Tail], Commands) ->
    case {command_prefix(Module), Module:commands()} of
        {undefined, C} when is_list(C) ->
            scan_modules(Tail, C ++ Commands);
        {P, C} when is_list(P), is_list(C) ->
            Cm = lists:map(fun({T, H, F}) -> {P ++ T, H, F} end, C),
            scan_modules(Tail, Cm ++ Commands)
    end.

command_prefix(Module) ->
    case lists:member({prefix, 0}, Module:module_info(exports)) of
        false ->
            [];
        true ->
            case Module:prefix() of
                L when is_list(L) ->
                    L
            end
    end.

process_tokens(_, ["exit"]) ->
    exit;
process_tokens(C, ["help"]) ->
    {processed, "Help.~n" ++ p_syntax(C), []};
process_tokens([], _Tokens) -> syntax;
process_tokens([{[H | _], _, {subshell, Mods, _}} | _], [H|Tokens]) when length(Tokens) > 0 ->
    C = scan_modules(Mods),
    case process_tokens(C, Tokens) of
        syntax -> {error, "Syntax Error.~n" ++ p_syntax(C), []};
        T -> T
    end;
process_tokens([{Match, Help, C} = E| CTail], Tokens) ->
    case command_match(Match, Tokens) of
        false ->
            process_tokens(CTail, Tokens);
        syntax ->
            {error, "Syntax Error.~n" ++ p_syntax([E]), []};
        A when is_list(A) ->
            process_command(Help, C, A)
    end;
process_tokens([_ | CTail], Tokens) ->
    process_tokens(CTail, Tokens).

command_match(C, T) -> command_match(C, T, []).
command_match([], [], A) -> A;
command_match([H | MT], [H | TT], A) when is_list(H) ->
    command_match(MT, TT, A);
command_match([{_, atom} | MT], [H | TT], Ar) ->
    case catch list_to_existing_atom(H) of
        A when is_atom(A) -> command_match(MT, TT, Ar ++ [A]);
        {'EXIT',{badarg,[{erlang,list_to_existing_atom,[H]} | _]}} -> syntax
    end;
command_match([{_, bool} | MT], [H | TT], Ar) ->
    case catch list_to_existing_atom(string:to_lower(H)) of
        A when A == true; A == false -> command_match(MT, TT, Ar ++ [A]);
        A when is_atom(A) -> syntax
    end;<commit_msg>* handle multi-token subshells properly<commit_after>-module(shellbeam).

-include("magicbeam.hrl").

-export([test/0]).
-export([behaviour_info/1]).
-export([start_shell/2, start/0, spawn_shell/2, spawn_shell/0]).

test() ->
    application:start(sasl),
    application:start(magicbeam).

behaviour_info(callbacks) ->

    [
     {commands,0}
    ].

start() ->
    application:start(sasl), application:start(crypto), application:start(ssh), application:start(magicbeam),
    timer:sleep(2000),
    user_drv:start(['tty_sl -c -e', {shellbeam, spawn_shell, [[magicbeam_shell], list_to_atom(node()) ++ "OTP 4 LYFE"]}]).

spawn_shell() ->
    spawn_shell(
      magicbeam_util:appenv(shellbeam_modules, [magicbeam_shell]),
      magicbeam_util:appenv(shellbeam_prompt, "shellz")
     ).

spawn_shell(Modules, Prompt) ->
    spawn(fun() -> start_shell(Modules, Prompt) end).

start_shell(Modules, Prompt) when is_list(Modules), is_list(Prompt) ->
    io:format("Magicbeam Shell v~s~n", [erlang:system_info(version)]),
    handle_shell(0, scan_modules(Modules), Prompt).

handle_shell(I, Commands, Prompt) ->
    case io:get_line(colour(green, Prompt) ++ " " ++ colour(red, integer_to_list(I)) ++ " > ") of
        eof -> ok;
        {error, _} = E -> error_out("Unable to get_line -> ~p", [E]), handle_shell(I, Commands, Prompt);
        D when is_list(D) ->
            case string:tokens(string:strip(D, right, $\n), " ") of
                [] ->
                    handle_shell(I, Commands, Prompt);
                T when is_list(T) ->
                    case process_tokens(Commands, T) of
                        {processed, F, A} ->
                            normal_out(F, A),
                            handle_shell(I + 1, Commands, Prompt);
                        syntax ->
                            error_out("Syntax Error.~n" ++ p_syntax(Commands), []),
                            handle_shell(I + 1, Commands, Prompt);
                        {error, F, A} ->
                            error_out(F, A),
                            handle_shell(I + 1, Commands, Prompt);
                        exit ->
                            ok;
                        {subshell, M, P} ->
                            ok = handle_shell(0, scan_modules(M), P),
                            handle_shell(I + 1, Commands, Prompt)
                    end
            end
    end.


scan_modules(Modules) -> scan_modules(Modules, []).
scan_modules([], Commands) ->
    Commands;
scan_modules([Module | Tail], Commands) ->
    case {command_prefix(Module), Module:commands()} of
        {undefined, C} when is_list(C) ->
            scan_modules(Tail, C ++ Commands);
        {P, C} when is_list(P), is_list(C) ->
            Cm = lists:map(fun({T, H, F}) -> {P ++ T, H, F} end, C),
            scan_modules(Tail, Cm ++ Commands)
    end.

command_prefix(Module) ->
    case lists:member({prefix, 0}, Module:module_info(exports)) of
        false ->
            [];
        true ->
            case Module:prefix() of
                L when is_list(L) ->
                    L
            end
    end.

process_tokens(_, ["exit"]) ->
    exit;
process_tokens(C, ["help"]) ->
    {processed, "Help.~n" ++ p_syntax(C), []};
process_tokens([], _Tokens) -> syntax;
process_tokens([{H, _, {subshell, Mods, _}} | CTail], Tokens) when length(Tokens) > length(H) ->
    case lists:split(length(H), Tokens) of
        {H, T} ->
            C = scan_modules(Mods),
            case process_tokens(C, T) of
                syntax -> {error, "Syntax Error.~n" ++ p_syntax(C), []};
		R -> R
            end;
        {L, _T} when is_list(L) -> process_tokens(CTail, Tokens)
    end;
process_tokens([{Match, Help, C} = E| CTail], Tokens) ->
    case command_match(Match, Tokens) of
        false ->
            process_tokens(CTail, Tokens);
        syntax ->
            {error, "Syntax Error.~n" ++ p_syntax([E]), []};
        A when is_list(A) ->
            process_command(Help, C, A)
    end;
process_tokens([_ | CTail], Tokens) ->
    process_tokens(CTail, Tokens).

command_match(C, T) -> command_match(C, T, []).
command_match([], [], A) -> A;
command_match([H | MT], [H | TT], A) when is_list(H) ->
    command_match(MT, TT, A);
command_match([{_, atom} | MT], [H | TT], Ar) ->
    case catch list_to_existing_atom(H) of
        A when is_atom(A) -> command_match(MT, TT, Ar ++ [A]);
        {'EXIT',{badarg,[{erlang,list_to_existing_atom,[H]} | _]}} -> syntax
    end;
command_match([{_, bool} | MT], [H | TT], Ar) ->
    case catch list_to_existing_atom(string:to_lower(H)) of
        A when A == true; A == false -> command_match(MT, TT, Ar ++ [A]);
        A when is_atom(A) -> syntax
    end;
{'fraction_non_alphanumeric': 0.16409476200825907, 'fraction_numerical': 0.004238209084981526, 'mean_word_length': 2.144174923129484, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 107, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5478248', 'n_tokens_mistral': 3125, 'n_tokens_neox': 3038, 'n_words': 643}