nanoc 2.0 → 2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +54 -0
- data/LICENSE +1 -1
- data/README +63 -3
- data/Rakefile +61 -14
- data/bin/nanoc +7 -199
- data/lib/nanoc/base/asset.rb +113 -0
- data/lib/nanoc/base/asset_defaults.rb +21 -0
- data/lib/nanoc/base/asset_rep.rb +277 -0
- data/lib/nanoc/base/binary_filter.rb +44 -0
- data/lib/nanoc/base/code.rb +41 -0
- data/lib/nanoc/base/compiler.rb +46 -34
- data/lib/nanoc/base/core_ext/hash.rb +51 -7
- data/lib/nanoc/base/core_ext/string.rb +8 -0
- data/lib/nanoc/base/data_source.rb +253 -20
- data/lib/nanoc/base/defaults.rb +30 -0
- data/lib/nanoc/base/enhancements.rb +9 -84
- data/lib/nanoc/base/filter.rb +109 -6
- data/lib/nanoc/base/layout.rb +91 -0
- data/lib/nanoc/base/notification_center.rb +66 -0
- data/lib/nanoc/base/page.rb +94 -121
- data/lib/nanoc/base/page_defaults.rb +20 -0
- data/lib/nanoc/base/page_rep.rb +318 -0
- data/lib/nanoc/base/plugin.rb +57 -9
- data/lib/nanoc/base/proxies/asset_proxy.rb +29 -0
- data/lib/nanoc/base/proxies/asset_rep_proxy.rb +26 -0
- data/lib/nanoc/base/proxies/layout_proxy.rb +25 -0
- data/lib/nanoc/base/proxies/page_proxy.rb +35 -0
- data/lib/nanoc/base/proxies/page_rep_proxy.rb +28 -0
- data/lib/nanoc/base/proxy.rb +37 -0
- data/lib/nanoc/base/router.rb +72 -0
- data/lib/nanoc/base/site.rb +220 -91
- data/lib/nanoc/base/template.rb +64 -0
- data/lib/nanoc/binary_filters/image_science_thumbnail.rb +28 -0
- data/lib/nanoc/cli/base.rb +219 -0
- data/lib/nanoc/cli/cli.rb +16 -0
- data/lib/nanoc/cli/command.rb +105 -0
- data/lib/nanoc/cli/commands/autocompile.rb +80 -0
- data/lib/nanoc/cli/commands/compile.rb +273 -0
- data/lib/nanoc/cli/commands/create_layout.rb +85 -0
- data/lib/nanoc/cli/commands/create_page.rb +85 -0
- data/lib/nanoc/cli/commands/create_site.rb +327 -0
- data/lib/nanoc/cli/commands/create_template.rb +76 -0
- data/lib/nanoc/cli/commands/help.rb +69 -0
- data/lib/nanoc/cli/commands/info.rb +114 -0
- data/lib/nanoc/cli/commands/switch.rb +141 -0
- data/lib/nanoc/cli/commands/update.rb +91 -0
- data/lib/nanoc/cli/ext.rb +37 -0
- data/lib/nanoc/cli/logger.rb +66 -0
- data/lib/nanoc/cli/option_parser.rb +168 -0
- data/lib/nanoc/cli.rb +1 -0
- data/lib/nanoc/data_sources/filesystem.rb +650 -215
- data/lib/nanoc/data_sources/filesystem_combined.rb +495 -0
- data/lib/nanoc/extra/auto_compiler.rb +265 -0
- data/lib/nanoc/extra/context.rb +22 -0
- data/lib/nanoc/extra/core_ext/hash.rb +54 -0
- data/lib/nanoc/extra/core_ext/time.rb +13 -0
- data/lib/nanoc/extra/file_proxy.rb +29 -0
- data/lib/nanoc/extra/vcs.rb +48 -0
- data/lib/nanoc/extra/vcses/bazaar.rb +21 -0
- data/lib/nanoc/extra/vcses/dummy.rb +20 -0
- data/lib/nanoc/extra/vcses/git.rb +21 -0
- data/lib/nanoc/extra/vcses/mercurial.rb +21 -0
- data/lib/nanoc/extra/vcses/subversion.rb +21 -0
- data/lib/nanoc/filters/bluecloth.rb +13 -0
- data/lib/nanoc/filters/erb.rb +6 -22
- data/lib/nanoc/filters/erubis.rb +14 -0
- data/lib/nanoc/filters/haml.rb +11 -6
- data/lib/nanoc/filters/markaby.rb +5 -5
- data/lib/nanoc/filters/maruku.rb +14 -0
- data/lib/nanoc/filters/old.rb +19 -0
- data/lib/nanoc/filters/rdiscount.rb +13 -0
- data/lib/nanoc/filters/rdoc.rb +5 -4
- data/lib/nanoc/filters/redcloth.rb +14 -0
- data/lib/nanoc/filters/rubypants.rb +14 -0
- data/lib/nanoc/filters/sass.rb +13 -0
- data/lib/nanoc/helpers/blogging.rb +170 -0
- data/lib/nanoc/helpers/capturing.rb +59 -0
- data/lib/nanoc/helpers/html_escape.rb +23 -0
- data/lib/nanoc/helpers/link_to.rb +69 -0
- data/lib/nanoc/helpers/render.rb +47 -0
- data/lib/nanoc/helpers/tagging.rb +52 -0
- data/lib/nanoc/helpers/xml_sitemap.rb +58 -0
- data/lib/nanoc/routers/default.rb +54 -0
- data/lib/nanoc/routers/no_dirs.rb +66 -0
- data/lib/nanoc/routers/versioned.rb +79 -0
- data/lib/nanoc.rb +83 -12
- metadata +144 -47
- data/lib/nanoc/base/auto_compiler.rb +0 -124
- data/lib/nanoc/base/layout_processor.rb +0 -33
- data/lib/nanoc/base/page_proxy.rb +0 -31
- data/lib/nanoc/base/plugin_manager.rb +0 -33
- data/lib/nanoc/data_sources/database.rb +0 -259
- data/lib/nanoc/data_sources/trivial.rb +0 -145
- data/lib/nanoc/filters/markdown.rb +0 -13
- data/lib/nanoc/filters/smartypants.rb +0 -13
- data/lib/nanoc/filters/textile.rb +0 -13
- data/lib/nanoc/layout_processors/erb.rb +0 -35
- data/lib/nanoc/layout_processors/haml.rb +0 -18
- data/lib/nanoc/layout_processors/markaby.rb +0 -16
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
module Nanoc::CLI
|
|
2
|
+
|
|
3
|
+
class SwitchCommand < Command # :nodoc:
|
|
4
|
+
|
|
5
|
+
def name
|
|
6
|
+
'switch'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def aliases
|
|
10
|
+
[]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def short_desc
|
|
14
|
+
'switch the site to a new data source'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def long_desc
|
|
18
|
+
'Move the data stored in the site to a new, given data source.' +
|
|
19
|
+
"\n" +
|
|
20
|
+
'The given new data source may need additional configuration ' +
|
|
21
|
+
'parameters that cannot be specified on the commandline. These ' +
|
|
22
|
+
'should be stored in the configuration file (config.yaml) BEFORE ' +
|
|
23
|
+
'executing the switch command.' +
|
|
24
|
+
"\n" +
|
|
25
|
+
'This command first loads all existing data into memory, destroys ' +
|
|
26
|
+
'the on-disk data, changes the site\'s data source, and finally ' +
|
|
27
|
+
'writes the data back to the disk using the new data source. Because ' +
|
|
28
|
+
'of this action\'s destructive nature, THIS OPERATION SHOULD NOT BE ' +
|
|
29
|
+
'INTERRUPTED as interruption could result in data loss.' +
|
|
30
|
+
"\n" +
|
|
31
|
+
'This command will change data, and it is therefore recommended to ' +
|
|
32
|
+
'make a backup in case something goes wrong.'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def usage
|
|
36
|
+
"nanoc switch [options]"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def option_definitions
|
|
40
|
+
[
|
|
41
|
+
# --vcs
|
|
42
|
+
{
|
|
43
|
+
:long => 'vcs', :short => 'c', :argument => :required,
|
|
44
|
+
:desc => 'select the VCS to use'
|
|
45
|
+
},
|
|
46
|
+
# --yes
|
|
47
|
+
{
|
|
48
|
+
:long => 'yes', :short => 'y', :argument => :forbidden,
|
|
49
|
+
:desc => 'switches the data source without warning'
|
|
50
|
+
},
|
|
51
|
+
# --datasource
|
|
52
|
+
{
|
|
53
|
+
:long => 'datasource', :short => 'd', :argument => :required,
|
|
54
|
+
:desc => 'specify the new data source for the site'
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def run(options, arguments)
|
|
60
|
+
# Check arguments
|
|
61
|
+
if arguments.size != 0
|
|
62
|
+
$stderr.puts "usage: #{usage}"
|
|
63
|
+
exit 1
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Check options
|
|
67
|
+
unless options.has_key?(:datasource)
|
|
68
|
+
$stderr.puts 'A new data source should be specified using the ' +
|
|
69
|
+
'-d/--datasource option.'
|
|
70
|
+
exit 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Find data source
|
|
74
|
+
data_source = Nanoc::DataSource.named(options[:datasource])
|
|
75
|
+
if data_source.nil?
|
|
76
|
+
$stderr.puts "Unrecognised data source: #{options[:datasource]}"
|
|
77
|
+
exit 1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Make sure we are in a nanoc site directory
|
|
81
|
+
@base.require_site
|
|
82
|
+
|
|
83
|
+
# Set VCS if possible
|
|
84
|
+
@base.set_vcs(options[:vcs])
|
|
85
|
+
|
|
86
|
+
# Check for -y switch
|
|
87
|
+
unless options.has_key?(:yes)
|
|
88
|
+
$stderr.puts '*************'
|
|
89
|
+
$stderr.puts '** WARNING **'
|
|
90
|
+
$stderr.puts '*************'
|
|
91
|
+
$stderr.puts
|
|
92
|
+
$stderr.puts 'Are you absolutely sure you want to set up the data ' +
|
|
93
|
+
'source for this site? Setting up the data source ' +
|
|
94
|
+
'will remove existing data. This operation is ' +
|
|
95
|
+
'destructive and cannot be reverted. Please do not ' +
|
|
96
|
+
'interrupt this operation; doing so can result in ' +
|
|
97
|
+
'data loss. As always, consider making a backup copy.'
|
|
98
|
+
$stderr.puts
|
|
99
|
+
$stderr.puts 'To continue, use the -y/--yes option, like "nanoc ' +
|
|
100
|
+
'switch -y".'
|
|
101
|
+
exit 1
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Setup notifications
|
|
105
|
+
Nanoc::NotificationCenter.on(:file_created) do |file_path|
|
|
106
|
+
Nanoc::CLI::Logger.instance.file(:high, :create, file_path)
|
|
107
|
+
end
|
|
108
|
+
Nanoc::NotificationCenter.on(:file_updated) do |file_path|
|
|
109
|
+
Nanoc::CLI::Logger.instance.file(:high, :update, file_path)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Load data
|
|
113
|
+
@base.site.load_data
|
|
114
|
+
|
|
115
|
+
# Destroy existing data
|
|
116
|
+
@base.site.data_source.destroy
|
|
117
|
+
|
|
118
|
+
# Update configuration
|
|
119
|
+
@base.site.config[:data_source] = options[:datasource]
|
|
120
|
+
@base.site.instance_eval { @data_source = data_source.new(self) }
|
|
121
|
+
File.open('config.yaml', 'w') { |io| io.write(YAML.dump(@base.site.config.stringify_keys)) }
|
|
122
|
+
|
|
123
|
+
# Set VCS on new data source if possible
|
|
124
|
+
@base.set_vcs(options[:vcs])
|
|
125
|
+
|
|
126
|
+
@base.site.data_source.loading do
|
|
127
|
+
# Create initial data source
|
|
128
|
+
@base.site.data_source.setup
|
|
129
|
+
|
|
130
|
+
# Store all data
|
|
131
|
+
@base.site.pages.each { |p| @base.site.data_source.save_page(p) }
|
|
132
|
+
@base.site.data_source.save_page_defaults(@base.site.page_defaults)
|
|
133
|
+
@base.site.layouts.each { |l| @base.site.data_source.save_layout(l) }
|
|
134
|
+
@base.site.templates.each { |t| @base.site.data_source.save_template(t) }
|
|
135
|
+
@base.site.data_source.save_code(@base.site.code)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module Nanoc::CLI
|
|
2
|
+
|
|
3
|
+
class UpdateCommand < Command # :nodoc:
|
|
4
|
+
|
|
5
|
+
def name
|
|
6
|
+
'update'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def aliases
|
|
10
|
+
[]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def short_desc
|
|
14
|
+
'update the data stored by the data source to a newer version'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def long_desc
|
|
18
|
+
'Update the data stored by the data source to a newer format. The ' +
|
|
19
|
+
'format in which data is stored can change between releases, and ' +
|
|
20
|
+
'even though backward compatibility is usually preserved, it is ' +
|
|
21
|
+
'often a good idea to store the site data in a newer format so newer ' +
|
|
22
|
+
'features can be taken advantage of.' +
|
|
23
|
+
"\n" +
|
|
24
|
+
'This command will change data, and it is therefore recommended to ' +
|
|
25
|
+
'make a backup in case something goes wrong.'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def usage
|
|
29
|
+
"nanoc update [options]"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def option_definitions
|
|
33
|
+
[
|
|
34
|
+
# --vcs
|
|
35
|
+
{
|
|
36
|
+
:long => 'vcs', :short => 'c', :argument => :required,
|
|
37
|
+
:desc => 'select the VCS to use'
|
|
38
|
+
},
|
|
39
|
+
# --yes
|
|
40
|
+
{
|
|
41
|
+
:long => 'yes', :short => 'y', :argument => :forbidden,
|
|
42
|
+
:desc => 'updates the data without warning'
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def run(options, arguments)
|
|
48
|
+
# Check arguments
|
|
49
|
+
if arguments.size != 0
|
|
50
|
+
$stderr.puts "usage: #{usage}"
|
|
51
|
+
exit 1
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Make sure we are in a nanoc site directory
|
|
55
|
+
@base.require_site
|
|
56
|
+
|
|
57
|
+
# Set VCS if possible
|
|
58
|
+
@base.set_vcs(options[:vcs])
|
|
59
|
+
|
|
60
|
+
# Check for -y switch
|
|
61
|
+
unless options.has_key?(:yes)
|
|
62
|
+
$stderr.puts '*************'
|
|
63
|
+
$stderr.puts '** WARNING **'
|
|
64
|
+
$stderr.puts '*************'
|
|
65
|
+
$stderr.puts
|
|
66
|
+
$stderr.puts 'Are you absolutely sure you want to update the ' +
|
|
67
|
+
'content for this site? Updating the site content ' +
|
|
68
|
+
'will change the structure of existing data. This ' +
|
|
69
|
+
'operation is destructive and cannot be reverted. ' +
|
|
70
|
+
'Please do not interrupt this operation; doing so can ' +
|
|
71
|
+
'result in data loss. As always, consider making a ' +
|
|
72
|
+
'backup copy.'
|
|
73
|
+
$stderr.puts
|
|
74
|
+
$stderr.puts 'If this nanoc site is versioned using a VCS ' +
|
|
75
|
+
'supported by nanoc, consider using the --vcs option ' +
|
|
76
|
+
'to have nanoc perform add/delete/move operations ' +
|
|
77
|
+
'using the specified VCS. To get a list of VCSes ' +
|
|
78
|
+
'supported by nanoc, issue the "info" command.'
|
|
79
|
+
$stderr.puts
|
|
80
|
+
$stderr.puts 'To continue, use the -y/--yes option, like "nanoc ' +
|
|
81
|
+
'update -y".'
|
|
82
|
+
exit 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Update
|
|
86
|
+
@base.site.data_source.update
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
class String
|
|
2
|
+
|
|
3
|
+
# Word-wraps and indents the string.
|
|
4
|
+
#
|
|
5
|
+
# +width+:: The maximal width of each line. This also includes indentation,
|
|
6
|
+
# i.e. the actual maximal width of the text is width-indentation.
|
|
7
|
+
#
|
|
8
|
+
# +indentation+:: The number of spaces to indent each wrapped line.
|
|
9
|
+
def wrap_and_indent(width, indentation)
|
|
10
|
+
# Split into paragraphs
|
|
11
|
+
paragraphs = self.split("\n").map { |p| p.strip }.reject { |p| p == '' }
|
|
12
|
+
|
|
13
|
+
# Wrap and indent each paragraph
|
|
14
|
+
paragraphs.map do |paragraph|
|
|
15
|
+
# Initialize
|
|
16
|
+
lines = []
|
|
17
|
+
line = ''
|
|
18
|
+
|
|
19
|
+
# Split into words
|
|
20
|
+
paragraph.split(/\s/).each do |word|
|
|
21
|
+
# Begin new line if it's too long
|
|
22
|
+
if (line + ' ' + word).length >= width
|
|
23
|
+
lines << line
|
|
24
|
+
line = ''
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Add word to line
|
|
28
|
+
line += (line == '' ? '' : ' ' ) + word
|
|
29
|
+
end
|
|
30
|
+
lines << line
|
|
31
|
+
|
|
32
|
+
# Join lines
|
|
33
|
+
lines.map { |l| ' '*indentation + l }.join("\n")
|
|
34
|
+
end.join("\n\n")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
|
|
3
|
+
module Nanoc::CLI
|
|
4
|
+
|
|
5
|
+
# Nanoc::CLI::Logger is a singleton class responsible for generating
|
|
6
|
+
# feedback in the terminal.
|
|
7
|
+
class Logger
|
|
8
|
+
|
|
9
|
+
ACTION_COLORS = {
|
|
10
|
+
:create => "\e[1m" + "\e[32m", # bold + green
|
|
11
|
+
:update => "\e[1m" + "\e[33m", # bold + yellow
|
|
12
|
+
:identical => "\e[1m", # bold
|
|
13
|
+
:skip => "\e[1m" # bold
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
include Singleton
|
|
17
|
+
|
|
18
|
+
# The log leve, which can be :high, :low or :off (which will log all
|
|
19
|
+
# messages, only high-priority messages, or no messages at all,
|
|
20
|
+
# respectively).
|
|
21
|
+
attr_accessor :level
|
|
22
|
+
|
|
23
|
+
def initialize # :nodoc:
|
|
24
|
+
@level = :high
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Logs a file-related action.
|
|
28
|
+
#
|
|
29
|
+
# +level+:: The importance of this action. Can be :high or :low.
|
|
30
|
+
#
|
|
31
|
+
# +action+:: The kind of file action. Can be :create, :update or
|
|
32
|
+
# :identical.
|
|
33
|
+
#
|
|
34
|
+
# +path+:: The path to the file the action was performed on.
|
|
35
|
+
def file(level, action, path, duration=nil)
|
|
36
|
+
log(
|
|
37
|
+
level,
|
|
38
|
+
'%s%12s%s %s%s' % [
|
|
39
|
+
ACTION_COLORS[action.to_sym],
|
|
40
|
+
action,
|
|
41
|
+
"\e[0m",
|
|
42
|
+
duration.nil? ? '' : "[%2.2fs] " % [ duration ],
|
|
43
|
+
path
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Logs a message.
|
|
49
|
+
#
|
|
50
|
+
# +level+:: The importance of this message. Can be :high or :low.
|
|
51
|
+
#
|
|
52
|
+
# +s+:: The message to be logged.
|
|
53
|
+
#
|
|
54
|
+
# +io+:: The IO instance to which the message will be written. Defaults to
|
|
55
|
+
# standard output.
|
|
56
|
+
def log(level, s, io=$stdout)
|
|
57
|
+
# Don't log when logging is disabled
|
|
58
|
+
return if @level == :off
|
|
59
|
+
|
|
60
|
+
# Log when level permits it
|
|
61
|
+
io.puts(s) if (@level == :low or @level == level)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
module Nanoc::CLI
|
|
2
|
+
|
|
3
|
+
# Nanoc::CLI::OptionParser is used for parsing commandline options.
|
|
4
|
+
class OptionParser
|
|
5
|
+
|
|
6
|
+
# Error that will be raised when an unknown option is encountered.
|
|
7
|
+
class IllegalOptionError < RuntimeError ; end
|
|
8
|
+
|
|
9
|
+
# Error that will be raised when an option without argument is
|
|
10
|
+
# encountered.
|
|
11
|
+
class OptionRequiresAnArgumentError < RuntimeError ; end
|
|
12
|
+
|
|
13
|
+
# Parses the commandline arguments in +arguments_and_options+, using the
|
|
14
|
+
# commandline option definitions in +definitions+.
|
|
15
|
+
#
|
|
16
|
+
# +arguments_and_options+ is an array of commandline arguments and
|
|
17
|
+
# options. This will usually be +ARGV+.
|
|
18
|
+
#
|
|
19
|
+
# +definitions+ contains a list of hashes defining which options are
|
|
20
|
+
# allowed and how they will be handled. Such a hash has three keys:
|
|
21
|
+
#
|
|
22
|
+
# :short:: The short name of the option, e.g. +a+. Do not include the '-'
|
|
23
|
+
# prefix.
|
|
24
|
+
#
|
|
25
|
+
# :long:: The long name of the option, e.g. +all+. Do not include the '--'
|
|
26
|
+
# prefix.
|
|
27
|
+
#
|
|
28
|
+
# :argument:: Whether this option's argument is required (:required) or
|
|
29
|
+
# forbidden (:forbidden).
|
|
30
|
+
#
|
|
31
|
+
# A sample array of definition hashes could look like this:
|
|
32
|
+
#
|
|
33
|
+
# [
|
|
34
|
+
# { :short => 'a', :long => 'all', :argument => :forbidden },
|
|
35
|
+
# { :short => 'p', :long => 'port', :argument => :required },
|
|
36
|
+
# ]
|
|
37
|
+
#
|
|
38
|
+
# During parsing, two errors can be raised:
|
|
39
|
+
#
|
|
40
|
+
# IllegalOptionError:: An unrecognised option was encountered, i.e. an
|
|
41
|
+
# option that is not present in the list of option
|
|
42
|
+
# definitions.
|
|
43
|
+
#
|
|
44
|
+
# OptionRequiresAnArgumentError:: An option was found that did not have a
|
|
45
|
+
# value, even though this value was
|
|
46
|
+
# required.
|
|
47
|
+
#
|
|
48
|
+
# What will be returned, is a hash with two keys, :arguments and :options.
|
|
49
|
+
# The :arguments value contains a list of arguments, and the :options
|
|
50
|
+
# value contains a hash with key-value pairs for each option. Options
|
|
51
|
+
# without values will have a +nil+ value instead.
|
|
52
|
+
#
|
|
53
|
+
# For example, the following commandline options (which should not be
|
|
54
|
+
# passed as a string, but as an array of strings):
|
|
55
|
+
#
|
|
56
|
+
# foo bar -xyz -a hiss --level 50 --father=ani -n luke squeak
|
|
57
|
+
#
|
|
58
|
+
# with the following option definitions:
|
|
59
|
+
#
|
|
60
|
+
# [
|
|
61
|
+
# { :short => 'x', :long => 'xxx', :argument => :forbidden },
|
|
62
|
+
# { :short => 'y', :long => 'yyy', :argument => :forbidden },
|
|
63
|
+
# { :short => 'z', :long => 'zzz', :argument => :forbidden },
|
|
64
|
+
# { :short => 'a', :long => 'all', :argument => :forbidden },
|
|
65
|
+
# { :short => 'l', :long => 'level', :argument => :required },
|
|
66
|
+
# { :short => 'f', :long => 'father', :argument => :required },
|
|
67
|
+
# { :short => 'n', :long => 'name', :argument => :required }
|
|
68
|
+
# ]
|
|
69
|
+
#
|
|
70
|
+
# will be translated into:
|
|
71
|
+
#
|
|
72
|
+
# {
|
|
73
|
+
# :arguments => [ 'foo', 'bar', 'hiss', 'squeak' ],
|
|
74
|
+
# :options => {
|
|
75
|
+
# :xxx => nil,
|
|
76
|
+
# :yyy => nil,
|
|
77
|
+
# :zzz => nil,
|
|
78
|
+
# :all => nil,
|
|
79
|
+
# :level => '50',
|
|
80
|
+
# :father => 'ani',
|
|
81
|
+
# :name => 'luke'
|
|
82
|
+
# }
|
|
83
|
+
# }
|
|
84
|
+
def self.parse(arguments_and_options, definitions)
|
|
85
|
+
# Don't touch original argument
|
|
86
|
+
unprocessed_arguments_and_options = arguments_and_options.dup
|
|
87
|
+
|
|
88
|
+
# Initialize
|
|
89
|
+
arguments = []
|
|
90
|
+
options = {}
|
|
91
|
+
|
|
92
|
+
# Determines whether we've passed the '--' marker or not
|
|
93
|
+
no_more_options = false
|
|
94
|
+
|
|
95
|
+
loop do
|
|
96
|
+
# Get next item
|
|
97
|
+
e = unprocessed_arguments_and_options.shift
|
|
98
|
+
break if e.nil?
|
|
99
|
+
|
|
100
|
+
# Handle end-of-options marker
|
|
101
|
+
if e == '--'
|
|
102
|
+
no_more_options = true
|
|
103
|
+
# Handle incomplete options
|
|
104
|
+
elsif e =~ /^--./ and !no_more_options
|
|
105
|
+
# Get option key, and option value if included
|
|
106
|
+
if e =~ /^--([^=]+)=(.+)$/
|
|
107
|
+
option_key = $1
|
|
108
|
+
option_value = $2
|
|
109
|
+
else
|
|
110
|
+
option_key = e[2..-1]
|
|
111
|
+
option_value = nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Find definition
|
|
115
|
+
definition = definitions.find { |d| d[:long] == option_key }
|
|
116
|
+
raise IllegalOptionError.new(option_key) if definition.nil?
|
|
117
|
+
|
|
118
|
+
if definition[:argument] == :required
|
|
119
|
+
# Get option value if necessary
|
|
120
|
+
if option_value.nil?
|
|
121
|
+
option_value = unprocessed_arguments_and_options.shift
|
|
122
|
+
raise OptionRequiresAnArgumentError.new(option_key) if option_value.nil?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Store option
|
|
126
|
+
options[definition[:long].to_sym] = option_value
|
|
127
|
+
else
|
|
128
|
+
# Store option
|
|
129
|
+
options[definition[:long].to_sym] = nil
|
|
130
|
+
end
|
|
131
|
+
# Handle -xyz options
|
|
132
|
+
elsif e =~ /^-./ and !no_more_options
|
|
133
|
+
# Get option keys
|
|
134
|
+
option_keys = e[1..-1].scan(/./)
|
|
135
|
+
|
|
136
|
+
# For each key
|
|
137
|
+
option_keys.each do |option_key|
|
|
138
|
+
# Find definition
|
|
139
|
+
definition = definitions.find { |d| d[:short] == option_key }
|
|
140
|
+
raise IllegalOptionError.new(option_key) if definition.nil?
|
|
141
|
+
|
|
142
|
+
if option_keys.length > 1 and definition[:argument] == :required
|
|
143
|
+
# This is a combined option and it requires an argument, so complain
|
|
144
|
+
raise OptionRequiresAnArgumentError.new(option_key) if option_value.nil?
|
|
145
|
+
elsif definition[:argument] == :required
|
|
146
|
+
# Get option value
|
|
147
|
+
option_value = unprocessed_arguments_and_options.shift
|
|
148
|
+
raise OptionRequiresAnArgumentError.new(option_key) if option_value.nil?
|
|
149
|
+
|
|
150
|
+
# Store option
|
|
151
|
+
options[definition[:long].to_sym] = option_value
|
|
152
|
+
else
|
|
153
|
+
# Store option
|
|
154
|
+
options[definition[:long].to_sym] = nil
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
# Handle normal arguments
|
|
158
|
+
else
|
|
159
|
+
arguments << e
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
{ :options => options, :arguments => arguments }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
end
|
data/lib/nanoc/cli.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Nanoc.load('cli', '*.rb')
|