destruct 0.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.
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+
3
+ set -eo pipefail
4
+
5
+ echo -e "--- \033[33mWhat does this command do?\033[0m"
6
+ echo
7
+ echo " This script runs our LendingHome platform wide rubocop Docker image"
8
+ echo " for the gem or service mounted at /app. It uses a configuration file"
9
+ echo " that has been added directly into the rubocop Docker image itself. This"
10
+ echo " configuration lives in the lendinghome-dockerfiles repository."
11
+ echo
12
+ echo " Check out the documentation on rubocop for more information!"
13
+ echo
14
+ echo " * https://github.com/bbatsov/rubocop"
15
+ echo " * https://github.com/bbatsov/ruby-style-guide"
16
+ echo
17
+
18
+ if [ -v REBUILD_CACHE ]; then
19
+ echo "--- :docker: Pulling latest docker image"
20
+ docker-compose pull rubocop
21
+
22
+ echo "--- :recycle: Updating build cache"
23
+ echo "The build was triggered with REBUILD_CACHE"
24
+ echo "Skipping the remaining build steps"
25
+ else
26
+ echo "--- :docker: Starting docker"
27
+ docker-compose pull rubocop
28
+ docker-compose run rubocop $@
29
+ fi
@@ -0,0 +1,64 @@
1
+ #!/bin/bash
2
+
3
+ set -eo pipefail
4
+
5
+ if [[ -z "$DOCKER" ]]; then
6
+ echo "--- :buildkite: Building destruct"
7
+ buildkite-agent --version
8
+
9
+ echo "branch: $BUILDKITE_BRANCH"
10
+ echo "commit: $BUILDKITE_COMMIT"
11
+ echo "image: $DOCKER_IMAGE"
12
+
13
+ if [ -v REBUILD_CACHE ]; then
14
+ echo "--- :minidisc: Pulling images"
15
+ docker-compose pull --ignore-pull-failures
16
+ fi
17
+
18
+ echo "--- :docker: Starting docker"
19
+ docker --version
20
+ docker-compose --version
21
+
22
+ echo "Building $DOCKER_IMAGE"
23
+ docker-compose run app /app/bin/test $@
24
+
25
+ if [ -v REBUILD_CACHE ]; then
26
+ echo "--- :recycle: Updating build cache"
27
+ echo "The build was triggered with REBUILD_CACHE"
28
+
29
+ echo "Generating cache.tar.lz4"
30
+ tar -c --use-compress-program=lz4 -f cache.tar.lz4 "$BUILD_CACHE" \
31
+ || echo "Ignoring permission denied failures"
32
+
33
+ [ -v BUILDKITE ] && buildkite-agent meta-data set "rebuild-cache" "1"
34
+ fi
35
+
36
+ exit 0
37
+ fi
38
+
39
+ echo "--- :terminal: Loading environment"
40
+ echo "bash: `bash --version | head -1`"
41
+ echo "git: `git --version`"
42
+ echo "imagemagick: `identify --version | head -1`"
43
+ echo "java: `java -version 2>&1 | head -1`"
44
+ echo "os: `cat /etc/issue | head -1`"
45
+ echo "phantomjs: `phantomjs --version`"
46
+
47
+ echo "--- :ruby: Installing ruby"
48
+ rbenv install --skip-existing
49
+ ruby --version
50
+ rbenv --version
51
+
52
+ echo "--- :rubygems: Installing ruby gems"
53
+ echo "gem $(gem --version)"
54
+ bundler --version
55
+ cpus=$(nproc 2>/dev/null || echo 1)
56
+ [[ $cpus -eq 1 ]] && jobs=1 || jobs=$((cpus - 1))
57
+ options="--path vendor/bundle --jobs $jobs --retry 3 --frozen --no-cache --no-prune"
58
+ bundle check || bundle install $options
59
+
60
+ if [[ -z "$REBUILD_CACHE" ]]; then
61
+ echo "+++ :rspec: Running rspec tests"
62
+ echo "rspec $(bundle exec rspec --version)"
63
+ bundle exec rspec $@
64
+ fi
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.author = "LendingHome"
3
+ s.email = "github@lendinghome.com"
4
+ s.extra_rdoc_files = %w(LICENSE)
5
+ s.files = `git ls-files`.split("\n")
6
+ s.homepage = "https://github.com/lendinghome/destruct"
7
+ s.license = "MIT"
8
+ s.name = "destruct"
9
+ s.rdoc_options = %w(--charset=UTF-8 --inline-source --line-numbers --main README.md)
10
+ s.require_paths = %w(lib)
11
+ s.required_ruby_version = ">= 2.3.0"
12
+ s.summary = "ES6 style object destructuring in Ruby"
13
+ s.test_files = `git ls-files -- spec/*`.split("\n")
14
+ s.version = "0.0.0"
15
+ end
@@ -0,0 +1,21 @@
1
+ version: "2"
2
+
3
+ services:
4
+ app:
5
+ build:
6
+ context: "."
7
+ image: $DOCKER_IMAGE
8
+ environment:
9
+ - CI
10
+ - DOCKER=true
11
+ - REBUILD_CACHE
12
+ volumes:
13
+ - .:/app
14
+ - $BUILD_CACHE/bundler:/app/vendor/bundle
15
+
16
+ rubocop:
17
+ image: lendinghome/rubocop
18
+ environment:
19
+ - RUBOCOP_VERSION=2.3
20
+ volumes:
21
+ - .:/app
@@ -0,0 +1,39 @@
1
+ require_relative "destruct/dsl"
2
+ require_relative "destruct/hash"
3
+ require_relative "destruct/object"
4
+ require_relative "destruct/resolver"
5
+
6
+ Object.send(:include, Destruct::Object)
7
+
8
+ class Destruct
9
+ GEMSPEC = File.expand_path("../../destruct.gemspec", __FILE__)
10
+ VERSION = Gem::Specification.load(GEMSPEC).version.to_s
11
+
12
+ def initialize(object, *paths, &block)
13
+ @object = object
14
+ @paths = paths
15
+ @block = block || proc { }
16
+ end
17
+
18
+ def to_h
19
+ paths.each_with_object(Hash.new) do |path, hash|
20
+ resolve(path).each do |key, value|
21
+ hash[key] = value
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def dsl
29
+ DSL.new(&@block)
30
+ end
31
+
32
+ def paths
33
+ @paths + dsl.paths
34
+ end
35
+
36
+ def resolve(path)
37
+ Resolver.new(@object, path).to_h
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ class Destruct
2
+ class DSL
3
+ def initialize(&block)
4
+ @paths = []
5
+ instance_eval(&block) if block
6
+ end
7
+
8
+ def [](path)
9
+ self.class.new.tap do |dsl|
10
+ @paths << [path, dsl]
11
+ end
12
+ end
13
+
14
+ def paths
15
+ @paths.each_with_object([]) do |(key, dsl), paths|
16
+ nested = dsl.paths
17
+ paths << [key] if nested.empty?
18
+ nested.each { |path| paths << path.unshift(key) }
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def method_missing(method, *)
25
+ self[method]
26
+ end
27
+
28
+ def respond_to_missing?(method, *)
29
+ true
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ class Destruct
2
+ class Hash < ::Hash
3
+ def [](key)
4
+ super(key) || super(key.to_s) || to_ary[key]
5
+ rescue TypeError
6
+ nil
7
+ end
8
+
9
+ def []=(key, value)
10
+ to_ary << value
11
+ super
12
+ end
13
+
14
+ def key?(key)
15
+ super(key) || super(key.to_s)
16
+ end
17
+
18
+ def to_ary
19
+ @to_ary ||= values
20
+ end
21
+
22
+ private
23
+
24
+ def method_missing(method, *)
25
+ key?(method) ? self[method] : super
26
+ end
27
+
28
+ def respond_to_missing?(method, *)
29
+ key?(method) || super
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ class Destruct
2
+ module Object
3
+ def destruct(*paths, &block)
4
+ Destruct.new(self, *paths, &block).to_h
5
+ end
6
+
7
+ def dig(method, *paths)
8
+ object = send(method) if respond_to?(method)
9
+ paths.any? ? object&.dig(*paths) : object
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ class Destruct
2
+ class Resolver
3
+ def initialize(object, path)
4
+ @object = object
5
+ @path = path
6
+ end
7
+
8
+ def to_h
9
+ case @path
10
+ when Array
11
+ { @path.last => @object.dig(*@path) }
12
+ when ::Hash
13
+ resolve_recursively
14
+ else
15
+ self.class.new(@object, paths).to_h
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def paths
22
+ Array(@path)
23
+ end
24
+
25
+ def resolve(key, values)
26
+ @object.dig(key).tap do |object|
27
+ Array(values).each do |value|
28
+ yield self.class.new(object, value)
29
+ end
30
+ end
31
+ end
32
+
33
+ def resolve_recursively
34
+ @path.each_with_object({}) do |(key, values), hash|
35
+ resolve(key, values) do |resolver|
36
+ hash.update(resolver.to_h)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,94 @@
1
+ RSpec.describe Destruct::Hash do
2
+ subject { described_class.new.replace(hash) }
3
+
4
+ let(:hash) do
5
+ {
6
+ status: 200,
7
+ "multi word" => "example",
8
+ "test" => "string"
9
+ }
10
+ end
11
+
12
+ describe "#[]" do
13
+ it "looks up by key" do
14
+ expect(subject[:status]).to eq(200)
15
+ expect(subject["test"]).to eq("string")
16
+ end
17
+
18
+ it "looks up by stringified key" do
19
+ expect(subject[:test]).to eq("string")
20
+ end
21
+
22
+ it "looks up by multi-word key" do
23
+ expect(subject["multi word"]).to eq("example")
24
+ end
25
+
26
+ it "looks up by index key" do
27
+ expect(subject[0]).to eq(200)
28
+ expect(subject[1]).to eq("example")
29
+ expect(subject[2]).to eq("string")
30
+ expect(subject[-1]).to eq("string")
31
+ end
32
+
33
+ it "returns nil when key is not found" do
34
+ expect(subject[:invalid]).to be_nil
35
+ expect(subject["invalid"]).to be_nil
36
+ expect(subject[99]).to be_nil
37
+ expect(subject[{}]).to be_nil
38
+ end
39
+ end
40
+
41
+ describe "#key?" do
42
+ it "looks up by key" do
43
+ expect(subject).to be_key(:status)
44
+ expect(subject).to be_key("test")
45
+ end
46
+
47
+ it "looks up by stringified key" do
48
+ expect(subject).to be_key(:test)
49
+ end
50
+
51
+ it "looks up by multi-word key" do
52
+ expect(subject).to be_key("multi word")
53
+ end
54
+
55
+ it "returns false when key is not found" do
56
+ expect(subject).not_to be_key(:invalid)
57
+ expect(subject).not_to be_key("invalid")
58
+ expect(subject).not_to be_key(1)
59
+ end
60
+ end
61
+
62
+ describe "#method_missing" do
63
+ it "resolves symbol keys" do
64
+ expect(subject.status).to eq(200)
65
+ expect(subject.test).to eq("string")
66
+ end
67
+
68
+ it "resolves string keys" do
69
+ expect(subject.test).to eq("string")
70
+ end
71
+
72
+ it "raises NoMethodError otherwise" do
73
+ expect { subject.invalid }.to raise_error(NoMethodError)
74
+ end
75
+ end
76
+
77
+ describe "#respond_to?" do
78
+ it "returns true if keys exist" do
79
+ expect(subject).to be_respond_to(:status)
80
+ expect(subject).to be_respond_to(:test)
81
+ expect(subject).to be_respond_to("test")
82
+ end
83
+
84
+ it "returns false if keys do not exist" do
85
+ expect(subject).not_to be_respond_to(:invalid)
86
+ end
87
+ end
88
+
89
+ describe "#to_ary" do
90
+ it "returns the subject values" do
91
+ expect(subject.to_ary).to eq(subject.values)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,42 @@
1
+ RSpec.describe Destruct::Object do
2
+ subject { "test" }
3
+
4
+ describe "#destruct" do
5
+ it "is defined on all objects" do
6
+ expect(subject).to respond_to(:destruct)
7
+ expect(Object).to respond_to(:destruct)
8
+ expect(Object.new).to respond_to(:destruct)
9
+ end
10
+ end
11
+
12
+ describe "#dig" do
13
+ it "resolves nested methods" do
14
+ actual = subject.dig(:upcase, :reverse)
15
+ expect(actual).to eq("TSET")
16
+ end
17
+
18
+ it "resolves nested hashes" do
19
+ struct = Struct.new(:hash)
20
+ object = struct.new(one: { two: 2 })
21
+ actual = object.dig(:hash, :one, :two)
22
+ expect(actual).to eq(2)
23
+ end
24
+
25
+ it "resolves nested arrays" do
26
+ struct = Struct.new(:array)
27
+ object = struct.new([1, [2, 3]])
28
+ actual = object.dig(:array, 1, 0)
29
+ expect(actual).to eq(2)
30
+ end
31
+
32
+ it "returns nil for undefined methods" do
33
+ actual = subject.dig(:upcase, :undefined, :reverse)
34
+ expect(actual).to be_nil
35
+ end
36
+
37
+ it "returns nil for invalid methods" do
38
+ actual = subject.dig(:upcase, "invalid method", :reverse)
39
+ expect(actual).to be_nil
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ RSpec.describe Destruct::Resolver do
2
+ subject { described_class.new(object, path) }
3
+
4
+ let(:object) { "test" }
5
+
6
+ describe "#to_h" do
7
+ let(:path) { :upcase }
8
+
9
+ context "with object argument" do
10
+ it "parses correctly" do
11
+ expect(subject.to_h).to eq(upcase: "TEST")
12
+ end
13
+ end
14
+
15
+ context "with array argument" do
16
+ let(:path) { %i(upcase reverse) }
17
+
18
+ it "parses correctly" do
19
+ expect(subject.to_h).to eq(reverse: "TSET")
20
+ end
21
+ end
22
+
23
+ context "with hash argument" do
24
+ let(:path) { { upcase: :reverse } }
25
+
26
+ it "parses correctly" do
27
+ expect(subject.to_h).to eq(reverse: "TSET")
28
+ end
29
+ end
30
+ end
31
+ end