Falcco

Falcco is a Falcon port of Docco under a The MIT License.

Copyright (C) 2012 Kibleur C. (aka Kib²)

Falcco is a quick-and-dirty, literate-programming-style documentation generator. It produces HTML that displays your comments alongside your code. Comments are passed through Creole and the code is passed through the new Falcon's Prism syntax highlighter.

This page is the result of running Falcco against its own source file.

You can also see the result of Falcco's output against the Prism sources: here

Download

You'll find Falcco zipped archive here: archive

Currently Falcco only supports documenting Falcon files (i.e those with the traditional .fal extension).

Internal libs

load regex

External libs used:

  • wikiparser & htmlhandler for Creole (Kib²)
  • Prism for the syntax highlighting (Kib²)
load wikiparser
load htmlhandler
load Prism

Useful IO functions

function loadText(fname)
    size = FileStat(fname).size
    contents = strBuffer(size)
    f = InputStream( fname )
    buf = strBuffer( 10960 )
    while not f.eof()
       f.read(buf)
       contents += buf
    end
    f.close()
    return contents
end

function saveText(fname, contents)
    f = OutputStream( fname )
    wpos = 0
    loop
       wpos += f.write( contents, contents.len(), wpos )
    end wpos == contents.len()
    f.close()
end

The main Falcco class

class Falcco(file_name, file_ext, engine)
  file_name = file_name
  file_ext  = file_ext
  engine = engine
  markup_engine = engine

  form = nil
  lbfm = nil

At the moment, Falcco only supports Falcon sources

  langs = [ 
    'fal'=> [
        'single' => '^\s*\/\/',
        'multi' => [ 
          'start'  => '^\s*/\*',
          'middle' => '^\s*\*',
          'end'    => '\*/'
        ]
    ]
  ] 
  ignore = nil
  source = nil
  block_comment_start  = nil
  block_comment_mid    = nil
  block_comment_end    = nil
  single_line_comment  = nil

  init
    self.form   = htmlHandler() // This sets the Creole formatter
    self.lbfm   = LBFSM(self.form)
    self.source = loadText(file_name + '.' + file_ext)
    self.set_language()
  end

  function set_language()
    dico = self.langs[self.file_ext]
    self.single_line_comment = dico['single']
    if 'ignore' in dico.keys()
      self.ignore = dico['ignore']
    end

    if 'multi' in dico.keys()
      self.block_comment_start = dico['multi']['start']
      self.block_comment_mid   = dico['multi']['middle']
      self.block_comment_end   = dico['multi']['end']
    end
  end

  function parse()
    sections, docs, code, lines = [], [], [], self.source.split("\n")
    if Regex('^\#\!').match(lines[0]) : lines = lines[1:]
    in_comment_block = false

    for line in lines
      if self.ignore and Regex(self.ignore).match(line)
        continue
      end

      if in_comment_block
        if self.block_comment_end and Regex(self.block_comment_end).match(line)
          in_comment_block = false
        else
          if self.block_comment_mid 
            docs += Regex(self.block_comment_mid).replace(line,'')
          else
            docs += line
          end
        end
      else
        if self.block_comment_start and Regex(self.block_comment_start).match(line)
          in_comment_block = true
          if code != []
            sections += [[docs, code]]
            docs = []
            code = []
          end
        elif self.single_line_comment and Regex(self.single_line_comment).match(line)
          if code != []
            sections += [[docs, code]]
            docs = []
            code = []
          end
          docs += Regex(self.single_line_comment).replace(line,'')
        else
          code += line
        end
      end

    end // foreach

    if docs != [] or code != []
      sections += [[docs, code]]
    end
    sections = self.normalize_leading_spaces(sections)
    return sections
  end

  function normalize_leading_spaces(sections)
    sec = []
    k = Regex('(^\s+)')
    for docsection,srcsection in sections
      if docsection != [] and docsection[0] != []
        if m = k.match(docsection[0][0])
          fist_line_blanks = Regex('^' + docsection[0][0][k.captured(0)])
          res = []
          for line in docsection
            res += fist_line_blanks.replace(line, '')
          end
          truc = [res, srcsection]
        end
      else
        truc = [docsection,srcsection]
      end
      sec += [truc]
    end
    return sec
  end

Get the contents of our given $filename template. Then replace any placeholder like place_holder inside At the moment, there is only one placeholder inside the templates: the filename.

  function get_tpl_contents(filename)
    nfn = self.file_name[0].upper()
    output = loadText('templates/' + filename + '.tpl')
    newname = nfn + self.file_name[1:]
    re = Regex('\[\[([a-zA-Z0-9_]+)\]\]')
    while re.match(output)
      output = re.replace(output, newname) //self.file_name) //self.cb_replace)  
    end
    return output
  end

Callback method for preg_replace_callback used inside the get_file_contents method.

  function cb_replace(matches)
    dico = [
      'filename' => self.filename
    ]
    return dico[matches[1]]
  end

Take the list of paired sections two-tuples and split into two separate lists: one holding the comments with leaders removed and one with the code blocks.

  function split(sections)
    docs_blocks, code_blocks = [], []
    for s in sections     
      docs = "\n".merge(s[0])
      code = "\n".merge(s[1])
      docs_blocks += docs
      code_blocks += code
    end
    res = [docs_blocks, code_blocks]
    return res
  end

The main method

  function build_doc()
    res = self.split(self.parse())
    db  = res[0] // docs block
    cb  = res[1] // code block
    nb  = max(db.len(), cb.len())
    result = self.get_tpl_contents('head')

    for i in [0:nb]

Process the docs with Creole markup and Prism output.

      cr = LBFSM(htmlHandler())
      f  = HtmlFormatter(false)
      hl = Prism(f, "falcon" )
      doc = cr.parse(db[i])
      src = hl.highlight_from_string(cb[i])

      section = @"
<tr id='section-$(i)'> 
  <td class=docs>
    $(doc)
  </td> 
  <td class=code> 
    <div class='highlight'><pre class=\"code\">$(src)</pre></div> 
  </td> 
</tr>"
      result += section
    end
    result += self.get_tpl_contents('foot')
    return result
  end

Save the doc to HTML given a $output_name filename Note: the .html extension is automatically added.

  function save_doc(output_name)
    res = self.build_doc(self.source)
    out = output_name + '.html'
    saveText(out, res)
    > @"Your file $out has been saved. Thanks for using Falcco."
  end
end

export Falcco