Skip to content

Add remove and add commands #672

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft

Conversation

navid-m
Copy link

@navid-m navid-m commented May 8, 2025

As discussed, edits can be made to match exact requirements for checks.

@crysbot
Copy link

crysbot commented May 9, 2025

@straight-shoota straight-shoota marked this pull request as draft May 9, 2025 17:19
Copy link
Contributor

@ysbaddaden ysbaddaden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Praise: Thanks for starting something on this topic. It's pretty nice to keep the comments and overall the file format. Luckily yaml doesn't care much about indentation so that shouldn't break too much.

As expected, this is much more complex than it ought to be. I left a bunch of comments because shards supports much more than github, and yaml is a loose format.

Oh, and there should be specs, especially for all the edge cases.

@@ -23,6 +25,8 @@ module Shards
Commands:
build [<targets>] [<build_options>] - Build the specified <targets> in `bin` path, all build_options are delegated to `crystal build`.
check - Verify all dependencies are installed.
add [<url>] [<version>] - Add a shard to `shard.yml`, then install it.
rm [<shards>] - Remove a shard from `shard.yml`, then prune.
Copy link
Contributor

@ysbaddaden ysbaddaden May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The actual name is remove, and other dependency managers (Cargo, Bundler) just use that.

Suggested change
rm [<shards>] - Remove a shard from `shard.yml`, then prune.
remove [<shard> ...] - Remove named shard(s) from `shard.yml`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought: Automatically pruning will remove unrelated installed dependencies. Neither Cargo nor Bundler seem to uninstall anything (they only modify the dep file). We might want to regenerate the lockfile, though, which the prune command doesn't do (it even relies on the lockfile).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: this file ain't a command but a set of helpers. They must be put somewhere else.

@@ -23,6 +25,8 @@ module Shards
Commands:
build [<targets>] [<build_options>] - Build the specified <targets> in `bin` path, all build_options are delegated to `crystal build`.
check - Verify all dependencies are installed.
add [<url>] [<version>] - Add a shard to `shard.yml`, then install it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought: The add command must support all the resolvers supported by Shards, that is PATH, GIT, FOSSIL and MERCURIAL. We can detect the common hosts from the URL, and thus the resolver, but we need some explicit args for the rest. The remote URL can also be a local path:

  • <path> => assumes --path
  • <url> => assumes from known hosts, and fails otherwise;
  • --path <path>
  • --git <path_or_url>[@<version]
  • --fossil <path_or_url>[@<version]
  • --mercurial <path_or_url>[@<version] (and/or --hg?)

We should also be able to target the development dependencies:

  • --dev[elopment]

And maybe:

  • --branch <branch>
  • --tag <tag>
  • --commit <commit>

And fail if we try to use these with the PATH resolver.

end
end

File.write(spec_path, lines.join("\n"))
Copy link
Contributor

@ysbaddaden ysbaddaden May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: If the shard.yml uses [] or {} notations, this will generate invalid code. It's valid YAML and we never restricted their use. We don't have to support them, but we musn't generate an invalid YAML file. For example:

dependencies: []
dependencies:
  one: { git: "..." }
  two: { git: "..." }

Suggestion: follow @straight-shoota's idea to parse the original shard.yml and add the dependency to it, then parse the patched shard.yml and compare whether the dependencies match. That would valid that the file is a valid YAML file and a valid SPEC file, and that all the dependencies are correctly defined (we didn't break anything).

Log.info { "Added dependency #{dep[:name]} from #{dep[:provider]}: #{dep[:repo]}#{version ? " with version #{version}" : ""}" }
end

Commands::Lock.new(path).run([] of String)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to manually create the lockfile. The install command already resolves new shards.

Suggested change
Commands::Lock.new(path).run([] of String)

dep = Commands.git_url_to_dependency(url)
Log.info { "Adding dependency: #{dep[:name]} from #{dep[:provider]}: #{dep[:repo]}" }

lines = File.read_lines(spec_path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The command should focus on the logic (parse, add dependency, validate, file, invoke install), not go into the details of parsing and manipulating the YAML file. There should be a custom parser to parse and patch the YAML file (and specs to validate its behavior).

The manual parsing is interesting, but maybe we could follow @straight-shoota's idea to parse the file using libyaml to detect the dependencies mapping position (start/end position and indent) and of each dependency, using the official parser (more robust), then open the file and apply patches at the discovered positions. I feel like it would be end up being simpler.

We could even detect the usage of the [] or {} notations, and either fail early, or support some simple cases.

name: parts.last.gsub(".git", "").downcase,
repo: "#{parts[0]}/#{parts[1]}",
provider: provider
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: that should be a Shard::Dependency object.

File.write("shard.yml", lines.join("\n"))
end

def self.git_url_to_dependency(url : String) : NamedTuple(name: String, repo: String, provider: String)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: As stated above, Shards supports much more resolvers than Git. Each resolver should be in charge to transform an input String into a Shard::Dependency.

begin
File.read_lines("shard.yml")
rescue
Log.error{"No shard.yml was found in the current directory."}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: instead of failing, the add command could just invoke the init command.

module Shards
module Commands
class Remove < Command
def run(url : String)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything said above for the add command also applies to the remove command.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants