GithubHelp home page GithubHelp logo

gjtorikian / biscotto Goto Github PK

View Code? Open in Web Editor NEW
105.0 7.0 18.0 2.62 MB

UNMAINTAINED. CoffeeScript API documentation tool that uses TomDoc notation.

Home Page: https://gjtorikian.github.io/biscotto/

License: MIT License

CoffeeScript 51.76% Ruby 0.67% JavaScript 35.31% CSS 12.26%

biscotto's People

Contributors

abe33 avatar adam-lynch avatar aenario avatar alappe avatar alexjwayne avatar anaisbetts avatar benogle avatar gjtorikian avatar grahamlyus avatar kevinsawicki avatar mattijs avatar michaelklishin avatar netzpirat avatar okbel avatar paullecam avatar pborreli avatar peterdavehello avatar philschatz avatar skabbes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

biscotto's Issues

Add support for javascript code too

I'd love this, as some of my code is in CoffeeScript and some in JavaScript.

Also an article here with the same request: http://www.aaron-gray.com/tomdoc-for-javascript/

I understand that GitHub writes everything in CoffeeScript now, so if this isn't a priority for GitHub, it would be nice to expose a plugin system or the like to be able to add new languages to the biscotto eco-system.

gh-pages is out of sync

The generated docs reference a branch that isn't there anymore. Re-run biscotto on master. Otherwise the "source" links won't work.

[WARN] Cannot resolve link to in class Blah

Command:

$ /Users/balupton/Projects/taskgroup/node_modules/biscotto/bin/biscotto -n TaskGroup --title "TaskGroup API Documentation" -r README.md -o docs src - LICENSE.md HISTORY.md
[WARN] Cannot resolve link to 
 in class Task
[WARN] Cannot resolve link to 
 in class Task
[WARN] Cannot resolve link to 
 in class Task
Parsed files:           6
Classes:                2 (0 undocumented)
Mixins:                 0
Non-Class files:        0
Methods:               45 (0 undocumented)
Constants:              0 (0 undocumented)
 100.00% documented (47 total, 0 with no doc)
 3 errors

The problem is that error message is not very helpful, it doesn't tell me what or where I should be looking.

File is from bevry/taskgroup:

# Import
queue = global?.setImmediate or process.nextTick  # node 0.8 b/c
events = require('events')
domain = (try require('domain')) ? null
util = require('util')
{EventEmitter} = require('events')
ambi = require('ambi')
csextends = require('csextends')
wait = (delay, fn) -> setTimeout(fn, delay)


# =====================================
# Interface

# Internal: Base class containing common functionality for {Task} and {TaskGroup}.
class Interface extends EventEmitter

    # Adds support for the done event while
    # ensuring that errors are always handled correctly.
    #
    # It does this by listening to the `error` and `completed` events,
    # and when the emit, we check if there is a `done` listener:
    # - if there is, then emit the done event with the original event arguments
    # - if there isn't, then output the error to stderr and throw it.
    constructor: ->
        super
        me = @

        # Add support for the done event
        # If we do have an error, then throw it if there is no existing or done listeners
        @on 'error', (args...) ->
            err = args[0]
            if me.listeners('done').length isnt 0  # has done listener, forward to that
                @emit('done', args...)
            else if err and me.listeners('error').length is 1  # has error, but no done listener and no event listener, throw err
                # this isn't good enough, throw the error
                console.error(err.stack or err)
                throw err

        @on 'completed', (args...) ->
            err = args[0]
            if me.listeners('done').length isnt 0  # has done listener, forward to that
                @emit('done', args...)
            else if err and me.listeners('completed').length is 1  # has error, but no done listener and no event listener, throw err
                # this isn't good enough, throw the error
                console.error(err.stack or err)
                throw err

        # Chain
        @

    # Internal: Fire our completion event.
    complete: ->
        err = throw Error('interface should provide this')
        @emit('error', err)
        @

    # Public: Attaches the listener to the `done` event to be emitted each time.
    #
    # listener - The {Function} to attach to the `done` event.
    whenDone: (listener) ->
        # check if we have a listener
        if typeof listener is 'function'
            @on('done', listener.bind(@))

        # Chain
        @

    # Public: Attaches the listener to the `done` event to be emitted only once, then removed to not fire again.
    #
    # listener - The {Function} to attach to the `done` event.
    onceDone: (listener) ->
        # check if we have a listener
        if typeof listener is 'function'
            @once('done', listener)

        # Chain
        @

    # Public: Alias for {::onceDone}
    done: (args...) ->
        return @onceDone(args...)

    # Public: Get our name with all of our parent names into a {String} or {Array}.
    #
    # opts - The options
    #        :format - (default: 'string') A {String} that determines the format that we return, when `string` it will output a string of all our names, when `array` it will return the names as an array
    #        :seperator - (default: ' ➞  ') A {String} that is used to join our array when returning a joined {String}
    #
    # Returns either a joined {String} or an {Array} based on the value of the `format` option.
    getNames: (opts={}) ->
        # Prepare
        opts.format ?= 'string'
        opts.separator ?= ''

        # Fetch
        names = @config.parent?.getNames(format: 'array') or []
        names.push(name)  if name = @getName()

        # Format
        if opts.format isnt 'array'
            names = names.join(opts.separator)

        # Return
        return names

    # Public: Get the name of our instance.
    #
    # If the name was never configured, then return the name in the format of
    # `'#{@type} #{Math.random()}'` to output something like `task 0.2123`
    #
    # Returns the configured name {String}.
    getName: ->
        return @config.name ?= "#{@type} #{Math.random()}"

    # Public: Get the configuration of our instance.
    #
    # Returns our configuration {Object} directly.
    getConfig: -> @config


# =====================================

# Public: Our Task Class.
#
# Available configuration is documented in {::setConfig}.
#
# Available events:
#
# - `started()` - emitted when we start execution
# - `running()` - emitted when the method starts execution
# - `failed(err)` - emitted when execution exited with a failure
# - `passed()` - emitted when execution exited with a success
# - `completed(err, args...)` - emitted when execution exited, `args` are the result arguments from the method
# - `error(err)` - emtited if an unexpected error occurs without ourself
# - `done(err, args...)` - emitted when either execution completes (the `completed` event) or when an unexpected error occurs (the `error` event)
#
# Available internal statuses:
#
# - `null` - execution has not yet started
# - `'started'` - execution has begun
# - `'running'` - execution of our method has begun
# - `'failed'` - execution of our method has failed
# - `'passed'` - execution of our method has succeeded
# - `'destroyed'` - we've been destroyed and can no longer execute
#
# Examples
#
#  task = require('taskgroup').Task.create('my synchronous task', function(){
#    return 5
#  }).done(console.log)  // [null, 5]
#
#  task = require('taskgroup').Task.create('my asynchronous task', function(complete){
#    complete(null, 5)
#  }).done(console.log)  // [null, 5]
#
#  task = require('taskgroup').Task.create('my task that errors', function(){
#    var err = new Error('deliberate error')
#    return err;  // if asynchronous, can also do: complete(err)
#    // thrown and uncaught errors are also caught thanks to domains, but that should be avoided
#    // as it would put your app in an unknown state
#  }).done(console.log)  // [Error('deliberator error')]
class Task extends Interface
    # Internal: The type of our class for the purpose of duck typing
    # which is needed when working with node virtual machines
    # as instanceof will not work in those environments.
    type: 'task'

    # Public: A helper method to check if the passed argument is an instanceof a {Task}.
    #
    # item - The possible instance of the {Task} that we want to check
    #
    # Returns a {Boolean} of whether or not the item is a {Task} instance.
    @isTask: (item) -> return item?.type is 'task' or item instanceof Task

    # Public: A helper method to create a new subclass with our extensions.
    #
    # extensions - An {Object} of extensions to apply to the new subclass
    #
    # Returns the new sub {Class}
    @subclass: csextends

    # Public: Creates a new {Task} instance.
    #
    # args - The Arguments to forwarded along to the {::constructor}.
    #
    # Returns the new {Task} instance.
    @create: (args...) -> return new @(args...)

    # Internal: The first {Error} that has occured.
    err: null

    # Internal: An {Array} of the result arguments of our method.
    # The first item in the array should be the {Error} if it exists.
    result: null

    # Internal: A {String} containing our current status. See our {Task} description for available values.
    status: null

    # Internal: An {Array} of the events that we may emit. Events that will be executed can be found in the {Task} description.
    events: null

    # Internal: The {Domain} that we create to capture errors for our method.
    taskDomain: null

    # Internal: The configuration for our {Task} instance. See {::setConfig} for available configuration.
    config: null

    # Public: Initialize our new {Task} instance. Forwards arguments onto {::setConfig}.
    constructor: (args...) ->
        super

        # Prepare
        @config ?= {}
        @config.run ?= false
        @config.onError ?= 'exit'
        @config.ambi ?= true
        @config.domain ?= true
        @events ?= []
        @events.push('error', 'started', 'running', 'failed', 'passed', 'completed', 'done', 'destroyed')

        # Apply configuration
        @setConfig(args)

        # Chain
        @

    # Public: Set the configuration for our instance.
    #
    # Despite accepting an {Object} of configuration, we can also accept an {Array} of configuration.
    # When using an array, a {String} becomes the :name, a {Function} becomes the :method, and an {Object} becomes the :config
    #
    # config - Our configuration {Object} can contain the following fields:
    #   :name - (default: null) A {String} for what we would like our name to be, useful for debugging.
    #   :done - (default: null) A {Function} that we would like passed to {::onceDone} (aliases are :onceDone, and :next)
    #   :whenDone - (default: null) A {Function} that we would like passed to {::whenDone}
    #   :on - (default: null) An {Object} of (eventName => listener) that we would like bound via EventEmitter.on.
    #   :once - (default: null) An {Object} of (eventName => listener) that we would like bound via EventEmitter.once.
    #   :method - (default: null) The {Function} that we would like to execute within our task.
    #   :parent - (default: null) A parent {TaskGroup} that we may be attached to.
    #   :onError - (default: 'exit') A {String} that is either `'exit'` or `'ignore'`, when `'ignore'` duplicate run errors are not reported, useful when combined with the timeout option.
    #   :args - (default: null) An {Array} of arguments that we would like to forward onto our method when we execute it.
    #   :timeout - (default: null) A {Number} of millesconds that we would like to wait before timing out the method.
    #   :ambi - (default: true) A {Boolean} for whether or not to use bevry/ambi to determine if the method is asynchronous or synchronous and execute it appropriately
    #   :domain - (default: true) A {Boolean} for whether or not to wrap the task execution in a domain to attempt to catch background errors (aka errors that are occuring in other ticks than the initial execution)
    setConfig: (opts={}) ->
        # Handle arguments
        if Array.isArray(opts)
            # Prepare configuration
            args = opts
            opts = {}

            # Extract the configuration from the arguments
            for arg in args
                continue  unless arg
                switch typeof arg
                    when 'string'
                        opts.name = arg
                    when 'function'
                        opts.method = arg
                    when 'object'
                        for own key,value of arg
                            opts[key] = value

        # Apply the configuration directly to our instance
        for own key,value of opts
            switch key
                when 'on'
                    for own _key,_value of value
                        @on(_key, _value)  if value

                when 'once'
                    for own _key,_value of value
                        @once(_key, _value)  if value

                when 'whenDone'
                    @whenDone(value)  if value

                when 'onceDone', 'done', 'next'
                    @done(value)  if value

                else
                    @config[key] = value

        # Chain
        @

    # Public: Have we started execution yet?
    #
    # Returns a {Boolean} which is `true` if we have commenced execution
    hasStarted: ->
        return @status isnt null

    # Public: Have we finished its execution yet?
    #
    # Returns a {Boolean} which is `true` if we have finished execution
    hasExited: ->
        return @status in ['completed', 'destroyed']

    # Public: Have we been destroyed?
    #
    # Returns a {Boolean} which is `true` if we have bene destroyed
    isDestroyed: ->
        return @status is 'destroyed'

    # Public: Have we completed its execution yet?
    #
    # Returns a {Boolean} which is `true` if we have completed
    isComplete: ->
        return @status in ['failed', 'passed', 'destroyed']

    # Internal: Handles the completion and error conditions for ourself.
    #
    # Should only ever execute once, if it executes more than once, then we error.
    #
    # args - The arguments {Array} that will be applied to the {::result} variable. First argument is the {Error} if it exists.
    exit: (args...) ->
        # Store the first error
        @err ?= args[0]  if args[0]?

        # Complete for the first (and hopefully only) time
        if @isComplete() is false
            # Apply the result if it exists
            if args.length isnt 0
                @result = args

            # Did we error?
            @status = (if @err? then 'failed' else 'passed')

            # Notify our listeners of our status
            @emit(@status, @err)

            # Finish up
            @complete()

        # Error as we have already completed before
        else if @config.onError isnt 'ignore'
            err = new Error """
                The task [#{@getNames()}] just completed, but it had already completed earlier, this is unexpected. State information is:
                #{util.inspect({error:@err, previousResult:@result, currentArguments:args})}
                """
            @emit('error', err)

        # Chain
        @

    # Internal: Completetion Emitter. Used to emit the `completed` event and to cleanup our state.
    complete: ->
        complete = @isComplete()

        if complete
            # Notify our listeners we have completed
            @emit 'completed', (@result or [])...

            # Prevent the error from persisting
            @err = null

            # Should we reset results?
            # @results = []
            # no, it would break the promise nature of done
            # as it would mean that if multiple done listener are added, they would each get different results
            # if they wish to reset the results, they should do so manually via resetResults

            # Should we reset the status?
            # @status = null
            # no, it would break the promise nature of done
            # as it would mean that once a done is fired, no more can be fired, until run is called again

        return complete

    # Public: When Done Promise.
    # Fires the listener, either on the next tick if we are already done, or if not, each time the `done` event fires.
    #
    # listener - The {Function} to attach or execute.
    whenDone: (listener) ->
        if @isComplete()
            queue =>  # avoid zalgo
                listener.apply(@, @result or [])
        else
            super(listener)
        @

    # Public: Once Done Promise.
    # Fires the listener once, either on the next tick if we are already done, or if not, once the `done` event fires.
    #
    # listener - The {Function} to attach or execute.
    onceDone: (listener) ->
        if @isComplete()
            queue =>  # avoid zalgo
                listener.apply(@, @result or [])
        else
            super(listener)
        @

    # Internal: Reset the results.
    #
    # At this point this method is internal, as it's functionality may change in the future, and it's outside use is not yet confirmed. If you need such an ability, let us know via the issue tracker.
    resetResults: ->
        @result = []
        @

    # Public: Destroy the task and prevent it from executing ever again.
    destroy: ->
        @done =>
            # Are we already destroyed?
            return  if @status is 'destroyed'

            # Update our status and notify our listeners
            @emit(@status = 'destroyed')

            # Clear results
            @resetResults()
            # item arrays should already be wiped due to done completion

            # Remove all isteners
            # @TODO should we exit or dispose of the domain?
            @removeAllListeners()

        # Chain
        @

    # Internal: Fire the task method with our config arguments and wrapped in a domain.
    fire: ->
        # Prepare
        me = @
        args = (@config.args or []).slice()

        # Check that we have a method to fire
        if me.config.method?.bind? is false
            err = new Error """
                The task [#{me.getNames()}] failed to run as no method was defined for it.
                """
            me.emit('error', err)
            return @

        # Prepare the task domain if it doesn't already exist
        if @config.domain isnt false and @taskDomain? is false and domain?.create?
            # Setup the domain
            @taskDomain = domain.create()
            @taskDomain.on('error', @exit.bind(@))

            # Make sure if the method supports a callback, we prevent the domain from entering our task
            # RE: https://github.com/bevry/taskgroup/issues/17
            complete = (args...) ->
                me.taskDomain?.exit()
                return me.exit(args...)
        else
            complete = @exit.bind(@)

        # Our fire function that will be wrapped in a domain or executed directly
        fireMethod = ->
            # Bind the method to ourself
            methodToFire = me.config.method.bind(me)

            # Execute with ambi if appropriate
            if me.config.ambi isnt false
                ambi(methodToFire, args...)

            # Otherwise execute directly if appropriate
            else
                methodToFire(args...)

        # Setup timeout if appropriate
        if me.config.timeout
            me.timeout = wait me.config.timeout, ->
                if me.isComplete() is false
                    err = new Error """
                        The task [#{me.getNames()}] has timed out.
                        """
                    me.exit(err)

        # Notify that we are now running
        me.emit(me.status = 'running')

        # Check for crippled domain and use try...catch instead
        if process.browser or process.versions.node.substr(0,3) is '0.8'
            # Use the next tick workaround to escape the try...catch scope
            # Which would otherwise catch errors inside our code when it shouldn't therefore suppressing errors
            completeOriginal = complete
            complete = (args...) ->
                process.nextTick -> completeOriginal(args...)
                return me

            # Add the competion callback to the arguments our method will receive
            args.push(complete)

            # Wrap in a try...catch to at least provide some protection
            try
                # Fire the method within the domain if desired, otherwise execute directly
                if @taskDomain? then @taskDomain.run(fireMethod) else fireMethod()
            catch err
                me.exit(err)
        else
            # Add the competion callback to the arguments our method will receive
            args.push(complete)

            # Fire the method within the domain if desired, otherwise execute directly
            if @taskDomain? then @taskDomain.run(fireMethod) else fireMethod()

        # Chain
        @

    # Public: Start the execution of the task.
    #
    # Will emit an `error` event if the task has already started before.
    run: ->
        queue =>
            # Already completed or even destroyed?
            if @hasStarted()
                err = new Error """
                    The task [#{@getNames()}] was just about to start, but it already started earlier, this is unexpected.
                    """
                @emit('error', err)

            # Not yet completed, so lets run!
            else
                # Apply our new status and notify our listeners
                @emit(@status = 'started')

                # Fire the task
                @fire()

        # Chain
        @



# =====================================
# Task Group

# Public: Our TaskGroup class.
#
# Available configuration is documented in {::setConfig}.
#
# Available events:
# - `started()` - emitted when we start execution
# - `running()` - emitted when the first item starts execution
# - `failed(err)` - emitted when execution exited with a failure
# - `passed()` - emitted when execution exited with a success
# - `completed(err, results)` - emitted when execution exited, `results` is an {Array} of the result arguments for each item that executed
# - `error(err)` - emtited if an unexpected error occured within ourself
# - `done(err, results)` - emitted when either the execution completes (the `completed` event) or when an unexpected error occurs (the `error` event)
# - `item.*(...)` - bubbled events from an added item
# - `task.*(...)` - bubbled events from an added {Task}
# - `group.*(...)` - bubbled events from an added {TaskGroup}
#
# Available internal statuses:
# - `null` - execution has not yet started
# - `'started'` - execution has begun
# - `'running'` - execution of items has begun
# - `'failed'` - execution has exited with failure status
# - `'passed'` - execution has exited with success status
# - `'destroyed'` - we've been destroyed and can no longer execute
class TaskGroup extends Interface
    # Internal: The type of our class for the purpose of duck typing
    # which is needed when working with node virtual machines
    # as instanceof will not work in those environments.
    type: 'taskgroup'

    # Public: A helper method to check if the passed argument is an instanceof a {TaskGroup}.
    #
    # item - The possible instance of the {TaskGroup} that we want to check
    #
    # Returns a {Boolean} of whether or not the item is a {TaskGroup} instance.
    @isTaskGroup: (group) -> return group?.type is 'taskgroup' or group instanceof TaskGroup

    # Public: A helper method to create a new subclass with our extensions.
    #
    # extensions - An {Object} of extensions to apply to the new subclass
    #
    # Returns the new sub {Class}
    @subclass: csextends


    # Public: Creates a new {TaskGroup} instance.
    #
    # args - The Arguments to be forwarded along to the {::constructor}.
    #
    # Returns the new {TaskGroup} instance.
    @create: (args...) -> return new @(args...)

    # Internal: An {Array} of the items that are still yet to execute
    itemsRemaining: null

    # Internal: An {Array} of the items that are currently running
    itemsRunning: null

    # Internal: An {Array} of the items that have completed
    itemsCompleted: null

    # Internal: An {Array} of the result Arguments for each completed item when their :includeInResults configuration option is not `false`
    results: null

    # Internal: An {Error} object if execution has failed at some point
    err: null

    # Internal: A {String} containing our current status. See our {Task} description for available values.
    status: null

    # Internal: An {Array} of the events that we may emit. Events that will be executed can be found in the {Task} description.
    events: null

    # Internal: The configuration for our {Task} instance. See {::setConfig} for available configuration.
    config: null

    # Public: Initialize our new {TaskGroup} instance. Forwards Arguments onto {::setConfig}.
    constructor: (args...) ->
        # Init
        me = @
        super
        @config ?= {}
        @config.concurrency ?= 1
        @config.onError ?= 'exit'
        @itemsRemaining ?= []
        @itemsRunning ?= []
        @itemsCompleted ?= []
        @results ?= []
        @events ?= []
        @events.push('error', 'started', 'running', 'passed', 'failed', 'completed', 'done', 'destroyed')

        # Apply configuration
        @setConfig(args)

        # Give setConfig enough chance to fire
        # Changing this to setImmediate breaks a lot of things
        # As tasks inside nested taskgroups will fire in any order
        queue(@autoRun.bind(@))

        # Chain
        @



    # ---------------------------------
    # Configuration

    # Public: Set Nested Task Config
    setNestedTaskConfig: (config={}) ->
        @config.nestedTaskConfig ?= {}
        for own key,value of config
            @config.nestedTaskConfig[key] = value
        @

    # Public: Set Nested Config
    setNestedConfig: (config={}) ->
        @setConfig(config)
        @config.nestedConfig ?= {}
        for own key,value of config
            @config.nestedConfig[key] = value
        @

    # Public: Set the configuration for our instance.
    #
    # Despite accepting an {Object} of configuration, we can also accept an {Array} of configuration.
    # When using an array, a {String} becomes the :name, a {Function} becomes the :method, and an {Object} becomes the :config
    #
    # config - Our configuration {Object} can contain the following fields:
    #   :name - (default: null) A {String} for what we would like our name to be, useful for debugging.
    #   :done - (default: null) A {Function} that we would like passed to {::onceDone} (aliases are :onceDone, and :next)
    #   :whenDone - (default: null) A {Function} that we would like passed to {::whenDone}
    #   :on - (default: null) An {Object} of (eventName => listener) that we would like bound via EventEmitter.on.
    #   :once - (default: null) An {Object} of (eventName => listener) that we would like bound via EventEmitter.once.  #   :method - (default: null) A {Function} that we would like to use to created nested groups and tasks using an inline style.
    #   :parent - (default: null) A parent {TaskGroup} that we may be attached to.
    #   :onError - (default: 'exit') A {String} that is either `'exit'` or `'ignore'`, when `'ignore'` errors that occur within items will not halt execution and will not be reported in the completion callbacks `err` argument (but will still be in the `results` argument).
    #   :concurrency - (default: 1) The {Number} of items that we would like to execute at the same time. Use `0` for unlimited. `1` accomplishes serial execution, everything else accomplishes parallel execution.
    #   :run - (default: true) A {Boolean} for whether or not to the :method (if specified) automatically.
    #   :nestedConfig - (default: null) An {Object} of nested configuration to be applied to all items of this group.
    #   :nestedTaskConfig - (default: null) An {Object} of nested configuration to be applied to all {Task}s of this group.
    #   :tasks - (default: null) An {Array} of tasks to be added as children.
    #   :groups - (default: null) An {Array} of groups to be added as children.
    #   :items - (default: null) An {Array} of {Task} and/or {TaskGroup} instances to be added to this group.
    setConfig: (opts={}) ->
        # Handle arguments
        if Array.isArray(opts)
            # Prepare configuration
            args = opts
            opts = {}

            # Extract the configuration from the arguments
            for arg in args
                continue  unless arg
                switch typeof arg
                    when 'string'
                        opts.name = arg
                    when 'function'
                        opts.method = arg
                    when 'object'
                        for own key,value of arg
                            opts[key] = value

        # Apply the configuration directly to our instance
        for own key,value of opts
            switch key
                when 'on'
                    for own _key,_value of value
                        @on(_key, _value)  if value

                when 'once'
                    for own _key,_value of value
                        @once(_key, _value)  if value

                when 'whenDone'
                    @whenDone(value)  if value

                when 'onceDone', 'done', 'next'
                    @done(value)  if value

                when 'task', 'tasks'
                    @addTasks(value)  if value

                when 'group', 'groups'
                    @addGroups(value)  if value

                when 'item', 'items'
                    @addItems(value)  if value

                else
                    @config[key] = value

        # Chain
        @


    # ---------------------------------
    # TaskGroup Method

    # Internal: Prepare the method and it's configuration, and add it as a task to be executed.
    #
    # method - The {Function} of our method
    # config - An optional {Object} of configuration for the task to be created for our method
    addMethod: (method, config={}) ->
        method ?= @config.method.bind(@)
        method.isTaskGroupMethod = true
        config.name ?= 'taskgroup method for '+@getName()
        config.args ?= [@addGroup.bind(@), @addTask.bind(@)]
        config.includeInResults ?= false
        return @addTask(method, config)

    # Internal: Autorun ourself under certain conditions.
    #
    # Those conditions being:
    # - if we the :method configuration is defined, and we have no :parent
    # - if we the :run configuration is `true`
    #
    # Used primarily to cause the :method to fire at the appropriate time when using inline style.
    autoRun: ->
        # Auto run if we are going the inline style and have no parent
        if @config.method
            # Add the function as our first unamed task with the extra arguments
            item = @addMethod()

            # If we are the topmost group
            if @config.parent? is false
                # then run if possible
                @config.run ?= true

        # Auto run if we are configured to
        if @config.run is true
            @run()

        # Chain
        @


    # ---------------------------------
    # Add Item

    # Internal: Add an item to ourself and configure it accordingly
    #
    # item - A {Task} or {TaskGroup} instance that we would like added to ourself
    # args - Additional configuration Arguments to apply to each item
    addItem: (item, args...) ->
        # Prepare
        me = @

        # Only add the item if it exists
        return null  unless item

        # Link our item to ourself
        item.setConfig({parent: @})
        item.setConfig(args...)  if args.length isnt 0
        item.config.name ?= "#{item.type} #{@getItemsTotal()+1} for #{@getName()}"

        # Bubble task events
        if Task.isTask(item)
            # Nested configuration
            item.setConfig(@config.nestedConfig)  if @config.nestedConfig?
            item.setConfig(@config.nestedTaskConfig)  if @config.nestedTaskConfig?

            item.events.forEach (event) ->
                item.on event, (args...) ->
                    me.emit("task.#{event}", item, args...)

            # Notify our intention
            @emit('task.add', item)

        # Bubble group events
        else if TaskGroup.isTaskGroup(item)
            # Nested configuration
            item.setNestedConfig(@config.nestedConfig)  if @config.nestedConfig?
            item.setConfig(nestedTaskConfig: @config.nestedTaskConfig)  if @config.nestedTaskConfig?

            # Bubble item events
            item.events.forEach (event) ->
                item.on event, (args...) ->
                    me.emit("group.#{event}", item, args...)

            # Notify our intention
            @emit('group.add', item)

        # Bubble item events
        item.events.forEach (event) ->
            item.on event, (args...) ->
                me.emit("item.#{event}", item, args...)

        ###
        # Bubble item error event directly
        item.on 'error', (args...) ->
            me.emit('error', args...)
        ###

        # Notify our intention
        @emit('item.add', item)

        # Handle item completion and errors once
        # we can't just do item.done, or item.once('done'), because we need the item to be the argument, rather than `this`
        item.done (args...) ->
            me.itemCompletionCallback(item, args...)

        # Add the item
        @itemsRemaining.push(item)

        # We may be running and expecting items, if so, fire
        @fire()

        # Return the item
        return item

    # Internal: Add items to ourself and configure them accordingly
    #
    # items - An {Array} of {Task} and/or {TaskGroup} instances to add to ourself
    # args - Optional Arguments to configure each added item
    addItems: (items, args...) ->
        items = [items]  unless Array.isArray(items)
        return (@addItem(item, args...)  for item in items)


    # ---------------------------------
    # Add Task

    # Public: Create a {Task} instance from some configuration.
    #
    # If the first argument is already a {Task} instance, then just update it's configuration with the remaning arguments.
    #
    # args - Arguments to use to configure the {Task} instance
    #
    # Returns the new {Task} instance
    createTask: (args...) ->
        # Support receiving an existing task instance
        if Task.isTask(args[0])
            task = args[0]
            task.setConfig(args.slice(1)...)

        # Support receiving arguments to create a task instance
        else
            task = new Task(args...)

        # Return the new task
        return task

    # Public: Add a {Task} with some configuration to ourself, create it if needed.
    #
    # args - Arguments to configure (and if needed, create) the task
    addTask: (args...) ->
        task = @addItem @createTask args...

        # Chain
        @

    # Public: Add {Task}s with some configuration to ourself, create it if needed.
    #
    # items - An {Array} of {Task} items to add to ourself
    # args - Optional Arguments to configure each added {Task}
    addTasks: (items, args...) ->
        items = [items]  unless Array.isArray(items)
        tasks = (@addTask(item, args...)  for item in items)

        # Chain
        @


    # ---------------------------------
    # Add Group

    # Public: Create a {TaskGroup} instance from some configuration.
    #
    # If the first argument is already a {TaskGroup} instance, then just update it's configuration with the remaning arguments.
    #
    # args - Arguments to use to configure the {TaskGroup} instance
    #
    # Returns the new {TaskGroup} instance
    createGroup: (args...) ->
        # Support recieving an existing taskgroup instance
        if TaskGroup.isTaskGroup(args[0])
            taskgroup = args[0]
            taskgroup.setConfig(args.slice(1)...)

        # Support receiving arugments to create a taskgroup intance
        else
            taskgroup = new TaskGroup(args...)

        # Return the taskgroup instance
        return taskgroup

    # Public: Add a {TaskGroup} with some configuration to ourself, create it if needed.
    #
    # args - Arguments to configure (and if needed, create) the {TaskGroup}
    addGroup: (args...) ->
        group = @addItem @createGroup args...

        # Chain
        @

    # Public: Add {TaskGroup}s with some configuration to ourself, create it if needed.
    #
    # items - An {Array} of {TaskGroup} items to add to ourself
    # args - Optional Arguments to configure each added {TaskGroup}
    addGroups: (items, args...) ->
        items = [items]  unless Array.isArray(items)
        groups = (@addGroup(item, args...)  for item in items)

        # Chain
        @


    # ---------------------------------
    # Status Indicators

    # Public: Gets the total number of items
    #
    # Returns a {Number} of the total items we have
    getItemsTotal: ->
        running = @itemsRunning.length
        remaining = @itemsRemaining.length
        completed = @itemsCompleted.length
        total = running + remaining + completed
        return total

    # Public: Gets the names of the items, the total number of items, and their results for the purpose of debugging.
    #
    # Returns an {Object} containg the hashes:
    #   :remaining - An {Array} of the names of the remaining items
    #   :running - An {Array} of the names of the running items
    #   :completed - An {Array} of the names of the completed items
    #   :total - A {Number} of the total items we have
    #   :results - An {Array} of the results of the compelted items
    getItemNames: ->
        running = @itemsRunning.map (item) -> item.getName()
        remaining = @itemsRemaining.map (item) -> item.getName()
        completed = @itemsCompleted.map (item) -> item.getName()
        results = @results
        total = running.length + remaining.length + completed.length
        return {
            remaining
            running
            completed
            total
            results
        }

    # Public: Gets the total number count of each of our item lists.
    #
    # Returns an {Object} containg the hashes:
    #   :remaining - A {Number} of the total remaining items
    #   :running - A {Number} of the total running items
    #   :completed - A {Number} of the total completed items
    #   :total - A {Number} of the total items we have
    #   :results - A {Number} of the total results we have
    getItemTotals: ->
        running = @itemsRunning.length
        remaining = @itemsRemaining.length
        completed = @itemsCompleted.length
        results = @results.length
        total = running + remaining + completed
        return {
            remaining
            running
            completed
            total
            results
        }

    # Public: Whether or not we have any running items
    #
    # Returns a {Boolean} which is `true` if we have any items that are currently running
    hasRunning: ->
        return @itemsRunning.length isnt 0

    # Public: Whether or not we have any items that are yet to execute
    #
    # Returns a {Boolean} which is `true` if we have any items that are still yet to be executed
    hasRemaining: ->
        return @itemsRemaining.length isnt 0

    # Public: Whether or not we have any items
    #
    # Returns a {Boolean} which is `true` if we have any running or remaining items
    hasItems: ->
        return @hasRunning() or @hasRemaining()

    # Public: Have we started execution yet?
    #
    # Returns a {Boolean} which is `true` if we have commenced execution
    hasStarted: ->
        return @status isnt null

    # Public
    hasResult: ->
        return @err? or @results.length isnt 0

    # Public: Have we finished its execution yet?
    #
    # Returns a {Boolean} which is `true` if we have finished execution
    hasExited: ->
        return @status in ['completed', 'destroyed']

    # Internal: Whether or not we have any available slots to execute more items.
    #
    # Returns a {Boolean} which is `true` if we have available slots.
    hasSlots: ->
        return (
            @config.concurrency is 0 or
            @itemsRunning.length < @config.concurrency
        )

    # Internal: Whether or not we have errord and want to pause when we have an error.
    #
    # Returns a {Boolean} which is `true` if we are paused.
    shouldPause: ->
        return (
            @config.onError is 'exit' and
            @err?
        )

    # Internal: Whether or not we are capable of firing more items.
    #
    # This is determined whether or not we are not paused, and we have remaning items, and we have slots able to execute those remaning items.
    #
    # Returns a {Boolean} which is `true` if we can fire more items.
    shouldFire: ->
        return (
            @shouldPause() is false and
            @hasRemaining() and
            @hasSlots()
        )

    # Public: Whether or not we have no items left
    #
    # Returns a {Boolean} which is `true` if we have no more running or remaining items
    isEmpty: ->
        return @hasItems() is false

    # Public
    isPaused: ->
        return (
            @shouldPause() and
            @hasRunning() is false
        )

    # Public: Have we completed its execution yet?
    #
    # Completion of executed is determined of whether or not we have started, and whether or not we are currently paused or have no remaining and running items left
    #
    # Returns a {Boolean} which is `true` if we have completed execution
    isComplete: ->
        return (
            @hasStarted() and
            (
                @isPaused() or
                @isEmpty()
            )
        )


    # ---------------------------------
    # Firers

    # Internal: Completetion Emitter. Used to emit the `completed` event and to cleanup our state.
    complete: ->
        complete = @isComplete()

        if complete
            # Notity our listners we have completed
            @emit('completed', @err, @results)

            # Prevent the error from persisting
            @err = null

            # Cleanup the items that will now go unused
            for item in @itemsCompleted
                item.destroy()
            @itemsCompleted = []

            # Should we reset results?
            # @results = []
            # no, it would break the promise nature of done
            # as it would mean that if multiple done handlers are added, they would each get different results
            # if they wish to reset the results, they should do so manually via resetResults

            # Should we reset the status?
            # @status = null
            # no, it would break the promise nature of done
            # as it would mean that once a done is fired, no more can be fired, until run is called again

        return complete

    # Public: When Done Promise.
    # Fires the listener, either on the next tick if we are already done, or if not, each time the `done` event fires.
    #
    # listener - The {Function} to attach or execute.
    whenDone: (handler) ->
        if @isComplete()
            queue =>  # avoid zalgo
                handler.call(@, @err, @results)
        else
            super(handler)
        @

    # Public: Once Done Promise.
    # Fires the listener once, either on the next tick if we are already done, or if not, once the `done` event fires.
    #
    # listener - The {Function} to attach or execute.
    onceDone: (handler) ->
        if @isComplete()
            queue =>  # avoid zalgo
                handler.call(@, @err, @results)
        else
            super(handler)
        @

    # Internal: Reset the results.
    #
    # At this point this method is internal, as it's functionality may change in the future, and it's outside use is not yet confirmed. If you need such an ability, let us know via the issue tracker.
    resetResults: ->
        @results = []
        @

    # Internal: Fire the next items.
    #
    # Returns either an {Array} items that was fired, or `false` if no items were fired.
    fireNextItems: ->
        items = []

        # Fire the next items
        while true
            item = @fireNextItem()
            if item
                items.push(item)
            else
                break

        result =
            if items.length isnt 0
                items
            else
                false

        return result

    # Internal: Fire the next item.
    #
    # Returns either the item that was fired, or `false` if no item was fired.
    fireNextItem: ->
        # Prepare
        result = false
        fire = @shouldFire()

        # Can we run the next item?
        if fire
            # Fire the next item

            # Update our status and notify our listeners
            @emit(@status = 'running')  if @status isnt 'running'

            # Get the next item and remove it from the remaining items
            item = @itemsRemaining.shift()
            @itemsRunning.push(item)

            # Run it
            item.run()

            # Return the item
            result = item

        # Return
        return result

    # Internal: What to do when an item completes
    itemCompletionCallback: (item, args...) ->
        # Update error if it exists
        @err ?= args[0]  if @config.onError is 'exit' and args[0]

        # Mark that one less item is running
        index = @itemsRunning.indexOf(item)
        if index is -1
            # this should never happen, but maybe it could, in which case we definitely want to know about it
            @err ?= indexError = new Error("Could not find [#{item.getNames()}] in the running queue")
            console.error(indexError.stack or indexError)
        else
            @itemsRunning = @itemsRunning.slice(0, index).concat(@itemsRunning.slice(index+1))

        # Add to the completed queue
        @itemsCompleted.push(item)

        # Add the result
        @results.push(args)  if item.config.includeInResults isnt false

        # Fire
        @fire()

        # Chain
        @

    # Internal: Either execute the reamining items we are not paused, or complete execution by exiting.
    fire: ->
        # Have we actually started?
        if @hasStarted()
            # Check if we are complete, if so, exit
            if @isComplete()
                @exit()

            # Otherwise continue firing items if we are wanting to pause
            else if @shouldPause() is false
                @fireNextItems()

        # Chain
        @

    # Public: Clear remaning items.
    clear: ->
        # Destroy all the items
        for item in @itemsRemaining
            item.destroy()
        @itemsRemaining = []

        # Chain
        @

    # Public: Destroy all remaining items and remove listeners.
    destroy: ->
        # Destroy all the items
        @clear()

        # Once finished, destroy it
        @done =>
            # Are we already destroyed?
            return  if @status is 'destroyed'

            # Apply our new status and notify our listeners
            @emit(@status = 'destroyed')

            # Clear results
            @resetResults()
            # item arrays should already be wiped due to done completion

            # Remove listeners
            @removeAllListeners()

        # Chain
        @


    # Internal: We now want to exit.
    exit: (err) ->
        # Update error if set
        @err ?= err  if err?

        # Update the status
        @status = (if @err? then 'failed' else 'passed')

        # Notify our listeners
        @emit(@status, @err)

        # Fire the completion callback
        @complete()

        # Chain
        @

    # Public: Start the execution.
    run: (args...) ->
        queue =>
            # Apply our new status and notify our intention to run
            @emit(@status = 'started')

            # Give time for the listeners to complete before continuing
            @fire()

        # Chain
        @

