libis-tools 1.0.5-java

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