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 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Contours
4
+ VERSION = "0.1.0"
5
+ end
data/lib/contours.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "contours/version"
4
+ require_relative "contours/blended_hash"
5
+ require_relative "contours/structured_string"
6
+
7
+ module Contours
8
+ end
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: []