Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
WGUNDERWOOD committed Jan 12, 2025

Verified

This commit was signed with the committer’s verified signature.
niknetniko Niko Strijbol
2 parents dce3d02 + daaee40 commit a7651a4
Showing 24 changed files with 444 additions and 89 deletions.
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -52,6 +52,49 @@ jobs:
- run: cargo install cross
- name: Build
run: cross build --target ${{ matrix.target }}
wasm:
name: Cargo wasm build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- uses: jetli/[email protected]
with:
version: '0.2.95'
- name: Build wasm
run: cargo build -r --lib --target wasm32-unknown-unknown
- name: Bind wasm
run: |
wasm-bindgen --target web --out-dir web/pkg \
target/wasm32-unknown-unknown/release/tex_fmt.wasm
- name: Optimize wasm
uses: NiklasEi/wasm-opt-action@v2
with:
options: -Oz
file: web/pkg/tex_fmt_bg.wasm
output: web/pkg/tex_fmt_bg.wasm
- name: Upload wasm
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v3
with:
path: "web/pkg"
pages:
if: github.ref == 'refs/heads/main'
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
needs: wasm
steps:
- uses: actions/checkout@v3
- name: Download WASM and JS artifacts
uses: actions/download-artifact@v3
- run: mkdir -p web/pkg && mv pkg/* web/pkg/ && rmdir pkg
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: web
nix:
name: Nix build
runs-on: ubuntu-latest
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
**/*.rs.bk
*.pdb
/result
*.html
*.log
flamegraph.svg
perf.data*
127 changes: 108 additions & 19 deletions Cargo.lock

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

12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -17,12 +17,15 @@ clap_mangen = "0.2.24"
colored = "2.2.0"
dirs = "5.0.1"
env_logger = "0.11.6"
js-sys = "0.3.72"
lazy_static = "1.5.0"
log = "0.4.22"
merge = "0.1.0"
regex = "1.11.1"
similar = "2.6.0"
toml = "0.8.19"
wasm-bindgen = "0.2.95"
web-time = "1.1.0"

[features]
shellinstall = []
@@ -34,3 +37,12 @@ clap_mangen = "0.2.24"

[profile.release]
codegen-units = 1

[lib]
name = "tex_fmt"
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]

