shortcut_ruby 0.11.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +298 -0
- data/Rakefile +6 -0
- data/bin/console +9 -0
- data/bin/setup +13 -0
- data/lib/shortcut_ruby/constants.rb +66 -0
- data/lib/shortcut_ruby/path_builder.rb +90 -0
- data/lib/shortcut_ruby/request.rb +77 -0
- data/lib/shortcut_ruby/version.rb +3 -0
- data/lib/shortcut_ruby.rb +28 -0
- data/shortcut_ruby.gemspec +26 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1ff156e9d4a091b1c438786f5e8ca4eabc9bb8a3f6e6ed70b37dd27583118c99
|
4
|
+
data.tar.gz: b4c3883610550b56e01c67a65f99a87b9543ceec95fa9999dca7733bfa4ace39
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a3c3a00088cd581dae1970ba7b93a1dc92de5e2d347c5a6b95960d3d3907a352bb3e08c2d1816da4d591e90d24aafe7e611ab7c219f2ee1fc4f1c3ae6f84b328
|
7
|
+
data.tar.gz: f84cb20b33cd00726bf20e1a5cd4384dfb7aa611275cf2ce45d30a52da1cf2d96e845659bb6161aad623d4498619507b96f37dacdd48ac277811fc4a622896d1
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Jake Sorce
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
# ShortcutRuby
|
2
|
+
|
3
|
+
ShortcutRuby is a lightweight Ruby wrapper of the
|
4
|
+
[Shortcut REST API](https://shortcut.com/api/rest/v3).
|
5
|
+
|
6
|
+
This gem is a fork of the original, [clubhouse_ruby](https://github.com/jakesorce/clubhouse_ruby), renamed and updated to work with Shortcut.
|
7
|
+
|
8
|
+
[Shortcut](https://shortcut.com) is a radical project management tool
|
9
|
+
particularly well suited to software development. If you're not familiar with
|
10
|
+
them, go check them out! :heart:
|
11
|
+
|
12
|
+
This gem is built with the philosophy that a good API wrapper is a simpler
|
13
|
+
alternative to a comprehensive client library and can provide a nice interface
|
14
|
+
to the API using dynamic Ruby metaprogramming techniques rather than mapping
|
15
|
+
functionality from the API to the library piece by piece.
|
16
|
+
|
17
|
+
This enables the wrapper to be loosely coupled to the current implementation of
|
18
|
+
the API, which makes it more resilient to change. Also, this approach takes much
|
19
|
+
less code and maintenance effort, allowing the developer to be lazy. A
|
20
|
+
reasonable person might fairly assume this to be the true rationale behind the
|
21
|
+
philosophy. They'd be right.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Add this line to your application's Gemfile:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem 'shortcut_ruby'
|
29
|
+
```
|
30
|
+
|
31
|
+
And then execute:
|
32
|
+
|
33
|
+
$ bundle
|
34
|
+
|
35
|
+
Or install it globally:
|
36
|
+
|
37
|
+
$ gem install shortcut_ruby
|
38
|
+
|
39
|
+
Or transcribe the code by carving it character by character into the
|
40
|
+
mechanically articulated hand built stone sculpture you've developed that
|
41
|
+
operates as an effective turing machine when lubricated with oil.
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
This gem is a lightweight API wrapper. That means you'll need to refer to the
|
46
|
+
[API documentation](https://shortcut.com/api/rest/v3) to figure out what resources
|
47
|
+
and actions exist.
|
48
|
+
|
49
|
+
On the plus side, once you know what you want to do, using this gem should be
|
50
|
+
simple.
|
51
|
+
|
52
|
+
Instantiate an object to interface with the API:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
shortcut = ShortcutRuby::Shortcut.new(<YOUR SHORTCUT API TOKEN>)
|
56
|
+
```
|
57
|
+
|
58
|
+
The API can also provide responses in CSV format instead of the default JSON:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
shortcut = ShortcutRuby::Shortcut.new(<YOUR SHORTCUT API TOKEN>, response_format: :csv)
|
62
|
+
```
|
63
|
+
|
64
|
+
Then, call methods on the object matching the resource(s) and action you are
|
65
|
+
interested in. For example, if you want to list all available epics, you need to
|
66
|
+
access the endpoint at https://shortcut.com/api/rest/v3#List-Epics. The
|
67
|
+
shortcut_ruby gem uses an explicit action:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
shortcut.epics.list
|
71
|
+
# => {
|
72
|
+
# code: "200",
|
73
|
+
# status: "OK",
|
74
|
+
# content: [
|
75
|
+
# {
|
76
|
+
# "entity_type" => "epic",
|
77
|
+
# "id" => 1,
|
78
|
+
# "external_id" => nil,
|
79
|
+
# "name" => "An Odyssian Epic",
|
80
|
+
# "description" => "Outrageously epic.",
|
81
|
+
# "created_at" => "...",
|
82
|
+
# "updated_at" => "...",
|
83
|
+
# "deadline "=> nil,
|
84
|
+
# "state" => "to do",
|
85
|
+
# "position" => 1,
|
86
|
+
# "started" => false,
|
87
|
+
# "started_at" => nil,
|
88
|
+
# "started_at_override" => nil,
|
89
|
+
# "completed" => false,
|
90
|
+
# "completed_at" => nil,
|
91
|
+
# "completed_at_override" => nil,
|
92
|
+
# "archived" => false,
|
93
|
+
# "labels" => [...],
|
94
|
+
# "milestone_id" => nil,
|
95
|
+
# "follower_ids" => [...],
|
96
|
+
# "owner_ids" => [...],
|
97
|
+
# "project_ids" => [...],
|
98
|
+
# "comments" => [...],
|
99
|
+
# "stats" => {...},
|
100
|
+
# }, ...
|
101
|
+
# ]
|
102
|
+
# }
|
103
|
+
```
|
104
|
+
|
105
|
+
If the endpoint you want requires parameters, say if you wanted to create an
|
106
|
+
epic, you provide a hash to the action call following the resource:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
shortcut.epics.create(name: "My New Epic", state: "to do")
|
110
|
+
# => {
|
111
|
+
# code: "201",
|
112
|
+
# status: "Created",
|
113
|
+
# content: {
|
114
|
+
# "entity_type" => "epic",
|
115
|
+
# "id" => 2,
|
116
|
+
# "extenal_id" => nil,
|
117
|
+
# "name" => "My New Epic",
|
118
|
+
# "description" => "",
|
119
|
+
# ...
|
120
|
+
# }
|
121
|
+
# }
|
122
|
+
```
|
123
|
+
|
124
|
+
If the endpoint you want is nested, you can build a path by chaining method
|
125
|
+
calls, providing any required parent resource id as an argument to that method
|
126
|
+
in the chain. For example, if you wanted to list all the stories associated
|
127
|
+
with a particular project:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
shortcut.projects(<project_id>).stories.list
|
131
|
+
# => {
|
132
|
+
# code: "200",
|
133
|
+
# status: "OK",
|
134
|
+
# content: [
|
135
|
+
# {
|
136
|
+
# "entity_type" => "story",
|
137
|
+
# "archived" => false,
|
138
|
+
# "created_at" => "...",
|
139
|
+
# "updated_at" => "...",
|
140
|
+
# "id" => 1,
|
141
|
+
# "external_id" => nil,
|
142
|
+
# "name" => "Rescue Prince",
|
143
|
+
# "story_type" => "feature",
|
144
|
+
# "description" => "The prince is trapped in a tower and needs freeing.",
|
145
|
+
# "position" => 1,
|
146
|
+
# ...
|
147
|
+
# }, ...
|
148
|
+
# ]
|
149
|
+
# }
|
150
|
+
```
|
151
|
+
|
152
|
+
You can search stories, using standard Shortcut [search operators](https://help.shortcut.com/hc/en-us/articles/360000046646-SearSC-Operators):
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
shortcut.search_stories(page_size: 25, query: 'state:500000016')
|
156
|
+
# => {
|
157
|
+
# code: "200",
|
158
|
+
# status: "OK",
|
159
|
+
# content: {
|
160
|
+
# "next" => "/api/v3/search/stories?query=state%3A500000016&page_size=25&next=a8acc6577548df7a213272f7f9f617bcb1f8a831~24",
|
161
|
+
# "data" => [
|
162
|
+
# {
|
163
|
+
# "entity_type" => "story",
|
164
|
+
# "archived" => false,
|
165
|
+
# "created_at" => "...",
|
166
|
+
# "updated_at" => "...",
|
167
|
+
# ...
|
168
|
+
# }, ...
|
169
|
+
# ]
|
170
|
+
# }
|
171
|
+
# }
|
172
|
+
```
|
173
|
+
|
174
|
+
You can build a path in steps rather than all at once, and execution is deferred
|
175
|
+
until the action call:
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
shortcut.projects(<project_id>)
|
179
|
+
shortcut.stories
|
180
|
+
shortcut.list
|
181
|
+
# => as above
|
182
|
+
```
|
183
|
+
|
184
|
+
If you are building a path and you make a mistake, you can clear the path:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
shortcut.projects(project_id)
|
188
|
+
shortcut.epics
|
189
|
+
shortcut.clear_path
|
190
|
+
# => []
|
191
|
+
```
|
192
|
+
|
193
|
+
You don't need to clear the path after a complete request, as that happens
|
194
|
+
automatically.
|
195
|
+
|
196
|
+
Note that the chained methods are always resources (with an id for a parent when
|
197
|
+
accessing nested resources) followed by a final action that matches the methods
|
198
|
+
in the Shortcut API documentation.
|
199
|
+
|
200
|
+
These resources and methods are enumerated in the source code
|
201
|
+
[here](https://github.com/doubleloopapp/shortcut_ruby/blob/master/lib/shortcut_ruby/constants.rb)
|
202
|
+
but generally you should find the url you are interested in from the docs.
|
203
|
+
|
204
|
+
## Errors
|
205
|
+
|
206
|
+
Errors are passed through from the API relatively undecorated:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
shortcut = ShortcutRuby::Shortcut.new("unrecognized token")
|
210
|
+
shortcut.epics.list
|
211
|
+
# => {
|
212
|
+
# code: "401",
|
213
|
+
# status: "Unauthorized",
|
214
|
+
# content: {
|
215
|
+
# "message" => "Unauthorized",
|
216
|
+
# "tag" => "unauthorized"
|
217
|
+
# }
|
218
|
+
# }
|
219
|
+
```
|
220
|
+
|
221
|
+
Arbitrary combinations of resources not building a path that matches a url the
|
222
|
+
API knows about will fail.
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
shortcut.epics(epic_id).stories.list
|
226
|
+
# => {
|
227
|
+
# code: "404",
|
228
|
+
# status: "Not Found",
|
229
|
+
# content: {
|
230
|
+
# "message" => "Page not Found"
|
231
|
+
# }
|
232
|
+
# }
|
233
|
+
```
|
234
|
+
|
235
|
+
Note: the v1 API returns forbidden rather than not found.
|
236
|
+
|
237
|
+
Attempting to access a nested resource without providing the parent id as an
|
238
|
+
argument is a bad request:
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
shortcut.projects.stories.list
|
242
|
+
# => {
|
243
|
+
# code: "400",
|
244
|
+
# status: "Bad Request",
|
245
|
+
# content: {
|
246
|
+
# "message" => "The request included invalid or missing parameters.",
|
247
|
+
# "errors" => {
|
248
|
+
# "project-public-id" => [
|
249
|
+
# "not", [
|
250
|
+
# "integer?",
|
251
|
+
# "projects"
|
252
|
+
# ]
|
253
|
+
# ]
|
254
|
+
# }
|
255
|
+
# }
|
256
|
+
# }
|
257
|
+
```
|
258
|
+
|
259
|
+
## Version
|
260
|
+
|
261
|
+
The current version of the shortcut_ruby gem supports the current version of
|
262
|
+
the API, version 3.
|
263
|
+
|
264
|
+
If you want something that definitely works with:
|
265
|
+
|
266
|
+
- v1, use version 0.2.0 of shortcut_ruby
|
267
|
+
- v2, use version 0.3.0 of shortcut_ruby
|
268
|
+
|
269
|
+
## Development
|
270
|
+
|
271
|
+
After checking out the repo, run `bin/setup` to install dependencies and
|
272
|
+
following the instructions. Specifically, you can choose to provide a genuine
|
273
|
+
Shortcut API token in the `.env` file. This will be important if you want to
|
274
|
+
use `bin/console` for an interactive prompt that allows you to experiment with
|
275
|
+
the gem and real API responses.
|
276
|
+
|
277
|
+
Use `rake spec` to run the tests. The tests don't make external requests but
|
278
|
+
rather use VCR for stubbed responses. If you want to play with the tests and
|
279
|
+
get real API responses (perhaps to extend the suite or for a new feature) then
|
280
|
+
you'll need to have an API token in the env as described above.
|
281
|
+
|
282
|
+
Note that the current test suite is far from exhaustive and could do with some
|
283
|
+
love.
|
284
|
+
|
285
|
+
**NB: If you have implemented a feature that requires a new cassette, make sure
|
286
|
+
you change the uri referenced by the cassette you added to remove the API token
|
287
|
+
if you have updated the environment to use your token. Otherwise your API token
|
288
|
+
will be in publicly visible from the code in this repo.**
|
289
|
+
|
290
|
+
## Contributing
|
291
|
+
|
292
|
+
Bug reports and pull requests are entirely welcome on GitHub at
|
293
|
+
https://github.com/doubleloopapp/shortcut_ruby.
|
294
|
+
|
295
|
+
## License
|
296
|
+
|
297
|
+
The gem is available as open source under the terms of the
|
298
|
+
[MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
|
5
|
+
bundle install
|
6
|
+
|
7
|
+
touch .env
|
8
|
+
if [[ $(grep API_TOKEN .env) == "" ]]; then
|
9
|
+
echo "API_TOKEN=<YOUR SHORTCUT API TOKEN>" >> .env
|
10
|
+
echo ;
|
11
|
+
echo "DEVELOPER: Please edit .env to contain your shortcut api token if you wish to use the console."
|
12
|
+
echo ;
|
13
|
+
fi
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'csv'
|
3
|
+
|
4
|
+
module ShortcutRuby
|
5
|
+
API_URL = "https://api.app.shortcut.com/api/v3/".freeze
|
6
|
+
|
7
|
+
# Response formats the shortcut api knows about
|
8
|
+
FORMATS = {
|
9
|
+
json: {
|
10
|
+
headers: { header: 'Content-Type', content: 'application/json' },
|
11
|
+
parser: JSON
|
12
|
+
},
|
13
|
+
csv: {
|
14
|
+
headers: { header: 'Accept', content: 'text/csv' },
|
15
|
+
parser: CSV
|
16
|
+
}
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
# Action words are nice for our internal api and match the api path too
|
20
|
+
ACTIONS = {
|
21
|
+
get: :Get,
|
22
|
+
update: :Put,
|
23
|
+
delete: :Delete,
|
24
|
+
list: :Get,
|
25
|
+
create: :Post
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
# These are the resource for the shortcut api and can form part of the path
|
29
|
+
RESOURCES = [
|
30
|
+
:categories,
|
31
|
+
:entity_templates,
|
32
|
+
:epics,
|
33
|
+
:files,
|
34
|
+
:iterations,
|
35
|
+
:labels,
|
36
|
+
:linked_files,
|
37
|
+
:member,
|
38
|
+
:members,
|
39
|
+
:milestones,
|
40
|
+
:projects,
|
41
|
+
:repositories,
|
42
|
+
:stories,
|
43
|
+
:story_links,
|
44
|
+
:teams,
|
45
|
+
:workflows,
|
46
|
+
:tasks,
|
47
|
+
:comments,
|
48
|
+
:groups
|
49
|
+
].freeze
|
50
|
+
|
51
|
+
# These are the annoying edge cases in the shortcut api that are don't fit
|
52
|
+
EXCEPTIONS = {
|
53
|
+
bulk_create: {
|
54
|
+
path: :bulk,
|
55
|
+
action: :Post
|
56
|
+
},
|
57
|
+
bulk_update: {
|
58
|
+
path: :bulk,
|
59
|
+
action: :Put
|
60
|
+
},
|
61
|
+
search_stories: {
|
62
|
+
path: 'search/stories',
|
63
|
+
action: :Get
|
64
|
+
}
|
65
|
+
}.freeze
|
66
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ShortcutRuby
|
2
|
+
module PathBuilder
|
3
|
+
def self.included(_)
|
4
|
+
class_exec do
|
5
|
+
attr_accessor :path
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
# Uh oh! This will allow the class including this module to "build a path"
|
10
|
+
# by chaining calls to resources, terminated with a method linked to an
|
11
|
+
# action that will execute the api call.
|
12
|
+
#
|
13
|
+
# For example:
|
14
|
+
#
|
15
|
+
# `foo.stories(story_id).comments.update(id: comment_id, text: "comment text")`
|
16
|
+
#
|
17
|
+
# This example will execute a call to:
|
18
|
+
#
|
19
|
+
# `https://api.app.shortcut.com/api/v3/stories/{story-id}/comments/{comment-id}`
|
20
|
+
#
|
21
|
+
# with arguments:
|
22
|
+
#
|
23
|
+
# `{ text: "comment text" }`
|
24
|
+
#
|
25
|
+
def method_missing(name, *args)
|
26
|
+
if known_action?(name)
|
27
|
+
execute_request(ACTIONS[name], args.first)
|
28
|
+
elsif known_resource?(name)
|
29
|
+
build_path(name, args.first)
|
30
|
+
elsif known_exception?(name)
|
31
|
+
build_path(EXCEPTIONS[name][:path], nil)
|
32
|
+
execute_request(EXCEPTIONS[name][:action], args.first)
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# You can build a path without executing in stages like this:
|
39
|
+
#
|
40
|
+
# `foo.stories(story_id)`
|
41
|
+
#
|
42
|
+
# This will partly populate foo:path, but won't execute the call (which
|
43
|
+
# clears it). In case you made a mistake and want to start again, you can
|
44
|
+
# clear the path using this public method.
|
45
|
+
#
|
46
|
+
def clear_path
|
47
|
+
self.path = []
|
48
|
+
end
|
49
|
+
|
50
|
+
# We'd better not lie when asked.
|
51
|
+
#
|
52
|
+
def respond_to_missing?(name, include_private = false)
|
53
|
+
known_action?(name) ||
|
54
|
+
known_resource?(name) ||
|
55
|
+
known_exception?(name) ||
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def known_action?(name)
|
62
|
+
ACTIONS.keys.include?(name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def known_resource?(name)
|
66
|
+
RESOURCES.include?(name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def known_exception?(name)
|
70
|
+
EXCEPTIONS.keys.include?(name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def execute_request(action, params)
|
74
|
+
req = Request.new(
|
75
|
+
self,
|
76
|
+
action: action,
|
77
|
+
params: params
|
78
|
+
)
|
79
|
+
clear_path
|
80
|
+
req.fetch
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_path(resource, id)
|
84
|
+
self.path ||= []
|
85
|
+
self.path << resource
|
86
|
+
self.path << id if id
|
87
|
+
self
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module ShortcutRuby
|
4
|
+
class Request
|
5
|
+
attr_accessor :uri, :action, :response_format, :params
|
6
|
+
|
7
|
+
# Prepares a fancy request object and ensures the inputs make as much sense
|
8
|
+
# as possible. It's still totally possible to just provide a path that
|
9
|
+
# doesn't match a real url though.
|
10
|
+
#
|
11
|
+
def initialize(shortcut, action:, params: {})
|
12
|
+
raise ArgumentError unless validate_input(shortcut, action, params)
|
13
|
+
|
14
|
+
self.params = params || {}
|
15
|
+
self.uri = construct_uri(shortcut)
|
16
|
+
self.action = action
|
17
|
+
self.response_format = shortcut.response_format
|
18
|
+
end
|
19
|
+
|
20
|
+
# Executes the http(s) request and provides the response with some
|
21
|
+
# additional decoration in a Hash.
|
22
|
+
#
|
23
|
+
def fetch
|
24
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |https|
|
25
|
+
req = Net::HTTP.const_get(action).new(uri)
|
26
|
+
|
27
|
+
set_body(req)
|
28
|
+
set_format_header(req)
|
29
|
+
|
30
|
+
wrap_response(https.request(req))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def validate_input(shortcut, action, params)
|
37
|
+
shortcut.is_a?(Shortcut) &&
|
38
|
+
!shortcut.path.nil? &&
|
39
|
+
!shortcut.token.nil? &&
|
40
|
+
!shortcut.response_format.nil? &&
|
41
|
+
ACTIONS.values.include?(action) &&
|
42
|
+
(params.is_a?(Hash) || params.nil?)
|
43
|
+
end
|
44
|
+
|
45
|
+
def construct_uri(shortcut)
|
46
|
+
base_url = API_URL
|
47
|
+
path = shortcut.path.map(&:to_s).map { |p| p.gsub('_', '-') }.join('/')
|
48
|
+
object_id = "/#{self.params.delete(:id)}" if self.params.key?(:id)
|
49
|
+
token = shortcut.token
|
50
|
+
URI("#{base_url}#{path}#{object_id}?token=#{token}")
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_format_header(req)
|
54
|
+
format_header = FORMATS[response_format][:headers][:header]
|
55
|
+
format_content = FORMATS[response_format][:headers][:content]
|
56
|
+
req[format_header] = format_content
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_body(req)
|
60
|
+
req.body = params.to_json if params
|
61
|
+
end
|
62
|
+
|
63
|
+
def wrap_response(res)
|
64
|
+
begin
|
65
|
+
content = FORMATS[response_format][:parser].parse(res.body) if res.body
|
66
|
+
rescue FORMATS[response_format][:parser]::ParserError
|
67
|
+
content = res.body
|
68
|
+
end
|
69
|
+
|
70
|
+
{
|
71
|
+
code: res.code,
|
72
|
+
status: res.message,
|
73
|
+
content: content
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "shortcut_ruby/version"
|
2
|
+
require "shortcut_ruby/constants"
|
3
|
+
require "shortcut_ruby/path_builder"
|
4
|
+
require "shortcut_ruby/request"
|
5
|
+
|
6
|
+
module ShortcutRuby
|
7
|
+
class Shortcut
|
8
|
+
include PathBuilder
|
9
|
+
|
10
|
+
attr_accessor :token, :response_format
|
11
|
+
|
12
|
+
# This is the basic object to interact with the shortcut api. An api token
|
13
|
+
# is required, and optionally the response format can be set.
|
14
|
+
#
|
15
|
+
def initialize(token, response_format: :json)
|
16
|
+
raise ArgumentError unless input_valid?(token, response_format)
|
17
|
+
|
18
|
+
self.token = token
|
19
|
+
self.response_format = response_format
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def input_valid?(token, response_format)
|
25
|
+
!token.nil? && FORMATS.keys.include?(response_format)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'shortcut_ruby/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'shortcut_ruby'
|
7
|
+
spec.version = ShortcutRuby::VERSION
|
8
|
+
spec.authors = ['Michael Carey', 'Jake Sorce']
|
9
|
+
spec.email = ['engineering@doubleloop.app']
|
10
|
+
|
11
|
+
spec.summary = 'A lightweight Ruby wrapper for the Shortcut REST API.'
|
12
|
+
spec.homepage = 'https://github.com/doubleloopapp/shortcut_ruby'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = 'exe'
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 2.1'
|
21
|
+
spec.add_development_dependency 'dotenv', '~> 2.1'
|
22
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
24
|
+
spec.add_development_dependency 'vcr', '~> 6.0'
|
25
|
+
spec.add_development_dependency 'webmock', '~> 3.7'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shortcut_ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.11.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Carey
|
8
|
+
- Jake Sorce
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-09-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '2.1'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '2.1'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: dotenv
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.1'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.1'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '13.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '13.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '3.0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: vcr
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '6.0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '6.0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: webmock
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '3.7'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '3.7'
|
98
|
+
description:
|
99
|
+
email:
|
100
|
+
- engineering@doubleloop.app
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- ".rspec"
|
107
|
+
- ".travis.yml"
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/console
|
113
|
+
- bin/setup
|
114
|
+
- lib/shortcut_ruby.rb
|
115
|
+
- lib/shortcut_ruby/constants.rb
|
116
|
+
- lib/shortcut_ruby/path_builder.rb
|
117
|
+
- lib/shortcut_ruby/request.rb
|
118
|
+
- lib/shortcut_ruby/version.rb
|
119
|
+
- shortcut_ruby.gemspec
|
120
|
+
homepage: https://github.com/doubleloopapp/shortcut_ruby
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubygems_version: 3.1.6
|
140
|
+
signing_key:
|
141
|
+
specification_version: 4
|
142
|
+
summary: A lightweight Ruby wrapper for the Shortcut REST API.
|
143
|
+
test_files: []
|