libis-tools 1.0.5-java

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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +40 -0
  6. data/Gemfile +7 -0
  7. data/README.md +202 -0
  8. data/Rakefile +11 -0
  9. data/bin/libis_tool +5 -0
  10. data/lib/libis-tools.rb +1 -0
  11. data/lib/libis/tools.rb +25 -0
  12. data/lib/libis/tools/assert.rb +52 -0
  13. data/lib/libis/tools/checksum.rb +106 -0
  14. data/lib/libis/tools/cli/cli_helper.rb +189 -0
  15. data/lib/libis/tools/cli/reorg.rb +416 -0
  16. data/lib/libis/tools/command.rb +133 -0
  17. data/lib/libis/tools/command_line.rb +23 -0
  18. data/lib/libis/tools/config.rb +147 -0
  19. data/lib/libis/tools/config_file.rb +85 -0
  20. data/lib/libis/tools/csv.rb +38 -0
  21. data/lib/libis/tools/deep_struct.rb +71 -0
  22. data/lib/libis/tools/extend/array.rb +16 -0
  23. data/lib/libis/tools/extend/empty.rb +7 -0
  24. data/lib/libis/tools/extend/hash.rb +147 -0
  25. data/lib/libis/tools/extend/kernel.rb +25 -0
  26. data/lib/libis/tools/extend/ostruct.rb +3 -0
  27. data/lib/libis/tools/extend/roo.rb +91 -0
  28. data/lib/libis/tools/extend/string.rb +94 -0
  29. data/lib/libis/tools/extend/struct.rb +29 -0
  30. data/lib/libis/tools/extend/symbol.rb +8 -0
  31. data/lib/libis/tools/logger.rb +130 -0
  32. data/lib/libis/tools/mets_dnx.rb +61 -0
  33. data/lib/libis/tools/mets_file.rb +504 -0
  34. data/lib/libis/tools/mets_objects.rb +547 -0
  35. data/lib/libis/tools/parameter.rb +372 -0
  36. data/lib/libis/tools/spreadsheet.rb +196 -0
  37. data/lib/libis/tools/temp_file.rb +42 -0
  38. data/lib/libis/tools/thread_safe.rb +31 -0
  39. data/lib/libis/tools/version.rb +5 -0
  40. data/lib/libis/tools/xml_document.rb +583 -0
  41. data/libis-tools.gemspec +55 -0
  42. data/spec/assert_spec.rb +65 -0
  43. data/spec/checksum_spec.rb +68 -0
  44. data/spec/command_spec.rb +90 -0
  45. data/spec/config_file_spec.rb +83 -0
  46. data/spec/config_spec.rb +113 -0
  47. data/spec/csv_spec.rb +159 -0
  48. data/spec/data/test-headers.csv +2 -0
  49. data/spec/data/test-headers.tsv +2 -0
  50. data/spec/data/test-noheaders.csv +1 -0
  51. data/spec/data/test-noheaders.tsv +1 -0
  52. data/spec/data/test.data +9 -0
  53. data/spec/data/test.xlsx +0 -0
  54. data/spec/data/test.xml +8 -0
  55. data/spec/data/test.yml +2 -0
  56. data/spec/data/test_config.yml +15 -0
  57. data/spec/deep_struct_spec.rb +138 -0
  58. data/spec/logger_spec.rb +165 -0
  59. data/spec/mets_file_spec.rb +223 -0
  60. data/spec/parameter_container_spec.rb +152 -0
  61. data/spec/parameter_spec.rb +148 -0
  62. data/spec/spec_helper.rb +29 -0
  63. data/spec/spreadsheet_spec.rb +1820 -0
  64. data/spec/temp_file_spec.rb +76 -0
  65. data/spec/test.xsd +20 -0
  66. data/spec/thread_safe_spec.rb +64 -0
  67. data/spec/xmldocument_spec.rb +421 -0
  68. data/test/test_helper.rb +7 -0
  69. data/test/webservices/test_ca_item_info.rb +59 -0
  70. data/test/webservices/test_ca_search.rb +35 -0
  71. metadata +437 -0
