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.
Files changed (99) hide show
  1. data/ChangeLog +54 -0
  2. data/LICENSE +1 -1
  3. data/README +63 -3
  4. data/Rakefile +61 -14
  5. data/bin/nanoc +7 -199
  6. data/lib/nanoc/base/asset.rb +113 -0
  7. data/lib/nanoc/base/asset_defaults.rb +21 -0
  8. data/lib/nanoc/base/asset_rep.rb +277 -0
  9. data/lib/nanoc/base/binary_filter.rb +44 -0
  10. data/lib/nanoc/base/code.rb +41 -0
  11. data/lib/nanoc/base/compiler.rb +46 -34
  12. data/lib/nanoc/base/core_ext/hash.rb +51 -7
  13. data/lib/nanoc/base/core_ext/string.rb +8 -0
  14. data/lib/nanoc/base/data_source.rb +253 -20
  15. data/lib/nanoc/base/defaults.rb +30 -0
  16. data/lib/nanoc/base/enhancements.rb +9 -84
  17. data/lib/nanoc/base/filter.rb +109 -6
  18. data/lib/nanoc/base/layout.rb +91 -0
  19. data/lib/nanoc/base/notification_center.rb +66 -0
  20. data/lib/nanoc/base/page.rb +94 -121
  21. data/lib/nanoc/base/page_defaults.rb +20 -0
  22. data/lib/nanoc/base/page_rep.rb +318 -0
  23. data/lib/nanoc/base/plugin.rb +57 -9
  24. data/lib/nanoc/base/proxies/asset_proxy.rb +29 -0
  25. data/lib/nanoc/base/proxies/asset_rep_proxy.rb +26 -0
  26. data/lib/nanoc/base/proxies/layout_proxy.rb +25 -0
  27. data/lib/nanoc/base/proxies/page_proxy.rb +35 -0
  28. data/lib/nanoc/base/proxies/page_rep_proxy.rb +28 -0
  29. data/lib/nanoc/base/proxy.rb +37 -0
  30. data/lib/nanoc/base/router.rb +72 -0
  31. data/lib/nanoc/base/site.rb +220 -91
  32. data/lib/nanoc/base/template.rb +64 -0
  33. data/lib/nanoc/binary_filters/image_science_thumbnail.rb +28 -0
  34. data/lib/nanoc/cli/base.rb +219 -0
  35. data/lib/nanoc/cli/cli.rb +16 -0
  36. data/lib/nanoc/cli/command.rb +105 -0
  37. data/lib/nanoc/cli/commands/autocompile.rb +80 -0
  38. data/lib/nanoc/cli/commands/compile.rb +273 -0
  39. data/lib/nanoc/cli/commands/create_layout.rb +85 -0
  40. data/lib/nanoc/cli/commands/create_page.rb +85 -0
  41. data/lib/nanoc/cli/commands/create_site.rb +327 -0
  42. data/lib/nanoc/cli/commands/create_template.rb +76 -0
  43. data/lib/nanoc/cli/commands/help.rb +69 -0
  44. data/lib/nanoc/cli/commands/info.rb +114 -0
  45. data/lib/nanoc/cli/commands/switch.rb +141 -0
  46. data/lib/nanoc/cli/commands/update.rb +91 -0
  47. data/lib/nanoc/cli/ext.rb +37 -0
  48. data/lib/nanoc/cli/logger.rb +66 -0
  49. data/lib/nanoc/cli/option_parser.rb +168 -0
  50. data/lib/nanoc/cli.rb +1 -0
  51. data/lib/nanoc/data_sources/filesystem.rb +650 -215
  52. data/lib/nanoc/data_sources/filesystem_combined.rb +495 -0
  53. data/lib/nanoc/extra/auto_compiler.rb +265 -0
  54. data/lib/nanoc/extra/context.rb +22 -0
  55. data/lib/nanoc/extra/core_ext/hash.rb +54 -0
  56. data/lib/nanoc/extra/core_ext/time.rb +13 -0
  57. data/lib/nanoc/extra/file_proxy.rb +29 -0
  58. data/lib/nanoc/extra/vcs.rb +48 -0
  59. data/lib/nanoc/extra/vcses/bazaar.rb +21 -0
  60. data/lib/nanoc/extra/vcses/dummy.rb +20 -0
  61. data/lib/nanoc/extra/vcses/git.rb +21 -0
  62. data/lib/nanoc/extra/vcses/mercurial.rb +21 -0
  63. data/lib/nanoc/extra/vcses/subversion.rb +21 -0
  64. data/lib/nanoc/filters/bluecloth.rb +13 -0
  65. data/lib/nanoc/filters/erb.rb +6 -22
  66. data/lib/nanoc/filters/erubis.rb +14 -0
  67. data/lib/nanoc/filters/haml.rb +11 -6
  68. data/lib/nanoc/filters/markaby.rb +5 -5
  69. data/lib/nanoc/filters/maruku.rb +14 -0
  70. data/lib/nanoc/filters/old.rb +19 -0
  71. data/lib/nanoc/filters/rdiscount.rb +13 -0
  72. data/lib/nanoc/filters/rdoc.rb +5 -4
  73. data/lib/nanoc/filters/redcloth.rb +14 -0
  74. data/lib/nanoc/filters/rubypants.rb +14 -0
  75. data/lib/nanoc/filters/sass.rb +13 -0
  76. data/lib/nanoc/helpers/blogging.rb +170 -0
  77. data/lib/nanoc/helpers/capturing.rb +59 -0
  78. data/lib/nanoc/helpers/html_escape.rb +23 -0
  79. data/lib/nanoc/helpers/link_to.rb +69 -0
  80. data/lib/nanoc/helpers/render.rb +47 -0
  81. data/lib/nanoc/helpers/tagging.rb +52 -0
  82. data/lib/nanoc/helpers/xml_sitemap.rb +58 -0
  83. data/lib/nanoc/routers/default.rb +54 -0
  84. data/lib/nanoc/routers/no_dirs.rb +66 -0
  85. data/lib/nanoc/routers/versioned.rb +79 -0
  86. data/lib/nanoc.rb +83 -12
  87. metadata +144 -47
  88. data/lib/nanoc/base/auto_compiler.rb +0 -124
  89. data/lib/nanoc/base/layout_processor.rb +0 -33
  90. data/lib/nanoc/base/page_proxy.rb +0 -31
  91. data/lib/nanoc/base/plugin_manager.rb +0 -33
  92. data/lib/nanoc/data_sources/database.rb +0 -259
  93. data/lib/nanoc/data_sources/trivial.rb +0 -145
  94. data/lib/nanoc/filters/markdown.rb +0 -13
  95. data/lib/nanoc/filters/smartypants.rb +0 -13
  96. data/lib/nanoc/filters/textile.rb +0 -13
  97. data/lib/nanoc/layout_processors/erb.rb +0 -35
  98. data/lib/nanoc/layout_processors/haml.rb +0 -18
  99. data/lib/nanoc/layout_processors/markaby.rb +0 -16
