convert_to_spaces/
lib.rs

1#![forbid(unsafe_code)]
2#![forbid(missing_docs)]
3
4//! # convert-to-spaces
5//!
6//! [![Crates.io](https://img.shields.io/crates/v/convert-to-spaces.svg)](https://crates.io/crates/convert-to-spaces)
7//! [![Docs.rs](https://docs.rs/convert-to-spaces/badge.svg)](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//! ![Unlicense logo](https://unlicense.org/pd-icon.png)
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;