Class: Pandocomatic::ConvertFileCommand

Inherits:
Command
  • Object
show all
Defined in:
lib/pandocomatic/command/convert_file_command.rb

Overview

Command to convert a file

Instance Attribute Summary collapse

Attributes inherited from Command

#errors, #index

Instance Method Summary collapse

Methods inherited from Command

#all_errors, #count, #debug?, #directory?, #dry_run?, #errors?, #execute, #file_modified?, #index_to_s, #make_quiet, #modified_only?, #multiple?, #quiet?, reset, #runnable?, #skip?, #src_root, #uncount

Constructor Details

#initialize(config, src, dst, template_name = nil) ⇒ ConvertFileCommand

Create a new ConvertFileCommand

Parameters:

  • config (Configuration)

    pandocomatic’s configuration

  • src (String)

    the path to the file to convert

  • dst (String)

    the path to save the output of the conversion

  • template_name (String = nil) (defaults to: nil)

    the template to use while converting this file



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/pandocomatic/command/convert_file_command.rb', line 91

def initialize(config, src, dst, template_name = nil)
  super()

  @config = config
  @src = src
  @dst = dst

  @template_name = if template_name.nil? || template_name.empty?
                     @config.determine_template @src
                   else
                     template_name
                   end

  @metadata = PandocMetadata.load_file @src
  @dst = @config.set_destination @dst, @template_name, @metadata

  @errors.push IOError.new(:file_does_not_exist, nil, @src) unless File.exist? @src
  @errors.push IOError.new(:file_is_not_a_file, nil, @src) unless File.file? @src
  @errors.push IOError.new(:file_is_not_readable, nil, @src) unless File.readable? @src
end

Instance Attribute Details

#configConfiguration

Returns the configuration of pandocomatic used to convert the file.

Returns:

  • (Configuration)

    the configuration of pandocomatic used to convert the file



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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/pandocomatic/command/convert_file_command.rb', line 81

