Skip to content

Commit

Permalink
initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
Im-Beast committed Nov 30, 2021
0 parents commit 0b290fa
Show file tree
Hide file tree
Showing 11 changed files with 770 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"deno.enable": true,
"deno.lint": true,
"deno.unstable": true,
"deno.suggest.imports.hosts": {
"https://deno.land": true
}
}
20 changes: 20 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# The MIT License (MIT)

## Copyright © 2021 Im-Beast

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 📝 Anzu
Anzu is very light CLI tool for checking whether files have license header

![Anzu help output](./docs/help.png)
![Anzu example output](./docs/example-output.png)

## Installation
- Without explicit permissions:
- `deno install https://deno.land/[email protected]/src/cli.ts -n anzu`
- With explicit permissions:
- `deno install --allow-read --allow-write --allow-net https://deno.land/[email protected]/src/cli.ts -n anzu`


## 📚 Why not [deno_license_checker](https://github.com/kt3k/deno_license_checker)
I got discouraged from deno_license_checker because of several things, majorly:
- Its size – 177KB
- Performance
- Requirement of config file
- Doesn't ask for permissions – they have to be specified otherwise it'll exit

Anzu solves some of these problems:
- It's significantly smaller – 27KB
- In my case its multiple times faster (benchmarks needed)
- Every option has to be set in CLI
- If you want to launch same command using one command – just create bash script that does that (see [here](./find-license.sh))
- If permissions aren't specified it automatically requests you for them

Why not Anzu?
- It's not compatible with windows (Im 99.9% sure, but didn't tested it)
- If you prefer having external config file Anzu probably isn't for you

What can Anzu also do?
- Additionally it can load license template from given URL and Path
- It can search for license using RegExp
- Exclude files and directories using regexp
- You can see full functionality using `-h` option.

## 🤝 Contributing

I'm open to any idea and criticism. Feel free to add any commits, issues and
pull requests!

## 📝 Licensing

This project is available under MIT License conditions.
Binary file added docs/example-output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/help.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions find-license.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Order of options doesn't matter – they get sorted automatically by priority
# Check licenses in ./ that match /.+\.ts/ js regexp
# Ignore files that match "deps.ts" regexp pattern
# Look for license with this pattern (when license is regexp it cannot be prepended!)
# Prepend license to the top of the file when

deno run ./src/cli.ts \
-i ./ "/.+\.ts/" \
-e "deps.ts" \
-l "// Copyright 2021 Im-Beast. All rights reserved. MIT license." \
-p

5 changes: 5 additions & 0 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2021 Im-Beast. All rights reserved. MIT license.
export * from "./src/cli.ts";
export * from "./src/deps.ts";
export * from "./src/license_check.ts";
export * from "./src/parse_args.ts";
278 changes: 278 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
// Copyright 2021 Im-Beast. All rights reserved. MIT license.
import {
bold,
cyan,
gray,
green,
italic,
magenta,
red,
yellow,
} from "./deps.ts";
import {
checkDirectoryForLicenses,
compileLicense,
compileStringRegexp,
PrependLicense,
} from "./license_check.ts";
import { parseArgs } from "./parse_args.ts";

type Option =
& {
message: string;
value?: string[];
priority: number;
}
& ({
args?: string[];
func?: (...args: string[]) => void;
} | {
args?: never;
func?: never;
});

interface Options {
[name: string]: Option;
}

const optionLinks = new Map<string, Option>();

