destruct 0.0.0

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