ccios 5.0.0 → 5.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0173944aad4daed89817a5a20eb66ebeb7cacb57d96f6bd1ab60ff79569680b
4
- data.tar.gz: 927fecdf93f40d5636a1fcdefa173410b307ece874ffc6015ec9bb6e405cd7af
3
+ metadata.gz: 27b359e8339dcd511d85488eb93f75d19e7b03bc6ed7cceedebdd721c3b9ad03
4
+ data.tar.gz: db07b4baa0b8143fcec3f70c3ecbd45b9e4a26b1885659128da67a52ffb1afc3
5
5
  SHA512:
6
- metadata.gz: 35a7291a319e2384a3c25662d58925dc8a77ca459fabc606a16d84ed257618f03584c42eb7dfd98d3103e3f2b3b2ceabcfe0a5663397c16d58a98e7b18e2b9c7
7
- data.tar.gz: 5a254c0e5d56a4f9076da36dabab143673ae58a12351fc93eccb30ab7a1f07a9d240ee97f500d1a5023b210fcaf6fc6fbaa25507bca0257786fb79b97c31eabd
6
+ metadata.gz: 25e62f91e331fe7b2e1e8b58dd1e44559faefe40be02b61a8d3858a0ba174618af52d897d9e87fa2b91b7df21e82a6130c8891d51bb0bc706764142c2b8916a4
7
+ data.tar.gz: c924d07dc088ca07efddc97de3222411f8fa564935b28e971480856bce1c08f88e3323134725f721d385e061980c62d50083057d9291a55c6c399db5b4ec0ca2
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.2.2
1
+ 3.3.0
data/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [5.2.0]
8
+
9
+ ### Added
10
+
11
+ - Add support for Swift Packages and Xcode 16 synchronized folders
12
+ - Add new optional variable nammed "project_type" which accepts 2 values: "xcode" and "filesystem". When missing ccios use "xcode" by default.
13
+ - When "project_type" is set to "filesystem" ccios will not try to update a pbxproj and will only generate files.
14
+ - When "project_type" is set to "filesystem" multi target definition is no longer supported for generated files.
15
+ - When generating files for an filesystem project, the target name in the header is either: the target defined in the template variables, the target defined in `.ccios.yml`, or it will try to guess the name when using SPM by searching the target name inside the standard naming scheme of: "Sources/<target_name>/".
16
+
17
+ ## [5.1.0]
18
+
19
+ ### Changed
20
+
21
+ - Target variable is now optional, an empty string or an unset value will use the first target of the project. This change allows templates to not overrides global target settings in `.ccios.yml`
22
+ - When multiple targets are provided for a file, `{{project_name}}` will now be replaced by the name of the project instead of the name of the first target
23
+ - `@MainActor` has been added to relevent files in Coordinator and Presenter templates to improve Swift 6 support
24
+ - dependency provider snippets has been updated to handle Swift 6 issue (see [this issue](https://github.com/Swinject/Swinject/issues/571) for why this is required)
25
+
7
26
  ## [5.0.0]
8
27
 
9
28
  This release is an entire rewrite of the templating system, allowing customization of template and settings.
data/Gemfile.lock CHANGED
@@ -1,41 +1,60 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ccios (5.0.0)
4
+ ccios (5.2.0)
5
5
  activesupport (> 4)
6
6
  mustache (~> 1.0)
7
- xcodeproj (~> 1.4)
7
+ xcodeproj (~> 1.27)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- CFPropertyList (3.0.6)
12
+ CFPropertyList (3.0.7)
13
+ base64
14
+ nkf
13
15
  rexml
14
- activesupport (7.0.8)
15
- concurrent-ruby (~> 1.0, >= 1.0.2)
16
+ activesupport (8.0.2)
17
+ base64
18
+ benchmark (>= 0.3)
19
+ bigdecimal
20
+ concurrent-ruby (~> 1.0, >= 1.3.1)
21
+ connection_pool (>= 2.2.5)
22
+ drb
16
23
  i18n (>= 1.6, < 2)
24
+ logger (>= 1.4.2)
17
25
  minitest (>= 5.1)
18
- tzinfo (~> 2.0)
26
+ securerandom (>= 0.3)
27
+ tzinfo (~> 2.0, >= 2.0.5)
28
+ uri (>= 0.13.1)
19
29
  atomos (0.1.3)
30
+ base64 (0.3.0)
31
+ benchmark (0.4.1)
32
+ bigdecimal (3.2.2)
20
33
  claide (1.1.0)
21
34
  colored2 (3.1.2)
22
- concurrent-ruby (1.2.2)
23
- i18n (1.14.1)
35
+ concurrent-ruby (1.3.5)
36
+ connection_pool (2.5.3)
37
+ drb (2.2.3)
38
+ i18n (1.14.7)
24
39
  concurrent-ruby (~> 1.0)
25
- minitest (5.20.0)
40
+ logger (1.7.0)
41
+ minitest (5.25.5)
26
42
  mustache (1.1.1)
27
- nanaimo (0.3.0)
28
- rake (12.3.3)
29
- rexml (3.2.6)
43
+ nanaimo (0.4.0)
44
+ nkf (0.2.0)
45
+ rake (13.3.0)
46
+ rexml (3.4.1)
47
+ securerandom (0.4.1)
30
48
  tzinfo (2.0.6)
31
49
  concurrent-ruby (~> 1.0)
32
- xcodeproj (1.23.0)
50
+ uri (1.0.3)
51
+ xcodeproj (1.27.0)
33
52
  CFPropertyList (>= 2.3.3, < 4.0)
34
53
  atomos (~> 0.1.3)
35
54
  claide (>= 1.0.2, < 2.0)
36
55
  colored2 (~> 3.1)
37
- nanaimo (~> 0.3.0)
38
- rexml (~> 3.2.4)
56
+ nanaimo (~> 0.4.0)
57
+ rexml (>= 3.3.6, < 4.0)
39
58
 
40
59
  PLATFORMS
41
60
  ruby
@@ -43,7 +62,7 @@ PLATFORMS
43
62
  DEPENDENCIES
44
63
  ccios!
45
64
  minitest (~> 5.11)
46
- rake (~> 12.3)
65
+ rake (~> 13.2)
47
66
 
48
67
  BUNDLED WITH
49
68
  2.4.10
data/MAINTAINER.md ADDED
@@ -0,0 +1,12 @@
1
+
2
+ ## How to release a new version
3
+
4
+ - Create the release branch `release/vA.B.C`
5
+ - Complete the changelog and add the new vertion title
6
+ - Update the version in ccios.gemspec & run `bundle install`
7
+ - Create a Pull Request
8
+ - Once merged, create a tag `A.B.C` on the merged commit
9
+ - Create release in GitHub
10
+ - Publish release to RubyGem:
11
+ - `make build`
12
+ - `gem push ccios-A.B.C.gem`
data/README.md CHANGED
@@ -137,7 +137,9 @@ templates_collection: ccios/templates
137
137
 
138
138
  # Global overrides of variables [Optional]
139
139
  variables:
140
+ project_type: xcode
140
141
  project: Project.xcodeproj
142
+ target: SomeDefaultTarget
141
143
 
142
144
  # Per template variables override
143
145
  templates_config:
@@ -220,12 +222,15 @@ parameters:
220
222
  # List of templates variables that is used to generate files in an xcode project. [Optional]
221
223
  # Those variables can be overridden in config file, see section "Variable hierarchy" for more informations.
222
224
  variables:
225
+ # Type of project "filesystem" or "xcode", will be considered as "xcode" if not specified. [Optional]
226
+ # You want to use "filesystem" if you want to generate files for an SPM project, or if your Xcode project uses the new synchronized group from Xcode 16.
227
+ project_type: filesystem
223
228
  # The name of the xcode project. "*.xcodeproj" will use the first it finds. [required]
224
229
  project: "*.xcodeproj"
225
230
  # The base path used to generate an element. This variable must be defined once here, or on each elements below.
226
231
  base_path: "path/to/base_group"
227
- # The target in which files are added. Can be a string, a list of strings, or an empty string. This variable must be defined once here, or on each elements below. If an empty string is provided, it will use the first target found in the Xcode project.
228
- target: ""
232
+ # The target in which files are added. Can be a string, a list of strings, or an empty string. This variable must be defined once here, or on each elements below. If this variable is not set or if an empty string is provided, it will use the first target found in the Xcode project. If present it will override the global default target. [Optional]
233
+ target: "SomeTarget"
229
234
  # List of generated elements. [Required]
230
235
  # Each element can be a file (using `file`), or an empty folder (using `group`)
231
236
  generated_elements:
@@ -296,3 +301,16 @@ Element will use variables in this order (first in this list is used): (For file
296
301
  - Config Template variables
297
302
  - Default templates variables
298
303
  - Config Global variables
304
+
305
+ # How to develop
306
+
307
+ 1. install the gem locally using
308
+ ```bash
309
+ make install
310
+ ```
311
+ 2. Run use the locally installed ccios gem on your project
312
+ ```bash
313
+ ccios presenter MyNewPresenterStack
314
+ ```
315
+
316
+ Note: ensure that you use the same ruby version when building, installing and running this local version of ccios.
data/ccios.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'ccios'
3
- s.version = '5.0.0'
3
+ s.version = '5.2.0'
4
4
  s.executables << 'ccios'
5
5
  s.date = '2016-08-03'
6
6
  s.summary = "Clean Code iOS Generator"
@@ -12,9 +12,9 @@ Gem::Specification.new do |s|
12
12
  s.homepage = 'http://rubygems.org/gems/hola'
13
13
  s.license = 'MIT'
14
14
  s.add_dependency 'activesupport', '> 4'
15
- s.add_dependency 'xcodeproj', '~> 1.4'
15
+ s.add_dependency 'xcodeproj', '~> 1.27'
16
16
  s.add_dependency "mustache", "~> 1.0"
17
17
 
18
- s.add_development_dependency 'rake', '~> 12.3'
18
+ s.add_development_dependency 'rake', '~> 13.2'
19
19
  s.add_development_dependency 'minitest', '~> 5.11'
20
20
  end
@@ -3,15 +3,10 @@ require 'fileutils'
3
3
  require 'logger'
4
4
  require 'xcodeproj'
5
5
 
6
- class Xcodeproj::Project::Object::PBXGroup
7
-
8
- def pf_new_group(associate_path_to_group:, name:, path:)
9
- # When using "Group with folder" we only provide a path
10
- # When using "Group without folder" we only provide a name
11
- new_group(
12
- associate_path_to_group ? nil : name,
13
- associate_path_to_group ? path : nil
14
- )
6
+ class Xcodeproj::Project
7
+
8
+ def project_name_from_path
9
+ File.basename(@path, File.extname(@path))
15
10
  end
16
11
  end
17
12
 
@@ -25,12 +20,24 @@ class FileCreator
25
20
  FileCreator.logger
26
21
  end
27
22
 
28
- def templater_options(target)
23
+ def templater_options(targets, project)
29
24
  defaults = {
30
- project_name: target.display_name,
31
25
  full_username: git_username,
32
26
  date: DateTime.now.strftime("%d/%m/%Y"),
33
27
  }
28
+ if project.nil?
29
+ if targets.count == 1
30
+ defaults["project_name"] = targets[0]
31
+ else
32
+ raise "A file outside an xcode project cannot require multiple targets"
33
+ end
34
+ else
35
+ if targets.count == 1
36
+ defaults["project_name"] = targets[0].display_name
37
+ else
38
+ defaults["project_name"] = project.project_name_from_path
39
+ end
40
+ end
34
41
  defaults
35
42
  end
36
43
 
@@ -44,7 +51,7 @@ class FileCreator
44
51
  tags
45
52
  end
46
53
 
47
- def create_file_using_template_path(template_path, generated_filename, group, targets, context)
54
+ def create_file_using_template_path(template_path, generated_filename, group, targets, project, context)
48
55
  file_path = File.join(group.real_path, generated_filename)
49
56
 
50
57
  raise "File #{file_path} already exists" if File.exist?(file_path)
@@ -52,16 +59,13 @@ class FileCreator
52
59
  FileUtils.mkdir_p dirname unless File.directory?(dirname)
53
60
  file = File.new(file_path, 'w')
54
61
 
55
- context = context.merge(templater_options(targets[0]))
62
+ context = context.merge(templater_options(targets, project))
56
63
  file_content = CodeTemplater.new.render_file_content_from_template(template_path, generated_filename, context)
57
64
 
58
65
  file.puts(file_content)
59
66
 
60
67
  file.close
61
- file_ref = group.new_reference(file_path)
62
- targets.each do |target|
63
- target.add_file_references([file_ref])
64
- end
68
+ group.register_file_to_targets(file_path, targets)
65
69
  end
66
70
 
67
71
  def print_file_content_using_template(filename, template_path, context)
@@ -72,8 +76,12 @@ class FileCreator
72
76
  logger.info file_content
73
77
  end
74
78
 
75
- def create_empty_directory(group)
79
+ def create_empty_directory_for_group(group)
76
80
  dirname = group.real_path
81
+ create_empty_directory(dirname)
82
+ end
83
+
84
+ def create_empty_directory(dirname)
77
85
  FileUtils.mkdir_p dirname unless File.directory?(dirname)
78
86
 
79
87
  git_keep_path = File.join(dirname, ".gitkeep")
@@ -1,6 +1,6 @@
1
1
  require_relative 'code_templater'
2
2
  require_relative 'file_creator'
3
- require_relative 'pbxproj_parser'
3
+ require_relative 'xcode_group_representation'
4
4
 
5
5
  class FileTemplateDefinition
6
6
  def initialize(file_template_definition_hash)
@@ -33,20 +33,34 @@ class FileTemplateDefinition
33
33
  base_path = merged_variables["base_path"]
34
34
  raise "Missing base_path variable" if base_path.nil?
35
35
 
36
- base_group = project[base_path]
37
- raise "Base path \"#{base_path}\" is missing" if base_group.nil?
38
-
39
36
  target_name = merged_variables["target"]
40
- if target_name.is_a?(String)
41
- target = parser.target_for(project, target_name)
42
- raise "Unable to find target \"#{target_name}\"" if target.nil?
43
- elsif target_name.is_a?(Array)
44
- target_name.each do |target_name|
37
+ if project.nil?
38
+ if target_name.is_a?(String)
39
+ target = target_name
40
+ elsif target_name.nil?
41
+ guessed_name = guess_target_name_from_path(base_path)
42
+ raise "Unable to guess the target from the base path \"#{base_path}\", please specify the target in your config file" if guessed_name.nil?
43
+ target = guessed_name
44
+ elsif target_name.is_a?(Array)
45
+ raise "A template generating files in an filesystem project type cannot specify multiple targets"
46
+ else
47
+ raise "Invalid target in template #{@name}"
48
+ end
49
+ else
50
+ base_group = XcodeGroupRepresentation.findGroup(base_path, project)
51
+ raise "Base path \"#{base_path}\" is missing" if base_group.nil?
52
+
53
+ if target_name.is_a?(String) || target_name.nil?
45
54
  target = parser.target_for(project, target_name)
46
55
  raise "Unable to find target \"#{target_name}\"" if target.nil?
56
+ elsif target_name.is_a?(Array)
57
+ target_name.each do |target_name|
58
+ target = parser.target_for(project, target_name)
59
+ raise "Unable to find target \"#{target_name}\"" if target.nil?
60
+ end
61
+ else
62
+ raise "Invalid target in template #{@name}"
47
63
  end
48
- else
49
- raise "Invalid target in template #{@name}"
50
64
  end
51
65
 
52
66
  end
@@ -55,32 +69,30 @@ class FileTemplateDefinition
55
69
  merged_variables = config.variables_for_template_element(template_definition, @name, @variables)
56
70
 
57
71
  base_path = merged_variables["base_path"]
58
- base_group = project[base_path]
72
+ base_group = XcodeGroupRepresentation.findGroup(base_path, project)
59
73
  file_path = CodeTemplater.new.render_string(@path, context)
60
74
 
61
75
  intermediates_groups = file_path.split("/")[0...-1]
62
76
  generated_filename = file_path.split("/")[-1]
63
77
 
64
- group = base_group
65
- associate_path_to_group = !base_group.path.nil?
66
-
67
- intermediates_groups.each do |group_name|
68
- new_group_path = File.join(group.real_path, group_name)
69
- existing_group = group.groups.find { |g| g.display_name == group_name }
70
- group = existing_group || group.pf_new_group(
71
- associate_path_to_group: associate_path_to_group,
72
- name: group_name,
73
- path: new_group_path
74
- )
75
- end
78
+ group = base_group.create_groups_if_needed_for_intermediate_groups(intermediates_groups)
76
79
 
77
80
  target_name = merged_variables["target"]
78
81
 
79
82
  targets = []
80
- if target_name.is_a?(String)
81
- targets = [parser.target_for(project, target_name)]
82
- elsif target_name.is_a?(Array)
83
- targets = target_name.map { |name| parser.target_for(project, name) }
83
+ if project.nil?
84
+ if target_name.is_a?(String)
85
+ targets = [target_name]
86
+ elsif target_name.nil?
87
+ guessed_name = guess_target_name_from_path(base_path)
88
+ targets = [guessed_name]
89
+ end
90
+ else
91
+ if target_name.is_a?(String) || target_name.nil?
92
+ targets = [parser.target_for(project, target_name)]
93
+ elsif target_name.is_a?(Array)
94
+ targets = target_name.map { |name| parser.target_for(project, name) }
95
+ end
84
96
  end
85
97
 
86
98
  FileCreator.new.create_file_using_template_path(
@@ -88,7 +100,19 @@ class FileTemplateDefinition
88
100
  generated_filename,
89
101
  group,
90
102
  targets,
103
+ project,
91
104
  context
92
105
  )
93
106
  end
94
- end
107
+
108
+ private def guess_target_name_from_path(path)
109
+ # SPM standard format
110
+ parts = path.split(File::SEPARATOR)
111
+ sources_index = parts.index("Sources")
112
+ if sources_index && sources_index + 1 < parts.length
113
+ return parts[sources_index + 1]
114
+ else
115
+ return nil
116
+ end
117
+ end
118
+ end
@@ -1,6 +1,5 @@
1
1
  require_relative 'code_templater'
2
2
  require_relative 'file_creator'
3
- require_relative 'pbxproj_parser'
4
3
 
5
4
  class GroupTemplateDefinition
6
5
  def initialize(group_template_definition_hash)
@@ -22,7 +21,7 @@ class GroupTemplateDefinition
22
21
  base_path = merged_variables["base_path"]
23
22
  raise "Missing base_path variable" if base_path.nil?
24
23
 
25
- base_group = project[base_path]
24
+ base_group = XcodeGroupRepresentation.findGroup(base_path, project)
26
25
  raise "Base path \"#{base_path}\" is missing" if base_group.nil?
27
26
  end
28
27
 
@@ -30,25 +29,13 @@ class GroupTemplateDefinition
30
29
  merged_variables = config.variables_for_template_element(template_definition, @name, @variables)
31
30
 
32
31
  base_path = merged_variables["base_path"]
33
- base_group = project[base_path]
32
+ base_group = XcodeGroupRepresentation.findGroup(base_path, project)
34
33
  group_path = CodeTemplater.new.render_string(@path, context)
35
34
 
36
- group_path = group_path.split("/")
37
-
38
- group = base_group
39
- associate_path_to_group = !base_group.path.nil?
40
-
41
- group_path.each do |group_name|
42
- new_group_path = File.join(group.real_path, group_name)
43
- existing_group = group.groups.find { |g| g.display_name == group_name }
44
- group = existing_group || group.pf_new_group(
45
- associate_path_to_group: associate_path_to_group,
46
- name: group_name,
47
- path: new_group_path
48
- )
49
- end
35
+ group_path_components = group_path.split("/")
36
+ group = base_group.create_groups_if_needed_for_intermediate_groups(group_path_components)
50
37
 
51
38
  file_creator = FileCreator.new
52
- file_creator.create_empty_directory(group)
39
+ file_creator.create_empty_directory_for_group(group)
53
40
  end
54
- end
41
+ end
@@ -27,10 +27,10 @@ class PBXProjParser
27
27
  end
28
28
 
29
29
  def target_for(project, target_name)
30
- if target_name.blank?
30
+ if target_name.blank? || target_name.nil?
31
31
  project.targets.find { |t| t.product_type == "com.apple.product-type.application" }
32
32
  else
33
33
  project.targets.find { |t| t.name == target_name }
34
34
  end
35
35
  end
36
- end
36
+ end
@@ -59,10 +59,17 @@ class TemplateDefinition
59
59
  raise "Error: invalid template name" unless @name.is_a? String
60
60
 
61
61
  merged_variables = config.variables_for_template(self)
62
- project_path = merged_variables["project"]
63
-
64
- project = parser.project_for(project_path)
65
- raise "Error: Unable to find project \"#{project_path}\"" if project.nil?
62
+ project_type = merged_variables["project_type"] || "xcode"
63
+ case project_type
64
+ when "xcode"
65
+ project_path = merged_variables["project"]
66
+ project = parser.project_for(project_path)
67
+ raise "Error: Unable to find project \"#{project_path}\"" if project.nil?
68
+ when "filesystem"
69
+ project = nil
70
+ else
71
+ raise "Invalid project_type given \"#{project_type}\", only \"xcode\" and \"fiilesystem\" are supported"
72
+ end
66
73
 
67
74
  @template_file_source.each do |file_template_name, path|
68
75
  raise "Missing template source file for \"#{file_template_name}\"" unless File.exist?(self.template_source_file(file_template_name))
@@ -93,9 +100,16 @@ class TemplateDefinition
93
100
  options = agrument_transformed_options(options)
94
101
 
95
102
  merged_variables = config.variables_for_template(self)
96
- project_path = merged_variables["project"]
97
103
 
98
- project = parser.project_for project_path
104
+ project_type = merged_variables["project_type"] || "xcode"
105
+ case project_type
106
+ when "xcode"
107
+ project_path = merged_variables["project"]
108
+ project = parser.project_for project_path
109
+ when "filesystem"
110
+ project = nil
111
+
112
+ end
99
113
 
100
114
  @generated_elements.each do |element|
101
115
  element.generate(parser, project, options, self, config)
@@ -10,6 +10,7 @@ import Foundation
10
10
  import ADCoordinator
11
11
 
12
12
  {{#generate_delegate}}
13
+ @MainActor
13
14
  protocol {{name}}CoordinatorDelegate: AnyObject {
14
15
 
15
16
  }
@@ -23,8 +24,10 @@ class {{name}}Coordinator: Coordinator {
23
24
  private let dependencyProvider: ApplicationDependencyProvider
24
25
  private unowned var navigationController: UINavigationController
25
26
 
26
- init(navigationController: UINavigationController,
27
- dependencyProvider: ApplicationDependencyProvider) {
27
+ init(
28
+ navigationController: UINavigationController,
29
+ dependencyProvider: ApplicationDependencyProvider
30
+ ) {
28
31
  self.navigationController = navigationController
29
32
  self.dependencyProvider = dependencyProvider
30
33
  }
@@ -3,7 +3,6 @@ description: "Generate NameCoordinator"
3
3
  variables:
4
4
  project: "*.xcodeproj"
5
5
  base_path: "Coordinator"
6
- target: ""
7
6
  parameters:
8
7
  - argument: "name"
9
8
  removeSuffix: "Coordinator"
@@ -10,9 +10,9 @@ import Foundation
10
10
 
11
11
  public class {{name}}InteractorImplementation: {{name}}Interactor {
12
12
 
13
- public init() {
13
+ public init() {
14
14
 
15
- }
15
+ }
16
16
 
17
17
  // MARK: - {{name}}Interactor
18
18
 
@@ -6,7 +6,6 @@ parameters:
6
6
  variables:
7
7
  project: "*.xcodeproj"
8
8
  base_path: "Core/Interactor"
9
- target: ""
10
9
  generated_elements:
11
10
  - name: "interactor"
12
11
  file: "{{ name }}Interactor/{{ name }}Interactor.swift"
@@ -2,13 +2,17 @@
2
2
  func {{lowercased_name}}Presenter(viewContract: {{name}}ViewContract, presenterDelegate: {{name}}PresenterDelegate) -> {{name}}Presenter? {
3
3
  return presenterAssembler
4
4
  .resolver
5
- .resolve({{name}}Presenter.self, arguments: viewContract, presenterDelegate)
5
+ .resolve(
6
+ {{name}}Presenter.self,
7
+ arguments: viewContract as {{name}}ViewContract,
8
+ presenterDelegate as {{name}}PresenterDelegate
9
+ )
6
10
  }
7
11
  {{/generate_delegate}}
8
12
  {{^generate_delegate}}
9
13
  func {{lowercased_name}}Presenter(viewContract: {{name}}ViewContract) -> {{name}}Presenter? {
10
14
  return presenterAssembler
11
15
  .resolver
12
- .resolve({{name}}Presenter.self, argument: viewContract)
16
+ .resolve({{name}}Presenter.self, argument: viewContract as {{name}}ViewContract)
13
17
  }
14
18
  {{/generate_delegate}}
@@ -8,11 +8,13 @@
8
8
 
9
9
  import Foundation
10
10
 
11
+ @MainActor
11
12
  protocol {{name}}Presenter {
12
13
  func start()
13
14
  }
14
15
  {{#generate_delegate}}
15
16
 
17
+ @MainActor
16
18
  protocol {{name}}PresenterDelegate: AnyObject {
17
19
 
18
20
  }
@@ -15,6 +15,8 @@ class {{name}}PresenterImplementation: {{name}}Presenter {
15
15
  private weak var delegate: {{name}}PresenterDelegate?
16
16
  {{/generate_delegate}}
17
17
 
18
+ // MARK: - Lifecycle
19
+
18
20
  {{#generate_delegate}}
19
21
  init(viewContract: {{name}}ViewContract, delegate: {{name}}PresenterDelegate) {
20
22
  self.viewContract = viewContract
@@ -30,6 +32,7 @@ class {{name}}PresenterImplementation: {{name}}Presenter {
30
32
  // MARK: - {{name}}Presenter
31
33
 
32
34
  func start() {
33
-
35
+ let viewModel = {{name}}ViewModelMapper().map()
36
+ viewContract?.configure(with: viewModel)
34
37
  }
35
38
  }
@@ -11,7 +11,6 @@ parameters:
11
11
  variables:
12
12
  project: "*.xcodeproj"
13
13
  base_path: "App"
14
- target: ""
15
14
  generated_elements:
16
15
  - name: "ui_view_group"
17
16
  group: "{{ name }}/UI/View"
@@ -32,8 +31,13 @@ generated_elements:
32
31
  file: "{{ name }}/Presenter/{{ name }}PresenterImplementation.swift"
33
32
  template: "presenter_implementation"
34
33
  variables: {}
35
- - name: "model_group"
36
- group: "{{ name }}/Model"
34
+ - name: "view_model"
35
+ file: "{{ name }}/Model/{{ name }}ViewModel.swift"
36
+ template: "view_model"
37
+ variables: {}
38
+ - name: "view_model_mapper"
39
+ file: "{{ name }}/Model/Mapper/{{ name }}ViewModelMapper.swift"
40
+ template: "view_model_mapper"
37
41
  variables: {}
38
42
  code_snippets:
39
43
  - name: DependencyProvider
@@ -45,5 +49,7 @@ template_file_source:
45
49
  view_contract: "view_contract.mustache"
46
50
  presenter: "presenter.mustache"
47
51
  presenter_implementation: "presenter_implementation.mustache"
52
+ view_model: "view_model.mustache"
53
+ view_model_mapper: "view_model_mapper.mustache"
48
54
  dependency_provider: "dependency_provider.mustache"
49
55
  presenter_assembly: "presenter_assembly.mustache"
@@ -8,6 +8,7 @@
8
8
 
9
9
  import Foundation
10
10
 
11
+ @MainActor
11
12
  protocol {{name}}ViewContract: AnyObject {
12
-
13
+ func configure(with viewModel: {{name}}ViewModel)
13
14
  }
@@ -12,6 +12,8 @@ import UIKit
12
12
  class {{name}}ViewController: SharedViewController, {{name}}ViewContract {
13
13
  var presenter: {{name}}Presenter?
14
14
 
15
+ // MARK: - Lifecycle
16
+
15
17
  override func viewDidLoad() {
16
18
  super.viewDidLoad()
17
19
  presenter?.start()
@@ -19,4 +21,6 @@ class {{name}}ViewController: SharedViewController, {{name}}ViewContract {
19
21
 
20
22
  // MARK: - {{name}}ViewContract
21
23
 
24
+ func configure(with viewModel: {{name}}ViewModel) {
25
+ }
22
26
  }
@@ -0,0 +1,12 @@
1
+ //
2
+ // {{name}}ViewModel.swift
3
+ // {{project_name}}
4
+ //
5
+ // Created by {{full_username}} on {{date}}.
6
+ //
7
+ //
8
+
9
+ import Foundation
10
+
11
+ struct {{name}}ViewModel {
12
+ }
@@ -0,0 +1,16 @@
1
+ //
2
+ // {{name}}ViewModelMapper.swift
3
+ // {{project_name}}
4
+ //
5
+ // Created by {{full_username}} on {{date}}.
6
+ //
7
+ //
8
+
9
+ import Foundation
10
+
11
+ struct {{name}}ViewModelMapper {
12
+
13
+ func map() -> {{name}}ViewModel {
14
+ {{name}}ViewModel()
15
+ }
16
+ }
@@ -5,7 +5,6 @@ parameters:
5
5
  removeSuffix: "Repository"
6
6
  variables:
7
7
  project: "*.xcodeproj"
8
- target: ""
9
8
  generated_elements:
10
9
  - name: "repository"
11
10
  file: "{{ name }}/{{ name }}Repository.swift"
@@ -0,0 +1,107 @@
1
+ require_relative 'pbxproj_parser'
2
+
3
+ # This object handles Xcode groups, folder reference and synchronized folder reference
4
+ class XcodeGroupRepresentation
5
+ def self.findGroup(path, project)
6
+ intermediates_groups = path.split("/")
7
+
8
+ if project.nil?
9
+ return self.new nil, nil, intermediates_groups
10
+ end
11
+
12
+ deepest_group = project.main_group
13
+ additional_path = []
14
+
15
+ intermediates_groups.each do |group_name|
16
+ if deepest_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup)
17
+ additional_path.append(group_name)
18
+ elsif deepest_group.is_a?(Xcodeproj::Project::Object::PBXGroup)
19
+ deepest_group = deepest_group.find_subpath(group_name)
20
+ return nil if deepest_group.nil?
21
+ else
22
+ raise "Unsupported element found with name \"#{group_name}\": #{deepest_group}"
23
+ end
24
+ end
25
+ self.new project, deepest_group, additional_path
26
+ end
27
+
28
+ def initialize(project, xcode_group, additional_path = [])
29
+ if project.nil?
30
+ throw "Unexpected xcode_group when project is nil, we should be in an filesystem context" unless xcode_group.nil?
31
+ else
32
+ throw "Unsupported group type" unless xcode_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup) || xcode_group.is_a?(Xcodeproj::Project::Object::PBXGroup)
33
+ if !additional_path.empty? && !xcode_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup)
34
+ throw "additional_path can only be specified for a synchronized file system group"
35
+ end
36
+ end
37
+ # This represent the xcode project if present, if nil we should be generating files in an Swift package
38
+ @project = project
39
+ # This represents the deepest group or folder reference in the project
40
+ @xcode_deepest_group = xcode_group
41
+ # This represents the additional filesystem path after `xcode_deepest_group` as an Array of strings. This should be non empty only when deepest group is a synchronized group
42
+ @additional_path = additional_path
43
+ end
44
+
45
+ def real_path
46
+ if @xcode_deepest_group.nil?
47
+ @additional_path.join("/")
48
+ else
49
+ Xcodeproj::Project::Object::GroupableHelper.real_path(@xcode_deepest_group) + @additional_path.join("/")
50
+ end
51
+ end
52
+
53
+
54
+ def register_file_to_targets(file_path, targets)
55
+ if @xcode_deepest_group.nil?
56
+ return
57
+ end
58
+ if @xcode_deepest_group.is_a?(Xcodeproj::Project::Object::PBXGroup)
59
+ file_ref = @xcode_deepest_group.new_reference(file_path)
60
+ targets.each do |target|
61
+ target.add_file_references([file_ref])
62
+ end
63
+ else
64
+ # no file to register, unless exceptions needs to be made
65
+ # TODO: Handle synchronized groups (no new reference, but use exceptions)
66
+ targets.each do |taget|
67
+ puts "Unsupported target mismatch between \"#{file_path}\" and synchronized group #{ @xcode_deepest_group.display_name }" unless taget.file_system_synchronized_groups.index(@xcode_deepest_group) != nil
68
+ end
69
+ end
70
+ end
71
+
72
+ # @return [XcodeGroupRepresentation] the created group or self if the intermediates_groups is empty
73
+ def create_groups_if_needed_for_intermediate_groups(intermediates_groups)
74
+ return self if intermediates_groups.empty?
75
+
76
+ new_deepest_group = @xcode_deepest_group
77
+ new_additional_path = @additional_path
78
+
79
+ intermediates_groups.each do |group_name|
80
+ if new_deepest_group.nil? || new_deepest_group.is_a?(Xcodeproj::Project::Object::PBXFileSystemSynchronizedRootGroup)
81
+ new_additional_path.append(group_name)
82
+ elsif new_deepest_group.is_a?(Xcodeproj::Project::Object::PBXGroup)
83
+ existing_child = new_deepest_group.find_subpath(group_name)
84
+ new_group_path = File.join(new_deepest_group.real_path, group_name)
85
+ new_deepest_group = existing_child || new_deepest_group.pf_new_group(
86
+ associate_path_to_group: !new_deepest_group.path.nil?,
87
+ name: group_name,
88
+ path: new_group_path
89
+ )
90
+ else
91
+ raise "Unsupported element found for \"#{group_name}\": #{new_deepest_group}"
92
+ end
93
+ end
94
+ XcodeGroupRepresentation.new @project, new_deepest_group, new_additional_path
95
+ end
96
+ end
97
+
98
+ # Private utility method
99
+
100
+ class Xcodeproj::Project::Object::PBXGroup
101
+ def pf_new_group(associate_path_to_group:, name:, path:)
102
+ new_group(
103
+ associate_path_to_group ? nil : name,
104
+ associate_path_to_group ? path : nil
105
+ )
106
+ end
107
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ccios
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pierre Felgines
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.4'
33
+ version: '1.27'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.4'
40
+ version: '1.27'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mustache
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '12.3'
61
+ version: '13.2'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '12.3'
68
+ version: '13.2'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +93,7 @@ files:
93
93
  - CHANGELOG.md
94
94
  - Gemfile
95
95
  - Gemfile.lock
96
+ - MAINTAINER.md
96
97
  - Makefile
97
98
  - README.md
98
99
  - Rakefile
@@ -122,11 +123,14 @@ files:
122
123
  - lib/ccios/templates/Presenter/template.yml
123
124
  - lib/ccios/templates/Presenter/view_contract.mustache
124
125
  - lib/ccios/templates/Presenter/view_controller.mustache
126
+ - lib/ccios/templates/Presenter/view_model.mustache
127
+ - lib/ccios/templates/Presenter/view_model_mapper.mustache
125
128
  - lib/ccios/templates/Repository/repository.mustache
126
129
  - lib/ccios/templates/Repository/repository_assembly.mustache
127
130
  - lib/ccios/templates/Repository/repository_implementation.mustache
128
131
  - lib/ccios/templates/Repository/template.yml
129
132
  - lib/ccios/templates_loader.rb
133
+ - lib/ccios/xcode_group_representation.rb
130
134
  - templates_library/async/Coordinator/coordinator.mustache
131
135
  - templates_library/async/Coordinator/template.yml
132
136
  - templates_library/async/Presenter/dependency_provider.mustache
@@ -156,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
160
  - !ruby/object:Gem::Version
157
161
  version: '0'
158
162
  requirements: []
159
- rubygems_version: 3.5.6
163
+ rubygems_version: 3.5.3
160
164
  signing_key:
161
165
  specification_version: 4
162
166
  summary: Clean Code iOS Generator