// SPDX-License-Identifier: CC-BY-SA-4.0
// (PMPL-1.0-or-later preferred; MPL-2.0 required for Hex.pm)
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>

= a2ml_ex
:author: Jonathan D.A. Jewell
:toc: preamble
:icons: font

== Overview

A2ML parser and renderer for https://elixir-lang.org[Elixir]. Parses
https://github.com/hyperpolymath/standards/tree/main/a2ml[A2ML (Attested Markup Language)]
source text into a typed Elixir AST and renders it back to A2ML surface syntax with
round-trip fidelity.

A2ML is a lightweight markup format designed for AI-generated content with provenance
tracking through attestation chains and trust levels.

== Installation

Add `a2ml_ex` to your dependencies in `mix.exs`:

[source,elixir]
----
defp deps do
  [{:a2ml_ex, "~> 0.1.0"}]
end
----

Then fetch:

[source,sh]
----
mix deps.get
----

Package: https://hex.pm/packages/a2ml_ex[hex.pm/packages/a2ml_ex]

== Usage

[source,elixir]
----
# Parse an A2ML document
{:ok, doc} = A2ML.parse("# Hello\n\n@version 1.0\n\nA paragraph.")

# Render back to A2ML text
output = A2ML.render(doc)

# Extract a manifest (collects directives and attestations)
manifest = A2ML.Types.Manifest.from_document(doc)
manifest.version  #=> "1.0"
----

=== Working with the AST

[source,elixir]
----
{:ok, doc} = A2ML.parse(input)

# Pattern-match on blocks
Enum.each(doc.blocks, fn
  {:heading, level, inlines} ->
    IO.puts("Heading level #{level}")
  {:paragraph, inlines} ->
    IO.puts("Paragraph")
  {:directive, %A2ML.Types.Directive{name: name, value: value}} ->
    IO.puts("Directive: @#{name} #{value}")
  {:attestation, %A2ML.Types.Attestation{identity: id, trust_level: level}} ->
    IO.puts("Attested by #{id} at #{A2ML.Types.TrustLevel.to_string(level)}")
  _ ->
    :ok
end)
----

=== Trust Level Comparison

[source,elixir]
----
alias A2ML.Types.TrustLevel

TrustLevel.compare(:reviewed, :automated)  #=> :gt
TrustLevel.compare(:unverified, :verified) #=> :lt

{:ok, level} = TrustLevel.from_string("verified")
TrustLevel.to_string(level)  #=> "verified"
----

== API Overview

=== Top-Level Functions

[cols="2,3"]
|===
| Function | Description

| `A2ML.parse(input)`
| Parse an A2ML string into `{:ok, Document.t()}` or `{:error, term()}`

| `A2ML.render(doc)`
| Render a `Document.t()` back to A2ML text
|===

=== Modules

[cols="2,3"]
|===
| Module | Contents

| `A2ML.Types`
| Umbrella module for all core data types

| `A2ML.Types.Document`
| Struct with `title`, `directives`, `blocks`, `attestations`

| `A2ML.Types.Block`
| Type spec for block-level elements (tagged tuples)

| `A2ML.Types.Inline`
| Type spec for inline-level elements (tagged tuples)

| `A2ML.Types.Directive`
| Struct with `name`, `value`, `attributes`

| `A2ML.Types.Attestation`
| Struct with `identity`, `role`, `trust_level`, `timestamp`, `note`

| `A2ML.Types.TrustLevel`
| Atom type (`:unverified`, `:automated`, `:reviewed`, `:verified`) with `from_string/1`, `to_string/1`, `compare/2`

| `A2ML.Types.Manifest`
| Struct with `version`, `title`, `directives`, `attestations` and `from_document/1`

| `A2ML.Parser`
| `parse(String.t()) :: {:ok, Document.t()} \| {:error, term()}`

| `A2ML.Renderer`
| `render(Document.t()) :: String.t()`
|===

== Requirements

* Elixir >= 1.14
* No runtime dependencies (ex_doc is dev-only)

== Licensing

SPDX-License-Identifier: MPL-2.0

PMPL-1.0-or-later is preferred. MPL-2.0 is used as a fallback because Hex.pm requires an
OSI-approved license identifier. See link:LICENSE[LICENSE] for the full text.

== Part of the A2ML Ecosystem

This binding is part of the link:../../README.adoc[A2ML specification and tooling] in the
https://github.com/hyperpolymath/standards[standards monorepo]. See the
link:../README.adoc[bindings overview] for all available language bindings.
