cocoapods-lhj-bin 0.0.1

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +11 -0
  4. data/lib/cocoapods-lhj-bin.rb +2 -0
  5. data/lib/cocoapods-lhj-bin/command.rb +1 -0
  6. data/lib/cocoapods-lhj-bin/command/bin.rb +59 -0
  7. data/lib/cocoapods-lhj-bin/command/bin/archive.rb +233 -0
  8. data/lib/cocoapods-lhj-bin/command/bin/auto.rb +198 -0
  9. data/lib/cocoapods-lhj-bin/command/bin/code.rb +232 -0
  10. data/lib/cocoapods-lhj-bin/command/bin/dup.rb +78 -0
  11. data/lib/cocoapods-lhj-bin/command/bin/init.rb +69 -0
  12. data/lib/cocoapods-lhj-bin/command/bin/initHotKey.rb +70 -0
  13. data/lib/cocoapods-lhj-bin/command/bin/install.rb +44 -0
  14. data/lib/cocoapods-lhj-bin/command/bin/lhj.rb +46 -0
  15. data/lib/cocoapods-lhj-bin/command/bin/lib/lint.rb +69 -0
  16. data/lib/cocoapods-lhj-bin/command/bin/repo/update.rb +43 -0
  17. data/lib/cocoapods-lhj-bin/command/bin/spec/create.rb +73 -0
  18. data/lib/cocoapods-lhj-bin/command/bin/spec/push.rb +115 -0
  19. data/lib/cocoapods-lhj-bin/command/bin/update.rb +153 -0
  20. data/lib/cocoapods-lhj-bin/config/config.rb +137 -0
  21. data/lib/cocoapods-lhj-bin/config/config_asker.rb +57 -0
  22. data/lib/cocoapods-lhj-bin/config/config_builder.rb +216 -0
  23. data/lib/cocoapods-lhj-bin/config/config_hot_key.rb +103 -0
  24. data/lib/cocoapods-lhj-bin/config/config_hot_key_asker.rb +57 -0
  25. data/lib/cocoapods-lhj-bin/gem_version.rb +9 -0
  26. data/lib/cocoapods-lhj-bin/helpers.rb +5 -0
  27. data/lib/cocoapods-lhj-bin/helpers/Info.plist +0 -0
  28. data/lib/cocoapods-lhj-bin/helpers/build_helper.rb +158 -0
  29. data/lib/cocoapods-lhj-bin/helpers/build_utils.rb +93 -0
  30. data/lib/cocoapods-lhj-bin/helpers/framework.rb +85 -0
  31. data/lib/cocoapods-lhj-bin/helpers/framework_builder.rb +444 -0
  32. data/lib/cocoapods-lhj-bin/helpers/library.rb +54 -0
  33. data/lib/cocoapods-lhj-bin/helpers/library_builder.rb +90 -0
  34. data/lib/cocoapods-lhj-bin/helpers/sources_helper.rb +36 -0
  35. data/lib/cocoapods-lhj-bin/helpers/spec_creator.rb +168 -0
  36. data/lib/cocoapods-lhj-bin/helpers/spec_files_helper.rb +75 -0
  37. data/lib/cocoapods-lhj-bin/helpers/spec_source_creator.rb +227 -0
  38. data/lib/cocoapods-lhj-bin/helpers/upload_helper.rb +87 -0
  39. data/lib/cocoapods-lhj-bin/native.rb +19 -0
  40. data/lib/cocoapods-lhj-bin/native/acknowledgements.rb +27 -0
  41. data/lib/cocoapods-lhj-bin/native/analyzer.rb +55 -0
  42. data/lib/cocoapods-lhj-bin/native/file_accessor.rb +28 -0
  43. data/lib/cocoapods-lhj-bin/native/installation_options.rb +25 -0
  44. data/lib/cocoapods-lhj-bin/native/installer.rb +135 -0
  45. data/lib/cocoapods-lhj-bin/native/linter.rb +26 -0
  46. data/lib/cocoapods-lhj-bin/native/path_source.rb +33 -0
  47. data/lib/cocoapods-lhj-bin/native/pod_source_installer.rb +19 -0
  48. data/lib/cocoapods-lhj-bin/native/pod_target_installer.rb +94 -0
  49. data/lib/cocoapods-lhj-bin/native/podfile.rb +91 -0
  50. data/lib/cocoapods-lhj-bin/native/podfile_env.rb +37 -0
  51. data/lib/cocoapods-lhj-bin/native/podfile_generator.rb +199 -0
  52. data/lib/cocoapods-lhj-bin/native/podspec_finder.rb +25 -0
  53. data/lib/cocoapods-lhj-bin/native/resolver.rb +230 -0
  54. data/lib/cocoapods-lhj-bin/native/sandbox_analyzer.rb +34 -0
  55. data/lib/cocoapods-lhj-bin/native/source.rb +35 -0
  56. data/lib/cocoapods-lhj-bin/native/sources_manager.rb +20 -0
  57. data/lib/cocoapods-lhj-bin/native/specification.rb +31 -0
  58. data/lib/cocoapods-lhj-bin/native/target_validator.rb +41 -0
  59. data/lib/cocoapods-lhj-bin/native/validator.rb +40 -0
  60. data/lib/cocoapods-lhj-bin/source_provider_hook.rb +66 -0
  61. data/lib/cocoapods_plugin.rb +2 -0
  62. data/spec/command/bin_spec.rb +12 -0
  63. data/spec/spec_helper.rb +50 -0
  64. metadata +177 -0
