Skip to content

Commit

Permalink
Merge pull request #5 from nhpip/version_4.0
Browse files Browse the repository at this point in the history
Version 4.0
  • Loading branch information
nhpip authored Jun 1, 2022
2 parents 3cb40fc + 18453ba commit 8b87a6d
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 18 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ Initializes the History app. Takes the following parameters:
### History.state()
Displays the current state:
```
History version 3.0 is
History version 4.0 is
enabled:
Current history is 199 commands in size.
Current bindings are 153 variables in size.
Expand All @@ -187,12 +187,28 @@ Displays the current state:
### History.clear()
Clears the history and bindings. If scope is :global the IEx session needs restarting for the changes to take effect.

### History.clear_history(range)
Clears the history only, if no argument all history is cleared, else history from 1 to value is cleared

### History.clear_bindings()
Clears bindings only

### History.unbind(vars)
Unbinds a variable or list of variables, varibales should be expressed as atoms

### History.stop_clear()
Clears the history and bindings then stops the service. If scope is :global the IEx session needs restarting for the changes to take effect.

### History.configuration()
Displays the current conifuration

### History.save_config(filename)
Saves the configuration to filename

### History.load_config(filename)
Loads the configuration from filename.
NOTE: All changes may not be applied, to do this specify the filename in `History.initialize/1` instead of a config keyword list

### History.configure/2
Allows the following options to be changed, but not saved:
```
Expand Down
72 changes: 63 additions & 9 deletions lib/history.ex
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ defmodule History do
A word about aliases. Rather than using something like #{IO.ANSI.cyan()}alias History, as: H#{IO.ANSI.white()}, please use #{IO.ANSI.cyan()}History.alias(H)#{IO.ANSI.white()} instead.
"""

@version "3.0"
@version "4.0"
@module_name String.trim_leading(Atom.to_string(__MODULE__), "Elixir.")
@exec_name String.trim_leading(Atom.to_string(__MODULE__) <> ".x", "Elixir.")

Expand Down Expand Up @@ -155,11 +155,14 @@ defmodule History do
]
]
Alternatively a filename can be given that was saved with #{IO.ANSI.cyan()}History.save_config()#{IO.ANSI.white()}
#{IO.ANSI.cyan()}scope#{IO.ANSI.white()} can be one of #{IO.ANSI.cyan()}:local, :global#{IO.ANSI.white()} or a #{IO.ANSI.cyan()}node()#{IO.ANSI.white()} name
"""
def initialize(config \\ []) do
def initialize(config_or_filename \\ []) do
config = do_load_config(config_or_filename)
if history_configured?(config) && not is_enabled?() do
new_config = save_config(config)
new_config = init_save_config(config)
History.Bindings.inject_command("IEx.configure(colors: [syntax_colors: [atom: :black]])")
History.Events.initialize(new_config)
|> History.Bindings.initialize()
Expand Down Expand Up @@ -291,6 +294,26 @@ defmodule History do
:ok
end

@doc """
Clears the history only. If #{IO.ANSI.cyan()}scope#{IO.ANSI.white()} is #{IO.ANSI.cyan()}:global#{IO.ANSI.white()}
the IEx session needs restarting for the changes to take effect. If a value is passed it will clear that many history
entries from start, otherwise the entire history is cleared.
"""
def clear_history(val \\ :all) do
History.Events.clear_history(val)
if History.configuration(:scope, :local) == :global && val == :all, do:
IO.puts("\n#{IO.ANSI.green()}Please restart your shell session for the changes to take effect")
:ok
end

@doc """
Clears the bindings.
"""
def clear_bindings() do
History.Bindings.clear()
:ok
end

