chore: add orgize-{cli,common,lsp} package
This commit is contained in:
parent
6930640866
commit
4cc1130a17
131 changed files with 6577 additions and 56 deletions
25
orgize-cli/Cargo.toml
Normal file
25
orgize-cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "orgize-cli"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "CLI tools for org-mode file, powered by orgize."
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
clap = { version = "4.4.11", features = ["derive"] }
|
||||
clap-verbosity-flag = "2.1.0"
|
||||
jetscii = "0.5.3"
|
||||
nom = "7.1.3"
|
||||
orgize = { path = "../orgize" }
|
||||
orgize-common = { path = "../orgize-common" }
|
||||
resolve-path = "0.1.0"
|
||||
tempfile = "3.8.1"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3", features = ["fmt"] }
|
||||
|
||||
[[bin]]
|
||||
name = "orgize"
|
||||
path = "src/main.rs"
|
||||
80
orgize-cli/src/detangle.rs
Normal file
80
orgize-cli/src/detangle.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use clap::Args;
|
||||
use orgize::{
|
||||
export::{Container, Event, TraversalContext, Traverser},
|
||||
Org,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::diff;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Command {
|
||||
path: Vec<PathBuf>,
|
||||
|
||||
#[arg(short, long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn run(self) -> anyhow::Result<()> {
|
||||
for path in self.path {
|
||||
if !path.exists() {
|
||||
tracing::error!("{:?} is not existed", path);
|
||||
}
|
||||
|
||||
let orgi = std::fs::read_to_string(&path)?;
|
||||
|
||||
let mut t = DetangleTraverser {
|
||||
results: Vec::new(),
|
||||
org_file_path: path,
|
||||
};
|
||||
|
||||
let org = Org::parse(&orgi);
|
||||
org.traverse(&mut t);
|
||||
|
||||
if self.dry_run {
|
||||
diff::print(&orgi, t.results);
|
||||
} else {
|
||||
diff::write_to_file(&orgi, t.results, t.org_file_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct DetangleTraverser {
|
||||
results: Vec<(usize, usize, String)>,
|
||||
org_file_path: PathBuf,
|
||||
}
|
||||
|
||||
impl Traverser for DetangleTraverser {
|
||||
fn event(&mut self, event: Event, ctx: &mut TraversalContext) {
|
||||
match event {
|
||||
Event::Enter(Container::SourceBlock(block)) => {
|
||||
if let Ok(Some((start, end, content))) =
|
||||
orgize_common::detangle(block, &self.org_file_path)
|
||||
{
|
||||
self.results.push((start, end, content));
|
||||
}
|
||||
|
||||
ctx.skip();
|
||||
}
|
||||
|
||||
// skip some containers for performance
|
||||
Event::Enter(Container::List(_))
|
||||
| Event::Enter(Container::OrgTable(_))
|
||||
| Event::Enter(Container::SpecialBlock(_))
|
||||
| Event::Enter(Container::QuoteBlock(_))
|
||||
| Event::Enter(Container::CenterBlock(_))
|
||||
| Event::Enter(Container::VerseBlock(_))
|
||||
| Event::Enter(Container::CommentBlock(_))
|
||||
| Event::Enter(Container::ExampleBlock(_))
|
||||
| Event::Enter(Container::ExportBlock(_)) => {
|
||||
ctx.skip();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
orgize-cli/src/diff.rs
Normal file
46
orgize-cli/src/diff.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::builder::styling::{AnsiColor, Color, Style};
|
||||
|
||||
pub fn print(orgi: &str, mut patches: Vec<(usize, usize, String)>) {
|
||||
patches.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let mut off = 0;
|
||||
|
||||
for (start, end, content) in patches {
|
||||
print!("{}", &orgi[off..(start)]);
|
||||
|
||||
if orgi[start..end] != content {
|
||||
let style = Style::new().fg_color(Color::Ansi(AnsiColor::Cyan).into());
|
||||
print!("{}{}{}", style.render(), &content, style.render_reset());
|
||||
} else {
|
||||
print!("{}", &content);
|
||||
}
|
||||
|
||||
off = end;
|
||||
}
|
||||
|
||||
print!("{}", &orgi[off..]);
|
||||
}
|
||||
|
||||
pub fn write_to_file(
|
||||
orgi: &str,
|
||||
mut patches: Vec<(usize, usize, String)>,
|
||||
path: PathBuf,
|
||||
) -> anyhow::Result<()> {
|
||||
patches.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let file = &mut OpenOptions::new().write(true).open(path)?;
|
||||
let mut off = 0;
|
||||
|
||||
for (start, end, content) in patches {
|
||||
write!(file, "{}{}", &orgi[off..start], &content)?;
|
||||
off = end;
|
||||
}
|
||||
|
||||
write!(file, "{}", &orgi[off..])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
86
orgize-cli/src/execute_src_block.rs
Normal file
86
orgize-cli/src/execute_src_block.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use clap::Args;
|
||||
use orgize::{
|
||||
export::{Container, Event, TraversalContext, Traverser},
|
||||
Org,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::diff;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Command {
|
||||
path: Vec<PathBuf>,
|
||||
|
||||
#[arg(short, long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn run(self) -> anyhow::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
|
||||
tracing::debug!("Create tempdir {:?}", dir.path().to_string_lossy());
|
||||
|
||||
for path in self.path {
|
||||
if !path.exists() {
|
||||
tracing::error!("{:?} is not existed", path);
|
||||
}
|
||||
|
||||
let mut t = ExecuteTraverser {
|
||||
results: Vec::new(),
|
||||
dir: &dir,
|
||||
};
|
||||
|
||||
let orgi = std::fs::read_to_string(&path)?;
|
||||
let org = Org::parse(&orgi);
|
||||
org.traverse(&mut t);
|
||||
|
||||
t.results.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
if self.dry_run {
|
||||
diff::print(&orgi, t.results);
|
||||
} else {
|
||||
diff::write_to_file(&orgi, t.results, path)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct ExecuteTraverser<'a> {
|
||||
results: Vec<(usize, usize, String)>,
|
||||
|
||||
dir: &'a tempfile::TempDir,
|
||||
}
|
||||
|
||||
impl<'a> Traverser for ExecuteTraverser<'a> {
|
||||
fn event(&mut self, event: Event, ctx: &mut TraversalContext) {
|
||||
match event {
|
||||
Event::Enter(Container::SourceBlock(block)) => {
|
||||
if let Ok(Some((start, end, content))) =
|
||||
orgize_common::execute(block, self.dir.path())
|
||||
{
|
||||
self.results.push((start, end, content));
|
||||
}
|
||||
|
||||
ctx.skip();
|
||||
}
|
||||
|
||||
// skip some containers for performance
|
||||
Event::Enter(Container::List(_))
|
||||
| Event::Enter(Container::OrgTable(_))
|
||||
| Event::Enter(Container::SpecialBlock(_))
|
||||
| Event::Enter(Container::QuoteBlock(_))
|
||||
| Event::Enter(Container::CenterBlock(_))
|
||||
| Event::Enter(Container::VerseBlock(_))
|
||||
| Event::Enter(Container::CommentBlock(_))
|
||||
| Event::Enter(Container::ExampleBlock(_))
|
||||
| Event::Enter(Container::ExportBlock(_)) => {
|
||||
ctx.skip();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
orgize-cli/src/main.rs
Normal file
54
orgize-cli/src/main.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
mod detangle;
|
||||
mod diff;
|
||||
mod execute_src_block;
|
||||
mod tangle;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap_verbosity_flag::{InfoLevel, LevelFilter as CLevelFilter, Verbosity};
|
||||
use tracing::level_filters::LevelFilter;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(name = "orgize-tools", version)]
|
||||
pub struct App {
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
|
||||
#[command(flatten)]
|
||||
verbose: Verbosity<InfoLevel>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
#[clap(name = "tangle")]
|
||||
Tangle(tangle::Command),
|
||||
|
||||
#[clap(name = "detangle")]
|
||||
Detangle(detangle::Command),
|
||||
|
||||
#[clap(name = "execute-src-block")]
|
||||
ExecuteSrcBlock(execute_src_block::Command),
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let parsed = App::parse();
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(match parsed.verbose.log_level_filter() {
|
||||
CLevelFilter::Off => LevelFilter::OFF,
|
||||
CLevelFilter::Error => LevelFilter::ERROR,
|
||||
CLevelFilter::Warn => LevelFilter::WARN,
|
||||
CLevelFilter::Info => LevelFilter::INFO,
|
||||
CLevelFilter::Debug => LevelFilter::DEBUG,
|
||||
CLevelFilter::Trace => LevelFilter::TRACE,
|
||||
})
|
||||
.without_time()
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.init();
|
||||
|
||||
match parsed.command {
|
||||
Command::Tangle(cmd) => cmd.run(),
|
||||
Command::Detangle(cmd) => cmd.run(),
|
||||
Command::ExecuteSrcBlock(cmd) => cmd.run(),
|
||||
}
|
||||
}
|
||||
111
orgize-cli/src/tangle.rs
Normal file
111
orgize-cli/src/tangle.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use clap::{
|
||||
builder::styling::{AnsiColor, Color, Style},
|
||||
Args,
|
||||
};
|
||||
use orgize::{
|
||||
export::{Container, Event, TraversalContext, Traverser},
|
||||
Org,
|
||||
};
|
||||
use std::fs;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
pub struct Command {
|
||||
path: Vec<PathBuf>,
|
||||
|
||||
#[arg(short, long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn run(self) -> anyhow::Result<()> {
|
||||
let mut t = TangleTraverser::default();
|
||||
|
||||
for path in self.path {
|
||||
if !path.exists() {
|
||||
tracing::error!("{:?} is not existed", path);
|
||||
}
|
||||
|
||||
let string = std::fs::read_to_string(&path)?;
|
||||
let org = Org::parse(string);
|
||||
t.org_file_path = path;
|
||||
t.count = 0;
|
||||
org.traverse(&mut t);
|
||||
tracing::info!(
|
||||
"Found {} code block from {}",
|
||||
t.count,
|
||||
t.org_file_path.to_string_lossy()
|
||||
);
|
||||
}
|
||||
|
||||
if self.dry_run {
|
||||
for (path, (permission, content, mkdir)) in t.results {
|
||||
let style = Style::new()
|
||||
.fg_color(Color::Ansi(AnsiColor::BrightYellow).into())
|
||||
.underline()
|
||||
.bold();
|
||||
print!(
|
||||
"{}{}{}",
|
||||
style.render(),
|
||||
path.to_string_lossy(),
|
||||
style.render_reset(),
|
||||
);
|
||||
if let Some(permission) = permission {
|
||||
print!(" (permission: {:o})", permission);
|
||||
}
|
||||
if mkdir {
|
||||
print!(" (mkdir: yes)");
|
||||
}
|
||||
println!("\n{}", content);
|
||||
}
|
||||
} else {
|
||||
for (path, (_, contents, _)) in t.results {
|
||||
fs::write(&path, contents)?;
|
||||
tracing::info!("Wrote to {}", path.to_string_lossy());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TangleTraverser {
|
||||
results: HashMap<PathBuf, (Option<u32>, String, bool)>,
|
||||
count: usize,
|
||||
org_file_path: PathBuf,
|
||||
}
|
||||
|
||||
impl Traverser for TangleTraverser {
|
||||
fn event(&mut self, event: Event, ctx: &mut TraversalContext) {
|
||||
match event {
|
||||
Event::Enter(Container::SourceBlock(block)) => {
|
||||
if let Ok(Some((path, permission, content, mkdir))) =
|
||||
orgize_common::tangle(block, &self.org_file_path)
|
||||
{
|
||||
let value = self.results.entry(path).or_default();
|
||||
value.0 = permission;
|
||||
value.1.push_str(&content);
|
||||
value.2 = mkdir;
|
||||
}
|
||||
|
||||
ctx.skip();
|
||||
}
|
||||
|
||||
// skip some containers for performance
|
||||
Event::Enter(Container::List(_))
|
||||
| Event::Enter(Container::OrgTable(_))
|
||||
| Event::Enter(Container::SpecialBlock(_))
|
||||
| Event::Enter(Container::QuoteBlock(_))
|
||||
| Event::Enter(Container::CenterBlock(_))
|
||||
| Event::Enter(Container::VerseBlock(_))
|
||||
| Event::Enter(Container::CommentBlock(_))
|
||||
| Event::Enter(Container::ExampleBlock(_))
|
||||
| Event::Enter(Container::ExportBlock(_)) => {
|
||||
ctx.skip();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue