use(require(“atomy”))

require(“set”)

data(Block(@content, @style = nil)):

Table(@rows)
Itemization(@elements)
NestedFlow(@blocks)
Paragraph(@content, @closed? = false)
MetaBlock(@action)
TraverseBlock(@action)
ResolveBlock(@action)
List(@elements):
  OrderedList(@elements)

fn(style-contains-prose?(style)):

style match:
  .tt:
    false

  .verbatim:
    false

  .header(_):
    false

  .classed(sub, *classes):
    style-contains-prose?(sub)

  _:
    true

def(Block prose-content?):

style-contains-prose?(@style)

data(Element(@content, @style = nil)):

Target(@name)
TOC(@content)
Reference(@tag, @content = nil)
Index(@keys, @description)
TraverseElement(@action)
ResolveElement(@action)
CollectElement(@action)
RenderElement(@action)

data(Style(@type = nil, @properties = []))

data(Tag(@part, @name, @target = .self, @display = nil))

data(

Part(
  -- nil or title content
  @title = nil

  -- the part style
  @style = Style new

  -- initial content before sub-parts
  @body = []

  -- sub-parts
  @parts = []

  -- parent part
  @parent = nil

  -- tags
  -- tag -> reference
  @tags = #{}
  @tag-prefix = nil

  -- tags that cannot be found by parents
  @local-tags = #{}

  -- the primary tag used for this Part
  -- used for URIs
  @tag = nil

  -- content that is inspected during the collect pass,
  -- but doesn't produce output
  @to-collect = []

  -- dependent assets
  @assets = []

  -- css files to add
  @css-additions = Set new
  @css-url-additions = Set new

  -- js files to add (order matters; thankfully Set preserves insertion order)
  @js-additions = Set new

  -- template to use when rendering html
  @template = nil

  -- omit children from table of contents
  @omit-children-from-table-of-contents? = false

  -- whether or not the part will handle mobile device sizes elegantly
  @mobile-optimized? = false))

def(Part set-tag(name, target = .self, display = nil)):

@tags[name] = Tag new(self, name, target, display)

def(Part set-local-tag(name, target = nil, display = nil)):

@local-tags[name] = Tag new(self, name, target, display)

def(Part inline-sections?):

style properties include?(.inline-sections)

def(Part inlined?):

inline-sections? || (@parent && @parent inlined?)

def(Part split-sections?):

style properties include?(.split-sections)

def(Part toc?):

style properties include?(.toc)

def(Part page-depth):

if(@parent)
  then:
    if(@parent split-sections? || @parent inline-sections?)
      then: 0
      else: @parent page-depth + 1
  else:
    0

def(Part section-label):

if(@parent)
  then:
    parent-label = @parent section-label
    self-index = (@parent parts index(self) + 1)

    if(parent-label empty?)
      then: i"#{self-index}"
      else: i"#{parent-label}.#{self-index}"
  else: ""

def(Part inspect): i“#<Part: '#{@tag name}'>”

def(Part template):

@template || (@parent && @parent template)

– search in order: – 1. local hidden tags – 2. local tags – 3. children – 1. local tags – 2. children – 4. parent – 1. local hidden tags – 2. tags – 3. siblings, searching in order – 4. parent … def(Part find-tag(tag, excluding = nil)):

catch(.found):
  search-tag-excluding(tag, nil)
  nil

def(Part search-tag-excluding(name, excluding)): do:

when(found = (@local-tags[name] || @tags[name])):
  throw(.found, found)

when(name == @title):
  throw(.found, @tag)

@parts each [p]:
  unless(p == excluding):
    p search-tag(name)

when(@parent):
  @parent search-tag-excluding(name, self)

nil

– called from find-tag; only search exposed tags and children def(Part search-tag(name)): do:

when(name == @title):
  throw(.found, @tag)

when(found = @tags[name]):
  throw(.found, found)

@parts each [p]:
  p search-tag(name)

def(Part top):

if(@parent)
  then: @parent top
  else: self

def(Part contains-part?(other)):

if(self == other)
  then: true
  else: @parts any? &.contains-part?(other)

def(content?(String)): true def(content?(Element)): true def(content?(nil)): true def(content?(x & Array)): x all? &.content? def(content?(_)): false

def(sanitize(x)):

contents-of(x) gsub(r" & ", " and ")
  gsub(r"\s+", "-")
  gsub(r"[^[:alnum:]_\-]", "")
  downcase

def(contents-of(s & String)): s def(contents-of(e & Element)): contents-of(e content) def(contents-of(b & Block)): contents-of(b content) def(contents-of(a & Array)): a collect [x] { contents-of(x) } join def(contents-of(nil)): “”