typekitable 0.1.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.
@@ -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