data_forge 0.1

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 (62) hide show
  1. data/.gitignore +19 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +20 -0
  6. data/README.md +49 -0
  7. data/Rakefile +16 -0
  8. data/bin/forge +4 -0
  9. data/config/cucumber.yml +8 -0
  10. data/data_forge.gemspec +26 -0
  11. data/features/accessing_command_line_parameters.feature +52 -0
  12. data/features/deduplication.feature +49 -0
  13. data/features/file/file_format_options.feature +146 -0
  14. data/features/file/has_header_row.feature +62 -0
  15. data/features/step_definitions/file_steps.rb +8 -0
  16. data/features/support/env.rb +8 -0
  17. data/features/transform/output_command.feature +123 -0
  18. data/features/transform/outputting_to_multiple_files.feature +57 -0
  19. data/features/transform/overwrite_original_file.feature +37 -0
  20. data/features/transform/record_transformation.feature +47 -0
  21. data/lib/data_forge/cli/main.rb +21 -0
  22. data/lib/data_forge/cli/options.rb +62 -0
  23. data/lib/data_forge/cli.rb +24 -0
  24. data/lib/data_forge/dsl/attributes.rb +15 -0
  25. data/lib/data_forge/dsl/commands.rb +23 -0
  26. data/lib/data_forge/dsl/helpers.rb +22 -0
  27. data/lib/data_forge/dsl.rb +9 -0
  28. data/lib/data_forge/file/csv/csv_record_file_definition.rb +46 -0
  29. data/lib/data_forge/file/csv/csv_record_file_reader.rb +42 -0
  30. data/lib/data_forge/file/csv/csv_record_file_writer.rb +62 -0
  31. data/lib/data_forge/file/csv.rb +13 -0
  32. data/lib/data_forge/file/record_file_definition.rb +17 -0
  33. data/lib/data_forge/file/record_file_reader.rb +22 -0
  34. data/lib/data_forge/file/record_file_writer.rb +32 -0
  35. data/lib/data_forge/file.rb +36 -0
  36. data/lib/data_forge/transformation/deduplication.rb +38 -0
  37. data/lib/data_forge/transformation/ruby_transformation.rb +33 -0
  38. data/lib/data_forge/transformation/ruby_transformation_context.rb +27 -0
  39. data/lib/data_forge/transformation/transformation_base.rb +29 -0
  40. data/lib/data_forge/transformation.rb +10 -0
  41. data/lib/data_forge/version.rb +3 -0
  42. data/lib/data_forge.rb +13 -0
  43. data/spec/data_forge/cli/main_spec.rb +45 -0
  44. data/spec/data_forge/cli/options_spec.rb +64 -0
  45. data/spec/data_forge/cli_spec.rb +54 -0
  46. data/spec/data_forge/dsl/commands_spec.rb +42 -0
  47. data/spec/data_forge/dsl/helpers_spec.rb +24 -0
  48. data/spec/data_forge/file/csv/csv_record_file_definition_spec.rb +97 -0
  49. data/spec/data_forge/file/csv/csv_record_file_reader_spec.rb +78 -0
  50. data/spec/data_forge/file/csv/csv_record_file_writer_spec.rb +100 -0
  51. data/spec/data_forge/file/record_file_definition_spec.rb +17 -0
  52. data/spec/data_forge/file/record_file_reader_spec.rb +15 -0
  53. data/spec/data_forge/file/record_file_writer_spec.rb +15 -0
  54. data/spec/data_forge/file_spec.rb +49 -0
  55. data/spec/data_forge/transformation/deduplication_spec.rb +77 -0
  56. data/spec/data_forge/transformation/ruby_transformation_context_spec.rb +49 -0
  57. data/spec/data_forge/transformation/ruby_transformation_spec.rb +71 -0
  58. data/spec/data_forge_spec.rb +9 -0
  59. data/spec/spec_helper.rb +17 -0
  60. data/spec/support/helpers/record_reader_helper.rb +17 -0
  61. data/spec/support/helpers/record_writer_helper.rb +16 -0
  62. metadata +218 -0
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .ruby-version
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ - "2.1.2"
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Zoltan Ormandi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ [![Build Status](https://travis-ci.org/zormandi/data_forge.svg)](https://travis-ci.org/zormandi/data_forge)
2
+
3
+ # DataForge
4
+
5
+ DataForge is a data manipulation tool for transferring (and transforming) data between flat files and databases.
6
+
7
+ ## Requirements
8
+
9
+ * Ruby >= 1.9
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ``` ruby
16
+ gem 'data_forge'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ ``` sh
22
+ $ bundle
23
+ ```
24
+
25
+ Or install it yourself as:
26
+
27
+ ``` sh
28
+ $ gem install data_forge
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ``` sh
34
+ $ forge [options] <command script>
35
+ ```
36
+
37
+ For information on how to use Data Forge please see the `features` directory, which has code examples on every working
38
+ feature of the gem.
39
+
40
+ For the complete documentation (coming with the 0.2 version) and the detailed development roadmap, please see the
41
+ [Wiki](https://github.com/zormandi/data_forge/wiki).
42
+
43
+ ## Contributing
44
+
45
+ 1. Fork it
46
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
47
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
48
+ 4. Push to the branch (`git push origin my-new-feature`)
49
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require 'cucumber/rake/task'
4
+
5
+ desc "Run RSpec code examples (options: RSPEC_SEED=seed)"
6
+ RSpec::Core::RakeTask.new :spec do |task|
7
+ task.verbose = false
8
+ task.rspec_opts = "--format progress --order random"
9
+ task.rspec_opts << " --seed #{ENV['RSPEC_SEED']}" if ENV['RSPEC_SEED']
10
+ end
11
+
12
+ Cucumber::Rake::Task.new(:features, "Run Cucumber features") do |task|
13
+ task.cucumber_opts = %w[--profile build]
14
+ end
15
+
16
+ task :default => [:spec, :features]
data/bin/forge ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'data_forge'
3
+
4
+ DataForge::CLI::Main.new(ARGV.dup).execute!
@@ -0,0 +1,8 @@
1
+ default:
2
+ --backtrace
3
+ --tags ~@wip
4
+
5
+ build:
6
+ --backtrace
7
+ --format progress
8
+ --tags ~@wip
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'data_forge/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "data_forge"
8
+ spec.version = DataForge::VERSION
9
+ spec.authors = ["Zoltan Ormandi"]
10
+ spec.email = ["zoltan.ormandi@gmail.com"]
11
+ spec.description = %q{DataForge is a data manipulation tool for transferring (and transforming) data between flat files and databases.}
12
+ spec.summary = %q{Pure Ruby ETL and data manipulation tool.}
13
+ spec.homepage = "https://github.com/zormandi/data_forge"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "cucumber"
25
+ spec.add_development_dependency "aruba"
26
+ end
@@ -0,0 +1,52 @@
1
+ Feature: Accessing command line parameters
2
+
3
+ The following command line parameters are available in the command script during execution:
4
+ - COMMAND_SCRIPT: The name of the command script currently executing.
5
+ - PARAMS: The user-defined parameters passed to the script as a hash.
6
+
7
+
8
+ Scenario: Accessing the command script
9
+ Given a file named "config.rb" with:
10
+ """
11
+ File.write 'script_name.txt', COMMAND_SCRIPT
12
+ """
13
+ And a file named "command_script.rb" with:
14
+ """
15
+ require_relative 'config'
16
+ """
17
+ When I run `forge command_script.rb`
18
+ Then the exit status should be 0
19
+ And a file named "script_name.txt" should exist
20
+ And the file "script_name.txt" should contain "command_script.rb"
21
+
22
+
23
+ Scenario: Passing user-defined parameters to a file definition and a transform block
24
+ Given a file named "command_script.rb" with:
25
+ """
26
+ file :items do
27
+ file_name PARAMS[:data_file]
28
+ field :id
29
+ end
30
+
31
+ transform :items do |record|
32
+ record[:id] = PARAMS[:id]
33
+ output record
34
+ end
35
+ """
36
+ And a file named "ids.csv" with:
37
+ """
38
+ id
39
+ 1
40
+ 2
41
+ 3
42
+ """
43
+ When I run `forge -Udata_file=ids.csv -Uid=5 command_script.rb`
44
+ Then the exit status should be 0
45
+ And the file "ids.csv" should contain exactly:
46
+ """
47
+ id
48
+ 5
49
+ 5
50
+ 5
51
+
52
+ """
@@ -0,0 +1,49 @@
1
+ Feature: Deduplicating data in a file
2
+
3
+ The `deduplicate` keyword can be used to remove duplicate records from a file. Only the first occurence of each
4
+ duplicate record is kept.
5
+
6
+ Parameters:
7
+ - The source file to be deduplicated. This parameter is mandatory.
8
+ - into: The target file name. If no target file is specified then the source file is overwritten with the result
9
+ of the deduplication.
10
+ - using: The fields to be used to determine whether or not a record is a duplicate. If not specified then all
11
+ fields of the source file are used.
12
+
13
+
14
+ Scenario: Single file transformation
15
+ Given a file named "command_script.rb" with:
16
+ """
17
+ file :items do
18
+ field :rownum
19
+ field :item_id
20
+ field :item_name
21
+ end
22
+
23
+ file :unique_items do
24
+ field :item_id
25
+ field :item_name
26
+ end
27
+
28
+ deduplicate :items, into: :unique_items, using: :item_id
29
+ """
30
+ And a file named "items.csv" with:
31
+ """
32
+ rownum,item_id,item_name
33
+ 1,Item1,Item name 1
34
+ 2,Item1,Item name 1
35
+ 3,Item2,Item name 2
36
+ 4,Item2,Item name 2
37
+ 5,Item3,Item name 3
38
+ """
39
+ When I run `forge command_script.rb`
40
+ Then the exit status should be 0
41
+ And a file named "unique_items.csv" should exist
42
+ And the file "unique_items.csv" should contain exactly:
43
+ """
44
+ item_id,item_name
45
+ Item1,Item name 1
46
+ Item2,Item name 2
47
+ Item3,Item name 3
48
+
49
+ """
@@ -0,0 +1,146 @@
1
+ Feature: Formatting options for CSV files
2
+
3
+ The `file` block offers options for setting the CSV file's delimiter character, quote character and encoding.
4
+ All native Ruby encodings are supported.
5
+
6
+ The default values are:
7
+ - delimiter: ,
8
+ - quote character: "
9
+ - encoding: UTF-8
10
+
11
+
12
+ Scenario: Using the default delimiter and quote character
13
+ Given a file named "command_script.rb" with:
14
+ """
15
+ file :items do
16
+ field :id
17
+ field :name
18
+ field :category
19
+ end
20
+
21
+ file :items_copy do
22
+ field :id
23
+ field :name
24
+ field :category
25
+ end
26
+
27
+ transform :items, into: :items_copy do |record|
28
+ output record
29
+ end
30
+ """
31
+ And a file named "items.csv" with:
32
+ """
33
+ id,name,category
34
+ 1,"27"" screen",Screens
35
+ """
36
+ When I run `forge command_script.rb`
37
+ Then the exit status should be 0
38
+ And a file named "items_copy.csv" should exist
39
+ And the file "items_copy.csv" should contain exactly:
40
+ """
41
+ id,name,category
42
+ 1,"27"" screen",Screens
43
+
44
+ """
45
+
46
+
47
+ Scenario: Using custom delimiter and quote character
48
+ Given a file named "command_script.rb" with:
49
+ """
50
+ file :items do
51
+ delimiter ";"
52
+ quote "'"
53
+
54
+ field :id
55
+ field :name
56
+ field :category
57
+ end
58
+
59
+ file :items_copy do
60
+ delimiter "|"
61
+ quote "`"
62
+
63
+ field :id
64
+ field :name
65
+ field :category
66
+ end
67
+
68
+ transform :items, into: :items_copy do |record|
69
+ output record
70
+ end
71
+ """
72
+ And a file named "items.csv" with:
73
+ """
74
+ id;name;category
75
+ 1;'27'' screen';Screens
76
+ 2;24` screen;Screens
77
+ """
78
+ When I run `forge command_script.rb`
79
+ Then the exit status should be 0
80
+ And a file named "items_copy.csv" should exist
81
+ And the file "items_copy.csv" should contain exactly:
82
+ """
83
+ id|name|category
84
+ 1|27' screen|Screens
85
+ 2|`24`` screen`|Screens
86
+
87
+ """
88
+
89
+
90
+ Scenario: Using custom encoding
91
+ Given a file named "command_script.rb" with:
92
+ """
93
+ file :items do
94
+ encoding "UTF-8"
95
+
96
+ field :id
97
+ field :name
98
+ field :category
99
+ end
100
+
101
+ file :items_latin2 do
102
+ encoding "ISO-8859-2"
103
+
104
+ field :id
105
+ field :name
106
+ field :category
107
+ end
108
+
109
+ file :items_copy do
110
+ encoding "UTF-8"
111
+
112
+ field :id
113
+ field :name
114
+ field :category
115
+ end
116
+
117
+ transform :items, into: :items_latin2 do |record|
118
+ output record
119
+ end
120
+
121
+ transform :items_latin2, into: :items_copy do |record|
122
+ output record
123
+ end
124
+ """
125
+ And a file named "items.csv" with:
126
+ """
127
+ id,name,category
128
+ 1,Képernyő,Screens
129
+ """
130
+ When I run `forge command_script.rb`
131
+ Then the exit status should be 0
132
+ And the following files should exist:
133
+ | items_latin2.csv |
134
+ | items_copy.csv |
135
+ And the file "items_latin2.csv" should contain in "ISO-8859-2" encoding exactly:
136
+ """
137
+ id,name,category
138
+ 1,Képernyő,Screens
139
+
140
+ """
141
+ And the file "items_copy.csv" should contain in "UTF-8" encoding exactly:
142
+ """
143
+ id,name,category
144
+ 1,Képernyő,Screens
145
+
146
+ """
@@ -0,0 +1,62 @@
1
+ Feature: File `has_header` option
2
+
3
+ The `has_header_row` option of a `file` block specifies whether or not the corresponding CSV file has a header row.
4
+ If a file is specified to have a header row then the first row of the file is skipped during transformation.
5
+ If not, then all rows of the file are processed. When writing into files with a header row, the fields specified
6
+ for that file are written as the header of the CSV file. If an output file is specified to have no header then
7
+ only data rows are written into it.
8
+
9
+
10
+ Scenario: Using the has_header option
11
+ Given a file named "command_script.rb" with:
12
+ """
13
+ file :items do
14
+ has_header_row true
15
+
16
+ field :id
17
+ field :name
18
+ end
19
+
20
+ file :items_without_header do
21
+ has_header_row false
22
+
23
+ field :id
24
+ field :name
25
+ end
26
+
27
+ file :items_copy do
28
+ has_header_row true
29
+
30
+ field :id
31
+ field :name
32
+ end
33
+
34
+ transform :items, into: :items_without_header do |record|
35
+ output record
36
+ end
37
+
38
+ transform :items_without_header, into: :items_copy do |record|
39
+ output record
40
+ end
41
+ """
42
+ And a file named "items.csv" with:
43
+ """
44
+ id,name
45
+ 1,data
46
+ """
47
+ When I run `forge command_script.rb`
48
+ Then the exit status should be 0
49
+ And the following files should exist:
50
+ | items_without_header.csv |
51
+ | items_copy.csv |
52
+ And the file "items_without_header.csv" should contain exactly:
53
+ """
54
+ 1,data
55
+
56
+ """
57
+ And the file "items_copy.csv" should contain exactly:
58
+ """
59
+ id,name
60
+ 1,data
61
+
62
+ """
@@ -0,0 +1,8 @@
1
+ Then /^the file "([^"]*)" should be empty$/ do |file_name|
2
+ step %Q(the file "#{file_name}" should contain exactly:), ""
3
+ end
4
+
5
+
6
+ Then /^the file "([^"]*)" should contain in "([^"]*)" encoding exactly:$/ do |file_name, encoding, content|
7
+ prep_for_fs_check { expect(IO.read(file_name, encoding: encoding).encode("UTF-8", encoding)).to eq content }
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'aruba'
2
+ require 'aruba/in_process'
3
+ require 'aruba/cucumber'
4
+
5
+ require 'data_forge'
6
+
7
+ Aruba::InProcess.main_class = DataForge::CLI::Main
8
+ Aruba.process = Aruba::InProcess
@@ -0,0 +1,123 @@
1
+ Feature: Using the `output` command
2
+
3
+ The `output` command in a transformation block writes the specified data (HasH) into the output file.
4
+ Omitting this command conditionally for a row will skip the record and omitting it unconditionally will
5
+ simply create an empty file without outputting any data into it.
6
+
7
+
8
+ Background:
9
+ Given a file named "items.csv" with:
10
+ """
11
+ id
12
+ 1
13
+ 2
14
+ 3
15
+ """
16
+
17
+
18
+ Scenario: Outputting arbitrary data as Hash
19
+ Given a file named "command_script.rb" with:
20
+ """
21
+ file :items do
22
+ field :id
23
+ end
24
+
25
+ file :books do
26
+ field :author
27
+ field :title
28
+ end
29
+
30
+ transform :items, into: :books do |record|
31
+ output title: "Title #{record[:id]}",
32
+ author: "Author #{record[:id]}"
33
+ end
34
+ """
35
+ When I run `forge command_script.rb`
36
+ Then the exit status should be 0
37
+ And a file named "books.csv" should exist
38
+ And the file "books.csv" should contain exactly:
39
+ """
40
+ author,title
41
+ Author 1,Title 1
42
+ Author 2,Title 2
43
+ Author 3,Title 3
44
+
45
+ """
46
+
47
+
48
+ Scenario: No output in transform block creates empty file
49
+ Given a file named "command_script.rb" with:
50
+ """
51
+ file :items do
52
+ field :id
53
+ end
54
+
55
+ file :items_empty do
56
+ field :id
57
+ end
58
+
59
+ transform :items, into: :items_empty do end
60
+ """
61
+ When I run `forge command_script.rb`
62
+ Then the exit status should be 0
63
+ And a file named "items_empty.csv" should exist
64
+ And the file "items_empty.csv" should be empty
65
+
66
+
67
+ Scenario: Omitting output conditionally skips current record
68
+ Given a file named "command_script.rb" with:
69
+ """
70
+ file :items do
71
+ field :id
72
+ end
73
+
74
+ file :items_missing do
75
+ field :id
76
+ end
77
+
78
+ transform :items, into: :items_missing do |record|
79
+ output record unless "2" == record[:id]
80
+ end
81
+ """
82
+ When I run `forge command_script.rb`
83
+ Then the exit status should be 0
84
+ And a file named "items_missing.csv" should exist
85
+ And the file "items_missing.csv" should contain exactly:
86
+ """
87
+ id
88
+ 1
89
+ 3
90
+
91
+ """
92
+
93
+
94
+ Scenario: Multiple output commands multiply lines
95
+ Given a file named "command_script.rb" with:
96
+ """
97
+ file :items do
98
+ field :id
99
+ end
100
+
101
+ file :items_doubled do
102
+ field :id
103
+ end
104
+
105
+ transform :items, into: :items_doubled do |record|
106
+ output record
107
+ output record
108
+ end
109
+ """
110
+ When I run `forge command_script.rb`
111
+ Then the exit status should be 0
112
+ And a file named "items_doubled.csv" should exist
113
+ And the file "items_doubled.csv" should contain exactly:
114
+ """
115
+ id
116
+ 1
117
+ 1
118
+ 2
119
+ 2
120
+ 3
121
+ 3
122
+
123
+ """
@@ -0,0 +1,57 @@
1
+ Feature: Splitting a file into multiple files
2
+
3
+ The `transform` block can take multiple output file parameters, in which case the `output` command can be used
4
+ to specify which file to write to.
5
+
6
+
7
+ Scenario:
8
+ Given a file named "command_script.rb" with:
9
+ """
10
+ file :items do
11
+ field :name
12
+ end
13
+
14
+ file :items_not_a do
15
+ field :name
16
+ end
17
+
18
+ file :items_not_b do
19
+ field :name
20
+ end
21
+
22
+ transform :items, into: [:items_not_a, :items_not_b] do |record|
23
+ if record[:name].start_with? "a"
24
+ output record, to: :items_not_b
25
+ elsif record[:name].start_with? "b"
26
+ output record, to: :items_not_a
27
+ else
28
+ output record, to: [:items_not_a, :items_not_b]
29
+ end
30
+ end
31
+ """
32
+ And a file named "items.csv" with:
33
+ """
34
+ name
35
+ a
36
+ b
37
+ c
38
+ """
39
+ When I run `forge command_script.rb`
40
+ Then the exit status should be 0
41
+ And the following files should exist:
42
+ | items_not_a.csv |
43
+ | items_not_b.csv |
44
+ And the file "items_not_a.csv" should contain exactly:
45
+ """
46
+ name
47
+ b
48
+ c
49
+
50
+ """
51
+ And the file "items_not_b.csv" should contain exactly:
52
+ """
53
+ name
54
+ a
55
+ c
56
+
57
+ """