nanoc3 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/ChangeLog +3 -0
  2. data/LICENSE +19 -0
  3. data/NEWS.rdoc +262 -0
  4. data/README.rdoc +80 -0
  5. data/Rakefile +11 -0
  6. data/bin/nanoc3 +16 -0
  7. data/lib/nanoc3/base/code_snippet.rb +42 -0
  8. data/lib/nanoc3/base/compiler.rb +225 -0
  9. data/lib/nanoc3/base/compiler_dsl.rb +110 -0
  10. data/lib/nanoc3/base/core_ext/array.rb +21 -0
  11. data/lib/nanoc3/base/core_ext/hash.rb +23 -0
  12. data/lib/nanoc3/base/core_ext/string.rb +14 -0
  13. data/lib/nanoc3/base/core_ext.rb +5 -0
  14. data/lib/nanoc3/base/data_source.rb +197 -0
  15. data/lib/nanoc3/base/dependency_tracker.rb +291 -0
  16. data/lib/nanoc3/base/errors.rb +95 -0
  17. data/lib/nanoc3/base/filter.rb +60 -0
  18. data/lib/nanoc3/base/item.rb +87 -0
  19. data/lib/nanoc3/base/item_rep.rb +236 -0
  20. data/lib/nanoc3/base/layout.rb +53 -0
  21. data/lib/nanoc3/base/notification_center.rb +68 -0
  22. data/lib/nanoc3/base/plugin.rb +88 -0
  23. data/lib/nanoc3/base/preprocessor_context.rb +37 -0
  24. data/lib/nanoc3/base/rule.rb +37 -0
  25. data/lib/nanoc3/base/rule_context.rb +68 -0
  26. data/lib/nanoc3/base/site.rb +334 -0
  27. data/lib/nanoc3/base.rb +25 -0
  28. data/lib/nanoc3/cli/base.rb +151 -0
  29. data/lib/nanoc3/cli/commands/autocompile.rb +89 -0
  30. data/lib/nanoc3/cli/commands/compile.rb +279 -0
  31. data/lib/nanoc3/cli/commands/create_item.rb +79 -0
  32. data/lib/nanoc3/cli/commands/create_layout.rb +94 -0
  33. data/lib/nanoc3/cli/commands/create_site.rb +320 -0
  34. data/lib/nanoc3/cli/commands/help.rb +71 -0
  35. data/lib/nanoc3/cli/commands/info.rb +114 -0
  36. data/lib/nanoc3/cli/commands/update.rb +96 -0
  37. data/lib/nanoc3/cli/commands.rb +13 -0
  38. data/lib/nanoc3/cli/logger.rb +73 -0
  39. data/lib/nanoc3/cli.rb +16 -0
  40. data/lib/nanoc3/data_sources/delicious.rb +66 -0
  41. data/lib/nanoc3/data_sources/filesystem.rb +231 -0
  42. data/lib/nanoc3/data_sources/filesystem_combined.rb +202 -0
  43. data/lib/nanoc3/data_sources/filesystem_common.rb +22 -0
  44. data/lib/nanoc3/data_sources/filesystem_compact.rb +232 -0
  45. data/lib/nanoc3/data_sources/last_fm.rb +103 -0
  46. data/lib/nanoc3/data_sources/twitter.rb +53 -0
  47. data/lib/nanoc3/data_sources.rb +20 -0
  48. data/lib/nanoc3/extra/auto_compiler.rb +97 -0
  49. data/lib/nanoc3/extra/chick.rb +119 -0
  50. data/lib/nanoc3/extra/context.rb +24 -0
  51. data/lib/nanoc3/extra/core_ext/time.rb +19 -0
  52. data/lib/nanoc3/extra/core_ext.rb +3 -0
  53. data/lib/nanoc3/extra/deployers/rsync.rb +64 -0
  54. data/lib/nanoc3/extra/deployers.rb +12 -0
  55. data/lib/nanoc3/extra/file_proxy.rb +31 -0
  56. data/lib/nanoc3/extra/validators/links.rb +0 -0
  57. data/lib/nanoc3/extra/validators/w3c.rb +71 -0
  58. data/lib/nanoc3/extra/validators.rb +12 -0
  59. data/lib/nanoc3/extra/vcs.rb +65 -0
  60. data/lib/nanoc3/extra/vcses/bazaar.rb +21 -0
  61. data/lib/nanoc3/extra/vcses/dummy.rb +20 -0
  62. data/lib/nanoc3/extra/vcses/git.rb +21 -0
  63. data/lib/nanoc3/extra/vcses/mercurial.rb +21 -0
  64. data/lib/nanoc3/extra/vcses/subversion.rb +21 -0
  65. data/lib/nanoc3/extra/vcses.rb +17 -0
  66. data/lib/nanoc3/extra.rb +16 -0
  67. data/lib/nanoc3/filters/bluecloth.rb +13 -0
  68. data/lib/nanoc3/filters/coderay.rb +17 -0
  69. data/lib/nanoc3/filters/erb.rb +19 -0
  70. data/lib/nanoc3/filters/erubis.rb +17 -0
  71. data/lib/nanoc3/filters/haml.rb +20 -0
  72. data/lib/nanoc3/filters/less.rb +13 -0
  73. data/lib/nanoc3/filters/markaby.rb +14 -0
  74. data/lib/nanoc3/filters/maruku.rb +14 -0
  75. data/lib/nanoc3/filters/rainpress.rb +13 -0
  76. data/lib/nanoc3/filters/rdiscount.rb +13 -0
  77. data/lib/nanoc3/filters/rdoc.rb +23 -0
  78. data/lib/nanoc3/filters/redcloth.rb +14 -0
  79. data/lib/nanoc3/filters/relativize_paths.rb +32 -0
  80. data/lib/nanoc3/filters/rubypants.rb +14 -0
  81. data/lib/nanoc3/filters/sass.rb +17 -0
  82. data/lib/nanoc3/filters.rb +37 -0
  83. data/lib/nanoc3/helpers/blogging.rb +226 -0
  84. data/lib/nanoc3/helpers/breadcrumbs.rb +25 -0
  85. data/lib/nanoc3/helpers/capturing.rb +71 -0
  86. data/lib/nanoc3/helpers/filtering.rb +46 -0
  87. data/lib/nanoc3/helpers/html_escape.rb +22 -0
  88. data/lib/nanoc3/helpers/link_to.rb +120 -0
  89. data/lib/nanoc3/helpers/rendering.rb +76 -0
  90. data/lib/nanoc3/helpers/tagging.rb +58 -0
  91. data/lib/nanoc3/helpers/text.rb +40 -0
  92. data/lib/nanoc3/helpers/xml_sitemap.rb +69 -0
  93. data/lib/nanoc3/helpers.rb +16 -0
  94. data/lib/nanoc3/package.rb +106 -0
  95. data/lib/nanoc3/tasks/clean.rake +16 -0
  96. data/lib/nanoc3/tasks/clean.rb +33 -0
  97. data/lib/nanoc3/tasks/deploy/rsync.rake +11 -0
  98. data/lib/nanoc3/tasks/validate.rake +35 -0
  99. data/lib/nanoc3/tasks.rb +9 -0
  100. data/lib/nanoc3.rb +19 -0
  101. data/vendor/cri/ChangeLog +0 -0
  102. data/vendor/cri/LICENSE +19 -0
  103. data/vendor/cri/NEWS +0 -0
  104. data/vendor/cri/README +4 -0
  105. data/vendor/cri/Rakefile +25 -0
  106. data/vendor/cri/lib/cri/base.rb +153 -0
  107. data/vendor/cri/lib/cri/command.rb +105 -0
  108. data/vendor/cri/lib/cri/core_ext/string.rb +41 -0
  109. data/vendor/cri/lib/cri/core_ext.rb +8 -0
  110. data/vendor/cri/lib/cri/option_parser.rb +186 -0
  111. data/vendor/cri/lib/cri.rb +12 -0
  112. data/vendor/cri/test/test_base.rb +6 -0
  113. data/vendor/cri/test/test_command.rb +6 -0
  114. data/vendor/cri/test/test_core_ext.rb +21 -0
  115. data/vendor/cri/test/test_option_parser.rb +279 -0
  116. metadata +225 -0
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc3::Helpers
4
+
5
+ # Nanoc3::Helpers::Text contains several useful text-related helper functions.
6
+ module Text
7
+
8
+ # Returns an excerpt for the given string. HTML tags are ignored, so if
9
+ # you don't want them to turn up, they should be stripped from the string
10
+ # before passing it to the excerpt function.
11
+ #
12
+ # +params+ is a hash where the following keys can be set:
13
+ #
14
+ # +length+:: The maximum number of characters this excerpt can contain,
15
+ # including the omission. Defaults to 25.
16
+ #
17
+ # +omission+:: The string to append to the excerpt when the excerpt is
18
+ # shorter than the original string. Defaults to '...' (but in
19
+ # HTML, you may want to use something more fancy, like
20
+ # '…').
21
+ def excerptize(string, params={})
22
+ # Initialize params
23
+ params[:length] ||= 25
24
+ params[:omission] ||= '...'
25
+
26
+ # Get excerpt
27
+ length = params[:length] - params[:omission].length
28
+ length = 0 if length < 0
29
+ (string.length > params[:length] ? string[0...length] + params[:omission] : string)
30
+ end
31
+
32
+ # Strips all HTML tags out of the given string.
33
+ def strip_html(string)
34
+ # FIXME will need something more sophisticated than this, because it sucks
35
+ string.gsub(/<[^>]*(>+|\s*\z)/m, '').strip
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc3::Helpers
4
+
5
+ # Nanoc3::Helpers::XMLSitemap contains functionality for building XML
6
+ # sitemaps that will be crawled by search engines. See the Sitemaps protocol
7
+ # web site, http://www.sitemaps.org, for details.
8
+ #
9
+ # To activate this helper, +include+ it, like this:
10
+ #
11
+ # include Nanoc3::Helpers::XMLSitemap
12
+ module XMLSitemap
13
+
14
+ # Returns the XML sitemap as a string.
15
+ #
16
+ # The following attributes can optionally be set on items to change the
17
+ # behaviour of the sitemap:
18
+ #
19
+ # * 'changefreq', containing the estimated change frequency as defined by
20
+ # the Sitemaps protocol.
21
+ #
22
+ # * 'priority', containing the item's priority, ranging from 0.0 to 1.0,
23
+ # as defined by the Sitemaps protocol.
24
+ #
25
+ # The sitemap will also include dates on which the items were updated.
26
+ # These are generated automatically; the way this happens depends on the
27
+ # used data source (the filesystem data source checks the file mtimes, for
28
+ # instance).
29
+ #
30
+ # The site configuration will need to have the following attributes:
31
+ #
32
+ # * 'base_url', containing the URL to the site, without trailing slash.
33
+ # For example, if the site is at "http://example.com/", the base_url
34
+ # would be "http://example.com".
35
+ def xml_sitemap
36
+ require 'builder'
37
+
38
+ # Create builder
39
+ buffer = ''
40
+ xml = Builder::XmlMarkup.new(:target => buffer, :indent => 2)
41
+
42
+ # Check for required attributes
43
+ if @site.config[:base_url].nil?
44
+ raise RuntimeError.new("The Nanoc3::Helpers::XMLSitemap helper requires the site configuration to specify the base URL for the site.")
45
+ end
46
+
47
+ # Build sitemap
48
+ xml.instruct!
49
+ xml.urlset(:xmlns => 'http://www.google.com/schemas/sitemap/0.84') do
50
+ # Add item
51
+ @items.reject { |i| i[:is_hidden] }.each do |item|
52
+ item.reps.reject { |r| r.raw_path.nil? }.each do |rep|
53
+ xml.url do
54
+ xml.loc @site.config[:base_url] + rep.path
55
+ xml.lastmod item.mtime.to_iso8601_date unless item.mtime.nil?
56
+ xml.changefreq item[:changefreq] unless item[:changefreq].nil?
57
+ xml.priority item[:priority] unless item[:priority].nil?
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ # Return sitemap
64
+ buffer
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc3::Helpers
4
+
5
+ autoload 'Blogging', 'nanoc3/helpers/blogging'
6
+ autoload 'Breadcrumbs', 'nanoc3/helpers/breadcrumbs'
7
+ autoload 'Capturing', 'nanoc3/helpers/capturing'
8
+ autoload 'Filtering', 'nanoc3/helpers/filtering'
9
+ autoload 'HTMLEscape', 'nanoc3/helpers/html_escape'
10
+ autoload 'LinkTo', 'nanoc3/helpers/link_to'
11
+ autoload 'Rendering', 'nanoc3/helpers/rendering'
12
+ autoload 'Tagging', 'nanoc3/helpers/tagging'
13
+ autoload 'Text', 'nanoc3/helpers/text'
14
+ autoload 'XMLSitemap', 'nanoc3/helpers/xml_sitemap'
15
+
16
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ require 'singleton'
4
+
5
+ module Nanoc3
6
+
7
+ # Nanoc3::Package is a singleton that contains metadata about the nanoc
8
+ # project, which is used for packaging releases.
9
+ class Package
10
+
11
+ include Singleton
12
+
13
+ # The name of the application.
14
+ def name
15
+ 'nanoc3'
16
+ end
17
+
18
+ # The files to include in the package. This is also the list of files that
19
+ # will be included in the documentation (with the exception of the files
20
+ # in undocumented_files).
21
+ def files
22
+ @files ||= (%w( ChangeLog LICENSE NEWS.rdoc Rakefile README.rdoc ) +
23
+ Dir['bin/**/*'] +
24
+ Dir['lib/**/*'] +
25
+ Dir['vendor/**/*']).reject { |f| File.directory?(f) }
26
+ end
27
+
28
+ # The files that are included in the documentation by default.
29
+ def files_documented_by_default
30
+ Dir['lib/**/*'].reject { |f| File.directory?(f) }
31
+ end
32
+
33
+ # The files that should not be included in the documentation.
34
+ def files_not_in_documentation
35
+ Dir['lib/**/*.rake'] +
36
+ Dir['vendor/**/*'].reject { |f| File.directory?(f) }
37
+ end
38
+
39
+ # The files that should be included in the documentation.
40
+ def files_in_documentation
41
+ files - files_not_in_documentation
42
+ end
43
+
44
+ # The files that are not documented by RDoc by default, but should still
45
+ # be included in the documentation.
46
+ def extra_rdoc_files
47
+ files_in_documentation - files_documented_by_default
48
+ end
49
+
50
+ # The name of the file that should be used as entry point for the
51
+ # documentation.
52
+ def main_documentation_file
53
+ 'README.rdoc'
54
+ end
55
+
56
+ # The Gem::Specification used for packaging.
57
+ def gem_spec
58
+ @gem_spec ||= Gem::Specification.new do |s|
59
+ s.name = self.name
60
+ s.version = Nanoc3::VERSION
61
+ s.platform = Gem::Platform::RUBY
62
+ s.summary = 'a tool that runs on your local computer ' +
63
+ 'and compiles Markdown, Textile, Haml, ' +
64
+ '... documents into static web pages'
65
+ s.description = s.summary
66
+ s.homepage = 'http://nanoc.stoneship.org/'
67
+ s.rubyforge_project = 'nanoc3'
68
+
69
+ s.author = 'Denis Defreyne'
70
+ s.email = 'denis.defreyne@stoneship.org'
71
+
72
+ s.post_install_message = <<EOS
73
+ ------------------------------------------------------------------------------
74
+ Thanks for installing nanoc 3.0! Here are some resources to help you get started:
75
+
76
+ * The tutorial at <http://nanoc.stoneship.org/help/tutorial/>
77
+ * The manual at <http://nanoc.stoneship.org/help/manual/>
78
+ * The discussion group at <http://groups.google.com/group/nanoc>
79
+
80
+ Because nanoc 3.0 has a lot of new features, be sure to check out the nanoc blog at <http://nanoc.stoneship.org/blog/> for details about this release.
81
+
82
+ Enjoy!
83
+ ------------------------------------------------------------------------------
84
+ EOS
85
+
86
+ s.required_ruby_version = '>= 1.8.5'
87
+
88
+ s.has_rdoc = true
89
+ s.extra_rdoc_files = self.extra_rdoc_files
90
+ s.rdoc_options = []
91
+ s.rdoc_options += [ '--title', self.name ]
92
+ s.rdoc_options += [ '--main', self.main_documentation_file ]
93
+ self.files_not_in_documentation.each do |file|
94
+ s.rdoc_options += [ '--exclude', file ]
95
+ end
96
+
97
+ s.files = self.files
98
+ s.executables = [ 'nanoc3' ]
99
+ s.require_path = 'lib'
100
+ s.bindir = 'bin'
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ desc 'Remove output files generated by nanoc3'
4
+ task :clean do
5
+ # Load site
6
+ site = Nanoc3::Site.new('.')
7
+ if site.nil?
8
+ $stderr.puts 'The current working directory does not seem to be a ' +
9
+ 'valid/complete nanoc site directory; aborting.'
10
+ exit 1
11
+ end
12
+
13
+ # Clean
14
+ clean = ::Nanoc3::Tasks::Clean.new(site)
15
+ clean.run
16
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc3::Tasks
4
+
5
+ class Clean
6
+
7
+ def initialize(site)
8
+ @site = site
9
+ end
10
+
11
+ def run
12
+ # Load site data
13
+ @site.load_data
14
+
15
+ # Delete all compiled item reps
16
+ filenames.each do |filename|
17
+ FileUtils.rm_f filename unless filename.nil?
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def filenames
24
+ @site.items.map do |item|
25
+ item.reps.map do |rep|
26
+ rep.raw_path
27
+ end
28
+ end.flatten
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :deploy do
4
+
5
+ desc 'Upload the compiled site using rsync'
6
+ task :rsync do
7
+ deployer = Nanoc3::Extra::Deployers::Rsync.new
8
+ deployer.run :dry_run => !!ENV['DRY_RUN']
9
+ end
10
+
11
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ namespace :validate do
4
+
5
+ desc 'Validate the site\'s HTML files'
6
+ task :html do
7
+ # Load site
8
+ site = Nanoc3::Site.new('.')
9
+ if site.nil?
10
+ $stderr.puts 'The current working directory does not seem to be a ' +
11
+ 'valid/complete nanoc site directory; aborting.'
12
+ exit 1
13
+ end
14
+
15
+ # Validate
16
+ validator = ::Nanoc3::Extra::Validators::W3C.new(site, :html)
17
+ validator.run
18
+ end
19
+
20
+ desc 'Validate the site\'s CSS files'
21
+ task :css do
22
+ # Load site
23
+ site = Nanoc3::Site.new(YAML.load_file(File.join(Dir.getwd, 'config.yaml')))
24
+ if site.nil?
25
+ $stderr.puts 'The current working directory does not seem to be a ' +
26
+ 'valid/complete nanoc site directory; aborting.'
27
+ exit 1
28
+ end
29
+
30
+ # Validate
31
+ validator = ::Nanoc3::Extra::Validators::W3C.new(site, :css)
32
+ validator.run
33
+ end
34
+
35
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require 'nanoc3'
4
+
5
+ module Nanoc3::Tasks
6
+ end
7
+
8
+ Dir[File.dirname(__FILE__) + '/tasks/**/*.rb'].each { |f| load f }
9
+ Dir[File.dirname(__FILE__) + '/tasks/**/*.rake'].each { |f| load f }
data/lib/nanoc3.rb ADDED
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc3
4
+
5
+ # The current nanoc version.
6
+ VERSION = '3.0.0'
7
+
8
+ end
9
+
10
+ # Load requirements
11
+ require 'yaml'
12
+ require 'fileutils'
13
+
14
+ # Load nanoc
15
+ require 'nanoc3/base'
16
+ require 'nanoc3/extra'
17
+ require 'nanoc3/data_sources'
18
+ require 'nanoc3/filters'
19
+ require 'nanoc3/helpers'
File without changes
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Denis Defreyne and contributors
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/vendor/cri/NEWS ADDED
File without changes
data/vendor/cri/README ADDED
@@ -0,0 +1,4 @@
1
+ Cri
2
+ ===
3
+
4
+ Cri is a library for building easy-to-use commandline tools.
@@ -0,0 +1,25 @@
1
+ ##### Requirements
2
+
3
+ # Rake etc
4
+ require 'rake'
5
+ require 'minitest/unit'
6
+
7
+ # Cri itself
8
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/lib'))
9
+ require 'cri'
10
+
11
+ ##### Testing
12
+
13
+ desc 'Runs all tests'
14
+ task :test do
15
+ ENV['QUIET'] ||= 'true'
16
+
17
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/..'))
18
+
19
+ MiniTest::Unit.autorun
20
+
21
+ test_files = Dir['test/test_*.rb']
22
+ test_files.each { |f| require f }
23
+ end
24
+
25
+ task :default => :test
@@ -0,0 +1,153 @@
1
+ module Cri
2
+
3
+ # Cri::Base is the central class representing a commandline tool. It has a
4
+ # list of commands.
5
+ class Base
6
+
7
+ # The CLI's list of commands (should also contain the help command)
8
+ attr_reader :commands
9
+
10
+ # The CLI's help command (required)
11
+ attr_accessor :help_command
12
+
13
+ # Creates a new instance of the commandline tool.
14
+ def initialize(tool_name)
15
+ @tool_name = tool_name
16
+
17
+ @commands = []
18
+ end
19
+
20
+ # Parses the given commandline arguments and executes the requested
21
+ # command.
22
+ def run(args)
23
+ # Check arguments
24
+ if args.length == 0
25
+ @help_command.run([], [])
26
+ exit 1
27
+ end
28
+
29
+ # Partition options
30
+ opts_before_command = []
31
+ command_name = nil
32
+ opts_and_args_after_command = []
33
+ stage = 0
34
+ args.each do |arg|
35
+ # Update stage if necessary
36
+ stage = 1 if stage == 0 && !is_option?(arg)
37
+
38
+ # Add
39
+ opts_before_command << arg if stage == 0
40
+ command_name = arg if stage == 1
41
+ opts_and_args_after_command << arg if stage == 2
42
+
43
+ # Update stage if necessary
44
+ stage = 2 if stage == 1
45
+ end
46
+
47
+ # Handle options before command
48
+ begin
49
+ parsed_arguments = Cri::OptionParser.parse(opts_before_command, global_option_definitions)
50
+ rescue Cri::OptionParser::IllegalOptionError => e
51
+ $stderr.puts "illegal option -- #{e}"
52
+ exit 1
53
+ end
54
+ parsed_arguments[:options].keys.each do |option|
55
+ handle_option(option)
56
+ end
57
+
58
+ # Get command
59
+ if command_name.nil?
60
+ $stderr.puts "no command given"
61
+ exit 1
62
+ end
63
+ command = command_named(command_name)
64
+ if command.nil?
65
+ $stderr.puts "no such command: #{command_name}"
66
+ exit 1
67
+ end
68
+
69
+ # Parse arguments
70
+ option_definitions = command.option_definitions + global_option_definitions
71
+ begin
72
+ parsed_arguments = Cri::OptionParser.parse(opts_and_args_after_command, option_definitions)
73
+ rescue Cri::OptionParser::IllegalOptionError => e
74
+ $stderr.puts "illegal option -- #{e}"
75
+ exit 1
76
+ rescue Cri::OptionParser::OptionRequiresAnArgumentError => e
77
+ $stderr.puts "option requires an argument -- #{e}"
78
+ exit 1
79
+ end
80
+
81
+ # Handle global options
82
+ global_options = global_option_definitions.map { |o| o[:long] }
83
+ global_options.delete_if { |o| !parsed_arguments[:options].keys.include?(o.to_sym) }
84
+ global_options.each { |o| handle_option(o.to_sym) }
85
+
86
+ if parsed_arguments[:options].has_key?(:help)
87
+ # Show help for this command
88
+ show_help(command)
89
+ else
90
+ # Run command
91
+ command.run(parsed_arguments[:options], parsed_arguments[:arguments])
92
+ end
93
+ end
94
+
95
+ # Returns the command with the given name.
96
+ def command_named(name)
97
+ # Find by exact name or alias
98
+ command = @commands.find { |c| c.name == name or c.aliases.include?(name) }
99
+ return command unless command.nil?
100
+
101
+ # Find by approximation
102
+ commands = @commands.select { |c| c.name[0, name.length] == name }
103
+ if commands.length > 1
104
+ $stderr.puts "#{@tool_name}: '#{name}' is ambiguous:"
105
+ $stderr.puts " #{commands.map { |c| c.name }.join(' ') }"
106
+ exit 1
107
+ elsif commands.length == 0
108
+ $stderr.puts "#{@tool_name}: unknown command '#{name}'\n"
109
+ show_help
110
+ exit 1
111
+ else
112
+ return commands[0]
113
+ end
114
+ end
115
+
116
+ # Shows the help text for the given command, or shows the general help
117
+ # text if no command is given.
118
+ def show_help(command=nil)
119
+ if command.nil?
120
+ @help_command.run([], [])
121
+ else
122
+ @help_command.run([], [ command.name ])
123
+ end
124
+ end
125
+
126
+ # Returns the list of global option definitions.
127
+ def global_option_definitions
128
+ []
129
+ end
130
+
131
+ # Adds the given command to the list of commands. Adding a command will
132
+ # also cause the command's +base+ to be set to this instance.
133
+ def add_command(command)
134
+ @commands << command
135
+ command.base = self
136
+ end
137
+
138
+ # Handles the given option.
139
+ def handle_option(option)
140
+ false
141
+ end
142
+
143
+ private
144
+
145
+ # Returns true if the given string is an option (i.e. -foo or --foo),
146
+ # false otherwise.
147
+ def is_option?(string)
148
+ string =~ /^-/
149
+ end
150
+
151
+ end
152
+
153
+ end
@@ -0,0 +1,105 @@
1
+ module Cri
2
+
3
+ # Cri::Command represents a command that can be executed on the commandline.
4
+ # It is an abstract superclass for all commands.
5
+ class Command
6
+
7
+ attr_accessor :base
8
+
9
+ # Returns a string containing the name of thi command. Subclasses must
10
+ # implement this method.
11
+ def name
12
+ raise NotImplementedError.new("Command subclasses should override #name")
13
+ end
14
+
15
+ # Returns an array of strings containing the aliases for this command.
16
+ # Subclasses must implement this method.
17
+ def aliases
18
+ raise NotImplementedError.new("Command subclasses should override #aliases")
19
+ end
20
+
21
+ # Returns a string containing this command's short description, which
22
+ # should not be longer than 50 characters. Subclasses must implement this
23
+ # method.
24
+ def short_desc
25
+ raise NotImplementedError.new("Command subclasses should override #short_desc")
26
+ end
27
+
28
+ # Returns a string containing this command's complete description, which
29
+ # should explain what this command does and how it works in detail.
30
+ # Subclasses must implement this method.
31
+ def long_desc
32
+ raise NotImplementedError.new("Command subclasses should override #long_desc")
33
+ end
34
+
35
+ # Returns a string containing this command's usage. Subclasses must
36
+ # implement this method.
37
+ def usage
38
+ raise NotImplementedError.new("Command subclasses should override #usage")
39
+ end
40
+
41
+ # Returns an array containing this command's option definitions. See the
42
+ # documentation for Cri::OptionParser for details on what option
43
+ # definitions look like. Subclasses may implement this method if the
44
+ # command has options.
45
+ def option_definitions
46
+ []
47
+ end
48
+
49
+ # Executes the command. Subclasses must implement this method
50
+ # (obviously... what's the point of a command that can't be run?).
51
+ #
52
+ # +options+:: A hash containing the parsed commandline options. For
53
+ # example, '--foo=bar' will be converted into { :foo => 'bar'
54
+ # }. See the Cri::OptionParser documentation for details.
55
+ #
56
+ # +arguments+:: An array of strings representing the commandline arguments
57
+ # given to this command.
58
+ def run(options, arguments)
59
+ raise NotImplementedError.new("Command subclasses should override #run")
60
+ end
61
+
62
+ # Returns the help text for this command.
63
+ def help
64
+ text = ''
65
+
66
+ # Append usage
67
+ text << usage + "\n"
68
+
69
+ # Append aliases
70
+ unless aliases.empty?
71
+ text << "\n"
72
+ text << "aliases: #{aliases.join(' ')}\n"
73
+ end
74
+
75
+ # Append short description
76
+ text << "\n"
77
+ text << short_desc + "\n"
78
+
79
+ # Append long description
80
+ text << "\n"
81
+ text << long_desc.wrap_and_indent(78, 4) + "\n"
82
+
83
+ # Append options
84
+ all_option_definitions = base.global_option_definitions + option_definitions
85
+ unless all_option_definitions.empty?
86
+ text << "\n"
87
+ text << "options:\n"
88
+ text << "\n"
89
+ all_option_definitions.sort { |x,y| x[:long] <=> y[:long] }.each do |opt_def|
90
+ text << sprintf(" -%1s --%-10s %s\n", opt_def[:short], opt_def[:long], opt_def[:desc])
91
+ end
92
+ end
93
+
94
+ # Return text
95
+ text
96
+ end
97
+
98
+ # Compares this command's name to the other given command's name.
99
+ def <=>(other)
100
+ self.name <=> other.name
101
+ end
102
+
103
+ end
104
+
105
+ end