Created
September 1, 2012 23:10
-
-
Save alexspeller/3590638 to your computer and use it in GitHub Desktop.
Docopt Parselet proof of concept
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'parslet' | |
class Docopt < Parslet::Parser | |
# ========== | |
# = Basics = | |
# ========== | |
# Simple Tokens | |
rule(:equals) { str('=') } | |
rule(:newline) { str("\n") } | |
rule(:lt) { str('<') } | |
rule(:gt) { str('>') } | |
rule(:lbracket) { str('(') } | |
rule(:rbracket) { str(')') } | |
rule(:lsquare) { str('[') } | |
rule(:rsquare) { str(']') } | |
rule(:tab) { str("\t") } | |
rule(:dash) { str('-') } | |
rule(:pipe) { str('|') } | |
rule(:dots) { str('...').as(:dots) } | |
rule(:options_shortcut) { str("[options]").as(:options) } # just matches "[options]" as its own shortcut | |
# some more complex tokens | |
rule(:identifier) { match['a-z'].repeat(1) } # matches any word a-z | |
rule(:exclusive) { whitespace.maybe >> pipe >> whitespace.maybe } # matches "|" or " | " | |
rule(:option_start) { dash >> dash.maybe } # matches "-" or "--" | |
# types of whitespace | |
rule(:whitespace) { (spaces | newline | tab).repeat(1) } # any whitespace | |
rule(:indentation) { (spaces | tab).repeat(1) } # space or tab only | |
rule(:spaces) { str(" ").repeat(1) } # space only | |
# =================== | |
# = Root level rule = | |
# =================== | |
# This is the main rule for the parser, describing the root level | |
rule(:docstring) { title_and_description >> usage >> whitespace >> options >> whitespace.maybe } | |
root :docstring | |
# Matches anything up to "Usage:" | |
rule(:title_and_description) { (usage.absent? >> any).repeat(1).as(:title_and_description) } | |
# ================== | |
# = Usage examples = | |
# ================== | |
# this is the root level rule matching the whole usage examples section | |
rule(:usage) { str('Usage:') >> newline.maybe >> usage_examples.as(:usage_examples) } | |
rule(:usage_examples) { usage_example.repeat(1) } | |
rule(:usage_example) { (indentation >> program_name >> usage_content >> newline) } | |
rule(:program_name) { identifier } | |
rule(:usage_content) { (newline.absent? >> usage_body.as(:example)) >> dots.maybe } | |
# match an actual usage specification after stripping off program name and end of line | |
rule(:usage_body) { (options_shortcut | command_spec | argument_spec | option_spec | spaces).repeat(1) } | |
# commands in the usage line | |
rule(:command_spec) { optional(command | command_set, :command_set) } # match commands in or out of [] | |
rule(:command) { identifier.as(:command) } | |
rule(:command_set) { lbracket >> command_list >> rbracket } # match commands inside brackets e.g. "(set|delete)" | |
rule(:command_list) { command >> (exclusive >> command).repeat(0) } # matches "set", "set|delete", "set|delete|vaporize" | |
# arguments in usage line | |
rule(:argument_spec) { optional(argument, :argument) } # match arguments in or out of [] | |
rule(:argument) { lt >> identifier >> gt } # match "<foo>" | |
# options in usage line | |
rule(:option_spec) { optional(option_set, :option_set) } # match options in or out of [] | |
rule(:option_set) { option_alternative >> other_alternatives.repeat(0) } # matches "--left", "--left|--right" | |
rule(:other_alternatives) { exclusive >> option_alternative } # matches "|--right" | |
rule(:option_alternative) { option_start >> (option_with_argument | boolean_option) } # matches "--foo", "-f", "--foo=<bar>", "-f <bar>" | |
rule(:boolean_option) { identifier.as(:option) } # matches "foo" | |
rule(:option_with_argument) { identifier.as(:option) >> (spaces | equals) >> argument.as(:argument) } # matches "--foo=<bar>", "-f <bar>" | |
# matches "[thing]" or "thing". if it matches "[thing]", tag it as "optional_name", otherwise | |
# just tag it as "name" | |
def optional thing, name | |
(lsquare >> thing.as(:"optional_#{name}") >> rsquare) | thing.as(name) | |
end | |
# ================ | |
# = Option lines = | |
# ================ | |
# the root level rule for the whole options section | |
rule(:options) { str('Options:') >> newline >> option_lines.as(:option_lines) } | |
rule(:option_lines) { option_line.repeat(1) } # one or more lines | |
rule(:option_line) { indentation >> option_names >> indentation.maybe >> option_description.maybe } # description of a line | |
rule(:option_names) { (option_alternative | whitespace).repeat(1) } # one or more alternative names e.g. "-h --help" | |
rule(:option_description) { (newline.absent? >> any).repeat(1).as(:description) } # match the description to the end of the line | |
end | |
DOC = "Naval Fate. | |
Usage: | |
naval ship new <name> | |
naval ship new <name>... | |
naval ship new [options] | |
\tnaval ship <name> move <x> <y> --fast | |
naval ship <name> [move] <x> <y> | |
naval ship [<name>] [move] <x> <y> | |
naval ship <name> move <x> <y> [--fast] | |
naval ship <name> move <x> <y> --speed=<kn> | |
naval ship <name> move <x> <y> -s <kn> | |
naval ship <name> move <x> <y> [--fast|--slow] | |
naval ship <name> move <x> <y> [--fast | --slow] | |
naval mine (set|remove) <x> <y> [--moored|--drifting] | |
naval --version | |
Options: | |
-h --help | |
-h --help Show this screen. | |
" | |
require 'pp' | |
pp Docopt.new.parse(DOC) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{:title_and_description=>"Naval Fate.\n\n"@0, | |
:usage_examples=> | |
[{:example=> | |
[{:command_set=>{:command=>"ship"@28}}, | |
{:command_set=>{:command=>"new"@33}}, | |
{:argument=>"<name>"@37}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@52}}, | |
{:command_set=>{:command=>"new"@57}}, | |
{:argument=>"<name>"@61}], | |
:dots=>"..."@67}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@79}}, | |
{:command_set=>{:command=>"new"@84}}, | |
{:options=>"[options]"@88}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@105}}, | |
{:argument=>"<name>"@110}, | |
{:command_set=>{:command=>"move"@117}}, | |
{:argument=>"<x>"@122}, | |
{:argument=>"<y>"@126}, | |
{:option_set=>{:option=>"fast"@132}}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@145}}, | |
{:argument=>"<name>"@150}, | |
{:optional_command_set=>{:command=>"move"@158}}, | |
{:argument=>"<x>"@164}, | |
{:argument=>"<y>"@168}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@180}}, | |
{:optional_argument=>"<name>"@186}, | |
{:optional_command_set=>{:command=>"move"@195}}, | |
{:argument=>"<x>"@201}, | |
{:argument=>"<y>"@205}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@217}}, | |
{:argument=>"<name>"@222}, | |
{:command_set=>{:command=>"move"@229}}, | |
{:argument=>"<x>"@234}, | |
{:argument=>"<y>"@238}, | |
{:optional_option_set=>{:option=>"fast"@245}}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@259}}, | |
{:argument=>"<name>"@264}, | |
{:command_set=>{:command=>"move"@271}}, | |
{:argument=>"<x>"@276}, | |
{:argument=>"<y>"@280}, | |
{:option_set=>{:option=>"speed"@286, :argument=>"<kn>"@292}}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@305}}, | |
{:argument=>"<name>"@310}, | |
{:command_set=>{:command=>"move"@317}}, | |
{:argument=>"<x>"@322}, | |
{:argument=>"<y>"@326}, | |
{:option_set=>{:option=>"s"@331, :argument=>"<kn>"@333}}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@346}}, | |
{:argument=>"<name>"@351}, | |
{:command_set=>{:command=>"move"@358}}, | |
{:argument=>"<x>"@363}, | |
{:argument=>"<y>"@367}, | |
{:optional_option_set=>[{:option=>"fast"@374}, {:option=>"slow"@381}]}]}, | |
{:example=> | |
[{:command_set=>{:command=>"ship"@395}}, | |
{:argument=>"<name>"@400}, | |
{:command_set=>{:command=>"move"@407}}, | |
{:argument=>"<x>"@412}, | |
{:argument=>"<y>"@416}, | |
{:optional_option_set=>[{:option=>"fast"@423}, {:option=>"slow"@432}]}]}, | |
{:example=> | |
[{:command_set=>{:command=>"mine"@446}}, | |
{:command_set=>[{:command=>"set"@452}, {:command=>"remove"@456}]}, | |
{:argument=>"<x>"@464}, | |
{:argument=>"<y>"@468}, | |
{:optional_option_set=> | |
[{:option=>"moored"@475}, {:option=>"drifting"@484}]}]}, | |
{:example=>[{:option_set=>{:option=>"version"@504}}]}], | |
:option_lines=> | |
[{:option=>"h"@525}, | |
{:option=>"help"@529}, | |
{:option=>"h"@537}, | |
{:option=>"help"@541}, | |
{:description=>"Show this screen."@550}]} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment