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" name = "broot"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"indexmap",
"open",
"pulldown-cmark", "pulldown-cmark",
"serde", "serde",
"serde_json", "serde_json",
] ]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]] [[package]]
name = "getopts" name = "getopts"
version = "0.2.21" version = "0.2.21"
@ -26,18 +34,82 @@ dependencies = [
"unicode-width", "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]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.95"

View file

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

View file

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