Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite template system #23

Merged
merged 38 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
333bf61
Implement new templating system to provide more customization of temp…
alexandre-pod Feb 13, 2024
c55723e
Transform existing templates to new format
alexandre-pod Feb 13, 2024
f55e1ac
Allow configuration of which project is used through ccios config file
alexandre-pod Feb 6, 2024
04a15af
Use variables hierarchy for generated element base_path
alexandre-pod Feb 6, 2024
3f01270
Update config to support overrides of variables globally, for a templ…
alexandre-pod Feb 6, 2024
f7046ff
WIP support hierarchical variable management
alexandre-pod Feb 6, 2024
0e0db9c
Use variables mecanism to choose target to use for each file
alexandre-pod Feb 6, 2024
aa45481
Add support for custom collections
alexandre-pod Feb 6, 2024
44250ae
Fix name conflict in code_templater
alexandre-pod Feb 6, 2024
cd23ec9
Make file creator capable of adding file to multiple targets
alexandre-pod Feb 6, 2024
11ffa63
Update README.md
alexandre-pod Feb 13, 2024
4dfe6bb
Update Changelog
alexandre-pod Feb 13, 2024
9ff4452
Fix changelog
alexandre-pod Feb 20, 2024
9804664
Create TemplatesLoader
alexandre-pod Feb 20, 2024
e7464eb
Add missing require
alexandre-pod Feb 20, 2024
e2080ff
Add missing description field to TemplateDefinition
alexandre-pod Feb 20, 2024
0a9f16b
Add tests for templates_loader
alexandre-pod Feb 20, 2024
b10968a
Update tests for config
alexandre-pod Feb 20, 2024
8c224e8
Update test for CodeTemplater
alexandre-pod Feb 20, 2024
f7e81d3
Update test for PBXProjParser
alexandre-pod Feb 20, 2024
5e679fc
Update tests of generated files with or without groups
alexandre-pod Feb 20, 2024
2d49779
Fix template definition parser when no snippets are given
alexandre-pod Feb 20, 2024
ff27fc5
Add missing target definition to Coordinator template
alexandre-pod Feb 20, 2024
e49ff64
Fix snippets for default presenter template
alexandre-pod Feb 20, 2024
cf62cb7
Remove old comment
alexandre-pod Feb 20, 2024
0235946
Remove old test snapshots
alexandre-pod Feb 20, 2024
d9ef67e
Reimplement argument suffix removal
alexandre-pod Feb 20, 2024
b39cfda
Test auto suffix removal
alexandre-pod Feb 20, 2024
f548b17
Update changelog
alexandre-pod Feb 20, 2024
927edf8
Migrate sample async templates to new template system
alexandre-pod Feb 20, 2024
623e87d
Fix presenter snippet for dependency provider
alexandre-pod Feb 23, 2024
6ed096f
Add missing documentation
alexandre-pod Feb 23, 2024
1dce784
Use generic description and names in readme instead of values used in…
alexandre-pod Mar 5, 2024
83cf238
Fix typos in Readme and Changelog
alexandre-pod Mar 5, 2024
fe80bd2
Fix inconsistent naming in presenter template
alexandre-pod Mar 5, 2024
22bfc07
Add missing documentation for code_snippets
alexandre-pod Mar 5, 2024
ec0c2bf
Fix inconsistent naming in async presenter template
alexandre-pod Mar 5, 2024
117d4fc
Fix help message for templates with multiple argument parameters
alexandre-pod Mar 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 73 additions & 43 deletions lib/ccios.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,97 @@
require 'xcodeproj'
require 'optparse'
require 'active_support'
require 'ccios/presenter_generator'
require 'ccios/coordinator_generator'
require 'ccios/interactor_generator'
require 'ccios/repository_generator'
require 'ccios/config'
require 'ccios/template_definition'

config = Config.parse ".ccios.yml"
source_path = Dir.pwd

default_template_folder = File.join(File.dirname(__FILE__), "ccios", "templates")
default_templates = Dir.children(default_template_folder)
.map { |name| File.join(default_template_folder, name) }
.select { |path| File.directory? path }
.map { |template_path| TemplateDefinition.parse(template_path) }
.compact

templates = {}
subcommands = {}

options = {}
OptionParser.new do |opts|
opts.banner = "Usage: ccios [options]"

default_templates.each do |template|
templates[template.name] = template
subcommands[template.name] = OptionParser.new do |opts|

arguments = template.parameters.select { |p| p.is_a?(ArgumentTemplateParameter) }
flags = template.parameters.select { |p| p.is_a?(FlagTemplateParameter) }

arguments_line = arguments.map { |argument| "<#{argument.name}>" }.join(" ")
ThibaultFarnier marked this conversation as resolved.
Show resolved Hide resolved

opts.banner = "Usage: ccios #{template.name} <name> [options]"

flags.each do |flagParameter|
if flagParameter.short_name != nil
opts.on("-#{flagParameter.short_name}", "--#{flagParameter.name}", flagParameter.description) do |v|
options[flagParameter.template_variable_name] = v
end
else
opts.on("--#{flagParameter.name}", flagParameter.description) do |v|
options[flagParameter.template_variable_name] = v
end
end
end
end
end

