typekitable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1 @@
1
+ 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'aruba'
8
+ gem 'cucumber'
9
+ gem 'vcr'
10
+ gem 'webmock'
11
+ end
@@ -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.
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require "typekitable"
3
+
4
+ exit unless Typekitable::Tokenizer.has_token? || Typekitable::Authenticator.new.get_authentication
5
+
6
+ Typekitable::Commander.start
@@ -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,5 @@
1
+ Given(/^I have a stored token$/) do
2
+ File.open(File.absolute_path('.typekitable', Dir.home), 'w') do |file|
3
+ file.write("5e4ce50b7c5b996b2fb5e65ee4a6b870b9bd3297")
4
+ end
5
+ end
@@ -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
@@ -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