ascii-data-tools 0.9

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 (70) hide show
  1. data/.gitignore +3 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +4 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +40 -0
  6. data/LICENSE.GPL2 +339 -0
  7. data/README.rdoc +52 -0
  8. data/Rakefile +42 -0
  9. data/TODO +4 -0
  10. data/ascii-data-tools.gemspec +30 -0
  11. data/bin/ascii-data-cat +13 -0
  12. data/bin/ascii-data-edit +13 -0
  13. data/bin/ascii-data-norm +13 -0
  14. data/bin/ascii-data-qdiff +13 -0
  15. data/bin/ascii-data-tools-config +9 -0
  16. data/examples/big +10000 -0
  17. data/examples/built_in_records.gz +0 -0
  18. data/examples/slightly_modified_built_in_records.gz +0 -0
  19. data/features/ascii-data-cat.feature +110 -0
  20. data/features/ascii-data-edit.feature +91 -0
  21. data/features/ascii-data-qdiff.feature +54 -0
  22. data/features/encoding_decoding.feature +68 -0
  23. data/features/normaliser.feature +27 -0
  24. data/features/plugins.feature +73 -0
  25. data/features/record_recognition.feature +61 -0
  26. data/features/step_definitions/ascii-data-cat_steps.rb +48 -0
  27. data/features/step_definitions/ascii-data-edit_steps.rb +38 -0
  28. data/features/step_definitions/ascii-data-norm_steps.rb +7 -0
  29. data/features/step_definitions/ascii-data-qdiff_steps.rb +43 -0
  30. data/features/step_definitions/encoding_decoding_steps.rb +23 -0
  31. data/features/step_definitions/plugins_steps.rb +11 -0
  32. data/features/step_definitions/record_recognition_steps.rb +10 -0
  33. data/features/support/env.rb +5 -0
  34. data/lib/ascii-data-tools.rb +8 -0
  35. data/lib/ascii-data-tools/configuration.rb +169 -0
  36. data/lib/ascii-data-tools/configuration_printer.rb +38 -0
  37. data/lib/ascii-data-tools/controller.rb +123 -0
  38. data/lib/ascii-data-tools/discover.rb +19 -0
  39. data/lib/ascii-data-tools/external_programs.rb +23 -0
  40. data/lib/ascii-data-tools/filter.rb +148 -0
  41. data/lib/ascii-data-tools/filter/diffing.rb +139 -0
  42. data/lib/ascii-data-tools/formatting.rb +109 -0
  43. data/lib/ascii-data-tools/global_autodiscovery.rb +21 -0
  44. data/lib/ascii-data-tools/record.rb +50 -0
  45. data/lib/ascii-data-tools/record_type.rb +139 -0
  46. data/lib/ascii-data-tools/record_type/builder.rb +50 -0
  47. data/lib/ascii-data-tools/record_type/decoder.rb +77 -0
  48. data/lib/ascii-data-tools/record_type/encoder.rb +17 -0
  49. data/lib/ascii-data-tools/record_type/field.rb +168 -0
  50. data/lib/ascii-data-tools/record_type/normaliser.rb +38 -0
  51. data/lib/ascii-data-tools/ruby_extensions.rb +7 -0
  52. data/lib/ascii-data-tools/version.rb +3 -0
  53. data/spec/ascii-data-tools/configuration_printer_spec.rb +51 -0
  54. data/spec/ascii-data-tools/configuration_spec.rb +153 -0
  55. data/spec/ascii-data-tools/discover_spec.rb +8 -0
  56. data/spec/ascii-data-tools/filter/diffing_spec.rb +82 -0
  57. data/spec/ascii-data-tools/filter_spec.rb +107 -0
  58. data/spec/ascii-data-tools/formatting_spec.rb +106 -0
  59. data/spec/ascii-data-tools/record_spec.rb +49 -0
  60. data/spec/ascii-data-tools/record_type/builder_spec.rb +69 -0
  61. data/spec/ascii-data-tools/record_type/decoder_spec.rb +73 -0
  62. data/spec/ascii-data-tools/record_type/encoder_spec.rb +32 -0
  63. data/spec/ascii-data-tools/record_type/field_spec.rb +160 -0
  64. data/spec/ascii-data-tools/record_type/normaliser_spec.rb +25 -0
  65. data/spec/ascii-data-tools/record_type_spec.rb +175 -0
  66. data/spec/filter_helper.rb +24 -0
  67. data/spec/record_type_helpers.rb +8 -0
  68. data/spec/spec.opts +2 -0
  69. data/spec/spec_helper.rb +5 -0
  70. metadata +196 -0
@@ -0,0 +1,48 @@
1
+ require 'stringio'
2
+
3
+ Before do |scenario|
4
+ @output_stream = StringIO.new
5
+ @input_stream = StringIO.new
6
+ @user_feedback_stream = StringIO.new
7
+
8
+ @command_line = []
9
+
10
+ AsciiDataTools.record_types.clear
11
+ load 'ascii-data-tools/discover.rb'
12
+ @record_types = AsciiDataTools.record_types
13
+ end
14
+
15
+ Given /^a record stream containing$/ do |string|
16
+ @input_stream.string = string
17
+ end
18
+
19
+ Given /^file "([^\"]*)" containing$/ do |filename, string|
20
+ @record_source_filename = filename
21
+ @input_stream.string = string
22
+ end
23
+
24
+ When /^ascii-data-cat is invoked$/ do
25
+ AsciiDataTools::Controller::CatController.new(
26
+ :input_sources => [AsciiDataTools::InputSource.new(@record_source_filename, @input_stream)],
27
+ :output_stream => @output_stream,
28
+ :record_types => @record_types
29
+ ).run
30
+ end
31
+
32
+ When /^([^\"]*) is invoked on a file "([^\"]*)" containing$/ do |executable, filename, string|
33
+ Given "file \"#{filename}\" containing", string
34
+ When "#{executable} is invoked"
35
+ end
36
+
37
+ When /^([^\"]*) is invoked on a record stream containing$/ do |executable, string|
38
+ Given "a record stream containing", string
39
+ When "#{executable} is invoked"
40
+ end
41
+
42
+ Then /^the following is printed out:$/ do |string|
43
+ @output_stream.string.should == string
44
+ end
45
+
46
+ Then /^the user receives the following feedback:$/ do |string|
47
+ @user_feedback_stream.string.should == string
48
+ end
@@ -0,0 +1,38 @@
1
+ When /^ascii-data-edit is invoked$/ do
2
+ AsciiDataTools::Controller::EditController.new(
3
+ :input_sources => [AsciiDataTools::InputSource.new(@record_source_filename, @input_stream)],
4
+ :output_stream => @output_stream,
5
+ :record_types => @record_types,
6
+ :user_feedback_stream => @user_feedback_stream,
7
+ :editor => lambda do |filenames|
8
+ edited_file = File.new(filenames.first)
9
+ edited_file.extend(AsciiDataTools::ExternalPrograms)
10
+ @text_prior_to_edit = edited_file.read
11
+ File.open(edited_file.path, 'w') {|f| f << @text_after_edit}
12
+ edited_file.modify_file_mtime_to(Time.now + 1)
13
+ end
14
+ ).run
15
+ end
16
+
17
+ When /^the output is successfully ascii\-edited to the following:$/ do |string|
18
+ @text_after_edit = string
19
+ When "ascii-data-edit is invoked"
20
+ end
21
+
22
+ When /^the output is ascii\-edited without alteration$/ do
23
+ AsciiDataTools::Controller::EditController.new(
24
+ :input_sources => [AsciiDataTools::InputSource.new(@record_source_filename, @input_stream)],
25
+ :output_stream => @output_stream,
26
+ :record_types => @record_types,
27
+ :user_feedback_stream => @user_feedback_stream,
28
+ :editor => lambda do |filenames| end
29
+ ).run
30
+ end
31
+
32
+ Then /^the editor shows:$/ do |string|
33
+ @text_prior_to_edit.should == string
34
+ end
35
+
36
+ Then /^the encoded record stream contains:$/ do |string|
37
+ @output_stream.string.should == string
38
+ end
@@ -0,0 +1,7 @@
1
+ When /^ascii\-data\-norm is invoked$/ do
2
+ AsciiDataTools::Controller::NormalisationController.new(
3
+ :input_sources => [AsciiDataTools::InputSource.new(@record_source_filename, @input_stream)],
4
+ :output_stream => @output_stream,
5
+ :record_types => @record_types
6
+ ).run
7
+ end
@@ -0,0 +1,43 @@
1
+ Given /^streams containing$/ do |text|
2
+ @input_stream1 = StringIO.new
3
+ @input_stream2 = StringIO.new
4
+
5
+ text.split("\n").each do |line|
6
+ left_line, right_line = line.split("||").map(&:strip)
7
+ @input_stream1 << left_line.gsub('\n', "\n") unless left_line == "-" * left_line.length and not left_line.empty?
8
+ @input_stream2 << right_line.gsub('\n', "\n") unless right_line == "-" * right_line.length and not right_line.empty?
9
+ end
10
+ @input_stream1.rewind
11
+ @input_stream2.rewind
12
+ end
13
+
14
+ When /^ascii\-data\-qdiff is invoked on files containing:$/ do |string|
15
+ Given "streams containing", string
16
+ When "ascii-data-qdiff is invoked"
17
+ end
18
+
19
+ When /^ascii\-data\-qdiff is invoked$/ do
20
+ @actual_output1 = @actual_output2 = nil
21
+ AsciiDataTools::Controller::QDiffController.new(
22
+ :input_sources => [AsciiDataTools::InputSource.new(nil, @input_stream1),
23
+ AsciiDataTools::InputSource.new(nil, @input_stream2)],
24
+ :editor => lambda do |filenames|
25
+ @actual_output1 = File.read(filenames.first)
26
+ @actual_output2 = File.read(filenames.last)
27
+ end,
28
+ :output_stream => @output_stream,
29
+ :record_types => @record_types,
30
+ :user_feedback_stream => @user_feedback_stream
31
+ ).run
32
+ end
33
+
34
+ Then /^the diffed result should be:$/ do |text|
35
+ expected_output1, expected_output2 = "", ""
36
+ text.split("\n").each do |line|
37
+ left_line, right_line = line.split("||").map(&:strip)
38
+ expected_output1 << left_line + "\n" unless left_line == "-" * left_line.length and not left_line.empty?
39
+ expected_output2 << right_line + "\n" unless right_line == "-" * right_line.length and not right_line.empty?
40
+ end
41
+ @actual_output1.should == expected_output1
42
+ @actual_output2.should == expected_output2
43
+ end
@@ -0,0 +1,23 @@
1
+ When /^I decode an encoded record "([^\"]*)" of type "([^\"]*)"$/ do |ascii_text, record_type_name|
2
+ ascii_text.gsub!('\\n', "\n")
3
+ @record = @record_types.find_by_name(record_type_name).decode(:ascii_string => ascii_text)
4
+ end
5
+
6
+ When /^I encode a record of type "([^\"]*)" and contents:$/ do |record_type_name, table|
7
+ record_type = @record_types.find_by_name(record_type_name)
8
+ values = table.hashes.collect {|hash| hash["field value"].gsub('\\n', "\n") }
9
+ @record = AsciiDataTools::Record::Record.new(record_type, values)
10
+ end
11
+
12
+ Then /^I should have a decoded record of type "([^\"]*)" and contents:$/ do |intended_record_type_name, table|
13
+ @record.type_name.should == intended_record_type_name
14
+ table.hashes.each do |hash|
15
+ expected_value = hash["field value"].gsub('\\n', "\n")
16
+ @record[hash["field name"]].should == expected_value
17
+ end
18
+ end
19
+
20
+ Then /^I should have a encoded record "([^\"]*)"$/ do |expected_encoded_text|
21
+ expected_encoded_text.gsub!('\\n', "\n")
22
+ @record.encode.should == expected_encoded_text
23
+ end
@@ -0,0 +1,11 @@
1
+ Given /^the following configuration:$/ do |code|
2
+ @record_types.instance_eval(code)
3
+ end
4
+
5
+ When /^the record type configuration is printed$/ do
6
+ @configuration_printout = AsciiDataTools::RecordTypesConfigurationPrinter.for_record_types(@record_types).summary
7
+ end
8
+
9
+ Then /^it should look like this:$/ do |expected_string|
10
+ @configuration_printout.should == expected_string
11
+ end
@@ -0,0 +1,10 @@
1
+ When /^record "([^\"]*)" coming from ([^\"]*) is analysed$/ do |ascii_text, record_source_name|
2
+ record_source_name = nil if record_source_name == "unspecified"
3
+ ascii_text.gsub!('\\n', "\n")
4
+ type_determiner = AsciiDataTools::RecordType::TypeDeterminer.new(@record_types)
5
+ @type = type_determiner.determine_type_for(:ascii_string => ascii_text, :filename => record_source_name)
6
+ end
7
+
8
+ Then /^its type should be recognised as "([^\"]*)"$/ do |type_name|
9
+ @type.name.should == type_name
10
+ end
@@ -0,0 +1,5 @@
1
+ lib_path = File.expand_path("#{File.dirname(__FILE__)}/../../lib")
2
+ $LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
3
+
4
+ require 'ascii-data-tools'
5
+ require 'ascii-data-tools/configuration_printer'
@@ -0,0 +1,8 @@
1
+ require 'ascii-data-tools/ruby_extensions'
2
+
3
+ require 'ascii-data-tools/record'
4
+ require 'ascii-data-tools/record_type'
5
+ require 'ascii-data-tools/formatting'
6
+ require 'ascii-data-tools/configuration'
7
+ require 'ascii-data-tools/global_autodiscovery'
8
+ require 'ascii-data-tools/controller'
@@ -0,0 +1,169 @@
1
+ require 'optparse'
2
+ require 'zlib'
3
+ require 'tempfile'
4
+
5
+ module AsciiDataTools
6
+ class Configuration
7
+ attr_reader :input_sources, :output_stream, :errors, :record_types, :editor, :user_feedback_stream
8
+
9
+ def initialize(arguments, overrides = {})
10
+ @arguments = arguments
11
+ @overrides = overrides
12
+ @errors = []
13
+
14
+ @opts = define_optionparser_configuration
15
+ remainder = parse(arguments)
16
+
17
+ @output_stream = overrides[:output_stream] || STDOUT
18
+ @input_sources = overrides[:input_sources] || make_input_streams(remainder, overrides)
19
+ @record_types = overrides[:record_types] || load_record_types
20
+ @editor = overrides[:editor]
21
+ @user_feedback_stream = overrides[:user_feedback_stream] || STDOUT
22
+ end
23
+
24
+ def valid?
25
+ @errors.empty?
26
+ end
27
+
28
+ def error_info_with_usage
29
+ @errors.each {|error| puts error}
30
+ puts
31
+ puts @opts
32
+ exit 1
33
+ end
34
+
35
+ protected
36
+ def make_input_streams(remainder, overrides)
37
+ begin
38
+ return InputSourceFactory.new(overrides).input_sources_from(remainder)
39
+ rescue Exception => e
40
+ @errors << e.message
41
+ return nil
42
+ end
43
+ end
44
+
45
+ def define_optionparser_configuration
46
+ OptionParser.new do |opts|
47
+ opts.banner = [
48
+ "Usage: #{File.basename($0)} [options] <input source>",
49
+ "An input source can be either a flat file or a gzipped flat file.",
50
+ @overrides[:input_pipe_accepted] ? "For this command, - (STDIN) is also allowed." : nil,
51
+ "\n"].compact.join("\n")
52
+
53
+ opts.separator ""
54
+ opts.separator "Other options:"
55
+
56
+ opts.on_tail("-h", "--help", "Show this message") do
57
+ puts opts
58
+ exit
59
+ end
60
+ end
61
+ end
62
+
63
+ def parse(arguments)
64
+ begin
65
+ return @opts.parse(arguments)
66
+ rescue SystemExit => e
67
+ exit e.status
68
+ rescue Exception => e
69
+ @errors << e.message
70
+ return []
71
+ end
72
+ end
73
+
74
+ def load_record_types
75
+ AsciiDataTools.autodiscover
76
+ AsciiDataTools.record_types
77
+ end
78
+ end
79
+
80
+ class InputSourceFactory
81
+ def initialize(properties = {})
82
+ @expected_argument_number = properties[:expected_argument_number] || 1
83
+ @input_pipe_accepted = properties[:input_pipe_accepted].nil? ? true : properties[:input_pipe_accepted]
84
+ end
85
+
86
+ def input_sources_from(input_arguments)
87
+ validate_number_of input_arguments
88
+ return input_arguments.collect {|arg| make_input_source_from(arg)}
89
+ end
90
+
91
+ protected
92
+ def validate_number_of(input_arguments)
93
+ raise "No input specified." if input_arguments.empty?
94
+ error_message = "#{input_arguments.size} input sources detected: #{input_arguments.inspect}. " +
95
+ "This command accepts #{@expected_argument_number} input source(s)."
96
+ raise error_message if input_arguments.length != @expected_argument_number
97
+ end
98
+
99
+ def make_input_source_from(input_argument)
100
+ if input_argument == "-"
101
+ if @input_pipe_accepted
102
+ return InputSource.new(nil, STDIN)
103
+ else
104
+ raise "STDIN not accepted for this command."
105
+ end
106
+ end
107
+
108
+ path_to_file = input_argument
109
+ raise "File #{path_to_file} does not exist!" unless File.exists?(path_to_file)
110
+ return InputSource.new(path_to_file, Zlib::GzipReader.open(path_to_file)) if path_to_file =~ /[.]gz$/
111
+ return InputSource.new(path_to_file, File.open(path_to_file))
112
+ end
113
+ end
114
+
115
+ class InputSource < Struct.new(:filename, :stream)
116
+ def read
117
+ stream.readline
118
+ end
119
+
120
+ def has_records?
121
+ not stream.eof?
122
+ end
123
+ end
124
+
125
+ class Editor
126
+ def initialize(&edit_command)
127
+ @tempfiles = {}
128
+ @preedit_mtimes = {}
129
+ @postedit_mtimes = {}
130
+ @edit_command = edit_command
131
+ end
132
+
133
+ def [](n)
134
+ @tempfiles[n] ||= Tempfile.new("ascii_tools")
135
+ end
136
+
137
+ def edit
138
+ close_all_tempfiles
139
+ save_preedit_mtimes
140
+ edit_files
141
+ save_postedit_mtimes
142
+ end
143
+
144
+ def changed?(n)
145
+ not @preedit_mtimes[n] == @postedit_mtimes[n]
146
+ end
147
+
148
+ protected
149
+ def close_all_tempfiles
150
+ @tempfiles.values.each {|f| f.close }
151
+ end
152
+
153
+ def save_preedit_mtimes
154
+ @tempfiles.each {|n, f| @preedit_mtimes[n] = File.mtime(f.path)}
155
+ end
156
+
157
+ def save_postedit_mtimes
158
+ @tempfiles.each {|n, f| @postedit_mtimes[n] = File.mtime(f.path)}
159
+ end
160
+
161
+ def edit_files
162
+ @edit_command[sorted_filenames]
163
+ end
164
+
165
+ def sorted_filenames
166
+ @tempfiles.sort.collect {|number, tempfile| tempfile.path}
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,38 @@
1
+ require 'terminal-table/import'
2
+
3
+ module AsciiDataTools
4
+ class RecordTypesConfigurationPrinter
5
+ def initialize(presenter)
6
+ @presenter = presenter
7
+ end
8
+
9
+ def summary
10
+ table do |t|
11
+ t.headings = @presenter.headings
12
+ @presenter.record_type_summaries.each {|summary| t << summary}
13
+ end.to_s
14
+ end
15
+
16
+ class << self
17
+ def for_record_types(record_types)
18
+ new(RecordTypesConfigurationPresenter.new(record_types))
19
+ end
20
+ end
21
+ end
22
+
23
+ class RecordTypesConfigurationPresenter
24
+ def initialize(record_types)
25
+ @record_types = record_types
26
+ end
27
+
28
+ def headings
29
+ ["type name", "total length", "constraints", "normalised fields"]
30
+ end
31
+
32
+ def record_type_summaries
33
+ @record_types.sort_by {|record_type| record_type.total_length_of_fields}.inject([]) do |summaries, record_type|
34
+ summaries << [record_type.name, record_type.total_length_of_fields, record_type.constraints_description, record_type.names_of_normalised_fields]
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,123 @@
1
+ require 'ascii-data-tools/filter'
2
+ require 'ascii-data-tools/filter/diffing'
3
+ require 'ascii-data-tools/external_programs'
4
+
5
+ module AsciiDataTools
6
+ module Controller
7
+ class AbstractController
8
+ def initialize(configuration_or_command_line_arguments)
9
+ case configuration_or_command_line_arguments
10
+ when Hash then
11
+ @configuration = Configuration.new([], defaults.merge(configuration_or_command_line_arguments))
12
+ when Array then
13
+ @configuration = Configuration.new(configuration_or_command_line_arguments, defaults)
14
+ when Configuration then
15
+ @configuration = configuration_or_command_line_arguments
16
+ end
17
+ @configuration.error_info_with_usage unless @configuration.valid?
18
+ end
19
+
20
+ def type_determiner
21
+ @type_determiner ||= RecordType::TypeDeterminer.new(@configuration.record_types)
22
+ end
23
+
24
+ def run
25
+ raise "should be implemented!"
26
+ end
27
+
28
+ protected
29
+ def input_source
30
+ @configuration.input_sources.first
31
+ end
32
+
33
+ def defaults
34
+ {:expected_argument_number => 1, :input_pipe_accepted => true}
35
+ end
36
+ end
37
+
38
+ class CatController < AbstractController
39
+ def run
40
+ formatting_filter = Filter::FormattingFilter.new(input_source.filename, type_determiner)
41
+ formatting_filter << input_source
42
+ formatting_filter.write(@configuration.output_stream)
43
+ end
44
+ end
45
+
46
+ class EditController < AbstractController
47
+ include ExternalPrograms
48
+ include Filter
49
+
50
+ def run
51
+ editor = Editor.new(&@configuration.editor)
52
+ formatting_filter = FormattingFilter.new(input_source.filename, type_determiner)
53
+ formatting_filter << input_source
54
+
55
+ formatting_filter.write(editor[0])
56
+ editor.edit
57
+
58
+ if not editor.changed?(0)
59
+ @configuration.user_feedback_stream.puts "The file is unmodified."
60
+ else
61
+ encoding_filter = Filter::Filter.new {|record| record.encode }
62
+ parsing_filter = ParsingFilter.new(@configuration.record_types)
63
+ encoding_filter << (parsing_filter << InputSource.new(nil, editor[0].open))
64
+ encoding_filter.write(output_stream)
65
+ end
66
+ end
67
+
68
+ protected
69
+ def output_stream
70
+ @configuration.output_stream == STDOUT ? File.open(input_source.filename, 'w') : @configuration.output_stream
71
+ end
72
+
73
+ def defaults
74
+ {:expected_argument_number => 1,
75
+ :input_pipe_accepted => false,
76
+ :editor => lambda {|filenames| edit_differences(filenames)} }
77
+ end
78
+ end
79
+
80
+ class NormalisationController < AbstractController
81
+ def run
82
+ normalising_filter = Filter::NormalisingFilter.new(input_source.filename, type_determiner)
83
+ normalising_filter << input_source
84
+ normalising_filter.write(@configuration.output_stream)
85
+ end
86
+ end
87
+
88
+ class QDiffController < AbstractController
89
+ include ExternalPrograms
90
+ include Filter
91
+ include Filter::Diffing
92
+
93
+ def run
94
+ editor = Editor.new(&@configuration.editor)
95
+
96
+ normaliser1 = NormalisingFilter.new( @configuration.input_sources[0].filename, type_determiner)
97
+ normaliser2 = NormalisingFilter.new( @configuration.input_sources[1].filename, type_determiner)
98
+ sorter1 = SortingFilter.new
99
+ sorter2 = SortingFilter.new
100
+ diff_executer = DiffExecutingFilter.new
101
+ diff_parser = DiffParsingFilter.new
102
+ diff_formatter = DiffFormattingFilter.new(type_determiner)
103
+
104
+ diff_formatter << (diff_parser << (diff_executer << [sorter1 << (normaliser1 << @configuration.input_sources[0]),
105
+ sorter2 << (normaliser2 << @configuration.input_sources[1])]))
106
+
107
+ begin
108
+ diff_formatter.write(editor[0], editor[1])
109
+ editor.edit
110
+ rescue StreamsEqualException => e
111
+ @configuration.user_feedback_stream.puts "The files are identical."
112
+ end
113
+ end
114
+
115
+ protected
116
+ def defaults
117
+ {:expected_argument_number => 2,
118
+ :input_pipe_accepted => false,
119
+ :editor => lambda {|filenames| edit_differences(filenames)} }
120
+ end
121
+ end
122
+ end
123
+ end