contours 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []