cocoapods-util 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +21 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +54 -0
  7. data/Rakefile +13 -0
  8. data/cocoapods-util.gemspec +25 -0
  9. data/lib/cocoapods-util/CocoapodsUtilHook.rb +18 -0
  10. data/lib/cocoapods-util/cocoapods-extend/extend.rb +2 -0
  11. data/lib/cocoapods-util/cocoapods-extend/install/list.rb +140 -0
  12. data/lib/cocoapods-util/cocoapods-extend/install.rb +15 -0
  13. data/lib/cocoapods-util/cocoapods-extend/repo/push.rb +40 -0
  14. data/lib/cocoapods-util/cocoapods-extend/repo/push_helper.rb +36 -0
  15. data/lib/cocoapods-util/cocoapods-extend/repo.rb +15 -0
  16. data/lib/cocoapods-util/command/util.rb +28 -0
  17. data/lib/cocoapods-util/command.rb +1 -0
  18. data/lib/cocoapods-util/gem_version.rb +3 -0
  19. data/lib/cocoapods-util/hooks/installer.rb +4 -0
  20. data/lib/cocoapods-util/libsource/source.rb +81 -0
  21. data/lib/cocoapods-util/libsource/source_linker.rb +184 -0
  22. data/lib/cocoapods-util/package/helper/builder.rb +194 -0
  23. data/lib/cocoapods-util/package/helper/framework.rb +61 -0
  24. data/lib/cocoapods-util/package/helper/framework_builder.rb +147 -0
  25. data/lib/cocoapods-util/package/helper/library_builder.rb +47 -0
  26. data/lib/cocoapods-util/package/helper/pod_utils.rb +126 -0
  27. data/lib/cocoapods-util/package/package.rb +177 -0
  28. data/lib/cocoapods-util/user_interface/build_failed_report.rb +15 -0
  29. data/lib/cocoapods-util/xcframework/xcframework.rb +66 -0
  30. data/lib/cocoapods-util/xcframework/xcramework_build.rb +133 -0
  31. data/lib/cocoapods-util.rb +1 -0
  32. data/lib/cocoapods_plugin.rb +3 -0
  33. data/spec/command/util_spec.rb +12 -0
  34. data/spec/spec_helper.rb +50 -0
  35. metadata +126 -0
