changed CHANGELOG.md
 
@@ -1,5 +1,11 @@
1
1
# Changelog
2
2
3
+ ## v0.1.1
4
+
5
+ * Changes
6
+ * Add `Fortune.info/1` utility for help debugging issues when the expected
7
+ fortunes aren't found.
8
+
3
9
## v0.1.0
4
10
5
11
Initial release to hex.
changed README.md
 
@@ -3,6 +3,7 @@
3
3
[![Hex version](https://img.shields.io/hexpm/v/fortune.svg "Hex version")](https://hex.pm/packages/fortune)
4
4
[![API docs](https://img.shields.io/hexpm/v/fortune.svg?label=hexdocs "API docs")](https://hexdocs.pm/fortune/)
5
5
[![CircleCI](https://circleci.com/gh/fhunleth/elixir-fortune.svg?style=svg)](https://circleci.com/gh/fhunleth/elixir-fortune)
6
+ [![REUSE status](https://api.reuse.software/badge/github.com/fhunleth/elixir-fortune)](https://api.reuse.software/info/github.com/fhunleth/elixir-fortune)
6
7
7
8
Get a fortune!
8
9
 
@@ -14,18 +15,23 @@ implementation. It is compatible with Unix fortune and can read most Unix
14
15
fortune files.
15
16
16
17
```elixir
17
- iex> Fortune.random()
18
- {:ok, "Harness the power of the BEAM, one Elixir potion at a time."}
18
+ iex> Fortune.random! |> IO.puts
19
+ Harness the power of the BEAM, one Elixir potion at a time.
20
+ :ok
19
21
```
20
22
21
23
No fortunes are provided, though. You'll need to add your own, add Elixir
22
24
libraries to your mix dependencies that have fortunes, or configure Fortune to
23
25
use your system ones.
24
26
25
- Here's an example on Mac when you've installed `fortune` via Homebrew:
27
+ An easy way to start is to install the Unix `fortune` program using your package
28
+ manager. If no fortunes are supplied by Elixir libraries, Fortune will consult
29
+ it. You can also force it using the `:include_system_fortunes?` option.
26
30
27
31
```elixir
28
- Fortune.random(include_system_fortunes?: true)
32
+ iex> Fortune.random!(include_system_fortunes?: true) |> IO.puts
33
+ Ignore the next fortune
34
+ :ok
29
35
```
30
36
31
37
Fortunes provided by Elixir libraries are stored in that library's
changed hex_metadata.config
 
@@ -1,18 +1,17 @@
1
1
{<<"links">>,
2
2
[{<<"Github">>,<<"https://github.com/fhunleth/elixir-fortune">>}]}.
3
3
{<<"name">>,<<"fortune">>}.
4
- {<<"version">>,<<"0.1.0">>}.
4
+ {<<"version">>,<<"0.1.1">>}.
5
5
{<<"description">>,<<"Get a fortune!">>}.
6
6
{<<"elixir">>,<<"~> 1.11">>}.
7
7
{<<"app">>,<<"fortune">>}.
8
8
{<<"files">>,
9
- [<<"CHANGELOG.md">>,<<"lib">>,<<"lib/fortune.ex">>,<<"lib/mix">>,
10
- <<"lib/mix/tasks">>,<<"lib/mix/tasks/compile">>,
11
- <<"lib/mix/tasks/compile/fortune_compiler.ex">>,<<"lib/fortune">>,
12
- <<"lib/fortune/strfile_writer.ex">>,<<"lib/fortune/strfile_reader.ex">>,
13
- <<"lib/fortune/finder.ex">>,<<"LICENSES/Apache-2.0.txt">>,
14
- <<"LICENSES/CC-BY-4.0.txt">>,<<"LICENSES/CC0-1.0.txt">>,<<"mix.exs">>,
15
- <<"README.md">>]}.
9
+ [<<"CHANGELOG.md">>,<<"lib">>,<<"lib/fortune.ex">>,<<"lib/fortune">>,
10
+ <<"lib/fortune/strfile_reader.ex">>,<<"lib/fortune/finder.ex">>,
11
+ <<"lib/fortune/strfile_writer.ex">>,<<"lib/mix">>,<<"lib/mix/tasks">>,
12
+ <<"lib/mix/tasks/compile">>,<<"lib/mix/tasks/compile/fortune_compiler.ex">>,
13
+ <<"LICENSES/Apache-2.0.txt">>,<<"LICENSES/CC-BY-4.0.txt">>,
14
+ <<"LICENSES/CC0-1.0.txt">>,<<"mix.exs">>,<<"README.md">>]}.
16
15
{<<"licenses">>,[<<"Apache-2.0">>]}.
17
16
{<<"requirements">>,[]}.
18
17
{<<"build_tools">>,[<<"mix">>]}.
changed lib/fortune.ex
 
@@ -58,23 +58,20 @@ defmodule Fortune do
58
58
59
59
@doc """
60
60
Return a random fortune
61
+
62
+ See `Fortune` for an overview and `fortune_options/0` for modifying fortune
63
+ search paths.
61
64
"""
62
65
@spec random(fortune_options) :: {:ok, String.t()} | {:error, atom()}
63
66
def random(options \\ []) do
64
- merged_options = Keyword.merge(Application.get_all_env(:fortune), options)
65
- strfiles = merged_options |> Finder.fortune_paths() |> open_all()
66
-
67
- if strfiles != [] do
68
- num_fortunes =
69
- Enum.reduce(strfiles, 0, fn strfile, acc -> acc + strfile.header.num_string end)
67
+ with {:ok, strfiles} <- open_all_strfiles(options) do
68
+ num_fortunes = count_fortunes(strfiles)
70
69
71
70
rand_fortune = :rand.uniform(num_fortunes - 1)
72
71
result = nth_fortune(strfiles, rand_fortune)
73
72
74
73
Enum.each(strfiles, &StrfileReader.close/1)
75
74
result
76
- else
77
- {:error, :no_fortunes}
78
75
end
79
76
end
80
77
 
@@ -86,6 +83,17 @@ defmodule Fortune do
86
83
end
87
84
end
88
85
86
+ defp open_all_strfiles(options) do
87
+ merged_options = Keyword.merge(Application.get_all_env(:fortune), options)
88
+ strfiles = merged_options |> Finder.fortune_paths() |> open_all()
89
+
90
+ if strfiles != [] do
91
+ {:ok, strfiles}
92
+ else
93
+ {:error, :no_fortunes}
94
+ end
95
+ end
96
+
89
97
defp open_all(paths, acc \\ [])
90
98
defp open_all([], acc), do: acc
91
99
 
@@ -97,7 +105,10 @@ defmodule Fortune do
97
105
end
98
106
99
107
@doc """
100
- Raising version of random/1
108
+ Return a random fortune or raise an exception
109
+
110
+ See `Fortune` for an overview and `fortune_options/0` for modifying fortune
111
+ search paths.
101
112
"""
102
113
@spec random!(fortune_options()) :: String.t()
103
114
def random!(options \\ []) do
 
@@ -106,4 +117,42 @@ defmodule Fortune do
106
117
{:error, reason} -> raise RuntimeError, "Fortune.random failed with #{reason}"
107
118
end
108
119
end
120
+
121
+ @doc """
122
+ Return statistics on fortunes
123
+
124
+ This can be useful for finding out what fortunes are available. The options are
125
+ the same as the ones for `random/1`.
126
+
127
+ NOTE: The returned map may change in the future which is why it is untyped.
128
+ """
129
+ @spec info(fortune_options) :: {:ok, map()} | {:error, atom()}
130
+ def info(options \\ []) do
131
+ with {:ok, strfiles} <- open_all_strfiles(options) do
132
+ num_fortunes = count_fortunes(strfiles)
133
+ num_files = Enum.count(strfiles)
134
+
135
+ files =
136
+ Enum.reduce(strfiles, %{}, fn strfile, acc ->
137
+ Map.put(acc, strfile.path, %{
138
+ num_string: strfile.header.num_string,
139
+ size: file_size(strfile.path)
140
+ })
141
+ end)
142
+
143
+ Enum.each(strfiles, &StrfileReader.close/1)
144
+ {:ok, %{num_fortunes: num_fortunes, num_files: num_files, files: files}}
145
+ end
146
+ end
147
+
148
+ defp file_size(path) do
149
+ case File.stat(path) do
150
+ {:ok, stat} -> stat.size
151
+ _ -> -1
152
+ end
153
+ end
154
+
155
+ defp count_fortunes(strfiles) do
156
+ Enum.reduce(strfiles, 0, fn strfile, acc -> acc + strfile.header.num_string end)
157
+ end
109
158
end
changed lib/fortune/strfile_reader.ex
 
@@ -5,12 +5,14 @@ defmodule Fortune.StrfileReader do
5
5
@moduledoc false
6
6
@strfile_max_header_len 44
7
7
8
+ @typep strfile() :: %{header: map(), path: String.t(), io: IO.device()}
9
+
8
10
@spec search_paths([String.t()]) :: [String.t()]
9
11
def search_paths(paths) do
10
12
Enum.flat_map(paths, &scan_for_fortunes/1)
11
13
end
12
14
13
- @spec open(Path.t()) :: {:ok, map()} | {:error, atom()}
15
+ @spec open(Path.t()) :: {:ok, strfile()} | {:error, atom()}
14
16
def open(path) do
15
17
with {:ok, io} <- open_index(path),
16
18
{:ok, header} <- read_header(io) do
 
@@ -18,11 +20,13 @@ defmodule Fortune.StrfileReader do
18
20
end
19
21
end
20
22
23
+ @spec close(strfile()) :: :ok
21
24
def close(strfile) do
22
25
_ = File.close(strfile.io)
23
26
:ok
24
27
end
25
28
29
+ @spec read_string(strfile(), non_neg_integer()) :: {:ok, String.t()} | {:error, atom()}
26
30
def read_string(strfile, index) do
27
31
offset_size = strfile.header.word_size
28
32
offset_size_bits = strfile.header.word_size * 8
 
@@ -34,14 +38,6 @@ defmodule Fortune.StrfileReader do
34
38
end
35
39
end
36
40
37
- def read_info(path) do
38
- with {:ok, io} <- open_index(path),
39
- {:ok, header} <- read_header(io) do
40
- _ = File.close(io)
41
- {:ok, header}
42
- end
43
- end
44
-
45
41
defp scan_for_fortunes(path) do
46
42
case File.stat(path) do
47
43
{:ok, %{type: :directory}} -> scan_dir_for_fortunes(path)
changed lib/mix/tasks/compile/fortune_compiler.ex
 
@@ -32,6 +32,7 @@ defmodule Mix.Tasks.Compile.FortuneCompiler do
32
32
@recursive true
33
33
34
34
@doc false
35
+ @spec run([String.t()]) :: :ok
35
36
def run(_args) do
36
37
check_priv()
changed mix.exs
 
@@ -1,7 +1,7 @@
1
1
defmodule Fortune.MixProject do
2
2
use Mix.Project
3
3
4
- @version "0.1.0"
4
+ @version "0.1.1"
5
5
@source_url "https://github.com/fhunleth/elixir-fortune"
6
6
7
7
def project() do