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}