diff --git a/examples/completions/test.sh b/examples/completions/test.sh index 64d92ef8..2762dd07 100644 --- a/examples/completions/test.sh +++ b/examples/completions/test.sh @@ -2,7 +2,7 @@ set -x -bashly add comp function +bashly add comp function --force bashly generate ### Try Me ### diff --git a/lib/bashly.rb b/lib/bashly.rb index 3a4b343f..cb56c08f 100644 --- a/lib/bashly.rb +++ b/lib/bashly.rb @@ -11,4 +11,5 @@ requires 'bashly/exceptions' requires 'bashly/script/base' requires 'bashly/commands/base' +requires 'bashly/library/base' requires 'bashly' diff --git a/lib/bashly/commands/add.rb b/lib/bashly/commands/add.rb index 6b596dd3..2c6ccd29 100644 --- a/lib/bashly/commands/add.rb +++ b/lib/bashly/commands/add.rb @@ -9,7 +9,7 @@ class Add < Base usage "bashly add colors [--force]" usage "bashly add yaml [--force]" usage "bashly add validations [--force]" - usage "bashly add comp FORMAT [OUTPUT]" + usage "bashly add comp FORMAT [OUTPUT --force]" usage "bashly add (-h|--help)" option "-f --force", "Overwrite existing files" @@ -32,129 +32,81 @@ class Add < Base environment "BASHLY_SOURCE_DIR", "The path containing the bashly configuration and source files [default: src]" def strings_command - safe_copy asset("templates/strings.yml"), "#{Settings.source_dir}/bashly-strings.yml" + add_lib Library::Strings.new end def lib_command - safe_copy_file "sample_function.sh" + add_lib Library::Sample.new end def config_command - safe_copy_file "config.sh" + add_lib Library::Config.new end def colors_command - safe_copy_file "colors.sh" + add_lib Library::Colors.new end def yaml_command - safe_copy_file "yaml.sh" + add_lib Library::YAML.new end def validations_command - safe_copy_dir "validations" + add_lib Library::Validations.new end def comp_command format = args['FORMAT'] output = args['OUTPUT'] - + case format + when "script" + path = output || "#{Settings.target_dir}/completions.bash" + add_lib Library::CompletionsScript.new(path) + when "function" - save_comp_function output + function = output || "send_completions" + path = "#{Settings.source_dir}/lib/#{function}.sh" + add_lib Library::CompletionsFunction.new(path, function: function) + when "yaml" - save_comp_yaml output - when "script" - save_comp_script output + path = output || "#{Settings.target_dir}/completions.yml" + add_lib Library::CompletionsYAML.new(path) + else raise Error, "Unrecognized format: #{format}" + end end private - def safe_copy_dir(dir) - Dir[asset("templates/lib/#{dir}/*.sh")].sort.each do |file| - safe_copy_file "#{dir}/#{File.basename file}" + def add_lib(handler) + files_created = 0 + handler.files.each do |file| + created = safe_write file[:path], file[:content] + files_created += 1 if created end + message = handler.post_install_message + say "\n#{message}" if message and files_created > 0 end - def safe_copy_file(file) - safe_copy asset("templates/lib/#{file}"), "#{Settings.source_dir}/lib/#{file}" - end - - def safe_copy(source, target) + def safe_write(path, content) if !Dir.exist? Settings.source_dir raise InitError, "Directory !txtgrn!#{Settings.source_dir}!txtrst! does not exist\nRun !txtpur!bashly init!txtrst! first" end - if File.exist? target and !args['--force'] - say "skipped !txtgrn!#{target}!txtrst! (exists)" + if File.exist? path and !args['--force'] + say "skipped !txtgrn!#{path}!txtrst! (exists)" + false + else - deep_copy source, target - say "created !txtgrn!#{target}" - end - end - - def deep_copy(source, target) - target_dir = File.dirname target - FileUtils.mkdir_p target_dir unless Dir.exist? target_dir - FileUtils.cp source, target - end - - def config - @config ||= Config.new "#{Settings.source_dir}/bashly.yml" - end - - def command - @command ||= Script::Command.new config - end - - def completions - @completions ||= command.completion_data - end - - def completions_script - @completions_script ||= command.completion_script - end - - def completions_function - @completions_function ||= command.completion_function - end - - def save_comp_yaml(filename = nil) - filename ||= "#{Settings.target_dir}/completions.yml" - File.write filename, completions.to_yaml - say "created !txtgrn!#{filename}" - say "" - say "This file can be converted to a completions script using the !txtgrn!completely!txtrst! gem." - end - - def save_comp_script(filename = nil) - filename ||= "#{Settings.target_dir}/completions.bash" - File.write filename, completions_script - say "created !txtgrn!#{filename}" - say "" - say "In order to enable completions, run:" - say "" - say " !txtpur!$ source #{filename}" - end - - def save_comp_function(name = nil) - name ||= "send_completions" - target_dir = "#{Settings.source_dir}/lib" - filename = "#{target_dir}/#{name}.sh" + File.deep_write path, content + say "created !txtgrn!#{path}" + true - FileUtils.mkdir_p target_dir unless Dir.exist? target_dir - File.write filename, completions_function - - say "created !txtgrn!#{filename}" - say "" - say "In order to use it in your script, create a command or a flag (for example: !txtgrn!#{command.name} completions!txtrst! or !txtgrn!#{command.name} --completions!txtrst!) that calls the !txtgrn!#{name}!txtrst! function." - say "Your users can then run something like this to enable completions:" - say "" - say " !txtpur!$ eval \"$(#{command.name} completions)\"" + end end end diff --git a/lib/bashly/concerns/asset_helper.rb b/lib/bashly/concerns/asset_helper.rb index 28cb559b..f5ff4409 100644 --- a/lib/bashly/concerns/asset_helper.rb +++ b/lib/bashly/concerns/asset_helper.rb @@ -3,5 +3,9 @@ module AssetHelper def asset(path) File.expand_path "../#{path}", __dir__ end + + def asset_content(path) + File.read asset(path) + end end end \ No newline at end of file diff --git a/lib/bashly/extensions/file.rb b/lib/bashly/extensions/file.rb new file mode 100644 index 00000000..fa90dd64 --- /dev/null +++ b/lib/bashly/extensions/file.rb @@ -0,0 +1,9 @@ +require "fileutils" + +class File + def self.deep_write(file, content) + dir = File.dirname file + FileUtils.mkdir_p dir unless Dir.exist? dir + File.write file, content + end +end diff --git a/lib/bashly/library/base.rb b/lib/bashly/library/base.rb new file mode 100644 index 00000000..8a13c33a --- /dev/null +++ b/lib/bashly/library/base.rb @@ -0,0 +1,57 @@ +module Bashly + module Library + class Base + include AssetHelper + + attr_reader :target_path, :options + + def initialize(target_path = nil, options = nil) + @target_path = target_path || Settings.source_dir + @options = options || {} + end + + def files + case content + when String then content_from_string content + when Hash then [content] + else content + end + end + + def post_install_message + nil + end + + def content + raise NotImplementedError, "Please implement either #content" + end + + private + + def content_from_string(string) + if File.directory? asset("templates/lib/#{string}") + content_for_dir string + else + [content_for_file(string)] + end + end + + def content_for_file(file) + { + path: "#{target_path}/lib/#{file}", + content: asset_content("templates/lib/#{file}") + } + end + + def content_for_dir(dir) + Dir[asset("templates/lib/#{dir}/*.sh")].sort.map do |file| + { + path: "#{target_path}/lib/#{dir}/#{File.basename file}", + content: File.read(file) + } + end + end + + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/colors.rb b/lib/bashly/library/colors.rb new file mode 100644 index 00000000..6b26ff98 --- /dev/null +++ b/lib/bashly/library/colors.rb @@ -0,0 +1,9 @@ +module Bashly + module Library + class Colors < Base + def content + "colors.sh" + end + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/completions.rb b/lib/bashly/library/completions.rb new file mode 100644 index 00000000..5c989b9a --- /dev/null +++ b/lib/bashly/library/completions.rb @@ -0,0 +1,28 @@ +module Bashly + module Library + class Completions < Base + def content + { path: target_path, content: file_content } + end + + def file_content + raise NotImplementedError, "Please implement #file_content" + end + + protected + + def completions + @completions ||= command.completion_data + end + + def config + @config ||= Bashly::Config.new "#{Settings.source_dir}/bashly.yml" + end + + def command + @command ||= Script::Command.new config + end + + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/completions_function.rb b/lib/bashly/library/completions_function.rb new file mode 100644 index 00000000..57bdacf6 --- /dev/null +++ b/lib/bashly/library/completions_function.rb @@ -0,0 +1,23 @@ +module Bashly + module Library + class CompletionsFunction < Completions + def file_content + command.completion_function function_name + end + + def post_install_message + <<~EOF + In order to enable completions in your script, create a command or a flag (for example: !txtgrn!#{command.name} completions!txtrst! or !txtgrn!#{command.name} --completions!txtrst!) that calls the !txtgrn!#{function_name}!txtrst! function. + + Your users can then run something like this to enable completions: + + !txtpur!$ eval \"$(#{command.name} completions)\" + EOF + end + + def function_name + options[:function] + end + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/completions_script.rb b/lib/bashly/library/completions_script.rb new file mode 100644 index 00000000..07d608a8 --- /dev/null +++ b/lib/bashly/library/completions_script.rb @@ -0,0 +1,17 @@ +module Bashly + module Library + class CompletionsScript < Completions + def file_content + command.completion_script + end + + def post_install_message + <<~EOF + In order to enable completions, run: + + !txtpur!$ source #{target_path} + EOF + end + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/completions_yaml.rb b/lib/bashly/library/completions_yaml.rb new file mode 100644 index 00000000..adaddf36 --- /dev/null +++ b/lib/bashly/library/completions_yaml.rb @@ -0,0 +1,15 @@ +module Bashly + module Library + class CompletionsYAML < Completions + def file_content + completions.to_yaml + end + + def post_install_message + <<~EOF + This file can be converted to a completions script using the !txtgrn!completely!txtrst! gem. + EOF + end + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/config.rb b/lib/bashly/library/config.rb new file mode 100644 index 00000000..1bfe5c47 --- /dev/null +++ b/lib/bashly/library/config.rb @@ -0,0 +1,9 @@ +module Bashly + module Library + class Config < Base + def content + "config.sh" + end + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/sample.rb b/lib/bashly/library/sample.rb new file mode 100644 index 00000000..2dae8c34 --- /dev/null +++ b/lib/bashly/library/sample.rb @@ -0,0 +1,9 @@ +module Bashly + module Library + class Sample < Base + def content + "sample_function.sh" + end + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/strings.rb b/lib/bashly/library/strings.rb new file mode 100644 index 00000000..6e673fbd --- /dev/null +++ b/lib/bashly/library/strings.rb @@ -0,0 +1,12 @@ +module Bashly + module Library + class Strings < Base + def content + { + path: "#{target_path}/bashly-strings.yml", + content: asset_content("templates/strings.yml") + } + end + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/validations.rb b/lib/bashly/library/validations.rb new file mode 100644 index 00000000..4b370f95 --- /dev/null +++ b/lib/bashly/library/validations.rb @@ -0,0 +1,9 @@ +module Bashly + module Library + class Validations < Base + def content + "validations" + end + end + end +end \ No newline at end of file diff --git a/lib/bashly/library/yaml.rb b/lib/bashly/library/yaml.rb new file mode 100644 index 00000000..a978f140 --- /dev/null +++ b/lib/bashly/library/yaml.rb @@ -0,0 +1,9 @@ +module Bashly + module Library + class YAML < Base + def content + "yaml.sh" + end + end + end +end \ No newline at end of file diff --git a/spec/approvals/cli/add/comp-function b/spec/approvals/cli/add/comp-function index 7e034b8d..fb2ea773 100644 --- a/spec/approvals/cli/add/comp-function +++ b/spec/approvals/cli/add/comp-function @@ -1,6 +1,8 @@ created spec/tmp/src/lib/send_completions.sh -In order to use it in your script, create a command or a flag (for example: cli completions or cli --completions) that calls the send_completions function. +In order to enable completions in your script, create a command or a flag (for example: cli completions or cli --completions) that calls the send_completions function. + Your users can then run something like this to enable completions: $ eval "$(cli completions)" + diff --git a/spec/approvals/cli/add/comp-script b/spec/approvals/cli/add/comp-script index f539fd6e..e5d1b545 100644 --- a/spec/approvals/cli/add/comp-script +++ b/spec/approvals/cli/add/comp-script @@ -3,3 +3,4 @@ created spec/tmp/completions.bash In order to enable completions, run: $ source spec/tmp/completions.bash + diff --git a/spec/approvals/cli/add/comp-yaml b/spec/approvals/cli/add/comp-yaml index fbd35516..25930fcd 100644 --- a/spec/approvals/cli/add/comp-yaml +++ b/spec/approvals/cli/add/comp-yaml @@ -1,3 +1,4 @@ created spec/tmp/completions.yml This file can be converted to a completions script using the completely gem. + diff --git a/spec/approvals/cli/add/help b/spec/approvals/cli/add/help index e47669bf..c4bbed62 100644 --- a/spec/approvals/cli/add/help +++ b/spec/approvals/cli/add/help @@ -7,7 +7,7 @@ Usage: bashly add colors [--force] bashly add yaml [--force] bashly add validations [--force] - bashly add comp FORMAT [OUTPUT] + bashly add comp FORMAT [OUTPUT --force] bashly add (-h|--help) Commands: diff --git a/spec/approvals/cli/generate/usage b/spec/approvals/cli/generate/usage index 4cbabbf3..152f0f0e 100644 --- a/spec/approvals/cli/generate/usage +++ b/spec/approvals/cli/generate/usage @@ -5,5 +5,5 @@ Usage: bashly add colors [--force] bashly add yaml [--force] bashly add validations [--force] - bashly add comp FORMAT [OUTPUT] + bashly add comp FORMAT [OUTPUT --force] bashly add (-h|--help) diff --git a/spec/approvals/examples/completions b/spec/approvals/examples/completions index abea0118..97beb800 100644 --- a/spec/approvals/examples/completions +++ b/spec/approvals/examples/completions @@ -1,10 +1,12 @@ -+ bashly add comp function ++ bashly add comp function --force created src/lib/send_completions.sh -In order to use it in your script, create a command or a flag (for example: cli completions or cli --completions) that calls the send_completions function. +In order to enable completions in your script, create a command or a flag (for example: cli completions or cli --completions) that calls the send_completions function. + Your users can then run something like this to enable completions: $ eval "$(cli completions)" + + bashly generate creating user files in src skipped src/initialize.sh (exists) diff --git a/spec/approvals/library/base/dir b/spec/approvals/library/base/dir new file mode 100644 index 00000000..953e5490 --- /dev/null +++ b/spec/approvals/library/base/dir @@ -0,0 +1,21 @@ +--- +- :path: spec/tmp/src/lib/validations/validate_dir_exists.sh + :content: | + validate_dir_exists() { + [[ -d "$1" ]] || echo "must be an existing directory" + } +- :path: spec/tmp/src/lib/validations/validate_file_exists.sh + :content: | + validate_file_exists() { + [[ -f "$1" ]] || echo "must be an existing file" + } +- :path: spec/tmp/src/lib/validations/validate_integer.sh + :content: |- + validate_integer() { + [[ "$1" =~ ^[0-9]+$ ]] || echo "must be an integer" + } +- :path: spec/tmp/src/lib/validations/validate_not_empty.sh + :content: | + validate_not_empty() { + [[ -z "$1" ]] && echo "must not be empty" + } diff --git a/spec/bashly/concerns/asset_helper_spec.rb b/spec/bashly/concerns/asset_helper_spec.rb new file mode 100644 index 00000000..9677cf48 --- /dev/null +++ b/spec/bashly/concerns/asset_helper_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe AssetHelper do + subject { Class.new { include AssetHelper }.new } + let(:path) { "version.rb" } + + describe '#asset' do + it "returns a path to any file in the source tree" do + expect(subject.asset path).to end_with "lib/bashly/#{path}" + end + end + + describe '#asset_content' do + it "returns the contents of any file in the source tree" do + expect(subject.asset_content path).to eq File.read(subject.asset path) + end + end +end diff --git a/spec/bashly/extensions/file_spec.rb b/spec/bashly/extensions/file_spec.rb new file mode 100644 index 00000000..51fe2bde --- /dev/null +++ b/spec/bashly/extensions/file_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe File do + describe '::deep_write' do + it "creates parent directories and writes a file" do + expect(FileUtils).to receive(:mkdir_p).with('some/path') + expect(File).to receive(:write).with('some/path/file.txt', 'text') + File.deep_write 'some/path/file.txt', 'text' + end + end +end diff --git a/spec/bashly/library/base_spec.rb b/spec/bashly/library/base_spec.rb new file mode 100644 index 00000000..01488845 --- /dev/null +++ b/spec/bashly/library/base_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe Library::Base do + let(:lib_dir) { 'lib/bashly/templates/lib' } + + describe '#content' do + it "raises a NotImplementedError" do + expect { subject.content }.to raise_error(NotImplementedError) + end + end + + describe '#files' do + it "raises a NotImplementedError" do + expect { subject.files }.to raise_error(NotImplementedError) + end + + context "when #content is a string representing a lib/file" do + subject do + Class.new(described_class) { def content; "colors.sh"; end }.new + end + + it "returns an array of hashes" do + expect(subject.files).to be_an Array + expect(subject.files.first).to be_a Hash + end + + it "returns the content and target paths of the library files" do + matter = subject.files.first + expect(matter.keys).to eq [:path, :content] + expect(matter[:path]).to eq "#{Settings.target_dir}/src/lib/#{subject.content}" + expect(matter[:content]).to eq File.read("#{lib_dir}/#{subject.content}") + end + end + + context "when #content is a string representing a lib/dir" do + subject do + Class.new(described_class) { def content; "validations"; end }.new + end + + it "returns an array of hashes" do + expect(subject.files).to be_an Array + expect(subject.files.first).to be_a Hash + end + + it "returns an array as big as the library folder" do + expect(subject.files.count).to be > 1 + expect(subject.files.count).to eq Dir["#{lib_dir}/#{subject.content}/*.sh"].count + end + + it "returns the content and target paths of the library files" do + expect(subject.files.to_yaml).to match_approval 'library/base/dir' + end + end + + context "when #content is a hash" do + subject do + Class.new(described_class) { def content; { some: 'hash' }; end }.new + end + + it "returns an array with the hash as its first and only element" do + expect(subject.files).to be_an Array + expect(subject.files.count).to eq 1 + expect(subject.files.first).to eq subject.content + end + end + + context "when #content is something else" do + subject do + Class.new(described_class) { def content; ["something else"]; end }.new + end + + it "returns it as is" do + expect(subject.files).to eq subject.content + end + end + end + + describe '#post_install_message' do + it "returns nil" do + expect(subject.post_install_message).to be_nil + end + end +end diff --git a/spec/bashly/library/completions_spec.rb b/spec/bashly/library/completions_spec.rb new file mode 100644 index 00000000..a13cdff8 --- /dev/null +++ b/spec/bashly/library/completions_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe Library::Completions do + describe '#file_content' do + it "raises a NotImplementedError" do + expect { subject.content }.to raise_error(NotImplementedError) + end + end +end