lyber-core 0.9.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ require 'dlss_service'
2
+ require 'dor/suri_service'
3
+ require 'dor/workflow_service'
4
+ require 'dor/base'
5
+ require 'lyber_core/connection'
6
+ require 'lyber_core/destroyer'
7
+ require 'lyber_core/log'
8
+ require 'lyber_core/robots/robot'
9
+ require 'lyber_core/robots/workflow'
10
+ require 'lyber_core/robots/workspace'
11
+ require 'lyber_core/robots/work_queue'
12
+ require 'lyber_core/robots/work_item'
13
+ require 'lyber_core/exceptions/empty_queue'
14
+
@@ -0,0 +1,97 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require 'cgi'
4
+
5
+ module LyberCore
6
+ class Connection
7
+ def Connection.get_https_connection(url)
8
+ LyberCore::Log.debug("Establishing connection to #{url.host} on port #{url.port}")
9
+ https = Net::HTTP.new(url.host, url.port)
10
+ if(url.scheme == 'https')
11
+ https.use_ssl = true
12
+ LyberCore::Log.debug("Using SSL")
13
+ https.cert = OpenSSL::X509::Certificate.new( File.read(LyberCore::CERT_FILE) )
14
+ LyberCore::Log.debug("Using cert file #{LyberCore::CERT_FILE}")
15
+ https.key = OpenSSL::PKey::RSA.new( File.read(LyberCore::KEY_FILE), LyberCore::KEY_PASS )
16
+ LyberCore::Log.debug("Using key file #{LyberCore::KEY_FILE} with pass #{LyberCore::KEY_PASS}")
17
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
18
+ LyberCore::Log.debug("https.verify_mode = #{https.verify_mode} (should eql #{OpenSSL::SSL::VERIFY_NONE})")
19
+ end
20
+ https
21
+ end
22
+
23
+ # Returns body of the HTTP response, or passes the response to the block if it's passed in
24
+ #
25
+ # == Required Parameters
26
+ # - <b>full_url</b> - A string containing the full url to the resource you're trying to connect to
27
+ # - <b>method</b> - Recognizes the following symbols which correspond to an HTTP verb. The convenience methods take care of this
28
+ # :get for HTTP GET
29
+ # :post for HTTP POST
30
+ # :put for HTTP PUT
31
+ # - <b>body</b> The body of your request. Can be nil if you don't have one.
32
+ #
33
+ # == Options
34
+ # - <b>:auth_user</b> for basic HTTP authentication. :auth_user and :auth_password must both be set if using authentication
35
+ # - <b>:auth_password</b> for basic HTTP authentication. :auth_user and :auth_password must both be set if using authentication
36
+ # - <b>:content_type</b> if not passed in as an option, then it is set to 'application/xml'
37
+ #
38
+ # == Block
39
+ # By default, this method returns the body of the response, Net::HTTPResponse.body . If you want to work with the Net::HTTPResponse
40
+ # object, you can pass in a block, and the response will be passed to it.
41
+ #
42
+ # == Exceptions
43
+ # Any exceptions thrown while trying to connect should be handled by the caller
44
+ def Connection.connect(full_url, method, body, options = {}, &block)
45
+ url = URI.parse(full_url)
46
+ case method
47
+ when :get
48
+ req = Net::HTTP::Get.new(url.request_uri)
49
+ when :post
50
+ req = Net::HTTP::Post.new(url.request_uri)
51
+ when :put
52
+ req = Net::HTTP::Put.new(url.request_uri)
53
+ end
54
+ req.body = body unless(body.nil?)
55
+ if(options.include?(:content_type))
56
+ req.content_type = options[:content_type]
57
+ else
58
+ req.content_type = 'application/xml'
59
+ end
60
+
61
+ if(options.include?(:auth_user))
62
+ req.basic_auth options[:auth_user], options[:auth_password]
63
+ end
64
+
65
+ res = Connection.get_https_connection(url).start {|http| http.request(req) }
66
+ case res
67
+ when Net::HTTPSuccess
68
+ if(block_given?)
69
+ block.call(res)
70
+ else
71
+ return res.body
72
+ end
73
+ else
74
+ raise res.error!
75
+ end
76
+
77
+ end
78
+ end
79
+
80
+
81
+ # Convenience method for performing an HTTP GET using Connection.connect
82
+ def Connection.get(full_url, options = {}, &b)
83
+ Connection.connect(full_url, :get, nil, options, &b)
84
+ end
85
+
86
+ # Convenience method for performing an HTTP POST using Connection.connect
87
+ def Connection.post(full_url, body, options = {}, &b)
88
+ Connection.connect(full_url, :post, body, options, &b)
89
+ end
90
+
91
+ # Convenience method for performing an HTTP PUT using Connection.connect
92
+ def Connection.put(full_url, body, options = {}, &b)
93
+ Connection.connect(full_url, :put, body, options, &b)
94
+ end
95
+
96
+
97
+ end
@@ -0,0 +1,74 @@
1
+ module LyberCore
2
+
3
+ class LyberCore::Destroyer
4
+
5
+ require 'rubygems'
6
+ require 'active-fedora'
7
+ require 'open-uri'
8
+
9
+ attr_reader :repository
10
+ attr_reader :workflow
11
+ attr_reader :registration_robot
12
+ attr_reader :druid_list
13
+ attr_reader :current_druid
14
+
15
+ # Given a repository, a workflow and the name of a robot that registers objects in fedora
16
+ # we can generate a list of all the druids that were created by that robot
17
+ def initialize(repository, workflow, registration_robot)
18
+ @repository = repository
19
+ @workflow = workflow
20
+ @registration_robot = registration_robot
21
+ @druid_list = self.get_druid_list
22
+ end
23
+
24
+ def get_druid_list
25
+ begin
26
+ druid_list = []
27
+ url_string = "#{WORKFLOW_URI}/workflow_queue?repository=#{@repository}&workflow=#{@workflow}&completed=#{@registration_robot}"
28
+ LyberCore::Log.info("Fetching druids from #{url_string}")
29
+ doc = Nokogiri::XML(open(url_string))
30
+ doc.xpath("//objects/object/@id").each do |id|
31
+ druid_list << id.to_s
32
+ end
33
+ return druid_list
34
+ rescue Exception => e
35
+ raise e, "Couldn't fetch druid list from #{url_string}"
36
+ end
37
+ end
38
+
39
+ # Iterate through the druids in @druid_list and delete each of them from FEDORA
40
+ def delete_druids
41
+ begin
42
+ connect_to_fedora
43
+ @druid_list.each do |druid|
44
+ @current_druid = druid
45
+ LyberCore::Log.info("Deleting #{@current_druid}")
46
+ begin
47
+ obj = ActiveFedora::Base.load_instance(@current_druid)
48
+ obj.delete
49
+ rescue ActiveFedora::ObjectNotFoundError
50
+ LyberCore::Log.info("#{@current_druid} does not exist in this repository")
51
+ end
52
+ end
53
+ rescue Exception => e
54
+ raise e
55
+ end
56
+ end
57
+
58
+ def connect_to_fedora
59
+ begin
60
+ if @repository == "dor"
61
+ repo_url = FEDORA_URI
62
+ elsif @repository == "sdr"
63
+ repo_url = SEDORA_URI
64
+ else
65
+ raise "I don't know how to connect to repository #{@repository}"
66
+ end
67
+ LyberCore::Log.info("connecting to #{repo_url}")
68
+ Fedora::Repository.register(repo_url)
69
+ rescue Errno::ECONNREFUSED => e
70
+ raise e, "Can't connect to Fedora at url #{repo_url}"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,9 @@
1
+ require 'dor_service'
2
+
3
+ module LyberCore
4
+ module Exceptions
5
+ class EmptyQueue < RuntimeError
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,105 @@
1
+
2
+ module LyberCore
3
+
4
+ class LyberCore::Log
5
+ require 'logger'
6
+
7
+ # Default values
8
+ DEFAULT_LOGFILE = "/tmp/lybercore_log.log"
9
+ DEFAULT_LOG_LEVEL = Logger::INFO
10
+ DEFAULT_FORMATTER = proc{|s,t,p,m|"%5s [%s] (%s) %s :: %s\n" % [s,
11
+ t.strftime("%Y-%m-%d %H:%M:%S"), $$, p, m]}
12
+
13
+ # Initial state
14
+ @@logfile = DEFAULT_LOGFILE
15
+ @@log ||= Logger.new(@@logfile)
16
+ # $stdout.reopen(@@logfile)
17
+ # $stderr.reopen(@@logfile)
18
+ @@log.level = DEFAULT_LOG_LEVEL
19
+ @@log.formatter = DEFAULT_FORMATTER
20
+
21
+ # Restore LyberCore::Log to its default state
22
+ def Log.restore_defaults
23
+ @@log.level = DEFAULT_LOG_LEVEL
24
+ Log.set_logfile(DEFAULT_LOGFILE)
25
+ @@log.formatter = DEFAULT_FORMATTER
26
+ end
27
+
28
+ # The current location of the logfile
29
+ def Log.logfile
30
+ return @@logfile
31
+ end
32
+
33
+
34
+
35
+ # Accepts a filename as an argument, and checks to see whether that file can be
36
+ # opened for writing. If it can be opened, it closes the existing Logger object
37
+ # and re-opens it with the new logfile location. It raises an exception if it
38
+ # cannot write to the specified logfile.
39
+ def Log.set_logfile(new_logfile)
40
+ begin
41
+ current_log_level = @@log.level
42
+ current_formatter = @@log.formatter
43
+ @@log = Logger.new(new_logfile)
44
+ @@logfile = new_logfile
45
+ @@log.level = current_log_level
46
+ @@log.formatter = current_formatter
47
+ rescue Exception => e
48
+ raise e, "Couldn't initialize logfile #{new_logfile}: #{e.backtrace}"
49
+ end
50
+
51
+ end
52
+
53
+ # Set the log level.
54
+ # See http://ruby-doc.org/core/classes/Logger.html for more info.
55
+ # Possible values are:
56
+ # Logger::FATAL (4): an unhandleable error that results in a program crash
57
+ # Logger::ERROR (3): a handleable error condition
58
+ # Logger::WARN (2): a warning
59
+ # Logger::INFO (1): generic (useful) information about system operation
60
+ # Logger::DEBUG (0): low-level information for developers
61
+ def Log.set_level(loglevel)
62
+ begin
63
+ if [0,1,2,3,4].contains? loglevel
64
+ @@log.level = loglevel
65
+ @@log.debug "Setting LyberCore::Log.level to #{loglevel}"
66
+ else
67
+ @@log.warn "I received an invalid option for log level. I expected a number between 0 and 4 but I got #{loglevel}"
68
+ @@log.warn "I'm setting the loglevel to 0 (debug) because you seem to be having trouble."
69
+ @@log.level = 0
70
+ end
71
+ rescue Exception => e
72
+ raise e, "Couldn't set log level: #{e.backtrace}"
73
+ end
74
+ end
75
+
76
+ # Return the current log level
77
+ def Log.level
78
+ @@log.level
79
+ end
80
+
81
+ def Log.fatal(msg)
82
+ @@log.add(Logger::FATAL) { msg }
83
+ end
84
+
85
+ def Log.error(msg)
86
+ @@log.add(Logger::ERROR) { msg }
87
+ end
88
+
89
+ def Log.warn(msg)
90
+ @@log.add(Logger::WARN) { msg }
91
+ end
92
+
93
+ def Log.info(msg)
94
+ @@log.add(Logger::INFO) { msg }
95
+ end
96
+
97
+ def Log.debug(msg)
98
+ @@log.add(Logger::DEBUG) { msg }
99
+ end
100
+
101
+
102
+ end
103
+
104
+
105
+ end
@@ -0,0 +1,126 @@
1
+ require 'rake/tasklib'
2
+ require 'pony'
3
+
4
+ module LyberCore
5
+
6
+ # A rake task that will tag, build and publish your gem to the DLSS gemserver
7
+ #
8
+ # == Usage
9
+ # Include the following two lines in your <tt>Rakefile</tt>:
10
+ # require 'lyber_core/rake/dlss_release'
11
+ # LyberCore::DlssRelease.new
12
+ #
13
+ # To build and release the gem, run the following AFTER pushing your code to the git repository on AFS:
14
+ # rake dlss_release
15
+ #
16
+ # == Requirements
17
+ # - You need <b>ONE</b> <tt>your-gemname.gemspec</tt> file in the root of your project. See the example in <tt>lyber-core.gemspec</tt> or look at http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/
18
+ # - Inside the <tt>Gem::Specification</tt> block of the <tt>your-gemname.gemspec</tt> file, you must set the version number. For example
19
+ # Gem::Specification.new do |s|
20
+ # s.name = "your-gemname"
21
+ # ...
22
+ # s.version = "0.9.6"
23
+ # ...
24
+ # end
25
+ # - You need to have access to the account on the DLSS gemserver, usually by adding your sunetid to the .k5login. You also need git installed.
26
+ class DlssRelease < Rake::TaskLib
27
+ # Name of the rake task. Defaults to :dlss_release
28
+ attr_accessor :name
29
+
30
+ # Name of the gem
31
+ attr_accessor :gemname
32
+
33
+ # Version of the gem being released
34
+ attr_accessor :version
35
+
36
+ def initialize(name=:dlss_release)
37
+ @name = name
38
+ @gemname = determine_gemname
39
+ yield self if block_given?
40
+ raise "Gemname must be set" if @gemname.nil?
41
+ define
42
+ end
43
+
44
+ # Tries to parse the gemname from the prefix of the <tt>your-gemname.gemspec</tt> file.
45
+ def determine_gemname
46
+ files = Dir.glob("*.gemspec")
47
+ raise "Unable to find project gemspec file.\nEither it doesn't exist or there are too many files ending in .gemspec" if(files.size != 1)
48
+
49
+ files.first =~ /(.*)\.gemspec/
50
+ $1
51
+ end
52
+
53
+ def send_release_announcement
54
+ Pony.mail(:to => 'dlss-developers@lists.stanford.edu',
55
+ :from => 'dlss-developers@lists.stanford.edu',
56
+ :subject => "Release #{@version} of the #{@gemname} gem",
57
+ :body => "The #{@gemname}-#{@version}.gem has been released to the DLSS gemserver",
58
+ :via => :smtp,
59
+ :via_options => {
60
+ :address => 'smtp.stanford.edu',
61
+ :port => '25',
62
+ :enable_starttls_auto => true,
63
+ :authentication => :plain, # :plain, :login, :cram_md5, no auth by default
64
+ :domain => "localhost.localdomain" # the HELO domain provided by the client to the server
65
+ }
66
+ )
67
+ end
68
+
69
+ def define
70
+ desc "Tag, build, and release DLSS specified gem"
71
+ task @name do
72
+ IO.foreach(File.join(Rake.original_dir, "#{@gemname}.gemspec")) do |line|
73
+ if(line =~ /\.version.*=.*"(.*)"/)
74
+ @version = $1
75
+ break
76
+ end
77
+ end
78
+
79
+ if(@version.nil?)
80
+ raise "Unable to find version number in #{@gemname}.gemspec"
81
+ end
82
+ created_gem = "#{@gemname}-#{@version}.gem"
83
+
84
+ puts "Make sure:"
85
+ puts " 1) Version #{@version} of #{@gemname} has not been tagged and released previously"
86
+ puts " 2) All of the tests pass"
87
+ puts " 3) You've pushed the code to the git repository on AFS (usually 'git push origin master')"
88
+ puts "Type 'yes' to continue if all of these statements are true"
89
+
90
+ resp = STDIN.gets.chomp
91
+ unless(resp =~ /yes/ )
92
+ raise "Please complete the prerequisites"
93
+ end
94
+
95
+ puts "Releasing version #{@version} of the #{@gemname} gem"
96
+
97
+ begin
98
+ tags = `git tag`.split("\n")
99
+ raise "tag v#{@version} already exists. Change the version number in the #{@gemname}.gemspec file" if tags.include?("v#{@version}")
100
+
101
+ puts "...Tagging release"
102
+ sh "git tag -a v#{@version} -m 'Gem version #{@version}'"
103
+ sh "git push origin --tags"
104
+
105
+ puts "...Building gem"
106
+ sh "gem build #{@gemname}.gemspec"
107
+
108
+ puts "...Publishing gem to sulair-rails-dev DLSS gemserver"
109
+ sh "scp #{created_gem} webteam@sulair-rails-dev.stanford.edu:/var/www/html/gems"
110
+ sh "ssh webteam@sulair-rails-dev.stanford.edu gem generate_index -d /var/www/html"
111
+
112
+ puts "Done!!!!! A local copy of the gem is in the pkg directory"
113
+ FileUtils.mkdir("pkg") unless File.exists?("pkg")
114
+ FileUtils.mv("#{created_gem}", "pkg")
115
+ rescue Exception => e
116
+ FileUtils.rm("#{created_gem}") if(File.exists?("#{created_gem}"))
117
+ raise e
118
+ end
119
+
120
+ puts "Sending release announcement to DLSS developers list"
121
+ send_release_announcement
122
+
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,214 @@
1
+ # == Usage
2
+ # ruby_cl_skeleton [options] source_file
3
+ #
4
+ # For help use: ruby_cl_skeleton -h
5
+
6
+ module LyberCore
7
+ module Robots
8
+ require 'optparse'
9
+ require 'ostruct'
10
+
11
+ # ===== Usage
12
+ # User defined robots should derive from this class and override the #process_item method
13
+ class Robot
14
+ attr_accessor :workflow_name
15
+ attr_accessor :workflow_step
16
+
17
+ # A LyberCore::Robots::Workflow object
18
+ attr_accessor :workflow
19
+ attr_accessor :collection_name
20
+ attr_accessor :workspace
21
+ attr_accessor :args
22
+ attr_accessor :options
23
+
24
+
25
+ # Available options
26
+ # - :collection_name - The collection this workflow should work with.
27
+ # Defined as a subdirectory within ROBOT_ROOT/config/workflows/your_workflow/your_collection
28
+ # - :workspace - Full path of where to find content for a particular workflow
29
+ # - :logfile - Where to write log messages
30
+ # - :loglevel - Level of logging from 0 - 4 where 0 = DEBUG and 4 = FATAL
31
+ def initialize(workflow_name, workflow_step, args = {})
32
+ @workflow_name = workflow_name
33
+ @workflow_step = workflow_step
34
+ @collection_name = args[:collection_name]
35
+ @opts = args
36
+
37
+ if args[:logfile]
38
+ LyberCore::Log.set_logfile(args[:logfile])
39
+ else
40
+ FileUtils.mkdir(File.join(ROBOT_ROOT, 'log')) unless(File.exists?(File.join(ROBOT_ROOT, 'log')))
41
+ robot_logfile = File.join(ROBOT_ROOT,'log',workflow_step+'.log')
42
+ LyberCore::Log.set_logfile(robot_logfile)
43
+ end
44
+
45
+ LyberCore::Log.set_level(args[:loglevel]) if args[:loglevel]
46
+
47
+ # Set defaults
48
+ @options = OpenStruct.new
49
+ self.parse_options
50
+ self.create_workflow
51
+ self.set_workspace
52
+ end
53
+
54
+ # Some workflows require a directory where their content lives
55
+ # If a robot is invoked with a :workspace => true option, its @workspace
56
+ # should be set from the value in
57
+ def set_workspace
58
+ if(@opts[:workspace])
59
+ @workspace = LyberCore::Robots::Workspace.new(@workflow_name, @collection_name)
60
+ LyberCore::Log.debug("workspace = #{workspace.inspect}")
61
+ end
62
+ end
63
+
64
+ # Create the workflow at instantiation, not when we start running the robot.
65
+ # That way we can do better error checking and ensure that everything is going
66
+ # to run okay before we actually start things.
67
+ def create_workflow
68
+
69
+ unless defined?(WORKFLOW_URI)
70
+ LyberCore::Log.fatal "FATAL: WORKFLOW_URI is not defined"
71
+ LyberCore::Log.fatal "Usually this is a value like 'http://lyberservices-dev.stanford.edu/workflow'"
72
+ LyberCore::Log.fatal "Usually you load it by setting ROBOT_ENVIRONMENT when you invoke your robot"
73
+ raise "WORKFLOW_URI is not set! Do you need to set your ROBOT_ENVIRONMENT value?"
74
+ end
75
+ LyberCore::Log.debug("About to instatiate a Workflow object
76
+ -- LyberCore::Robots::Workflow.new(#{@workflow_name},#{collection_name}")
77
+ @workflow = LyberCore::Robots::Workflow.new(@workflow_name, {:logger => @logger, :collection_name => @collection_name})
78
+
79
+ end
80
+
81
+ # == Create a new workflow
82
+ def start()
83
+
84
+ begin
85
+ LyberCore::Log.debug("Starting robot...")
86
+
87
+ queue = @workflow.queue(@workflow_step)
88
+
89
+ # If we have arguments, parse out the parts that indicate druids
90
+ if(@options.file or @options.druid)
91
+ queue.enqueue_druids(get_druid_list)
92
+ else
93
+ queue.enqueue_workstep_waiting()
94
+ end
95
+ process_queue(queue)
96
+ # TODO: Implement a FatalError class
97
+ # rescue LyberCore::Exceptions::FatalError => e
98
+ # LyberCore::Log.fatal("e.msg")
99
+ # exit
100
+ rescue LyberCore::Exceptions::EmptyQueue
101
+ LyberCore::Log.info("Empty queue -- no objects to process")
102
+ rescue Exception => e
103
+ LyberCore::Log.error(e.msg)
104
+ LyberCore::Log.error(e.backtrace.join("\n"))
105
+ end
106
+ end
107
+
108
+ # Generate a list of druids to process
109
+ def get_druid_list
110
+
111
+ druid_list = Array.new
112
+
113
+ # append any druids passed explicitly
114
+ if(@options.druid)
115
+ druid_list << @options.druid
116
+ end
117
+
118
+ # identifier list is in a file
119
+ if (@options.file && File.exist?(@options.file))
120
+ File.open(@options.file) do |file|
121
+ file.each_line do |line|
122
+ druid = line.strip
123
+ if (druid.length > 0)
124
+ druid_list << druid
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ return druid_list
131
+ end
132
+
133
+ def process_queue(queue)
134
+ while work_item = queue.next_item do
135
+ begin
136
+ #call overridden method
137
+ process_item(work_item)
138
+ work_item.set_success
139
+ rescue Exception => e
140
+ # LyberCore::Log.error("Encountered exception processing #{work_item.druid}: #{e.to_s}")
141
+ # LyberCore::Log.debug("Encountered exception processing #{work_item.druid}: #{e.backtrace.join("\n")}")
142
+ work_item.set_error(e)
143
+ end
144
+ end
145
+ queue.print_stats()
146
+ end
147
+
148
+ # Override this method in your robot instance. The method in this base class will throw an exception if it is not overriden.
149
+ def process_item(work_item)
150
+ #to be overridden by child classes
151
+ raise 'You must implement this method in your subclass'
152
+ end
153
+
154
+ # ###########################
155
+ # command line option parsing
156
+
157
+ def parse_options
158
+
159
+ options = {}
160
+
161
+ o = OptionParser.new do |opts|
162
+ opts.banner = "Usage: example.rb [options]"
163
+ opts.separator ""
164
+
165
+ opts.on("-d DRUID", "--druid DRUID", "Pass in a druid to process") do |d|
166
+ @options.druid = d
167
+ end
168
+
169
+ opts.on("-f", "--file FILE", "Pass in a file of druids to process") do |f|
170
+ @options.file = f
171
+ end
172
+
173
+ end
174
+
175
+ # Parse the command line options and ignore anything not specified above
176
+ begin
177
+ o.parse!
178
+ rescue OptionParser::InvalidOption => e
179
+ LyberCore::Log.debug("e.inspect")
180
+ rescue OptionParser::ParseError => e
181
+ LyberCore::Log.error("Couldn't parse options: #{e.backtrace}")
182
+ raise e
183
+ end
184
+
185
+ end
186
+
187
+ # def output_options
188
+ # puts "Options:\n"
189
+ #
190
+ # @options.marshal_dump.each do |name, val|
191
+ # puts " #{name} = #{val}"
192
+ # end
193
+ # end
194
+ #
195
+ # def output_help
196
+ # output_version
197
+ # RDoc::usage() #exits app
198
+ # end
199
+ #
200
+ # def output_usage
201
+ # RDoc::usage('usage') # gets usage from comments above
202
+ # end
203
+ #
204
+ # def output_version
205
+ # puts "#{File.basename(__FILE__)} version #{VERSION}"
206
+ # end
207
+
208
+ # ##################################
209
+ # end of command line option parsing
210
+ # ##################################
211
+
212
+ end # end of class
213
+ end # end of Robots module
214
+ end # end of LyberCore module