Apipie / apipie-dsl

Apache License 2.0
1 stars 3 forks source link

DSL Documentation Tool

Apipie-dsl is a DSL for documenting DSLs written in Ruby. Instead of traditional use of #comments, ApipieDSL lets you describe the code, through the code.

Getting started

Within Rails application

The easiest way to get ApipieDSL up and running with your app is:

  echo "gem 'apipie-dsl'" >> Gemfile
  bundle install
  rails g apipie:install

Now you can start documenting your DSLs (see DSL Reference for more info):

  apipie :method, 'Method description' do
    required :string, String, 'Required string parameter'
  end
  def print(string)
   # ...
  end

Within plain Ruby application

  echo "gem 'apipie-dsl'" >> Gemfile
  # To be able to generate HTML documentation via rake command
  echo "gem 'rake'" >> Gemfile
  echo "gem 'actionview'" >> Gemfile
  bundle install

In case if you want to generate HTML documentation via rake command, I'd suggest have the following in your Rakefile:

require 'apipie-dsl'

# Configuration is required!
ApipieDSL.configure do |config|
  config.app_name = 'My DSL Docs'
  # Matchers are needed to load your code with documentation
  config.dsl_classes_matchers = [
    "#{File.dirname(__dir__)}/dsl_example/common_classes.rb",
    "#{File.dirname(__dir__)}/dsl_example/dsl.rb"
  ]
  # This is important for plain ruby application!
  config.rails = false
  # ... other configurations
end
spec = Gem::Specification.find_by_name('apipie-dsl')
rakefile = "#{spec.gem_dir}/lib/apipie_dsl/Rakefile"
load(rakefile)

Documentation

Contents

Configuration Reference

Create a configuration file (e.g. /config/initializers/apipie-dsl.rb for Rails). You can set the application name, footer text, DSL and documentation base URL and turn off validations. You can also choose your favorite markup language for full descriptions.

Example:

ApipieDSL.configure do |config|
  config.app_name = "Test app"
  config.copyright = "© 2019 Oleh Fedorenko"
  config.doc_base_url = "/apipie-dsl"
  config.validate = false
  config.markup = ApipieDSL::Markup::Markdown.new
  config.dsl_controllers_matchers = [
    "#{File.dirname(__dir__)}/dsl_example/common.rb",
    "#{File.dirname(__dir__)}/dsl_example/dsl.rb"
  ]
  config.app_info["1.0"] = "
   This is where you can inform user about your application and DSL
   in general.
  "
  config.sections = %i[all additional]
end

DSL Reference

Common Description

Class/Module Description

Describe your class via:

  apipie :class do
    # ...
  end

Inheritance is supported, so you can specify common params for group of classes in their parent class.

The following keywords are available (all are optional):

Example:
  apipie :class, 'Base tag' do
    name 'Base::Tag'
    sections only: :additional
    refs 'BaseTag', 'Base::Tag', 'Tag'
    dsl_version 'development'
    meta :author => {:name => 'John', :surname => 'Doe'}
    deprecated false
    description <<-EOS
      == Long description
      Example class for dsl documentation
      ...
    EOS
  end

Method Description

Then describe methods available to your DSL:

  apipie :method do
    # ...
  end
Example:
  apipie :method, 'Short description' do
    description 'Full method description'
    required :id, String, desc: 'ID for tag'
    optional :type, String, desc: 'Optional type', default: ''
    param :css_class, String, :desc => 'CSS class', type: :optional, default: ''
    keyword :content, Hash, :desc => 'Hash with content' do
      optional :text, String, 'Text string', default: 'Default text'
    end
    returns BaseTag
    raises ArgumentError, 'String is expected'
    raises ArgumentError, 'Hash is expected'
    meta :message => 'Some very important info'
    see 'html#tag', 'Link description'
    see :link => 'html#tags', :desc => 'Another link description'
    show false
  end
  def tag(id, type = '', css_class = '', content: { text: 'Default text' })
   #...
  end

Parameter Description

Use param to describe every possible parameter. You can use the Hash validator in conjunction with a block given to the param method to describe nested parameters.

