Skip to content

Commit

Permalink
Merge pull request #2 from ProducerMatt/iodata_overhaul
Browse files Browse the repository at this point in the history
  • Loading branch information
ProducerMatt authored Mar 14, 2024
2 parents b35875b + e112146 commit 591aaaa
Show file tree
Hide file tree
Showing 22 changed files with 772 additions and 230 deletions.
14 changes: 14 additions & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
%{
configs: [
%{
name: "default",
checks: %{
disabled: [
{Credo.Check.Refactor.CyclomaticComplexity, []},
{Credo.Check.Refactor.Nesting, []},
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
]
}
}
]
}
14 changes: 8 additions & 6 deletions .dialyzer_ignore.exs
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
[
{"deps/nostrum/lib/nostrum/consumer.ex", "Function Nostrum.ConsumerGroup.join/1 does not exist."},
{"lib/service/discord.ex", "Function Nostrum.Api.create_message/2 does not exist."},
{"lib/service/discord.ex", "Function Nostrum.Api.get_user!/1 does not exist."},
{"lib/service/discord.ex", "Function Nostrum.Struct.User.full_name/1 does not exist."},
{"lib/service/discord.ex", "Function Nostrum.Api.get_channel_message/2 does not exist."},
{"lib/service/discord.ex", "Function Nostrum.Cache.Me.get/0 does not exist."},
{"lib/service/discord.ex", "Function Nostrum.Struct.User.full_name/1 does not exist."},
{"lib/stampede/application.ex", "Function Mix.env/0 does not exist."},
{"lib/stampede/interact.ex", "Function Mix.env/0 does not exist."},
{"/build/source/lib/elixir/lib/gen_server.ex", "Callback info about the Nostrum.Consumer behaviour is not available."},
{"deps/type_check/lib/type_check/spec.ex", "The pattern can never match the type {:ok, [], _}."},
{"lib/plugin.ex", "Function usage_tuples/0 has no local return."},
{"lib/plugin.ex", "Function job_result/0 has no local return."},
{"lib/plugin.ex", "Function plugin_job_result/0 has no local return."},
{"lib/service/discord.ex", "Function logger_state/0 has no local return."},
{"lib/service/dummy.ex", "@spec for send_msg has more types than are returned by the function."},
{"lib/service/dummy.ex", "Function dummy_channel_id/0 has no local return."},
{"lib/service/dummy.ex", "Function dummy_msg_id/0 has no local return."},
{"lib/service/dummy.ex", "Function msg_content/0 has no local return."},
{"lib/service/dummy.ex", "Function msg_reference/0 has no local return."},
{"lib/service/dummy.ex", "Function msg_tuple/0 has no local return."},
{"lib/service/dummy.ex", "Function msg_tuple_incoming/0 has no local return."},
{"lib/service/dummy.ex", "Function channel/0 has no local return."},
{"lib/service/dummy.ex", "Function channel_buffers/0 has no local return."},
{"lib/service/dummy.ex", "Function dummy_servers/0 has no local return."},
{"lib/site_config.ex", "Function schema/0 has no local return."},
{"lib/stampede.ex", "Function log_level/0 has no local return."},
{"lib/stampede.ex", "Function log_msg/0 has no local return."},
{"lib/stampede.ex", "Function prefix/0 has no local return."},
{"lib/stampede.ex", "Function module_function_args/0 has no local return."},
{"lib/stampede.ex", "Function io_list/0 has no local return."},
{"lib/stampede.ex", "Function str_list/0 has no local return."},
{"lib/stampede.ex", "Function traceback/0 has no local return."},
{"lib/stampede.ex", "Function enabled_plugs/0 has no local return."},
{"lib/stampede.ex", "Function channel_lock_action/0 has no local return."},
{"lib/stampede.ex", "Function channel_lock_status/0 has no local return."},
{"lib/stampede.ex", "Function throw_internal_error/0 has no local return."},
{"lib/stampede.ex", "Function throw_internal_error/1 only terminates with explicit exception."},
{"lib/stampede/interact.ex", "Function mod_state/0 has no local return."},
{"deps/type_check/lib/type_check/spec.ex", "The pattern can never match the type {:ok, [], _}."},
{"lib/plugin.ex", "Function usage_tuples/0 has no local return."}
{"lib/txt_block.ex", "Function block/0 has no local return."},
{"lib/txt_block.ex", "Function type/0 has no local return."},
{"lib/txt_block.ex", "Function t/0 has no local return."},
]
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ erl_crash.dump