@@ -0,0 +1,44 @@
1
+
2
+ require 'cocoapods-lhj-bin/command/bin/update'
3
+ module Pod
4
+ class Command
5
+ class Bin < Command
6
+ class Install < Bin
7
+ include Pod
8
+
9
+ self.summary = 'pod install 拦截器,会加载本地Podfile_local文件,DSL加载到原始Podfile文件中。'
10
+
11
+ self.description = <<-DESC
12
+ pod install 拦截器,会加载本地Podfile_local文件
13
+ 会通过DSL加载到原始Podfile文件中
14
+ 支持 pod 'xxx' 各种写法
15
+ 支持 post_install/pre_install钩子,采用覆盖做法
16
+ DESC
17
+ def self.options
18
+ [
19
+ ['--repo-update', 'Force running `pod repo update` before install'],
20
+ ['--deployment', 'Disallow any changes to the Podfile or the Podfile.lock during installation'],
21
+ ['--clean-install', 'Ignore the contents of the project cache and force a full pod installation. This only ' \
22
+ 'applies to projects that have enabled incremental installation']
23
+ ].concat(super).reject { |(name, _)| name == '--no-repo-update' }
24
+ end
25
+
26
+ def initialize(argv)
27
+ @update = argv.flag?('update')
28
+ super
29
+ @additional_args = argv.remainder!
30
+ end
31
+
32
+ def run
33
+ Update.load_local_podfile
34
+ argvs = [
35
+ *@additional_args
36
+ ]
37
+ gen = Pod::Command::Install.new(CLAide::ARGV.new(argvs))
38
+ gen.validate!
39
+ gen.run
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,46 @@
1
+ require 'cocoapods-lhj-bin/config/config_hot_key_asker'
2
+
3
+ module Pod
4
+ class Command
5
+ class Bin < Command
6
+ class Lhj < Bin
7
+ self.summary = '快捷键'
8
+ self.description = <<-DESC
9
+ 创建 文件,在其中保存插件需要的配置信息,
10
+ 如二进制私有源地址、源码私有源地址等。
11
+ DESC
12
+
13
+ self.arguments = [
14
+ CLAide::Argument.new('1', false)
15
+ ]
16
+
17
+ def self.options
18
+ [
19
+ ].concat(super)
20
+ end
21
+
22
+ def initialize(argv)
23
+ @hot_key = argv.shift_argument || '1'
24
+ super
25
+ end
26
+
27
+ def run
28
+ CBin.config_hot_key.set_hot_key_index(@hot_key)
29
+ UI.puts "cd #{CBin.config_hot_key.hot_key_dir}".yellow
30
+
31
+ if Dir.exist?(CBin.config_hot_key.hot_key_dir)
32
+ Dir.chdir(CBin.config_hot_key.hot_key_dir) do
33
+ UI.puts " #{CBin.config_hot_key.hot_key_cmd}".yellow
34
+ system CBin.config_hot_key.hot_key_cmd
35
+ end
36
+ else
37
+ raise "配置项中文件目录不存在 #{CBin.config_hot_key.hot_key_dir}"
38
+ end
39
+
40
+
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ require 'cocoapods-lhj-bin/config/config'
2
+ require 'cocoapods-lhj-bin/native/podfile'
3
+
4
+ module Pod
5
+ class Command
6
+ class Bin < Command
7
+ class Lib < Bin
8
+ class Lint < Lib
9
+ self.summary = 'lint 组件.'
10
+ self.description = <<-DESC
11
+ lint 二进制组件 / 源码组件
12
+ DESC
13
+
14
+ self.arguments = [
15
+ CLAide::Argument.new('NAME.podspec', false)
16
+ ]
17
+
18
+ # lib lint 不会下载 source,所以不能进行二进制 lint
19
+ # 要 lint 二进制版本,需要进行 spec lint,此 lint 会去下载 source
20
+ def self.options
21
+ [
22
+ ['--code-dependencies', '使用源码依赖进行 lint'],
23
+ ['--loose-options', '添加宽松的 options, 包括 --use-libraries (可能会造成 entry point (start) undefined)'],
24
+ ['--allow-prerelease', '允许使用 prerelease 的版本 lint']
25
+ ].concat(Pod::Command::Lib::Lint.options).concat(super).uniq
26
+ end
27
+
28
+ def initialize(argv)
29
+ @loose_options = argv.flag?('loose-options')
30
+ @code_dependencies = argv.flag?('code-dependencies')
31
+ @sources = argv.option('sources') || []
32
+ @allow_prerelease = argv.flag?('allow-prerelease')
33
+ @podspec = argv.shift_argument
34
+ super
35
+
36
+ @additional_args = argv.remainder!
37
+ end
38
+
39
+ def run
40
+ Podfile.execute_with_bin_plugin do
41
+ Podfile.execute_with_allow_prerelease(@allow_prerelease) do
42
+ Podfile.execute_with_use_binaries(!@code_dependencies) do
43
+ argvs = [
44
+ @podspec || code_spec_files.first,
45
+ "--sources=#{sources_option(@code_dependencies, @sources)}",
46
+ *@additional_args
47
+ ]
48
+
49
+ if @loose_options
50
+ argvs << '--allow-warnings'
51
+ if code_spec&.all_dependencies&.any?
52
+ argvs << '--use-libraries'
53
+ end
54
+ end
55
+
56
+ lint = Pod::Command::Lib::Lint.new(CLAide::ARGV.new(argvs))
57
+ lint.validate!
58
+ lint.run
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,43 @@
1
+ require 'parallel'
2
+
3
+ module Pod
4
+ class Command
5
+ class Bin < Command
6
+ class Repo < Bin
7
+ class Update < Repo
8
+ self.summary = '更新私有源'
9
+
10
+ self.arguments = [
11
+ CLAide::Argument.new('NAME', false)
12
+ ]
13
+
14
+ def self.options
15
+ [
16
+ ['--all', '更新所有私有源,默认只更新二进制相关私有源']
17
+ ].concat(super)
18
+ end
19
+
20
+ def initialize(argv)
21
+ @all = argv.flag?('all')
22
+ @name = argv.shift_argument
23
+ super
24
+ end
25
+
26
+ def run
27
+ show_output = !config.silent?
28
+ if @name || @all
29
+ config.sources_manager.update(@name, show_output)
30
+ else
31
+ Parallel.each(valid_sources, in_threads: 4) do |source|
32
+ UI.puts "更新私有源仓库 #{source.to_s}".yellow
33
+ source.update(show_output)
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,73 @@
1
+ require 'cocoapods-lhj-bin/helpers'
2
+
3
+ module Pod
4
+ class Command
5
+ class Bin < Command
6
+ class Spec < Bin
7
+ class Create < Spec
8
+ self.summary = '创建二进制 spec.'
9
+ self.description = <<-DESC
10
+ 根据源码 podspec 文件,创建对应的二进制 podspec 文件.
11
+ DESC
12
+
13
+ def self.options
14
+ [
15
+ ['--platforms=ios', '生成二进制 spec 支持的平台'],
16
+ ['--template-podspec=A.binary-template.podspec', '生成拥有 subspec 的二进制 spec 需要的模版 podspec, 插件会更改 version 和 source'],
17
+ ['--no-overwrite', '不允许覆盖']
18
+ ].concat(super)
19
+ end
20
+
21
+ def initialize(argv)
22
+ @platforms = argv.option('platforms', 'ios')
23
+ @allow_overwrite = argv.flag?('overwrite', true)
24
+ @template_podspec = argv.option('template-podspec')
25
+ @podspec = argv.shift_argument
26
+ super
27
+ end
28
+
29
+ def run
30
+ UI.puts "开始读取 podspec 文件...\n"
31
+
32
+ code_spec = Pod::Specification.from_file(spec_file)
33
+ if template_spec_file
34
+ template_spec = Pod::Specification.from_file(template_spec_file)
35
+ end
36
+
37
+ if binary_spec && !@allow_overwrite
38
+ UI.warn "二进制 podspec 文件 #{binary_spec_files.first} 已存在.\n"
39
+ else
40
+ UI.puts "开始生成二进制 podspec 文件...\n"
41
+ spec_file = create_binary_spec_file(code_spec, template_spec)
42
+ UI.puts "创建二进制 podspec 文件 #{spec_file} 成功.\n".green
43
+ end
44
+ end
45
+
46
+ def template_spec_file
47
+ @template_spec_file ||= begin
48
+ if @template_podspec
49
+ find_spec_file(@template_podspec)
50
+ else
51
+ binary_template_spec_file
52
+ end
53
+ end
54
+ end
55
+
56
+ def spec_file
57
+ @spec_file ||= begin
58
+ if @podspec
59
+ find_spec_file(@podspec)
60
+ else
61
+ if code_spec_files.empty?
62
+ raise Informative, '当前目录下没有找到可用源码 podspec.'
63
+ end
64
+
65
+ code_spec_files.first
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,115 @@
1
+ require 'cocoapods-lhj-bin/config/config'
2
+ require 'cocoapods-lhj-bin/native/podfile'
3
+
4
+ module Pod
5
+ class Command
6
+ class Bin < Command
7
+ class Repo < Bin
8
+ class Push < Repo
9
+ self.summary = '发布组件.'
10
+ self.description = <<-DESC
11
+ 发布二进制组件 / 源码组件
12
+ DESC
13
+
14
+ self.arguments = [
15
+ CLAide::Argument.new('NAME.podspec', false)
16
+ ]
17
+
18
+ def self.options
19
+ [
20
+ ['--binary', '发布组件的二进制版本'],
21
+ ['--template-podspec=A.binary-template.podspec', '生成拥有 subspec 的二进制 spec 需要的模版 podspec, 插件会更改 version 和 source'],
22
+ ['--reserve-created-spec', '保留生成的二进制 spec 文件'],
23
+ ['--code-dependencies', '使用源码依赖进行 lint'],
24
+ ['--loose-options', '添加宽松的 options, 包括 --use-libraries (可能会造成 entry point (start) undefined)'],
25
+ ['--allow-prerelease', '允许使用 prerelease 的版本 lint']
26
+ ].concat(Pod::Command::Repo::Push.options).concat(super).uniq
27
+ end
28
+
29
+ def initialize(argv)
30
+ @podspec = argv.shift_argument
31
+ @binary = argv.flag?('binary')
32
+ @loose_options = argv.flag?('loose-options')
33
+ @code_dependencies = argv.flag?('code-dependencies')
34
+ @sources = argv.option('sources') || []
35
+ @reserve_created_spec = argv.flag?('reserve-created-spec')
36
+ @template_podspec = argv.option('template-podspec')
37
+ @allow_prerelease = argv.flag?('allow-prerelease')
38
+ super
39
+
40
+ @additional_args = argv.remainder!
41
+ end
42
+
43
+ def run
44
+ UI.puts "进入方法"
45
+ Podfile.execute_with_bin_plugin do
46
+ Podfile.execute_with_allow_prerelease(@allow_prerelease) do
47
+ Podfile.execute_with_use_binaries(!@code_dependencies) do
48
+ argvs = [
49
+ repo,
50
+ "--sources=#{sources_option(@code_dependencies, @sources)}",
51
+ *@additional_args
52
+ ]
53
+
54
+ argvs << spec_file if spec_file
55
+
56
+ if @loose_options
57
+ argvs += ['--allow-warnings', '--use-json']
58
+ if code_spec&.all_dependencies&.any?
59
+ argvs << '--use-libraries'
60
+ end
61
+ end
62
+
63
+ push = Pod::Command::Repo::Push.new(CLAide::ARGV.new(argvs))
64
+ push.validate!
65
+ push.run
66
+ end
67
+ end
68
+ end
69
+ ensure
70
+ clear_binary_spec_file_if_needed unless @reserve_created_spec
71
+ end
72
+
73
+ private
74
+
75
+ def template_spec_file
76
+ @template_spec_file ||= begin
77
+ if @template_podspec
78
+ find_spec_file(@template_podspec)
79
+ else
80
+ binary_template_spec_file
81
+ end
82
+ end
83
+ end
84
+
85
+ def spec_file
86
+ @spec_file ||= begin
87
+ if @podspec
88
+ find_spec_file(@podspec)
89
+ else
90
+ if code_spec_files.empty?
91
+ raise Informative, '当前目录下没有找到可用源码 podspec.'
92
+ end
93
+
94
+ spec_file = if @binary
95
+ code_spec = Pod::Specification.from_file(code_spec_files.first)
96
+ if template_spec_file
97
+ template_spec = Pod::Specification.from_file(template_spec_file)
98
+ end
99
+ create_binary_spec_file(code_spec, template_spec)
100
+ else
101
+ code_spec_files.first
102
+ end
103
+ spec_file
104
+ end
105
+ end
106
+ end
107
+
108
+ def repo
109
+ @binary ? binary_source.name : code_source.name
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,153 @@
1
+ require 'cocoapods'
2
+ require 'cocoapods-lhj-bin/native/podfile_env'
3
+ require 'cocoapods-lhj-bin/native/podfile'
4
+
5
+ module Pod
6
+ class Command
7
+ class Bin < Command
8
+ class Update < Bin
9
+ include Pod
10
+ include Pod::Podfile::DSL
11
+
12
+ self.summary = 'pod update 拦截器,会加载本地Podfile_local文件,DSL加载到原始Podfile文件中。'
13
+
14
+ self.description = <<-DESC
15
+ pod update 拦截器,会加载本地Podfile_local文件
16
+ 会通过DSL加载到原始Podfile文件中
17
+ 支持 pod 'xxx' 各种写法
18
+ 支持 post_install/pre_install钩子,采用覆盖做法
19
+ DESC
20
+ def self.options
21
+ [
22
+ ["--sources=#{Pod::TrunkSource::TRUNK_REPO_URL}", 'The sources from which to update dependent pods. ' \
23
+ 'Multiple sources must be comma-delimited'],
24
+ ['--exclude-pods=podName', 'Pods to exclude during update. Multiple pods must be comma-delimited'],
25
+ ['--clean-install', 'Ignore the contents of the project cache and force a full pod installation. This only ' \
26
+ 'applies to projects that have enabled incremental installation'],
27
+ ['--project-directory=/project/dir/', 'The path to the root of the project directory'],
28
+ ['--no-repo-update', 'Skip running `pod repo update` before install']
29
+ ].concat(super)
30
+ end
31
+
32
+ def initialize(argv)
33
+ @update = argv.flag?('update')
34
+ super
35
+ @additional_args = argv.remainder!
36
+ end
37
+
38
+ def run
39
+ Update.load_local_podfile
40
+
41
+ argvs = [
42
+ *@additional_args
43
+ ]
44
+
45
+ gen = Pod::Command::Update.new(CLAide::ARGV.new(argvs))
46
+ gen.validate!
47
+ gen.run
48
+ end
49
+
50
+ def self.load_local_podfile
51
+ # 同步 Podfile_local 文件
52
+ project_root = Pod::Config.instance.project_root
53
+ path = File.join(project_root.to_s, 'Podfile_local')
54
+ unless File.exist?(path)
55
+ path = File.join(project_root.to_s, 'Podfile_local')
56
+ end
57
+
58
+ if File.exist?(path)
59
+ contents = File.open(path, 'r:utf-8', &:read)
60
+
61
+ podfile = Pod::Config.instance.podfile
62
+ local_podfile = Podfile.from_file(path)
63
+
64
+ if local_podfile
65
+ local_pre_install_callback = nil
66
+ local_post_install_callback = nil
67
+ local_podfile.instance_eval do
68
+ local_pre_install_callback = @pre_install_callback
69
+ local_post_install_callback = @post_install_callback
70
+ end
71
+ end
72
+
73
+ podfile.instance_eval do
74
+ begin
75
+
76
+ # podfile HASH_KEYS才有plugins字段,否则会被限制
77
+ if local_podfile.plugins.any?
78
+ hash_plugins = podfile.plugins || {}
79
+ hash_plugins = hash_plugins.merge(local_podfile.plugins)
80
+ set_hash_value(%w[plugins].first, hash_plugins)
81
+
82
+ # 加入源码白名单,避免本地库被二进制了
83
+ podfile.set_use_source_pods(local_podfile.use_source_pods) if local_podfile.use_source_pods
84
+ podfile.use_binaries!(local_podfile.use_binaries?)
85
+ end
86
+
87
+ # 在target把local-target中到dependencies值删除了,再设置
88
+ # 把本地和原始到dependencies 合并,设置dependencies
89
+ local_podfile&.target_definition_list&.each do |local_target|
90
+ next if local_target.name == 'Pods'
91
+
92
+ target_definition_list.each do |target|
93
+
94
+ unless target.name == local_target.name &&
95
+ (local_target.to_hash['dependencies'] &&local_target.to_hash['dependencies'].any?)
96
+ next
97
+ end
98
+
99
+
100
+
101
+ target.instance_exec do
102
+ # 在target把local-target中到dependencies值删除了,再设置
103
+
104
+ local_dependencies = local_target.to_hash['dependencies']
105
+ target_dependencies = target.to_hash['dependencies']
106
+
107
+ local_dependencies.each do |local_dependency|
108
+ unless local_dependency.is_a?(Hash) && local_dependency.keys.first
109
+ next
110
+ end
111
+
112
+ target_dependencies.each do |target_dependency|
113
+ next unless target_dependency.is_a?(Hash) &&
114
+ target_dependency.keys.first &&
115
+ target_dependency.keys.first == local_dependency.keys.first
116
+
117
+ target_dependencies.delete target_dependency
118
+ break
119
+ end
120
+ end
121
+ # 把本地和原始到dependencies 合并,设置dependencies
122
+ local_dependencies.each do |d|
123
+ UI.message "Development Pod #{d.to_yaml}"
124
+ if podfile.plugins.keys.include?('cocoapods-lhj-bin')
125
+ podfile.set_use_source_pods(d.keys.first) if (d.is_a?(Hash) && d.keys.first)
126
+ end
127
+ end
128
+ new_dependencies = target_dependencies + local_dependencies
129
+ set_hash_value(%w[dependencies].first, new_dependencies)
130
+
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ if local_pre_install_callback
137
+ @pre_install_callback = local_pre_install_callback
138
+ end
139
+ if local_post_install_callback
140
+ @post_install_callback = local_post_install_callback
141
+ end
142
+ rescue Exception => e
143
+ message = "Invalid `#{path}` file: #{e.message}"
144
+ raise Pod::DSLError.new(message, path, e, contents)
145
+ end
146
+ end
147
+
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end