bwkfanboy 1.4.1 → 2.0.0

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.
Files changed (81) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +51 -0
  4. data/Procfile +1 -0
  5. data/README.rdoc +40 -77
  6. data/Rakefile +13 -48
  7. data/bin/bwkfanboy +47 -166
  8. data/bin/bwkfanboy_generate +7 -19
  9. data/bin/bwkfanboy_parse +21 -17
  10. data/bwkfanboy.gemspec +40 -0
  11. data/config.ru +3 -0
  12. data/doc/NEWS.rdoc +21 -79
  13. data/doc/plugin.rdoc +63 -79
  14. data/etc/bwkfanboy.yaml +2 -0
  15. data/etc/sinatra.rb +34 -0
  16. data/lib/bwkfanboy/cliconfig.rb +141 -0
  17. data/lib/bwkfanboy/cliutils.rb +114 -0
  18. data/lib/bwkfanboy/fetch.rb +22 -24
  19. data/lib/bwkfanboy/generator.rb +78 -0
  20. data/lib/bwkfanboy/home.rb +53 -0
  21. data/lib/bwkfanboy/meta.rb +5 -2
  22. data/lib/bwkfanboy/plugin.rb +247 -0
  23. data/lib/bwkfanboy/plugin_skeleton.erb +19 -23
  24. data/lib/bwkfanboy/server.rb +73 -0
  25. data/lib/bwkfanboy/utils.rb +39 -129
  26. data/plugins/bwk.rb +25 -0
  27. data/plugins/econlib.rb +22 -0
  28. data/plugins/freebsd-ports-update.rb +73 -0
  29. data/plugins/inc.rb +29 -0
  30. data/plugins/test.rb +29 -0
  31. data/public/.gitattributes +1 -0
  32. data/public/favicon.ico +0 -0
  33. data/public/jquery-1.7.2.min.js +0 -0
  34. data/public/list.js +111 -0
  35. data/public/loading.gif +0 -0
  36. data/public/style.css +54 -0
  37. data/shotgun.rb +20 -0
  38. data/test/example/.gitattributes +1 -0
  39. data/test/example/.gitignore +1 -0
  40. data/test/example/02/plugins/bwk.html +0 -0
  41. data/test/{plugins → example/02/plugins}/empty.rb +0 -0
  42. data/test/example/02/plugins/garbage.rb +1 -0
  43. data/test/example/02/plugins/inc.html +0 -0
  44. data/test/helper.rb +30 -27
  45. data/test/helper_cliutils.rb +34 -0
  46. data/test/test_cli.rb +86 -0
  47. data/test/test_fetch.rb +49 -18
  48. data/test/test_generate.rb +43 -16
  49. data/test/test_home.rb +33 -0
  50. data/test/test_plugin.rb +141 -0
  51. data/test/test_server.rb +21 -32
  52. data/views/list.haml +38 -0
  53. metadata +223 -110
  54. data/bin/bwkfanboy_fetch +0 -13
  55. data/bin/bwkfanboy_server +0 -126
  56. data/doc/README.erb +0 -114
  57. data/doc/README.rdoc +0 -141
  58. data/doc/TODO +0 -7
  59. data/doc/bwkfanboy_fetch.rdoc +0 -4
  60. data/doc/bwkfanboy_generate.rdoc +0 -7
  61. data/doc/bwkfanboy_parse.rdoc +0 -7
  62. data/doc/bwkfanboy_server.rdoc +0 -35
  63. data/doc/rakefile.rb +0 -59
  64. data/lib/bwkfanboy/generate.rb +0 -63
  65. data/lib/bwkfanboy/parser.rb +0 -156
  66. data/lib/bwkfanboy/plugins/bwk.rb +0 -33
  67. data/lib/bwkfanboy/plugins/econlib.rb +0 -34
  68. data/lib/bwkfanboy/plugins/freebsd-ports-update.rb +0 -76
  69. data/lib/bwkfanboy/plugins/inc.rb +0 -37
  70. data/lib/bwkfanboy/schema.js +0 -39
  71. data/test/popen4.sh +0 -4
  72. data/test/rake_git.rb +0 -36
  73. data/test/semis/Rakefile +0 -35
  74. data/test/semis/bwk.html +0 -393
  75. data/test/semis/bwk.json +0 -82
  76. data/test/semis/econlib.html +0 -21
  77. data/test/semis/inc.html +0 -1067
  78. data/test/semis/links.txt +0 -4
  79. data/test/test_parse.rb +0 -27
  80. data/test/xml-clean.sh +0 -8
  81. data/web/bwkfanboy.cgi +0 -36