### Elixir Patch ###
/doc
/.elixir-tools

# Nix resources
/.nix-mix
Expand Down
27 changes: 23 additions & 4 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import Config

stampede_metadata = [
:stampede_component,
:stampede_msg_id,
:stampede_plugin,
:interaction_id
]

nostrum_metadata = [:shard, :guild, :channel]

extra_metadata =
[
:crash_reason,
:error_code,
:file,
:line
] ++
stampede_metadata ++
nostrum_metadata

config :stampede,
compile_env: Mix.env()

config :logger, :console,
level: :debug,
# extra nostrum metadata
metadata: [:shard, :guild, :channel]
metadata: extra_metadata

config :logger,
handle_otp_reports: true,
Expand All @@ -17,7 +36,7 @@ config :stampede, :logger, [
{:handler, :file_log, :logger_std_h,
%{
config: %{
file: ~c"logs/stampede.log",
file: ~c"logs/#{Mix.env()}/#{node()}.log",
filesync_repeat_interval: 5000,
file_check: 5000,
max_no_bytes: 10_000_000,
Expand All @@ -29,7 +48,7 @@ config :stampede, :logger, [
format: {LogstashLoggerFormatter, :format},
colors: [enabled: false],
level: :all,
metadata: [:shard, :guild, :channel, :stampede_component, :interaction_id]
metadata: extra_metadata
)
}}
]
Expand All @@ -41,6 +60,6 @@ config :mnesia,
# Notice the single quotes
dir: ~c".mnesia/#{Mix.env()}/#{node()}"

for config <- "../config/*.secret.exs" |> Path.expand(__DIR__) |> Path.wildcard() do
for config <- "./*.secret.exs" |> Path.expand(__DIR__) |> Path.wildcard() do
import_config config
end
146 changes: 106 additions & 40 deletions lib/plugin.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
defmodule PluginCrashInfo do
use TypeCheck
use TypeCheck.Defstruct

defstruct!(
plugin: _ :: module(),
type: _ :: :throw | :error,
error: _ :: Exception.t(),
stacktrace: _ :: Exception.stacktrace()
)

defmacro new(kwlist) do
quote do
struct!(
unquote(__MODULE__),
unquote(kwlist)
)
end
end
end

defmodule Plugin do
use TypeCheck
require Logger
require PluginCrashInfo
alias PluginCrashInfo, as: CrashInfo
alias Stampede, as: S
alias S.{Msg, Response, Interaction}
require Interaction
Expand All @@ -13,7 +36,7 @@ defmodule Plugin do
"""
@type! usage_tuples :: list(String.t() | {String.t(), String.t()})
@callback process_msg(SiteConfig.t(), Msg.t()) :: nil | Response.t()
@callback is_at_module(SiteConfig.t(), Msg.t()) :: boolean() | {:cleaned, text :: String.t()}
@callback at_module?(SiteConfig.t(), Msg.t()) :: boolean() | {:cleaned, text :: String.t()}
@callback usage() :: usage_tuples()
@callback description() :: String.t()

Expand All @@ -22,7 +45,7 @@ defmodule Plugin do
@behaviour unquote(__MODULE__)

@impl Plugin
def is_at_module(cfg, msg) do
def at_module?(cfg, msg) do
# Should we process the message?
text =
SiteConfig.fetch!(cfg, :prefix)
Expand All @@ -35,12 +58,26 @@ defmodule Plugin do
end
end

defoverridable is_at_module: 2
defoverridable at_module?: 2
end
end

@doc "returns loaded modules using the Plugin behavior."
@spec! ls() :: MapSet.t(module())
def ls() do
S.find_submodules(__MODULE__)
S.find_submodules(Plugin)
|> Enum.reduce(MapSet.new(), fn
mod, acc ->
b =
mod.__info__(:attributes)
|> Keyword.get(:behaviour, [])

if Plugin in b do
MapSet.put(acc, mod)
else
acc
end
end)
end

def default_plugin_mfa(plug, [cfg, msg]) do
Expand Down Expand Up @@ -76,25 +113,29 @@ defmodule Plugin do
}
catch
t, e ->
error_type =
case t do
:error ->
"an error"

:throw ->
"a throw"
end

st = Exception.format(t, e, __STACKTRACE__)

log = """
Message from #{inspect(msg.author_id)} lead to #{error_type} in plugin #{m}:
#{st}
"""

Logger.error(log)

_ = spawn(SiteConfig.fetch!(cfg, :service), :log_plugin_error, [cfg, log])
st = __STACKTRACE__

error_info =
CrashInfo.new(plugin: m, type: t, error: e, stacktrace: st)

{:ok, formatted} =
Service.apply_service_function(
cfg,
:log_plugin_error,
[cfg, msg, error_info]
)

Logger.error(
fn ->
formatted
|> TxtBlock.to_str_list(:logger)
|> IO.iodata_to_binary()
end,
crash_reason: {e, st},
stampede_component: SiteConfig.fetch!(cfg, :service),
stampede_msg_id: msg.id,
stampede_plugin: m
)

{:job_error, {e, st}}
end
Expand Down Expand Up @@ -191,7 +232,6 @@ defmodule Plugin do
)
|> S.Interact.record_interaction!()

# TODO: logging interactions
chosen_response

%Response{callback: {mod, fun, args}} ->
Expand All @@ -200,7 +240,9 @@ defmodule Plugin do

new_tb = [
traceback,
"\nTop response was a callback, so i called it. It responded with: \n\"#{followup.text}\"",
"\nTop response was a callback, so i called it. It responded with: \n\"",
followup.text,
"\"",
followup.why
]

Expand All @@ -225,7 +267,13 @@ defmodule Plugin do

Map.update!(response, :why, fn tb ->
[
"Channel #{msg.channel_id} was locked to module #{m}, function #{f}, so we called it.\n"
"Channel ",
msg.channel_id |> inspect(),
"was locked to module ",
m |> inspect(),
", function ",
"f",
", so we called it.\n"
| tb
]
end)
Expand Down Expand Up @@ -268,7 +316,11 @@ defmodule Plugin do
end)
end

@spec! resolve_responses(list(plugin_job_result())) :: map()
@spec! resolve_responses(nonempty_list(plugin_job_result())) :: %{
# NOTE: reversing order from 'nil | response' to 'response | nil' makes Dialyzer not count nil?
r: nil | S.Response.t(),
tb: S.traceback()
}
def resolve_responses(tlist) do
do_rr(tlist, nil, [])
end
Expand All @@ -286,8 +338,10 @@ defmodule Plugin do
traceback
) do
do_rr(rest, chosen_response, [
traceback
| "\nWe asked #{inspect(plug)}, and it decided not to answer."
traceback,
"\nWe asked ",
plug |> inspect(),
", and it decided not to answer."
])
end

Expand All @@ -297,8 +351,10 @@ defmodule Plugin do
traceback
) do
do_rr(rest, chosen_response, [
traceback
| "\nWe asked #{inspect(plug)}, but it timed out."
traceback,
"\nWe asked ",
plug |> inspect(),
", but it timed out."
])
end

Expand All @@ -311,17 +367,23 @@ defmodule Plugin do
if response.callback do
[
traceback,
"\nWe asked #{inspect(plug)}, and it responded with confidence #{inspect(response.confidence)} offering a callback.\nWhen asked why, it said: \"",
"\nWe asked ",
plug |> inspect(),
", and it responded with confidence ",
response.confidence |> inspect(),
" offering a callback.\nWhen asked why, it said: \"",
response.why,
"\""
]
else
[
traceback,
"""
We asked #{inspect(plug)}, and it responded with confidence #{inspect(response.confidence)}:
#{S.markdown_quote(response.text)}
""",
"\nWe asked ",
plug |> inspect(),
", and it responded with confidence ",
response.confidence |> inspect(),
":\n",
{:quote_block, response.text},
"When asked why, it said: \"",
response.why,
"\""
Expand All @@ -330,8 +392,8 @@ defmodule Plugin do

if chosen_response == nil do
do_rr(rest, response, [
tb
| "\nWe chose this response."
tb,
"\nWe chose this response."
])
else
do_rr(rest, chosen_response, tb)
Expand All @@ -347,8 +409,12 @@ defmodule Plugin do
rest,
chosen_response,
[
traceback
| "\nWe asked #{inspect(plug)}, but there was an error of type #{inspect(val)}."
traceback,
"\nWe asked ",
plug |> inspect(),
", but there was an error of type ",
val |> inspect(),
"."
]
)
end
Expand Down
Loading

0 comments on commit 591aaaa

Please sign in to comment.