typekitable 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.ruby-version +1 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +13 -0
- data/bin/typekitable +6 -0
- data/features/authentication.feature +13 -0
- data/features/kits.feature +22 -0
- data/features/step_definitions/kit_steps.rb +5 -0
- data/features/support/env.rb +15 -0
- data/lib/typekitable.rb +11 -0
- data/lib/typekitable/authenticator.rb +22 -0
- data/lib/typekitable/commander.rb +51 -0
- data/lib/typekitable/request.rb +66 -0
- data/lib/typekitable/request_fetcher.rb +72 -0
- data/lib/typekitable/response_formatter.rb +120 -0
- data/lib/typekitable/tokenizer.rb +23 -0
- data/lib/typekitable/version.rb +3 -0
- data/spec/fixtures/vcr_cassettes/add_kit.yml +63 -0
- data/spec/fixtures/vcr_cassettes/add_kit_maximum_error.yml +61 -0
- data/spec/fixtures/vcr_cassettes/add_kit_unauthorized_error.yml +61 -0
- data/spec/fixtures/vcr_cassettes/kits.yml +61 -0
- data/spec/fixtures/vcr_cassettes/kits_error.yml +59 -0
- data/spec/fixtures/vcr_cassettes/libraries.yml +61 -0
- data/spec/lib/typekitable/commander_spec.rb +17 -0
- data/spec/lib/typekitable/request_fetcher_spec.rb +67 -0
- data/spec/lib/typekitable/request_spec.rb +151 -0
- data/spec/lib/typekitable/response_formatter_spec.rb +199 -0
- data/spec/lib/typekitable/tokenizer_spec.rb +48 -0
- data/spec/spec_helper.rb +23 -0
- data/typekitable.gemspec +24 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 07bb313e5d918cc2e3eb1315fd657baa888d4368
|
4
|
+
data.tar.gz: 9862a8897cc8be29054bb2b05d4509198df538fe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a5022b0861150976b4afa6a2a74659521f5bdba0c9194fc3a575dea2db1ac535b69bf4eb9ff45455c14743ff63581d55466b4aa45cc557b956fbfd3a902c1cf7
|
7
|
+
data.tar.gz: 828707e0d58dcb70564740b6637c5782a80e8ec5a73a3a0ef267b21f7987cf70803f978dacbf2f39e9603ab2eb3a4b2064b2c67a3e92e194f9e85279e3b3e826
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.0
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Persa Zula
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Typekitable
|
2
|
+
|
3
|
+
A CLI for interacting with your Typekit kits.
|
4
|
+
|
5
|
+
Get an API token [here](https://typekit.com/account/tokens) and get started!
|
6
|
+
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Install it yourself as:
|
11
|
+
|
12
|
+
$ gem install typekitable
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
Command | Description
|
17
|
+
------------------------------- |-------------------------------------
|
18
|
+
typekitable kit_add NAME DOMAINS| # Adds a new kit
|
19
|
+
typekitable kit_info KIT_ID | # Get information on a specific kit
|
20
|
+
typekitable kit_list | # Get a list of all of your kits
|
21
|
+
typekitable kit_publish KIT_ID | # Publish a draft kit
|
22
|
+
typekitable re-authenticate | # Reset your Typekit API token
|
23
|
+
|
24
|
+
## Tests
|
25
|
+
|
26
|
+
Unit tests are written with [rspec](https://github.com/rspec/rspec) and can be run with the command `rake spec`
|
27
|
+
|
28
|
+
Feature tests are written with [cucumber](https://github.com/cucumber/cucumber) and [aruba](https://github.com/cucumber/aruba) and can be run with the command `rake features`
|
29
|
+
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
1. Fork it ( https://github.com/pzula/typekitable/fork )
|
34
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
35
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
36
|
+
4. Add spec coverage
|
37
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
38
|
+
6. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'cucumber'
|
4
|
+
require 'cucumber/rake/task'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
8
|
+
t.cucumber_opts = "features --format pretty"
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
12
|
+
spec.rspec_opts = "--format documentation --color"
|
13
|
+
end
|
data/bin/typekitable
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
@mock_home_directory
|
2
|
+
Feature: As a user, I can enter my API token credentials
|
3
|
+
|
4
|
+
Scenario: Invoking the CLI to re-authenticate with a token results in success
|
5
|
+
Given I run `bundle exec ruby bin/typekitable re-authenticate` interactively
|
6
|
+
And I type "y"
|
7
|
+
And I type "4d6141e7c82cb30affebcc392abc2ce3ab0ea4c1"
|
8
|
+
Then the output should contain "Thank you, your Typekit API token has been stored"
|
9
|
+
|
10
|
+
Scenario: Invoking the CLI to re-authenticate without a token gives me API token generation information
|
11
|
+
Given I run `bundle exec ruby bin/typekitable re-authenticate` interactively
|
12
|
+
And I type "n"
|
13
|
+
Then the output should contain "Generate an API token"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
@mock_home_directory
|
2
|
+
Feature: As a user with a stored token, I can interact with the commands
|
3
|
+
|
4
|
+
Background:
|
5
|
+
* I have a stored token
|
6
|
+
|
7
|
+
Scenario: I can get a list of all of my commands
|
8
|
+
Given I run `bundle exec ruby bin/typekitable help`
|
9
|
+
Then the output should contain "Commands"
|
10
|
+
|
11
|
+
Scenario: I can get a list of my kits
|
12
|
+
Given I run `bundle exec ruby bin/typekitable kit_list`
|
13
|
+
Then the output should contain "rmc4kry"
|
14
|
+
|
15
|
+
Scenario: I can get information about a kit
|
16
|
+
Given I run `bundle exec ruby bin/typekitable kit_info "rmc4kry"`
|
17
|
+
Then the output should contain "droid kit"
|
18
|
+
|
19
|
+
Scenario: I can publish a draft kit
|
20
|
+
Given I run `bundle exec ruby bin/typekitable kit_publish "rmc4kry"`
|
21
|
+
Then the output should contain "published"
|
22
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'cucumber'
|
2
|
+
require 'aruba/cucumber'
|
3
|
+
|
4
|
+
Before do
|
5
|
+
@dirs = [File.expand_path(File.join(File.dirname(__FILE__), '../..'))]
|
6
|
+
end
|
7
|
+
|
8
|
+
Before '@mocked_home_directory' do
|
9
|
+
set_env 'HOME', File.expand_path(File.join(current_dir, 'home'))
|
10
|
+
FileUtils.mkdir_p ENV['HOME']
|
11
|
+
end
|
12
|
+
|
13
|
+
After '@mocked_home_directory' do
|
14
|
+
FileUtils.rm_f ENV['HOME']
|
15
|
+
end
|
data/lib/typekitable.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "net/http"
|
3
|
+
require "formatador"
|
4
|
+
require "json"
|
5
|
+
require "typekitable/version"
|
6
|
+
require "typekitable/commander"
|
7
|
+
require "typekitable/tokenizer"
|
8
|
+
require "typekitable/authenticator"
|
9
|
+
require "typekitable/request_fetcher"
|
10
|
+
require "typekitable/request"
|
11
|
+
require "typekitable/response_formatter"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Typekitable
|
2
|
+
class Authenticator < Thor
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
no_commands do
|
6
|
+
def get_authentication
|
7
|
+
if no?("Do you have a Typekit API token? [y/N]", :yellow)
|
8
|
+
say("Generate an API token at https://typekit.com/account/tokens and come back once you have it, we will only ask for it once", :red)
|
9
|
+
exit
|
10
|
+
else
|
11
|
+
request_token
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_token
|
16
|
+
token = ask("What is your Typekit API token?:", :red)
|
17
|
+
Tokenizer.store(token)
|
18
|
+
say("Thank you, your Typekit API token has been stored.", :green)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Typekitable
|
2
|
+
class Commander < Thor
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
desc "re-authenticate", "Reset your Typekit API token"
|
6
|
+
def re_authenticate
|
7
|
+
Authenticator.new.get_authentication
|
8
|
+
invoke :help
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "kit_list", "Get a list of all of your kits"
|
12
|
+
def kit_list
|
13
|
+
result = formatted_response(response_for_command(:kit_list))
|
14
|
+
result.output_heading
|
15
|
+
result.output_body
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "kit_add NAME DOMAINS", "Adds a new kit"
|
19
|
+
def kit_add(name, domains)
|
20
|
+
response = response_for_command(:kit_add, { :name => name, :domains => [domains] })
|
21
|
+
result = formatted_response(response)
|
22
|
+
result.output_heading
|
23
|
+
result.output_body
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "kit_info KIT_ID", "Get information on a specific kit"
|
27
|
+
def kit_info(kit_id)
|
28
|
+
result = formatted_response(response_for_command(:kit_info, {}, kit_id))
|
29
|
+
result.output_heading
|
30
|
+
result.output_body
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "kit_publish KIT_ID", "Publish a draft kit"
|
34
|
+
def kit_publish(kit_id)
|
35
|
+
result = formatted_response(response_for_command(:kit_publish, {}, kit_id))
|
36
|
+
result.output_heading
|
37
|
+
result.output_body
|
38
|
+
end
|
39
|
+
|
40
|
+
no_commands do
|
41
|
+
def response_for_command(command_type, options = {}, resource_id = nil)
|
42
|
+
RequestFetcher.new(command_type, options, resource_id).response
|
43
|
+
end
|
44
|
+
|
45
|
+
def formatted_response(response)
|
46
|
+
ResponseFormatter.new(response)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Typekitable
|
2
|
+
class Request
|
3
|
+
attr_reader :path, :verb, :parameters
|
4
|
+
|
5
|
+
BASE_URL = "https://typekit.com/api/v1/json/"
|
6
|
+
|
7
|
+
def initialize(path, verb, parameters)
|
8
|
+
@path = path
|
9
|
+
@verb = verb
|
10
|
+
@parameters = parameters
|
11
|
+
end
|
12
|
+
|
13
|
+
def token
|
14
|
+
Tokenizer.get_token
|
15
|
+
end
|
16
|
+
|
17
|
+
def response
|
18
|
+
case verb
|
19
|
+
when "GET" then get_request_response
|
20
|
+
when "POST" then post_request_response
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def uri
|
25
|
+
URI.parse(BASE_URL + path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def headers
|
29
|
+
{ "X-Typekit-Token" => token }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def http_request
|
35
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |https|
|
36
|
+
yield(https, uri)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_request_response
|
41
|
+
http_request do |https, uri|
|
42
|
+
response = https.get(uri.path, headers)
|
43
|
+
build_response(response.code, response.message, response.body)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def post_request_response
|
48
|
+
req = Net::HTTP::Post.new(uri.path, headers)
|
49
|
+
req.set_form_data(parameters)
|
50
|
+
http_request do |https, uri|
|
51
|
+
response = https.request(req)
|
52
|
+
build_response(response.code, response.message, response.body)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_response(code, message, body)
|
57
|
+
Response.new(:code => code, :message => message, :body => body)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Response < Struct.new(:code, :message, :body)
|
62
|
+
def initialize(from_hash)
|
63
|
+
super(*from_hash.values_at(*members))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Typekitable
|
2
|
+
class RequestFetcher
|
3
|
+
attr_reader :type, :options, :resource_id
|
4
|
+
|
5
|
+
VALID_TYPES = [:kit_list, :kit_add, :kit_info, :kit_publish]
|
6
|
+
VALID_OPTIONS = {
|
7
|
+
:kit_add => [:name, :domains]
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(type, options = {}, resource_id = nil)
|
11
|
+
@type = validate_type(type)
|
12
|
+
@options = validate_options(options)
|
13
|
+
@resource_id = resource_id
|
14
|
+
end
|
15
|
+
|
16
|
+
def request_path
|
17
|
+
request_config[type][:request_path]
|
18
|
+
end
|
19
|
+
|
20
|
+
def verb
|
21
|
+
request_config[type][:verb]
|
22
|
+
end
|
23
|
+
|
24
|
+
def parameters
|
25
|
+
options
|
26
|
+
end
|
27
|
+
|
28
|
+
def response
|
29
|
+
Request.new(request_path, verb, parameters).response
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def request_config
|
35
|
+
{
|
36
|
+
:kit_list => {
|
37
|
+
:request_path => "kits",
|
38
|
+
:verb => "GET"
|
39
|
+
},
|
40
|
+
:kit_add => {
|
41
|
+
:request_path => "kits",
|
42
|
+
:verb => "POST"
|
43
|
+
},
|
44
|
+
:kit_info => {
|
45
|
+
:request_path => "kits/#{resource_id}",
|
46
|
+
:verb => "GET"
|
47
|
+
},
|
48
|
+
:kit_publish => {
|
49
|
+
:request_path => "kits/#{resource_id}/publish",
|
50
|
+
:verb => "POST"
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_type(type)
|
56
|
+
raise InvalidTypeError unless VALID_TYPES.include?(type)
|
57
|
+
|
58
|
+
return type
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_options(options_list)
|
62
|
+
if options_list.empty? || VALID_OPTIONS[:kit_add].all? {|element| options_list.keys.include?(element) }
|
63
|
+
return options_list
|
64
|
+
else
|
65
|
+
raise InvalidOptionsError
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class InvalidTypeError < StandardError; end
|
71
|
+
class InvalidOptionsError < StandardError; end
|
72
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Typekitable
|
2
|
+
class ResponseFormatter
|
3
|
+
attr_reader :response
|
4
|
+
|
5
|
+
ERRORS = {
|
6
|
+
"400" => "Error in data sent",
|
7
|
+
"401" => "Not authorized",
|
8
|
+
"403" => "Rate limit reached",
|
9
|
+
"404" => "Not found",
|
10
|
+
"500" => "Unable to process request",
|
11
|
+
"503" => "Service is offline"
|
12
|
+
}
|
13
|
+
|
14
|
+
def initialize(response)
|
15
|
+
@response = response
|
16
|
+
end
|
17
|
+
|
18
|
+
def error?
|
19
|
+
ERRORS.keys.include?(response.code)
|
20
|
+
end
|
21
|
+
|
22
|
+
def parsed_body
|
23
|
+
JSON.parse(response.body, :symbolize_names => true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def table_headers
|
27
|
+
if error?
|
28
|
+
error_key
|
29
|
+
elsif collection?
|
30
|
+
collection_headers
|
31
|
+
elsif singular_resource?
|
32
|
+
singular_resource_headers
|
33
|
+
else
|
34
|
+
[main_key]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def table_body
|
39
|
+
if error?
|
40
|
+
error_message
|
41
|
+
elsif collection?
|
42
|
+
collection_data
|
43
|
+
elsif singular_resource?
|
44
|
+
singular_resource_data
|
45
|
+
else
|
46
|
+
[parsed_body]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def data_heading
|
51
|
+
main_key.to_s.capitalize
|
52
|
+
end
|
53
|
+
|
54
|
+
def output_heading
|
55
|
+
display_line(data_heading)
|
56
|
+
end
|
57
|
+
|
58
|
+
def output_body
|
59
|
+
display_table(table_body, table_headers)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def data_element
|
65
|
+
parsed_body[main_key]
|
66
|
+
end
|
67
|
+
|
68
|
+
def singular_resource?
|
69
|
+
!error? && data_element.is_a?(Hash)
|
70
|
+
end
|
71
|
+
|
72
|
+
def collection?
|
73
|
+
!error? && data_element.is_a?(Array)
|
74
|
+
end
|
75
|
+
|
76
|
+
def empty_response?
|
77
|
+
data_element.any?
|
78
|
+
end
|
79
|
+
|
80
|
+
def main_key
|
81
|
+
parsed_body.keys.first
|
82
|
+
end
|
83
|
+
|
84
|
+
def collection_headers
|
85
|
+
data_element.map do |data|
|
86
|
+
data.keys
|
87
|
+
end.flatten.uniq
|
88
|
+
end
|
89
|
+
|
90
|
+
def collection_data
|
91
|
+
data_element.map do |line|
|
92
|
+
line
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def singular_resource_headers
|
97
|
+
data_element.keys
|
98
|
+
end
|
99
|
+
|
100
|
+
def singular_resource_data
|
101
|
+
[data_element]
|
102
|
+
end
|
103
|
+
|
104
|
+
def error_key
|
105
|
+
[response.code]
|
106
|
+
end
|
107
|
+
|
108
|
+
def error_message
|
109
|
+
[{response.code => data_element.pop}]
|
110
|
+
end
|
111
|
+
|
112
|
+
def display_line(line)
|
113
|
+
Formatador.display_line(line)
|
114
|
+
end
|
115
|
+
|
116
|
+
def display_table(table_data, headers)
|
117
|
+
Formatador.display_table(table_data, headers)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|