# Export
module.exports = {Task,TaskGroup}

Generate as markdown

It would be cool if markdown docs were supported. So for example if I'm working on a module with just a index.coffee with one class in it, then I could generate the doc for that class and link to it from my readme. Or even better, I could actually append it to my readme with gulp-biscotto (& other Gulp plugins).

Proposal for a new documentation slug format

I have a proposal for the structure of a metadata format that I think would be ideal for Biscotto to generate. This format would give us coherent and complete information that would give us a lot of power to generate rich documentation and iterate on our organization system. Please help me riff on this. Hopefully together we can arrive at a really powerful and general metadata system that will fit all our potential needs.

Documentation Metadata Format

The goal of Biscotto is to produce a precise description of the contents of an npm module. Its output is a JSON "documentation slug" that metadata can be stored in a file called metadata.json in the root of the module and distributed via npm.

Within a node module, objects are essentially namespaced via the file system due to the use of require. Node module's depend on each other via the namespace defined by the npm repository and version numbers. Biscotto aims to mirror this structure as closely as possible.

Top-Level Structure of a Documentation Slug

An documentation slug for an npm contains a main key indicating which file represents the top-level file in the module, along with a files key pointing to an object containing a file descriptor for every file in the module, keyed by the relative path of each file.

main: 'src/text-buffer.coffee'
files:
  'src/buffer-patch.coffee': {} # file descriptor
  'src/helpers.coffee': {} # file descriptor
  # etc ...

