hostcraft-core
The shared foundation powering the hostcraft ecosystem — a suite of tools for managing your system hosts file (/etc/hosts on macOS/Linux, C:\Windows\System32\drivers\etc\hosts on Windows). This crate contains all parsing, data modelling, and file I/O logic, deliberately free of any presentation or platform concerns so it can be consumed equally by a CLI, a desktop GUI, or any other Rust application.
Ecosystem
| Crate | Description | Status |
|---|---|---|
hostcraft-core |
Shared library (this crate) | ✅ Published |
hostcraft-cli |
Terminal interface | ✅ Published |
hostcraft-gui |
Desktop GUI (Tauri) | 🚧 Planned |
Every consumer in the ecosystem depends on this crate for its data types and business logic. Presentation — colours, layout, widgets — is always the consumer's responsibility.
Design Philosophy
- No I/O surprises — the library never prints to stdout or stderr. It returns data and
Resulttypes; what you do with them is up to you. - Partial name matching —
removeandtoggleoperate on substrings, so"myapp"matchesmyapp.local,myapp.dev, and so on. - Active / Inactive model — entries are never silently deleted to disable them. Inactive entries are preserved as commented-out lines (
# ip hostname) so they can be re-enabled at any time. - Duplicate safety — adding an entry that shares both an IP and a hostname with an existing one is rejected before any write occurs.
- IPv4 and IPv6 — all IP handling goes through Rust's standard
IpAddr, so both address families work out of the box.
Installation
[]
= "0.1.6"
Quick Start
use ;
use ;
use IpAddr;
// 1. Read and parse
let lines = read_file.expect;
let mut entries = parse_contents;
// 2. Manipulate
let ip: IpAddr = "127.0.0.1".parse.unwrap;
add_entry.expect;
// 3. Write back
write_file.expect;
Note: Writing to
/etc/hostsrequires elevated privileges (sudoon macOS/Linux). Your application is responsible for requesting or documenting this requirement.
Usage
Reading and parsing
read_file returns a lazy line iterator — nothing is loaded into memory until parse_contents consumes it.
use read_file;
use parse_contents;
let lines = read_file.expect;
let entries = parse_contents;
// HostEntry implements Display: "127.0.0.1: example.com is Active"
for entry in &entries
Lines that do not match a valid host pattern (pure comments, blank lines, malformed entries) are silently skipped during parsing. This mirrors how the OS itself handles the file.
Adding an entry
New entries are always added as Active. Adding a duplicate (same IP and same hostname) returns Err(HostError::DuplicateEntry) without modifying the list.
use ;
use IpAddr;
let ip: IpAddr = "192.168.1.10".parse.unwrap;
match add_entry
Removing an entry
Matches by substring — all entries whose hostname contains partial_name are removed. Returns Err(HostError::EntryNotFound) if nothing matched.
use ;
match remove_entry
Toggling an entry
Toggling flips an entry between active and inactive without removing it:
127.0.0.1 myapp.local → # 127.0.0.1 myapp.local
# 127.0.0.1 myapp.local → 127.0.0.1 myapp.local
All entries matching the partial name are toggled in one call.
use ;
match toggle_entry
Writing back
write_file truncates and rewrites the file. Active entries are written as plain lines; inactive entries are written as commented-out lines.
use write_file;
write_file.expect;
API Reference
HostEntry
The core data type. Represents one parsed line from the hosts file.
| Field | Type | Description |
|---|---|---|
status |
HostStatus |
Whether the entry is active or inactive |
ip |
IpAddr |
The IP address — IPv4 or IPv6 |
name |
String |
The hostname |
Methods
| Method | Description |
|---|---|
toggle() |
Flips Active ↔ Inactive in place |
Trait implementations: Debug, Clone, Display ("ip: name is Status")
HostStatus
Trait implementations: Debug, Clone, PartialEq, Display ("Active" / "Inactive")
HostError
Trait implementations: Debug, Display, std::error::Error
host module — functions
| Function | Signature | Description |
|---|---|---|
parse_contents |
(impl Iterator<Item = io::Result<String>>) -> Vec<HostEntry> |
Parses a line iterator into a list of entries |
add_entry |
(&mut Vec<HostEntry>, IpAddr, impl Into<String>) -> Result<(), HostError> |
Adds an active entry; rejects duplicates |
remove_entry |
(&mut Vec<HostEntry>, &str) -> Result<(), HostError> |
Removes all entries matching the partial name |
toggle_entry |
(&mut Vec<HostEntry>, &str) -> Result<(), HostError> |
Toggles all entries matching the partial name |
file module — functions
| Function | Signature | Description |
|---|---|---|
read_file |
(impl AsRef<Path>) -> io::Result<Lines<BufReader<File>>> |
Opens the hosts file and returns a line iterator |
write_file |
(impl AsRef<Path>, &[HostEntry]) -> io::Result<()> |
Serialises entries and writes them to the file |
Building for Consumers
If you are building a consumer on top of hostcraft-core, the typical pattern is:
- Read with
file::read_file - Parse with
host::parse_contents→Vec<HostEntry> - Mutate using
add_entry/remove_entry/toggle_entry - Present however your layer requires — the types all implement
DisplayandDebug - Write back with
file::write_file
The library holds no global state and makes no assumptions about the platform, runtime, or output format.
License
MIT — see LICENSE for details.