convert_to_spaces/
lib.rs

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