Skip to main content

provenant/copyright/
types.rs

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