Files
Vanillae/utils/jex/src/jex.erl
T

832 lines
25 KiB
Erlang

%%% @doc
%%% jex: jex
%%%
%%% This module is currently named `jex', but you may want to change that.
%%% Remember that changing the name in `-module()' below requires renaming
%%% this file, and it is recommended to run `zx update .app` in the main
%%% project directory to make sure the ebin/jex.app file stays in
%%% sync with the project whenever you add, remove or rename a module.
%%% @end
-module(jex).
-vsn("0.1.0").
-export([start/1]).
-compile([export_all, nowarn_export_all]).
-include("$zx_include/zx_logger.hrl").
-spec start(ArgV) -> ok
when ArgV :: [string()].
start(ArgV) ->
ok = log(info, "ArgV: ~tp", [ArgV]),
ok = dispatch(ArgV),
%ok.
zx:silent_stop().
help() ->
tell(info, "~ts~n", [help_screen()]).
% TODONE: make tsdocs
% TODONE: jex pull
% TODONE: delete before mindist
% TODONE: delete before push
% TODONE: delete before pushdocs
% TODONE: jex viewdocs PKG [port]
% TODONE: jex fulldist
% TODONE: jex install
% TODONE: jex get_mindist PKG
% TODONE: jex dwim++ = install
% TODO: jex directory based on version
% TODO: "external" install
% - pull only on library
% TODO: use less than full qualified names (not priority)
% TODO: --name option for docs (not priority)
% TODO: make fulldist for arbitrary installed package (requires storing jex.eterms, not hard but also not a priority)
% TODO: be smart about creating jex_include
% TODO: tarball shas
% TODO: tarball signatures
help_screen() ->
["Jex: simple JavaScript packaging system\n"
"\n"
"QUICK REFERENCE\n"
" You have a package tarball and want to install it ->\n"
" jex install foo-bar-X.Y.Z.tar.gz\n"
" You are in the current directory of a project and want to build it ->\n"
" jex install\n"
" There is a package installed and you need its mindist to put on a server or something ->\n"
" jex get_mindist foo-bar-X.Y.Z # must use fully qualified package name\n"
"\n"
"PORCELAIN COMMANDS:\n"
" dwim- build project but don't make a release (init, clean, pull, build)\n"
" dwim+ build and make a minimal release (init, clean, pull, build, mindist, push)\n"
" dwim++ build and make a full release (init, clean, pull, build, mindist, push, mkdocs, pushdocs)\n"
" install synonym for dwim++\n"
" install [TARBALL_PATH] install the given package\n"
" ls list installed packages\n"
" viewdocs [PKG [PORT]] view package docs for PKG in browser\n"
" get_mindist [PKG] get the mindist tarball for an installed package\n"
"\n"
"PLUMBING COMMANDS:\n"
" init mkdir -p $HOME/.jex/{dev,docs,tmp}\n"
" clean rm -r ./src/jex_include && rm -r dist\n"
" build tsc && cp -r ./src/jex_include ./dist/\n"
" -w, --weak continue building even if tsc fails\n"
" -f, --force use cp -rf instead of cp -r\n"
" mindist mkdir jex_mindist && cp -r src jex_mindist && cp -r dist jex_mindist && rm -r jex_mindist/src/jex_include\n"
" -f, --force use cp -rf instead of cp -r\n"
" push rsync -a jex_mindist/ PKG_DEVDIR\n"
" mkdocs (maybe run dwim- first) npx typedoc\n"
" pushdocs rsync -a docs/ PKG_DOCSDIR\n"
" rmpkg PKG rm -r $HOME/.jex/dev/PKG\n"
" pull pull each dependency into src/jx_include\n"
" fulldist make a release tarball for current package\n"
"\n"
"DEBUG COMMANDS\n"
" man show the manual\n"
" tree tree $HOME/.jex/\n"
" cfgbarf barf out the jex.eterms file (mostly to make sure it parses correctly)\n"
" echo home echo $HOME\n"
" echo jexdir echo $HOME/.jex\n"
" echo devdir echo $HOME/.jex/dev\n"
" echo docsdir echo $HOME/.jex/docs\n"
" echo tmpdir echo $HOME/.jex/tmp\n"
" echo pkg_devdir echo $HOME/.jex/dev/realm-name-X.Y.Z\n"
" echo pkg_devdir PKG echo $HOME/.jex/dev/PKG\n"
" echo pkg_docsdir echo $HOME/.jex/docs/realm-name-X.Y.Z\n"
" echo pkg_docsdir PKG echo $HOME/.jex/docs/PKG\n"
" echo pkg_name name of current package\n"
% " echo pkg_tmpdir echo $HOME/.jex/tmp/realm-name-X.Y.Z\n"
% " echo pkg_tmpdir PKG echo $HOME/.jex/tmp/PKG\n"
" echo pkg_type type of current package\n"
" echo deps list dependencies of current package\n"
" echo serverpid [PORT] show the pid for the documentation server, if alive\n"
].
%% Porcelain
dispatch(["dwim-"]) -> dwim(minus);
dispatch(["dwim+"]) -> dwim(plus);
dispatch(["dwim++"]) -> dwim(plus_plus);
dispatch(["install"]) -> install();
dispatch(["install", Path]) -> install(Path);
dispatch(["ls"]) -> ls();
dispatch(["viewdocs"]) -> viewdocs();
dispatch(["viewdocs", Pkg]) -> viewdocs(Pkg);
dispatch(["viewdocs", Pkg, Port]) -> viewdocs(Pkg, Port);
dispatch(["get_mindist", Pkg]) -> get_mindist(Pkg);
%% plumbing
dispatch(["init"]) -> init();
dispatch(["clean"]) -> clean();
dispatch(["build" | Opts]) -> build(Opts);
dispatch(["mindist" | Opts]) -> mindist(Opts);
dispatch(["push"]) -> push();
dispatch(["mkdocs"]) -> mkdocs();
dispatch(["pushdocs"]) -> pushdocs();
dispatch(["rmpkg", Pkg]) -> rmpkg(Pkg);
dispatch(["pull"]) -> pull();
dispatch(["fulldist"]) -> fulldist();
%% Debug
dispatch(["man"]) -> man();
dispatch(["tree"]) -> tree();
dispatch(["cfgbarf"]) -> cfgbarf();
dispatch(["echo", "home"]) -> echo(home);
dispatch(["echo", "jexdir"]) -> echo(jexdir);
dispatch(["echo", "devdir"]) -> echo(devdir);
dispatch(["echo", "docsdir"]) -> echo(docsdir);
dispatch(["echo", "tmpdir"]) -> echo(tmpdir);
dispatch(["echo", "pkg_devdir"]) -> echo(pkg_devdir);
dispatch(["echo", "pkg_devdir", PkgName]) -> echo({pkg_devdir, PkgName});
dispatch(["echo", "pkg_docsdir"]) -> echo(pkg_docsdir);
dispatch(["echo", "pkg_docsdir", PkgName]) -> echo({pkg_docsdir, PkgName});
dispatch(["echo", "pkg_name"]) -> echo(pkg_name);
dispatch(["echo", "pkg_type"]) -> echo(pkg_type);
dispatch(["echo", "deps"]) -> echo(deps);
dispatch(["echo", "serverpid"]) -> echo(serverpid);
dispatch(["echo", "serverpid", Port]) -> echo({serverpid, Port});
dispatch(_) -> help().
%%-----------------------------------------------------------------------------
%% jex man
%%-----------------------------------------------------------------------------
man() ->
ManFile = filename:join([zx:get_home(), "priv", "MANUAL.txt"]),
{ok, ManBytes} = file:read_file(ManFile),
tell(info, "~ts~n", [string:chomp(ManBytes)]).
%os:cmd(io_lib:format("less ~ts", [ManFile])),
%ok.
%%-----------------------------------------------------------------------------
%% jex dwim
%%-----------------------------------------------------------------------------
dwim(minus) ->
init(),
clean(),
pull(),
build([]);
dwim(plus) ->
dwim(minus),
mindist([]),
push();
dwim(plus_plus) ->
dwim(plus),
mkdocs(),
pushdocs().
%%-----------------------------------------------------------------------------
%% jex cfgbarf
%%-----------------------------------------------------------------------------
cfgbarf() ->
tell(info, "~tp~n", [cfg(unsafe)]).
cfg() ->
cfg(safe).
-spec cfg(MaybeSafe) -> Result
when MaybeSafe :: safe | unsafe,
Result :: {ok, Cfg :: proplists:proplist()}
| {error, Reason :: term()}.
% @doc "safe" means it checks to make sure the
cfg(unsafe) ->
{ok, Terms} = file:consult("jex.eterms"),
{ok, Terms};
cfg(safe) ->
case file:consult("jex.eterms") of
{ok, Terms} -> cfg2(Terms);
Error -> Error
end.
cfg2(Cfg) ->
% see pkg_type/0, relies on this for validation
% allowed values:
% - library
% - external
% - extension
case proplists:get_value(type, Cfg) of
% allowed values
library -> {ok, Cfg};
external -> {ok, Cfg};
extension -> {ok, Cfg};
% no key
undefined -> {error, {no_key, type}};
% bad value
BadType -> {error, {bad_type, BadType}}
end.
%%-----------------------------------------------------------------------------
%% jex echo
%%-----------------------------------------------------------------------------
echo(home) ->
tell(info, "~ts", [home()]);
echo(jexdir) ->
tell(info, "~ts", [jexdir()]);
echo(devdir) ->
tell(info, "~ts", [devdir()]);
echo(docsdir) ->
tell(info, "~ts", [docsdir()]);
echo(tmpdir) ->
tell(info, "~ts", [tmpdir()]);
echo(pkg_devdir) ->
{_Exists, PkgDevDir} = pkg_devdir(),
tell(info, "~ts", [PkgDevDir]);
echo({pkg_devdir, PkgName}) ->
case pkg_devdir(PkgName) of
{exists, Dir} -> tell(info, "~ts", [Dir]);
Error -> tell(error, "Error: ~tp", [Error])
end;
echo(pkg_docsdir) ->
{_Exists, PkgDocsDir} = pkg_docsdir(),
tell(info, "~ts", [PkgDocsDir]);
echo({pkg_docsdir, PkgName}) ->
case pkg_docsdir(PkgName) of
{exists, Dir} -> tell(info, "~ts", [Dir]);
Error -> tell(error, "Error: ~tp", [Error])
end;
echo(pkg_name) ->
tell(info, "~ts", [pkg_name()]);
echo(pkg_tmpdir) ->
{_Exists, PkgTmpDir} = pkg_tmpdir(),
tell(info, "~ts", [PkgTmpDir]);
echo({pkg_tmpdir, PkgName}) ->
case pkg_tmpdir(PkgName) of
{exists, Dir} -> tell(info, "~ts", [Dir]);
Error -> tell(error, "Error: ~tp", [Error])
end;
echo(pkg_type) ->
case pkg_type() of
{ok, PkgType} ->
tell(info, "~tp", [PkgType]);
{error, Error} ->
error(Error)
end;
echo(deps) ->
PrintDep = fun(Dep) -> tell(info, "~ts", [Dep]) end,
lists:foreach(PrintDep, deps());
echo(serverpid) ->
tell(info, "~ts~n", [serverpid(6969)]);
echo({serverpid, PortS}) ->
PortN = list_to_integer(PortS),
tell(info, "~ts~n", [serverpid(PortN)]).
home() ->
case os:getenv("HOME") of
false -> error("You are running some retard system that doesn't have $HOME defined. Fuck off");
HomeD -> HomeD
end.
jexdir() ->
filename:join(home(), ".jex").
devdir() ->
filename:join(jexdir(), "dev").
docsdir() ->
filename:join(jexdir(), "docs").
tmpdir() ->
filename:join(jexdir(), "tmp").
pkg_devdir() ->
pkg_devdir(pkg_name()).
pkg_devdir(Pkg) ->
Filename = filename:join(devdir(), Pkg),
case file_exists(Filename) of
true -> {exists, Filename};
false -> {dne, Filename}
end.
pkg_docsdir() ->
pkg_docsdir(pkg_name()).
pkg_docsdir(PkgName) ->
Filename = filename:join([docsdir(), PkgName]),
case file_exists(Filename) of
true -> {exists, Filename};
false -> {dne, Filename}
end.
pkg_name() ->
{ok, Cfg} = cfg(),
Realm = proplists:get_value(realm, Cfg),
Name = proplists:get_value(name, Cfg),
Vsn = proplists:get_value(version, Cfg),
io_lib:format("~tp-~tp-~ts", [Realm, Name, Vsn]).
pkg_tmpdir() ->
pkg_tmpdir(pkg_name()).
pkg_tmpdir(PkgName) ->
Filename = filename:join([tmpdir(), PkgName]),
case file_exists(Filename) of
true -> {exists, Filename};
false -> {dne, Filename}
end.
-spec pkg_type() -> Result
when Result :: {ok, PkgType}
| {error, Reason :: term()},
PkgType :: library | external | extension.
pkg_type() ->
% cfg errors if package type is invalid
% see cfg2/1
case cfg() of
{ok, Cfg} -> {ok, proplists:get_value(type, Cfg)};
Error -> Error
end.
deps() ->
{ok, Cfg} = cfg(),
case proplists:get_value(deps, Cfg) of
undefined ->
error("jex.eterms is missing key `deps`");
Deps ->
Deps
end.
file_exists(Filename) ->
case file:read_file_info(Filename) of
{ok, _} -> true;
{error, _} -> false
end.
%%-----------------------------------------------------------------------------
%% jex init
%%-----------------------------------------------------------------------------
init() ->
_ = cmd(["mkdir -p ", devdir()]),
_ = cmd(["mkdir -p ", docsdir()]),
_ = cmd(["mkdir -p ", tmpdir()]),
ok.
%%-----------------------------------------------------------------------------
%% jex build
%%-----------------------------------------------------------------------------
% need to branch on if type is external
build(Opts) ->
case pkg_type() of
{ok, PkgType} -> build(PkgType, Opts);
{error, Error} -> error(Error)
end.
-spec build(PkgType, Opts) -> ok
when PkgType :: library | external | extension,
Opts :: [string()].
build(library, Opts) ->
% flags if flag default
OptsConfig = [{["-w", "--weak"], {weak, weak}, {weak, strict}},
{["-f", "--force"], {force, force}, {force, dont_force}}],
#{force := Force, weak := Weak} = parseopts(OptsConfig, Opts),
_ = cmd("mkdir -p src/jex_include"),
ok = tsc(Weak),
ok = cp_jex_include(Force),
ok;
build(external, _Opts) ->
% difference is we're not running tsc
% no deps to pull
ok;
build(extension, _Opts) ->
error(nyi).
tsc(strict) ->
"" = cmd("tsc"),
ok;
tsc(weak) ->
_ = cmd("tsc"),
ok.
cp_jex_include(dont_force) ->
_ = cmd("cp -rv src/jex_include dist"),
ok;
cp_jex_include(force) ->
_ = cmd("cp -rvf src/jex_include dist"),
ok.
%%-----------------------------------------------------------------------------
%% jex mindist
%%-----------------------------------------------------------------------------
mindist(Opts) ->
% flags if flag default
OptsConfig = [{["-f", "--force"], {force, force}, {force, dont_force}}],
#{force := Force} = parseopts(OptsConfig, Opts),
ok =
case file_exists("jex_mindist") of
true -> _ = cmd("rm -r jex_mindist"), ok;
false -> ok
end,
_ = cmd("mkdir -p jex_mindist"),
_ = mindist_cp(Force),
_ = cmd("rm -r jex_mindist/src/jex_include"),
ok.
mindist_cp(dont_force) ->
_ = cmd("rsync -avv --exclude=\"*.swp\" --exclude=\"*.swo\" src jex_mindist"),
_ = cmd("cp -rv dist jex_mindist"),
ok;
mindist_cp(force) ->
_ = cmd("cp -rvf src jex_mindist"),
_ = cmd("cp -rvf dist jex_mindist"),
ok.
%%-----------------------------------------------------------------------------
%% jex push
%%-----------------------------------------------------------------------------
push() ->
case pkg_devdir() of
{exists, Dest} ->
_ = cmd(["rm -r ", Dest]),
_ = cmd(["rsync -avv jex_mindist/ ", Dest]),
ok;
{dne, Dest} ->
_ = cmd(["rsync -avv jex_mindist/ ", Dest]),
ok
end.
%%-----------------------------------------------------------------------------
%% jex pushdocs
%%-----------------------------------------------------------------------------
pushdocs() ->
case pkg_type() of
{ok, library} -> pushdocs2();
{ok, Other} -> error({nyi, {pushdocs_pkg_type, Other}});
{error, E} -> error(E)
end.
pushdocs2() ->
case pkg_docsdir() of
{exists, DocsDestDir} ->
_ = cmd(["rm -r ", DocsDestDir]),
_ = cmd(["rsync -avv docs/ ", DocsDestDir]),
ok;
{dne, DocsDestDir} ->
_ = cmd(["rsync -avv docs/ ", DocsDestDir]),
ok
end.
%%-----------------------------------------------------------------------------
%% jex ls
%%-----------------------------------------------------------------------------
ls() ->
_ = cmd(io_lib:format("ls ~ts", [devdir()])),
ok.
%%-----------------------------------------------------------------------------
%% jex tree
%%-----------------------------------------------------------------------------
tree() ->
_ = cmd(io_lib:format("tree ~ts", [jexdir()])),
ok.
%%-----------------------------------------------------------------------------
%% jex rmpkg Pkg
%%-----------------------------------------------------------------------------
rmpkg(Pkg) ->
case pkg_devdir(Pkg) of
{exists, Filename} -> cmd(io_lib:format("rm -r ~ts", [Filename]));
{dne, _ilename} -> tell(error, "package not installed: ~ts", [Pkg])
end,
case pkg_docsdir(Pkg) of
{exists, Filename2} -> cmd(io_lib:format("rm -r ~ts", [Filename2]));
{dne, _ilename2} -> tell(error, "documentation not installed: ~ts", [Pkg])
end,
ok.
%%-----------------------------------------------------------------------------
%% jex pull
%%-----------------------------------------------------------------------------
pull() ->
pull(deps()).
pull([Dep | Deps]) ->
pull_dep(Dep),
pull(Deps);
pull([]) ->
tell(info, "no more dependencies to pull", []),
ok.
pull_dep(Dep) ->
case pkg_devdir(Dep) of
{exists, Src} ->
Dst = filename:join("./src/jex_include", Dep),
_ = cmd(io_lib:format("mkdir -p ~ts", [Dst])),
%tell(info, "path of ~s: ~s", [Dep, Src]),
_ = cmd(io_lib:format("rsync -avv ~ts/ ~ts", [Src, Dst]));
DNE ->
error(DNE)
end,
ok.
%%-----------------------------------------------------------------------------
%% jex mkdocs
%%-----------------------------------------------------------------------------
mkdocs() ->
case pkg_type() of
{ok, library} -> mkdocs2();
Other -> error({nyi, {mkdocs_pkg_type, Other}})
end.
mkdocs2() ->
_ = cmd("npx typedoc --entryPointStrategy expand --sort source-order src"),
ok.
%%-----------------------------------------------------------------------------
%% jex viewdocs
%%-----------------------------------------------------------------------------
viewdocs() ->
viewdocs(all, 6969).
viewdocs(PkgName) ->
viewdocs(PkgName, 6969).
viewdocs(all, Port) ->
ok = ensure_docserver(Port),
firefox(all, Port);
viewdocs(PkgName, Port) ->
ok = ensure_docserver(Port),
case pkg_docsdir(PkgName) of
{exists, _} -> firefox(PkgName, Port);
DNE -> error(DNE)
end.
% if the server is up, just return, otherwise, spin it up
ensure_docserver(Port) ->
case serverpid(Port) of
"" -> spawn(fun() -> servedocs(docsdir(), Port) end), ok;
_Pid -> ok
end.
servedocs(DocsPath, Port) ->
_ = cmd(io_lib:format("cd ~ts && ~ts", [DocsPath, pythoncmd(Port)])),
ok.
pythoncmd(Port) ->
io_lib:format("python3 -m http.server ~tp", [Port]).
firefox(all, Port) ->
_ = cmd(io_lib:format("firefox localhost:~tp", [Port])),
ok;
firefox(PkgName, Port) ->
_ = cmd(io_lib:format("firefox localhost:~tp/~ts", [Port, PkgName])),
ok.
serverpid(Port) ->
string:chomp(cmd(io_lib:format("ps ax | grep '~ts' | grep --invert-match grep | awk '{print $1}'", [pythoncmd(Port)]))).
%%-----------------------------------------------------------------------------
%% jex fulldist
%%-----------------------------------------------------------------------------
fulldist() ->
PkgName = pkg_name(),
PkgTmpDir =
case pkg_tmpdir() of
{exists, D} ->
_ = cmd(io_lib:format("rm -r ~ts", [D])),
D;
{dne, D} ->
D
end,
ReadmePath = srsly_readme_path(),
_ = cmdf("mkdir -p ~ts", [PkgTmpDir]),
_ = cmdf("cp jex.eterms ~ts", [PkgTmpDir]),
_ = cmdf("cp -r docs ~ts", [PkgTmpDir]),
_ = cmdf("cp -r jex_mindist ~ts", [PkgTmpDir]),
_ = cmdf("cp ~ts ~ts", [ReadmePath, PkgTmpDir]),
GenTmpDir = tmpdir(),
{ok, OriginalDir} = file:get_cwd(),
_ = cmdf("cd ~ts"
"&& tar -czf ~ts.tar.gz ~ts"
"&& rm -r ~ts"
"&& mv ~ts.tar.gz ~ts",
[GenTmpDir,
PkgName, PkgName,
PkgName,
PkgName, OriginalDir]),
ok.
srsly_readme_path() ->
filename:join([zx:get_home(), "priv", "SERIOUSLY_README.txt"]).
%%-----------------------------------------------------------------------------
%% jex install TARBALL_PATH
%%-----------------------------------------------------------------------------
install() ->
dwim(plus_plus).
install(TarballPath) ->
case file_exists(TarballPath) of
false -> error({file_dne, TarballPath});
true -> install2(TarballPath)
end.
install2(TarballPath) ->
init(),
% ~/.jex/tmp
GenTmpDir = tmpdir(),
% local-sidekick-0.2.0
PkgName = gasoline(TarballPath),
% **copy** the tarball to ~/.jex/tmp
_ = cmdf("cp ~ts ~ts", [TarballPath, GenTmpDir]),
% unpack the tarball
_ = cmdf("cd ~ts" % cd ~/.jex/tmp
" && tar -xzf ~ts.tar.gz", % && tar -xzf local-sidekick-0.2.0.tar.gz
[GenTmpDir,
PkgName]),
% actually do the install
% ~/.jex/tmp/local-sidekick-0.2.0
PkgTmpDir = filename:join([GenTmpDir, PkgName]),
{ok, OrigDir} = file:get_cwd(),
ok = file:set_cwd(PkgTmpDir),
ok = push(),
ok = pushdocs(),
% clean up
% delete the tmp tarball
_ = cmdf("rm ~ts/~ts.tar.gz", [GenTmpDir, PkgName]),
% delete the tmp tree
_ = cmdf("rm -r ~ts/~ts", [GenTmpDir, PkgName]),
% change back to original directory
ok = file:set_cwd(OrigDir),
% we're done I think
ok.
% remove slashes and .tar.gz
gasoline(TarballPath) ->
Filename_tar_gz = lists:last(string:split(TarballPath, "/", trailing)),
[Filename | _] = string:split(Filename_tar_gz, ".tar.gz", leading),
Filename.
%%-----------------------------------------------------------------------------
%% jex get_mindist PACKAGE
%%-----------------------------------------------------------------------------
get_mindist(PkgName) ->
% does package exist
case pkg_devdir(PkgName) of
{exists, _} -> get_mindist2(PkgName);
{dne, _} -> error({package_not_installed, PkgName})
end.
get_mindist2(PkgName) ->
DevDir = devdir(),
{ok, CWD} = file:get_cwd(),
_ = cmdf("cd ~ts" % cd ~/.jex/dev
" && tar -czf ~ts.tar.gz ~ts" % && tar -czf local-sidekick-0.2.0.tar.gz local-sidekick-0.2.0
" && mv ~ts.tar.gz ~ts", % && mv local-sidekick-0.2.0 CWD
[DevDir,
PkgName, PkgName,
PkgName, CWD]),
ok.
%%-----------------------------------------------------------------------------
%% jex clean
%%-----------------------------------------------------------------------------
% TODO: branch here on package type
clean() ->
{ok, PkgType} = pkg_type(),
clean(PkgType).
clean(library) ->
_ = cmdf("rm -r ./src/jex_include", []),
_ = cmdf("rm -r ./dist", []),
ok;
clean(external) ->
ok;
clean(extension) ->
error(nyi).
%%-----------------------------------------------------------------------------
%% INTERNALS
%%-----------------------------------------------------------------------------
cmdf(Format, Args) ->
cmd(io_lib:format(Format, Args)).
cmd(Command) ->
ok = tell("$ ~ts", [Command]),
S = os:cmd(Command),
ok = tell("~ts", [S]),
S.
parseopts(OptsConfig, Opts) ->
log(info, "OptsConfig: ~tp", [OptsConfig]),
log(info, "Opts: ~tp", [Opts]),
DefaultOpts = default_opts(OptsConfig, #{}),
log(info, "DefaultOpts: ~tp", [DefaultOpts]),
Updater = updater(OptsConfig, #{}),
log(info, "Updater: ~tp", [Updater]),
Flags = getflags(Opts, []),
UpdatedFlags = update_flags(Flags, Updater, DefaultOpts),
log(info, "UpdatedFlags: ~tp", [UpdatedFlags]),
UpdatedFlags.
update_flags([Flag | Rest], Updater, AccFlags) ->
NewAcc =
case maps:find(Flag, Updater) of
{ok, {NewFlag, NewValue}} ->
AccFlags#{NewFlag => NewValue};
error ->
error(["invalid flag", Flag])
end,
update_flags(Rest, Updater, NewAcc);
update_flags([], _Updater, FinalAcc) ->
FinalAcc.
updater([{DashedFlags, IfFlag, _Def} | Rest], Acc) ->
UndashedFlags = getflags(DashedFlags, []),
Flagger =
fun (UndashedFlag, UndashedFlagToOptionMap) ->
UndashedFlagToOptionMap#{UndashedFlag => IfFlag}
end,
NewAcc = lists:foldl(Flagger, Acc, UndashedFlags),
updater(Rest, NewAcc);
updater([], Acc) ->
Acc.
default_opts([{_X, _Y, {Z, W}} | Rest], Acc) ->
default_opts(Rest, Acc#{Z => W});
default_opts([], FinalAcc) ->
FinalAcc.
% --flag (long flag)
getflags(["--"++LongFlag | Flags], Acc) ->
NewAcc = [LongFlag | Acc],
getflags(Flags, NewAcc);
% -xyz (many short flags)
getflags(["-"++ShortFlags | Flags], Acc) ->
NewAcc = lists:map(fun(Char) -> [Char] end, ShortFlags) ++ Acc,
getflags(Flags, NewAcc);
% no more options
getflags([], FinalAcc) ->
FinalAcc.