-
Notifications
You must be signed in to change notification settings - Fork 28
Add support for .licensir.exs
file
#18
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Copyright (c) 2015-2020 René Föhring | ||
|
||
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
This directory contains a copy of [rrrene/credo](https://github.com/rrrene/credo). | ||
|
||
A hard copy is used instead of a dependency so that `mix archive.install ...`, | ||
which does not recognize the archive's defined dependencies, is supported. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
defmodule Credo.ExsLoader do | ||
@moduledoc false | ||
|
||
def parse(exs_string) do | ||
case Code.string_to_quoted(exs_string) do | ||
{:ok, ast} -> | ||
{:ok, process_exs(ast)} | ||
|
||
{:error, {line_meta, message, trigger}} when is_list(line_meta) -> | ||
{:error, {line_meta[:line], message, trigger}} | ||
|
||
{:error, value} -> | ||
{:error, value} | ||
end | ||
end | ||
|
||
defp process_exs(v) | ||
when is_atom(v) or is_binary(v) or is_float(v) or is_integer(v), | ||
do: v | ||
|
||
defp process_exs(list) when is_list(list) do | ||
Enum.map(list, &process_exs/1) | ||
end | ||
|
||
defp process_exs({:sigil_w, _, [{:<<>>, _, [list_as_string]}, []]}) do | ||
String.split(list_as_string, ~r/\s+/) | ||
end | ||
|
||
# TODO: support regex modifiers | ||
defp process_exs({:sigil_r, _, [{:<<>>, _, [regex_as_string]}, []]}) do | ||
Regex.compile!(regex_as_string) | ||
end | ||
|
||
defp process_exs({:%{}, _meta, body}) do | ||
process_map(body, %{}) | ||
end | ||
|
||
defp process_exs({:{}, _meta, body}) do | ||
process_tuple(body, {}) | ||
end | ||
|
||
defp process_exs({:__aliases__, _meta, name_list}) do | ||
Module.safe_concat(name_list) | ||
end | ||
|
||
defp process_exs({{:__aliases__, _meta, name_list}, options}) do | ||
{Module.safe_concat(name_list), process_exs(options)} | ||
end | ||
|
||
defp process_exs({key, value}) when is_atom(key) or is_binary(key) do | ||
{process_exs(key), process_exs(value)} | ||
end | ||
|
||
defp process_tuple([], acc), do: acc | ||
|
||
defp process_tuple([head | tail], acc) do | ||
acc = process_tuple_item(head, acc) | ||
process_tuple(tail, acc) | ||
end | ||
|
||
defp process_tuple_item(value, acc) do | ||
Tuple.append(acc, process_exs(value)) | ||
end | ||
|
||
defp process_map([], acc), do: acc | ||
|
||
defp process_map([head | tail], acc) do | ||
acc = process_map_item(head, acc) | ||
process_map(tail, acc) | ||
end | ||
|
||
defp process_map_item({key, value}, acc) | ||
when is_atom(key) or is_binary(key) do | ||
Map.put(acc, key, process_exs(value)) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
defmodule Licensir.ConfigFile do | ||
@moduledoc """ | ||
Parse a project's .licensir.exs file to determine what licenses are acceptable to the user, not acceptable, and projects that are allowed | ||
""" | ||
|
||
@config_filename ".licensir.exs" | ||
|
||
defstruct allowlist: [], denylist: [], allow_deps: [] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change to |
||
|
||
def parse(nil), do: parse(@config_filename) | ||
def parse(file) do | ||
if File.exists?(file) do | ||
{:ok, raw} = | ||
file | ||
|> File.read!() | ||
|> Credo.ExsLoader.parse() | ||
|
||
{:ok, | ||
%__MODULE__{ | ||
allowlist: raw[:allowlist] || [], | ||
denylist: raw[:denylist] || [], | ||
allow_deps: Enum.map(raw[:allow_deps] || [], &Atom.to_string/1) | ||
}} | ||
else | ||
{:ok, %__MODULE__{}} | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ defmodule Licensir.License do | |
license: nil, | ||
certainty: 0.0, | ||
mix: nil, | ||
status: :unknown, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm open to a better name here. This is also what would be in the csv and stdout output. |
||
hex_metadata: nil, | ||
file: nil | ||
|
||
|
@@ -33,6 +34,9 @@ defmodule Licensir.License do | |
certainty: float(), | ||
mix: list(String.t()) | nil, | ||
hex_metadata: list(String.t()) | nil, | ||
status: status(), | ||
file: String.t() | nil | ||
} | ||
|
||
@type status :: :allowed | :not_allowed | :unknown | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,16 +19,32 @@ defmodule Mix.Tasks.Licenses do | |
def run(argv) do | ||
{opts, _argv} = OptionParser.parse!(argv, switches: @switches) | ||
|
||
Licensir.Scanner.scan(opts) | ||
|> Enum.sort_by(fn license -> license.name end) | ||
licenses = | ||
opts | ||
|> Licensir.Scanner.scan() | ||
|> Enum.sort_by(fn license -> license.name end) | ||
|
||
licenses | ||
|> Enum.map(&to_row/1) | ||
|> render(opts) | ||
|
||
exit_status(licenses) | ||
end | ||
|
||
defp exit_status(licenses) do | ||
if Enum.any?(licenses, &(&1.status == :not_allowed)) do | ||
exit({:shutdown, 1}) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't see a good way to test this since it would also shutdown the test, but running it manually proved it worked. |
||
end | ||
|
||
defp to_row(map) do | ||
[map.name, map.version, map.license] | ||
[map.name, map.version, map.license, license_status(map.status)] | ||
end | ||
|
||
def license_status(:allowed), do: "Allowed" | ||
def license_status(:not_allowed), do: "Not allowed" | ||
def license_status(_), do: "Unknown" | ||
|
||
defp render(rows, opts) do | ||
cond do | ||
Keyword.get(opts, :csv) -> render_csv(rows) | ||
|
@@ -40,13 +56,13 @@ defmodule Mix.Tasks.Licenses do | |
_ = Mix.Shell.IO.info([:yellow, "Notice: This is not a legal advice. Use the information below at your own risk."]) | ||
|
||
rows | ||
|> TableRex.quick_render!(["Package", "Version", "License"]) | ||
|> TableRex.quick_render!(["Package", "Version", "License", "Status"]) | ||
|> IO.puts() | ||
end | ||
|
||
defp render_csv(rows) do | ||
rows | ||
|> List.insert_at(0, ["Package", "Version", "License"]) | ||
|> List.insert_at(0, ["Package", "Version", "License", "Status"]) | ||
|> CSV.encode() | ||
|> Enum.each(&IO.write/1) | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
%{ | ||
allowlist: ["MIT", "Apache 2.0"], | ||
denylist: ["GPLv2", "Licensir Mock License"], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I opted for the human name since that's the output folks would see in their stdout/csv. |
||
allow_deps: [:dep_mock_license] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
defmodule Licensir.ConfigFileTest do | ||
use Licensir.Case | ||
alias Licensir.{ConfigFile} | ||
|
||
describe "parse" do | ||
test "returns options" do | ||
assert {:ok, config} = ConfigFile.parse("test/fixtures/licensir-config.exs") | ||
assert config.allowlist == ["MIT", "Apache 2.0"] | ||
assert config.denylist == ["GPLv2", "Licensir Mock License"] | ||
assert config.allow_deps == ["dep_mock_license"] | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
original issue mentioned
.licenses.exs
but I used.licensir.exs
instead. LMK which you prefer