rgrove-larch 1.0.2.2 → 1.0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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