Example:
  param :user, Hash, :desc => 'User info' do
    param :username, String, desc: 'Username for login', required: true
    param :password, String, desc: 'Password for login', required: true
    param :membership, ['standard','premium'], desc: 'User membership'
    param :admin_override, String, desc: 'Not shown in documentation', show: false
  end
  def create
    #...
  end

DRY with param_group

Often, params occur together in more methods. These params can be extracted with def_param_group and param_group keywords.

The definition is looked up in the scope of the class. If the group is defined in a different class, it might be referenced by specifying the second argument.

Example:
  # v1/users_class.rb
  def_param_group :address do
    param :street, String
    param :number, Integer
    param :zip, String
  end

  def_param_group :user do
    param :user, Hash do
      param :name, String, 'Name of the user'
      param_group :address
    end
  end

  apipie :method, 'Create an user' do
    param_group :user
  end
  def create(user)
    # ...
  end

  apipie :method, 'Update an user' do
    param_group :user
  end
  def update(user)
    # ...
  end

  # v2/users_class.rb
  apipie :method, 'Create an user' do
    param_group :user, V1::UsersClass
  end
  def create(user)
    # ...
  end

Return Description

TODO

Example:
  # TODO

Rails Integration

TODO

Validators

TODO

TypeValidator

Check the parameter type. Only String, Hash and Array are supported for the sake of simplicity. Read more to find out how to add your own validator.

  # TODO

RegexpValidator

Check parameter value against given regular expression.

  param :regexp_param, /^[0-9]* years/, desc: 'regexp param'

EnumValidator

Check if parameter value is included in the given array.

  param :enum_param, [100, 'one', 'two', 1, 2], desc: 'enum validator'

ProcValidator

If you need more complex validation and you know you won't reuse it, you can use the Proc/lambda validator. Provide your own Proc, taking the value of the parameter as the only argument. Return true if value passes validation or return some text about what is wrong otherwise. Don't use the keyword return if you provide an instance of Proc (with lambda it is ok), just use the last statement return property of ruby.

  param :proc_param, lambda { |val|
    val == 'param value' ? true : "The only good value is 'param value'."
  }, desc: 'proc validator'

HashValidator

You can describe hash parameters in depth if you provide a block with a description of nested values.

   param :user, Hash, desc: 'User info' do
     required :username, String, desc: 'Username for login'
     required :password, String, desc: 'Password for login'
     param :membership, ['standard','premium'], desc: 'User membership'
   end

NumberValidator

Check if the parameter is a positive integer number or zero.

  required :product_id, :number, desc: 'Identifier of the product'
  required :quantity, :number, desc: 'Number of products to order'

DecimalValidator

Check if the parameter is a decimal number.

  required :latitude, :decimal, desc: 'Geographic latitude'
  required :longitude, :decimal, desc: 'Geographic longitude'

ArrayValidator

Check if the parameter is an array.

  required :array_param, Array, desc: 'array param'
Additional options
Examples:

Assert things is an array of any items.

  param :things, Array

Assert hits must be an array of integer values.

  param :hits, Array, of: Integer

Assert colors must be an array of valid string values.

  param :colors, Array, in: ['red', 'green', 'blue']

The retrieving of valid items can be deferred until needed using a lambda. It is evaluated only once

  param :colors, Array, in: ->  { Colors.all.map(&:name) }

NestedValidator

You can describe nested parameters in depth if you provide a block with a description of nested values.

  param :comments, Array, desc: 'User comments' do
    required :name, String, desc: 'Name of the comment'
    required :comment, String, desc: 'Full comment'
  end

Adding custom validator

TODO

Versioning

TODO

Markup

The default markup language is RDoc. It can be changed in the config file (config.markup=) to one of these:

Or provide you own object with a to_html(text) method. For inspiration, this is how Textile markup usage is implemented:

  class Textile
    def initialize
      require 'RedCloth'
    end

    def to_html(text)
      RedCloth.new(text).to_html
    end
  end

Localization

TODO

Static files

TODO

Known issues