File Descriptors

Each file descriptor contains information about all objects defined in the file, along with information about the file's exports.

main: 'src/text-buffer.coffee'
files:
  'src/buffer-patch.coffee':
    path: 'src/text-buffer.coffee'
    objects: {} # object descriptors
    exports: {} # export descriptors

Objects

The objects key points to a hash of descriptors every object in the file. By object, we mean "code artifact". An object can be a class, an array, a hash, a function, etc. The objects hash is organized by the line number on which the object begins. Object descripors contains a startLineNumber, endLineNumber, name, type. It can also contain other keys containing type-specific metadata.

main: 'src/text-buffer.coffee'
files:
  'src/buffer-patch.coffee':
    path: 'src/buffer-patch.coffee'
    objects:
      69:
        name: 'TextBuffer'
        type: 'class'
        startLineNumber: 69
        endLineNumber: 1076
        doc: "Public: A mutable text container with undo/redo support and..."
        classProperties: [70, 71, ...] # line number references
        prototypeProperties: [79, 80, ...] # line number references

In the example above, the TextBuffer class is documented. In addition to the name, type, doc string, and the line number range spanned by the class, it contains additional keys containing lists of references to other objects in the file by their line number.

You may wonder why we reference the properties of the class by line number instead of just including them directly. We are keeping the structure of the file descriptor deliberately shallow to keep references within and between files as simple and language-agnostic as possible. Rather than complex, syntax-dependent paths, we only need a path and a line number to describe a reference. Since all Node.js code entities ultimately live at particular locations in files on the file system, path and line number makes a reasonable common denominator for organizing the file.

