convert_to_spaces/lib.rs
1#![forbid(unsafe_code)]
2#![forbid(missing_docs)]
3
4//! # convert-to-spaces
5//!
6//! [](https://crates.io/crates/convert-to-spaces)
7//! [](https://docs.rs/crates/convert-to-spaces)
8//!
9//! Convert tabs to spaces in a string, respecting tab stop logic.
10//!
11//! ## Overview
12//!
13//! `convert-to-spaces` is a lightweight Rust crate designed to precisely transform tab characters (`\t`)
14//! into the appropriate number of spaces. Unlike simple find-and-replace, this crate correctly
15//! applies **tab stop logic**, ensuring that text aligns to the next multiple of your specified tab size.
16//! This is crucial for maintaining consistent indentation and code readability, mimicking the behavior
17//! of modern text editors.
18//!
19//! Whether you're processing code, cleaning up text files, or preparing content for display,
20//! `convert-to-spaces` helps standardize your whitespace.
21//!
22//! ## Features
23//!
24//! * **Accurate Tab Stop Conversion:** Converts `\t` characters into the correct number of spaces
25//! based on the current column position and a defined tab size.
26//! * **Flexible Input:** Accepts any type that can be referenced as a string (`&str`, `String`, `&String`, etc.)
27//! using [`AsRef<str>`].
28//! * **Configurable Tab Size:** Easily specify how many spaces each tab stop represents.
29//! * **Lightweight:** Minimal dependencies, focusing solely on efficient whitespace conversion.
30//!
31//! ## Installation
32//!
33//! Add `convert-to-spaces` to your `Cargo.toml` file:
34//!
35//! ```toml
36//! [dependencies]
37//! convert-to-spaces = "0.1.0" # Use the latest version from crates.io
38//! ```
39//!
40//! Then, run `cargo build` or `cargo update`.
41//!
42//! ## Usage
43//!
44//! The primary function provided by this crate is [`convert_to_spaces`].
45//!
46//! Here's how to use it:
47//!
48//! ```rust
49//! use convert_to_spaces::convert_to_spaces;
50//!
51//! fn main() {
52//! // Example 1: Basic conversion with a tab size of 4
53//! let input1 = "fn main() {\n\tprintln!(\"Hello, world!\");\n}";
54//! let output1 = convert_to_spaces(input1, 4);
55//! println!("Input:\n{}", input1);
56//! println!("Output (tab size 4):\n{}", output1);
57//! // Expected output:
58//! // fn main() {
59//! // println!("Hello, world!");
60//! // }
61//!
62//! println!("--------------------");
63//!
64//! // Example 2: Text that doesn't start at column 0
65//! let input2 = " Some text\taligned"; // 4 spaces before "Some text"
66//! let output2 = convert_to_spaces(input2, 8);
67//! println!("Input:\n{}", input2);
68//! println!("Output (tab size 8):\n{}", output2);
69//! // Expected output:
70//! // Some text aligned
71//! // ("Some text" is 9 chars. current_column=4+9=13. Next multiple of 8 after 13 is 16. Need 16-13=3 spaces)
72//!
73//! println!("--------------------");
74//!
75//! // Example 3: Zero tab size (effectively removes tabs)
76//! let input3 = "Tab\tremoved";
77//! let output3 = convert_to_spaces(input3, 0);
78//! println!("Input:\n{}", input3);
79//! println!("Output (tab size 0):\n{}", output3);
80//! // Expected output:
81//! // Tabremoved
82//! }
83//! ```
84//!
85//! ## License
86//!
87//! This is free and unencumbered software released into the **public domain**.
88//!
89//! You may use, modify, distribute, and contribute to this code without restriction. To the extent possible under law, the author(s) of this work waive all copyright and related rights.
90//!
91//! Licensed under [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/) OR [Unlicense](http://unlicense.org/).
92//!
93//! 
94
95/// Converts tabs in a string to a specified number of spaces, respecting tab stop logic.
96///
97/// Each tab character (`\t`) will advance the text to the next multiple of the
98/// `tab_size`. The number of spaces inserted will vary depending on the
99/// current column position.
100///
101/// # Arguments
102///
103/// * `input` - The string-like reference (`AsRef<str>`) to convert. This allows
104/// passing `&str`, `String`, `&String`, etc.
105/// * `tab_size` - The size of a tab stop. If 0, tabs are effectively removed.
106///
107/// # Returns
108///
109/// A `String` with all tab characters (`\t`) correctly replaced by spaces
110/// according to tab stop rules.
111///
112/// # Examples
113///
114/// ```
115/// use convert_to_spaces::convert_to_spaces;
116///
117/// assert_eq!(convert_to_spaces("SPACE\tTAB", 4), "SPACE TAB");
118/// assert_eq!(convert_to_spaces("\tHello", 4), " Hello");
119/// assert_eq!(convert_to_spaces("Line 1\n\tLine 2", 2), "Line 1\n Line 2");
120/// assert_eq!(convert_to_spaces(String::from("MyString\t"), 8), "MyString ");
121/// assert_eq!(convert_to_spaces("No tabs here", 4), "No tabs here");
122/// ```
123pub fn convert_to_spaces<S: AsRef<str>>(input: S, tab_size: usize) -> String {
124 let input_str = input.as_ref();
125
126 // If tab_size is 0, a tab will insert 0 spaces, effectively removing tabs.
127 if tab_size == 0 {
128 return input_str.replace('\t', "");
129 }
130
131 let mut result = String::new();
132 let mut current_column = 0;
133
134 for char_code in input_str.chars() {
135 match char_code {
136 '\t' => {
137 // Calculate how many spaces are needed to reach the next tab stop
138 let spaces_to_add = tab_size - (current_column % tab_size);
139 for _ in 0..spaces_to_add {
140 result.push(' ');
141 }
142 current_column += spaces_to_add;
143 }
144 '\n' => {
145 // Reset column count for a new line
146 result.push('\n');
147 current_column = 0;
148 }
149 c => {
150 // For other characters, simply append and increment column count
151 result.push(c);
152 // NOTE: This assumes character width is 1. For actual wide/emoji characters,
153 // `current_column` would need to be based on grapheme clusters
154 // and their display widths (e.g., using `unicode-width` crate).
155 current_column += 1;
156 }
157 }
158 }
159
160 result
161}
162
163#[cfg(test)]
164#[path = "lib_tests.rs"]
165mod tests;