@@ -1,6 +1,9 @@
1
1
  module Bwkfanboy
2
- module Meta
2
+ module Meta # :nodoc:
3
3
  NAME = 'bwkfanboy'
4
- VERSION = '1.4.1'
4
+ VERSION = '2.0.0'
5
+ AUTHOR = 'Alexander Gromnitsky'
6
+ EMAIL = 'alexander.gromnitsky@gmail.com'
7
+ HOMEPAGE = 'http://github.com/gromnitsky/' + NAME
5
8
  end
6
9
  end
@@ -0,0 +1,247 @@
1
+ require 'msgpack'
2
+ require 'nokogiri'
3
+
4
+ module Bwkfanboy
5
+
6
+ # Helpers for plugin authors.
7
+ module BH
8
+ extend self
9
+
10
+ # FIXME: clean unsafe html for 'html' content_type
11
+ def clean t
12
+ return '' unless t
13
+ t
14
+ end
15
+
16
+ # Tries to parse _s_ string as a date.
17
+ # Return the result in ISO 8601 format.
18
+ def date(t)
19
+ DateTime.parse(BH.clean(t)).iso8601
20
+ rescue
21
+ DateTime.now.iso8601
22
+ end
23
+
24
+ # See test_plugin.rb
25
+ def all_set? t
26
+ return false unless t
27
+
28
+ if t.is_a?(Array)
29
+ return false if t.size == 0
30
+
31
+ t.each {|i|
32
+ return false unless i
33
+ return false if i.to_s.strip.size == 0
34
+ }
35
+ end
36
+
37
+ return false if t.to_s.strip.size == 0
38
+ true
39
+ end
40
+
41
+ end
42
+
43
+ class PluginException < StandardError
44
+ def initialize msg
45
+ super msg
46
+ end
47
+
48
+ alias :orig_to_s :to_s
49
+ # looks clumsy
50
+ def to_s
51
+ "plugin: #{orig_to_s}"
52
+ end
53
+ end
54
+
55
+ class PluginInvalidName < PluginException
56
+ end
57
+
58
+ class PluginNotFound < PluginException
59
+ end
60
+
61
+ class PluginNoOptions < PluginException
62
+ end
63
+
64
+ # Requires defined 'parse(streams)' method in plugin.
65
+ #
66
+ # Raises only PluginException on purpose.
67
+ class Plugin
68
+ include Enumerable
69
+
70
+ MAX_ENTRIES = 128
71
+ NAME_RE = /^[a-zA-Z0-9-]+$/
72
+
73
+ # [path] an array
74
+ # [name] plugin's name (without .rb extension)
75
+ # [opt] an array
76
+ # [&block] you can examine the Plugin object there
77
+ def initialize path, name, opt, &block
78
+ @path = path
79
+ raise PluginInvalidName, "name doesn't match #{NAME_RE}" unless validName?(name)
80
+ @name = name
81
+ @origin = nil # a path where plugin was found
82
+ @syslib = File.dirname __FILE__
83
+
84
+ # Variables for plugin authours
85
+ @opt = (opt && opt.map(&:to_s)) || []
86
+ @uri = []
87
+ @enc = 'UTF-8'
88
+ @version = 1
89
+ @copyright = ''
90
+ @title = ''
91
+ @content_type = ''
92
+
93
+ @data = []
94
+ load &block
95
+ end
96
+
97
+ attr_accessor :origin
98
+ attr_accessor :uri, :enc, :version, :copyright, :title, :content_type
99
+
100
+ def validName? name
101
+ name =~ NAME_RE
102
+ end
103
+
104
+ def each &b
105
+ @data.each &b
106
+ end
107
+
108
+ def << obj
109
+ return @data if full?
110
+
111
+ ['title', 'link', 'updated', 'author', 'content'].each {|idx|
112
+ obj[idx] &&= BH.clean obj[idx]
113
+ raise PluginException, "empty '#{idx}' in the entry #{obj.inspect}" if obj[idx].size == 0
114
+ }
115
+
116
+ @data << obj
117
+ end
118
+
119
+ def full?
120
+ @data.size >= MAX_ENTRIES
121
+ end
122
+
123
+ def [] index
124
+ @data[index]
125
+ end
126
+
127
+ def size
128
+ @data.size
129
+ end
130
+
131
+ def pack stream = ''
132
+ # hopefully, urf8 will survive
133
+ MessagePack.pack export, stream
134
+ end
135
+
136
+ def export
137
+ {
138
+ 'channel' => {
139
+ 'updated' => entryMostRecent,
140
+ 'id' => @uri.to_s,
141
+ 'author' => @copyright,
142
+ 'title' => @title,
143
+ 'link' => @uri.first,
144
+ 'x_entries_content_type' => @content_type,
145
+ },
146
+ 'x_entries' => @data
147
+ }
148
+ end
149
+
150
+ # We can do this while adding a new entry, not here
151
+ def entryMostRecent
152
+ return nil if @data.size == 0
153
+
154
+ max = DateTime.parse @data.sample['updated']
155
+ @data.each {|idx|
156
+ cur = DateTime.parse idx['updated']
157
+ max = cur if max < cur
158
+ }
159
+
160
+ return max.iso8601
161
+ end
162
+
163
+ def load
164
+ raise PluginException, 'invalid search path' unless @path && @path.respond_to?(:each)
165
+
166
+ p = nil
167
+ @path.each {|idx|
168
+ contents = Dir.glob "#{idx}/*.rb"
169
+ pos = contents.index "#{idx}/#{@name}.rb"
170
+ if pos && p = contents[pos]
171
+ @origin = idx
172
+ break
173
+ end
174
+ }
175
+
176
+ raise PluginNotFound, "'#{@name}' not found" unless p
177
+
178
+ begin
179
+ instance_eval File.read(p)
180
+ rescue Exception
181
+ raise PluginException, "'#{@name}' failed to parse: #{$!}"
182
+ end
183
+
184
+ unless BH.all_set?(uri)
185
+ raise PluginException, 'uri must be an array of strings' if @opt.size != 0
186
+ raise PluginNoOptions, 'don\'t we forget about additional options?'
187
+ end
188
+ raise PluginException, 'enc is unset' unless BH.all_set?(enc)
189
+ raise PluginException, 'version must be an integer' unless BH.all_set?(version)
190
+ raise PluginException, 'copyright is unset' unless BH.all_set?(copyright)
191
+ raise PluginException, 'title is unset' unless BH.all_set?(title)
192
+ raise PluginException, 'content_type is unset' unless BH.all_set?(content_type)
193
+
194
+ # use this, for example, to print a message to user that loading
195
+ # was fine
196
+ yield self if block_given?
197
+ end
198
+
199
+ # Runs loaded plugin's parser
200
+ def run_parser streams
201
+ ok = streams ? true : false
202
+ streams.each {|i| ok = false unless i.respond_to?(:eof) } if streams
203
+ raise PluginException, 'parser expects a valid array of IO objects' unless ok
204
+
205
+ begin
206
+ parse streams
207
+ rescue Exception
208
+ raise PluginException, "'#{@name}' failed to parse: #{$!}"
209
+ end
210
+
211
+ check
212
+ end
213
+
214
+ def check
215
+ raise PluginException, "it ain't grab anything" if @data.size == 0
216
+ end
217
+
218
+ end
219
+
220
+ module PluginInfo
221
+ extend self
222
+
223
+ def about path, name, opt
224
+ p = Plugin.new path, name, opt
225
+ r = {}
226
+ ['title', 'version', 'copyright', 'uri'].each {|idx|
227
+ r[idx] = p.send(idx)
228
+ }
229
+ r
230
+ end
231
+
232
+ def getList path
233
+ r = []
234
+ path.each {|idx|
235
+ dir = idx.to_s
236
+ e = { dir => [] }
237
+ Dir.glob("#{dir}/*.rb").each {|file|
238
+ e[dir] << File.basename(file, '.rb')
239
+ }
240
+ r << e
241
+ }
242
+
243
+ r
244
+ end
245
+
246
+ end
247
+ end
@@ -1,30 +1,26 @@
1
- # This is a skeleton for a <%= Bwkfanboy::Meta::NAME %> <%= Bwkfanboy::Meta::VERSION %> plugin. To understand how
2
- # plugins work please read doc/plugins.rdoc file from <%= Bwkfanboy::Meta::NAME %>'s
1
+ # This is a skeleton for a <%= Meta::NAME %> <%= Meta::VERSION %> plugin. To understand how
2
+ # plugins work please read doc/plugins.rdoc file from <%= Meta::NAME %>'s
3
3
  # distribution.
