pandocomatic 0.0.13 → 0.1.0.b

Sign up to get free protection for your applications and to get access to all the features.
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