jenkins-plugin-runtime 0.1.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.
- data/.gitignore +7 -0
- data/.travis.yml +2 -0
- data/Gemfile +4 -0
- data/Rakefile +21 -0
- data/jenkins-plugin-runtime.gemspec +30 -0
- data/lib/jenkins/launcher.rb +15 -0
- data/lib/jenkins/model.rb +91 -0
- data/lib/jenkins/model/action.rb +37 -0
- data/lib/jenkins/model/build.rb +21 -0
- data/lib/jenkins/model/describable.rb +75 -0
- data/lib/jenkins/model/descriptor.rb +40 -0
- data/lib/jenkins/model/listener.rb +47 -0
- data/lib/jenkins/plugin.rb +141 -0
- data/lib/jenkins/plugin/cli.rb +20 -0
- data/lib/jenkins/plugin/proxies.rb +131 -0
- data/lib/jenkins/plugin/proxies/build_wrapper.rb +50 -0
- data/lib/jenkins/plugin/proxies/builder.rb +22 -0
- data/lib/jenkins/plugin/proxy.rb +76 -0
- data/lib/jenkins/plugin/runtime.rb +13 -0
- data/lib/jenkins/plugin/runtime/version.rb +7 -0
- data/lib/jenkins/slaves/cloud.rb +9 -0
- data/lib/jenkins/tasks/build_wrapper.rb +39 -0
- data/lib/jenkins/tasks/builder.rb +33 -0
- data/spec/jenkins/launcher_spec.rb +8 -0
- data/spec/jenkins/model/action_spec.rb +36 -0
- data/spec/jenkins/model/build_spec.rb +8 -0
- data/spec/jenkins/model/describable_spec.rb +51 -0
- data/spec/jenkins/model/listener_spec.rb +31 -0
- data/spec/jenkins/model_spec.rb +100 -0
- data/spec/jenkins/plugin/proxies/build_wrapper_spec.rb +25 -0
- data/spec/jenkins/plugin/proxies/builder_spec.rb +24 -0
- data/spec/jenkins/plugin/proxies/proxy_helper.rb +24 -0
- data/spec/jenkins/plugin/proxies_spec.rb +148 -0
- data/spec/jenkins/plugin/proxy_spec.rb +43 -0
- data/spec/jenkins/plugin_spec.rb +7 -0
- data/spec/jenkins/tasks/build_wrapper_spec.rb +7 -0
- data/spec/jenkins/tasks/builder_spec.rb +8 -0
- data/spec/spec_helper.rb +11 -0
- data/src/jenkins/ruby/DoDynamic.java +18 -0
- data/src/jenkins/ruby/Get.java +18 -0
- metadata +175 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
require 'jenkins/war'
|
8
|
+
Jenkins::War.classpath
|
9
|
+
ClassPath = FileList[File.join(ENV['HOME'], '.jenkins', 'wars', Jenkins::War::VERSION, "**/*.jar")].to_a.join(':')
|
10
|
+
|
11
|
+
desc "compile java source code"
|
12
|
+
task "compile" => "target" do
|
13
|
+
puts command = "javac -classpath #{ClassPath} #{FileList['src/**/*.java']} -d target"
|
14
|
+
system(command)
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'rake/clean'
|
18
|
+
directory "target"
|
19
|
+
CLEAN.include("target")
|
20
|
+
|
21
|
+
task :default => [:compile, :spec]
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "jenkins/plugin/runtime/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "jenkins-plugin-runtime"
|
7
|
+
s.version = Jenkins::Plugin::Runtime::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Charles Lowell"]
|
10
|
+
s.email = ["cowboyd@thefrontside.net"]
|
11
|
+
s.homepage = "http://github.com/cowboyd/jenkins-plugins.rb"
|
12
|
+
s.summary = %q{Runtime support libraries for Jenkins Ruby plugins}
|
13
|
+
s.description = %q{I'll think of a better description later, but if you're reading this, then I haven't}
|
14
|
+
|
15
|
+
s.rubyforge_project = "jenkins-plugin-runtime"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency "json"
|
23
|
+
|
24
|
+
s.add_development_dependency "rake"
|
25
|
+
s.add_development_dependency "rspec", "~> 2.0"
|
26
|
+
s.add_development_dependency "rspec-spies"
|
27
|
+
s.add_development_dependency "cucumber", "~> 1.0"
|
28
|
+
s.add_development_dependency "jenkins-war"
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
module Jenkins
|
3
|
+
|
4
|
+
# Launch processes on build slaves. No functionality is currently exposed
|
5
|
+
class Launcher
|
6
|
+
# the nantive hudson.Launcher object
|
7
|
+
attr_reader :native
|
8
|
+
|
9
|
+
def initialize(native = nil)
|
10
|
+
@native = native
|
11
|
+
end
|
12
|
+
|
13
|
+
Plugin::Proxies.register self, Java.hudson.Launcher
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
|
2
|
+
module Jenkins
|
3
|
+
module Model
|
4
|
+
|
5
|
+
module Included
|
6
|
+
def included(cls)
|
7
|
+
super(cls)
|
8
|
+
if cls.class == Module
|
9
|
+
cls.extend(Included)
|
10
|
+
else
|
11
|
+
cls.extend(Inherited)
|
12
|
+
cls.extend(ClassDisplayName)
|
13
|
+
cls.extend(Transience)
|
14
|
+
cls.send(:include, InstanceDisplayName)
|
15
|
+
end
|
16
|
+
Model.descendant(cls)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
extend Included
|
20
|
+
|
21
|
+
module Inherited
|
22
|
+
def inherited(cls)
|
23
|
+
super(cls)
|
24
|
+
Model.descendant(cls)
|
25
|
+
cls.extend(Inherited)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceDisplayName
|
30
|
+
# Get the display name of this Model. This value will be used as a default
|
31
|
+
# whenever this model needs to be shown in the UI. If no display name has
|
32
|
+
# been set, then it will use the Model's class name.
|
33
|
+
#
|
34
|
+
# @return [String] the display name
|
35
|
+
def display_name
|
36
|
+
self.class.display_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassDisplayName
|
41
|
+
|
42
|
+
# Set or get the display name of this Model Class.
|
43
|
+
#
|
44
|
+
# If `name` is not nil, then sets the display name.
|
45
|
+
# @return [String] the display name
|
46
|
+
def display_name(name = nil)
|
47
|
+
name.nil? ? @display_name || self.name : @display_name = name.to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module Transience
|
52
|
+
|
53
|
+
# Mark a set of properties that should not be persisted as part of this Model's lifecycle.
|
54
|
+
#
|
55
|
+
# Jenkins supports transparent persistent
|
56
|
+
def transient(*properties)
|
57
|
+
properties.each do |p|
|
58
|
+
transients[p.to_sym] = true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def transient?(property)
|
63
|
+
transients.keys.member?(property.to_sym) || (superclass < Model && superclass.transient?(property))
|
64
|
+
end
|
65
|
+
|
66
|
+
def transients
|
67
|
+
@transients ||= {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module Descendants
|
72
|
+
def descendant(mod)
|
73
|
+
@descendants ||= clear
|
74
|
+
@descendants[mod] = true
|
75
|
+
end
|
76
|
+
|
77
|
+
def descendants
|
78
|
+
@descendants.keys
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear
|
82
|
+
@descendants = {}
|
83
|
+
end
|
84
|
+
|
85
|
+
def descendant?(cls)
|
86
|
+
@descendants[cls]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
extend Descendants
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
module Jenkins
|
3
|
+
module Model
|
4
|
+
module Action
|
5
|
+
include Model
|
6
|
+
|
7
|
+
module Included
|
8
|
+
def included(cls)
|
9
|
+
super(cls)
|
10
|
+
if cls.class == Module
|
11
|
+
cls.extend(Included)
|
12
|
+
else
|
13
|
+
cls.extend(ClassMethods)
|
14
|
+
cls.send(:include, InstanceMethods)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
extend Included
|
19
|
+
|
20
|
+
# def included(cls)
|
21
|
+
# super(cls)
|
22
|
+
# cls.send(:include)
|
23
|
+
# end
|
24
|
+
module InstanceMethods
|
25
|
+
def icon
|
26
|
+
self.class.icon
|
27
|
+
end
|
28
|
+
end
|
29
|
+
#
|
30
|
+
module ClassMethods
|
31
|
+
def icon(path = nil)
|
32
|
+
path.nil? ? @path : @path = path.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Jenkins
|
3
|
+
module Model
|
4
|
+
##
|
5
|
+
# Represents a single build. In general, you won't need this
|
6
|
+
#
|
7
|
+
class Build
|
8
|
+
|
9
|
+
##
|
10
|
+
# the Hudson::Model::AbstractBuild represented by this build
|
11
|
+
attr_reader :native
|
12
|
+
|
13
|
+
def initialize(native = nil)
|
14
|
+
@native = native
|
15
|
+
end
|
16
|
+
|
17
|
+
Jenkins::Plugin::Proxies.register self, Java.hudson.model.AbstractBuild
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
module Jenkins
|
3
|
+
module Model
|
4
|
+
|
5
|
+
# Register a Ruby class as a Jenkins extension point
|
6
|
+
|
7
|
+
# When Jenkins is searching for all the extension points of a particular
|
8
|
+
# type... let's say `Builder` for example. It first asks for all the `Descriptor`s
|
9
|
+
# registered to describe the `Builder` type. It will then use the descriptors
|
10
|
+
# it finds to do things like construct and validate the extension objects.
|
11
|
+
#
|
12
|
+
# It helps me to think about the Jenkins `Descriptor` as a Ruby class. It is a
|
13
|
+
# factory for instances, and contains data about what those instances can do.
|
14
|
+
#
|
15
|
+
# This module, when included, provides a single class method `describe_as`
|
16
|
+
# which tell Jenkins in effect "when you are looking for Java Classes of this type"
|
17
|
+
# you can use this ruby class too. e.g.
|
18
|
+
#
|
19
|
+
# class Builder
|
20
|
+
# include Jenkins::Model::Describeable
|
21
|
+
# describe_as Java.hudson.tasks.Builder
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# behind the scenes, this creates a `Descriptor` instance registered against the java type
|
25
|
+
# `Java.hudson.tasks.Builder`. Now, any time Jenkins asks about what kind of `hudson.tasks.Builder`s there
|
26
|
+
# are in the system, this class will come up.
|
27
|
+
#
|
28
|
+
# This class should generally not be needed by plugin authors since it is part of the
|
29
|
+
# glue layer and not the public runtime API.
|
30
|
+
module Describable
|
31
|
+
DescribableError = Class.new(StandardError)
|
32
|
+
|
33
|
+
module DescribeAs
|
34
|
+
def describe_as cls
|
35
|
+
if defined?(cls.java_class) && cls.is_a?(Class)
|
36
|
+
@describe_as_type = cls.java_class
|
37
|
+
else
|
38
|
+
fail DescribableError, "#{cls.class.inspect} is not an instance of java.lang.Class"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
def describe_as_type
|
42
|
+
@describe_as_type
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# When a Describable class is subclassed, make it also Describable
|
47
|
+
module Inherited
|
48
|
+
def inherited(cls)
|
49
|
+
super(cls)
|
50
|
+
cls.extend Inherited
|
51
|
+
describe_as_type = @describe_as_type
|
52
|
+
cls.class_eval do
|
53
|
+
@describe_as_type = describe_as_type
|
54
|
+
end
|
55
|
+
if Jenkins::Plugin.instance
|
56
|
+
Jenkins::Plugin.instance.register_describable(cls, describe_as_type)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module Included
|
62
|
+
def included(mod)
|
63
|
+
super
|
64
|
+
if mod.is_a? Class
|
65
|
+
mod.extend DescribeAs
|
66
|
+
mod.extend Inherited
|
67
|
+
else
|
68
|
+
warn "tried to include Describable into a Module. Are you sure?"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
self.extend Included
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Jenkins
|
5
|
+
module Model
|
6
|
+
class Descriptor < Java.hudson.model.Descriptor
|
7
|
+
|
8
|
+
def initialize(impl, plugin, java_type)
|
9
|
+
super(Java.org.jruby.RubyObject.java_class)
|
10
|
+
@impl, @plugin, @java_type = impl, plugin, java_type
|
11
|
+
end
|
12
|
+
|
13
|
+
def getDisplayName
|
14
|
+
@impl.display_name
|
15
|
+
end
|
16
|
+
|
17
|
+
def getT()
|
18
|
+
@java_type
|
19
|
+
end
|
20
|
+
|
21
|
+
def newInstance(request, form)
|
22
|
+
properties = JSON.parse(form.toString(2))
|
23
|
+
properties.delete("kind")
|
24
|
+
properties.delete("stapler-class")
|
25
|
+
instance = @plugin.export(construct(properties))
|
26
|
+
puts "instance created: #{instance}"
|
27
|
+
return instance
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def construct(attrs)
|
33
|
+
@impl.new(attrs)
|
34
|
+
rescue ArgumentError
|
35
|
+
@impl.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
module Jenkins
|
3
|
+
module Model
|
4
|
+
|
5
|
+
# Receive/Send events about a running task
|
6
|
+
class Listener
|
7
|
+
|
8
|
+
# the underlying hudson.model.TaskListener object
|
9
|
+
attr_reader :native
|
10
|
+
|
11
|
+
def initialize(native = nil)
|
12
|
+
@native = native
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Insert a clickable hyperlink into this tasks's output
|
17
|
+
# @param [String] url the link target
|
18
|
+
# @param [String] text the link content
|
19
|
+
def hyperlink(url, text)
|
20
|
+
@native.hyperlink(url, text)
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Append a message to the task output.
|
25
|
+
# @param [String] msg the message
|
26
|
+
def log(msg)
|
27
|
+
@native.getLogger().write(msg.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Append an error message to the task output.
|
32
|
+
# @param [String] msg the error message
|
33
|
+
def error(msg)
|
34
|
+
@native.error(msg.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Append a fatal error message to the task output
|
39
|
+
# @param [String] msg the fatal error message
|
40
|
+
def fatal(msg)
|
41
|
+
@native.fatalError(msg.to_s)
|
42
|
+
end
|
43
|
+
|
44
|
+
Jenkins::Plugin::Proxies.register self, Java.hudson.util.AbstractTaskListener
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Jenkins
|
5
|
+
# Acts as the primary gateway between Ruby and Jenkins
|
6
|
+
# There is one instance of this object for the entire
|
7
|
+
# plugin
|
8
|
+
#
|
9
|
+
# On the Java side, it contains a reference to an instance
|
10
|
+
# of RubyPlugin. These two objects talk to each other to
|
11
|
+
# get things done.
|
12
|
+
#
|
13
|
+
# Each running ruby plugin has exactly one instance of
|
14
|
+
# `Jenkins::Plugin`
|
15
|
+
class Plugin
|
16
|
+
|
17
|
+
# A list of all the hudson.model.Descriptor objects
|
18
|
+
# of which this plugin is aware *indexed by Wrapper class*
|
19
|
+
#
|
20
|
+
# This is used so that wrappers can always have a single place
|
21
|
+
# to go when they are asked for a descriptor. That way, wrapper
|
22
|
+
# instances can always return the descriptor associated with
|
23
|
+
# their class.
|
24
|
+
#
|
25
|
+
# This may go away.
|
26
|
+
attr_reader :descriptors
|
27
|
+
|
28
|
+
# the instance of jenkins.ruby.RubyPlugin with which this Plugin is associated
|
29
|
+
attr_reader :peer
|
30
|
+
|
31
|
+
# Initializes this plugin by reading the models.rb
|
32
|
+
# file. This is a manual registration process
|
33
|
+
# Where ruby objects register themselves with the plugin
|
34
|
+
# In the future, this process will be automatic, but
|
35
|
+
# I haven't decided the best way to do this yet.
|
36
|
+
#
|
37
|
+
# @param [org.jenkinsci.ruby.RubyPlugin] java a native java RubyPlugin
|
38
|
+
def initialize(java)
|
39
|
+
@java = @peer = java
|
40
|
+
@start = @stop = proc {}
|
41
|
+
@descriptors = {}
|
42
|
+
@proxies = Proxies.new(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Initialize the singleton instance that will run for a
|
46
|
+
# ruby plugin. This method is designed to be called by the
|
47
|
+
# Java side when setting up the ruby plugin
|
48
|
+
# @return [Jenkins::Plugin] the singleton instance
|
49
|
+
def self.initialize(java)
|
50
|
+
#TODO: check for double initialization?!?
|
51
|
+
@instance = new(java)
|
52
|
+
@instance.load_models
|
53
|
+
return @instance
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get the singleton instance associated with this plugin
|
57
|
+
#
|
58
|
+
# This is useful when code in the plugin needs to get a
|
59
|
+
# reference to the plugin in which it is running e.g.
|
60
|
+
#
|
61
|
+
# Jenkins::Plugin.instance #=> the running plugin
|
62
|
+
#
|
63
|
+
# @return [Jenkins::Plugin] the singleton instance
|
64
|
+
def self.instance
|
65
|
+
@instance
|
66
|
+
end
|
67
|
+
|
68
|
+
# Register a ruby class as a Jenkins extension point of
|
69
|
+
# a particular java type
|
70
|
+
#
|
71
|
+
# This method is invoked automatically as part of the auto-registration
|
72
|
+
# process, and should not need to be invoked by plugin code.
|
73
|
+
#
|
74
|
+
# @param [Class] ruby_class the class implementing the extension point
|
75
|
+
# @param [java.lang.Class] java_class that Jenkins will see this extention point as
|
76
|
+
def register_describable(ruby_class, java_class)
|
77
|
+
descriptor = Jenkins::Model::Descriptor.new(ruby_class, self, java_class)
|
78
|
+
@peer.addExtension(descriptor)
|
79
|
+
@descriptors[ruby_class] = descriptor
|
80
|
+
end
|
81
|
+
|
82
|
+
# unique identifier for this plugin in the Jenkins server
|
83
|
+
def name
|
84
|
+
@peer.getWrapper().getShortName()
|
85
|
+
end
|
86
|
+
|
87
|
+
# Called once when Jenkins first initializes this plugin
|
88
|
+
# currently does nothing, but plugin startup hooks would
|
89
|
+
# go here.
|
90
|
+
def start
|
91
|
+
@start.call()
|
92
|
+
end
|
93
|
+
|
94
|
+
# Called one by Jenkins (via RubyPlugin) when this plugin
|
95
|
+
# is shut down. Currently this does nothing, but plugin
|
96
|
+
# shutdown hooks would go here.
|
97
|
+
def stop
|
98
|
+
@stop.call()
|
99
|
+
end
|
100
|
+
|
101
|
+
# Reflect an Java object coming from Jenkins into the context of this plugin
|
102
|
+
# If the object is originally from the ruby plugin, and it was previously
|
103
|
+
# exported, then it will unwrap it. Otherwise, it will just use the object
|
104
|
+
# as a normal Java object.
|
105
|
+
#
|
106
|
+
# @param [Object] object the object to bring in from the outside
|
107
|
+
# @return the best representation of that object for this plugin
|
108
|
+
def import(object)
|
109
|
+
@proxies.import object
|
110
|
+
end
|
111
|
+
|
112
|
+
# Reflect a native Ruby object into its External Java form.
|
113
|
+
#
|
114
|
+
# Delegates to `Proxies` for the heavy lifting.
|
115
|
+
#
|
116
|
+
# @param [Object] object the object
|
117
|
+
# @returns [java.lang.Object] the Java proxy
|
118
|
+
def export(object)
|
119
|
+
@proxies.export object
|
120
|
+
end
|
121
|
+
|
122
|
+
# Link a plugin-local Ruby object to an external Java object.
|
123
|
+
#
|
124
|
+
# see 'Proxies#link`
|
125
|
+
#
|
126
|
+
# @param [Object] internal the object on the Ruby side of the link
|
127
|
+
# @param [java.lang.Object] external the object on the Java side of the link
|
128
|
+
def link(internal, external)
|
129
|
+
@proxies.link internal, external
|
130
|
+
end
|
131
|
+
|
132
|
+
def load_models
|
133
|
+
p = @java.getModelsPath().getPath()
|
134
|
+
puts "Trying to load models from #{p}"
|
135
|
+
for filename in Dir["#{p}/**/*.rb"]
|
136
|
+
puts "Loading "+filename
|
137
|
+
load filename
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|