Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mkaput committed Feb 21, 2023
1 parent ba89e17 commit 22a30ed
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 6 deletions.
64 changes: 62 additions & 2 deletions scarb/src/bin/scarb/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ impl ScarbArgs {
pub enum Command {
// Keep these sorted alphabetically.
// External should go last.
/// Add dependencies to a manifest file.
Add,
/// Add dependencies to a Scarb.toml manifest file.
Add(AddArgs),
/// Compile current project.
Build,
/// Remove generated artifacts.
Expand Down Expand Up @@ -134,6 +134,66 @@ pub struct FmtArgs {
pub package: Option<PackageName>,
}

/// Arguments accepted by the `add` command.
#[derive(Parser, Clone, Debug)]
pub struct AddArgs {
/// Reference to a package to add as a dependency
///
/// You can reference a package by:
/// - `<name>`, like `scarb add quaireaux` (the latest version will be used)
/// - `<name>@<version-req>`, like `scarb add quaireaux@1` or `scarb add quaireaux@=0.1.0`
#[arg(value_name = "DEP_ID", verbatim_doc_comment)]
pub packages: Vec<String>,

/// Do not actually write the manifest.
#[arg(long)]
pub dry_run: bool,

/// Specify package to modify.
#[arg(short, long)]
pub package: Option<PackageName>,

/// _Source_ section.
#[command(flatten, next_help_heading = "Source")]
pub source: AddSourceArgs,
}

/// _Source_ section of [`AddArgs`].
#[derive(Parser, Clone, Debug)]
pub struct AddSourceArgs {
/// Filesystem path to local package to add.
#[arg(long, conflicts_with_all = ["git", "GitRefGroup"])]
pub path: Option<Utf8PathBuf>,

/// Git repository location
///
/// Without any other information, Scarb will use latest commit on the default branch.
#[arg(long, value_name = "URI")]
pub git: Option<String>,

/// Git reference args for `--git`.
#[command(flatten)]
pub git_ref: GitRefGroup,
}

/// Git reference specification arguments.
#[derive(Parser, Clone, Debug)]
pub struct GitRefGroup {
/// Git branch to download the package from.
#[arg(long, requires = "git", conflicts_with_all = ["tag", "rev"])]
pub branch: Option<String>,

/// Git tag to download the package from.
#[arg(long, requires = "git", conflicts_with_all = ["branch", "rev"])]
pub tag: Option<String>,

/// Git reference to download the package from
///
/// This is the catch-all, handling hashes to named references in remote repositories.
#[arg(long, requires = "git")]
pub rev: Option<String>,
}

#[cfg(test)]
mod tests {
use clap::CommandFactory;
Expand Down
27 changes: 24 additions & 3 deletions scarb/src/bin/scarb/commands/add.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
use anyhow::Result;
use anyhow::{anyhow, Result};

use scarb::core::Config;
use scarb::ops;

use crate::args::AddArgs;

#[tracing::instrument(skip_all, level = "info")]
pub fn run(_conf: &Config) -> Result<()> {
todo!()
pub fn run(args: AddArgs, config: &mut Config) -> Result<()> {
let ws = ops::read_workspace(config.manifest_path(), config)?;

// TODO(mkaput): Extract more generic pattern for this. See `Packages` in Cargo.
let package = match args.package {
Some(name) => ws
.members()
.find(|pkg| pkg.id.name == name)
.ok_or_else(|| anyhow!("package `{name}` not found in workspace `{ws}`"))?,
None => ws.current_package()?.clone(),
};

if !args.dry_run {
// Reload the workspace since we have changed dependencies
let ws = ops::read_workspace(config.manifest_path(), config)?;
let _ = ops::resolve_workspace(&ws)?;
}

Ok(())
}
2 changes: 1 addition & 1 deletion scarb/src/bin/scarb/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub fn run(command: Command, config: &mut Config) -> Result<()> {

match command {
// Keep these sorted alphabetically.
Add => add::run(config),
Add(args) => add::run(args, config),
Build => build::run(config),
Clean => clean::run(config),
Commands => commands::run(config),
Expand Down
1 change: 1 addition & 0 deletions scarb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod core;
pub mod dirs;
pub mod flock;
mod internal;
pub mod manifest_editor;
pub mod metadata;
pub mod ops;
mod process;
Expand Down
87 changes: 87 additions & 0 deletions scarb/src/manifest_editor/add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use anyhow::Result;
use camino::{Utf8Path, Utf8PathBuf};
use semver::VersionReq;
use toml_edit::Document;

use crate::core::{GitReference, PackageName};

use super::tomlx::get_table_mut;
use super::{Op, OpCtx};

pub struct AddDependency {
pub name: Option<PackageName>,
pub version: Option<VersionReq>,
pub path: Option<Utf8PathBuf>,
pub git: Option<String>,
pub branch: Option<String>,
pub tag: Option<String>,
pub rev: Option<String>,
}

struct Dep {
name: PackageName,
source: Source,
}

enum Source {
Registry(RegistrySource),
Path(PathSource),
Git(GitSource),
}

struct RegistrySource {
version: VersionReq,
}

struct PathSource {
version: Option<VersionReq>,
path: Utf8PathBuf,
}

struct GitSource {
version: Option<VersionReq>,
git: String,
reference: GitReference,
}

impl Op for AddDependency {
fn apply_to(self: Box<Self>, doc: &mut Document, ctx: OpCtx<'_>) -> Result<()> {
let table = get_table_mut(doc, &["dependencies"])?;

let dep_key = self.toml_key();

match table
.as_table_like_mut()
.unwrap()
.get_key_value_mut(dep_key)
{
Some((mut dep_key, dep_item)) => self.update_toml(&mut dep_key, dep_item),
None => {
table[dep_key] = self.to_toml();
}
}

if let Some(t) = table.as_inline_table_mut() {
t.fmt()
}

Ok(())
}
}

impl AddDependency {
// TODO(mkaput): With namespaced packages, this should produce a path.
fn toml_key(&self) -> &str {
self.name
.as_ref()
.expect("package name should be known at this point")
.as_str()
}
}

fn path_value(manifest_path: &Utf8Path, abs_path: &Utf8Path) -> String {
pathdiff::diff_utf8_paths(abs_path, manifest_path)
.expect("Both paths should be absolute at this point.")
.as_str()
.replace('\\', "/")
}
64 changes: 64 additions & 0 deletions scarb/src/manifest_editor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! `Scarb.toml` editor.

use std::str::FromStr;

use anyhow::{Context, Result};
use camino::Utf8Path;
use toml_edit::Document;

pub use add::AddDependency;

use crate::core::Config;
use crate::internal::fsx;

mod add;
mod tomlx;

pub trait Op {
fn apply_to(self: Box<Self>, doc: &mut Document, ctx: OpCtx<'_>) -> Result<()>;
}

pub struct OpCtx<'c> {
manifest_path: &'c Utf8Path,
opts: &'c EditManifestOptions<'c>,
}

pub struct EditManifestOptions<'c> {
pub config: &'c Config,
pub dry_run: bool,
}

/// Edit manifest file (for example, add dependencies).
#[tracing::instrument(level = "trace", skip(ops, opts))]
pub fn edit_manifest(
manifest_path: &Utf8Path,
ops: Vec<Box<dyn Op>>,
opts: EditManifestOptions<'_>,
) -> Result<()> {
let original_raw_manifest = fsx::read_to_string(manifest_path)?;
let mut doc = Document::from_str(&original_raw_manifest)
.with_context(|| format!("failed to read manifest at `{manifest_path}`"))?;

for op in ops {
op.apply_to(
&mut doc,
OpCtx {
manifest_path,
opts: &opts,
},
)?;
}

// TODO(mkaput): Sort dependencies and scripts etc.

let new_raw_manifest = doc.to_string();
if new_raw_manifest == original_raw_manifest {
opts.config.ui().warn("no changes have to be made");
} else if opts.dry_run {
opts.config.ui().warn("aborting due to dry run");
} else {
fsx::write(manifest_path, new_raw_manifest.as_bytes())?;
}

Ok(())
}
25 changes: 25 additions & 0 deletions scarb/src/manifest_editor/tomlx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use anyhow::{ensure, Result};
use toml_edit::{Document, Item, Table};

pub fn get_table_mut<'a>(doc: &'a mut Document, path: &[&str]) -> Result<&'a mut Item> {
return visit(doc.as_item_mut(), path);

fn visit<'a>(item: &'a mut Item, path: &[&str]) -> Result<&'a mut Item> {
if let Some(segment) = path.first() {
let item = item[segment].or_insert({
let mut table = Table::new();
table.set_implicit(true);
Item::Table(table)
});

ensure!(
item.is_table_like(),
"the table `{segment}` could not be found."
);
visit(item, &path[1..])
} else {
assert!(item.is_table_like());
Ok(item)
}
}
}

0 comments on commit 22a30ed

Please sign in to comment.