use indexmap crate for sorted output

This commit is contained in:
Troy 2025-05-22 00:59:04 +01:00
parent 928887c1a3
commit 40c40f83d6
Signed by: troy
GPG key ID: DFC06C02ED3B4711
3 changed files with 88 additions and 11 deletions

72
Cargo.lock generated
View file

@ -12,11 +12,19 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
name = "broot"
version = "0.0.1"
dependencies = [
"indexmap",
"open",
"pulldown-cmark",
"serde",
"serde_json",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "getopts"
version = "0.2.21"
@ -26,18 +34,82 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "hashbrown"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
[[package]]
name = "indexmap"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "is-docker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
dependencies = [
"once_cell",
]
[[package]]
name = "is-wsl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
dependencies = [
"is-docker",
"once_cell",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "open"
version = "5.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95"
dependencies = [
"is-wsl",
"libc",
"pathdiff",
]
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "proc-macro2"
version = "1.0.95"

View file

@ -6,6 +6,8 @@ description = "Markdown to JSON bookmark opener."
authors = ["Troy Lusty <hello@troylusty.com>"]
[dependencies]
indexmap = "2.9.0"
open = "5.3.2"
pulldown-cmark = "0.13.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"

View file

@ -1,6 +1,6 @@
use indexmap::IndexMap;
use pulldown_cmark::{Event, LinkType, Parser, Tag, TagEnd};
use serde::Serialize;
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::Write;
@ -13,27 +13,27 @@ struct Link {
}
fn main() -> std::io::Result<()> {
let path = env::args().nth(1).unwrap_or_else(|| {
eprintln!("❌ Usage: select_bookmark <input.md>");
std::process::exit(1);
});
let path = env::args()
.nth(1)
.expect("❌ Usage: select_bookmark <input.md>");
let markdown = fs::read_to_string(path)?;
let parser = Parser::new(&markdown);
let mut links_by_heading: HashMap<String, Vec<Link>> = HashMap::new();
let mut current_heading = "No Heading".to_string();
let mut links_by_heading: IndexMap<String, Vec<Link>> = IndexMap::new();
let mut current_heading = String::from("No Heading");
let mut heading_buf = String::new();
let mut current_link: Option<Link> = None;
for event in parser {
match event {
Event::Start(Tag::Heading { .. }) => heading_buf.clear(),
Event::End(TagEnd::Heading(_)) => {
if !heading_buf.trim().is_empty() {
current_heading = heading_buf.trim().to_string();
current_heading = heading_buf.trim().to_owned();
}
}
Event::Start(Tag::Link {
link_type: LinkType::Inline,
dest_url,
@ -44,6 +44,7 @@ fn main() -> std::io::Result<()> {
title: String::new(),
});
}
Event::End(TagEnd::Link) => {
if let Some(link) = current_link.take() {
links_by_heading
@ -52,6 +53,7 @@ fn main() -> std::io::Result<()> {
.push(link);
}
}
Event::Text(text) => {
if let Some(link) = current_link.as_mut() {
link.title.push_str(&text);
@ -59,6 +61,7 @@ fn main() -> std::io::Result<()> {
heading_buf.push_str(&text);
}
}
_ => {}
}
}
@ -92,11 +95,11 @@ fn main() -> std::io::Result<()> {
}
let output = child.wait_with_output()?;
let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
let selected = String::from_utf8_lossy(&output.stdout).trim().to_owned();
if let Some((_, url)) = entries.iter().find(|(label, _)| label == &selected) {
println!("🌐 Opening: {}", url);
Command::new("xdg-open").arg(url).spawn()?;
open::that(url)?;
} else {
println!("❌ Selection not found or canceled.");
}