bitsa 0.10

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bitsa (0.10.beta3)
5
+ gdata (= 1.1.1)
6
+ trollop (= 1.15)
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ diff-lcs (1.1.2)
12
+ fakeweb (1.2.8)
13
+ gdata (1.1.1)
14
+ rspec (2.0.1)
15
+ rspec-core (~> 2.0.1)
16
+ rspec-expectations (~> 2.0.1)
17
+ rspec-mocks (~> 2.0.1)
18
+ rspec-core (2.0.1)
19
+ rspec-expectations (2.0.1)
20
+ diff-lcs (>= 1.1.2)
21
+ rspec-mocks (2.0.1)
22
+ rspec-core (~> 2.0.1)
23
+ rspec-expectations (~> 2.0.1)
24
+ trollop (1.15)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ bitsa!
31
+ bundler (>= 1.0.0)
32
+ fakeweb (~> 1.2.8)
33
+ gdata (= 1.1.1)
34
+ rspec (~> 2.0.0.beta.22)
35
+ trollop (= 1.15)
@@ -0,0 +1,4 @@
1
+ === 0.10 25 April 2011
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,117 @@
1
+ # Bitsa
2
+
3
+ * <http://github.com/colbell/bitsa>
4
+
5
+
6
+ A command line tool to access your GMail/Google Apps contacts. Designed to be used
7
+ from Mutt but should be able to be used from any email client that
8
+ supports calling external programs.
9
+
10
+
11
+ ## Installation
12
+
13
+ gem install bitsa
14
+
15
+
16
+ ## Configuration
17
+
18
+ Bitsa is configured through the configuration file
19
+ `~/.bitsa_config.yml`. Use your GMail (or Google Apps) email address
20
+ for the login.
21
+
22
+ ---
23
+ :login: myself@gmail.com
24
+ :password: mypassword
25
+ :cache_file_path: ~/.bitsa_cache.yml
26
+
27
+ The configuration file is not mandatory, you can pass in your email address
28
+ and password on the command line, see [Usage](#usage).
29
+
30
+ If you have no configuration file or if `cache_file_path` is not specified in the
31
+ configuration file it will default to `~/.bitsa_cache.yml`
32
+
33
+ If you store your email password in the configuration file you should
34
+ ensue that it is only readable by you:
35
+
36
+ chmod 0600 .bitsa_config.yml
37
+
38
+
39
+ ## <a name="usage">Usage</a>
40
+
41
+ Usage: bitsa [global-options] [subcommand] [command-opts]
42
+
43
+ Global options are:
44
+ --config-file, -c <s>: Configuration file (default: ~/.bitsa_config.yml)
45
+ --login, -l <s>: Login
46
+ --password, -p <s>: Password
47
+
48
+ bitsa sub-commands
49
+ update: get the latest changes from Gmail
50
+ reload: Clear all cached addresses and reload from Gmail
51
+ search: Search for the passed string
52
+
53
+ Information about this program
54
+ --version, -v: Print version and exit
55
+ --help, -h: Show this message
56
+
57
+
58
+ To search for all contacts that contain the string rob:
59
+
60
+ $ bitsa search rob
61
+
62
+ Rob_Smith@example.com.au Robert Smith
63
+ Rob_Smith@example.com Robert Smith
64
+ robert@example.com Robert Jones
65
+ jeff@example.net Robert Smith
66
+ bob@robertsystems Robert Brown
67
+
68
+ The first time you run Bitsa and then if it has been more than a day
69
+ since it was last updated it will get the latest changes from your
70
+ GMail contacts and copy them to a local cache (~/.bitsa_cache.yml).
71
+
72
+ You can update your cache with the latest changes at any time by using
73
+ the `update` sub-command:
74
+
75
+ $ bitsa update
76
+
77
+ If you want to clear your local cache and reload from GMail use the
78
+ `reload` sub-command:
79
+
80
+ $ bitsa reload
81
+
82
+ ## Usage - Mutt
83
+
84
+ To use for address lookup (&lt;ctrl&gt; t) in Mutt put the following in your
85
+ `~/.muttrc` file:
86
+
87
+ set query_command = "bitsa search '%s'"
88
+
89
+ ## Testing
90
+
91
+ To run the tests after cloning the repository you first need to
92
+ install the required libraries:
93
+
94
+ bundle install
95
+
96
+ And then you can run the tests:
97
+
98
+ rake spec
99
+
100
+ ## License:
101
+
102
+ Copyright 2011 Colin Bell.
103
+
104
+ Bitsa is free software: you can redistribute it and/or modify
105
+ it under the terms of the GNU General Public License as published by
106
+ the Free Software Foundation, either version 3 of the License, or
107
+ (at your option) any later version.
108
+
109
+ This program is distributed in the hope that it will be useful,
110
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
111
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
112
+ GNU General Public License for more details.
113
+
114
+ You should have received a copy of the GNU General Public License
115
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
116
+
117
+ * * * * *
@@ -0,0 +1,10 @@
1
+ #require 'rubygems'
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'fileutils'
6
+ require 'rake/rdoctask'
7
+ require 'rake/testtask'
8
+ require './lib/bitsa'
9
+
10
+ Dir['tasks/**/*.rake'].each { |t| load t }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ #
5
+ # Copyright 2011 Colin Noel Bell.
6
+ #
7
+ # This file is part of Bitsa.
8
+ #
9
+ # Bitsa is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ # begin
23
+ # require 'rubygems'
24
+ # rescue LoadError
25
+ # end
26
+
27
+ require "bitsa"
28
+ require "bitsa/args_processor"
29
+
30
+ args = Bitsa::ArgsProcessor.new
31
+ args.parse(ARGV)
32
+
33
+ app = Bitsa::BitsaApp.new
34
+ app.run(args.global_opts, args.cmd, args.search_data)
35
+
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "bitsa/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "bitsa"
7
+ s.version = Bitsa::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Colin Noel Bell"]
10
+ s.email = ["col@baibell.org"]
11
+ s.homepage = "https://github.com/colbell/bitsa"
12
+ s.summary = %q{Command line GMail Contacts lookup tool.}
13
+ s.description = %q{Allows you to lookup GMail contacts and cache contacts locally from the command line.}
14
+
15
+ s.required_rubygems_version = ">= 1.3.6"
16
+
17
+ s.add_dependency "trollop", "1.15"
18
+ s.add_dependency "gdata", "1.1.1"
19
+
20
+ s.add_development_dependency "bundler", ">= 1.0.0"
21
+ s.add_development_dependency "rspec", "~> 2.0.0.beta.22"
22
+ s.add_development_dependency "fakeweb", "~> 1.2.8"
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.require_path = "lib"
28
+ end
@@ -0,0 +1,50 @@
1
+ # Copyright 2011 Colin Bell.
2
+ #
3
+ # This file is part of Bitsa.
4
+ #
5
+ # Bitsa is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ $:.unshift(File.dirname(__FILE__)) unless
16
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
17
+
18
+ require "bitsa/config_file"
19
+ require "bitsa/contacts_cache"
20
+ require "bitsa/gmail_contacts_loader"
21
+ require "bitsa/settings"
22
+
23
+ module Bitsa
24
+
25
+ # Application entry point.
26
+ class BitsaApp
27
+
28
+ # Run application.
29
+ def run(global_opts, cmd, search_data)
30
+ settings = Settings.new
31
+ settings.load(ConfigFile.new(global_opts[:config_file]), global_opts)
32
+ cache = ContactsCache.new(settings.cache_file_path, 1)
33
+
34
+ if cmd == "reload"
35
+ cache.clear!
36
+ end
37
+
38
+ if ["reload", "update"].include?(cmd) || cache.stale?
39
+ loader = GmailContactsLoader.new(settings.login, settings.password)
40
+ loader.update_cache(cache)
41
+ end
42
+
43
+ if cmd == "search"
44
+ puts "" # Force first entry to be displayed in mutt
45
+ cache.search(search_data).each {|k,v| puts "#{k}\t#{v}"}
46
+ end
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,80 @@
1
+ # Command line arguments handler
2
+ #
3
+ # Copyright (C) 2011 Colin Noel Bell.
4
+ #
5
+ # This file is part of Bitsa.
6
+ #
7
+ # Bitsa is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require "trollop"
21
+
22
+ require "bitsa/version"
23
+
24
+ module Bitsa #:nodoc:
25
+
26
+ # Arguments passed on the command line. Trollop http://trollop.rubyforge.org
27
+ # is used to handle the parsing.
28
+ class ArgsProcessor
29
+ # Valid commands.
30
+ SUB_COMMANDS = %w(update reload search)
31
+
32
+ # Global options passed on the command line.
33
+ attr_reader :global_opts
34
+
35
+ # The command to execute
36
+ attr_reader :cmd
37
+
38
+ # Data to search cached contacts for.
39
+ attr_reader :search_data
40
+
41
+ # Parse arguments and setup attributes. If invalid data is passed a
42
+ # Trollop exception is thrown and the program terminated.
43
+ #
44
+ # It also handles showing the Help and Version information.
45
+ def parse(args)
46
+ @global_opts = Trollop::options(args) do
47
+ version "bitsa v#{Bitsa::VERSION}"
48
+ banner <<EOS
49
+ Usage: bitsa [global-options] [subcommand] [command-opts]
50
+
51
+ Global options are:
52
+ EOS
53
+ opt :config_file, "Configuration file", :default => "~/.bitsa_config.yml"
54
+ opt :login, "Login", :type => String
55
+ opt :password, "Password", :type => String
56
+
57
+ stop_on SUB_COMMANDS
58
+
59
+ banner <<EOS
60
+
61
+ bitsa subcommands
62
+ update: get the latest changes from Gmail
63
+ reload: Clear all cached addresses and reload from Gmail
64
+ search: Search for the passed string
65
+
66
+ Information about this program
67
+ EOS
68
+ end
69
+
70
+ @cmd = args.shift || ''
71
+ @search_data = ''
72
+
73
+ if cmd == "search"
74
+ @search_data << args.shift unless args.empty?
75
+ elsif !["search", "update", "reload"].include?(cmd)
76
+ Trollop::die "unknown subcommand '#{cmd}'"
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,39 @@
1
+ # Loads configuration data from configuration file.
2
+ #
3
+ # Copyright (C) 2011 Colin Noel Bell.
4
+ #
5
+ # This file is part of Bitsa.
6
+ #
7
+ # Bitsa is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require "yaml"
21
+
22
+ module Bitsa #:nodoc:
23
+
24
+ # Loads configuration data from a yaml file.
25
+ class ConfigFile
26
+
27
+ # Loaded configuration data as a Hash.
28
+ attr_reader :data
29
+
30
+ # Load data from passed file path.
31
+ def initialize(config_file_path_name)
32
+ c_f_n = File.expand_path(config_file_path_name)
33
+ if File.exist?(c_f_n)
34
+ @data = YAML.load_file(c_f_n)
35
+ end
36
+ @data = {} unless @data
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,100 @@
1
+ # Cache of Contacts.
2
+ #
3
+ # Copyright (C) 2011 Colin Noel Bell.
4
+ #
5
+ # This file is part of Bitsa.
6
+ #
7
+ # Bitsa is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require "forwardable"
21
+
22
+ module Bitsa #:nodoc:
23
+
24
+ # Cache of Contacts.
25
+ class ContactsCache
26
+ extend Forwardable
27
+
28
+ # Number of entries in cache.
29
+ def_delegator :@addresses, :size
30
+
31
+ # True if cache is empty.
32
+ def_delegator :@addresses, :empty?
33
+
34
+ # Date/Time cache was last updated.
35
+ attr_accessor :source_last_modified
36
+
37
+ # Load cache from file system. After <tt>lifespan_days</tt> the cache is considered stale.
38
+ def initialize(cache_file_path, lifespan_days)
39
+ @cache_file_path = File.expand_path(cache_file_path || "~/.bitsa_cache.yml")
40
+ @lifespan_days = lifespan_days
41
+ @addresses = {}
42
+ @source_source_last_modified = nil
43
+ load_from_file_system
44
+ end
45
+
46
+ def stale?
47
+ (@source_last_modified.nil? ||
48
+ (DateTime.parse(@source_last_modified)+@lifespan_days) < DateTime.now)
49
+ end
50
+
51
+ # Remove all entries from cache.
52
+ def clear!
53
+ @addresses.clear
54
+ @source_last_modified = nil
55
+ end
56
+
57
+ def get(id)
58
+ @addresses[id]
59
+ end
60
+
61
+ def search(qry)
62
+ qry ||= ""
63
+ rg = Regexp.new(qry, Regexp::IGNORECASE)
64
+
65
+ # Flattens.each_slices to an array with [email1, name1, email2, name2] etc.
66
+ results = @addresses.values.flatten.each_slice(2).find_all do |e, n|
67
+ e.match(rg) || n.match(rg)
68
+ end
69
+
70
+ # Sort by case-insensitive email address
71
+ results.sort{|a,b| a[0].downcase <=> b[0].downcase}
72
+ end
73
+
74
+ def update(id, name, addresses)
75
+ @addresses[id] = addresses.map { | a | [a, name]}
76
+ end
77
+
78
+ def delete(id)
79
+ @addresses.delete(id)
80
+ end
81
+
82
+ def save
83
+ File.open(@cache_file_path, "w") do |f|
84
+ f.write(YAML::dump([@source_last_modified, @addresses]))
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def load_from_file_system
91
+ if File.exist?(@cache_file_path)
92
+ @source_last_modified, @addresses = YAML::load_file(@cache_file_path)
93
+ unless @addresses
94
+ @addresses = {}
95
+ @source_last_modified = nil
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end