esr-rim 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +40 -0
- data/README.md +3 -0
- data/Rakefile +56 -0
- data/bin/rim +3 -0
- data/lib/rim/command/command.rb +37 -0
- data/lib/rim/command/status.rb +110 -0
- data/lib/rim/command/sync.rb +69 -0
- data/lib/rim/command/upload.rb +33 -0
- data/lib/rim/command_helper.rb +119 -0
- data/lib/rim/dirty_check.rb +111 -0
- data/lib/rim/file_helper.rb +58 -0
- data/lib/rim/file_logger.rb +21 -0
- data/lib/rim/git.rb +339 -0
- data/lib/rim/manifest/helper.rb +82 -0
- data/lib/rim/manifest/json_reader.rb +40 -0
- data/lib/rim/manifest/manifest.json +7 -0
- data/lib/rim/manifest/model.rb +33 -0
- data/lib/rim/manifest/repo_reader.rb +61 -0
- data/lib/rim/module_helper.rb +52 -0
- data/lib/rim/module_info.rb +30 -0
- data/lib/rim/processor.rb +126 -0
- data/lib/rim/rev_status.rb +61 -0
- data/lib/rim/rim.rb +93 -0
- data/lib/rim/rim_exception.rb +15 -0
- data/lib/rim/rim_info.rb +129 -0
- data/lib/rim/status_builder.rb +219 -0
- data/lib/rim/sync_helper.rb +121 -0
- data/lib/rim/sync_module_helper.rb +115 -0
- data/lib/rim/upload_helper.rb +67 -0
- data/lib/rim/upload_module_helper.rb +152 -0
- data/lib/rim/version.rb +10 -0
- data/test/dirty_check_test.rb +210 -0
- data/test/file_helper_test.rb +132 -0
- data/test/git_test.rb +49 -0
- data/test/manifest_helper_test.rb +29 -0
- data/test/manifest_test_dir/manifest.rim +9 -0
- data/test/manifest_test_dir/subdir/only_to_keep_folder_in_git.txt +0 -0
- data/test/processor_test.rb +32 -0
- data/test/rim_info_test.rb +93 -0
- data/test/status_builder_test.rb +488 -0
- data/test/sync_helper_test.rb +193 -0
- data/test/sync_module_helper_test.rb +96 -0
- data/test/test_helper.rb +39 -0
- data/test/unit_tests.rb +14 -0
- data/test/upload_helper_test.rb +338 -0
- data/test/upload_module_helper_test.rb +92 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e39178ba7e2f000cec05a0ca417d4064b1c06cb6
|
4
|
+
data.tar.gz: e51d10cecfd216dcb0890a5b3a47ab13086c46c4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a09bfcc5ef6fd0138f5c3be1be93c9c19da499cfd6701cf5efc46eafb3c213b047a8aa0e6eb0b6342f42884a65f1913a5776631104f50970bcc30c64167ed9cb
|
7
|
+
data.tar.gz: 7941fdf8455ba6ccc8594a89fe17634ad82f3458aee8a56e6871c65834ddbdd17808c18ff58b1d1b2adc60f81d3c71fbd9ad5e8b05510f91039290fe202626eb
|
data/CHANGELOG
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# 0.1.0
|
2
|
+
|
3
|
+
* initial version
|
4
|
+
|
5
|
+
# 0.1.1
|
6
|
+
|
7
|
+
* take latest clean commit to create rim branch on
|
8
|
+
* Avoid additional parameters in message.
|
9
|
+
|
10
|
+
# 0.1.2
|
11
|
+
|
12
|
+
* fixed rim status verify clean when remotes are dirty [BS-236]
|
13
|
+
|
14
|
+
# 0.1.3
|
15
|
+
|
16
|
+
* made rim status faster
|
17
|
+
* fixed rim exception when project git is renamed
|
18
|
+
|
19
|
+
# 1.0.0
|
20
|
+
|
21
|
+
* changed checksum calculation, not backward compatible!
|
22
|
+
* fixed commits becoming dirty when line ending format changes
|
23
|
+
* allow specification of multiple modules on sync and upload
|
24
|
+
* fixed sync with create option
|
25
|
+
* made printing of working copy status optional
|
26
|
+
* major performance improvements
|
27
|
+
|
28
|
+
# 1.0.1
|
29
|
+
|
30
|
+
* fixed dirty check to ignore empty directories
|
31
|
+
|
32
|
+
# 1.0.2
|
33
|
+
|
34
|
+
* write all logs to temporary file
|
35
|
+
|
36
|
+
# 1.1.0
|
37
|
+
|
38
|
+
* added --gerrit option, needed for pushing new branchs to gerrit [BS-202]
|
39
|
+
* fixed exception on rim sync after moving working copy [BS-248]
|
40
|
+
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)+"/lib")
|
2
|
+
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
require 'rdoc/task'
|
5
|
+
require 'rim/version'
|
6
|
+
|
7
|
+
DocFiles = [
|
8
|
+
"README.md", "CHANGELOG"
|
9
|
+
]
|
10
|
+
|
11
|
+
RTextGemSpec = Gem::Specification.new do |s|
|
12
|
+
s.name = "esr-rim"
|
13
|
+
s.version = RIM::Version::Version
|
14
|
+
s.date = Time.now.strftime("%Y-%m-%d")
|
15
|
+
s.summary = "RIM - multi git tool"
|
16
|
+
s.description = "RIM lets you work with multiple git repositories from within one single git repository."
|
17
|
+
s.authors = "ESR Labs AG"
|
18
|
+
s.homepage = "http://esrlabs.com"
|
19
|
+
s.add_dependency('subcommand', '>= 1.0.6')
|
20
|
+
gemfiles = Rake::FileList.new
|
21
|
+
gemfiles.include("{lib,test}/**/*")
|
22
|
+
gemfiles.include(DocFiles)
|
23
|
+
gemfiles.include("Rakefile")
|
24
|
+
s.files = gemfiles
|
25
|
+
s.rdoc_options = ["--main", "README.md", "-x", "test"]
|
26
|
+
s.extra_rdoc_files = DocFiles
|
27
|
+
s.bindir = "bin"
|
28
|
+
s.executables = ["rim"]
|
29
|
+
end
|
30
|
+
|
31
|
+
RDoc::Task.new do |rd|
|
32
|
+
rd.main = "README.md"
|
33
|
+
rd.rdoc_files.include(DocFiles)
|
34
|
+
rd.rdoc_files.include("lib/**/*.rb")
|
35
|
+
rd.rdoc_dir = "doc"
|
36
|
+
end
|
37
|
+
|
38
|
+
RTextPackageTask = Gem::PackageTask.new(RTextGemSpec) do |p|
|
39
|
+
p.need_zip = false
|
40
|
+
end
|
41
|
+
|
42
|
+
task :prepare_package_rdoc => :rdoc do
|
43
|
+
RTextPackageTask.package_files.include("doc/**/*")
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'run unit tests'
|
47
|
+
task :run_tests do
|
48
|
+
sh "ruby test/unit_tests.rb"
|
49
|
+
end
|
50
|
+
|
51
|
+
task :release => [:prepare_package_rdoc, :package]
|
52
|
+
|
53
|
+
task :clobber => [:clobber_rdoc, :clobber_package]
|
54
|
+
|
55
|
+
task :default => :run_tests
|
56
|
+
|
data/bin/rim
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module RIM
|
2
|
+
module Command
|
3
|
+
|
4
|
+
class Command
|
5
|
+
attr_writer :logger
|
6
|
+
|
7
|
+
def initialize(processor)
|
8
|
+
@processor = processor
|
9
|
+
end
|
10
|
+
|
11
|
+
def project_git_dir
|
12
|
+
git_dir = find_git_dir(".")
|
13
|
+
raise RimException.new("The current path is not part of a git repository.") if !git_dir
|
14
|
+
git_dir
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def find_git_dir(start_dir)
|
20
|
+
last_dir = nil
|
21
|
+
dir = File.expand_path(start_dir)
|
22
|
+
while dir != last_dir
|
23
|
+
if File.exist?("#{dir}/.git") || dir =~ /\.git$/
|
24
|
+
return dir
|
25
|
+
end
|
26
|
+
last_dir = dir
|
27
|
+
# returns itself on file system root
|
28
|
+
dir = File.dirname(dir)
|
29
|
+
end
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'rim/status_builder'
|
2
|
+
|
3
|
+
module RIM
|
4
|
+
module Command
|
5
|
+
|
6
|
+
class Status < Command
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
opts.banner = "Usage: rim status [<options>] [<to-rev>|<from-rev>..<to-rev>]"
|
10
|
+
opts.description = "Prints commits and their RIM status"
|
11
|
+
opts.separator ""
|
12
|
+
opts.separator "Without revision arguments checks the current branch and all local ancestors."
|
13
|
+
opts.separator "With a single <to-rev> checks that revision and all local ancestors."
|
14
|
+
opts.separator "Otherwise checks <to-rev> and ancestors without <from-rev> and ancestors."
|
15
|
+
opts.separator "With the --gerrit option, assumes all yet unknown commits to be 'local'."
|
16
|
+
opts.separator ""
|
17
|
+
opts.on("-d", "--detailed", "print detailed status") do
|
18
|
+
@detailed = true
|
19
|
+
end
|
20
|
+
opts.on("-w", "--working-copy", "print working copy status") do
|
21
|
+
@wc_status = true
|
22
|
+
end
|
23
|
+
opts.on("-f", "--fast", "fast status assuming remote is clean") do
|
24
|
+
@fast = true
|
25
|
+
end
|
26
|
+
opts.on("--verify-clean", "exit with error code 1 if commits are dirty") do
|
27
|
+
@verify_clean = true
|
28
|
+
end
|
29
|
+
opts.on("--gerrit", "special gerrit mode which stops on all known commits") do
|
30
|
+
@gerrit_mode = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def invoke()
|
35
|
+
root = project_git_dir
|
36
|
+
rev_arg = ARGV.shift
|
37
|
+
stat = nil
|
38
|
+
RIM.git_session(root) do |gs|
|
39
|
+
sb = RIM::StatusBuilder.new
|
40
|
+
if @wc_status
|
41
|
+
stat = sb.fs_status(root)
|
42
|
+
print_status(gs, stat)
|
43
|
+
end
|
44
|
+
if rev_arg
|
45
|
+
if rev_arg =~ /\.\./
|
46
|
+
from_rev, to_rev = rev_arg.split("..")
|
47
|
+
else
|
48
|
+
from_rev, to_rev = nil, rev_arg
|
49
|
+
end
|
50
|
+
stat = sb.rev_history_status(gs, to_rev, :stop_rev => from_rev, :fast => @fast, :gerrit => @gerrit_mode)
|
51
|
+
print_status(gs, stat)
|
52
|
+
else
|
53
|
+
branch = gs.current_branch_name
|
54
|
+
stat = sb.rev_history_status(gs, branch, :fast => @fast)
|
55
|
+
print_status(gs, stat)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
if @verify_clean && any_dirty?(stat)
|
59
|
+
exit(1)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def any_dirty?(stat)
|
66
|
+
# don't check the last (remote) status nodes (leaves of the status tree)
|
67
|
+
# these are normally remote commits which could be dirty
|
68
|
+
# the initial commit would also be a leave
|
69
|
+
# TODO: see print_status
|
70
|
+
stat.dirty? || stat.parents.any?{|p| !p.parents.empty? && any_dirty?(p)}
|
71
|
+
end
|
72
|
+
|
73
|
+
def print_status(gs, stat)
|
74
|
+
# don't print the last (remote) status nodes
|
75
|
+
# note: this also excludes the initial commit
|
76
|
+
# TODO: make sure to print the first commit
|
77
|
+
# otherwise the first could be dirty but won't be shown to the user
|
78
|
+
return if stat.git_rev && stat.parents.empty?
|
79
|
+
dirty_mods = stat.modules.select{|m| m.dirty?}
|
80
|
+
stat_info = dirty_mods.empty? ? "[ OK]" : "[DIRTY]"
|
81
|
+
headline = ""
|
82
|
+
if stat.git_rev
|
83
|
+
out = gs.execute "git rev-list --format=oneline -n 1 #{stat.git_rev}"
|
84
|
+
if out =~ /^(\w+) (.*)/
|
85
|
+
sha1, comment = $1, $2
|
86
|
+
comment = comment[0..56]+"..." if comment.size > 60
|
87
|
+
headline += "#{stat_info} #{sha1[0..6]} #{comment}"
|
88
|
+
end
|
89
|
+
else
|
90
|
+
headline += "#{stat_info} ------- uncommitted changes"
|
91
|
+
end
|
92
|
+
if @detailed
|
93
|
+
@logger.info headline
|
94
|
+
dirty_mods.each do |m|
|
95
|
+
@logger.info " - #{m.dir}"
|
96
|
+
end
|
97
|
+
elsif dirty_mods.size > 0
|
98
|
+
@logger.info "#{headline} (#{dirty_mods.size} modules dirty)"
|
99
|
+
else
|
100
|
+
@logger.info headline
|
101
|
+
end
|
102
|
+
stat.parents.each do |p|
|
103
|
+
print_status(gs, p)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rim/command/command'
|
2
|
+
require 'rim/manifest/helper'
|
3
|
+
require 'rim/rim_exception'
|
4
|
+
require 'rim/rim_info'
|
5
|
+
require 'rim/sync_helper'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module RIM
|
9
|
+
module Command
|
10
|
+
|
11
|
+
class Sync < Command
|
12
|
+
|
13
|
+
include RIM::Manifest
|
14
|
+
|
15
|
+
def initialize(opts)
|
16
|
+
opts.banner = "Usage: rim sync [<options>] [<local_module_path>]"
|
17
|
+
opts.description = "Synchronize specified rim modules with remote repository revisions."
|
18
|
+
opts.separator ""
|
19
|
+
opts.on("-m", "--manifest [MANIFEST]", String, "Read information from manifest.", \
|
20
|
+
"If no manifest file is specified a 'manifest.rim' file will be used.") do |manifest|
|
21
|
+
@manifest = manifest ? manifest : Helpers::default_manifest
|
22
|
+
end
|
23
|
+
opts.on("-c", "--create", "Synchronize module initially to <local_module_path>.", \
|
24
|
+
"Specify the remote URL and the target revision with the options.") do
|
25
|
+
@create = true
|
26
|
+
end
|
27
|
+
@module_options = {}
|
28
|
+
opts.on("-u", "--remote-url URL", String, "Set the remote URL of the module.", \
|
29
|
+
"A relative path will be applied to ssh://gerrit/") do |url|
|
30
|
+
@module_options[:remote_url] = url
|
31
|
+
end
|
32
|
+
opts.on("-r", "--target-revision REVISION", String, "Set the target revision of the module.") do |target_revision|
|
33
|
+
@module_options[:target_revision] = target_revision
|
34
|
+
end
|
35
|
+
opts.on("-i", "--ignore [PATTERN_LIST]", String, "Set the ignore patterns by specifying a comma separated list.") do |ignores|
|
36
|
+
@module_options[:ignores] = ignores || ""
|
37
|
+
end
|
38
|
+
opts.on("-m", "--message MESSAGE", String, "Message header to provide to each commit.") do |message|
|
39
|
+
@message = message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def invoke()
|
44
|
+
helper = SyncHelper.new(project_git_dir, @logger)
|
45
|
+
if @manifest
|
46
|
+
helper.modules_from_manifest(@manifest)
|
47
|
+
elsif @create
|
48
|
+
local_path = ARGV.shift || "."
|
49
|
+
if helper.find_file_dir_in_workspace(local_path, RimInfo::InfoFileName)
|
50
|
+
raise RimException.new("There's already a module file. Don't use the create option to sync the module.")
|
51
|
+
elsif !@module_options[:remote_url] || !@module_options[:target_revision]
|
52
|
+
raise RimException.new("Please specify remote URL and target revision for the new module.")
|
53
|
+
else
|
54
|
+
helper.add_module_info(helper.create_module_info(@module_options[:remote_url], local_path, @module_options[:target_revision], \
|
55
|
+
@module_options[:ignores]))
|
56
|
+
end
|
57
|
+
else
|
58
|
+
helper.modules_from_paths(ARGV, @module_options)
|
59
|
+
end
|
60
|
+
helper.check_arguments
|
61
|
+
helper.sync(@message)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rim/command/command'
|
2
|
+
require 'rim/processor'
|
3
|
+
require 'rim/upload_helper'
|
4
|
+
|
5
|
+
module RIM
|
6
|
+
module Command
|
7
|
+
|
8
|
+
class Upload < Command
|
9
|
+
|
10
|
+
include RIM::Manifest
|
11
|
+
|
12
|
+
def initialize(opts)
|
13
|
+
@review = true
|
14
|
+
opts.banner = "Usage: rim upload <local_module_path>"
|
15
|
+
opts.description = "Upload changes from rim module synchronized to <local_module_path> to remote repository."
|
16
|
+
opts.on("-n", "--no-review", "Uploads without review. The changes will be pushed directly to the module's target branch.") do
|
17
|
+
@review = false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def invoke()
|
22
|
+
helper = UploadHelper.new(project_git_dir, @review, @logger)
|
23
|
+
helper.modules_from_paths(ARGV)
|
24
|
+
helper.check_arguments
|
25
|
+
helper.upload
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'rim/file_helper'
|
2
|
+
require 'rim/processor'
|
3
|
+
require 'rim/module_info'
|
4
|
+
require 'rim/rim_info'
|
5
|
+
require 'rim/manifest/json_reader'
|
6
|
+
require 'rim/status_builder'
|
7
|
+
|
8
|
+
module RIM
|
9
|
+
|
10
|
+
class CommandHelper < Processor
|
11
|
+
|
12
|
+
include Manifest
|
13
|
+
|
14
|
+
def initialize(workspace_root, logger, module_infos = nil)
|
15
|
+
super(workspace_root, logger)
|
16
|
+
@paths = []
|
17
|
+
@logger = logger
|
18
|
+
if module_infos
|
19
|
+
module_infos.each do |m|
|
20
|
+
add_module_info(m)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# check whether workspace is not touched
|
26
|
+
def check_ready
|
27
|
+
raise RimException.new("The workspace git contains uncommitted changes.") if !local_changes?(@ws_root)
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_arguments
|
31
|
+
raise RimException.new("Unexpected command line arguments.") if !ARGV.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_module_info(remote_url, local_path, target_revision, ignores)
|
35
|
+
ModuleInfo.new(remote_url, get_relative_path(local_path), target_revision, ignores, get_remote_branch_format(remote_url))
|
36
|
+
end
|
37
|
+
|
38
|
+
def modules_from_manifest(path)
|
39
|
+
manifest = read_manifest(path)
|
40
|
+
manifest.modules.each do |mod|
|
41
|
+
add_unique_module_info(create_module_info(mod.remote_path, mod.local_path, mod.target_revision, mod.ignores))
|
42
|
+
end
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def modules_from_paths(paths, opts = {})
|
47
|
+
if paths.empty?
|
48
|
+
module_from_path(nil, opts)
|
49
|
+
elsif paths.length == 1 || opts.empty?
|
50
|
+
while !paths.empty?
|
51
|
+
module_from_path(paths.shift, opts)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise RimException.new("Multiple modules cannot be used with additional options.")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def module_from_path(path, opts = {})
|
59
|
+
module_path = find_file_dir_in_workspace(path || ".", RimInfo::InfoFileName)
|
60
|
+
if module_path
|
61
|
+
rim_info = RimInfo.from_dir(module_path)
|
62
|
+
add_unique_module_info(create_module_info(opts.has_key?(:remote_url) ? opts[:remote_url] : rim_info.remote_url, \
|
63
|
+
module_path, opts.has_key?(:target_revision) ? opts[:target_revision] : rim_info.target_revision, \
|
64
|
+
opts.has_key?(:ignores) ? opts[:ignores] : rim_info.ignores))
|
65
|
+
module_path
|
66
|
+
else
|
67
|
+
raise RimException.new(path ? "No module info found in '#{path}'." : "No module info found.")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def modules_from_workspace()
|
72
|
+
if File.directory?(File.join(@ws_root, ".rim"))
|
73
|
+
status = StatusBuilder.new.fs_status(@ws_root)
|
74
|
+
status.modules.each do |mod|
|
75
|
+
rim_info = mod.rim_info
|
76
|
+
add_unique_module_info(ModuleInfo.new(rim_info.remote_url, mod.dir, rim_info.upstream, rim_info.ignores))
|
77
|
+
end
|
78
|
+
true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_unique_module_info(module_info)
|
83
|
+
if !@paths.include?(module_info.local_path)
|
84
|
+
@paths.push(module_info.local_path)
|
85
|
+
add_module_info(module_info)
|
86
|
+
else
|
87
|
+
raise RimException.new("Module '#{module_info.local_path}' specified more than once.")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_remote_branch_format(remote_url)
|
92
|
+
get_absolute_remote_url(remote_url).start_with?(GerritServer) ? "refs/for/%s" : nil
|
93
|
+
#"refs/for/%s"
|
94
|
+
end
|
95
|
+
|
96
|
+
def find_file_dir_in_workspace(start_dir, file)
|
97
|
+
path = File.expand_path(start_dir)
|
98
|
+
while path != @ws_root
|
99
|
+
if File.exist?(File.join(path, file))
|
100
|
+
return path
|
101
|
+
else
|
102
|
+
parent = File.dirname(path)
|
103
|
+
if parent != path
|
104
|
+
path = parent
|
105
|
+
else
|
106
|
+
break
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
def add_module_info(module_info)
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|