Skip to content

Commit 31ce914

Browse files
feat: Python client interface, tests, bump to v0.4.0 (#2)
Expose Config, UrlEntry, and TomlStorage to Python via PyO3 for programmatic bookmark management. Add CLI integration tests, config error-path tests, and Python tests (67 total across Rust + Python). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b3b7b35 commit 31ce914

File tree

18 files changed

+945
-180
lines changed

18 files changed

+945
-180
lines changed

Cargo.lock

Lines changed: 183 additions & 80 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/check-py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ echo "Running ruff..."
66
uv run --only-group dev ruff check .
77
uv run --only-group dev ruff format --check .
88

9+
echo "Building Python bindings for type checking..."
10+
uv run maturin develop
11+
912
echo "Running ty..."
10-
uv run --only-group dev ty check
13+
uv run ty check
1114

1215
echo "Python checks passed!"

crates/bookmarks-app/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dkdc-bookmarks-app"
3-
version = "0.3.3"
3+
version = "0.4.0"
44
edition = "2024"
55
authors = ["Cody <cody@dkdc.dev>"]
66
description = "Desktop app for bookmarks (iced)"
@@ -13,7 +13,7 @@ name = "bookmarks_app"
1313
path = "src/lib.rs"
1414

1515
[dependencies]
16-
dkdc-bookmarks-core = { version = "0.3.3", path = "../bookmarks-core" }
16+
dkdc-bookmarks-core = { version = "0.4.0", path = "../bookmarks-core" }
1717
iced = { version = "0.14", features = ["tokio", "svg"] }
1818
open = "5"
1919
png = "0.17"

crates/bookmarks-cli/Cargo.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dkdc-bookmarks"
3-
version = "0.3.3"
3+
version = "0.4.0"
44
edition = "2024"
55
authors = ["Cody <cody@dkdc.dev>"]
66
description = "Bookmarks in your filesystem"
@@ -23,7 +23,10 @@ webapp = ["dep:dkdc-bookmarks-webapp"]
2323

2424
[dependencies]
2525
anyhow = "1"
26-
dkdc-bookmarks-core = { version = "0.3.3", path = "../bookmarks-core" }
27-
dkdc-bookmarks-app = { version = "0.3.3", path = "../bookmarks-app", optional = true }
28-
dkdc-bookmarks-webapp = { version = "0.3.3", path = "../bookmarks-webapp", optional = true }
26+
dkdc-bookmarks-core = { version = "0.4.0", path = "../bookmarks-core" }
27+
dkdc-bookmarks-app = { version = "0.4.0", path = "../bookmarks-app", optional = true }
28+
dkdc-bookmarks-webapp = { version = "0.4.0", path = "../bookmarks-webapp", optional = true }
2929
clap = { version = "4.5", features = ["derive"] }
30+
31+
[dev-dependencies]
32+
tempfile = "3"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use std::fs;
2+
use std::io::Write;
3+
4+
use tempfile::tempdir;
5+
6+
const TEST_CONFIG: &str = r#"[urls]
7+
github = { url = "https://github.com", aliases = ["gh"] }
8+
dkdc-bookmarks = "https://github.com/lostmygithubaccount/bookmarks"
9+
10+
[groups]
11+
dev = ["gh", "dkdc-bookmarks"]
12+
"#;
13+
14+
fn write_config(dir: &std::path::Path) -> std::path::PathBuf {
15+
let path = dir.join("bookmarks.toml");
16+
let mut f = fs::File::create(&path).unwrap();
17+
f.write_all(TEST_CONFIG.as_bytes()).unwrap();
18+
path
19+
}
20+
21+
#[test]
22+
fn test_print_config() {
23+
let dir = tempdir().unwrap();
24+
let path = write_config(dir.path());
25+
let result = bookmarks::run_cli(["bookmarks", "-f", path.to_str().unwrap()]);
26+
assert!(result.is_ok());
27+
}
28+
29+
#[test]
30+
fn test_file_not_found() {
31+
let result = bookmarks::run_cli(["bookmarks", "-f", "/nonexistent/bookmarks.toml"]);
32+
assert!(result.is_err());
33+
let err = result.unwrap_err().to_string();
34+
assert!(err.contains("not found"), "unexpected error: {err}");
35+
}
36+
37+
#[test]
38+
fn test_unknown_bookmark() {
39+
let dir = tempdir().unwrap();
40+
let path = write_config(dir.path());
41+
let result = bookmarks::run_cli(["bookmarks", "-f", path.to_str().unwrap(), "nonexistent"]);
42+
assert!(result.is_err());
43+
}
44+
45+
#[test]
46+
fn test_local_creates_config() {
47+
let dir = tempdir().unwrap();
48+
let config_path = dir.path().join("bookmarks.toml");
49+
assert!(!config_path.exists());
50+
51+
// Set cwd to temp dir so --local creates bookmarks.toml there
52+
let original_dir = std::env::current_dir().unwrap();
53+
std::env::set_current_dir(dir.path()).unwrap();
54+
let result = bookmarks::run_cli(["bookmarks", "--local"]);
55+
std::env::set_current_dir(original_dir).unwrap();
56+
57+
assert!(result.is_ok());
58+
assert!(config_path.exists());
59+
}

crates/bookmarks-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dkdc-bookmarks-core"
3-
version = "0.3.3"
3+
version = "0.4.0"
44
edition = "2024"
55
authors = ["Cody <cody@dkdc.dev>"]
66
description = "Core library for bookmarks"

crates/bookmarks-core/src/config.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ impl UrlEntry {
7777
}
7878
}
7979

