Skip to content

Commit

Permalink
Merge pull request #16 from gtk-rs/missing-license
Browse files Browse the repository at this point in the history
Add check for missing license header
  • Loading branch information
GuillaumeGomez authored Dec 14, 2020
2 parents df3ac62 + 8be96b3 commit 55f5240
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 194 deletions.
49 changes: 49 additions & 0 deletions src/license.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::fs;
use std::io::{BufRead, BufReader};
use std::path::Path;

use crate::types::CheckResult;

fn check_if_license_is_present(path: &Path) -> CheckResult<bool> {
let f =
fs::File::open(path).map_err(|e| format!("Failed to open `{}`: {}", path.display(), e))?;
let f = BufReader::new(f);
let header: Vec<String> = f.lines().take(2).map(|x| x.unwrap()).collect();
if header.len() != 2 {
println!("xx> Missing header in `{}`", path.display());
Ok(false)
} else if header[0]
!= "// Take a look at the license at the top of the repository in the LICENSE file."
{
println!("xx> Missing header in `{}`", path.display());
Ok(false)
} else if !header[1].is_empty() {
println!(
"xx> Expected empty line after license header in `{}`",
path.display()
);
Ok(false)
} else {
Ok(true)
}
}

pub fn run_check<P: AsRef<Path>>(folder: &P) -> CheckResult<bool> {
let folder = folder.as_ref();
let src_dir = folder.join("src");
println!("==> Checking license headers from {:?}", src_dir.display());
let mut nb_errors = 0;
for entry in fs::read_dir(&src_dir)
.map_err(|e| format!("Failed to read directory {:?}: {}", src_dir, e))?
{
let entry = entry.expect("Failed to enter directory");
let path = entry.path();
if !path.is_dir() {
if !check_if_license_is_present(&path)? {
nb_errors += 1;
}
}
}
println!("<== done");
Ok(nb_errors == 0)
}
226 changes: 32 additions & 194 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
extern crate toml;

use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::Path;
use std::process::exit;

use toml::Value;

type Result<T, E = String> = std::result::Result<T, E>;

macro_rules! get_vec {
($toml:expr, $key:expr) => {{
match $toml.lookup_vec($key) {
Expand All @@ -19,203 +13,42 @@ macro_rules! get_vec {
}};
}

// This trait comes from https://github.com/gtk-rs/gir
pub trait TomlHelper
where
Self: Sized,
{
fn lookup<'a>(&'a self, option: &str) -> Option<&'a toml::Value>;
fn lookup_str<'a>(&'a self, option: &'a str) -> Option<&'a str>;
fn lookup_vec<'a>(&'a self, option: &'a str) -> Option<&'a Vec<Self>>;
}

impl TomlHelper for toml::Value {
fn lookup<'a>(&'a self, option: &str) -> Option<&'a toml::Value> {
let mut value = self;
for opt in option.split('.') {
let table = match value.as_table() {
Some(table) => table,
None => return None,
};
value = match table.get(opt) {
Some(value) => value,
None => return None,
};
}
Some(value)
}
fn lookup_str<'a>(&'a self, option: &'a str) -> Option<&'a str> {
self.lookup(option)?.as_str()
}
fn lookup_vec<'a>(&'a self, option: &'a str) -> Option<&'a Vec<Self>> {
self.lookup(option)?.as_array()
}
}

#[derive(Debug)]
struct Info {
correctly_declared_manual_traits: HashSet<String>,
listed_crate_objects: HashSet<String>,
}

// Return a map where the key is the full object name and has the manual associated traits.
fn get_objects(toml_file: &Path) -> Result<Info> {
println!("==> Getting objects from {:?}", toml_file.display());
let mut correctly_declared_manual_traits: HashSet<String> = HashSet::new();
let mut listed_crate_objects: HashSet<String> = HashSet::new();

let toml: Value = toml::from_str(
&fs::read_to_string(toml_file)
.map_err(|e| format!("Failed to read {:?}: {}", toml_file, e))?,
)
.expect("invalid toml");

let current_lib = toml
.lookup_str("options.library")
.expect("failed to get current library");
for entry in get_vec!(toml, "options.generate")
.iter()
.filter_map(|x| x.as_str())
{
listed_crate_objects.insert(
entry
.split(".")
.skip(1)
.next()
.expect("couldn't extract name")
.to_owned(),
);
}
for entry in get_vec!(toml, "options.builders")
.iter()
.filter_map(|x| x.as_str())
{
listed_crate_objects.insert(
entry
.split(".")
.skip(1)
.next()
.expect("couldn't extract name")
.to_owned(),
);
}
for entry in get_vec!(toml, "options.manual")
.iter()
.filter_map(|x| x.as_str())
{
let mut parts = entry.split(".");
let lib = parts.next().expect("failed to extract lib");
if lib != current_lib {
continue;
}
listed_crate_objects.insert(parts.next().expect("couldn't extract name").to_owned());
}
for objs in toml.lookup("object").map(|a| a.as_array().unwrap()) {
for obj in objs {
if let Some(name) = obj.lookup_str("name") {
let mut parts = name.split(".");
let lib = parts.next().expect("failed to extract lib");
if lib != current_lib {
continue;
}
if let Some(name) = parts.next() {
for elem in get_vec!(obj, "manual_traits")
.iter()
.filter_map(|x| x.as_str())
.map(|x| x.to_owned())
{
correctly_declared_manual_traits.insert(elem);
}
listed_crate_objects.insert(name.to_owned());
}
}
}
}
println!("<== done");
Ok(Info {
correctly_declared_manual_traits,
listed_crate_objects,
})
}

