diff options
| author | elijah <elijah@riseup.net> | 2012-12-09 22:24:57 -0800 | 
|---|---|---|
| committer | elijah <elijah@riseup.net> | 2013-02-23 16:46:28 -0800 | 
| commit | 0db0bc3b6f0bc616fa9b1319be3083e4ea6affce (patch) | |
| tree | 2ea0c6390a86143b773a7dc66304ca074046ffc4 | |
| parent | ef718864cabfa95d4fbed037022f6688d86f87b1 (diff) | |
initial experiments with auto doc creation
| -rw-r--r-- | Rakefile | 108 | ||||
| -rw-r--r-- | lib/leap_cli/app.rb | 57 | ||||
| -rw-r--r-- | lib/lib_ext/markdown_document_listener.rb | 122 | 
3 files changed, 287 insertions, 0 deletions
| @@ -27,6 +27,8 @@ $spec_path = 'leap_cli.gemspec'  $base_dir  = File.dirname(__FILE__)  $spec      = eval(File.read(File.join($base_dir, $spec_path)))  $gem_path  = File.join($base_dir, 'pkg', "#{$spec.name}-#{$spec.version}.gem") +$lib_dir   = "#{$base_dir}/lib" +$LOAD_PATH.unshift $lib_dir  def built_gem_path    Dir[File.join($base_dir, "#{$spec.name}-*.gem")].sort_by{|f| File.mtime(f)}.last @@ -122,3 +124,109 @@ end  #   rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")  #   rd.title = 'Your application title'  # end + +desc "Generate documentation" +task 'doc' do +  require 'leap_cli' +  require 'leap_cli/app' + +  class DocMaker < GLI::Command +    def initialize(app) +      @app = app +      @listener = GLI::Commands::RdocDocumentListener.new([],[],[]) +    end + +    def create +      @listener.beginning +      @listener.program_desc(@app.program_desc) unless @app.program_desc.nil? +      @listener.program_long_desc(@app.program_long_desc) unless @app.program_long_desc.nil? +      @listener.version(@app.version_string) +      if any_options?(@app) +        @listener.options +      end +      document_flags_and_switches(@listener, @app.flags.values.sort(&by_name), @app.switches.values.sort(&by_name)) +      if any_options?(@app) +        @listener.end_options +      end +      @listener.commands +      document_commands(@listener, @app) +      @listener.end_commands +      @listener.ending +    end + +    private + +    def document_commands(document_listener,context) +      context.commands.values.reject {|_| _.nodoc }.sort(&by_name).each do |command| +        call_command_method_being_backwards_compatible(document_listener,command) +        document_listener.options if any_options?(command) +        document_flags_and_switches(document_listener,command_flags(command),command_switches(command)) +        document_listener.end_options if any_options?(command) +        document_listener.commands if any_commands?(command) +        document_commands(document_listener,command) +        document_listener.end_commands if any_commands?(command) +        document_listener.end_command(command.name) +      end +      document_listener.default_command(context.get_default_command) +    end + +    def call_command_method_being_backwards_compatible(document_listener,command) +      command_args = [command.name, +                      Array(command.aliases), +                      command.description, +                      command.long_description, +                      command.arguments_description] +      if document_listener.method(:command).arity == 6 +        command_args << command.arguments_options +      end +      document_listener.command(*command_args) +    end + +    def by_name +      lambda { |a,b| a.name.to_s <=> b.name.to_s } +    end + +    def command_flags(command) +      command.topmost_ancestor.flags.values.select { |flag| flag.associated_command == command }.sort(&by_name) +    end + +    def command_switches(command) +      command.topmost_ancestor.switches.values.select { |switch| switch.associated_command == command }.sort(&by_name) +    end + +    def document_flags_and_switches(document_listener,flags,switches) +      flags.each do |flag| +        document_listener.flag(flag.name, +                               Array(flag.aliases), +                               flag.description, +                               flag.long_description, +                               flag.safe_default_value, +                               flag.argument_name, +                               flag.must_match, +                               flag.type) +      end +      switches.each do |switch| +        document_listener.switch(switch.name, +                                 Array(switch.aliases), +                                 switch.description, +                                 switch.long_description, +                                 switch.negatable) +      end +    end + +    def any_options?(context) +      options = if context.kind_of?(GLI::Command) +                  command_flags(context) + command_switches(context) +                else +                  context.flags.values + context.switches.values +                end +      !options.empty? +    end + +    def any_commands?(command) +      !command.commands.empty? +    end +  end + +  puts DocMaker.new(LeapCli::Commands).create +end diff --git a/lib/leap_cli/app.rb b/lib/leap_cli/app.rb new file mode 100644 index 0000000..90c4ae9 --- /dev/null +++ b/lib/leap_cli/app.rb @@ -0,0 +1,57 @@ +require 'gli' +require 'highline' +require 'forwardable' +require 'lib_ext/gli' # our custom extensions to gli + +# +# Typically, GLI and Highline methods are loaded into the global namespace. +# Instead, here we load these into the module LeapCli::Commands in order to +# ensure that the cli logic and code is kept isolated to leap_cli/commands/*.rb +# +# no cheating! +# +module LeapCli::Commands +  extend GLI::App +  extend Forwardable + +  # +  # delegate highline methods to make them available to sub-commands +  # +  @terminal = HighLine.new +  def_delegator :@terminal, :ask,    'self.ask' +  def_delegator :@terminal, :agree,  'self.agree' +  def_delegator :@terminal, :choose, 'self.choose' +  def_delegator :@terminal, :say,    'self.say' +  def_delegator :@terminal, :color,  'self.color' +  def_delegator :@terminal, :list,   'self.list' + +  # +  # make config manager available as 'manager' +  # +  def self.manager +    @manager ||= begin +      manager = LeapCli::Config::Manager.new +      manager.load +      manager +    end +  end + +  # +  # info about leap command line suite +  # +  program_desc       LeapCli::SUMMARY +  program_long_desc  LeapCli::DESCRIPTION + +  # +  # handle --version ourselves +  # +  if ARGV.grep(/--version/).any? +    puts "leap #{LeapCli::VERSION}, ruby #{RUBY_VERSION}" +    exit(0) +  end + +  # +  # load commands and run +  # +  commands_from('leap_cli/commands') +end diff --git a/lib/lib_ext/markdown_document_listener.rb b/lib/lib_ext/markdown_document_listener.rb new file mode 100644 index 0000000..55026e9 --- /dev/null +++ b/lib/lib_ext/markdown_document_listener.rb @@ -0,0 +1,122 @@ +require 'stringio' +require 'gli/commands/help_modules/arg_name_formatter' + +# +# adaption of RdocDocumentListener to use Markdown +# see http://rtomayko.github.com/ronn/ronn-format.7 +# + +module GLI +  module Commands +    class MarkdownDocumentListener + +      def initialize(global_options,options,arguments) +        @io = STDOUT #File.new(File.basename($0) + ".rdoc",'w') +        @nest = '' +        @arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new +      end + +      def beginning +      end + +      # Called when processing has completed +      def ending +        #@io.close +      end + +      # Gives you the program description +      def program_desc(desc) +        @io.puts "== #{File.basename($0)} - #{desc}" +        @io.puts +      end + +      def program_long_desc(desc) +        @io.puts desc +        @io.puts +      end + +      # Gives you the program version +      def version(version) +        @io.puts "v#{version}" +        @io.puts +      end + +      def options +        if @nest.size == 0 +          @io.puts "=== Global Options" +        else +          @io.puts "#{@nest}=== Options" +        end +      end + +      # Gives you a flag in the current context +      def flag(name,aliases,desc,long_desc,default_value,arg_name,must_match,type) +        invocations = ([name] + Array(aliases)).map { |_| add_dashes(_) }.join('|') +        usage = "#{invocations} #{arg_name || 'arg'}" +        @io.puts "#{@nest}=== #{usage}" +        @io.puts +        @io.puts String(desc).strip +        @io.puts +        @io.puts "[Default Value] #{default_value || 'None'}" +        @io.puts "[Must Match] #{must_match.to_s}" unless must_match.nil? +        @io.puts String(long_desc).strip +        @io.puts +      end + +      # Gives you a switch in the current context +      def switch(name,aliases,desc,long_desc,negetable) +        if negetable +          name = "[no-]#{name}" if name.to_s.length > 1 +          aliases = aliases.map { |_|  _.to_s.length > 1 ? "[no-]#{_}" : _ } +        end +        invocations = ([name] + aliases).map { |_| add_dashes(_) }.join('|') +        @io.puts "#{@nest}=== #{invocations}" +        @io.puts String(desc).strip +        @io.puts +        @io.puts String(long_desc).strip +        @io.puts +      end + +      def end_options +      end + +      def commands +        @io.puts "#{@nest}=== Commands" +        @nest = "#{@nest}=" +      end + +      # Gives you a command in the current context and creates a new context of this command +      def command(name,aliases,desc,long_desc,arg_name,arg_options) +        @io.puts "#{@nest}=== Command: <tt>#{([name] + aliases).join('|')} #{@arg_name_formatter.format(arg_name,arg_options)}</tt>" +        @io.puts String(desc).strip +        @io.puts +        @io.puts String(long_desc).strip +        @nest = "#{@nest}=" +      end + +      # Ends a command, and "pops" you back up one context +      def end_command(name) +        @nest.gsub!(/=$/,'') +      end + +      # Gives you the name of the current command in the current context +      def default_command(name) +        @io.puts "[Default Command] #{name}" unless name.nil? +      end + +      def end_commands +        @nest.gsub!(/=$/,'') +      end + +    private + +      def add_dashes(name) +        name = "-#{name}" +        name = "-#{name}" if name.length > 2 +        name +      end + + +    end +  end +end | 
