pandocomatic 0.0.13 → 0.1.0.b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/bin/pandocomatic +2 -78
  3. data/lib/pandocomatic/cli.rb +183 -0
  4. data/lib/pandocomatic/command/command.rb +113 -0
  5. data/lib/pandocomatic/command/convert_dir_command.rb +155 -0
  6. data/lib/pandocomatic/command/convert_file_command.rb +163 -0
  7. data/lib/pandocomatic/command/copy_file_command.rb +49 -0
  8. data/lib/pandocomatic/command/create_link_command.rb +85 -0
  9. data/lib/pandocomatic/command/skip_command.rb +50 -0
  10. data/lib/pandocomatic/configuration.rb +212 -164
  11. data/lib/pandocomatic/error/cli_error.rb +49 -0
  12. data/lib/pandocomatic/error/configuration_error.rb +39 -0
  13. data/lib/pandocomatic/error/io_error.rb +50 -0
  14. data/lib/pandocomatic/error/pandoc_error.rb +30 -0
  15. data/lib/pandocomatic/error/pandocomatic_error.rb +48 -0
  16. data/lib/pandocomatic/error/processor_error.rb +33 -0
  17. data/lib/pandocomatic/fileinfo_preprocessor.rb +34 -13
  18. data/lib/pandocomatic/pandoc_metadata.rb +66 -34
  19. data/lib/pandocomatic/pandocomatic.rb +176 -0
  20. data/lib/pandocomatic/printer/command_printer.rb +28 -0
  21. data/lib/pandocomatic/printer/configuration_errors_printer.rb +29 -0
  22. data/lib/pandocomatic/printer/error_printer.rb +34 -0
  23. data/lib/pandocomatic/printer/finish_printer.rb +44 -0
  24. data/lib/pandocomatic/printer/help_printer.rb +27 -0
  25. data/lib/pandocomatic/printer/printer.rb +43 -0
  26. data/lib/pandocomatic/printer/summary_printer.rb +35 -0
  27. data/lib/pandocomatic/printer/version_printer.rb +30 -0
  28. data/lib/pandocomatic/printer/views/cli_error.txt +12 -0
  29. data/lib/pandocomatic/printer/views/command.txt +1 -0
  30. data/lib/pandocomatic/printer/views/configuration_error.txt +1 -0
  31. data/lib/pandocomatic/printer/views/configuration_errors.txt +10 -0
  32. data/lib/pandocomatic/printer/views/error.txt +5 -0
  33. data/lib/pandocomatic/printer/views/finish.txt +1 -0
  34. data/lib/pandocomatic/printer/views/help.txt +135 -0
  35. data/lib/pandocomatic/printer/views/io_error.txt +12 -0
  36. data/lib/pandocomatic/printer/views/pandoc_error.txt +1 -0
  37. data/lib/pandocomatic/printer/views/processor_error.txt +6 -0
  38. data/lib/pandocomatic/printer/views/summary.txt +1 -0
  39. data/lib/pandocomatic/printer/views/version.txt +7 -0
  40. data/lib/pandocomatic/printer/views/warning.txt +1 -0
  41. data/lib/pandocomatic/printer/warning_printer.rb +33 -0
  42. data/lib/pandocomatic/processor.rb +25 -8
  43. data/lib/pandocomatic/warning.rb +36 -0
  44. metadata +79 -17
  45. data/lib/pandocomatic/dir_converter.rb +0 -119
  46. data/lib/pandocomatic/file_converter.rb +0 -99
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2789cfe2c555b46519a2bdacc5c149072ee968fa
4
- data.tar.gz: b6bb2f9fcd18377be26faa63f40cdedd51c6806d
3
+ metadata.gz: dffe0869e5d1d21dea4701c4a102be0965a5fb38
4
+ data.tar.gz: f0c876b6f2833288b1c70d02e64c65efee067985
5
5
  SHA512:
