rgrove-larch 1.0.2.2 → 1.0.2.3

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/HISTORY CHANGED
@@ -6,9 +6,17 @@ Version 1.1.0 (git)
6
6
  database, which allows Larch to resync and resume interrupted syncs much
7
7
  more quickly without having to rescan all messages. As a result, SQLite 3 is
8
8
  now a dependency.
9
+ * Larch now loads config options from ~/.larch/config.yaml if it exists, or
10
+ from the file specified by the --config command-line option. This file may
11
+ contain multiple sections. If a section name is specified via the
12
+ command-line, Larch will use the options in that section for the session;
13
+ otherwise it will use the options in the "default" section. See the README
14
+ for more details.
9
15
  * Folders are now copied recursively by default.
10
16
  * Progress information is now displayed regularly while scanning large
11
17
  mailboxes.
18
+ * Added short versions of common command-line options.
19
+ * The --fast-scan option has been removed.
12
20
 
13
21
  Version 1.0.2 (2009-08-05)
14
22
  * Fixed a bug that caused Larch to try to set the read-only \Recent flag on
data/README.rdoc CHANGED
@@ -2,90 +2,99 @@
2
2
 
3
3
  Larch is a tool to copy messages from one IMAP server to another quickly and
4
4
  safely. It's smart enough not to copy messages that already exist on the
5
- destination and robust enough to deal with ornery or misbehaving servers.
5
+ destination and robust enough to deal with interruptions caused by flaky
6
+ connections or misbehaving servers.
6
7
 
7
8
  Larch is particularly well-suited for copying email to, from, or between Gmail
8
9
  accounts.
9
10
 
10
11
  *Author*:: Ryan Grove (mailto:ryan@wonko.com)
11
- *Version*:: 1.0.2 (2009-08-05)
12
+ *Version*:: 1.1.0 (git)
12
13
  *Copyright*:: Copyright (c) 2009 Ryan Grove. All rights reserved.
13
14
  *License*:: GPL 2.0 (http://opensource.org/licenses/gpl-2.0.php)
14
15
  *Website*:: http://github.com/rgrove/larch
15
16
 
16
17
  == Installation
17
18
 
18
- Install Larch via RubyGems:
19
+ Install the latest stable version of Larch via RubyGems:
19
20
 
20
21
  gem install larch
21
22
 
22
- == Usage
23
-
24
- larch --from <uri> --to <uri> [options]
23
+ Or you can install the latest development version from GitHub:
25
24
 
26
- Required:
27
- --from, -f <s>: URI of the source IMAP server.
28
- --to, -t <s>: URI of the destination IMAP server.
29
-
30
- Copy Options:
31
- --all: Copy all folders recursively
32
- --all-subscribed: Copy all subscribed folders recursively
33
- --exclude <s+>: List of mailbox names/patterns that shouldn't be
34
- copied
35
- --exclude-file <s>: Filename containing mailbox names/patterns that
36
- shouldn't be copied
37
- --from-folder <s>: Source folder to copy from (default: INBOX)
38
- --from-pass <s>: Source server password (default: prompt)
39
- --from-user <s>: Source server username (default: prompt)
40
- --to-folder <s>: Destination folder to copy to (default: INBOX)
41
- --to-pass <s>: Destination server password (default: prompt)
42
- --to-user <s>: Destination server username (default: prompt)
25
+ gem sources -a http://gems.github.com
26
+ gem install rgrove-larch
43
27
 
44
- General Options:
45
- --dry-run, -n: Don't actually make any changes.
46
- --fast-scan: Use a faster (but less accurate) method to scan
47
- mailboxes. This may result in messages being
48
- re-copied.
49
- --max-retries <i>: Maximum number of times to retry after a recoverable
50
- error (default: 3)
51
- --no-create-folder: Don't create destination folders that don't already
52
- exist
53
- --ssl-certs <s>: Path to a trusted certificate bundle to use to
54
- verify server SSL certificates
55
- --ssl-verify: Verify server SSL certificates
56
- --verbosity, -V <s>: Output verbosity: debug, info, warn, error, or fatal
57
- (default: info)
58
- --version, -v: Print version and exit
59
- --help, -h: Show this message
60
-
61
- == Examples
62
-
63
- Larch is run from the command line. At a minimum, you must specify a source
64
- server and a destination server in the form of IMAP URIs. You may also specify
65
- one or more options:
28
+ == Usage
66
29
 
30
+ larch [config section] [options]
67
31
  larch --from <uri> --to <uri> [options]
68
32
 
69
- For an overview of all available command-line options, run:
70
-
71
- larch --help
33
+ Server Options:
34
+ --from, -f <s>: URI of the source IMAP server.
35
+ --from-folder, -F <s>: Source folder to copy from (default: INBOX)
36
+ --from-pass, -p <s>: Source server password (default: prompt)
37
+ --from-user, -u <s>: Source server username (default: prompt)
38
+ --to, -t <s>: URI of the destination IMAP server.
39
+ --to-folder, -T <s>: Destination folder to copy to (default: INBOX)
40
+ --to-pass, -P <s>: Destination server password (default: prompt)
41
+ --to-user, -U <s>: Destination server username (default: prompt)
42
+
43
+ Sync Options:
44
+ --all, -a: Copy all folders recursively
45
+ --all-subscribed, -s: Copy all subscribed folders recursively
46
+ --exclude <s+>: List of mailbox names/patterns that shouldn't be
47
+ copied
48
+ --exclude-file <s>: Filename containing mailbox names/patterns that
49
+ shouldn't be copied
72
50
 
73
- Specify a source server and a destination server and Larch will prompt you for
74
- the necessary usernames and passwords, then sync the contents of the source's
75
- +INBOX+ folder to the destination:
51
+ General Options:
52
+ --config, -c <s>: Specify a non-default config file to use (default:
53
+ ~/.larch/config.yaml)
54
+ --database <s>: Specify a non-default message database to use
55
+ (default: ~/.larch/larch.db)
56
+ --dry-run, -n: Don't actually make any changes
57
+ --max-retries <i>: Maximum number of times to retry after a recoverable
58
+ error (default: 3)
59
+ --no-create-folder: Don't create destination folders that don't already
60
+ exist
61
+ --ssl-certs <s>: Path to a trusted certificate bundle to use to
62
+ verify server SSL certificates
63
+ --ssl-verify: Verify server SSL certificates
64
+ --verbosity, -V <s>: Output verbosity: debug, info, warn, error, or fatal
65
+ (default: info)
66
+ --version, -v: Print version and exit
67
+ --help, -h: Show this message
68
+
69
+ == Usage Examples
70
+
71
+ Larch is run from the command line. The following examples demonstrate how to
72
+ run Larch using only command line arguments, but you may also place these
73
+ options in a config file and run Larch without any arguments if you prefer. See
74
+ the "Configuration" section below for more details.
75
+
76
+ For an overview of all available options, run:
77
+
78
+ larch -h
79
+
80
+ At a minimum, you must <b>specify a source server and a destination server</b>
81
+ in the form of IMAP URIs:
76
82
 
77
83
  larch --from imap://mail.example.com --to imap://imap.gmail.com
78
84
 
85
+ Larch will prompt you for the necessary usernames and passwords, then sync the
86
+ contents of the source's +INBOX+ folder to the destination's INBOX folder.
87
+
79
88
  To <b>connect using SSL</b>, specify a URI beginning with <tt>imaps://</tt>:
80
89
 
81
90
  larch --from imaps://mail.example.com --to imaps://imap.gmail.com
82
91
 
83
92
  If you'd like to <b>sync a specific folder</b> other than +INBOX+, specify the
84
93
  source and destination folders using <tt>--from-folder</tt> and
85
- <tt>--to-folder</tt>:
94
+ <tt>--to-folder</tt>. Folder names containing spaces must be enclosed in quotes:
86
95
 
87
96
  larch --from imaps://mail.example.com --to imaps://imap.gmail.com \
88
- --from-folder "Sent Mail" --to-folder "Sent Mail"
97
+ --from-folder 'Sent Mail' --to-folder 'Sent Mail'
89
98
 
90
99
  To <b>sync all folders</b>, use the <tt>--all</tt> option (or
91
100
  <tt>--all-subscribed</tt> if you only want to <b>sync subscribed folders</b>):
@@ -99,7 +108,7 @@ already exist. To prevent this, add the <tt>--no-create-folder</tt> option:
99
108
  --no-create-folder
100
109
 
101
110
  You can <b>prevent Larch from syncing one or more folders</b> by using the
102
- <tt>--exclude</tt> option:
111
+ <tt>--exclude</tt> option, which accepts multiple arguments:
103
112
 
104
113
  larch --from imaps://mail.example.com --to imaps://imap.gmail.com --all \
105
114
  --exclude Spam Trash Drafts "[Gmail]/*"
@@ -112,10 +121,83 @@ option:
112
121
  --exclude-file exclude.txt
113
122
 
114
123
  The wildcard characters <tt>*</tt> and <tt>?</tt> are supported in exclusion
115
- lists. You can also use a regular expression by enclosing a pattern in
124
+ lists. You may also use a regular expression by enclosing a pattern in
116
125
  forward slashes, so the previous example could be achieved with the
117
126
  pattern <tt>/(Spam|Trash|Drafts|\[Gmail\]\/.*)/</tt>
118
127
 
128
+ == Configuration
129
+
130
+ While it's possible to control Larch entirely from the command line, this can be
131
+ inconvenient if you need to specify a lot of options or if you run Larch
132
+ frequently and can't always remember which options to use. Using a configuration
133
+ file can simplify things.
134
+
135
+ By default, Larch looks for a config file at <tt>~/.larch/config.yaml</tt> and
136
+ uses it if found. You may specify a custom config file using the
137
+ <tt>--config</tt> command line option.
138
+
139
+ The Larch configuration file is a simple YAML[http://yaml.org/] file that may
140
+ contain multiple sections, each with a different set of options, as well as a
141
+ special +default+ section. The options in the +default+ section will be used
142
+ unless they're overridden either in another config section or on the command
143
+ line.
144
+
145
+ === Example
146
+
147
+ Here's a sample Larch config file:
148
+
149
+ default:
150
+ all-subscribed: true # Copy all subscribed folders by default
151
+
152
+ # Copy mail from Gmail to my server, excluding stuff I don't want.
153
+ gmail to my server:
154
+ from: imaps://imap.gmail.com
155
+ from-user: example
156
+ from-pass: secret
157
+
158
+ to: imaps://mail.example.com
159
+ to-user: example
160
+ to-pass: secret
161
+
162
+ exclude:
163
+ - "[Gmail]/Sent Mail"
164
+ - "[Gmail]/Spam"
165
+ - "[Gmail]/Trash"
166
+
167
+ # Copy mail from my INBOX to Gmail's INBOX
168
+ my inbox to gmail inbox:
169
+ all-subscribed: false
170
+
171
+ from: imaps://mail.example.com
172
+ from-folder: INBOX
173
+ from-user: example
174
+ from-pass: secret
175
+
176
+ to: imaps://imap.gmail.com
177
+ to-folder: INBOX
178
+ to-user: example
179
+ to-pass: secret
180
+
181
+ This file contains three sections. The options from +default+ will be used in
182
+ all other sections as well unless they're overridden.
183
+
184
+ To specify which config section you want Larch to use, just pass its name on the
185
+ command line (use quotes if the name contains spaces):
186
+
187
+ larch 'gmail to my server'
188
+
189
+ If you specify additional command line options, they'll override options in the
190
+ config file:
191
+
192
+ larch 'gmail to my server' --from-user anotheruser
193
+
194
+ Running Larch with no command line arguments will cause the +default+ section
195
+ to be used. With the example above, this will result in an error since the
196
+ +default+ section doesn't contain the required +from+ and +to+ options, but if
197
+ you only need to use Larch with a single configuration, you could use the
198
+ +default+ section for everything and save yourself some typing on the command
199
+ line.
200
+
119
201
  == Server Compatibility
120
202
 
121
203
  Larch should work well with any server that properly supports
data/bin/larch CHANGED
@@ -11,123 +11,97 @@ module Larch
11
11
  # Parse command-line options.
12
12
  options = Trollop.options do
13
13
  version "Larch #{APP_VERSION}\n" << APP_COPYRIGHT
14
- banner <<EOS
14
+ banner <<-EOS
15
15
  Larch syncs messages from one IMAP server to another. Awesomely.
16
16
 
17
17
  Usage:
18
+ larch [config section] [options]
18
19
  larch --from <uri> --to <uri> [options]
19
20
 
20
- Required:
21
+ Server Options:
21
22
  EOS
22
- opt :from, "URI of the source IMAP server.", :short => '-f', :type => :string, :required => true
23
- opt :to, "URI of the destination IMAP server.", :short => '-t', :type => :string, :required => true
23
+ opt :from, "URI of the source IMAP server.", :short => '-f', :type => :string
24
+ opt :from_folder, "Source folder to copy from", :short => '-F', :default => Config::DEFAULT['from-folder']
25
+ opt :from_pass, "Source server password (default: prompt)", :short => '-p', :type => :string
26
+ opt :from_user, "Source server username (default: prompt)", :short => '-u', :type => :string
27
+ opt :to, "URI of the destination IMAP server.", :short => '-t', :type => :string
28
+ opt :to_folder, "Destination folder to copy to", :short => '-T', :default => Config::DEFAULT['to-folder']
29
+ opt :to_pass, "Destination server password (default: prompt)", :short => '-P', :type => :string
30
+ opt :to_user, "Destination server username (default: prompt)", :short => '-U', :type => :string
24
31
 
25
32
  text "\nSync Options:"
26
- opt :all, "Copy all folders recursively", :short => :none
27
- opt :all_subscribed, "Copy all subscribed folders recursively", :short => :none
33
+ opt :all, "Copy all folders recursively", :short => '-a'
34
+ opt :all_subscribed, "Copy all subscribed folders recursively", :short => '-s'
28
35
  opt :exclude, "List of mailbox names/patterns that shouldn't be copied", :short => :none, :type => :strings, :multi => true
29
36
  opt :exclude_file, "Filename containing mailbox names/patterns that shouldn't be copied", :short => :none, :type => :string
30
- opt :from_folder, "Source folder to copy from", :short => :none, :default => 'INBOX'
31
- opt :from_pass, "Source server password (default: prompt)", :short => :none, :type => :string
32
- opt :from_user, "Source server username (default: prompt)", :short => :none, :type => :string
33
- opt :to_folder, "Destination folder to copy to", :short => :none, :default => 'INBOX'
34
- opt :to_pass, "Destination server password (default: prompt)", :short => :none, :type => :string
35
- opt :to_user, "Destination server username (default: prompt)", :short => :none, :type => :string
36
37
 
37
38
  text "\nGeneral Options:"
38
- opt :database, "Specify a non-default message database to use", :short => :none, :default => File.join('~', '.larch', 'larch.db')
39
+ opt :config, "Specify a non-default config file to use", :short => '-c', :default => Config::DEFAULT['config']
40
+ opt :database, "Specify a non-default message database to use", :short => :none, :default => Config::DEFAULT['database']
39
41
  opt :dry_run, "Don't actually make any changes", :short => '-n'
40
- opt :fast_scan, "Use a faster (but less accurate) method to scan mailboxes. This may result in messages being re-copied.", :short => :none
41
- opt :max_retries, "Maximum number of times to retry after a recoverable error", :short => :none, :default => 3
42
+ opt :max_retries, "Maximum number of times to retry after a recoverable error", :short => :none, :default => Config::DEFAULT['max-retries']
42
43
  opt :no_create_folder, "Don't create destination folders that don't already exist", :short => :none
43
44
  opt :ssl_certs, "Path to a trusted certificate bundle to use to verify server SSL certificates", :short => :none, :type => :string
44
45
  opt :ssl_verify, "Verify server SSL certificates", :short => :none
45
- opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal", :short => '-V', :default => 'info'
46
+ opt :verbosity, "Output verbosity: debug, info, warn, error, or fatal", :short => '-V', :default => Config::DEFAULT['verbosity']
46
47
  end
47
48
 
48
- # Validate command-line options.
49
- [:from, :to].each do |sym|
50
- unless options[sym] =~ IMAP::REGEX_URI
51
- Trollop.die sym, "must be a valid IMAP URI (e.g. imap://example.com)"
52
- end
53
- end
54
-
55
- unless Logger::LEVELS.has_key?(options[:verbosity].to_sym)
56
- Trollop.die :verbosity, "must be one of: #{Logger::LEVELS.keys.join(', ')}"
57
- end
58
-
59
- if options[:exclude_file_given]
60
- filename = options[:exclude_file]
61
-
62
- Trollop.die :exclude_file, ": file not found: #{filename}" unless File.file?(filename)
63
- Trollop.die :exclude_file, ": file cannot be read: #{filename}" unless File.readable?(filename)
49
+ # Load config.
50
+ config = Config.new(ARGV.shift || 'default', options[:config], options)
51
+
52
+ if options[:config_given]
53
+ Trollop.die :config, ": file not found: #{options[:config]}" unless File.exist?(options[:config])
64
54
  end
65
55
 
66
- # Prevent conflicting options from being used.
67
- if options[:all_given]
68
- [:all_subscribed, :from_folder, :to_folder].each do |o|
69
- Trollop.die :all, "may not be used with --#{o.to_s.gsub('_', '-')}" if options["#{o}_given".to_sym]
70
- end
71
- end
72
-
73
- if options[:all_subscribed_given]
74
- [:all, :from_folder, :to_folder].each do |o|
75
- Trollop.die :all_subscribed, "may not be used with --#{o.to_s.gsub('_', '-')}" if options["#{o}_given".to_sym]
76
- end
56
+ # Validate config.
57
+ begin
58
+ config.validate
59
+ rescue Config::Error => e
60
+ abort "Config error: #{e}"
77
61
  end
78
62
 
79
63
  # Create URIs.
80
- uri_from = URI(options[:from])
81
- uri_to = URI(options[:to])
64
+ uri_from = URI(config.from)
65
+ uri_to = URI(config.to)
82
66
 
83
- # --all and --all-subscribed options override folders specified in URIs
84
- if options[:all_given] || options[:all_subscribed_given]
67
+ # Use --from-folder and --to-folder unless folders were specified in the URIs.
68
+ uri_from.path ||= '/' + CGI.escape(config.from_folder.gsub(/^\//, ''))
69
+ uri_to.path ||= '/' + CGI.escape(config.to_folder.gsub(/^\//, ''))
70
+
71
+ # --all and --all-subscribed options override folders
72
+ if config.all || config.all_subscribed
85
73
  uri_from.path = ''
86
74
  uri_to.path = ''
87
75
  end
88
76
 
89
- # --from-folder and --to-folder options override folders specified in URIs
90
- if options[:from_folder_given] || options[:to_folder_given]
91
- uri_from.path = '/' + CGI.escape(options[:from_folder].gsub(/^\//, ''))
92
- uri_to.path = '/' + CGI.escape(options[:to_folder].gsub(/^\//, ''))
93
- end
94
-
95
77
  # Usernames and passwords specified as arguments override those in the URIs
96
- uri_from.user = CGI.escape(options[:from_user]) if options[:from_user]
97
- uri_from.password = CGI.escape(options[:from_pass]) if options[:from_pass]
98
- uri_to.user = CGI.escape(options[:to_user]) if options[:to_user]
99
- uri_to.password = CGI.escape(options[:to_pass]) if options[:to_pass]
78
+ uri_from.user = CGI.escape(config.from_user) if config.from_user
79
+ uri_from.password = CGI.escape(config.from_pass) if config.from_pass
80
+ uri_to.user = CGI.escape(config.to_user) if config.to_user
81
+ uri_to.password = CGI.escape(config.to_pass) if config.to_pass
100
82
 
101
- # If usernames/passwords aren't specified in either URIs or args, then prompt.
83
+ # If usernames/passwords aren't specified in either URIs or config, then prompt.
102
84
  uri_from.user ||= CGI.escape(ask("Source username (#{uri_from.host}): "))
103
85
  uri_from.password ||= CGI.escape(ask("Source password (#{uri_from.host}): ") {|q| q.echo = false })
104
86
  uri_to.user ||= CGI.escape(ask("Destination username (#{uri_to.host}): "))
105
87
  uri_to.password ||= CGI.escape(ask("Destination password (#{uri_to.host}): ") {|q| q.echo = false })
106
88
 
107
89
  # Go go go!
108
- init(options[:database],
109
- :exclude => options[:exclude] ? options[:exclude].flatten : [],
110
- :exclude_file => options[:exclude_file],
111
- :log_level => options[:verbosity]
112
- )
113
-
114
- Net::IMAP.debug = true if @log.level == :insane
90
+ init(config)
115
91
 
116
92
  imap_from = Larch::IMAP.new(uri_from,
117
- :dry_run => options[:dry_run],
118
- :fast_scan => options[:fast_scan],
119
- :max_retries => options[:max_retries],
120
- :ssl_certs => options[:ssl_certs] || nil,
121
- :ssl_verify => options[:ssl_verify]
93
+ :dry_run => config[:dry_run],
94
+ :max_retries => config[:max_retries],
95
+ :ssl_certs => config[:ssl_certs] || nil,
96
+ :ssl_verify => config[:ssl_verify]
122
97
  )
123
98
 
124
99
  imap_to = Larch::IMAP.new(uri_to,
125
- :create_mailbox => !options[:no_create_folder] && !options[:dry_run],
126
- :dry_run => options[:dry_run],
127
- :fast_scan => options[:fast_scan],
128
- :max_retries => options[:max_retries],
129
- :ssl_certs => options[:ssl_certs] || nil,
130
- :ssl_verify => options[:ssl_verify]
100
+ :create_mailbox => !config[:no_create_folder] && !config[:dry_run],
101
+ :dry_run => config[:dry_run],
102
+ :max_retries => config[:max_retries],
103
+ :ssl_certs => config[:ssl_certs] || nil,
104
+ :ssl_verify => config[:ssl_verify]
131
105
  )
132
106
 
133
107
  unless RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince|java/
@@ -139,9 +113,9 @@ EOS
139
113
  end
140
114
  end
141
115
 
142
- if options[:all_given]
116
+ if config.all
143
117
  copy_all(imap_from, imap_to)
144
- elsif options[:all_subscribed_given]
118
+ elsif config.all_subscribed
145
119
  copy_all(imap_from, imap_to, true)
146
120
  else
147
121
  copy_folder(imap_from, imap_to)
data/lib/larch.rb CHANGED
@@ -8,10 +8,12 @@ require 'fileutils'
8
8
  require 'net/imap'
9
9
  require 'time'
10
10
  require 'uri'
11
+ require 'yaml'
11
12
 
12
13
  require 'sequel'
13
14
  require 'sequel/extensions/migration'
14
15
 
16
+ require 'larch/config'
15
17
  require 'larch/errors'
16
18
  require 'larch/imap'
17
19
  require 'larch/imap/mailbox'
@@ -21,21 +23,19 @@ require 'larch/version'
21
23
  module Larch
22
24
 
23
25
  class << self
24
- attr_reader :db, :log, :exclude
26
+ attr_reader :config, :db, :log, :exclude
25
27
 
26
28
  EXCLUDE_COMMENT = /#.*$/
27
29
  EXCLUDE_REGEX = /^\s*\/(.*)\/\s*/
28
30
  GLOB_PATTERNS = {'*' => '.*', '?' => '.'}
29
31
  LIB_DIR = File.join(File.dirname(File.expand_path(__FILE__)), 'larch')
30
32
 
31
- def init(database, config = {})
32
- @config = {
33
- :exclude => [],
34
- :log_level => :info
35
- }.merge(config)
33
+ def init(config)
34
+ raise ArgumentError, "config must be a Larch::Config instance" unless config.is_a?(Config)
36
35
 
37
- @log = Logger.new(@config[:log_level])
38
- @db = open_db(database)
36
+ @config = config
37
+ @log = Logger.new(@config[:verbosity])
38
+ @db = open_db(@config[:database])
39
39
 
40
40
  @exclude = @config[:exclude].map do |e|
41
41
  if e =~ EXCLUDE_REGEX
@@ -47,6 +47,8 @@ module Larch
47
47
 
48
48
  load_exclude_file(@config[:exclude_file]) if @config[:exclude_file]
49
49
 
50
+ Net::IMAP.debug = true if @log.level == :insane
51
+
50
52
  # Stats
51
53
  @copied = 0
52
54
  @failed = 0
@@ -0,0 +1,105 @@
1
+ module Larch
2
+
3
+ class Config
4
+ attr_reader :filename, :section
5
+
6
+ DEFAULT = {
7
+ 'all' => false,
8
+ 'all-subscribed' => false,
9
+ 'config' => File.join('~', '.larch', 'config.yaml'),
10
+ 'database' => File.join('~', '.larch', 'larch.db'),
11
+ 'dry-run' => false,
12
+ 'exclude' => [],
13
+ 'exclude-file' => nil,
14
+ 'from' => nil,
15
+ 'from-folder' => 'INBOX',
16
+ 'from-pass' => nil,
17
+ 'from-user' => nil,
18
+ 'max-retries' => 3,
19
+ 'no-create-folder' => false,
20
+ 'ssl-certs' => nil,
21
+ 'ssl-verify' => false,
22
+ 'to' => nil,
23
+ 'to-folder' => 'INBOX',
24
+ 'to-pass' => nil,
25
+ 'to-user' => nil,
26
+ 'verbosity' => 'info'
27
+ }.freeze
28
+
29
+ def initialize(section = 'default', filename = DEFAULT['config'], override = {})
30
+ @section = section.to_s
31
+ @override = {}
32
+
33
+ override.each do |k, v|
34
+ k = k.to_s.gsub('_', '-')
35
+ @override[k] = v if DEFAULT.has_key?(k) && v != DEFAULT[k]
36
+ end
37
+
38
+ load_file(filename)
39
+ end
40
+
41
+ def fetch(name)
42
+ (@cached || {})[name.to_s.gsub('_', '-')] || nil
43
+ end
44
+ alias [] fetch
45
+
46
+ def load_file(filename)
47
+ @filename = File.expand_path(filename)
48
+
49
+ config = {}
50
+
51
+ if File.exist?(@filename)
52
+ begin
53
+ config = YAML.load_file(@filename)
54
+ rescue => e
55
+ raise Larch::Config::Error, "config error in #{filename}: #{e}"
56
+ end
57
+ end
58
+
59
+ @lookup = [@override, config[@section] || {}, config['default'] || {}, DEFAULT]
60
+ cache_config
61
+ end
62
+
63
+ def method_missing(name)
64
+ fetch(name)
65
+ end
66
+
67
+ def validate
68
+ ['from', 'to'].each do |s|
69
+ raise Error, "'#{s}' must be a valid IMAP URI (e.g. imap://example.com)" unless fetch(s) =~ IMAP::REGEX_URI
70
+ end
71
+
72
+ unless Logger::LEVELS.has_key?(verbosity.to_sym)
73
+ raise Error, "'verbosity' must be one of: #{Logger::LEVELS.keys.join(', ')}"
74
+ end
75
+
76
+ if exclude_file
77
+ raise Error, "exclude file not found: #{exclude_file}" unless File.file?(exclude_file)
78
+ raise Error, "exclude file cannot be read: #{exclude_file}" unless File.readable?(exclude_file)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ # Merges configs such that those earlier in the lookup chain override those
85
+ # later in the chain.
86
+ def cache_config
87
+ @cached = {}
88
+
89
+ @lookup.reverse.each do |c|
90
+ c.each {|k, v| @cached[k] = config_merge(@cached[k] || {}, v) }
91
+ end
92
+ end
93
+
94
+ def config_merge(master, value)
95
+ if value.is_a?(Hash)
96
+ value.each {|k, v| master[k] = config_merge(master[k] || {}, v) }
97
+ return master
98
+ end
99
+
100
+ value
101
+ end
102
+
103
+ end
104
+
105
+ end
data/lib/larch/errors.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  module Larch
2
2
  class Error < StandardError; end
3
3
 
4
+ class Config
5
+ class Error < Larch::Error; end
6
+ end
7
+
4
8
  class IMAP
5
9
  class Error < Larch::Error; end
6
10
  class FatalError < Error; end
data/lib/larch/imap.rb CHANGED
@@ -30,14 +30,6 @@ class IMAP
30
30
  # that it's not actually possible to simulate mailbox creation, so
31
31
  # +:dry_run+ mode always behaves as if +:create_mailbox+ is +false+.
32
32
  #
33
- # [:fast_scan]
34
- # If +true+, a faster but less accurate method will be used to scan
35
- # mailboxes. This will speed up the initial mailbox scan, but will also
36
- # reduce the effectiveness of the message unique id generator. This is
37
- # probably acceptable when copying a very large mailbox to an empty mailbox,
38
- # but if the destination already contains messages, using this option is not
39
- # advised.
40
- #
41
33
  # [:max_retries]
42
34
  # After a recoverable error occurs, retry the operation up to this many
43
35
  # times. Default is 3.
@@ -181,7 +181,7 @@ class Mailbox
181
181
  if @db_mailbox.uidvalidity && @db_mailbox.uidnext &&
182
182
  status['UIDVALIDITY'] == @db_mailbox.uidvalidity
183
183
 
184
- # The UIDVALIDITY is the same as what we saw last time we scanned at this
184
+ # The UIDVALIDITY is the same as what we saw last time we scanned this
185
185
  # mailbox, which means that all the existing messages in the database are
186
186
  # still valid. We only need to request headers for new messages.
187
187
  #
data/lib/larch/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Larch
2
2
  APP_NAME = 'Larch'
3
- APP_VERSION = '1.0.2.2'
3
+ APP_VERSION = '1.0.2.3'
4
4
  APP_AUTHOR = 'Ryan Grove'
5
5
  APP_EMAIL = 'ryan@wonko.com'
6
6
  APP_URL = 'http://github.com/rgrove/larch/'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rgrove-larch
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2.2
4
+ version: 1.0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Grove
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-19 00:00:00 -07:00
12
+ date: 2009-08-22 00:00:00 -07:00
13
13
  default_executable: larch
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -65,6 +65,7 @@ files:
65
65
  - LICENSE
66
66
  - README.rdoc
67
67
  - bin/larch
68
+ - lib/larch/config.rb
68
69
  - lib/larch/db/account.rb
69
70
  - lib/larch/db/mailbox.rb
70
71
  - lib/larch/db/message.rb