contours 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/LICENSE +21 -0
- data/README.md +67 -0
- data/Rakefile +13 -0
- data/lib/contours/blended_hash.rb +125 -0
- data/lib/contours/structured_string.rb +127 -0
- data/lib/contours/version.rb +5 -0
- data/lib/contours.rb +8 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4045b6e13180f9b89bda47ed3c389c467e804368f4d9bd48acd2b1a5ef0c0255
|
4
|
+
data.tar.gz: 8ceb930137c7479306379051a523c7baa49f9a7466e57d97e060f70f8b5b869d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4012d37792abdf2073efb3fffa0773424b8680721fabffd0552b6f3630774ca41041f5240bb27e2acff38b45bbee3c464a4ad821448efb9715aac7cdf1b7fdee
|
7
|
+
data.tar.gz: c214234c537c604499cc781e8e9e17d9a9504c191d19417a3142b2319aa7ffe2da9ea38245f867a061d2dc4b1e2bd6360997e081bee863b98f08a60c35157c29
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 SOFware LLC
|
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,67 @@
|
|
1
|
+
# Contours
|
2
|
+
|
3
|
+
Contours provids objects for building configuration.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
8
|
+
|
9
|
+
$ bundle add contours
|
10
|
+
|
11
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
12
|
+
|
13
|
+
$ gem install contours
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Build a configuration object by creating a class that inherits from `Contours::BlendedHash` and define the configuration attributes.
|
18
|
+
|
19
|
+
Specify a list of keys that should be blended into the configuration object by setting the `@blended_keys` variable on the class.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Configuration < Contours::BlendedHash
|
23
|
+
@blended_keys = %i[class data]
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
Create a new object using `init`
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
config = Configuration.init({class: "test", data: [1, 2, 3]})
|
31
|
+
```
|
32
|
+
|
33
|
+
The `config` object will be able to be merged with other configuration objects.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
config.merge(Configuration.init({class: "overwrite", data: [4, 5, 6]}))
|
37
|
+
|
38
|
+
config.to_h # => {class: "overwrite", data: [1, 2, 3, 4, 5, 6]}
|
39
|
+
```
|
40
|
+
|
41
|
+
You can specify a custom way to blend the values for the `@blended_keys`
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class Configuration < Contours::BlendedHash
|
45
|
+
@blended_keys = %i[class data]
|
46
|
+
|
47
|
+
blend(:class, with: StructuredString)
|
48
|
+
|
49
|
+
blend :data do |existing, new_value|
|
50
|
+
existing.sum(new_value.sum)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
config.merge(Configuration.init({class: "overwrite", data: [4, 5, 6]}))
|
55
|
+
|
56
|
+
config.to_h # => {class: "test overwrite", data: 21}
|
57
|
+
```
|
58
|
+
|
59
|
+
## Development
|
60
|
+
|
61
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
62
|
+
|
63
|
+
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).
|
64
|
+
|
65
|
+
## Contributing
|
66
|
+
|
67
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/SOFware/contours.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.warning = true
|
10
|
+
t.test_files = FileList["test/**/test_*.rb"]
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: :test
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module Contours
|
6
|
+
# BlendedHash is a utility class that allows you to merge two hashes.
|
7
|
+
# It may be used to specify how to combine values that are specified
|
8
|
+
# more than once. For example, if you have a BlendedHash like this:
|
9
|
+
#
|
10
|
+
# class CssConfig < BlendedHash
|
11
|
+
# @blended_keys = %i[class]
|
12
|
+
# def blend_class(original, extras)
|
13
|
+
# [original, extras].flatten.compact.uniq.join(" ")
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# CssConfig.new({class: "foo"})
|
18
|
+
#
|
19
|
+
# And you merge it with another hash like this:
|
20
|
+
#
|
21
|
+
# CssConfig.new({class: "foo"}).merge({class: "bar"})
|
22
|
+
#
|
23
|
+
# The result will be:
|
24
|
+
# {
|
25
|
+
# class: "foo bar"
|
26
|
+
# }
|
27
|
+
#
|
28
|
+
# See the source of this #{name} class for more details.
|
29
|
+
class BlendedHash < DelegateClass(Hash)
|
30
|
+
alias_method :to_hash, :__getobj__
|
31
|
+
|
32
|
+
# Ensure that the initial hash is a BlendedHash
|
33
|
+
def self.init(initial_hash)
|
34
|
+
if initial_hash.is_a?(BlendedHash)
|
35
|
+
initial_hash
|
36
|
+
else
|
37
|
+
new({}).merge(initial_hash)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Define a method that will be used to blend the value of a key.
|
42
|
+
# The method name will be "blend_#{key}".
|
43
|
+
# If a block is given, it will be used as the implementation of the method.
|
44
|
+
# Otherwise, the default implementation will be used where the value of the
|
45
|
+
# with argument is expected to receive 'init' with the original value and
|
46
|
+
# then 'merge' with the extras value.
|
47
|
+
def self.blend(key, with: nil, &block)
|
48
|
+
if block
|
49
|
+
define_method("blend_#{key}", &block)
|
50
|
+
else
|
51
|
+
define_method("blend_#{key}") do |original, extras|
|
52
|
+
with.init(original).merge(extras)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Recursively check for keys that are specified as blended and apply
|
58
|
+
# the blend method to them or execute the blend_#{key} method if it exists
|
59
|
+
# to set the new value.
|
60
|
+
def merge(overrides)
|
61
|
+
return self if overrides.nil? || overrides.empty?
|
62
|
+
self.class.new(overrides.each_with_object(to_hash.dup) do |(key, value), hash|
|
63
|
+
hash[key] = if blended_keys.include?(key)
|
64
|
+
if respond_to?("blend_#{key}")
|
65
|
+
send("blend_#{key}", hash[key], value)
|
66
|
+
else
|
67
|
+
blend(hash[key], value)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
value
|
71
|
+
end
|
72
|
+
hash
|
73
|
+
end)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Ensure that the return value of these methods is a BlendedHash
|
77
|
+
def [](...)
|
78
|
+
if super.is_a?(Hash)
|
79
|
+
self.class.init(super)
|
80
|
+
else
|
81
|
+
super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def fetch(...)
|
86
|
+
if super.is_a?(Hash)
|
87
|
+
self.class.init(super)
|
88
|
+
else
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def dig(...)
|
94
|
+
if super.is_a?(Hash)
|
95
|
+
self.class.init(super)
|
96
|
+
else
|
97
|
+
super
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# The keys that should be blended when merging two hashes.
|
102
|
+
# Specify this in subclasses to customize the behavior.
|
103
|
+
@blended_keys = []
|
104
|
+
class << self
|
105
|
+
attr_reader :blended_keys
|
106
|
+
end
|
107
|
+
def blended_keys = self.class.blended_keys
|
108
|
+
|
109
|
+
# Default implementation of the blend method. Override this in subclasses
|
110
|
+
# with a custom blend_#{key} method to customize the blending behavior for a
|
111
|
+
# specific key.
|
112
|
+
#
|
113
|
+
|
114
|
+
def blend(original, extra)
|
115
|
+
case original
|
116
|
+
when BlendedHash, Hash
|
117
|
+
self.class.init(original).merge(extra)
|
118
|
+
when Array
|
119
|
+
[original, extra].flatten.compact.uniq
|
120
|
+
else
|
121
|
+
extra
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Contours
|
4
|
+
# This class represents a string which has a particular order to its content.
|
5
|
+
# The intended use is for setting up CSS class values for HTML.
|
6
|
+
#
|
7
|
+
# An element may required that the first part of a class be a particular value
|
8
|
+
# and that the last part of a class be a particular value. For example, a
|
9
|
+
# field with an error might have a class like this:
|
10
|
+
#
|
11
|
+
# "input my-special-class input-error"
|
12
|
+
#
|
13
|
+
# Setting that up with a StructuredString would look like this:
|
14
|
+
#
|
15
|
+
# config = StructuredString.new("input").last("input-error")
|
16
|
+
# config << "my-special-class"
|
17
|
+
# config.to_s #=> "input my-special-class input-error"
|
18
|
+
#
|
19
|
+
class StructuredString < DelegateClass(String)
|
20
|
+
# Ensure that the argument string is a StructuredString
|
21
|
+
def self.init(base)
|
22
|
+
if base.is_a?(StructuredString)
|
23
|
+
base
|
24
|
+
else
|
25
|
+
new(base)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Initialize with base string that is used to build the StructuredString
|
30
|
+
# Other values will be added to the string in the order they are added.
|
31
|
+
def initialize(base = "", separator: " ")
|
32
|
+
@base = [base]
|
33
|
+
@separator = separator
|
34
|
+
@first = []
|
35
|
+
@last = []
|
36
|
+
@other = []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add a value to the beginning of the string
|
40
|
+
def first(value)
|
41
|
+
@first << value.to_s
|
42
|
+
__setobj__ to_s
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
# Add a value to the end of the string
|
47
|
+
def last(value)
|
48
|
+
@last << value.to_s
|
49
|
+
__setobj__ to_s
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add a value to the middle of the string
|
54
|
+
def <<(other)
|
55
|
+
@other << other.to_s
|
56
|
+
__setobj__ to_s
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# Read a particular portion of the structured string or raise an error
|
61
|
+
def read(key)
|
62
|
+
case key
|
63
|
+
when :first
|
64
|
+
@first
|
65
|
+
when :base
|
66
|
+
@base
|
67
|
+
when :other
|
68
|
+
@other
|
69
|
+
when :last
|
70
|
+
@last
|
71
|
+
else
|
72
|
+
raise ArgumentError, "Unknown accessor: #{key.inspect}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Add a value to the string in a particular position
|
77
|
+
# The argument must be a string or a 2 element array
|
78
|
+
# If it is a 2 element array, the first element must be a string and the
|
79
|
+
# second element must the name of a method used to merge the string to the
|
80
|
+
# StructuredString.
|
81
|
+
#
|
82
|
+
# For example:
|
83
|
+
#
|
84
|
+
# config = StructuredString.new("input")
|
85
|
+
# config.merge("my-special-class")
|
86
|
+
# config.merge(["input-error", :last])
|
87
|
+
# config.to_s #=> "input my-special-class input-error"
|
88
|
+
#
|
89
|
+
def merge(data)
|
90
|
+
case data
|
91
|
+
in StructuredString
|
92
|
+
@first += data.read :first
|
93
|
+
@base += data.read :base
|
94
|
+
@other += data.read :other
|
95
|
+
@last += data.read :last
|
96
|
+
in [String, Symbol]
|
97
|
+
if method(data.last)&.arity == 1
|
98
|
+
send(data.last, data.first)
|
99
|
+
else
|
100
|
+
raise ArgumentError, %(Tried to use '#{data.last}' with "#{data.first}" but it doesn't take 1 argument)
|
101
|
+
end
|
102
|
+
in String
|
103
|
+
send(:<<, data)
|
104
|
+
else
|
105
|
+
raise ArgumentError, "Must be a string or a 2 element array got: #{data.inspect}"
|
106
|
+
end
|
107
|
+
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# Return the string representation of the StructuredString
|
112
|
+
def to_s
|
113
|
+
[@first, @base, @other, @last]
|
114
|
+
.flatten
|
115
|
+
.compact
|
116
|
+
.map(&:to_s)
|
117
|
+
.reject(&:empty?)
|
118
|
+
.join(@separator)
|
119
|
+
end
|
120
|
+
alias_method :to_str, :to_s
|
121
|
+
alias_method :inspect, :to_s
|
122
|
+
|
123
|
+
def ==(other)
|
124
|
+
to_s == other.to_s
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/contours.rb
ADDED
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: contours
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jim Gay
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-10-10 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |
|
14
|
+
Provides objects with which you can define a configuration object
|
15
|
+
which can be customized by merging in other configuration objects.
|
16
|
+
email:
|
17
|
+
- jim@saturnflyer.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- LICENSE
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- lib/contours.rb
|
26
|
+
- lib/contours/blended_hash.rb
|
27
|
+
- lib/contours/structured_string.rb
|
28
|
+
- lib/contours/version.rb
|
29
|
+
homepage: https://github.com/SOFware/contours
|
30
|
+
licenses: []
|
31
|
+
metadata:
|
32
|
+
allowed_push_host: https://rubygems.org
|
33
|
+
homepage_uri: https://github.com/SOFware/contours
|
34
|
+
source_code_uri: https://github.com/SOFware/contours.git
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.7.0
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubygems_version: 3.4.20
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Support for building customizable configuration objects.
|
54
|
+
test_files: []
|