asciidoctor-reducer 1.0.0.alpha.6 → 1.0.0.alpha.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30089ea8ab059ef848713ada56dd9457f4d6681c80de8028b92a9d02b21b0544
4
- data.tar.gz: f38c68d1f15627a4d74fd5376fb3ec9a5b6ad29345c10ea882ff988c875922a9
3
+ metadata.gz: 80723a5097c54761a2bee3b3ea88f827c88614b7a5572a6b5f1b10cb7af60c8c
4
+ data.tar.gz: d6f23a15e6dae8db02f5810d22018f537af9d6e67ac8362dbc2fe3155417df2d
5
5
  SHA512:
6
- metadata.gz: a3ec843b92513164762d9647d963238073bff89c66619205166b26199a23e65d85ee5aaf23c2a60c953eae52d17ed36cbb7499dd7afb2040692912065171db12
7
- data.tar.gz: df40e3f0c244ed21ea052536844df17f536c84a12252008d1c7dbdc04ad85ec00f3aabae81242550eda9a394497db386bd12b34f5110f323f61a8bdbc8ec30c6
6
+ metadata.gz: db4173fd2899008fec759e9d76f2732a7f5f7ca0a284f5c66a93040b9fca81dba22cf151e77e86b2b73c535545cec4b3a54c88407324514811260832b8ed1c7d
7
+ data.tar.gz: ec7a7d27cb1b87dcb0772af642f185002f7348e0a540c40bf3a6e98334d7be0b088b8b00312ca10dd5470cdaff60007e70c2d27708d97c4677c7a3302694473e
data/CHANGELOG.adoc CHANGED
@@ -4,6 +4,76 @@
4
4
  This document provides a high-level view of the changes to the Asciidoctor Reducer by release.
5
5
  For a detailed view of what has changed, refer to the {url-repo}/commits/main[commit history] on GitHub.
6
6
 
7
+ == 1.0.0.alpha.9 (2022-04-21) - @mojavelinux
8
+
9
+ === Added
10
+
11
+ * Add `Asciidoctor::Reducer::IncludeMapper` auxiliary extension, required by `asciidoctor/reducer/include_mapper/extension` (#26)
12
+ * Register `Asciidoctor::Reducer::IncludeMapper` extension when `asciidoctor/reducer/include_mapper` is required (#26)
13
+ * Add `Asciidoctor::Reducer::Extensions.key` method that returns key for registering extension group
14
+ * Update help text to note that the `-a` and `-r` CLI options may be specified multiple times
15
+ * Automate the release process
16
+
17
+ === Changed
18
+
19
+ * Rename x_include_replacements attr on reader to include_replacements since it's public
20
+ * Don't pass `:to` option to `Asciidoctor.load_file`
21
+ * Make `Asciidoctor::Reducer::Cli` a module instead of a class
22
+
23
+ === Fixed
24
+
25
+ * Replace remote include with link if `allow-uri-read` attribute is not set
26
+ * Don't raise error if `Asciidoctor::Reducer::Extensions.unregister` is called when extensions are not registered globally
27
+ * Ensure output is written to file with universal newlines (\n) on Windows
28
+
29
+ === Details
30
+
31
+ {url-repo}/releases/tag/v1.0.0.alpha.9[git tag] | {url-repo}/compare/v1.0.0.alpha.8\...v1.0.0.alpha.9[full diff]
32
+
33
+ == 1.0.0.alpha.8 (2022-02-23) - @mojavelinux
34
+
35
+ === Added
36
+
37
+ * Add secure mode as value of `-S` CLI option (#31)
38
+ * Add `--trace` option to CLI to trace cause of application errors (#29)
39
+
40
+ === Changed
41
+
42
+ * Replace include directive with link macro if safe mode is secure (#31)
43
+ * Track line numbers in include replacements using 1-based index
44
+ * Only mix in preprocessor conditional tracker if `:preserve_conditionals` option is not set (#36)
45
+
46
+ === Fixed
47
+
48
+ * Handle signals gracefully (#33)
49
+
50
+ === Details
51
+
52
+ {url-repo}/releases/tag/v1.0.0.alpha.8[git tag]
53
+
54
+ == 1.0.0.alpha.7 (2022-02-14) - @mojavelinux
55
+
56
+ === Added
57
+
58
+ * Add asciidoctor/reducer/api to require main API (#3)
59
+ * Add `Asciidoctor::Reducer.reduce` and `Asciidoctor::Reducer.reduce_file` API methods (#3)
60
+ * Add asciidoctor/reducer/extensions to require extensions API (#3)
61
+ * Add `Asciidoctor::Reducer::Extensions` API (#3)
62
+
63
+ === Changed
64
+
65
+ * Scope extensions to single call instead of registering them globally (#3)
66
+ * Use `:safe` as the default safe mode when using the API
67
+ * Make `CurrentPosition` module private to the `PreprocessorDirectiveTracker` module
68
+
69
+ === Fixed
70
+
71
+ * Require asciidoctor/reducer/version automatically when `Asciidoctor::Reducer::VERSION` is accessed
72
+
73
+ === Details
74
+
75
+ {url-repo}/releases/tag/v1.0.0.alpha.7[git tag]
76
+
7
77
  == 1.0.0.alpha.6 (2022-02-10) - @mojavelinux
8
78
 
9
79
  === Added
@@ -24,6 +94,10 @@ For a detailed view of what has changed, refer to the {url-repo}/commits/main[co
24
94
  * Prevent custom extension registry from activating extensions twice during reload (#21)
25
95
  * Retain includes table in document catalog when reloading document (#23)
26
96
 
97
+ === Details
98
+
99
+ {url-repo}/releases/tag/v1.0.0.alpha.6[git tag]
100
+
27
101
  == 1.0.0.alpha.5 (2022-02-06) - @mojavelinux
28
102
 
29
103
  === Changed
@@ -37,12 +111,20 @@ For a detailed view of what has changed, refer to the {url-repo}/commits/main[co
37
111
 
38
112
  * Suppress log messages when reloading document (#14)
39
113
 
114
+ === Details
115
+
116
+ {url-repo}/releases/tag/v1.0.0.alpha.5[git tag]
117
+
40
118
  == 1.0.0.alpha.4 (2022-02-03) - @mojavelinux
41
119
 
42
120
  === Fixed
43
121
 
44
122
  * Fix replacement of nested empty and unresolved includes
45
123
 
124
+ === Details
125
+
126
+ {url-repo}/releases/tag/v1.0.0.alpha.4[git tag]
127
+
46
128
  == 1.0.0.alpha.3 (2022-02-02) - @mojavelinux
47
129
 
48
130
  === Changed
@@ -52,6 +134,10 @@ For a detailed view of what has changed, refer to the {url-repo}/commits/main[co
52
134
  * Only reload document if source lines have changed; otherwise, update source lines on reader directly
53
135
  * Change default safe mode for CLI to :unsafe
54
136
 
137
+ === Details
138
+
139
+ {url-repo}/releases/tag/v1.0.0.alpha.3[git tag]
140
+
55
141
  == 1.0.0.alpha.2 (2022-01-27) - @mojavelinux
56
142
 
57
143
  === Added
@@ -71,30 +157,14 @@ For a detailed view of what has changed, refer to the {url-repo}/commits/main[co
71
157
 
72
158
  * Preserve return value when overridding `preprocess_include_directive` method
73
159
 
74
- == 1.0.0.alpha.1 (2022-01-12) - @mojavelinux
75
-
76
- Initial release.
77
-
78
- === Details
79
-
80
- {url-repo}/releases/tag/v1.0.0.alpha.1[git tag]
81
-
82
160
  === Details
83
161
 
84
162
  {url-repo}/releases/tag/v1.0.0.alpha.2[git tag]
85
163
 
86
- === Details
87
-
88
- {url-repo}/releases/tag/v1.0.0.alpha.3[git tag]
89
-
90
- === Details
91
-
92
- {url-repo}/releases/tag/v1.0.0.alpha.4[git tag]
93
-
94
- === Details
164
+ == 1.0.0.alpha.1 (2022-01-12) - @mojavelinux
95
165
 
96
- {url-repo}/releases/tag/v1.0.0.alpha.5[git tag]
166
+ Initial release.
97
167
 
98
168
  === Details
99
169
 
100
- {url-repo}/releases/tag/v1.0.0.alpha.6[git tag]
170
+ {url-repo}/releases/tag/v1.0.0.alpha.1[git tag]
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (C) 2021 Dan Allen
3
+ Copyright (C) 2021-present Dan Allen
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.adoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = {project-name}
2
2
  Dan Allen <https://github.com/mojavelinux[@mojavelinux]>
3
- v1.0.0.alpha.6, 2022-02-10
3
+ v1.0.0.alpha.9, 2022-04-21
4
4
  :idprefix:
5
5
  :idseparator: -
6
6
  ifndef::env-github[:icons: font]
@@ -16,16 +16,16 @@ endif::[]
16
16
  :url-rvm: https://rvm.io
17
17
  :url-repo: https://github.com/asciidoctor/{project-handle}
18
18
 
19
- {project-name} is a tool that reduces a composite AsciiDoc document containing includes to a single AsciiDoc document by expanding any includes reachable from the parent document.
20
- The tool also applies preprocessor conditionals (unless the option to preserve them is specified), leaving behind only the enabled lines.
19
+ {project-name} is a tool that reduces an AsciiDoc document that contains includes to a single AsciiDoc document by expanding any includes reachable from the parent document.
20
+ The tool also applies preprocessor conditionals (unless the option to preserve them is specified), leaving behind only the selected lines.
21
21
  If the document does not contain any preprocessor directives, the tool returns the original source.
22
22
 
23
23
  == Prerequisites
24
24
 
25
- {project-name} is a Ruby program that you install using Ruby packaging.
25
+ {project-name} is a Ruby application that you install using Ruby packaging.
26
26
  To install and run {project-name}, you need Ruby 2.5 or better.
27
27
 
28
- Run the following command to check whether you have Ruby installed and to check which version:
28
+ Run the following command to check which version of Ruby you have installed, if any:
29
29
 
30
30
  $ ruby -v
31
31
 
@@ -41,7 +41,9 @@ You can install the latest version of the gem using the following command:
41
41
  $ gem install asciidoctor-reducer --pre
42
42
 
43
43
  Installing this gem makes the `asciidoctor-reducer` command available on your $PATH.
44
- You can also require the gem into the Ruby runtime to use it as an Asciidoctor extension.
44
+ You can also require the gem into the Ruby runtime to use it as a library or Asciidoctor extension.
45
+
46
+ === Project-scoped
45
47
 
46
48
  If you prefer to manage the application as a project-scoped dependency, you can declare the gem in the project's [.path]_Gemfile_:
47
49
 
@@ -61,7 +63,7 @@ Installing the gem this way makes the `bundle exec asciidoctor-reducer` command
61
63
 
62
64
  == Usage
63
65
 
64
- === As command
66
+ === Command
65
67
 
66
68
  You can run this tool using the provided command (i.e., CLI), named `asciidoctor-reducer`.
67
69
  To learn how to use the command, and to verify it's available, run the command with the `-h` option:
@@ -75,7 +77,7 @@ asciidoctor-reducer [OPTION]... FILE
75
77
  ....
76
78
 
77
79
  The argument `FILE` is the AsciiDoc file you want to reduce.
78
- The options, represented by `[OPTION]...`, are optional, as the name suggestions.
80
+ The options, represented by `+[OPTION]...+`, are optional, as the name suggestions.
79
81
 
80
82
  Thus, to use the command, pass the AsciiDoc file as the sole argument:
81
83
 
@@ -91,27 +93,26 @@ To use the command in this way, pass `-` as the first argument:
91
93
 
92
94
  $ cat input.adoc | asciidoctor-reducer -
93
95
 
94
- To write the output to a file instead of stdout, also specify an output file using the `-o` option:
96
+ To write the output to a file in this case, specify an output file using the `-o` option:
95
97
 
96
98
  $ cat input.adoc | asciidoctor-reducer -o output.adoc -
97
99
 
98
- Note that top-level include files in the input AsciiDoc document are resolved relative to current working directory.
99
-
100
- === As extension
100
+ === API
101
101
 
102
- You can use this tool as an Asciidoctor extension when using the Asciidoctor API.
103
- To do so, require it before calling the `Asciidoctor.load` method.
102
+ You can also use this tool from a Ruby application using the provided API.
103
+ To begin, require the API for this library.
104
104
 
105
105
  [,ruby]
106
106
  ----
107
- require 'asciidoctor/reducer'
107
+ require 'asciidoctor/reducer/api'
108
108
  ----
109
109
 
110
- Next, load a parent document that contains includes.
110
+ Next, reduce a parent document that contains includes.
111
+ This works without having to specify the safe mode since the default safe mode when using the API is `:safe`.
111
112
 
112
113
  [,ruby]
113
114
  ----
114
- doc = Asciidoctor.load_file 'sample.adoc', safe: :safe
115
+ doc = Asciidoctor::Reducer.reduce_file 'sample.adoc'
115
116
  ----
116
117
 
117
118
  Finally, you can retrieve the reduced source from the returned document.
@@ -121,11 +122,60 @@ Finally, you can retrieve the reduced source from the returned document.
121
122
  puts doc.source
122
123
  ----
123
124
 
124
- You can write this source to a file to save the reduced document.
125
+ The benefit of this approach is that you can access the reduced source and the parsed document that corresponds to it.
126
+
127
+ If you don't need the parsed document, you can retrieve the reduced source directly by passing the `String` type to the `:to` option:
128
+
129
+ [,ruby]
130
+ ----
131
+ puts Asciidoctor::Reducer.reduce_file 'sample.adoc', to: String
132
+ ----
133
+
134
+ You can write the reduced source directly to a file by passing a file path to the `:to` option:
135
+
136
+ [,ruby]
137
+ ----
138
+ Asciidoctor::Reducer.reduce_file 'sample.adoc', to: 'sample-reduced.adoc'
139
+ ----
140
+
141
+ == Extension
142
+
143
+ Instead of using the API for this library, you can use the load API provided by Asciidoctor.
144
+ If you want to register the extension globally, require the library as follows:
145
+
146
+ [,ruby]
147
+ ----
148
+ require 'asciidoctor/reducer'
149
+ ----
150
+
151
+ When you use the Asciidoctor load API, the document will automatically be reduced.
152
+
153
+ [,ruby]
154
+ ----
155
+ puts (Asciidoctor.load_file 'sample.adoc', safe: :safe).source
156
+ ----
157
+
158
+ If you want to keep the extension scoped to the call, require the library as follows:
159
+
160
+ [,ruby]
161
+ ----
162
+ require 'asciidoctor/reducer/extensions'
163
+ ----
164
+
165
+ Next, use the extensions API to prepare an extension registry and pass it to the Asciidoctor load API:
166
+
167
+ [,ruby]
168
+ ----
169
+ puts (Asciidoctor.load_file 'sample.adoc', safe: :safe, extension_registry: Asciidoctor::Reducer.prepare_registry).source
170
+ ----
171
+
172
+ Working with the extension directly is intended for low-level operations.
173
+ Most of the time, you should use the API provided by this library.
125
174
 
126
175
  == How it Works
127
176
 
128
177
  {project-name} uses a collection of Asciidoctor extensions to rebuild the AsciiDoc source as a single document.
178
+ Top-level include files in the input AsciiDoc document are resolved relative to current working directory.
129
179
 
130
180
  It starts by using a preprocessor extension to enhance the PreprocessorReader class to be notified each time an include is entered (pushed) or exited (popped).
131
181
  When an include directive is encountered, the enhanced reader stores the resolved lines and location of the include directive, thus keeping track of where those lines should be inserted in the original source.
@@ -147,6 +197,69 @@ This call will cause extensions that run during the load phase to be invoked aga
147
197
  An extension can check for this secondary load by checking for the `:reduced` option in the `Document#options` hash.
148
198
  If this option is set (the value of which will be `true`), then Asciidoctor is loading the reduced document.
149
199
 
200
+ == Include Mapper (Experimental)
201
+
202
+ One of the challenges of reducing a document is that interdocument xrefs that rely on the includes being registered in the document catalog no longer work.
203
+ That's because when the reduced document is converted, it has no includes and thus all interdocument xrefs are colocated in the same source file.
204
+ To work around this shortcoming, {project-name} provides a utility extension named the include mapper that will carry over the includes in the document catalog to the reduced document so they can be imported during conversion.
205
+
206
+ CAUTION: The include mapper is experimental and thus subject to change.
207
+
208
+ To use the include mapper when using the CLI to reduce the document, require it using the `-r` option as follows:
209
+
210
+ $ asciidoctor-reducer -r asciidoctor/reducer/include_mapper -o input-reduced.adoc input.adoc
211
+
212
+ To use the include mapper when converting the reduced document, again require it using the `-r` option as follows:
213
+
214
+ $ asciidoctor -r asciidoctor/reducer/include_mapper input-reduced.adoc
215
+
216
+ To use the include mapper when using the API, first require the extension:
217
+
218
+ [,ruby]
219
+ ----
220
+ require 'asciidocotor/reducer/include_mapper/extension'
221
+ ----
222
+
223
+ You then need to register the extension when reducing the document:
224
+
225
+ [,ruby]
226
+ ----
227
+ Asciidoctor::Reducer.reduce_file 'sample.adoc', to: 'sample-reduced.adoc', extensions: proc {
228
+ next if document.options[:reduced]
229
+ tree_processor Asciidoctor::Reducer::IncludeMapper
230
+ }
231
+ ----
232
+
233
+ Then register it again when converting the reduced document:
234
+
235
+ [,ruby]
236
+ ----
237
+ Asciidoctor.convert_file 'sample-reduced.adoc', safe: :safe, extensions: proc {
238
+ tree_processor Asciidoctor::Reducer::IncludeMapper
239
+ }
240
+ ----
241
+
242
+ You can also register the extension globally:
243
+
244
+ [,ruby]
245
+ ----
246
+ require 'asciidocotor/reducer/include_mapper'
247
+ ----
248
+
249
+ In this case, you don't have to pass it to the API explicitly.
250
+
251
+ === How it Works
252
+
253
+ The include mapper works by adding a magic comment to the bottom of the reduced file.
254
+ Here's an example of that comment:
255
+
256
+ [,asciidoc]
257
+ ----
258
+ //# includes=chapters/chapter-a,chapters/chapter-b
259
+ ----
260
+
261
+ When a document that contains the magic comment is converted, the include mapper reads the comma-separated paths in the value and loads them into the includes table of the document catalog.
262
+
150
263
  == Development
151
264
 
152
265
  Follow the instructions below to learn how to help develop the project or test-drive the development version.
@@ -191,7 +304,7 @@ For more fine-grained control, you can also run the tests directly using RSpec:
191
304
 
192
305
  To run all tests in a single spec, point RSpec at the spec file:
193
306
 
194
- $ bundle exec rspec spec/asciidoctor_reducer_spec.rb
307
+ $ bundle exec rspec spec/reducer_spec.rb
195
308
 
196
309
  ==== Run specific tests
197
310
 
@@ -35,5 +35,5 @@ Gem::Specification.new do |s|
35
35
  s.add_runtime_dependency 'asciidoctor', '~> 2.0'
36
36
 
37
37
  s.add_development_dependency 'rake', '~> 13.0.0'
38
- s.add_development_dependency 'rspec', '~> 3.10.0'
38
+ s.add_development_dependency 'rspec', '~> 3.11.0'
39
39
  end
@@ -1,13 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- asciidoctor_reducer = File.absolute_path '../lib/asciidoctor/reducer', __dir__
5
- if File.exist? asciidoctor_reducer
6
- require asciidoctor_reducer
7
- else
8
- require 'asciidoctor/reducer'
9
- end
10
-
11
- require 'asciidoctor/reducer/cli'
12
-
4
+ asciidoctor_reducer_cli = File.join (File.dirname __dir__), 'lib/asciidoctor/reducer/cli.rb'
5
+ require (File.file? asciidoctor_reducer_cli) ? asciidoctor_reducer_cli : 'asciidoctor/reducer/cli'
13
6
  exit Asciidoctor::Reducer::Cli.run
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ autoload :Pathname, 'pathname'
4
+ require_relative 'extensions'
5
+
6
+ module Asciidoctor::Reducer
7
+ autoload :VERSION, (::File.join __dir__, 'version.rb')
8
+
9
+ class << self
10
+ def reduce input, opts = {}
11
+ opts = opts&.merge || {}
12
+ if (extension_registry = Extensions.prepare_registry opts[:extension_registry] || opts[:extensions])
13
+ opts[:extension_registry] = extension_registry
14
+ end
15
+ opts[:safe] ||= :safe
16
+ to = opts.delete :to
17
+ case input
18
+ when ::File
19
+ doc = ::Asciidoctor.load_file input, opts
20
+ when ::Pathname
21
+ doc = ::Asciidoctor.load_file input.to_path, opts
22
+ else
23
+ doc = ::Asciidoctor.load input, opts
24
+ end
25
+ write doc, to
26
+ end
27
+
28
+ def reduce_file input_file, opts = {}
29
+ reduce (::Pathname.new input_file), opts
30
+ end
31
+
32
+ private
33
+
34
+ def write doc, to
35
+ return doc unless to && to != '/dev/null'
36
+ output = doc.source
37
+ return output if to == ::String
38
+ output += LF unless output.empty?
39
+ if ::Pathname === to || (!(to.respond_to? :write) && (to = ::Pathname.new to.to_s))
40
+ to.dirname.mkpath
41
+ to.write output, encoding: UTF_8, newline: :universal
42
+ else
43
+ to.write output
44
+ end
45
+ doc
46
+ end
47
+ end
48
+
49
+ LF = ?\n
50
+ UTF_8 = ::Encoding::UTF_8
51
+
52
+ private_constant :LF, :UTF_8
53
+ end
@@ -1,127 +1,141 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'api'
3
4
  autoload :OptionParser, 'optparse'
4
- autoload :Pathname, 'pathname'
5
5
 
6
6
  module Asciidoctor::Reducer
7
- autoload :VERSION, (::File.join __dir__, 'version.rb')
8
-
9
- class Cli
10
- LOG_LEVELS = (::Logger::Severity.constants false).each_with_object({}) do |level, accum|
11
- accum[level.to_s.downcase] = (::Logger::Severity.const_get level) unless level == :UNKNOWN
12
- end
7
+ module Cli
8
+ class << self
9
+ def parse args
10
+ options = { attributes: {}, log_level: LOG_LEVELS['warn'], safe: :unsafe }
11
+
12
+ opt_parser = ::OptionParser.new do |opts|
13
+ opts.program_name = 'asciidoctor-reducer'
14
+ opts.banner = <<~END
15
+ Usage: #{opts.program_name} [OPTION]... FILE
16
+
17
+ Reduces a composite AsciiDoc document containing includes and conditionals to a single AsciiDoc document.
18
+
19
+ END
20
+
21
+ opts.on '-a KEY[=VALUE]', '--attribute=KEY[=VALUE]',
22
+ 'set a document attribute in the AsciiDoc document: [key, key!, key=value]',
23
+ 'may be specified multiple times' do |attr|
24
+ key, val = attr.split '=', 2
25
+ val ||= ''
26
+ options[:attributes][key] = val
27
+ end
13
28
 
14
- def parse args
15
- options = { attributes: {}, log_level: LOG_LEVELS['warn'], safe: :unsafe }
29
+ opts.on '--log-level LEVEL', %w(debug info warn error fatal),
30
+ 'set the minimum level of messages to log: [debug, info, warn, error, fatal] (default: warn)' do |level|
31
+ options[:log_level] = level
32
+ end
16
33
 
17
- opt_parser = ::OptionParser.new do |opts|
18
- opts.program_name = 'asciidoctor-reducer'
19
- opts.banner = <<~EOS
20
- Usage: #{opts.program_name} [OPTION]... FILE
34
+ opts.on '-o FILE', '--output=FILE', 'set the output filename or stream' do |file|
35
+ options[:output_file] = file
36
+ end
21
37
 
22
- Reduces a composite AsciiDoc document containing includes and conditionals to a single AsciiDoc document.
38
+ opts.on '--preserve-conditionals', 'preserve preprocessor conditional directives in the reduced source' do
39
+ options[:preserve_conditionals] = true
40
+ end
23
41
 
24
- EOS
42
+ opts.on '-q', '--quiet', 'suppress all application log messages' do
43
+ options[:log_level] = nil
44
+ end
25
45
 
26
- opts.on '-a KEY[=VALUE]', '--attribute=KEY[=VALUE]',
27
- 'set a document attribute in the AsciiDoc document: [key, key!, key=value]' do |attr|
28
- key, val = attr.split '=', 2
29
- val ||= ''
30
- options[:attributes][key] = val
31
- end
46
+ opts.on '-rLIBRARY', '--require LIBRARY', 'require the specified library or libraries before reducing',
47
+ 'may be specified multiple times' do |path|
48
+ (options[:requires] ||= []).concat path.split ','
49
+ end
32
50
 
33
- opts.on '--log-level LEVEL', %w(debug info warn error fatal),
34
- 'set the minimum level of messages to log: [debug, info, warn, error, fatal] (default: warn)' do |level|
35
- options[:log_level] = level
36
- end
51
+ opts.on '-S', '--safe-mode SAFE_MODE', ['unsafe', 'safe', 'server', 'secure'],
52
+ 'set safe mode level: [unsafe, safe, server, secure] (default: unsafe)' do |name|
53
+ options[:safe] = name.to_sym
54
+ end
37
55
 
38
- opts.on '-o FILE', '--output=FILE', 'set the output filename or stream' do |file|
39
- options[:output_file] = file
40
- end
56
+ opts.on '--trace', 'trace the cause of application errors (default: false)' do
57
+ options[:trace] = true
58
+ end
41
59
 
42
- opts.on '--preserve-conditionals', 'preserve preprocessor conditional directives in the reduced source' do
43
- options[:preserve_conditionals] = true
44
- end
60
+ opts.on '-v', '--version', 'display the version information and exit' do
61
+ print_version opts
62
+ return 0
63
+ end
45
64
 
46
- opts.on '-q', '--quiet', 'suppress all application log messages' do
47
- options[:log_level] = nil
65
+ opts.on '-h', '--help', 'display this help text and exit' do
66
+ print_help opts
67
+ return 0
68
+ end
48
69
  end
49
70
 
50
- opts.on '-rLIBRARY', '--require LIBRARY', 'require the specified library or libraries before running' do |path|
51
- (options[:requires] ||= []).concat path.split ','
71
+ if (args = opt_parser.parse args).empty?
72
+ opt_parser.warn 'Please specify an AsciiDoc file to reduce.'
73
+ print_help opt_parser
74
+ 1
75
+ elsif args.size == 1
76
+ if (requires = options.delete :requires)
77
+ requires.uniq.each do |path|
78
+ require path
79
+ rescue ::LoadError
80
+ $stderr.puts %(#{opt_parser.program_name}: '#{path}' could not be required (reason: #{$!.message}))
81
+ return 1
82
+ end
83
+ end
84
+ options[:input_file] = args[0]
85
+ options[:output_file] = '-' unless options[:output_file]
86
+ [0, options]
87
+ else
88
+ opt_parser.warn %(extra arguments detected (unparsed arguments: #{(args.drop 1).join ' '}))
89
+ print_help opt_parser
90
+ 1
52
91
  end
92
+ rescue ::OptionParser::InvalidOption
93
+ $stderr.puts %(#{opt_parser.program_name}: #{$!.message})
94
+ print_help opt_parser
95
+ 1
96
+ end
53
97
 
54
- opts.on '-S', '--safe-mode SAFE_MODE', ['unsafe', 'safe', 'server'],
55
- 'set supported safe mode level: [unsafe, safe, server] (default: unsafe)' do |name|
56
- options[:safe] = ::Asciidoctor::SafeMode.value_for_name name
98
+ def run args = ARGV
99
+ code, options = parse (Array args)
100
+ return code unless code == 0 && options
101
+ trace = options.delete :trace
102
+ old_logger = ::Asciidoctor::LoggerManager.logger
103
+ if (log_level = options.delete :log_level)
104
+ (options[:logger] = ::Asciidoctor::Logger.new $stderr).level = log_level
105
+ else
106
+ options[:logger] = nil
57
107
  end
108
+ options[:to] = (output_file = options.delete :output_file) == '-' ? $stdout : (::Pathname.new output_file)
109
+ input = (input_file = options.delete :input_file) == '-' ? $stdin : (::Pathname.new input_file)
110
+ ::Asciidoctor::Reducer.reduce input, options
111
+ 0
112
+ rescue ::SignalException
113
+ $stderr.puts if ::Interrupt === $!
114
+ $!.signo
115
+ rescue
116
+ raise $! if trace
117
+ $stderr.puts %(asciidoctor-reducer: #{$!.message.delete_prefix 'asciidoctor: '})
118
+ $stderr.puts ' Use --trace to show backtrace'
119
+ 1
120
+ ensure
121
+ ::Asciidoctor::LoggerManager.logger = old_logger if old_logger
122
+ end
58
123
 
59
- opts.on '-v', '--version', 'display the version information and exit' do
60
- $stdout.write %(#{opts.program_name} #{VERSION}\n)
61
- return 0
62
- end
124
+ private
63
125
 
64
- opts.on '-h', '--help', 'display this help text and exit' do
65
- $stdout.write opts.help
66
- return 0
67
- end
126
+ def print_help opt_parser
127
+ $stdout.puts opt_parser.help.chomp
68
128
  end
69
129
 
70
- args = opt_parser.parse args
71
-
72
- if args.empty?
73
- opt_parser.warn 'Please specify an AsciiDoc file to reduce.'
74
- $stdout.write opt_parser.help
75
- 1
76
- elsif args.size == 1
77
- if (requires = options.delete :requires)
78
- requires.uniq.each do |path|
79
- require path
80
- rescue ::LoadError
81
- $stderr.write %(#{opt_parser.program_name}: '#{path}' could not be required (reason: #{$!.message})\n)
82
- return 1
83
- end
84
- end
85
- options[:input_file] = args[0]
86
- options[:output_file] = '-' unless options[:output_file]
87
- [0, options]
88
- else
89
- opt_parser.warn %(extra arguments detected (unparsed arguments: #{(args.drop 1).join ' '}))
90
- $stdout.write opt_parser.help
91
- 1
130
+ def print_version opt_parser
131
+ $stdout.puts %(#{opt_parser.program_name} #{VERSION})
92
132
  end
93
- rescue ::OptionParser::InvalidOption
94
- $stderr.write %(#{opt_parser.program_name}: #{$!.message}\n)
95
- $stdout.write opt_parser.help
96
- 1
97
133
  end
98
134
 
99
- def self.run args = ARGV
100
- code, options = new.parse (Array args)
101
- return code unless code == 0 && options
102
- old_logger = ::Asciidoctor::LoggerManager.logger
103
- if (log_level = options.delete :log_level)
104
- (options[:logger] = ::Asciidoctor::Logger.new $stderr).level = log_level
105
- else
106
- options[:logger] = nil
107
- end
108
- if (output_file = options.delete :output_file) == '-'
109
- to = $stdout
110
- else
111
- (to = ::Pathname.new output_file).dirname.mkpath
112
- end
113
- if (input_file = options.delete :input_file) == '-'
114
- reduced = (::Asciidoctor.load $stdin, options).source + ?\n
115
- else
116
- reduced = (::Asciidoctor.load_file input_file, options).source + ?\n
117
- end
118
- ::Pathname === to ? (to.write reduced, encoding: ::Encoding::UTF_8) : (to.write reduced)
119
- 0
120
- rescue
121
- $stderr.write %(asciidoctor-reducer: #{$!.message}\n)
122
- 1
123
- ensure
124
- ::Asciidoctor::LoggerManager.logger = old_logger if old_logger
135
+ LOG_LEVELS = (::Logger::Severity.constants false).each_with_object({}) do |level, accum|
136
+ accum[level.to_s.downcase] = (::Logger::Severity.const_get level) unless level == :UNKNOWN
125
137
  end
138
+
139
+ private_constant :LOG_LEVELS
126
140
  end
127
141
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor::Reducer
4
+ module ConditionalDirectiveTracker
5
+ def preprocess_conditional_directive keyword, target, delimiter, text
6
+ skip_active = @skipping
7
+ depth = @conditional_stack.size
8
+ cond_lineno = @lineno
9
+ result = super
10
+ return result if @skipping && skip_active
11
+ drop = @include_replacements.current[:drop] ||= []
12
+ if (depth_change = @conditional_stack.size - depth) < 0
13
+ if skip_active
14
+ drop.push(*(drop.pop..cond_lineno))
15
+ else
16
+ drop << cond_lineno
17
+ end
18
+ elsif depth_change > 0 || cond_lineno == @lineno
19
+ drop << cond_lineno
20
+ else
21
+ drop << [cond_lineno, text]
22
+ end
23
+ result
24
+ end
25
+ end
26
+ end
@@ -1,4 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'asciidoctor' unless defined? Asciidoctor.load
3
4
  require_relative 'preprocessor'
4
5
  require_relative 'tree_processor'
6
+
7
+ module Asciidoctor::Reducer
8
+ module Extensions
9
+ module_function
10
+
11
+ def group
12
+ proc do
13
+ next if document.options[:reduced]
14
+ preprocessor Preprocessor
15
+ tree_processor TreeProcessor
16
+ end
17
+ end
18
+
19
+ def key
20
+ :reducer
21
+ end
22
+
23
+ def prepare_registry registry = nil
24
+ registry = ::Asciidoctor::Extensions.create(&registry) if ::Proc === registry
25
+ return registry if ::Asciidoctor::Extensions.groups[key]
26
+ if registry
27
+ registry.groups[key] = group
28
+ registry
29
+ else
30
+ ::Asciidoctor::Extensions.create key, &group
31
+ end
32
+ end
33
+
34
+ def register
35
+ ::Asciidoctor::Extensions.register key, &group
36
+ end
37
+
38
+ def unregister
39
+ ::Asciidoctor::Extensions.groups.delete key # NOTE `Extensions.unregister key` fails if groups is not initialized
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor::Reducer
4
+ module IncludeDirectiveTracker
5
+ attr_reader :include_replacements
6
+ attr_writer :source_lines
7
+
8
+ def self.extended instance
9
+ instance.instance_variable_set :@include_replacements, ([{}].extend CurrentPosition)
10
+ instance.instance_variable_set :@x_reducer, {}
11
+ end
12
+
13
+ def preprocess_include_directive target, attrlist
14
+ @x_reducer[:include_directive_line] = %(include::#{target}[#{attrlist}])
15
+ @x_reducer[:include_pushed] = false
16
+ inc_lineno = @lineno # we're currently on the include line, which is 1-based
17
+ result = super
18
+ unless @x_reducer[:include_pushed]
19
+ if ((ln = peek_line true)&.end_with? ']') && !(unresolved = ln.start_with? 'Unresolved directive in ') &&
20
+ inc_lineno == @lineno && (unresolved = ln.start_with? 'link:')
21
+ ln = %(#{ln.slice 0, (ln.length - 1)}role=include])
22
+ end
23
+ push_include_replacement inc_lineno, (unresolved ? [ln] : []), unresolved
24
+ end
25
+ @x_reducer.clear
26
+ result
27
+ end
28
+
29
+ def push_include data, file, path, lineno, attrs
30
+ @x_reducer[:include_pushed] = true
31
+ inc_lineno = @lineno - 1 # we're below the include line, which is 1-based
32
+ prev_inc_depth = @include_stack.size
33
+ result = super
34
+ push_include_replacement inc_lineno, (@include_stack.size > prev_inc_depth ? lines : [])
35
+ result
36
+ end
37
+
38
+ def pop_include
39
+ @include_replacements.up unless @x_reducer[:include_pushed]
40
+ super
41
+ end
42
+
43
+ private
44
+
45
+ def push_include_replacement lineno, lines, unresolved = false
46
+ (inc_replacements = @include_replacements) << {
47
+ into: inc_replacements.pointer,
48
+ lineno: lineno,
49
+ line: @x_reducer[:include_directive_line],
50
+ lines: lines,
51
+ }
52
+ inc_replacements.to_end unless unresolved || lines.empty?
53
+ nil
54
+ end
55
+ end
56
+
57
+ module CurrentPosition
58
+ attr_reader :pointer
59
+
60
+ def self.extended instance
61
+ instance.to_end
62
+ end
63
+
64
+ def current
65
+ self[@pointer]
66
+ end
67
+
68
+ def to_end
69
+ @pointer = size - 1
70
+ end
71
+
72
+ def up
73
+ @pointer = current[:into]
74
+ end
75
+ end
76
+
77
+ private_constant :CurrentPosition
78
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asciidoctor::Reducer
4
+ class IncludeMapper < ::Asciidoctor::Extensions::TreeProcessor
5
+ def process doc
6
+ if doc.extensions.groups[:reducer]
7
+ unless (includes = doc.catalog[:includes].select {|_, v| v }.keys).empty?
8
+ doc.source_lines.concat ['', %(//# includes=#{includes.join ','})]
9
+ end
10
+ elsif (last_line = doc.source_lines[-1])&.start_with? '//# includes='
11
+ doc.catalog[:includes].update ((last_line.slice 13, last_line.length).split ',').map {|it| [it, true] }.to_h
12
+ end
13
+ doc
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'include_mapper/extension'
4
+
5
+ Asciidoctor::Extensions.register do
6
+ next if document.options[:reduced]
7
+ tree_processor Asciidoctor::Reducer::IncludeMapper
8
+ end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'preprocessor_directive_tracker'
3
+ require_relative 'include_directive_tracker'
4
+ require_relative 'conditional_directive_tracker'
4
5
 
5
6
  module Asciidoctor::Reducer
6
7
  class Preprocessor < ::Asciidoctor::Extensions::Preprocessor
7
- def process _, reader
8
- reader.extend PreprocessorDirectiveTracker
8
+ def process doc, reader
9
+ doc.options[:preserve_conditionals] ?
10
+ (reader.extend IncludeDirectiveTracker) : (reader.extend ConditionalDirectiveTracker, IncludeDirectiveTracker)
9
11
  end
10
12
  end
11
13
  end
@@ -3,19 +3,21 @@
3
3
  module Asciidoctor::Reducer
4
4
  class TreeProcessor < ::Asciidoctor::Extensions::TreeProcessor
5
5
  def process doc
6
- unless (inc_replacements = doc.reader.x_include_replacements).length == 1 && inc_replacements[0][:drop].empty?
6
+ if (inc_replacements = doc.reader.include_replacements).size > 1 || !(inc_replacements[0][:drop] || []).empty?
7
7
  inc_replacements[0][:lines] = doc.source_lines.dup
8
8
  inc_replacements.reverse_each do |it|
9
9
  if (into = it[:into])
10
10
  target_lines = inc_replacements[into][:lines]
11
11
  # adds extra bit of assurance that we're replacing the correct line
12
- next unless target_lines[(index = it[:lineno])] == it[:line]
12
+ next unless target_lines[(idx = it[:lineno] - 1)] == it[:line]
13
13
  end
14
14
  lines = it[:lines]
15
- unless (drop = it[:drop]).empty?
16
- drop.reverse_each {|idx| ::Array === idx ? (lines[idx[0]] = idx[1]) : (lines.delete_at idx) }
15
+ unless (drop = it[:drop] || []).empty?
16
+ drop.reverse_each do |drop_it|
17
+ ::Array === drop_it ? (lines[drop_it[0] - 1] = drop_it[1]) : (lines.delete_at drop_it - 1)
18
+ end
17
19
  end
18
- target_lines[index] = lines if target_lines
20
+ target_lines[idx] = lines if target_lines
19
21
  end
20
22
  source_lines = inc_replacements[0][:lines].flatten
21
23
  if doc.sourcemap
@@ -30,7 +32,7 @@ module Asciidoctor::Reducer
30
32
  doc.parse
31
33
  ::Asciidoctor::LoggerManager.logger = logger
32
34
  else
33
- source_lines.pop while (last = source_lines[-1]) && last.empty?
35
+ source_lines.pop while (source_lines[-1] || :eof).empty?
34
36
  doc.reader.source_lines = source_lines
35
37
  end
36
38
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module Reducer
5
- VERSION = '1.0.0.alpha.6'
5
+ VERSION = '1.0.0.alpha.9'
6
6
  end
7
7
  end
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'asciidoctor'
4
3
  require_relative 'reducer/extensions'
5
4
 
6
- Asciidoctor::Extensions.register :reducer do
7
- next if document.options[:reduced]
8
- preprocessor Asciidoctor::Reducer::Preprocessor
9
- tree_processor Asciidoctor::Reducer::TreeProcessor
10
- end
5
+ Asciidoctor::Reducer::Extensions.register
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoctor-reducer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.alpha.6
4
+ version: 1.0.0.alpha.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Allen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-10 00:00:00.000000000 Z
11
+ date: 2022-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 3.10.0
47
+ version: 3.11.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 3.10.0
54
+ version: 3.11.0
55
55
  description: A tool that reduces a composite AsciiDoc document containing preprocessor
56
56
  directives (includes and conditionals) to a single AsciiDoc document by expanding
57
57
  the includes and applying the conditionals.
@@ -68,10 +68,14 @@ files:
68
68
  - bin/asciidoctor-reducer
69
69
  - lib/asciidoctor-reducer.rb
70
70
  - lib/asciidoctor/reducer.rb
71
+ - lib/asciidoctor/reducer/api.rb
71
72
  - lib/asciidoctor/reducer/cli.rb
73
+ - lib/asciidoctor/reducer/conditional_directive_tracker.rb
72
74
  - lib/asciidoctor/reducer/extensions.rb
75
+ - lib/asciidoctor/reducer/include_directive_tracker.rb
76
+ - lib/asciidoctor/reducer/include_mapper.rb
77
+ - lib/asciidoctor/reducer/include_mapper/extension.rb
73
78
  - lib/asciidoctor/reducer/preprocessor.rb
74
- - lib/asciidoctor/reducer/preprocessor_directive_tracker.rb
75
79
  - lib/asciidoctor/reducer/tree_processor.rb
76
80
  - lib/asciidoctor/reducer/version.rb
77
81
  homepage: https://asciidoctor.org
@@ -97,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
101
  - !ruby/object:Gem::Version
98
102
  version: 1.3.1
99
103
  requirements: []
100
- rubygems_version: 3.2.32
104
+ rubygems_version: 3.2.33
101
105
  signing_key:
102
106
  specification_version: 4
103
107
  summary: Reduces a composite AsciiDoc document containing includes and conditionals
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Asciidoctor::Reducer
4
- module PreprocessorDirectiveTracker
5
- attr_writer :source_lines
6
- attr_reader :x_include_replacements
7
-
8
- def self.extended instance
9
- instance.instance_variable_set :@x_include_replacements, ([{ drop: [] }].extend CurrentPosition)
10
- instance.instance_variable_set :@x_include_directive_line, nil
11
- instance.instance_variable_set :@x_include_pushed, nil
12
- end
13
-
14
- def preprocess_conditional_directive keyword, target, delimiter, text
15
- return super if @document.options[:preserve_conditionals]
16
- skip_active = @skipping
17
- depth = @conditional_stack.length
18
- cond_lineno = @lineno - 1
19
- result = super
20
- return result if @skipping && skip_active
21
- drop = @x_include_replacements.current[:drop]
22
- if (depth_change = @conditional_stack.length - depth) < 0
23
- if skip_active
24
- drop.push(*(drop.pop..cond_lineno))
25
- else
26
- drop << cond_lineno
27
- end
28
- elsif depth_change > 0 || cond_lineno == @lineno - 1
29
- drop << cond_lineno
30
- else
31
- drop << [cond_lineno, text]
32
- end
33
- result
34
- end
35
-
36
- def preprocess_include_directive target, attrlist
37
- @x_include_directive_line = %(include::#{target}[#{attrlist}])
38
- @x_include_pushed = false
39
- inc_lineno = @lineno - 1 # we're currently on the include line, which is 1-based
40
- result = super
41
- unless @x_include_pushed
42
- unresolved = (l = peek_line true) && (l.start_with? 'Unresolved directive in ') && (l.end_with? ']') || nil
43
- push_include_replacement inc_lineno, (unresolved ? [l] : []), unresolved
44
- end
45
- @x_include_directive_line = @x_include_pushed = nil
46
- result
47
- end
48
-
49
- def push_include data, file, path, lineno, attrs
50
- @x_include_pushed = true
51
- inc_lineno = @lineno - 2 # we're below the include line, which is 1-based
52
- prev_inc_depth = @include_stack.length
53
- result = super
54
- push_include_replacement inc_lineno, (@include_stack.length > prev_inc_depth ? lines : [])
55
- result
56
- end
57
-
58
- def pop_include
59
- @x_include_replacements.pos = @x_include_replacements.current[:into] unless @x_include_pushed
60
- super
61
- end
62
-
63
- private
64
-
65
- def push_include_replacement inc_lineno, inc_lines, unresolved = false
66
- @x_include_replacements << {
67
- into: @x_include_replacements.pos,
68
- lineno: inc_lineno,
69
- line: @x_include_directive_line,
70
- lines: inc_lines,
71
- drop: [],
72
- }
73
- @x_include_replacements.pos = @x_include_replacements.length - 1 unless unresolved || inc_lines.empty?
74
- nil
75
- end
76
- end
77
-
78
- module CurrentPosition
79
- attr_accessor :pos
80
-
81
- def self.extended instance
82
- instance.pos = instance.length - 1
83
- end
84
-
85
- def current
86
- self[@pos]
87
- end
88
- end
89
- end