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 ADDED
@@ -0,0 +1,18 @@
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
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gmail_cli.gemspec
4
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ module GmailCli
2
+ VERSION = "0.0.1"
3
+ end
data/lib/gmail_cli.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "gmail_cli/version"
2
+ require "gmail_cli/logger"
3
+ require "gmail_cli/oauth2_helper"
4
+ require "gmail_cli/shell"
5
+ require "gmail_cli/imap"
6
+
@@ -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