fix: add @spec to all public functions across 24 modules (CSM-019)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ defmodule BDS.Generation.GeneratedFileHash do
|
||||
field :updated_at, :integer
|
||||
end
|
||||
|
||||
@spec changeset(%__MODULE__{}, map()) :: Ecto.Changeset.t()
|
||||
def changeset(record, attrs) do
|
||||
record
|
||||
|> cast(attrs, [:project_id, :relative_path, :content_hash, :updated_at], empty_values: [nil])
|
||||
|
||||
@@ -286,6 +286,7 @@ defmodule BDS.Publishing do
|
||||
defp rsync_excludes(%{kind: :media}), do: ["--exclude=*.meta"]
|
||||
defp rsync_excludes(_target), do: []
|
||||
|
||||
@spec ensure_trailing_slash(String.t()) :: String.t()
|
||||
def ensure_trailing_slash(path), do: String.trim_trailing(path, "/") <> "/"
|
||||
|
||||
defp remote_dir_spec(credentials, remote_dir) do
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
defmodule BDS.Rendering.FileSystem do
|
||||
@moduledoc false
|
||||
|
||||
@type t :: %__MODULE__{root_paths: [String.t()]}
|
||||
defstruct [:root_paths]
|
||||
|
||||
@spec new([String.t()] | String.t()) :: t()
|
||||
def new(root_paths) when is_list(root_paths) do
|
||||
%__MODULE__{root_paths: Enum.uniq(root_paths)}
|
||||
end
|
||||
@@ -11,6 +13,7 @@ defmodule BDS.Rendering.FileSystem do
|
||||
new([root_path])
|
||||
end
|
||||
|
||||
@spec full_path(t(), String.t()) :: String.t()
|
||||
def full_path(%__MODULE__{root_paths: root_paths}, template_path) do
|
||||
normalized_path = to_string(template_path)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ defmodule BDS.Rendering.Filters do
|
||||
|
||||
alias BDS.Slug
|
||||
|
||||
@spec i18n(term(), String.t(), Liquex.Context.t()) :: String.t()
|
||||
def i18n(value, language, _context) do
|
||||
key = value |> to_string() |> String.trim()
|
||||
|
||||
@@ -15,6 +16,16 @@ defmodule BDS.Rendering.Filters do
|
||||
end
|
||||
end
|
||||
|
||||
@spec markdown(
|
||||
term(),
|
||||
term(),
|
||||
term(),
|
||||
map(),
|
||||
map(),
|
||||
String.t(),
|
||||
term(),
|
||||
Liquex.Context.t()
|
||||
) :: String.t()
|
||||
def markdown(
|
||||
value,
|
||||
_post_id,
|
||||
@@ -28,6 +39,8 @@ defmodule BDS.Rendering.Filters do
|
||||
render_markdown(value, canonical_post_paths, canonical_media_paths, language, context)
|
||||
end
|
||||
|
||||
@spec render_markdown(term(), map() | nil, map() | nil, String.t(), Liquex.Context.t()) ::
|
||||
String.t()
|
||||
def render_markdown(value, canonical_post_paths, canonical_media_paths, language, context) do
|
||||
value
|
||||
|> to_string()
|
||||
@@ -36,6 +49,7 @@ defmodule BDS.Rendering.Filters do
|
||||
|> rewrite_rendered_html_urls(canonical_post_paths || %{}, canonical_media_paths || %{})
|
||||
end
|
||||
|
||||
@spec slugify(term(), Liquex.Context.t()) :: String.t()
|
||||
def slugify(value, _context) do
|
||||
value
|
||||
|> to_string()
|
||||
|
||||
@@ -8,6 +8,7 @@ defmodule BDS.Rendering.Labels do
|
||||
|
||||
use Gettext, backend: BDS.Gettext
|
||||
|
||||
@spec for_language(String.t()) :: map()
|
||||
def for_language(language) do
|
||||
Gettext.with_locale(BDS.Gettext, language, fn ->
|
||||
%{
|
||||
@@ -34,6 +35,7 @@ defmodule BDS.Rendering.Labels do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec month_name(integer() | nil, String.t()) :: String.t() | nil
|
||||
def month_name(nil, _language), do: nil
|
||||
|
||||
def month_name(1, language) do
|
||||
|
||||
@@ -9,6 +9,7 @@ defmodule BDS.Rendering.LinksAndLanguages do
|
||||
alias BDS.Posts.Post
|
||||
alias BDS.Repo
|
||||
|
||||
@spec canonical_post_path_by_slug(String.t(), String.t()) :: %{String.t() => String.t()}
|
||||
def canonical_post_path_by_slug(project_id, main_language) do
|
||||
posts =
|
||||
Repo.all(
|
||||
@@ -36,6 +37,7 @@ defmodule BDS.Rendering.LinksAndLanguages do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec canonical_media_path_by_source_path(String.t()) :: %{String.t() => String.t()}
|
||||
def canonical_media_path_by_source_path(project_id) do
|
||||
Repo.all(from media in MediaAsset, where: media.project_id == ^project_id)
|
||||
|> Enum.reduce(%{}, fn media, acc ->
|
||||
@@ -54,6 +56,7 @@ defmodule BDS.Rendering.LinksAndLanguages do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec post_path(map(), String.t() | nil) :: String.t()
|
||||
def post_path(post, language_prefix)
|
||||
when is_binary(language_prefix) and language_prefix != "" do
|
||||
String.trim_trailing(language_prefix, "/") <> post_path(post, nil)
|
||||
@@ -73,11 +76,15 @@ defmodule BDS.Rendering.LinksAndLanguages do
|
||||
]) <> "/"
|
||||
end
|
||||
|
||||
@spec post_path(map(), String.t() | nil, String.t()) :: String.t()
|
||||
def post_path(post, language, main_language) do
|
||||
prefix = language_prefix(language, main_language)
|
||||
post_path(post, prefix)
|
||||
end
|
||||
|
||||
@spec link_contexts(String.t() | nil, String.t() | nil, :incoming | :outgoing, String.t()) :: [
|
||||
map()
|
||||
]
|
||||
def link_contexts(_project_id, nil, _direction, _main_language), do: []
|
||||
|
||||
def link_contexts(project_id, post_id, :incoming, main_language) do
|
||||
@@ -113,10 +120,12 @@ defmodule BDS.Rendering.LinksAndLanguages do
|
||||
end
|
||||
end
|
||||
|
||||
@spec language_prefix(String.t() | nil, String.t()) :: String.t()
|
||||
def language_prefix(language, main_language) when language == main_language, do: ""
|
||||
def language_prefix(nil, _main_language), do: ""
|
||||
def language_prefix(language, _main_language), do: "/#{language}"
|
||||
|
||||
@spec normalize_language(String.t() | nil, String.t()) :: String.t()
|
||||
def normalize_language(nil, fallback), do: fallback
|
||||
def normalize_language("", fallback), do: fallback
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ defmodule BDS.Rendering.ListArchive do
|
||||
alias BDS.Rendering.TemplateSelection
|
||||
use Gettext, backend: BDS.Gettext
|
||||
|
||||
@spec list_assigns(String.t(), map()) :: map()
|
||||
def list_assigns(project_id, assigns) do
|
||||
metadata = RenderMetadata.project_metadata(project_id)
|
||||
template_context = TemplateSelection.template_render_context(project_id)
|
||||
@@ -114,6 +115,7 @@ defmodule BDS.Rendering.ListArchive do
|
||||
}
|
||||
end
|
||||
|
||||
@spec not_found_assigns(String.t(), map()) :: map()
|
||||
def not_found_assigns(project_id, assigns) do
|
||||
metadata = RenderMetadata.project_metadata(project_id)
|
||||
|
||||
|
||||
@@ -14,16 +14,19 @@ defmodule BDS.Rendering.Metadata do
|
||||
alias BDS.Posts.Translation
|
||||
alias BDS.Tags.Tag
|
||||
|
||||
@spec project_metadata(String.t()) :: map()
|
||||
def project_metadata(project_id) do
|
||||
{:ok, metadata} = ProjectMetadata.get_project_metadata(project_id)
|
||||
metadata
|
||||
end
|
||||
|
||||
@spec menu_items(String.t()) :: [map()]
|
||||
def menu_items(project_id) do
|
||||
{:ok, %{items: items}} = Menu.get_menu(project_id)
|
||||
Enum.map(items, &to_template_menu_item/1)
|
||||
end
|
||||
|
||||
@spec menu_items_from_raw([map()]) :: [map()]
|
||||
def menu_items_from_raw(items) when is_list(items) do
|
||||
Enum.map(items, &to_template_menu_item/1)
|
||||
end
|
||||
@@ -52,6 +55,7 @@ defmodule BDS.Rendering.Metadata do
|
||||
defp menu_item_href(%{kind: :submenu}), do: "#"
|
||||
defp menu_item_href(_item), do: "#"
|
||||
|
||||
@spec blog_languages(map(), String.t()) :: [map()]
|
||||
def blog_languages(metadata, current_language) do
|
||||
([metadata.main_language] ++ (metadata.blog_languages || []))
|
||||
|> Enum.reject(&(&1 in [nil, ""]))
|
||||
@@ -72,11 +76,13 @@ defmodule BDS.Rendering.Metadata do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec tag_color_by_name(String.t()) :: %{String.t() => String.t() | nil}
|
||||
def tag_color_by_name(project_id) do
|
||||
Repo.all(from tag in Tag, where: tag.project_id == ^project_id, select: {tag.name, tag.color})
|
||||
|> Enum.into(%{}, fn {name, color} -> {name, color} end)
|
||||
end
|
||||
|
||||
@spec alternate_links(Post.t() | nil, String.t(), String.t()) :: [map()]
|
||||
def alternate_links(nil, _project_id, _main_language), do: []
|
||||
|
||||
def alternate_links(%Post{} = post, project_id, main_language) do
|
||||
@@ -104,22 +110,27 @@ defmodule BDS.Rendering.Metadata do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec backlinks([map()]) :: [map()]
|
||||
def backlinks(incoming_links) do
|
||||
Enum.map(incoming_links, fn link ->
|
||||
%{path: link.href, display_slug: link.display_slug, title: link.title}
|
||||
end)
|
||||
end
|
||||
|
||||
@spec default_pico_stylesheet_href(String.t() | nil) :: String.t()
|
||||
def default_pico_stylesheet_href(theme), do: PreviewAssets.stylesheet_href(theme)
|
||||
|
||||
@spec href_for_language(String.t()) :: String.t()
|
||||
def href_for_language(""), do: "/"
|
||||
def href_for_language(prefix), do: String.trim_trailing(prefix, "/") <> "/"
|
||||
|
||||
@spec calendar_initial_year(map() | nil) :: integer() | nil
|
||||
def calendar_initial_year(%{created_at: created_at}) when is_integer(created_at),
|
||||
do: Persistence.from_unix_ms!(created_at).year
|
||||
|
||||
def calendar_initial_year(_post), do: nil
|
||||
|
||||
@spec calendar_initial_month(map() | nil) :: integer() | nil
|
||||
def calendar_initial_month(%{created_at: created_at}) when is_integer(created_at),
|
||||
do: Persistence.from_unix_ms!(created_at).month
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ defmodule BDS.Rendering.PostRendering do
|
||||
alias BDS.Posts.Translation
|
||||
alias BDS.Repo
|
||||
|
||||
@spec post_assigns(String.t(), map()) :: map()
|
||||
def post_assigns(project_id, assigns) do
|
||||
metadata = RenderMetadata.project_metadata(project_id)
|
||||
template_context = TemplateSelection.template_render_context(project_id)
|
||||
@@ -180,6 +181,7 @@ defmodule BDS.Rendering.PostRendering do
|
||||
end
|
||||
end
|
||||
|
||||
@spec post_data_json_value(map()) :: String.t()
|
||||
def post_data_json_value(post_context) do
|
||||
case Jason.encode(%{
|
||||
id: Map.get(post_context, :id),
|
||||
@@ -238,6 +240,13 @@ defmodule BDS.Rendering.PostRendering do
|
||||
}
|
||||
end
|
||||
|
||||
@spec render_post_content(
|
||||
String.t() | nil,
|
||||
map(),
|
||||
map(),
|
||||
String.t(),
|
||||
Liquex.Context.t()
|
||||
) :: String.t()
|
||||
def render_post_content(
|
||||
content,
|
||||
canonical_post_paths,
|
||||
|
||||
@@ -11,6 +11,8 @@ defmodule BDS.Rendering.TemplateSelection do
|
||||
alias BDS.StarterTemplates
|
||||
alias BDS.Templates.Template
|
||||
|
||||
@spec load_template_source(String.t(), atom(), String.t() | nil) ::
|
||||
{:ok, String.t()} | {:error, term()}
|
||||
def load_template_source(project_id, kind, slug) do
|
||||
project = Projects.get_project!(project_id)
|
||||
|
||||
@@ -88,6 +90,8 @@ defmodule BDS.Rendering.TemplateSelection do
|
||||
end
|
||||
end
|
||||
|
||||
@spec render_template(String.t(), String.t(), map()) ::
|
||||
{:ok, String.t()} | {:error, String.t()}
|
||||
def render_template(project_id, source, assigns) do
|
||||
with {:ok, template_ast} <- Liquex.parse(source) do
|
||||
project = Projects.get_project!(project_id)
|
||||
@@ -145,6 +149,7 @@ defmodule BDS.Rendering.TemplateSelection do
|
||||
defp bundled_template_slug(_kind, slug) when is_binary(slug) and slug != "", do: slug
|
||||
defp bundled_template_slug(kind, _slug), do: StarterTemplates.default_slug(kind)
|
||||
|
||||
@spec template_render_context(String.t()) :: Liquex.Context.t()
|
||||
def template_render_context(project_id) do
|
||||
project = Projects.get_project!(project_id)
|
||||
|
||||
|
||||
@@ -1207,6 +1207,7 @@ defmodule BDS.Scripting.ApiDocs do
|
||||
}
|
||||
]
|
||||
|
||||
@spec render() :: String.t()
|
||||
def render do
|
||||
[
|
||||
"# API Documentation",
|
||||
|
||||
@@ -22,6 +22,7 @@ defmodule BDS.Scripting.Capabilities do
|
||||
import PostsCaps
|
||||
import ProjectsCaps
|
||||
|
||||
@spec for_project(String.t(), keyword()) :: map()
|
||||
def for_project(project_id, opts \\ []) when is_binary(project_id) and is_list(opts) do
|
||||
%{
|
||||
app: %{
|
||||
|
||||
@@ -3,12 +3,14 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
|
||||
alias BDS.Projects
|
||||
|
||||
@spec project_path(String.t()) :: String.t()
|
||||
def project_path(project_id) do
|
||||
project_id
|
||||
|> Projects.get_project()
|
||||
|> Projects.project_data_dir()
|
||||
end
|
||||
|
||||
@spec sanitize(term()) :: term()
|
||||
def sanitize(%DateTime{} = value), do: DateTime.to_iso8601(value)
|
||||
|
||||
def sanitize(%_struct{} = struct) do
|
||||
@@ -27,9 +29,11 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
def sanitize(value) when is_atom(value), do: Atom.to_string(value)
|
||||
def sanitize(value), do: value
|
||||
|
||||
@spec sanitize_nilable(term()) :: term()
|
||||
def sanitize_nilable(nil), do: nil
|
||||
def sanitize_nilable(value), do: sanitize(value)
|
||||
|
||||
@spec normalize_input(term()) :: term()
|
||||
def normalize_input(%_struct{} = struct), do: struct |> Map.from_struct() |> normalize_input()
|
||||
|
||||
def normalize_input(map) when is_map(map) do
|
||||
@@ -71,6 +75,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
def normalize_input(value) when is_atom(value), do: Atom.to_string(value)
|
||||
def normalize_input(value), do: value
|
||||
|
||||
@spec normalize_input_key(term()) :: term()
|
||||
def normalize_input_key(key) when is_integer(key), do: key
|
||||
def normalize_input_key(key) when is_float(key) and trunc(key) == key, do: trunc(key)
|
||||
|
||||
@@ -84,6 +89,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
def normalize_input_key(key) when is_atom(key), do: Atom.to_string(key)
|
||||
def normalize_input_key(key), do: key
|
||||
|
||||
@spec numeric_sequence_map?(map()) :: boolean()
|
||||
def numeric_sequence_map?(map) when map == %{}, do: false
|
||||
|
||||
def numeric_sequence_map?(map) do
|
||||
@@ -91,6 +97,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
Enum.all?(keys, &is_integer/1) and Enum.sort(keys) == Enum.to_list(1..length(keys))
|
||||
end
|
||||
|
||||
@spec normalize_map(term()) :: map()
|
||||
def normalize_map(value) when is_map(value) do
|
||||
case normalize_input(value) do
|
||||
normalized when is_map(normalized) -> normalized
|
||||
@@ -108,6 +115,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
|
||||
def normalize_map(_value), do: %{}
|
||||
|
||||
@spec normalize_string_list(term()) :: [String.t()]
|
||||
def normalize_string_list(value) when is_list(value), do: Enum.map(value, &to_string/1)
|
||||
|
||||
def normalize_string_list(value) when is_map(value) do
|
||||
@@ -121,6 +129,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
|
||||
def normalize_string_list(_value), do: []
|
||||
|
||||
@spec normalize_search_filters(term()) :: map()
|
||||
def normalize_search_filters(filters) do
|
||||
filters
|
||||
|> normalize_map()
|
||||
@@ -136,19 +145,24 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec integer_or_default(term(), integer()) :: integer()
|
||||
def integer_or_default(value, _default) when is_integer(value), do: value
|
||||
def integer_or_default(value, _default) when is_float(value), do: trunc(value)
|
||||
def integer_or_default(_value, default), do: default
|
||||
|
||||
@spec string_or_nil(term()) :: String.t() | nil
|
||||
def string_or_nil(value) when is_binary(value), do: value
|
||||
def string_or_nil(value) when is_atom(value), do: Atom.to_string(value)
|
||||
def string_or_nil(value) when is_number(value), do: to_string(value)
|
||||
def string_or_nil(_value), do: nil
|
||||
|
||||
@spec truthy?(term()) :: boolean()
|
||||
def truthy?(value), do: value in [true, "true", 1, 1.0, "1"]
|
||||
|
||||
@spec pad2(integer()) :: String.t()
|
||||
def pad2(value), do: value |> Integer.to_string() |> String.pad_leading(2, "0")
|
||||
|
||||
@spec blank_to_nil(term()) :: term()
|
||||
def blank_to_nil(nil), do: nil
|
||||
|
||||
def blank_to_nil(value) when is_binary(value) do
|
||||
@@ -157,13 +171,16 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
|
||||
def blank_to_nil(value), do: value
|
||||
|
||||
@spec maybe_put_query(map(), term(), term()) :: map()
|
||||
def maybe_put_query(query, _key, false), do: query
|
||||
def maybe_put_query(query, _key, nil), do: query
|
||||
def maybe_put_query(query, key, value), do: Map.put(query, key, value)
|
||||
|
||||
@spec maybe_put_opt(keyword(), atom(), term()) :: keyword()
|
||||
def maybe_put_opt(opts, _key, nil), do: opts
|
||||
def maybe_put_opt(opts, key, value), do: Keyword.put(opts, key, value)
|
||||
|
||||
@spec maybe_put_normalized_list(map(), atom() | String.t()) :: map()
|
||||
def maybe_put_normalized_list(attrs, key) do
|
||||
case Map.fetch(attrs, key) do
|
||||
{:ok, value} -> Map.put(attrs, key, normalize_string_list(value))
|
||||
@@ -171,9 +188,11 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
end
|
||||
end
|
||||
|
||||
@spec compare_optional(term(), (term() -> boolean())) :: boolean()
|
||||
def compare_optional(nil, _fun), do: true
|
||||
def compare_optional(value, fun) when is_function(fun, 1), do: fun.(value)
|
||||
|
||||
@spec parse_datetime(term()) :: DateTime.t() | nil
|
||||
def parse_datetime(nil), do: nil
|
||||
def parse_datetime(value) when is_integer(value), do: DateTime.from_unix!(value, :millisecond)
|
||||
|
||||
@@ -186,16 +205,20 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
|
||||
def parse_datetime(_value), do: nil
|
||||
|
||||
@spec unwrap_result({:ok, term()} | {:error, term()}, (term() -> term())) :: term()
|
||||
def unwrap_result(result, transform \\ &sanitize/1)
|
||||
def unwrap_result({:ok, value}, transform), do: transform.(value)
|
||||
def unwrap_result({:error, _reason}, _transform), do: nil
|
||||
|
||||
@spec boolean_result({:ok, term()} | {:error, term()}) :: boolean()
|
||||
def boolean_result({:ok, _value}), do: true
|
||||
def boolean_result({:error, _reason}), do: false
|
||||
|
||||
@spec atom_result({:ok, term()} | {:error, term()}, term()) :: boolean()
|
||||
def atom_result({:ok, value}, expected_value), do: value == expected_value
|
||||
def atom_result(_result, _expected_value), do: false
|
||||
|
||||
@spec thumbnail_size(term()) :: :small | :medium | :large | :ai
|
||||
def thumbnail_size(size) do
|
||||
case blank_to_nil(size) do
|
||||
"medium" -> :medium
|
||||
@@ -205,6 +228,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
end
|
||||
end
|
||||
|
||||
@spec thumbnail_mime(String.t()) :: String.t()
|
||||
def thumbnail_mime(path) do
|
||||
case Path.extname(path) do
|
||||
".jpg" -> "image/jpeg"
|
||||
@@ -213,6 +237,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
end
|
||||
end
|
||||
|
||||
@spec shell_open_system_path(String.t()) :: :ok | {:error, term()}
|
||||
def shell_open_system_path(path) do
|
||||
{command, args} =
|
||||
case :os.type() do
|
||||
@@ -229,6 +254,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
error -> {:error, error}
|
||||
end
|
||||
|
||||
@spec shell_reveal_system_path(String.t()) :: :ok | {:error, term()}
|
||||
def shell_reveal_system_path(path) do
|
||||
{command, args} =
|
||||
case :os.type() do
|
||||
@@ -245,6 +271,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
error -> {:error, error}
|
||||
end
|
||||
|
||||
@spec zero_or_one_arg((term() -> term())) :: (list(), tuple() -> {list(), tuple()})
|
||||
def zero_or_one_arg(callback) when is_function(callback, 1) do
|
||||
fn args, state ->
|
||||
decoded_args = :luerl.decode_list(args, state)
|
||||
@@ -253,6 +280,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
end
|
||||
end
|
||||
|
||||
@spec one_arg((term() -> term())) :: (list(), tuple() -> {list(), tuple()})
|
||||
def one_arg(callback) when is_function(callback, 1) do
|
||||
fn args, state ->
|
||||
decoded_args = :luerl.decode_list(args, state)
|
||||
@@ -267,6 +295,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
end
|
||||
end
|
||||
|
||||
@spec two_arg((term(), term() -> term())) :: (list(), tuple() -> {list(), tuple()})
|
||||
def two_arg(callback) when is_function(callback, 2) do
|
||||
fn args, state ->
|
||||
decoded_args = :luerl.decode_list(args, state)
|
||||
@@ -282,6 +311,7 @@ defmodule BDS.Scripting.Capabilities.Util do
|
||||
end
|
||||
end
|
||||
|
||||
@spec three_arg((term(), term(), term() -> term())) :: (list(), tuple() -> {list(), tuple()})
|
||||
def three_arg(callback) when is_function(callback, 3) do
|
||||
fn args, state ->
|
||||
decoded_args = :luerl.decode_list(args, state)
|
||||
|
||||
@@ -3,10 +3,12 @@ defmodule BDS.Scripting.JobRunner do
|
||||
|
||||
use GenServer, restart: :temporary
|
||||
|
||||
@spec start_link(keyword()) :: GenServer.on_start()
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts)
|
||||
end
|
||||
|
||||
@spec cancel(pid()) :: :ok
|
||||
def cancel(pid) when is_pid(pid) do
|
||||
GenServer.call(pid, :cancel)
|
||||
end
|
||||
|
||||
@@ -3,30 +3,37 @@ defmodule BDS.Scripting.JobStore do
|
||||
|
||||
use GenServer
|
||||
|
||||
@spec start_link(term()) :: GenServer.on_start()
|
||||
def start_link(_opts) do
|
||||
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
|
||||
end
|
||||
|
||||
@spec put_job(map()) :: :ok
|
||||
def put_job(job) when is_map(job) do
|
||||
GenServer.call(__MODULE__, {:put_job, job})
|
||||
end
|
||||
|
||||
@spec update_job(String.t(), map()) :: :ok
|
||||
def update_job(job_id, attrs) when is_binary(job_id) and is_map(attrs) do
|
||||
GenServer.call(__MODULE__, {:update_job, job_id, attrs})
|
||||
end
|
||||
|
||||
@spec attach_runner(String.t(), pid()) :: :ok
|
||||
def attach_runner(job_id, pid) when is_binary(job_id) and is_pid(pid) do
|
||||
GenServer.call(__MODULE__, {:attach_runner, job_id, pid})
|
||||
end
|
||||
|
||||
@spec detach_runner(String.t()) :: :ok
|
||||
def detach_runner(job_id) when is_binary(job_id) do
|
||||
GenServer.call(__MODULE__, {:detach_runner, job_id})
|
||||
end
|
||||
|
||||
@spec fetch_job(String.t()) :: map() | nil
|
||||
def fetch_job(job_id) when is_binary(job_id) do
|
||||
GenServer.call(__MODULE__, {:fetch_job, job_id})
|
||||
end
|
||||
|
||||
@spec fetch_job!(String.t()) :: map()
|
||||
def fetch_job!(job_id) when is_binary(job_id) do
|
||||
case fetch_job(job_id) do
|
||||
nil -> raise KeyError, key: job_id, term: :jobs
|
||||
@@ -34,6 +41,7 @@ defmodule BDS.Scripting.JobStore do
|
||||
end
|
||||
end
|
||||
|
||||
@spec runner_for(String.t()) :: pid() | nil
|
||||
def runner_for(job_id) when is_binary(job_id) do
|
||||
GenServer.call(__MODULE__, {:runner_for, job_id})
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ defmodule BDS.Scripting.JobSupervisor do
|
||||
|
||||
use DynamicSupervisor
|
||||
|
||||
@spec start_link(term()) :: Supervisor.on_start()
|
||||
def start_link(_opts) do
|
||||
DynamicSupervisor.start_link(__MODULE__, :ok, name: __MODULE__)
|
||||
end
|
||||
|
||||
@@ -31,6 +31,7 @@ defmodule BDS.UI.Commands do
|
||||
%{id: :upload_site, accelerator: "CTRL+SHIFT+U"}
|
||||
]
|
||||
|
||||
@spec handle_shortcut(BDS.UI.Workbench.t(), map()) :: BDS.UI.Workbench.t()
|
||||
def handle_shortcut(state, shortcut) when is_map(shortcut) do
|
||||
case command_for_shortcut(shortcut) do
|
||||
nil -> state
|
||||
@@ -38,6 +39,7 @@ defmodule BDS.UI.Commands do
|
||||
end
|
||||
end
|
||||
|
||||
@spec command_for_shortcut(map()) :: atom() | nil
|
||||
def command_for_shortcut(shortcut) when is_map(shortcut) do
|
||||
key = shortcut |> BDS.MapUtils.attr(:key, "") |> String.downcase()
|
||||
|
||||
@@ -54,6 +56,7 @@ defmodule BDS.UI.Commands do
|
||||
end
|
||||
end
|
||||
|
||||
@spec client_shortcuts() :: [map()]
|
||||
def client_shortcuts do
|
||||
@menu_shortcuts
|
||||
|> Enum.filter(&Map.has_key?(&1, :key))
|
||||
@@ -67,6 +70,7 @@ defmodule BDS.UI.Commands do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec accelerator_label(atom()) :: String.t() | nil
|
||||
def accelerator_label(command_id) when is_atom(command_id) do
|
||||
case Enum.find(@menu_shortcuts, &(&1.id == command_id)) do
|
||||
%{accelerator: accelerator} -> accelerator
|
||||
|
||||
@@ -8,6 +8,7 @@ defmodule BDS.UI.Dashboard do
|
||||
alias BDS.Repo
|
||||
alias BDS.Tags.Tag
|
||||
|
||||
@spec snapshot(String.t() | nil) :: map()
|
||||
def snapshot(nil), do: empty_snapshot()
|
||||
|
||||
def snapshot(project_id) when is_binary(project_id) do
|
||||
@@ -23,6 +24,7 @@ defmodule BDS.UI.Dashboard do
|
||||
}
|
||||
end
|
||||
|
||||
@spec empty_snapshot() :: map()
|
||||
def empty_snapshot do
|
||||
%{
|
||||
title: "dashboard.title",
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule BDS.UI.MenuBar do
|
||||
alias BDS.UI.Registry
|
||||
alias BDS.UI.Workbench
|
||||
|
||||
@spec default_groups(keyword()) :: [map()]
|
||||
def default_groups(opts \\ []) do
|
||||
dev_mode? = Keyword.get(opts, :dev_mode?, false)
|
||||
|
||||
@@ -80,6 +81,7 @@ defmodule BDS.UI.MenuBar do
|
||||
]
|
||||
end
|
||||
|
||||
@spec execute(Workbench.t(), atom()) :: Workbench.t()
|
||||
def execute(state, :toggle_sidebar), do: Workbench.toggle_sidebar(state)
|
||||
def execute(state, :toggle_panel), do: Workbench.toggle_panel(state)
|
||||
def execute(state, :toggle_assistant_sidebar), do: Workbench.toggle_assistant_sidebar(state)
|
||||
|
||||
@@ -3,8 +3,10 @@ defmodule BDS.UI.Registry do
|
||||
|
||||
use Gettext, backend: BDS.Gettext
|
||||
|
||||
@spec default_sidebar_view() :: atom()
|
||||
def default_sidebar_view, do: :posts
|
||||
|
||||
@spec sidebar_views() :: [map()]
|
||||
def sidebar_views do
|
||||
[
|
||||
%{
|
||||
@@ -90,6 +92,7 @@ defmodule BDS.UI.Registry do
|
||||
]
|
||||
end
|
||||
|
||||
@spec editor_routes() :: [map()]
|
||||
def editor_routes do
|
||||
[
|
||||
%{id: :dashboard, singleton: true, entity_tab: false, title: dgettext("ui", "Dashboard")},
|
||||
@@ -138,6 +141,8 @@ defmodule BDS.UI.Registry do
|
||||
]
|
||||
end
|
||||
|
||||
@spec sidebar_view(atom()) :: map() | nil
|
||||
def sidebar_view(id) when is_atom(id), do: Enum.find(sidebar_views(), &(&1.id == id))
|
||||
@spec editor_route(atom()) :: map() | nil
|
||||
def editor_route(id) when is_atom(id), do: Enum.find(editor_routes(), &(&1.id == id))
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule BDS.UI.Session do
|
||||
alias BDS.BoundedAtoms
|
||||
alias BDS.UI.Workbench
|
||||
|
||||
@spec serialize(Workbench.t()) :: map()
|
||||
def serialize(state) do
|
||||
%{
|
||||
"sidebar_visible" => state.sidebar_visible,
|
||||
@@ -28,6 +29,7 @@ defmodule BDS.UI.Session do
|
||||
}
|
||||
end
|
||||
|
||||
@spec restore(map()) :: Workbench.t()
|
||||
def restore(payload) when is_map(payload) do
|
||||
state =
|
||||
Workbench.new(
|
||||
|
||||
@@ -16,6 +16,7 @@ defmodule BDS.UI.Sidebar do
|
||||
|
||||
@default_page_size 500
|
||||
|
||||
@spec snapshot(String.t() | nil) :: map()
|
||||
def snapshot(nil), do: empty_snapshot()
|
||||
|
||||
def snapshot(project_id) when is_binary(project_id) do
|
||||
@@ -39,6 +40,7 @@ defmodule BDS.UI.Sidebar do
|
||||
}
|
||||
end
|
||||
|
||||
@spec view(String.t() | nil, String.t() | atom(), map()) :: map()
|
||||
def view(project_id, view_id, params \\ %{})
|
||||
|
||||
def view(nil, view_id, _params), do: empty_view(view_id)
|
||||
@@ -102,6 +104,7 @@ defmodule BDS.UI.Sidebar do
|
||||
end
|
||||
end
|
||||
|
||||
@spec empty_snapshot() :: map()
|
||||
def empty_snapshot do
|
||||
%{
|
||||
"posts" => empty_view("posts"),
|
||||
|
||||
@@ -19,6 +19,8 @@ defmodule BDS.UI.Workbench do
|
||||
:find_duplicates
|
||||
])
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
defstruct sidebar_visible: true,
|
||||
sidebar_width: 280,
|
||||
active_view: :posts,
|
||||
@@ -30,6 +32,7 @@ defmodule BDS.UI.Workbench do
|
||||
editor_route: :dashboard,
|
||||
dirty_tabs: MapSet.new()
|
||||
|
||||
@spec new(keyword()) :: t()
|
||||
def new(opts \\ []) do
|
||||
%__MODULE__{
|
||||
sidebar_visible: Keyword.get(opts, :sidebar_visible, true),
|
||||
@@ -48,14 +51,17 @@ defmodule BDS.UI.Workbench do
|
||||
|> normalize_panel()
|
||||
end
|
||||
|
||||
@spec set_sidebar_width(t(), integer()) :: t()
|
||||
def set_sidebar_width(state, width) when is_integer(width) do
|
||||
%{state | sidebar_width: clamp_sidebar_width(width)}
|
||||
end
|
||||
|
||||
@spec set_assistant_sidebar_width(t(), integer()) :: t()
|
||||
def set_assistant_sidebar_width(state, width) when is_integer(width) do
|
||||
%{state | assistant_sidebar_width: clamp_assistant_sidebar_width(width)}
|
||||
end
|
||||
|
||||
@spec open_tab(t(), atom() | String.t(), String.t(), atom()) :: t()
|
||||
def open_tab(state, type, id, intent) do
|
||||
{tabs, opened_tab} = upsert_tab(state.tabs, normalize_type(type), id, intent)
|
||||
|
||||
@@ -66,6 +72,7 @@ defmodule BDS.UI.Workbench do
|
||||
|> normalize_panel()
|
||||
end
|
||||
|
||||
@spec open_tab_in_background(t(), atom() | String.t(), String.t(), atom()) :: t()
|
||||
def open_tab_in_background(state, type, id, intent) do
|
||||
current_active = state.active_tab
|
||||
{tabs, _opened_tab} = upsert_tab(state.tabs, normalize_type(type), id, intent)
|
||||
@@ -77,6 +84,7 @@ defmodule BDS.UI.Workbench do
|
||||
|> normalize_panel()
|
||||
end
|
||||
|
||||
@spec close_tab(t(), atom() | String.t(), String.t()) :: t()
|
||||
def close_tab(state, type, id) do
|
||||
type = normalize_type(type)
|
||||
target = {type, id}
|
||||
@@ -103,6 +111,7 @@ defmodule BDS.UI.Workbench do
|
||||
end
|
||||
end
|
||||
|
||||
@spec pin_tab(t(), atom() | String.t(), String.t()) :: t()
|
||||
def pin_tab(state, type, id) do
|
||||
type = normalize_type(type)
|
||||
|
||||
@@ -114,11 +123,13 @@ defmodule BDS.UI.Workbench do
|
||||
%{state | tabs: tabs}
|
||||
end
|
||||
|
||||
@spec clear_tabs(t()) :: t()
|
||||
def clear_tabs(state) do
|
||||
%{state | tabs: [], active_tab: nil, editor_route: :dashboard, dirty_tabs: MapSet.new()}
|
||||
|> normalize_panel()
|
||||
end
|
||||
|
||||
@spec mark_dirty(t(), atom() | String.t(), String.t()) :: t()
|
||||
def mark_dirty(state, type, id) do
|
||||
if normalize_type(type) == :post do
|
||||
%{state | dirty_tabs: MapSet.put(state.dirty_tabs, {normalize_type(type), id})}
|
||||
@@ -127,32 +138,40 @@ defmodule BDS.UI.Workbench do
|
||||
end
|
||||
end
|
||||
|
||||
@spec clear_dirty(t(), atom() | String.t(), String.t()) :: t()
|
||||
def clear_dirty(state, type, id) do
|
||||
%{state | dirty_tabs: MapSet.delete(state.dirty_tabs, {normalize_type(type), id})}
|
||||
end
|
||||
|
||||
@spec dirty?(t(), atom() | String.t(), String.t()) :: boolean()
|
||||
def dirty?(state, type, id) do
|
||||
MapSet.member?(state.dirty_tabs, {normalize_type(type), id})
|
||||
end
|
||||
|
||||
@spec toggle_sidebar(t()) :: t()
|
||||
def toggle_sidebar(state), do: %{state | sidebar_visible: not state.sidebar_visible}
|
||||
|
||||
@spec set_panel_visible(t(), boolean()) :: t()
|
||||
def set_panel_visible(state, visible) when is_boolean(visible) do
|
||||
%{state | panel: %{state.panel | visible: visible}}
|
||||
end
|
||||
|
||||
@spec toggle_panel(t()) :: t()
|
||||
def toggle_panel(state) do
|
||||
set_panel_visible(state, not state.panel.visible)
|
||||
end
|
||||
|
||||
@spec toggle_assistant_sidebar(t()) :: t()
|
||||
def toggle_assistant_sidebar(state) do
|
||||
%{state | assistant_sidebar_visible: not state.assistant_sidebar_visible}
|
||||
end
|
||||
|
||||
@spec set_panel_tab(t(), atom()) :: t()
|
||||
def set_panel_tab(state, tab) when tab in [:tasks, :output, :post_links, :git_log] do
|
||||
%{state | panel: %{state.panel | active_tab: tab}}
|
||||
end
|
||||
|
||||
@spec click_activity(t(), atom() | String.t()) :: t()
|
||||
def click_activity(state, activity_id) do
|
||||
activity_id = normalize_type(activity_id)
|
||||
|
||||
@@ -163,6 +182,7 @@ defmodule BDS.UI.Workbench do
|
||||
end
|
||||
end
|
||||
|
||||
@spec activity_buttons(t(), integer()) :: [map()]
|
||||
def activity_buttons(state, git_badge_count \\ 0) do
|
||||
Registry.sidebar_views()
|
||||
|> Enum.map(fn view ->
|
||||
@@ -176,6 +196,7 @@ defmodule BDS.UI.Workbench do
|
||||
end)
|
||||
end
|
||||
|
||||
@spec status_bar(t(), keyword()) :: map()
|
||||
def status_bar(state, opts) do
|
||||
post_count = Keyword.get(opts, :post_count, 0)
|
||||
media_count = Keyword.get(opts, :media_count, 0)
|
||||
|
||||
Reference in New Issue
Block a user