Files
n8n-openai-adapter/lib/elixir/lib/kernel/cli.ex
T

401 lines
11 KiB
Elixir

defmodule Kernel.CLI do
@moduledoc false
defrecord Config, commands: [], output: ".", compile: [],
halt: true, compiler_options: [], errors: [],
verbose_compile: false
@doc """
This is the API invoked by Elixir boot process.
"""
def main(argv) do
argv = lc arg inlist argv, do: String.from_char_list!(arg)
{ config, argv } = process_argv(argv, Kernel.CLI.Config.new)
System.argv(argv)
run fn ->
command_results = Enum.map(Enum.reverse(config.commands), &process_command(&1, config))
command_errors = lc { :error, msg } inlist command_results, do: msg
errors = Enum.reverse(config.errors) ++ command_errors
if errors != [] do
Enum.each(errors, &IO.puts(:stderr, &1))
System.halt(1)
end
end, config.halt
end
@doc """
Runs the given function by catching any failure
and printing them to stdout. `at_exit` hooks are
also invoked before exiting.
This function is used by Elixir's CLI and also
by escripts generated by Elixir.
"""
def run(fun, halt // true) do
try do
fun.()
if halt do
at_exit(0)
System.halt(0)
end
catch
:exit, reason when is_integer(reason) ->
at_exit(reason)
System.halt(reason)
:exit, :normal ->
at_exit(0)
System.halt(0)
kind, reason ->
at_exit(1)
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
System.halt(1)
end
end
## Helpers
defp at_exit(status) do
hooks = :elixir_code_server.call(:flush_at_exit)
lc hook inlist hooks do
try do
hook.(status)
catch
kind, reason ->
print_error(kind, Exception.normalize(kind, reason), System.stacktrace)
end
end
# If an at_exit callback adds a
# new hook we need to invoke it.
unless hooks == [], do: at_exit(status)
end
defp shared_option?(list, config, callback) do
case process_shared(list, config) do
{ [h|hs], _ } when h == hd(list) ->
new_config = config.update_errors &["#{h} : Unknown option" | &1]
callback.(hs, new_config)
{ new_list, new_config } ->
callback.(new_list, new_config)
end
end
defp print_error(:error, exception, trace) do
IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}"
IO.puts :stderr, format_stacktrace(trace)
end
defp print_error(kind, reason, trace) do
IO.puts :stderr, "** #{kind} #{inspect(reason)}"
IO.puts :stderr, format_stacktrace(trace)
end
defp format_stacktrace(stack) do
Exception.format_stacktrace(prune_stacktrace(stack))
end
@elixir_internals [:elixir_compiler, :elixir_module]
defp prune_stacktrace([{ mod, _, _, _ }|t]) when mod in @elixir_internals do
prune_stacktrace(t)
end
defp prune_stacktrace([{ __MODULE__, :wrapper, 1, _ }|_]) do
[]
end
defp prune_stacktrace([h|t]) do
[h|prune_stacktrace(t)]
end
defp prune_stacktrace([]) do
[]
end
# Process shared options
defp process_shared([opt|_t], _config) when opt in ["-v", "--version"] do
IO.puts "Elixir #{System.version}"
System.halt 0
end
defp process_shared(["-pa", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.prepend_path(&1)
process_shared t, config
end
defp process_shared(["-pz", h|t], config) do
Enum.each Path.wildcard(Path.expand(h)), &Code.append_path(&1)
process_shared t, config
end
defp process_shared(["--app", h|t], config) do
process_shared t, config.update_commands &[{:app, h}|&1]
end
defp process_shared(["--no-halt"|t], config) do
process_shared t, config.halt(false)
end
defp process_shared(["-e", h|t], config) do
process_shared t, config.update_commands &[{:eval, h}|&1]
end
defp process_shared(["-r", h|t], config) do
process_shared t, config.update_commands &[{:require, h}|&1]
end
defp process_shared(["-pr", h|t], config) do
process_shared t, config.update_commands &[{:parallel_require, h}|&1]
end
defp process_shared([erl, _|t], config) when erl in ["--erl", "--sname", "--name", "--cookie"] do
process_shared t, config
end
defp process_shared([erl|t], config) when erl in ["--detached", "--hidden"] do
process_shared t, config
end
defp process_shared(list, config) do
{ list, config }
end
# Process init options
defp process_argv(["--"|t], config) do
{ config, t }
end
defp process_argv(["+compile"|t], config) do
process_compiler t, config
end
defp process_argv(["+iex"|t], config) do
process_iex t, config
end
defp process_argv(["-S", h|t], config) do
{ config.update_commands(&[{:script, h}|&1]), t }
end
defp process_argv([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_argv(&1, &2)
_ ->
{ config.update_commands(&[{:file, h}|&1]), t }
end
end
defp process_argv([], config) do
{ config, [] }
end
# Process compiler options
defp process_compiler(["--"|t], config) do
{ config, t }
end
defp process_compiler(["-o", h|t], config) do
process_compiler t, config.output(h)
end
defp process_compiler(["--no-docs"|t], config) do
process_compiler t, config.update_compiler_options(&[{:docs, false}|&1])
end
defp process_compiler(["--no-debug-info"|t], config) do
process_compiler t, config.update_compiler_options(&[{:debug_info, false}|&1])
end
defp process_compiler(["--ignore-module-conflict"|t], config) do
process_compiler t, config.update_compiler_options(&[{:ignore_module_conflict, true}|&1])
end
defp process_compiler(["--warnings-as-errors"|t], config) do
process_compiler t, config.update_compiler_options(&[{:warnings_as_errors, true}|&1])
end
defp process_compiler(["--verbose"|t], config) do
process_compiler t, config.verbose_compile(true)
end
defp process_compiler([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_compiler(&1, &2)
_ ->
pattern = if :filelib.is_dir(h), do: "#{h}/**/*.ex", else: h
process_compiler t, config.update_compile &[pattern|&1]
end
end
defp process_compiler([], config) do
{ config.update_commands(&[{:compile, config.compile}|&1]), [] }
end
# Process iex options
defp process_iex(["--"|t], config) do
{ config, t }
end
# This clause is here so that Kernel.CLI does not error out with "unknown
# option"
defp process_iex(["--dot-iex", _|t], config) do
process_iex t, config
end
defp process_iex([opt, _|t], config) when opt in ["--remsh"] do
process_iex t, config
end
defp process_iex(["-S", h|t], config) do
{ config.update_commands(&[{:script, h}|&1]), t }
end
defp process_iex([h|t] = list, config) do
case h do
"-" <> _ ->
shared_option? list, config, &process_iex(&1, &2)
_ ->
{ config.update_commands(&[{:file, h}|&1]), t }
end
end
defp process_iex([], config) do
{ config, [] }
end
# Process commands
defp process_command({:cookie, h}, _config) do
if Node.alive? do
wrapper fn -> Node.set_cookie(binary_to_atom(h)) end
else
{ :error, "--cookie : Cannot set cookie if the node is not alive (set --name or --sname)" }
end
end
defp process_command({:eval, expr}, _config) when is_binary(expr) do
wrapper fn -> Code.eval_string(expr, []) end
end
defp process_command({:app, app}, _config) when is_binary(app) do
case Application.Behaviour.start(binary_to_atom(app)) do
{ :error, reason } ->
{ :error, "--app : Could not start application #{app}: #{inspect reason}" }
:ok ->
:ok
end
end
defp process_command({:script, file}, _config) when is_binary(file) do
if exec = find_elixir_executable(file) do
wrapper fn -> Code.require_file(exec) end
else
{ :error, "-S : Could not find executable #{file}" }
end
end
defp process_command({:file, file}, _config) when is_binary(file) do
if :filelib.is_regular(file) do
wrapper fn -> Code.require_file(file) end
else
{ :error, "No file named #{file}" }
end
end
defp process_command({:require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
wrapper fn -> Enum.map files, &Code.require_file(&1) end
else
{ :error, "-r : No files matched pattern #{pattern}" }
end
end
defp process_command({:parallel_require, pattern}, _config) when is_binary(pattern) do
files = Path.wildcard(pattern)
files = Enum.uniq(files)
files = Enum.filter files, &:filelib.is_regular(&1)
if files != [] do
wrapper fn -> Kernel.ParallelRequire.files(files) end
else
{ :error, "-pr : No files matched pattern #{pattern}" }
end
end
defp process_command({:compile, patterns}, config) do
:filelib.ensure_dir(:filename.join(config.output, "."))
case match_regular_files(patterns) do
{ :ok, [] } ->
{ :error, "No files matched provided patterns" }
{ :ok, files } ->
wrapper fn ->
Code.compiler_options(config.compiler_options)
Kernel.ParallelCompiler.files_to_path(files, config.output,
each_file: fn file -> if config.verbose_compile do IO.puts "Compiled #{file}" end end)
end
{ :missing, missing } ->
{ :error, "No files matched pattern(s) #{Enum.join(missing, ",")}" }
end
end
defp match_regular_files(patterns) do
matched_files = Enum.map patterns, fn(pattern) ->
case Path.wildcard(pattern) do
[] -> { :missing, pattern }
files -> { :ok, files }
end
end
files = Enum.filter_map matched_files,
fn(match) -> elem(match, 0) == :ok end,
&elem(&1, 1)
missing_patterns = Enum.filter_map matched_files,
fn(match) -> elem(match, 0) == :missing end,
&elem(&1, 1)
if missing_patterns == [] do
files = Enum.uniq(Enum.concat(files))
files = Enum.filter files, &:filelib.is_regular(&1)
{ :ok, files }
else
{ :missing, Enum.uniq(missing_patterns) }
end
end
defp wrapper(fun) do
fun.()
:ok
end
defp find_elixir_executable(file) do
if exec = System.find_executable(file) do
# If we are on Windows, the executable is going to be
# a .bat file that must be in the same directory as
# the actual Elixir executable.
case :os.type() do
{ :win32, _ } ->
exec = Path.rootname(exec)
if File.regular?(exec), do: exec
_ ->
exec
end
end
end
end