In this case, properties of the class can use a parent key to indicate their logical membership in the class:

# ...
files:
  'src/buffer-patch.coffee':
    # ...
    objects:
      69:
        name: 'TextBuffer'
        type: 'class'
        # ...
      # ...
      131:
        name: 'getText'
        bindingType: 'prototypeProperty'
        startLineNumber: 131
        endLineNumber: 139
        type: 'function'
        parent: 69
        paramNames: []

In this case, the getText method is described as a code object at line 131 that is a function bound to a prototype property inside the code object starting at line 69. Any code interpreting this metadata can follow the references to draw its own conclusions about the structural relationship between these objects. We are free to use fields in the descriptor to provide a rich description of the object. For example, if the function were bound to a local variable, it could have the bindingType of variable and still slot cleanly into this organization.

Code entities can also contain references to other files and modules, but before we cover that, let's discuss exports.

Exports

In the Node module system, files can export one or more objects. This is expressed by the exports field. Take the following example file.

# Returns hello
foo = -> 'hello'

exports.foo = foo

# Exporting the answer to life, the universe, and everything.
exports.bar = 42

The documentation would contain descriptors for all the objects in the file, and also an exports key describing what the file exports.

objects:
  1:
    name: 'foo'
    bindingType: 'variable'
    type: 'function'
    doc: 'Returns hello'
    # ....
  6:
    name: 'bar'
    bindingType: 'exportsProperty'
    type: 'number'
    doc: 'Exporting the answer to life, the universe, and everything.'
    # ...
exports:
  foo: 1
  bar: 6

In the above example, the exports section contains a hash of exported property names mapping to the line numbers of the actual objects being exported.

There's also a common pattern of exporting a single object from a file by assigning module.exports:

module.exports =
class Foo

The above file would be described as follows.

objects:
  1:
    name: 'Foo'
    bindingType: 'exports'
    type: 'class'
    # ....
exports: 1

In this case, instead of a hash of exported property names, we just include a reference directly to the object that's being exported.

References

In certain cases, we may need to make a reference to an entity defined elsewhere. For example, what if the getText method were imported from another file, such as a helpers module?

{getText} = require './helpers'

module.exports =
class TextBuffer
  getText: getText

In this case, we need to make a reference to the code entity in which getText is actually defined. The reference supplies a path for a file in the current module and the optional name of an exported symbol.

# ...
files:
  'src/buffer-patch.coffee':
    # ...
    objects:
      3:
        name: 'TextBuffer'
        type: 'class'
        # ...
      # ...
      4:
        name: 'getText'
        bindingType: 'prototypeProperty'
        startLineNumber: 4
        endLineNumber: 5
        reference:
          path: 'src/helpers.coffee'
          exportsProperty: 'getText'
  'src/helpers.coffee':
    # ...
    objects:
      # ...
      56:
        name: 'getText'
        bindingType: 'exportsProperty'
        startLineNumber: 56
        endLineNumber: 62
        type: 'function'
    exports:
      getText: 56

If we want to reference a file in a completely different node module we can do that as well. We can also supply an optional top-level exports property or, more rarely, a path to a file within the module.

# Assumes we're references the entire module.exports of the referenced module
reference:
  module: '[email protected]'

# Or we can reference a specific exported property of the referenced module
reference:
  module: '[email protected]'
  exportsProperty: 'getText'

# In rare cases, we can reference a file other than the top-level module
reference:
  module: '[email protected]'
  path: 'src/helpers.coffee'
  exportsProperty: 'getText'

Conclusion

By harvesting metadata from node modules in a manner that mirrors their actual lexical structure, we set ourselves up to build tools that can describe the systems we're building out of these modules really well.

We easily list documentation for all objects in a module, as well as modules it references. We can also automatically link references between modules. We can easily bundle the documentation with the actual source code for modules, and automatically upgrade documentation as our dependencies change.

What's better, we can evolve the infrastructure for harvesting data separately from the code for presenting the documentation.

Class list is not sorted

The out of order elements were added at the end of the biscotto args which is why I assume they are added last but I would have expected the class list to always be sorted regardless of the order of the args.

screen shot 2014-01-20 at 11 31 29 am

Rendered documentation includes options in the description

If I have:

# Public: The exported CSON singleton
class CSON

    # Public: Converts an {Object} into a {String} of the desired format
    #
    # If the format option is not specified, we default to CSON
    #
    # data - {Object} The data to convert
    # opts - {Object} The options (options may also be forwarded onto the parser library)
    #        :format - {String} The format to use: "cson" (default), "json", "coffeescript", or "javascript"
    #        :cson - {Boolean} Whether or not the CSON format should be allowed (defaults to `true`)
    #        :json - {Boolean} Whether or not the JSON format should be allowed (defaults to `true`)
    #
    # Returns {String} or {Error}
    createString: (data, opts) ->
        return @action('createString', 'create', 'String', data, opts)

It's being rendered like:

screenshot 2015-02-06 01 15 44

Which is not what we expect or want.

Using the command:

/Users/balupton/Projects/cson/node_modules/biscotto/bin/biscotto -n CSON --title "CSON API Documentation" -r README.md -o docs src - LICENSE.md HISTORY.md

Tried with Biscotto versions 2.3.1, 2.3.0, and 2.2.4 (failed on 2.1) - all produce the same result

Source file is: https://github.com/bevry/cson/blob/6991fc9a7577341ea05ca6260a448d4aa3ea7d23/src/lib/cson.coffee

Rendered doc file is:
https://github.com/bevry/cson/blob/6991fc9a7577341ea05ca6260a448d4aa3ea7d23/docs/classes/CSON.html

Weird issue with spacing and block statuses

The smallest reproducible case:

module.exports =
class Stuff

   # YO!
  bar: ->
    console.log 'yo yo'

  ### Internal ###

  @doSomething: ->

Yields Cannot parse file -> Invalid object key.

Inheritance support?

Hi,

let's say I have a Base class with some methods and lots of classes extending from the Base class, without methods declaration (since they are inherited). The Base class should be private, thus only the subclasses should be visible in the documentation with all the inherited methods.
Is that even possible?

# Private
class Base
  foo: -> 'bar'
  bar: -> 'foo'

# Public
class Foo extends Base

# Public
class Bar extends Base

I would like to Foo and Bar to be visible in the documentation with foo and bar methods, but not Base.

Thanks :)

.biscottoopts extras

I can't seem to get extra files included.

Right now I've my documentation generated into /docs/code with an extra file in there called display.html.

This doesn't work

./app
-
./docs/code/display.html

I've tried without the ./, the full path, relative to the docs root, etc.

I'm on Windows.

No easy way of documenting events

Looking through Atom and Biscotto I couldn't find any examples of documenting emitted events and felt this was a weakness of Biscotto/TomDoc. Since Biscotto has other extensions I propose adding an Emits keyword:

# Emits loaded whenever something is loaded. The first argument will be an
#   {Object} and the second argument will be a {Boolean}

Prototypical method specification

When using prototype method specification using ::, the methods aren't listed as part of the class, rather put under "prototype"

Example:

Foo::foo() gets listed under the class, but Foo::bar() is listed as "method" Foo.prototype.bar().

class Foo
   foo: () -> 'foo'

Foo::bar = () -> 'bar'

Is this a design choice? I tend to chop up functionality into separate files from time to time, and it would be nice to still generate a single class documentation.

--listMissing still generates docs

It seems like --listMissing still causes docs to be generated.

Is there a way to just list the missing docs without generating any actual doc output?

Doc generation is failing when using functions.

When using biscotto on sources that use functions and not objects or classes, generation fails. This is my test case:

# Public: Generating those docs will fail.
func = (val) ->

module.exports =
  func: func

And the error it generates:

undefined:46
'" + ($e($c(this.parent.repo))) + "/" + ($e($c(this.parent.classData.file))) +
                                                                    ^
TypeError: Cannot read property 'file' of undefined
  at Object.eval (<anonymous>:46:112)
  at Array.partials/method_list (/home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/node_modules/haml-coffee/src/hamlc.coffee:53:26)
  at Object.eval (<anonymous>:65:57)
  at Array.file (/home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/node_modules/haml-coffee/src/hamlc.coffee:53:26)
  at Templater.module.exports.Templater.render (/home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/src/util/templater.coffee:59:12)
  at Generator.module.exports.Generator.generateFiles (/home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/src/generator.coffee:199:18)
  at Generator.module.exports.Generator.generate (/home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/src/generator.coffee:40:6)
  at /home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/src/biscotto.coffee:237:21
  at /home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/node_modules/async/lib/async.js:592:17
  at done (/home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/node_modules/async/lib/async.js:135:19)
  at /home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/node_modules/async/lib/async.js:32:16
  at /home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/node_modules/async/lib/async.js:589:21
  at /home/crito/.nvm/v0.10.29/lib/node_modules/biscotto/src/biscotto.coffee:317:7
  at ChildProcess.exithandler (child_process.js:651:7)
  at ChildProcess.emit (events.js:98:17)
  at maybeClose (child_process.js:755:16)
  at Socket.<anonymous> (child_process.js:968:11)
  at Socket.emit (events.js:95:17)
  at Pipe.close (net.js:465:12)

If I wrap this function in an object, it works fine. So I wonder if documenting functions will ever be supported or if this is by design.

Haml-coffee error on Windows

I'm testing doc generation on Windows. This my file:

require('coffee-script/register');
var biscotto = require('./src/biscotto');

biscotto.run();

I get the following error:

undefined:25
$o.push("  " + $c(this.JST['partials/head'](this)));
                                           ^
TypeError: Object  has no method 'partials/head'
  at Object.eval (<anonymous>:25:44)
  at Array.frames (C:\wamp\www\GitHub\biscotto\node_modules\haml-coffee\src\hamlc.coffee:53:26)
  at Templater.module.exports.Templater.render (C:\wamp\www\GitHub\biscotto\src\util\templater.coffee:56:12)
  at Generator.module.exports.Generator.generateIndex (C:\wamp\www\GitHub\biscotto\src\generator.coffee:68:16)
  at Generator.module.exports.Generator.generate (C:\wamp\www\GitHub\biscotto\src\generator.coffee:36:6)
  at C:\wamp\www\GitHub\biscotto\src\biscotto.coffee:237:21
  at C:\wamp\www\GitHub\biscotto\node_modules\async\lib\async.js:533:17
  at C:\wamp\www\GitHub\biscotto\node_modules\async\lib\async.js:119:25
  at C:\wamp\www\GitHub\biscotto\node_modules\async\lib\async.js:24:16
  at C:\wamp\www\GitHub\biscotto\node_modules\async\lib\async.js:530:21
  at C:\wamp\www\GitHub\biscotto\src\biscotto.coffee:331:7
  at ChildProcess.exithandler (child_process.js:635:7)
  at ChildProcess.EventEmitter.emit (events.js:98:17)
  at maybeClose (child_process.js:735:16)
  at Socket.<anonymous> (child_process.js:948:11)
  at Socket.EventEmitter.emit (events.js:95:17)
  at Pipe.close (net.js:466:12)

