Skip to main content

provenant/copyright/
types.rs

1// SPDX-FileCopyrightText: nexB Inc. and others
2// ScanCode is a trademark of nexB Inc.
3// SPDX-FileCopyrightText: Provenant contributors
4// SPDX-License-Identifier: Apache-2.0
5// Derived from ScanCode Toolkit (Apache-2.0); modified. See NOTICE.
6
7//! Core types for copyright detection.
8//!
9//! This module defines:
10//! - Detection result types ([`CopyrightDetection`], [`HolderDetection`], [`AuthorDetection`])
11//! - The POS tag enum ([`PosTag`]) with 55 variants for token classification
12//! - Parse tree types ([`ParseNode`], [`TreeLabel`]) for grammar-based extraction
13//! - The [`Token`] struct linking text values to POS tags and source locations
14
15use crate::models::LineNumber;
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct CopyrightDetection {
19    pub copyright: String,
20    pub start_line: LineNumber,
21    pub end_line: LineNumber,
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub struct HolderDetection {
26    pub holder: String,
27    pub start_line: LineNumber,
28    pub end_line: LineNumber,
29}
30
31#[derive(Debug, Clone, PartialEq)]
32pub struct AuthorDetection {
33    pub author: String,
34    pub start_line: LineNumber,
35    pub end_line: LineNumber,
36}
37
38/// Part-of-Speech tag for a token (type-safe, not stringly-typed)
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum PosTag {
41    // Copyright keywords
42    Copy,        // "Copyright", "(c)", "Copr.", etc.
43    SpdxContrib, // "SPDX-FileContributor"
44
45    // Year-related
46    Yr,     // A year like "2024"
47    YrPlus, // Year with plus: "2024+"
48    BareYr, // Short year: "99"
49
50    // Names and entities
51    Nnp,      // Proper noun: "John", "Smith"
52    Nn,       // Common noun (catch-all)
53    Caps,     // All-caps word: "MIT", "IBM"
54    Pn,       // Dotted name: "P.", "DMTF."
55    MixedCap, // Mixed case: "LeGrande"
56
57    // Organization suffixes
58    Comp, // Company suffix: "Inc.", "Ltd.", "GmbH"
59    Uni,  // University: "University", "College"
60
61    // Author keywords
62    Auth,         // "Author", "@author"
63    Auth2,        // "Written", "Developed", "Created"
64    Auths,        // "Authors", "author's"
65    AuthDot,      // "Author.", "Authors."
66    Maint,        // "Maintainer", "Developer"
67    Contributors, // "Contributors"
68    Commit,       // "Committers"
69
70    // Rights reserved
71    Right,    // "Rights", "Rechte", "Droits"
72    Reserved, // "Reserved", "Vorbehalten", "Réservés"
73
74    // Conjunctions and prepositions
75    Cc,   // "and", "&", ","
76    Of,   // "of", "De", "Di"
77    By,   // "by"
78    In,   // "in", "en"
79    Van,  // "van", "von", "de", "du"
80    To,   // "to"
81    Dash, // "-", "--", "/"
82
83    // Special
84    Email,      // Email address
85    EmailStart, // Email opening bracket like "<foo"
86    EmailEnd,   // Email closing bracket like "bar>"
87    Url,        // URL with scheme
88    Url2,       // URL without scheme (domain.com)
89    Holder,     // "Holder", "Holders"
90    Is,         // "is", "are"
91    Held,       // "held"
92    Notice,     // "NOTICE"
93    Portions,   // "Portions", "Parts"
94    Oth,        // "Others", "et al."
95    Following,  // "following"
96    Mit,        // "MIT" (special handling)
97    Linux,      // "Linux"
98    Parens,     // "(" or ")"
99    At,         // "AT" (obfuscated email)
100    Dot,        // "DOT" (obfuscated email)
101    Ou,         // "OU" (org unit in certs)
102
103    // Structural
104    EmptyLine, // Empty line marker
105    Junk,      // Junk to ignore
106
107    // Cardinals
108    Cd,    // Cardinal number
109    Cds,   // Small cardinal (0-39)
110    Month, // Month abbreviation
111    Day,   // Day of week
112}
113
114#[derive(Debug, Clone)]
115pub struct Token {
116    pub value: String,
117    pub tag: PosTag,
118    pub start_line: LineNumber,
119}
120
121/// A node in the parse tree
122#[derive(Debug, Clone)]
123pub enum ParseNode {
124    Leaf(Token),
125    Tree {
126        label: TreeLabel,
127        children: Vec<ParseNode>,
128    },
129}
130
131/// Labels for parse tree nodes (grammar non-terminals)
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
133pub enum TreeLabel {
134    YrRange,
135    YrAnd,
136    AllRightReserved,
137    Name,
138    NameEmail,
139    NameYear,
140    NameCopy,
141    NameCaps,
142    Company,
143    AndCo,
144    Copyright,
145    Copyright2,
146    Author,
147    AndAuth,
148    InitialDev,
149    DashCaps,
150}
151
152impl ParseNode {
153    /// Get the tag of this node (for leaf tokens) or None (for trees)
154    pub fn tag(&self) -> Option<PosTag> {
155        match self {
156            ParseNode::Leaf(token) => Some(token.tag),
157            ParseNode::Tree { .. } => None,
158        }
159    }
160
161    /// Get the label of this node (for trees) or None (for leaf tokens)
162    pub fn label(&self) -> Option<TreeLabel> {
163        match self {
164            ParseNode::Tree { label, .. } => Some(*label),
165            ParseNode::Leaf(_) => None,
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn test_copyright_detection_creation() {
176        let d = CopyrightDetection {
177            copyright: "Copyright 2024 Acme Inc.".to_string(),
178            start_line: LineNumber::ONE,
179            end_line: LineNumber::ONE,
180        };
181        assert_eq!(d.copyright, "Copyright 2024 Acme Inc.");
182    }
183
184    #[test]
185    fn test_token_creation() {
186        let t = Token {
187            value: "Copyright".to_string(),
188            tag: PosTag::Copy,
189            start_line: LineNumber::ONE,
190        };
191        assert_eq!(t.tag, PosTag::Copy);
192    }
193
194    #[test]
195    fn test_parse_node_leaf() {
196        let node = ParseNode::Leaf(Token {
197            value: "2024".to_string(),
198            tag: PosTag::Yr,
199            start_line: LineNumber::new(5).unwrap(),
200        });
201        assert_eq!(node.tag(), Some(PosTag::Yr));
202        assert_eq!(node.label(), None);
203    }
204
205    #[test]
206    fn test_parse_node_tree() {
207        let child = ParseNode::Leaf(Token {
208            value: "2024".to_string(),
209            tag: PosTag::Yr,
210            start_line: LineNumber::new(3).unwrap(),
211        });
212        let tree = ParseNode::Tree {
213            label: TreeLabel::YrRange,
214            children: vec![child],
215        };
216        assert_eq!(tree.label(), Some(TreeLabel::YrRange));
217        assert_eq!(tree.tag(), None);
218    }
219}