@@ -0,0 +1,189 @@
1
+ require 'tty-prompt'
2
+ require 'tty-config'
3
+ require 'pastel'
4
+
5
+ module Libis
6
+ module Tools
7
+ module Cli
8
+ module Helper
9
+
10
+ module ClassMethods
11
+
12
+ def exit_on_failure?
13
+ true
14
+ end
15
+
16
+ end
17
+
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ end
21
+
22
+ attr_reader :prompt, :config, :pastel, :config_file_prefix
23
+
24
+ def initialize(*args)
25
+ @prompt = TTY::Prompt.new
26
+ @config = TTY::Config.new
27
+ @pastel = Pastel.new
28
+ @config.append_path Dir.home
29
+ @config_file_prefix = '.tools.'
30
+ prompt.warn "Default config file: #{config.filename}"
31
+ super
32
+ end
33
+
34
+ protected
35
+
36
+ private
37
+
38
+ def index_of(list, value)
39
+ i = list.index(value)
40
+ i += 1 if i
41
+ i || 1
42
+ end
43
+
44
+ def config_write(name = nil)
45
+ set_config_name(name || new_config)
46
+ unless get_config_name
47
+ prompt.error 'Could not write the configuration file: configuration not set'
48
+ return
49
+ end
50
+ config.write force: true
51
+ end
52
+
53
+ def config_read(name = nil)
54
+ config.filename = name ?
55
+ "#{config_file_prefix}#{name}" :
56
+ select_config_file(with_new: false)
57
+ unless get_config_name
58
+ prompt.error 'Could not read the configuration file: configuration not set'
59
+ return
60
+ end
61
+ config.read
62
+ rescue TTY::Config::ReadError
63
+ prompt.error('Could not read the configuration file.')
64
+ exit
65
+ end
66
+
67
+ def toggle_config(field)
68
+ config.set(field, value: !config.fetch(field))
69
+ end
70
+
71
+ def get_config_name
72
+ return $1 if get_config_file.match(config_file_regex)
73
+ nil
74
+ end
75
+
76
+ def get_config_file
77
+ config.filename
78
+ end
79
+
80
+ def config_file_regex(with_ext: false)
81
+ /^#{Regexp.quote(config_file_prefix)}(.+)#{Regexp.quote(config.extname) if with_ext}$/
82
+ end
83
+
84
+ def set_config_name(name)
85
+ config.filename = "#{config_file_prefix}#{name}" if name && !name.empty?
86
+ end
87
+
88
+ def set_config_file(name)
89
+ config.filename = name if name && !name.empty?
90
+ end
91
+
92
+ def select_config_file(*args)
93
+ "#{config_file_prefix}#{select_config_name *args}"
94
+ end
95
+
96
+ def select_config_name(with_new: true, force_select: false)
97
+ current_cfg = get_config_name
98
+ return current_cfg if !force_select && current_cfg
99
+
100
+ cfgs = []
101
+ cfgs << {
102
+ name: '-- new configuration --',
103
+ value: -> do
104
+ new_config
105
+ end
106
+ } if with_new
107
+ cfgs += Dir.glob(File.join(Dir.home, "#{config_file_prefix}*")).reduce([]) do |a, x|
108
+ a.push($1) if File.basename(x).match(config_file_regex(with_ext: true))
109
+ a
110
+ end
111
+
112
+ return nil if cfgs.empty?
113
+
114
+ prompt.select '[ Select config menu ]', cfgs, default: index_of(cfgs, current_cfg), filter: true
115
+ end
116
+
117
+ def new_config
118
+ while true
119
+ name = prompt.ask('Enter a name for the configuration:', modify: :trim)
120
+ return name unless File.exist?(File.join(Dir.home, "#{config_file_prefix}#{name}#{config.extname}")) &&
121
+ !prompt.yes?("Configuration '#{name}' already exists. Overwrite?")
122
+ end
123
+ end
124
+
125
+ def ask(question, field, bool: false, enum: nil, default: nil, mask: false, if_empty: false)
126
+ cmd, args, opts = :ask, [question], {}
127
+ default ||= config.fetch(field)
128
+ if enum
129
+ cmd = :select
130
+ args << enum
131
+ # Change default to its index in the enum
132
+ default = index_of(enum, default)
133
+ # Force the question if the supplied value is not valid
134
+ config.delete field unless !if_empty || enum.include?(config.fetch field)
135
+ end
136
+ cmd = :mask if mask
137
+ opts[:default] = config.fetch(field)
138
+ opts[:default] = default if default
139
+ cmd = (opts[:default] ? :yes? : :no?) if bool
140
+ config.set(field, value: prompt.send(cmd, *args, opts)) unless if_empty && config.fetch(field)
141
+ end
142
+
143
+ def tree_select(path, question: nil, file: false, page_size: 22, filter: true, cycle: false, create: false,
144
+ default_choices: nil)
145
+ path = Pathname.new(path) unless path.is_a? Pathname
146
+
147
+ return path unless path.exist?
148
+ path = path.realpath
149
+
150
+ dirs = path.children.select(&:directory?).sort
151
+ files = file ? path.children.select(&:file?).sort : []
152
+
153
+ choices = []
154
+ choices << {name: "Folder: #{path}", value: path, disabled: file ? '' : false}
155
+ choices += default_choices if default_choices
156
+ choices << {name: '-- new directory --', value: -> do
157
+ new_name = prompt.ask('new directory name:', modify: :trim, required: true)
158
+ new_path = path + new_name
159
+ FileUtils.mkdir(new_path.to_path)
160
+ new_path
161
+ end
162
+ } if create
163
+
164
+ choices << {name: "-- new file --", value: -> do
165
+ new_name = prompt.ask('new file name:', modify: :trim, required: true)
166
+ path + new_name
167
+ end
168
+ } if file && create
169
+
170
+ choices << {name: '[..]', value: path.parent}
171
+
172
+ dirs.each {|d| choices << {name: "[#{d.basename}]", value: d}}
173
+ files.each {|f| choices << {name: f.basename.to_path, value: f}}
174
+
175
+ question ||= "Select #{'file or ' if files}directory"
176
+ selection = prompt.select question, choices,
177
+ per_page: page_size, filter: filter, cycle: cycle, default: file ? 2 : 1
178
+
179
+ return selection unless selection.is_a? Pathname
180
+ return selection.to_path if selection == path || selection.file?
181
+
182
+ tree_select selection, question: question, file: file, page_size: page_size, filter: filter,
183
+ cycle: cycle, create: create, default_choices: default_choices
184
+ end
185
+
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,416 @@
1
+ require 'libis/tools/spreadsheet'
2
+ require 'awesome_print'
3
+
4
+ module Libis
5
+ module Tools
6
+ module Cli
7
+ module Reorg
8
+
9
+ # noinspection RubyExpressionInStringInspection
10
+ DEFAULT_CONFIG = {
11
+ base: '.',
12
+ filter: '^(.*)$',
13
+ expression: 'target/#{file_name}',
14
+ action: 'move',
15
+ overwrite: false,
16
+ interactive: false,
17
+ report: nil,
18
+ dummy: false,
19
+ config: nil,
20
+ unattended: false
21
+ }
22
+
23
+ # noinspection RubyStringKeysInHashInspection
24
+ VALID_ACTIONS = {
25
+ 'move' => 'moved',
26
+ 'copy' => 'copied',
27
+ 'link' => 'linked'
28
+ }
29
+
30
+ STRING_CONFIG = {
31
+ base: "Source Directory to organize",
32
+ filter: "File matching filter",
33
+ expression: "New file path expression",
34
+ action: "Action to perform",
35
+ overwrite: "Overwite target files if newer",
36
+ interactive: "Ask for action on changed files",
37
+ report: "Report file",
38
+ dummy: "Perform phantom actions (not affecting files)",
39
+ config: "Load saved configuration parameters"
40
+ }
41
+
42
+ REQ_HEADERS = {term: 'Term'}
43
+ OPT_HEADERS = {pid: 'Pid', filename: 'File'}
44
+
45
+ def self.included(klass)
46
+ klass.class_exec do
47
+ def klass.description(field)
48
+ "#{STRING_CONFIG[field]}." + (DEFAULT_CONFIG[field].nil? ? '' : " default: #{DEFAULT_CONFIG[field]}")
49
+ end
50
+
51
+ desc 'reorg [options]', 'Reorganize files'
52
+ long_desc <<-DESC
53
+
54
+ 'reorg [options]' will reorganize files based on the name of the files.
55
+
56
+ The base directory will be scanned for files that match the FILTER regular expression. For each matching
57
+ file, an action will be performed. The outcome of the action is determined by the expression that is given.
58
+
59
+ The expression will be evaluated as a Ruby string expression and supports string interpolation in the form
60
+ '\#{<thing>}', where <thing> can be any of:
61
+
62
+ . $x : refers to the x-th group in the FILTER. Groups are numbered by the order of the opening '('
63
+
64
+ . file_name : the original file name
65
+
66
+ The action that will be performed on the action depens on the configured ACTION. The valid ACTIONs are;
67
+ 'move', copy' and 'link'. Please note that in the latter case only the files will be soft-linked and any
68
+ directory in the target path will be created. The tool will therefore never create soft-links to directories.
69
+ The soft-links are created with an absolute reference path. This allows you to later move and rename the
70
+ soft-links later as you seem fit without affecting the source files. You could for instance run this tool on
71
+ the soft-links with the 'move' action to do so.
72
+
73
+ By default, if the target file already exists, the file ACTION will not be performed. The '--overwrite'
74
+ option will cause the tool to compare the file dates and checksums of source and target files in that case.
75
+ Only if the checksums are different and the source file has a more recent modification date, the target file
76
+ will be overwritten. If you want to be asked for overwrite confirmation for each such file, you can add the
77
+ '--interactive' option.
78
+
79
+ The tool can generate a report on all the file actions that have been performed. To do so, specify a file
80
+ name for the '--report' option. The format of the report will be determined by the file extension you supply:
81
+
82
+ - *.csv : comma-separated file
83
+
84
+ - *.tsv : tab-separated file
85
+
86
+ - *.yml : YAML file
87
+
88
+ - *.xml : XML file
89
+
90
+ By adding the --dummy option, you can test your settings without performing the real actions on the file.
91
+ The tool will still report on its progress as if it would perform the actions.
92
+
93
+ All the options can be saved into a configuration file to be reused later. You can specify which
94
+ configuration file you want to use with the '--config' option. If you specify a configuration file, the tool
95
+ will first load the options from the configuration file and then process the command-line options. The
96
+ command-line options therefore have priority over the options in the configuration file.
97
+
98
+ By default the tool allows you to review the activated options and gives you the opportunity to modify them
99
+ before continuing of bailing out. If you are confident the settings are fine, you can skip this with the
100
+ '--unatttended' option. Handle with care!
101
+
102
+ Unless you have specified the '--unattended' options, you will be presented with a menu that allows you to
103
+ change the configuration parameters, run the tool with the current config or bail out.
104
+
105
+ DESC
106
+
107
+ method_option :base, aliases: '-b',
108
+ desc: description(:base)
109
+ method_option :filter, aliases: '-f',
110
+ desc: description(:filter)
111
+ method_option :expression, aliases: '-e',
112
+ desc: description(:expression)
113
+
114
+ method_option :action, aliases: '-a', enum: VALID_ACTIONS.keys,
115
+ desc: description(:action)
116
+ method_option :overwrite, aliases: '-o', type: :boolean,
117
+ desc: description(:overwrite)
118
+ method_option :interactive, aliases: '-i', type: :boolean,
119
+ desc: description(:interactive)
120
+
121
+ method_option :report, aliases: '-r', banner: 'FILE',
122
+ desc: description(:report)
123
+
124
+ method_option :dummy, aliases: '-d', type: :boolean,
125
+ desc: description(:dummy)
126
+
127
+ method_option :config, aliases: '-c', type: :string,
128
+ desc: description(:config)
129
+
130
+ method_option :unattended, aliases: '-u', type: :boolean,
131
+ desc: description(:unattended)
132
+
133
+ end
134
+
135
+ end
136
+
137
+ def reorg
138
+ @config_file_prefix = '.reorg.'
139
+
140
+ # return config_write
141
+
142
+ DEFAULT_CONFIG.each {|key, value| config.set(key, value: value) unless value.nil?}
143
+ config_read(options[:config]) if options[:config]
144
+ DEFAULT_CONFIG.each {|key, _| config.set(key, value: options[key]) if options.has_key?(key.to_s)}
145
+ run_menu unless options[:unattended]
146
+ do_reorg
147
+ end
148
+
149
+ protected
150
+
151
+ def run_menu
152
+
153
+ begin
154
+ choices = []
155
+
156
+ choices << {name: "Configuration editor",
157
+ value: -> {config_menu; 1}
158
+ }
159
+
160
+ choices << {name: "Run", value: nil}
161
+ choices << {name: "Exit", value: -> {exit}}
162
+
163
+ selection = prompt.select "[ LIBIS Tool - ReOrg ]",
164
+ choices, cycle: true, default: 1
165
+
166
+ end until selection.nil?
167
+
168
+ end
169
+
170
+ def print_field(field)
171
+ value = config.fetch(field)
172
+ value = 'Yes' if value.is_a?(TrueClass)
173
+ value = 'No' if value.is_a?(FalseClass)
174
+ "#{STRING_CONFIG[field]} : #{pastel.green(value)}"
175
+ end
176
+
177
+ def config_menu
178
+
179
+ selection = 1
180
+
181
+ begin
182
+ choices = []
183
+ choices << {name: print_field(:base),
184
+ value: -> do
185
+ config.set :base,
186
+ value: tree_select(config.fetch(:base) || '.', question: 'Select source directory:')
187
+ 1
188
+ end
189
+ }
190
+ choices << {name: print_field(:filter),
191
+ value: -> {ask 'File filter regex:', :filter; 2}
192
+ }
193
+ choices << {name: print_field(:expression),
194
+ value: -> {ask 'New path expression:', :expression; 3}
195
+ }
196
+ choices << {name: print_field(:action),
197
+ value: -> {ask 'Action:', :action, enum: VALID_ACTIONS.keys; 4}
198
+ }
199
+ choices << {name: print_field(:overwrite),
200
+ value: -> {toggle_config(:overwrite); prompt.say print_field(:overwrite); 5}
201
+ }
202
+ choices << {name: print_field(:interactive),
203
+ value: -> {toggle_config(:interactive); prompt.say print_field(:interactive); 6}
204
+ }
205
+ choices << {name: print_field(:report),
206
+ value: -> do
207
+ report = config.fetch(:report)
208
+ default = '.'
209
+ default = File.dirname(report) if report && File.file?(report)
210
+ report = tree_select(default, question: 'Select source directory',
211
+ file: true, create: true,
212
+ default_choices: [{name: "-- no report --", value: nil}])
213
+ if report
214
+ config.set(:report, value: report)
215
+ else
216
+ config.delete(:report)
217
+ end
218
+ 7
219
+ end
220
+ }
221
+ choices << {name: print_field(:dummy),
222
+ value: -> {toggle_config(:dummy); prompt.say print_field(:dummy); 8}
223
+ }
224
+ choices << {name: "-- save configuration '#{get_config_name}' --",
225
+ value: -> {config_write get_config_name; 9}
226
+ } if get_config_name
227
+ choices << {name: "-- save to new configuration --",
228
+ value: -> {config_write new_config; 10}
229
+ }
230
+ choices << {name: "-- read configuration --",
231
+ value: -> {config_read; 11}
232
+ }
233
+ choices << {name: "-- return to main menu --", value: nil}
234
+
235
+ selection = prompt.select "[ Configuration menu ]",
236
+ choices, per_page: 20, cycle: true, default: selection
237
+
238
+ end until selection.nil?
239
+
240
+ end
241
+
242
+ def do_reorg
243
+ prompt.ok 'This can take a while. Please sit back and relax, grab a cup of coffee, have a quick nap or read a good book ...'
244
+
245
+ # keeps track of folders created
246
+ require 'set'
247
+ target_dir_list = Set.new
248
+
249
+ open_report
250
+
251
+ require 'fileutils'
252
+ count = {move: 0, duplicate: 0, update: 0, reject: 0, skipped_dir: 0, unmatched_file: 0}
253
+
254
+ base_dir = config.fetch(:base)
255
+ parse_regex = Regexp.new(config.fetch(:filter))
256
+ path_expression = "#{config.fetch(:expression)}"
257
+ dummy_operation = config.fetch(:dummy)
258
+ interactive = config.fetch(:interactive)
259
+ overwrite = config.fetch(:overwrite)
260
+ file_operation = config.fetch(:action)
261
+ Dir.new(base_dir).entries.each do |file_name|
262
+ next if file_name =~ /^\.\.?$/
263
+ entry = File.join(File.absolute_path(base_dir), file_name)
264
+ unless File.file?(entry)
265
+ prompt.say "Skipping directory #{entry}." unless @report
266
+ write_report(entry, '', '', 'Directory - skipped.')
267
+ count[:skipped_dir] += 1
268
+ next
269
+ end
270
+ unless file_name =~ parse_regex
271
+ prompt.say "Skipping file #{file_name}. File name does not match expression." unless @report
272
+ write_report(entry, '', '', 'Mismatch - skipped.')
273
+ count[:unmatched_file] += 1
274
+ next
275
+ end
276
+ target = eval('"' + path_expression + '"')
277
+ target_file = File.basename(target)
278
+ target_dir = File.dirname(target)
279
+ target_dir = File.join(base_dir, target_dir) unless target_dir[0] == '/'
280
+ unless target_dir_list.include?(target_dir)
281
+ prompt.say "-> Create directory '#{target_dir}'" unless @report
282
+ FileUtils.mkpath(target_dir) unless dummy_operation
283
+ target_dir_list << target_dir
284
+ end
285
+ target_path = File.join(target_dir, target_file)
286
+ remark = nil
287
+ action = false
288
+ if File.exist?(target_path)
289
+ if compare_entry(entry, target_path)
290
+ remark = 'Duplicate - skipped.'
291
+ count[:duplicate] += 1
292
+ prompt.error "Duplicate file entry: #{entry}." unless @report
293
+ else
294
+ # puts "source: #{File.mtime(entry)} #{'%11s' % Filesize.new(File.size(entry)).pretty} #{entry}"
295
+ # puts "target: #{File.mtime(target_path)} #{'%11s' % Filesize.new(File.size(target_path)).pretty} #{target_path}"
296
+ if interactive ? prompt.send((overwrite ? :yes : :no), 'Overwrite target?') : overwrite
297
+ remark = 'Duplicate - updated'
298
+ action = true
299
+ count[:update] += 1
300
+ else
301
+ remark = 'Duplicate - rejected.'
302
+ prompt.error "ERROR: #{entry} exists with different content." unless @report
303
+ count[:reject] += 1
304
+ end
305
+ end
306
+ else
307
+ action = true
308
+ count[:move] += 1
309
+ end
310
+ if action
311
+ prompt.say "-> #{file_operation} '#{file_name}' to '#{target}'" unless @report
312
+ case file_operation
313
+ when 'move'
314
+ FileUtils.move(entry, File.join(target_dir, target_file), force: true)
315
+ when 'copy'
316
+ FileUtils.copy(entry, File.join(target_dir, target_file))
317
+ when 'link'
318
+ FileUtils.symlink(entry, File.join(target_dir, target_file), force: true)
319
+ else
320
+ # Shouldn't happen
321
+ raise RuntimeError, "Bad file operation: '#{file_operation}'"
322
+ end unless dummy_operation
323
+ end
324
+ write_report(entry, target_dir, target_file, remark)
325
+ end
326
+
327
+ prompt.ok "#{'%8d' % count[:skipped_dir]} dir(s) found and skipped."
328
+ prompt.ok "#{'%8d' % count[:unmatched_file]} file(s) found that did not match and skipped."
329
+ prompt.ok "#{'%8d' % count[:move]} file(s) #{VALID_ACTIONS[file_operation]}."
330
+ prompt.ok "#{'%8d' % count[:duplicate]} duplicate(s) found and skipped."
331
+ prompt.ok "#{'%8d' % count[:update]} changed file(s) found and updated."
332
+ prompt.ok "#{'%8d' % count[:reject]} changed file(s) found and rejected."
333
+
334
+ close_report
335
+
336
+ prompt.ok 'Done!'
337
+
338
+ end
339
+
340
+
341
+ def open_report
342
+ if (report_file = config.fetch(:report))
343
+ # noinspection RubyStringKeysInHashInspection
344
+ @report_type = {'.csv' => :csv, '.tsv' => :tsv, '.xml' => :xml, '.yml' => :yml}[File.extname(report_file)]
345
+ unless @report_type
346
+ prompt.error "Unknown file type: #{File.extname(report_file)}"
347
+ exit
348
+ end
349
+ @report = File.open(report_file, 'w+')
350
+ end
351
+ end
352
+
353
+ def for_tsv(string)
354
+ ; string =~ /\t\n/ ? "\"#{string.gsub('"', '""')}\"" : string;
355
+ end
356
+
357
+ def for_csv(string)
358
+ ; string =~ /,\n/ ? "\"#{string.gsub('"', '""')}\"" : string;
359
+ end
360
+
361
+ def for_xml(string, type = :attr)
362
+ ; string.encode(xml: type);
363
+ end
364
+
365
+ def for_yml(string)
366
+ ; string.inspect.to_yaml;
367
+ end
368
+
369
+ def write_report(old_name, new_folder, new_name, remark = nil)
370
+ return unless @report
371
+ case @report_type
372
+ when :tsv
373
+ @report.puts "old_name\tnew_folder\tnew_name\tremark" if @report.size == 0
374
+ @report.puts "#{for_tsv(old_name)}\t#{for_tsv(new_folder)}" +
375
+ "\t#{for_tsv(new_name)}\t#{for_tsv(remark)}"
376
+ when :csv
377
+ @report.puts 'old_name,new_folder,new_name' if @report.size == 0
378
+ @report.puts "#{for_csv(old_name)},#{for_csv(new_folder)}" +
379
+ ",#{for_csv(new_name)},#{for_csv(remark)}"
380
+ when :xml
381
+ @report.puts '<?xml version="1.0" encoding="UTF-8"?>' if @report.size == 0
382
+ @report.puts '<report>' if @report.size == 1
383
+ @report.puts ' <file>'
384
+ @report.puts " <old_name>#{for_xml(old_name, :text)}</old_name>"
385
+ @report.puts " <new_folder>#{for_xml(new_folder, :text)}</new_folder>"
386
+ @report.puts " <new_name>#{for_xml(new_name, :text)}</new_name>"
387
+ @report.puts " <remark>#{for_xml(remark, :text)}</remark>" if remark
388
+ @report.puts ' </file>'
389
+ when :yml
390
+ @report.puts '# Reorganisation report' if @report.size == 0
391
+ @report.puts "- old_name: #{for_yml(old_name)}" +
392
+ "\n new_folder: #{for_yml(new_folder)}" +
393
+ "\n new_name: #{for_yml(new_name)}" +
394
+ (remark ? "\n remark: #{for_yml(remark)}" : '')
395
+ else
396
+ #nothing
397
+ end
398
+ end
399
+
400
+ def close_report
401
+ return unless @report
402
+ if @report_type == :xml
403
+ @report.puts '</report>'
404
+ end
405
+ @report.close
406
+ end
407
+
408
+ def compare_entry(src, tgt)
409
+ hasher = Libis::Tools::Checksum.new(:SHA256)
410
+ hasher.digest(src) == hasher.digest(tgt)
411
+ end
412
+
413
+ end
414
+ end
415
+ end
416
+ end