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
|