Skip to content
This repository has been archived by the owner on Apr 21, 2021. It is now read-only.

Added an option to let dogma parse source from stdin #194

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,47 @@ Inspecting 27 files.
How handy!


## Options
```--format``` can be one of ```flycheck```, ```json``` or ```simple``` (default)

```--stdin``` makes dogma read the source from stdin

## STDIN parsing
It is possible to use dogma to parse source direcly piped into it.
You can do this by cloning the repository, building and installing the archive like so :
```bash
git clone git@github.com:lpil/dogma.git
cd dogma
mix deps.get
mix archive.build
mix archive.install
```

You can now use dogma as a stand alone.
To let dogma parse stdin use the ```--stdin``` flag. If there is no filename given it will use "stdin" as filename.

examples :
```elixir
$> echo 'IO.puts( "hello world");' | mix dogma --stdin
#> Inspecting 1 file.
#>
#> X
#>
#> 1 files, 1 error!
#>
#> == stdin ==
#> 1: ModuleDoc: Module BearCenter.Picture is missing a @moduledoc.
```
or

```elixir
$> echo 'IO.puts( "hello world");' | mix dogma --format=flycheck --stdin /my/path/example/hello_world.ex
#> /my/path/example/hello_world.ex:1:1: W: Expressions should not be terminated by semicolons.
```

The previous example is mostly for linters like Flycheck ((spac)emacs) which will flush a buffer via stdin, but requires a file mapping of the results.


## Contributor Information

### Test it
Expand Down
27 changes: 25 additions & 2 deletions lib/dogma.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,43 @@ defmodule Dogma do

alias Dogma.Rules
alias Dogma.ScriptSources
alias Dogma.Script

@version Mix.Project.config[:version]

def run(dir_or_file, config, dispatcher) do
dir_or_file
|> ScriptSources.find(config.exclude)
|> ScriptSources.to_scripts
|> get_sources(config)
|> notify_start(dispatcher)
|> Rules.test(config.rules, dispatcher)
|> notify_finish(dispatcher)
end

def get_sources(dir_or_file, %{read_stdin: true} = _config) do
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't use _config, don't assign it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

if dir_or_file == nil, do: dir_or_file="stdin"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't define vars inside blocks and then use them outside of said blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean here...

case read_from_stdin do
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we either use ()s or pass in the accumulator to this function to make it clear it is a call

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passing the accumulator now.

{:ok, source} -> [source |> Script.parse(dir_or_file)]
_ -> System.halt(666)
end
end

def get_sources(dir_or_file, config) do
dir_or_file
|> ScriptSources.find(config.exclude)
|> ScriptSources.to_scripts
end

def version, do: @version

defp read_from_stdin(source \\ "") do
case IO.read(:stdio, :line) do
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an argument to read entirely to EOF in one go?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking to read the source in one go so its easier to add a check later to detect if the source is valid before we hand it off for parsing ?

{:error, reason} -> {:error, reason}
:eof -> {:ok, source}
data -> source = source <> data
read_from_stdin(source)
end
end

defp notify_start(scripts, dispatcher) do
GenEvent.sync_notify(dispatcher, {:start, scripts})
scripts
Expand Down
8 changes: 5 additions & 3 deletions lib/dogma/config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ defmodule Dogma.Config do
"""

defstruct(
rules: [],
exclude: [],
rules: [],
exclude: [],
read_stdin: nil
)


Expand All @@ -15,10 +16,11 @@ defmodule Dogma.Config do
@doc """
Build the config struct for the current project.
"""
def build do
def build(settings \\ %{read_stdin: false}) do
%__MODULE__{
rules: get_rules,
exclude: get_exclude,
read_stdin: settings[:read_stdin]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Funny spacing here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}
end

Expand Down
12 changes: 6 additions & 6 deletions lib/mix/tasks/dogma.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@ defmodule Mix.Tasks.Dogma do
@config_file_path "config/dogma.exs"

def run(argv) do
{dir, reporter, noerror} = argv |> parse_args
{dir_or_file, reporter, noerror, read_stdin?} = argv |> parse_args
if File.regular?(@config_file_path) do
Mix.Tasks.Loadconfig.run([ @config_file_path])
end
config = Config.build

config = Config.build(read_stdin: read_stdin?)
{:ok, dispatcher} = GenEvent.start_link([])
GenEvent.add_handler(dispatcher, reporter, [])

dir
dir_or_file
|> Dogma.run(config, dispatcher)
|> any_errors?
|> if do
Expand All @@ -30,18 +29,19 @@ defmodule Mix.Tasks.Dogma do
end

def parse_args(argv) do
switches = [format: :string, error: :boolean]
switches = [format: :string, error: :boolean, stdin: :boolean]
{switches, files, []} = OptionParser.parse(argv, switches: switches)

noerror = !Keyword.get(switches, :error, true)
read_stdin? = Keyword.get(switches, :stdin, false)
format = Keyword.get(switches, :format)
reporter = Map.get(
Reporters.reporters,
format,
Reporters.default_reporter
)

{List.first(files), reporter, noerror}
{List.first(files), reporter, noerror, read_stdin?}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tuple is starting to get a big unwieldy I think. Perhaps something we could fix in another pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

end

defp any_errors?(scripts) do
Expand Down
22 changes: 15 additions & 7 deletions test/mix/tasks/dogma_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ defmodule Dogma.OptionParserTest do
having ".parse_args" do
having "empty args" do
should "return nil and default reporter" do
assert parse_args([]) == {nil, @default_reporter, false}
assert parse_args([]) == {nil, @default_reporter, false, false}
end
end

having "directories given" do
should "return directory and default reporter" do
assert parse_args(["lib/foo"]) == {"lib/foo", @default_reporter, false}
assert parse_args(["lib/foo"]) ==
{"lib/foo", @default_reporter, false, false}
end

having "multiple directories given" do
should "return first directory only" do
parsed = parse_args(["lib/foo", "lib/bar"])
assert parsed == {"lib/foo", @default_reporter, false}
assert parsed == {"lib/foo", @default_reporter, false, false}
end
end
end
Expand All @@ -31,29 +32,36 @@ defmodule Dogma.OptionParserTest do
having "simple passed" do
should "return simple reporter" do
parsed = parse_args(["--format", "simple"])
assert parsed == {nil, Dogma.Reporter.Simple, false}
assert parsed == {nil, Dogma.Reporter.Simple, false, false}
end
end

having "flycheck passed" do
should "return flycheck reporter" do
parsed = parse_args(["--format=flycheck"])
assert parsed == {nil, Dogma.Reporter.Flycheck, false}
assert parsed == {nil, Dogma.Reporter.Flycheck, false, false}
end
end

having "unknown reporter passed" do
should "return default reporter" do
parsed = parse_args(["--format", "false"])
assert parsed == {nil, @default_reporter, false}
assert parsed == {nil, @default_reporter, false, false}
end
end
end

having "no-error option passed" do
should "return disabled error exit code" do
parsed = parse_args(["--no-error"])
assert parsed == {nil, @default_reporter, true}
assert parsed == {nil, @default_reporter, true, false}
end
end

having "stdin option passed" do
should "return stdin flag" do
parsed = parse_args(["--stdin"])
assert parsed == {nil, @default_reporter, false, true}
end
end
end
Expand Down