6
- metadata.gz: 734ed488a938cced2e73405b729ce2874a935ee93917fe4a28a2130f66634ae3e26f7804a7d7381e43b318b1dff0d6ca34822ea217279ab1520f1e0906728359
7
- data.tar.gz: 833dcc225450ee3c849d2d1d097d71d581d1581665141a576154813b1e1c6b9f19c4b5068803ad0ad0566a2c7341509ff1de844c76ce9df251c1c886dcd2725f
6
+ metadata.gz: 13a606257cc004eb37f775f24aa561459e0d44adf4657ea0b28dcd7c5e4991a4d7bd5f34f985eca94eef1c972e1800f53be2cf54f8b65247c4edbd5a3e072ca2
7
+ data.tar.gz: 0c8bb7e39e23858b4a123a5e213005aaed34e63ff42fd213c8c55f54a1cb3583cba51f6eded036012a989dfca15e0b379c524da8dbfb926798aa534b4659ddc5
data/bin/pandocomatic CHANGED
@@ -1,79 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
-
3
- require 'trollop'
4
- require 'fileutils'
5
-
6
- require_relative '../lib/pandocomatic/dir_converter.rb'
7
- require_relative '../lib/pandocomatic/file_converter.rb'
8
- require_relative '../lib/pandocomatic/configuration.rb'
9
-
10
- opts = Trollop::options do
11
- version "pandocomatic 0.0.13"
12
- banner <<-EOB
13
-
14
- Pandocomatic automates the use of pandoc (<http://www.pandoc.org>). It can be
15
- used to convert one file or a whole directory (tree).
16
-
17
- Usage:
18
-
19
- pandocomatic [--config pandocomatic.yaml] --output output input
20
-
21
- Options:
22
- EOB
23
-
24
- opt :config, "Pandocomatic configuration file, default is ./pandocomatic.yaml", :type => :string
25
- opt :output, "output file or directory", :type => :string
26
-
27
-
28
- end
29
-
30
- # input file/directory
31
- Trollop::die "Expect exactly one input file or directory" if ARGV.length != 1
32
- input = ARGV.first
33
-
34
- # output file/directory
35
- Trollop::die "Expect an output file or directory" if opts[:output].nil?
36
- output = opts[:output]
37
-
38
- # Configuration
39
- if opts[:config].nil?
40
- if Dir.entries(Dir.pwd).include? 'pandocomatic.yaml'
41
- settings_file = File.join Dir.pwd, 'pandocomatic.yaml'
42
- else
43
- default_dir = File.join Dir.home, '.pandocomatic'
44
- default_config = File.join default_dir, 'pandocomatic.yaml'
45
- begin
46
- if not Dir.exist? default_dir
47
- Dir.mkdir default_dir
48
- end
49
-
50
- if not File.exist? default_config
51
- config_template = File.absolute_path '../lib/pandocomatic/default_configuration.yaml', __dir__
52
- FileUtils.cp config_template, default_config
53
- end
54
- rescue Exception => e
55
- raise "Failed creating default pandocomatic file at #{default_config}: #{e.message}"
56
- end
57
- settings_file = default_config
58
- end
59
- else
60
- settings_file = opts[:config]
61
- end
62
-
63
- if not File.exist? settings_file or not File.readable? settings_file then
64
- Trollop::die "Cannot read configuration file #{settings_file}" if File
65
- end
66
-
67
- config = Pandocomatic::Configuration.new settings_file
68
-
69
- if File.directory? input then
70
- if File.exist? output and not File.directory? output then
71
- Trollop::die "Expected an output directory, found a file instead"
72
- end
73
-
74
- Pandocomatic::DirConverter.new(input, output, config).convert
75
-
76
- else
77
- # input is one file
78
- Pandocomatic::FileConverter.new.convert input, output, config
79
- end
2
+ require 'pandocomatic/pandocomatic'
3
+ Pandocomatic::Pandocomatic.run ARGV
@@ -0,0 +1,183 @@
1
+ #--
2
+ # Copyright 2017, Huub de Beer <Huub@heerdebeer.org>
3
+ #
4
+ # This file is part of pandocomatic.
5
+ #
6
+ # Pandocomatic is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by the
8
+ # Free Software Foundation, either version 3 of the License, or (at your
9
+ # option) any later version.
10
+ #
11
+ # Pandocomatic is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
+ # for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License along
17
+ # with pandocomatic. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+ module Pandocomatic
20
+ require 'trollop'
21
+
22
+ require_relative './error/cli_error.rb'
23
+
24
+ ##
25
+ # Command line options parser for pandocomatic using trollop.
26
+ #
27
+ class CLI
28
+
29
+ ##
30
+ # Parse the arguments, returns a triplet with the global options, an
31
+ # optional subcommand, and the (optional) options for that subcommand.
32
+ #
33
+ # @param args [String, Array] A command line invocation string or a list of strings like ARGV
34
+ #
35
+ # @return [Hash] The options to pandocomatic
36
+ # the subcommand's options
37
+ #
38
+ def self.parse(args)
39
+ args = args.split if args.is_a? String
40
+
41
+ begin
42
+ options = parse_options args || {:help => true, :help_given => true}
43
+ options
44
+ rescue Trollop::CommandlineError => e
45
+ raise CLIError.new(:problematic_invocation, e, args)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # Parse pandocomatic's global options.
52
+ def self.parse_options(args)
53
+ parser = Trollop::Parser.new do
54
+ # General options
55
+ opt :dry_run, 'Do a dry run', :short => '-y'
56
+ opt :quiet, 'Run quietly', :short => '-q'
57
+ opt :modified_only, 'Modified files only', :short => '-m'
58
+
59
+ # Configuration of the converter
60
+ opt :data_dir, 'Data dir', :short => '-d', :type => String
61
+ opt :config, 'Configuration file', :short => '-c', :type => String
62
+
63
+ # What to convert and where to put it
64
+ opt :output, 'Output', :short => '-o', :type => String
65
+ opt :input, 'Input', :short => '-i', :type => String
66
+
67
+ # Version and help
68
+ opt :show_version, 'Version', :short => '-v', :long => 'version'
69
+ opt :show_help, 'Help', :short => '-h', :long => 'help'
70
+ end
71
+
72
+ # All options should be parsed according to the specification given in the parser
73
+ begin
74
+ options = parser.parse args
75
+ rescue Trollop::CommandlineError => e
76
+ raise CLIError.new(:problematic_invocation, e, args)
77
+ end
78
+
79
+ options = use_custom_version options
80
+ options = use_custom_help options
81
+
82
+ if options_need_to_be_validated? options
83
+ # if no input option is specified, it should follow as the last item
84
+ if not options[:input_given]
85
+ options[:input] = args.shift
86
+ options[:input_given] = true
87
+ end
88
+
89
+ # There should be no other options left.
90
+ raise CLIError.new(:too_many_options, nil, args) if not args.empty?
91
+
92
+ # There should be an input specified
93
+ raise CLIError.new(:no_input_given) if options[:input].nil? or options[:input].empty?
94
+
95
+ # The input file or directory should exist
96
+ input = File.absolute_path options[:input]
97
+ raise CLIError.new(:input_does_not_exist, nil, options[:input]) unless File.exist? input
98
+ raise CLIError.new(:input_is_not_readable, nil, input) unless File.readable? input
99
+
100
+ if options[:output_given]
101
+ output = File.absolute_path options[:output]
102
+ # Input and output should be both files or directories
103
+ match_file_types input, output
104
+
105
+ # The output, if it already exist, should be writable
106
+ raise CLIError.new(:output_is_not_writable, nil, output) unless not File.exist? output or File.writable? output
107
+ else
108
+ # If the input is a directory, an output directory should be
109
+ # specified as well. If the input is a file, the output could be
110
+ # specified in the input file, or STDOUT could be used.
111
+ raise CLIError.new(:no_output_given) if File.directory? input
112
+ end
113
+
114
+ # Data dir, if specified, should be an existing and readable directory
115
+ if options[:data_dir_given]
116
+ data_dir = File.absolute_path options[:data_dir]
117
+
118
+ raise CLIError.new(:data_dir_does_not_exist, nil, options[:data_dir]) unless File.exist? data_dir
119
+ raise CLIError.new(:data_dir_is_not_readable, nil, data_dir) unless File.readable? data_dir
120
+ raise CLIError.new(:data_dir_is_not_a_directory, nil, data_dir) unless File.directory? data_dir
121
+ end
122
+
123
+ # Config file, if specified, should be an existing and readable file
124
+ if options[:config_given]
125
+ config = File.absolute_path options[:config]
126
+
127
+ raise CLIError.new(:config_file_does_not_exist, nil, options[:config]) unless File.exist? config
128
+ raise CLIError.new(:config_file_is_not_readable, nil, config) unless File.readable? config
129
+ raise CLIError.new(:config_file_is_not_a_file, nil, config) unless File.file? config
130
+ end
131
+
132
+ end
133
+
134
+ options
135
+ end
136
+
137
+ def self.options_need_to_be_validated? options
138
+ not options[:version_given] and not options[:help_given]
139
+ end
140
+
141
+ #--
142
+ #Trollop has special behavior for the version and help options. To
143
+ # overcome, "show_version" and "show_help" options are introduced. When
144
+ # set, these are put in the options as "version" and "help"
145
+ # respectively.
146
+ #++
147
+
148
+ def self.use_custom_version options
149
+ if options[:show_version]
150
+ options.delete :show_version
151
+ options.delete :show_version_given
152
+ options[:version] = true
153
+ options[:version_given] = true
154
+ end
155
+ options
156
+ end
157
+
158
+ def self.use_custom_help options
159
+ if options[:show_help]
160
+ options.delete :show_help
161
+ options.delete :show_help_given
162
+ options[:help] = true
163
+ options[:help_given] = true
164
+ end
165
+ options
166
+ end
167
+
168
+ # If output does not exist, the output can be
169
+ # created with the same type. If output does exist, however, it should
170
+ # have the same type as the input.
171
+ def self.matching_file_types?(input, output)
172
+ not File.exist?(output) or File.ftype(input) == File.ftype(output)
173
+ end
174
+
175
+ def self.match_file_types(input, output)
176
+ if not matching_file_types? input, output
177
+ raise CLIError.new(:output_is_not_a_file, nil, input) if File.file? input
178
+ raise CLIError.new(:output_is_not_a_directory, nil, input) if File.directory? input
179
+ end
180
+ end
181
+
182
+ end
183
+ end
@@ -0,0 +1,113 @@
1
+ #--
2
+ # Copyright 2017, Huub de Beer <Huub@heerdebeer.org>
3
+ #
4
+ # This file is part of pandocomatic.
5
+ #
6
+ # Pandocomatic is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by the
8
+ # Free Software Foundation, either version 3 of the License, or (at your
9
+ # option) any later version.
10
+ #
11
+ # Pandocomatic is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
+ # for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License along
17
+ # with pandocomatic. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+ module Pandocomatic
20
+
21
+ require_relative '../printer/command_printer.rb'
22
+
23
+ class Command
24
+
25
+ attr_reader :errors, :index
26
+
27
+ @@total = 0
28
+ @@dry_run = false
29
+ @@quiet = false
30
+ @@src_root = "."
31
+ @@modified_only = false
32
+
33
+ def initialize()
34
+ @errors = []
35
+ @@total += 1
36
+ @index = @@total
37
+ end
38
+
39
+ def self.reset(src_root = ".", dry_run = false, quiet = false, modified_only)
40
+ @@src_root = src_root
41
+ @@dry_run = dry_run
42
+ @@quiet = quiet
43
+ @@modified_only = modified_only
44
+ @@total = 0
45
+ end
46
+
47
+ def src_root()
48
+ @@src_root
49
+ end
50
+
51
+ def dry_run?()
52
+ @@dry_run
53
+ end
54
+
55
+ def quiet?()
56
+ @@quiet
57
+ end
58
+
59
+ def modified_only?()
60
+ @@modified_only
61
+ end
62
+
63
+ def count()
64
+ 1
65
+ end
66
+
67
+ def all_errors()
68
+ @errors
69
+ end
70
+
71
+ def index_to_s()
72
+ "#{@@total - @index + 1}".rjust(@@total.to_s.size)
73
+ end
74
+
75
+ def execute()
76
+ CommandPrinter.new(self).print unless quiet?
77
+ run if not dry_run? and runnable?
78
+ end
79
+
80
+ def run()
81
+ end
82
+
83
+ def runnable?()
84
+ not has_errors?
85
+ end
86
+
87
+ def to_s()
88
+ 'command'
89
+ end
90
+
91
+ def directory?()
92
+ false
93
+ end
94
+
95
+ def skip?()
96
+ false
97
+ end
98
+
99
+ def uncount()
100
+ @@total -= 1
101
+ end
102
+
103
+ def has_errors?()
104
+ not @errors.empty?
105
+ end
106
+
107
+ # src file is newer than the dstination file?
108
+ def file_modified?(src, dst)
109
+ not File.exist? dst or File.mtime(src) > File.mtime(dst)
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,155 @@
1
+ #--
2
+ # Copyright 2017, Huub de Beer <Huub@heerdebeer.org>
3
+ #
4
+ # This file is part of pandocomatic.
5
+ #
6
+ # Pandocomatic is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by the
8
+ # Free Software Foundation, either version 3 of the License, or (at your
9
+ # option) any later version.
10
+ #
11
+ # Pandocomatic is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
+ # for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License along
17
+ # with pandocomatic. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+ module Pandocomatic
20
+
21
+ require_relative '../error/io_error.rb'
22
+
23
+ require_relative 'command.rb'
24
+ require_relative 'create_link_command.rb'
25
+ require_relative 'convert_file_command.rb'
26
+ require_relative 'copy_file_command.rb'
27
+ require_relative 'skip_command.rb'
28
+
29
+ class ConvertDirCommand < Command
30
+
31
+ attr_reader :config, :src_dir, :dst_dir, :subcommands
32
+
33
+ def initialize(current_config, src_dir, dst_dir)
34
+ super()
35
+ @src_dir = src_dir
36
+ @config = current_config
37
+
38
+ begin
39
+ @config = reconfigure current_config, @src_dir
40
+ rescue ConfigurationError => e
41
+ @errors.push e
42
+ end
43
+
44
+ @dst_dir = dst_dir
45
+
46
+ if Dir.exist? @dst_dir
47
+ @errors.push IOError.new(:directory_is_not_readable, nil, @dst_dir) unless File.readable? @dst_dir
48
+ @errors.push IOError.new(:directory_is_not_writable, nil, @dst_dir) unless File.writable? @dst_dir
49
+ @errors.push IOError.new(:directory_is_not_a_directory, nil, @dst_dir) unless File.directory? @dst_dir
50
+ end
51
+
52
+ @subcommands = []
53
+
54
+ Dir.foreach @src_dir do |filename|
55
+ src = File.join @src_dir, filename
56
+
57
+ next if config.skip? src
58
+
59
+ @errors.push IOError.new(:file_or_directory_does_not_exist, nil, src) unless File.exist? src
60
+
61
+ dst = File.join @dst_dir, filename
62
+
63
+ if File.symlink? src and not config.follow_links?
64
+ subcommand = CreateLinkCommand.new(src, dst)
65
+ elsif File.directory? src then
66
+ if config.recursive? then
67
+ subcommand = ConvertDirCommand.new(config, src, dst)
68
+ else
69
+ subcommand = SkipCommand.new(src, :skipping_directory)
70
+ end
71
+ elsif File.file? src
72
+ if config.convert? src then
73
+ dst = config.set_extension dst
74
+ if not modified_only? or file_modified? src, dst then
75
+ subcommand = ConvertFileCommand.new(config, src, dst)
76
+ end
77
+ else
78
+ if not modified_only? or file_modified? src, dst then
79
+ subcommand = CopyFileCommand.new(src, dst)
80
+ end
81
+ end
82
+ else
83
+ subcommand = SkipCommand.new(src, :unclear_what_to_do)
84
+ end
85
+
86
+ @subcommands.push subcommand unless subcommand.nil? or subcommand.skip?
87
+ end
88
+
89
+ # Empty commands do not count to the total amount of commands to execute
90
+ uncount if skip?
91
+ end
92
+
93
+ def skip?()
94
+ @subcommands.empty?
95
+ end
96
+
97
+ def count()
98
+ @subcommands.reduce(if skip? then 0 else 1 end) do |total, subcommand|
99
+ total += subcommand.count
100
+ end
101
+ end
102
+
103
+ def all_errors()
104
+ @subcommands.reduce(@errors) do |total, subcommand|
105
+ total += subcommand.all_errors
106
+ end
107
+ end
108
+
109
+ def directory?()
110
+ true
111
+ end
112
+
113
+ def to_s()
114
+ "convert #{@src_dir}" + '; ' + if create_directory? then 'create and ' else '' end + "enter #{@dst_dir}"
115
+ end
116
+
117
+ def run()
118
+ begin
119
+ Dir.mkdir @dst_dir if create_directory?
120
+ rescue SystemError => e
121
+ raise IOError.new(:error_creating_directory, e, @dst_dir)
122
+ end
123
+ end
124
+
125
+ def execute()
126
+ if not @subcommands.empty?
127
+ CommandPrinter.new(self).print unless quiet?
128
+ run if not dry_run? and runnable?
129
+
130
+ @subcommands.each do |subcommand|
131
+ subcommand.execute
132
+ end
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+ def create_directory?()
139
+ not File.exist? @dst_dir or not File.directory? @dst_dir
140
+ end
141
+
142
+ # If the source directory contains a configuration file, use it to
143
+ # reconfigure the converter. Otherwise, use the current configuration
144
+ def reconfigure(current_config, src_dir)
145
+ config_file = File.join src_dir, Pandocomatic::CONFIG_FILE
146
+ if File.exist? config_file then
147
+ config = current_config.reconfigure config_file
148
+ else
149
+ config = current_config
150
+ end
151
+ config
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,163 @@
1
+ #--
2
+ # Copyright 2017, Huub de Beer <Huub@heerdebeer.org>
3
+ #
4
+ # This file is part of pandocomatic.
5
+ #
6
+ # Pandocomatic is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by the
8
+ # Free Software Foundation, either version 3 of the License, or (at your
9
+ # option) any later version.
10
+ #
11
+ # Pandocomatic is distributed in the hope that it will be useful, but
12
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13
+ # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
+ # for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License along
17
+ # with pandocomatic. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+ module Pandocomatic
20
+
21
+ require 'paru'
22
+
23
+ require_relative '../pandoc_metadata.rb'
24
+ require_relative '../processor.rb'
25
+ require_relative '../fileinfo_preprocessor'
26
+
27
+ require_relative '../error/io_error.rb'
28
+ require_relative '../error/configuration_error.rb'
29
+ require_relative '../error/processor_error.rb'
30
+
31
+ require_relative 'command.rb'
32
+
33
+ class ConvertFileCommand < Command
34
+
35
+ attr_reader :config, :src, :dst
36
+
37
+ def initialize(config, src, dst)
38
+ super()
39
+ @config = config
40
+ @src = src
41
+ @dst = dst
42
+
43
+ @errors.push IOError.new(:file_does_not_exist, nil, @src) unless File.exist? @src
44
+ @errors.push IOError.new(:file_is_not_a_file, nil, @src) unless File.file? @src
45
+ @errors.push IOError.new(:file_is_not_readable, nil, @src) unless File.readable? @src
46
+ end
47
+
48
+ def run
49
+ convert_file
50
+ end
51
+
52
+ def to_s
53
+ "convert #{File.basename @src} -> #{File.basename @dst}"
54
+ end
55
+
56
+ private
57
+
58
+ def convert_file
59
+ metadata = PandocMetadata.load_file @src
60
+
61
+ if metadata.has_template? then
62
+ template_name = metadata.template_name
63
+ else
64
+ template_name = @config.determine_template @src
65
+ end
66
+
67
+ raise ConfigurationError.new(:no_such_template, nil, template_name) unless @config.has_template? template_name
68
+
69
+ template = @config.get_template template_name
70
+
71
+ pandoc_options = (template['pandoc'] || {}).merge(metadata.pandoc_options || {})
72
+
73
+ input = File.read @src
74
+ input = FileInfoPreprocessor.run input, @src
75
+ input = preprocess input, template
76
+ input = pandoc input, pandoc_options, File.dirname(@src)
77
+ output = postprocess input, template
78
+
79
+ if @dst.to_s.empty? and metadata.pandoc_options.has_key? 'output'
80
+ @dst = metadata.pandoc_options['output']
81
+ end
82
+
83
+ begin
84
+ File.open(@dst, 'w') do |file|
85
+ raise IOError.new(:file_is_not_a_file, nil, @dst) unless File.file? @dst
86
+ raise IOError.new(:file_is_not_writable, nil, @dst) unless File.writable? @dst
87
+ file << output
88
+ end
89
+ rescue StandardError => e
90
+ raise IOError.new(:error_writing_file, e, @dst)
91
+ end
92
+ end
93
+
94
+ # TODO: update this list
95
+ PANDOC_OPTIONS_WITH_PATH = [
96
+ 'filter',
97
+ 'template',
98
+ 'css',
99
+ 'include-in-header',
100
+ 'include-before-body',
101
+ 'include-after-body',
102
+ 'reference-odt',
103
+ 'reference-docx',
104
+ 'epub-stylesheet',
105
+ 'epub-cover-image',
106
+ 'epub-metadata',
107
+ 'epub-embed-font',
108
+ 'bibliography',
109
+ 'csl'
110
+ ]
111
+
112
+ def pandoc(input, options, src_dir)
113
+ converter = Paru::Pandoc.new
114
+ options.each do |option, value|
115
+
116
+ value = @config.update_path value, src_dir if
117
+ PANDOC_OPTIONS_WITH_PATH.include? option
118
+
119
+ converter.send option, value unless option == 'output'
120
+ # don't let pandoc write the output to enable postprocessing
121
+ end
122
+
123
+ begin
124
+ converter << input
125
+ rescue Paru::Error => e
126
+ raise PandocError.new(:error_running_pandoc, e, input_document)
127
+ end
128
+ end
129
+
130
+ def preprocess(input, config = {})
131
+ process input, 'preprocessors', config
132
+ end
133
+
134
+ def postprocess(input, config = {})
135
+ process input, 'postprocessors', config
136
+ end
137
+
138
+ # Run the input string through a list of filters called processors. There
139
+ # are to types: preprocessors and postprocessors
140
+ def process(input, type, config = {})
141
+ if config.has_key? type then
142
+ processors = config[type]
143
+ output = input
144
+ processors.each do |processor|
145
+ script = @config.update_path(processor, File.dirname(@src))
146
+
147
+ raise ProcessorError.new(:script_does_not_exist, nil, script) unless File.exist? script
148
+ raise ProcessorError.new(:script_is_not_executable, nil, script) unless File.executable? script
149
+
150
+ begin
151
+ output = Processor.run(script, output)
152
+ rescue StandardError => e
153
+ ProcessorError.new(:error_processing_script, e, [script, @src])
154
+ end
155
+ end
156
+ output
157
+ else
158
+ input
159
+ end
160
+ end
161
+
162
+ end
163
+ end