@@ -1,143 +1,272 @@
1
1
  module Nanoc
2
+
3
+ # A Nanoc::Site is the in-memory representation of a nanoc site. It holds
4
+ # references to the following site data:
5
+ #
6
+ # * +pages+ is a list of Nanoc::Page instances representing pages
7
+ # * +assets+ is a list of Nanoc::Asset instances representing assets
8
+ # * +page_defaults+ is a Nanoc::PageDefaults instance representing page
9
+ # defaults
10
+ # * +asset_defaults+ is a Nanoc::AssetDefaults instance representing asset
11
+ # defaults
12
+ # * +layouts+ is a list of Nanoc::Layout instances representing layouts
13
+ # * +templates+ is a list of Nanoc::Template representing templates
14
+ # * +code+ is a Nanoc::Code instance representing custom site code
15
+ #
16
+ # In addition, each site has a +config+ hash which stores the site
17
+ # configuration. This configuration hash can have the following keys:
18
+ #
19
+ # +output_dir+:: The directory to which compiled pages and assets will be
20
+ # written. This path is relative to the current working
21
+ # directory, but can also be an absolute path.
22
+ #
23
+ # +data_source+:: The identifier of the data source that will be used for
24
+ # loading site data.
25
+ #
26
+ # +router+:: The identifier of the router that will be used for determining
27
+ # page and asset representation paths.
28
+ #
29
+ # +index_filenames+:: A list of filenames that will be stripped off full
30
+ # page and asset paths to create cleaner URLs (for
31
+ # example, '/about/' will be used instead of
32
+ # '/about/index.html'). The default value should be okay
33
+ # in most cases.
34
+ #
35
+ # A site also has several helper classes:
36
+ #
37
+ # * +router+ is a Nanoc::Router subclass instance used for determining page
38
+ # and asset paths.
39
+ # * +data_source+ is a Nanoc::DataSource subclass instance used for managing
40
+ # site data.
41
+ # * +compiler+ is a Nanoc::Compiler instance that compiles page and asset
42
+ # representations.
43
+ #
44
+ # The physical representation of a Nanoc::Site is usually a directory that
45
+ # contains a configuration file, site data, and some rake tasks. However,
46
+ # different frontends may store data differently. For example, a web-based
47
+ # frontend would probably store the configuration and the site content in a
48
+ # database, and would not have rake tasks at all.
2
49
  class Site
