hostcraft-core 0.1.8

Core library for the hostcraft ecosystem — parsing, manipulation and file I/O for system hosts
Documentation

hostcraft-core

Crates.io License: MIT Rust

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 Result types; what you do with them is up to you.
  • Partial name matchingremove and toggle operate on substrings, so "myapp" matches myapp.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

[dependencies]
hostcraft-core = "0.1.6"

Quick Start

use hostcraft_core::file::{read_file, write_file};
use hostcraft_core::host::{add_entry, parse_contents, toggle_entry};
use std::net::IpAddr;

// 1. Read and parse
let lines = read_file("/etc/hosts").expect("Failed to read hosts file");
let mut entries = parse_contents(lines);

// 2. Manipulate
let ip: IpAddr = "127.0.0.1".parse().unwrap();
add_entry(&mut entries, ip, "myapp.local".to_string()).expect("Failed to add entry");

// 3. Write back
write_file("/etc/hosts", &entries).expect("Failed to write hosts file");

Note: Writing to /etc/hosts requires elevated privileges (sudo on 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 hostcraft_core::file::read_file;
use hostcraft_core::host::parse_contents;

let lines = read_file("/etc/hosts").expect("Failed to read hosts file");
let entries = parse_contents(lines);

// HostEntry implements Display: "127.0.0.1: example.com is Active"
for entry in &entries {
    println!("{}", entry);
}

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 hostcraft_core::host::{add_entry, HostError};
use std::net::IpAddr;

let ip: IpAddr = "192.168.1.10".parse().unwrap();

match add_entry(&mut entries, ip, "myapp.local".to_string()) {
    Ok(())                        => println!("Entry added"),
    Err(HostError::DuplicateEntry) => println!("Already exists"),
    Err(e)                        => println!("Error: {}", e),
}

Removing an entry

Matches by substring — all entries whose hostname contains partial_name are removed. Returns Err(HostError::EntryNotFound) if nothing matched.

use hostcraft_core::host::{remove_entry, HostError};

match remove_entry(&mut entries, "myapp") {
    Ok(())                       => println!("Removed"),
    Err(HostError::EntryNotFound) => println!("No match found"),
    Err(e)                       => println!("Error: {}", e),
}

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 hostcraft_core::host::{toggle_entry, HostError};

match toggle_entry(&mut entries, "myapp") {
    Ok(())                       => println!("Toggled"),
    Err(HostError::EntryNotFound) => println!("No match found"),
    Err(e)                       => println!("Error: {}", e),
}

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 hostcraft_core::file::write_file;

write_file("/etc/hosts", &entries).expect("Failed to write hosts file");

API Reference

HostEntry

The core data type. Represents one parsed line from the hosts file.

pub struct HostEntry {
    pub status: HostStatus,
    pub ip:     IpAddr,
    pub name:   String,
}
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 ActiveInactive in place

Trait implementations: Debug, Clone, Display ("ip: name is Status")


HostStatus

pub enum HostStatus {
    Active,   // Written as:   127.0.0.1 hostname
    Inactive, // Written as: # 127.0.0.1 hostname
}

Trait implementations: Debug, Clone, PartialEq, Display ("Active" / "Inactive")


HostError

pub enum HostError {
    DuplicateEntry, // add_entry: same IP + hostname already exists
    EntryNotFound,  // remove_entry / toggle_entry: no name matched
}

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:

  1. Read with file::read_file
  2. Parse with host::parse_contentsVec<HostEntry>
  3. Mutate using add_entry / remove_entry / toggle_entry
  4. Present however your layer requires — the types all implement Display and Debug
  5. 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.