dumb_delimited 1.1.0 → 3.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -3
- data/Gemfile +3 -0
- data/README.md +45 -42
- data/Rakefile +0 -13
- data/dumb_delimited.gemspec +12 -14
- data/lib/dumb_delimited/version.rb +1 -1
- data/lib/dumb_delimited.rb +216 -88
- metadata +14 -75
- data/.gitignore +0 -9
- data/.travis.yml +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98873351e7fa4aeb8624f716a4de4c2090e066b8b14b138d69bd01a5c7d6f4c4
|
|
4
|
+
data.tar.gz: 50595400f33304f7244c850cd72750dcbc70be0caabcbf95cee61dd671167cd1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8797e92b1c578dfaa961b85d31eac7004539123fc3994f9b08ecbb05d7c6675ac01fa961e881795820b59218a27104f8ee60cd133a23a56364a77d77631bc5cc
|
|
7
|
+
data.tar.gz: 71795adf574729557f3524f4007c62baeee9bcb8953874362afbc5843a67c424c88dadf30531738ed57bcd56499ed5807e8fe230214bc9cee30910f0768ec514
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
|
+
## 3.0.0
|
|
2
|
+
|
|
3
|
+
* [BREAKING] Drop support for Ruby < 3.4
|
|
4
|
+
* Fix CSV keyword-argument compatibility
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## 2.0.0
|
|
8
|
+
|
|
9
|
+
* [BREAKING] Do not activate `options[:converters]` by default
|
|
10
|
+
* [BREAKING] Remove *pleasant_path* dependency. This is considered a
|
|
11
|
+
breaking change because *pleasant_path* adds extension methods to
|
|
12
|
+
several Ruby core classes that consumer code may depend on.
|
|
13
|
+
* [BREAKING] Remove `append_to_file` instance method
|
|
14
|
+
* [BREAKING] Rename `each_in_file` class method to `read_each`
|
|
15
|
+
* Add `parse_each` class method
|
|
16
|
+
* Rename `parse_file` class method to `read`, and alias as `parse_file`
|
|
17
|
+
* Rename `parse_text` class method to `parse`, and alias as `parse_text`
|
|
18
|
+
* Add `write` class method
|
|
19
|
+
* Add `append` class method
|
|
20
|
+
* Add `eol` parameter to `to_s` instance method
|
|
21
|
+
* Add `DumbDelimited.csv`, `.psv`, and `.tsv` convenience shortcuts
|
|
22
|
+
* Activate `options[:liberal_parsing]` by default
|
|
23
|
+
* Make `options` inheritable
|
|
24
|
+
* Fix behavior when `options[:headers]` is set
|
|
25
|
+
* Fix behavior when `options[:write_headers]` is set
|
|
26
|
+
|
|
27
|
+
|
|
1
28
|
## 1.1.0
|
|
2
29
|
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
30
|
+
* Add `parse_text` class method
|
|
31
|
+
* Add `append_to_file` instance method
|
|
32
|
+
* Fix gem load on case-sensitive file systems
|
|
6
33
|
|
|
7
34
|
|
|
8
35
|
## 1.0.0
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dumb_delimited
|
|
2
2
|
|
|
3
|
-
A library for unsophisticated delimited flat file IO.
|
|
3
|
+
A library for unsophisticated delimited flat file IO. `dumb_delimited`
|
|
4
4
|
mixes models and persistence in that "probably wrong but feels so right"
|
|
5
5
|
kind of way.
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ used as a superclass or simply assigned to a constant.
|
|
|
33
33
|
```ruby
|
|
34
34
|
class Product < DumbDelimited[:sku, :name, :base_price, :sale_price]
|
|
35
35
|
def on_sale?
|
|
36
|
-
sale_price < base_price
|
|
36
|
+
sale_price.to_f < base_price.to_f
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -42,80 +42,83 @@ Customer.delimiter = "|"
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Because "customers.psv" is pipe-delimited, we also set the delimiter
|
|
45
|
-
for the Customer class. By default, model
|
|
46
|
-
|
|
45
|
+
for the Customer class. By default, a model class uses a comma (`","`)
|
|
46
|
+
as its delimiter. Whenever a delimiter is set, it applies to all future
|
|
47
47
|
IO operations for that model class.
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Convenience shortcuts that create a model class and set its delimiter
|
|
50
|
+
are also provided for a few common delimiters. Notably,
|
|
51
|
+
`DumbDelimited::psv` for a model class with a pipe (`"|"`) delimiter.
|
|
52
|
+
Thus, the `Customer` class could alternatively be written as:
|
|
50
53
|
|
|
51
54
|
```ruby
|
|
52
|
-
|
|
53
|
-
customers = Customer.parse_file("customers.psv")
|
|
55
|
+
Customer = DumbDelimited.psv(:name, :email, :address)
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
|
|
58
|
+
Using our model classes, we can read each flat file, and receive an
|
|
59
|
+
array of model objects:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
products = Product.read("products.csv")
|
|
63
|
+
customers = Customer.read("customers.psv")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
However, this will load the entire contents of each file into memory.
|
|
57
67
|
Let's say our customers file is very large, and we would prefer to
|
|
58
|
-
iterate over it rather than load it all into memory at
|
|
59
|
-
we can use the `
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
iterate over it one row at a time rather than load it all into memory at
|
|
69
|
+
once. To do so, we can use the `read_each` method. Below is a complete
|
|
70
|
+
example in which we load our product data, create a listing of products
|
|
71
|
+
on sale, and iterate over our customers, notifying each customer of the
|
|
72
|
+
sale products:
|
|
63
73
|
|
|
64
74
|
```ruby
|
|
65
|
-
products = Product.
|
|
75
|
+
products = Product.read("products.csv")
|
|
66
76
|
|
|
67
77
|
listing = products.select(&:on_sale?).map do |product|
|
|
68
78
|
"* #{product.name} (#{product.sale_price})"
|
|
69
79
|
end.join("\n")
|
|
70
80
|
|
|
71
|
-
Customer.
|
|
72
|
-
message =
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
Customer.read_each("customers.psv") do |customer|
|
|
82
|
+
message = <<~MESSAGE
|
|
83
|
+
Hi #{customer.name}!
|
|
84
|
+
|
|
85
|
+
The following products are on sale:
|
|
86
|
+
|
|
87
|
+
#{listing}
|
|
88
|
+
MESSAGE
|
|
75
89
|
|
|
76
90
|
notify(customer.email, message)
|
|
77
91
|
end
|
|
78
92
|
```
|
|
79
93
|
|
|
80
94
|
Let's say the sale is now over, and we want to change our sale prices
|
|
81
|
-
back to our base prices.
|
|
82
|
-
|
|
83
|
-
offers a fluent API for writing files. To finish our task, we use the
|
|
84
|
-
`Array#write_to_file` method provided by *pleasant_path*, which in turn
|
|
85
|
-
invokes `Product#to_s` (provided by *dumb_delimited*) on each model
|
|
86
|
-
object.
|
|
95
|
+
back to our base prices. We can load our product data, modify it
|
|
96
|
+
directly, and finally persist it back with the `write` method:
|
|
87
97
|
|
|
88
98
|
```ruby
|
|
89
|
-
Product.
|
|
99
|
+
products = Product.read("products.csv")
|
|
100
|
+
|
|
101
|
+
products.each do |product|
|
|
90
102
|
product.sale_price = product.base_price
|
|
91
|
-
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Product.write("products.csv", products)
|
|
92
106
|
```
|
|
93
107
|
|
|
94
|
-
For a more detailed explanation of the
|
|
95
|
-
[
|
|
108
|
+
For a more detailed explanation of the `dumb_delimited` API, browse the
|
|
109
|
+
[API documentation](https://www.rubydoc.info/gems/dumb_delimited/).
|
|
96
110
|
|
|
97
111
|
|
|
98
112
|
## Installation
|
|
99
113
|
|
|
100
|
-
Install
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
$ gem install dumb_delimited
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Then require in your Ruby script:
|
|
107
|
-
|
|
108
|
-
```ruby
|
|
109
|
-
require "dumb_delimited"
|
|
110
|
-
```
|
|
114
|
+
Install the [`dumb_delimited` gem](https://rubygems.org/gems/dumb_delimited).
|
|
111
115
|
|
|
112
116
|
|
|
113
117
|
## Contributing
|
|
114
118
|
|
|
115
|
-
Run `rake test` to run the tests.
|
|
116
|
-
interactive prompt that pre-loads the project code.
|
|
119
|
+
Run `rake test` to run the tests.
|
|
117
120
|
|
|
118
121
|
|
|
119
122
|
## License
|
|
120
123
|
|
|
121
|
-
[MIT License](
|
|
124
|
+
[MIT License](LICENSE.txt)
|
data/Rakefile
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
require "bundler/gem_tasks"
|
|
2
2
|
require "rake/testtask"
|
|
3
|
-
require "yard"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
YARD::Rake::YardocTask.new(:doc) do |t|
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
desc "Launch IRB with this gem pre-loaded"
|
|
10
|
-
task :irb do
|
|
11
|
-
require "dumb_delimited"
|
|
12
|
-
require "irb"
|
|
13
|
-
ARGV.clear
|
|
14
|
-
IRB.start
|
|
15
|
-
end
|
|
16
3
|
|
|
17
4
|
Rake::TestTask.new(:test) do |t|
|
|
18
5
|
t.libs << "test"
|
data/dumb_delimited.gemspec
CHANGED
|
@@ -1,29 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
require "dumb_delimited/version"
|
|
1
|
+
require_relative "lib/dumb_delimited/version"
|
|
5
2
|
|
|
6
3
|
Gem::Specification.new do |spec|
|
|
7
4
|
spec.name = "dumb_delimited"
|
|
8
5
|
spec.version = DumbDelimited::VERSION
|
|
9
6
|
spec.authors = ["Jonathan Hefner"]
|
|
10
|
-
spec.email = ["jonathan
|
|
7
|
+
spec.email = ["jonathan@hefner.pro"]
|
|
11
8
|
|
|
12
|
-
spec.summary = %q{
|
|
9
|
+
spec.summary = %q{Unsophisticated delimited flat file IO}
|
|
13
10
|
spec.homepage = "https://github.com/jonathanhefner/dumb_delimited"
|
|
14
11
|
spec.license = "MIT"
|
|
12
|
+
spec.required_ruby_version = ">= 3.4"
|
|
15
13
|
|
|
16
|
-
spec.
|
|
17
|
-
|
|
14
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
15
|
+
spec.metadata["changelog_uri"] = spec.metadata["source_code_uri"] + "/blob/master/CHANGELOG.md"
|
|
16
|
+
|
|
17
|
+
# Specify which files should be added to the gem when it is released.
|
|
18
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
19
|
+
spec.files = Dir.chdir(__dir__) do
|
|
20
|
+
`git ls-files -z`.split("\x0").reject { |f| f.start_with?("test/", ".git") }
|
|
18
21
|
end
|
|
19
22
|
spec.bindir = "exe"
|
|
20
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
21
24
|
spec.require_paths = ["lib"]
|
|
22
25
|
|
|
23
|
-
spec.
|
|
24
|
-
|
|
25
|
-
spec.add_development_dependency "bundler", "~> 1.15"
|
|
26
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
|
27
|
-
spec.add_development_dependency "minitest", "~> 5.0"
|
|
28
|
-
spec.add_development_dependency "yard", "~> 0.9"
|
|
26
|
+
spec.add_dependency "csv"
|
|
29
27
|
end
|
data/lib/dumb_delimited.rb
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
require "csv"
|
|
2
|
-
require "pleasant_path"
|
|
3
2
|
require_relative "dumb_delimited/version"
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
module DumbDelimited
|
|
7
6
|
|
|
8
7
|
# Returns a model class for delimited data consisting of the specified
|
|
9
|
-
# columns
|
|
10
|
-
# {https://ruby-
|
|
11
|
-
# manipulation via accessor methods, via indexing by
|
|
12
|
-
# via indexing by column number. See {ClassMethods}
|
|
13
|
-
# {InstanceMethods} for the
|
|
8
|
+
# +columns+. The returned class inherits from Ruby's
|
|
9
|
+
# {https://docs.ruby-lang.org/en/master/Struct.html +Struct+},
|
|
10
|
+
# allowing data manipulation via accessor methods, via indexing by
|
|
11
|
+
# column name, and via indexing by column number. See {ClassMethods}
|
|
12
|
+
# and {InstanceMethods} for the additional methods the returned class
|
|
13
|
+
# provides.
|
|
14
14
|
#
|
|
15
15
|
# @example
|
|
16
16
|
# class Product < DumbDelimited[:sku, :name, :base_price, :sale_price]
|
|
17
17
|
# def on_sale?
|
|
18
|
-
# sale_price < base_price
|
|
18
|
+
# sale_price.to_f < base_price.to_f
|
|
19
19
|
# end
|
|
20
20
|
# end
|
|
21
21
|
#
|
|
22
|
+
# @example
|
|
22
23
|
# Customer = DumbDelimited[:name, :email, :address]
|
|
23
24
|
#
|
|
24
|
-
# @param columns [
|
|
25
|
-
# @return [Class]
|
|
25
|
+
# @param columns [Array<Symbol>]
|
|
26
|
+
# @return [Class<Struct>]
|
|
26
27
|
def self.[](*columns)
|
|
27
28
|
Struct.new(*columns) do
|
|
28
29
|
extend DumbDelimited::ClassMethods
|
|
@@ -30,42 +31,111 @@ module DumbDelimited
|
|
|
30
31
|
end
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
# Convenience shortcut to create a model class and set
|
|
35
|
+
# {ClassMethods#delimiter} to +","+.
|
|
36
|
+
#
|
|
37
|
+
# Note: This method exists mostly for parity with {psv} and {tsv}.
|
|
38
|
+
# Unless +CSV::DEFAULT_OPTIONS+ has been modified, the delimiter will
|
|
39
|
+
# already default to +","+.
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# # This...
|
|
43
|
+
# Point = DumbDelimited.csv(:x, :y, :z)
|
|
44
|
+
#
|
|
45
|
+
# # ...is equivalent to:
|
|
46
|
+
# Point = DumbDelimited[:x, :y, :z]
|
|
47
|
+
# Point.delimiter = ","
|
|
48
|
+
#
|
|
49
|
+
# @param columns [Array<Symbol>]
|
|
50
|
+
# @return [Class<Struct>]
|
|
51
|
+
def self.csv(*columns)
|
|
52
|
+
klass = self[*columns]
|
|
53
|
+
klass.delimiter = ","
|
|
54
|
+
klass
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Convenience shortcut to create a model class and set
|
|
58
|
+
# {ClassMethods#delimiter} to +"|"+.
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# # This...
|
|
62
|
+
# Point = DumbDelimited.psv(:x, :y, :z)
|
|
63
|
+
#
|
|
64
|
+
# # ...is equivalent to:
|
|
65
|
+
# Point = DumbDelimited[:x, :y, :z]
|
|
66
|
+
# Point.delimiter = "|"
|
|
67
|
+
#
|
|
68
|
+
# @param columns [Array<Symbol>]
|
|
69
|
+
# @return [Class<Struct>]
|
|
70
|
+
def self.psv(*columns)
|
|
71
|
+
klass = self[*columns]
|
|
72
|
+
klass.delimiter = "|"
|
|
73
|
+
klass
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Convenience shortcut to create a model class and set
|
|
77
|
+
# {ClassMethods#delimiter} to <code>"\t"</code>.
|
|
78
|
+
#
|
|
79
|
+
# @example
|
|
80
|
+
# # This...
|
|
81
|
+
# Point = DumbDelimited.tsv(:x, :y, :z)
|
|
82
|
+
#
|
|
83
|
+
# # ...is equivalent to:
|
|
84
|
+
# Point = DumbDelimited[:x, :y, :z]
|
|
85
|
+
# Point.delimiter = "\t"
|
|
86
|
+
#
|
|
87
|
+
# @param columns [Array<Symbol>]
|
|
88
|
+
# @return [Class<Struct>]
|
|
89
|
+
def self.tsv(*columns)
|
|
90
|
+
klass = self[*columns]
|
|
91
|
+
klass.delimiter = "\t"
|
|
92
|
+
klass
|
|
93
|
+
end
|
|
94
|
+
|
|
33
95
|
end
|
|
34
96
|
|
|
35
97
|
|
|
36
98
|
module DumbDelimited::ClassMethods
|
|
37
99
|
|
|
38
|
-
# Returns the
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# about available options, see Ruby's
|
|
42
|
-
# {http://ruby-doc.org/stdlib/libdoc/csv/rdoc/CSV.html#method-c-new
|
|
43
|
-
# CSV module}.
|
|
100
|
+
# Returns the CSV options Hash. The Hash is not +dup+'d and can be
|
|
101
|
+
# modified directly. Any modifications will be applied to all future
|
|
102
|
+
# IO operations for the model class.
|
|
44
103
|
#
|
|
45
|
-
#
|
|
104
|
+
# For detailed information about available options, see Ruby's
|
|
105
|
+
# {https://docs.ruby-lang.org/en/master/CSV.html#method-c-new CSV
|
|
106
|
+
# class}.
|
|
107
|
+
#
|
|
108
|
+
# @return [Hash{Symbol => Object}]
|
|
46
109
|
def options
|
|
47
|
-
@options ||=
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
110
|
+
@options ||= if superclass == Struct
|
|
111
|
+
CSV::DEFAULT_OPTIONS.merge(
|
|
112
|
+
skip_blanks: true,
|
|
113
|
+
liberal_parsing: true,
|
|
114
|
+
)
|
|
115
|
+
else
|
|
116
|
+
superclass.options.dup
|
|
117
|
+
end
|
|
52
118
|
end
|
|
53
119
|
|
|
54
|
-
# Sets the
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# detailed information about available options, see Ruby's
|
|
58
|
-
# {http://ruby-doc.org/stdlib/libdoc/csv/rdoc/CSV.html#method-c-new
|
|
59
|
-
# CSV module}.
|
|
120
|
+
# Sets the CSV options Hash. The entire Hash is replaced, and the new
|
|
121
|
+
# values will be applied to all future IO operations for the model
|
|
122
|
+
# class. To set options individually, see {options}.
|
|
60
123
|
#
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
124
|
+
# For detailed information about available options, see Ruby's
|
|
125
|
+
# {https://docs.ruby-lang.org/en/master/CSV.html#method-c-new CSV
|
|
126
|
+
# class}.
|
|
127
|
+
#
|
|
128
|
+
# @param opts [Hash{Symbol => Object}]
|
|
129
|
+
# @return [opts]
|
|
130
|
+
def options=(opts)
|
|
131
|
+
@options = opts
|
|
64
132
|
end
|
|
65
133
|
|
|
66
134
|
# Returns the column delimiter used in IO operations. Defaults to a
|
|
67
135
|
# comma (<code>","</code>).
|
|
68
136
|
#
|
|
137
|
+
# Equivalent to <code>options[:col_sep]</code>.
|
|
138
|
+
#
|
|
69
139
|
# @return [String]
|
|
70
140
|
def delimiter
|
|
71
141
|
self.options[:col_sep]
|
|
@@ -76,6 +146,8 @@ module DumbDelimited::ClassMethods
|
|
|
76
146
|
# delimiter can be safely chosen, and all IO operations will quote
|
|
77
147
|
# field values as necessary.
|
|
78
148
|
#
|
|
149
|
+
# Equivalent to <code>options[:col_sep] = delim</code>.
|
|
150
|
+
#
|
|
79
151
|
# @example
|
|
80
152
|
# Point = DumbDelimited[:x, :y, :z]
|
|
81
153
|
# p = Point.new(1, 2, 3)
|
|
@@ -83,9 +155,10 @@ module DumbDelimited::ClassMethods
|
|
|
83
155
|
# Point.delimiter = "|"
|
|
84
156
|
# p.to_s # == "1|2|3"
|
|
85
157
|
#
|
|
86
|
-
# @param
|
|
87
|
-
|
|
88
|
-
|
|
158
|
+
# @param delim [String]
|
|
159
|
+
# @return [delim]
|
|
160
|
+
def delimiter=(delim)
|
|
161
|
+
self.options[:col_sep] = delim
|
|
89
162
|
end
|
|
90
163
|
|
|
91
164
|
# Parses a single delimited line into a model object.
|
|
@@ -95,34 +168,53 @@ module DumbDelimited::ClassMethods
|
|
|
95
168
|
# Point.parse_line("1,2,3") # == Point.new(1, 2, 3)
|
|
96
169
|
#
|
|
97
170
|
# @param line [String]
|
|
98
|
-
# @return [
|
|
171
|
+
# @return [Struct]
|
|
99
172
|
def parse_line(line)
|
|
100
|
-
|
|
173
|
+
parse_each(line).first
|
|
101
174
|
end
|
|
102
175
|
|
|
103
|
-
# Parses a string into an array of model objects.
|
|
176
|
+
# Parses a string or IO object into an array of model objects.
|
|
104
177
|
#
|
|
105
178
|
# @example
|
|
106
179
|
# Point = DumbDelimited[:x, :y, :z]
|
|
107
|
-
# Point.
|
|
180
|
+
# Point.parse("1,2,3\n4,5,6\n7,8,9\n")
|
|
108
181
|
# # == [
|
|
109
182
|
# # Point.new(1, 2, 3),
|
|
110
183
|
# # Point.new(4, 5, 6),
|
|
111
184
|
# # Point.new(7, 8, 9)
|
|
112
185
|
# # ]
|
|
113
186
|
#
|
|
114
|
-
# @param
|
|
115
|
-
# @return [Array<
|
|
116
|
-
def
|
|
117
|
-
|
|
118
|
-
# memory allocation and deallocation
|
|
119
|
-
CSV.new(text, self.options).each.map{|row| self.new(*row) }
|
|
187
|
+
# @param data [String, IO]
|
|
188
|
+
# @return [Array<Struct>]
|
|
189
|
+
def parse(data)
|
|
190
|
+
parse_each(data).to_a
|
|
120
191
|
end
|
|
121
192
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
#
|
|
125
|
-
#
|
|
193
|
+
alias_method :parse_text, :parse
|
|
194
|
+
|
|
195
|
+
# Parses a string or IO object one line at a time, yielding a model
|
|
196
|
+
# object for each line.
|
|
197
|
+
#
|
|
198
|
+
# If no block is given, an Enumerator is returned instead.
|
|
199
|
+
#
|
|
200
|
+
# @overload parse_each(data, &block)
|
|
201
|
+
# @param data [String, IO]
|
|
202
|
+
# @yieldparam model [Struct]
|
|
203
|
+
# @return [void]
|
|
204
|
+
#
|
|
205
|
+
# @overload parse_each(data)
|
|
206
|
+
# @param data [String, IO]
|
|
207
|
+
# @return [Enumerator<Struct>]
|
|
208
|
+
def parse_each(data, &block)
|
|
209
|
+
return to_enum(__method__, data) unless block_given?
|
|
210
|
+
|
|
211
|
+
csv_each(CSV.new(data, **self.options), &block)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Parses a file into an array of model objects. This will load the
|
|
215
|
+
# entire contents of the file into memory, and may not be suitable for
|
|
216
|
+
# large files. To iterate over file contents without loading it all
|
|
217
|
+
# into memory at once, use {read_each}.
|
|
126
218
|
#
|
|
127
219
|
# @example
|
|
128
220
|
# # CONTENTS OF FILE "points.csv":
|
|
@@ -131,7 +223,7 @@ module DumbDelimited::ClassMethods
|
|
|
131
223
|
# # 7,8,9
|
|
132
224
|
#
|
|
133
225
|
# Point = DumbDelimited[:x, :y, :z]
|
|
134
|
-
# Point.
|
|
226
|
+
# Point.read("points.csv")
|
|
135
227
|
# # == [
|
|
136
228
|
# # Point.new(1, 2, 3),
|
|
137
229
|
# # Point.new(4, 5, 6),
|
|
@@ -139,27 +231,77 @@ module DumbDelimited::ClassMethods
|
|
|
139
231
|
# # ]
|
|
140
232
|
#
|
|
141
233
|
# @param path [String, Pathname]
|
|
142
|
-
# @return [Array<
|
|
143
|
-
def
|
|
144
|
-
|
|
234
|
+
# @return [Array<Struct>]
|
|
235
|
+
def read(path)
|
|
236
|
+
read_each(path).to_a
|
|
145
237
|
end
|
|
146
238
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
# methods, such as +Enumerator#to_a+, will cause the entire contents
|
|
153
|
-
# of the file to be loaded into memory regardless.
|
|
239
|
+
alias_method :parse_file, :read
|
|
240
|
+
|
|
241
|
+
# Parses a file one line at a time, yielding a model object for each
|
|
242
|
+
# line. This avoids loading the entire contents of the file into
|
|
243
|
+
# memory at once.
|
|
154
244
|
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
|
|
245
|
+
# If no block is given, an Enumerator is returned instead. Note that
|
|
246
|
+
# some Enumerator methods, such as +Enumerator#to_a+, can cause the
|
|
247
|
+
# entire contents of the file to be loaded into memory.
|
|
248
|
+
#
|
|
249
|
+
# @overload read_each(path, &block)
|
|
250
|
+
# @param path [String, Pathname]
|
|
251
|
+
# @yieldparam model [Struct]
|
|
252
|
+
# @return [void]
|
|
253
|
+
#
|
|
254
|
+
# @overload read_each(path)
|
|
255
|
+
# @param path [String, Pathname]
|
|
256
|
+
# @return [Enumerator<Struct>]
|
|
257
|
+
def read_each(path, &block)
|
|
159
258
|
return to_enum(__method__, path) unless block_given?
|
|
160
259
|
|
|
161
|
-
CSV.
|
|
162
|
-
|
|
260
|
+
CSV.open(path, **self.options) do |csv|
|
|
261
|
+
csv_each(csv, &block)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Writes a collection of model objects to a file in delimited format.
|
|
266
|
+
# The previous contents of the file are overwritten, unless +append+
|
|
267
|
+
# is set to true.
|
|
268
|
+
#
|
|
269
|
+
# Column headers are written to the file if +:write_headers+ in
|
|
270
|
+
# {options} is set to true *and* either +append+ is false or the file
|
|
271
|
+
# is empty / non-existent. The column headers will be derived from
|
|
272
|
+
# either the value of +:headers+ in {options} if it is an Array, or
|
|
273
|
+
# otherwise from the columns defined by the model.
|
|
274
|
+
#
|
|
275
|
+
# @param path [String, Pathname]
|
|
276
|
+
# @param models [Enumerable<Struct>]
|
|
277
|
+
# @param append [Boolean]
|
|
278
|
+
# @return [void]
|
|
279
|
+
def write(path, models, append: false)
|
|
280
|
+
mode = append ? "a" : "w"
|
|
281
|
+
write_headers = options[:write_headers] && !(append && File.exist?(path) && File.size(path) > 0)
|
|
282
|
+
headers = (!options[:headers].is_a?(Array) && write_headers) ? members : options[:headers]
|
|
283
|
+
|
|
284
|
+
CSV.open(path, mode, **options, write_headers: write_headers, headers: headers) do |csv|
|
|
285
|
+
models.each{|model| csv << model }
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Appends a collection of model objects to a file in delimited format.
|
|
290
|
+
# Convenience shortcut for {write} with +append: true+.
|
|
291
|
+
#
|
|
292
|
+
# @param path [String, Pathname]
|
|
293
|
+
# @param models [Enumerable<Struct>]
|
|
294
|
+
# @return [void]
|
|
295
|
+
def append(path, models)
|
|
296
|
+
write(path, models, append: true)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
private
|
|
300
|
+
|
|
301
|
+
def csv_each(csv, &block)
|
|
302
|
+
csv.each do |row|
|
|
303
|
+
row = row.fields if row.is_a?(CSV::Row)
|
|
304
|
+
block.call(self.new(*row))
|
|
163
305
|
end
|
|
164
306
|
end
|
|
165
307
|
|
|
@@ -169,33 +311,19 @@ end
|
|
|
169
311
|
module DumbDelimited::InstanceMethods
|
|
170
312
|
|
|
171
313
|
# Serializes a model object to a delimited string, using the delimiter
|
|
172
|
-
# specified by {ClassMethods
|
|
314
|
+
# specified by {ClassMethods#delimiter}. By default, the string will
|
|
315
|
+
# not end with a line terminator. To end the string with a line
|
|
316
|
+
# terminator designated by +:row_sep+ in {ClassMethods#options}, set
|
|
317
|
+
# +eol+ to true.
|
|
173
318
|
#
|
|
319
|
+
# @param eol [Boolean]
|
|
174
320
|
# @return [String]
|
|
175
|
-
def to_s
|
|
176
|
-
|
|
177
|
-
end
|
|
321
|
+
def to_s(eol = false)
|
|
322
|
+
row_sep = eol ? self.class.options[:row_sep] : -""
|
|
178
323
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# This method is convenient when working with single model objects,
|
|
183
|
-
# for example, appending a single entry to a log file. However, it is
|
|
184
|
-
# not recommended for use with an array of model objects, due to the
|
|
185
|
-
# overhead of opening and closing the file for each append.
|
|
186
|
-
#
|
|
187
|
-
# @example
|
|
188
|
-
# Point = DumbDelimited[:x, :y, :z]
|
|
189
|
-
# Point.new(1, 2, 3).append_to_file("out.txt") # == Point.new(1, 2, 3)
|
|
190
|
-
# File.read("out.txt") # == "1,2,3\n"
|
|
191
|
-
# Point.new(4, 5, 6).append_to_file("out.txt") # == Point.new(4, 5, 6)
|
|
192
|
-
# File.read("out.txt") # == "1,2,3\n4,5,6\n"
|
|
193
|
-
#
|
|
194
|
-
# @param file [String, Pathname]
|
|
195
|
-
# @return [self]
|
|
196
|
-
def append_to_file(file)
|
|
197
|
-
CSV.generate_line(self, self.class.options).append_to_file(file)
|
|
198
|
-
self
|
|
324
|
+
CSV.generate(**self.class.options, row_sep: row_sep, write_headers: false) do |csv|
|
|
325
|
+
csv << self
|
|
326
|
+
end
|
|
199
327
|
end
|
|
200
328
|
|
|
201
329
|
end
|
metadata
CHANGED
|
@@ -1,94 +1,34 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dumb_delimited
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonathan Hefner
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: csv
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
|
-
- - "
|
|
16
|
+
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
18
|
+
version: '0'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
|
-
- - "
|
|
23
|
+
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: bundler
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1.15'
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1.15'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rake
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '10.0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '10.0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: minitest
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - "~>"
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '5.0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '5.0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: yard
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - "~>"
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0.9'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - "~>"
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0.9'
|
|
83
|
-
description:
|
|
25
|
+
version: '0'
|
|
84
26
|
email:
|
|
85
|
-
- jonathan
|
|
27
|
+
- jonathan@hefner.pro
|
|
86
28
|
executables: []
|
|
87
29
|
extensions: []
|
|
88
30
|
extra_rdoc_files: []
|
|
89
31
|
files:
|
|
90
|
-
- ".gitignore"
|
|
91
|
-
- ".travis.yml"
|
|
92
32
|
- CHANGELOG.md
|
|
93
33
|
- Gemfile
|
|
94
34
|
- LICENSE.txt
|
|
@@ -100,8 +40,9 @@ files:
|
|
|
100
40
|
homepage: https://github.com/jonathanhefner/dumb_delimited
|
|
101
41
|
licenses:
|
|
102
42
|
- MIT
|
|
103
|
-
metadata:
|
|
104
|
-
|
|
43
|
+
metadata:
|
|
44
|
+
source_code_uri: https://github.com/jonathanhefner/dumb_delimited
|
|
45
|
+
changelog_uri: https://github.com/jonathanhefner/dumb_delimited/blob/master/CHANGELOG.md
|
|
105
46
|
rdoc_options: []
|
|
106
47
|
require_paths:
|
|
107
48
|
- lib
|
|
@@ -109,16 +50,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
109
50
|
requirements:
|
|
110
51
|
- - ">="
|
|
111
52
|
- !ruby/object:Gem::Version
|
|
112
|
-
version: '
|
|
53
|
+
version: '3.4'
|
|
113
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
55
|
requirements:
|
|
115
56
|
- - ">="
|
|
116
57
|
- !ruby/object:Gem::Version
|
|
117
58
|
version: '0'
|
|
118
59
|
requirements: []
|
|
119
|
-
|
|
120
|
-
rubygems_version: 2.7.6
|
|
121
|
-
signing_key:
|
|
60
|
+
rubygems_version: 4.0.10
|
|
122
61
|
specification_version: 4
|
|
123
|
-
summary:
|
|
62
|
+
summary: Unsophisticated delimited flat file IO
|
|
124
63
|
test_files: []
|
data/.gitignore
DELETED