nanoc 3.2.4 → 3.3.0

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 (230) hide show
  1. data/.gemtest +0 -0
  2. data/ChangeLog +3 -0
  3. data/Gemfile +32 -0
  4. data/LICENSE +19 -0
  5. data/NEWS.md +470 -0
  6. data/README.md +114 -0
  7. data/Rakefile +14 -0
  8. data/bin/nanoc +7 -27
  9. data/bin/nanoc3 +3 -0
  10. data/doc/yardoc_templates/default/layout/html/footer.erb +10 -0
  11. data/lib/nanoc.rb +41 -0
  12. data/lib/nanoc/base.rb +49 -0
  13. data/lib/nanoc/base/compilation/checksum_store.rb +57 -0
  14. data/lib/nanoc/base/compilation/compiled_content_cache.rb +62 -0
  15. data/lib/nanoc/base/compilation/compiler.rb +458 -0
  16. data/lib/nanoc/base/compilation/compiler_dsl.rb +214 -0
  17. data/lib/nanoc/base/compilation/dependency_tracker.rb +200 -0
  18. data/lib/nanoc/base/compilation/filter.rb +165 -0
  19. data/lib/nanoc/base/compilation/item_rep_proxy.rb +103 -0
  20. data/lib/nanoc/base/compilation/item_rep_recorder_proxy.rb +102 -0
  21. data/lib/nanoc/base/compilation/outdatedness_checker.rb +223 -0
  22. data/lib/nanoc/base/compilation/outdatedness_reasons.rb +46 -0
  23. data/lib/nanoc/base/compilation/rule.rb +73 -0
  24. data/lib/nanoc/base/compilation/rule_context.rb +84 -0
  25. data/lib/nanoc/base/compilation/rule_memory_calculator.rb +40 -0
  26. data/lib/nanoc/base/compilation/rule_memory_store.rb +53 -0
  27. data/lib/nanoc/base/compilation/rules_collection.rb +243 -0
  28. data/lib/nanoc/base/context.rb +47 -0
  29. data/lib/nanoc/base/core_ext.rb +6 -0
  30. data/lib/nanoc/base/core_ext/array.rb +62 -0
  31. data/lib/nanoc/base/core_ext/hash.rb +63 -0
  32. data/lib/nanoc/base/core_ext/pathname.rb +26 -0
  33. data/lib/nanoc/base/core_ext/string.rb +46 -0
  34. data/lib/nanoc/base/directed_graph.rb +275 -0
  35. data/lib/nanoc/base/errors.rb +211 -0
  36. data/lib/nanoc/base/memoization.rb +67 -0
  37. data/lib/nanoc/base/notification_center.rb +84 -0
  38. data/lib/nanoc/base/ordered_hash.rb +200 -0
  39. data/lib/nanoc/base/plugin_registry.rb +181 -0
  40. data/lib/nanoc/base/result_data/item_rep.rb +492 -0
  41. data/lib/nanoc/base/source_data/code_snippet.rb +58 -0
  42. data/lib/nanoc/base/source_data/configuration.rb +24 -0
  43. data/lib/nanoc/base/source_data/data_source.rb +234 -0
  44. data/lib/nanoc/base/source_data/item.rb +301 -0
  45. data/lib/nanoc/base/source_data/layout.rb +130 -0
  46. data/lib/nanoc/base/source_data/site.rb +361 -0
  47. data/lib/nanoc/base/store.rb +135 -0
  48. data/lib/nanoc/cli.rb +137 -0
  49. data/lib/nanoc/cli/command_runner.rb +104 -0
  50. data/lib/nanoc/cli/commands/autocompile.rb +58 -0
  51. data/lib/nanoc/cli/commands/compile.rb +297 -0
  52. data/lib/nanoc/cli/commands/create_item.rb +60 -0
  53. data/lib/nanoc/cli/commands/create_layout.rb +73 -0
  54. data/lib/nanoc/cli/commands/create_site.rb +411 -0
  55. data/lib/nanoc/cli/commands/debug.rb +117 -0
  56. data/lib/nanoc/cli/commands/deploy.rb +79 -0
  57. data/lib/nanoc/cli/commands/info.rb +98 -0
  58. data/lib/nanoc/cli/commands/nanoc.rb +38 -0
  59. data/lib/nanoc/cli/commands/prune.rb +50 -0
  60. data/lib/nanoc/cli/commands/update.rb +70 -0
  61. data/lib/nanoc/cli/commands/view.rb +82 -0
  62. data/lib/nanoc/cli/commands/watch.rb +124 -0
  63. data/lib/nanoc/cli/error_handler.rb +199 -0
  64. data/lib/nanoc/cli/logger.rb +92 -0
  65. data/lib/nanoc/data_sources.rb +29 -0
  66. data/lib/nanoc/data_sources/deprecated/delicious.rb +42 -0
  67. data/lib/nanoc/data_sources/deprecated/last_fm.rb +87 -0
  68. data/lib/nanoc/data_sources/deprecated/twitter.rb +38 -0
  69. data/lib/nanoc/data_sources/filesystem.rb +299 -0
  70. data/lib/nanoc/data_sources/filesystem_unified.rb +121 -0
  71. data/lib/nanoc/data_sources/filesystem_verbose.rb +91 -0
  72. data/lib/nanoc/extra.rb +24 -0
  73. data/lib/nanoc/extra/auto_compiler.rb +103 -0
  74. data/lib/nanoc/extra/chick.rb +125 -0
  75. data/lib/nanoc/extra/core_ext.rb +6 -0
  76. data/lib/nanoc/extra/core_ext/enumerable.rb +33 -0
  77. data/lib/nanoc/extra/core_ext/pathname.rb +30 -0
  78. data/lib/nanoc/extra/core_ext/time.rb +19 -0
  79. data/lib/nanoc/extra/deployer.rb +47 -0
  80. data/lib/nanoc/extra/deployers.rb +15 -0
  81. data/lib/nanoc/extra/deployers/fog.rb +98 -0
  82. data/lib/nanoc/extra/deployers/rsync.rb +70 -0
  83. data/lib/nanoc/extra/file_proxy.rb +40 -0
  84. data/lib/nanoc/extra/pruner.rb +86 -0
  85. data/lib/nanoc/extra/validators.rb +12 -0
  86. data/lib/nanoc/extra/validators/links.rb +268 -0
  87. data/lib/nanoc/extra/validators/w3c.rb +95 -0
  88. data/lib/nanoc/extra/vcs.rb +66 -0
  89. data/lib/nanoc/extra/vcses.rb +17 -0
  90. data/lib/nanoc/extra/vcses/bazaar.rb +25 -0
  91. data/lib/nanoc/extra/vcses/dummy.rb +24 -0
  92. data/lib/nanoc/extra/vcses/git.rb +25 -0
  93. data/lib/nanoc/extra/vcses/mercurial.rb +25 -0
  94. data/lib/nanoc/extra/vcses/subversion.rb +25 -0
  95. data/lib/nanoc/filters.rb +59 -0
  96. data/lib/nanoc/filters/asciidoc.rb +38 -0
  97. data/lib/nanoc/filters/bluecloth.rb +19 -0
  98. data/lib/nanoc/filters/coderay.rb +21 -0
  99. data/lib/nanoc/filters/coffeescript.rb +20 -0
  100. data/lib/nanoc/filters/colorize_syntax.rb +298 -0
  101. data/lib/nanoc/filters/erb.rb +38 -0
  102. data/lib/nanoc/filters/erubis.rb +34 -0
  103. data/lib/nanoc/filters/haml.rb +27 -0
  104. data/lib/nanoc/filters/kramdown.rb +20 -0
  105. data/lib/nanoc/filters/less.rb +53 -0
  106. data/lib/nanoc/filters/markaby.rb +20 -0
  107. data/lib/nanoc/filters/maruku.rb +20 -0
  108. data/lib/nanoc/filters/mustache.rb +24 -0
  109. data/lib/nanoc/filters/rainpress.rb +19 -0
  110. data/lib/nanoc/filters/rdiscount.rb +22 -0
  111. data/lib/nanoc/filters/rdoc.rb +33 -0
  112. data/lib/nanoc/filters/redcarpet.rb +62 -0
  113. data/lib/nanoc/filters/redcloth.rb +47 -0
  114. data/lib/nanoc/filters/relativize_paths.rb +94 -0
  115. data/lib/nanoc/filters/rubypants.rb +20 -0
  116. data/lib/nanoc/filters/sass.rb +74 -0
  117. data/lib/nanoc/filters/slim.rb +25 -0
  118. data/lib/nanoc/filters/typogruby.rb +23 -0
  119. data/lib/nanoc/filters/uglify_js.rb +42 -0
  120. data/lib/nanoc/filters/xsl.rb +46 -0
  121. data/lib/nanoc/filters/yui_compressor.rb +23 -0
  122. data/lib/nanoc/helpers.rb +16 -0
  123. data/lib/nanoc/helpers/blogging.rb +319 -0
  124. data/lib/nanoc/helpers/breadcrumbs.rb +40 -0
  125. data/lib/nanoc/helpers/capturing.rb +138 -0
  126. data/lib/nanoc/helpers/filtering.rb +50 -0
  127. data/lib/nanoc/helpers/html_escape.rb +55 -0
  128. data/lib/nanoc/helpers/link_to.rb +151 -0
  129. data/lib/nanoc/helpers/rendering.rb +140 -0
  130. data/lib/nanoc/helpers/tagging.rb +71 -0
  131. data/lib/nanoc/helpers/text.rb +44 -0
  132. data/lib/nanoc/helpers/xml_sitemap.rb +76 -0
  133. data/lib/nanoc/tasks.rb +10 -0
  134. data/lib/nanoc/tasks/clean.rake +16 -0
  135. data/lib/nanoc/tasks/clean.rb +29 -0
  136. data/lib/nanoc/tasks/deploy/rsync.rake +16 -0
  137. data/lib/nanoc/tasks/validate.rake +92 -0
  138. data/nanoc.gemspec +49 -0
  139. data/tasks/doc.rake +16 -0
  140. data/tasks/test.rake +46 -0
  141. data/test/base/core_ext/array_spec.rb +73 -0
  142. data/test/base/core_ext/hash_spec.rb +98 -0
  143. data/test/base/core_ext/pathname_spec.rb +27 -0
  144. data/test/base/core_ext/string_spec.rb +37 -0
  145. data/test/base/test_checksum_store.rb +35 -0
  146. data/test/base/test_code_snippet.rb +31 -0
  147. data/test/base/test_compiler.rb +403 -0
  148. data/test/base/test_compiler_dsl.rb +161 -0
  149. data/test/base/test_context.rb +31 -0
  150. data/test/base/test_data_source.rb +46 -0
  151. data/test/base/test_dependency_tracker.rb +262 -0
  152. data/test/base/test_directed_graph.rb +288 -0
  153. data/test/base/test_filter.rb +83 -0
  154. data/test/base/test_item.rb +179 -0
  155. data/test/base/test_item_rep.rb +579 -0
  156. data/test/base/test_layout.rb +59 -0
  157. data/test/base/test_memoization.rb +90 -0
  158. data/test/base/test_notification_center.rb +34 -0
  159. data/test/base/test_outdatedness_checker.rb +394 -0
  160. data/test/base/test_plugin.rb +30 -0
  161. data/test/base/test_rule.rb +19 -0
  162. data/test/base/test_rule_context.rb +65 -0
  163. data/test/base/test_site.rb +190 -0
  164. data/test/cli/commands/test_compile.rb +33 -0
  165. data/test/cli/commands/test_create_item.rb +14 -0
  166. data/test/cli/commands/test_create_layout.rb +28 -0
  167. data/test/cli/commands/test_create_site.rb +24 -0
  168. data/test/cli/commands/test_deploy.rb +74 -0
  169. data/test/cli/commands/test_help.rb +12 -0
  170. data/test/cli/commands/test_info.rb +11 -0
  171. data/test/cli/commands/test_prune.rb +98 -0
  172. data/test/cli/commands/test_update.rb +10 -0
  173. data/test/cli/test_cli.rb +102 -0
  174. data/test/cli/test_error_handler.rb +29 -0
  175. data/test/cli/test_logger.rb +10 -0
  176. data/test/data_sources/test_filesystem.rb +433 -0
  177. data/test/data_sources/test_filesystem_unified.rb +536 -0
  178. data/test/data_sources/test_filesystem_verbose.rb +357 -0
  179. data/test/extra/core_ext/test_enumerable.rb +30 -0
  180. data/test/extra/core_ext/test_pathname.rb +17 -0
  181. data/test/extra/core_ext/test_time.rb +15 -0
  182. data/test/extra/deployers/test_fog.rb +67 -0
  183. data/test/extra/deployers/test_rsync.rb +100 -0
  184. data/test/extra/test_auto_compiler.rb +417 -0
  185. data/test/extra/test_file_proxy.rb +19 -0
  186. data/test/extra/test_vcs.rb +22 -0
  187. data/test/extra/validators/test_links.rb +62 -0
  188. data/test/extra/validators/test_w3c.rb +47 -0
  189. data/test/filters/test_asciidoc.rb +22 -0
  190. data/test/filters/test_bluecloth.rb +18 -0
  191. data/test/filters/test_coderay.rb +44 -0
  192. data/test/filters/test_coffeescript.rb +18 -0
  193. data/test/filters/test_colorize_syntax.rb +379 -0
  194. data/test/filters/test_erb.rb +105 -0
  195. data/test/filters/test_erubis.rb +78 -0
  196. data/test/filters/test_haml.rb +96 -0
  197. data/test/filters/test_kramdown.rb +18 -0
  198. data/test/filters/test_less.rb +113 -0
  199. data/test/filters/test_markaby.rb +24 -0
  200. data/test/filters/test_maruku.rb +18 -0
  201. data/test/filters/test_mustache.rb +25 -0
  202. data/test/filters/test_rainpress.rb +29 -0
  203. data/test/filters/test_rdiscount.rb +31 -0
  204. data/test/filters/test_rdoc.rb +18 -0
  205. data/test/filters/test_redcarpet.rb +73 -0
  206. data/test/filters/test_redcloth.rb +33 -0
  207. data/test/filters/test_relativize_paths.rb +533 -0
  208. data/test/filters/test_rubypants.rb +18 -0
  209. data/test/filters/test_sass.rb +229 -0
  210. data/test/filters/test_slim.rb +35 -0
  211. data/test/filters/test_typogruby.rb +21 -0
  212. data/test/filters/test_uglify_js.rb +30 -0
  213. data/test/filters/test_xsl.rb +105 -0
  214. data/test/filters/test_yui_compressor.rb +44 -0
  215. data/test/gem_loader.rb +11 -0
  216. data/test/helper.rb +207 -0
  217. data/test/helpers/test_blogging.rb +754 -0
  218. data/test/helpers/test_breadcrumbs.rb +81 -0
  219. data/test/helpers/test_capturing.rb +41 -0
  220. data/test/helpers/test_filtering.rb +106 -0
  221. data/test/helpers/test_html_escape.rb +32 -0
  222. data/test/helpers/test_link_to.rb +249 -0
  223. data/test/helpers/test_rendering.rb +89 -0
  224. data/test/helpers/test_tagging.rb +87 -0
  225. data/test/helpers/test_text.rb +24 -0
  226. data/test/helpers/test_xml_sitemap.rb +103 -0
  227. data/test/tasks/test_clean.rb +67 -0
  228. metadata +327 -15
  229. data/bin/nanoc-select +0 -86
  230. data/lib/nanoc-select.rb +0 -11
@@ -0,0 +1,135 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc
4
+
5
+ # An abstract superclass for classes that need to store data to the
6
+ # filesystem, such as checksums, cached compiled content and dependency
7
+ # graphs.
8
+ #
9
+ # Each store has a version number. When attempting to load data from a store
10
+ # that has an incompatible version number, no data will be loaded, but
11
+ # {#version_mismatch_detected} will be called.
12
+ #
13
+ # @abstract Subclasses must implement {#data} and {#data=}, and may
14
+ # implement {#no_data_found} and {#version_mismatch_detected}.
15
+ #
16
+ # @api private
17
+ class Store
18
+
19
+ # @return [String] The name of the file where data will be loaded from and
20
+ # stored to.
21
+ attr_reader :filename
22
+
23
+ # @return [Numeric] The version number corresponding to the file format
24
+ # the data is in. When the file format changes, the version number
25
+ # should be incremented.
26
+ attr_reader :version
27
+
28
+ # Creates a new store for the given filename.
29
+ #
30
+ # @param [String] filename The name of the file where data will be loaded
31
+ # from and stored to.
32
+ #
33
+ # @param [Numeric] version The version number corresponding to the file
34
+ # format the data is in. When the file format changes, the version
35
+ # number should be incremented.
36
+ def initialize(filename, version)
37
+ @filename = filename
38
+ @version = version
39
+ end
40
+
41
+ # @group Loading and storing data
42
+
43
+ # @return The data that should be written to the disk
44
+ #
45
+ # @abstract This method must be implemented by the subclass.
46
+ def data
47
+ raise NotImplementedError.new("Nanoc::Store subclasses must implement #data and #data=")
48
+ end
49
+
50
+ # @param new_data The data that has been loaded from the disk
51
+ #
52
+ # @abstract This method must be implemented by the subclass.
53
+ #
54
+ # @return [void]
55
+ def data=(new_data)
56
+ raise NotImplementedError.new("Nanoc::Store subclasses must implement #data and #data=")
57
+ end
58
+
59
+ # Loads the data from the filesystem into memory. This method will set the
60
+ # loaded data using the {#data=} method.
61
+ #
62
+ # @return [void]
63
+ def load
64
+ # Don’t load twice
65
+ if @loaded
66
+ return
67
+ end
68
+
69
+ # Check file existance
70
+ if !File.file?(self.filename)
71
+ no_data_found
72
+ @loaded = true
73
+ return
74
+ end
75
+
76
+ pstore.transaction do
77
+ # Check version
78
+ if pstore[:version] != self.version
79
+ version_mismatch_detected
80
+ @loaded = true
81
+ return
82
+ end
83
+
84
+ # Load
85
+ self.data = pstore[:data]
86
+ @loaded = true
87
+ end
88
+ end
89
+
90
+ # Undoes the effects of {#load}. Used when {#load} raises an exception.
91
+ #
92
+ # @api private
93
+ def unload
94
+ end
95
+
96
+ # Stores the data contained in memory to the filesystem. This method will
97
+ # use the {#data} method to fetch the data that should be written.
98
+ #
99
+ # @return [void]
100
+ def store
101
+ FileUtils.mkdir_p(File.dirname(self.filename))
102
+
103
+ pstore.transaction do
104
+ pstore[:data] = self.data
105
+ pstore[:version] = self.version
106
+ end
107
+ end
108
+
109
+ # @group Callback methods
110
+
111
+ # Callback method that is called when no data file was found. By default,
112
+ # this implementation does nothing, but it should probably be overridden
113
+ # by the subclass.
114
+ #
115
+ # @return [void]
116
+ def no_data_found
117
+ end
118
+
119
+ # Callback method that is called when a version mismatch is detected. By
120
+ # default, this implementation does nothing, but it should probably be
121
+ # overridden by the subclass.
122
+ #
123
+ # @return [void]
124
+ def version_mismatch_detected
125
+ end
126
+
127
+ private
128
+
129
+ def pstore
130
+ @pstore ||= PStore.new(self.filename)
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,137 @@
1
+ # encoding: utf-8
2
+
3
+ require 'cri'
4
+
5
+ module Nanoc::CLI
6
+
7
+ module Commands
8
+ end
9
+
10
+ autoload 'Logger', 'nanoc/cli/logger'
11
+ autoload 'CommandRunner', 'nanoc/cli/command_runner'
12
+ autoload 'ErrorHandler', 'nanoc/cli/error_handler'
13
+
14
+ # Deprecated; use CommandRunner instead
15
+ # TODO [in nanoc 4.0] remove me
16
+ autoload 'Command', 'nanoc/cli/command_runner'
17
+
18
+ # @return [Boolean] true if debug output is enabled, false if not
19
+ #
20
+ # @since 3.2.0
21
+ def self.debug?
22
+ @debug || false
23
+ end
24
+
25
+ # @param [Boolean] boolean true if debug output should be enabled,
26
+ # false if it should not
27
+ #
28
+ # @return [void]
29
+ #
30
+ # @since 3.2.0
31
+ def self.debug=(boolean)
32
+ @debug = boolean
33
+ end
34
+
35
+ # Invokes the nanoc commandline tool with the given arguments.
36
+ #
37
+ # @param [Array<String>] args An array of commandline arguments
38
+ #
39
+ # @return [void]
40
+ def self.run(args)
41
+ Nanoc::CLI::ErrorHandler.handle_while do
42
+ self.setup
43
+ self.load_custom_commands
44
+ self.root_command.run(args)
45
+ end
46
+ end
47
+
48
+ # Adds the given command to the collection of available commands.
49
+ #
50
+ # @param [Cri::Command] cmd The command to add
51
+ #
52
+ # @return [void]
53
+ def self.add_command(cmd)
54
+ self.root_command.add_command(cmd)
55
+ end
56
+
57
+ protected
58
+
59
+ # Makes the commandline interface ready for using by loading the commands.
60
+ #
61
+ # @return [void]
62
+ def self.setup
63
+ # Reinit
64
+ @root_command = nil
65
+
66
+ # Add help command
67
+ help_cmd = Cri::Command.new_basic_help
68
+ self.add_command(help_cmd)
69
+
70
+ # Add other commands
71
+ cmd_filenames = Dir[File.dirname(__FILE__) + '/cli/commands/*.rb']
72
+ cmd_filenames.each do |filename|
73
+ next if File.basename(filename, '.rb') == 'nanoc'
74
+ cmd = self.load_command_at(filename)
75
+ self.add_command(cmd)
76
+ end
77
+ end
78
+
79
+ # Loads the commands in `commands/`.
80
+ #
81
+ # @return [void]
82
+ def self.load_custom_commands
83
+ self.recursive_contents_of('commands').each do |filename|
84
+ # Create command
85
+ command = Nanoc::CLI.load_command_at(filename)
86
+
87
+ # Get supercommand
88
+ pieces = filename.gsub(/^commands\/|\.rb$/, '').split('/')
89
+ pieces = pieces[0, pieces.size-1] || []
90
+ root = Nanoc::CLI.root_command
91
+ supercommand = pieces.inject(root) do |cmd, piece|
92
+ cmd.nil? ? nil : cmd.command_named(piece)
93
+ end
94
+
95
+ # Add to supercommand
96
+ if supercommand.nil?
97
+ raise "Cannot load command at #{filename} because its supercommand cannot be found"
98
+ end
99
+ supercommand.add_command(command)
100
+ end
101
+ end
102
+
103
+ # Loads the command in the file with the given filename.
104
+ #
105
+ # @param [String] filename The name of the file that contains the command
106
+ #
107
+ # @return [Cri::Command] The loaded command
108
+ def self.load_command_at(filename, command_name=nil)
109
+ # Load
110
+ code = File.read(filename)
111
+ cmd = Cri::Command.define(code, filename)
112
+
113
+ # Set name
114
+ command_name ||= File.basename(filename, '.rb')
115
+ cmd.modify { name command_name }
116
+
117
+ # Done
118
+ cmd
119
+ end
120
+
121
+ # @return [Cri::Command] The root command, i.e. the commandline tool itself
122
+ def self.root_command
123
+ @root_command ||= begin
124
+ filename = File.dirname(__FILE__) + "/cli/commands/nanoc.rb"
125
+ self.load_command_at(filename)
126
+ end
127
+ end
128
+
129
+ # @return [Array] The directory contents
130
+ def self.recursive_contents_of(path)
131
+ return [] unless File.directory?(path)
132
+ files, dirs = *Dir[path + '/*'].sort.partition { |e| File.file?(e) }
133
+ dirs.each { |d| files.concat self.recursive_contents_of(d) }
134
+ files
135
+ end
136
+
137
+ end
@@ -0,0 +1,104 @@
1
+ # encoding: utf-8
2
+
3
+ module Nanoc::CLI
4
+
5
+ # A command runner subclass for nanoc commands that adds nanoc-specific
6
+ # convenience methods and error handling.
7
+ class CommandRunner < ::Cri::CommandRunner
8
+
9
+ # @see http://rubydoc.info/gems/cri/Cri/CommandRunner#call-instance_method
10
+ #
11
+ # @return [void]
12
+ def call
13
+ Nanoc::CLI::ErrorHandler.handle_while(:command => self) do
14
+ self.run
15
+ end
16
+ end
17
+
18
+ # Gets the site ({Nanoc::Site} instance) in the current directory and
19
+ # loads its data.
20
+ #
21
+ # @return [Nanoc::Site] The site in the current working directory
22
+ def site
23
+ # Load site if possible
24
+ @site ||= nil
25
+ if File.file?('config.yaml') && @site.nil?
26
+ begin
27
+ @site = Nanoc::Site.new('.')
28
+ rescue Nanoc::Errors::UnknownDataSource => e
29
+ $stderr.puts "Unknown data source: #{e}"
30
+ exit 1
31
+ end
32
+ end
33
+
34
+ @site
35
+ end
36
+
37
+ # @deprecated use `Cri::CommandDSL#runner`
38
+ #
39
+ # @see http://rubydoc.info/gems/cri/Cri/CommandDSL#runner-instance_method
40
+ def self.call(opts, args, cmd)
41
+ self.new(opts, args, cmd).call
42
+ end
43
+
44
+ protected
45
+
46
+ # @return [Boolean] true if debug output is enabled, false if not
47
+ #
48
+ # @see Nanoc::CLI.debug?
49
+ def debug?
50
+ Nanoc::CLI.debug?
51
+ end
52
+
53
+ # Asserts that the current working directory contains a site
54
+ # ({Nanoc::Site} instance). If no site is present, prints an error
55
+ # message and exits.
56
+ #
57
+ # @return [void]
58
+ def require_site
59
+ @site = nil
60
+ if site.nil?
61
+ $stderr.puts 'The current working directory does not seem to be a ' +
62
+ 'valid/complete nanoc site directory; aborting.'
63
+ exit 1
64
+ end
65
+ end
66
+
67
+ # Sets the data source's VCS to the VCS with the given name. Does nothing
68
+ # when the site's data source does not support VCSes (i.e. does not
69
+ # implement #vcs=).
70
+ #
71
+ # @param [String] vcs_name The name of the VCS that should be used
72
+ #
73
+ # @return [void]
74
+ def set_vcs(vcs_name)
75
+ # Skip if not possible
76
+ return if vcs_name.nil? || site.nil?
77
+
78
+ # Find VCS
79
+ vcs_class = Nanoc::Extra::VCS.named(vcs_name.to_sym)
80
+ if vcs_class.nil?
81
+ $stderr.puts "A VCS named #{vcs_name} was not found; aborting."
82
+ exit 1
83
+ end
84
+
85
+ site.data_sources.each do |data_source|
86
+ # Skip if not possible
87
+ next if !data_source.respond_to?(:vcs=)
88
+
89
+ # Set VCS
90
+ data_source.vcs = vcs_class.new
91
+ end
92
+ end
93
+
94
+ # @return [Array] The compilation stack.
95
+ def stack
96
+ (self.site && self.site.compiler.stack) || []
97
+ end
98
+
99
+ end
100
+
101
+ # @deprecated Use {Nanoc::CLI::CommandRunner} instead
102
+ Command = CommandRunner
103
+
104
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ usage 'autocompile [options]'
4
+ summary 'start the autocompiler'
5
+ aliases :aco
6
+ description <<-EOS
7
+ Start the autocompiler web server. Unless specified, the web server will run
8
+ on port 3000 and listen on all IP addresses. Running the autocompiler requires
9
+ the 'mime/types' and 'rack' gems.
10
+ EOS
11
+
12
+ required :H, :handler, 'specify the handler to use (webrick/mongrel/...)'
13
+ required :o, :host, 'specify the host to listen on (default: 0.0.0.0)'
14
+ required :p, :port, 'specify the port to listen on (default: 3000)'
15
+
16
+ module Nanoc::CLI::Commands
17
+
18
+ class AutoCompile < ::Nanoc::CLI::CommandRunner
19
+
20
+ def run
21
+ require 'rack'
22
+
23
+ # Make sure we are in a nanoc site directory
24
+ self.require_site
25
+
26
+ # Set options
27
+ options_for_rack = {
28
+ :Port => (options[:port] || 3000).to_i,
29
+ :Host => (options[:host] || '0.0.0.0')
30
+ }
31
+
32
+ # Guess which handler we should use
33
+ unless handler = Rack::Handler.get(options[:handler])
34
+ begin
35
+ handler = Rack::Handler::Mongrel
36
+ rescue LoadError => e
37
+ handler = Rack::Handler::WEBrick
38
+ end
39
+ end
40
+
41
+ # Build app
42
+ autocompiler = Nanoc::Extra::AutoCompiler.new('.')
43
+ app = Rack::Builder.new do
44
+ use Rack::CommonLogger, $stderr
45
+ use Rack::ShowExceptions
46
+ run autocompiler
47
+ end.to_app
48
+
49
+ # Run autocompiler
50
+ puts "Running on http://#{options_for_rack[:Host]}:#{options_for_rack[:Port]}/"
51
+ handler.run(app, options_for_rack)
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ runner Nanoc::CLI::Commands::AutoCompile
@@ -0,0 +1,297 @@
1
+ # encoding: utf-8
2
+
3
+ usage 'compile [options]'
4
+ summary 'compile items of this site'
5
+ description <<-EOS
6
+ Compile all items of the current site.
7
+
8
+ The compile command will show all items of the site as they are processed. The time spent compiling the item will be printed, as well as a status message, which can be one of the following:
9
+
10
+ CREATED - The compiled item did not yet exist and has been created
11
+
12
+ UPDATED - The compiled item did already exist and has been modified
13
+
14
+ IDENTICAL - The item was deemed outdated and has been recompiled, but the compiled version turned out to be identical to the already existing version
15
+
16
+ SKIP - The item was deemed not outdated and was therefore not recompiled
17
+
18
+ EOS
19
+
20
+ option :a, :all, '(ignored)'
21
+ option :f, :force, '(ignored)'
22
+
23
+ module Nanoc::CLI::Commands
24
+
25
+ class Compile < ::Nanoc::CLI::CommandRunner
26
+
27
+ def run
28
+ # Make sure we are in a nanoc site directory
29
+ puts "Loading site data..."
30
+ self.require_site
31
+
32
+ # Check presence of --all option
33
+ if options.has_key?(:all) || options.has_key?(:force)
34
+ $stderr.puts "Warning: the --force option (and its deprecated --all alias) are, as of nanoc 3.2, no longer supported and have no effect."
35
+ end
36
+
37
+ # Warn if trying to compile a single item
38
+ if arguments.size == 1
39
+ $stderr.puts '-' * 80
40
+ $stderr.puts 'Note: As of nanoc 3.2, it is no longer possible to compile a single item. When invoking the “compile” command, all items in the site will be compiled.'.make_compatible_with_env
41
+ $stderr.puts '-' * 80
42
+ end
43
+
44
+ # Give feedback
45
+ puts "Compiling site..."
46
+
47
+ # Initialize profiling stuff
48
+ time_before = Time.now
49
+ @rep_times = {}
50
+ @filter_times = {}
51
+ setup_notifications
52
+
53
+ # Prepare for generating diffs
54
+ setup_diffs
55
+
56
+ # Compile
57
+ self.site.compile
58
+
59
+ # Find reps
60
+ reps = self.site.items.map { |i| i.reps }.flatten
61
+
62
+ # Show skipped reps
63
+ reps.select { |r| !r.compiled? }.each do |rep|
64
+ rep.raw_paths.each do |snapshot_name, filename|
65
+ next if filename.nil?
66
+ duration = @rep_times[filename]
67
+ Nanoc::CLI::Logger.instance.file(:high, :skip, filename, duration)
68
+ end
69
+ end
70
+
71
+ # Stop diffing
72
+ teardown_diffs
73
+
74
+ # Prune
75
+ if self.site.config[:auto_prune]
76
+ Nanoc::Extra::Pruner.new(self.site).run
77
+ end
78
+
79
+ # Give general feedback
80
+ puts
81
+ puts "Site compiled in #{format('%.2f', Time.now - time_before)}s."
82
+
83
+ # Give detailed feedback
84
+ if options.has_key?(:verbose)
85
+ print_profiling_feedback(reps)
86
+ end
87
+ end
88
+
89
+ def setup_notifications
90
+ # Diff generation
91
+ require 'tempfile'
92
+ old_contents = {}
93
+ Nanoc::NotificationCenter.on(:will_write_rep) do |rep, snapshot|
94
+ path = rep.raw_path(:snapshot => snapshot)
95
+ old_contents[rep] = File.file?(path) ? File.read(path) : nil
96
+ end
97
+ Nanoc::NotificationCenter.on(:rep_written) do |rep, path, is_created, is_modified|
98
+ if !rep.binary? && self.site.config[:enable_output_diff]
99
+ new_contents = File.file?(path) ? File.read(path) : nil
100
+ if old_contents[rep] && new_contents
101
+ generate_diff_for(rep, old_contents[rep], new_contents)
102
+ end
103
+ end
104
+ end
105
+
106
+ # File notifications
107
+ Nanoc::NotificationCenter.on(:rep_written) do |rep, path, is_created, is_modified|
108
+ action = (is_created ? :create : (is_modified ? :update : :identical))
109
+ duration = Time.now - @rep_times[rep.raw_path] if @rep_times[rep.raw_path]
110
+ Nanoc::CLI::Logger.instance.file(:high, action, path, duration)
111
+ end
112
+
113
+ # Debug notifications
114
+ if self.debug?
115
+ Nanoc::NotificationCenter.on(:compilation_started) do |rep|
116
+ puts "*** Started compilation of #{rep.inspect}"
117
+ end
118
+ Nanoc::NotificationCenter.on(:compilation_ended) do |rep|
119
+ puts "*** Ended compilation of #{rep.inspect}"
120
+ puts
121
+ end
122
+ Nanoc::NotificationCenter.on(:compilation_failed) do |rep, e|
123
+ puts "*** Suspended compilation of #{rep.inspect}: #{e.message}"
124
+ end
125
+ Nanoc::NotificationCenter.on(:cached_content_used) do |rep|
126
+ puts "*** Used cached compiled content for #{rep.inspect} instead of recompiling"
127
+ end
128
+ Nanoc::NotificationCenter.on(:filtering_started) do |rep, filter_name|
129
+ puts "*** Started filtering #{rep.inspect} with #{filter_name}"
130
+ end
131
+ Nanoc::NotificationCenter.on(:filtering_ended) do |rep, filter_name|
132
+ puts "*** Ended filtering #{rep.inspect} with #{filter_name}"
133
+ end
134
+ Nanoc::NotificationCenter.on(:visit_started) do |item|
135
+ puts "*** Started visiting #{item.inspect}"
136
+ end
137
+ Nanoc::NotificationCenter.on(:visit_ended) do |item|
138
+ puts "*** Ended visiting #{item.inspect}"
139
+ end
140
+ Nanoc::NotificationCenter.on(:dependency_created) do |src, dst|
141
+ puts "*** Dependency created from #{src.inspect} onto #{dst.inspect}"
142
+ end
143
+ end
144
+
145
+ # Timing notifications
146
+ Nanoc::NotificationCenter.on(:compilation_started) do |rep|
147
+ @rep_times[rep.raw_path] = Time.now
148
+ end
149
+ Nanoc::NotificationCenter.on(:compilation_ended) do |rep|
150
+ @rep_times[rep.raw_path] = Time.now - @rep_times[rep.raw_path]
151
+ end
152
+ Nanoc::NotificationCenter.on(:filtering_started) do |rep, filter_name|
153
+ @filter_times[filter_name] ||= []
154
+ @filter_times[filter_name] << Time.now
155
+ start_filter_progress(rep, filter_name)
156
+ end
157
+ Nanoc::NotificationCenter.on(:filtering_ended) do |rep, filter_name|
158
+ @filter_times[filter_name] << Time.now - @filter_times[filter_name].pop
159
+ stop_filter_progress(rep, filter_name)
160
+ end
161
+ end
162
+
163
+ def setup_diffs
164
+ @diff_lock = Mutex.new
165
+ @diff_threads = []
166
+ FileUtils.rm('output.diff') if File.file?('output.diff')
167
+ end
168
+
169
+ def teardown_diffs
170
+ @diff_threads.each { |t| t.join }
171
+ end
172
+
173
+ def generate_diff_for(rep, old_content, new_content)
174
+ return if old_content == new_content
175
+
176
+ @diff_threads << Thread.new do
177
+ # Generate diff
178
+ diff = diff_strings(old_content, new_content)
179
+ diff.sub!(/^--- .*/, '--- ' + rep.raw_path)
180
+ diff.sub!(/^\+\+\+ .*/, '+++ ' + rep.raw_path)
181
+
182
+ # Write diff
183
+ @diff_lock.synchronize do
184
+ File.open('output.diff', 'a') { |io| io.write(diff) }
185
+ end
186
+ end
187
+ end
188
+
189
+ # TODO move this elsewhere
190
+ def diff_strings(a, b)
191
+ require 'open3'
192
+
193
+ # Create files
194
+ Tempfile.open('old') do |old_file|
195
+ Tempfile.open('new') do |new_file|
196
+ # Write files
197
+ old_file.write(a)
198
+ old_file.flush
199
+ new_file.write(b)
200
+ new_file.flush
201
+
202
+ # Diff
203
+ cmd = [ 'diff', '-u', old_file.path, new_file.path ]
204
+ Open3.popen3(*cmd) do |stdin, stdout, stderr|
205
+ result = stdout.read
206
+ return (result == '' ? nil : result)
207
+ end
208
+ end
209
+ end
210
+ rescue Errno::ENOENT
211
+ warn 'Failed to run `diff`, so no diff with the previously compiled ' \
212
+ 'content will be available.'
213
+ nil
214
+ end
215
+
216
+ def start_filter_progress(rep, filter_name)
217
+ # Only show progress on terminals
218
+ return if !$stdout.tty?
219
+
220
+ @progress_thread = Thread.new do
221
+ delay = 1.0
222
+ step = 0
223
+
224
+ text = "Running #{filter_name} filter… ".make_compatible_with_env
225
+
226
+ while !Thread.current[:stopped]
227
+ sleep 0.1
228
+
229
+ # Wait for a while before showing text
230
+ delay -= 0.1
231
+ next if delay > 0.05
232
+
233
+ # Print progress
234
+ $stdout.print text + %w( | / - \\ )[step] + "\r"
235
+ step = (step + 1) % 4
236
+ end
237
+
238
+ # Clear text
239
+ if delay < 0.05
240
+ $stdout.print ' ' * (text.length + 1 + 1) + "\r"
241
+ end
242
+ end
243
+ end
244
+
245
+ def stop_filter_progress(rep, filter_name)
246
+ # Only show progress on terminals
247
+ return if !$stdout.tty?
248
+
249
+ @progress_thread[:stopped] = true
250
+ end
251
+
252
+ def print_profiling_feedback(reps)
253
+ # Get max filter length
254
+ max_filter_name_length = @filter_times.keys.map { |k| k.to_s.size }.max
255
+ return if max_filter_name_length.nil?
256
+
257
+ # Print warning if necessary
258
+ if reps.any? { |r| !r.compiled? }
259
+ $stderr.puts
260
+ $stderr.puts "Warning: profiling information may not be accurate because " +
261
+ "some items were not compiled."
262
+ end
263
+
264
+ # Print header
265
+ puts
266
+ puts ' ' * max_filter_name_length + ' | count min avg max tot'
267
+ puts '-' * max_filter_name_length + '-+-----------------------------------'
268
+
269
+ @filter_times.to_a.sort_by { |r| r[1] }.each do |row|
270
+ # Extract data
271
+ filter_name, samples = *row
272
+
273
+ # Calculate stats
274
+ count = samples.size
275
+ min = samples.min
276
+ tot = samples.inject { |memo, i| memo + i}
277
+ avg = tot/count
278
+ max = samples.max
279
+
280
+ # Format stats
281
+ count = format('%4d', count)
282
+ min = format('%4.2f', min)
283
+ avg = format('%4.2f', avg)
284
+ max = format('%4.2f', max)
285
+ tot = format('%5.2f', tot)
286
+
287
+ # Output stats
288
+ filter_name = format("%#{max_filter_name_length}s", filter_name)
289
+ puts "#{filter_name} | #{count} #{min}s #{avg}s #{max}s #{tot}s"
290
+ end
291
+ end
292
+
293
+ end
294
+
295
+ end
296
+
297
+ runner Nanoc::CLI::Commands::Compile