diff --git a/src/main.rs b/src/main.rs index 09d0240..cdcf179 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,6 +62,63 @@ impl Language { } } +/// A source file's header information. +pub struct Header { + /// The copyrights on this source file. + pub copyrights: Vec, + + /// The SPDX license identifier that this source file is covered under. + pub spdx: Option, + + /// This header's body, as a list of lines. + pub body: Vec, +} + +impl Header { + /// Parses a header from a list of comment-less lines. + pub fn parse(src: Vec) -> Result { + let mut src = src.into_iter().peekable(); + let mut copyrights = Vec::new(); + let mut spdx = None; + + while let Some(line) = src.peek() { + match Copyright::parse(line.as_str()) { + Ok(copyright) => { + copyrights.push(copyright); + src.next(); + } + Err(CopyrightError::Empty | CopyrightError::InvalidPrefix) => { + break; + } + Err(err) => { + return Err(HeaderError::Copyright(err)); + } + } + } + + if let Some(line) = src.peek() { + if let Some(body) = line.strip_prefix("SPDX-License-Identifier: ") { + spdx = Some(body.to_string()); + src.next(); + } + } + + let body: Vec<_> = src.collect(); + + Ok(Self { + copyrights, + spdx, + body, + }) + } +} + +/// An error that occurred during header parsing. +#[derive(Clone, Copy, Debug)] +pub enum HeaderError { + Copyright(CopyrightError), +} + /// A single copyright notice on a source file. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Copyright { @@ -197,4 +254,24 @@ mod tests { } ); } + + #[test] + fn parse_header() { + let src = b"#!/bin/sh\n// Copyright (c) 2024 Marceline Cramer\n// SPDX-License-Identifier: AGPL-3.0-or-later\n// body here"; + let lines = Language::RUST.read_header(&mut src.as_slice()).unwrap(); + let header = Header::parse(lines).unwrap(); + + assert_eq!( + header.copyrights, + vec![Copyright { + holder: "Marceline Cramer".into(), + first_year: 2024, + last_year: 2024, + }] + ); + + assert_eq!(header.spdx, Some("AGPL-3.0-or-later".to_string())); + + assert_eq!(header.body, vec!["body here"]); + } }