cloudapp 0.0.4 → 0.0.5

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.
data/Gemfile.lock CHANGED
@@ -1,17 +1,38 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cloudapp (0.0.1)
4
+ cloudapp (0.0.4)
5
+ addressable
6
+ faraday (~> 0.8.0.rc2)
7
+ formatador
8
+ leadlight
5
9
  main
6
10
  typhoeus
7
- yajl-ruby
8
11
 
9
12
  GEM
10
13
  remote: http://rubygems.org/
11
14
  specs:
15
+ addressable (2.2.6)
12
16
  arrayfields (4.7.4)
13
- chronic (0.6.6)
17
+ chronic (0.6.7)
18
+ crack (0.3.1)
19
+ diff-lcs (1.1.3)
20
+ fail-fast (1.0.0)
21
+ faraday (0.8.0.rc2)
22
+ multipart-post (~> 1.1)
14
23
  fattr (2.2.1)
24
+ formatador (0.2.1)
25
+ hookr (1.1.1)
26
+ fail-fast (= 1.0.0)
27
+ leadlight (0.0.3)
28
+ addressable
29
+ faraday
30
+ fattr
31
+ hookr
32
+ link_header
33
+ mime-types
34
+ multi_json
35
+ link_header (0.0.5)
15
36
  main (4.8.1)
16
37
  arrayfields (~> 4.7.4)
17
38
  chronic (~> 0.6.2)
@@ -19,10 +40,23 @@ GEM
19
40
  map (~> 5.1.0)
20
41
  map (5.1.0)
21
42
  mime-types (1.17.2)
43
+ multi_json (1.0.4)
44
+ multipart-post (1.1.4)
22
45
  rake (0.9.2.2)
46
+ rspec (2.8.0)
47
+ rspec-core (~> 2.8.0)
48
+ rspec-expectations (~> 2.8.0)
49
+ rspec-mocks (~> 2.8.0)
50
+ rspec-core (2.8.0)
51
+ rspec-expectations (2.8.0)
52
+ diff-lcs (~> 1.1.2)
53
+ rspec-mocks (2.8.0)
23
54
  typhoeus (0.3.3)
24
55
  mime-types
25
- yajl-ruby (1.1.0)
56
+ vcr (2.0.0.rc1)
57
+ webmock (1.7.10)
58
+ addressable (~> 2.2, > 2.2.5)
59
+ crack (>= 0.1.7)
26
60
 
27
61
  PLATFORMS
28
62
  ruby
@@ -30,3 +64,6 @@ PLATFORMS
30
64
  DEPENDENCIES
31
65
  cloudapp!
32
66
  rake
67
+ rspec
68
+ vcr (~> 2.0.0.rc1)
69
+ webmock
data/README.md CHANGED
@@ -1,23 +1,65 @@
1
- # CloudApp CLI
1
+ # CloudApp Ruby Client
2
2
 
3
- Experience all the pleasures of sharing with CloudApp now in your terminal.
3
+ Interact with the [CloudApp API][] from Ruby. Comes with a command line
4
+ interface to CloudApp as an added bonus.
4
5
 
6
+ [cloudapp api]: http://developer.getcloudapp.com
5
7
 
6
- ## Desired Usage
7
8
 
8
- **These commands don't exist yet. They're just here as a wish list of sorts.**
9
+ ## Usage
9
10
 
10
- The goal of `cloudapp` is to be Unix-friendly and handle the following uses:
11
+ _Usage from Ruby is still a work in progress._
11
12
 
12
- - Bookmark a link: `cloudapp bookmark http://getcloudapp.com`
13
- - Share a file: `cloudapp file screenshot.png`
14
- - Share several files: `cloudapp file *.png`
15
- - Archive and share several files: `cloudapp file *.png --archive`
16
- - Encrypt and share a file: `cloudapp file launch_codes.txt --encrypt`
17
- - Download and decrypt an encrypted drop: `cloudapp http://cl.ly/abc123 def456`
18
13
 
14
+ ## CLI
19
15
 
