cloudapp 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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