@@ -0,0 +1,126 @@
1
+ module Pod
2
+ class Command
3
+ class Util < Command
4
+ class Package < Util
5
+ private
6
+
7
+ def build_static_sandbox
8
+ static_sandbox_root = Pathname.new(config.sandbox_root)
9
+ Sandbox.new(static_sandbox_root)
10
+ end
11
+
12
+ def install_pod(platform_name, sandbox)
13
+ podfile = podfile_from_spec(
14
+ @path,
15
+ @spec.name,
16
+ platform_name,
17
+ @spec.deployment_target(platform_name),
18
+ @subspecs,
19
+ @spec_sources,
20
+ @use_modular_headers,
21
+ @dependency_config
22
+ )
23
+
24
+ static_installer = Installer.new(sandbox, podfile)
25
+ static_installer.install!
26
+
27
+ unless static_installer.nil?
28
+ # default build settings
29
+ default_build_settings = Hash.new
30
+ default_build_settings["CLANG_MODULES_AUTOLINK"] = "NO"
31
+ default_build_settings["GCC_GENERATE_DEBUGGING_SYMBOLS"] = "YES" # 生成Debug编译信息
32
+ default_build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" unless @xcframework # 非xcframework排除ios simulator 64位架构
33
+ default_build_settings["EXCLUDED_ARCHS[sdk=appletvsimulator*]"] = "arm64" unless @xcframework # 非xcframework排除tvos simulator 64位架构
34
+ default_build_settings["BUILD_LIBRARY_FOR_DISTRIBUTION"] = "YES" # 编译swift生成swiftinterface文件
35
+
36
+ # merge user setting
37
+ default_build_settings.merge!(@build_settings) unless @build_settings.empty?
38
+
39
+ static_installer.pods_project.targets.each do |target|
40
+ target.build_configurations.each do |config|
41
+ default_build_settings.each { |key, value| config.build_settings[key.to_s] = value.to_s }
42
+ end
43
+ end
44
+ static_installer.pods_project.save
45
+ end
46
+
47
+ static_installer
48
+ end
49
+
50
+ def podfile_from_spec(path, spec_name, platform_name, deployment_target, subspecs, sources, use_modular_headers, dependency_config)
51
+ options = {}
52
+ if path
53
+ if @local
54
+ options[:path] = path
55
+ else
56
+ options[:podspec] = path
57
+ end
58
+ end
59
+ options[:subspecs] = subspecs if subspecs
60
+ Pod::Podfile.new do
61
+ sources.each { |s| source s }
62
+ platform(platform_name, deployment_target)
63
+ pod(spec_name, options) unless dependency_config.keys.include?(spec_name)
64
+
65
+ dependency_config.each do |name, config|
66
+ dependency_options = {}
67
+ config.each do |key, value|
68
+ dependency_options[key.to_sym] = value
69
+ end
70
+ pod(name, dependency_options)
71
+ end
72
+
73
+ use_modular_headers! if use_modular_headers
74
+ install!('cocoapods',
75
+ :integrate_targets => false,
76
+ :deterministic_uuids => false)
77
+
78
+ target('packager') do
79
+ inherit! :complete
80
+ end
81
+ end
82
+ end
83
+
84
+ def binary_only?(spec)
85
+ deps = spec.dependencies.map { |dep| spec_with_name(dep.name) }
86
+ [spec, *deps].each do |specification|
87
+ %w(vendored_frameworks vendored_libraries).each do |attrib|
88
+ if specification.attributes_hash[attrib]
89
+ return true
90
+ end
91
+ end
92
+ end
93
+
94
+ false
95
+ end
96
+
97
+ def spec_with_name(name)
98
+ return if name.nil?
99
+
100
+ set = Pod::Config.instance.sources_manager.search(Dependency.new(name))
101
+ return nil if set.nil?
102
+
103
+ set.specification.root
104
+ end
105
+
106
+ def spec_with_path(path)
107
+ return if path.nil? || !Pathname.new(path).exist?
108
+
109
+ @path = Pathname.new(path).expand_path
110
+
111
+ if @path.directory?
112
+ help! @path + ': is a directory.'
113
+ return
114
+ end
115
+
116
+ unless ['.podspec', '.json'].include? @path.extname
117
+ help! @path + ': is not a podspec.'
118
+ return
119
+ end
120
+
121
+ Specification.from_file(@path)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,177 @@
1
+ require 'cocoapods-util/package/helper/pod_utils'
2
+ require 'cocoapods-util/package/helper/builder'
3
+ require 'cocoapods-util/package/helper/framework'
4
+
5
+ require 'tmpdir'
6
+ require 'json'
7
+
8
+ module Pod
9
+ class Command
10
+ class Util < Command
11
+ class Package < Util
12
+ self.summary = '通过podspec编译静态库(library/framework/xcframework)'
13
+ self.description = "本插件属于在cocoapods-packager的基础上做的修改,支持编译swift。另外支持use_modular_headers!、排除模拟器、排除特定架构、编译xcframework等。"
14
+ self.arguments = [
15
+ CLAide::Argument.new('NAME', true),
16
+ CLAide::Argument.new('SOURCE', false)
17
+ ]
18
+
19
+ def self.options
20
+ [
21
+ ['--force', 'Overwrite existing files.'],
22
+ ['--library', 'Generate static library.'],
23
+ ['--framework', 'Generate static framework.'],
24
+ ['--xcframework', 'Generate static xcframework.'],
25
+ ['--local', 'Use local state rather than published versions.'],
26
+ ['--configuration', 'Build the specified configuration (e.g. Debug). Defaults to Release'],
27
+ ['--subspecs', 'Only include the given subspecs'],
28
+ ['--spec-sources=private,https://github.com/CocoaPods/Specs.git', 'The sources to pull dependent ' \
29
+ 'pods from (defaults to https://github.com/CocoaPods/Specs.git)'],
30
+ ['--exclude-sim', '排除模拟器架构,仅编译真机对应的架构。'],
31
+ ['--use-modular-headers', '开启use_modular_headers!'],
32
+ ['--dependency-config={}', '依赖的pod文件配置,为一个json dictionary,可以配置branch、tag、source源等。配置方式:{"PodA":{"git":"xxx","branch":"xxx"},"PodB":{"source":"xxx"}}'],
33
+ ['--contains-resources', '生成的framework中是否包含bundle文件,默认不把bundle文件放到framework中。'],
34
+ ['--platforms=ios,osx,watchos,tvos,all', '选择编译的平台架构,默认`all`,编译全部平台。'],
35
+ ['--build-settings={}', 'xcode build_settings。{"EXCLUDED_ARCHS[sdk=iphonesimulator*]":"arm64"}']
36
+ ]
37
+ end
38
+
39
+ def initialize(argv)
40
+ @framework = argv.flag?('framework')
41
+ @xcframework = argv.flag?('xcframework')
42
+ @library = argv.flag?('library')
43
+ @local = argv.flag?('local', false)
44
+ @package_type = if @xcframework
45
+ :static_xcframework
46
+ elsif @framework
47
+ :static_framework
48
+ elsif @library
49
+ :static_library
50
+ else
51
+ :static_library
52
+ end
53
+ @force = argv.flag?('force')
54
+ @exclude_sim = argv.flag?('exclude-sim', false)
55
+ @use_modular_headers = argv.flag?('use-modular-headers', false)
56
+ @name = argv.shift_argument
57
+ @source = argv.shift_argument
58
+ @spec_sources = argv.option('spec-sources', 'https://github.com/CocoaPods/Specs.git').split(',')
59
+
60
+ subspecs = argv.option('subspecs')
61
+ @subspecs = subspecs.split(',') unless subspecs.nil?
62
+
63
+ @framework_contains_resources = argv.flag?('contains-resources', false)
64
+ @platforms = argv.option('platforms', 'all')
65
+ @build_settings = JSON.parse(argv.option('build-settings', '{}'))
66
+
67
+ dependency_config = argv.option('dependency-config', '{}')
68
+ @dependency_config = JSON.parse(dependency_config)
69
+
70
+ @config = argv.option('configuration', 'Release')
71
+
72
+ @source_dir = Dir.pwd
73
+ @is_spec_from_path = false
74
+ @spec = spec_with_path(@name)
75
+ @is_spec_from_path = true if @spec
76
+ @spec ||= spec_with_name(@name)
77
+ super
78
+ end
79
+
80
+ def validate!
81
+ super
82
+ help! 'A podspec name or path is required.' unless @spec
83
+ help! '--local option can only be used when a local `.podspec` path is given.' if @local && !@is_spec_from_path
84
+ help! '编译静态库.a不允许设置包含resources' if !(@framework || @xcframework) && @framework_contains_resources
85
+ end
86
+
87
+ def run
88
+ if @spec.nil?
89
+ help! 'Unable to find a podspec with path or name.'
90
+ return
91
+ end
92
+
93
+ target_dir, work_dir = create_working_directory
94
+ return if target_dir.nil?
95
+ build_package
96
+
97
+ `mv "#{work_dir}" "#{target_dir}"`
98
+ Dir.chdir(@source_dir)
99
+ end
100
+
101
+ private
102
+
103
+ def build_in_sandbox(platform)
104
+ config.installation_root = Pathname.new(Dir.pwd)
105
+ config.sandbox_root = 'Pods'
106
+
107
+ static_sandbox = build_static_sandbox()
108
+ static_installer = install_pod(platform.name, static_sandbox)
109
+
110
+ begin
111
+ perform_build(platform, static_sandbox, static_installer)
112
+ ensure # in case the build fails; see Builder#xcodebuild.
113
+ Pathname.new(config.sandbox_root).rmtree
114
+ FileUtils.rm_f('Podfile.lock')
115
+ # clean build
116
+ FileUtils.rm_rf('build')
117
+ end
118
+ end
119
+
120
+ def build_package
121
+ platforms = @platforms.split(',')
122
+ all_platforms = platforms.include?('all')
123
+ @spec.available_platforms.each do |platform|
124
+ if all_platforms || platforms.include?(platform.name.to_s)
125
+ build_in_sandbox(platform)
126
+ end
127
+ end
128
+ end
129
+
130
+ def create_target_directory
131
+ target_dir = "#{@source_dir}/#{@spec.name}-#{@spec.version}"
132
+ if File.exist? target_dir
133
+ if @force
134
+ Pathname.new(target_dir).rmtree
135
+ else
136
+ UI.puts "Target directory '#{target_dir}' already exists."
137
+ return nil
138
+ end
139
+ end
140
+ target_dir
141
+ end
142
+
143
+ def create_working_directory
144
+ target_dir = create_target_directory
145
+ return if target_dir.nil?
146
+
147
+ work_dir = Dir.tmpdir + '/cocoapods-' + Array.new(8) { rand(36).to_s(36) }.join
148
+ UI.puts "work_dir: #{work_dir}" if @verbose
149
+ Pathname.new(work_dir).mkdir
150
+ Dir.chdir(work_dir)
151
+
152
+ [target_dir, work_dir]
153
+ end
154
+
155
+ def perform_build(platform, static_sandbox, static_installer)
156
+ static_sandbox_root = config.sandbox_root.to_s
157
+
158
+ builder = Pod::Builder.new(
159
+ platform,
160
+ static_installer,
161
+ @source_dir,
162
+ static_sandbox_root,
163
+ static_sandbox.public_headers.root,
164
+ @spec,
165
+ @config,
166
+ @exclude_sim,
167
+ @framework_contains_resources,
168
+ @verbose
169
+ )
170
+
171
+ builder.build(@package_type)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+
@@ -0,0 +1,15 @@
1
+ module Pod
2
+ module UserInterface
3
+ module BuildFailedReport
4
+ class << self
5
+ def report(command, output)
6
+ <<-EOF
7
+ Build command failed: #{command}
8
+ Output:
9
+ #{output.map { |line| " #{line}" }.join}
10
+ EOF
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,66 @@
1
+ require 'cocoapods-util/xcframework/xcramework_build.rb'
2
+
3
+ module Pod
4
+ class Command
5
+ class Util < Command
6
+ class XCFramework < Util
7
+ self.summary = '根据传入framework在同目录下生成xcframework'
8
+ self.description = <<-DESC
9
+ 根据传入framework在同目录下生成xcframework
10
+ DESC
11
+ self.command = 'xcframework'
12
+ self.arguments = [
13
+ CLAide::Argument.new('FRAMEWORK_PATH', true)
14
+ ]
15
+
16
+ def self.options
17
+ [
18
+ ['--force', '覆盖已经存在的文件'],
19
+ ['--create-swiftinterface', '有编译swift文件的framework,如果不包含swiftinterface则无法生成xcframework。
20
+ 设置该参数会生成一个swiftinterface文件解决create失败的问题。']
21
+ ]
22
+ end
23
+
24
+ def initialize(argv)
25
+ @file_path = argv.shift_argument
26
+ @force = argv.flag?('force')
27
+ @create_swiftinterface = argv.flag?('create-swiftinterface')
28
+ super
29
+ end
30
+
31
+ def validate!
32
+ super
33
+ help! '必须传入framework路径或名称.' unless @file_path
34
+ end
35
+
36
+ def run
37
+ # 获取真实路径,~ 为进程所有者的主目录
38
+ @file_path = File.expand_path(@file_path)
39
+ if !File.exist?(@file_path) || !(@file_path =~ /\.framework$/)
40
+ help! "路径不存在或传入的路径不是framework文件"
41
+ return
42
+ end
43
+
44
+ source_dir = File.dirname(@file_path)
45
+ framework_name = File.basename(@file_path, ".framework")
46
+
47
+ target_dir = "#{source_dir}/#{framework_name}.xcframework"
48
+ if File.exist?(target_dir)
49
+ if @force
50
+ Pathname.new(target_dir).rmtree
51
+ else
52
+ help! "#{target_dir}已经存在,使用`--force`可以覆盖已有文件"
53
+ end
54
+ end
55
+
56
+ builder = XCFrameworkBuilder.new(
57
+ framework_name,
58
+ source_dir,
59
+ @create_swiftinterface
60
+ )
61
+ builder.build_static_xcframework
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,133 @@
1
+ module Pod
2
+ class XCFrameworkBuilder
3
+ def initialize(name, source_dir, create_swiftinterface)
4
+ @name = name
5
+ @source_dir = source_dir
6
+ @create_swiftinterface = create_swiftinterface
7
+ end
8
+
9
+ def build_static_xcframework
10
+ framework_path = "#{@source_dir}/#{@name}.framework"
11
+ # 可执行文件名称
12
+ lib_file = "#{@name}"
13
+ # 可执行文件完整路径
14
+ lib_path = "#{framework_path}/#{lib_file}"
15
+ # 可执行文件不存在,退出
16
+ unless File.exist? lib_path
17
+ UI.puts("没有找到可执行文件,请检查输入的framework")
18
+ return nil
19
+ end
20
+ # 如果可执行文件为软链接类型,获取realpath
21
+ if File.ftype(lib_path) == 'link'
22
+ lib_file = File.readlink(lib_path)
23
+ lib_path = "#{framework_path}/#{lib_file}"
24
+ end
25
+
26
+ framework_paths = Array.new
27
+ # 获取可执行文件的支持架构
28
+ archs = `lipo -archs #{lib_path}`.split
29
+ os_archs = sim_archs = []
30
+ if archs.empty?
31
+ UI.puts "framework文件中没有检查到任何编译架构,请使用`lipo -info`或`lipo -archs`检查文件支持的架构。"
32
+ return
33
+ elsif archs.count == 1
34
+ framework_paths += [framework_path]
35
+ else
36
+ platform = `strings #{lib_path} | grep -E -i '/Platforms/.*\.platform/' | head -n 1`.chomp!
37
+ if platform =~ /iPhone[^\.]*\.platform/ # iphoneos iphonesimulator
38
+ os_archs = archs & ['arm64', 'armv7', 'armv7s']
39
+ sim_archs = archs & ['i386', 'x86_64']
40
+ elsif platform =~ /MacOSX.platform/ # macosx
41
+ os_archs = ['arm64', 'x86_64']
42
+ elsif platform =~ /Watch[^\.]*\.platform/ # watchos watchsimulator
43
+ os_archs = archs & ['armv7k', 'arm64_32']
44
+ sim_archs = archs & ['arm64', 'i386', 'x86_64']
45
+ elsif platform =~ /AppleTV[^\.]*\.platform/ # appletvos appletvsimulator
46
+ os_archs = archs & ['arm64']
47
+ sim_archs = archs & ['x86_64'] # 'arm64' 'x86_64'
48
+ else
49
+ os_archs = archs & ['arm64', 'armv7', 'armv7s']
50
+ sim_archs = archs & ['i386', 'x86_64']
51
+ end
52
+ end
53
+
54
+ # 1. remove os/simulator paths
55
+ clean_intermediate_path
56
+
57
+ # 2. copy os framework
58
+ if os_archs.count > 0
59
+ path = Pathname.new("#{@source_dir}/#{os_target_path}")
60
+ path.mkdir unless path.exist?
61
+ `cp -a #{framework_path} #{path}/`
62
+
63
+ fwk_path = "#{path}/#{@name}.framework"
64
+ framework_paths += ["#{fwk_path}"]
65
+ `lipo -extract #{os_archs.join(' -extract ')} "#{fwk_path}/#{lib_file}" -output "#{fwk_path}/#{lib_file}"`
66
+ end
67
+ # 3. copy simulation framework
68
+ if sim_archs.count > 0
69
+ path = Pathname.new("#{@source_dir}/#{simulator_target_path}")
70
+ path.mkdir unless path.exist?
71
+ `cp -a #{framework_path} #{path}/`
72
+
73
+ fwk_path = "#{path}/#{@name}.framework"
74
+ framework_paths += ["#{fwk_path}"]
75
+ `lipo -extract #{sim_archs.join(' -extract ')} "#{fwk_path}/#{lib_file}" -output "#{fwk_path}/#{lib_file}"`
76
+ end
77
+
78
+ # 4. generate xcframework
79
+ begin
80
+ generate_xcframework(framework_paths)
81
+ ensure # in case the create-xcframework fails; remove the temp directory.
82
+ clean_intermediate_path
83
+ end
84
+ end
85
+
86
+ def generate_xcframework(framework_paths)
87
+ UI.puts("Generate #{@name}.xcframework")
88
+
89
+ framework_paths.each { |framework_path|
90
+ # check_swiftmodule
91
+ swiftmodule_path = Dir.glob("#{framework_path}/Modules/*.swiftmodule").first
92
+ unless swiftmodule_path.nil?
93
+ if Dir.glob("#{swiftmodule_path}/*.swiftinterface").empty?
94
+ unless @create_swiftinterface
95
+ UI.puts "Framework中包含swiftmodule文件,但是没有swiftinterface,无法创建xcframework,请检查Framework文件。或者使用`--create-swiftinterface`参数"
96
+ return
97
+ end
98
+ arm_swiftinterface = Pathname.new("#{swiftmodule_path}/arm.swiftinterface")
99
+ File.new(arm_swiftinterface, "w+").close
100
+ end
101
+ end
102
+ }
103
+
104
+ # build xcframework
105
+ command = "xcodebuild -create-xcframework -framework #{framework_paths.join(' -framework ')} -output #{@source_dir}/#{@name}.xcframework 2>&1"
106
+ output = `#{command}`.lines.to_a
107
+ result = $?
108
+ # show error
109
+ if result.exitstatus != 0
110
+ puts UI::BuildFailedReport.report(command, output)
111
+ Process.exit
112
+ end
113
+ UI.puts("Generate #{@name}.xcframework succees")
114
+ end
115
+
116
+ private
117
+ def clean_intermediate_path
118
+ [os_target_path, simulator_target_path].each do |path|
119
+ file_path = "#{@source_dir}/#{path}"
120
+ if File.exist? file_path
121
+ FileUtils.rm_rf(file_path)
122
+ end
123
+ end
124
+ end
125
+
126
+ def os_target_path
127
+ "#{@name}_temp_os"
128
+ end
129
+ def simulator_target_path
130
+ "#{@name}_temp_simulator"
131
+ end
132
+ end
133
+ end
@@ -0,0 +1 @@
1
+ require 'cocoapods-util/gem_version'
@@ -0,0 +1,3 @@
1
+ require 'cocoapods-util/command'
2
+ require 'cocoapods-util/user_interface/build_failed_report.rb'
3
+ require 'cocoapods-util/CocoapodsUtilHook.rb'
@@ -0,0 +1,12 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ module Pod
4
+ describe Command::Util do
5
+ describe 'CLAide' do
6
+ it 'registers it self' do
7
+ Command.parse(%w{ util }).should.be.instance_of Command::Util
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,50 @@
1
+ require 'pathname'
2
+ ROOT = Pathname.new(File.expand_path('../../', __FILE__))
3
+ $:.unshift((ROOT + 'lib').to_s)
4
+ $:.unshift((ROOT + 'spec').to_s)
5
+
6
+ require 'bundler/setup'
7
+ require 'bacon'
8
+ require 'mocha-on-bacon'
9
+ require 'pretty_bacon'
10
+ require 'pathname'
11
+ require 'cocoapods'
12
+
13
+ Mocha::Configuration.prevent(:stubbing_non_existent_method)
14
+
15
+ require 'cocoapods_plugin'
16
+
17
+ #-----------------------------------------------------------------------------#
18
+
19
+ module Pod
20
+
21
+ # Disable the wrapping so the output is deterministic in the tests.
22
+ #
23
+ UI.disable_wrap = true
24
+
25
+ # Redirects the messages to an internal store.
26
+ #
27
+ module UI
28
+ @output = ''
29
+ @warnings = ''
30
+
31
+ class << self
32
+ attr_accessor :output
33
+ attr_accessor :warnings
34
+
35
+ def puts(message = '')
36
+ @output << "#{message}\n"
37
+ end
38
+
39
+ def warn(message = '', actions = [])
40
+ @warnings << "#{message}\n"
41
+ end
42
+
43
+ def print(message)
44
+ @output << message
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ #-----------------------------------------------------------------------------#