From c0f529c70c30e3e502e14689e9c636dc2d867bcd Mon Sep 17 00:00:00 2001 From: Navid M Date: Thu, 8 May 2025 01:05:15 +0100 Subject: [PATCH 1/5] integrate add --- src/cli.cr | 11 +++++ src/commands/add.cr | 115 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 src/commands/add.cr diff --git a/src/cli.cr b/src/cli.cr index 0ace4160..cd661e78 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -3,6 +3,8 @@ require "./commands/*" module Shards BUILTIN_COMMANDS = %w[ + add + remove build run check @@ -23,6 +25,8 @@ module Shards Commands: build [] [] - Build the specified in `bin` path, all build_options are delegated to `crystal build`. check - Verify all dependencies are installed. + add [] [] - Add a shard to `shard.yml`, then prune and update + rm [] - Remove a shard from `shard.yml`, then prune and update init - Initialize a `shard.yml` file. install - Install dependencies, creating or using the `shard.lock` file. list [--tree] - List installed dependencies. @@ -90,6 +94,13 @@ module Shards Commands::Run.run(path, targets, run_options, options) when "check" Commands::Check.run(path) + when "add" + urls = args[1..-1] + version = nil + if urls.size > 1 && !urls.last.includes?("://") && !urls.last.includes?('@') + version = urls.pop + end + Commands::Add.new(path).run(urls, version) when "init" Commands::Init.run(path) when "install" diff --git a/src/commands/add.cr b/src/commands/add.cr new file mode 100644 index 00000000..929c1664 --- /dev/null +++ b/src/commands/add.cr @@ -0,0 +1,115 @@ +require "./command" + +module Shards + module Commands + class Add < Command + def run(urls : Array(String), version : String? = nil) + spec_path = File.join(path, SPEC_FILENAME) + raise Error.new("#{SPEC_FILENAME} not found") unless File.exists?(spec_path) + + urls.each do |url| + dep = git_url_to_dependency(url) + Log.info { "Adding dependency: #{dep[:name]} from #{dep[:provider]}: #{dep[:repo]}" } + + lines = File.read_lines(spec_path) + dependencies_index = -1 + dependencies_indentation = "" + + lines.each_with_index do |line, index| + if line =~ /^(\s*)dependencies\s*:/ + dependencies_index = index + dependencies_indentation = $1 + break + end + end + + if dependencies_index == -1 + lines << "" if lines.last != "" + lines << "dependencies:" + dependencies_index = lines.size - 1 + dependencies_indentation = "" + end + + dep_name = dep[:name] + dep_start_index = -1 + dep_end_index = -1 + + (dependencies_index + 1).upto(lines.size - 1) do |i| + break if i >= lines.size || lines[i] =~ /^\S/ && !lines[i].starts_with?("#") + if lines[i] =~ /^\s+#{Regex.escape(dep_name)}\s*:/ + dep_start_index = i + + j = i + 1 + while j < lines.size && (lines[j].empty? || lines[j].starts_with?("#") || lines[j] =~ /^\s+/) + if lines[j] =~ /^(\s+)/ && $1.size > lines[i].index(/\S/).not_nil! + dep_end_index = j + end + j += 1 + end + + break + end + end + + dep_indentation = "#{dependencies_indentation} " + prop_indentation = "#{dependencies_indentation} " + dep_lines = ["#{dep_indentation}#{dep_name}:"] + + dep_lines << "#{prop_indentation}#{dep[:provider]}: #{dep[:repo]}" + dep_lines << "#{prop_indentation}version: #{version}" if version + + if dep_start_index != -1 + lines.delete_at(dep_start_index..dep_end_index) + dep_lines.each_with_index do |line, idx| + lines.insert(dep_start_index + idx, line) + end + else + insert_index = dependencies_index + 1 + + while insert_index < lines.size && + (lines[insert_index].empty? || + lines[insert_index].starts_with?("#") || + lines[insert_index] =~ /^\s+/) + insert_index += 1 + end + + dep_lines.each_with_index do |line, idx| + lines.insert(insert_index + idx, line) + end + end + + File.write(spec_path, lines.join("\n")) + Log.info { "Added dependency #{dep[:name]} from #{dep[:provider]}: #{dep[:repo]}#{version ? " with version #{version}" : ""}." } + end + + Commands::Lock.new(path).run([] of String) + Commands::Install.new(path).run + end + + private HOSTS = { + "github.com" => "github", + "gitlab.com" => "gitlab", + "codeberg.org" => "codeberg" + } + + private def git_url_to_dependency(url : String) : NamedTuple(name: String, repo: String, provider: String) + uri = URI.parse(url) + provider = HOSTS[uri.host]? + + unless provider + raise Error.new("Unsupported git host: #{uri.host}") + end + + parts = uri.path.split("/").reject(&.empty?) + if parts.size < 2 + raise Error.new("Invalid git URL format") + end + + name = parts.last.gsub(".git", "").downcase + repo = "#{parts[0]}/#{parts[1]}" + + return {name: name, repo: repo, provider: provider} + end + end + end +end \ No newline at end of file From 867978d701117e69d96d8fe654562517bcea12de Mon Sep 17 00:00:00 2001 From: Navid M Date: Thu, 8 May 2025 01:33:05 +0100 Subject: [PATCH 2/5] remove --- src/cli.cr | 7 ++++ src/commands/add.cr | 27 +------------- src/commands/remove.cr | 84 ++++++++++++++++++++++++++++++++++++++++++ src/commands/url.cr | 43 +++++++++++++++++++++ 4 files changed, 136 insertions(+), 25 deletions(-) create mode 100644 src/commands/remove.cr create mode 100644 src/commands/url.cr diff --git a/src/cli.cr b/src/cli.cr index cd661e78..2a659a1a 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -101,6 +101,13 @@ module Shards version = urls.pop end Commands::Add.new(path).run(urls, version) + when "remove" + url = args[1] + if url.nil? + Log.error{"ERROR: missing dependency URL"} + exit 1 + end + Commands::Remove.new(path).run(url) when "init" Commands::Init.run(path) when "install" diff --git a/src/commands/add.cr b/src/commands/add.cr index 929c1664..3c8c75d2 100644 --- a/src/commands/add.cr +++ b/src/commands/add.cr @@ -1,4 +1,5 @@ require "./command" +require "./url" module Shards module Commands @@ -8,7 +9,7 @@ module Shards raise Error.new("#{SPEC_FILENAME} not found") unless File.exists?(spec_path) urls.each do |url| - dep = git_url_to_dependency(url) + dep = Commands.git_url_to_dependency(url) Log.info { "Adding dependency: #{dep[:name]} from #{dep[:provider]}: #{dep[:repo]}" } lines = File.read_lines(spec_path) @@ -86,30 +87,6 @@ module Shards Commands::Install.new(path).run end - private HOSTS = { - "github.com" => "github", - "gitlab.com" => "gitlab", - "codeberg.org" => "codeberg" - } - - private def git_url_to_dependency(url : String) : NamedTuple(name: String, repo: String, provider: String) - uri = URI.parse(url) - provider = HOSTS[uri.host]? - - unless provider - raise Error.new("Unsupported git host: #{uri.host}") - end - - parts = uri.path.split("/").reject(&.empty?) - if parts.size < 2 - raise Error.new("Invalid git URL format") - end - - name = parts.last.gsub(".git", "").downcase - repo = "#{parts[0]}/#{parts[1]}" - - return {name: name, repo: repo, provider: provider} - end end end end \ No newline at end of file diff --git a/src/commands/remove.cr b/src/commands/remove.cr new file mode 100644 index 00000000..3233f051 --- /dev/null +++ b/src/commands/remove.cr @@ -0,0 +1,84 @@ +require "./command" +require "./url" + +module Shards + module Commands + class Remove < Command + def run(url : String) + dep = Commands.git_url_to_dependency(url) + Log.info{"Removing dependency: #{dep[:name]}..."} + + lines = Commands.read_shard_yml + dependencies_index = -1 + + lines.each_with_index do |line, index| + if line =~ /^(\s*)dependencies\s*:/ + dependencies_index = index + break + end + end + + if dependencies_index == -1 + Log.warn{"Dependency: #{dep[:name]} not found, nothing to remove."} + return + end + + dep_name = dep[:name] + dep_start_index = -1 + dep_end_index = -1 + dep_indentation = nil + + (dependencies_index + 1).upto(lines.size - 1) do |i| + break if i >= lines.size || (lines[i] =~ /^\S/ && !lines[i].starts_with?("#")) + + if lines[i] =~ /^(\s+)#{Regex.escape(dep_name)}\s*:/ + dep_start_index = i + dep_indentation = $1.size + + j = i + 1 + while j < lines.size + if !lines[j].empty? && !lines[j].starts_with?("#") && lines[j] =~ /^(\s*)\S/ + current_indent = $1.size + if current_indent <= dep_indentation + break + end + dep_end_index = j + end + j += 1 + end + + break + end + end + + if dep_start_index != -1 + if dep_end_index != -1 + lines.delete_at(dep_start_index..dep_end_index) + else + lines.delete_at(dep_start_index) + end + + has_other_deps = false + (dependencies_index + 1).upto(lines.size - 1) do |i| + break if i >= lines.size || (lines[i] =~ /^\S/ && !lines[i].starts_with?("#")) + if lines[i] =~ /^\s+\S+\s*:/ + has_other_deps = true + break + end + end + + if !has_other_deps + lines.delete_at(dependencies_index) + end + + Commands.write_shard_yml(lines) + Commands::Prune.new(path).run + + Log.info{"Removed dependency #{dep[:name]}."} + else + Log.warn{"Dependency: #{dep[:name]} not found, nothing to remove."} + end + end + end + end +end \ No newline at end of file diff --git a/src/commands/url.cr b/src/commands/url.cr new file mode 100644 index 00000000..ccb99b47 --- /dev/null +++ b/src/commands/url.cr @@ -0,0 +1,43 @@ +module Shards + module Commands + def self.read_shard_yml : Array(String) + begin + File.read_lines("shard.yml") + rescue + Log.error{"No shard.yml was found in the current directory."} + exit 1 + end + end + + def self.write_shard_yml(lines : Array(String)) + File.write("shard.yml", lines.join("\n")) + end + + def self.git_url_to_dependency(url : String) : NamedTuple(name: String, repo: String, provider: String) + hosts = { + "github.com" => "github", + "gitlab.com" => "gitlab", + "codeberg.org" => "codeberg" + } + + uri = URI.parse(url) + provider = hosts[uri.host]? + + parts = uri.path.split("/").reject(&.empty?) + if parts.size < 2 + raise Error.new("Invalid git URL format") + end + + name = parts.last.gsub(".git", "").downcase + repo = "#{parts[0]}/#{parts[1]}" + if !provider + provider = "github" + end + return { + name: name, + repo: repo, + provider: provider + } + end + end +end From e8efe4551f3a354b9003529fbea83155a9780186 Mon Sep 17 00:00:00 2001 From: Navid M Date: Thu, 8 May 2025 01:35:32 +0100 Subject: [PATCH 3/5] rename --- src/commands/add.cr | 2 +- src/commands/{url.cr => io.cr} | 0 src/commands/remove.cr | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/commands/{url.cr => io.cr} (100%) diff --git a/src/commands/add.cr b/src/commands/add.cr index 3c8c75d2..f541d264 100644 --- a/src/commands/add.cr +++ b/src/commands/add.cr @@ -1,5 +1,5 @@ require "./command" -require "./url" +require "./io" module Shards module Commands diff --git a/src/commands/url.cr b/src/commands/io.cr similarity index 100% rename from src/commands/url.cr rename to src/commands/io.cr diff --git a/src/commands/remove.cr b/src/commands/remove.cr index 3233f051..bd01f82b 100644 --- a/src/commands/remove.cr +++ b/src/commands/remove.cr @@ -1,5 +1,5 @@ require "./command" -require "./url" +require "./io" module Shards module Commands From 2df547a15e11fe32b7c133da80f943ace85fac60 Mon Sep 17 00:00:00 2001 From: Navid M Date: Thu, 8 May 2025 01:45:48 +0100 Subject: [PATCH 4/5] fmt --- src/cli.cr | 4 ++-- src/commands/io.cr | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/cli.cr b/src/cli.cr index 2a659a1a..b0af6bac 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -25,8 +25,8 @@ module Shards Commands: build [] [] - Build the specified in `bin` path, all build_options are delegated to `crystal build`. check - Verify all dependencies are installed. - add [] [] - Add a shard to `shard.yml`, then prune and update - rm [] - Remove a shard from `shard.yml`, then prune and update + add [] [] - Add a shard to `shard.yml`, then install it. + rm [] - Remove a shard from `shard.yml`, then prune. init - Initialize a `shard.yml` file. install - Install dependencies, creating or using the `shard.lock` file. list [--tree] - List installed dependencies. diff --git a/src/commands/io.cr b/src/commands/io.cr index ccb99b47..af510bd6 100644 --- a/src/commands/io.cr +++ b/src/commands/io.cr @@ -22,20 +22,18 @@ module Shards uri = URI.parse(url) provider = hosts[uri.host]? - - parts = uri.path.split("/").reject(&.empty?) + parts = uri.path.split("/").reject(&.empty?) + if parts.size < 2 raise Error.new("Invalid git URL format") end - - name = parts.last.gsub(".git", "").downcase - repo = "#{parts[0]}/#{parts[1]}" if !provider provider = "github" end + return { - name: name, - repo: repo, + name: parts.last.gsub(".git", "").downcase, + repo: "#{parts[0]}/#{parts[1]}", provider: provider } end From 14bbaaa15d1c6c5c50f4c19abb889fd6bc152c58 Mon Sep 17 00:00:00 2001 From: Navid M Date: Thu, 8 May 2025 01:53:25 +0100 Subject: [PATCH 5/5] fmt --- src/cli.cr | 4 ++-- src/commands/add.cr | 2 +- src/commands/remove.cr | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cli.cr b/src/cli.cr index b0af6bac..22807769 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -4,7 +4,7 @@ require "./commands/*" module Shards BUILTIN_COMMANDS = %w[ add - remove + rm build run check @@ -101,7 +101,7 @@ module Shards version = urls.pop end Commands::Add.new(path).run(urls, version) - when "remove" + when "rm" url = args[1] if url.nil? Log.error{"ERROR: missing dependency URL"} diff --git a/src/commands/add.cr b/src/commands/add.cr index f541d264..2628729b 100644 --- a/src/commands/add.cr +++ b/src/commands/add.cr @@ -80,7 +80,7 @@ module Shards end File.write(spec_path, lines.join("\n")) - Log.info { "Added dependency #{dep[:name]} from #{dep[:provider]}: #{dep[:repo]}#{version ? " with version #{version}" : ""}." } + Log.info { "Added dependency #{dep[:name]} from #{dep[:provider]}: #{dep[:repo]}#{version ? " with version #{version}" : ""}" } end Commands::Lock.new(path).run([] of String) diff --git a/src/commands/remove.cr b/src/commands/remove.cr index bd01f82b..7a7fb4ba 100644 --- a/src/commands/remove.cr +++ b/src/commands/remove.cr @@ -6,7 +6,7 @@ module Shards class Remove < Command def run(url : String) dep = Commands.git_url_to_dependency(url) - Log.info{"Removing dependency: #{dep[:name]}..."} + Log.info{"Removing dependency: #{dep[:name]}"} lines = Commands.read_shard_yml dependencies_index = -1 @@ -19,7 +19,7 @@ module Shards end if dependencies_index == -1 - Log.warn{"Dependency: #{dep[:name]} not found, nothing to remove."} + Log.warn{"Dependency #{dep[:name]} not found, nothing to remove."} return end