kvcsv 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rspec_status +34 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE +21 -0
- data/README.md +124 -0
- data/Rakefile +10 -0
- data/lib/kvcsv/settings.rb +106 -0
- data/lib/kvcsv/version.rb +5 -0
- data/lib/kvcsv.rb +8 -0
- data/sig/kvcsv.rbs +4 -0
- data/spec/kvcsv/settings_spec.rb +283 -0
- data/spec/spec_helper.rb +24 -0
- metadata +99 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c196d089eae70c2cb577159c522bb1be9c6a8fb4e3f58a99665b4ebae483c379
|
|
4
|
+
data.tar.gz: f3f178417a8cddfb3fd40afb6080a454f45acb2ff5fae6dec6927c76db82ead0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 791e43d7cb1308d7fd629eb531ac8cf918077cde255c30da8ba0efa65ab7050f9621a797b40ac18df0adaf8a8c33832c8c6fa71fec0c67576cf73472184c5fe0
|
|
7
|
+
data.tar.gz: 48dde60b19009ed1d7e1b83cdf5caa6f695e5545085d505495bc5d636264e818b72d225c0a19defddfca3b632f0670b26cfb19c262a8989b09dd64adb9b814ca
|
data/.rspec
ADDED
data/.rspec_status
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
example_id | status | run_time |
|
|
2
|
+
-------------------------------------- | ------ | --------------- |
|
|
3
|
+
./spec/kvcsv/settings_spec.rb[1:1:1] | passed | 0.00059 seconds |
|
|
4
|
+
./spec/kvcsv/settings_spec.rb[1:1:2] | passed | 0.00214 seconds |
|
|
5
|
+
./spec/kvcsv/settings_spec.rb[1:1:3] | passed | 0.0006 seconds |
|
|
6
|
+
./spec/kvcsv/settings_spec.rb[1:1:4] | passed | 0.00056 seconds |
|
|
7
|
+
./spec/kvcsv/settings_spec.rb[1:1:5] | passed | 0.00057 seconds |
|
|
8
|
+
./spec/kvcsv/settings_spec.rb[1:2:1] | passed | 0.00066 seconds |
|
|
9
|
+
./spec/kvcsv/settings_spec.rb[1:2:2] | passed | 0.00058 seconds |
|
|
10
|
+
./spec/kvcsv/settings_spec.rb[1:3:1] | passed | 0.00091 seconds |
|
|
11
|
+
./spec/kvcsv/settings_spec.rb[1:3:2] | passed | 0.00056 seconds |
|
|
12
|
+
./spec/kvcsv/settings_spec.rb[1:3:3] | passed | 0.00233 seconds |
|
|
13
|
+
./spec/kvcsv/settings_spec.rb[1:4:1] | passed | 0.00191 seconds |
|
|
14
|
+
./spec/kvcsv/settings_spec.rb[1:5:1] | passed | 0.00068 seconds |
|
|
15
|
+
./spec/kvcsv/settings_spec.rb[1:6:1:1] | passed | 0.00053 seconds |
|
|
16
|
+
./spec/kvcsv/settings_spec.rb[1:6:1:2] | passed | 0.00098 seconds |
|
|
17
|
+
./spec/kvcsv/settings_spec.rb[1:6:1:3] | passed | 0.00055 seconds |
|
|
18
|
+
./spec/kvcsv/settings_spec.rb[1:6:1:4] | passed | 0.00058 seconds |
|
|
19
|
+
./spec/kvcsv/settings_spec.rb[1:6:1:5] | passed | 0.00054 seconds |
|
|
20
|
+
./spec/kvcsv/settings_spec.rb[1:6:1:6] | passed | 0.00056 seconds |
|
|
21
|
+
./spec/kvcsv/settings_spec.rb[1:6:2:1] | passed | 0.00091 seconds |
|
|
22
|
+
./spec/kvcsv/settings_spec.rb[1:6:2:2] | passed | 0.00055 seconds |
|
|
23
|
+
./spec/kvcsv/settings_spec.rb[1:6:2:3] | passed | 0.00056 seconds |
|
|
24
|
+
./spec/kvcsv/settings_spec.rb[1:6:2:4] | passed | 0.00055 seconds |
|
|
25
|
+
./spec/kvcsv/settings_spec.rb[1:6:2:5] | passed | 0.00054 seconds |
|
|
26
|
+
./spec/kvcsv/settings_spec.rb[1:6:2:6] | passed | 0.00054 seconds |
|
|
27
|
+
./spec/kvcsv/settings_spec.rb[1:6:3:1] | passed | 0.00055 seconds |
|
|
28
|
+
./spec/kvcsv/settings_spec.rb[1:6:3:2] | passed | 0.00145 seconds |
|
|
29
|
+
./spec/kvcsv/settings_spec.rb[1:6:3:3] | passed | 0.00056 seconds |
|
|
30
|
+
./spec/kvcsv/settings_spec.rb[1:6:3:4] | passed | 0.00057 seconds |
|
|
31
|
+
./spec/kvcsv/settings_spec.rb[1:6:3:5] | passed | 0.00058 seconds |
|
|
32
|
+
./spec/kvcsv/settings_spec.rb[1:6:3:6] | passed | 0.00061 seconds |
|
|
33
|
+
./spec/kvcsv/settings_spec.rb[1:6:4] | passed | 0.00063 seconds |
|
|
34
|
+
./spec/kvcsv/settings_spec.rb[1:7:1] | passed | 0.00105 seconds |
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2025-09-24
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial release of KVCSV gem
|
|
8
|
+
- Load settings from one or more CSV files
|
|
9
|
+
- Automatic type conversion for boolean values (true/false)
|
|
10
|
+
- Automatic type conversion for nil values
|
|
11
|
+
- Symbol-based key access
|
|
12
|
+
- Support for merging multiple configuration files
|
|
13
|
+
- Enumerable interface with map, select, and fetch methods
|
|
14
|
+
- YARD documentation for all public methods
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ryan Duryea
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, 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,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Kvcsv
|
|
2
|
+
|
|
3
|
+
A lightweight Ruby gem for managing application settings from CSV files. Kvcsv provides a simple, read-only interface for loading configuration from one or more CSV files with automatic type conversion for boolean and nil values.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Load settings from one or more CSV files
|
|
8
|
+
- Automatic type conversion (true/false/nil)
|
|
9
|
+
- Simple key-based access with symbol keys
|
|
10
|
+
- Support for default values via `fetch`
|
|
11
|
+
- Enumerable interface (map, select, etc.)
|
|
12
|
+
- Later files override earlier ones for easy environment-specific configuration
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bundle add kvcsv
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
gem install kvcsv
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### CSV File Format
|
|
31
|
+
|
|
32
|
+
Create CSV files with two columns: `key` and `value`:
|
|
33
|
+
|
|
34
|
+
```csv
|
|
35
|
+
key,value
|
|
36
|
+
database_host,localhost
|
|
37
|
+
database_port,5432
|
|
38
|
+
debug,true
|
|
39
|
+
cache_enabled,false
|
|
40
|
+
api_key,sk-1234567890
|
|
41
|
+
timeout,30
|
|
42
|
+
environment,production
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Basic Usage
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
require 'kvcsv'
|
|
49
|
+
|
|
50
|
+
# Load from a single file
|
|
51
|
+
settings = KVCSV::Settings.new("config/app.csv")
|
|
52
|
+
|
|
53
|
+
# Access settings
|
|
54
|
+
database_host = settings[:database_host] # => "localhost"
|
|
55
|
+
debug_mode = settings[:debug] # => true (automatically converted)
|
|
56
|
+
missing_key = settings[:missing_key] # => nil
|
|
57
|
+
|
|
58
|
+
# Use fetch with defaults
|
|
59
|
+
port = settings.fetch(:port, 3000) # => 3000 (default value)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Loading Multiple Files
|
|
63
|
+
|
|
64
|
+
Later files override values from earlier files:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# config/defaults.csv has debug=false
|
|
68
|
+
# config/production.csv has debug=true
|
|
69
|
+
settings = KVCSV::Settings.new(
|
|
70
|
+
"config/defaults.csv",
|
|
71
|
+
"config/production.csv"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
settings[:debug] # => true (from production.csv)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Type Conversion
|
|
78
|
+
|
|
79
|
+
The following values are automatically converted:
|
|
80
|
+
|
|
81
|
+
- **True values:** `t`, `1`, `true`, `yes`, `y` (case-insensitive)
|
|
82
|
+
- **False values:** `f`, `0`, `false`, `no`, `n` (case-insensitive)
|
|
83
|
+
- **Nil values:** `nil`, `null`, `na`, `n/a` (case-insensitive), or empty values
|
|
84
|
+
|
|
85
|
+
```csv
|
|
86
|
+
key,value
|
|
87
|
+
feature_enabled,true
|
|
88
|
+
verbose,yes
|
|
89
|
+
disabled,0
|
|
90
|
+
optional_field,nil
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
settings = KVCSV::Settings.new("config.csv")
|
|
95
|
+
settings[:feature_enabled] # => true
|
|
96
|
+
settings[:verbose] # => true
|
|
97
|
+
settings[:disabled] # => false
|
|
98
|
+
settings[:optional_field] # => nil
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Enumerable Interface
|
|
102
|
+
|
|
103
|
+
Settings objects support enumerable methods:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
# Map over settings
|
|
107
|
+
urls = settings.map { |key, value| value if key.to_s.end_with?("_url") }.compact
|
|
108
|
+
|
|
109
|
+
# Select specific settings
|
|
110
|
+
database_settings = settings.select { |key, _| key.to_s.start_with?("database_") }
|
|
111
|
+
|
|
112
|
+
# Check for existence
|
|
113
|
+
settings.fetch(:required_key) # Raises KeyError if not found
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
119
|
+
|
|
120
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
121
|
+
|
|
122
|
+
## Contributing
|
|
123
|
+
|
|
124
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/aguynamedryan/kvcsv.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
require "forwardable"
|
|
5
|
+
|
|
6
|
+
module KVCSV
|
|
7
|
+
# Settings class for managing application settings from CSV files
|
|
8
|
+
#
|
|
9
|
+
# This class loads settings from one or more CSV files and provides
|
|
10
|
+
# read-only access to the merged configuration. CSV files should have
|
|
11
|
+
# two columns: "key" and "value".
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage
|
|
14
|
+
# settings = KVCSV::Settings.new("config/app.csv", "config/local.csv")
|
|
15
|
+
# database_host = settings[:database_host]
|
|
16
|
+
# debug_mode = settings[:debug]
|
|
17
|
+
#
|
|
18
|
+
# @example Using fetch with default value
|
|
19
|
+
# port = settings.fetch(:port, 3000)
|
|
20
|
+
#
|
|
21
|
+
# @example CSV file format
|
|
22
|
+
# key,value
|
|
23
|
+
# database_host,localhost
|
|
24
|
+
# database_port,5432
|
|
25
|
+
# debug,true
|
|
26
|
+
# cache_enabled,false
|
|
27
|
+
#
|
|
28
|
+
class Settings
|
|
29
|
+
extend Forwardable
|
|
30
|
+
|
|
31
|
+
# @!method [](key)
|
|
32
|
+
# Access a setting value by key
|
|
33
|
+
# @param key [String, Symbol] The setting key to retrieve
|
|
34
|
+
# @return [String, TrueClass, FalseClass, nil] The setting value
|
|
35
|
+
#
|
|
36
|
+
# @!method fetch(key, default = nil)
|
|
37
|
+
# Access a setting value with optional default
|
|
38
|
+
# @param key [String, Symbol] The setting key to retrieve
|
|
39
|
+
# @param default [Object] Default value if key not found
|
|
40
|
+
# @return [Object] The setting value or default
|
|
41
|
+
#
|
|
42
|
+
# @!method map(&block)
|
|
43
|
+
# Iterate over settings with map
|
|
44
|
+
# @yield [key, value] Each key-value pair
|
|
45
|
+
# @return [Array] Results of the block
|
|
46
|
+
#
|
|
47
|
+
# @!method select(&block)
|
|
48
|
+
# Select settings matching criteria
|
|
49
|
+
# @yield [key, value] Each key-value pair
|
|
50
|
+
# @return [Hash] Filtered settings
|
|
51
|
+
def_delegators :@settings, :[], :fetch, :map, :select
|
|
52
|
+
|
|
53
|
+
# Values that are converted to true
|
|
54
|
+
TRUE_VALUES = %w[t 1 true yes y].freeze
|
|
55
|
+
|
|
56
|
+
# Values that are converted to false
|
|
57
|
+
FALSE_VALUES = %w[f 0 false no n].freeze
|
|
58
|
+
|
|
59
|
+
# Values that are converted to nil
|
|
60
|
+
NIL_VALUES = %w[nil null na n/a].freeze
|
|
61
|
+
|
|
62
|
+
# Initialize a new Settings object with one or more CSV files
|
|
63
|
+
#
|
|
64
|
+
# @param file_paths [Array<String>] Variable number of paths to CSV files
|
|
65
|
+
# @note Non-existent files are silently ignored
|
|
66
|
+
# @note Later files override values from earlier files
|
|
67
|
+
#
|
|
68
|
+
# @example Load from multiple files
|
|
69
|
+
# settings = KVCSV::Settings.new(
|
|
70
|
+
# "config/defaults.csv",
|
|
71
|
+
# "config/environment.csv",
|
|
72
|
+
# "config/local.csv"
|
|
73
|
+
# )
|
|
74
|
+
def initialize(*file_paths)
|
|
75
|
+
@settings = {}
|
|
76
|
+
file_paths.compact.each do |file_path|
|
|
77
|
+
next unless File.exist?(file_path)
|
|
78
|
+
|
|
79
|
+
load_file(file_path)
|
|
80
|
+
end
|
|
81
|
+
symbolize_keys!
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def symbolize_keys!
|
|
87
|
+
@settings = @settings.transform_keys(&:to_sym)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def load_file(file_path)
|
|
91
|
+
CSV.foreach(file_path, headers: true) do |row|
|
|
92
|
+
key = row["key"]
|
|
93
|
+
value = convert_value(row["value"])
|
|
94
|
+
@settings[key] = value
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def convert_value(value)
|
|
99
|
+
return nil if value.nil? || NIL_VALUES.include?(value.downcase)
|
|
100
|
+
return true if TRUE_VALUES.include?(value.downcase)
|
|
101
|
+
return false if FALSE_VALUES.include?(value.downcase)
|
|
102
|
+
|
|
103
|
+
value
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/kvcsv.rb
ADDED
data/sig/kvcsv.rbs
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe KVCSV::Settings do
|
|
6
|
+
let(:temp_dir) { Dir.mktmpdir }
|
|
7
|
+
|
|
8
|
+
after do
|
|
9
|
+
FileUtils.rm_rf(temp_dir)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create_csv_file(filename, content)
|
|
13
|
+
path = File.join(temp_dir, filename)
|
|
14
|
+
File.write(path, content)
|
|
15
|
+
path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "#initialize" do
|
|
19
|
+
it "loads settings from a single CSV file" do
|
|
20
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
21
|
+
key,value
|
|
22
|
+
database_host,localhost
|
|
23
|
+
database_port,5432
|
|
24
|
+
CSV
|
|
25
|
+
|
|
26
|
+
settings = described_class.new(file)
|
|
27
|
+
expect(settings[:database_host]).to eq("localhost")
|
|
28
|
+
expect(settings[:database_port]).to eq("5432")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "loads settings from multiple CSV files" do
|
|
32
|
+
file1 = create_csv_file("defaults.csv", <<~CSV)
|
|
33
|
+
key,value
|
|
34
|
+
host,default.com
|
|
35
|
+
port,3000
|
|
36
|
+
debug,false
|
|
37
|
+
CSV
|
|
38
|
+
|
|
39
|
+
file2 = create_csv_file("overrides.csv", <<~CSV)
|
|
40
|
+
key,value
|
|
41
|
+
host,override.com
|
|
42
|
+
timeout,30
|
|
43
|
+
CSV
|
|
44
|
+
|
|
45
|
+
settings = described_class.new(file1, file2)
|
|
46
|
+
expect(settings[:host]).to eq("override.com")
|
|
47
|
+
expect(settings[:port]).to eq("3000")
|
|
48
|
+
expect(settings[:debug]).to be false
|
|
49
|
+
expect(settings[:timeout]).to eq("30")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "ignores non-existent files" do
|
|
53
|
+
valid_file = create_csv_file("valid.csv", <<~CSV)
|
|
54
|
+
key,value
|
|
55
|
+
setting,value
|
|
56
|
+
CSV
|
|
57
|
+
|
|
58
|
+
settings = described_class.new("/nonexistent/file.csv", valid_file, "/another/missing.csv")
|
|
59
|
+
expect(settings[:setting]).to eq("value")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "ignores nil file paths" do
|
|
63
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
64
|
+
key,value
|
|
65
|
+
foo,bar
|
|
66
|
+
CSV
|
|
67
|
+
|
|
68
|
+
settings = described_class.new(nil, file, nil)
|
|
69
|
+
expect(settings[:foo]).to eq("bar")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "symbolizes keys" do
|
|
73
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
74
|
+
key,value
|
|
75
|
+
string_key,value
|
|
76
|
+
CSV
|
|
77
|
+
|
|
78
|
+
settings = described_class.new(file)
|
|
79
|
+
expect(settings[:string_key]).to eq("value")
|
|
80
|
+
expect(settings["string_key"]).to be_nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "#[]" do
|
|
85
|
+
let(:file) do
|
|
86
|
+
create_csv_file("settings.csv", <<~CSV)
|
|
87
|
+
key,value
|
|
88
|
+
existing_key,value
|
|
89
|
+
CSV
|
|
90
|
+
end
|
|
91
|
+
let(:settings) { described_class.new(file) }
|
|
92
|
+
|
|
93
|
+
it "returns value for existing key" do
|
|
94
|
+
expect(settings[:existing_key]).to eq("value")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "returns nil for non-existing key" do
|
|
98
|
+
expect(settings[:missing_key]).to be_nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "#fetch" do
|
|
103
|
+
let(:file) do
|
|
104
|
+
create_csv_file("settings.csv", <<~CSV)
|
|
105
|
+
key,value
|
|
106
|
+
existing_key,value
|
|
107
|
+
CSV
|
|
108
|
+
end
|
|
109
|
+
let(:settings) { described_class.new(file) }
|
|
110
|
+
|
|
111
|
+
it "returns value for existing key" do
|
|
112
|
+
expect(settings.fetch(:existing_key)).to eq("value")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "returns default value for non-existing key" do
|
|
116
|
+
expect(settings.fetch(:missing_key, "default")).to eq("default")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "raises error for non-existing key without default" do
|
|
120
|
+
expect { settings.fetch(:missing_key) }.to raise_error(KeyError)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe "#map" do
|
|
125
|
+
let(:file) do
|
|
126
|
+
create_csv_file("settings.csv", <<~CSV)
|
|
127
|
+
key,value
|
|
128
|
+
key1,value1
|
|
129
|
+
key2,value2
|
|
130
|
+
CSV
|
|
131
|
+
end
|
|
132
|
+
let(:settings) { described_class.new(file) }
|
|
133
|
+
|
|
134
|
+
it "maps over key-value pairs" do
|
|
135
|
+
result = settings.map { |k, v| "#{k}=#{v}" }
|
|
136
|
+
expect(result).to contain_exactly("key1=value1", "key2=value2")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe "#select" do
|
|
141
|
+
let(:file) do
|
|
142
|
+
create_csv_file("settings.csv", <<~CSV)
|
|
143
|
+
key,value
|
|
144
|
+
enabled,true
|
|
145
|
+
disabled,false
|
|
146
|
+
name,test
|
|
147
|
+
CSV
|
|
148
|
+
end
|
|
149
|
+
let(:settings) { described_class.new(file) }
|
|
150
|
+
|
|
151
|
+
it "selects matching key-value pairs" do
|
|
152
|
+
result = settings.select { |_k, v| v == true }
|
|
153
|
+
expect(result).to eq({ enabled: true })
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe "value conversion" do
|
|
158
|
+
context "true values" do
|
|
159
|
+
%w[t 1 true yes y].each do |true_value|
|
|
160
|
+
it "converts '#{true_value}' to true" do
|
|
161
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
162
|
+
key,value
|
|
163
|
+
bool_setting,#{true_value}
|
|
164
|
+
CSV
|
|
165
|
+
|
|
166
|
+
settings = described_class.new(file)
|
|
167
|
+
expect(settings[:bool_setting]).to be true
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "converts uppercase TRUE values" do
|
|
172
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
173
|
+
key,value
|
|
174
|
+
upper,TRUE
|
|
175
|
+
mixed,True
|
|
176
|
+
CSV
|
|
177
|
+
|
|
178
|
+
settings = described_class.new(file)
|
|
179
|
+
expect(settings[:upper]).to be true
|
|
180
|
+
expect(settings[:mixed]).to be true
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
context "false values" do
|
|
185
|
+
%w[f 0 false no n].each do |false_value|
|
|
186
|
+
it "converts '#{false_value}' to false" do
|
|
187
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
188
|
+
key,value
|
|
189
|
+
bool_setting,#{false_value}
|
|
190
|
+
CSV
|
|
191
|
+
|
|
192
|
+
settings = described_class.new(file)
|
|
193
|
+
expect(settings[:bool_setting]).to be false
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "converts uppercase FALSE values" do
|
|
198
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
199
|
+
key,value
|
|
200
|
+
upper,FALSE
|
|
201
|
+
mixed,False
|
|
202
|
+
CSV
|
|
203
|
+
|
|
204
|
+
settings = described_class.new(file)
|
|
205
|
+
expect(settings[:upper]).to be false
|
|
206
|
+
expect(settings[:mixed]).to be false
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
context "nil values" do
|
|
211
|
+
%w[nil null na n/a].each do |nil_value|
|
|
212
|
+
it "converts '#{nil_value}' to nil" do
|
|
213
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
214
|
+
key,value
|
|
215
|
+
nil_setting,#{nil_value}
|
|
216
|
+
CSV
|
|
217
|
+
|
|
218
|
+
settings = described_class.new(file)
|
|
219
|
+
expect(settings[:nil_setting]).to be_nil
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "converts uppercase NIL values" do
|
|
224
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
225
|
+
key,value
|
|
226
|
+
upper,NULL
|
|
227
|
+
mixed,Nil
|
|
228
|
+
CSV
|
|
229
|
+
|
|
230
|
+
settings = described_class.new(file)
|
|
231
|
+
expect(settings[:upper]).to be_nil
|
|
232
|
+
expect(settings[:mixed]).to be_nil
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "converts empty values to nil" do
|
|
236
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
237
|
+
key,value
|
|
238
|
+
empty,
|
|
239
|
+
CSV
|
|
240
|
+
|
|
241
|
+
settings = described_class.new(file)
|
|
242
|
+
expect(settings[:empty]).to be_nil
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
it "preserves string values that don't match special values" do
|
|
247
|
+
file = create_csv_file("settings.csv", <<~CSV)
|
|
248
|
+
key,value
|
|
249
|
+
string,regular string
|
|
250
|
+
number,42
|
|
251
|
+
truthy,truth
|
|
252
|
+
falsy,failure
|
|
253
|
+
CSV
|
|
254
|
+
|
|
255
|
+
settings = described_class.new(file)
|
|
256
|
+
expect(settings[:string]).to eq("regular string")
|
|
257
|
+
expect(settings[:number]).to eq("42")
|
|
258
|
+
expect(settings[:truthy]).to eq("truth")
|
|
259
|
+
expect(settings[:falsy]).to eq("failure")
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
describe "file merging" do
|
|
264
|
+
it "later files override earlier files" do
|
|
265
|
+
file1 = create_csv_file("first.csv", <<~CSV)
|
|
266
|
+
key,value
|
|
267
|
+
setting1,first
|
|
268
|
+
setting2,original
|
|
269
|
+
CSV
|
|
270
|
+
|
|
271
|
+
file2 = create_csv_file("second.csv", <<~CSV)
|
|
272
|
+
key,value
|
|
273
|
+
setting2,overridden
|
|
274
|
+
setting3,new
|
|
275
|
+
CSV
|
|
276
|
+
|
|
277
|
+
settings = described_class.new(file1, file2)
|
|
278
|
+
expect(settings[:setting1]).to eq("first")
|
|
279
|
+
expect(settings[:setting2]).to eq("overridden")
|
|
280
|
+
expect(settings[:setting3]).to eq("new")
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "kvcsv"
|
|
5
|
+
require "tempfile"
|
|
6
|
+
require "fileutils"
|
|
7
|
+
|
|
8
|
+
RSpec.configure do |config|
|
|
9
|
+
# Enable flags like --only-failures and --next-failure
|
|
10
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
|
11
|
+
|
|
12
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
|
13
|
+
config.disable_monkey_patching!
|
|
14
|
+
|
|
15
|
+
config.expect_with :rspec do |c|
|
|
16
|
+
c.syntax = :expect
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Run specs in random order to surface order dependencies
|
|
20
|
+
config.order = :random
|
|
21
|
+
|
|
22
|
+
# Seed global randomization in this process using the `--seed` CLI option
|
|
23
|
+
Kernel.srand config.seed
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kvcsv
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ryan Duryea
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: csv
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.0'
|
|
54
|
+
description: A lightweight Ruby gem for managing application settings from CSV files
|
|
55
|
+
with automatic type conversion and multi-file support.
|
|
56
|
+
email:
|
|
57
|
+
- aguynamedryan@gmail.com
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- ".rspec"
|
|
63
|
+
- ".rspec_status"
|
|
64
|
+
- CHANGELOG.md
|
|
65
|
+
- LICENSE
|
|
66
|
+
- README.md
|
|
67
|
+
- Rakefile
|
|
68
|
+
- lib/kvcsv.rb
|
|
69
|
+
- lib/kvcsv/settings.rb
|
|
70
|
+
- lib/kvcsv/version.rb
|
|
71
|
+
- sig/kvcsv.rbs
|
|
72
|
+
- spec/kvcsv/settings_spec.rb
|
|
73
|
+
- spec/spec_helper.rb
|
|
74
|
+
homepage: https://github.com/aguynamedryan/kvcsv
|
|
75
|
+
licenses:
|
|
76
|
+
- MIT
|
|
77
|
+
metadata:
|
|
78
|
+
allowed_push_host: https://rubygems.org
|
|
79
|
+
homepage_uri: https://github.com/aguynamedryan/kvcsv
|
|
80
|
+
source_code_uri: https://github.com/aguynamedryan/kvcsv
|
|
81
|
+
changelog_uri: https://github.com/aguynamedryan/kvcsv/blob/main/CHANGELOG.md
|
|
82
|
+
rdoc_options: []
|
|
83
|
+
require_paths:
|
|
84
|
+
- lib
|
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 3.2.0
|
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
requirements: []
|
|
96
|
+
rubygems_version: 3.6.7
|
|
97
|
+
specification_version: 4
|
|
98
|
+
summary: Key-value pairs from stackable CSV files
|
|
99
|
+
test_files: []
|