| Crates.io | tdoc |
| lib.rs | tdoc |
| version | 0.9.2 |
| created_at | 2025-10-30 06:26:32.713387+00 |
| updated_at | 2026-01-11 06:50:58.839506+00 |
| description | Library and assorted CLI tools for working with FTML (Formatted Text Markup Language) documents |
| homepage | |
| repository | https://github.com/roblillack/tdoc |
| max_upload_size | |
| id | 1907771 |
| size | 6,258,452 |
A command-line tool and Rust library for handling all kinds of text documents (Markdown, HTML, Gemini, and FTML - Formatted Text Markup Language).
This project is a partial rewrite of the Go library available at https://github.com/roblillack/ftml, bringing FTML support to the Rust ecosystem with improved performance and memory safety.

tdoc is the unified CLI for viewing and exporting FTML, HTML, Markdown, and Gemini content.
When no input path is provided it reads from stdin. The output format is detected
from the --output/-o file extension.
# View a local FTML file with ANSI styling (defaults to a pager)
tdoc document.ftml
# View a local Markdown file with ANSI styling
tdoc notes.md
# View a local HTML file with ANSI styling
tdoc email.html
# View a local Gemini file with ANSI styling
tdoc capsule.gmi
# View from a URL
tdoc https://example.com/document.html
# Disable ANSI formatting (disables the pager and emits ASCII)
tdoc --no-ansi document.ftml
# Read from stdin (defaults to FTML)
cat document.ftml | tdoc
# Force the input format for stdin/unknown extensions
cat notes.md | tdoc --input-format markdown
# Export to different formats (extension determines the output)
tdoc paper.ftml --output paper.md # Markdown
tdoc paper.ftml --output paper.ftml # FTML
tdoc paper.ftml --output paper.html # HTML
tdoc paper.ftml --output paper.gmi # Gemini
tdoc paper.ftml --output paper.txt # Wrapped ASCII text
FTML (Formatted Text Markup Language) is a lightweight document format designed for simplicity and ease of processing. As a strict subset of HTML5, it remains fully compatible with standard web technologies while being far easier to parse and work with programmatically. FTML provides the essential features needed for rich text documents—such as paragraph structures, headings, lists, and inline styles—without the complexity of full HTML or Markdown. It’s ideal for straightforward text content like emails, memos, notes, and help documentation.
Key features:
For the full FTML specification, see the original repository.
tdoc provides a comprehensive toolkit for working with FTML documents in Rust:
code, clickable links and all supported paragraph typesftml! macro for ergonomic test fixtures and examplesFTML documents consist of a hierarchy of elements:
<p>)<h1>, <h2>, <h3>)<pre>)<ol>) or unordered (<ul>)<ul> whose items begin with checkboxes, or Markdown - [ ] task lists)<blockquote>)Task lists are a special kind of unordered list whose entries start with checkbox inputs (HTML/FTML) or Markdown’s - [ ] syntax. tdoc now keeps those entries intact—including deeply nested child tasks—across the parser, Markdown/HTML writers, and the terminal formatter. That means you can read Markdown like this:
- [x] Ship release
- [ ] Update screenshots
- [x] Publish announcement
<pre> elements and emitted via the code { "..." } block in the ftml! macro.---- separators with hard character-level wrapping.Text spans can have optional styles:
<b>)<i>)<u>)<s>)<mark>)<code>)<a href="...">)FormattingStyle::link_index_format.mailto: links with matching descriptions reuse their text instead of adding redundant indices.<h1>This <i>very</i> simple example shows ...</h1>
<p>How FTML really is this:</p>
<ul>
<li><p>A <mark>strict</mark> subset of HTML,</p></li>
<li><p>That is <b>easy</b> to wrap your head around.</p></li>
</ul>
use tdoc::{parse, Document};
use std::fs::File;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse from a file
let file = File::open("document.ftml")?;
let document = parse(file)?;
// Access document structure
for paragraph in &document.paragraphs {
println!("Paragraph type: {}", paragraph.paragraph_type());
}
Ok(())
}
use tdoc::{write, Document, Paragraph, Span};
use std::io::stdout;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a document programmatically
let mut doc = Document::new();
let paragraph = Paragraph::new_text()
.with_content(vec![
Span::new_text("Hello, "),
Span::new_styled(tdoc::InlineStyle::Bold)
.with_children(vec![Span::new_text("world!")]),
]);
doc.add_paragraph(paragraph);
// Write to stdout
write(&mut stdout(), &doc)?;
Ok(())
}
ftml! macrouse tdoc::{ftml, write};
fn main() -> tdoc::Result<()> {
// Compose a document inline, similar to RSX or JSX
let doc = ftml! {
h1 { "Hello World!" }
ul {
li {
p { "This is a text paragraph inside a list item" }
quote { p { "And this is a quoted paragraph in the same item" } }
}
}
p { "Inline styles work " b { "just as well" } "." }
code {
"fn main() {\n"
" println!(\"Hello from FTML!\");\n"
"}\n"
}
};
write(&mut std::io::stdout(), &doc)?;
Ok(())
}
use tdoc::{parse, markdown};
use std::fs::File;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = File::open("document.ftml")?;
let document = parse(file)?;
// Export to Markdown
markdown::write(&mut std::io::stdout(), &document)?;
Ok(())
}
use tdoc::gemini;
use std::fs::File;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse a Gemini file
let file = File::open("capsule.gmi")?;
let document = gemini::parse(file)?;
// Export to Gemini
gemini::write(&mut std::io::stdout(), &document)?;
Ok(())
}
use tdoc::html;
use std::fs::File;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = File::open("document.html")?;
let document = html::parse(file)?;
// Now you have an FTML document
println!("Parsed {} paragraphs", document.paragraphs.len());
Ok(())
}
This Rust implementation is a work in progress. Here's how it compares to the Go version:
| Feature | Rust (tdoc) | Go (ftml) | Notes |
|---|---|---|---|
| Core Library | |||
| FTML Parsing | ✅ Full | ✅ Full | Both implementations complete |
| FTML Writing | ✅ Full | ✅ Full | Both implementations complete |
| Terminal Rendering | |||
| ASCII Support | ✅ Full | ✅ Full | Both implementations complete |
| ANSI Support | ✅ Full | ✅ Full | Both implementations complete |
| Import/Export | |||
| Markdown Import | ✅ Full | ❌ Planned | Only Rust version has implementation |
| Markdown Export | ✅ Full | ✅ Full | Both implementations complete |
| Gemini Import | ✅ Full | ❌ None | Only Rust version has implementation |
| Gemini Export | ✅ Full | ❌ None | Only Rust version has implementation |
| HTML Import | ✅ Full | ✅ Full | Both implementations complete |
| HTML Export | ⚠️ Basic | ✅ Full | tdoc wraps canonical FTML in HTML |
| CLI Tools | |||
| Document Viewer | ✅ tdoc |
✅ viewftml |
Both with terminal formatting |
| Format Converter | ✅ tdoc |
✅ ftml2md |
Go version only supports FTML to Markdown |
| Formatter | ✅ tdoc |
✅ ftmlfmt |
Both support FTML formatting |
| Advanced Features | |||
| URL Fetching | ✅ Yes | ✅ Yes | tdoc & viewftml can fetch from URLs |
| Paged Output | ✅ Yes | ✅ Yes | Both support pager integration |
# Ensure the OpenSSL/LibreSSL headers are available, for example:
sudo apt install libssl-dev
# Build the library and all tools
cargo build --release
# Run tests
cargo test
# Build specific binary
cargo build --release --bin tdoc
MIT
This is a work in progress. Contributions are welcome! Please see the original FTML repository for the specification details.