From 45756313d6c9ee5285d7ee7113122b4d16eace31 Mon Sep 17 00:00:00 2001 From: Tristan Jahnke Date: Mon, 4 Nov 2024 22:14:55 -0600 Subject: [PATCH] feat: add docs stuff --- lib/portfolio/blog.ex | 38 ++++++++- lib/portfolio/blog/post.ex | 36 ++++++++- lib/portfolio_web.ex | 25 +++++- .../components/core_components.ex | 78 ++++++++++++++++++- lib/portfolio_web/components/layouts.ex | 10 +++ lib/portfolio_web/controllers/error_html.ex | 35 ++++++++- lib/portfolio_web/controllers/error_json.ex | 25 ++++++ .../controllers/page_controller.ex | 59 ++++++++++++++ lib/portfolio_web/gettext.ex | 28 ++++--- lib/portfolio_web/router.ex | 32 +++++--- mix.exs | 14 +++- mix.lock | 7 ++ 12 files changed, 354 insertions(+), 33 deletions(-) diff --git a/lib/portfolio/blog.ex b/lib/portfolio/blog.ex index 2762cbb..a1b7812 100644 --- a/lib/portfolio/blog.ex +++ b/lib/portfolio/blog.ex @@ -1,6 +1,28 @@ defmodule Portfolio.Blog do + @moduledoc """ + This module handles fetching and caching blog posts. + + It uses the ConCache library to cache the list of blog posts for a specified + time-to-live (TTL) of 1 hour. The `list_posts/0` function retrieves the cached + posts or fetches them from a remote source if the cache is empty or expired. + + The `get_post_by_slug/1` function retrieves a specific blog post by its slug + from the cached list of posts. + + The `refresh_cache/0` function clears the cache and fetches the latest blog + posts from the remote source. + """ + @cache_ttl :timer.hours(1) + @doc """ + Retrieves the list of blog posts. + + If the cache is empty or expired, it fetches the posts from a remote source + and caches them for the specified TTL. + + Returns a list of blog post structs. + """ def list_posts do case ConCache.get_or_store(:blog_cache, :posts, fn -> posts = Portfolio.Blog.Post.fetch_posts("remoterabbit", "blog-posts") @@ -11,14 +33,28 @@ defmodule Portfolio.Blog do end end + @doc """ + Retrieves a blog post by its slug. + + Searches the cached list of blog posts for a post with the given slug. + + Returns the blog post struct if found, or `nil` if not found. + """ def get_post_by_slug(slug) do list_posts() |> Enum.find(&(&1.slug == slug)) end + @doc """ + Refreshes the blog post cache. + + Clears the cached list of blog posts and fetches the latest posts from the + remote source. + + Returns the updated list of blog post structs. + """ def refresh_cache do ConCache.delete(:blog_cache, :posts) list_posts() end end - diff --git a/lib/portfolio/blog/post.ex b/lib/portfolio/blog/post.ex index 75a0187..0724968 100644 --- a/lib/portfolio/blog/post.ex +++ b/lib/portfolio/blog/post.ex @@ -1,6 +1,41 @@ defmodule Portfolio.Blog.Post do + @moduledoc """ + This module defines a struct to represent a blog post and provides functions + to fetch and parse blog posts from a GitHub repository. + + ## Struct Fields + + - `title`: The title of the blog post. + - `content`: The HTML content of the blog post. + - `date`: The date the blog post was published. + - `slug`: The slug (URL-friendly version of the title) for the blog post. + - `description`: A short description of the blog post. + - `status`: The status of the blog post (e.g., "published", "draft"). + + ## Functions + + - `fetch_posts/2`: Fetches blog posts from a GitHub repository. + - `parse_content/2`: Parses the content of a blog post file. + """ defstruct [:title, :content, :date, :slug, :description, :status] + @doc """ + Fetches blog posts from a GitHub repository. + + This function retrieves the contents of the specified `path` (defaulting to "posts/") + in the given `owner` and `repo`. It filters the contents to only include Markdown files, + parses the content of each file, and returns a list of `%Portfolio.Blog.Post{}` structs + representing the published blog posts, sorted in descending order by date. + + ## Examples + + iex> Portfolio.Blog.Post.fetch_posts("owner", "repo") + [%Portfolio.Blog.Post{...}, ...] + + iex> Portfolio.Blog.Post.fetch_posts("owner", "repo", "custom/path/") + [%Portfolio.Blog.Post{...}, ...] + + """ def fetch_posts(owner, repo, path \\ "posts/") do case Tentacat.Contents.find(owner, repo, path) do {200, contents, _} -> @@ -41,4 +76,3 @@ defmodule Portfolio.Blog.Post do end end end - diff --git a/lib/portfolio_web.ex b/lib/portfolio_web.ex index e462229..69fbba0 100644 --- a/lib/portfolio_web.ex +++ b/lib/portfolio_web.ex @@ -5,8 +5,8 @@ defmodule PortfolioWeb do This can be used in your application as: - use PortfolioWeb, :controller - use PortfolioWeb, :html + use PortfolioWeb, :controller + use PortfolioWeb, :html The definitions below will be executed for every controller, component, etc, so keep them short and clean, focused @@ -17,8 +17,14 @@ defmodule PortfolioWeb do those modules here. """ + @doc """ + Provides the list of static paths for the application. + """ def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + @doc """ + Defines a quote for the router. + """ def router do quote do use Phoenix.Router, helpers: false @@ -30,12 +36,18 @@ defmodule PortfolioWeb do end end + @doc """ + Defines a quote for channels. + """ def channel do quote do use Phoenix.Channel end end + @doc """ + Defines a quote for controllers. + """ def controller do quote do use Phoenix.Controller, @@ -49,6 +61,9 @@ defmodule PortfolioWeb do end end + @doc """ + Defines a quote for live views. + """ def live_view do quote do use Phoenix.LiveView, @@ -58,6 +73,9 @@ defmodule PortfolioWeb do end end + @doc """ + Defines a quote for live components. + """ def live_component do quote do use Phoenix.LiveComponent @@ -66,6 +84,9 @@ defmodule PortfolioWeb do end end + @doc """ + Defines a quote for HTML components. + """ def html do quote do use Phoenix.Component diff --git a/lib/portfolio_web/components/core_components.ex b/lib/portfolio_web/components/core_components.ex index 0dc4bdd..865121a 100644 --- a/lib/portfolio_web/components/core_components.ex +++ b/lib/portfolio_web/components/core_components.ex @@ -588,8 +588,23 @@ defmodule PortfolioWeb.CoreComponents do """ end - ## JS Commands + @doc """ + Shows an element on the page with a transition animation. + + ## Parameters + + - `js` (optional): A `JS` struct representing the JavaScript context. If not provided, a new `JS` struct will be created. + - `selector`: A CSS selector string identifying the element(s) to be shown. + + ## Examples + + iex> show(~s(#my-element)) + %JS{...} + iex> show(%JS{}, ~s(.my-class)) + %JS{...} + + """ def show(js \\ %JS{}, selector) do JS.show(js, to: selector, @@ -600,6 +615,23 @@ defmodule PortfolioWeb.CoreComponents do ) end + @doc """ + Hides an element on the page with a transition animation. + + ## Parameters + + - `js` (optional): A `JS` struct representing the JavaScript context. If not provided, a new `JS` struct will be created. + - `selector`: A CSS selector string identifying the element(s) to be hidden. + + ## Examples + + iex> hide(~s(#my-element)) + %JS{...} + + iex> hide(%JS{}, ~s(.my-class)) + %JS{...} + + """ def hide(js \\ %JS{}, selector) do JS.hide(js, to: selector, @@ -611,6 +643,29 @@ defmodule PortfolioWeb.CoreComponents do ) end + @doc ~S""" + Shows a modal dialog with the given `id`. + + This function takes an optional `js` argument, which is a `Phoenix.LiveView.JS` struct + that can be used to chain additional JavaScript commands. If no `js` struct is provided, + a new one is created. + + The `id` argument is a string that represents the unique identifier of the modal dialog + to be shown. It is used to target the corresponding HTML elements in the DOM. + + The function performs the following actions: + + 1. Shows the modal dialog element with the given `id`. + 2. Shows the background overlay element with the ID `#{id}-bg`, applying a transition + effect to fade it in. + 3. Shows the container element with the ID `#{id}-container`. + 4. Adds the `overflow-hidden` class to the `` element to prevent scrolling while + the modal is open. + 5. Focuses the first focusable element within the modal content area with the ID + `#{id}-content`. + + Returns the updated `Phoenix.LiveView.JS` struct with the added JavaScript commands. + """ def show_modal(js \\ %JS{}, id) when is_binary(id) do js |> JS.show(to: "##{id}") @@ -618,11 +673,30 @@ defmodule PortfolioWeb.CoreComponents do to: "##{id}-bg", transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"} ) - |> show("##{id}-container") + |> JS.show(to: "##{id}-container") |> JS.add_class("overflow-hidden", to: "body") |> JS.focus_first(to: "##{id}-content") end + @doc """ + Hides a modal by applying CSS transitions and classes to the modal elements. + + This function takes an optional `JS` struct and the `id` of the modal to hide. + It returns a new `JS` struct with the necessary operations to hide the modal. + + The modal is hidden by: + 1. Fading out the background overlay + 2. Hiding the modal container + 3. Setting the modal element to `display: hidden` + 4. Removing the `overflow-hidden` class from the `body` element + 5. Popping the focus from the modal + + ## Examples + + iex> hide_modal(%JS{}, "my-modal") + %JS{...} + + """ def hide_modal(js \\ %JS{}, id) do js |> JS.hide( diff --git a/lib/portfolio_web/components/layouts.ex b/lib/portfolio_web/components/layouts.ex index 5f408ab..1c7988d 100644 --- a/lib/portfolio_web/components/layouts.ex +++ b/lib/portfolio_web/components/layouts.ex @@ -1,4 +1,14 @@ defmodule PortfolioWeb.Layouts do + @moduledoc """ + This module provides layout templates for the Phoenix application. + + The layouts are used to render the main HTML structure of the application, + including the header, footer, and other common elements. + + The `embed_templates` macro is used to embed the layout templates + located in the "layouts/*" directory. + """ + use PortfolioWeb, :html embed_templates "layouts/*" diff --git a/lib/portfolio_web/controllers/error_html.ex b/lib/portfolio_web/controllers/error_html.ex index b83257f..b2412d9 100644 --- a/lib/portfolio_web/controllers/error_html.ex +++ b/lib/portfolio_web/controllers/error_html.ex @@ -1,4 +1,20 @@ defmodule PortfolioWeb.ErrorHTML do + @moduledoc """ + Module for handling error pages in the PortfolioWeb application. + + This module provides functions for rendering error pages when an error occurs + in the application. By default, it renders a plain text page based on the + template name (e.g., "404.html" becomes "Not Found"). + + To customize the error pages, you can uncomment the `embed_templates/1` call + and add pages to the `error` directory, such as: + + * `lib/portfolio_web/controllers/error_html/404.html.heex` + * `lib/portfolio_web/controllers/error_html/500.html.heex` + + These files can contain HTML templates for rendering more detailed error pages. + """ + use PortfolioWeb, :html # If you want to customize your error pages, @@ -10,9 +26,22 @@ defmodule PortfolioWeb.ErrorHTML do # # embed_templates "error_html/*" - # The default is to render a plain text page based on - # the template name. For example, "404.html" becomes - # "Not Found". + @doc """ + Renders a plain text page based on the template name. + + This function is called when an error occurs and no custom error page is + provided. It takes the template name (e.g., "404.html") and returns a plain + text message based on the template name using `Phoenix.Controller.status_message_from_template/1`. + + ## Examples + + iex> PortfolioWeb.ErrorHTML.render("404.html", []) + "Not Found" + + iex> PortfolioWeb.ErrorHTML.render("500.html", []) + "Internal Server Error" + + """ def render(template, _assigns) do Phoenix.Controller.status_message_from_template(template) end diff --git a/lib/portfolio_web/controllers/error_json.ex b/lib/portfolio_web/controllers/error_json.ex index cec3546..5687d8c 100644 --- a/lib/portfolio_web/controllers/error_json.ex +++ b/lib/portfolio_web/controllers/error_json.ex @@ -1,4 +1,29 @@ defmodule PortfolioWeb.ErrorJSON do + @doc """ + Renders JSON error responses. + + This module is responsible for rendering JSON error responses in case of + errors during the request processing. It provides a default implementation + that returns the status message from the template name (e.g., "404.json" + becomes "Not Found"). + + You can customize the error rendering for specific status codes by adding + clauses to the `render/2` function. For example: + + def render("500.json", _assigns) do + %{errors: %{detail: "Internal Server Error"}} + end + + ## Examples + + iex> PortfolioWeb.ErrorJSON.render("404.json", %{}) + %{errors: %{detail: "Not Found"}} + + iex> PortfolioWeb.ErrorJSON.render("500.json", %{}) + %{errors: %{detail: "Internal Server Error"}} + + """ + # If you want to customize a particular status code, # you may add your own clauses, such as: # diff --git a/lib/portfolio_web/controllers/page_controller.ex b/lib/portfolio_web/controllers/page_controller.ex index 53dab18..6eb3599 100644 --- a/lib/portfolio_web/controllers/page_controller.ex +++ b/lib/portfolio_web/controllers/page_controller.ex @@ -1,16 +1,63 @@ defmodule PortfolioWeb.PageController do + @moduledoc """ + The PageController module handles the routing and rendering of pages in the Portfolio web application. + + ## Functions + + - `home/2`: Renders the home page. + - `blog/2`: Retrieves a list of blog posts and renders the blog page. + - `show_posts/2`: Retrieves a specific blog post by its slug and renders the post page. If the post is not found, it renders a 404 page. + - `projects/2`: Renders the projects page. + """ + use PortfolioWeb, :controller alias Portfolio.Blog + @doc """ + Renders the home page. + + ## Parameters + + - `conn`: The connection struct. + - `_params`: Not used. + + ## Returns + + - The rendered home page. + """ def home(conn, _params) do render(conn, :home) end + @doc """ + Retrieves a list of blog posts and renders the blog page. + + ## Parameters + + - `conn`: The connection struct. + - `_params`: Not used. + + ## Returns + + - The rendered blog page with a list of posts. + """ def blog(conn, _params) do posts = Blog.list_posts() render(conn, :blog, posts: posts) end + @doc """ + Retrieves a specific blog post by its slug and renders the post page. If the post is not found, it renders a 404 page. + + ## Parameters + + - `conn`: The connection struct. + - `%{"slug" => slug}`: A map containing the slug of the blog post. + + ## Returns + + - The rendered post page if the post is found, or a 404 page if the post is not found. + """ def show_posts(conn, %{"slug" => slug}) do case Blog.get_post_by_slug(slug) do nil -> @@ -23,6 +70,18 @@ defmodule PortfolioWeb.PageController do end end + @doc """ + Renders the projects page. + + ## Parameters + + - `conn`: The connection struct. + - `_params`: Not used. + + ## Returns + + - The rendered projects page. + """ def projects(conn, _params) do render(conn, :projects) end diff --git a/lib/portfolio_web/gettext.ex b/lib/portfolio_web/gettext.ex index cb1ad5c..2c3dad7 100644 --- a/lib/portfolio_web/gettext.ex +++ b/lib/portfolio_web/gettext.ex @@ -2,23 +2,31 @@ defmodule PortfolioWeb.Gettext do @moduledoc """ A module providing Internationalization with a gettext-based API. - By using [Gettext](https://hexdocs.pm/gettext), - your module gains a set of macros for translations, for example: + This module provides a set of macros for translations, including: - import PortfolioWeb.Gettext + - `gettext/1`: Simple translation of a string. + - `ngettext/3`: Plural translation of a string, taking a count argument. + - `dgettext/2`: Domain-based translation of a string, allowing for context-specific translations. - # Simple translation - gettext("Here is the string to translate") + These macros are based on the [Gettext](https://hexdocs.pm/gettext) library, which provides a + comprehensive solution for internationalization and localization in Elixir applications. - # Plural translation - ngettext("Here is the string to translate", - "Here are the strings to translate", - 3) + ## Examples + + import PortfolioWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) # Domain-based translation dgettext("errors", "Here is the error message to translate") - See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage and configuration options. """ use Gettext, otp_app: :portfolio end diff --git a/lib/portfolio_web/router.ex b/lib/portfolio_web/router.ex index cf66c15..f38f438 100644 --- a/lib/portfolio_web/router.ex +++ b/lib/portfolio_web/router.ex @@ -1,5 +1,24 @@ defmodule PortfolioWeb.Router do - # alias PortfolioWeb.PageController + @moduledoc """ + This module defines the routes for the PortfolioWeb application. + + The router is responsible for mapping incoming requests to the appropriate controller and action. + It also sets up pipelines for handling different types of requests (e.g., browser and API requests). + + The `scope` macro is used to define a group of routes that share a common prefix or pipeline. + The `pipe_through` macro is used to apply a pipeline to a group of routes. + + The routes defined in this module include: + + - `/` - Renders the home page + - `/blog` - Renders the blog index page + - `/blog/:slug` - Renders a specific blog post + - `/projects` - Renders the projects page + + If the application is running in development mode, additional routes are defined for the LiveDashboard + and Swoosh mailbox preview. + """ + use PortfolioWeb, :router pipeline :browser do @@ -24,18 +43,7 @@ defmodule PortfolioWeb.Router do get "/projects", PageController, :projects end - # Other scopes may use custom stacks. - # scope "/api", PortfolioWeb do - # pipe_through :api - # end - - # Enable LiveDashboard and Swoosh mailbox preview in development if Application.compile_env(:portfolio, :dev_routes) do - # If you want to use the LiveDashboard in production, you should put - # it behind authentication and allow only admins to access it. - # If your application does not have an admins-only section yet, - # you can use Plug.BasicAuth to set up some basic authentication - # as long as you are also using SSL (which you should anyway). import Phoenix.LiveDashboard.Router scope "/dev" do diff --git a/mix.exs b/mix.exs index 0a7d3cb..843fcfa 100644 --- a/mix.exs +++ b/mix.exs @@ -9,7 +9,15 @@ defmodule Portfolio.MixProject do elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), - deps: deps() + deps: deps(), + # Docs + name: "RemoteRabbit Portfolio", + source_url: "https://github.com/remoterabbit/portfolio-elixir", + homepage_url: "https://www.remoterabbit.io", + docs: [ + main: "Portfolio", + extras: ["README.md"] + ] ] end @@ -43,7 +51,9 @@ defmodule Portfolio.MixProject do {:tentacat, "~> 2.2"}, {:earmark, "~> 1.4"}, {:yaml_front_matter, "~> 1.0"}, - {:con_cache, "~> 1.0"} + {:con_cache, "~> 1.0"}, + {:ex_doc, "~> 0.34", only: :dev, runtime: false}, + {:makeup_html, ">= 0.0.0", only: :dev, runtime: false} ] end diff --git a/mix.lock b/mix.lock index 0f6fa40..8230b93 100644 --- a/mix.lock +++ b/mix.lock @@ -6,7 +6,9 @@ "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, "earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"}, + "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, @@ -17,11 +19,16 @@ "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "makeup_html": {:hex, :makeup_html, "0.1.1", "c3d4abd39d5f7e925faca72ada6e9cc5c6f5fa7cd5bc0158315832656cf14d7f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "44f2a61bc5243645dd7fafeaa6cc28793cd22f3c76b861e066168f9a5b2c26a4"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},