Commit 0c5d4eb7 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Create a little DSL for describing options.

Instead of calling optparser.on and opts.attribute directly, call
option and attribute methods that simply delegate to these objects.
This will also make it easier to refactor things in the future.
parent 2c55613d
Loading
Loading
Loading
Loading
+775 −752
Original line number Diff line number Diff line
@@ -18,7 +18,8 @@ module JsDuck

        @root_dir = File.dirname(File.dirname(File.dirname(File.dirname(__FILE__))))

        @optparser = create_parser
        @optparser = Options::HelpfulParser.new
        init_parser
      end

      # Parses array of command line options, returning
@@ -31,30 +32,30 @@ module JsDuck

      private

      def create_parser
        return Options::HelpfulParser.new do | optparser |
          optparser.banner = "Usage: jsduck [options] files/dirs..."
          optparser.separator ""
          optparser.separator "For example:"
          optparser.separator ""
          optparser.separator "    # Documentation for builtin JavaScript classes like Array and String"
          optparser.separator "    jsduck --output output/dir  --builtin-classes"
          optparser.separator ""
          optparser.separator "    # Documentation for your own JavaScript"
          optparser.separator "    jsduck --output output/dir  input-file.js some/input/dir"
          optparser.separator ""
          optparser.separator "The main options:"
          optparser.separator ""

          @opts.attribute(:input_files, [])
          @opts.validator do
      def init_parser
        banner "Usage: jsduck [options] files/dirs..."

        separator ""
        separator "For example:"
        separator ""
        separator "    # Documentation for builtin JavaScript classes like Array and String"
        separator "    jsduck --output output/dir  --builtin-classes"
        separator ""
        separator "    # Documentation for your own JavaScript"
        separator "    jsduck --output output/dir  input-file.js some/input/dir"
        separator ""
        separator "The main options:"
        separator ""

        attribute :input_files, []
        validator do
          if @opts.input_files.empty? && !@opts.welcome && !@opts.guides && !@opts.videos && !@opts.examples
            "Please specify some input files, otherwise there's nothing I can do :("
          end
        end

          @opts.attribute(:output_dir)
          optparser.on('-o', '--output=PATH',
        attribute :output_dir
        option('-o', '--output=PATH',
          "Directory to write all this documentation.",
          "",
          "This option is REQUIRED.  When the directory exists,",
@@ -67,7 +68,7 @@ module JsDuck
            @opts.cache_dir = @opts.output_dir + "/.cache" unless @opts.cache_dir
          end
        end
          @opts.validator do
        validator do
          if @opts.output_dir == :stdout
            # No output dir needed for export
            if !@opts.export
@@ -82,8 +83,8 @@ module JsDuck
          end
        end

          @opts.attribute(:export)
          optparser.on('--export=full/examples',
        attribute :export
        option('--export=full/examples',
          "Exports docs in JSON.",
          "",
          "For each JavaScript class a JSON file gets written,",
@@ -93,13 +94,13 @@ module JsDuck
          "- examples - inline examples from classes and guides.") do |format|
          @opts.export = format.to_sym
        end
          @opts.validator do
        validator do
          if ![nil, :full, :examples].include?(@opts.export)
            "Unknown export format: #{@opts.export}"
          end
        end

          optparser.on('--builtin-classes',
        option('--builtin-classes',
          "Includes docs for JavaScript builtins.",
          "",
          "Docs for the following classes are included:",
@@ -115,8 +116,8 @@ module JsDuck
          @opts.input_files << @root_dir + "/js-classes"
        end

          @opts.attribute(:seo, false)
          optparser.on('--seo', "Enables SEO-friendly print version.",
        attribute :seo, false
        option('--seo', "Enables SEO-friendly print version.",
          "",
          "Instead of index.html creates index.php that will serve",
          "the regular docs, print-friendly docs, and search-engine-",
@@ -124,8 +125,8 @@ module JsDuck
          @opts.seo = true
        end

          @opts.attribute(:config)
          optparser.on('--config=PATH',
        attribute :config
        option('--config=PATH',
          "Loads config options from JSON file.",
          "",
          "An alternative to listing all options on command line.",
@@ -149,13 +150,13 @@ module JsDuck
          @opts.config = path
        end

          @opts.attribute(:encoding)
          optparser.on('--encoding=NAME', "Input encoding (defaults to UTF-8).") do |encoding|
        attribute :encoding
        option('--encoding=NAME', "Input encoding (defaults to UTF-8).") do |encoding|
          @opts.encoding = encoding
        end

          @opts.attribute(:exclude, [])
          optparser.on('--exclude=PATH1,PATH2', Array, "Exclude input file or directory.",
        attribute :exclude, []
        option('--exclude=PATH1,PATH2', Array, "Exclude input file or directory.",
          "",
          "For example to include all the subdirs of",
          "/app/js except /app/js/new, run JSDuck with:",
@@ -164,13 +165,13 @@ module JsDuck
          @opts.exclude += paths.map {|p| canonical(p) }
        end

          optparser.separator ""
          optparser.separator "Customizing output:"
          optparser.separator ""
        separator ""
        separator "Customizing output:"
        separator ""

          @opts.attribute(:title, "Documentation - JSDuck")
          @opts.attribute(:header, "<strong>Documentation</strong> JSDuck")
          optparser.on('--title=TEXT',
        attribute :title, "Documentation - JSDuck"
        attribute :header, "<strong>Documentation</strong> JSDuck"
        option('--title=TEXT',
          "Custom title text for the documentation.",
          "",
          "Defaults to 'Documentation - JSDuck'",
@@ -182,8 +183,8 @@ module JsDuck
          @opts.header = text.sub(/^(.*?) +- +/, "<strong>\\1 </strong>")
        end

          @opts.attribute(:footer, "Generated on {DATE} by {JSDUCK} {VERSION}.")
          optparser.on('--footer=TEXT',
        attribute :footer, "Generated on {DATE} by {JSDUCK} {VERSION}."
        option('--footer=TEXT',
          "Custom footer text for the documentation.",
          "",
          "The text can contain various placeholders:",
@@ -196,8 +197,8 @@ module JsDuck
          @opts.footer = text
        end

          @opts.attribute(:head_html, "")
          optparser.on('--head-html=HTML',
        attribute :head_html, ""
        option('--head-html=HTML',
          "HTML for the <head> section of index.html.",
          "",
          "Useful for adding extra <style> and other tags.",
@@ -210,8 +211,8 @@ module JsDuck
          @opts.head_html += maybe_file(html)
        end

          @opts.attribute(:body_html, "")
          optparser.on('--body-html=HTML',
        attribute :body_html, ""
        option('--body-html=HTML',
          "HTML for the <body> section of index.html.",
          "",
          "Useful for adding extra markup to the page.",
@@ -224,8 +225,8 @@ module JsDuck
          @opts.body_html += maybe_file(html)
        end

          @opts.attribute(:css, "")
          optparser.on('--css=CSS',
        attribute :css, ""
        option('--css=CSS',
          "Extra CSS rules to include to the page.",
          "",
          "Also a name of a CSS file can be passed.",
@@ -236,8 +237,8 @@ module JsDuck
          @opts.css += maybe_file(css)
        end

          @opts.attribute(:message, "")
          optparser.on('--message=HTML',
        attribute :message, ""
        option('--message=HTML',
          "(Warning) message to show prominently.",
          "",
          "Useful for warning users that they are viewing an old",
@@ -246,8 +247,8 @@ module JsDuck
          @opts.message += html
        end

          @opts.attribute(:welcome)
          optparser.on('--welcome=PATH',
        attribute :welcome
        option('--welcome=PATH',
          "File with content for welcome page.",
          "",
          "Either HTML or Markdown file with content for welcome page.",
@@ -258,8 +259,8 @@ module JsDuck
          @opts.welcome = canonical(path)
        end

          @opts.attribute(:guides)
          optparser.on('--guides=PATH',
        attribute :guides
        option('--guides=PATH',
          "JSON file describing the guides.",
          "",
          "The file should be in a dir containing the actual guides.",
@@ -270,8 +271,8 @@ module JsDuck
          @opts.guides = canonical(path)
        end

          @opts.attribute(:guides_toc_level, 2)
          optparser.on('--guides-toc-level=LEVEL',
        attribute :guides_toc_level, 2
        option('--guides-toc-level=LEVEL',
          "Max heading level for guides' tables of contents.",
          "",
          "Values between 1 and 6 define the maximum level of a heading",
@@ -285,30 +286,30 @@ module JsDuck
          "6 - <H2>,<H3>,<H4>,<H5>,<H6> headings are included.") do |level|
          @opts.guides_toc_level = level.to_i
        end
          @opts.validator do
        validator do
          if !(1..6).include?(@opts.guides_toc_level)
            "Unsupported --guides-toc-level: '#{@opts.guides_toc_level}'"
          end
        end

          @opts.attribute(:videos)
          optparser.on('--videos=PATH',
        attribute :videos
        option('--videos=PATH',
          "JSON file describing the videos.",
          "",
          "See also: https://github.com/senchalabs/jsduck/wiki/Videos") do |path|
          @opts.videos = canonical(path)
        end

          @opts.attribute(:examples)
          optparser.on('--examples=PATH',
        attribute :examples
        option('--examples=PATH',
          "JSON file describing the examples.",
          "",
          "See also: https://github.com/senchalabs/jsduck/wiki/Examples") do |path|
          @opts.examples = canonical(path)
        end

          @opts.attribute(:categories_path)
          optparser.on('--categories=PATH',
        attribute :categories_path
        option('--categories=PATH',
          "JSON file defining categories for classes.",
          "",
          "Without this option the classes will be categorized",
@@ -318,14 +319,14 @@ module JsDuck
          @opts.categories_path = canonical(path)
        end

          @opts.attribute(:source, true)
          optparser.on('--no-source',
        attribute :source, true
        option('--no-source',
          "Turns off the output of source files.") do
          @opts.source = false
        end

          @opts.attribute(:images, [])
          optparser.on('--images=PATH1,PATH2', Array,
        attribute :images, []
        option('--images=PATH1,PATH2', Array,
          "Paths for images referenced by {@img} tag.",
          "",
          "This option only applies to {@img} tags used in",
@@ -335,14 +336,14 @@ module JsDuck
          @opts.images += paths.map {|p| canonical(p) }
        end

          @opts.attribute(:tests, false)
          optparser.on('--tests',
        attribute :tests, false
        option('--tests',
          "Creates page for testing inline examples.") do
          @opts.tests = true
        end

          @opts.attribute(:imports, [])
          optparser.on('--import=VERSION:PATH',
        attribute :imports, []
        option('--import=VERSION:PATH',
          "Imports docs generating @since & @new.",
          "",
          "For example:",
@@ -368,8 +369,8 @@ module JsDuck
          end
        end

          @opts.attribute(:new_since)
          optparser.on('--new-since=VERSION',
        attribute :new_since
        option('--new-since=VERSION',
          "Since when to label items with @new tag.",
          "",
          "The VERSION must be one of the version names defined",
@@ -379,8 +380,8 @@ module JsDuck
          @opts.new_since = v
        end

          @opts.attribute(:comments_url)
          optparser.on('--comments-url=URL',
        attribute :comments_url
        option('--comments-url=URL',
          "Address of comments server.",
          "",
          "For example: http://projects.sencha.com/auth",
@@ -389,8 +390,8 @@ module JsDuck
          @opts.comments_url = url
        end

          @opts.attribute(:comments_domain)
          optparser.on('--comments-domain=STRING',
        attribute :comments_domain
        option('--comments-domain=STRING',
          "A name identifying the subset of comments.",
          "",
          "A string consisting of <name>/<version>.",
@@ -401,8 +402,8 @@ module JsDuck
          @opts.comments_domain = domain
        end

          @opts.attribute(:search, {})
          optparser.on('--search-url=URL',
        attribute :search, {}
        option('--search-url=URL',
          "Address of guides search server.",
          "",
          "When supplied, the search for guides is performed through this",
@@ -417,7 +418,7 @@ module JsDuck
          @opts.search[:url] = url
        end

          optparser.on('--search-domain=STRING',
        option('--search-domain=STRING',
          "A name identifying the subset to search from.",
          "",
          "A string consisting of <name>/<version>.",
@@ -433,12 +434,12 @@ module JsDuck
          @opts.search[:product], @opts.search[:version] = domain.split(/\//)
        end

          optparser.separator ""
          optparser.separator "Tweaking:"
          optparser.separator ""
        separator ""
        separator "Tweaking:"
        separator ""

          @opts.attribute(:tags, [])
          optparser.on('--tags=PATH1,PATH2', Array,
        attribute :tags, []
        option('--tags=PATH1,PATH2', Array,
          "Paths to custom tag implementations.",
          "",
          "Paths can point to specific Ruby files or to a directory,",
@@ -448,8 +449,8 @@ module JsDuck
          @opts.tags += paths.map {|p| canonical(p) }
        end

          @opts.attribute(:ignore_global, false)
          optparser.on('--ignore-global',
        attribute :ignore_global, false
        option('--ignore-global',
          "Turns off the creation of 'global' class.",
          "",
          "The 'global' class gets created when members that",
@@ -462,7 +463,7 @@ module JsDuck
          @opts.ignore_global = true
        end

          @opts.attribute(:external_classes, [
        attribute :external_classes, [
          # JavaScript builtins
          "Object",
          "String",
@@ -495,8 +496,8 @@ module JsDuck
          "XMLHttpRequest",
          # Special anything-goes type
          "Mixed",
            ])
          optparser.on('--external=Foo,Bar,Baz', Array,
        ]
        option('--external=Foo,Bar,Baz', Array,
          "Declares list of external classes.",
          "",
          "These classes will then no more generate warnings",
@@ -511,8 +512,8 @@ module JsDuck
          @opts.external_classes += classes
        end

          @opts.attribute(:ext4_events)
          optparser.on('--[no-]ext4-events',
        attribute :ext4_events
        option('--[no-]ext4-events',
          "Forces Ext4 options param on events.",
          "",
          "In Ext JS 4 and Sencha Touch 2 all event handlers are",
@@ -530,16 +531,16 @@ module JsDuck
          @opts.ext4_events = e
        end

          @opts.attribute(:examples_base_url)
          optparser.on('--examples-base-url=URL',
        attribute :examples_base_url
        option('--examples-base-url=URL',
          "Base URL for examples with relative URL-s.",
          " ",
          "Defaults to: 'extjs-build/examples/'") do |path|
          @opts.examples_base_url = path
        end

          @opts.attribute(:link_tpl, '<a href="#!/api/%c%-%m" rel="%c%-%m" class="docClass">%a</a>')
          optparser.on('--link=TPL',
        attribute :link_tpl, '<a href="#!/api/%c%-%m" rel="%c%-%m" class="docClass">%a</a>'
        option('--link=TPL',
          "HTML template for replacing {@link}.",
          "",
          "Possible placeholders:",
@@ -555,8 +556,8 @@ module JsDuck
          @opts.link_tpl = tpl
        end

          @opts.attribute(:img_tpl, '<p><img src="%u" alt="%a" width="%w" height="%h"></p>')
          optparser.on('--img=TPL',
        attribute :img_tpl, '<p><img src="%u" alt="%a" width="%w" height="%h"></p>'
        option('--img=TPL',
          "HTML template for replacing {@img}.",
          "",
          "Possible placeholders:",
@@ -570,8 +571,8 @@ module JsDuck
          @opts.img_tpl = tpl
        end

          @opts.attribute(:eg_iframe)
          optparser.on('--eg-iframe=PATH',
        attribute :eg_iframe
        option('--eg-iframe=PATH',
          "HTML file used to display inline examples.",
          "",
          "The file will be used inside <iframe> that renders the",
@@ -583,8 +584,8 @@ module JsDuck
          @opts.eg_iframe = canonical(path)
        end

          @opts.attribute(:ext_namespaces)
          optparser.on('--ext-namespaces=Ext,Foo', Array,
        attribute :ext_namespaces
        option('--ext-namespaces=Ext,Foo', Array,
          "Additional Ext JS namespaces to recognize.",
          "",
          "Defaults to 'Ext'",
@@ -598,8 +599,8 @@ module JsDuck
          @opts.ext_namespaces = namespaces
        end

          @opts.attribute(:touch_examples_ui, false)
          optparser.on('--touch-examples-ui',
        attribute :touch_examples_ui, false
        option('--touch-examples-ui',
          "Turns on phone/tablet UI for examples.",
          "",
          "This is a very Sencha Touch 2 docs specific option.",
@@ -607,8 +608,8 @@ module JsDuck
          @opts.touch_examples_ui = true
        end

          @opts.attribute(:ignore_html, {})
          optparser.on('--ignore-html=TAG1,TAG2', Array,
        attribute :ignore_html, {}
        option('--ignore-html=TAG1,TAG2', Array,
          "Ignore a particular unclosed HTML tag.",
          "",
          "Normally all tags like <foo> that aren't followed at some",
@@ -625,12 +626,12 @@ module JsDuck
          end
        end

          optparser.separator ""
          optparser.separator "Performance:"
          optparser.separator ""
        separator ""
        separator "Performance:"
        separator ""

          @opts.attribute(:processes)
          optparser.on('-p', '--processes=COUNT',
        attribute :processes
        option('-p', '--processes=COUNT',
          "The number of parallel processes to use.",
          "",
          "Defaults to the number of processors/cores.",
@@ -643,8 +644,8 @@ module JsDuck
          @opts.processes = count.to_i
        end

          @opts.attribute(:cache, false)
          optparser.on('--[no-]cache',
        attribute :cache, false
        option('--[no-]cache',
          "Turns parser cache on/off (EXPERIMENTAL).",
          "",
          "Defaults to off.",
@@ -663,8 +664,8 @@ module JsDuck
          @opts.cache = enabled
        end

          @opts.attribute(:cache_dir)
          optparser.on('--cache-dir=PATH',
        attribute :cache_dir
        option('--cache-dir=PATH',
          "Directory where to cache the parsed source.",
          "",
          "Defaults to: <output-dir>/.cache",
@@ -685,20 +686,20 @@ module JsDuck
          @opts.cache_dir = path
        end

          optparser.separator ""
          optparser.separator "Debugging:"
          optparser.separator ""
        separator ""
        separator "Debugging:"
        separator ""

          @opts.attribute(:verbose, false)
          optparser.on('-v', '--verbose',
        attribute :verbose, false
        option('-v', '--verbose',
          "Turns on excessive logging.",
          "",
          "Log messages are written to STDERR.") do
          @opts.verbose = true
        end

          @opts.attribute(:warnings, [])
          optparser.on('--warnings=+A,-B,+C',
        attribute :warnings, []
        option('--warnings=+A,-B,+C',
          "Turns warnings selectively on/off.",
          "",
          " +all - to turn on all warnings.",
@@ -728,8 +729,8 @@ module JsDuck
          end
        end

          @opts.attribute(:warnings_exit_nonzero, false)
          optparser.on('--warnings-exit-nonzero',
        attribute :warnings_exit_nonzero, false
        option('--warnings-exit-nonzero',
          "Exits with non-zero exit code on warnings.",
          "",
          "By default JSDuck only exits with non-zero exit code",
@@ -740,8 +741,8 @@ module JsDuck
          @opts.warnings_exit_nonzero = true
        end

          @opts.attribute(:color)
          optparser.on('--[no-]color',
        attribute :color
        option('--[no-]color',
          "Turn on/off colorized terminal output.",
          "",
          "By default the colored output is on, but gets disabled",
@@ -750,8 +751,8 @@ module JsDuck
          @opts.color = on
        end

          @opts.attribute(:pretty_json)
          optparser.on('--pretty-json',
        attribute :pretty_json
        option('--pretty-json',
          "Turns on pretty-printing of JSON.",
          "",
          "This is useful when studying the JSON files generated",
@@ -761,14 +762,14 @@ module JsDuck
          @opts.pretty_json = true
        end

          @opts.attribute(:template_dir, @root_dir + "/template-min")
          optparser.on('--template=PATH',
        attribute :template_dir, @root_dir + "/template-min"
        option('--template=PATH',
          "Dir containing the UI template files.",
          "",
          "Useful when developing the template files.") do |path|
          @opts.template_dir = canonical(path)
        end
          @opts.validator do
        validator do
          if @opts.export
            # Don't check these things when exporting
          elsif !File.exists?(@opts.template_dir + "/extjs")
@@ -788,8 +789,8 @@ module JsDuck
          end
        end

          @opts.attribute(:template_links, false)
          optparser.on('--template-links',
        attribute :template_links, false
        option('--template-links',
          "Creates symlinks to UI template files.",
          "",
          "Useful for template files development.",
@@ -797,7 +798,7 @@ module JsDuck
          @opts.template_links = true
        end

          optparser.on('-d', '--debug',
        option('-d', '--debug',
          "Same as --template=template --template-links.",
          "",
          "Useful shorthand during development.") do
@@ -805,8 +806,8 @@ module JsDuck
          @opts.template_links = true
        end

          @opts.attribute(:extjs_path, "extjs/ext-all.js")
          optparser.on('--extjs-path=PATH',
        attribute :extjs_path, "extjs/ext-all.js"
        option('--extjs-path=PATH',
          "Path for main ExtJS JavaScript file.",
          "",
          "This is the ExtJS file that's used by the docs app UI.",
@@ -817,15 +818,15 @@ module JsDuck
          @opts.extjs_path = path # NB! must be relative path
        end

          @opts.attribute(:local_storage_db, "docs")
          optparser.on('--local-storage-db=NAME',
        attribute :local_storage_db, "docs"
        option('--local-storage-db=NAME',
          "Prefix for LocalStorage database names.",
          "",
          "Defaults to 'docs'") do |name|
          @opts.local_storage_db = name
        end

          optparser.on('-h', '--help[=--some-option]',
        option('-h', '--help[=--some-option]',
          "Use --help=--option for help on option.",
          "",
          "For example To get help on --processes option any of the",
@@ -836,18 +837,40 @@ module JsDuck
          "    --help=-p",
          "    --help=p") do |v|
          if v
              puts optparser.help_single(v)
            puts @optparser.help_single(v)
          else
              puts optparser.help
            puts @optparser.help
          end
          exit
        end

          optparser.on('--version', "Prints JSDuck version") do
        option('--version', "Prints JSDuck version") do
          puts "JSDuck " + JsDuck::VERSION + " (Ruby #{RUBY_VERSION})"
          exit
        end
      end

      # A little language for describing the options.
      # Simple delegetions to @optparser and @opts.

      def banner(txt)
        @optparser.banner = txt
      end

      def separator(txt)
        @optparser.separator(txt)
      end

      def option(*params, &block)
        @optparser.on(*params, &block)
      end

      def attribute(name, value=nil)
        @opts.attribute(name, value)
      end

      def validator(&block)
        @opts.validator(&block)
      end

      # Parses the given command line options