global_cli = OptionParser.new do |opts|
opts.banner = "Usage: ccios <template> [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options[:verbose] = v
end
opts.on("-pName", "--presenter=Name", "Generate NamePresenter, NamePresenterImplementation, NameViewContract and NameViewController") do |name|
options[:presenter] = name
end
opts.on("-cName", "--coordinator=Name", "Generate NameCoordinator") do |name|
options[:coordinator] = name
end
opts.on("-iName", "--interactor=Name", "Generate NameInteractor and NameInteractorImplementation") do |name|
options[:interactor] = name
end
opts.on("-rName", "--repository=Name", "Generate NameRepository and NameRepositoryImplementation") do |name|
options[:repository] = name
end
opts.on("-d", "--delegate", "Add delegate for curent generation") do |add_delegate|
options[:generate_delegate] = add_delegate
end
opts.on("-h", "--help", "Print this help") do
puts opts
puts "Available templates: #{subcommands.keys.sort.join(", ")}"
exit
end
end.parse!
end

source_path = Dir.pwd
config = Config.parse ".ccios.yml"
parser = PBXProjParser.new(source_path, config)
global_cli.order!
template_name = ARGV.shift

if options[:presenter]
presenter_name = options[:presenter]
presenter_generator = PresenterGenerator.new(parser, config)
generator_options = {generate_delegate: options[:generate_delegate]}
presenter_generator.generate(presenter_name, generator_options)
if template_name.nil?
puts "Error: Missing template name"
puts global_cli
exit(1)
end

if options[:coordinator]
coordinator_name = options[:coordinator]
coordinator_generator = CoordinatorGenerator.new(parser, config)
generator_options = {generate_delegate: options[:generate_delegate]}
coordinator_generator.generate(coordinator_name, generator_options)
if subcommands[template_name].nil?
puts "Error: Unknown template \"#{template_name}\""
puts global_cli
exit(1)
end

if options[:interactor]
interactor_name = options[:interactor]
interactor_generator = InteractorGenerator.new(parser, config)
interactor_generator.generate(interactor_name)
template = templates[template_name]
expected_remaining_arguments = template.parameters.select { |p| p.is_a?(ArgumentTemplateParameter) }

subcommands[template_name].order! do |argument|
expected_argument = expected_remaining_arguments.shift
if expected_argument.nil?
puts "Unexpected argument: #{argument}"
exit 1
end
options[expected_argument.name] = argument
end

if options[:repository]
repository_name = options[:repository]
repository_generator = RepositoryGenerator.new(parser, config)
repository_generator.generate(repository_name)
if !expected_remaining_arguments.empty?
puts "Missing arguments: #{expected_remaining_arguments.map { |p| p.name }.join(", ")}"
exit 1
end

# TODO: use template.project
parser = PBXProjParser.new(source_path, config)

template.validate(parser, options)
template.generate(parser, options)

parser.save
12 changes: 12 additions & 0 deletions lib/ccios/argument_template_parameter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class ArgumentTemplateParameter
attr_reader :name, :description, :template_variable_name

def initialize(parameter_template_definition_hash)
@name = parameter_template_definition_hash["argument"]
@description = parameter_template_definition_hash["description"] || ""
@template_variable_name = parameter_template_definition_hash["template_variable_name"] || @name

raise "Missing argument name" if @name.nil? || @name.empty?
raise "Invalid argument template_variable_name for #{@name}" if @template_variable_name.nil? || template_variable_name.empty?
end
end
32 changes: 24 additions & 8 deletions lib/ccios/code_templater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@
require 'active_support/core_ext/string'

class CodeTemplater
def initialize(options = {}, templates_path)
@options = options
@templates_path = templates_path

def render_string(template, context)
Mustache.render(template, context)
end

def get_unknown_context_keys_for_string(template)
stringView = Mustache.new
stringView.template = template
tags = stringView.template.tags || []
tags = tags.map { |t| t.split(".")[-1] }.to_set
tags
end

def render_file_content_from_template(template_path, filename, context)
filename = File.basename(filename, File.extname(filename))
context = context.merge({name: filename, lowercased_name: filename.camelize(:lower)})
Mustache.render(File.read(template_path), context)
end

def content_for_suffix(prefix, suffix)
template_name = suffix.underscore
options = @options.merge({name: prefix, lowercased_name: prefix.camelize(:lower)})
template_file = File.join(@templates_path, "#{template_name}.mustache")
Mustache.render(File.read(template_file), options)
def get_unknown_context_keys_for_template(template_path)
templateView = Mustache.new
templateView.template_file = template_path
tags = (templateView.template.tags || [])
tags = tags.map { |t| t.split(".")[-1] }.to_set
tags.subtract(Set["name", "lowercased_name"])
tags
end
end
18 changes: 0 additions & 18 deletions lib/ccios/coordinator_generator.rb

This file was deleted.

41 changes: 20 additions & 21 deletions lib/ccios/file_creator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,42 +24,51 @@ def logger
FileCreator.logger
end

def initialize(options = {}, config)
@options = options
@config = config
end

def templater_options(target)
defaults = {
project_name: target.display_name,
full_username: git_username,
date: DateTime.now.strftime("%d/%m/%Y"),
}
defaults.merge(@options)
defaults
end

def git_username
`git config user.name`.strip
end

def create_file(prefix, suffix, group, target)
file_path = File.join(group.real_path, prefix + suffix + '.swift')
def get_unknown_template_tags_for(template_path)
tags = CodeTemplater.new.get_unknown_context_keys_for_template(template_path)
tags.subtract(Set["project_name", "full_username", "date"])
tags
end

def create_file_using_template_path(template_path, generated_filename, group, target, context)
file_path = File.join(group.real_path, generated_filename)

raise "File #{file_path} already exists" if File.exist?(file_path)
dirname = File.dirname(file_path)
FileUtils.mkdir_p dirname unless File.directory?(dirname)
file = File.new(file_path, 'w')

templater_options = templater_options(target)
code_templater = CodeTemplater.new(templater_options, @config.templates.path)
file_content = code_templater.content_for_suffix(prefix, suffix)
context = context.merge(templater_options(target))
file_content = CodeTemplater.new.render_file_content_from_template(template_path, generated_filename, context)

file.puts(file_content)

file.close
file_ref = group.new_reference(file_path)
target.add_file_references([file_ref])
end

def print_file_content_using_template(filename, template_path, context)
file_content = CodeTemplater.new.render_file_content_from_template(template_path, filename, context)
Mustache.render(File.read(template_path), context)

logger.info "Add this snippet to #{filename}"
logger.info file_content
end

def create_empty_directory(group)
dirname = group.real_path
FileUtils.mkdir_p dirname unless File.directory?(dirname)
Expand All @@ -68,16 +77,6 @@ def create_empty_directory(group)
FileUtils.touch(git_keep_path) if Dir.empty?(dirname)
end

def print_file_content(prefix, suffix)
file_name = suffix + '.swift'

code_templater = CodeTemplater.new(@options, @config.templates.path)
template = code_templater.content_for_suffix(prefix, suffix)

logger.info "Add this snippet to #{file_name}"
logger.info template
end

private

def self.create_logger
Expand Down
66 changes: 66 additions & 0 deletions lib/ccios/file_template_definition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require_relative 'code_templater'
require_relative 'file_creator'
require_relative 'pbxproj_parser'

class FileTemplateDefinition
def initialize(file_template_definition_hash)
@base_path = file_template_definition_hash["base_path"]
@path = file_template_definition_hash["file"]
@template = file_template_definition_hash["template"]
@target = file_template_definition_hash["target"]
end

def validate(parser, project, context, template_definition)
code_templater = CodeTemplater.new
expected_context_keys = template_definition.provided_context_keys
pathTags = code_templater.get_unknown_context_keys_for_string(@path)
pathTags.each do |tag|
raise "Unknown parameter \"#{tag}\" in path \"#{@path}\"" unless expected_context_keys.include?(tag)
end

file_path = code_templater.render_string(@path, context)
raise "File #{file_path} already exists" if File.exist?(file_path)

file_creator = FileCreator.new
contentTags = file_creator.get_unknown_template_tags_for(template_definition.template_source_file(@template))
contentTags.each do |tag|
raise "Unknown parameter \"#{tag}\" in template \"#{@template}\"" unless expected_context_keys.include?(tag)
end

base_group = project[@base_path]
raise "Base path \"#{@base_path}\" is missing" if base_group.nil?

target = parser.target_for(project, @target)
raise "Unable to find target \"#{@target}\"" if target.nil?
end

def generate(parser, project, context, template_definition)
base_group = project[@base_path]
file_path = CodeTemplater.new.render_string(@path, context)

intermediates_groups = file_path.split("/")[0...-1]
generated_filename = file_path.split("/")[-1]

group = base_group
associate_path_to_group = !base_group.path.nil?

intermediates_groups.each do |group_name|
new_group_path = File.join(group.real_path, group_name)
existing_group = group.groups.find { |g| g.display_name == group_name }
group = existing_group || group.pf_new_group(
associate_path_to_group: associate_path_to_group,
name: group_name,
path: new_group_path
)
end

target = parser.target_for(project, @target)
FileCreator.new.create_file_using_template_path(
template_definition.template_source_file(@template),
generated_filename,
group,
target,
context
)
end
end
13 changes: 13 additions & 0 deletions lib/ccios/flag_template_parameter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class FlagTemplateParameter
attr_reader :name, :short_name, :description, :template_variable_name

def initialize(parameter_template_definition_hash)
@name = parameter_template_definition_hash["flag"]
@short_name = parameter_template_definition_hash["short_name"]
@description = parameter_template_definition_hash["description"] || ""
@template_variable_name = parameter_template_definition_hash["template_variable_name"] || @name

raise "Missing flag name" if @name.nil? || @name.empty?
raise "Invalid flag template_variable_name for #{@name}" if @template_variable_name.nil? || template_variable_name.empty?
end
end
Loading