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}