whimsy-asf 0.0.75 → 0.0.76

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fbe033a993bc2428dbed396c0a98fece7b33f82b
4
- data.tar.gz: 17ca4624c5340b1354ff01f540999338d426f2b0
3
+ metadata.gz: 42ee442311d0fc054e4dd8fb6f2bdb4635d03ce1
4
+ data.tar.gz: d556f03175acf95b7882d59b1abfe0b3332c81b5
5
5
  SHA512:
6
- metadata.gz: 801153db8122f912ead1cd2df9a56ad65349bb50c95b1719497dd65da332862c2b3ccbf0e26b92f17981dc1ccb5a34474b2bd1eb4266a6267eb5104b97de34cf
7
- data.tar.gz: 28230fd4887ed1198b94f5d27bba0d2a38eec271ffeaeaa4d010d4aa9b07129a27ab0bb21b621058df05ed551c2ff621218237cff9a003496c3f5fc316d76824
6
+ metadata.gz: 5a00c4fa7f49262e06d04cdffb11929b1955e27afb55b3bc5f2224703a1ccd3d6d609a3656e6a6a3d54677bfcd8e7191641e944888aeebc8359028ab33d7c57c
7
+ data.tar.gz: ed851c6a5edc6fef8325345c96d75d9abfaafa2b1e6753b2de2a8ff3d65ccdb30f35f5bbc74fc0912eb2cdd87227c27d08c4564610b753a06e84721638b2525c
@@ -1 +1 @@
1
- 0.0.75
1
+ 0.0.76
@@ -35,7 +35,7 @@ class ASF::Board::Agenda
35
35
  unless @quick
36
36
  begin
37
37
  committee = ASF::Committee.find(attrs['title'])
38
- attrs['chair_email'] = committee.chair.mail.first
38
+ attrs['chair_email'] = "#{committee.chair.id}@apache.org"
39
39
  attrs['mail_list'] = committee.mail_list
40
40
  attrs.delete('mail_list') if attrs['mail_list'].include? ' '
41
41
 
@@ -6,11 +6,10 @@ module ASF
6
6
  end
7
7
 
8
8
  class Committee < Base
9
- attr_accessor :info, :emeritus, :report, :roster, :established, :chairs,
9
+ attr_accessor :info, :report, :roster, :established, :chairs,
10
10
  :schedule
11
11
  def initialize(*args)
12
12
  @info = []
13
- @emeritus = []
14
13
  @chairs = []
15
14
  @roster = {}
16
15
  super
@@ -49,12 +48,12 @@ module ASF
49
48
  file = "#{board}/committee-info.txt"
50
49
  return unless File.exist? file
51
50
 
52
- list = Hash.new {|hash, name| hash[name] = find(name)}
53
-
54
51
  if @committee_info and File.mtime(file) == @committee_mtime
55
52
  return @committee_info
56
53
  end
57
54
 
55
+ list = Hash.new {|hash, name| hash[name] = find(name)}
56
+
58
57
  @committee_mtime = File.mtime(file)
