spigot 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a336365a8fdebfc3d1bd45fa11afea3c0a489ba6
4
- data.tar.gz: 30a5f6d8ce9c7fce3644233db8d35ff57cc45f17
3
+ metadata.gz: c6e0174fc401969ff57755d4eb751c1ebb3bd5c5
4
+ data.tar.gz: 2844483e9860825c20e4e6b6d255ffe80586843c
5
5
  SHA512:
6
- metadata.gz: b9df09433c59668681cfe6bd9ddb3c1c591b9e3d31c0aff96596477da1ca6144ecd94f1fcbed2b587a54e47bd6d2596cf9bfd6258020939d5229831c31711d96
7
- data.tar.gz: e725567b5260a2b0d9e358191cdb5dee627c6a36e844d49582de010d3f1795e8a6d2dcb8e304ece3e24962de3752aa9a78cf1d369a813f55d9faaed5e2b49a11
6
+ metadata.gz: 6edf56a3600624852a424f314faf8a1b092eb77a3e216e894964972a081a86348033f52f485540a0970a3c006683a91e7ebfcdba39a5ab9b6c489e08dd34d7ee
7
+ data.tar.gz: a88764f01212671cf27efdcf1d12218ceec4915b49c767c17b2b1b8560cabefc7ddca0e9ea7ab93686fe1d36f95d9f035cb46c793bd7077d0181eb57094cfc89
data/README.md CHANGED
@@ -1,45 +1,38 @@
1
1
  # Spigot
2
2
 
3
- Spigot provides a clean interface translating API data into context relevant objects
4
-
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- gem 'spigot'
10
-
11
- And then execute:
12
-
13
- $ bundle
14
-
15
- Or install it yourself as:
16
-
17
- $ gem install spigot
18
-
19
- ## Usage
3
+ Spigot is an attempt to bring some sanity to consuming external API data. Without Spigot, you need
4
+ to do this manual mapping at creation, such as:
5
+
6
+ Pull.where(number: pull.number).first_or_initialize.tap do |t|
7
+ t.title = pull.title
8
+ t.body = pull.body
9
+ t.url = pull._links.html.href
10
+ t.head_ref = pull.head.ref
11
+ t.head_sha = pull.head.sha
12
+ t.base_ref = pull.base.ref
13
+ t.base_sha = pull.base.sha
14
+ t.save
15
+ end
20
16
 
21
- You express the mapping between your model's data and the data received from an API in
22
- a yaml file. The mappings follow the structure of the data received. Any attribute you wish
23
- to retain, assign the name of your model's attribute to the name of the key received.
17
+ Spigot reads config files in an expected format to map the data you receive to the columns of your database.
18
+ This becomes particularly difficult as you start having multiple sources for the same resource (ex: users).
24
19
 
25
- Remember, the key is their attribute, the value is yours:
20
+ Spigot is able to do the translation for you and put the API data into a language your implementation understands.
21
+ This leaves you with a simple statement to accomplish the same as above:
26
22
 
27
- user:
28
- login: 'email'
29
- full_name: 'name'
23
+ Pull.find_or_create_by_api(:github, pull)
30
24
 
