nanoc-cli 4.11.13
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.
- checksums.yaml +7 -0
- data/NEWS.md +3 -0
- data/README.md +3 -0
- data/lib/nanoc-cli.rb +3 -0
- data/lib/nanoc/cli.rb +237 -0
- data/lib/nanoc/cli/ansi_string_colorizer.rb +30 -0
- data/lib/nanoc/cli/cleaning_stream.rb +160 -0
- data/lib/nanoc/cli/command_runner.rb +74 -0
- data/lib/nanoc/cli/commands/compile.rb +57 -0
- data/lib/nanoc/cli/commands/create-site.rb +257 -0
- data/lib/nanoc/cli/commands/nanoc.rb +42 -0
- data/lib/nanoc/cli/commands/prune.rb +49 -0
- data/lib/nanoc/cli/commands/shell.rb +57 -0
- data/lib/nanoc/cli/commands/show-data.rb +185 -0
- data/lib/nanoc/cli/commands/show-plugins.rb +97 -0
- data/lib/nanoc/cli/commands/view.rb +68 -0
- data/lib/nanoc/cli/compile_listeners/abstract.rb +58 -0
- data/lib/nanoc/cli/compile_listeners/aggregate.rb +50 -0
- data/lib/nanoc/cli/compile_listeners/debug_printer.rb +100 -0
- data/lib/nanoc/cli/compile_listeners/diff_generator.rb +101 -0
- data/lib/nanoc/cli/compile_listeners/file_action_printer.rb +80 -0
- data/lib/nanoc/cli/compile_listeners/timing_recorder.rb +170 -0
- data/lib/nanoc/cli/error_handler.rb +365 -0
- data/lib/nanoc/cli/logger.rb +77 -0
- data/lib/nanoc/cli/stack_trace_writer.rb +51 -0
- data/lib/nanoc/cli/stream_cleaners/abstract.rb +23 -0
- data/lib/nanoc/cli/stream_cleaners/ansi_colors.rb +15 -0
- data/lib/nanoc/cli/stream_cleaners/utf8.rb +20 -0
- data/lib/nanoc/cli/transform.rb +18 -0
- data/lib/nanoc/cli/version.rb +7 -0
- metadata +127 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
usage 'shell'
|
4
|
+
summary 'open a shell on the Nanoc environment'
|
5
|
+
aliases 'console', 'sh'
|
6
|
+
description "
|
7
|
+
Open an IRB shell on a context that contains @items, @layouts, and @config.
|
8
|
+
"
|
9
|
+
flag :p, :preprocess, 'run preprocessor'
|
10
|
+
no_params
|
11
|
+
|
12
|
+
module Nanoc::CLI::Commands
|
13
|
+
class Shell < ::Nanoc::CLI::CommandRunner
|
14
|
+
def run
|
15
|
+
require 'pry'
|
16
|
+
|
17
|
+
# Needed to make pry behave properly sometimes -- see nanoc/nanoc#1309
|
18
|
+
Signal.trap('SIGINT') { raise Interrupt }
|
19
|
+
|
20
|
+
@site = load_site
|
21
|
+
Nanoc::Core::Compiler.new_for(@site).run_until_preprocessed if options[:preprocess]
|
22
|
+
|
23
|
+
Nanoc::Core::Context.new(env).pry
|
24
|
+
end
|
25
|
+
|
26
|
+
def env
|
27
|
+
self.class.env_for_site(@site)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.reps_for(site)
|
31
|
+
Nanoc::Core::ItemRepRepo.new.tap do |reps|
|
32
|
+
action_provider = Nanoc::Core::ActionProvider.named(site.config.action_provider).for(site)
|
33
|
+
builder = Nanoc::Core::ItemRepBuilder.new(site, action_provider, reps)
|
34
|
+
builder.run
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.view_context_for(site)
|
39
|
+
Nanoc::Core::ViewContextForShell.new(
|
40
|
+
items: site.items,
|
41
|
+
reps: reps_for(site),
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.env_for_site(site)
|
46
|
+
view_context = view_context_for(site)
|
47
|
+
|
48
|
+
{
|
49
|
+
items: Nanoc::Core::ItemCollectionWithRepsView.new(site.items, view_context),
|
50
|
+
layouts: Nanoc::Core::LayoutCollectionView.new(site.layouts, view_context),
|
51
|
+
config: Nanoc::Core::ConfigView.new(site.config, view_context),
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
runner Nanoc::CLI::Commands::Shell
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
usage 'show-data'
|
4
|
+
aliases :debug
|
5
|
+
summary 'show data in this site'
|
6
|
+
description <<~EOS
|
7
|
+
Show information about all items, item representations and layouts in the
|
8
|
+
current site, along with dependency information.
|
9
|
+
EOS
|
10
|
+
no_params
|
11
|
+
|
12
|
+
module Nanoc::CLI::Commands
|
13
|
+
class ShowData < ::Nanoc::CLI::CommandRunner
|
14
|
+
def run
|
15
|
+
site = load_site
|
16
|
+
res = Nanoc::Core::Compiler.new_for(site).run_until_precompiled
|
17
|
+
|
18
|
+
items = site.items
|
19
|
+
layouts = site.layouts
|
20
|
+
reps = res.fetch(:reps)
|
21
|
+
dependency_store = res.fetch(:dependency_store)
|
22
|
+
outdatedness_checker = res.fetch(:outdatedness_checker)
|
23
|
+
|
24
|
+
# Print data
|
25
|
+
print_item_dependencies(items, dependency_store)
|
26
|
+
print_item_rep_paths(items, reps)
|
27
|
+
print_item_rep_outdatedness(items, outdatedness_checker, reps)
|
28
|
+
print_layouts(layouts, outdatedness_checker)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def sorted_with_prev(objects)
|
34
|
+
prev = nil
|
35
|
+
objects.sort_by(&:identifier).each do |object|
|
36
|
+
yield(object, prev)
|
37
|
+
prev = object
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def sorted_reps_with_prev(items, reps)
|
42
|
+
prev = nil
|
43
|
+
items.sort_by(&:identifier).each do |item|
|
44
|
+
reps[item].sort_by { |r| r.name.to_s }.each do |rep|
|
45
|
+
yield(rep, prev)
|
46
|
+
prev = rep
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def print_header(title)
|
52
|
+
header = '=' * 78
|
53
|
+
header[3..(title.length + 5)] = " #{title} "
|
54
|
+
|
55
|
+
puts
|
56
|
+
puts header
|
57
|
+
puts
|
58
|
+
end
|
59
|
+
|
60
|
+
def print_item_dependencies(items, dependency_store)
|
61
|
+
print_header('Item dependencies')
|
62
|
+
|
63
|
+
puts 'Legend:'
|
64
|
+
puts ' r = dependency on raw content'
|
65
|
+
puts ' a = dependency on attributes'
|
66
|
+
puts ' c = dependency on compiled content'
|
67
|
+
puts ' p = dependency on the path'
|
68
|
+
puts
|
69
|
+
|
70
|
+
sorter =
|
71
|
+
lambda do |dep|
|
72
|
+
case dep
|
73
|
+
when Nanoc::Core::Document
|
74
|
+
dep.from.identifier.to_s
|
75
|
+
else
|
76
|
+
''
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
sorted_with_prev(items) do |item, prev|
|
81
|
+
puts if prev
|
82
|
+
puts "item #{item.identifier} depends on:"
|
83
|
+
dependencies =
|
84
|
+
dependency_store
|
85
|
+
.dependencies_causing_outdatedness_of(item)
|
86
|
+
.sort_by(&sorter)
|
87
|
+
dependencies.each do |dep|
|
88
|
+
pred = dep.from
|
89
|
+
|
90
|
+
type =
|
91
|
+
case pred
|
92
|
+
when Nanoc::Core::Layout
|
93
|
+
'layout'
|
94
|
+
when Nanoc::Core::Item
|
95
|
+
'item'
|
96
|
+
when Nanoc::Core::Configuration
|
97
|
+
'config'
|
98
|
+
when Nanoc::Core::ItemCollection
|
99
|
+
'items'
|
100
|
+
when Nanoc::Core::LayoutCollection
|
101
|
+
'layouts'
|
102
|
+
else
|
103
|
+
raise Nanoc::Core::Errors::InternalInconsistency, "unexpected pred type #{pred}"
|
104
|
+
end
|
105
|
+
|
106
|
+
pred_identifier =
|
107
|
+
case pred
|
108
|
+
when Nanoc::Core::Document
|
109
|
+
pred.identifier.to_s
|
110
|
+
when Nanoc::Core::Configuration
|
111
|
+
nil
|
112
|
+
when Nanoc::Core::IdentifiableCollection
|
113
|
+
case dep.props.raw_content
|
114
|
+
when true
|
115
|
+
'matching any'
|
116
|
+
else
|
117
|
+
"matching any of #{dep.props.raw_content.sort.join(', ')}"
|
118
|
+
end
|
119
|
+
else
|
120
|
+
raise Nanoc::Core::Errors::InternalInconsistency, "unexpected pred type #{pred}"
|
121
|
+
end
|
122
|
+
|
123
|
+
if pred
|
124
|
+
puts " [ #{format '%6s', type} ] (#{dep.props}) #{pred_identifier}"
|
125
|
+
else
|
126
|
+
puts ' ( removed item )'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
puts ' (nothing)' if dependencies.empty?
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def print_item_rep_paths(items, reps)
|
134
|
+
print_header('Item representation paths')
|
135
|
+
|
136
|
+
sorted_reps_with_prev(items, reps) do |rep, prev|
|
137
|
+
puts if prev
|
138
|
+
puts "item #{rep.item.identifier}, rep #{rep.name}:"
|
139
|
+
if rep.raw_paths.empty?
|
140
|
+
puts ' (not written)'
|
141
|
+
end
|
142
|
+
length = rep.raw_paths.keys.map { |s| s.to_s.length }.max
|
143
|
+
rep.raw_paths.each do |snapshot_name, raw_paths|
|
144
|
+
raw_paths.each do |raw_path|
|
145
|
+
puts format(" [ %-#{length}s ] %s", snapshot_name, raw_path)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def print_item_rep_outdatedness(items, outdatedness_checker, reps)
|
152
|
+
print_header('Item representation outdatedness')
|
153
|
+
|
154
|
+
sorted_reps_with_prev(items, reps) do |rep, prev|
|
155
|
+
puts if prev
|
156
|
+
puts "item #{rep.item.identifier}, rep #{rep.name}:"
|
157
|
+
print_outdatedness_reasons_for(rep, outdatedness_checker)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def print_layouts(layouts, outdatedness_checker)
|
162
|
+
print_header('Layouts')
|
163
|
+
|
164
|
+
sorted_with_prev(layouts) do |layout, prev|
|
165
|
+
puts if prev
|
166
|
+
puts "layout #{layout.identifier}:"
|
167
|
+
print_outdatedness_reasons_for(layout, outdatedness_checker)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def print_outdatedness_reasons_for(obj, outdatedness_checker)
|
172
|
+
reasons = outdatedness_checker.outdatedness_reasons_for(obj)
|
173
|
+
if reasons.any?
|
174
|
+
puts ' is outdated:'
|
175
|
+
reasons.each do |reason|
|
176
|
+
puts " - #{reason.message}"
|
177
|
+
end
|
178
|
+
else
|
179
|
+
puts ' is not outdated'
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
runner Nanoc::CLI::Commands::ShowData
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
summary 'show all available plugins'
|
4
|
+
aliases :info
|
5
|
+
usage 'show-plugins [options]'
|
6
|
+
description <<~EOS
|
7
|
+
Show a list of available plugins, including filters and data sources.
|
8
|
+
If the current directory contains a Nanoc web site, the plugins defined in this site will be shown as well.
|
9
|
+
EOS
|
10
|
+
no_params
|
11
|
+
|
12
|
+
module Nanoc::CLI::Commands
|
13
|
+
class ShowPlugins < ::Nanoc::CLI::CommandRunner
|
14
|
+
# rubocop:disable Style/MutableConstant
|
15
|
+
# These constants are intended to be mutated (through #add_plugin_class)
|
16
|
+
|
17
|
+
PLUGIN_CLASS_ORDER = [
|
18
|
+
Nanoc::Core::Filter,
|
19
|
+
Nanoc::Core::DataSource,
|
20
|
+
]
|
21
|
+
|
22
|
+
PLUGIN_CLASSES = {
|
23
|
+
Nanoc::Core::Filter => 'Filters',
|
24
|
+
Nanoc::Core::DataSource => 'Data Sources',
|
25
|
+
}
|
26
|
+
|
27
|
+
# rubocop:enable Style/MutableConstant
|
28
|
+
|
29
|
+
def run
|
30
|
+
# Get list of plugins (before and after)
|
31
|
+
plugins_before = PLUGIN_CLASSES.keys.each_with_object({}) { |c, acc| acc[c] = c.all }
|
32
|
+
site = load_site
|
33
|
+
site&.code_snippets
|
34
|
+
plugins_after = PLUGIN_CLASSES.keys.each_with_object({}) { |c, acc| acc[c] = c.all }
|
35
|
+
|
36
|
+
# Divide list of plugins into builtin and custom
|
37
|
+
plugins_builtin = plugins_before
|
38
|
+
plugins_custom = plugins_after.each_with_object({}) do |(superclass, klasses), acc|
|
39
|
+
acc[superclass] = klasses - plugins_before[superclass]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Find max identifiers length
|
43
|
+
all_identifiers = plugins_after.values.flatten.map(&:identifiers)
|
44
|
+
max_identifiers_length = all_identifiers.map(&:to_s).map(&:size).max
|
45
|
+
|
46
|
+
PLUGIN_CLASS_ORDER.each do |superclass|
|
47
|
+
plugins_with_this_superclass = {
|
48
|
+
builtin: plugins_builtin.fetch(superclass, []),
|
49
|
+
custom: plugins_custom.fetch(superclass, []),
|
50
|
+
}
|
51
|
+
|
52
|
+
# Print kind
|
53
|
+
kind = name_for_plugin_class(superclass)
|
54
|
+
puts "#{kind}:"
|
55
|
+
puts
|
56
|
+
|
57
|
+
# Print plugins organised by subtype
|
58
|
+
%i[builtin custom].each do |type|
|
59
|
+
# Find relevant plugins
|
60
|
+
relevant_plugins = plugins_with_this_superclass[type]
|
61
|
+
|
62
|
+
# Print type
|
63
|
+
puts " #{type}:"
|
64
|
+
if relevant_plugins.empty?
|
65
|
+
puts ' (none)'
|
66
|
+
next
|
67
|
+
end
|
68
|
+
|
69
|
+
# Print plugins
|
70
|
+
relevant_plugins.sort_by { |k| k.identifiers.join(', ') }.each do |plugin|
|
71
|
+
# Display
|
72
|
+
puts format(
|
73
|
+
" %-#{max_identifiers_length}s (%s)",
|
74
|
+
plugin.identifiers.join(', '),
|
75
|
+
plugin.to_s.sub(/^::/, ''),
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
puts
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.add_plugin_class(klass, name)
|
85
|
+
PLUGIN_CLASS_ORDER << klass
|
86
|
+
PLUGIN_CLASSES[klass] = name
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def name_for_plugin_class(klass)
|
92
|
+
PLUGIN_CLASSES[klass.to_s]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
runner Nanoc::CLI::Commands::ShowPlugins
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
usage 'view [options]'
|
4
|
+
summary 'start the web server that serves static files'
|
5
|
+
description <<~EOS
|
6
|
+
Start the static web server. Unless specified, the web server will run on port
|
7
|
+
3000 and listen on all IP addresses. Running this static web server requires
|
8
|
+
`adsf` (not `asdf`!).
|
9
|
+
EOS
|
10
|
+
|
11
|
+
required :H, :handler, 'specify the handler to use (webrick/mongrel/...)'
|
12
|
+
required :o, :host, 'specify the host to listen on (default: 127.0.0.1)', default: '127.0.0.1'
|
13
|
+
required :p, :port, 'specify the port to listen on (default: 3000)', transform: Nanoc::CLI::Transform::Port, default: 3000
|
14
|
+
flag :L, :'live-reload', 'reload on changes'
|
15
|
+
no_params
|
16
|
+
|
17
|
+
module Nanoc::CLI::Commands
|
18
|
+
class View < ::Nanoc::CLI::CommandRunner
|
19
|
+
DEFAULT_HANDLER_NAME = :thin
|
20
|
+
|
21
|
+
def run
|
22
|
+
load_adsf
|
23
|
+
|
24
|
+
config = Nanoc::Core::ConfigLoader.new.new_from_cwd
|
25
|
+
|
26
|
+
# Create output dir so that viewer/watcher doesn’t explode.
|
27
|
+
FileUtils.mkdir_p(config.output_dir)
|
28
|
+
|
29
|
+
server =
|
30
|
+
Adsf::Server.new(
|
31
|
+
root: File.absolute_path(config.output_dir),
|
32
|
+
live: options[:'live-reload'],
|
33
|
+
index_filenames: config[:index_filenames],
|
34
|
+
host: options[:host],
|
35
|
+
port: options[:port],
|
36
|
+
handler: options[:handler],
|
37
|
+
)
|
38
|
+
|
39
|
+
server.run
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def load_adsf
|
45
|
+
# Load adsf
|
46
|
+
begin
|
47
|
+
require 'adsf'
|
48
|
+
return
|
49
|
+
rescue LoadError
|
50
|
+
$stderr.puts "Could not find the required 'adsf' gem, " \
|
51
|
+
'which is necessary for the view command.'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Check asdf
|
55
|
+
begin
|
56
|
+
require 'asdf'
|
57
|
+
$stderr.puts "You appear to have 'asdf' installed, " \
|
58
|
+
"but not 'adsf'. Please install 'adsf' (check the spelling)!"
|
59
|
+
rescue LoadError
|
60
|
+
end
|
61
|
+
|
62
|
+
# Done
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
runner Nanoc::CLI::Commands::View
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Nanoc::CLI::CompileListeners
|
4
|
+
class Abstract
|
5
|
+
def initialize(*)
|
6
|
+
super()
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.enable_for?(command_runner, site) # rubocop:disable Lint/UnusedMethodArgument
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
# @abstract
|
14
|
+
def start
|
15
|
+
raise NotImplementedError, "Subclasses of #{self.class} must implement #start"
|
16
|
+
end
|
17
|
+
|
18
|
+
# @abstract
|
19
|
+
def stop; end
|
20
|
+
|
21
|
+
def wrapped_start
|
22
|
+
@_notification_names = []
|
23
|
+
start
|
24
|
+
end
|
25
|
+
|
26
|
+
def wrapped_stop
|
27
|
+
stop
|
28
|
+
|
29
|
+
Nanoc::Core::NotificationCenter.sync
|
30
|
+
|
31
|
+
@_notification_names.each do |name|
|
32
|
+
Nanoc::Core::NotificationCenter.remove(name, self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def run_while
|
37
|
+
wrapped_start
|
38
|
+
yield
|
39
|
+
ensure
|
40
|
+
wrapped_stop
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_safely
|
44
|
+
wrapped_start
|
45
|
+
@_started = true
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop_safely
|
49
|
+
wrapped_stop if @_started
|
50
|
+
@_started = false
|
51
|
+
end
|
52
|
+
|
53
|
+
def on(sym)
|
54
|
+
@_notification_names << sym
|
55
|
+
Nanoc::Core::NotificationCenter.on(sym, self) { |*args| yield(*args) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|