Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7c55d75
feat: add lis all remote templates
JheisonMB Apr 1, 2026
78cc53f
Merge pull request #3 from JheisonMB/feature/improve-fmt-templates-co…
JheisonMB Apr 1, 2026
1de4271
feat: add texforge init command to migrate existing LaTeX projects
JheisonMB Apr 1, 2026
cbaa718
feat: detect \lstinputlisting and \inputminted missing files in linter
JheisonMB Apr 1, 2026
4f1fce3
feat: auto-install tectonic on first build if not found
JheisonMB Apr 1, 2026
58ff966
feat: add install.ps1 for Windows and simplify install.sh (tectonic a…
JheisonMB Apr 1, 2026
342f694
docs: update README with init command, template list --all, and tecto…
JheisonMB Apr 1, 2026
dd99641
fix: resolve clippy warnings in current_target cfg blocks
JheisonMB Apr 1, 2026
3c8cc3b
style: apply cargo fmt
JheisonMB Apr 1, 2026
1d30dda
Merge pull request #4 from JheisonMB/feature/add-init-command
JheisonMB Apr 1, 2026
6eb7196
feat: render embedded mermaid diagrams to PNG before tectonic compila…
JheisonMB Apr 1, 2026
6048723
fix: process diagrams on build/ copies, preserve originals, support f…
JheisonMB Apr 1, 2026
9bfc6c5
fix: add float package to embedded general template for mermaid diagr…
JheisonMB Apr 1, 2026
eff6af5
fix: validate mermaid pos option against valid float placements
JheisonMB Apr 1, 2026
efba766
fix: mirror assets to build/, exclude build/ from fmt, add clean comm…
JheisonMB Apr 1, 2026
f43f7b5
chore: add init and clean command modules to mermaid-support branch
JheisonMB Apr 1, 2026
d74155d
refactor: use symlinks for asset dirs in build/ (fallback to copy on …
JheisonMB Apr 1, 2026
8434843
chore: bump version to 0.2.0
JheisonMB Apr 1, 2026
70742df
test: add unit tests for formatter, linter, new command and diagrams …
JheisonMB Apr 1, 2026
91dd491
chore: apply fmt and restore staged changes from stash
JheisonMB Apr 1, 2026
864ab19
fix: resolve clippy warnings in current_target cfg blocks
JheisonMB Apr 1, 2026
8d2aaeb
chore: restore correct compiler with auto-install and clippy clean
JheisonMB Apr 1, 2026
aff9d53
test: add unit tests for formatter, linter, new command and diagrams …
JheisonMB Apr 1, 2026
ac53aea
style: apply cargo fmt to test code
JheisonMB Apr 1, 2026
71f4e5d
fix: resolve clippy warnings (unused var, doc backticks, let-else)
JheisonMB Apr 1, 2026
e335ac3
Merge pull request #5 from JheisonMB/feature/mermaid-support
JheisonMB Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
* text=auto eol=lf
install.sh text eol=lf
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "texforge"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
rust-version = "1.75"
description = "Self-contained LaTeX to PDF compiler CLI"
Expand Down Expand Up @@ -31,11 +31,16 @@ walkdir = "2.5"
dirs = "6.0"

# HTTP client (template downloads)
reqwest = { version = "0.13", features = ["blocking"] }
reqwest = { version = "0.13", features = ["blocking", "json"] }

# Archive extraction (GitHub tarballs)
# Archive extraction (GitHub tarballs + tectonic zip on Windows)
flate2 = "1.1"
tar = "0.4"
zip = { version = "2", default-features = false, features = ["deflate"] }

# Mermaid diagram rendering
mermaid-rs-renderer = { version = "0.2", default-features = false }
resvg = "0.46"

[dev-dependencies]
tempfile = "3.8"
Expand Down
33 changes: 22 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ Self-contained LaTeX to PDF compiler — one curl, zero friction. No TeX Live, n

### Quick install (recommended)

**Linux / macOS:**

```bash
curl -fsSL https://raw.githubusercontent.com/JheisonMB/texforge/main/install.sh | sh
```

This downloads and installs both `texforge` and `tectonic` (the LaTeX engine). Nothing else needed. No Rust toolchain required.
**Windows (PowerShell):**

```powershell
irm https://raw.githubusercontent.com/JheisonMB/texforge/main/install.ps1 | iex
```

This downloads and installs `texforge`. No Rust toolchain required. Tectonic (the LaTeX engine) is installed automatically on first build.

You can customize the install:

Expand All @@ -29,17 +37,18 @@ VERSION=0.1.0 curl -fsSL https://raw.githubusercontent.com/JheisonMB/texforge/ma
INSTALL_DIR=/usr/local/bin curl -fsSL https://raw.githubusercontent.com/JheisonMB/texforge/main/install.sh | sh
```

```powershell
# Pin a specific version (PowerShell)
$env:VERSION="0.1.0"; irm https://raw.githubusercontent.com/JheisonMB/texforge/main/install.ps1 | iex
```

### Via cargo

```bash
cargo install texforge
```

**Note:** This only installs texforge. You also need tectonic for compilation:

```bash
cargo install tectonic
```
Tectonic (the LaTeX engine) is installed automatically on first build. No extra steps needed.

Available on [crates.io](https://crates.io/crates/texforge).

Expand Down Expand Up @@ -72,6 +81,9 @@ texforge fmt

# Build to PDF
texforge build

# Remove build artifacts
texforge clean
```

---
Expand All @@ -82,11 +94,14 @@ texforge build
|---|---|
| `texforge new <name>` | Create new project from template |
| `texforge new <name> -t <template>` | Create with specific template |
| `texforge init` | Initialize texforge in an existing LaTeX project |
| `texforge build` | Compile to PDF |
| `texforge clean` | Remove build artifacts |
| `texforge fmt` | Format .tex files |
| `texforge fmt --check` | Check formatting without modifying |
| `texforge check` | Lint without compiling |
| `texforge template list` | List installed templates |
| `texforge template list --all` | List installed + available in registry |
| `texforge template add <name>` | Download template from registry |
| `texforge template remove <name>` | Remove installed template |
| `texforge template validate <name>` | Verify template compatibility |
Expand Down Expand Up @@ -197,7 +212,7 @@ texforge fmt --check # check without modifying (CI-friendly)
```
~/.texforge/
bin/
tectonic # LaTeX engine (installed by install.sh)
tectonic # LaTeX engine (auto-installed on first build)
templates/
general/ # Cached templates
apa-general/
Expand Down Expand Up @@ -231,10 +246,6 @@ texforge fmt --check # check without modifying (CI-friendly)

---

## Roadmap

See [texforge-spec.md](texforge-spec.md) for the complete specification and roadmap.

---

## License
Expand Down
77 changes: 77 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# install.ps1 — download and install texforge on Windows
# tectonic (LaTeX engine) is installed automatically on first build
# Usage: irm https://raw.githubusercontent.com/JheisonMB/texforge/main/install.ps1 | iex
#
# Options (set as env vars before running):
# $env:VERSION = "0.1.0" # pin a specific version
# $env:INSTALL_DIR = "C:\my\bin" # custom install directory

$ErrorActionPreference = "Stop"

$Repo = "JheisonMB/texforge"
$Binary = "texforge.exe"
$Target = "x86_64-pc-windows-msvc"
$InstallDir = if ($env:INSTALL_DIR) { $env:INSTALL_DIR } else { "$env:USERPROFILE\.local\bin" }

function Info($label, $msg) {
Write-Host " " -NoNewline
Write-Host $label -ForegroundColor Blue -NoNewline
Write-Host " $msg"
}

function Fail($msg) {
Write-Host " error: $msg" -ForegroundColor Red
exit 1
}

# --- resolve version ---
if ($env:VERSION) {
$Tag = "v$($env:VERSION)"
Info "version" "$Tag (pinned)"
} else {
$latest = Invoke-RestMethod "https://api.github.com/repos/$Repo/releases/latest"
$Tag = $latest.tag_name
if (-not $Tag) { Fail "Could not resolve latest release tag" }
Info "version" "$Tag (latest)"
}

# --- download ---
$Archive = "texforge-$Tag-$Target.zip"
$Url = "https://github.com/$Repo/releases/download/$Tag/$Archive"
$Tmp = Join-Path $env:TEMP "texforge-install"
New-Item -ItemType Directory -Force -Path $Tmp | Out-Null

Info "download" $Url
try {
Invoke-WebRequest -Uri $Url -OutFile "$Tmp\$Archive" -UseBasicParsing
} catch {
Fail "Download failed: $_`nURL: $Url"
}

# --- extract ---
Expand-Archive -Path "$Tmp\$Archive" -DestinationPath $Tmp -Force
$extracted = Join-Path $Tmp $Binary
if (-not (Test-Path $extracted)) { Fail "Binary not found in archive" }

# --- install ---
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
Copy-Item $extracted "$InstallDir\$Binary" -Force
Info "installed" "$InstallDir\$Binary"

# --- ensure PATH ---
$userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($userPath -notlike "*$InstallDir*") {
[Environment]::SetEnvironmentVariable("PATH", "$InstallDir;$userPath", "User")
$env:PATH = "$InstallDir;$env:PATH"
Info "updated" "User PATH"
}

# --- cleanup ---
Remove-Item $Tmp -Recurse -Force

# --- verify ---
$ver = & "$InstallDir\$Binary" --version 2>$null
Info "done" $ver
Write-Host ""
Info "ready" "Run 'texforge new my-project' to get started!"
Info "note" "tectonic (LaTeX engine) will be installed automatically on first build"
45 changes: 6 additions & 39 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
#!/bin/sh
# install.sh — download and install texforge + tectonic from GitHub Releases
# install.sh — download and install texforge from GitHub Releases
# tectonic (LaTeX engine) is installed automatically on first build
# Usage: curl -fsSL https://raw.githubusercontent.com/JheisonMB/texforge/main/install.sh | sh
set -eu

REPO="JheisonMB/texforge"
BINARY="texforge"
TECTONIC_VERSION="0.15.0+20251006"
INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"
TEXFORGE_DIR="$HOME/.texforge/bin"

info() { printf ' \033[1;34m%s\033[0m %s\n' "$1" "$2"; }
error() { printf ' \033[1;31merror:\033[0m %s\n' "$1" >&2; exit 1; }
Expand Down Expand Up @@ -35,34 +34,7 @@ TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT

# ============================================================
# 1. Install tectonic (LaTeX engine)
# ============================================================

if command -v tectonic >/dev/null 2>&1; then
info "tectonic" "already installed ($(tectonic --version 2>/dev/null || echo 'unknown version'))"
elif [ -x "$TEXFORGE_DIR/tectonic" ]; then
info "tectonic" "already installed at $TEXFORGE_DIR/tectonic"
else
info "tectonic" "installing v${TECTONIC_VERSION}..."

TECTONIC_ARCHIVE="tectonic-${TECTONIC_VERSION}-${TARGET}.tar.gz"
TECTONIC_URL="https://github.com/tectonic-typesetting/tectonic/releases/download/continuous/${TECTONIC_ARCHIVE}"

info "download" "$TECTONIC_URL"
HTTP_CODE=$(curl -fSL -w '%{http_code}' -o "$TMPDIR/$TECTONIC_ARCHIVE" "$TECTONIC_URL" 2>/dev/null) || true
[ "$HTTP_CODE" = "200" ] || error "Tectonic download failed (HTTP $HTTP_CODE). URL:\n $TECTONIC_URL"

tar xzf "$TMPDIR/$TECTONIC_ARCHIVE" -C "$TMPDIR"
[ -f "$TMPDIR/tectonic" ] || error "Tectonic binary not found in archive"

mkdir -p "$TEXFORGE_DIR"
mv "$TMPDIR/tectonic" "$TEXFORGE_DIR/tectonic"
chmod +x "$TEXFORGE_DIR/tectonic"
info "installed" "$TEXFORGE_DIR/tectonic"
fi

# ============================================================
# 2. Install texforge
# 1. Install texforge
# ============================================================

# --- resolve latest version ---
Expand Down Expand Up @@ -94,18 +66,14 @@ chmod +x "$INSTALL_DIR/$BINARY"
info "installed" "$INSTALL_DIR/$BINARY"

# ============================================================
# 3. Ensure PATH includes both directories
# 2. Ensure PATH
# ============================================================

PATHS_TO_ADD=""
case ":$PATH:" in
*":$INSTALL_DIR:"*) ;;
*) PATHS_TO_ADD="$INSTALL_DIR" ;;
esac
case ":$PATH:" in
*":$TEXFORGE_DIR:"*) ;;
*) PATHS_TO_ADD="$PATHS_TO_ADD $TEXFORGE_DIR" ;;
esac

if [ -n "$PATHS_TO_ADD" ]; then
for dir in $PATHS_TO_ADD; do
Expand All @@ -125,11 +93,10 @@ if [ -n "$PATHS_TO_ADD" ]; then
fi

# ============================================================
# 4. Verify
# 3. Verify
# ============================================================

info "done" "$($INSTALL_DIR/$BINARY --version 2>/dev/null || echo "$BINARY installed")"
TECTONIC_BIN=$(command -v tectonic 2>/dev/null || echo "$TEXFORGE_DIR/tectonic")
info "tectonic" "$($TECTONIC_BIN --version 2>/dev/null || echo "installed")"
echo ""
info "ready" "Run 'texforge new my-project' to get started!"
info "note" "tectonic (LaTeX engine) will be installed automatically on first build"
14 changes: 12 additions & 2 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ pub struct Cli {

#[derive(Subcommand)]
enum Commands {
/// Remove build artifacts
Clean,
/// Initialize a texforge project in the current directory
Init,
/// Create a new project from a template
New {
/// Project name
Expand Down Expand Up @@ -43,7 +47,11 @@ enum Commands {
#[derive(Subcommand)]
enum TemplateAction {
/// List available templates
List,
List {
/// Also show templates available in the remote registry
#[arg(long)]
all: bool,
},
/// Add a template from URL or registry
Add { source: String },
/// Remove a template
Expand All @@ -55,12 +63,14 @@ enum TemplateAction {
impl Cli {
pub fn execute(self) -> Result<()> {
match self.command {
Commands::Clean => commands::clean::execute(),
Commands::Init => commands::init::execute(),
Commands::New { name, template } => commands::new::execute(&name, template.as_deref()),
Commands::Build => commands::build::execute(),
Commands::Fmt { check } => commands::fmt::execute(check),
Commands::Check => commands::check::execute(),
Commands::Template { action } => match action {
TemplateAction::List => commands::template::list(),
TemplateAction::List { all } => commands::template::list(all),
TemplateAction::Add { source } => commands::template::add(&source),
TemplateAction::Remove { name } => commands::template::remove(&name),
TemplateAction::Validate { name } => commands::template::validate(&name),
Expand Down
13 changes: 11 additions & 2 deletions src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use anyhow::Result;

use crate::compiler;
use crate::diagrams;
use crate::domain::project::Project;

/// Compile project to PDF.
Expand All @@ -11,10 +12,18 @@ pub fn execute() -> Result<()> {

println!("Building project: {}", project.config.documento.titulo);

// Ensure build directory exists
std::fs::create_dir_all(project.root.join("build"))?;

compiler::compile(&project.root, &project.config.compilacion.entry)?;
// Pre-process embedded diagrams — works on copies in build/, originals untouched
diagrams::process(&project.root, &project.config.compilacion.entry)?;

// Compile from build/ — all assets are mirrored there, diagrams use relative paths
let build_dir = project.root.join("build");
let entry_filename = std::path::Path::new(&project.config.compilacion.entry)
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or(project.config.compilacion.entry.clone());
compiler::compile(&build_dir, &entry_filename)?;

let pdf_name = std::path::Path::new(&project.config.compilacion.entry).with_extension("pdf");
println!("✅ build/{}", pdf_name.display());
Expand Down
20 changes: 20 additions & 0 deletions src/commands/clean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! `texforge clean` command implementation.

use anyhow::Result;

use crate::domain::project::Project;

/// Remove the build/ directory.
pub fn execute() -> Result<()> {
let project = Project::load()?;
let build_dir = project.root.join("build");

if !build_dir.exists() {
println!("Nothing to clean.");
return Ok(());
}

std::fs::remove_dir_all(&build_dir)?;
println!("✅ build/ removed");
Ok(())
}
Loading
Loading