bitsa 0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile 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