lib/pdk/module/templatedir.rb in pdk-akerl-1.9.1.1 vs lib/pdk/module/templatedir.rb in pdk-akerl-1.14.0.1

- old
+ new

@@ -1,25 +1,19 @@ -require 'yaml' -require 'deep_merge' -require 'pdk/util' -require 'pdk/util/git' -require 'pdk/cli/errors' -require 'pdk/template_file' - module PDK module Module class TemplateDir attr_accessor :module_metadata + attr_reader :uri # Initialises the TemplateDir object with the path or URL to the template # and the block of code to run to be run while the template is available. # # The template directory is only guaranteed to be available on disk # within the scope of the block passed to this method. # - # @param path_or_url [String] The path to a directory to use as the - # template or a URL to a git repository. + # @param uri [PDK::Util::TemplateURI] The path to a directory to use as the + # template or a URI to a git repository. # @param module_metadata [Hash] A Hash containing the module metadata. # Defaults to an empty Hash. # @yieldparam self [PDK::Module::TemplateDir] The initialised object with # the template available on disk. # @@ -35,21 +29,39 @@ # @raise [ArgumentError] If no block is given to this method. # @raise [PDK::CLI::FatalError] (see #clone_repo) # @raise [ArgumentError] (see #validate_module_template!) # # @api public - def initialize(path_or_url, module_metadata = {}, init = false) + def initialize(uri, module_metadata = {}, init = false) + require 'pdk/analytics' + require 'pdk/util/template_uri' + require 'pdk/util/git' + unless block_given? raise ArgumentError, _('%{class_name} must be initialized with a block.') % { class_name: self.class.name } end + unless uri.is_a? PDK::Util::TemplateURI + raise ArgumentError, _('PDK::Module::TemplateDir.new must be initialized with a PDK::Util::TemplateURI, got a %{uri_type}') % { uri_type: uri.class } + end - if PDK::Util::Git.repo?(path_or_url) - @path = self.class.clone_template_repo(path_or_url) - @repo = path_or_url + if PDK::Util::Git.repo?(uri.git_remote) + # This is either a bare local repo or a remote. either way it needs cloning. + @path = clone_template_repo(uri) + temp_dir_clone = true else - @path = path_or_url + # if it is a local path & non-bare repo then we can use it directly. + # Still have to check the branch. + @path = uri.shell_path + # We don't do a checkout of local-path repos. There are lots of edge + # cases or user un-expectations. + if PDK::Util::Git.work_tree?(@path) + PDK.logger.warn _("Repository '%{repo}' has a work-tree; skipping git reset.") % { + repo: @path, + } + end end + @uri = uri @init = init @moduleroot_dir = File.join(@path, 'moduleroot') @moduleroot_init = File.join(@path, 'moduleroot_init') @dirs = [@moduleroot_dir] @@ -58,15 +70,19 @@ validate_module_template! @module_metadata = module_metadata + template_type = uri.default? ? 'default' : 'custom' + PDK.analytics.event('TemplateDir', 'initialize', label: template_type) + yield self ensure # If we cloned a git repo to get the template, remove the clone once # we're done with it. - if @repo + if temp_dir_clone + require 'fileutils' FileUtils.remove_dir(@path) end end # Retrieve identifying metadata for the template. @@ -76,20 +92,17 @@ # # @return [Hash{String => String}] A hash of identifying metadata. # # @api public def metadata - result = { - 'pdk-version' => PDK::Util::Version.version_string, - } + require 'pdk/util/version' - result['template-url'] = @repo ? @repo : @path - - ref_result = PDK::Util::Git.git('--git-dir', File.join(@path, '.git'), 'describe', '--all', '--long', '--always') - result['template-ref'] = ref_result[:stdout].strip if ref_result[:exit_code].zero? - - result + { + 'pdk-version' => PDK::Util::Version.version_string, + 'template-url' => uri.metadata_format, + 'template-ref' => cache_template_ref(@path), + } end # Loop through the files in the template, yielding each rendered file to # the supplied block. # @@ -102,29 +115,36 @@ # # @return [void] # # @api public def render + require 'pdk/template_file' + PDK::Module::TemplateDir.files_in_template(@dirs).each do |template_file, template_loc| template_file = template_file.to_s PDK.logger.debug(_("Rendering '%{template}'...") % { template: template_file }) dest_path = template_file.sub(%r{\.erb\Z}, '') config = config_for(dest_path) - dest_status = :manage + dest_status = if template_loc.start_with?(@moduleroot_init) + :init + else + :manage + end + if config['unmanaged'] dest_status = :unmanage elsif config['delete'] dest_status = :delete else begin dest_content = PDK::TemplateFile.new(File.join(template_loc, template_file), configs: config, template_dir: self).render - rescue => e + rescue => error error_msg = _( "Failed to render template '%{template}'\n" \ '%{exception}: %{message}', - ) % { template: template_file, exception: e.class, message: e.message } + ) % { template: template_file, exception: error.class, message: error.message } raise PDK::CLI::FatalError, error_msg end end yield dest_path, dest_content, dest_status @@ -145,17 +165,19 @@ # # @api public def object_template_for(object_type) object_path = File.join(@object_dir, "#{object_type}.erb") type_path = File.join(@object_dir, "#{object_type}_type.erb") + device_path = File.join(@object_dir, "#{object_type}_device.erb") spec_path = File.join(@object_dir, "#{object_type}_spec.erb") type_spec_path = File.join(@object_dir, "#{object_type}_type_spec.erb") if File.file?(object_path) && File.readable?(object_path) result = { object: object_path } result[:type] = type_path if File.file?(type_path) && File.readable?(type_path) result[:spec] = spec_path if File.file?(spec_path) && File.readable?(spec_path) + result[:device] = device_path if File.file?(device_path) && File.readable?(device_path) result[:type_spec] = type_spec_path if File.file?(type_spec_path) && File.readable?(type_spec_path) result else nil end @@ -185,10 +207,12 @@ # # @api private def validate_module_template! # rubocop:disable Style/GuardClause unless File.directory?(@path) + require 'pdk/util' + if PDK::Util.package_install? && File.fnmatch?(File.join(PDK::Util.package_cachedir, '*'), @path) raise ArgumentError, _('The built-in template has substantially changed. Please run "pdk convert" on your module to continue.') else raise ArgumentError, _("The specified template '%{path}' is not a directory.") % { path: @path } end @@ -201,10 +225,11 @@ unless File.directory?(@moduleroot_init) # rubocop:disable Metrics/LineLength raise ArgumentError, _("The template at '%{path}' does not contain a 'moduleroot_init/' directory, which indicates you are using an older style of template. Before continuing please use the --template-url flag when running the pdk new commands to pass a new style template.") % { path: @path } # rubocop:enable Metrics/LineLength Style/GuardClause end + # rubocop:enable Style/GuardClause end # Get a list of template files in the template directory. # # @return [Hash{String=>String}] A hash of key file names and @@ -237,24 +262,42 @@ # @return [Hash] The data that will be available to the template via the # `@configs` instance variable. # # @api private def config_for(dest_path, sync_config_path = nil) + require 'pdk/util' + require 'pdk/analytics' + module_root = PDK::Util.module_root sync_config_path ||= File.join(module_root, '.sync.yml') unless module_root.nil? config_path = File.join(@path, 'config_defaults.yml') if @config.nil? + require 'deep_merge' conf_defaults = read_config(config_path) - sync_config = read_config(sync_config_path) unless sync_config_path.nil? + @sync_config = read_config(sync_config_path) unless sync_config_path.nil? @config = conf_defaults - @config.deep_merge!(sync_config, knockout_prefix: '---') unless sync_config.nil? + @config.deep_merge!(@sync_config, knockout_prefix: '---') unless @sync_config.nil? end file_config = @config.fetch(:global, {}) file_config['module_metadata'] = @module_metadata file_config.merge!(@config.fetch(dest_path, {})) unless dest_path.nil? - file_config.merge!(@config) + file_config.merge!(@config).tap do |c| + if uri.default? + file_value = if c['unmanaged'] + 'unmanaged' + elsif c['delete'] + 'deleted' + elsif @sync_config && @sync_config.key?(dest_path) + 'customized' + else + 'default' + end + + PDK.analytics.event('TemplateDir', 'file', label: dest_path, value: file_value) + end + end end # Generates a hash of data from a given yaml file location. # # @param loc [String] The path of the yaml config file. @@ -265,10 +308,12 @@ # @return [Hash] The data that has been read in from the given yaml file. # # @api private def read_config(loc) if File.file?(loc) && File.readable?(loc) + require 'yaml' + begin YAML.safe_load(File.read(loc), [], [], true) rescue Psych::SyntaxError => e PDK.logger.warn _("'%{file}' is not a valid YAML file: %{problem} %{context} at line %{line} column %{column}") % { file: loc, @@ -288,34 +333,57 @@ # # @raise [PDK::CLI::FatalError] If unable to clone the given origin_repo into a tempdir. # @raise [PDK::CLI::FatalError] If reset HEAD of the cloned repo to desired ref. # # @api private - def self.clone_template_repo(origin_repo) + def clone_template_repo(uri) # @todo When switching this over to using rugged, cache the cloned # template repo in `%AppData%` or `$XDG_CACHE_DIR` and update before # use. + require 'pdk/util' + require 'pdk/util/git' + temp_dir = PDK::Util.make_tmpdir_name('pdk-templates') - git_ref = (origin_repo == PDK::Util.default_template_url) ? PDK::Util.default_template_ref : 'origin/master' + origin_repo = uri.git_remote + git_ref = uri.git_ref clone_result = PDK::Util::Git.git('clone', origin_repo, temp_dir) if clone_result[:exit_code].zero? - Dir.chdir(temp_dir) do - reset_result = PDK::Util::Git.git('reset', '--hard', git_ref) - unless reset_result[:exit_code].zero? - PDK.logger.error reset_result[:stdout] - PDK.logger.error reset_result[:stderr] - raise PDK::CLI::FatalError, _("Unable to set HEAD of git repository at '%{repo}' to ref:'%{ref}'.") % { repo: temp_dir, ref: git_ref } - end - end + checkout_template_ref(temp_dir, git_ref) else PDK.logger.error clone_result[:stdout] PDK.logger.error clone_result[:stderr] raise PDK::CLI::FatalError, _("Unable to clone git repository at '%{repo}' into '%{dest}'.") % { repo: origin_repo, dest: temp_dir } end PDK::Util.canonical_path(temp_dir) + end + + # @api private + def checkout_template_ref(path, ref) + require 'pdk/util/git' + + if PDK::Util::Git.work_dir_clean?(path) + Dir.chdir(path) do + full_ref = PDK::Util::Git.ls_remote(path, ref) + cache_template_ref(path, full_ref) + reset_result = PDK::Util::Git.git('reset', '--hard', full_ref) + return if reset_result[:exit_code].zero? + + PDK.logger.error reset_result[:stdout] + PDK.logger.error reset_result[:stderr] + raise PDK::CLI::FatalError, _("Unable to checkout '%{ref}' of git repository at '%{path}'.") % { ref: ref, path: path } + end + else + PDK.logger.warn _("Uncommitted changes found when attempting to checkout '%{ref}' of git repository at '%{path}'; skipping git reset.") % { ref: ref, path: path } + end + end + + def cache_template_ref(path, ref = nil) + require 'pdk/util/git' + + @template_ref ||= PDK::Util::Git.describe(File.join(path, '.git'), ref) end end end end