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}