diff options
author | Trygve Laugstøl <trygvis@inamo.no> | 2024-02-23 07:08:18 +0100 |
---|---|---|
committer | Trygve Laugstøl <trygvis@inamo.no> | 2024-02-23 07:08:18 +0100 |
commit | 5a9cdd3cc89507d4d74f8bded56ce5e037b3b56e (patch) | |
tree | 982ca2e7f9ac4e8c350dfb5c4f60bcfdfff5afaf /learn-you-some-erlang/release/erlcount-1.0/src | |
parent | 05ae56e5e89abf2993f84e6d52b250131f247c35 (diff) | |
download | erlang-workshop-5a9cdd3cc89507d4d74f8bded56ce5e037b3b56e.tar.gz erlang-workshop-5a9cdd3cc89507d4d74f8bded56ce5e037b3b56e.tar.bz2 erlang-workshop-5a9cdd3cc89507d4d74f8bded56ce5e037b3b56e.tar.xz erlang-workshop-5a9cdd3cc89507d4d74f8bded56ce5e037b3b56e.zip |
wip
Diffstat (limited to 'learn-you-some-erlang/release/erlcount-1.0/src')
5 files changed, 202 insertions, 0 deletions
diff --git a/learn-you-some-erlang/release/erlcount-1.0/src/erlcount.erl b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount.erl new file mode 100644 index 0000000..16a9f23 --- /dev/null +++ b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount.erl @@ -0,0 +1,9 @@ +-module(erlcount). +-behaviour(application). +-export([start/2, stop/1]). + +start(normal, _Args) -> + erlcount_sup:start_link(). + +stop(_State) -> + ok. diff --git a/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_counter.erl b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_counter.erl new file mode 100644 index 0000000..c42fd4d --- /dev/null +++ b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_counter.erl @@ -0,0 +1,35 @@ +-module(erlcount_counter). +-behaviour(gen_server). +-export([start_link/4]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {dispatcher, ref, file, re}). + +start_link(DispatcherPid, Ref, FileName, Regex) -> + gen_server:start_link(?MODULE, [DispatcherPid, Ref, FileName, Regex], []). + +init([DispatcherPid, Ref, FileName, Regex]) -> + self() ! start, + {ok, #state{dispatcher=DispatcherPid, + ref = Ref, + file = FileName, + re = Regex}}. + +handle_call(_Msg, _From, State) -> + {noreply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(start, S = #state{re=Re, ref=Ref}) -> + {ok, Bin} = file:read_file(S#state.file), + Count = erlcount_lib:regex_count(Re, Bin), + erlcount_dispatch:complete(S#state.dispatcher, Re, Ref, Count), + {stop, normal, S}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. diff --git a/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_dispatch.erl b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_dispatch.erl new file mode 100644 index 0000000..c125da3 --- /dev/null +++ b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_dispatch.erl @@ -0,0 +1,86 @@ +-module(erlcount_dispatch). +-behaviour(gen_fsm). +-export([start_link/0, complete/4]). +-export([init/1, dispatching/2, listening/2, handle_event/3, + handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). + +-define(POOL, erlcount). +-record(data, {regex=[], refs=[]}). + +%%% PUBLIC API +start_link() -> + gen_fsm:start_link(?MODULE, [], []). + +complete(Pid, Regex, Ref, Count) -> + gen_fsm:send_all_state_event(Pid, {complete, Regex, Ref, Count}). + +%%% GEN_FSM +%% Two states: dispatching and listening +init([]) -> + %% Move the get_env stuff to the supervisor's init. + {ok, Re} = application:get_env(regex), + {ok, Dir} = application:get_env(directory), + {ok, MaxFiles} = application:get_env(max_files), + ppool:start_pool(?POOL, MaxFiles, {erlcount_counter, start_link, []}), + case lists:all(fun valid_regex/1, Re) of + true -> + %% creates a regex entry of the form [{Re, Count}] + self() ! {start, Dir}, + {ok, dispatching, #data{regex=[{R,0} || R <- Re]}}; + false -> + {stop, invalid_regex} + end. + +dispatching({continue, File, Continuation}, Data = #data{regex=Re, refs=Refs}) -> + F = fun({Regex,_Count}, NewRefs) -> + Ref = make_ref(), + ppool:async_queue(?POOL, [self(), Ref, File, Regex]), + [Ref|NewRefs] + end, + NewRefs = lists:foldl(F, Refs, Re), + gen_fsm:send_event(self(), Continuation()), + {next_state, dispatching, Data#data{refs = NewRefs}}; +dispatching(done, Data) -> + %% This is a special case. We can not assume that all messages have NOT + %% been received by the time we hit 'done'. As such, we directly move to + %% listening/2 without waiting for an external event. + listening(done, Data). + +listening(done, #data{regex=Re, refs=[]}) -> % all received! + [io:format("Regex ~s has ~p results~n", [R,C]) || {R, C} <- Re], + {stop, normal, done}; +listening(done, Data) -> % entries still missing + {next_state, listening, Data}. + +handle_event({complete, Regex, Ref, Count}, State, Data = #data{regex=Re, refs=Refs}) -> + {Regex, OldCount} = lists:keyfind(Regex, 1, Re), + NewRe = lists:keyreplace(Regex, 1, Re, {Regex, OldCount+Count}), + NewData = Data#data{regex=NewRe, refs=Refs--[Ref]}, + case State of + dispatching -> + {next_state, dispatching, NewData}; + listening -> + listening(done, NewData) + end. + +handle_sync_event(Event, _From, State, Data) -> + io:format("Unexpected event: ~p~n", [Event]), + {next_state, State, Data}. + +handle_info({start, Dir}, State, Data) -> + gen_fsm:send_event(self(), erlcount_lib:find_erl(Dir)), + {next_state, State, Data}. + +terminate(_Reason, _State, _Data) -> + init:stop(). + +code_change(_OldVsn, State, Data, _Extra) -> + {ok, State, Data}. + +%%% PRIVATE +valid_regex(Re) -> + try re:run("", Re) of + _ -> true + catch + error:badarg -> false + end. diff --git a/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_lib.erl b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_lib.erl new file mode 100644 index 0000000..70c062f --- /dev/null +++ b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_lib.erl @@ -0,0 +1,55 @@ +-module(erlcount_lib). +-export([find_erl/1, regex_count/2]). +-include_lib("kernel/include/file.hrl"). + +%% Finds all files ending in .erl +find_erl(Directory) -> + find_erl(Directory, queue:new()). + +regex_count(Re, Str) -> + case re:run(Str, Re, [global]) of + nomatch -> 0; + {match, List} -> length(List) + end. + +%%% Private +%% Dispatches based on file type +find_erl(Name, Queue) -> + {ok, F = #file_info{}} = file:read_file_info(Name), + case F#file_info.type of + directory -> handle_directory(Name, Queue); + regular -> handle_regular_file(Name, Queue); + _Other -> dequeue_and_run(Queue) + end. + +%% Opens directories and enqueues files in there +handle_directory(Dir, Queue) -> + case file:list_dir(Dir) of + {ok, []} -> + dequeue_and_run(Queue); + {ok, Files} -> + dequeue_and_run(enqueue_many(Dir, Files, Queue)) + end. + +%% Checks if the file finishes in .erl +handle_regular_file(Name, Queue) -> + case filename:extension(Name) of + ".erl" -> + {continue, Name, fun() -> dequeue_and_run(Queue) end}; + _NonErl -> + dequeue_and_run(Queue) + end. + +%% Pops an item from the queue and runs it. +dequeue_and_run(Queue) -> + case queue:out(Queue) of + {empty, _} -> done; + {{value, File}, NewQueue} -> find_erl(File, NewQueue) + end. + +%% Adds a bunch of items to the queue. +enqueue_many(Path, Files, Queue) -> + F = fun(File, Q) -> queue:in(filename:join(Path,File), Q) end, + lists:foldl(F, Queue, Files). + + diff --git a/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_sup.erl b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_sup.erl new file mode 100644 index 0000000..b8633a3 --- /dev/null +++ b/learn-you-some-erlang/release/erlcount-1.0/src/erlcount_sup.erl @@ -0,0 +1,17 @@ +-module(erlcount_sup). +-behaviour(supervisor). +-export([start_link/0, init/1]). + +start_link() -> + supervisor:start_link(?MODULE, []). + +init([]) -> + MaxRestart = 5, + MaxTime = 100, + {ok, {{one_for_one, MaxRestart, MaxTime}, + [{dispatch, + {erlcount_dispatch, start_link, []}, + transient, + 60000, + worker, + [erlcount_dispatch]}]}}. |