class ConvertFileCommand < Command
  attr_reader :config, :src, :dst

  # Create a new ConvertFileCommand
  #
  # @param config [Configuration] pandocomatic's configuration
  # @param src [String] the path to the file to convert
  # @param dst [String] the path to save the output of the conversion
  # @param template_name [String = nil] the template to use while converting
  #   this file
  def initialize(config, src, dst, template_name = nil)
    super()

    @config = config
    @src = src
    @dst = dst

    @template_name = if template_name.nil? || template_name.empty?
                       @config.determine_template @src
                     else
                       template_name
                     end

    @metadata = PandocMetadata.load_file @src
    @dst = @config.set_destination @dst, @template_name, @metadata

    @errors.push IOError.new(:file_does_not_exist, nil, @src) unless File.exist? @src
    @errors.push IOError.new(:file_is_not_a_file, nil, @src) unless File.file? @src
    @errors.push IOError.new(:file_is_not_readable, nil, @src) unless File.readable? @src
  end

  # Execute this ConvertFileCommand
  def run
    convert_file
  end

  # Create a string representation of this ConvertFileCommand
  #
  # @return [String]
  def to_s
    str = "convert #{File.basename @src} #{"-> #{File.basename @dst}" unless @dst.nil?}"
    unless @metadata.unique?
      str += "\n\t encountered multiple YAML metadata blocks with a pandocomatic property. " \
             'Only the pandocomatic property in the first YAML metadata block is being used; ' \
             'the others are discarded.'
    end
    str
  end

  private

  INTERNAL_TEMPLATE = 'internal template'

  def convert_file
    pandoc_options = @metadata.pandoc_options || {}
    template = nil

    # Determine the actual options and settings to use when converting this
    # file.
    if !@template_name.nil? && !@template_name.empty?
      unless @config.template? @template_name
        raise ConfigurationError.new(:no_such_template, nil,
                                     @template_name)
      end

      template = @config.get_template @template_name
      pandoc_options = Template.extend_value(pandoc_options, template.pandoc)
    else
      template = Template.new INTERNAL_TEMPLATE
    end

    # Ignore the `--verbose` option, and warn about ignoring it
    if pandoc_options.key? 'verbose'
      pandoc_options.delete 'verbose'
      warn 'WARNING: Ignoring the pandoc option --verbose because it ' \
           'might interfere with the working of pandocomatic.'
    end

    template.merge! Template.new(INTERNAL_TEMPLATE, @metadata.pandocomatic) if @metadata.pandocomatic?

    # Write out the results of the conversion process to file.
    @dst = @metadata.pandoc_options['output'] if @dst.to_s.empty? && @metadata.pandoc_options.key?('output')

    # Run setup scripts
    setup template

    # Read in the file to convert
    input = File.read @src

    # Run the default preprocessors to mix-in information about the file
    # that is being converted and mix-in the template's metadata section as
    # well
    input = FileInfoPreprocessor.run input, @src, src_root, pandoc_options
    input = MetadataPreprocessor.run input, template. if template.metadata?

    # Convert the file by preprocessing it, run pandoc on it, and
    # postprocessing the output
    input = preprocess input, template
    input = pandoc input, pandoc_options, File.dirname(@src)
    output = postprocess input, template

    begin
      # Either output to file or to STDOUT.

      if @config.stdout?
        puts output
        @dst.close!
      else
        unless use_output_option @dst
          File.open(@dst, 'w') do |file|
            raise IOError.new(:file_is_not_a_file, nil, @dst) unless File.file? @dst
            raise IOError.new(:file_is_not_writable, nil, @dst) unless File.writable? @dst

            file << output
          end
        end
      end
    rescue StandardError => e
      raise IOError.new(:error_writing_file, e, @dst)
    end

    # run cleanup scripts
    cleanup template
  end

  def pandoc(input, options, src_dir)
    absolute_dst = File.expand_path @dst
    Dir.chdir(src_dir) do
      converter = Paru::Pandoc.new
      options.each do |option, value|
        # Options come from a YAML string. In YAML, properties without a value get value nil.
        # Interpret these empty properties as "skip this property"
        next if value.nil?

        if PANDOC_OPTIONS_WITH_PATH.include? option
          executable = option == 'filter'
          value = if value.is_a? Array
                    value.map { |v| Path.update_path(@config, v, absolute_dst, check_executable: executable) }
                  else
                    Path.update_path(@config, value, @dst, check_executable: executable)
                  end
        end

        # There is no "pdf" output format; change it to latex but keep the
        # extension.
        value = determine_output_for_pdf(options) if (option == 'to') && (value == 'pdf')

        begin
          # Pandoc multi-word options can have the multiple words separated by
          # both underscore (_) and dash (-).
          option = option.gsub '-', '_'
          converter.send option, value unless
                  (option == 'output') ||
                  (option == 'use_extension') ||
                  (option == 'rename')
          # don't let pandoc write the output to enable postprocessing
        rescue StandardError
          if debug?
            warn "WARNING: The pandoc option '#{option}' (with value '#{value}') " \
                 'is not recognized by paru. This option is skipped.'
          end
        end
      end

      converter.send 'output', absolute_dst if use_output_option absolute_dst

      begin
        puts converter.to_command if debug?
        converter << input
      rescue Paru::Error => e
        raise PandocError.new(:error_running_pandoc, e, input)
      end
    end
  end

  # Preprocess the input
  #
  # @param input [String] the input to preprocess
  # @param template [Template] template
  #
  # @return [String] the generated output
  def preprocess(input, template)
    process input, Template::PREPROCESSORS, template
  end

  # Postprocess the input
  #
  # @param input [String] the input to postprocess
  # @param template [Template] template
  #
  # @return [String] the generated output
  def postprocess(input, template)
    process input, Template::POSTPROCESSORS, template
  end

  # Run setup scripts
  #
  # @param template [Template] template
  def setup(template)
    process '', Template::SETUP, template
  end

  # Run cleanup scripts
  #
  # @param template [Template] template
  def cleanup(template)
    process '', Template::CLEANUP, template
  end

  # Run the input string through a list of filters called processors. There
  # are various types: preprocessors and postprocessors, setup and
  # cleanup, and rename
  def process(input, type, template)
    if template.send "#{type}?"
      processors = template.send type
      output = input
      processors.each do |processor|
        script = if Path.local_path? processor
                   processor
                 else
                   Path.update_path(@config, processor, @dst, check_executable: true)
                 end

        command, *parameters = script.shellsplit # split on spaces unless it is preceded by a backslash

        unless File.exist? command
          command = Path.which(command)
          script = "#{command} #{parameters.join(' ')}"

          raise ProcessorError.new(:script_does_not_exist, nil, command) if command.nil?
        end

        raise ProcessorError.new(:script_is_not_executable, nil, command) unless File.executable? command

        begin
          output = Processor.run(script, output)
        rescue StandardError => e
          ProcessorError.new(:error_processing_script, e, [script, @src])
        end
      end
      output
    else
      input
    end
  end

  def use_output_option(dst)
    OUTPUT_FORMATS.include?(File.extname(dst).slice(1..-1))
  end

  # Pandoc version 2 supports multiple pdf engines. Determine which
  # to use given the options.
  #
  # @param options [Hash] the options to a paru pandoc converter
  # @return [String] the output format for the pdf engine to use.
  def determine_output_for_pdf(options)
    if options.key? 'pdf-engine'
      engine = options['pdf-engine']
      case engine
      when 'context'
        'context'
      when 'pdfroff'
        'ms'
      when 'wkhtmltopdf', 'weasyprint', 'prince'
        'html'
      else
        'latex'
      end
    else
      # According to pandoc's manual, the default is LaTeX
      'latex'
    end
  end