fn get_manual_traits_from_file(
src_file: &Path,
objects: &Info,
ret: &mut Vec<String>,
) -> Result<()> {
let content = fs::read_to_string(src_file)
.map_err(|e| format!("Failed to read {:?}: {}", src_file, e))?;
for line in content.lines() {
let line = line.trim();
if !line.starts_with("pub trait ") {
continue;
}
let line = &line[10..];
let mut pos = (line.find('{').unwrap_or(line.len()), '{');
for x in &['<', ':'] {
if let Some(p) = line.find(*x) {
if p < pos.0 {
pos.0 = p;
pos.1 = *x;
}
}
}
let name = line.split(pos.1).next().expect("failed to get trait name");
if !name.ends_with("ExtManual") {
continue;
}
let obj = &name[..name.len() - 9];
if !objects.correctly_declared_manual_traits.contains(name)
&& objects.listed_crate_objects.contains(obj)
{
ret.push(name.to_owned());
}
}

Ok(())
}

fn get_manual_traits(src_dir: &Path, objects: &Info) -> Result<Vec<String>> {
println!("==> Getting manual traits from {:?}", src_dir.display());
let mut ret = Vec::new();

for entry in fs::read_dir(src_dir)
.map_err(|e| format!("Failed to read directory {:?}: {}", src_dir, e))?
{
let entry = entry.expect("Failed to enter directory");
let path = entry.path();
if !path.is_dir() {
get_manual_traits_from_file(&path, objects, &mut ret)?;
}
mod license;
mod manual_traits;
mod types;

fn run_check<P: AsRef<Path>>(
folder: P,
gir_file: &str,
check_manual_traits: bool,
check_license: bool,
) -> types::CheckResult<bool> {
println!("=> Running for {}", folder.as_ref().display());
let mut result = true;
if check_manual_traits {
result = manual_traits::run_check(&folder, gir_file)?;
}
println!("<== done");
Ok(ret)
}

fn run_check<P: AsRef<Path>>(folder: P, gir_file: &str) -> Result<bool> {
let folder = folder.as_ref();
println!("=> Running for {}", folder.display());

let objects = get_objects(&folder.join(gir_file))?;
let results = get_manual_traits(&folder.join("src"), &objects)?;
if !results.is_empty() {
println!("xx> Some manual traits are missing from the Gir.toml file:");
for result in results.iter() {
println!("{}", result);
}
if check_license {
result &= license::run_check(&folder)?;
}
println!("<= done");
Ok(results.is_empty())
Ok(result)
}

fn show_help() {
println!("== checker options ==");
println!(" --gir-file : Set gir file path to be used for all following folders");
println!(" -h | --help : Display this help");
println!(" --gir-file : Set gir file path to be used for all following folders");
println!(" -h | --help : Display this help");
println!(" --no-manual-traits : Don't run manual_traits check");
println!(" --no-license : Don't run license check");
println!("");
println!("Any other argument will be the folder to run `checker` into.");
}

fn main() -> Result<()> {
fn main() -> types::CheckResult<()> {
let mut gir_file = "Gir.toml".to_owned();
let mut check_manual_traits = true;
let mut check_license = true;
let mut result = true;
let args = env::args().into_iter().skip(1).collect::<Vec<_>>();
let mut i = 0;
Expand All @@ -230,10 +63,15 @@ fn main() -> Result<()> {
} else if arg == "--help" || arg == "-h" {
show_help();
return Ok(());
} else if arg == "--no-manual-traits" {
check_manual_traits = false;
} else if arg == "--no-license" {
check_license = false;
} else {
if !run_check(&arg, &gir_file)? {
if !run_check(&arg, &gir_file, check_manual_traits, check_license)? {
result = false;
}
break;
}
i += 1;
}
Expand Down
Loading

0 comments on commit 55f5240

Please sign in to comment.