20
- ## Harness the Power
16
+ Experience all the pleasures of sharing with CloudApp now in your terminal. The
17
+ goal of `cloudapp` is to be simple and Unix-friendly.
18
+
19
+ ### Installation
20
+
21
+ Installation is done via RubyGems: `gem install cloudapp`.
22
+
23
+ ### Usage
24
+
25
+ - Bookmark a link: `cloudapp new bookmark http://getcloudapp.com`
26
+ - Share a file: `cloudapp new file screenshot.png`
27
+ - List newest drops: `cloudapp list [--count=5]`
28
+
29
+ ### Wish List
30
+
31
+ Some baseic features that should be added:
32
+
33
+ - Download a drop: `cloudapp download http://cl.ly/abc123`
34
+
35
+ A little more flare would be swell.
36
+
37
+ - Share several files: `cloudapp new *.png`
38
+ - Archive and share several files: `cloudapp new --archive *.png`
39
+ - Share everything at once: `cloudapp new http://google.com *.png http://bing.com`
40
+ - Encrypt and share a file: `cloudapp new --encrypt launch_codes.txt`
41
+ - Download and decrypt and encrypted drop: `cloudapp download http://cl.ly/abc123 def456`
42
+
43
+ While we're dreaming, what could you do if it kept a local copy of all your
44
+ drops? Bonus points for a light weight daemon that kept everything in sync.
45
+
46
+ - Find all your screen shots: `cloudapp list /^screen ?shot.*\.png$/`
47
+ - Trash all your stale drops: `cloudapp delete --last-viewed="> 1 month ago"`
48
+ - See your drop views in real time: `cloudapp --tail`
49
+
50
+ There's bound to be a better way to express some of these commands, but you get
51
+ the picture.
52
+
53
+ ### Authentication
54
+
55
+ As of right now, `cloudapp` makes use of
56
+ [`main`'s built-in configuration handling][main-config] to store your CloudApp
57
+ credentials **in plain text** at `~/.cloudapp/config.yml`. This file will be
58
+ created and opened in your `$EDITOR` the first time it's needed.
59
+
60
+ [main-config]: https://github.com/ahoward/main/blob/master/README.erb#L220-232
61
+
62
+ ### Harness the Power
21
63
 
22
64
  Sure you could copy the new drop's link by piping the output to `pbcopy`, but
23
65
  that's a lot of extra key presses. Instead, try setting this super secret
data/Rakefile CHANGED
@@ -43,13 +43,13 @@ end
43
43
  #
44
44
  #############################################################################
45
45
 
46
- # require 'rspec'
47
- # require 'rspec/core/rake_task'
46
+ require 'rspec'
47
+ require 'rspec/core/rake_task'
48
48
 
49
- # desc "Run all specs"
50
- # task RSpec::Core::RakeTask.new('spec')
49
+ desc "Run all specs"
50
+ task RSpec::Core::RakeTask.new('spec')
51
51
 
52
- # task :default => "spec"
52
+ task :default => "spec"
53
53
 
54
54
  #############################################################################
55
55
  #
data/bin/cloudapp CHANGED
@@ -1,67 +1,81 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'formatador'
3
4
  require 'main'
4
- require 'typhoeus'
5
- require 'yajl'
6
5
 
7
6
  # Explicitly require yaml because ruby 1.9.2 (and greater?) define YAML which
8
7
  # causes main to not require it.
9
8
  require 'yaml'
10
9
 
10
+ require 'cloudapp/drop_service'
11
+ require 'ostruct'
12
+
11
13
  Main do
14
+ def identity
15
+ OpenStruct.new email: config[:email], password: config[:password]
16
+ end
17
+
18
+ def service
19
+ CloudApp::DropService.as_identity(identity).tap do |service|
20
+ service.logger.level = Logger::WARN
21
+ end
22
+ end
23
+
12
24
  config email: 'arthur@dent.com', password: 'towel'
13
25
 
14
- # Bookmark a link: `cloudapp bookmark http://getcloudapp.com`
15
- mode :bookmark do
16
- argument('uri') { cast :uri }
26
+ mode :new do
17
27
 
18
- def run
19
- uri = params[:uri].value
20
- body = Yajl::Encoder.encode item: { redirect_url: uri }
21
- response = Typhoeus::Request.post 'http://my.cl.ly/items',
22
- verbose: ENV.fetch('DEBUG', false),
23
- body: body,
24
- username: config[:email],
25
- password: config[:password],
26
- auth_method: :digest,
27
- headers: { 'Accept' => 'application/json',
28
- 'Content-Type' => 'application/json' }
29
-
30
- json = Yajl::Parser.new(symbolize_keys: true).parse(response.body)
31
- puts json.fetch :url
28
+ # Bookmark a link: `cloudapp new bookmark http://getcloudapp.com`
29
+ mode :bookmark do
30
+ argument('url') { cast :uri }
31
+
32
+ def run
33
+ response = service.create url: params[:url].value
34
+ puts response.fetch 'url'
35
+ end
32
36
  end
33
- end
34
37
 
35
- # Share a file: `cloudapp file screenshot.png`
36
- mode :file do
37
- argument('file') { cast :pathname }
38
+ # Share a file: `cloudapp new file screenshot.png`
39
+ mode :file do
40
+ argument('file') { cast :pathname }
38
41
 
39
- def run
40
- api_params = { username: config[:email],
41
- password: config[:password],
42
- auth_method: :digest,
43
- headers: { 'Accept' => 'application/json',
44
- 'Content-Type' => 'application/json' }}
42
+ def run
43
+ response = service.create path: params[:file].value
44
+ puts response.fetch 'url'
45
+ end
46
+ end
47
+ end
45
48
 
46
- details_response = Typhoeus::Request.get 'http://my.cl.ly/items/new',
47
- api_params.merge(verbose: ENV.fetch('DEBUG', false))
49
+ # List newest drops: `cloudapp list [--count=5]`
50
+ mode :list do
51
+ option 'count', 'n' do
52
+ argument :optional
53
+ cast :integer
54
+ default 20
55
+ end
48
56
 
49
- details = Yajl::Parser.new(symbolize_keys: true).
50
- parse(details_response.body)
57
+ def run
58
+ count = params[:count].value
59
+ drops = service.drops count
51
60
 
52
- url = details[:url]
53
- upload_params = details[:params].merge file: File.open(params[:file].value)
54
- upload_response = Typhoeus::Request.post url,
55
- verbose: ENV.fetch('DEBUG', false),
56
- params: upload_params
61
+ name_width = drops.map {|drop| drop['name'].size }.max
62
+ views_width = [ 5, drops.map {|drop| drop['view_counter'].to_s.size }.max ].max
63
+ link_width = drops.map {|drop| drop['url'].size }.max
57
64
 
58
- follow_url = upload_response.headers_hash['Location']
65
+ header = [ "[bold]#{ 'Name' .ljust(name_width) }[/]",
66
+ "[bold]#{ 'Link' .ljust(link_width) }[/]",
67
+ "[bold]#{ 'Views'.center(views_width) }[/]" ].join(' ')
68
+ Formatador.display_line header
59
69
 
60
- create_response = Typhoeus::Request.get follow_url,
61
- api_params.merge(verbose: ENV.fetch('DEBUG', false))
70
+ lines = drops.map do |drop|
71
+ [ drop['name'].ljust(name_width),
72
+ drop['url'],
73
+ drop['view_counter'].to_s.center(views_width)
74
+ ].join ' '
75
+ end
62
76
 
63
- json = Yajl::Parser.new(symbolize_keys: true).parse(create_response.body)
64
- puts json.fetch :url
77
+ Formatador.display_lines lines
65
78
  end
66
79
  end
80
+
67
81
  end
data/cloudapp.gemspec CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  ## If your rubyforge_project name is different, then edit it and comment out
14
14
  ## the sub! line in the Rakefile
15
15
  s.name = 'cloudapp'
16
- s.version = '0.0.4'
17
- s.date = '2012-02-01'
16
+ s.version = '0.0.5'
17
+ s.date = '2012-02-06'
18
18
  s.rubyforge_project = 'cloudapp'
19
19
 
20
20
  ## Make sure your summary is short. The description may be as long
@@ -43,13 +43,19 @@ Gem::Specification.new do |s|
43
43
 
44
44
  ## List your runtime dependencies here. Runtime dependencies are those
45
45
  ## that are needed for an end user to actually USE your code.
46
+ s.add_dependency 'addressable'
47
+ s.add_dependency 'faraday', '~> 0.8.0.rc2'
48
+ s.add_dependency 'formatador'
49
+ s.add_dependency 'leadlight'
46
50
  s.add_dependency 'main'
47
51
  s.add_dependency 'typhoeus'
48
- s.add_dependency 'yajl-ruby'
49
52
 
50
53
  ## List your development dependencies here. Development dependencies are
51
54
  ## those that are only needed during development
52
55
  s.add_development_dependency 'rake'
56
+ s.add_development_dependency 'rspec'
57
+ s.add_development_dependency 'vcr', '~> 2.0.0.rc1'
58
+ s.add_development_dependency 'webmock'
53
59
 
54
60
  ## Leave this section as-is. It will be automatically generated from the
55
61
  ## contents of your Git repository via the gemspec task. DO NOT REMOVE
@@ -64,6 +70,20 @@ Gem::Specification.new do |s|
64
70
  bin/cloudapp
65
71
  cloudapp.gemspec
66
72
  lib/cloudapp.rb
73
+ lib/cloudapp/digestable_typhoeus.rb
74
+ lib/cloudapp/drop_service.rb
75
+ spec/cassettes/CloudApp_DropService/_create/creates_a_bookmark.yml
76
+ spec/cassettes/CloudApp_DropService/_create/creates_a_bookmark_with_a_name.yml
77
+ spec/cassettes/CloudApp_DropService/_create/creates_a_file.yml
78
+ spec/cassettes/CloudApp_DropService/_drops/limits_list_to_the_given_number_of_drops.yml
79
+ spec/cassettes/CloudApp_DropService/_drops/returns_a_list_of_drops.yml
80
+ spec/cassettes/CloudApp_DropService/_drops/returns_a_list_of_trashed_drops.yml
81
+ spec/cloudapp/drop_service_spec.rb
82
+ spec/helper.rb
83
+ spec/support/files/favicon.ico
84
+ spec/support/stub_class_or_module.rb
85
+ spec/support/vcr.rb
86
+ spec/support/vcr_rspec.rb
67
87
  ]