[[bin]]
name = "tex-fmt"
path = "src/bin.rs"
6 changes: 5 additions & 1 deletion completion/tex-fmt.fish
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
complete -c tex-fmt -s l -l wraplen -d 'Line length for wrapping [default: 80]' -r
complete -c tex-fmt -s t -l tabsize -d 'Number of characters to use as tab size [default: 2]' -r
complete -c tex-fmt -l config -d 'Path to configuration file' -r -F
complete -c tex-fmt -l completion -d 'Generate shell completion script' -r -f -a "{bash\t'',elvish\t'',fish\t'',powershell\t'',zsh\t''}"
complete -c tex-fmt -l completion -d 'Generate shell completion script' -r -f -a "bash\t''
elvish\t''
fish\t''
powershell\t''
zsh\t''"
complete -c tex-fmt -s c -l check -d 'Check formatting, do not modify files'
complete -c tex-fmt -s p -l print -d 'Print to stdout, do not modify files'
complete -c tex-fmt -s n -l nowrap -d 'Do not wrap long lines'
8 changes: 4 additions & 4 deletions flake.lock

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

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
description = "LaTeX formatter written in Rust";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = {
13 changes: 10 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
default: test doc clippy format shellcheck shellinstall
default: test doc clippy format shellcheck shellinstall wasm

all: default prof perf binary logo latex
all: default prof binary logo perf latex

alias b := build
alias d := doc
@@ -19,7 +19,7 @@ doc:
@cargo doc

shellinstall:
@cargo build --features shellinstall
@cargo build -r --features shellinstall

testignored:
@cargo test -- --ignored
@@ -34,6 +34,13 @@ format:
latex:
@cd extra && bash latex.sh

wasm:
@mkdir -p web/pkg
@cargo build -r --lib --target wasm32-unknown-unknown
@wasm-bindgen --target web --out-dir web/pkg \
target/wasm32-unknown-unknown/release/tex_fmt.wasm
@cd web/pkg && wasm-opt -Oz -o tex_fmt_bg.wasm tex_fmt_bg.wasm

perf:
@cd extra && bash perf.sh

1 change: 1 addition & 0 deletions notes.org
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
** TODO Add to Nix home-manager with empty config file
** TODO Indicate which args are CLI and which are config
** TODO Add link to treefmt-nix in README
** TODO Update wasm-bindgen to 0.2.99 when released in nixpkgs
* Options and documentation
** Args struct
** OptionArgs struct
3 changes: 3 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ pkgs.mkShell {
in [
pkgs.alejandra
pkgs.bacon
pkgs.binaryen
pkgs.cacert
pkgs.cargo-edit
pkgs.cargo-flamegraph
@@ -19,11 +20,13 @@ pkgs.mkShell {
pkgs.diff-so-fancy
pkgs.gh
pkgs.hyperfine
pkgs.lld
pkgs.poppler_utils
pkgs.ripgrep
pkgs.rustfmt
pkgs.shellcheck
pkgs.texlive.combined.scheme-full
pkgs.wasm-bindgen-cli
python
];
}
6 changes: 3 additions & 3 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
use crate::cli::*;
use crate::config::*;
use crate::logging::*;
use crate::Log;
use colored::Colorize;
use log::Level;
use log::LevelFilter;
@@ -114,7 +113,8 @@ impl Default for OptionArgs {
/// Get all arguments from CLI, config file, and defaults, and merge them
pub fn get_args() -> Args {
let mut args = get_cli_args();
let config_args = get_config_args(&args);
let config = get_config(&args);
let config_args = get_config_args(config);
if let Some(c) = config_args {
args.merge(c);
}
@@ -124,7 +124,7 @@ pub fn get_args() -> Args {

impl Args {
/// Construct concrete arguments from optional arguments
fn from(args: OptionArgs) -> Self {
pub fn from(args: OptionArgs) -> Self {
Self {
check: args.check.unwrap(),
print: args.print.unwrap(),
33 changes: 3 additions & 30 deletions src/main.rs → src/bin.rs
Original file line number Diff line number Diff line change
@@ -11,37 +11,10 @@
#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::module_name_repetitions)]

use std::fs;
use std::process::ExitCode;

mod args;
mod cli;
mod comments;
mod config;
mod format;
mod ignore;
mod indent;
mod logging;
mod read;
mod regexes;
mod subs;
mod verbatim;
mod wrap;
mod write;
use crate::args::*;
use crate::format::*;
use crate::logging::*;

#[cfg(test)]
mod tests;

#[cfg(target_family = "unix")]
/// Line ending for unix
const LINE_END: &str = "\n";

#[cfg(target_family = "windows")]
/// Line ending for Windows
const LINE_END: &str = "\r\n";
use tex_fmt::args::get_args;
use tex_fmt::format::run;
use tex_fmt::logging::{init_logger, print_logs, Log};

fn main() -> ExitCode {
let mut args = get_args();
22 changes: 17 additions & 5 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -65,22 +65,34 @@ fn find_git_root() -> Option<PathBuf> {
None
}

/// Parse arguments from a config file path
pub fn get_config_args(args: &OptionArgs) -> Option<OptionArgs> {
/// Read content from a config file path
pub fn get_config(args: &OptionArgs) -> Option<(PathBuf, String, String)> {
let config_path = resolve_config_path(args);
#[allow(clippy::question_mark)]
if config_path.is_none() {
return None;
};
let config_string = config_path
let config_path_string = config_path
.clone()
.unwrap()
.into_os_string()
.into_string()
.unwrap();
let config = read_to_string(config_path.clone().unwrap()).unwrap();
Some((config_path.unwrap(), config_path_string, config))
}

/// Parse arguments from a config file path
pub fn get_config_args(
config: Option<(PathBuf, String, String)>,
) -> Option<OptionArgs> {
#[allow(clippy::question_mark)]
if config.is_none() {
return None;
}
let (config_path, config_path_string, config) = config.unwrap();
let config = config.parse::<Table>().unwrap_or_else(|_| {
panic!("Failed to read config file at {config_string}")
panic!("Failed to read config file at {config_path_string}")
});

let verbosity = match config.get("verbosity").map(|x| x.as_str().unwrap()) {
@@ -112,7 +124,7 @@ pub fn get_config_args(args: &OptionArgs) -> Option<OptionArgs> {
.map(|x| x.as_integer().unwrap().try_into().unwrap()),
tabchar,
stdin: config.get("stdin").map(|x| x.as_bool().unwrap()),
config: config_path,
config: Some(config_path),
noconfig: None,
lists: config
.get("lists")
6 changes: 6 additions & 0 deletions src/format.rs
Original file line number Diff line number Diff line change
@@ -210,6 +210,12 @@ impl State {
}
}

impl Default for State {
fn default() -> Self {
Self::new()
}
}

/// Record whether a line contains certain patterns to avoid recomputing
pub struct Pattern {
/// Whether a begin environment pattern is present
6 changes: 6 additions & 0 deletions src/ignore.rs
Original file line number Diff line number Diff line change
@@ -23,6 +23,12 @@ impl Ignore {
}
}

impl Default for Ignore {
fn default() -> Self {
Self::new()
}
}

/// Determine whether a line should be ignored
pub fn get_ignore(
line: &str,
6 changes: 6 additions & 0 deletions src/indent.rs
Original file line number Diff line number Diff line change
@@ -33,6 +33,12 @@ impl Indent {
}
}

impl Default for Indent {
fn default() -> Self {
Self::new()
}
}

/// Calculate total indentation change due to the current line
fn get_diff(
line: &str,
28 changes: 28 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Main library
pub mod args;
pub mod cli;
pub mod comments;
pub mod config;
pub mod format;
pub mod ignore;
pub mod indent;
pub mod logging;
pub mod read;
pub mod regexes;
pub mod subs;
pub mod verbatim;
pub mod wasm;
pub mod wrap;
pub mod write;

#[cfg(test)]
pub mod tests;

#[cfg(any(target_family = "unix", target_family = "wasm"))]
/// Line ending for unix
const LINE_END: &str = "\n";

#[cfg(target_family = "windows")]
/// Line ending for Windows
const LINE_END: &str = "\r\n";
66 changes: 47 additions & 19 deletions src/logging.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Utilities for logging
use crate::args::Args;
use colored::{Color, Colorize};
use env_logger::Builder;
use log::Level;
@@ -8,7 +9,7 @@ use log::LevelFilter;
use std::cmp::Reverse;
use std::io::Write;
use std::path::Path;
use std::time::Instant;
use web_time::Instant;

/// Holds a log entry
#[derive(Debug)]
@@ -112,8 +113,8 @@ pub fn init_logger(level_filter: LevelFilter) {
.init();
}

/// Display all of the logs collected
pub fn print_logs(logs: &mut Vec<Log>) {
/// Sort and remove duplicates
fn preprocess_logs(logs: &mut Vec<Log>) {
logs.sort_by_key(|l| {
(
l.level,
@@ -141,23 +142,53 @@ pub fn print_logs(logs: &mut Vec<Log>) {
)
});
logs.sort_by_key(|l| l.time);
}

for log in logs {
let linum_new = log
.linum_new
.map_or_else(String::new, |i| format!("Line {i} "));
/// Format a log entry
fn format_log(log: &Log) -> String {
let linum_new = log
.linum_new
.map_or_else(String::new, |i| format!("Line {i} "));

let linum_old = log
.linum_old
.map_or_else(String::new, |i| format!("({i}). "));

let linum_old = log
.linum_old
.map_or_else(String::new, |i| format!("({i}). "));
let line = log
.line
.as_ref()
.map_or_else(String::new, |l| l.trim_start().to_string());

let log_string = format!(
"{}{}{} {}",
linum_new.white().bold(),
linum_old.white().bold(),
log.message.yellow().bold(),
line,
);
log_string
}

let line = log
.line
.as_ref()
.map_or_else(String::new, |l| l.trim_start().to_string());
/// Format all of the logs collected
pub fn format_logs(logs: &mut Vec<Log>, args: &Args) -> String {
preprocess_logs(logs);
let mut logs_string = "".to_string();
for log in logs {
if log.level <= args.verbosity {
let log_string = format_log(log);
logs_string.push_str(&log_string);
logs_string.push('\n');
}
}
logs_string
}

/// Print all of the logs collected
pub fn print_logs(logs: &mut Vec<Log>) {
preprocess_logs(logs);
for log in logs {
let log_string = format!(
"{} {}: {}{}{} {}",
"{} {}: {}",
"tex-fmt".magenta().bold(),
match log.file.as_str() {
"<stdin>" | "" => "<stdin>".blue().bold(),
@@ -169,10 +200,7 @@ pub fn print_logs(logs: &mut Vec<Log>) {
.blue()
.bold(),
},
linum_new.white().bold(),
linum_old.white().bold(),
log.message.yellow().bold(),
line,
format_log(log),
);

match log.level {
4 changes: 2 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::args::*;
use crate::format_file;
use crate::fs;
use crate::format::format_file;
use crate::logging::*;
use colored::Colorize;
use similar::{ChangeTag, TextDiff};
use std::fs;

fn test_file(source_file: &str, target_file: &str) -> bool {
let args = Args::default();
6 changes: 6 additions & 0 deletions src/verbatim.rs
Original file line number Diff line number Diff line change
@@ -24,6 +24,12 @@ impl Verbatim {
}
}

impl Default for Verbatim {
fn default() -> Self {
Self::new()
}
}

/// Determine whether a line is in a verbatim environment
pub fn get_verbatim(
line: &str,
32 changes: 32 additions & 0 deletions src/wasm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use js_sys::{Object, Reflect};
use merge::Merge;
use std::path::PathBuf;
use wasm_bindgen::prelude::*;

use crate::args::*;
use crate::config::*;
use crate::format::*;
use crate::logging::*;

#[wasm_bindgen]
pub fn main(text: &str, config: &str) -> JsValue {
// Get args
let config = Some((PathBuf::new(), "".to_string(), config.to_string()));
let mut args: OptionArgs = get_config_args(config).unwrap();
args.merge(OptionArgs::default());
let mut args = Args::from(args);
args.stdin = true;

// Run tex-fmt
let mut logs = Vec::<Log>::new();
args.resolve(&mut logs);
let file = "input";
let new_text = format_file(text, file, &args, &mut logs);
let logs = format_logs(&mut logs, &args);

// Wrap into JS object
let js_object = Object::new();
Reflect::set(&js_object, &"output".into(), &new_text.into()).unwrap();
Reflect::set(&js_object, &"logs".into(), &logs.into()).unwrap();
js_object.into()
}
2 changes: 1 addition & 1 deletion src/write.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Utilities for writing formatted files
use crate::args::*;
use crate::fs;
use crate::logging::*;
use log::Level::Error;
use std::fs;
use std::path;

/// Write a formatted file to disk
54 changes: 54 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> tex-fmt </title>
</head>

<body>
<h1> tex-fmt </h1>
<h3> An extremely fast LaTex formatter </h3>

<!-- Buttons -->
<div>
<button id="submitButton">Format</button>
<button id="copyButton">Copy output to clipboard</button>
</div>

<!-- Input and output -->
<div style="display: flex; gap: 20px;">

<div style="flex: 1; display: flex; flex-direction: column; max-width: 600px;">
<h3> Input </h3>
<textarea id="textInput" rows="30" cols="80"></textarea>
</div>

<div style="flex: 1; display: flex; flex-direction: column; max-width: 600px;">
<h3> Output </h3>
<textarea id="textOutput" rows="30" cols="80" readonly></textarea>
</div>

</div>

<!-- Config and logs -->
<div style="display: flex; gap: 20px;">

<div style="flex: 1; display: flex; flex-direction: column; max-width: 600px;">
<h3> Config (optional) </h3>
<textarea id="textConfig" rows="20" cols="80"></textarea>
</div>

<div style="flex: 1; display: flex; flex-direction: column; max-width: 600px;">
<h3> Logs </h3>
<textarea id="textLog" rows="20" cols="80" readonly></textarea>
</div>

</div>

<!-- Import script -->
<script type="module" src="./index.js"></script>

</body>
</html>
46 changes: 46 additions & 0 deletions web/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Import wasm
import init, { main } from './pkg/tex_fmt.js';

// Initialize wasm
(async () => {
try {
await init();
console.log('WASM initialized successfully.');
} catch (error) {
console.error('Error initializing WASM :', error);
alert('Failed to initialize WASM. Check console for details.');
}
})();

// Submit button logic
document.getElementById('submitButton').addEventListener(
'click', async () => {
const inputText = document.getElementById('textInput').value;
const outputBox = document.getElementById('textOutput');
const logBox = document.getElementById('textLog');
try {
const configText = document.getElementById('textConfig').value;
const result = await main(inputText, configText);
outputBox.value = result.output;
logBox.value = result.logs;
} catch (error) {
console.error('Error calling WebAssembly function:', error);
alert('An error occurred. Check the console for details.');
}
}
);

// Copy output text to clipboard
document.getElementById('copyButton').addEventListener(
'click', () => {
const outputBox = document.getElementById('textOutput');
outputBox.select();
outputBox.setSelectionRange(0, 99999);
try {
document.execCommand('copy');
alert('Copied to clipboard:\n\n' + outputBox.value);
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
);

0 comments on commit a7651a4

Please sign in to comment.