3
50
 
51
+ # The default configuration for a site. A site's configuration overrides
52
+ # these options: when a Nanoc::Site is created with a configuration that
53
+ # lacks some options, the default value will be taken from
54
+ # +DEFAULT_CONFIG+.
4
55
  DEFAULT_CONFIG = {
5
- :output_dir => 'output',
6
- :eruby_engine => 'erb',
7
- :data_source => 'filesystem'
56
+ :output_dir => 'output',
57
+ :data_source => 'filesystem',
58
+ :router => 'default',
59
+ :index_filenames => [ 'index.html' ]
8
60
  }
9
61
 
10
62
  attr_reader :config
11
- attr_reader :compiler, :data_source
12
- attr_reader :code, :pages, :page_defaults, :layouts, :templates
13
-
14
- # Creating a Site object
15
-
16
- def self.from_cwd
17
- File.file?('config.yaml') ? new : nil
18
- end
19
-
20
- def initialize
63
+ attr_reader :compiler, :data_source, :router
64
+ attr_reader :page_defaults, :asset_defaults
65
+ attr_reader :pages, :assets, :layouts, :templates, :code
66
+
67
+ # Returns a Nanoc::Site object for the site specified by the given
68
+ # configuration hash +config+.
69
+ #
70
+ # +config+:: A hash containing the site configuration.
71
+ def initialize(config)
21
72
  # Load configuration
22
- @config = DEFAULT_CONFIG.merge((YAML.load_file('config.yaml') || {}).clean)
73
+ @config = DEFAULT_CONFIG.merge(config.clean)
23
74
 
24
75
  # Create data source
25
- @data_source_class = PluginManager.data_source_named(@config[:data_source])
26
- error "Unrecognised data source: #{@config[:data_source]}" if @data_source_class.nil?
76
+ @data_source_class = Nanoc::DataSource.named(@config[:data_source])
77
+ raise Nanoc::Errors::UnknownDataSourceError.new(@config[:data_source]) if @data_source_class.nil?
27
78
  @data_source = @data_source_class.new(self)
28
79
 
29
80
  # Create compiler
30
- @compiler = Compiler.new(self)
31
- @autocompiler = AutoCompiler.new(self)
32
-
33
- # Set not loaded
34
- @data_loaded = false
81
+ @compiler = Compiler.new(self)
82
+
83
+ # Load code (necessary for custom routers)
84
+ load_code
85
+
86
+ # Create router
87
+ @router_class = Nanoc::Router.named(@config[:router])
88
+ raise Nanoc::Errors::UnknownRouterError.new(@config[:router]) if @router_class.nil?
89
+ @router = @router_class.new(self)
90
+
91
+ # Initialize data
92
+ @page_defaults = PageDefaults.new({})
93
+ @page_defaults.site = self
94
+ @asset_defaults = AssetDefaults.new({})
95
+ @asset_defaults.site = self
96
+ @pages = []
97
+ @assets = []
98
+ @layouts = []
99
+ @templates = []
35
100
  end
36
101
 
102
+ # Loads the site data. This will query the Nanoc::DataSource associated
103
+ # with the site and fetch all site data. The site data is cached, so
104
+ # calling this method will not have any effect the second time, unless
105
+ # +force+ is true.
106
+ #
107
+ # +force+:: If true, will force load the site data even if it has been
108
+ # loaded before, to circumvent caching issues.
37
109
  def load_data(force=false)
110
+ # Don't load data twice
111
+ @data_loaded ||= false
38
112
  return if @data_loaded and !force
39
113
 
40
- # Load data
114
+ # Load all data
41
115
  @data_source.loading do
42
- @code = @data_source.code
43
- @pages = @data_source.pages.map { |p| Page.new(p, self) }
44
- @page_defaults = @data_source.page_defaults
45
- @layouts = @data_source.layouts
46
- @templates = @data_source.templates
116
+ load_code(force)
117
+ load_page_defaults
118
+ load_pages
119
+ load_asset_defaults
120
+ load_assets
121
+ load_layouts
122
+ load_templates
47
123
  end
48
124
 
125
+ # Set loaded
126
+ @data_loaded = true
127
+ end
128
+
129
+ private
130
+
131
+ # Loads this site's code and executes it.
132
+ def load_code(force=false)
133
+ # Don't load code twice
134
+ @code_loaded ||= false
135
+ return if @code_loaded and !force
136
+
137
+ # Get code
138
+ @code = @data_source.code
139
+
140
+ # Fix code if outdated
141
+ if @code.is_a? String
142
+ warn_data_source('Code', 'code', false)
143
+ @code = Code.new(code)
144
+ end
145
+
146
+ # Set site
147
+ @code.site = self
148
+
149
+ # Execute code
150
+ # FIXME This could be dangerous when using nanoc as a framework
151
+ # (a separate ruby process should probably be forked, and the code
152
+ # should only be loaded in this forked process)
153
+ @code.load
154
+
155
+ # Set loaded
156
+ @code_loaded = true
157
+ end
158
+
159
+ # Loads this site's page defaults.
160
+ def load_page_defaults
161
+ # Get page defaults
162
+ @page_defaults = @data_source.page_defaults
163
+
164
+ # Fix page defaults if outdated
165
+ if @page_defaults.is_a? Hash
166
+ warn_data_source('PageDefaults', 'page_defaults', false)
167
+ @page_defaults = PageDefaults.new(@page_defaults)
168
+ end
169
+
170
+ # Set site
171
+ @page_defaults.site = self
172
+ end
173
+
174
+ # Loads this site's pages, sets up page child-parent relationships and
175
+ # builds each page's list of page representations.
176
+ def load_pages
177
+ # Get pages
178
+ @pages = @data_source.pages
179
+
180
+ # Fix pages if outdated
181
+ if @pages.any? { |p| p.is_a? Hash }
182
+ warn_data_source('Page', 'pages', true)
183
+ @pages.map! { |p| Page.new(p[:uncompiled_content], p, p[:path]) }
184
+ end
185
+
186
+ # Set site
187
+ @pages.each { |p| p.site = self }
188
+
49
189
  # Setup child-parent links
50
190
  @pages.each do |page|
51
- # Skip pages without parent
52
- next if page.path == '/'
53
-
54
191
  # Get parent
55
192
  parent_path = page.path.sub(/[^\/]+\/$/, '')
56
193
  parent = @pages.find { |p| p.path == parent_path }
194
+ next if parent.nil? or page.path == '/'
57
195
 
58
196
  # Link
59
197
  page.parent = parent
60
198
  parent.children << page
61
199
  end
62
200
 
63
- # Set loaded
64
- @data_loaded = true
65
- end
66
-
67
- # Creating a new site on disk
68
-
69
- def self.create(sitename)
70
- # Check whether site exists
71
- error "A site named '#{sitename}' already exists." if File.exist?(sitename)
72
-
73
- FileUtils.mkdir_p sitename
74
- in_dir([sitename]) do
75
- # Create output
76
- FileManager.create_dir 'output'
77
-
78
- # Create config
79
- FileManager.create_file 'config.yaml' do
80
- "output_dir: \"output\"\n" +
81
- "data_source: \"filesystem\"\n"
82
- end
83
-
84
- # Create rakefile
85
- FileManager.create_file 'Rakefile' do
86
- "Dir['tasks/**/*.rake'].sort.each { |rakefile| load rakefile }\n" +
87
- "\n" +
88
- "task :default do\n" +
89
- " puts 'This is an example rake task.'\n" +
90
- "end\n"
91
- end
92
-
93
- # Create tasks
94
- FileManager.create_file 'tasks/default.rake' do
95
- "task :example do\n" +
96
- " puts 'This is an example rake task in tasks/default.rake.'\n" +
97
- "end\n"
98
- end
99
-
100
- # Setup site
101
- Site.from_cwd.setup
102
- end
201
+ # Build page representations
202
+ @pages.each { |p| p.build_reps }
103
203
  end
104
204
 
105
- # Compiling
205
+ # Loads this site's asset defaults.
206
+ def load_asset_defaults
207
+ # Get page defaults
208
+ @asset_defaults = @data_source.asset_defaults
106
209
 
107
- def compile
108
- @compiler.run
210
+ # Set site
211
+ @asset_defaults.site = self
212
+ rescue NotImplementedError
213
+ @asset_defaults = AssetDefaults.new({})
214
+ @asset_defaults.site = self
109
215
  end
110
216
 
111
- def autocompile(port)
112
- @autocompiler.start(port)
113
- end
217
+ # Loads this site's assets and builds each asset's list of asset
218
+ # representations.
219
+ def load_assets
220
+ # Get assets
221
+ @assets = @data_source.assets
114
222
 
115
- # Creating
223
+ # Set site
224
+ @assets.each { |a| a.site = self }
116
225
 
117
- def setup
118
- @data_source.loading { @data_source.setup }
226
+ # Build asset representations
227
+ @assets.each { |p| p.build_reps }
228
+ rescue NotImplementedError
229
+ @assets = []
119
230
  end
120
231
 
121
- def create_page(name, template_name='default')
122
- load_data
232
+ # Loads this site's layouts.
233
+ def load_layouts
234
+ # Get layouts
235
+ @layouts = @data_source.layouts
123
236
 
124
- template = @templates.find { |t| t[:name] == template_name }
125
- error "A template named '#{template_name}' was not found; aborting." if template.nil?
237
+ # Fix layouts if outdated
238
+ if @layouts.any? { |l| l.is_a? Hash }
239
+ warn_data_source('Layout', 'layouts', true)
240
+ @layouts.map! { |l| Layout.new(l[:content], l, l[:path] || l[:name]) }
241
+ end
126
242
 
127
- @data_source.loading { @data_source.create_page(name, template) }
243
+ # Set site
244
+ @layouts.each { |l| l.site = self }
128
245
  end
129
246
 
130
- def create_template(name)
131
- load_data
247
+ # Loads this site's templates.
248
+ def load_templates
249
+ # Get templates
250
+ @templates = @data_source.templates
132
251
 
133
- @data_source.loading {@data_source.create_template(name) }
134
- end
252
+ # Fix templates if outdated
253
+ if @templates.any? { |t| t.is_a? Hash }
254
+ warn_data_source('Template', 'templates', true)
255
+ @templates.map! { |t| Template.new(t[:content], t[:meta].is_a?(String) ? YAML.load(t[:meta]) : t[:meta], t[:name]) }
256
+ end
135
257
 
136
- def create_layout(name)
137
- load_data
258
+ # Set site
259
+ @templates.each { |t| t.site = self }
260
+ end
138
261
 
139
- @data_source.loading { @data_source.create_layout(name) }
262
+ # Raises a warning about an outdated data source method.
263
+ def warn_data_source(class_name, method_name, is_array)
264
+ warn(
265
+ "In nanoc 2.1, DataSource##{method_name} should return #{is_array ? 'an array of' : 'a' } Nanoc::#{class_name} object#{is_array ? 's' : ''}. Future versions will not support these outdated data sources.",
266
+ 'DEPRECATION WARNING'
267
+ )
140
268
  end
141
269
 
142
270
  end
271
+
143
272
  end
@@ -0,0 +1,64 @@
1
+ module Nanoc
2
+
3
+ # A Nanoc::Template represents a template, which can be used for creating
4
+ # new pages. Pages don't necessary have to be created using templates, but
5
+ # they can be useful for generating pages where you only have to "fill in
6
+ # the blanks".
7
+ class Template
8
+
9
+ # The Nanoc::Site this template belongs to.
10
+ attr_accessor :site
11
+
12
+ # The name of this template.
13
+ attr_reader :name
14
+
15
+ # The raw content a page created using this template will have.
16
+ attr_reader :page_content
17
+
18
+ # A hash containing the attributes a page created using this template will
19
+ # have.
20
+ attr_reader :page_attributes
21
+
22
+ # Creates a new template.
23
+ #
24
+ # +name+:: The name of this template.
25
+ #
26
+ # +page_content+:: The raw content a page created using this template will
27
+ # have.
28
+ #
29
+ # +page_attributes+:: A hash containing the attributes a page created
30
+ # using this template will have.
31
+ def initialize(page_content, page_attributes, name)
32
+ @page_content = page_content
33
+ @page_attributes = page_attributes.clean
34
+ @name = name
35
+ end
36
+
37
+ # Saves the template in the database, creating it if it doesn't exist yet
38
+ # or updating it if it already exists. Tells the site's data source to
39
+ # save the template.
40
+ def save
41
+ @site.data_source.loading do
42
+ @site.data_source.save_template(self)
43
+ end
44
+ end
45
+
46
+ # Renames the template. Tells the site's data source to rename the
47
+ # template.
48
+ def move_to(new_name)
49
+ @site.data_source.loading do
50
+ @site.data_source.move_template(self, new_name)
51
+ end
52
+ end
53
+
54
+ # Deletes the template. Tells the site's data source to delete the
55
+ # template.
56
+ def delete
57
+ @site.data_source.loading do
58
+ @site.data_source.delete_template(self)
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,28 @@
1
+ module Nanoc::BinaryFilters
2
+
3
+ class ImageScienceThumbnail < Nanoc::BinaryFilter
4
+
5
+ identifier :image_science_thumbnail
6
+
7
+ def run(file)
8
+ require 'image_science'
9
+
10
+ # Get temporary file path
11
+ tmp_file = Tempfile.new('filter')
12
+ tmp_path = tmp_file.path
13
+ tmp_file.close
14
+
15
+ # Create thumbnail
16
+ ImageScience.with_image(file.path) do |img|
17
+ img.thumbnail(@asset_rep.thumbnail_size || 150) do |thumbnail|
18
+ thumbnail.save(tmp_path)
19
+ end
20
+ end
21
+
22
+ # Return thumbnail file
23
+ File.open(tmp_path)
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,219 @@
1
+ module Nanoc::CLI
2
+
3
+ # Nanoc::CLI::Base is the central class representing a commandline nanoc
4
+ # tool. It has a list of commands, and is linked to a specific nanoc site.
5
+ class Base
6
+
7
+ attr_reader :commands, :site
8
+
9
+ # Creates a new instance of the commandline nanoc tool.
10
+ def initialize
11
+ create_commands
12
+ end
13
+
14
+ # Parses the given commandline arguments and executes the requested
15
+ # command.
16
+ def run(args)
17
+ # Check arguments
18
+ if args.length == 0
19
+ @help_command.run([], [])
20
+ exit 1
21
+ end
22
+
23
+ # Find version or help options
24
+ if args.length == 1
25
+ # Parse arguments
26
+ begin
27
+ parsed_arguments = Nanoc::CLI::OptionParser.parse(args[0..1], global_option_definitions)
28
+ rescue Nanoc::CLI::OptionParser::IllegalOptionError => e
29
+ $stderr.puts "illegal option -- #{e}"
30
+ exit 1
31
+ end
32
+
33
+ # Handle version option
34
+ if parsed_arguments[:options].has_key?(:version)
35
+ puts "nanoc #{Nanoc::VERSION} (c) 2007-2008 Denis Defreyne."
36
+ exit 1
37
+ # Handle help option
38
+ elsif parsed_arguments[:options].has_key?(:help)
39
+ show_help
40
+ exit 1
41
+ end
42
+ end
43
+
44
+ # Find command
45
+ command = command_named(args[0])
46
+
47
+ # Get extended option definitions (with help)
48
+ extended_option_definitions = command.option_definitions + [
49
+ # --help
50
+ {
51
+ :long => 'help', :short => 'h', :argument => :forbidden,
52
+ :desc => 'show this help message and quit'
53
+ },
54
+ # --verbose
55
+ {
56
+ :long => 'verbose', :short => 'V', :argument => :forbidden,
57
+ :desc => 'enable more detailed output'
58
+ }
59
+ ]
60
+
61
+ # Parse arguments
62
+ begin
63
+ parsed_arguments = Nanoc::CLI::OptionParser.parse(args[1..-1], extended_option_definitions)
64
+ rescue Nanoc::CLI::OptionParser::IllegalOptionError => e
65
+ $stderr.puts "illegal option -- #{e}"
66
+ exit 1
67
+ rescue Nanoc::CLI::OptionParser::OptionRequiresAnArgumentError => e
68
+ $stderr.puts "option requires an argument -- #{e}"
69
+ exit 1
70
+ end
71
+
72
+ # Check help option
73
+ if parsed_arguments[:options].has_key?(:help)
74
+ show_help(command)
75
+ exit 1
76
+ end
77
+
78
+ # Check verbose option
79
+ if parsed_arguments[:options].has_key?(:verbose)
80
+ Nanoc::CLI::Logger.instance.level = :low
81
+ end
82
+
83
+ # Find and run command
84
+ command.run(parsed_arguments[:options], parsed_arguments[:arguments])
85
+ end
86
+
87
+ # Returns the command with the given name.
88
+ def command_named(name)
89
+ # Find by exact name or alias
90
+ command = @commands.find { |c| c.name == name or c.aliases.include?(name) }
91
+ return command unless command.nil?
92
+
93
+ # Find by approximation
94
+ commands = @commands.select { |c| c.name[0, name.length] == name }
95
+ if commands.length > 1
96
+ $stderr.puts "nanoc: '#{name}' is ambiguous:"
97
+ $stderr.puts " #{commands.map { |c| c.name }.join(' ') }"
98
+ exit 1
99
+ elsif commands.length == 0
100
+ $stderr.puts "nanoc: unknown command '#{name}'\n"
101
+ show_help
102
+ exit 1
103
+ else
104
+ return commands[0]
105
+ end
106
+ end
107
+
108
+ # Shows the help text for the given command, or shows the general help
109
+ # text if no command is given.
110
+ def show_help(command=nil)
111
+ if command.nil?
112
+ @help_command.run([], [])
113
+ else
114
+ @help_command.run([], [ command.name ])
115
+ end
116
+ end
117
+
118
+ # Helper function which can be called when a command is executed that
119
+ # requires a site, such as the compile command.
120
+ def require_site
121
+ if site.nil?
122
+ $stderr.puts 'The current working directory does not seem to be a ' +
123
+ 'valid/complete nanoc site directory; aborting.'
124
+ exit 1
125
+ end
126
+ end
127
+
128
+ # Gets the site (Nanoc::Site) in the current directory and loads its data.
129
+ def site
130
+ # Load site if possible
131
+ if File.file?('config.yaml') and @site.nil?
132
+ begin
133
+ @site = Nanoc::Site.new(YAML.load_file('config.yaml'))
134
+ @site.load_data
135
+ rescue Nanoc::Errors::UnknownDataSourceError => e
136
+ $stderr.puts "Unknown data source: #{e}"
137
+ exit 1
138
+ rescue Nanoc::Errors::UnknownRouterError => e
139
+ $stderr.puts "Unknown router: #{e}"
140
+ exit 1
141
+ rescue Exception => e
142
+ $stderr.puts "ERROR: An exception occured while loading this site."
143
+ $stderr.puts
144
+ $stderr.puts "If you think this is a bug in nanoc, please do report it at " +
145
+ "<http://nanoc.stoneship.org/trac/newticket> -- thanks!"
146
+ $stderr.puts
147
+ $stderr.puts 'Message:'
148
+ $stderr.puts ' ' + e.message
149
+ $stderr.puts
150
+ $stderr.puts 'Backtrace:'
151
+ $stderr.puts e.backtrace.map { |t| ' - ' + t }.join("\n")
152
+ exit 1
153
+ end
154
+ end
155
+
156
+ @site
157
+ end
158
+
159
+ # Sets the data source's VCS to the VCS with the given name. Does nothing
160
+ # when the site's data source does not support VCSes (i.e. does not
161
+ # implement #vcs=).
162
+ def set_vcs(vcs_name)
163
+ # Skip if not possible
164
+ return if vcs_name.nil?
165
+ return if site.nil? or !site.data_source.respond_to?(:vcs=)
166
+
167
+ # Find VCS
168
+ vcs_class = Nanoc::Extra::VCS.named(vcs_name.to_sym)
169
+ if vcs_class.nil?
170
+ $stderr.puts "A VCS named #{vcs_name} was not found; aborting."
171
+ exit 1
172
+ end
173
+
174
+ # Set VCS
175
+ site.data_source.vcs = vcs_class.new
176
+ end
177
+
178
+ # Returns the list of global option definitionss.
179
+ def global_option_definitions
180
+ [
181
+ {
182
+ :long => 'help', :short => 'h', :argument => :forbidden,
183
+ :desc => 'show this help message and quit'
184
+ },
185
+ {
186
+ :long => 'version', :short => 'v', :argument => :forbidden,
187
+ :desc => 'show version information and quit'
188
+ }
189
+ ]
190
+ end
191
+
192
+ protected
193
+
194
+ def create_commands
195
+ @commands = []
196
+
197
+ # Find all command classes
198
+ command_classes = []
199
+ ObjectSpace.each_object(Class) do |klass|
200
+ command_classes << klass if klass < Nanoc::CLI::Command
201
+ end
202
+
203
+ # Create commands
204
+ command_classes.each do |klass|
205
+ if klass.to_s == 'Nanoc::CLI::HelpCommand'
206
+ @help_command = HelpCommand.new
207
+ @commands << @help_command
208
+ else
209
+ @commands << klass.new
210
+ end
211
+ end
212
+
213
+ # Set base
214
+ @commands.each { |c| c.base = self }
215
+ end
216
+
217
+ end
218
+
219
+ end
@@ -0,0 +1,16 @@
1
+ module Nanoc::CLI # :nodoc:
2
+ end
3
+
4
+ # Load extensions
5
+ Nanoc.load('cli', 'ext.rb')
6
+ Nanoc.load('cli', 'option_parser.rb')
7
+
8
+ # Load logger
9
+ Nanoc.load('cli', 'logger.rb')
10
+
11
+ # Load commands
12
+ Nanoc.load('cli', 'command.rb')
13
+ Nanoc.load('cli', 'commands', '*.rb')
14
+
15
+ # Load base
16
+ Nanoc.load('cli', 'base.rb')