68
88
  # = MANIFEST =
69
89
 
@@ -0,0 +1,17 @@
1
+ require 'typhoeus'
2
+
3
+ class DigestableTyphoeus < Faraday::Adapter::Typhoeus
4
+ def request(env)
5
+ super.tap { |request| configure_authentication request, env }
6
+ end
7
+
8
+ def configure_authentication(request, env)
9
+ authentication = request_options(env)[:authentication]
10
+
11
+ request.auth_method = 'digest'
12
+ request.username = authentication[:username]
13
+ request.password = authentication[:password]
14
+ end
15
+ end
16
+
17
+ Faraday.register_middleware :adapter, digestable_typhoeus: DigestableTyphoeus
@@ -0,0 +1,159 @@
1
+ require 'leadlight'
2
+ require 'addressable/uri'
3
+ require 'cloudapp/digestable_typhoeus'
4
+
5
+ # Leadlight service for mucking about with drops in the CloudApp API.
6
+ #
7
+ # Usage:
8
+ #
9
+ # service = DropSerivce.as_identity email: 'arthur@dent.com',
10
+ # password: 'towel'
11
+ #
12
+ # # List all your drops
13
+ # service.drops
14
+ #
15
+ # # Create a bookmark
16
+ # service.create url: 'http://getcloudapp.com', name: 'CloudApp'
17
+ #
18
+ # # Upload a file
19
+ # service.create path: #<Pathname>, name: 'Screen shot'
20
+ #
21
+ # # List all your trashed drops
22
+ # service.trash
23
+ #
24
+ # # Delete a drop
25
+ # service.drops.get(123).destroy
26
+ #
27
+ # # Delete a drop from the trash
28
+ # service.trash.get(123).destroy
29
+ #
30
+ # # Restore a drop from the trash
31
+ # service.trash.get(123).restore
32
+ #
33
+ module CloudApp
34
+ class DropService
35
+ Leadlight.build_connection_common do |c|
36
+ c.request :multipart
37
+ c.request :url_encoded
38
+ c.adapter :digestable_typhoeus
39
+ end
40
+
41
+ Leadlight.build_service(self) do
42
+ url 'http://my.cl.ly'
43
+
44
+ # Add links present in the response body.
45
+ # { links: { self: "...", next: "...", prev: "..." } }
46
+ tint 'links' do
47
+ match Hash
48
+ match { key? 'links' }
49
+ self['links'].each do |rel, href|
50
+ add_link href, rel
51
+ end
52
+ end
53
+
54
+ tint 'root' do
55
+ match_path '/'
56
+ add_link '/items?api_version=1.2', 'drops', 'List owned drops'
57
+ add_link_template '/items?api_version=1.2&per_page={per_page=20}', 'paginated_drops'
58
+ add_link_template '/items?api_version=1.2&per_page={per_page=20}&deleted=true', 'paginated_trash'
59
+ end
60
+
61
+ tint 'create' do
62
+ match_path '/items'
63
+ # use "#{__location__}/new" when api_version isn't required in the query
64
+ # string.
65
+ add_link '/items/new', 'create_file', 'Create a new file drop'
66
+ end
67
+
68
+ # Add a rel=child link for each item in the list.
69
+ # { items: [{ id: 123, href: "..."}] }
70
+ tint 'drops' do
71
+ match_path '/items'
72
+ match { key? 'items' }
73
+ add_link_set 'child', :get do
74
+ self['items'].map do |item|
75
+ { href: item['href'], title: item['id'] }
76
+ end
77
+ end
78
+ end
79
+
80
+ # Add convenience methods on a drop representation to destroy and restore
81
+ # from the trash.
82
+ tint 'drop' do
83
+ match_template '/items/{id}'
84
+ extend do
85
+ def destroy
86
+ link('self').delete.
87
+ raise_on_error.submit_and_wait
88
+ end
89
+
90
+ def restore
91
+ link('self').put({}, deleted: true, item: { deleted_at: nil }).
92
+ raise_on_error.submit_and_wait
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def identity=(identity)
99
+ connection.options[:authentication] = { username: identity.email,
100
+ password: identity.password }
101
+ end
102
+
103
+ def self.as_identity(identity, service_options = {})
104
+ new(service_options).tap do |service|
105
+ service.identity = identity
106
+ end
107
+ end
108
+
109
+ def drops(count = 20)
110
+ root.paginated_drops(per_page: count)['items']
111
+ end
112
+
113
+ def trash(count = 20)
114
+ root.paginated_trash(per_page: count)['items']
115
+ end
116
+
117
+ def create(attributes)
118
+ body = { item: {}}
119
+
120
+ body[:item][:name] = attributes[:name] if attributes.key? :name
121
+ body[:item][:redirect_url] = attributes[:url] if attributes.key? :url
122
+
123
+ if attributes.key? :path
124
+ create_file attributes[:path], body
125
+ else
126
+ create_bookmark body
127
+ end
128
+ end
129
+
130
+ protected
131
+
132
+ def create_bookmark(body)
133
+ root.link('drops').post({}, body).raise_on_error.
134
+ submit_and_wait do |new_drop|
135
+ return new_drop
136
+ end
137
+ end
138
+
139
+ def create_file(path, body)
140
+ root.drops.link('create_file').get.raise_on_error.
141
+ submit_and_wait do |details|
142
+ uri = Addressable::URI.parse details['url']
143
+ file = Faraday::UploadIO.new File.open(path), 'image/png'
144
+ payload = details['params'].merge file: file
145
+
146
+ conn = Faraday.new(url: uri.site) do |builder|
147
+ builder.request :multipart
148
+ builder.request :url_encoded
149
+ builder.adapter :typhoeus
150
+ end
151
+
152
+ conn.post(uri.request_uri, payload).on_complete do |env|
153
+ get(env[:response_headers]['Location']).raise_on_error.
154
+ submit_and_wait { |created| return created }
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
data/lib/cloudapp.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module CloudApp
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end