59
58
  @@svn_change = Time.parse(
60
59
  `svn info #{file}`[/Last Changed Date: (.*) \(/, 1]).gmtime
@@ -86,11 +85,6 @@ module ASF
86
85
  committee = list[@@namemap.call(roster[/(\w.*?)\s+\(/,1])]
87
86
  # get the start date
88
87
  committee.established = roster[/\(est\. (.*?)\)/, 1]
89
- # Extract any emeritus members (now probably redundant)
90
- roster.gsub! /^.*\(\s*emeritus\s*\).*/i do |line|
91
- committee.emeritus += line.scan(/<(.*?)@apache\.org>/).flatten
92
- ''
93
- end
94
88
  # extract the availids (is this used?)
95
89
  committee.info = roster.scan(/<(.*?)@apache\.org>/).flatten
96
90
  # drop (chair) markers and extract 0: name, 1: availid, 2: [date], 3: date
@@ -1,7 +1,10 @@
1
1
  module ASF
2
2
 
3
3
  class ICLA
4
- attr_accessor :id, :legal_name, :name, :email
4
+ # N.B. only id and name should be considered public
5
+ # form and claRef may contain details of the legal name beyond that in the public name
6
+ attr_accessor :id, :legal_name, :name, :email, :form
7
+ attr_accessor :claRef # cla name or SVN revision info
5
8
 
6
9
  @@mtime = nil
7
10
 
@@ -31,9 +34,15 @@ module ASF
31
34
 
32
35
  # load ICLA information for every committer
33
36
  def self.preload
37
+ people = []
34
38
  each do |icla|
35
- ASF::Person.find(icla.id).icla = icla unless icla.id == 'notinaval'
39
+ unless icla.id == 'notinaval'
40
+ person = ASF::Person.find(icla.id)
41
+ people << person
42
+ person.icla = icla
43
+ end
36
44
  end
45
+ people
37
46
  end
38
47
 
39
48
  # find ICLA by ID
@@ -94,12 +103,18 @@ module ASF
94
103
  elsif @@name_index and not @@name_index.empty?
95
104
  @@name_index.values.each(&block)
96
105
  elsif SOURCE and File.exist?(SOURCE)
97
- File.read(SOURCE).scan(/^([-\w]+):(.*?):(.*?):(.*?):/).each do |list|
106
+ File.read(SOURCE).scan(/^([-\w]+):(.*?):(.*?):(.*?):(.*)/).each do |list|
98
107
  icla = ICLA.new()
99
108
  icla.id = list[0]
100
109
  icla.legal_name = list[1]
101
110
  icla.name = list[2]
102
111
  icla.email = list[3]
112
+ icla.form = list[4]
113
+ match = icla.form.match(/^Signed CLA(?:;(\S+)| \((\+=.+)\))/)
114
+ if match
115
+ # match either the cla name or the SVN ref (+=...)
116
+ icla.claRef = match[1] || match[2]
117
+ end
103
118
  block.call(icla)
104
119
  end
105
120
  end
@@ -33,6 +33,7 @@ require 'ldap'
33
33
  require 'weakref'
34
34
  require 'net/http'
35
35
  require 'base64'
36
+ require 'thread'
36
37
 
37
38
  module ASF
38
39
  module LDAP
@@ -48,6 +49,9 @@ module ASF
48
49
  ldaps://ldap2-lw-eu.apache.org:636
49
50
  )
50
51
 
52
+ CONNECT_LOCK = Mutex.new
53
+ HOST_QUEUE = Queue.new
54
+
51
55
  # fetch configuration from apache/infrastructure-puppet
52
56
  def self.puppet_config
53
57
  return @puppet if @puppet
@@ -71,8 +75,13 @@ module ASF
71
75
  end
72
76
 
73
77
  # connect to LDAP
74
- def self.connect
75
- hosts.shuffle.each do |host|
78
+ def self.connect(test = true)
79
+ # Try each host at most once
80
+ hosts.length.times do
81
+ # Ensure we use each host in turn
82
+ hosts.each {|host| HOST_QUEUE.push host} if HOST_QUEUE.empty?
83
+ host = HOST_QUEUE.shift
84
+
76
85
  Wunderbar.info "Connecting to LDAP server: #{host}"
77
86
 
78
87
  begin
@@ -85,7 +94,7 @@ module ASF
85
94
  end
86
95
 
87
96
  # test the connection
88
- ldap.bind
97
+ ldap.bind if test
89
98
 
90
99
  # save the host
91
100
  @host = host
@@ -97,14 +106,18 @@ module ASF
97
106
  end
98
107
 
99
108
  end
109
+
100
110
  Wunderbar.error "Failed to connect to any LDAP host"
101
111
  return nil
102
112
  end
103
113
  end
104
114
 
105
- # backwards compatibility for tools that called this interface
106
- def self.init_ldap
107
- @ldap ||= ASF::LDAP.connect
115
+ # public entry point for establishing a connection safely
116
+ def self.init_ldap(reset = false)
117
+ ASF::LDAP::CONNECT_LOCK.synchronize do
118
+ @ldap = nil if reset
119
+ @ldap ||= ASF::LDAP.connect(!reset)
120
+ end
108
121
  end
109
122
 
110
123
  # determine where ldap.conf resides
@@ -118,23 +131,39 @@ module ASF
118
131
  @ldap || self.init_ldap
119
132
  end
120
133
 
121
- # search with a scope of one
134
+ # search with a scope of one, with automatic retry/failover
122
135
  def self.search_one(base, filter, attrs=nil)
123
- init_ldap unless defined? @ldap
124
- return [] unless @ldap
125
136
 
126
- Wunderbar.info "ldapsearch -x -LLL -b #{base} -s one #{filter} " +
137
+ cmd = "ldapsearch -x -LLL -b #{base} -s one #{filter} " +
127
138
  "#{[attrs].flatten.join(' ')}"
128
-
139
+
140
+ # try once per host, with a minimum of two tries
141
+ attempts_left = [ASF::LDAP.hosts.length, 2].max
129
142
  begin
143
+ attempts_left -= 1
144
+ init_ldap unless @ldap
145
+ return [] unless @ldap
146
+
147
+ target = @ldap.get_option(::LDAP::LDAP_OPT_HOST_NAME) rescue '?'
148
+ Wunderbar.info "[#{target}] #{cmd}"
149
+
130
150
  result = @ldap.search2(base, ::LDAP::LDAP_SCOPE_ONELEVEL, filter, attrs)
131
- rescue
132
- result = []
151
+ rescue Exception => re
152
+ if attempts_left <= 0
153
+ Wunderbar.error "[#{target}] => #{re.inspect} for #{cmd}"
154
+ raise
155
+ else
156
+ Wunderbar.warn "[#{target}] => #{re.inspect} for #{cmd}, retrying ..."
157
+ @ldap.unbind if @ldap.bound? rescue nil
158
+ @ldap = nil # force new connection
159
+ sleep 1
160
+ retry
161
+ end
133
162
  end
134
163
 
135
164
  result.map! {|hash| hash[attrs]} if String === attrs
136
165
 
137
- result
166
+ result.compact
138
167
  end
139
168
 
140
169
  # safely dereference a weakref array attribute. Block provided is
@@ -146,7 +175,9 @@ module ASF
146
175
  rescue WeakRef::RefError
147
176
  value = block.call
148
177
  ensure
149
- if value and not value.instance_of? WeakRef
178
+ if not value or RUBY_VERSION.start_with? '1'
179
+ object.instance_variable_set(attr, value)
180
+ elsif value and not value.instance_of? WeakRef
150
181
  object.instance_variable_set(attr, WeakRef.new(value))
151
182
  end
152
183
  end
@@ -313,7 +344,7 @@ module ASF
313
344
  end
314
345
 
315
346
  def pgp_key_fingerprints
316
- attrs['asf-pgpKeyFingerprint']
347
+ attrs['asf-pgpKeyFingerprint'] || []
317
348
  end
318
349
 
319
350
  def urls
@@ -383,17 +414,18 @@ module ASF
383
414
  end
384
415
 
385
416
  def self.preload
386
- Hash[ASF.search_one(base, "cn=*", %w(dn memberUid modifyTimestamp)).map do |results|
417
+ Hash[ASF.search_one(base, "cn=*", %w(dn memberUid modifyTimestamp createTimestamp)).map do |results|
387
418
  cn = results['dn'].first[/^cn=(.*?),/, 1]
388
419
  group = ASF::Group.find(cn)
389
420
  group.modifyTimestamp = results['modifyTimestamp'].first # it is returned as an array of 1 entry
390
- members = results['memberUid']
391
- group.members = members || []
421
+ group.createTimestamp = results['createTimestamp'].first # it is returned as an array of 1 entry
422
+ members = results['memberUid'] || []
423
+ group.members = members
392
424
  [group, members]
393
425
  end]
394
426
  end
395
427
 
396
- attr_accessor :modifyTimestamp
428
+ attr_accessor :modifyTimestamp, :createTimestamp
397
429
 
398
430
  def members=(members)
399
431
  @members = WeakRef.new(members)
@@ -406,6 +438,24 @@ module ASF
406
438
 
407
439
  members.map {|uid| Person.find(uid)}
408
440
  end
441
+
442
+ def dn
443
+ @dn ||= ASF.search_one(base, "cn=#{name}", 'dn').first.first
444
+ end
445
+
446
+ def remove(people)
447
+ people = Array(people).map(&:id)
448
+ mod = ::LDAP::Mod.new(::LDAP::LDAP_MOD_DELETE, 'memberUid', people)
449
+ ASF.ldap.modify(self.dn, [mod])
450
+ @members = nil
451
+ end
452
+
453
+ def add(people)
454
+ people = Array(people).map(&:dn)
455
+ mod = ::LDAP::Mod.new(::LDAP::LDAP_MOD_ADD, 'memberUid', people)
456
+ ASF.ldap.modify(self.dn, [mod])
457
+ @members = nil
458
+ end
409
459
  end
410
460
 
411
461
  class Committee < Base
@@ -416,17 +466,18 @@ module ASF
416
466
  end
417
467
 
418
468
  def self.preload
419
- Hash[ASF.search_one(base, "cn=*", %w(dn member modifyTimestamp)).map do |results|
469
+ Hash[ASF.search_one(base, "cn=*", %w(dn member modifyTimestamp createTimestamp)).map do |results|
420
470
  cn = results['dn'].first[/^cn=(.*?),/, 1]
421
471
  committee = ASF::Committee.find(cn)
422
472
  committee.modifyTimestamp = results['modifyTimestamp'].first # it is returned as an array of 1 entry
423
- members = results['member']
473
+ committee.createTimestamp = results['createTimestamp'].first # it is returned as an array of 1 entry
474
+ members = results['member'] || []
424
475
  committee.members = members
425
476
  [committee, members]
426
477
  end]
427
478
  end
428
479
 
429
- attr_accessor :modifyTimestamp
480
+ attr_accessor :modifyTimestamp, :createTimestamp
430
481
 
431
482
  def members=(members)
432
483
  @members = WeakRef.new(members)
@@ -443,6 +494,20 @@ module ASF
443
494
  def dn
444
495
  @dn ||= ASF.search_one(base, "cn=#{name}", 'dn').first.first
445
496
  end
497
+
498
+ def remove(people)
499
+ people = Array(people).map(&:dn)
500
+ mod = ::LDAP::Mod.new(::LDAP::LDAP_MOD_DELETE, 'member', people)
501
+ ASF.ldap.modify(self.dn, [mod])
502
+ @members = nil
503
+ end
504
+
505
+ def add(people)
506
+ people = Array(people).map(&:dn)
507
+ mod = ::LDAP::Mod.new(::LDAP::LDAP_MOD_ADD, 'member', people)
508
+ ASF.ldap.modify(self.dn, [mod])
509
+ @members = nil
510
+ end
446
511
  end
447
512
 
448
513
  class Service < Base
@@ -456,21 +521,44 @@ module ASF
456
521
  "cn=#{id},#{self.class.base}"
457
522
  end
458
523
 
524
+ def self.preload
525
+ Hash[ASF.search_one(base, "cn=*", %w(dn member modifyTimestamp createTimestamp)).map do |results|
526
+ cn = results['dn'].first[/^cn=(.*?),/, 1]
527
+ service = ASF::Service.find(cn)
528
+ service.modifyTimestamp = results['modifyTimestamp'].first # it is returned as an array of 1 entry
529
+ service.createTimestamp = results['createTimestamp'].first # it is returned as an array of 1 entry
530
+ members = results['member'] || []
531
+ service.members = members
532
+ [service, members]
533
+ end]
534
+ end
535
+
536
+ attr_accessor :modifyTimestamp, :createTimestamp
537
+
538
+ def members=(members)
539
+ @members = WeakRef.new(members)
540
+ end
541
+
459
542
  def members
460
- ASF.search_one(base, "cn=#{name}", 'member').flatten.
461
- map {|uid| Person.find uid[/uid=(.*?),/,1]}
543
+ members = weakref(:members) do
544
+ ASF.search_one(base, "cn=#{name}", 'member').flatten
545
+ end
546
+
547
+ members.map {|uid| Person.find uid[/uid=(.*?),/,1]}
462
548
  end
463
549
 
464
550
  def remove(people)
465
551
  people = Array(people).map(&:dn)
466
552
  mod = ::LDAP::Mod.new(::LDAP::LDAP_MOD_DELETE, 'member', people)
467
553
  ASF.ldap.modify(self.dn, [mod])
554
+ @members = nil
468
555
  end
469
556
 
470
557
  def add(people)
471
558
  people = Array(people).map(&:dn)
472
559
  mod = ::LDAP::Mod.new(::LDAP::LDAP_MOD_ADD, 'member', people)
473
560
  ASF.ldap.modify(self.dn, [mod])
561
+ @members = nil
474
562
  end
475
563
  end
476
564
 
@@ -479,12 +567,13 @@ module ASF
479
567
  dn = ASF::Person.new(user).dn
480
568
  raise ::LDAP::ResultError.new('Unknown user') unless dn
481
569
 
482
- ASF.ldap.unbind rescue nil
570
+ ASF.ldap.unbind if ASF.ldap.bound? rescue nil
571
+ ldap = ASF.init_ldap(true)
483
572
  if block
484
- ASF.ldap.bind(dn, password, &block)
485
- ASF.init_ldap
573
+ ldap.bind(dn, password, &block)
574
+ ASF.init_ldap(true)
486
575
  else
487
- ASF.ldap.bind(dn, password)
576
+ ldap.bind(dn, password)
488
577
  end
489
578
  end
490
579
 
@@ -509,6 +598,7 @@ module ASF
509
598
 
510
599
  # determine what LDAP hosts are available
511
600
  def self.hosts
601
+ return @hosts if @hosts # cache the hosts list
512
602
  # try whimsy config
513
603
  hosts = Array(ASF::Config.get(:ldap))
514
604
 
@@ -518,23 +608,24 @@ module ASF
518
608
  if File.exist? conf
519
609
  uris = File.read(conf)[/^uri\s+(.*)/i, 1].to_s
520
610
  hosts = uris.scan(/ldaps?:\/\/\S+?:\d+/)
611
+ Wunderbar.debug "Using hosts from LDAP config"
521
612
  end
613
+ else
614
+ Wunderbar.debug "Using hosts from Whimsy config"
522
615
  end
523
616
 
524
617
  # if all else fails, use default list
618
+ Wunderbar.debug "Using default host list" if hosts.empty?
525
619
  hosts = ASF::LDAP::HOSTS if hosts.empty?
526
620
 
527
- hosts
528
- end
529
-
530
- # select LDAP host
531
- def self.host
532
- @host ||= hosts.sample
621
+ hosts.shuffle!
622
+ #Wunderbar.debug "Hosts:\n#{hosts.join(' ')}"
623
+ @hosts = hosts
533
624
  end
534
625
 
535
626
  # query and extract cert from openssl output
536
627
  def self.extract_cert
537
- host = LDAP.host[%r{//(.*?)(/|$)}, 1]
628
+ host = hosts.sample[%r{//(.*?)(/|$)}, 1]
538
629
  puts ['openssl', 's_client', '-connect', host, '-showcerts'].join(' ')
539
630
  out, err, rc = Open3.capture3 'openssl', 's_client',
540
631
  '-connect', host, '-showcerts'
@@ -1,15 +1,19 @@
1
+ require 'weakref'
1
2
 
2
3
  module ASF
3
4
 
4
5
  class Mail
5
6
  def self.list
6
- return @list if @list
7
+ begin
8
+ return Hash[@list.to_a] if @list
9
+ rescue NoMethodError, WeakRef::RefError
10
+ end
7
11
 
8
12
  list = Hash.new
9
13
 
10
14
  # load info from LDAP
11
15
  people = ASF::Person.preload(['mail', 'asf-altEmail'])
12
- people.each do |name, person|
16
+ people.each do |person|
13
17
  (person.mail+person.alt_email).each do |mail|
14
18
  list[mail.downcase] = person
15
19
  end
@@ -28,7 +32,8 @@ module ASF
28
32
  list["#{icla.id.downcase}@apache.org"] ||= person
29
33
  end
30
34
 
31
- @list = list
35
+ @list = WeakRef.new(list)
36
+ list
32
37
  end
33
38
 
34
39
  def self.lists(public_private= false)
@@ -1,3 +1,5 @@
1
+ require 'weakref'
2
+
1
3
  module ASF
2
4
  class Member
3
5
  include Enumerable
@@ -15,10 +17,16 @@ module ASF
15
17
  end
16
18
 
17
19
  def self.list
18
- result = Hash[self.new(true).map {|name, text| [name, {text: text}]}]
20
+ result = Hash[self.new.map {|id, text|
21
+ # extract 1st line and remove any trailing /* comment */
22
+ name = text[/(.*?)\n/, 1].sub(/\s+\/\*.*/,'')
23
+ [id, {text: text, name: name}]
24
+ }]
25
+
19
26
  self.status.each do |name, value|
20
27
  result[name]['status'] = value
21
28
  end
29
+
22
30
  result
23
31
  end
24
32
 
@@ -33,7 +41,11 @@ module ASF
33
41
  end
34
42
 
35
43
  def self.status
36
- return @status if @status
44
+ begin
45
+ return Hash[@status.to_a] if @status
46
+ rescue NoMethodError, WeakRef::RefError
47
+ end
48
+
37
49
  status = {}
38
50
  foundation = ASF::SVN['private/foundation']
39
51
  return status unless foundation
@@ -43,18 +55,15 @@ module ASF
43
55
  header.sub!(/s\n=+/,'')
44
56
  text.scan(/Avail ID: (.*)/).flatten.each {|id| status[id] = header}
45
57
  end
46
- @status = status
47
- end
48
58
 
49
- def initialize(full = nil)
50
- @full = (full.nil? ? ASF::Person.new($USER).asf_member? : full)
59
+ @status = WeakRef.new(status)
60
+ status
51
61
  end
52
62
 
53
63
  def each
54
64
  foundation = ASF::SVN['private/foundation']
55
65
  File.read("#{foundation}/members.txt").split(/^ \*\) /).each do |section|
56
66
  id = section[/Avail ID: (.*)/,1]
57
- section = '' unless @full
58
67
  yield id, section.sub(/\n.*\n===+\s*?\n(.*\n)+.*/,'').strip if id
59
68
  end
60
69
  nil
@@ -79,7 +88,7 @@ module ASF
79
88
 
80
89
  class Person
81
90
  def members_txt
82
- @members_txt ||= ASF::Member.find_text_by_id(name)
91
+ @members_txt ||= ASF::Member.find_text_by_id(id)
83
92
  end
84
93
 
85
94
  def member_emails
@@ -1,9 +1,14 @@
1
+ require 'weakref'
2
+
1
3
  module ASF
2
4
 
3
5
  class Person < Base
4
6
 
5
7
  def self.member_nominees
6
- return @member_nominees if @member_nominees
8
+ begin
9
+ return Hash[@member_nominees.to_a] if @member_nominees
10
+ rescue NoMethodError, WeakRef::RefError
11
+ end
7
12
 
8
13
  meetings = ASF::SVN['private/foundation/Meetings']
9
14
  nominations = Dir["#{meetings}/*/nominated-members.txt"].sort.last.untaint
@@ -22,11 +27,12 @@ module ASF
22
27
  nominees[find(id)] = nomination
23
28
  end
24
29
 
25
- @member_nominees = nominees
30
+ @member_nominees = WeakRef.new(nominees)
31
+ nominees
26
32
  end
27
33
 
28
34
  def member_nomination
29
- Person.member_nominees[self]
35
+ @member_nomination ||= Person.member_nominees[self]
30
36
  end
31
37
  end
32
38
  end
@@ -21,12 +21,14 @@ module ASF
21
21
  def self.decode(env)
22
22
  class << env; attr_accessor :user, :password; end
23
23
 
24
- if env['HTTP_AUTHORIZATION'].to_s.empty?
24
+ auth = env['HTTP_AUTHORIZATION'] || ENV['HTTP_AUTHORIZATION']
25
+
26
+ if auth.to_s.empty?
25
27
  env.user = env['REMOTE_USER'] || ENV['USER'] || Etc.getpwuid.name
26
28
  else
27
29
  require 'base64'
28
- env.user, env.password = Base64.decode64(env['HTTP_AUTHORIZATION'][
29
- /^Basic ([A-Za-z0-9+\/=]+)$/,1].to_s).split(':',2)
30
+ env.user, env.password = Base64.
31
+ decode64(auth[/^Basic ([A-Za-z0-9+\/=]+)$/,1].to_s).split(':',2)
30
32
  end
31
33
 
32
34
  env['REMOTE_USER'] ||= env.user
@@ -52,6 +52,10 @@ module ASF
52
52
  def self.list
53
53
  templates = ASF::SVN['asf/infrastructure/site/trunk/content']
54
54
  file = "#{templates}/index.html"
55
+ if not File.exist?(file)
56
+ Wunderbar.warn "Unable to find 'infrastructure/site/trunk/content'"
57
+ return {}
58
+ end
55
59
  return @@list if not @@list.empty? and File.mtime(file) == @@mtime
56
60
  @@mtime = File.mtime(file)
57
61
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whimsy-asf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.75
4
+ version: 0.0.76
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Ruby
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-25 00:00:00.000000000 Z
11
+ date: 2016-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri