pandocomatic 0.2.8 → 1.0.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.
- checksums.yaml +4 -4
- data/lib/pandocomatic/cli.rb +81 -64
- data/lib/pandocomatic/command/command.rb +37 -35
- data/lib/pandocomatic/command/convert_dir_command.rb +44 -46
- data/lib/pandocomatic/command/convert_file_command.rb +314 -290
- data/lib/pandocomatic/command/convert_file_multiple_command.rb +56 -53
- data/lib/pandocomatic/command/convert_list_command.rb +31 -34
- data/lib/pandocomatic/command/copy_file_command.rb +14 -15
- data/lib/pandocomatic/command/create_link_command.rb +24 -27
- data/lib/pandocomatic/command/skip_command.rb +12 -15
- data/lib/pandocomatic/configuration.rb +682 -867
- data/lib/pandocomatic/default_configuration.yaml +4 -0
- data/lib/pandocomatic/error/cli_error.rb +30 -26
- data/lib/pandocomatic/error/configuration_error.rb +10 -9
- data/lib/pandocomatic/error/io_error.rb +13 -13
- data/lib/pandocomatic/error/pandoc_error.rb +10 -9
- data/lib/pandocomatic/error/pandocomatic_error.rb +15 -14
- data/lib/pandocomatic/error/processor_error.rb +9 -9
- data/lib/pandocomatic/error/template_error.rb +50 -0
- data/lib/pandocomatic/input.rb +53 -54
- data/lib/pandocomatic/multiple_files_input.rb +79 -72
- data/lib/pandocomatic/output.rb +29 -0
- data/lib/pandocomatic/pandoc_metadata.rb +193 -181
- data/lib/pandocomatic/pandocomatic.rb +101 -97
- data/lib/pandocomatic/pandocomatic_yaml.rb +69 -0
- data/lib/pandocomatic/path.rb +171 -0
- data/lib/pandocomatic/printer/command_printer.rb +7 -5
- data/lib/pandocomatic/printer/configuration_errors_printer.rb +7 -6
- data/lib/pandocomatic/printer/error_printer.rb +12 -7
- data/lib/pandocomatic/printer/finish_printer.rb +11 -10
- data/lib/pandocomatic/printer/help_printer.rb +8 -6
- data/lib/pandocomatic/printer/printer.rb +34 -34
- data/lib/pandocomatic/printer/summary_printer.rb +39 -33
- data/lib/pandocomatic/printer/version_printer.rb +8 -8
- data/lib/pandocomatic/printer/views/cli_error.txt +5 -0
- data/lib/pandocomatic/printer/views/configuration_error.txt +2 -1
- data/lib/pandocomatic/printer/views/error.txt +1 -1
- data/lib/pandocomatic/printer/views/finish.txt +1 -1
- data/lib/pandocomatic/printer/views/help.txt +27 -15
- data/lib/pandocomatic/printer/views/summary.txt +7 -1
- data/lib/pandocomatic/printer/views/template_error.txt +1 -0
- data/lib/pandocomatic/printer/views/version.txt +3 -3
- data/lib/pandocomatic/printer/views/warning.txt +1 -1
- data/lib/pandocomatic/printer/warning_printer.rb +21 -19
- data/lib/pandocomatic/processor.rb +28 -28
- data/lib/pandocomatic/processors/fileinfo_preprocessor.rb +35 -30
- data/lib/pandocomatic/processors/metadata_preprocessor.rb +23 -22
- data/lib/pandocomatic/template.rb +244 -0
- data/lib/pandocomatic/warning.rb +24 -25
- metadata +32 -12
@@ -1,951 +1,766 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#--
|
2
|
-
# Copyright 2014—
|
3
|
-
#
|
4
|
+
# Copyright 2014—2022 Huub de Beer <Huub@heerdebeer.org>
|
5
|
+
#
|
4
6
|
# This file is part of pandocomatic.
|
5
|
-
#
|
7
|
+
#
|
6
8
|
# Pandocomatic is free software: you can redistribute it and/or modify
|
7
9
|
# it under the terms of the GNU General Public License as published by the
|
8
10
|
# Free Software Foundation, either version 3 of the License, or (at your
|
9
11
|
# option) any later version.
|
10
|
-
#
|
12
|
+
#
|
11
13
|
# Pandocomatic is distributed in the hope that it will be useful, but
|
12
14
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
13
15
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
14
16
|
# for more details.
|
15
|
-
#
|
17
|
+
#
|
16
18
|
# You should have received a copy of the GNU General Public License along
|
17
19
|
# with pandocomatic. If not, see <http://www.gnu.org/licenses/>.
|
18
20
|
#++
|
19
21
|
module Pandocomatic
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
# Update this configuration with a configuration file
|
147
|
-
#
|
148
|
-
# @param [String] filename path to the configuration file
|
149
|
-
#
|
150
|
-
# @return [Configuration] a new configuration
|
151
|
-
def reconfigure(filename)
|
152
|
-
begin
|
153
|
-
settings = YAML.load_file filename
|
154
|
-
new_config = Marshal.load(Marshal.dump(self))
|
155
|
-
new_config.configure settings
|
156
|
-
new_config
|
157
|
-
rescue StandardError => e
|
158
|
-
raise ConfigurationError.new(:unable_to_load_config_file, e, filename)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# Configure pandocomatic based on a settings Hash
|
163
|
-
#
|
164
|
-
# @param settings [Hash] a settings Hash to mixin in this
|
165
|
-
# Configuration.
|
166
|
-
def configure(settings)
|
167
|
-
reset_settings settings['settings'] if settings.has_key? 'settings'
|
168
|
-
if settings.has_key? 'templates' then
|
169
|
-
settings['templates'].each do |name, template|
|
170
|
-
full_template = {
|
171
|
-
'extends' => [],
|
172
|
-
'glob' => [],
|
173
|
-
'setup' => [],
|
174
|
-
'preprocessors' => [],
|
175
|
-
'metadata' => {},
|
176
|
-
'pandoc' => {},
|
177
|
-
'postprocessors' => [],
|
178
|
-
'cleanup' => []
|
179
|
-
}
|
180
|
-
|
181
|
-
reset_template name, full_template.merge(template)
|
22
|
+
require 'paru/pandoc'
|
23
|
+
|
24
|
+
require_relative './error/configuration_error'
|
25
|
+
require_relative './command/command'
|
26
|
+
require_relative './input'
|
27
|
+
require_relative './multiple_files_input'
|
28
|
+
require_relative './pandocomatic_yaml'
|
29
|
+
require_relative './path'
|
30
|
+
require_relative './template'
|
31
|
+
|
32
|
+
# The default configuration for pandocomatic is read from
|
33
|
+
# default_configuration.yaml.
|
34
|
+
DEFAULT_CONFIG = PandocomaticYAML.load_file File.join(__dir__, 'default_configuration.yaml')
|
35
|
+
|
36
|
+
# rubocop:disable Style/MutableConstant
|
37
|
+
|
38
|
+
# The default settings for pandocomatic:
|
39
|
+
# hidden files will always be skipped, as will pandocomatic
|
40
|
+
# configuration files, unless explicitly set to not skip via the
|
41
|
+
# "unskip" option
|
42
|
+
DEFAULT_SETTINGS = {
|
43
|
+
'skip' => ['.*', 'pandocomatic.yaml'],
|
44
|
+
'recursive' => true,
|
45
|
+
'follow-links' => false,
|
46
|
+
'match-files' => 'first'
|
47
|
+
}
|
48
|
+
# rubocop:enable Style/MutableConstant
|
49
|
+
|
50
|
+
# Maps pandoc output formats to their conventional default extension.
|
51
|
+
# Updated and in order of `pandoc --list-output-formats`.
|
52
|
+
DEFAULT_EXTENSION = {
|
53
|
+
'asciidoc' => 'adoc',
|
54
|
+
'asciidoctor' => 'adoc',
|
55
|
+
'beamer' => 'tex',
|
56
|
+
'bibtex' => 'bib',
|
57
|
+
'biblatex' => 'bib',
|
58
|
+
'commonmark' => 'md',
|
59
|
+
'context' => 'tex',
|
60
|
+
'csljson' => 'json',
|
61
|
+
'docbook' => 'docbook',
|
62
|
+
'docbook4' => 'docbook',
|
63
|
+
'docbook5' => 'docbook',
|
64
|
+
'docx' => 'docx',
|
65
|
+
'dokuwiki' => 'txt',
|
66
|
+
'dzslides' => 'html',
|
67
|
+
'epub' => 'epub',
|
68
|
+
'epub2' => 'epub',
|
69
|
+
'epub3' => 'epub',
|
70
|
+
'fb2' => 'fb2',
|
71
|
+
'gfm' => 'md',
|
72
|
+
'haddock' => 'hs',
|
73
|
+
'html' => 'html',
|
74
|
+
'html4' => 'html',
|
75
|
+
'html5' => 'html',
|
76
|
+
'icml' => 'icml',
|
77
|
+
'ipynb' => 'ipynb',
|
78
|
+
'jats' => 'jats',
|
79
|
+
'jats_archiving' => 'jats',
|
80
|
+
'jats_articleauthoring' => 'jats',
|
81
|
+
'jats_publishing' => 'jats',
|
82
|
+
'jira' => 'jira',
|
83
|
+
'json' => 'json',
|
84
|
+
'latex' => 'tex',
|
85
|
+
'man' => 'man',
|
86
|
+
'markdown' => 'md',
|
87
|
+
'markdown_github' => 'md',
|
88
|
+
'markdown_mmd' => 'md',
|
89
|
+
'markdown_phpextra' => 'md',
|
90
|
+
'markdown_strict' => 'md',
|
91
|
+
'media_wiki' => 'mediawiki',
|
92
|
+
'ms' => 'ms',
|
93
|
+
'muse' => 'muse',
|
94
|
+
'native' => 'hs',
|
95
|
+
'odt' => 'odt',
|
96
|
+
'opendocument' => 'odt',
|
97
|
+
'opml' => 'opml',
|
98
|
+
'org' => 'org',
|
99
|
+
'pdf' => 'pdf',
|
100
|
+
'plain' => 'txt',
|
101
|
+
'pptx' => 'pptx',
|
102
|
+
'revealjs' => 'html',
|
103
|
+
'rst' => 'rst',
|
104
|
+
's5' => 'html',
|
105
|
+
'slideous' => 'html',
|
106
|
+
'slidy' => 'html',
|
107
|
+
'tei' => 'tei',
|
108
|
+
'texinfo' => 'texi',
|
109
|
+
'textile' => 'textile',
|
110
|
+
'xwiki' => 'xwiki',
|
111
|
+
'zimwiki' => 'zimwiki'
|
112
|
+
}.freeze
|
113
|
+
|
114
|
+
# rubocop:disable Metrics
|
115
|
+
|
116
|
+
# Configuration models a pandocomatic configuration.
|
117
|
+
class Configuration
|
118
|
+
attr_reader :input, :config_files, :data_dir, :root_path
|
119
|
+
|
120
|
+
# Pandocomatic's default configuration file
|
121
|
+
CONFIG_FILE = 'pandocomatic.yaml'
|
122
|
+
|
123
|
+
# Create a new Configuration instance based on the command-line options
|
124
|
+
def initialize(options, input)
|
125
|
+
data_dirs = determine_data_dirs options
|
126
|
+
@options = options
|
127
|
+
@data_dir = data_dirs.first
|
128
|
+
@settings = DEFAULT_SETTINGS
|
129
|
+
@templates = {}
|
130
|
+
@convert_patterns = {}
|
131
|
+
|
132
|
+
load_configuration_hierarchy options, data_dirs
|
133
|
+
|
134
|
+
@input = if input.nil? || input.empty?
|
135
|
+
nil
|
136
|
+
elsif input.size > 1
|
137
|
+
MultipleFilesInput.new(input, self)
|
138
|
+
else
|
139
|
+
Input.new(input)
|
140
|
+
end
|
141
|
+
|
142
|
+
@output = if output?
|
143
|
+
options[:output]
|
144
|
+
elsif to_stdout? options
|
145
|
+
Tempfile.new(@input.base)
|
146
|
+
elsif @input.is_a? Input
|
147
|
+
@input.base
|
182
148
|
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
# Convert this Configuration to a String
|
187
|
-
#
|
188
|
-
# @return [String]
|
189
|
-
def to_s()
|
190
|
-
marshal_dump
|
191
|
-
end
|
192
|
-
|
193
|
-
# Is the dry run CLI option given?
|
194
|
-
#
|
195
|
-
# @return [Boolean]
|
196
|
-
def dry_run?()
|
197
|
-
@options[:dry_run_given] and @options[:dry_run]
|
198
|
-
end
|
199
|
-
|
200
|
-
# Is the quiet CLI option given?
|
201
|
-
#
|
202
|
-
# @return [Boolean]
|
203
|
-
def quiet?()
|
204
|
-
@options[:quiet_given] and @options[:quiet]
|
205
|
-
end
|
206
|
-
|
207
|
-
# Is the debug CLI option given?
|
208
|
-
#
|
209
|
-
# @return [Boolean]
|
210
|
-
def debug?()
|
211
|
-
@options[:debug_given] and @options[:debug]
|
212
|
-
end
|
213
|
-
|
214
|
-
# Is the modified only CLI option given?
|
215
|
-
#
|
216
|
-
# @return [Boolean]
|
217
|
-
def modified_only?()
|
218
|
-
@options[:modified_only_given] and @options[:modified_only]
|
219
|
-
end
|
220
|
-
|
221
|
-
# Is the version CLI option given?
|
222
|
-
#
|
223
|
-
# @return [Boolean]
|
224
|
-
def show_version?()
|
225
|
-
@options[:version_given]
|
226
|
-
end
|
227
|
-
|
228
|
-
# Is the help CLI option given?
|
229
|
-
#
|
230
|
-
# @return [Boolean]
|
231
|
-
def show_help?()
|
232
|
-
@options[:help_given]
|
233
|
-
end
|
234
|
-
|
235
|
-
# Is the data dir CLI option given?
|
236
|
-
#
|
237
|
-
# @return [Boolean]
|
238
|
-
def data_dir?()
|
239
|
-
@options[:data_dir_given]
|
240
|
-
end
|
241
149
|
|
242
|
-
|
243
|
-
#
|
244
|
-
# @return [Boolean]
|
245
|
-
def root_path?()
|
246
|
-
@options[:root_path_given]
|
247
|
-
end
|
248
|
-
|
249
|
-
# Is the config CLI option given?
|
250
|
-
#
|
251
|
-
# @return [Boolean]
|
252
|
-
def config?()
|
253
|
-
@options[:config_given]
|
254
|
-
end
|
255
|
-
|
256
|
-
# Is the output CLI option given and can that output be used?
|
257
|
-
#
|
258
|
-
# @return [Boolean]
|
259
|
-
def output?()
|
260
|
-
not @options.nil? and @options[:output_given] and @options[:output]
|
261
|
-
end
|
150
|
+
@root_path = Path.determine_root_path options
|
262
151
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
# Get the source root directory
|
271
|
-
#
|
272
|
-
# @return [String]
|
273
|
-
def src_root()
|
274
|
-
if @input.nil? then nil else @input.absolute_path end
|
275
|
-
end
|
152
|
+
# Extend the command classes by setting the source tree root
|
153
|
+
# directory, and the options quiet and dry-run, which are used when
|
154
|
+
# executing a command: if dry-run the command is not actually
|
155
|
+
# executed and if quiet the command is not printed to STDOUT
|
156
|
+
Command.reset(self)
|
157
|
+
end
|
276
158
|
|
277
|
-
|
278
|
-
|
279
|
-
|
159
|
+
# Read a configuration file and create a pandocomatic configuration object
|
160
|
+
#
|
161
|
+
# @param [String] filename Path to the configuration yaml file
|
162
|
+
# @return [Configuration] a pandocomatic configuration object
|
163
|
+
def load(filename)
|
164
|
+
begin
|
165
|
+
path = File.absolute_path filename
|
166
|
+
settings = PandocomaticYAML.load_file path
|
167
|
+
if settings['settings'] && settings['settings']['data-dir']
|
168
|
+
data_dir = settings['settings']['data-dir']
|
169
|
+
src_dir = File.dirname filename
|
170
|
+
@data_dir = if data_dir.start_with? '.'
|
171
|
+
File.absolute_path data_dir, src_dir
|
172
|
+
else
|
173
|
+
data_dir
|
174
|
+
end
|
280
175
|
end
|
176
|
+
rescue StandardError => e
|
177
|
+
raise ConfigurationError.new(:unable_to_load_config_file, e, filename)
|
178
|
+
end
|
281
179
|
|
282
|
-
|
283
|
-
|
284
|
-
# @return [String]
|
285
|
-
def input_file()
|
286
|
-
if @input.nil? then
|
287
|
-
nil
|
288
|
-
else
|
289
|
-
@input.name
|
290
|
-
end
|
291
|
-
end
|
180
|
+
configure settings, filename
|
181
|
+
end
|
292
182
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
183
|
+
# Update this configuration with a configuration file and return a new
|
184
|
+
# configuration
|
185
|
+
#
|
186
|
+
# @param [String] filename path to the configuration file
|
187
|
+
#
|
188
|
+
# @return [Configuration] a new configuration
|
189
|
+
def reconfigure(filename)
|
190
|
+
settings = PandocomaticYAML.load_file filename
|
191
|
+
new_config = Marshal.load(Marshal.dump(self))
|
192
|
+
new_config.configure settings, filename
|
193
|
+
new_config
|
194
|
+
rescue StandardError => e
|
195
|
+
raise ConfigurationError.new(:unable_to_load_config_file, e, filename)
|
196
|
+
end
|
299
197
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
end
|
308
|
-
end
|
198
|
+
# Configure pandocomatic based on a settings Hash
|
199
|
+
#
|
200
|
+
# @param settings [Hash] a settings Hash to mixin in this
|
201
|
+
# @param path [String] the configuration's path or filename
|
202
|
+
# Configuration.
|
203
|
+
def configure(settings, path)
|
204
|
+
reset_settings settings['settings'] if settings.key? 'settings'
|
309
205
|
|
310
|
-
|
311
|
-
#
|
312
|
-
# @param src [String] path to a source file
|
313
|
-
# @return [Boolean] True if this source file matches the pattern in
|
314
|
-
# the 'skip' setting, false otherwise.
|
315
|
-
def skip?(src)
|
316
|
-
if @settings.has_key? 'skip' then
|
317
|
-
@settings['skip'].any? {|glob| File.fnmatch glob, File.basename(src)}
|
318
|
-
else
|
319
|
-
false
|
320
|
-
end
|
321
|
-
end
|
206
|
+
return unless settings.key? 'templates'
|
322
207
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
def convert?(src)
|
328
|
-
@convert_patterns.values.flatten.any? {|glob| File.fnmatch glob, File.basename(src)}
|
329
|
-
end
|
208
|
+
settings['templates'].each do |name, template|
|
209
|
+
reset_template Template.new(name, template, path)
|
210
|
+
end
|
211
|
+
end
|
330
212
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
end
|
213
|
+
# Convert this Configuration to a String
|
214
|
+
#
|
215
|
+
# @return [String]
|
216
|
+
def to_s
|
217
|
+
marshal_dump
|
218
|
+
end
|
338
219
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
end
|
220
|
+
# Is the dry run CLI option given?
|
221
|
+
#
|
222
|
+
# @return [Boolean]
|
223
|
+
def dry_run?
|
224
|
+
@options[:dry_run_given] and @options[:dry_run]
|
225
|
+
end
|
346
226
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
def match_all_templates?()
|
354
|
-
@settings.has_key? 'match-files' and 'all' == @settings['match-files']
|
355
|
-
end
|
227
|
+
# Is the stdout CLI option given?
|
228
|
+
#
|
229
|
+
# @return [Boolean]
|
230
|
+
def stdout?
|
231
|
+
!@options.nil? and @options[:stdout_given] and @options[:stdout]
|
232
|
+
end
|
356
233
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
def match_first_template?()
|
364
|
-
@settings.has_key? 'match-files' and 'first' == @settings['match-files']
|
365
|
-
end
|
234
|
+
# Is the verbose CLI option given?
|
235
|
+
#
|
236
|
+
# @return [Boolean]
|
237
|
+
def verbose?
|
238
|
+
@options[:verbose_given] and @options[:verbose]
|
239
|
+
end
|
366
240
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
# @param metadata [PandocMetadata] the metadata in the source file
|
374
|
-
def set_extension(dst, template_name, metadata)
|
375
|
-
dir = File.dirname dst
|
376
|
-
ext = File.extname dst
|
377
|
-
basename = File.basename dst, ext
|
378
|
-
File.join dir, "#{basename}.#{find_extension(dst, template_name, metadata)}"
|
379
|
-
end
|
241
|
+
# Is the debug CLI option given?
|
242
|
+
#
|
243
|
+
# @return [Boolean]
|
244
|
+
def debug?
|
245
|
+
@options[:debug_given] and @options[:debug]
|
246
|
+
end
|
380
247
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
# @param metadata [PandocMetadata] the metadata in the source file
|
388
|
-
def set_destination(dst, template_name, metadata)
|
389
|
-
dir = File.dirname dst
|
390
|
-
|
391
|
-
# Use the output option when set.
|
392
|
-
determine_output_in_pandoc = lambda do |pandoc|
|
393
|
-
if pandoc.has_key? "output"
|
394
|
-
output = pandoc["output"]
|
395
|
-
if not output.start_with? "/"
|
396
|
-
# Put it relative to the current directory
|
397
|
-
output = File.join dir, output
|
398
|
-
end
|
399
|
-
output
|
400
|
-
else
|
401
|
-
nil
|
402
|
-
end
|
403
|
-
end
|
404
|
-
|
405
|
-
# Output options in pandoc property have precedence
|
406
|
-
destination = determine_output_in_pandoc.call metadata.pandoc_options
|
407
|
-
rename_script = metadata.pandoc_options["rename"]
|
408
|
-
|
409
|
-
# Output option in template's pandoc property is next
|
410
|
-
if destination.nil? and not template_name.nil? and not template_name.empty? then
|
411
|
-
if @templates[template_name].has_key? "pandoc" and not @templates[template_name]["pandoc"].nil?
|
412
|
-
pandoc = @templates[template_name]["pandoc"]
|
413
|
-
destination = determine_output_in_pandoc.call pandoc
|
414
|
-
rename_script ||= pandoc["rename"]
|
415
|
-
end
|
416
|
-
end
|
248
|
+
# Run pandocomatic in quiet mode?
|
249
|
+
#
|
250
|
+
# @return [Boolean]
|
251
|
+
def quiet?
|
252
|
+
[verbose?, debug?, dry_run?].none?
|
253
|
+
end
|
417
254
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
255
|
+
# Is the modified only CLI option given?
|
256
|
+
#
|
257
|
+
# @return [Boolean]
|
258
|
+
def modified_only?
|
259
|
+
@options[:modified_only_given] and @options[:modified_only]
|
260
|
+
end
|
422
261
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
262
|
+
# Is the version CLI option given?
|
263
|
+
#
|
264
|
+
# @return [Boolean]
|
265
|
+
def show_version?
|
266
|
+
@options[:version_given]
|
267
|
+
end
|
427
268
|
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
269
|
+
# Is the help CLI option given?
|
270
|
+
#
|
271
|
+
# @return [Boolean]
|
272
|
+
def show_help?
|
273
|
+
@options[:help_given]
|
274
|
+
end
|
434
275
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
# @param metadata [PandocMetadata] the metadata in the source file
|
442
|
-
#
|
443
|
-
# @return [String] the extension to use for the destination file
|
444
|
-
def find_extension(dst, template_name, metadata)
|
445
|
-
extension = "html"
|
446
|
-
|
447
|
-
# Pandoc supports enabling / disabling extensions
|
448
|
-
# using +EXTENSION and -EXTENSION
|
449
|
-
strip_extensions = lambda{|format| format.split(/[+-]/).first}
|
450
|
-
use_extension = lambda do |pandoc|
|
451
|
-
if pandoc.has_key? "use-extension"
|
452
|
-
pandoc["use-extension"]
|
453
|
-
else
|
454
|
-
nil
|
455
|
-
end
|
456
|
-
end
|
457
|
-
|
458
|
-
if template_name.nil? or template_name.empty? then
|
459
|
-
ext = use_extension.call metadata.pandoc_options
|
460
|
-
if not ext.nil?
|
461
|
-
extension = ext
|
462
|
-
elsif metadata.pandoc_options.has_key? "to"
|
463
|
-
extension = strip_extensions.call(metadata.pandoc_options["to"])
|
464
|
-
end
|
465
|
-
else
|
466
|
-
if @templates[template_name].has_key? "pandoc" and not @templates[template_name]["pandoc"].nil?
|
467
|
-
pandoc = @templates[template_name]["pandoc"]
|
468
|
-
ext = use_extension.call pandoc
|
469
|
-
|
470
|
-
if not ext.nil?
|
471
|
-
extension = ext
|
472
|
-
elsif pandoc.has_key? "to"
|
473
|
-
extension = strip_extensions.call(pandoc["to"])
|
474
|
-
end
|
475
|
-
end
|
476
|
-
end
|
276
|
+
# Is the data dir CLI option given?
|
277
|
+
#
|
278
|
+
# @return [Boolean]
|
279
|
+
def data_dir?
|
280
|
+
@options[:data_dir_given]
|
281
|
+
end
|
477
282
|
|
478
|
-
|
479
|
-
|
480
|
-
|
283
|
+
# Is the root path CLI option given?
|
284
|
+
#
|
285
|
+
# @return [Boolean]
|
286
|
+
def root_path?
|
287
|
+
@options[:root_path_given]
|
288
|
+
end
|
481
289
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
end
|
489
|
-
end
|
290
|
+
# Is the config CLI option given?
|
291
|
+
#
|
292
|
+
# @return [Boolean]
|
293
|
+
def config?
|
294
|
+
@options[:config_given]
|
295
|
+
end
|
490
296
|
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
def has_template?(template_name)
|
498
|
-
@templates.has_key? template_name
|
499
|
-
end
|
297
|
+
# Is the output CLI option given and can that output be used?
|
298
|
+
#
|
299
|
+
# @return [Boolean]
|
300
|
+
def output?
|
301
|
+
!@options.nil? and @options[:output_given] and @options[:output]
|
302
|
+
end
|
500
303
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
# @return [Hash] The template with template_name.
|
506
|
-
def get_template(template_name)
|
507
|
-
@templates[template_name]
|
508
|
-
end
|
304
|
+
# Get the output file name
|
305
|
+
#
|
306
|
+
# @return [String]
|
307
|
+
attr_reader :output
|
509
308
|
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
@convert_patterns.select do |template_name, globs|
|
517
|
-
globs.any? {|glob| File.fnmatch glob, File.basename(src)}
|
518
|
-
end.keys.first
|
519
|
-
end
|
309
|
+
# Get the source root directory
|
310
|
+
#
|
311
|
+
# @return [String]
|
312
|
+
def src_root
|
313
|
+
@input.nil? ? nil : @input.absolute_path
|
314
|
+
end
|
520
315
|
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
# @return [Array[String]] the template's name to use
|
526
|
-
def determine_templates(src)
|
527
|
-
matches = @convert_patterns.select do |template_name, globs|
|
528
|
-
globs.any? {|glob| File.fnmatch glob, File.basename(src)}
|
529
|
-
end.keys
|
530
|
-
|
531
|
-
if matches.empty?
|
532
|
-
[]
|
533
|
-
elsif match_all_templates?
|
534
|
-
matches
|
535
|
-
else
|
536
|
-
[matches.first]
|
537
|
-
end
|
538
|
-
end
|
316
|
+
# Have input CLI options be given?
|
317
|
+
def input?
|
318
|
+
@options[:input_given]
|
319
|
+
end
|
539
320
|
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
# @return [String] the updated path.
|
551
|
-
def update_path(path, src_dir, dst = "", check_executable = false, output = false)
|
552
|
-
updated_path = path
|
553
|
-
|
554
|
-
if is_local_path? path
|
555
|
-
# refers to a local dir; strip the './' before appending it to
|
556
|
-
# the source directory as to prevent /some/path/./to/path
|
557
|
-
updated_path = path[2..-1]
|
558
|
-
elsif is_absolute_path? path
|
559
|
-
updated_path = path
|
560
|
-
elsif is_root_relative_path? path
|
561
|
-
updated_path = make_path_root_relative path, dst, @root_path
|
562
|
-
else
|
563
|
-
if check_executable
|
564
|
-
updated_path = Configuration.which path
|
565
|
-
end
|
321
|
+
# Get the input file name
|
322
|
+
#
|
323
|
+
# @return [String]
|
324
|
+
def input_file
|
325
|
+
if @input.nil?
|
326
|
+
nil
|
327
|
+
else
|
328
|
+
@input.name
|
329
|
+
end
|
330
|
+
end
|
566
331
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
332
|
+
# Is this Configuration for converting directories?
|
333
|
+
#
|
334
|
+
# @return [Boolean]
|
335
|
+
def directory?
|
336
|
+
!@input.nil? and @input.directory?
|
337
|
+
end
|
572
338
|
|
573
|
-
|
574
|
-
|
339
|
+
# Clean up this configuration. This will remove temporary files
|
340
|
+
# created for the conversion process guided by this Configuration.
|
341
|
+
def clean_up!
|
342
|
+
# If a temporary file has been created while concatenating
|
343
|
+
# multiple input files, ensure it is removed.
|
344
|
+
@input.destroy! if @input.is_a? MultipleFilesInput
|
345
|
+
end
|
575
346
|
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
# 'remove' items are removed from the parent value.
|
589
|
-
#
|
590
|
-
# @param current [Object] the current value
|
591
|
-
# @param parent [Object] the parent value the current might extend
|
592
|
-
# @return [Object] the extended value
|
593
|
-
def self.extend_value(current, parent)
|
594
|
-
if parent.nil?
|
595
|
-
# If no parent value is specified, the current takes
|
596
|
-
# precedence
|
597
|
-
current
|
598
|
-
else
|
599
|
-
if current.nil?
|
600
|
-
# Current nil removes value of parent; follows YAML spec.
|
601
|
-
# Note. take care to actually remove this value from a
|
602
|
-
# Hash. (Like it is done in the next case)
|
603
|
-
nil
|
604
|
-
else
|
605
|
-
if parent.is_a? Hash
|
606
|
-
if current.is_a? Hash
|
607
|
-
# Mixin current and parent values
|
608
|
-
parent.each_pair do |property, value|
|
609
|
-
if current.has_key? property
|
610
|
-
extended_value = Configuration.extend_value(current[property], value)
|
611
|
-
if extended_value.nil?
|
612
|
-
current.delete property
|
613
|
-
else
|
614
|
-
current[property] = extended_value
|
615
|
-
end
|
616
|
-
else
|
617
|
-
current[property] = value
|
618
|
-
end
|
619
|
-
end
|
620
|
-
end
|
621
|
-
current
|
622
|
-
elsif parent.is_a? Array
|
623
|
-
if current.is_a? Hash
|
624
|
-
if current.has_key? 'remove'
|
625
|
-
to_remove = current['remove']
|
626
|
-
|
627
|
-
if to_remove.is_a? Array
|
628
|
-
parent.delete_if {|v| current['remove'].include? v}
|
629
|
-
else
|
630
|
-
parent.delete to_remove
|
631
|
-
end
|
632
|
-
end
|
633
|
-
|
634
|
-
if current.has_key? 'add'
|
635
|
-
to_add = current['add']
|
636
|
-
|
637
|
-
if to_add.is_a? Array
|
638
|
-
parent = current['add'].concat(parent).uniq
|
639
|
-
else
|
640
|
-
parent.push(to_add).uniq
|
641
|
-
end
|
642
|
-
end
|
643
|
-
|
644
|
-
parent
|
645
|
-
elsif current.is_a? Array
|
646
|
-
# Just combine parent and current arrays, current
|
647
|
-
# values take precedence
|
648
|
-
current.concat(parent).uniq
|
649
|
-
else
|
650
|
-
# Unknown what to do, assuming current should take
|
651
|
-
# precedence
|
652
|
-
current
|
653
|
-
end
|
654
|
-
else
|
655
|
-
# Simple values: current replaces parent
|
656
|
-
current
|
657
|
-
end
|
658
|
-
end
|
659
|
-
end
|
660
|
-
end
|
347
|
+
# Should the source file be skipped given this Configuration?
|
348
|
+
#
|
349
|
+
# @param src [String] path to a source file
|
350
|
+
# @return [Boolean] True if this source file matches the pattern in
|
351
|
+
# the 'skip' setting, false otherwise.
|
352
|
+
def skip?(src)
|
353
|
+
if @settings.key? 'skip'
|
354
|
+
@settings['skip'].any? { |glob| File.fnmatch glob, File.basename(src) }
|
355
|
+
else
|
356
|
+
false
|
357
|
+
end
|
358
|
+
end
|
661
359
|
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
360
|
+
# Should the source file be converted given this Configuration?
|
361
|
+
#
|
362
|
+
# @param src [String] True if this source file matches the 'glob'
|
363
|
+
# patterns in a template, false otherwise.
|
364
|
+
def convert?(src)
|
365
|
+
@convert_patterns.values.flatten.any? { |glob| File.fnmatch glob, File.basename(src) }
|
366
|
+
end
|
669
367
|
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
settings.each do |setting, value|
|
678
|
-
case setting
|
679
|
-
when 'skip'
|
680
|
-
@settings['skip'] = @settings['skip'].concat(value).uniq
|
681
|
-
when 'data-dir'
|
682
|
-
next # skip data-dir setting; is set once in initialization
|
683
|
-
else
|
684
|
-
@settings[setting] = value
|
685
|
-
end
|
686
|
-
end
|
687
|
-
end
|
368
|
+
# Should pandocomatic be run recursively given this Configuration?
|
369
|
+
#
|
370
|
+
# @return [Boolean] True if the setting 'recursive' is true, false
|
371
|
+
# otherwise
|
372
|
+
def recursive?
|
373
|
+
@settings.key? 'recursive' and @settings['recursive']
|
374
|
+
end
|
688
375
|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
376
|
+
# Should pandocomatic follow symbolic links given this Configuration?
|
377
|
+
#
|
378
|
+
# @return [Boolean] True if the setting 'follow_links' is true, false
|
379
|
+
# otherwise
|
380
|
+
def follow_links?
|
381
|
+
@settings.key? 'follow_links' and @settings['follow_links']
|
382
|
+
end
|
696
383
|
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
fields = [
|
708
|
-
'glob',
|
709
|
-
'metadata',
|
710
|
-
'setup',
|
711
|
-
'preprocessors',
|
712
|
-
'pandoc',
|
713
|
-
'postprocessors',
|
714
|
-
'cleanup'
|
715
|
-
]
|
716
|
-
|
717
|
-
fields.each do |field|
|
718
|
-
parent = base_template[field]
|
719
|
-
current = mixin_template[field]
|
720
|
-
extended_value = Configuration.extend_value current, parent
|
721
|
-
|
722
|
-
if extended_value.nil?
|
723
|
-
base_template.delete field
|
724
|
-
else
|
725
|
-
base_template[field] = extended_value
|
726
|
-
end
|
727
|
-
end
|
384
|
+
# Should pandocomatic convert a file with all matching templates or
|
385
|
+
# only with the first matching template? Note. A 'use-template'
|
386
|
+
# statement in a document will overrule this setting.
|
387
|
+
#
|
388
|
+
# @return [Boolean] True if the setting 'match-files' is 'all', false
|
389
|
+
# otherwise.
|
390
|
+
def match_all_templates?
|
391
|
+
@settings.key? 'match-files' and @settings['match-files'] == 'all'
|
392
|
+
end
|
728
393
|
|
729
|
-
|
730
|
-
|
394
|
+
# Should pandocomatic convert a file with the first matching templates
|
395
|
+
# or with all matching templates? Note. Multiple 'use-template'
|
396
|
+
# statements in a document will overrule this setting.
|
397
|
+
#
|
398
|
+
# @return [Boolean] True if the setting 'match-files' is 'first', false
|
399
|
+
# otherwise.
|
400
|
+
def match_first_template?
|
401
|
+
@settings.key? 'match-files' and @settings['match-files'] == 'first'
|
402
|
+
end
|
731
403
|
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
merge resolved_template, clone_template(@templates[name])
|
746
|
-
else
|
747
|
-
warn "Cannot find template with name '#{name}'. Skipping this template while extending: '#{template.to_s}'."
|
748
|
-
end
|
749
|
-
end
|
404
|
+
# Set the extension of the destination file given this Confguration,
|
405
|
+
# template, and metadata
|
406
|
+
#
|
407
|
+
# @param dst [String] path to a destination file
|
408
|
+
# @param template_name [String] the name of the template used to
|
409
|
+
# convert to destination
|
410
|
+
# @param metadata [PandocMetadata] the metadata in the source file
|
411
|
+
def set_extension(dst, template_name, metadata)
|
412
|
+
dir = File.dirname dst
|
413
|
+
ext = File.extname dst
|
414
|
+
basename = File.basename dst, ext
|
415
|
+
File.join dir, "#{basename}.#{find_extension(template_name, metadata)}"
|
416
|
+
end
|
750
417
|
|
751
|
-
|
752
|
-
|
418
|
+
# Set the destination file given this Confguration,
|
419
|
+
# template, and metadata
|
420
|
+
#
|
421
|
+
# @param dst [String] path to a destination file
|
422
|
+
# @param template_name [String] the name of the template used to
|
423
|
+
# convert to destination
|
424
|
+
# @param metadata [PandocMetadata] the metadata in the source file
|
425
|
+
def set_destination(dst, template_name, metadata)
|
426
|
+
return dst if dst.is_a? Tempfile
|
427
|
+
|
428
|
+
dir = File.dirname dst
|
429
|
+
|
430
|
+
# Use the output option when set.
|
431
|
+
determine_output_in_pandoc = lambda do |pandoc|
|
432
|
+
if pandoc.key? 'output'
|
433
|
+
output = pandoc['output']
|
434
|
+
unless output.start_with? '/'
|
435
|
+
# Put it relative to the current directory
|
436
|
+
output = File.join dir, output
|
437
|
+
end
|
438
|
+
output
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
# Output options in pandoc property have precedence
|
443
|
+
destination = determine_output_in_pandoc.call metadata.pandoc_options
|
444
|
+
rename_script = metadata.pandoc_options['rename']
|
445
|
+
|
446
|
+
# Output option in template's pandoc property is next
|
447
|
+
if destination.nil? && !template_name.nil? && !template_name.empty? && @templates[template_name].pandoc?
|
448
|
+
pandoc = @templates[template_name].pandoc
|
449
|
+
destination = determine_output_in_pandoc.call pandoc
|
450
|
+
rename_script ||= pandoc['rename']
|
451
|
+
end
|
452
|
+
|
453
|
+
# Else fall back to taking the input file as output file with the
|
454
|
+
# extension updated to the output format
|
455
|
+
if destination.nil?
|
456
|
+
destination = set_extension dst, template_name, metadata
|
457
|
+
|
458
|
+
destination = rename_destination(rename_script, destination) unless rename_script.nil?
|
459
|
+
end
|
460
|
+
|
461
|
+
# If there is a single file input without output specified, set
|
462
|
+
# the output now that we know what the output filename is.
|
463
|
+
@output = destination.delete_prefix './' unless output?
|
464
|
+
|
465
|
+
destination
|
466
|
+
end
|
753
467
|
|
754
|
-
|
755
|
-
|
468
|
+
# Find the extension of the destination file given this Confguration,
|
469
|
+
# template, and metadata
|
470
|
+
#
|
471
|
+
# @param template_name [String] the name of the template used to
|
472
|
+
# convert to destination
|
473
|
+
# @param metadata [PandocMetadata] the metadata in the source file
|
474
|
+
#
|
475
|
+
# @return [String] the extension to use for the destination file
|
476
|
+
def find_extension(template_name, metadata)
|
477
|
+
extension = 'html'
|
478
|
+
|
479
|
+
# Pandoc supports enabling / disabling extensions
|
480
|
+
# using +EXTENSION and -EXTENSION
|
481
|
+
strip_extensions = ->(format) { format.split(/[+-]/).first }
|
482
|
+
use_extension = lambda do |pandoc|
|
483
|
+
pandoc['use-extension'] if pandoc.key? 'use-extension'
|
484
|
+
end
|
485
|
+
|
486
|
+
if template_name.nil? || template_name.empty?
|
487
|
+
ext = use_extension.call metadata.pandoc_options
|
488
|
+
if !ext.nil?
|
489
|
+
extension = ext
|
490
|
+
elsif metadata.pandoc_options.key? 'to'
|
491
|
+
extension = strip_extensions.call(metadata.pandoc_options['to'])
|
492
|
+
end
|
493
|
+
elsif @templates[template_name].pandoc?
|
494
|
+
pandoc = @templates[template_name].pandoc
|
495
|
+
ext = use_extension.call pandoc
|
496
|
+
|
497
|
+
if !ext.nil?
|
498
|
+
extension = ext
|
499
|
+
elsif pandoc.key? 'to'
|
500
|
+
extension = strip_extensions.call(pandoc['to'])
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
DEFAULT_EXTENSION[extension] || extension
|
505
|
+
end
|
756
506
|
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
@templates[name] = extended_template
|
770
|
-
end
|
771
|
-
|
772
|
-
if extended_template.has_key? 'glob' then
|
773
|
-
@convert_patterns[name] = extended_template['glob']
|
774
|
-
end
|
775
|
-
end
|
507
|
+
# Is filename a markdown file according to its extension?
|
508
|
+
#
|
509
|
+
# @param filename [String] the filename to check
|
510
|
+
# @return [Boolean] True if filename has a markdown extension.
|
511
|
+
def markdown_file?(filename)
|
512
|
+
if filename.nil?
|
513
|
+
false
|
514
|
+
else
|
515
|
+
ext = File.extname(filename).delete_prefix('.')
|
516
|
+
DEFAULT_EXTENSION.key(ext) == 'markdown'
|
517
|
+
end
|
518
|
+
end
|
776
519
|
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
exts.each { |ext|
|
787
|
-
exe = File.join(path, "#{cmd}#{ext}")
|
788
|
-
return exe if File.executable?(exe) and
|
789
|
-
not File.directory?(exe)
|
790
|
-
}
|
791
|
-
end
|
792
|
-
return nil
|
793
|
-
end
|
520
|
+
# Is there a template with template_name in this Configuration?
|
521
|
+
#
|
522
|
+
# @param template_name [String] a template's name
|
523
|
+
#
|
524
|
+
# @return [Boolean] True if there is a template with name equal to
|
525
|
+
# template_name in this Configuration
|
526
|
+
def template?(template_name)
|
527
|
+
@templates.key? template_name
|
528
|
+
end
|
794
529
|
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
530
|
+
# Get the template with template_name from this Configuration
|
531
|
+
#
|
532
|
+
# @param template_name [String] a template's name
|
533
|
+
#
|
534
|
+
# @return [Template] The template with template_name.
|
535
|
+
def get_template(template_name)
|
536
|
+
@templates[template_name]
|
537
|
+
end
|
802
538
|
|
803
|
-
|
539
|
+
# Determine the template to use with this source document given this
|
540
|
+
# Configuration.
|
541
|
+
#
|
542
|
+
# @param src [String] path to the source document
|
543
|
+
# @return [String] the template's name to use
|
544
|
+
def determine_template(src)
|
545
|
+
@convert_patterns.select do |_, globs|
|
546
|
+
globs.any? { |glob| File.fnmatch glob, File.basename(src) }
|
547
|
+
end.keys.first
|
548
|
+
end
|
804
549
|
|
805
|
-
|
806
|
-
|
807
|
-
|
550
|
+
# Determine the templates to use with this source document given this
|
551
|
+
# Configuration.
|
552
|
+
#
|
553
|
+
# @param src [String] path to the source document
|
554
|
+
# @return [Array[String]] the template's name to use
|
555
|
+
def determine_templates(src)
|
556
|
+
matches = @convert_patterns.select do |_, globs|
|
557
|
+
globs.any? { |glob| File.fnmatch glob, File.basename(src) }
|
558
|
+
end.keys
|
559
|
+
|
560
|
+
if matches.empty?
|
561
|
+
[]
|
562
|
+
elsif match_all_templates?
|
563
|
+
matches
|
564
|
+
else
|
565
|
+
[matches.first]
|
566
|
+
end
|
567
|
+
end
|
808
568
|
|
809
|
-
|
810
|
-
|
569
|
+
private
|
570
|
+
|
571
|
+
# Reset the settings for pandocomatic based on a new settings Hash
|
572
|
+
#
|
573
|
+
# @param settings [Hash] the new settings to use to reset the settings in
|
574
|
+
# this Configuration with.
|
575
|
+
def reset_settings(settings)
|
576
|
+
settings.each do |setting, value|
|
577
|
+
case setting
|
578
|
+
when 'skip'
|
579
|
+
@settings['skip'] = @settings['skip'].concat(value).uniq
|
580
|
+
when 'data-dir'
|
581
|
+
next # skip data-dir setting; is set once in initialization
|
582
|
+
else
|
583
|
+
@settings[setting] = value
|
584
|
+
end
|
585
|
+
end
|
586
|
+
end
|
811
587
|
|
812
|
-
|
588
|
+
# Resolve the templates the templates extends and mixes them in, in
|
589
|
+
# order of occurrence.
|
590
|
+
#
|
591
|
+
# @param template [Template] the template to extend
|
592
|
+
# @return [Template] the resolved template
|
593
|
+
def extend_template(template)
|
594
|
+
resolved_template = Template.new template.name
|
813
595
|
|
814
|
-
|
815
|
-
renamed_dst = Processor.run(script, dst)
|
816
|
-
if not renamed_dst.nil? and not renamed_dst.empty?
|
817
|
-
renamed_dst.strip
|
818
|
-
else
|
819
|
-
raise StandardError,new("Running rename script '#{script}' on destination '#{dst}' did not result in a renamed destination.")
|
820
|
-
dst
|
821
|
-
end
|
822
|
-
rescue StandardError => e
|
823
|
-
ProcessorError.new(:error_processing_script, e, [script, dst])
|
824
|
-
dst
|
825
|
-
end
|
826
|
-
end
|
827
|
-
|
828
|
-
def marshal_dump()
|
829
|
-
[@data_dir, @settings, @templates, @convert_patterns]
|
830
|
-
end
|
596
|
+
missing = []
|
831
597
|
|
832
|
-
|
833
|
-
|
598
|
+
template.extends.each do |name|
|
599
|
+
if @templates.key? name
|
600
|
+
resolved_template.merge! Template.clone(@templates[name])
|
601
|
+
else
|
602
|
+
missing << name
|
834
603
|
end
|
604
|
+
end
|
835
605
|
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
end
|
606
|
+
unless missing.empty?
|
607
|
+
if template.internal?
|
608
|
+
warn "WARNING: Unable to find templates [#{missing.join(', ')}] while resolving internal template."
|
609
|
+
else
|
610
|
+
warn "WARNING: Unable to find templates [#{missing.join(', ')}] while resolving"\
|
611
|
+
" the external template '#{template.name}' from configuration file '#{template.path}'."
|
843
612
|
end
|
613
|
+
end
|
844
614
|
|
845
|
-
|
846
|
-
|
847
|
-
|
615
|
+
resolved_template.merge! template
|
616
|
+
resolved_template
|
617
|
+
end
|
848
618
|
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
619
|
+
# Reset the template with name in this Configuration based on a new
|
620
|
+
# template
|
621
|
+
#
|
622
|
+
# @param template [Template] the template to use to update the template in
|
623
|
+
# this Configuarion with
|
624
|
+
def reset_template(template)
|
625
|
+
name = template.name
|
626
|
+
extended_template = extend_template template
|
627
|
+
|
628
|
+
if @templates.key? name
|
629
|
+
@templates[name].merge! extended_template
|
630
|
+
else
|
631
|
+
@templates[name] = extended_template
|
632
|
+
end
|
633
|
+
|
634
|
+
@convert_patterns[name] = extended_template.glob if extended_template.glob?
|
635
|
+
end
|
855
636
|
|
856
|
-
|
857
|
-
|
637
|
+
# Rename path by using rename script. If script fails somehow, warn
|
638
|
+
# and return the original destination.
|
639
|
+
#
|
640
|
+
# @param rename_script [String] absolute path to script to run
|
641
|
+
# @param dst [String] original destination to rename
|
642
|
+
def rename_destination(rename_script, dst)
|
643
|
+
script = Path.update_path(self, rename_script)
|
644
|
+
|
645
|
+
command, *parameters = script.shellsplit # split on spaces unless it is preceded by a backslash
|
646
|
+
|
647
|
+
unless File.exist? command
|
648
|
+
command = Path.which(command)
|
649
|
+
script = "#{command} #{parameters.join(' ')}"
|
650
|
+
|
651
|
+
raise ProcessorError.new(:script_does_not_exist, nil, command) if command.nil?
|
652
|
+
end
|
653
|
+
|
654
|
+
raise ProcessorError.new(:script_is_not_executable, nil, command) unless File.executable? command
|
655
|
+
|
656
|
+
begin
|
657
|
+
renamed_dst = Processor.run(script, dst)
|
658
|
+
if !renamed_dst.nil? && !renamed_dst.empty?
|
659
|
+
renamed_dst.strip
|
660
|
+
else
|
661
|
+
raise StandardError, new("Running rename script '#{script}' on destination '#{dst}'"\
|
662
|
+
' did not result in a renamed destination.')
|
663
|
+
end
|
664
|
+
rescue StandardError => e
|
665
|
+
ProcessorError.new(:error_processing_script, e, [script, dst])
|
666
|
+
dst
|
667
|
+
end
|
668
|
+
end
|
858
669
|
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
end
|
670
|
+
def marshal_dump
|
671
|
+
[@data_dir, @settings, @templates, @convert_patterns]
|
672
|
+
end
|
863
673
|
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
"#{rel_start}#{path}"
|
868
|
-
end
|
869
|
-
else
|
870
|
-
# Because the destination is not in a subdirectory of root, a
|
871
|
-
# relative path to that root cannot be created. Instead,
|
872
|
-
# the path is assumed to be absolute relative to root
|
873
|
-
root = root.delete_suffix "/" if root.end_with? "/"
|
874
|
-
path = path.delete_prefix "/" if path.start_with? "/"
|
674
|
+
def marshal_load(array)
|
675
|
+
@data_dir, @settings, @templates, @convert_patterns = array
|
676
|
+
end
|
875
677
|
|
876
|
-
|
877
|
-
|
678
|
+
def to_stdout?(options)
|
679
|
+
!options.nil? and options[:stdout_given] and options[:stdout]
|
680
|
+
end
|
878
681
|
|
682
|
+
# Read a list of configuration files and create a
|
683
|
+
# pandocomatic object that mixes templates from most generic to most
|
684
|
+
# specific.
|
685
|
+
def load_configuration_hierarchy(options, data_dirs)
|
686
|
+
# Read and mixin templates from most generic config file to most
|
687
|
+
# specific, thus in reverse order.
|
688
|
+
@config_files = determine_config_files(options, data_dirs).reverse
|
689
|
+
@config_files.each do |config_file|
|
690
|
+
configure PandocomaticYAML.load_file(config_file), config_file
|
691
|
+
rescue StandardError => e
|
692
|
+
raise ConfigurationError.new(:unable_to_load_config_file, e, config_file)
|
693
|
+
end
|
694
|
+
|
695
|
+
load @config_files.last
|
696
|
+
end
|
879
697
|
|
880
|
-
|
698
|
+
def determine_config_files(options, data_dirs = [])
|
699
|
+
config_files = []
|
700
|
+
# Get config file from option, if any
|
701
|
+
config_files << options[:config] if options[:config_given]
|
881
702
|
|
882
|
-
|
883
|
-
|
703
|
+
# Get config file in each data_dir
|
704
|
+
data_dirs.each do |data_dir|
|
705
|
+
config_files << File.join(data_dir, CONFIG_FILE) if Dir.entries(data_dir).include? CONFIG_FILE
|
706
|
+
end
|
884
707
|
|
885
|
-
|
886
|
-
|
887
|
-
elsif Dir.entries(data_dir).include? CONFIG_FILE
|
888
|
-
config_file = File.join(data_dir, CONFIG_FILE)
|
889
|
-
elsif Dir.entries(Dir.pwd()).include? CONFIG_FILE
|
890
|
-
config_file = File.join(Dir.pwd(), CONFIG_FILE)
|
891
|
-
else
|
892
|
-
# Fall back to default configuration file distributed with
|
893
|
-
# pandocomatic
|
894
|
-
config_file = File.join(__dir__, 'default_configuration.yaml')
|
895
|
-
end
|
708
|
+
# Default configuration file distributes with pandocomatic
|
709
|
+
config_files << File.join(__dir__, 'default_configuration.yaml')
|
896
710
|
|
897
|
-
|
711
|
+
config_files.map do |config_file|
|
712
|
+
path = File.absolute_path config_file
|
898
713
|
|
899
|
-
|
900
|
-
|
901
|
-
|
714
|
+
raise ConfigurationError.new(:config_file_does_not_exist, nil, path) unless File.exist? path
|
715
|
+
raise ConfigurationError.new(:config_file_is_not_a_file, nil, path) unless File.file? path
|
716
|
+
raise ConfigurationError.new(:config_file_is_not_readable, nil, path) unless File.readable? path
|
902
717
|
|
903
|
-
|
904
|
-
|
718
|
+
path
|
719
|
+
end
|
720
|
+
end
|
905
721
|
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
if options[:data_dir_given]
|
910
|
-
data_dir = options[:data_dir]
|
911
|
-
else
|
912
|
-
# No data-dir option given: try to find the default one from pandoc
|
913
|
-
begin
|
914
|
-
data_dir = Paru::Pandoc.info()[:data_dir]
|
915
|
-
|
916
|
-
# If pandoc's data dir does not exist, however, fall back
|
917
|
-
# to the current directory
|
918
|
-
unless File.exist? File.absolute_path(data_dir)
|
919
|
-
data_dir = Dir.pwd
|
920
|
-
end
|
921
|
-
rescue Paru::Error => e
|
922
|
-
# If pandoc cannot be run, continuing probably does not work out
|
923
|
-
# anyway, so raise pandoc error
|
924
|
-
raise PandocError.new(:error_running_pandoc, e, data_dir)
|
925
|
-
rescue StandardError => e
|
926
|
-
# Ignore error and use the current working directory as default working directory
|
927
|
-
data_dir = Dir.pwd
|
928
|
-
end
|
929
|
-
end
|
722
|
+
def determine_config_file(options, data_dir = Dir.pwd)
|
723
|
+
determine_config_files(options, [data_dir]).first
|
724
|
+
end
|
930
725
|
|
931
|
-
|
932
|
-
|
726
|
+
# Determine all data directories to use
|
727
|
+
def determine_data_dirs(options)
|
728
|
+
data_dirs = []
|
933
729
|
|
934
|
-
|
935
|
-
|
936
|
-
raise ConfigurationError.new(:data_dir_is_not_readable, nil, path) unless File.readable? path
|
730
|
+
# Data dir from CLI option
|
731
|
+
data_dirs << options[:data_dir] if options[:data_dir_given]
|
937
732
|
|
938
|
-
|
939
|
-
|
733
|
+
# Pandoc's default data dir
|
734
|
+
begin
|
735
|
+
data_dir = Paru::Pandoc.info[:data_dir]
|
940
736
|
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
737
|
+
# If pandoc's data dir does not exist, however, fall back
|
738
|
+
# to the current directory
|
739
|
+
data_dirs << if File.exist? File.absolute_path(data_dir)
|
740
|
+
data_dir
|
741
|
+
else
|
742
|
+
Dir.pwd
|
743
|
+
end
|
744
|
+
rescue Paru::Error => e
|
745
|
+
# If pandoc cannot be run, continuing probably does not work out
|
746
|
+
# anyway, so raise pandoc error
|
747
|
+
raise PandocError.new(:error_running_pandoc, e, data_dir)
|
748
|
+
rescue StandardError
|
749
|
+
# Ignore error and use the current working directory as default working directory
|
750
|
+
data_dirs << Dir.pwd
|
751
|
+
end
|
752
|
+
|
753
|
+
# check if data directories do exist and are readable
|
754
|
+
data_dirs.uniq.map do |dir|
|
755
|
+
path = File.absolute_path dir
|
756
|
+
|
757
|
+
raise ConfigurationError.new(:data_dir_does_not_exist, nil, path) unless File.exist? path
|
758
|
+
raise ConfigurationError.new(:data_dir_is_not_a_directory, nil, path) unless File.directory? path
|
759
|
+
raise ConfigurationError.new(:data_dir_is_not_readable, nil, path) unless File.readable? path
|
760
|
+
|
761
|
+
path
|
762
|
+
end
|
950
763
|
end
|
764
|
+
end
|
765
|
+
# rubocop:enable Metrics
|
951
766
|
end
|