@doc """
Clears the history and bindings then stops the service. If #{IO.ANSI.cyan()}scope#{IO.ANSI.white()} is #{IO.ANSI.cyan()} :global#{IO.ANSI.white()} the IEx session needs restarting for the changes to take effect.
"""
Expand All @@ -313,11 +336,34 @@ defmodule History do
Returns the current shell bindings.
"""
def get_bindings() do
try do
:ets.tab2list(Process.get(:history_bindings_ets_label))
catch
_,_ -> []
end
History.Bindings.get_bindings()
end

@doc """
Unbinds a variable or list of variables (specify variables as atoms, e.g. foo becomes :foo).
"""
def unbind(vars) when is_list(vars), do:
History.Bindings.unbind(vars)
def unbind(var), do:
unbind([var])

@doc """
Saves the current configuration to file.
"""
def save_config(filename) do
data = :io_lib.format("~p.", [configuration()]) |> List.flatten()
:file.write_file(filename, data)
end

@doc """
Loads the current configuration to file #{IO.ANSI.cyan()}History.save_config()#{IO.ANSI.white()}.
NOTE: Not all options can be set during run-time. Instead pass the filename as a single argument to #{IO.ANSI.cyan()}History.initialize()#{IO.ANSI.white()}
"""
def load_config(filename) do
config = do_load_config(filename)
Process.put(:history_config, config)
config
end

@doc """
Expand Down Expand Up @@ -483,7 +529,15 @@ defmodule History do
do: raise(%ArgumentError{message: "History is not enabled"})
end

defp save_config(config) do
defp do_load_config(filename) when is_binary(filename) do
{:ok, [config]} = :file.consult(filename)
config
end

defp do_load_config(config), do:
config

