Command line tool for creating text-based documentation
Ruby

README.textile

Atom

Atom is a command suite for generating text-based documentation, and is loosely based on the Darwin Information Typing Architecture (DITA).

DITA is XML-based, and content is created as small topic items rather than long books or chapters and listed in maps to produce a document. Topics should be atomic in that they contain the smallest amount of information that makes sense with no further context. A DITA map contains links to topics, organized in the sequence (which may be hierarchical) in which they are intended to appear in finished documents.

Instead of being XML-based, atom uses textile as the markup language. This avoids having to use a special editor or hand coding XML. It also allows the source documents to be read more easily by a human. It’s open source and has a simple plug-in system that allows the content creator to create scripts that hook in to the processing stream.

Installation

$ gem install atom-doc

The Basics

Creating documentation is simple.

  1. Initialize a repository with the init command
  2. Add version control with git (optional)
  3. Add maps and topics with the new command
  4. Edit your maps to include topics
  5. Build your maps with the build command

The Repository

We can initialize a new repository by running the following command:

$ atom init docs
>
create  [Dir]: docs/source
create  [Dir]: docs/source/topics
create  [Dir]: docs/source/maps
create  [Dir]: docs/config
create  [Dir]: docs/temp
create  [Dir]: docs/output
create  [Dir]: docs/output/html
create  [Dir]: docs/templates
create  [Dir]: docs/plugins
create [File]: docs/.atom
create [File]: docs/.gitignore
create [File]: docs/config/atom.yml
create [File]: docs/templates/concept.textile
create [File]: docs/templates/procedure.textile
create [File]: docs/templates/map.textile
create [File]: docs/templates/default.html

This will create a series of directories and files that will be used as a framework to store and build your documentation. From inside the docs directory, you can run the rest of the atom commands.

Note that several template files are created:

  • concept.textile
  • map.textile
  • procedure.textile
  • default.html

These templates contain basic skeletons to generate documents from and are meant to be edited to suit the needs of the particular project. When you run the new command, the files are generated from these templates.

To generate a map, run:

$ atom new -m 'Working with Vagrant'
>
create [File]: source/maps/m_working_with_vagrant.textile

The new command takes a switch indicating the type of document you want to create [concept, procedure, map] and a title. It will create the document in the source directory, either under topics or maps. In the example above, a new map is created with a title of Working with Vagrant.

Next we should create some topics to be included in the map:

$ atom new -c 'VirtualBox'
>
create [File]: source/topics/c_virtualbox.textile
$ atom new -p 'Installing VirtualBox'
>
create [File]: source/topics/p_installing_virtualbox.textile

Notice that the -c creates a concept and the -p creates a procedure and that the respective files are prefixed witha a c and a p and placed in the source/topics directory.

Now that we have a few topics, we can include them in the map with the following lines:

=1 VirtualBox
=2 Installing VirtualBox

The = sign includes the topic that matches the given title, and the number tells atom where this topic fits in the hierarchy with 0 being the root and the default. Even though the c_virtualbox.textile file has an h1 header, it will have an h2 in the final document.

Now that we have some topics in our map, we can build it.

$ atom build 'Working with Vagrant'
>
create [File]: output/html/working_with_vagrant.html

We now have an html document that contains our documentation.

Advanced Usage

Plugins

Atom allows users to create plugins that hook in to the build process at two points. The first hook is after the map and subtopics have been assembeled into one document, but is still a textile document. The second hook is after the html document is created.

To create a plugin, create a new file in the plugins directory. I’ll create a file named contents.rb.

class Contents < Atom::Plugin
  def run(text)
    # logic here
  end
end

Your class must inherit from Atom::Plugin and override the run method. You’ll be passed the text of the file your hooking in to, which should be manipulated and returned.

In order to run this plugin, you’ll have to tell atom about it in the config/atom.yml file. When you first start an atom project, no plugins will be configured to run and the yaml file will contain the following line:

plugins: { pre: [], post: [] }

I want my plugin to run after the html has been generated, so I’ll place the name of my plugin in the post array.

plugins: { pre: [], post: [contents] }

Now let’s modify the plugin to do something useful:

require 'nokogiri'

class Contents < Atom::Plugin
  def run(text)
    doc = Nokogiri::HTML.parse(text)
    return create_table(doc, 4)
  end
  
  def create_table(doc, depth=3)
    # The following line creates an array like ["h1", "h2", "h3"]
    hs = (1..depth).to_a.reduce([]) {|r, e| r << "h#{e}"}
    table_node = Nokogiri::XML::Node.new('table', doc)
    
    doc.css(hs.join(',')).each_with_index do |h, i|
      anchor_node = Nokogiri::XML::Node.new('a',doc)
      link_node = Nokogiri::XML::Node.new('a',doc)
      row_node = Nokogiri::XML::Node.new('tr', doc)
      data_node = Nokogiri::XML::Node.new('td', doc)
      
      link_node['href'] = "##{i}"
      anchor_node['name'] = "#{i}"
      h.add_previous_sibling(anchor_node)
      link_node.content = h.content
      data_node.add_child(link_node)
      row_node.add_child(data_node)
      
      table_node.add_child(row_node)
    end
    
    body = doc.at_css "body"
    body.children.first.add_previous_sibling(table_node)
    
    return doc
  end
end

The above plugin will create a table listing all headings up to the depth specified with a link to each heading.