diff --git a/shard.yml b/shard.yml index 589d303..c5230c5 100644 --- a/shard.yml +++ b/shard.yml @@ -1,15 +1,15 @@ name: teeplate -version: 0.4.5 +version: 0.5.0 authors: - mosop -crystal: 0.21.1 +crystal: 0.24.1 development_dependencies: have_files: - github: mosop/have_files - version: ~> 0.3.3 + github: amberframework/have_files + version: ~> 0.4.0 stdio: github: mosop/stdio version: ~> 0.1.2 diff --git a/src/lib.cr b/src/lib.cr index 5403244..8a8a753 100644 --- a/src/lib.cr +++ b/src/lib.cr @@ -2,6 +2,6 @@ require "base64" require "colorize" require "ecr/macros" require "process" -require "./version" +require "./teeplate/*" require "./lib/as_data_entry" require "./lib/*" diff --git a/src/lib/file_entry_collector.cr b/src/lib/file_entry_collector.cr index 9d59a72..478aefc 100644 --- a/src/lib/file_entry_collector.cr +++ b/src/lib/file_entry_collector.cr @@ -8,10 +8,8 @@ module Teeplate def each_file(abs, rel, &block : String, String ->) Dir.open(abs) do |d| - d.each do |entry| - if entry != "." && entry != ".." - each_file abs, rel, entry, &block - end + d.each_child do |entry| + each_file abs, rel, entry, &block end end end @@ -27,6 +25,7 @@ module Teeplate end @entries : Array(AsDataEntry)? + def entries @entries ||= begin a = [] of AsDataEntry diff --git a/src/lib/file_tree.cr b/src/lib/file_tree.cr index a4bf0c7..7930ba9 100644 --- a/src/lib/file_tree.cr +++ b/src/lib/file_tree.cr @@ -1,6 +1,10 @@ module Teeplate # Collects template files from a local directory. abstract class FileTree + + # Array of paths to skip when performing destroy + @skip_on_destroy = [] of String + # Collects and embeds template files. # # It runs another macro process that collects template files and embeds the files as code. @@ -8,6 +12,11 @@ module Teeplate {{ run(__DIR__ + "/file_tree/macros/directory", dir.id) }} end + # Skip directory/file at the given path. The path is relative to the out_dir. + def skip_on_destroy(path) + @skip_on_destroy.push(path) + end + @file_entries : Array(AsDataEntry)? # Returns collected file entries. def file_entries : Array(AsDataEntry) @@ -28,6 +37,14 @@ module Teeplate renderer end + # Destroy the rendered files. + def destroy(out_dir, force : Bool = false, interactive : Bool = false, interact : Bool = false, list : Bool = false, color : Bool = false, per_entry : Bool = false, quit : Bool = true) + renderer = Renderer.new(out_dir, force: force, interact: interactive || interact, list: list, color: color, per_entry: per_entry, quit: quit) + renderer << destroy_file_entries + renderer.destroy(@skip_on_destroy) + renderer + end + # Returns file entries to be rendered. # # This method just returns the `#file_entries` method's result. To filter entries, override this method. @@ -35,6 +52,11 @@ module Teeplate file_entries end + # :nodoc: + def destroy_file_entries + file_entries + end + # :nodoc: def ____collect_files(files) end diff --git a/src/lib/file_tree/macros/directory.cr b/src/lib/file_tree/macros/directory.cr index 8991867..5c56640 100644 --- a/src/lib/file_tree/macros/directory.cr +++ b/src/lib/file_tree/macros/directory.cr @@ -2,10 +2,8 @@ require "base64" def each_file(abs, rel, &block : String, String ->) Dir.open(abs) do |d| - d.each do |entry| - if entry != "." && entry != ".." - each_file abs, rel, entry, &block - end + d.each_child do |entry| + each_file abs, rel, entry, &block end end end @@ -39,7 +37,7 @@ end def pack_blob(sb, abs, rel) STDOUT << "\n____files << ::Teeplate::Base64Data.new(\"#{rel}\", " io = IO::Memory.new - File.open(abs){|f| IO.copy(f, io)} + File.open(abs) { |f| IO.copy(f, io) } if io.size > 0 STDOUT << "#{io.size}_u64, <<-EOS\n" Base64.encode io, STDOUT diff --git a/src/lib/renderer.cr b/src/lib/renderer.cr index d137385..218fc6d 100644 --- a/src/lib/renderer.cr +++ b/src/lib/renderer.cr @@ -41,6 +41,9 @@ module Teeplate # :nodoc: getter data_entries = [] of AsDataEntry + # :nodoc: + getter? pending_destroy = false + # :nodoc: getter entries = [] of RenderingEntry @@ -97,5 +100,87 @@ module Teeplate @quitted = true end end + + # Destroy templates. + # + # If passing paths as skip, these paths will be skipped in the destroy process, and thus will remain on the + # file system + def destroy(skip : Array(String)?) + @pending_destroy = true + begin + if @interactive + @entries.each do |entry| + entry.destroy(should_destroy?(entry)) + end + elsif should_destroy_all?(@entries) + @entries.each do |entry| + entry.destroy(should_skip_on_destroy?(entry, skip)) + end + end + rescue ex : Quit + @quitted = true + end + end + + # Confirm whether the user wants to destroy a singe file. + def should_destroy?(entry : RenderingEntry) + STDOUT.puts "Destroy #{entry.out_path}? (y/n)" + + loop do + case input = ::STDIN.gets.to_s.strip.downcase + when "y" + return true + when "n" + return false + end + end + end + + # Confirm whether or not the user wishes to destroy multiple files. + def should_destroy_all?(entries : Array(RenderingEntry)) + return should_destroy?(entries.first) if entries.size == 1 + + STDOUT.puts "Destroy all the following files? (y/n)" + + entries.each do |entry| + STDOUT.puts entry.out_path + end + + loop do + case input = ::STDIN.gets.to_s.strip.downcase + when "y" + return true + when "n" + return false + end + end + end + + # Determine whether a file should be skipped upon performing #destroy, based on the + # provided array of paths to skip. + def should_skip_on_destroy?(file : RenderingEntry, skip : Array(String)?) : Bool + skip_file = false + + skip_path_parts : Array(String)? + entry_path_parts : Array(String)? + entry_path_parts = file.out_path.split("/") + + skip.each do |skip_path| + skip_path_parts = skip_path.split("/") + + skip_path_parts.unshift(entry_path_parts.first) + skip_path_parts.each_with_index do |part, i| + if i + 1 > entry_path_parts.size + skip_file = false + break + end + skip_file = part.downcase == entry_path_parts[i].downcase + end + + break if skip_file + end + + skip_file + end end end diff --git a/src/lib/rendering_entry.cr b/src/lib/rendering_entry.cr index a569ccc..63d2827 100644 --- a/src/lib/rendering_entry.cr +++ b/src/lib/rendering_entry.cr @@ -91,6 +91,20 @@ module Teeplate end end + # :nodoc: + def destroy(skip? = false) + unless skip? + begin + File.delete out_path + list_if_any "destroyed ", :red + rescue + list_if_any "skipped ", :yellow + end + else + list_if_any "skipped ", :yellow + end + end + # :nodoc: def set_perm if perm = @data.perm? && File.file?(out_path) @@ -125,6 +139,7 @@ module Teeplate return :keep if !@renderer.interactive? || @renderer.keeps_all? return modifies?("#{local_path} is a symlink...", diff: false) if File.symlink?(out_path) return :modify if appends? + return :destroy if @renderer.pending_destroy? return :none if identical? modifies?("#{local_path} already exists...", diff: true) end @@ -200,9 +215,9 @@ module Teeplate end begin if !GIT.empty? - Process.new(GIT, ["diff", "--no-index", "--", out_path, "-"], shell: true, input: r, output: true, error: true).wait + Process.new(GIT, ["diff", "--no-index", "--", out_path, "-"], shell: true, input: r, output: Process::Redirect::Inherit, error: Process::Redirect::Inherit).wait elsif !DIFF.empty? - Process.new(DIFF, ["-u", out_path, "-"], shell: true, input: r, output: true, error: true).wait + Process.new(DIFF, ["-u", out_path, "-"], shell: true, input: r, output: Process::Redirect::Inherit, error: Process::Redirect::Inherit).wait else STDOUT.puts "No diff command is installed." end diff --git a/src/version.cr b/src/teeplate/version.cr similarity index 50% rename from src/version.cr rename to src/teeplate/version.cr index 199502e..1143ece 100644 --- a/src/version.cr +++ b/src/teeplate/version.cr @@ -1,3 +1,3 @@ module Teeplate - VERSION = "0.4.5" + VERSION = "0.5.0" end