Changing the references from partials/* to ./partials/* doesn't change the result.

It might be something to do with haml-coffee#90 but as defined in the package.json, my coffee-script version is 1.6.2. 1.6.3 doesn't help either. I tried upping the haml-coffee version from 0.6.0 to 1.14.1 (mentioned in that issue) but still no luck.

cc @nathansobo

Possible to turn off the "Generated on xxx" output?

It's quite annoying as everytime I regenerate my documentation, even if there is no content changes, the "generated on xxx" output causes changes to occur anyway, which is frustrating for the build process as it means that there is always new changes, even if they aren't actually real changes.

An option to turn the "generated on" output off would be great.

Private classes now appear in listing

So I think something didn't work quite right in the change for 0.0.13, private classes are now appearing on the class listing and if you click on them there files don't exist.

Is it possible to maintain the delegate functionality without listing the private classes in the sidebar?

Handling optional arguments in coffeescript

So currently a method like this:

method: ({option1, option2})

gets rendering into this in the documentation:

method()

Is there a way to get the named arguments through? If not, is there a way to atleast substitute the word options instead of having nothing?

Markdown descriptions are not rendered correctly

The following:

# Public: Our Task Class.
#
# Available configuration is documented in {::setConfig}.
#
# Available events:
#
# - `started()` - emitted when we start execution
# - `running()` - emitted when the method starts execution
# - `failed(err)` - emitted when execution exited with a failure
# - `passed()` - emitted when execution exited with a success
# - `completed(err, args...)` - emitted when execution exited, `args` are the result arguments from the method
# - `error(err)` - emtited if an unexpected error occurs without ourself
# - `done(err, args...)` - emitted when either execution completes (the `completed` event) or when an unexpected error occurs (the `error` event)
#
# Available internal statuses:
#
# - `null` - execution has not yet started
# - `'started'` - execution has begun
# - `'running'` - execution of our method has begun
# - `'failed'` - execution of our method has failed
# - `'passed'` - execution of our method has succeeded
# - `'destroyed'` - we've been destroyed and can no longer execute
#
# Examples
#
#  task = require('taskgroup').Task.create('my synchronous task', function(){
#    return 5
#  }).done(console.log)  // [null, 5]
#
#  task = require('taskgroup').Task.create('my asynchronous task', function(complete){
#    complete(null, 5)
#  }).done(console.log)  // [null, 5]
#
#  task = require('taskgroup').Task.create('my task that errors', function(){
#    var err = new Error('deliberate error')
#    return err;  // if asynchronous, can also do: complete(err)
#    // thrown and uncaught errors are also caught thanks to domains, but that should be avoided
#    // as it would put your app in an unknown state
#  }).done(console.log)  // [Error('deliberator error')]

Gets outputted as:

https://www.dropbox.com/s/p1khmx4lqvkegww/Screenshot%202015-02-02%2008.04.44.png?dl=0

Some issues with the generated documentation (especially keybindings)

Sorry for not creating multiple issues but I don't think each comment would warrant its own.

  1. Instead of using ctrl+t (which prevents me from opening new tabs), how about using the same binding as Atom itself (ctrl+shift+p, or cmd) for the fuzzy finder dialog?
  2. FireFox still opens the chronic for ctrl+h, focusses the location ar for ctrl+l and wants to save the file with ctrl+s instead of focusing the search bar. I think you need to call some "prevent default" method on the event where you are parsing bindings - or use completely different bindings.
  3. Trying to open the "Biscotto" link at the bottom of http://atom.io/docs/api/v0.59.0/api/ just makes the inner frame white instead of opeing the url.
  4. The keyboard shortcut box's content is offset vertically. Screenshot

This is on FireFox 28 beta.

Crash when parsing class containing identifiers starting with upper case character

Minimum repro:

# Public: Test!
class Test
  TESTY_TEST: 1000

Results in:

/usr/local/lib/node_modules/biscotto/src/util/referencer.coffee:217
          for (index = _j = 0, _len1 = options.length; _j < _len1; index = ++_
                                              ^
TypeError: Cannot read property 'length' of undefined
  at Referencer.module.exports.Referencer.resolveDoc (/usr/local/lib/node_modules/biscotto/src/util/referencer.coffee:213:9)

... snip ...

May be a duplicate of #34? But without knowing what code triggered that issue, I can't be sure.

On the other hand, this code:

# Public: Test!
class Test
  tESTY_TEST: 1000

parses without crashing.

CoffeeScript 1.7.1 Support

Perhaps I'm reporting this in error. My apologies if so. Does biscotto support CoffeeScript 1.7+?

I'm getting parse errors when using with CS 1.7 syntax. Unexpected newlines, then this:

...project/node_modules/biscotto/src/util/referencer.coffee:217
          for (index = _j = 0, _len1 = options.length; _j < _len1; index = ++_
                                              ^
TypeError: Cannot read property 'length' of undefined
  at Referencer.module.exports.Referencer.resolveDoc (...project/node_modules/biscotto/src/util/referencer.coffee:213:9)

(filename path trimmed to save space)

Support TomDoc style nested hashes

Would be cool if this style was supported in param and return comments.

Example (taken from tomdoc.org)

# options - The Hash options used to refine the selection (default: {}):
#           :color  - The String color to restrict by (optional).
#           :weight - The Float weight to restrict by. The weight should
#                     be specified in grams (optional).

--statsOnly creates doc folder with assets

Docs say:

--statsOnly Only returns stats; generates no doc output

> rm -fr doc

> ls doc
ls: doc: No such file or directory

> biscotto --statsOnly src/
[WARN] Cannot resolve link to .script of class Biscotto in class Generator
[WARN] Cannot resolve link to .style of class Biscotto in class Generator
[WARN] Cannot find referenced class #getClassName in class Class (#getClassName)
[WARN] Cannot find referenced class #getClassName in class Class (#getClassName)
[WARN] Cannot find referenced class Mixin#getMixinName in class Mixin (Mixin#getMixinName)
[WARN] Cannot find referenced class Mixin#getMixinName in class Mixin (Mixin#getMixinName)
[WARN] Cannot find referenced class #getLink in class Referencer (#getLink)
[WARN] Cannot find referenced class #getLink in class Referencer (#getLink)
[WARN] Cannot find referenced class #linkTypes in class Referencer (#linkTypes)
Parsed files:           17
Classes:                16 (0 undocumented)
Mixins:                  0
Non-Class files:         0
Methods:               131 (5 undocumented)
Constants:               0 (0 undocumented)
 96.60% documented (147 total, 5 with no doc)
 9 errors

> ls -al doc/
total 0
drwxr-xr-x   4 kevin  staff  136 Apr 19 16:22 .
drwxr-xr-x  19 kevin  staff  646 Apr 19 16:22 ..
drwxr-xr-x   5 kevin  staff  170 Apr 19 16:22 assets
drwxr-xr-x   2 kevin  staff   68 Apr 19 16:22 classes

Examples aren't being parsed/generated correctly?

Hi there,

This is what I did:

  # Set the value of one or multiple fields in the modeled
  # document.
  #
  # {::set} can be called two ways: one-off and 
  # bulk.
  #
  #
  # Examples
  #
  #   model.set('flavor', 'turkish');
  #   model.set({flavor: 'turkish', price: 4});
  #
  #
  # Returns true if changes were propagated to the database

(https://github.com/pzatrick/meteor-questions/blob/master/packages/reactive-model/lib/reactive_model.coffee#L50-L53)

This is what I got:

image

I expected to see the examples displayed as pretty-printed code, or to at least be displayed. Am I doing something wrong?

Thanks,
Patrick

How to document Method Overloading ?

How to document Method Overloading ?

For example CODO is having @overload. This is a common pattern in .js & .coffee ( same method may determine the signature at run time, based on parameters or order it is received )

# This is a generic Store set method.
#
# @overload set(key, value)
#   Sets a value on key
#   @param [Symbol] key describe key param
#   @param [Object] value describe value param
#
# @overload set(value)
#   Sets a value on the default key `:foo`
#   @param [Object] value describe value param
#   @return [Boolean] true when success
#
set: (args...) ->

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.