sup 0.19.0 → 0.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.gitmodules +3 -0
  4. data/.travis.yml +3 -2
  5. data/CONTRIBUTORS +19 -13
  6. data/Gemfile +4 -0
  7. data/History.txt +41 -0
  8. data/Rakefile +41 -1
  9. data/ReleaseNotes +17 -0
  10. data/bin/sup +5 -18
  11. data/bin/sup-add +1 -2
  12. data/bin/sup-config +0 -1
  13. data/bin/sup-dump +0 -1
  14. data/bin/sup-import-dump +1 -2
  15. data/bin/sup-sync +0 -1
  16. data/bin/sup-sync-back-maildir +1 -2
  17. data/bin/sup-tweak-labels +1 -2
  18. data/contrib/colorpicker.rb +0 -2
  19. data/contrib/completion/_sup.bash +102 -0
  20. data/devel/profile.rb +0 -1
  21. data/ext/mkrf_conf_xapian.rb +47 -0
  22. data/lib/sup.rb +9 -8
  23. data/lib/sup/buffer.rb +12 -0
  24. data/lib/sup/colormap.rb +5 -2
  25. data/lib/sup/contact.rb +4 -2
  26. data/lib/sup/crypto.rb +41 -8
  27. data/lib/sup/draft.rb +8 -8
  28. data/lib/sup/hook.rb +1 -1
  29. data/lib/sup/index.rb +2 -2
  30. data/lib/sup/label.rb +1 -1
  31. data/lib/sup/maildir.rb +16 -5
  32. data/lib/sup/mbox.rb +13 -5
  33. data/lib/sup/message.rb +17 -3
  34. data/lib/sup/message_chunks.rb +10 -2
  35. data/lib/sup/mode.rb +33 -28
  36. data/lib/sup/modes/edit_message_mode.rb +3 -2
  37. data/lib/sup/modes/forward_mode.rb +22 -3
  38. data/lib/sup/modes/line_cursor_mode.rb +1 -1
  39. data/lib/sup/modes/text_mode.rb +6 -1
  40. data/lib/sup/modes/thread_index_mode.rb +11 -1
  41. data/lib/sup/modes/thread_view_mode.rb +103 -9
  42. data/lib/sup/person.rb +68 -61
  43. data/lib/sup/search.rb +1 -1
  44. data/lib/sup/sent.rb +1 -1
  45. data/lib/sup/util.rb +1 -75
  46. data/lib/sup/util/locale_fiddler.rb +24 -0
  47. data/lib/sup/version.rb +1 -1
  48. data/sup.gemspec +22 -5
  49. data/test/{messages → fixtures}/bad-content-transfer-encoding-1.eml +0 -0
  50. data/test/{messages → fixtures}/binary-content-transfer-encoding-2.eml +0 -0
  51. data/test/fixtures/blank-header-fields.eml +71 -0
  52. data/test/fixtures/contacts.txt +1 -0
  53. data/test/fixtures/malicious-attachment-names.eml +55 -0
  54. data/test/fixtures/missing-from-to.eml +18 -0
  55. data/test/{messages → fixtures}/missing-line.eml +0 -0
  56. data/test/fixtures/multi-part-2.eml +72 -0
  57. data/test/fixtures/multi-part.eml +61 -0
  58. data/test/fixtures/no-body.eml +18 -0
  59. data/test/fixtures/simple-message.eml +29 -0
  60. data/test/gnupg_test_home/gpg.conf +2 -1
  61. data/test/gnupg_test_home/key1.gen +15 -0
  62. data/test/gnupg_test_home/key2.gen +15 -0
  63. data/test/gnupg_test_home/key_ecc.gen +13 -0
  64. data/test/gnupg_test_home/pubring.gpg +0 -0
  65. data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
  66. data/test/gnupg_test_home/receiver_secring.gpg +0 -0
  67. data/test/gnupg_test_home/regen_keys.sh +38 -0
  68. data/test/gnupg_test_home/secring.gpg +0 -0
  69. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +22 -17
  70. data/test/integration/test_maildir.rb +75 -0
  71. data/test/integration/test_mbox.rb +69 -0
  72. data/test/test_crypto.rb +12 -2
  73. data/test/test_header_parsing.rb +1 -1
  74. data/test/test_helper.rb +6 -3
  75. data/test/test_message.rb +42 -342
  76. data/test/test_messages_dir.rb +4 -28
  77. data/test/test_yaml_regressions.rb +1 -1
  78. data/test/unit/test_contact.rb +33 -0
  79. data/test/unit/test_locale_fiddler.rb +15 -0
  80. data/test/unit/test_person.rb +37 -0
  81. metadata +108 -38
  82. data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
  83. data/test/gnupg_test_home/trustdb.gpg +0 -0
