Skip to content

Commit b8c420d

Browse files
committed
added sloc to command line
1 parent 8a87a8e commit b8c420d

13 files changed

Lines changed: 412 additions & 3 deletions

File tree

‎.gitignore‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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 3rd-party dependencies like ExDoc output generated docs.
11+
/doc
12+
13+
# If the VM crashes, it generates a dump, let's ignore it too.
14+
erl_crash.dump
15+
16+
# Also ignore archive artifacts (built via "mix archive.build").
17+
*.ez
18+
19+
.DS_Store
20+
21+
c

‎.travis.yml‎

Lines changed: 0 additions & 3 deletions
This file was deleted.

‎README.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# C
2+
3+
**TODO: Add description**
4+
5+
## Installation
6+
7+
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
8+
by adding `c` to your list of dependencies in `mix.exs`:
9+
10+
```elixir
11+
def deps do
12+
[{:c, "~> 0.1.0"}]
13+
end
14+
```
15+
16+
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
17+
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
18+
be found at [https://hexdocs.pm/c](https://hexdocs.pm/c).
19+

‎config/config.exs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use Mix.Config
2+
3+
# config/config.exs
4+
config :c, C.GitHub.Client,
5+
adapter: Revolver.Adapters.Hackney,
6+
host: "https://api.github.com",
7+
headers: [
8+
{"accept", "application/vnd.github.v3+json"},
9+
{"content-type", "application/json"}
10+
]
11+
12+
# Configure serializers for automatic encoding/decoding request/response bodies
13+
config :revolver,
14+
serializers: %{
15+
"application/json" => Poison,
16+
"application/vnd.github.v3+json" => Poison
17+
}

‎lib/c.ex‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule C do
2+
@moduledoc """
3+
Documentation for C.
4+
"""
5+
6+
@doc """
7+
Hello world.
8+
9+
## Examples
10+
11+
iex> C.hello
12+
:world
13+
14+
"""
15+
def hello do
16+
:world
17+
end
18+
end

‎lib/cli.ex‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
defmodule C.CLI do
2+
3+
def main(args) do
4+
{_opts, args, _} = OptionParser.parse(args)
5+
6+
[cmd | rest_args] = args
7+
execute(cmd, rest_args)
8+
end
9+
10+
# @grep_opts ~w(--break --heading --line-number --ignore-case --color=always --all-match)
11+
@grep_opts ~w(--ignore-case --color=always --all-match --name-only)
12+
def execute("look", keywords) do
13+
with {:ok, commits} <- rev_list(),
14+
{:ok, lines} <- cmd_value("git", ["grep"] ++ @grep_opts ++ match_patterns(keywords) ++ commits) do
15+
extract_file_names(lines) |> IO.inspect()
16+
end
17+
end
18+
def execute("sloc", args) do
19+
report = Sloc.get_report(get_path(args))
20+
IO.puts(Sloc.format_report(report))
21+
end
22+
def execute("req", _) do
23+
C.GitHub.API.list!() |> IO.inspect()
24+
end
25+
def execute("pwd", _), do: cmd("pwd")
26+
27+
def get_path([]), do: File.cwd!()
28+
def get_path([path]), do: path
29+
30+
def extract_file_names(commit_files) do
31+
commit_files
32+
|> String.split("\n")
33+
|> Enum.map(fn line ->
34+
[sha, file] = String.split(line, ":")
35+
%{file: file, sha: sha}
36+
end)
37+
|> group_by_map(&Map.get(&1, :file), &Map.get(&1, :sha))
38+
end
39+
40+
def group_by_map(items, grouper, mapper) do
41+
items
42+
|> Enum.group_by(grouper)
43+
|> Enum.map(fn {k, values} -> {k, Enum.map(values, mapper)} end)
44+
end
45+
46+
def cmd(command, args \\ []) do
47+
case System.cmd(command, args, [into: IO.stream(:stdio, :line), stderr_to_stdout: true, parallelism: false]) do
48+
{value, 0} -> {:ok, value}
49+
{_, 1} -> :error
50+
end
51+
end
52+
53+
def cmd_value(command, args) do
54+
case System.cmd(command, args) do
55+
{value, 0} -> {:ok, String.trim(value)}
56+
{err, 1} -> {:error, err}
57+
end
58+
end
59+
60+
def match_patterns(keywords) do
61+
Enum.flat_map(keywords, fn k -> ["-e", k] end)
62+
end
63+
64+
def rev_list() do
65+
with {rev_list, 0} <- System.cmd("git", ["rev-list", "--all"]) do
66+
commits = rev_list |> String.trim() |> String.split("\n")
67+
{:ok, commits}
68+
end
69+
end
70+
71+
end

‎lib/git.ex‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
defmodule C.Git do
2+
3+
def current_branch() do
4+
{:ok, "refs/heads/" <> branch_name} = cmd_value("git", ["symbolic-ref", "HEAD"])
5+
branch_name
6+
end
7+
8+
def create_pull_request do
9+
{org, repo} = github_org_and_repo()
10+
branch = current_branch()
11+
C.GitHub.API.get_pull_requests(org: org, repo: repo, head: branch, base: "master")
12+
end
13+
14+
def github_org_and_repo() do
15+
"git@github.com:" <> git_path = git_url("origin")
16+
[org, repo] = git_path |> String.replace(~r/\.git$/, "") |> String.split("/")
17+
{org, repo}
18+
end
19+
20+
def git_url(remote_name) do
21+
with {:ok, url} <- cmd_value("git", ["ls-remote", "--get-url", remote_name]), do: url
22+
end
23+
24+
def set_config(key, value) do
25+
cmd_value("git", ["config", "--global", key, value])
26+
end
27+
def get_config(key) do
28+
cmd_value("git", ["config", "--get", key])
29+
end
30+
31+
def cmd_value(command, args) do
32+
case System.cmd(command, args, cd: "/Users/venkat/Code/turtle/turtle-api") do
33+
{value, 0} -> {:ok, String.trim(value)}
34+
{err, 1} -> {:error, err}
35+
end
36+
end
37+
38+
end

‎lib/github_api.ex‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
defmodule C.GitHub.Client do
2+
use Revolver.Client, otp_app: :c
3+
end
4+
5+
defmodule C.GitHub.API do
6+
import C.GitHub.Client
7+
import Revolver.Conn
8+
9+
@doc """
10+
Fetches a list of repos for the give username.
11+
"""
12+
def list! do
13+
conn() |> get!("/users/venkatd/repos")
14+
end
15+
16+
def authorize! do
17+
auth = get_username_password()
18+
response = authorize_req(auth) |> post!("/authorizations")
19+
20+
otp = IO.gets("Please specify two-factor auth code: ") |> String.trim()
21+
22+
response = authorize_req(auth)
23+
|> put_req_header("x-github-otp", otp)
24+
|> post!("/authorizations")
25+
26+
token = response.resp_body["token"]
27+
28+
C.Git.set_config("code.github.token", token)
29+
30+
IO.puts("Authorized with GitHub")
31+
end
32+
33+
def authorize(conn) do
34+
case C.Git.get_config("code.github.token") do
35+
{:ok, token} ->
36+
put_req_header(conn, "authorization", "Bearer #{token}")
37+
end
38+
end
39+
40+
def create_pull_request(opts) do
41+
opts = Enum.into(opts, %{})
42+
do_create_pull_request(opts)
43+
end
44+
def do_create_pull_request(%{org: org, repo: repo, base: base, head: head}) do
45+
params = %{
46+
"title" => "Amazing new feature",
47+
"body" => "Please pull this in!",
48+
"head" => head,
49+
"base" => base
50+
}
51+
conn()
52+
|> authorize()
53+
|> put_req_body(params)
54+
|> post!("/repos/#{org}/#{repo}/pulls")
55+
end
56+
57+
def get_pull_requests(opts) do
58+
opts = Enum.into(opts, %{})
59+
do_get_pull_requests(opts)
60+
end
61+
def do_get_pull_requests(%{org: org, repo: repo, base: base, head: head}) do
62+
conn()
63+
|> authorize()
64+
|> get!("/repos/#{org}/#{repo}/pulls?state=open&base=#{base}&head=#{head}")
65+
end
66+
67+
defp authorize_req(auth) do
68+
conn()
69+
|> put_req_header("authorization", generate_basic_auth_header(auth))
70+
|> put_req_body(%{"scopes" => ["repo"], "note" => "code cli"})
71+
end
72+
73+
def get_username_password() do
74+
# {"venkatd", "9gbAQFPhFhQqp2"}
75+
username = IO.gets("username: ")
76+
password = IO.gets("password: ")
77+
{String.trim(username), String.trim(password)}
78+
end
79+
80+
def generate_basic_auth_header({username, password}) do
81+
"Basic " <> Base.encode64(username <> ":" <> password)
82+
end
83+
84+
end

‎lib/sloc.ex‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
defmodule Sloc do
2+
3+
defmodule Node do
4+
defstruct [:path, :type, line_count: 0, children: %{}]
5+
6+
def deep_map(%Node{type: :file}=node, func) do
7+
func.(node)
8+
end
9+
def deep_map(%Node{type: :directory, children: children}=node, func) do
10+
new_children = for {k, n} <- children, into: %{}, do: {k, deep_map(n, func)}
11+
func.(%{node | children: new_children})
12+
end
13+
end
14+
15+
@path "../turtle/turtle-api/lib"
16+
@whitelist ~w(.ex .exs .rb)
17+
18+
def run do
19+
report = get_report(@path)
20+
IO.puts(format_report(report))
21+
end
22+
23+
def format_report(node), do: format_report(node, 0)
24+
def format_report(%Node{path: path, line_count: line_count, children: children}, depth) do
25+
[
26+
String.duplicate(" ", depth),
27+
Path.basename(path), " (", to_string(line_count), ")",
28+
format_report(Map.values(children), depth + 1)
29+
]
30+
end
31+
def format_report(nodes, depth) when is_list(nodes) do
32+
nodes
33+
|> Enum.sort_by(fn n -> -n.line_count end)
34+
|> Enum.map(fn n -> ["\n", format_report(n, depth + 1)] end)
35+
end
36+
37+
def get_report(path) do
38+
get_tree(path)
39+
|> Node.deep_map(&calculate_line_count/1)
40+
|> Node.deep_map(&normalize_path(&1, path))
41+
|> Node.deep_map(&aggregate/1)
42+
end
43+
44+
def normalize_path(%Node{path: path}=node, root) do
45+
%{node | path: Path.relative_to(path, root)}
46+
end
47+
48+
def calculate_line_count(%Node{type: :file, path: path}=node) do
49+
%{node | line_count: line_count(path)}
50+
end
51+
def calculate_line_count(%Node{type: :directory}=node), do: node
52+
53+
def aggregate(%Node{type: :file}=node), do: node
54+
def aggregate(%Node{type: :directory, children: children}=node) do
55+
line_count = children |> Map.values() |> Enum.map(&Map.get(&1, :line_count)) |> Enum.sum()
56+
%{node | line_count: line_count}
57+
end
58+
59+
def get_tree(path) do
60+
cond do
61+
File.regular?(path) ->
62+
%Node{type: :file, path: path}
63+
File.dir?(path) ->
64+
children = for subpath <- get_filtered_subpaths(path), into: %{} do
65+
{Path.basename(subpath), get_tree(subpath)}
66+
end
67+
%Node{type: :directory, path: path, children: children}
68+
true -> nil
69+
end
70+
end
71+
72+
def line_count(filepath) do
73+
File.stream!(filepath)
74+
|> Stream.reject(&Regex.match?(~r/^\s*$/, &1))
75+
|> Enum.count()
76+
end
77+
78+
def get_filtered_subpaths(path) do
79+
path
80+
|> File.ls!()
81+
|> Enum.map(&Path.join(path, &1))
82+
|> Enum.reject(&is_dotfile?/1)
83+
|> Enum.filter(fn p ->
84+
File.dir?(p) || has_ext?(p, @whitelist)
85+
end)
86+
end
87+
88+
defp is_dotfile?(path) do
89+
case Path.basename(path) do
90+
"." <> _ -> true
91+
_ -> false
92+
end
93+
end
94+
95+
defp has_ext?(path, extensions) do
96+
Enum.member?(extensions, Path.extname(path))
97+
end
98+
99+
end

0 commit comments

Comments
 (0)