80-
#[derive(Debug, Serialize, Deserialize, Default)]
80+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8181
pub struct Config {
8282
#[serde(default)]
8383
pub urls: HashMap<String, UrlEntry>,
@@ -752,4 +752,38 @@ dev = ["gh", "dkdc-bookmarks"]
752752
assert!(config.delete_alias("nope").is_err());
753753
assert!(config.delete_group("nope").is_err());
754754
}
755+
756+
#[test]
757+
fn test_parse_malformed_toml() {
758+
assert!(toml::from_str::<Config>("this is not valid { toml").is_err());
759+
}
760+
761+
#[test]
762+
fn test_parse_url_wrong_type() {
763+
let toml = "[urls]\ngithub = 42";
764+
assert!(toml::from_str::<Config>(toml).is_err());
765+
}
766+
767+
#[test]
768+
fn test_parse_missing_url_in_full_entry() {
769+
let toml = "[urls.gh]\naliases = [\"x\"]";
770+
assert!(toml::from_str::<Config>(toml).is_err());
771+
}
772+
773+
#[test]
774+
fn test_parse_groups_only_no_urls() {
775+
let toml = "[groups]\ndev = [\"gh\"]";
776+
let config: Config = toml::from_str(toml).unwrap();
777+
assert!(config.urls.is_empty());
778+
let warnings = config.validate();
779+
assert!(warnings.iter().any(|w| w.contains("gh")));
780+
}
781+
782+
#[test]
783+
fn test_parse_extra_sections_ignored() {
784+
let toml = "[urls]\ngithub = \"https://github.com\"\n\n[metadata]\nauthor = \"test\"";
785+
// Config doesn't use deny_unknown_fields, so extra sections are ignored
786+
let result = toml::from_str::<Config>(toml);
787+
assert!(result.is_ok());
788+
}
755789
}

crates/bookmarks-core/src/toml_storage.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,30 @@ dev = ["gh"]
163163
let storage = TomlStorage::new(PathBuf::from("/tmp/test.toml"));
164164
assert_eq!(storage.backend_name(), "toml");
165165
}
166+
167+
#[test]
168+
fn test_load_nonexistent_file() {
169+
let storage = TomlStorage::new(PathBuf::from("/nonexistent/path/bookmarks.toml"));
170+
assert!(storage.load().is_err());
171+
}
172+
173+
#[test]
174+
fn test_load_malformed_file() {
175+
let dir = tempfile::tempdir().unwrap();
176+
let path = dir.path().join("bookmarks.toml");
177+
fs::write(&path, "this is not valid { toml").unwrap();
178+
let storage = TomlStorage::new(path);
179+
assert!(storage.load().is_err());
180+
}
181+
182+
#[test]
183+
fn test_load_empty_file() {
184+
let dir = tempfile::tempdir().unwrap();
185+
let path = dir.path().join("bookmarks.toml");
186+
fs::write(&path, "").unwrap();
187+
let storage = TomlStorage::new(path);
188+
let config = storage.load().unwrap();
189+
assert!(config.urls.is_empty());
190+
assert!(config.groups.is_empty());
191+
}
166192
}

0 commit comments

Comments
 (0)