end

#dstString

Returns the path to the output file.

Returns:

  • (String)

    the path to the output file



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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/pandocomatic/command/convert_file_command.rb', line 81

class ConvertFileCommand < Command
  attr_reader :config, :src, :dst

  # Create a new ConvertFileCommand
  #
  # @param config [Configuration] pandocomatic's configuration
  # @param src [String] the path to the file to convert
  # @param dst [String] the path to save the output of the conversion
  # @param template_name [String = nil] the template to use while converting
  #   this file
  def initialize(config, src, dst, template_name = nil)
    super()

    @config = config
    @src = src
    @dst = dst

    @template_name = if template_name.nil? || template_name.empty?
                       @config.determine_template @src
                     else
                       template_name
                     end

    @metadata = PandocMetadata.load_file @src
    @dst = @config.set_destination @dst, @template_name, @metadata

    @errors.push IOError.new(:file_does_not_exist, nil, @src) unless File.exist? @src
    @errors.push IOError.new(:file_is_not_a_file, nil, @src) unless File.file? @src
    @errors.push IOError.new(:file_is_not_readable, nil, @src) unless File.readable? @src
  end

  # Execute this ConvertFileCommand
  def run
    convert_file
  end

  # Create a string representation of this ConvertFileCommand
  #
  # @return [String]
  def to_s
    str = "convert #{File.basename @src} #{"-> #{File.basename @dst}" unless @dst.nil?}"
    unless @metadata.unique?
      str += "\n\t encountered multiple YAML metadata blocks with a pandocomatic property. " \
             'Only the pandocomatic property in the first YAML metadata block is being used; ' \
             'the others are discarded.'
    end
    str
  end

  private

  INTERNAL_TEMPLATE = 'internal template'

  def convert_file
    pandoc_options = @metadata.pandoc_options || {}
    template = nil

    # Determine the actual options and settings to use when converting this
    # file.
    if !@template_name.nil? && !@template_name.empty?
      unless @config.template? @template_name
        raise ConfigurationError.new(:no_such_template, nil,
                                     @template_name)
      end

      template = @config.get_template @template_name
      pandoc_options = Template.extend_value(pandoc_options, template.pandoc)
    else
      template = Template.new INTERNAL_TEMPLATE
    end

    # Ignore the `--verbose` option, and warn about ignoring it
    if pandoc_options.key? 'verbose'
      pandoc_options.delete 'verbose'
      warn 'WARNING: Ignoring the pandoc option --verbose because it ' \
           'might interfere with the working of pandocomatic.'
    end

    template.merge! Template.new(INTERNAL_TEMPLATE, @metadata.pandocomatic) if @metadata.pandocomatic?

    # Write out the results of the conversion process to file.
    @dst = @metadata.pandoc_options['output'] if @dst.to_s.empty? && @metadata.pandoc_options.key?('output')

    # Run setup scripts
    setup template

    # Read in the file to convert
    input = File.read @src

    # Run the default preprocessors to mix-in information about the file
    # that is being converted and mix-in the template's metadata section as
    # well
    input = FileInfoPreprocessor.run input, @src, src_root, pandoc_options
    input = MetadataPreprocessor.run input, template. if template.metadata?

    # Convert the file by preprocessing it, run pandoc on it, and
    # postprocessing the output
    input = preprocess input, template
    input = pandoc input, pandoc_options, File.dirname(@src)
    output = postprocess input, template

    begin
      # Either output to file or to STDOUT.

      if @config.stdout?
        puts output
        @dst.close!
      else
        unless use_output_option @dst
          File.open(@dst, 'w') do |file|
            raise IOError.new(:file_is_not_a_file, nil, @dst) unless File.file? @dst
            raise IOError.new(:file_is_not_writable, nil, @dst) unless File.writable? @dst

            file << output
          end
        end
      end
    rescue StandardError => e
      raise IOError.new(:error_writing_file, e, @dst)
    end

    # run cleanup scripts
    cleanup template
  end

  def pandoc(input, options, src_dir)
    absolute_dst = File.expand_path @dst
    Dir.chdir(src_dir) do
      converter = Paru::Pandoc.new
      options.each do |option, value|
        # Options come from a YAML string. In YAML, properties without a value get value nil.
        # Interpret these empty properties as "skip this property"
        next if value.nil?

        if PANDOC_OPTIONS_WITH_PATH.include? option
          executable = option == 'filter'
          value = if value.is_a? Array
                    value.map { |v| Path.update_path(@config, v, absolute_dst, check_executable: executable) }
                  else
                    Path.update_path(@config, value, @dst, check_executable: executable)
                  end
        end

        # There is no "pdf" output format; change it to latex but keep the
        # extension.
        value = determine_output_for_pdf(options) if (option == 'to') && (value == 'pdf')

        begin
          # Pandoc multi-word options can have the multiple words separated by
          # both underscore (_) and dash (-).
          option = option.gsub '-', '_'
          converter.send option, value unless
                  (option == 'output') ||
                  (option == 'use_extension') ||
                  (option == 'rename')
          # don't let pandoc write the output to enable postprocessing
        rescue StandardError
          if debug?
            warn "WARNING: The pandoc option '#{option}' (with value '#{value}') " \
                 'is not recognized by paru. This option is skipped.'
          end
        end
      end

      converter.send 'output', absolute_dst if use_output_option absolute_dst

      begin
        puts converter.to_command if debug?
        converter << input
      rescue Paru::Error => e
        raise PandocError.new(:error_running_pandoc, e, input)
      end
    end
  end

  # Preprocess the input
  #
  # @param input [String] the input to preprocess
  # @param template [Template] template
  #
  # @return [String] the generated output
  def preprocess(input, template)
    process input, Template::PREPROCESSORS, template
  end

  # Postprocess the input
  #
  # @param input [String] the input to postprocess
  # @param template [Template] template
  #
  # @return [String] the generated output
  def postprocess(input, template)
    process input, Template::POSTPROCESSORS, template
  end

  # Run setup scripts
  #
  # @param template [Template] template
  def setup(template)
    process '', Template::SETUP, template
  end

  # Run cleanup scripts
  #
  # @param template [Template] template
  def cleanup(template)
    process '', Template::CLEANUP, template
  end

  # Run the input string through a list of filters called processors. There
  # are various types: preprocessors and postprocessors, setup and
  # cleanup, and rename
  def process(input, type, template)
    if template.send "#{type}?"
      processors = template.send type
      output = input
      processors.each do |processor|
        script = if Path.local_path? processor
                   processor
                 else
                   Path.update_path(@config, processor, @dst, check_executable: true)
                 end

        command, *parameters = script.shellsplit # split on spaces unless it is preceded by a backslash

        unless File.exist? command
          command = Path.which(command)
          script = "#{command} #{parameters.join(' ')}"

          raise ProcessorError.new(:script_does_not_exist, nil, command) if command.nil?
        end

        raise ProcessorError.new(:script_is_not_executable, nil, command) unless File.executable? command

        begin
          output = Processor.run(script, output)
        rescue StandardError => e
          ProcessorError.new(:error_processing_script, e, [script, @src])
        end
      end
      output
    else
      input
    end
  end

  def use_output_option(dst)
    OUTPUT_FORMATS.include?(File.extname(dst).slice(1..-1))
  end

  # Pandoc version 2 supports multiple pdf engines. Determine which
  # to use given the options.
  #
  # @param options [Hash] the options to a paru pandoc converter
  # @return [String] the output format for the pdf engine to use.
  def determine_output_for_pdf(options)
    if options.key? 'pdf-engine'
      engine = options['pdf-engine']
      case engine
      when 'context'
        'context'
      when 'pdfroff'
        'ms'
      when 'wkhtmltopdf', 'weasyprint', 'prince'
        'html'
      else
        'latex'
      end
    else
      # According to pandoc's manual, the default is LaTeX
      'latex'
    end
  end
end

#srcString

Returns the path to the file to convert.

Returns:

  • (String)

    the path to the file to convert



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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/pandocomatic/command/convert_file_command.rb', line 81

class ConvertFileCommand < Command
  attr_reader :config, :src, :dst

  # Create a new ConvertFileCommand
  #
  # @param config [Configuration] pandocomatic's configuration
  # @param src [String] the path to the file to convert
  # @param dst [String] the path to save the output of the conversion
  # @param template_name [String = nil] the template to use while converting
  #   this file
  def initialize(config, src, dst, template_name = nil)
    super()

    @config = config
    @src = src
    @dst = dst

    @template_name = if template_name.nil? || template_name.empty?
                       @config.determine_template @src
                     else
                       template_name
                     end

    @metadata = PandocMetadata.load_file @src
    @dst = @config.set_destination @dst, @template_name, @metadata

    @errors.push IOError.new(:file_does_not_exist, nil, @src) unless File.exist? @src
    @errors.push IOError.new(:file_is_not_a_file, nil, @src) unless File.file? @src
    @errors.push IOError.new(:file_is_not_readable, nil, @src) unless File.readable? @src
  end

  # Execute this ConvertFileCommand
  def run
    convert_file
  end

  # Create a string representation of this ConvertFileCommand
  #
  # @return [String]
  def to_s
    str = "convert #{File.basename @src} #{"-> #{File.basename @dst}" unless @dst.nil?}"
    unless @metadata.unique?
      str += "\n\t encountered multiple YAML metadata blocks with a pandocomatic property. " \
             'Only the pandocomatic property in the first YAML metadata block is being used; ' \
             'the others are discarded.'
    end
    str
  end

  private

  INTERNAL_TEMPLATE = 'internal template'

  def convert_file
    pandoc_options = @metadata.pandoc_options || {}
    template = nil

    # Determine the actual options and settings to use when converting this
    # file.
    if !@template_name.nil? && !@template_name.empty?
      unless @config.template? @template_name
        raise ConfigurationError.new(:no_such_template, nil,
                                     @template_name)
      end

      template = @config.get_template @template_name
      pandoc_options = Template.extend_value(pandoc_options, template.pandoc)
    else
      template = Template.new INTERNAL_TEMPLATE
    end

    # Ignore the `--verbose` option, and warn about ignoring it
    if pandoc_options.key? 'verbose'
      pandoc_options.delete 'verbose'
      warn 'WARNING: Ignoring the pandoc option --verbose because it ' \
           'might interfere with the working of pandocomatic.'
    end

    template.merge! Template.new(INTERNAL_TEMPLATE, @metadata.pandocomatic) if @metadata.pandocomatic?

    # Write out the results of the conversion process to file.
    @dst = @metadata.pandoc_options['output'] if @dst.to_s.empty? && @metadata.pandoc_options.key?('output')

    # Run setup scripts
    setup template

    # Read in the file to convert
    input = File.read @src

    # Run the default preprocessors to mix-in information about the file
    # that is being converted and mix-in the template's metadata section as
    # well
    input = FileInfoPreprocessor.run input, @src, src_root, pandoc_options
    input = MetadataPreprocessor.run input, template. if template.metadata?

    # Convert the file by preprocessing it, run pandoc on it, and
    # postprocessing the output
    input = preprocess input, template
    input = pandoc input, pandoc_options, File.dirname(@src)
    output = postprocess input, template

    begin
      # Either output to file or to STDOUT.

      if @config.stdout?
        puts output
        @dst.close!
      else
        unless use_output_option @dst
          File.open(@dst, 'w') do |file|
            raise IOError.new(:file_is_not_a_file, nil, @dst) unless File.file? @dst
            raise IOError.new(:file_is_not_writable, nil, @dst) unless File.writable? @dst

            file << output
          end
        end
      end
    rescue StandardError => e
      raise IOError.new(:error_writing_file, e, @dst)
    end

    # run cleanup scripts
    cleanup template
  end

  def pandoc(input, options, src_dir)
    absolute_dst = File.expand_path @dst
    Dir.chdir(src_dir) do
      converter = Paru::Pandoc.new
      options.each do |option, value|
        # Options come from a YAML string. In YAML, properties without a value get value nil.
        # Interpret these empty properties as "skip this property"
        next if value.nil?

        if PANDOC_OPTIONS_WITH_PATH.include? option
          executable = option == 'filter'
          value = if value.is_a? Array
                    value.map { |v| Path.update_path(@config, v, absolute_dst, check_executable: executable) }
                  else
                    Path.update_path(@config, value, @dst, check_executable: executable)
                  end
        end

        # There is no "pdf" output format; change it to latex but keep the
        # extension.
        value = determine_output_for_pdf(options) if (option == 'to') && (value == 'pdf')

        begin
          # Pandoc multi-word options can have the multiple words separated by
          # both underscore (_) and dash (-).
          option = option.gsub '-', '_'
          converter.send option, value unless
                  (option == 'output') ||
                  (option == 'use_extension') ||
                  (option == 'rename')
          # don't let pandoc write the output to enable postprocessing
        rescue StandardError
          if debug?
            warn "WARNING: The pandoc option '#{option}' (with value '#{value}') " \
                 'is not recognized by paru. This option is skipped.'
          end
        end
      end

      converter.send 'output', absolute_dst if use_output_option absolute_dst

      begin
        puts converter.to_command if debug?
        converter << input
      rescue Paru::Error => e
        raise PandocError.new(:error_running_pandoc, e, input)
      end
    end
  end

  # Preprocess the input
  #
  # @param input [String] the input to preprocess
  # @param template [Template] template
  #
  # @return [String] the generated output
  def preprocess(input, template)
    process input, Template::PREPROCESSORS, template
  end

  # Postprocess the input
  #
  # @param input [String] the input to postprocess
  # @param template [Template] template
  #
  # @return [String] the generated output
  def postprocess(input, template)
    process input, Template::POSTPROCESSORS, template
  end

  # Run setup scripts
  #
  # @param template [Template] template
  def setup(template)
    process '', Template::SETUP, template
  end

  # Run cleanup scripts
  #
  # @param template [Template] template
  def cleanup(template)
    process '', Template::CLEANUP, template
  end

  # Run the input string through a list of filters called processors. There
  # are various types: preprocessors and postprocessors, setup and
  # cleanup, and rename
  def process(input, type, template)
    if template.send "#{type}?"
      processors = template.send type
      output = input
      processors.each do |processor|
        script = if Path.local_path? processor
                   processor
                 else
                   Path.update_path(@config, processor, @dst, check_executable: true)
                 end

        command, *parameters = script.shellsplit # split on spaces unless it is preceded by a backslash

        unless File.exist? command
          command = Path.which(command)
          script = "#{command} #{parameters.join(' ')}"

          raise ProcessorError.new(:script_does_not_exist, nil, command) if command.nil?
        end

        raise ProcessorError.new(:script_is_not_executable, nil, command) unless File.executable? command

        begin
          output = Processor.run(script, output)
        rescue StandardError => e
          ProcessorError.new(:error_processing_script, e, [script, @src])
        end
      end
      output
    else
      input
    end
  end

  def use_output_option(dst)
    OUTPUT_FORMATS.include?(File.extname(dst).slice(1..-1))
  end

  # Pandoc version 2 supports multiple pdf engines. Determine which
  # to use given the options.
  #
  # @param options [Hash] the options to a paru pandoc converter
  # @return [String] the output format for the pdf engine to use.
  def determine_output_for_pdf(options)
    if options.key? 'pdf-engine'
      engine = options['pdf-engine']
      case engine
      when 'context'
        'context'
      when 'pdfroff'
        'ms'
      when 'wkhtmltopdf', 'weasyprint', 'prince'
        'html'
      else
        'latex'
      end
    else
      # According to pandoc's manual, the default is LaTeX
      'latex'
    end
  end
end

Instance Method Details

#runObject

Execute this ConvertFileCommand



113
114
115
# File 'lib/pandocomatic/command/convert_file_command.rb', line 113

def run
  convert_file
end

#to_sString

Create a string representation of this ConvertFileCommand

Returns:

  • (String)


120
121
122
123
124
125
126
127
128
# File 'lib/pandocomatic/command/convert_file_command.rb', line 120

def to_s
  str = "convert #{File.basename @src} #{"-> #{File.basename @dst}" unless @dst.nil?}"
  unless @metadata.unique?
    str += "\n\t encountered multiple YAML metadata blocks with a pandocomatic property. " \
           'Only the pandocomatic property in the first YAML metadata block is being used; ' \
           'the others are discarded.'
  end
  str
end