cli: Add `rad config` command
Displays the current configuration. Also move to our tree-sitter highlighter for pretty-printing JSON and remove the `json-color` dependency.
This commit is contained in:
parent
59a08f21a4
commit
ad7a3addb6
|
|
@ -1668,17 +1668,6 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json-color"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6dc8c55175cad7234a98cc3e31ba3009e276800271692ed3ad2c2f1c574b6e8"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.4"
|
||||
|
|
@ -2298,7 +2287,6 @@ dependencies = [
|
|||
"anyhow",
|
||||
"chrono",
|
||||
"git-ref-format",
|
||||
"json-color",
|
||||
"lexopt",
|
||||
"localtime",
|
||||
"log",
|
||||
|
|
@ -2326,6 +2314,7 @@ dependencies = [
|
|||
"tree-sitter-go",
|
||||
"tree-sitter-highlight",
|
||||
"tree-sitter-html",
|
||||
"tree-sitter-json",
|
||||
"tree-sitter-md",
|
||||
"tree-sitter-python",
|
||||
"tree-sitter-ruby",
|
||||
|
|
@ -3540,6 +3529,16 @@ dependencies = [
|
|||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-json"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d82d2e33ee675dc71289e2ace4f8f9cf96d36d81400e9dae5ea61edaf5dea6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-md"
|
||||
version = "0.1.5"
|
||||
|
|
@ -3592,9 +3591,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tree-sitter-typescript"
|
||||
version = "0.20.2"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "079c695c32d39ad089101c66393aeaca30e967fba3486a91f573d2f0e12d290a"
|
||||
checksum = "a75049f0aafabb2aac205d7bb24da162b53dcd0cfb326785f25a2f32efa8071a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ path = "src/main.rs"
|
|||
anyhow = { version = "1" }
|
||||
chrono = { version = "0.4.26", default-features = false, features = ["clock", "std"] }
|
||||
git-ref-format = { version = "0.3.0", features = ["macro"] }
|
||||
json-color = { version = "0.7" }
|
||||
lexopt = { version = "0.2" }
|
||||
localtime = { version = "1.2.0" }
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
|
|
@ -32,8 +31,9 @@ thiserror = { version = "1" }
|
|||
timeago = { version = "0.3", default-features = false }
|
||||
tree-sitter = { version = "0.20.0" }
|
||||
tree-sitter-highlight = { version = "0.20" }
|
||||
tree-sitter-json = { version = "0.20.1" }
|
||||
tree-sitter-rust = { version = "0.20" }
|
||||
tree-sitter-typescript = { version = "0.20" }
|
||||
tree-sitter-typescript = { version = "0.20.3" }
|
||||
# N.b. This crate has a C++ token scanner that causes problems when building
|
||||
# for the musl target. Hence it is optional for now.
|
||||
tree-sitter-html = { version = "0.19", optional = true }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
The `rad config` command is used to manage the local user configuration.
|
||||
In its simplest form, `rad config` prints the current configuration.
|
||||
|
||||
```
|
||||
$ rad config
|
||||
{
|
||||
"publicExplorer": "https://app.radicle.xyz/nodes/$host/$rid",
|
||||
"preferredSeeds": [],
|
||||
"cli": {
|
||||
"hints": false
|
||||
},
|
||||
"node": {
|
||||
"alias": "alice",
|
||||
"listen": [],
|
||||
"peers": {
|
||||
"type": "dynamic",
|
||||
"target": 8
|
||||
},
|
||||
"connect": [],
|
||||
"externalAddresses": [],
|
||||
"network": "main",
|
||||
"relay": true,
|
||||
"limits": {
|
||||
"routingMaxSize": 1000,
|
||||
"routingMaxAge": 604800,
|
||||
"gossipMaxAge": 1209600,
|
||||
"fetchConcurrency": 1,
|
||||
"rate": {
|
||||
"inbound": {
|
||||
"fillRate": 0.2,
|
||||
"capacity": 32
|
||||
},
|
||||
"outbound": {
|
||||
"fillRate": 1.0,
|
||||
"capacity": 64
|
||||
}
|
||||
}
|
||||
},
|
||||
"policy": "block",
|
||||
"scope": "trusted"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -10,6 +10,8 @@ pub mod rad_clean;
|
|||
pub mod rad_clone;
|
||||
#[path = "commands/cob.rs"]
|
||||
pub mod rad_cob;
|
||||
#[path = "commands/config.rs"]
|
||||
pub mod rad_config;
|
||||
#[path = "commands/diff.rs"]
|
||||
pub mod rad_diff;
|
||||
#[path = "commands/fork.rs"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
#![allow(clippy::or_fun_call)]
|
||||
use std::ffi::OsString;
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::{Args, Error, Help};
|
||||
use crate::terminal::Element as _;
|
||||
|
||||
pub const HELP: Help = Help {
|
||||
name: "config",
|
||||
description: "Manage your local radicle configuration",
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
usage: r#"
|
||||
Usage
|
||||
|
||||
rad config [<option>...]
|
||||
|
||||
If no argument is specified, prints the current radicle configuration as JSON.
|
||||
|
||||
Options
|
||||
|
||||
--help Print help
|
||||
|
||||
"#,
|
||||
};
|
||||
|
||||
pub struct Options {}
|
||||
|
||||
impl Args for Options {
|
||||
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
|
||||
use lexopt::prelude::*;
|
||||
|
||||
let mut parser = lexopt::Parser::from_args(args);
|
||||
|
||||
#[allow(clippy::never_loop)]
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
Long("help") | Short('h') => {
|
||||
return Err(Error::Help.into());
|
||||
}
|
||||
_ => return Err(anyhow!(arg.unexpected())),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((Options {}, vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||
let profile = ctx.profile()?;
|
||||
let path = profile.home.config();
|
||||
let output = term::json::to_pretty(&profile.config, path.as_path())?;
|
||||
|
||||
output.print();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
#![allow(clippy::or_fun_call)]
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Context as _};
|
||||
use chrono::prelude::*;
|
||||
use json_color::{Color, Colorizer};
|
||||
|
||||
use radicle::identity::Id;
|
||||
use radicle::identity::Identity;
|
||||
|
|
@ -14,9 +13,11 @@ use radicle::node::tracking::Policy;
|
|||
use radicle::node::AliasStore as _;
|
||||
use radicle::storage::refs::RefsAt;
|
||||
use radicle::storage::{ReadRepository, ReadStorage};
|
||||
use radicle_term::Element;
|
||||
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::{Args, Error, Help};
|
||||
use crate::terminal::json;
|
||||
|
||||
pub const HELP: Help = Help {
|
||||
name: "inspect",
|
||||
|
|
@ -154,16 +155,10 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
refs(&repo)?;
|
||||
}
|
||||
Target::Payload => {
|
||||
println!(
|
||||
"{}",
|
||||
colorizer().colorize_json_str(&serde_json::to_string_pretty(&project.payload)?)?
|
||||
);
|
||||
json::to_pretty(&project.payload, Path::new("radicle.json"))?.print();
|
||||
}
|
||||
Target::Identity => {
|
||||
println!(
|
||||
"{}",
|
||||
colorizer().colorize_json_str(&serde_json::to_string_pretty(&project.doc)?)?
|
||||
);
|
||||
json::to_pretty(&project.doc, Path::new("radicle.json"))?.print();
|
||||
}
|
||||
Target::Sigrefs => {
|
||||
for remote in repo.remote_ids()? {
|
||||
|
|
@ -265,11 +260,10 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
}
|
||||
term::blank();
|
||||
}
|
||||
|
||||
let json = colorizer().colorize_json_str(&serde_json::to_string_pretty(&doc)?)?;
|
||||
for line in json.lines() {
|
||||
for line in json::to_pretty(&doc, Path::new("radicle.json"))? {
|
||||
println!(" {line}");
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
|
@ -281,17 +275,6 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// Used for JSON Colorizing
|
||||
fn colorizer() -> Colorizer {
|
||||
Colorizer::new()
|
||||
.null(Color::Cyan)
|
||||
.boolean(Color::Cyan)
|
||||
.number(Color::Magenta)
|
||||
.string(Color::Green)
|
||||
.key(Color::Blue)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn refs(repo: &radicle::storage::git::Repository) -> anyhow::Result<()> {
|
||||
let mut refs = Vec::new();
|
||||
for r in repo.references()? {
|
||||
|
|
|
|||
|
|
@ -132,6 +132,13 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
|
|||
args.to_vec(),
|
||||
);
|
||||
}
|
||||
"config" => {
|
||||
term::run_command_args::<rad_config::Options, _>(
|
||||
rad_config::HELP,
|
||||
rad_config::run,
|
||||
args.to_vec(),
|
||||
);
|
||||
}
|
||||
"diff" => {
|
||||
term::run_command_args::<rad_diff::Options, _>(
|
||||
rad_diff::HELP,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ pub use io::signer;
|
|||
pub mod comment;
|
||||
pub mod highlight;
|
||||
pub mod issue;
|
||||
pub mod json;
|
||||
pub mod patch;
|
||||
|
||||
use std::ffi::OsString;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const HIGHLIGHTS: &[&str] = &[
|
|||
"float.literal",
|
||||
"keyword",
|
||||
"label",
|
||||
"number",
|
||||
"operator",
|
||||
"property",
|
||||
"punctuation",
|
||||
|
|
@ -72,12 +73,14 @@ impl Theme {
|
|||
"keyword" => self.color("red"),
|
||||
"comment" => self.color("grey"),
|
||||
"constant" => self.color("orange"),
|
||||
"number" => self.color("blue"),
|
||||
"string" => self.color("teal"),
|
||||
"string.special" => self.color("green"),
|
||||
"function" => self.color("purple"),
|
||||
"operator" => self.color("blue"),
|
||||
// Eg. `true` and `false` in rust.
|
||||
"constant.builtin" => self.color("blue"),
|
||||
"type.builtin" => self.color("cyan"),
|
||||
"type.builtin" => self.color("teal"),
|
||||
"punctuation.bracket" | "punctuation.delimiter" => term::Color::default(),
|
||||
// Eg. the '#' in Markdown titles.
|
||||
"punctuation.special" => self.color("dim"),
|
||||
|
|
@ -124,14 +127,17 @@ impl Builder {
|
|||
for event in highlights {
|
||||
match event? {
|
||||
ts::HighlightEvent::Source { start, end } => {
|
||||
let range = &code[start..end];
|
||||
|
||||
for byte in range {
|
||||
for (i, byte) in code.iter().enumerate().skip(start).take(end - start) {
|
||||
if *byte == b'\n' {
|
||||
self.advance();
|
||||
// Start on new line.
|
||||
self.lines.push(term::Line::from(self.line.clone()));
|
||||
self.line.clear();
|
||||
} else if i == code.len() - 1 {
|
||||
// File has no `\n` at the end.
|
||||
self.label.push(*byte);
|
||||
self.advance();
|
||||
self.lines.push(term::Line::from(self.line.clone()));
|
||||
} else {
|
||||
// Add to existing label.
|
||||
self.label.push(*byte);
|
||||
|
|
@ -140,10 +146,11 @@ impl Builder {
|
|||
}
|
||||
ts::HighlightEvent::HighlightStart(h) => {
|
||||
let name = HIGHLIGHTS[h.0];
|
||||
let style =
|
||||
term::Style::default().fg(theme.highlight(name).unwrap_or_default());
|
||||
|
||||
self.advance();
|
||||
self.styles
|
||||
.push(term::Style::default().fg(theme.highlight(name).unwrap_or_default()));
|
||||
self.styles.push(style);
|
||||
}
|
||||
ts::HighlightEvent::HighlightEnd => {
|
||||
self.advance();
|
||||
|
|
@ -193,7 +200,8 @@ impl Highlighter {
|
|||
fn detect(&mut self, path: &Path, _code: &[u8]) -> Option<&mut ts::HighlightConfiguration> {
|
||||
match path.extension().and_then(|e| e.to_str()) {
|
||||
Some("rs") => self.config("rust"),
|
||||
Some("ts" | "js" | "json") => self.config("typescript"),
|
||||
Some("ts" | "js") => self.config("typescript"),
|
||||
Some("json") => self.config("json"),
|
||||
Some("sh" | "bash") => self.config("shell"),
|
||||
Some("md" | "markdown") => self.config("markdown"),
|
||||
Some("go") => self.config("go"),
|
||||
|
|
@ -220,6 +228,15 @@ impl Highlighter {
|
|||
)
|
||||
.expect("Highlighter::config: highlight configuration must be valid")
|
||||
})),
|
||||
"json" => Some(self.configs.entry(language).or_insert_with(|| {
|
||||
ts::HighlightConfiguration::new(
|
||||
tree_sitter_json::language(),
|
||||
tree_sitter_json::HIGHLIGHT_QUERY,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
.expect("Highlighter::config: highlight configuration must be valid")
|
||||
})),
|
||||
"typescript" => Some(self.configs.entry(language).or_insert_with(|| {
|
||||
ts::HighlightConfiguration::new(
|
||||
tree_sitter_typescript::language_typescript(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::terminal as term;
|
||||
|
||||
/// Pretty-print a JSON value with syntax highlighting.
|
||||
pub fn to_pretty(value: &impl serde::Serialize, path: &Path) -> anyhow::Result<Vec<term::Line>> {
|
||||
let json = serde_json::to_string_pretty(&value)?;
|
||||
let mut highlighter = term::highlight::Highlighter::default();
|
||||
|
||||
if let Some(highlighted) = highlighter.highlight(path, json.as_bytes())? {
|
||||
Ok(highlighted)
|
||||
} else {
|
||||
Ok(json.split('\n').map(term::Line::new).collect())
|
||||
}
|
||||
}
|
||||
|
|
@ -193,6 +193,21 @@ fn rad_inspect() {
|
|||
test("examples/rad-inspect-noauth.md", working.path(), None, []).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_config() {
|
||||
let mut environment = Environment::new();
|
||||
let profile = environment.profile("alice");
|
||||
let working = tempfile::tempdir().unwrap();
|
||||
|
||||
test(
|
||||
"examples/rad-config.md",
|
||||
working.path(),
|
||||
Some(&profile.home),
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_checkout() {
|
||||
let mut environment = Environment::new();
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ impl Environment {
|
|||
node: node::Config::new(alias.clone()),
|
||||
cli: cli::Config { hints: false },
|
||||
public_explorer: profile::Explorer::default(),
|
||||
preferred_seeds: vec![],
|
||||
};
|
||||
config.write(&home.config()).unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -291,6 +291,26 @@ impl Element for Line {
|
|||
}
|
||||
}
|
||||
|
||||
impl Element for Vec<Line> {
|
||||
fn size(&self, parent: Constraint) -> Size {
|
||||
let width = self
|
||||
.iter()
|
||||
.map(|e| e.columns(parent))
|
||||
.max()
|
||||
.unwrap_or_default();
|
||||
let height = self.len();
|
||||
|
||||
Size::new(width, height)
|
||||
}
|
||||
|
||||
fn render(&self, parent: Constraint) -> Vec<Line> {
|
||||
self.iter()
|
||||
.cloned()
|
||||
.flat_map(|l| l.render(parent))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Line {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for item in &self.items {
|
||||
|
|
|
|||
|
|
@ -163,11 +163,6 @@ pub enum ConfigError {
|
|||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Config {
|
||||
/// Node configuration.
|
||||
pub node: node::Config,
|
||||
/// CLI configuration.
|
||||
#[serde(default)]
|
||||
pub cli: cli::Config,
|
||||
/// Public explorer. This is used for generating links.
|
||||
#[serde(default)]
|
||||
pub public_explorer: Explorer,
|
||||
|
|
@ -175,16 +170,21 @@ pub struct Config {
|
|||
/// and in other situations when a seed needs to be chosen.
|
||||
#[serde(default)]
|
||||
pub preferred_seeds: Vec<node::config::ConnectAddress>,
|
||||
/// CLI configuration.
|
||||
#[serde(default)]
|
||||
pub cli: cli::Config,
|
||||
/// Node configuration.
|
||||
pub node: node::Config,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Create a new, default configuration.
|
||||
pub fn new(alias: Alias) -> Self {
|
||||
Self {
|
||||
node: node::Config::new(alias),
|
||||
cli: cli::Config::default(),
|
||||
public_explorer: Explorer::default(),
|
||||
preferred_seeds: vec![node::config::seeds::RADICLE_COMMUNITY_NODE.clone()],
|
||||
cli: cli::Config::default(),
|
||||
node: node::Config::new(alias),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue