chore: add orgize-{cli,common,lsp} package

This commit is contained in:
PoiScript 2023-12-20 21:56:10 +08:00
parent 6930640866
commit 4cc1130a17
No known key found for this signature in database
GPG key ID: 22C2B1249D99985E
131 changed files with 6577 additions and 56 deletions

25
orgize-cli/Cargo.toml Normal file
View 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"

View 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
View 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(())
}

View 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
View 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
View 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();
}
_ => {}
}
}
}