gmail_cli 0.0.1
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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +91 -0
- data/Rakefile +25 -0
- data/bin/gmail_cli +15 -0
- data/gmail_cli.gemspec +32 -0
- data/lib/gmail_cli/imap.rb +72 -0
- data/lib/gmail_cli/logger.rb +37 -0
- data/lib/gmail_cli/oauth2_helper.rb +124 -0
- data/lib/gmail_cli/shell.rb +71 -0
- data/lib/gmail_cli/tasks.rb +18 -0
- data/lib/gmail_cli/version.rb +3 -0
- data/lib/gmail_cli.rb +6 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/imap_spec.rb +88 -0
- data/spec/unit/logger_spec.rb +26 -0
- data/spec/unit/oauth2_helper_spec.rb +168 -0
- data/spec/unit/shell_spec.rb +31 -0
- data/spec/unit/tasks_spec.rb +21 -0
- metadata +219 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2, :all_on_start => false, :all_after_pass => false do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/gmail_cli/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
end
|
9
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Paul Gallagher
|
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,91 @@
|
|
1
|
+
# GmailCli
|
2
|
+
|
3
|
+
GmailCli packages the key tools and adds a sprinkling of goodness to make it just that much easier
|
4
|
+
to write primarily command-line utilities for Gmail/GoogleApps.
|
5
|
+
|
6
|
+
The primary use-case it currently covers is accessing Gmail (personal and GoogleApps) via IMAP with OAuth2.
|
7
|
+
It solves the problem of ensuring you keep access credentials refreshed without needing manual intervention,
|
8
|
+
which is critical if you are building something that is going to run as a scheduled or background process.
|
9
|
+
|
10
|
+
This gem doesn't do much of the hard lifting - it is primarily syntactic sugar and packaging for convenience.
|
11
|
+
The heavy lifting is provided by:
|
12
|
+
* [gmail_xoauth](https://github.com/nfo/gmail_xoauth) which provides OAuth2 support for Google IMAP and SMTP
|
13
|
+
* [google-api-ruby-client](https://github.com/google/google-api-ruby-client) which is the official Google API gem providing OAuth2 utilities
|
14
|
+
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
gem 'gmail_cli'
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install gmail_cli
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
There are three basic steps required:
|
33
|
+
|
34
|
+
1. Create your API project credntials in the [Google APIs console](https://code.google.com/apis/console/), from the "API Access" tab.
|
35
|
+
|
36
|
+
2. Authorize your credentials to access a specific account.
|
37
|
+
This requires human intervention to explicitly make the approval.
|
38
|
+
The authorization is typically for a limited time (1 hour).
|
39
|
+
GmailCli provides a command line utility and set of rake tasks to help you do this.
|
40
|
+
|
41
|
+
3. Connect to Gmail which will authenticate your credentials.
|
42
|
+
GmailCli provides a simple interface to do this, that takes care of resfreshing your credentials
|
43
|
+
without manual intervention so you don't have to keep going back to step 2 each time your credntials expire.
|
44
|
+
|
45
|
+
|
46
|
+
### How to authorize your OAuth2 credentials - command line approach
|
47
|
+
|
48
|
+
$ gmail_cli authorize
|
49
|
+
|
50
|
+
This will prompt you for required information (client_id, client_secret), or you can provide on the command line:
|
51
|
+
|
52
|
+
$ gmail_cli authorize --client_id 'my id' --client_secret 'my secret'
|
53
|
+
|
54
|
+
|
55
|
+
### How to authorize your OAuth2 credentials - Rake approach
|
56
|
+
|
57
|
+
In your Rakefile, include the line:
|
58
|
+
|
59
|
+
require 'gmail_cli/tasks'
|
60
|
+
|
61
|
+
Then from the command line you can:
|
62
|
+
|
63
|
+
$ rake gmail_cli:authorize
|
64
|
+
|
65
|
+
This will prompt you for required information (client_id, client_secret), or you can provide on the command line:
|
66
|
+
|
67
|
+
$ rake gmail_cli:authorize client_id='my id' client_secret='my secret'
|
68
|
+
|
69
|
+
|
70
|
+
### How to get an OAuth2-authorised IMAP connection to Gmail:
|
71
|
+
|
72
|
+
# how you store or set the credentials Hash is up to you, but it should have the following keys:
|
73
|
+
credentials = {
|
74
|
+
client_id: 'xxxx',
|
75
|
+
client_secret: 'yyyy',
|
76
|
+
refresh_token: 'zzzz'
|
77
|
+
}
|
78
|
+
imap = GmailCli.imap_connection(credentials)
|
79
|
+
|
80
|
+
On return, <tt>imap</tt> will either be an open Net::IMAP connection, or an error will have been raised. Possible exceptions include:
|
81
|
+
|
82
|
+
...
|
83
|
+
|
84
|
+
|
85
|
+
## Contributing
|
86
|
+
|
87
|
+
1. Fork it
|
88
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
89
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
90
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
91
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'gmail_cli/tasks'
|
5
|
+
|
6
|
+
desc "Run all test examples"
|
7
|
+
RSpec::Core::RakeTask.new do |t|
|
8
|
+
t.rspec_opts = ["-c", "-f progress"]
|
9
|
+
t.pattern = 'spec/**/*_spec.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => :spec
|
13
|
+
|
14
|
+
require 'rdoc/task'
|
15
|
+
RDoc::Task.new do |rdoc|
|
16
|
+
rdoc.main = "README.md"
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = "Gmail CLI"
|
19
|
+
rdoc.rdoc_files.include('README*', 'lib/**/*.rb')
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Open an irb session preloaded with this library"
|
23
|
+
task :console do
|
24
|
+
sh "irb -rubygems -I lib -r gmail_cli.rb"
|
25
|
+
end
|
data/bin/gmail_cli
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
|
4
|
+
require 'gmail_cli'
|
5
|
+
require 'getoptions'
|
6
|
+
|
7
|
+
begin
|
8
|
+
options = GetOptions.new(GmailCli::Shell::OPTIONS)
|
9
|
+
GmailCli::Shell.new(options,ARGV).run
|
10
|
+
rescue Exception => e
|
11
|
+
$stderr.puts "That wasn't meant to happen! #{e.message}"
|
12
|
+
GmailCli::Shell.usage
|
13
|
+
end
|
14
|
+
|
15
|
+
|
data/gmail_cli.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gmail_cli/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gmail_cli"
|
8
|
+
spec.version = GmailCli::VERSION
|
9
|
+
spec.authors = ["Paul Gallagher"]
|
10
|
+
spec.email = ["gallagher.paul@gmail.com"]
|
11
|
+
spec.description = %q{A simple toolbox for build utilities that talk to Gmail with OAuth2}
|
12
|
+
spec.summary = %q{GmailCli packages the key tools and adds a sprinkling of goodness to make it just that much easier to write primarily command-line utilities for Gmail/GoogleApps}
|
13
|
+
spec.homepage = "https://github.com/evendis/gmail_cli"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency(%q<getoptions>, ["~> 0.3"])
|
22
|
+
spec.add_runtime_dependency(%q<gmail_xoauth>, ["~> 0.4.1"])
|
23
|
+
spec.add_runtime_dependency(%q<google-api-client>, ["~> 0.6.4"])
|
24
|
+
|
25
|
+
spec.add_development_dependency(%q<bundler>, ["> 1.3"])
|
26
|
+
spec.add_development_dependency(%q<rake>, ["~> 0.9.2.2"])
|
27
|
+
spec.add_development_dependency(%q<rspec>, ["~> 2.13.0"])
|
28
|
+
spec.add_development_dependency(%q<rdoc>, ["~> 3.11"])
|
29
|
+
spec.add_development_dependency(%q<guard-rspec>, ["~> 3.0.2"])
|
30
|
+
spec.add_development_dependency(%q<rb-fsevent>, ["~> 0.9.3"])
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'net/imap'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
require 'gmail_xoauth'
|
5
|
+
|
6
|
+
module GmailCli
|
7
|
+
|
8
|
+
# Command: convenience method to return IMAP connection given +options+
|
9
|
+
def self.imap_connection(options={})
|
10
|
+
GmailCli::Imap.new(options).connect!
|
11
|
+
end
|
12
|
+
|
13
|
+
class Imap
|
14
|
+
include GmailCli::LoggerSupport
|
15
|
+
|
16
|
+
attr_accessor :imap, :options, :oauth_options
|
17
|
+
|
18
|
+
def initialize(options={})
|
19
|
+
options = options.dup
|
20
|
+
@oauth_options = {
|
21
|
+
client_id: options.delete(:client_id),
|
22
|
+
client_secret: options.delete(:client_secret),
|
23
|
+
access_token: options.delete(:access_token),
|
24
|
+
refresh_token: options.delete(:refresh_token)
|
25
|
+
}
|
26
|
+
@options = {
|
27
|
+
host: 'imap.gmail.com'
|
28
|
+
}.merge(options)
|
29
|
+
@options[:host_options] ||= {
|
30
|
+
port: 993,
|
31
|
+
ssl: true
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def refresh_access_token!
|
36
|
+
oauth_options[:access_token] = GmailCli::Oauth2Helper.new(oauth_options).refresh_access_token!
|
37
|
+
end
|
38
|
+
|
39
|
+
def host
|
40
|
+
options[:host]
|
41
|
+
end
|
42
|
+
def username
|
43
|
+
options[:username]
|
44
|
+
end
|
45
|
+
def host_options
|
46
|
+
options[:host_options]
|
47
|
+
end
|
48
|
+
def oauth_access_token
|
49
|
+
oauth_options[:access_token]
|
50
|
+
end
|
51
|
+
|
52
|
+
def connect!
|
53
|
+
disconnect!
|
54
|
+
refresh_access_token! # we cheat a bit here - refreshing the token every time we get a new connection
|
55
|
+
trace "#{__method__} to host", host
|
56
|
+
self.imap = Net::IMAP.new(host,host_options)
|
57
|
+
trace "imap capabilities", imap.capability
|
58
|
+
imap.authenticate('XOAUTH2', username, oauth_access_token)
|
59
|
+
imap
|
60
|
+
end
|
61
|
+
|
62
|
+
def disconnect!
|
63
|
+
return unless imap
|
64
|
+
trace "calling", __method__
|
65
|
+
imap.close
|
66
|
+
self.imap = nil
|
67
|
+
rescue Exception => e
|
68
|
+
trace "#{__method__} error", e
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class GmailCli::Logger
|
2
|
+
|
3
|
+
class << self
|
4
|
+
def log(msg)
|
5
|
+
$stdout.puts "#{Time.now}| #{msg}"
|
6
|
+
end
|
7
|
+
def trace(name,value) ; value ; end
|
8
|
+
|
9
|
+
def set_log_mode(verbose)
|
10
|
+
if verbose
|
11
|
+
class_eval <<-LOGGER_ACTION, __FILE__, __LINE__
|
12
|
+
def self.trace(name,value)
|
13
|
+
$stderr.puts "\#{Time.now}| \#{name}: \#{value.inspect}"
|
14
|
+
value
|
15
|
+
end
|
16
|
+
LOGGER_ACTION
|
17
|
+
else
|
18
|
+
class_eval <<-LOGGER_ACTION, __FILE__, __LINE__
|
19
|
+
def self.trace(name,value) ; value ; end
|
20
|
+
LOGGER_ACTION
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
module GmailCli::LoggerSupport
|
29
|
+
|
30
|
+
def trace(name,value)
|
31
|
+
GmailCli::Logger.trace name,value
|
32
|
+
end
|
33
|
+
def log(msg)
|
34
|
+
GmailCli::Logger.log msg
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'google/api_client'
|
2
|
+
|
3
|
+
class GmailCli::Oauth2Helper
|
4
|
+
include GmailCli::LoggerSupport
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# Command: convenience class method to invoke authorization phase
|
8
|
+
def authorize!(options={})
|
9
|
+
new(options).authorize!
|
10
|
+
rescue Interrupt
|
11
|
+
$stderr.puts "..interrupted."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :client_id, :client_secret
|
16
|
+
attr_accessor :authorization_code, :access_token, :refresh_token
|
17
|
+
attr_accessor :scope, :redirect_uri, :application_name, :application_version
|
18
|
+
|
19
|
+
def initialize(options={})
|
20
|
+
@client_id = options[:client_id]
|
21
|
+
@client_secret = options[:client_secret]
|
22
|
+
@access_token = options[:access_token]
|
23
|
+
@refresh_token = options[:refresh_token]
|
24
|
+
@scope = options[:scope] || 'https://mail.google.com/'
|
25
|
+
@redirect_uri = options[:redirect_uri] || 'urn:ietf:wg:oauth:2.0:oob'
|
26
|
+
@application_name = options[:application_name] || 'gmail_cli'
|
27
|
+
@application_version = options[:application_version] || GmailCli::VERSION
|
28
|
+
trace "#{self.class.name} resolved options", {
|
29
|
+
client_id: client_id, client_secret: client_secret,
|
30
|
+
access_token: access_token, refresh_token: refresh_token,
|
31
|
+
scope: scope, redirect_uri: redirect_uri,
|
32
|
+
application_name: application_name, application_version: application_version
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def echo(text)
|
37
|
+
puts text
|
38
|
+
end
|
39
|
+
|
40
|
+
# Command: requests and returns a fresh access_token
|
41
|
+
def refresh_access_token!
|
42
|
+
api_client.authorization.refresh_token = refresh_token
|
43
|
+
response = fetch_refresh_token!
|
44
|
+
# => {"access_token"=>"ya29.AHES6ZRclrR13BePPwPmwdPUtoVqRxJ4fyVKgN1LJzIg-f8", "token_type"=>"Bearer", "expires_in"=>3600}
|
45
|
+
trace "#{__method__} response", response
|
46
|
+
self.access_token = response['access_token']
|
47
|
+
end
|
48
|
+
|
49
|
+
# Command: runs an interactive authorization phase
|
50
|
+
def authorize!
|
51
|
+
echo %(
|
52
|
+
Performing Google OAuth2 client authorization
|
53
|
+
---------------------------------------------)
|
54
|
+
get_access_token!
|
55
|
+
end
|
56
|
+
|
57
|
+
def api_client
|
58
|
+
@api_client ||= if ensure_provided(:client_id) && ensure_provided(:client_secret) && ensure_provided(:redirect_uri) && ensure_provided(:scope)
|
59
|
+
# Initialize OAuth 2.0 client
|
60
|
+
api_client = Google::APIClient.new(application_name: application_name, application_version: application_version)
|
61
|
+
api_client.authorization.client_id = client_id
|
62
|
+
api_client.authorization.client_secret = client_secret
|
63
|
+
api_client.authorization.redirect_uri = redirect_uri
|
64
|
+
api_client.authorization.scope = scope
|
65
|
+
api_client
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_access_token!
|
70
|
+
# Request authorization
|
71
|
+
authorization_uri = get_authorization_uri
|
72
|
+
echo %(
|
73
|
+
Go to the following URL in a web browser to grant the authorization.
|
74
|
+
There you will be able to select specifically which gmail account the authorization is for.
|
75
|
+
|
76
|
+
#{authorization_uri}
|
77
|
+
|
78
|
+
When you have done that successfully it will provide a code to enter here:
|
79
|
+
)
|
80
|
+
api_client.authorization.code = ensure_provided(:authorization_code)
|
81
|
+
response = fetch_access_token!
|
82
|
+
# => {"access_token"=>"ya29.AHES6ZS_KHUpdO5P0nyvADWf4tL5o8e8C_q5UK0HyyYOF3jw", "token_type"=>"Bearer", "expires_in"=>3600, "refresh_token"=>"1/o4DFZX1_iu_riPiu-OO6FLJ9M8pE5QWmY5DDoUHyOGw"}
|
83
|
+
trace "#{__method__} response", response
|
84
|
+
self.access_token = response['access_token']
|
85
|
+
self.refresh_token = response['refresh_token']
|
86
|
+
echo %(
|
87
|
+
Authorization was successful! You can now use this credential to access gmail.
|
88
|
+
|
89
|
+
For example, to get an authenticated IMAP connection to the 'name@gmail.com' account:
|
90
|
+
|
91
|
+
credentials = {
|
92
|
+
client_id: '#{client_id}',
|
93
|
+
client_secret: '#{client_secret}',
|
94
|
+
access_token: '#{access_token}',
|
95
|
+
refresh_token: '#{refresh_token}',
|
96
|
+
username: 'name@gmail.com'
|
97
|
+
}
|
98
|
+
imap = GmailCli.imap_connection(credentials)
|
99
|
+
|
100
|
+
)
|
101
|
+
access_token
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_authorization_uri
|
105
|
+
api_client.authorization.authorization_uri
|
106
|
+
end
|
107
|
+
|
108
|
+
def fetch_access_token!
|
109
|
+
api_client.authorization.fetch_access_token!
|
110
|
+
end
|
111
|
+
|
112
|
+
def fetch_refresh_token!
|
113
|
+
api_client.authorization.refresh!
|
114
|
+
end
|
115
|
+
|
116
|
+
def ensure_provided(key)
|
117
|
+
eval("self.#{key} ||= ask_for_entry('#{key}: ')")
|
118
|
+
end
|
119
|
+
|
120
|
+
def ask_for_entry(prompt)
|
121
|
+
print prompt
|
122
|
+
STDIN.gets.chomp!
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# class that groks the command line options and invokes the required task
|
2
|
+
class GmailCli::Shell
|
3
|
+
|
4
|
+
# holds the parsed options
|
5
|
+
attr_reader :options
|
6
|
+
|
7
|
+
# holds the remaining command line arguments
|
8
|
+
attr_reader :args
|
9
|
+
|
10
|
+
# initializes the shell with command line argments:
|
11
|
+
#
|
12
|
+
# +options+ is expected to be the hash structure as provided by GetOptions.new(..)
|
13
|
+
#
|
14
|
+
# +args+ is the remaining command line arguments
|
15
|
+
#
|
16
|
+
def initialize(options,args)
|
17
|
+
@options = (options||{}).each{|k,v| {k => v} }
|
18
|
+
@args = args
|
19
|
+
GmailCli::Logger.set_log_mode(options[:verbose])
|
20
|
+
end
|
21
|
+
|
22
|
+
# Command: execute the task according to the options provided on initialisation
|
23
|
+
def run
|
24
|
+
case
|
25
|
+
when args.first =~ /authorize/i
|
26
|
+
authorize
|
27
|
+
else
|
28
|
+
usage
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# defines the valid command line options
|
33
|
+
OPTIONS = %w(help verbose client_id:s client_secret:s)
|
34
|
+
|
35
|
+
class << self
|
36
|
+
|
37
|
+
# prints usage/help information
|
38
|
+
def usage
|
39
|
+
$stderr.puts <<-EOS
|
40
|
+
|
41
|
+
GmailCli v#{GmailCli::VERSION}
|
42
|
+
===================================
|
43
|
+
|
44
|
+
Usage:
|
45
|
+
gmail_cli [options] [commands]
|
46
|
+
|
47
|
+
Options:
|
48
|
+
-h | --help : shows command help
|
49
|
+
-v | --verbose : run with verbose
|
50
|
+
|
51
|
+
--client_id "xxxx" : OAuth2 client_id
|
52
|
+
--client_secret "yyy" : OAuth2 client_secret
|
53
|
+
|
54
|
+
Commands:
|
55
|
+
authorize : perform Google OAuth2 client authorization
|
56
|
+
|
57
|
+
|
58
|
+
EOS
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# prints usage/help information
|
63
|
+
def usage
|
64
|
+
self.class.usage
|
65
|
+
end
|
66
|
+
|
67
|
+
def authorize
|
68
|
+
GmailCli::Oauth2Helper.authorize!(options)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
namespace :gmail_cli do
|
2
|
+
require 'gmail_cli'
|
3
|
+
|
4
|
+
desc "Perform Google OAuth2 client authorization with client_id=? client_secret=?"
|
5
|
+
task :authorize do |t|
|
6
|
+
options = {
|
7
|
+
client_id: ENV['client_id'],
|
8
|
+
client_secret: ENV['client_secret'],
|
9
|
+
scope: ENV['scope'],
|
10
|
+
redirect_uri: ENV['redirect_uri'],
|
11
|
+
application_name: ENV['application_name'],
|
12
|
+
application_version: ENV['application_version']
|
13
|
+
}
|
14
|
+
GmailCli::Logger.set_log_mode(t.application.options.trace)
|
15
|
+
GmailCli::Oauth2Helper.authorize!(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/gmail_cli.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'gmail_cli'
|
2
|
+
|
3
|
+
# Requires supporting files with custom matchers and macros, etc,
|
4
|
+
# in ./support/ and its subdirectories.
|
5
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
# == Mock Framework
|
9
|
+
#
|
10
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
11
|
+
#
|
12
|
+
# config.mock_with :mocha
|
13
|
+
# config.mock_with :flexmock
|
14
|
+
# config.mock_with :rr
|
15
|
+
config.mock_with :rspec
|
16
|
+
|
17
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
18
|
+
# config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
19
|
+
|
20
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
21
|
+
# examples within a transaction, remove the following line or assign false
|
22
|
+
# instead of true.
|
23
|
+
# config.use_transactional_fixtures = true
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GmailCli do
|
4
|
+
let(:options) { {} }
|
5
|
+
|
6
|
+
describe "##imap_connection" do
|
7
|
+
subject { GmailCli.imap_connection(options) }
|
8
|
+
it "should return connected GmailCli::Imap" do
|
9
|
+
GmailCli::Imap.any_instance.should_receive(:connect!).and_return('result!')
|
10
|
+
should eql('result!')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "::Imap" do
|
15
|
+
let(:imap) { GmailCli::Imap.new(options) }
|
16
|
+
subject { imap }
|
17
|
+
|
18
|
+
describe "#username" do
|
19
|
+
subject { imap.username }
|
20
|
+
it { should be_nil }
|
21
|
+
context "when given in options" do
|
22
|
+
let(:expected) { 'test@test.com' }
|
23
|
+
let(:options) { {username: expected} }
|
24
|
+
it { should eql(expected) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#host" do
|
29
|
+
subject { imap.host }
|
30
|
+
it { should eql('imap.gmail.com') }
|
31
|
+
context "when given in options" do
|
32
|
+
let(:expected) { 'test.com' }
|
33
|
+
let(:options) { {host: expected} }
|
34
|
+
it { should eql(expected) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#host_options" do
|
39
|
+
subject { imap.host_options }
|
40
|
+
it { should eql({port: 993, ssl: true}) }
|
41
|
+
context "when given in options" do
|
42
|
+
let(:expected) { {port: 111} }
|
43
|
+
let(:options) { {host_options: expected} }
|
44
|
+
it { should eql(expected) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#oauth_options" do
|
49
|
+
subject { imap.oauth_options }
|
50
|
+
it { should eql({
|
51
|
+
client_id: nil,
|
52
|
+
client_secret: nil,
|
53
|
+
access_token: nil,
|
54
|
+
refresh_token: nil
|
55
|
+
}) }
|
56
|
+
context "when some given in options" do
|
57
|
+
let(:options) { {client_id: 'abcd'} }
|
58
|
+
it { should eql({
|
59
|
+
client_id: 'abcd',
|
60
|
+
client_secret: nil,
|
61
|
+
access_token: nil,
|
62
|
+
refresh_token: nil
|
63
|
+
}) }
|
64
|
+
end
|
65
|
+
context "when all given in options" do
|
66
|
+
let(:options) { {client_id: 'abcd', client_secret: 'efg', access_token: '123', refresh_token: '456'} }
|
67
|
+
it { should eql({
|
68
|
+
client_id: 'abcd',
|
69
|
+
client_secret: 'efg',
|
70
|
+
access_token: '123',
|
71
|
+
refresh_token: '456'
|
72
|
+
}) }
|
73
|
+
it "should not be included in the options" do
|
74
|
+
imap.options.keys.should_not include(:client_id)
|
75
|
+
end
|
76
|
+
describe "#oauth_access_token" do
|
77
|
+
subject { imap.oauth_access_token }
|
78
|
+
it { should eql('123') }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GmailCli::Logger do
|
4
|
+
let(:logger) { GmailCli::Logger }
|
5
|
+
let(:trace_something) { logger.trace 'somthing', 'bogative' }
|
6
|
+
|
7
|
+
it "should not log when verbose mode not enabled" do
|
8
|
+
$stderr.should_receive(:puts).never
|
9
|
+
trace_something
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should log when verbose mode enabled" do
|
13
|
+
logger.set_log_mode(true)
|
14
|
+
$stderr.should_receive(:puts).and_return(nil)
|
15
|
+
trace_something
|
16
|
+
logger.set_log_mode(false)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not log when verbose mode enabled then disabled" do
|
20
|
+
logger.set_log_mode(true)
|
21
|
+
logger.set_log_mode(false)
|
22
|
+
$stderr.should_receive(:puts).never
|
23
|
+
trace_something
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GmailCli::Oauth2Helper do
|
4
|
+
let(:resource_class) { GmailCli::Oauth2Helper }
|
5
|
+
let(:instance) { resource_class.new(options) }
|
6
|
+
let(:options) { {} }
|
7
|
+
let(:expected) { 'canary' }
|
8
|
+
|
9
|
+
before do
|
10
|
+
# silence echo
|
11
|
+
resource_class.any_instance.stub(:echo).and_return(nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "##authorize!" do
|
15
|
+
subject { resource_class.authorize!(options) }
|
16
|
+
it "should invoke authorize! on instance" do
|
17
|
+
resource_class.any_instance.should_receive(:authorize!).and_return('result!')
|
18
|
+
should eql('result!')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#client_id" do
|
23
|
+
subject { instance.client_id }
|
24
|
+
it { should be_nil }
|
25
|
+
context "when given in options" do
|
26
|
+
let(:options) { {client_id: expected} }
|
27
|
+
it { should eql(expected) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#client_secret" do
|
32
|
+
subject { instance.client_secret }
|
33
|
+
it { should be_nil }
|
34
|
+
context "when given in options" do
|
35
|
+
let(:options) { {client_secret: expected} }
|
36
|
+
it { should eql(expected) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#authorization_code" do
|
41
|
+
subject { instance.authorization_code }
|
42
|
+
it { should be_nil }
|
43
|
+
context "cannot be given in options" do
|
44
|
+
let(:options) { {authorization_code: expected} }
|
45
|
+
it { should be_nil }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#access_token" do
|
50
|
+
subject { instance.access_token }
|
51
|
+
it { should be_nil }
|
52
|
+
context "when given in options" do
|
53
|
+
let(:options) { {access_token: expected} }
|
54
|
+
it { should eql(expected) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#refresh_token" do
|
59
|
+
subject { instance.refresh_token }
|
60
|
+
it { should be_nil }
|
61
|
+
context "when given in options" do
|
62
|
+
let(:options) { {refresh_token: expected} }
|
63
|
+
it { should eql(expected) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#scope" do
|
68
|
+
subject { instance.scope }
|
69
|
+
it { should eql('https://mail.google.com/') }
|
70
|
+
context "when given in options" do
|
71
|
+
let(:options) { {scope: expected} }
|
72
|
+
it { should eql(expected) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#redirect_uri" do
|
77
|
+
subject { instance.redirect_uri }
|
78
|
+
it { should eql('urn:ietf:wg:oauth:2.0:oob') }
|
79
|
+
context "when given in options" do
|
80
|
+
let(:options) { {redirect_uri: expected} }
|
81
|
+
it { should eql(expected) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#application_name" do
|
86
|
+
subject { instance.application_name }
|
87
|
+
it { should eql('gmail_cli') }
|
88
|
+
context "when given in options" do
|
89
|
+
let(:options) { {application_name: expected} }
|
90
|
+
it { should eql(expected) }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#application_version" do
|
95
|
+
subject { instance.application_version }
|
96
|
+
it { should eql(GmailCli::VERSION) }
|
97
|
+
context "when given in options" do
|
98
|
+
let(:options) { {application_version: expected} }
|
99
|
+
it { should eql(expected) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "#ensure_provided" do
|
104
|
+
let(:key) { :client_id }
|
105
|
+
subject { instance.ensure_provided(key) }
|
106
|
+
context "when value already set" do
|
107
|
+
let(:options) { {client_id: 'set'} }
|
108
|
+
it "should not ask_for_entry when already set" do
|
109
|
+
instance.should_receive(:ask_for_entry).never
|
110
|
+
subject
|
111
|
+
end
|
112
|
+
end
|
113
|
+
context "when value not already set" do
|
114
|
+
it "should ask_for_entry when already set" do
|
115
|
+
instance.should_receive(:ask_for_entry).and_return('got it')
|
116
|
+
subject
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#authorize!" do
|
122
|
+
subject { instance.authorize! }
|
123
|
+
it "should get_access_token" do
|
124
|
+
instance.should_receive(:get_access_token!).and_return(nil)
|
125
|
+
subject
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#api_client" do
|
130
|
+
let(:options) { {client_id: 'client_id', client_secret: 'client_secret'} }
|
131
|
+
subject { instance.api_client }
|
132
|
+
it { should be_a(Google::APIClient) }
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#get_access_token!" do
|
136
|
+
let(:options) { {client_id: 'client_id', client_secret: 'client_secret'} }
|
137
|
+
let(:mock_access_token) { "ya29.AHES6ZS_KHUpdO5P0nyvADWf4tL5o8e8C_q5UK0HyyYOF3jw" }
|
138
|
+
let(:mock_refresh_token) { "ya29.AHES6ZS_KHUpdO5P0nyvADWf4tL5o8e8C_q5UK0HyyYOF3jw" }
|
139
|
+
let(:mock_fetch_access_token_response) { {"access_token"=>mock_access_token, "token_type"=>"Bearer", "expires_in"=>3600, "refresh_token"=>mock_refresh_token} }
|
140
|
+
subject { instance.get_access_token! }
|
141
|
+
it "should orchestrate the fetch_access_token process correctly" do
|
142
|
+
instance.api_client.should be_a(Google::APIClient)
|
143
|
+
instance.should_receive(:get_authorization_uri).and_return('http://here')
|
144
|
+
instance.should_receive(:ensure_provided).with(:authorization_code).and_return('authorization_code')
|
145
|
+
instance.should_receive(:fetch_access_token!).and_return(mock_fetch_access_token_response)
|
146
|
+
subject.should eql(mock_access_token)
|
147
|
+
instance.access_token.should eql(mock_access_token)
|
148
|
+
instance.refresh_token.should eql(mock_refresh_token)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "#refresh_access_token!" do
|
153
|
+
let(:options) { {client_id: 'client_id', client_secret: 'client_secret', refresh_token: mock_refresh_token} }
|
154
|
+
let(:mock_access_token) { "ya29.AHES6ZS_KHUpdO5P0nyvADWf4tL5o8e8C_q5UK0HyyYOF3jw" }
|
155
|
+
let(:mock_refresh_token) { "ya29.AHES6ZS_KHUpdO5P0nyvADWf4tL5o8e8C_q5UK0HyyYOF3jw" }
|
156
|
+
let(:mock_fetch_refresh_token_response) { {"access_token"=>mock_access_token, "token_type"=>"Bearer", "expires_in"=>3600} }
|
157
|
+
subject { instance.refresh_access_token! }
|
158
|
+
it "should orchestrate the fetch_access_token process correctly" do
|
159
|
+
instance.api_client.should be_a(Google::APIClient)
|
160
|
+
instance.should_receive(:fetch_refresh_token!).and_return(mock_fetch_refresh_token_response)
|
161
|
+
subject.should eql(mock_access_token)
|
162
|
+
instance.access_token.should eql(mock_access_token)
|
163
|
+
instance.refresh_token.should eql(mock_refresh_token)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'getoptions'
|
3
|
+
|
4
|
+
describe GmailCli::Shell do
|
5
|
+
|
6
|
+
let(:getoptions) { GetOptions.new(GmailCli::Shell::OPTIONS, options) }
|
7
|
+
let(:shell) { GmailCli::Shell.new(getoptions,argv) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
$stderr.stub(:puts) # silence console feedback chatter
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#usage" do
|
14
|
+
let(:options) { ['-h'] }
|
15
|
+
let(:argv) { [] }
|
16
|
+
it "should print usage when run" do
|
17
|
+
shell.should_receive(:usage)
|
18
|
+
shell.run
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#authorize" do
|
23
|
+
let(:options) { [] }
|
24
|
+
let(:argv) { ["authorize"] }
|
25
|
+
it "should invoke authorize when run" do
|
26
|
+
GmailCli::Oauth2Helper.should_receive(:authorize!)
|
27
|
+
shell.run
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Rake Task gmail_cli:" do
|
4
|
+
require 'rake'
|
5
|
+
require 'gmail_cli/tasks'
|
6
|
+
|
7
|
+
describe "authorize" do
|
8
|
+
let(:task_name) { "gmail_cli:authorize" }
|
9
|
+
let :run_rake_task do
|
10
|
+
Rake::Task[task_name].reenable
|
11
|
+
Rake.application.invoke_task task_name
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should run successfully" do
|
15
|
+
GmailCli::Oauth2Helper.any_instance.should_receive(:authorize!).and_return(nil)
|
16
|
+
expect { run_rake_task }.to_not raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gmail_cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Paul Gallagher
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: getoptions
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.3'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: gmail_xoauth
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.4.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.4.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: google-api-client
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.6.4
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.6.4
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: bundler
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>'
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.3'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>'
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.3'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.9.2.2
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.9.2.2
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rspec
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 2.13.0
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 2.13.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rdoc
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.11'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '3.11'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: guard-rspec
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ~>
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 3.0.2
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 3.0.2
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: rb-fsevent
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.9.3
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.9.3
|
158
|
+
description: A simple toolbox for build utilities that talk to Gmail with OAuth2
|
159
|
+
email:
|
160
|
+
- gallagher.paul@gmail.com
|
161
|
+
executables:
|
162
|
+
- gmail_cli
|
163
|
+
extensions: []
|
164
|
+
extra_rdoc_files: []
|
165
|
+
files:
|
166
|
+
- .gitignore
|
167
|
+
- Gemfile
|
168
|
+
- Guardfile
|
169
|
+
- LICENSE.txt
|
170
|
+
- README.md
|
171
|
+
- Rakefile
|
172
|
+
- bin/gmail_cli
|
173
|
+
- gmail_cli.gemspec
|
174
|
+
- lib/gmail_cli.rb
|
175
|
+
- lib/gmail_cli/imap.rb
|
176
|
+
- lib/gmail_cli/logger.rb
|
177
|
+
- lib/gmail_cli/oauth2_helper.rb
|
178
|
+
- lib/gmail_cli/shell.rb
|
179
|
+
- lib/gmail_cli/tasks.rb
|
180
|
+
- lib/gmail_cli/version.rb
|
181
|
+
- spec/spec_helper.rb
|
182
|
+
- spec/unit/imap_spec.rb
|
183
|
+
- spec/unit/logger_spec.rb
|
184
|
+
- spec/unit/oauth2_helper_spec.rb
|
185
|
+
- spec/unit/shell_spec.rb
|
186
|
+
- spec/unit/tasks_spec.rb
|
187
|
+
homepage: https://github.com/evendis/gmail_cli
|
188
|
+
licenses:
|
189
|
+
- MIT
|
190
|
+
post_install_message:
|
191
|
+
rdoc_options: []
|
192
|
+
require_paths:
|
193
|
+
- lib
|
194
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
195
|
+
none: false
|
196
|
+
requirements:
|
197
|
+
- - ! '>='
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: '0'
|
200
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
requirements: []
|
207
|
+
rubyforge_project:
|
208
|
+
rubygems_version: 1.8.25
|
209
|
+
signing_key:
|
210
|
+
specification_version: 3
|
211
|
+
summary: GmailCli packages the key tools and adds a sprinkling of goodness to make
|
212
|
+
it just that much easier to write primarily command-line utilities for Gmail/GoogleApps
|
213
|
+
test_files:
|
214
|
+
- spec/spec_helper.rb
|
215
|
+
- spec/unit/imap_spec.rb
|
216
|
+
- spec/unit/logger_spec.rb
|
217
|
+
- spec/unit/oauth2_helper_spec.rb
|
218
|
+
- spec/unit/shell_spec.rb
|
219
|
+
- spec/unit/tasks_spec.rb
|