Skip to content

Commit

Permalink
feat: add (#10)
Browse files Browse the repository at this point in the history
* feat: add

* lints
  • Loading branch information
jondot authored Nov 11, 2022
1 parent 3c4c19f commit 4d3a7b2
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 149 deletions.
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backpack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interactive-actions = "1"
tera = "^1.17.0"
tera-text-filters = "^1.0.0"
content_inspector = "0.2.4"
edit = "*"

[dev-dependencies]
insta = { version = "1.17.1", features = ["backtrace", "redactions"] }
Expand Down
2 changes: 2 additions & 0 deletions backpack/src/bin/bp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ fn main() {
.subcommand(commands::new::command())
.subcommand(commands::apply::command())
.subcommand(commands::cache::command())
.subcommand(commands::add::command())
.subcommand(commands::config::command());

let matches = app.clone().get_matches();
Expand All @@ -27,6 +28,7 @@ fn main() {
("new", subcommand_matches) => commands::new::run(&matches, subcommand_matches),
("apply", subcommand_matches) => commands::apply::run(&matches, subcommand_matches),
("cache", subcommand_matches) => commands::cache::run(&matches, subcommand_matches),
("add", subcommand_matches) => commands::add::run(&matches, subcommand_matches),
("config", subcommand_matches) => commands::config::run(&matches, subcommand_matches),
_ => unreachable!(),
},
Expand Down
75 changes: 75 additions & 0 deletions backpack/src/bin/commands/add.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use anyhow::Context;
use anyhow::Result as AnyResult;
use backpack::config::Project;
use backpack::data::CopyMode;
use backpack::git::GitCmd;
use backpack::git::GitProvider;
use backpack::{config::Config, ui::Prompt};
use clap::{Arg, ArgMatches, Command};

pub fn command() -> Command<'static> {
Command::new("add")
.about("Add a repo as a project")
.arg(
Arg::new("git")
.short('g')
.long("git")
.help("prefer a git url")
.takes_value(false),
)
.arg(Arg::new("repo"))
}

pub fn run(_matches: &ArgMatches, subcommand_matches: &ArgMatches) -> AnyResult<bool> {
// get a repo
// arg given -> from arg
// arg not given -> git provider, cmd to extract current remote
// parse it to see that its valid
// initialize a Location, and then resolve it per usual.
// next,
// -> canonicalize to https and git, and ask which one
// if exists in config, say it and skip.
//
// show current projects,
// ask what to call it (populate with init-name)
// with selected, get the current config, load it, mutate, and store
// done

// XXX fix this to fallback onto git, and then bail if none there too
let repo = subcommand_matches
.get_one::<String>("repo")
.map_or_else(|| GitCmd::default().get_local_url(), |r| Ok(r.to_string()))?;

// build Location
// git preference
// if user flag -> true
// otherwise web

// load configuration
// show all projects
// ask how to call the new one
// store to config
// ask if to open
// open with an open crate

let (config, _) = Config::load_or_default().context("could not load configuration")?;

let prompt = &mut Prompt::build(&config, false, None);
prompt.show_projects(&CopyMode::All);
let name = prompt.ask_for_project_name(&repo)?;

// add it to the configuration and save
let mut config = config.clone();
if let Some(projects) = config.projects.as_mut() {
projects.insert(name.clone(), Project::from_link(&repo));
}
if prompt.are_you_sure(&format!("Save '{}' ({}) to configuration?", name, &repo))? {
config.save()?;
prompt.say(&format!("Saved '{}' to global config.", name));
}
prompt.suggest_edit(
"Would you like to add actions? (will open editor)",
Config::global_config_file()?.as_path(),
)?;
Ok(true)
}
7 changes: 7 additions & 0 deletions backpack/src/bin/commands/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ pub fn command() -> Command<'static> {
.help("fetch resources without using the cache")
.takes_value(false),
)
.arg(
Arg::new("config")
.short('c')
.long("config")
.help("use a specified configuration file")
.takes_value(true),
)
.arg(Arg::new("shortlink"))
.arg(Arg::new("dest"))
}
Expand Down
1 change: 1 addition & 0 deletions backpack/src/bin/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod add;
pub mod apply;
pub mod cache;
pub mod config;
Expand Down
10 changes: 9 additions & 1 deletion backpack/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ impl Default for ProjectSourceKind {
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Project {
#[serde(rename = "shortlink")]
pub shortlink: String,
Expand All @@ -296,6 +296,14 @@ pub struct Project {
#[serde(rename = "swaps")]
pub swaps: Option<Vec<Swap>>,
}
impl Project {
pub fn from_link(ln: &str) -> Self {
Self {
shortlink: ln.to_string(),
..Default::default()
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepoActionsConfig {
Expand Down
36 changes: 35 additions & 1 deletion backpack/src/git.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::data::Location;
use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use std::process::Command;
use tracing;

Expand All @@ -24,6 +24,13 @@ pub trait GitProvider {
///
/// This function will return an error if underlying remote resolving implementation failed.
fn get_ref_or_default(&self, location: &Location) -> Result<RemoteInfo>;

/// Get the current repo's main remote url
///
/// # Errors
///
/// This function will return an error if underlying remote resolving implementation failed.
fn get_local_url(&self) -> Result<String>;
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -133,4 +140,31 @@ impl GitProvider for GitCmd {
}
Ok(())
}

fn get_local_url(&self) -> anyhow::Result<String> {
let process = Command::new("git")
.arg("remote")
.arg("get-url")
.arg("origin")
.output()
.with_context(|| "cannot run git remote on local repo".to_string())?;

if !process.status.success() {
anyhow::bail!(
"cannot find local url: {}",
String::from_utf8_lossy(&process.stderr[..])
);
}

let url = String::from_utf8_lossy(&process.stdout[..]);
let lines = url.lines().collect::<Vec<_>>();
if lines.len() != 1 {
// too many urls, cannot decide
bail!("repo has more than one remote URL")
}
match lines.first() {
Some(ln) => Ok((*ln).to_string()),
_ => bail!("no repo remote URL found"),
}
}
}
6 changes: 5 additions & 1 deletion backpack/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ impl Runner {
// confirm
if !opts.always_yes
&& should_confirm
&& !prompt.are_you_sure(&shortlink, dest.as_deref())?
&& !prompt.are_you_sure(&format!(
"Generate from '{}' into '{}'?",
shortlink,
dest.as_deref().unwrap_or("a default folder")
))?
{
// bail out, user won't confirm
return Ok(());
Expand Down
2 changes: 1 addition & 1 deletion backpack/src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl Swapper {
.parent()
.ok_or_else(|| anyhow::anyhow!("cannot get parent for {:?}", swapped))?;
if !parent.exists() {
fs::create_dir_all(&parent)?;
fs::create_dir_all(parent)?;
};

let content_swaps = self
Expand Down
78 changes: 71 additions & 7 deletions backpack/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,15 @@ impl<'a> Prompt<'a> {
} else {
self.input_shortlink()?
};
Ok((shortlink, d.map(ToString::to_string), true))
if let Some(d) = d {
Ok((shortlink, Some(d.to_string()), true))
} else {
Ok((
shortlink,
self.input_dest(opts.mode == CopyMode::Copy)?,
true,
))
}
}
(Some(s), None) => Ok((
s.to_string(),
Expand Down Expand Up @@ -123,6 +131,50 @@ impl<'a> Prompt<'a> {
_ => Ok(None),
}
}

/// Shows the list of projects
pub fn show_projects(&self, mode: &CopyMode) {
println!("Current projects:");
match self.config.projects_for_selection(mode) {
projects if !projects.is_empty() => {
for (name, project) in projects {
println!(
"- {} ({})",
style(name).yellow(),
style(&project.shortlink).dim()
);
}
}
_ => {
println!("You have no projects yet.");
}
};
}

/// Returns the input shortlink of this [`Prompt`].
///
/// # Errors
///
/// This function will return an error if interaction is killed
pub fn ask_for_project_name(&mut self, repo: &str) -> AnyResult<String> {
let question = Question::input("question")
.validate(|v, _| {
if v.is_empty() {
Err("cannot be empty".into())
} else {
Ok(())
}
})
.message(format!("A name for '{}'?", repo))
.build();
let name = self
.prompt_one(question)?
.as_string()
.ok_or_else(|| anyhow::anyhow!("cannot parse input"))?
.to_string();
Ok(name)
}

/// Returns the input shortlink of this [`Prompt`].
///
/// # Errors
Expand Down Expand Up @@ -178,13 +230,9 @@ impl<'a> Prompt<'a> {
/// # Errors
///
/// This function will return an error if interaction is killed
pub fn are_you_sure(&mut self, shortlink: &str, dest: Option<&str>) -> AnyResult<bool> {
pub fn are_you_sure(&mut self, text: &str) -> AnyResult<bool> {
let question = Question::confirm("question")
.message(format!(
"Generate from '{}' into '{}'?",
shortlink,
dest.unwrap_or("a default folder"),
))
.message(text)
.default(true)
.build();

Expand Down Expand Up @@ -273,6 +321,22 @@ impl<'a> Prompt<'a> {
}
}

pub fn say(&self, text: &str) {
println!("{}", text);
}

/// Ask if user wants to edit a file and open editor
///
/// # Errors
///
/// This function will return an error if IO failed
pub fn suggest_edit(&mut self, text: &str, path: &Path) -> AnyResult<()> {
if self.are_you_sure(text)? {
edit::edit_file(path)?;
}
Ok(())
}

fn prompt_one<I: Into<Question<'a>>>(&mut self, question: I) -> AnyResult<Answer> {
match self.events {
Some(ref mut events) => {
Expand Down
Loading

0 comments on commit 4d3a7b2

Please sign in to comment.