├── .formatter.exs
├── .github
└── FUNDING.yml
├── .gitignore
├── .todo.exs
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── config
└── config.exs
├── lib
├── cli
│ └── cli.ex
├── codetag_entry.ex
├── config.ex
├── ex_todo.ex
├── file_summary.ex
├── file_utils.ex
├── mix
│ ├── todo.ex
│ └── todo.gen.config.ex
└── output_utils.ex
├── mix.exs
├── mix.lock
├── sample_output.jpg
└── test
├── file_utils_test.exs
├── sample_files
├── c_sample.c
├── ex_sample.ex
└── js
│ └── js_sample.js
└── test_helper.exs
/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [akoutmos]
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where third-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | ex_todo-*.tar
24 |
25 |
--------------------------------------------------------------------------------
/.todo.exs:
--------------------------------------------------------------------------------
1 | %ExTodo.Config{
2 | error_codetags: ["FIXME", "BUG"],
3 | skip_patterns: [
4 | ~r/\.git/,
5 | ~r/_build/,
6 | ~r/deps/,
7 | ~r/cover/,
8 | ~r/docs/,
9 | ~r/\.todo\.exs/,
10 | ~r/README\.md/,
11 | ~r/^lib\/.*/,
12 | ~r/file_utils_test\.exs$/
13 | ],
14 | supported_codetags: ["NOTE", "TODO", "FIXME", "HACK", "BUG"]
15 | }
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: elixir
2 | elixir:
3 | - 1.8.1
4 | otp_release:
5 | - 21.1
6 | matrix:
7 | include:
8 | - otp_release: 22.1
9 | elixir: 1.8
10 | - otp_release: 22.1
11 | elixir: 1.9
12 | - otp_release: 22.1
13 | elixir: 1.10
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [0.1.0] - 2019-6-18
11 |
12 | ### Added
13 |
14 | - Initial release of ExTodo
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Alexander Koutmos
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ExTodo
2 |
3 | [](http://hex.pm/packages/ex_todo) [](https://travis-ci.org/akoutmos/ex_todo)
4 |
5 | A simple utility to keep track of codetags in your project. The list of codetags that are captured is configurable via a `.todo.exs` file, and you can even have `mix todo` return a non-zero exit status if it finds certain codetags in your codebase. For example if you would like ex_todo to fail in CI/CD if FIXME or BUG codetags are found, you can do that.
6 |
7 | ExTodo works on all file formats and can be given a list of regular expressions for files/paths that should be skipped.
8 |
9 | Inspiration for output taken from [https://www.npmjs.com/package/leasot](https://www.npmjs.com/package/leasot)
10 |
11 | ## Installation
12 |
13 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed
14 | by adding `ex_todo` to your list of dependencies in `mix.exs`:
15 |
16 | ```elixir
17 | def deps do
18 | [
19 | {:ex_todo, "~> 0.1.0"}
20 | ]
21 | end
22 | ```
23 |
24 | Documentation can be found at [https://hexdocs.pm/ex_todo](https://hexdocs.pm/ex_todo).
25 |
26 | ## Usage
27 |
28 | ExTodo comes with 2 mix tasks. One to run the documentation coverage report, and another to generate a `.todo.exs` config file.
29 |
30 | To run the ex_todo mix task and generate a report run: `mix todo`
31 | To generate a `.todo.exs` config file with defaults, run: `mix todo.gen.config`
32 |
33 | ## Sample report
34 |
35 | By running `mix todo` in this repo we get:
36 |
37 |
38 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | # This configuration is loaded before any dependency and is restricted
6 | # to this project. If another project depends on this project, this
7 | # file won't be loaded nor affect the parent project. For this reason,
8 | # if you want to provide default values for your application for
9 | # third-party users, it should be done in your "mix.exs" file.
10 |
11 | # You can configure your application as:
12 | #
13 | # config :ex_todo, key: :value
14 | #
15 | # and access this configuration in your application as:
16 | #
17 | # Application.get_env(:ex_todo, :key)
18 | #
19 | # You can also configure a third-party app:
20 | #
21 | # config :logger, level: :info
22 | #
23 |
24 | # It is also possible to import configuration files, relative to this
25 | # directory. For example, you can emulate configuration per environment
26 | # by uncommenting the line below and defining dev.exs, test.exs and such.
27 | # Configuration from the imported file will override the ones defined
28 | # here (which is why it is important to import them last).
29 | #
30 | # import_config "#{Mix.env()}.exs"
31 |
--------------------------------------------------------------------------------
/lib/cli/cli.ex:
--------------------------------------------------------------------------------
1 | defmodule ExTodo.CLI do
2 | alias ExTodo.{Config, FileSummary, FileUtils, OutputUtils, CodetagEntry}
3 | alias Mix.Shell.IO
4 |
5 | @doc "Given a config, run the codetags report"
6 | def run_report(%Config{} = config) do
7 | config
8 | |> FileUtils.get_all_files()
9 | |> FileUtils.read_file_list_contents()
10 | |> FileUtils.get_file_list_codetags(config)
11 | |> Enum.map(fn {path, file_codetags} ->
12 | FileSummary.build(path, file_codetags)
13 | end)
14 | |> Enum.sort(fn file_summary_1, file_summary_2 ->
15 | file_summary_1.file_path <= file_summary_2.file_path
16 | end)
17 | |> output_report()
18 | |> output_summary(config)
19 | |> report_result(config)
20 | end
21 |
22 | defp output_report([]) do
23 | IO.info("No codetags of interest have been found in the codebase.")
24 | end
25 |
26 | defp output_report(entries) do
27 | Enum.each(entries, fn entry ->
28 | entry.file_path
29 | |> OutputUtils.blue_text()
30 | |> OutputUtils.underline_text()
31 | |> IO.info()
32 |
33 | Enum.each(entry.todo_entries, fn todo_entry ->
34 | line_label =
35 | " line #{Integer.to_string(todo_entry.line)}"
36 | |> OutputUtils.gen_fixed_width_string(12, 1)
37 | |> OutputUtils.green_text()
38 |
39 | type_label =
40 | todo_entry.type
41 | |> OutputUtils.gen_fixed_width_string(8, 1)
42 | |> OutputUtils.white_text()
43 |
44 | comment = OutputUtils.light_cyan_text(todo_entry.comment)
45 |
46 | IO.info("#{line_label}#{type_label}#{comment}")
47 | end)
48 |
49 | IO.info("")
50 | end)
51 |
52 | entries
53 | end
54 |
55 | defp output_summary(entries, %Config{} = config) do
56 | "ExTodo Scan Summary"
57 | |> OutputUtils.blue_text()
58 | |> OutputUtils.underline_text()
59 | |> IO.info()
60 |
61 | entries
62 | |> Enum.map(fn files ->
63 | files.todo_entries
64 | end)
65 | |> List.flatten()
66 | |> Enum.reduce(%{}, fn entry, acc ->
67 | acc
68 | |> Map.update(entry.type, 1, &(&1 + 1))
69 | end)
70 | |> Map.to_list()
71 | |> Enum.sort(fn {keyword_1, _}, {keyword_2, _} ->
72 | keyword_1 <= keyword_2
73 | end)
74 | |> Enum.each(fn {keyword, count} ->
75 | if keyword in config.error_codetags do
76 | type =
77 | " #{keyword}"
78 | |> OutputUtils.gen_fixed_width_string(10, 1)
79 | |> OutputUtils.red_text()
80 |
81 | count =
82 | count
83 | |> OutputUtils.gen_fixed_width_string(10, 1)
84 | |> OutputUtils.red_text()
85 |
86 | IO.info("#{type}#{count}")
87 | else
88 | type =
89 | " #{keyword}"
90 | |> OutputUtils.gen_fixed_width_string(10, 1)
91 | |> OutputUtils.green_text()
92 |
93 | count =
94 | count
95 | |> OutputUtils.gen_fixed_width_string(10, 1)
96 | |> OutputUtils.green_text()
97 |
98 | IO.info("#{type}#{count}")
99 | end
100 | end)
101 |
102 | entries
103 | end
104 |
105 | defp report_result(entries, %Config{} = config) do
106 | found_errors =
107 | entries
108 | |> Enum.map(fn files ->
109 | files.todo_entries
110 | end)
111 | |> List.flatten()
112 | |> Enum.find_value(false, fn %CodetagEntry{type: type} ->
113 | type in config.error_codetags
114 | end)
115 |
116 | not found_errors
117 | end
118 | end
119 |
--------------------------------------------------------------------------------
/lib/codetag_entry.ex:
--------------------------------------------------------------------------------
1 | defmodule ExTodo.CodetagEntry do
2 | @moduledoc """
3 | This module defines a struct which contains all the information for a single
4 | codetag entry.
5 | """
6 |
7 | alias __MODULE__
8 |
9 | defstruct ~w(type line comment)a
10 |
11 | @doc "Build a single codetag entry"
12 | def build(type, line, comment) do
13 | %CodetagEntry{
14 | type: type,
15 | line: line,
16 | comment: comment
17 | }
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/config.ex:
--------------------------------------------------------------------------------
1 | defmodule ExTodo.Config do
2 | @moduledoc """
3 | This module defines a struct which houses all the
4 | configuration data for ex_todo.
5 | """
6 |
7 | @config_file ".todo.exs"
8 |
9 | alias __MODULE__
10 |
11 | defstruct supported_codetags: ~w(NOTE TODO FIXME HACK BUG),
12 | error_codetags: ~w(FIXME BUG),
13 | skip_patterns: [~r/\.git/, ~r/_build/, ~r/deps/, ~r/cover/, ~r/docs/, ~r/\.todo\.exs/]
14 |
15 | @doc """
16 | Get the configuration defaults as a Config struct
17 | """
18 | def config_defaults_as_map, do: %Config{}
19 |
20 | @doc """
21 | Get the configuration defaults as a string
22 | """
23 | def config_defaults_as_string do
24 | config = quote do: unquote(%Config{})
25 |
26 | config
27 | |> Macro.to_string()
28 | |> Code.format_string!()
29 | end
30 |
31 | @doc """
32 | Get the configuration file name
33 | """
34 | def config_file, do: @config_file
35 | end
36 |
--------------------------------------------------------------------------------
/lib/ex_todo.ex:
--------------------------------------------------------------------------------
1 | defmodule ExTodo do
2 | @moduledoc """
3 | Documentation for ExTodo.
4 | """
5 | end
6 |
--------------------------------------------------------------------------------
/lib/file_summary.ex:
--------------------------------------------------------------------------------
1 | defmodule ExTodo.FileSummary do
2 | @moduledoc """
3 | This module defines a struct which is used to encapsulate all the information
4 | for a file that contains code tags
5 | """
6 |
7 | alias __MODULE__
8 |
9 | defstruct file_path: nil, todo_entries: []
10 |
11 | @doc "Build a file summary sruct"
12 | def build(file_path, todo_entries) do
13 | %FileSummary{
14 | file_path: file_path,
15 | todo_entries: todo_entries
16 | }
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/file_utils.ex:
--------------------------------------------------------------------------------
1 | defmodule ExTodo.FileUtils do
2 | @moduledoc """
3 | Utililities for deals with files and searching for code tags.
4 | """
5 |
6 | alias ExTodo.{Config, CodetagEntry}
7 |
8 | @glob_pattern "./**"
9 |
10 | @doc "Get all of the files according to the fileglob"
11 | def get_all_files(%Config{} = config, file_glob \\ @glob_pattern) do
12 | file_glob
13 | |> Path.wildcard(match_dot: true)
14 | |> Enum.reject(fn entry ->
15 | not_file?(entry) or path_in_ignore_list?(entry, config.skip_patterns)
16 | end)
17 | end
18 |
19 | @doc "Read the contents of all the files in the provided list"
20 | def read_file_list_contents(file_list) do
21 | file_list
22 | |> Enum.reduce([], fn file_path, acc ->
23 | file_path
24 | |> File.read()
25 | |> case do
26 | {:ok, file_contents} ->
27 | [{file_path, file_contents} | acc]
28 |
29 | {:error, _reason} ->
30 | acc
31 | end
32 | end)
33 | end
34 |
35 | @doc "Get all of the codetags within the list of files given the config settings"
36 | def get_file_list_codetags(file_contents_list, %Config{} = config) do
37 | file_contents_list
38 | |> Enum.reduce([], fn {file_path, file_contents}, acc ->
39 | file_contents
40 | |> String.split("\n")
41 | |> get_lines_with_codetags(config)
42 | |> case do
43 | [] ->
44 | acc
45 |
46 | codetag_entries ->
47 | [{file_path, codetag_entries} | acc]
48 | end
49 | end)
50 | end
51 |
52 | defp path_in_ignore_list?(path, skip_patterns) do
53 | Enum.find_value(skip_patterns, false, fn skip_pattern ->
54 | Regex.match?(skip_pattern, path)
55 | end)
56 | end
57 |
58 | defp not_file?(path), do: not File.regular?(path)
59 |
60 | defp get_lines_with_codetags(file_contents, config) do
61 | get_lines_with_codetags(file_contents, config, 1, [])
62 | end
63 |
64 | defp get_lines_with_codetags([], _config, _line_num, acc) do
65 | Enum.reverse(acc)
66 | end
67 |
68 | defp get_lines_with_codetags([current_line | tail], config, line_num, acc) do
69 | fuzzy_match_list =
70 | config.supported_codetags
71 | |> Enum.map(fn keyword ->
72 | [
73 | {keyword, "#{keyword}:"},
74 | {keyword, "#{keyword} :"},
75 | {keyword, "#{keyword}-"},
76 | {keyword, "#{keyword} -"},
77 | {keyword, keyword}
78 | ]
79 | end)
80 | |> List.flatten()
81 |
82 | {original, keyword_in_line} =
83 | Enum.find(fuzzy_match_list, {:not_found, :not_found}, fn {_original, keyword} ->
84 | String.contains?(current_line, keyword)
85 | end)
86 |
87 | acc =
88 | if keyword_in_line != :not_found do
89 | comment =
90 | current_line
91 | |> String.split(keyword_in_line, parts: 2)
92 | |> Enum.at(1)
93 | |> String.trim()
94 |
95 | [%CodetagEntry{type: original, line: line_num, comment: comment} | acc]
96 | else
97 | acc
98 | end
99 |
100 | get_lines_with_codetags(tail, config, line_num + 1, acc)
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/lib/mix/todo.ex:
--------------------------------------------------------------------------------
1 | defmodule Mix.Tasks.Todo do
2 | @moduledoc false
3 |
4 | use Mix.Task
5 |
6 | alias ExTodo.Config
7 | alias Mix.Shell.IO
8 |
9 | @shortdoc "Find TODO, FIXME, NOTE, etc throughout your codebase"
10 |
11 | @doc """
12 | This Mix task generates a ExTodo report of the project.
13 | """
14 | def run(_args) do
15 | result =
16 | Config.config_file()
17 | |> load_config_file()
18 | |> merge_defaults()
19 | |> ExTodo.CLI.run_report()
20 |
21 | unless result do
22 | System.at_exit(fn _ ->
23 | exit({:shutdown, 1})
24 | end)
25 | end
26 |
27 | :ok
28 | end
29 |
30 | defp load_config_file(file) do
31 | if File.exists?(file) do
32 | IO.info("ExTodo file found. Loading configuration.")
33 |
34 | {config, _bindings} = Code.eval_file(file)
35 |
36 | config
37 | else
38 | IO.info("ExTodo file not found. Using defaults.")
39 |
40 | %{}
41 | end
42 | end
43 |
44 | defp merge_defaults(config) do
45 | Map.merge(Config.config_defaults_as_map(), config)
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/mix/todo.gen.config.ex:
--------------------------------------------------------------------------------
1 | defmodule Mix.Tasks.Todo.Gen.Config do
2 | @moduledoc false
3 |
4 | use Mix.Task
5 |
6 | alias Mix.Shell.IO
7 | alias ExTodo.Config
8 |
9 | @shortdoc "Creates a .todo.exs config file with defaults"
10 |
11 | @doc """
12 | This Mix task generates a .todo.exs configuration file
13 | """
14 | def run(_args) do
15 | create_file =
16 | if File.exists?(Config.config_file()) do
17 | IO.yes?("An existing ex_todo config file already exists. Overwrite?")
18 | else
19 | true
20 | end
21 |
22 | if create_file do
23 | create_config_file()
24 |
25 | IO.info("Successfully created .todo.exs file.")
26 | else
27 | IO.info("Did not create .todo.exs file.")
28 | end
29 | end
30 |
31 | defp create_config_file do
32 | File.cwd!()
33 | |> Path.join(Config.config_file())
34 | |> File.write(Config.config_defaults_as_string())
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/output_utils.ex:
--------------------------------------------------------------------------------
1 | defmodule ExTodo.OutputUtils do
2 | @moduledoc """
3 | This module is used to format strings for STDOUT so that reports are easy
4 | to read.
5 | """
6 |
7 | alias Elixir.IO.ANSI
8 |
9 | @doc "Underline the provided text"
10 | def underline_text(text) do
11 | ANSI.underline() <> text <> ANSI.reset()
12 | end
13 |
14 | @doc "Make the provided text green"
15 | def green_text(text) do
16 | ANSI.green() <> text <> ANSI.reset()
17 | end
18 |
19 | @doc "Make the provided text blue"
20 | def blue_text(text) do
21 | ANSI.blue() <> text <> ANSI.reset()
22 | end
23 |
24 | @doc "Make the provided text white"
25 | def white_text(text) do
26 | ANSI.white() <> text <> ANSI.reset()
27 | end
28 |
29 | @doc "Make the provided text red"
30 | def red_text(text) do
31 | ANSI.red() <> text <> ANSI.reset()
32 | end
33 |
34 | @doc "Make the provided text ligth cyan"
35 | def light_cyan_text(text) do
36 | ANSI.light_cyan() <> text <> ANSI.reset()
37 | end
38 |
39 | @doc "Format a string to be of a certain width and have a certain padding"
40 | def gen_fixed_width_string(value, width, padding \\ 2)
41 |
42 | def gen_fixed_width_string(value, width, padding) when is_integer(value) do
43 | value
44 | |> Integer.to_string()
45 | |> gen_fixed_width_string(width, padding)
46 | end
47 |
48 | def gen_fixed_width_string(value, width, padding) do
49 | sub_string_length = width - (padding + 1)
50 |
51 | value
52 | |> String.slice(0..sub_string_length)
53 | |> String.pad_trailing(width)
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule ExTodo.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :ex_todo,
7 | version: "0.1.0",
8 | elixir: "~> 1.7",
9 | name: "ExTodo",
10 | source_url: "https://github.com/akoutmos/ex_todo",
11 | homepage_url: "https://hex.pm/packages/ex_todo",
12 | description: "A simple utility to find codetags within a project",
13 | elixirc_paths: elixirc_paths(Mix.env()),
14 | start_permanent: Mix.env() == :prod,
15 | docs: [
16 | main: "readme",
17 | extras: ["README.md"]
18 | ],
19 | package: package(),
20 | deps: deps()
21 | ]
22 | end
23 |
24 | def application do
25 | [
26 | extra_applications: [:logger]
27 | ]
28 | end
29 |
30 | defp package() do
31 | [
32 | name: "ex_todo",
33 | files: ~w(lib mix.exs README.md LICENSE CHANGELOG.md),
34 | licenses: ["MIT"],
35 | links: %{"GitHub" => "https://github.com/akoutmos/ex_todo"}
36 | ]
37 | end
38 |
39 | defp elixirc_paths(:test), do: ["lib", "test/sample_files"]
40 | defp elixirc_paths(_), do: ["lib"]
41 |
42 | defp deps do
43 | [
44 | {:ex_doc, ">= 0.0.0"}
45 | ]
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
3 | "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
4 | "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
5 | "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
6 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
7 | }
8 |
--------------------------------------------------------------------------------
/sample_output.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akoutmos/ex_todo/f1aaf2ccb4ecdefb3fb48a36ef2cc76a0d9045fa/sample_output.jpg
--------------------------------------------------------------------------------
/test/file_utils_test.exs:
--------------------------------------------------------------------------------
1 | defmodule FilUtilsTest do
2 | use ExUnit.Case
3 |
4 | alias ExTodo.{Config, FileUtils}
5 |
6 | describe "get_all_files/2" do
7 | test "should return a list of all the files that match the catch all glob" do
8 | files = get_all_sample_files()
9 |
10 | assert files == [
11 | "test/sample_files/c_sample.c",
12 | "test/sample_files/ex_sample.ex",
13 | "test/sample_files/js/js_sample.js"
14 | ]
15 | end
16 |
17 | test "should return a list of all the files that match a certain file glob" do
18 | files =
19 | %Config{}
20 | |> FileUtils.get_all_files("./**/*.c")
21 | |> Enum.sort()
22 |
23 | assert files == ["test/sample_files/c_sample.c"]
24 | end
25 |
26 | test "should return a list of all files except the skipped files" do
27 | files = get_all_sample_files(%Config{skip_patterns: [~r/c_sample\.c/]})
28 |
29 | assert files == [
30 | "test/sample_files/ex_sample.ex",
31 | "test/sample_files/js/js_sample.js"
32 | ]
33 | end
34 |
35 | test "should return a list of all files except the skipped directories" do
36 | files = get_all_sample_files(%Config{skip_patterns: [~r/test\/sample_files\/js/]})
37 |
38 | assert files == [
39 | "test/sample_files/c_sample.c",
40 | "test/sample_files/ex_sample.ex"
41 | ]
42 | end
43 | end
44 |
45 | describe "read_file_list_contents/1" do
46 | test "should read the contents of all the files provided" do
47 | file_paths = get_all_sample_files()
48 |
49 | files_contents =
50 | file_paths
51 | |> FileUtils.read_file_list_contents()
52 |
53 | Enum.each(files_contents, fn {file_path, contents} ->
54 | assert file_path in file_paths
55 | assert String.length(contents) > 0
56 | end)
57 | end
58 |
59 | test "should return an empty list if file list is empty" do
60 | files =
61 | %Config{}
62 | |> FileUtils.get_all_files("./**/*.no_file")
63 | |> FileUtils.read_file_list_contents()
64 |
65 | assert files == []
66 | end
67 | end
68 |
69 | describe "get_file_list_codetags/2" do
70 | test "should return all the configured codetags found within some files" do
71 | config = %Config{skip_patterns: [~r/c_sample\.c/, ~r/js_sample\.js/]}
72 | files = get_all_sample_files(config)
73 |
74 | [{"test/sample_files/ex_sample.ex", codetag_results}] =
75 | files
76 | |> FileUtils.read_file_list_contents()
77 | |> FileUtils.get_file_list_codetags(config)
78 |
79 | assert length(codetag_results) == 2
80 | end
81 |
82 | test "should return an empty list if no codetags are found within some files" do
83 | config = %Config{
84 | skip_patterns: [~r/c_sample\.c/, ~r/js_sample\.js/],
85 | supported_codetags: []
86 | }
87 |
88 | files = get_all_sample_files(config)
89 |
90 | results =
91 | files
92 | |> FileUtils.read_file_list_contents()
93 | |> FileUtils.get_file_list_codetags(config)
94 |
95 | assert results == []
96 | end
97 |
98 | test "should return a list of files that only fullfil the configured codetags" do
99 | config = %Config{
100 | skip_patterns: [~r/c_sample\.c/, ~r/js_sample\.js/],
101 | supported_codetags: ["FIXME"]
102 | }
103 |
104 | files = get_all_sample_files(config)
105 |
106 | [{"test/sample_files/ex_sample.ex", codetag_results}] =
107 | files
108 | |> FileUtils.read_file_list_contents()
109 | |> FileUtils.get_file_list_codetags(config)
110 |
111 | assert length(codetag_results) == 1
112 | end
113 | end
114 |
115 | defp get_all_sample_files(config \\ %Config{}) do
116 | config
117 | |> FileUtils.get_all_files("./test/sample_files/**")
118 | |> Enum.sort()
119 | end
120 | end
121 |
--------------------------------------------------------------------------------
/test/sample_files/c_sample.c:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * TODO: This app is pretty complex
4 | *
5 | * FIXME: I should refactor it
6 | *
7 | * NOTE: You needs to start somewhere
8 | *
9 | */
10 |
11 | #include
12 |
13 | int main(void) {
14 | printf("Hello World!\n");
15 | return 0;
16 | }
17 |
--------------------------------------------------------------------------------
/test/sample_files/ex_sample.ex:
--------------------------------------------------------------------------------
1 | defmodule ExSample do
2 | @moduledoc """
3 | This module is super complex.
4 |
5 | TODO: This should really really really be refactored
6 |
7 | FIXME: I mean it!
8 | """
9 |
10 | def do_th_things(num) do
11 | num + 1
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/sample_files/js/js_sample.js:
--------------------------------------------------------------------------------
1 | // FIXME: Don't do anything CPU bound and block the event loop
2 | // TODO: Watch https://www.youtube.com/watch?v=JvBT4XBdoUE to see why the BEAM is awesome
3 |
4 | const http = require('http')
5 |
6 | http
7 | .createServer(function(req, res) {
8 | res.end('hello world!')
9 | })
10 | .listen(9000)
11 |
12 | console.log('Server listening on port 9000')
13 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------