@@ -18,11 +18,16 @@ class Person
18
18
  @email = email.strip.gsub(/\s+/, " ")
19
19
  end
20
20
 
21
- def to_s; "#@name <#@email>" end
21
+ def to_s
22
+ if @name
23
+ "#@name <#@email>"
24
+ else
25
+ @email
26
+ end
27
+ end
22
28
 
23
29
  # def == o; o && o.email == email; end
24
30
  # alias :eql? :==
25
- # def hash; [name, email].hash; end
26
31
 
27
32
  def shortname
28
33
  case @name
@@ -37,26 +42,10 @@ class Person
37
42
  end
38
43
  end
39
44
 
40
- def longname
41
- if @name && @email
42
- "#@name <#@email>"
43
- else
44
- @email
45
- end
46
- end
47
-
48
45
  def mediumname; @name || @email; end
49
46
 
50
- def Person.full_address name, email
51
- if name && email
52
- if name =~ /[",@]/
53
- "#{name.inspect} <#{email}>" # escape quotes
54
- else
55
- "#{name} <#{email}>"
56
- end
57
- else
58
- email
59
- end
47
+ def longname
48
+ to_s
60
49
  end
61
50
 
62
51
  def full_address
@@ -79,56 +68,74 @@ class Person
79
68
  end.downcase
80
69
  end
81
70
 
82
- ## return "canonical" person using contact manager or create one if
83
- ## not found or contact manager not available
84
- def self.from_name_and_email name, email
85
- ContactManager.instantiated? && ContactManager.person_for(email) || Person.new(name, email)
71
+ def eql? o; email.eql? o.email end
72
+ def hash; email.hash end
73
+
74
+
75
+ ## see comments in self.from_address
76
+ def indexable_content
77
+ [name, email, email.split(/@/).first].join(" ")
86
78
  end
87
79
 
88
- def self.from_address s
89
- return nil if s.nil?
90
-
91
- ## try and parse an email address and name
92
- name, email = case s
93
- when /(.+?) ((\S+?)@\S+) \3/
94
- ## ok, this first match cause is insane, but bear with me. email
95
- ## addresses are stored in the to/from/etc fields of the index in a
96
- ## weird format: "name address first-part-of-address", i.e. spaces
97
- ## separating those three bits, and no <>'s. this is the output of
98
- ## #indexable_content. here, we reverse-engineer that format to extract
99
- ## a valid address.
100
- ##
101
- ## we store things this way to allow searches on a to/from/etc field to
102
- ## match any of those parts. a more robust solution would be to store a
103
- ## separate, non-indexed field with the proper headers. but this way we
104
- ## save precious bits, and it's backwards-compatible with older indexes.
105
- [$1, $2]
106
- when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/
107
- a, b = $1, $2
108
- [a.gsub('\"', '"'), b]
109
- when /<((\S+?)@\S+?)>/
110
- [$2, $1]
111
- when /((\S+?)@\S+)/
112
- [$2, $1]
80
+ class << self
81
+
82
+ def full_address name, email
83
+ if name && email
84
+ if name =~ /[",@]/
85
+ "#{name.inspect} <#{email}>" # escape quotes
86
+ else
87
+ "#{name} <#{email}>"
88
+ end
113
89
  else
114
- [nil, s]
90
+ email
115
91
  end
92
+ end
116
93
 
117
- from_name_and_email name, email
118
- end
94
+ ## return "canonical" person using contact manager or create one if
95
+ ## not found or contact manager not available
96
+ def from_name_and_email name, email
97
+ ContactManager.instantiated? && ContactManager.person_for(email) || Person.new(name, email)
98
+ end
119
99
 
120
- def self.from_address_list ss
121
- return [] if ss.nil?
122
- ss.dup.split_on_commas.map { |s| self.from_address s }
123
- end
100
+ def from_address s
101
+ return nil if s.nil?
102
+
103
+ ## try and parse an email address and name
104
+ name, email = case s
105
+ when /(.+?) ((\S+?)@\S+) \3/
106
+ ## ok, this first match cause is insane, but bear with me. email
107
+ ## addresses are stored in the to/from/etc fields of the index in a
108
+ ## weird format: "name address first-part-of-address", i.e. spaces
109
+ ## separating those three bits, and no <>'s. this is the output of
110
+ ## #indexable_content. here, we reverse-engineer that format to extract
111
+ ## a valid address.
112
+ ##
113
+ ## we store things this way to allow searches on a to/from/etc field to
114
+ ## match any of those parts. a more robust solution would be to store a
115
+ ## separate, non-indexed field with the proper headers. but this way we
116
+ ## save precious bits, and it's backwards-compatible with older indexes.
117
+ [$1, $2]
118
+ when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/
119
+ a, b = $1, $2
120
+ [a.gsub('\"', '"'), b]
121
+ when /<((\S+?)@\S+?)>/
122
+ [$2, $1]
123
+ when /((\S+?)@\S+)/
124
+ [$2, $1]
125
+ else
126
+ [nil, s]
127
+ end
128
+
129
+ from_name_and_email name, email
130
+ end
131
+
132
+ def from_address_list ss
133
+ return [] if ss.nil?
134
+ ss.dup.split_on_commas.map { |s| self.from_address s }
135
+ end
124
136
 
125
- ## see comments in self.from_address
126
- def indexable_content
127
- [name, email, email.split(/@/).first].join(" ")
128
137
  end
129
138
 
130
- def eql? o; email.eql? o.email end
131
- def hash; email.hash end
132
139
  end
133
140
 
134
141
  end
@@ -12,7 +12,7 @@ class SearchManager
12
12
  def initialize fn
13
13
  @fn = fn
14
14
  @searches = {}
15
- if File.exists? fn
15
+ if File.exist? fn
16
16
  IO.foreach(fn) do |l|
17
17
  l =~ /^([^:]*): (.*)$/ or raise "can't parse #{fn} line #{l.inspect}"
18
18
  @searches[$1] = $2
@@ -40,7 +40,7 @@ class SentLoader < MBox
40
40
 
41
41
  def initialize
42
42
  @filename = Redwood::SENT_FN
43
- File.open(@filename, "w") { } unless File.exists? @filename
43
+ File.open(@filename, "w") { } unless File.exist? @filename
44
44
  super "mbox://" + @filename, true, $config[:archive_sent]
45
45
  end
46
46
 
@@ -10,15 +10,6 @@ require 'benchmark'
10
10
  require 'unicode'
11
11
  require 'fileutils'
12
12
 
13
- ## time for some monkeypatching!
14
- class Symbol
15
- unless method_defined? :to_proc
16
- def to_proc
17
- proc { |obj, *args| obj.send(self, *args) }
18
- end
19
- end
20
- end
21
-
22
13
  class Lockfile
23
14
  def gen_lock_id
24
15
  Hash[
@@ -89,7 +80,7 @@ module RMail
89
80
  end
90
81
 
91
82
  def charset
92
- if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/i
83
+ if header.field?("content-type") && header.fetch("content-type") =~ /charset\s*=\s*"?(.*?)"?(;|$)/i
93
84
  $1
94
85
  end
95
86
  end
@@ -170,13 +161,6 @@ module RMail
170
161
  end
171
162
  end
172
163
 
173
- class Range
174
- ## only valid for integer ranges (unless I guess it's exclusive)
175
- def size
176
- last - first + (exclude_end? ? 0 : 1)
177
- end
178
- end
179
-
180
164
  class Module
181
165
  def bool_reader *args
182
166
  args.each { |sym| class_eval %{ def #{sym}?; @#{sym}; end } }
@@ -198,17 +182,6 @@ class Module
198
182
  end
199
183
 
200
184
  class Object
201
- def ancestors
202
- ret = []
203
- klass = self.class
204
-
205
- until klass == Object
206
- ret << klass
207
- klass = klass.superclass
208
- end
209
- ret
210
- end
211
-
212
185
  ## "k combinator"
213
186
  def returning x; yield x; x; end
214
187
 
@@ -382,8 +355,6 @@ class String
382
355
 
383
356
  # Fix the damn string! make sure it is valid utf-8, then convert to
384
357
  # user encoding.
385
- #
386
- # Not Ruby 1.8 compatible
387
358
  def fix_encoding!
388
359
  # first try to encode to utf-8 from whatever current encoding
389
360
  encode!('UTF-8', :invalid => :replace, :undef => :replace)
@@ -406,8 +377,6 @@ class String
406
377
 
407
378
  # transcode the string if original encoding is know
408
379
  # fix if broken.
409
- #
410
- # Not Ruby 1.8 compatible
411
380
  def transcode to_encoding, from_encoding
412
381
  begin
413
382
  encode!(to_encoding, from_encoding, :invalid => :replace, :undef => :replace)
@@ -474,13 +443,6 @@ class String
474
443
  out = out.fix_encoding! # this should now be an utf-8 string of ascii
475
444
  # compat chars.
476
445
  end
477
-
478
- unless method_defined? :ascii_only?
479
- def ascii_only?
480
- size.times { |i| return false if self[i] & 128 != 0 }
481
- return true
482
- end
483
- end
484
446
  end
485
447
 
486
448
  class Numeric
@@ -518,12 +480,6 @@ class Fixnum
518
480
  end
519
481
  end
520
482
 
521
- unless method_defined?(:ord)
522
- def ord
523
- self
524
- end
525
- end
526
-
527
483
  ## hacking the english language
528
484
  def pluralize s
529
485
  to_s + " " +
@@ -607,10 +563,6 @@ module Enumerable
607
563
  end
608
564
  end
609
565
 
610
- unless Object.const_defined? :Enumerator
611
- Enumerator = Enumerable::Enumerator
612
- end
613
-
614
566
  class Array
615
567
  def flatten_one_level
616
568
  inject([]) { |a, e| a + e }
@@ -706,32 +658,6 @@ class SavingHash
706
658
  defer_all_other_method_calls_to :hash
707
659
  end
708
660
 
709
- class OrderedHash < Hash
710
- alias_method :store, :[]=
711
- alias_method :each_pair, :each
712
- attr_reader :keys
713
-
714
- def initialize *a
715
- @keys = []
716
- a.each { |k, v| self[k] = v }
717
- end
718
-
719
- def []= key, val
720
- @keys << key unless member?(key)
721
- super
722
- end
723
-
724
- def values; keys.map { |k| self[k] } end
725
- def index key; @keys.index key end
726
-
727
- def delete key
728
- @keys.delete key
729
- super
730
- end
731
-
732
- def each; @keys.each { |k| yield k, self[k] } end
733
- end
734
-
735
661
  ## easy thread-safe class for determining who's the "winner" in a race (i.e.
736
662
  ## first person to hit the finish line
737
663
  class FinishLine
@@ -0,0 +1,24 @@
1
+ ## the following magic enables wide characters when used with a ruby
2
+ ## ncurses.so that's been compiled against libncursesw. (note the w.) why
3
+ ## this works, i have no idea. much like pretty much every aspect of
4
+ ## dealing with curses. cargo cult programming at its best.
5
+ require 'fiddle'
6
+ require 'fiddle/import'
7
+
8
+ module LocaleFiddler
9
+ extend Fiddle::Importer
10
+
11
+ SETLOCALE_LIB = case RbConfig::CONFIG['arch']
12
+ when /darwin/; "libc.dylib"
13
+ when /cygwin/; "cygwin1.dll"
14
+ when /freebsd/; "libc.so.7"
15
+ else; "libc.so.6"
16
+ end
17
+
18
+ dlload SETLOCALE_LIB
19
+ extern "char *setlocale(int, char const *)"
20
+
21
+ def setlocale(type, string)
22
+ LocaleFiddler.setlocale(type, string)
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Redwood
2
- VERSION = "0.19.0"
2
+ VERSION = "0.22.1"
3
3
  end
@@ -25,29 +25,46 @@ DESC
25
25
  SUP: please note that our old mailing lists have been shut down,
26
26
  re-subscribe to supmua@googlegroups.com to discuss and follow
27
27
  updates on sup (send email to: supmua+subscribe@googlegroups.com).
28
+
29
+ OpenBSD users:
30
+ If your operating system is OpenBSD you have some
31
+ additional, manual steps to do before Sup will work, see:
32
+ https://github.com/sup-heliotrope/sup/wiki/Installation%3A-OpenBSD.
28
33
  EOF
29
34
 
30
35
  s.files = `git ls-files -z`.split("\x0")
31
36
  s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
32
37
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
33
38
  s.require_paths = ["lib"]
39
+ s.extra_rdoc_files = Dir.glob("man/*")
40
+
41
+ s.required_ruby_version = '>= 2.0.0'
34
42
 
35
- s.required_ruby_version = '>= 1.9.3'
43
+ # this is here to support skipping the xapian-ruby installation on OpenBSD
44
+ # because the xapian-ruby gem doesn't install on OpenBSD, you must install
45
+ # xapian-core and xapian-bindings manually on OpenBSD
46
+ # see https://github.com/sup-heliotrope/sup/wiki/Installation%3A-OpenBSD
47
+ # and https://en.wikibooks.org/wiki/Ruby_Programming/RubyGems#How_to_install_different_versions_of_gems_depending_on_which_version_of_ruby_the_installee_is_using
48
+ s.extensions = %w[ext/mkrf_conf_xapian.rb]
49
+
50
+ ## remember to update the xapian dependency in
51
+ ## ext/mkrf_conf_xapian.rb and Gemfile.
36
52
 
37
- s.add_runtime_dependency "xapian-ruby", "~> 1.2.15"
38
53
  s.add_runtime_dependency "ncursesw", "~> 1.4.0"
39
54
  s.add_runtime_dependency "rmail-sup", "~> 1.0.1"
40
55
  s.add_runtime_dependency "highline"
41
56
  s.add_runtime_dependency "trollop", ">= 1.12"
42
57
  s.add_runtime_dependency "lockfile"
43
- s.add_runtime_dependency "mime-types", "~> 1.0"
58
+ s.add_runtime_dependency "mime-types", "> 2.0"
44
59
  s.add_runtime_dependency "locale", "~> 2.0"
45
60
  s.add_runtime_dependency "chronic", "~> 0.9.1"
46
61
  s.add_runtime_dependency "unicode", "~> 0.4.4"
47
62
 
48
63
  s.add_development_dependency "bundler", "~> 1.3"
49
64
  s.add_development_dependency "rake"
50
- s.add_development_dependency "minitest", "~> 4.7"
51
- s.add_development_dependency "rr", "~> 1.0.5"
65
+ s.add_development_dependency 'minitest', '~> 5.5.1'
66
+ s.add_development_dependency "rr", "~> 1.1"
52
67
  s.add_development_dependency "gpgme", ">= 2.0.2"
68
+ s.add_development_dependency "pry"
69
+
53
70
  end
@@ -0,0 +1,71 @@
1
+ Return-Path: <monitor-list-bounces@widget.com>
2
+ X-Original-To: nobody@localhost
3
+ Delivered-To: nobody@localhost.eng.widget.com
4
+ Received: from localhost (localhost.localdomain [127.0.0.1])
5
+ by soquel.eng.widget.com (Postfix) with ESMTP id 609BC13C0DB1
6
+ for <nobody@localhost>; Thu, 19 Mar 2009 13:43:21 -0700 (PDT)
7
+ MIME-Version: 1.0
8
+ Received: from pa-excas-vip.widget.com [10.16.67.200]
9
+ by localhost with IMAP (fetchmail-6.2.5)
10
+ for nobody@localhost (single-drop); Thu, 19 Mar 2009 13:43:21 -0700 (PDT)
11
+ Received: from pa-exht01.widget.com (10.113.81.167) by pa-excaht11.widget.com
12
+ (10.113.81.197) with Microsoft SMTP Server (TLS) id 8.1.311.2; Thu, 19 Mar
13
+ 2009 13:42:30 -0700
14
+ Received: from mailman2.widget.com (10.16.64.159) by pa-exht01.widget.com
15
+ (10.113.81.167) with Microsoft SMTP Server id 8.1.336.0; Thu, 19 Mar 2009
16
+ 13:42:30 -0700
17
+ Received: by mailman2.widget.com (Postfix) id 47095AE30856; Thu, 19 Mar 2009
18
+ 13:42:29 -0700 (PDT)
19
+ Received: from countchocula.widget.com (localhost.localdomain [127.0.0.1]) by
20
+ mailman2.widget.com (Postfix) with ESMTP id 5F782ABC5948; Thu, 19 Mar 2009
21
+ 13:42:28 -0700 (PDT)
22
+ Received: from mailhost4.widget.com (mailhost4.widget.com [10.16.67.124]) by
23
+ mailman2.widget.com (Postfix) with ESMTP id 6CDCCABC5948 for
24
+ <monitor-list@mailman2.widget.com>; Thu, 19 Mar 2009 13:42:26 -0700 (PDT)
25
+ Received: by mailhost4.widget.com (Postfix) id 2364AC9AC4; Thu, 19 Mar 2009
26
+ 13:42:26 -0700 (PDT)
27
+ Received: from pa-exht01.widget.com (pa-exht01.widget.com [10.113.81.167]) by
28
+ mailhost4.widget.com (Postfix) with ESMTP id 17A68C9AC3 for
29
+ <monitor-list@widget.com>; Thu, 19 Mar 2009 13:42:26 -0700 (PDT)
30
+ Received: from PA-EXMBX04.widget.com ([10.113.81.142]) by pa-exht01.widget.com
31
+ ([10.113.81.167]) with mapi; Thu, 19 Mar 2009 13:42:26 -0700
32
+ From: Some User <someuser@widget.com>
33
+ To: "monitor-list@widget.com" <monitor-list@widget.com>
34
+ Sender: "monitor-list-bounces@widget.com" <monitor-list-bounces@widget.com>
35
+ Date: Thu, 19 Mar 2009 13:42:25 -0700
36
+ Subject: Looking for a mac
37
+ Thread-Topic: Looking for a mac
38
+ Thread-Index: AQHJqNM1xIqqjNRWuUCUBaxzPFK5eQ==
39
+ Message-ID:
40
+ <D3C12B2AD838B44DA9D6B2CA334246D011E72A73A4@PA-EXMBX04.widget.com>
41
+ List-Help: <mailto:monitor-list-request@widget.com?subject=help>
42
+ List-Subscribe: <http://mailman2.widget.com/mailman/listinfo/monitor-list>,
43
+ <mailto:monitor-list-request@widget.com?subject=subscribe>
44
+ List-Unsubscribe:
45
+ <http://mailman2.widget.com/mailman/listinfo/monitor-list>,
46
+ <mailto:monitor-list-request@widget.com?subject=unsubscribe>
47
+ Accept-Language: en-US
48
+ Content-Language: en-US
49
+ X-MS-Exchange-Organization-AuthAs: Anonymous
50
+ X-MS-Exchange-Organization-AuthSource: pa-exht01.widget.com
51
+ X-MS-Has-Attach:
52
+ X-Auto-Response-Suppress: All
53
+ X-MS-TNEF-Correlator:
54
+ acceptlanguage: en-US
55
+ delivered-to: monitor-list@widget.com
56
+ errors-to: monitor-list-bounces@widget.com
57
+ list-id: engineering monitor related <monitor-list.widget.com>
58
+ x-mailman-version: 2.1.8
59
+ x-beenthere: monitor-list@widget.com
60
+ x-original-to: monitor-list@mailman2.widget.com
61
+ list-post: <mailto:monitor-list@widget.com>
62
+ list-archive: <http://mailman2.widget.com/pipermail/monitor-list>
63
+ Content-Type: text/plain; charset="us-ascii"
64
+ Content-Transfer-Encoding: quoted-printable
65
+
66
+ Hi all,
67
+
68
+ Just wondering if anybody can lend me a mac to reproduce PR 384931 ?
69
+ Thanks.
70
+
71
+ Michael=