use anyhow::Result;
use lazy_static::lazy_static;
lazy_static! {
static ref HEADER_DEFINITIONS: Vec<HeaderDefinition<'static>> = vec![
HeaderDefinition {
extensions: vec![".c", ".h", ".gv", ".java", ".scala", ".kt", ".kts"],
header_prefix: HeaderPrefix::new("/*", " * ", " */"),
},
HeaderDefinition {
extensions: vec![
".js", ".mjs", ".cjs", ".jsx", ".tsx", ".css", ".scss", ".sass", ".ts",
],
header_prefix: HeaderPrefix::new("/**", " * ", " */"),
},
HeaderDefinition {
extensions: vec![
".cc", ".cpp", ".cs", ".go", ".hcl", ".hh", ".hpp", ".m", ".mm", ".proto", ".rs",
".swift", ".dart", ".groovy", ".v", ".sv", ".php",
],
header_prefix: HeaderPrefix::new("", "// ", ""),
},
HeaderDefinition {
extensions: vec![
".py",
".sh",
".yaml",
".yml",
".dockerfile",
"dockerfile",
".rb",
"gemfile",
".tcl",
".tf",
".bzl",
".pl",
".pp",
"build",
".build",
".toml",
],
header_prefix: HeaderPrefix::new("", "# ", ""),
},
HeaderDefinition {
extensions: vec![".el", ".lisp"],
header_prefix: HeaderPrefix::new("", ";; ", ""),
},
HeaderDefinition {
extensions: vec![".erl"],
header_prefix: HeaderPrefix::new("", "% ", ""),
},
HeaderDefinition {
extensions: vec![".hs", ".sql", ".sdl"],
header_prefix: HeaderPrefix::new("", "-- ", ""),
},
HeaderDefinition {
extensions: vec![".html", ".xml", ".vue", ".wxi", ".wxl", ".wxs"],
header_prefix: HeaderPrefix::new("<!--", " ", "-->"),
},
HeaderDefinition {
extensions: vec![".j2"],
header_prefix: HeaderPrefix::new("{#", "", "#}"),
},
HeaderDefinition {
extensions: vec![".ml", ".mli", ".mll", ".mly"],
header_prefix: HeaderPrefix::new("(**", " ", "*)"),
},
];
}
const HEAD: &[&str] = &[
"#!",
"<?xml",
"<!doctype",
"# encoding:",
"# frozen_string_literal:",
"<?php",
"# escape",
"# syntax",
];
pub struct SourceHeaders;
impl SourceHeaders {
pub fn find_header_definition_by_extension<'a, E: AsRef<str>>(
extension: E,
) -> Option<&'a HeaderDefinition<'a>> {
HEADER_DEFINITIONS
.iter()
.find(|source| source.contains_extension(Some(&extension)))
}
pub fn find_header_prefix_for_extension<'a, E: AsRef<str>>(
extension: E,
) -> Option<&'a HeaderPrefix<'a>> {
SourceHeaders::find_header_definition_by_extension(&extension)
.map(|source| &source.header_prefix)
}
}
pub struct HeaderDefinition<'a> {
pub extensions: Vec<&'a str>,
pub header_prefix: HeaderPrefix<'a>,
}
impl HeaderDefinition<'_> {
pub fn contains_extension<E: AsRef<str>>(&self, extension: Option<E>) -> bool {
extension
.map_or(false, |e| self.extensions.contains(&e.as_ref()))
.to_owned()
}
}
#[derive(Debug, Clone)]
pub struct HeaderPrefix<'a> {
pub top: &'a str,
pub mid: &'a str,
pub bottom: &'a str,
}
impl<'a> HeaderPrefix<'a> {
pub fn apply<T>(&self, template: T) -> Result<String>
where
T: AsRef<str>,
{
let Self { bottom, mid, top } = &self;
let mut out = String::new();
if !top.is_empty() {
out.push_str(top);
out.push('\n');
}
let lines = template.as_ref().lines();
for line in lines {
out.push_str(mid);
out.push_str(line.trim_end_matches(char::is_whitespace));
out.push('\n');
}
if !bottom.is_empty() {
out.push_str(bottom);
out.push('\n');
}
out.push('\n');
Ok(out)
}
pub fn new(top: &'a str, mid: &'a str, bottom: &'a str) -> HeaderPrefix<'a> {
HeaderPrefix { top, mid, bottom }
}
}
pub fn extract_hash_bang(b: &[u8]) -> Option<Vec<u8>> {
let mut line = Vec::new();
for &c in b {
line.push(c);
if c == b'\n' {
break;
}
}
let first = String::from_utf8_lossy(&line).to_lowercase();
for &h in HEAD {
if first.starts_with(h) {
return Some(line);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::template::copyright::{SpdxCopyrightNotice, SPDX_COPYRIGHT_NOTICE};
#[test]
fn test_execute_template_spdx_copyright_notice() {
let rs_header_prefix = SourceHeaders::find_header_prefix_for_extension(".rs").unwrap();
let reg = handlebars::Handlebars::new();
let data = SpdxCopyrightNotice {
year: Some(2022),
owner: "Bilbo Baggins".to_string(),
license: "MIT".to_string(),
};
let template = reg.render_template(SPDX_COPYRIGHT_NOTICE, &data);
assert!(template.is_ok());
let template = template.unwrap();
let result = rs_header_prefix.apply(&template).unwrap();
let expected: &str = r#"// Copyright 2022 Bilbo Baggins
// SPDX-License-Identifier: MIT
"#;
assert_eq!(&result, expected);
let empty_template = "";
let result = rs_header_prefix.apply(empty_template).unwrap();
let expected = "\n";
assert_eq!(&result, expected);
let js_header_prefix = SourceHeaders::find_header_prefix_for_extension(".js").unwrap();
let result = js_header_prefix.apply(template).unwrap();
#[deny(clippy::all)]
let expected: &str = r#"/**
* Copyright 2022 Bilbo Baggins
* SPDX-License-Identifier: MIT
*/
"#;
assert_eq!(&result, expected);
}
#[test]
fn test_hash_bang_with_valid_prefix() {
let input = "#!/bin/bash\nrest of the script".as_bytes();
let result = extract_hash_bang(input);
let expected = Some(b"#!/bin/bash\n".to_vec());
assert_eq!(result, expected);
}
#[test]
fn test_hash_bang_with_invalid_prefix() {
let input = "Invalid hash-bang line\nrest of the script".as_bytes();
let result = extract_hash_bang(input);
let expected = None;
assert_eq!(result, expected);
}
#[test]
fn test_hash_bang_with_multiple_valid_prefixes() {
let input = "<?xml\nrest of the content".as_bytes();
let result = extract_hash_bang(input);
let expected = Some(b"<?xml\n".to_vec());
assert_eq!(result, expected);
}
#[test]
fn test_hash_bang_with_empty_input() {
let input = "".as_bytes();
let result = extract_hash_bang(input);
let expected = None;
assert_eq!(result, expected);
}
#[test]
fn test_hash_bang_with_partial_line() {
let input = "#!/usr/bin/env python".as_bytes();
let result = extract_hash_bang(input);
let expected = Some("#!/usr/bin/env python".as_bytes().to_vec());
assert_eq!(result, expected);
}
}