file_header/license/spdx/mod.rs
1// Copyright 2023 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Constructs headers for SPDX licenses from the `license` crate.
16//!
17//! Some licenses are effectively templates: certain tokens like `<yyyy>` or `year` are intended
18//! to be replaced with some user-defined value, like the copyright year in this case. These are
19//! represented by the [LicenseTokens] trait, with the replacement values needed to construct the
20//! final header text defined by [LicenseTokens::TokenReplacementValues]. If no tokens need to be
21//! replaced, [NoTokens] is available for that purpose.
22//!
23//! Several common licenses have structs defined, with `LicenseTokens` already appropriately
24//! implemented (e.g. `APACHE_2_0`).
25//!
26//! Most other licenses don't need anything other than the copyright year and the copyright holder,
27//! or have nothing at all, and can be easily turned into headers with [SpdxLicense], a type to
28//! define the tokens to replace (or [NoTokens]), and [YearCopyrightOwnerValue].
29//!
30//! If you find yourself needing a license that's not already available easily with a struct in
31//! this module, see the examples below, and consider making a PR to add support.
32//!
33//! # Examples
34//!
35//! ## Getting a header for the Apache 2.0 license:
36//!
37//! ```
38//! // Copyright 2023 Google LLC.
39//! // SPDX-License-Identifier: Apache-2.0
40//! use std::path;
41//! use file_header::license::spdx::*;
42//!
43//! // Apache 2 has all relevant types already defined, and just needs year and name
44//! let header = APACHE_2_0.build_header(YearCopyrightOwnerValue::new(2023, "Some copyright holder".to_string()));
45//!
46//! // use normal header API to check or add
47//!```
48//!
49//! ## Getting a header for an SPDX license with no tokens to replace
50//!
51//! ```
52//! // Copyright 2023 Google LLC.
53//! // SPDX-License-Identifier: Apache-2.0
54//! use file_header::license::spdx::*;
55//!
56//! let license = SpdxLicense::<NoTokens>::new(
57//! Box::new(license::licenses::Rpsl1_0),
58//! "Copyright (c) 1995-2002 RealNetworks, Inc. and/or its licensors".to_string(),
59//! 10
60//! );
61//!
62//! let header = license.build_header(());
63//! ```
64//!
65//!
66//! ## Getting a header for an SPDX license type that uses the typical year & name
67//!
68//!```
69//! // Copyright 2023 Google LLC.
70//! // SPDX-License-Identifier: Apache-2.0
71//! use file_header::license::spdx::*;
72//!
73//! // Replacement tokens used in LGPL2
74//! struct Lgpl2_0Tokens;
75//!
76//! impl LicenseTokens for Lgpl2_0Tokens {
77//! type TokenReplacementValues = YearCopyrightOwnerValue;
78//!
79//! fn replacement_pairs(replacements: Self::TokenReplacementValues) -> Vec<(&'static str, String)> {
80//! vec![
81//! ("year", replacements.year.to_string()),
82//! ("name of author", replacements.copyright_owner),
83//! ]
84//! }
85//! }
86//!
87//! let license = SpdxLicense::<Lgpl2_0Tokens>::new(
88//! Box::new(license::licenses::Lgpl2_0),
89//! "GNU Library General Public License as published by the Free Software Foundation; version 2.".to_string(),
90//! 10
91//! );
92//!
93//! let header = license.build_header(YearCopyrightOwnerValue::new(2023, "Foo Inc.".to_string()));
94//! ```
95//!
96//! ## Getting a header for an unusual SPDX license
97//!
98//!```
99//! // Copyright 2023 Google LLC.
100//! // SPDX-License-Identifier: Apache-2.0
101//! use file_header::license::spdx::*;
102//!
103//! /// Replacement values for `W3c20150513` license
104//! struct W3c20150513Values {
105//! name_of_software: String,
106//! distribution_uri: String,
107//! date_of_software: String,
108//! }
109//!
110//! impl W3c20150513Values {
111//! fn new(name_of_software: String, distribution_uri: String, date_of_software: String,) -> Self {
112//! Self {
113//! name_of_software,
114//! distribution_uri,
115//! date_of_software
116//! }
117//! }
118//! }
119//!
120//! /// Tokens in the W3c20150513 license
121//! struct W3c20150513Tokens;
122//!
123//! impl LicenseTokens for W3c20150513Tokens {
124//! type TokenReplacementValues = W3c20150513Values;
125//!
126//! fn replacement_pairs(
127//! replacements: Self::TokenReplacementValues,
128//! ) -> Vec<(&'static str, String)> {
129//! vec![
130//! ("$name_of_software", replacements.name_of_software),
131//! ("$distribution_URI", replacements.distribution_uri),
132//! // yes, it really does use dashes just for this one
133//! ("$date-of-software", replacements.date_of_software),
134//! ]
135//! }
136//!}
137//!
138//! let license = SpdxLicense::<W3c20150513Tokens>::new(
139//! Box::new(license::licenses::W3c20150513),
140//! "This work is distributed under the W3C® Software License".to_string(),
141//! 10
142//! );
143//!
144//! let header = license.build_header(W3c20150513Values::new(
145//! "2023".to_string(),
146//! "https://example.com".to_string(),
147//! "Foo Inc.".to_string()));
148//!
149//! ```
150
151use crate::{Header, SingleLineChecker};
152use lazy_static::lazy_static;
153use std::marker;
154
155/// Re-export of the `license` crate for user convenience
156pub use license;
157
158#[cfg(test)]
159mod tests;
160
161/// A boxed `license::License`.
162// Including the `Send` trait for compatibility with crates that use `lazy_static` with the `spin_no_std` feature.
163type BoxedLicense = Box<dyn license::License + Sync + Send>;
164
165/// Metadata around an SPDX license to enable constructing a [Header].
166///
167/// `<L>` is the [LicenseTokens] that defines what, if any, replacement tokens are needed.
168pub struct SpdxLicense<L: LicenseTokens> {
169 license_text: BoxedLicense,
170 search_pattern: String,
171 lines_to_search: usize,
172 marker: marker::PhantomData<L>,
173}
174
175impl<L: LicenseTokens> SpdxLicense<L> {
176 /// `spdx_license`: the SPDX license
177 /// `search_pattern`: the text to search for when checking for the presence of the license
178 /// `lines_to_search`: how many lines to search for `search_pattern` before giving up
179 pub fn new(license_text: BoxedLicense, search_pattern: String, lines_to_search: usize) -> Self {
180 Self {
181 license_text,
182 search_pattern,
183 lines_to_search,
184 marker: marker::PhantomData,
185 }
186 }
187
188 /// Build a header for this license using the provided `year` and `copyright_holder` to
189 /// interpolate into the license.
190 /// The license's header is used, if the license offers one, otherwise the main license text
191 /// is used instead.
192 pub fn build_header(
193 &self,
194 replacement_values: L::TokenReplacementValues,
195 ) -> Header<SingleLineChecker> {
196 let checker = SingleLineChecker::new(self.search_pattern.clone(), self.lines_to_search);
197 // use header, if the license has a specific header else the license text
198 let text = self
199 .license_text
200 .header()
201 .unwrap_or(self.license_text.text());
202
203 let header = L::replacement_pairs(replacement_values).iter().fold(
204 text.to_string(),
205 |current_text, (replace_token, replace_value)| {
206 // replacing only the first occurrence seems wise
207 current_text.replacen(replace_token, replace_value, 1)
208 },
209 );
210
211 Header::new(checker, header)
212 }
213}
214
215/// Tokens in license text to be replaced, e.g. `yyyy` which will be replaced with the copyright
216/// year.
217pub trait LicenseTokens {
218 /// Struct holding the replacement values needed for the tokens used by the license
219 type TokenReplacementValues;
220 /// List of `(token to search for, replacement value)`.
221 fn replacement_pairs(replacements: Self::TokenReplacementValues)
222 -> Vec<(&'static str, String)>;
223}
224
225/// For licenses with no tokens to replace
226pub struct NoTokens;
227
228impl LicenseTokens for NoTokens {
229 type TokenReplacementValues = ();
230
231 fn replacement_pairs(
232 _replacements: Self::TokenReplacementValues,
233 ) -> Vec<(&'static str, String)> {
234 Vec::new()
235 }
236}
237
238/// Tokens for the Apache 2 license
239#[doc(hidden)]
240pub struct Apache2Tokens;
241
242impl LicenseTokens for Apache2Tokens {
243 type TokenReplacementValues = YearCopyrightOwnerValue;
244
245 fn replacement_pairs(
246 replacements: Self::TokenReplacementValues,
247 ) -> Vec<(&'static str, String)> {
248 vec![
249 ("[yyyy]", replacements.year.to_string()),
250 ("[name of copyright owner]", replacements.copyright_owner),
251 ]
252 }
253}
254
255/// Tokens for the MIT license
256#[doc(hidden)]
257pub struct MitTokens;
258
259impl LicenseTokens for MitTokens {
260 type TokenReplacementValues = YearCopyrightOwnerValue;
261
262 fn replacement_pairs(
263 replacements: Self::TokenReplacementValues,
264 ) -> Vec<(&'static str, String)> {
265 vec![
266 ("<year>", replacements.year.to_string()),
267 ("<copyright holders>", replacements.copyright_owner),
268 ]
269 }
270}
271
272/// Tokens for the BSD 3-clause license
273#[doc(hidden)]
274pub struct Bsd3ClauseTokens {}
275
276impl LicenseTokens for Bsd3ClauseTokens {
277 type TokenReplacementValues = YearCopyrightOwnerValue;
278
279 fn replacement_pairs(
280 replacements: Self::TokenReplacementValues,
281 ) -> Vec<(&'static str, String)> {
282 vec![
283 ("<year>", replacements.year.to_string()),
284 ("<owner>", replacements.copyright_owner),
285 ]
286 }
287}
288
289/// Tokens for the GPL-3.0 license
290#[doc(hidden)]
291pub struct Gpl3Tokens {}
292
293impl LicenseTokens for Gpl3Tokens {
294 type TokenReplacementValues = YearCopyrightOwnerValue;
295
296 fn replacement_pairs(
297 replacements: Self::TokenReplacementValues,
298 ) -> Vec<(&'static str, String)> {
299 vec![
300 ("<year>", replacements.year.to_string()),
301 ("<name of author>", replacements.copyright_owner),
302 ]
303 }
304}
305
306/// Replacement values for licenses that use a _year_ and _copyright owner name_.
307pub struct YearCopyrightOwnerValue {
308 /// The year of the copyright
309 pub year: u32,
310 /// The holder of the copyright
311 pub copyright_owner: String,
312}
313
314impl YearCopyrightOwnerValue {
315 /// Construct a new instance with the provided year and copyright owner
316 pub fn new(year: u32, copyright_owner: String) -> Self {
317 Self {
318 year,
319 copyright_owner,
320 }
321 }
322}
323
324lazy_static! {
325 /// Apache 2.0 license
326 pub static ref APACHE_2_0: SpdxLicense<Apache2Tokens> = SpdxLicense ::new(
327 Box::new(license::licenses::Apache2_0),
328 "Apache License, Version 2.0".to_string(),
329 10
330 );
331}
332lazy_static! {
333 /// MIT license
334 pub static ref MIT: SpdxLicense<MitTokens> = SpdxLicense ::new(
335 Box::new(license::licenses::Mit),
336 "MIT License".to_string(),
337 10
338 );
339}
340lazy_static! {
341 /// BSD 3-clause license
342 pub static ref BSD_3: SpdxLicense<Bsd3ClauseTokens> = SpdxLicense ::new(
343 Box::new(license::licenses::Bsd3Clause),
344 "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:".to_string(),
345 10
346 );
347}
348lazy_static! {
349 /// GPL 3.0 license
350 pub static ref GPL_3_0_ONLY: SpdxLicense<Gpl3Tokens> = SpdxLicense ::new(
351 Box::new(license::licenses::Gpl3_0Only),
352 "GNU General Public License".to_string(),
353 10
354 );
355}
356lazy_static! {
357 /// EPL 2.0 license
358 pub static ref EPL_2_0: SpdxLicense<NoTokens> = SpdxLicense ::new(
359 Box::new(license::licenses::Epl2_0),
360 "Eclipse Public License - v 2.0".to_string(),
361 10
362 );
363}
364lazy_static! {
365 /// MPL 2.0 license
366 pub static ref MPL_2_0: SpdxLicense<NoTokens> = SpdxLicense ::new(
367 Box::new(license::licenses::Mpl2_0),
368 "Mozilla Public License, v. 2.0".to_string(),
369 10
370 );
371}