31
- Reads as: "For Users, their `login` is my `email` and their `full_name` is my `name`"
25
+ Much better. [Read More](https://github.com/mwerner/spigot/wiki)
32
26
 
33
27
  ## Example
34
28
 
35
29
  # Our Model
36
- class User
30
+ class User < ActiveRecord::Base
37
31
  include Spigot::Base
38
- attr_accessor :name, :email, :auth
39
32
  end
40
33
 
41
34
  # Api Data Received
42
- data = {"full_name":"Dean Martin","login":"dino@amore.io","token":"abc123"}
35
+ data = JSON.parse("{\"full_name\":\"Dean Martin\",\"login\":\"dino@amore.io\",\"token\":\"abc123\"}")
43
36
 
44
37
  # Spigot yaml file to map this data correctly
45
38
  user:
@@ -48,169 +41,22 @@ Reads as: "For Users, their `login` is my `email` and their `full_name` is my `n
48
41
  token: auth
49
42
 
50
43
  # Usage
51
- User.new_by_api(data).inspect
52
- #=> #<User:0x007ffa2918c7b8 @name="Dean Martin", @email="dino@amore.io", @auth="abc123">
53
-
54
- ## Map Format
55
-
56
- Each Spigot map file represents one service, with as many resources defined as you like.
57
-
58
- ##### Basic Map
59
-
60
- user:
61
- name: 'full_name'
62
- email: 'login'
63
- username: 'username'
64
-
65
- ##### Multiple Resources
66
-
67
- Spigot will look up the map for the name of the class implementing the method. This let's you map
68
- several of your resources that you're getting from the same service.
69
-
70
- # ./config/github.yml
71
- user:
72
- name: 'full_name'
73
- email: 'login'
74
- username: 'username'
75
-
76
- post:
77
- title: 'title'
78
- body: 'description'
79
- created_at: 'timestamp'
80
-
81
- ##### Options
82
-
83
- Spigot will look for an options key named `spigot` in the defined map.
84
- Those options let you configure additional options for that resource,
85
- such as denoting the identification in the data received.
86
-
87
- user:
88
- name: 'full_name'
89
- email: 'login'
90
- username: 'username'
91
- spigot:
92
- primary_key: 'service_id'
93
- foreign_key: 'id'
94
-
95
- ## ActiveRecord
96
-
97
- If you include Spigot on an ActiveRecord class, you get a few more methods.
98
-
99
- ##### `find_by_api`
100
-
101
- This uses the primary key defined in that resource's options to query the database
102
- with the value taken from the api data. (**Note** Does not update any values on the
103
- record received from the database.)
104
-
105
- # Spigot yaml file for the github service
106
- user:
107
- full_name: name
108
- login: email
109
- token: auth
110
- spigot:
111
- primary_key: email
112
-
113
- # API Data
114
- data = {"full_name":"Dean","login":"dino@amore.io","token":"bcd456"}
115
-
116
- #=> User.find_by_api(:github, data)
117
- User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'dino@amore.io' ORDER BY "users"."id" ASC LIMIT 1
44
+ User.find_or_create_by_api(:github, data).inspect
118
45
  #=> #<User id: 1, name: "Dean Martin", email: "dino@amore.io", token: "abc123">
119
46
 
120
- ##### `find_all_by_api`
121
-
122
- Operates just like `find_by_api`, but returns all matches on the primary key. The method
123
- returns an `ActveRecord::Relation` allowing you to chain other constraints if you need.
124
-
125
- # Spigot yaml file for the github service
126
- user:
127
- full_name: name
128
- login: email
129
- token: auth
130
- spigot:
131
- primary_key: full_name
132
-
133
- # API Data
134
- data = {"full_name":"Dean","login":"dino@amore.io","token":"bcd456"}
135
-
136
- #=> User.find_all_by_api(:github, data)
137
- User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."name" = 'Dean'
138
- => #<ActiveRecord::Relation [#<User id: 1, name: "Dean", email: "dino@amore.io", token: "abc123">, #<User id: 2, name: "Dean", email: "minerals@notrocks.io", token: '92fnd'>]>
139
-
140
- ##### `create_by_api`
141
-
142
- Creates a record in your database using the provided API data, without doing
143
- any kind of query before, beyond your model's defined validations. Notice the
144
- creation does not use any of the API that isn't defined in the map.
145
-
146
- # Spigot yaml file for the github service
147
- user:
148
- full_name: name
149
- login: email
150
- token: auth
151
- spigot:
152
- primary_key: email
153
-
154
- # API Data
155
- data = {"full_name":"Frank Sinatra","login":"live@tilidie.io","id":"3"}
156
-
157
- #=> User.create_by_api(:github, data)
158
- SQL (0.1ms) INSERT INTO "users" ("name", "email") VALUES (?, ?) [["name", "Frank Sinatra"], ["email", "live@tilidie.io"]]
159
- => #<User id: 4, name: "Frank Sinatra", email: "live@tilidie.io", token: nil>
160
-
161
- ##### `update_by_api`
162
-
163
- Updates a record in your database. If no record matching the primary key is found, nothing happens.
164
-
165
- # Spigot yaml file for the github service
166
- user:
167
- full_name: name
168
- login: email
169
- token: auth
170
- spigot:
171
- primary_key: email
172
-
173
- # API Data
174
- data = {"full_name":"Dino Baby","login":"dean@amore.io","token":"bcd456"}
175
-
176
- #=> User.update_by_api(:github, data)
177
- User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."email" = 'livetilidie' ORDER BY "active_users"."id" ASC LIMIT 1
178
- SQL (0.1ms) UPDATE "users" SET "name" = ?, token = ? WHERE "users"."id" = 3 [["name", "Dino Baby"], ["token", "bcd456"]]
179
- => #<User id: 3, name: "Dino Baby", email: "dean@amore.io", token: "bcd456">
180
-
181
- ##### `find_or_create_by_api`
182
-
183
- Query the database to find an existing record. If none is found, create one with the provided API data.
184
-
185
- ##### `create_or_update_by_api`
186
-
187
- Query the database to find an existing record. If a record is found, update
188
- the record with the received API data. If no record is found, create one with
189
- the provided API data.
47
+ ## Installation
190
48
 
191
- ## Configuration
49
+ Add this line to your application's Gemfile:
192
50
 
193
- There are a handful of options that let you make spigot work the way you need.
51
+ gem 'spigot'
194
52
 
195
- logger:
196
- Specify a logger you would like Spigot to log to.
197
- type: Object
198
- default: Logger.new(STDOUT)
53
+ And then execute:
199
54
 
200
- options_key:
201
- The key which Spigot uses for configuring a resource map.
202
- type: String
203
- default: 'spigot'
55
+ $ bundle
204
56
 
205
- path:
206
- The directory which holds all the yaml files for the implemented services
207
- type: String
208
- default: 'config/spigot'
57
+ Or install it yourself as:
209
58
 
210
- translations:
211
- A map that, if present, overrides the resource maps found in the `path` directory
212
- type: Hash
213
- default: nil
59
+ $ gem install spigot
214
60
 
215
61
  ## Contributing
216
62
 
@@ -0,0 +1,11 @@
1
+ class String
2
+ # Don't really like patching string here,
3
+ # but it's too clean not to.
4
+ def underscore
5
+ self.gsub(/::/, '/').
6
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
7
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
8
+ tr("-", "_").
9
+ downcase
10
+ end
11
+ end
@@ -1,4 +1,5 @@
1
1
  require 'yaml'
2
+ require 'hashie'
2
3
 
3
4
  module Spigot
4
5
  class Translator
@@ -37,16 +38,17 @@ module Spigot
37
38
  ## #format(custom_map)
38
39
  # Formats the hash of data passed in to the format specified in the yaml file.
39
40
  #
40
- # @param custom_map [Hash] Optional hash that you can prefer to use over the correlated translation.
41
- def format(custom_map=nil)
42
- translations = custom_map || mapping
43
- formatted = {}
44
- data.each_pair do |key, val|
45
- next if key == Spigot.config.options_key
46
- attribute = translations[key.to_s]
47
- formatted.merge!(attribute.to_s => data[key]) unless attribute.nil?
41
+ # @param custom_map [Hash] Optional hash that you can prefer to use over the correlated translation.
42
+ # @param custom_data [Hash] Optional data that you can prefer to use over the @data currently on the object.
43
+ def format(custom_map=nil, custom_data=nil)
44
+ map = Hashie::Mash.new(custom_map || mapping)
45
+ dataset = custom_data || data
46
+
47
+ if dataset.is_a?(Array)
48
+ dataset.map{|n| translate(map, n) }
49
+ elsif dataset.is_a?(Hash)
50
+ translate(map, dataset)
48
51
  end
49
- formatted
50
52
  end
51
53
 
52
54
  ## #id
@@ -97,23 +99,52 @@ module Spigot
97
99
  # Return a hash of the data map currently being used by this translator, including options.
98
100
  def mapping
99
101
  return @mapping if defined?(@mapping)
100
- @mapping = translations[resource_key.to_s]
102
+ @mapping = translations[resource_key]
101
103
  raise MissingResourceError, "There is no #{resource_key} mapping for #{service}" if @mapping.nil?
102
104
  @mapping
103
105
  end
104
106
 
105
107
  private
106
108
 
109
+ def translate(map, dataset)
110
+ formatted = {}
111
+
112
+ if dataset.is_a?(Array)
113
+ return dataset.map{|element| translate(map, element)}
114
+ else
115
+ dataset.each_pair do |key, val|
116
+ next if key == Spigot.config.options_key
117
+ attribute = map[key]
118
+ next if attribute.nil?
119
+
120
+ result = attribute.is_a?(Hash) ? translate(attribute, val) : val
121
+ formatted.merge! mergeable_content(key, result, map)
122
+ end
123
+ end
124
+
125
+ formatted
126
+ end
127
+
128
+ def mergeable_content(key, value, map)
129
+ if value.is_a?(Array)
130
+ {key.to_s => value}
131
+ elsif value.is_a?(Hash)
132
+ value
133
+ else
134
+ {map[key] => value}
135
+ end
136
+ end
137
+
107
138
  def condition_keys
108
139
  options['conditions'].to_s.split(',').map(&:strip)
109
140
  end
110
141
 
111
142
  def resource_key
112
- resource.to_s.downcase.gsub('::', '/')
143
+ resource.to_s.underscore
113
144
  end
114
145
 
115
146
  def translations
116
- @translations ||= Spigot.config.translations || YAML.load(translation_file)
147
+ @translations ||= Hashie::Mash.new(Spigot.config.translations || YAML.load(translation_file))
117
148
  end
118
149
 
119
150
  def translation_file
@@ -1,3 +1,3 @@
1
1
  module Spigot
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/spigot.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "spigot/version"
2
2
  require "spigot/errors"
3
+ require "spigot/patch"
3
4
 
4
5
  module Spigot
5
6
  autoload :Configuration, 'spigot/configuration'
@@ -13,6 +13,27 @@ module Spigot
13
13
  {'full_name' => 'Frank Sinatra', 'login' => 'livetilidie', 'auth_token' => '456bcd'}
14
14
  end
15
15
 
16
+ def self.nested_user
17
+ {'full_name' => 'Dean Martin', 'login' => {
18
+ 'email' => 'dino@amore.io',
19
+ 'user_name' => 'classyasfuck'
20
+ }, 'auth_token' => '123abc'}
21
+ end
22
+
23
+ def self.double_nested_user
24
+ {'full_name' => 'Dean Martin', 'login' => {
25
+ 'contact' => {'work_email' => 'dino@amore.io', 'user_name' => 'classyasfuck' }
26
+ }, 'auth_token' => '123abc'}
27
+ end
28
+
29
+ def self.user_array
30
+ [{'full_name' => 'Dean Martin', 'login' => 'classyasfuck'}, {'full_name' => 'Frank Sinatra', 'login' => 'livetilidie'}]
31
+ end
32
+
33
+ def self.nested_user_array
34
+ {'account' => 'Rockafella', 'users' => user_array, 'count' => 2}
35
+ end
36
+
16
37
  def self.basic_post
17
38
  {'title' => 'Brief Article', 'body' => 'lorem ipsum'}
18
39
  end
@@ -4,19 +4,19 @@ module Spigot
4
4
  class ActiveUser
5
5
 
6
6
  def self.basic
7
- {'activeuser' => base}
7
+ {'active_user' => base}
8
8
  end
9
9
 
10
10
  def self.with_options
11
- {'activeuser' => base.merge('spigot' => options)}
11
+ {'active_user' => base.merge('spigot' => options)}
12
12
  end
13
13
 
14
14
  def self.non_unique_key
15
- {'activeuser' => base.merge('auth_token' => 'token', 'spigot' => non_unique)}
15
+ {'active_user' => base.merge('auth_token' => 'token', 'spigot' => non_unique)}
16
16
  end
17
17
 
18
18
  def self.with_invalid_options
19
- {'activeuser' => base.merge('spigot' => invalid_options)}
19
+ {'active_user' => base.merge('spigot' => invalid_options)}
20
20
  end
21
21
 
22
22
  private
@@ -6,6 +6,32 @@ module Spigot
6
6
  {'user' => base}
7
7
  end
8
8
 
9
+ def self.symbolized
10
+ {user: {full_name: 'name', login: 'username'}}
11
+ end
12
+
13
+ def self.nested
14
+ {'user' => base.merge('login' => {'email' => 'contact', 'user_name' => 'username'})}
15
+ end
16
+
17
+ def self.nested_twice
18
+ {'user' => base.merge('login' => {
19
+ 'contact' => {'work_email' => 'email', 'user_name' => 'username'}
20
+ })}
21
+ end
22
+
23
+ def self.array
24
+ {'user' => base}
25
+ end
26
+
27
+ def self.nested_array
28
+ {'user' => {'account' => 'name', 'count' => 'user_count', 'users' => base}}
29
+ end
30
+
31
+ def self.nested_account_members
32
+ {'activeuser' => {'account_name' => 'name', 'url' => 'url', 'members' => {'login' => 'email', 'full_name' => 'name'}}}
33
+ end
34
+
9
35
  def self.with_options
10
36
  {'user' => base.merge('spigot' => options)}
11
37
  end
@@ -45,6 +45,18 @@ describe Spigot::Translator do
45
45
  end
46
46
  end
47
47
 
48
+ context 'with a symbol keyed map' do
49
+ let(:subject){Spigot::Translator.new(:github, User.new, Spigot::ApiData.basic_user)}
50
+
51
+ context 'with a basic mapping' do
52
+ with_mapping(:basic_user, Spigot::Mapping::User.symbolized)
53
+
54
+ it 'reads one layer' do
55
+ expect(subject.format).to eq({'name' => 'Dean Martin', 'username' => 'classyasfuck'})
56
+ end
57
+ end
58
+ end
59
+
48
60
  context 'and a valid resource map' do
49
61
  with_mapping(:basic_user, Spigot::Mapping::User.basic)
50
62
 
@@ -64,12 +76,46 @@ describe Spigot::Translator do
64
76
  end
65
77
  end
66
78
 
67
- context 'with a mapping containing several resources' do
68
- with_mapping(:multiple_resources, Spigot::Mapping.multiple_resources)
79
+ context 'with a mapping containing nested data' do
80
+ let(:subject){Spigot::Translator.new(:github, User.new, Spigot::ApiData.nested_user)}
81
+ with_mapping(:basic_user, Spigot::Mapping::User.nested)
69
82
 
70
- it 'selects the user map' do
71
- expect(subject.format).to eq({'name' => 'Dean Martin', 'username' => 'classyasfuck'})
83
+ it 'traverses into the nested hash' do
84
+ expect(subject.format).to eq({
85
+ 'name' => 'Dean Martin',
86
+ 'username' => 'classyasfuck',
87
+ 'contact' => 'dino@amore.io'
88
+ })
72
89
  end
90
+
91
+ context 'twice' do
92
+ with_mapping(:basic_user, Spigot::Mapping::User.nested_twice)
93
+ let(:subject){Spigot::Translator.new(:github, User.new, Spigot::ApiData.double_nested_user)}
94
+
95
+ it 'traverses multiple levels' do
96
+ expect(subject.format.values).to include(*['Dean Martin','classyasfuck','dino@amore.io'])
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ context 'with an array of values' do
103
+ let(:subject){Spigot::Translator.new(:github, User.new, Spigot::ApiData.user_array)}
104
+ with_mapping(:basic_user, Spigot::Mapping::User.basic)
105
+
106
+ it 'returns an array of formatted data' do
107
+ expect(subject.format.length).to eq(2)
108
+ expect(subject.format.map{|u| u['name']}).to include('Dean Martin', 'Frank Sinatra')
109
+ end
110
+ end
111
+
112
+ context 'with a nested array of values' do
113
+ let(:subject){Spigot::Translator.new(:github, User.new, Spigot::ApiData.nested_user_array)}
114
+ with_mapping(:basic_user, Spigot::Mapping::User.nested_array)
115
+
116
+ it 'handles a nested array of values' do
117
+ expect(subject.format.keys).to include('name', 'users', 'user_count')
118
+ expect(subject.format['users']).to be_a(Array)
73
119
  end
74
120
  end
75
121
 
data/spigot.gemspec CHANGED
@@ -18,11 +18,11 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib"]
19
19
 
20
20
  spec.add_dependency 'activerecord'
21
+ spec.add_dependency 'hashie'
21
22
 
22
23
  spec.add_development_dependency "bundler", "~> 1.3"
23
24
  spec.add_development_dependency "rake"
24
25
  spec.add_development_dependency "rspec"
25
26
  spec.add_development_dependency "mocha"
26
- spec.add_development_dependency 'hashie'
27
27
  spec.add_development_dependency 'sqlite3'
28
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spigot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Werner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-15 00:00:00.000000000 Z
11
+ date: 2013-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -25,35 +25,35 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: hashie
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - '>='
32
32
  - !ruby/object:Gem::Version
33
- version: '1.3'
34
- type: :development
33
+ version: '0'
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - '>='
39
39
  - !ruby/object:Gem::Version
40
- version: '1.3'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake
42
+ name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ~>
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '1.3'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '1.3'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rspec
56
+ name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - '>='
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: mocha
70
+ name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - '>='
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: hashie
84
+ name: mocha
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - '>='
@@ -132,6 +132,7 @@ files:
132
132
  - lib/spigot/config/spigot/github.yml
133
133
  - lib/spigot/configuration.rb
134
134
  - lib/spigot/errors.rb
135
+ - lib/spigot/patch.rb
135
136
  - lib/spigot/proxy.rb
136
137
  - lib/spigot/record.rb
137
138
  - lib/spigot/translator.rb