defp init_save_config(config) do
infinity_limit = History.Events.infinity_limit()
colors = Keyword.get(config, :colors, @default_colors)
new_colors = Enum.map(@default_colors,
Expand Down
31 changes: 23 additions & 8 deletions lib/history/events_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ defmodule History.Events.Server do
GenServer.call(__MODULE__, {:clear, self()})
end

@doc false
def clear_history(range) do
GenServer.call(__MODULE__, {:clear_history, range})
end

@doc false
def stop_clear() do
GenServer.call(__MODULE__, {:stop_clear, self()})
Expand Down Expand Up @@ -95,6 +100,12 @@ defmodule History.Events.Server do
end
end

def handle_call({:clear_history, range}, _from, process_info) do
new_process_info = %{process_info | limit: range}
apply_table_limits(new_process_info, :requested)
{:reply, :ok_done, process_info}
end

def handle_call(:stop_clear, _from, process_info) do
Enum.each(process_info,
fn({key, value}) when is_pid(key) ->
Expand Down Expand Up @@ -233,11 +244,12 @@ defmodule History.Events.Server do
{:noreply, new_process_info}
end

def handle_info({:DOWN, _, :process, shell_pid, :noproc}, %{scope: scope, store_count: store_count} = process_info) do
def handle_info({:DOWN, _, :process, shell_pid, _}, %{scope: scope, store_count: store_count} = process_info) do
case Map.get(process_info, shell_pid) do
%{store_name: store_name} ->
%{store_name: store_name, key_buffer_history_pid: kbh_pid} ->
store_count = History.Store.close_store(store_name, scope, store_count)
new_process_info = Map.delete(process_info, shell_pid)
Process.exit(kbh_pid, :down)
{:noreply, %{new_process_info | store_count: store_count}}
_ ->
{:noreply, process_info}
Expand Down Expand Up @@ -533,24 +545,27 @@ defmodule History.Events.Server do
end
end

defp apply_table_limits(%{limit: limit} = process_info) do
defp apply_table_limits(%{limit: limit} = process_info, type \\ :automatic) do
Enum.each(process_info,
fn({pid, %{store_name: name} = _map}) ->
current_size = History.Store.info(name, :size)
if current_size >= limit,
do: do_apply_table_limits(pid, name, current_size, limit)
limit = if limit == :all, do: current_size, else: limit
if current_size >= limit && type == :automatic,
do: do_apply_table_limits(pid, name, current_size, limit, type)
if type == :requested,
do: do_apply_table_limits(pid, name, current_size, limit, type)
(x)-> x
end)
end

defp do_apply_table_limits(pid, name, current_size, limit) do
defp do_apply_table_limits(pid, name, current_size, limit, type) do
table_name = inspect(pid) |> String.to_atom()
if :ets.info(table_name) == :undefined do
:ets.new(table_name, [:named_table, :ordered_set, :public])
History.Store.foldl(name, [], fn({key, _}, _) -> :ets.insert(table_name, {key, :ok}) end)
end
remove = round(limit * @table_limit_exceeded_factor) + current_size - limit
Enum.reduce(1..remove, :ets.first(table_name),
remove = if type == :automatic, do: round(limit * @table_limit_exceeded_factor) + current_size - limit, else: min(limit, current_size)
Enum.reduce(0..remove, :ets.first(table_name),
fn(_, key) ->
:ets.delete(table_name, key)
History.Store.delete_data(name, key)
Expand Down
45 changes: 45 additions & 0 deletions lib/history/history_bindings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ defmodule History.Bindings do
@doc false
def do_initialize(_), do: :not_ok

@doc false
def get_bindings() do
try do
:ets.tab2list(Process.get(:history_bindings_ets_label))
catch
_,_ -> []
end
end

@doc false
def get_value(label, ets_name) do
case :ets.lookup(ets_name, label) do
Expand All @@ -71,6 +80,19 @@ defmodule History.Bindings do
end
end

@doc false
def unbind(vars) do
save_bindings? = History.configuration(:save_bindings, true)
if save_bindings? do
send_msg({:unbind, vars, self()})
wait_rsp(:ok_done)
set_bindings_for_shell()
:ok
else
:bindings_disabled
end
end

@doc false
def clear() do
save_bindings? = History.configuration(:save_bindings, true)
Expand Down Expand Up @@ -168,6 +190,15 @@ defmodule History.Bindings do
History.Store.close_store(db_labels.store_name)
send(pid, :ok_done)

{:unbind, vars, pid} ->
Enum.map(vars, fn label ->
:ets.delete(db_labels.ets_name, label)
History.Store.delete_data(db_labels.store_name, label)
end)
size = :ets.info(config.db_labels.ets_name, :size)
send(pid, :ok_done)
binding_evaluator_loop(%{config | binding_count: size})

:check_bindings ->
new_bindings = get_bindings_from_shell(config)
persist_bindings(new_bindings, db_labels)
Expand All @@ -187,6 +218,10 @@ defmodule History.Bindings do
defp persist_bindings(bindings, %{ets_name: ets_name, store_name: store_name}) do
Enum.map(bindings, fn {label, value} ->
case :ets.lookup(ets_name, label) do
_ when value == :could_not_bind ->
:ets.delete(ets_name, label)
History.Store.delete_data(store_name, label)

[{_, ^value}] ->
:ok

Expand Down Expand Up @@ -226,6 +261,16 @@ defmodule History.Bindings do
inject_command("IEx.Evaluator.init(:ack, History.Bindings.find_server(), Process.group_leader(), [binding: []])")
end

defp set_bindings_for_shell() do
ets_name = Process.get(:history_bindings_ets_label)
clear_bindings_from_shell()
bindings = :ets.foldl(
fn {label, _value}, acc ->
["#{label} = History.Bindings.get_value(:#{label},:#{ets_name}); " | acc]
end, [], ets_name) |> List.to_string()
inject_command(bindings <> " :ok")
end

defp make_reg_name() do
gl_node = History.my_real_node() |> Atom.to_string()
String.to_atom("history_binding_finder_#{gl_node}")
Expand Down
12 changes: 12 additions & 0 deletions lib/history/history_events.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ defmodule History.Events do
end
end

@doc false
def clear_history(range) do
if History.configuration(:scope, :local) != :global do
Server.clear_history(range)
else
if range == :all, do:
History.get_log_path() <> "/erlang-shell*"
|> Path.wildcard()
|> Enum.each(fn file -> File.rm(file) end)
end
end

@doc false
def stop_clear() do
if History.configuration(:scope, :local) != :global do
Expand Down

0 comments on commit 8b87a6d

Please sign in to comment.