{"id":21564639,"url":"https://github.com/functionhaus/archivist","last_synced_at":"2025-04-10T13:07:45.449Z","repository":{"id":62428988,"uuid":"207627492","full_name":"functionhaus/archivist","owner":"functionhaus","description":"A no-nonsense, compile-time blogging engine in pure Elixir","archived":false,"fork":false,"pushed_at":"2019-09-24T17:56:49.000Z","size":2345,"stargazers_count":18,"open_issues_count":8,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T11:45:47.112Z","etag":null,"topics":["blogging","elixir"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/functionhaus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-10T17:56:17.000Z","updated_at":"2020-11-07T02:45:42.000Z","dependencies_parsed_at":"2022-11-01T20:02:08.375Z","dependency_job_id":null,"html_url":"https://github.com/functionhaus/archivist","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionhaus%2Farchivist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionhaus%2Farchivist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionhaus%2Farchivist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionhaus%2Farchivist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/functionhaus","download_url":"https://codeload.github.com/functionhaus/archivist/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247755554,"owners_count":20990626,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blogging","elixir"],"created_at":"2024-11-24T10:16:37.635Z","updated_at":"2025-04-10T13:07:45.431Z","avatar_url":"https://github.com/functionhaus.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Archivist\n\n[![CircleCI](https://circleci.com/gh/functionhaus/archivist.svg?style=svg)](https://circleci.com/gh/functionhaus/archivist)\n\nArchivist is a straightforward blogging utility for generating article content\nat compile time from version-controlled article and image files. It is built to\nbe used in conjunction with the [Arcdown plaintext article parser library](https://github.com/functionhaus/arcdown).\n\nArchivist is inspired by the general approach of Cẩm Huỳnh's great\n[Nabo](https://github.com/qcam/nabo) library with some key differences:\n\n* Articles are formatted in `Arcdown` format by default, allowing for more robust articles and article features.\n\n* Content parsing and sorting mechanisms are exposed as anonymous functions, easiliy exposing custom functionality.\n\n* Articles can be organized into nested *topic* directories for better organization. Topics are parsed in a hierarchical structure.\n\n* Use of an \"intermediate library pattern\" is supported, allowing content and articles to be stored in a dedicated library and separate repository.\n\n* Default attributes are included for both author names and email addresses\n\n* `created_at` and `published_at` timestamps are permitted\n\n* Flexible *tags* can be applied as desired to any article\n\n* Custom content constraints throw warnings during compilation if violated\n\n* Slug uniqueness is enforced by default and triggers compile-time warnings\n\n* Image files can be stored alongside articles, and accessed with helpers\n\n## Installation\n\nThe package can be installed by adding `archivist` to your list of\ndependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:archivist, \"~\u003e 0.3\"}\n  ]\nend\n```\n\n## Usage\n\nThe heart of Archivist is the `Archive` module, which acts as a repository for\nexposing query functions for articles, slugs, topics, etc. You can create an\nArchive out of any Elixir module by using `Archivist.Archive` like this:\n\n```elixir\ndefmodule MyApp.Archive\n  use Archivist.Archive\nend\n\n# this alias is just a nicety, not required\nalias MyApp.Archive\n\nArchive.articles()\nArchive.topics() # hierarchical topics\nArchive.topics_list() # flattened topics and sub-topics\nArchive.tags()\nArchive.slugs()\nArchive.authors()\n```\n\nAdditionally Archvist exposes helpers for reading paths for articles and\nimage files:\n\n```elixir\nArchive.article_paths()\nArchive.image_paths()\n```\n\nArchivist 0.3.x and 0.2.x versions expect you to create your article content directory at\n`priv/archive/articles` at the root of your elixir library, like this:\n\n`priv/archive/articles/journey_to_the_center_of_the_earth.ad`\n\nIf you'd like to customize any of your archive's behavior, you can define any of\nthe following options when it is used in the target archive directory. The values\nshown are the defaults:\n\n```elixir\ndefmodule MyApp.Archive\n  use Archivist.Archive,\n    archive_dir: \"priv/archive\",\n    content_dir: \"articles\",\n    content_pattern: \"**/*.ad\",\n    image_dir: \"images\",\n    image_pattern: \"**/*.{jpg,gif,png}\",\n    article_sorter: \u0026(Map.get(\u00261, :published_at) \u003e= Map.get(\u00262, :published_at)),\n    article_parser: \u0026Arcdown.parse_file(\u00261),\n    content_parser: \u0026Earmark.as_html!(\u00261),\n    slug_warnings: true,\n    application: nil,\n    valid_tags: nil,\n    valid_topics: nil,\n    valid_authors: nil\nend\n```\n\nArchivist will read any files with the `.ad` extension in your content directory\nor in any of its subdirectories, and parse the content of those files with the\nparser you've selected (Arcdown by default)\n\nIf you'd like to store your archive somewhere besides `priv/archive` you can\nassign a custom path to your archive like this:\n\n```elixir\ndefmodule MyApp.Archive\n  use Archivist.Archive, archive_dir: \"assets/archive\",\nend\n```\n\n## Arcdown\n\nArcdown supports the following features for articles:\n\n* Article Content\n* Article Summary\n* Topics\n* Sub-Topics\n* Tags\n* Published Datetime\n* Creation Datetime\n* Author Name\n* Author Email\n* Article Slug\n\nHere is an example article written in *Arcdown (.ad)* format:\n\n```\nThe Day the Earth Stood Still \u003cthe-day-the-earth-stood-still\u003e\nby Julian Blaustein \u003cjulian@blaustein.com\u003e\n\nFiled under: Films \u003e Sci-Fi \u003e Classic\n\nCreated @ 10:24pm on 1/20/2019\nPublished @ 10:20pm on 1/20/2019\n\n* Sci-Fi\n* Horror\n* Thrillers\n* Aliens\n\nSummary:\nA sci-fi classic about a flying saucer landing in Washington, D.C.\n\n---\n\nThe Day the Earth Stood Still (a.k.a. Farewell to the Master and Journey to the\nWorld) is a 1951 American black-and-white science fiction film from 20th Century\nFox, produced by Julian Blaustein and directed by Robert Wise.\n```\n\nBy default Archivist will parse and return article content as\n`Archivist.Article` structs. The parsing output of the above article example\nwould look like this:\n\n```elixir\n%Archivist.Article{\n  author: \"Julian Blaustein\",\n  content: \"The Day the Earth Stood Still (a.k.a. Farewell to the Master and Journey to the\\nWorld) is a 1951 American black-and-white science fiction film from 20th Century\\nFox, produced by Julian Blaustein and directed by Robert Wise.\\n\",\n  parsed_content: \"\u003cp\u003eThe Day the Earth Stood Still (a.k.a. Farewell to the Master and Journey to the\\nWorld) is a 1951 American black-and-white science fiction film from 20th Century\\nFox, produced by Julian Blaustein and directed by Robert Wise.\u003c/p\u003e\\n\",\n  created_at: #DateTime\u003c2019-01-20 22:24:00Z\u003e,\n  email: \"julian@blaustein.com\",\n  published_at: #DateTime\u003c2019-04-02 04:30:00Z\u003e,\n  slug: \"the-day-the-earth-stood-still\",\n  summary: \"A sci-fi classic about a flying saucer landing in Washington, D.C.\",\n  tags: [:sci_fi, :horror, :thrillers, :aliens],\n  title: \"The Day the Earth Stood Still\",\n  topics: [\"Films\", \"Sci-Fi\", \"Classic\"]\n}\n```\n## Intermediate Library Pattern\n\nWhile it's completely acceptable use Archivist.Archive within the same\napplication in which the content archive is located, sites with lots of content\nand publishers who commit changes frequently will quickly find the git\nhistory for their application littered with content-related commits that have\nnothing to do with the broader functionality of the application itself.\n\nTo remedy this issue, Archivist permits and encourages the use of an\nintermediate library to house the content archive (`myapp_blog` for example),\nand then to include that intermediate library in the target application where\nthe content is being used and displayed.\n\nThis approach requires you generate a new mix library with `mix new myapp_blog`,\nand then to publish that repository so that it's available to other Elixir and\nErlang applications, via hex.pm or hex.pm organizations for example.\n\nThe preferred way to implement this approach is to include `archivist` as a\ndependency in your intermediate library (rather than in your application), and\nthen to create a new Archive in your intermediate library like this:\n\n```elixir\ndefmodule MyappBlog.Archive do\n  use Archivist.Archive,\n    application: :myapp_blog,\n    archive_dir: \"archive\"\nend\n```\n\nNote that this approach requires you to add the name of your otp application in\nthe `application` flag when your archive is defined. Also note that `archive_dir`\nis compressed to just `archive` instead of `priv/archive` since this approach\nautomatically assumes that content will be stored in the `priv` directory of the\notp app indicated by the `application` option.\n\nHere is an example of an excerpt from the mixfile of an intermediate library:\n\n```elixir\n  defp deps do\n    [\n      {:archivist, \"~\u003e 0.3\"},\n      {:ex_doc, \"\u003e= 0.0.0\", only: :dev, runtime: false}\n    ]\n  end\n\n  defp package do\n    [\n     files: [\"lib\", \"priv\", \"mix.exs\", \"README.md\"],\n     organization: \"my_hex_org\"\n    ]\n  end\n```\n\nRequiring the priv dir here is essential to ensuring that the content archive is\npackaged with the hex release, and is then made available for the target\napplication.\n\nSetting the organization here scopes the published package to a hex\norganization, thus ensuring that it remains private.\n\nThen in the application where your content is being used, be sure to include the\nintermediate library as a dependency:\n\n```elixir\ndefp deps do\n  [\n    ...\n    {:myapp_blog, \"~\u003e 0.1\", organization: \"my_hex_org\"}\n  ]\nend\n```\n\nAnd then you should be able to use your content directly in your application:\n\n```elixir\nMyappBlog.Archive.articles()\nMyappBlog.Archive.topics()\n```\n\n## Parsed Content Constraints\n\nAs of Archivist version `0.2.6` archives can receive flags for lists of\n`valid_topics` and `valid_tags`. Version `0.2.9` added support for\n`valid_authors` constraints. Here are some examples of constraints:\n\n```elixir\ndefmodule Myapp.Archive do\n  use Archivist,\n    valid_topics: [\n      \"Action\",\n      \"Classic\",\n      \"Crime\",\n      \"Fiction\",\n      \"Films\",\n      \"Sci-Fi\"\n    ],\n    valid_tags: [\n      :action,\n      :adventure,\n      :aliens,\n      :crime,\n      :horror,\n      :literature,\n      :modern_classic,\n      :sci_fi,\n      :thrillers\n    ],\n    valid_authors: [\n      \"Jules Verne\",\n      \"Julian Blaustein\",\n      \"Michael Mann\"\n    ]\nend\n```\n\nAdding articles with tags, topics or authors that don't conform to these lists,\nor using a topic directory structure that doesn't conform to these lists will\nthrow warnings at compile time, like this:\n\n```elixir\nwarning: Archivist Archive contains invalid topics: Action, Classic\n  (archivist) lib/archivist/parsers/article_parser.ex:77: Archivist.ArticleParser.warn_invalid/3\n\nwarning: Archivist Archive contains invalid tags: action, adventure\n  (archivist) lib/archivist/parsers/article_parser.ex:77: Archivist.ArticleParser.warn_invalid/3\n\nwarning: Archivist Archive contains invalid authors: Ernest Hemingway\n  (archivist) lib/archivist/parsers/article_parser.ex:77: Archivist.ArticleParser.warn_invalid/3\n```\n\nCompilation will not cease, however, simply because these constraints are\nbeing violated.\n\nPlease note that only exact topic and author matches are accounted for here,\nso`\"Sci-Fi\"` will not be considered equivalent to `\"SciFi\"` and will throw a\nwarning. Similarly, \"J.D. Salinger\" will not be considered to be the same\nauthor as \"JD Salinger\" by the article parser.\n\nIf you do not want warnings for tags, topics or authors during compilation\nsimply don't declare any values for `valid_topics`, `valid_tags`, or\n`valid_authors` depending on your desired outcomes, and they'll be ignored.\n\nAlso note that enforcement of valid topics currently is only compared to the\nflattened list of topics and sub-topics. There is no functionality in place\nat the moment for constraining specific topic hierarchies.\n\nIt should additionally be noted that the `slug_warnings` filter is on by\ndefault, meaning that the parser will throw warnings if duplicate slugs are\nfound across articles in your content archive. This can be turned off by\nsetting `slug_warnings: false` when you declare your archive, like this:\n\n\n```elixir\ndefmodule Myapp.Archive do\n  use Archivist, slug_warnings: false\nend\n```\n\n## Mounting Images with Plug\n\nIf you choose to store images with your archive, it's probably most useful to\nhave that content mounted as a static assets path somewhere where the content\ncan be digested with Webpack or whichever assets manager you're using.\n\nFor systems built with Plug (including Phoenix), it's easy enough to mount the\nimages path with `Plug.Static` at the path of your choice. Simply call the name\nof the otp app where your content is stored along with the path to the images:\n\n```elixir\nplug Plug.Static,\n  at: \"/blog/images/\",\n  from: {:myapp_blog, \"priv/archive/images\"}\n```\n\nNote that for applications that employed the Intermediate Library Pattern, the\nflags for `Plug.Static` will look like the example above. For instances where\nArchivist is being used directly in the target application, the name of the\ncurrent application should be used here, like this:\n\n```elixir\nplug Plug.Static,\n  at: \"/blog/images/\",\n  from: {:myapp, \"priv/archive/images\"}\n```\n\nFor use within Phoenix in particular, your plug declaration would likely go in\nthe `MyappWeb.Endpoint` module, like this:\n\n```elixir\ndefmodule MyappWeb.Endpoint do\n  use Phoenix.Endpoint, otp_app: :myapp\n\n  # app name here will be :myapp or :myapp_blog depending on which otp app\n  # contains the content archive\n  plug Plug.Static,\n    at: \"/blog/images/\",\n    from: {:myapp_blog, \"priv/archive/images\"}\n```\n\n## Development Notes\n\nPlease find additional information about known issues and planned features for\nArchivist in the [issues tracker](https://github.com/functionhaus/archivist/issues).\n\n## Todo\n\nIssues and Todo enhancements are managed at the official\n[Archivist issues tracker](https://github.com/functionhaus/archivist/issues) on GitHub.\n\n## Availability\n\nSource code is available at the official\n[Archivist repository](https://github.com/functionhaus/arcdown)\non the [FunctionHaus GitHub Organization](https://github.com/functionhaus)\n\n## License\n\nArchivist source code is released under Apache 2 License.\nCheck LICENSE file for more information.\n\n\u0026copy; 2017 FunctionHaus, LLC\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunctionhaus%2Farchivist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffunctionhaus%2Farchivist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunctionhaus%2Farchivist/lists"}