ruby-on-railsrubytemplatesthor

How to copy directory of *.tt files with thor?


Is it possible to copy a directory of *.tt files without executing template engine. I'd like to improve my rails templates and add custom scaffold templates (views, controllers and specs), but every time I use directory method

directory "lib/templates", force: true

I see an exception when thor tries to start template engine for those files. How to just copy my directory?


Solution

  • I think the simplest and safest option is to glob and copy_file as described ^ by @engineersmnky.

    But, there is a way to use directory and not execute templates. The only thing that stands in the way is this constant:

    TEMPLATE_EXTNAME = ".tt"
    

    Normally, you shouldn't change constants, but it was asking for it by not being frozen. What's the worst that can happentm:

    # test.thor
    
    class Test < Thor
      include Thor::Actions
      def self.source_root = __dir__
    
      desc "example", "task"
      def example
        ignore_tt do
          directory "templates", "copy"           # <= ok
        end
        # directory_with_tt "templates", "copy"   # <= ok
        # directory         "templates", "copy"   # <= fail
      end
    
      private
    
      def ignore_tt
        # NOTE: change template extension so it would skip
        #       `when /#{TEMPLATE_EXTNAME}$/` condition and
        #        fallback to default `copy_file`
        Thor::TEMPLATE_EXTNAME.concat "_no_match" # => .tt_no_match
        yield
      ensure
        # NOTE: make sure to undo our dirty work after the block
        Thor::TEMPLATE_EXTNAME.chomp! "_no_match" # => .tt
      end
    
      def directory_with_tt(...)
        ignore_tt do
          directory(...)
        end
      end
    end
    

    and a little test:

    $ cat templates/*.tt
    class <%= name.capitalize %>
    end
    
    $ thor test:example
          create  copy
          create  copy/file.tt
    
    $ cat copy/*.tt
    class <%= name.capitalize %>
    end
    

    If the above method is too hacky, here is another one, wasn't as simple as I thought, just had to get source_root and destination_root right.

    class Test < Thor
      include Thor::Actions
      def self.source_root = "#{__dir__}/templates"
    
      argument :name
    
      desc "example", "task"
      def example
        # copy directory, skip .tt
        directory ".", "copy", exclude_pattern: /\.tt$/
    
        # copy .tt
        Pathname.new(self.class.source_root).glob("**/*.tt").each do |tt|
          copy_file tt, File.join("copy", tt.relative_path_from(self.class.source_root))
        end
    
        # # this works too
        # self.destination_root = "copy"
        # Pathname.new(self.class.source_root).glob("**/*.tt").each do |tt|
        #   copy_file tt.relative_path_from(self.class.source_root)
        # end
      end
    end
    
    # %name% and .empty_directory are handled by `directory`
    # file.tt is copied with `copy_file`
    
    templates
    ├── %name%.rb
    ├── empty
    │  └── .empty_directory
    └── tt
       └── file.tt
    
    $ thor test:example app_name
          create  copy
          create  copy/app_name.rb
          create  copy/empty
          create  copy/tt/file.tt