4
4
 
5
- require 'nokogiri'
6
-
7
- class Page < Bwkfanboy::Parse
8
- module Meta
9
- URI = 'http://example.org/news'
10
- ENC = 'UTF-8'
11
- VERSION = 1
12
- COPYRIGHT = '(c) <%= DateTime.now.year %> <%= Etc.getpwuid(Process.euid)[:gecos] %>'
13
- TITLE = "News from example.org"
14
- CONTENT_TYPE = 'html'
15
- end
5
+ @uri << 'http://example.org/news'
6
+ @enc = 'UTF-8'
7
+ @version = 1
8
+ @copyright = '(c) <%= DateTime.now.year %> <%= Etc.getpwuid(Process.euid)[:gecos] %>'
9
+ @title = "News from example.org"
10
+ @content_type = 'html'
16
11
 
17
- def myparse(stream)
18
- # read 'stream' IO object and parse it
19
- doc = Nokogiri::HTML(stream, nil, Meta::ENC)
20
- doc.xpath("XPATH QUERY").each {|i|
21
- t = clean(i.xpath("XPATH QUERY").text())
22
- l = clean(i.xpath("XPATH QUERY").text())
23
- u = date(i.xpath("XPATH QUERY").text())
24
- a = clean(i.xpath("XPATH QUERY").text())
25
- c = clean(i.xpath("XPATH QUERY").text())
12
+ def parse streams
13
+ streams.each do |io|
14
+ doc = Nokogiri::HTML io, nil, @enc
15
+ doc.xpath("XPATH QUERY").each {|idx|
16
+ t = idx.xpath("XPATH QUERY").text
17
+ l = idx.xpath("XPATH QUERY").text
18
+ u = BH.date idx.xpath("XPATH QUERY").text
19
+ a = idx.xpath("XPATH QUERY").text
20
+ c = idx.xpath("XPATH QUERY").text
26
21
 
27
- self << { title: t, link: l, updated: u, author: a, content: c }
22
+ self << { 'title' => t, 'link' => l, 'updated' => u,
23
+ 'author' => a, 'content' => c }
28
24
  }
29
25
  end
30
26
  end
@@ -0,0 +1,73 @@
1
+ require 'logger'
2
+ require 'haml'
3
+ require 'sinatra/base'
4
+ require 'json'
5
+
6
+ require_relative 'home'
7
+ require_relative 'utils'
8
+ require_relative '../../etc/sinatra'
9
+
10
+ module Bwkfanboy
11
+ class MyApp < Sinatra::Base
12
+ MySinatraConfig.read self
13
+
14
+ set :home, Home.new
15
+ set :public_folder, CliUtils::DIR_LIB_SRC.parent.parent + 'public'
16
+ set :views, CliUtils::DIR_LIB_SRC.parent.parent + 'views'
17
+
18
+ use Rack::Deflater
19
+
20
+ def getOpts opts
21
+ return [] unless opts
22
+ opts.gsub! /\s+/, ' '
23
+ opts.strip.split ' '
24
+ end
25
+
26
+ # List all plugins
27
+ get '/' do
28
+ list = PluginInfo.getList settings.home.conf[:plugins_path]
29
+ haml :list, locals: {
30
+ meta: Meta,
31
+ list: list
32
+ }
33
+ end
34
+
35
+ get %r{/info/([a-zA-Z0-9_-]+)} do |plugin|
36
+ cache_control :no_cache
37
+ opts = getOpts params['o']
38
+ begin
39
+ PluginInfo.about(settings.home.conf[:plugins_path], plugin, opts).to_json
40
+ rescue PluginInvalidName, PluginNoOptions
41
+ halt 400, $!.to_s
42
+ rescue PluginNotFound
43
+ halt 404, $!.to_s
44
+ rescue PluginException
45
+ halt 500, $!.to_s
46
+ end
47
+ end
48
+
49
+ get %r{/([a-zA-Z0-9_-]+)} do |plugin|
50
+ begin
51
+ opts = getOpts params['o']
52
+ r = Utils.atom(settings.home.conf[:plugins_path], plugin, opts).to_s
53
+
54
+ # Search for <updated> tag and set Last-Modified header
55
+ if (m = r.match('<updated>(.+?)</updated>'))
56
+ headers 'Last-Modified' => DateTime.parse(m.to_s).httpdate
57
+ end
58
+ content_type 'application/atom+xml; charset=UTF-8'
59
+ headers 'Content-Disposition' => "inline; filename=\"#{Meta::NAME}-#{plugin}.xml"
60
+
61
+ r
62
+ rescue PluginInvalidName, PluginNoOptions
63
+ halt 400, $!.to_s
64
+ rescue PluginNotFound
65
+ halt 404, $!.to_s
66
+ rescue FetchException, PluginException, GeneratorException
67
+ halt 500, $!.to_s
68
+ end
69
+ end
70
+
71
+ run! if app_file == $0
72
+ end
73
+ end
@@ -1,148 +1,58 @@
1
- require 'optparse'
2
- require 'logger'
1
+ require 'erb'
2
+ require 'digest/md5'
3
+ require 'etc'
3
4
 
4
- require 'open4'
5
- require 'active_support/core_ext/module/attribute_accessors'
6
-
7
- require_relative 'meta'
5
+ require_relative 'cliutils'
6
+ require_relative 'fetch'
7
+ require_relative 'plugin'
8
+ require_relative 'generator'
8
9
 
9
10
  module Bwkfanboy
10
- module Meta
11
- USER_AGENT = "#{NAME}/#{VERSION} (#{RUBY_PLATFORM}; N; #{Encoding.default_external.name}; #{RUBY_ENGINE}; rv:#{RUBY_VERSION}.#{RUBY_PATCHLEVEL})"
12
- PLUGIN_CLASS = 'Page'
13
- DIR_TMP = "/tmp/#{Meta::NAME}/#{ENV['USER']}"
14
- DIR_LOG = "#{DIR_TMP}/log"
15
- LOG_MAXSIZE = 64*1024
16
- PLUGIN_NAME = /^[ a-zA-Z0-9_-]+$/
17
- PLUGIN_OPTS = /^[ a-zA-Z'"0-9_-]+$/
18
- end
19
-
20
11
  module Utils
21
- mattr_accessor :cfg, :log
22
-
23
- self.cfg = Hash.new()
24
- cfg[:verbose] = 0
25
- cfg[:log] = "#{Meta::DIR_LOG}/general.log"
26
-
27
- def self.warnx(t)
28
- m = File.basename($0) +" warning: "+ t + "\n";
29
- $stderr.print(m);
30
- log.warn(m.chomp) if log
31
- end
32
-
33
- def self.errx(ec, t)
34
- m = File.basename($0) +" error: "+ t + "\n"
35
- $stderr.print(m);
36
- log.error(m.chomp) if log
37
- exit(ec)
38
- end
12
+ extend self
39
13
 
40
- def self.veputs(level, t)
41
- if cfg[:verbose] >= level then
42
- # p log
43
- log.info(t.chomp) if log
44
- print(t)
14
+ # [template] a full path to a .erb file
15
+ # [desiredName] a future skeleton name
16
+ def skeletonCreate template, desiredName
17
+ t = ERB.new File.read template
18
+ t.filename = template # to report errors relative to this file
19
+ begin
20
+ md5_system = Digest::MD5.hexdigest t.result(binding)
21
+ rescue Exception
22
+ CliUtils.errx EX_SOFTWARE, "cannot read the template: #{$!}"
45
23
  end
46
- end
47
-
48
- def self.vewarnx(level, t)
49
- warnx(t) if cfg[:verbose] >= level
50
- end
51
24
 
52
- # Logs and pidfiles the other temporal stuff sits here
53
- def self.dir_tmp_create()
54
- if ! File.writable?(Meta::DIR_TMP) then
25
+ if ! File.exists?(desiredName)
26
+ # create a new skeleton
55
27
  begin
56
- t = '/'
57
- Meta::DIR_TMP.split('/')[1..-1].each {|i|
58
- t += i + '/'
59
- Dir.mkdir(t) if ! Dir.exists?(t)
60
- }
28
+ File.open(desiredName, 'w+') { |fp| fp.puts t.result(binding) }
61
29
  rescue
62
- warnx("cannot create/open directory #{Meta::DIR_TMP} for writing")
30
+ CliUtils.errx EX_IOERR, "cannot write the skeleton: #{$!}"
31
+ end
32
+ else
33
+ # warn a careless user
34
+ if md5_system != Digest::MD5.file(desiredName).hexdigest
35
+ CliUtils.warnx "#{desiredName} already exists"
36
+ return false
63
37
  end
64
38
  end
65
- end
66
-
67
- def self.log_start()
68
- dir_tmp_create()
69
- begin
70
- Dir.mkdir(Meta::DIR_LOG) if ! File.writable?(Meta::DIR_LOG)
71
- log = Logger.new(cfg[:log], 2, Meta::LOG_MAXSIZE)
72
- rescue
73
- warnx("cannot open log #{cfg[:log]}");
74
- return nil
75
- end
76
- log.level = Logger::DEBUG
77
- log.datetime_format = "%H:%M:%S"
78
- log.info("#{$0} starting")
79
- log
80
- end
81
- self.log = log_start()
82
-
83
- # Loads (via <tt>require()</tt>) a Ruby code from _path_ (the full path to
84
- # the file). <em>class_name</em> is the name of the class to check
85
- # for existence after successful plugin loading.
86
- def self.plugin_load(path, class_name)
87
- begin
88
- require(path)
89
- # TODO get rid of eval()
90
- fail "class #{class_name} isn't defined" if (! eval("defined?#{class_name}") || ! eval(class_name).is_a?(Class) )
91
- rescue LoadError
92
- errx(1, "cannot load plugin '#{path}' #{$!}");
93
- rescue Exception
94
- errx(1, "plugin '#{path}' has errors: #{$!}\n\nBacktrace:\n\n#{$!.backtrace.join("\n")}")
95
- end
96
- end
97
39
 
98
- # Get possible options for the parser.
99
- def self.plugin_opts(a)
100
- opt = a.size >= 2 ? a[1..-1] : ''
40
+ true
101
41
  end
102
42
 
43
+ # Do all the work of reading the plugin, parsing and generating the
44
+ # atom feed.
45
+ # FIXME: ensure all streams are closed
46
+ def atom pluginsPath, name, opt
47
+ p = Plugin.new pluginsPath, name, opt
48
+ streams = Fetch.openStreams p.uri
49
+ p.run_parser(streams)
103
50
 
104
- # Parses command line options. _arr_ is an array of options (usually
105
- # +ARGV+). _banner_ is a help string that describes what your
106
- # program does.
107
- #
108
- # If _o_ is non nil function parses _arr_ immediately, otherwise it
109
- # only creates +OptionParser+ object and return it (if _simple_ is
110
- # false). See <tt>bwkfanboy</tt> script for examples.
111
- def self.cl_parse(arr, banner, o = nil, simple = false)
112
- if ! o then
113
- o = OptionParser.new
114
- o.banner = banner
115
- o.on('-v', 'Be more verbose.') { |i| Bwkfanboy::Utils.cfg[:verbose] += 1 }
116
- o.on('-V', 'Show version & exit.') { |i|
117
- puts Bwkfanboy::Meta::VERSION
118
- exit 0
119
- }
120
- return o if ! simple
121
- end
51
+ r = Generator.atom p.export
52
+ Fetch.closeStreams streams
122
53
 
123
- begin
124
- o.parse!(arr)
125
- rescue
126
- Bwkfanboy::Utils.errx(1, $!.to_s)
127
- end
128
- end
129
-
130
- # used in CGI and WEBrick examples
131
- def self.cmd_run(cmd)
132
- so = sr = ''
133
- status = Open4::popen4(cmd) { |pid, stdin, stdout, stderr|
134
- so = stdout.read
135
- sr = stderr.read
136
- }
137
- [status.exitstatus, sr, so]
138
- end
139
-
140
- def self.gem_dir_system
141
- t = ["#{File.dirname(File.expand_path($0))}/../lib/#{Meta::NAME}",
142
- "#{Gem.dir}/gems/#{Meta::NAME}-#{Meta::VERSION}/lib/#{Meta::NAME}"]
143
- t.each {|i| return i if File.readable?(i) }
144
- raise "both paths are invalid: #{t}"
54
+ r
145
55
  end
146
56
 
147
- end # utils
57
+ end
148
58
  end