Skip to main content

forehead_core/
comment.rs

1// بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيم
2// This file is part of forehead.
3//
4// Copyright (C) 2026-Present Afsall Inc.
5// SPDX-License-Identifier: Apache-2.0 OR MIT
6//
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10//
11// http://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18//
19// Alternatively, this file is available under the MIT License:
20// Permission is hereby granted, free of charge, to any person obtaining a copy
21// of this software and associated documentation files (the "Software"), to deal
22// in the Software without restriction, including without limitation the rights
23// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24// copies of the Software, and to permit persons to whom the Software is
25// furnished to do so, subject to the following conditions:
26//
27// The above copyright notice and this permission notice shall be included in all
28// copies or substantial portions of the Software.
29//
30// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36// SOFTWARE.
37
38use std::path::Path;
39
40#[derive(Debug, Clone, PartialEq)]
41pub enum CommentStyle {
42    Line(String),
43    Block(String, String),
44    /// For Python docstrings / multiline string comments
45    BlockTriple(String),
46}
47
48impl CommentStyle {
49    pub fn prefix(&self) -> &str {
50        match self {
51            CommentStyle::Line(p) => p,
52            CommentStyle::Block(o, _) => o,
53            CommentStyle::BlockTriple(d) => d,
54        }
55    }
56}
57
58pub fn comment_style_for(path: &Path) -> Option<CommentStyle> {
59    let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
60    let fname = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
61
62    // Check by exact filename
63    match fname {
64        "Makefile" | "makefile" | "Dockerfile" | "Containerfile" => {
65            return Some(CommentStyle::Line("#".to_string()));
66        }
67        "CMakeLists.txt" => return Some(CommentStyle::Line("#".to_string())),
68        "Rakefile" | "Gemfile" => return Some(CommentStyle::Line("#".to_string())),
69        _ => {}
70    }
71
72    // Check by extension
73    match ext {
74        // // line comments
75        "rs" | "go" | "c" | "h" | "cpp" | "hpp" | "cc" | "cxx" | "hh" | "java" | "js" | "ts"
76        | "jsx" | "tsx" | "kt" | "kts" | "swift" | "zig" | "dart" | "php" | "m" | "mm" | "cs"
77        | "scala" | "groovy" | "vala" | "v" | "nu" | "svelte" | "vue" => {
78            Some(CommentStyle::Line("//".to_string()))
79        }
80
81        // # line comments
82        "py" | "rb" | "pl" | "pm" | "sh" | "bash" | "zsh" | "fish" | "yaml" | "yml" | "toml"
83        | "r" | "R" | "rake" | "gemspec" | "erb" | "el" | "ex" | "exs" | "jl" | "coffee"
84        | "csh" | "awk" | "tcl" | "mk" | "ninja" | "bzl" | "BUILD" | "WORKSPACE" | "dhall"
85        | "nix" | "conf" | "cfg" | "ini" | "desktop" | "service" | "timer" | "diff" | "patch"
86        | "ziggy" | "ziggy-schema" => Some(CommentStyle::Line("#".to_string())),
87
88        // -- line comments
89        "sql" | "hs" | "lhs" | "lua" | "ada" | "adb" | "ads" | "sqlite" | "sqlite3" | "vhd"
90        | "vhdl" => Some(CommentStyle::Line("--".to_string())),
91
92        // % line comments
93        "tex" | "sty" | "cls" | "ltx" | "bib" | "matlab" | "maxima" | "prolog" => {
94            Some(CommentStyle::Line("%".to_string()))
95        }
96
97        // ; line comments
98        "lisp" | "lsp" | "clj" | "cljs" | "cljc" | "edn" | "scm" | "ss" => {
99            Some(CommentStyle::Line(";".to_string()))
100        }
101
102        // <!-- --> block comments
103        "html" | "htm" | "xhtml" | "xml" | "xsl" | "xslt" | "xsd" | "svg" | "rmd" | "mdown"
104        | "mkdn" | "mdx" => Some(CommentStyle::Block("<!--".into(), "-->".into())),
105
106        // /* */ block comments
107        "css" | "scss" | "sass" | "less" | "graphql" | "gql" | "proto" | "flatbuffers" | "fbs"
108        | "solidity" | "sol" => Some(CommentStyle::Block("/*".to_string(), "*/".to_string())),
109
110        _ => None,
111    }
112}
113
114pub fn format_as_comment(lines: &[String], style: &CommentStyle) -> String {
115    match style {
116        CommentStyle::Line(prefix) => lines
117            .iter()
118            .map(|l| {
119                if l.trim().is_empty() {
120                    prefix.clone()
121                } else {
122                    format!("{} {}", prefix, l.trim())
123                }
124            })
125            .collect::<Vec<_>>()
126            .join("\n"),
127        CommentStyle::Block(open, close) => {
128            let mut result = open.clone();
129            result.push('\n');
130            for line in lines {
131                if line.trim().is_empty() {
132                    result.push_str(open);
133                    result.push('\n');
134                } else {
135                    result.push_str(&format!("{} {}", open, line.trim()));
136                    result.push('\n');
137                }
138            }
139            result.push_str(close);
140            result.push('\n');
141            result
142        }
143        CommentStyle::BlockTriple(delim) => {
144            let mut result = delim.clone();
145            result.push('\n');
146            for line in lines {
147                result.push_str(line.trim());
148                result.push('\n');
149            }
150            result.push_str(delim);
151            result.push('\n');
152            result
153        }
154    }
155}