1
-module(ssh_sample_cli).
4
-export([listen/1, listen/2]).
6
%% %% our shell function
7
%% -export([start_our_shell/1]).
9
%% our command functions
10
-export([cli_prime/1, cli_primes/1, cli_gcd/2, cli_lcm/2,
11
cli_factors/1, cli_exit/0, cli_rho/1, cli_help/0,
12
cli_crash/0, cli_users/0, cli_self/0,
13
cli_user/0, cli_host/0]).
16
-import(lists, [reverse/1, reverse/2, seq/2, prefix/2]).
17
-import(math, [sqrt/1]).
23
listen(Port, Options) ->
24
ssh_cli:listen(fun(U, H) -> start_our_shell(U, H) end, Port, Options).
29
{"crash", cli_crash, " crash the cli"},
30
{"exit", cli_exit, " exit application"},
31
{"factors", cli_factors,"<int> prime factors of <int>"},
32
{"gcd", cli_gcd, "<int> <int> greatest common divisor"},
33
{"help", cli_help, " help text"},
34
{"lcm", cli_lcm, "<int> <int> least common multiplier"},
35
{"prime", cli_prime, "<int> check for primality"},
36
{"primes", cli_primes, "<int> print all primes up to <int>"},
37
{"rho", cli_rho, "<int> prime factors using rho's alg."},
38
{"who", cli_users, " lists users"},
39
{"user", cli_user, " print name of user"},
40
{"host", cli_host, " print host addr"},
41
{"self", cli_self, " print my pid"}
44
%% (we could of course generate this from module_info() something like this)
46
%% {value, {exports, Exports}} =
47
%% lists:keysearch(exports, 1, module_info()),
48
%% get_cli(Exports, []).
50
%% our_args1(N) -> our_args1(N, "").
51
%% our_args1(0, S) -> S;
52
%% our_args1(N, S) -> our_args1(N-1, S ++ "<int> ").
54
%% get_cli([], Acc) ->
56
%% get_cli([{A, Arity} | Rest], Acc) ->
57
%% L = atom_to_list(A),
58
%% case lists:prefix("cli_", L) of
59
%% true -> get_cli(Rest, [{tl4(L), A, our_args1(Arity)} | Acc]);
60
%% false -> get_cli(Rest, Acc)
63
%% the longest common prefix of two strings
64
common_prefix([C | R1], [C | R2], Acc) ->
65
common_prefix(R1, R2, [C | Acc]);
66
common_prefix(_, _, Acc) ->
69
%% longest prefix in a list, given a prefix
70
longest_prefix(List, Prefix) ->
71
case [A || {A, _, _} <- List, prefix(Prefix, A)] of
76
lists:foldl(fun(A, P) ->
77
common_prefix(A, P, [])
79
NewPrefix = nthtail(length(Prefix), NewPrefix0),
80
{prefix, NewPrefix, [S | Rest]}
83
%%% our expand function (called when the user presses TAB)
84
%%% input: a reversed list with the row to left of the cursor
85
%%% output: {yes|no, Expansion, ListofPossibleMatches}
86
%%% where the atom no yields a beep
87
%%% Expansion is a string inserted at the cursor
88
%%% List... is a list that will be printed
89
%%% Here we beep on prefixes that don't match and when the command
94
Before = reverse(RevBefore),
95
case longest_prefix(our_routines(), Before) of
106
%%% spawns out shell loop, we use plain io to input and output
107
%%% over ssh (the group module is our group leader, and takes
108
%%% care of sending input to the ssh_sample_cli server)
109
start_our_shell(User, Peer) ->
111
io:setopts([{expand_fun, fun(Bef) -> expand(Bef) end}]),
112
io:format("Enter command\n"),
114
put(peer_name, Peer),
118
%%% an ordinary Read-Eval-Print-loop
121
Line = io:get_line({format, "CLI> ", []}),
123
Result = eval_cli(Line),
125
io:format("---> ~p\n", [Result]),
127
done -> exit(normal);
129
_ -> our_shell_loop()
132
%%% translate a command to a function
133
command_to_function(Command) ->
134
case lists:keysearch(Command, 1, our_routines()) of
135
{value, {_, Proc, _}} -> Proc;
139
%%% evaluate a command line
141
case string:tokens(Line, " \n") of
143
[Command | ArgStrings] ->
144
Proc = command_to_function(Command),
145
case fix_args(ArgStrings) of
147
case catch apply(?MODULE, Proc, Args) of
149
{error, Error}; % wrong_number_of_arguments};
158
%%% make command arguments to integers
159
fix_args(ArgStrings) ->
160
case catch [list_to_integer(A) || A <- ArgStrings] of
162
{error, only_integer_arguments};
167
%%% the commands, check for reasonable arguments here too
168
cli_prime(N) when N < 1000000000 ->
169
rho(N) == [N] andalso is_prime(N);
170
cli_prime(N) when N < 10000 ->
173
cli_primes(N) when N < 1000000 ->
176
cli_gcd(A, B) when is_integer(A), is_integer(B) ->
179
cli_lcm(A, B) when is_integer(A), is_integer(B) ->
182
cli_factors(A) when A < 1000000 ->
192
case ssh_userauth:get_auth_users() of
194
UsersPids; % [U || {U, _} <- UsersPids];
215
help_str([{CommandName, _, HelpS} | Rest], Acc) ->
216
C = string:left(CommandName, 10),
217
help_str(Rest, [[C, " ", HelpS, $\n] | Acc]).
220
HelpString = ["CLI Sample\n" | help_str(our_routines())],
221
io:format("~s\n", [HelpString]).
223
%% a quite simple Sieve of Erastothenes (not tail-recursive, though)
225
era(sqrt(Size), seq(2,Size)).
227
era(Max, [H|T]) when H =< Max ->
228
[H | era(Max, sieve([H|T], H))];
232
sieve([H|T], N) when H rem N =/= 0 ->
239
%% another sieve, for getting the next prime incrementally
242
next_prime([2], 2) ->
244
next_prime(Primes, P) ->
245
next_prime1(Primes, P).
247
next_prime1(Primes, P) ->
249
case divides(Primes, trunc(sqrt(P1)), P1) of
251
true -> next_prime1(Primes, P1)
256
divides([A | _], Nsqrt, _) when A > Nsqrt ->
258
divides([A | _], _, N) when N rem A == 0 ->
260
divides([_ | R], Nsqrt, N) ->
261
divides(R, Nsqrt, N).
264
lists:all(fun(A) -> P rem A =/= 0 end, primes(trunc(sqrt(P)))).
266
%% Normal gcd, Euclid
267
gcd(R, Q) when abs(Q) < abs(R) -> gcd1(Q,R);
268
gcd(R, Q) -> gcd1(R,Q).
274
%% Least common multiple of (R,Q)
278
(Q div gcd(R, Q)) * R.
280
%%% Prime factors of a number (na�ve implementation)
282
Nsqrt = trunc(sqrt(N)),
283
factors([], N, 2, Nsqrt, []).
285
factors(_Primes, N, Prime, Nsqrt, Factors) when Prime > Nsqrt ->
286
reverse(Factors, [N]);
287
factors(Primes, N, Prime, Nsqrt, Factors) ->
290
%%io:format("factor ------- ~p\n", [Prime]),
292
factors(Primes, N1, Prime, trunc(sqrt(N1)), [Prime|Factors]);
294
Primes1 = Primes ++ [Prime],
295
Prime1 = next_prime(Primes1, Prime),
296
factors(Primes1, N, Prime1, Nsqrt, Factors)
299
%%% Prime factors using Rho's algorithm ("reminded" from wikipedia.org)
300
%%% (should perhaps have used Brent instead, but it's not as readable)
301
rho_pseudo(X, C, N) ->
304
rho(N) when N > 1000 ->
305
case rho(2, 2, 1, N, fun(X) -> rho_pseudo(X, 1, N) end) of
309
lists:sort(rho(F) ++ rho(N div F))
314
rho(X, Y, 1, N, Pseudo) ->
316
Y1 = Pseudo(Pseudo(Y)),
317
D = gcd(absdiff(X1, Y1), N),
318
rho(X1, Y1, D, N, Pseudo);
319
rho(_X, _Y, D, N, _Pseudo) when 1 < D, D < N ->
321
rho(_X, _Y, D, N, _Pseudo) when D == N ->
324
absdiff(A, B) when A > B ->
329
%%% nthtail as in lists, but no badarg if n > the length of list
331
nthtail(N, [_ | A]) -> nthtail(N-1, A);