const options: Options = {
help: {
message: "Shows this message",
func() {
console.log(options.help.message);
Deno.exit(0);
},
priority: 3,
},
license: {
message: "License text",
args: ["path|string|regexp|url"],
priority: 2,
},
prepend: {
message: "Prepend license header to files missing one",
args: ["?partial <false>"],
priority: 2,
},
normalizeNewlines: {
message: "converts \\n to newline in license field",
priority: 2,
},
quiet: {
message: "Do not print anything except errors",
priority: 2,
},
throwErrors: {
message: "Program will throw on errors instead of exiting",
priority: 2,
},
exclude: {
message: "Exclude given regexps",
args: ["file-regexp", "?dir-regexp"],
priority: 2,
},
input: {
message: "Directory that will be searched using regexp pattern",
args: ["path", "file-regexp", "?dir-regexp </.+/>"],
async func(
path: string,
$fileRegexp: string,
$dirRegexp = "^(?!node_modules).+",
) {
if (!options.license.value?.length) {
cliError(
`License pattern is missing, set it using ${
styleArg("--license")
} option`,
);
Deno.exit(1);
}

let prepend: PrependLicense = PrependLicense.Never;

if (options.prepend.value) {
prepend = PrependLicense.FullyMissing;

if (options.prepend.value.length) {
try {
if (JSON.parse(options.prepend.value[0])) {
prepend = PrependLicense.PartialOrFullyMissing;
}
} catch (error) {
cliError(
`Failed parsing argument of ${
styleArg("-p|--prepend")
} option ${error.message}`,
error,
);
Deno.exit(1);
}
}
}

let normalizeNewlines = false;
if (options.normalizeNewlines.value) {
normalizeNewlines = true;
}

const license = await compileLicense(
options.license.value[0],
normalizeNewlines,
);
if (!license) {
cliError(`Given ${styleArg("--license")} argument is invalid!`);
Deno.exit(1);
}

try {
let excludeFileRegexp: RegExp | undefined = undefined;
let excludeDirRegexp: RegExp | undefined = undefined;

if (options.exclude?.value?.length) {
excludeFileRegexp = compileStringRegexp(options.exclude.value[0]) ||
new RegExp(options.exclude.value[0]);
if (options.exclude?.value?.length > 1) {
excludeDirRegexp = compileStringRegexp(options.exclude.value[1]) ||
new RegExp(options.exclude.value[1]);
}
}

const fileRegexp = compileStringRegexp($fileRegexp) ||
new RegExp($fileRegexp);
const dirRegexp = compileStringRegexp($dirRegexp) ||
new RegExp($dirRegexp);

try {
await checkDirectoryForLicenses({
fileRegexp,
dirRegexp,
excludeFileRegexp,
excludeDirRegexp,
license,
log: !options.quiet.value,
path,
prepend,
});
} catch (error) {
cliError(
`Failed while researching directory – ${error.message}`,
error,
);
}
} catch (error) {
cliError(`Given regex is invalid – ${error.message}`, error);
}
},
priority: 1,
},
};

/**
* Error handler
* @param message – message to be displayed when error happened
*/
function cliError(message: string, error?: Error): void {
if (options.throwErrors.value) {
throw error || new Error(message);
} else {
console.log(`${red("Error")} ${yellow(">")} ${message}`);
}
}

function styleArg(arg: string): string {
const optional = arg[0] === "?";

let text = optional ? magenta("?") + arg.slice(1) : arg;
text = text.replace(/(\<|\>)/g, yellow("$1"));
text = text.replace(/(\|)/g, green("$1"));
text = gray(text);

return green(`[${text}]`);
}

if (import.meta.main) {
let message = bold("Anzu - deno license checker\n\n");
for (const [name, command] of Object.entries(options)) {
if (optionLinks.get(name)) {
throw new Error("Option with this name already exists!");
}

let short = `-${name[0]}`;
let i = 1;
while (optionLinks.get(short)) {
short = `-${name.slice(i++)}`;
}
const long = `--${name}`;

message += `${cyan(short)} ${cyan(long)} ${
command.args?.length ? `${command.args.map(styleArg).join(" ")} ` : ""
}${gray("–")} ${command.message}\n`;

optionLinks.set(short, command);
optionLinks.set(long, command);
}

message += italic(`\nLegend ${
styleArg(
`"${
magenta("?")
}" – argument is optional | "<value>" – default value | "|" – or`,
)
}`);

options.help.message = message;

const cliArgs = parseArgs(Deno.args);

interface Action {
priority: number;
func: () => void;
}

const actions: Action[] = [];
const entries = Object.entries(cliArgs);

if (entries.length === 0) {
entries.push(["--help", []]);
}

for (const [name, args] of entries) {
const option = optionLinks.get(name);
if (!option) {
cliError(`Option ${cyan(name)} has not been found`);
Deno.exit(1);
}

if (option.args) {
for (const [i, arg] of option.args.entries()) {
if (arg[0] !== "?" && !args[i]) {
cliError(
`Required option argument ${styleArg(arg)} for ${
cyan(name)
} is missing`,
);
Deno.exit(1);
}
}
}

const action: Action = {
priority: option.priority,
func: () => {
if (option.func) {
option.func(...args);
} else {
option.value = args;
}
},
};

actions.push(action);
}

for (const { func } of actions.sort((a, b) => b.priority - a.priority)) {
func();
}
}
11 changes: 11 additions & 0 deletions src/deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export {
bold,
cyan,
gray,
green,
italic,
magenta,
red,
white,
yellow,
} from "https://deno.land/[email protected]/fmt/colors.ts";
Loading

0 comments on commit 0b290fa

Please sign in to comment.