diff --git a/CHANGELOG.md b/CHANGELOG.md index 58f26cc..bf970d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.6.0 + +* Add CLI interface + ## v0.5.1 ### Bug fixes diff --git a/README.md b/README.md index 398a121..0a99598 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,28 @@ have any JavaScript, you will get warnings from validation tools such as See `BUPE.Builder`, `BUPE.Config`, and `BUPE.Item` for more details. +### Using the builder via command line + +You can build EPUB documents using the command line as follows: + +1. Install `BUPE` as an escript: + +```console +mix escript.install hex bupe +``` + +2. Then you are ready to use it in your projects: + +```console +bupe "EPUB_TITLE" -p egg.xhtml -p bacon.xhtml -l path/to/logo.png +``` + +For more details about using the command line tool, review the usage guide: + +```console +bupe --help +``` + ### Parser If you want to parse an EPUB file you can do the following: diff --git a/lib/bupe/cli.ex b/lib/bupe/cli.ex new file mode 100644 index 0000000..a649991 --- /dev/null +++ b/lib/bupe/cli.ex @@ -0,0 +1,107 @@ +defmodule BUPE.CLI do + @moduledoc """ + CLI interface for BUPE. + """ + + @aliases [ + h: :help, + l: :logo, + o: :output, + p: :page, + v: :version + ] + + @switches [ + help: :boolean, + language: :string, + logo: :string, + output: :string, + page: :keep, + version: :boolean + ] + + def main(args, builder \\ &BUPE.Builder.run/3) do + {opts, args, _invalid} = OptionParser.parse(args, aliases: @aliases, switches: @switches) + + cond do + Keyword.has_key?(opts, :version) -> + print_version() + + Keyword.has_key?(opts, :help) -> + print_usage() + + true -> + generate(args, opts, builder) + end + end + + defp generate([], _, _) do + IO.puts("Too few arguments.\n") + print_usage() + exit({:shutdown, 1}) + end + + defp generate([title], opts, builder) do + opts = + opts + |> Keyword.put(:title, title) + |> parse_pages() + + name = parse_output(opts, title) + + with {:ok, path} <- BUPE.Config |> struct(opts) |> builder.(name, []) do + IO.puts("EPUB successfully generated:") + path |> Path.relative_to_cwd() |> IO.puts() + end + end + + def parse_pages(opts) do + case Keyword.get_values(opts, :page) do + [] -> + IO.puts("At least one page is required\n") + print_usage() + exit({:shutdown, 1}) + + pages -> + opts + |> Keyword.delete(:page) + |> Keyword.put(:pages, pages) + end + end + + def parse_output(opts, title) do + Keyword.get_lazy(opts, :output, fn -> + title + |> String.downcase() + |> String.replace(" ", "_") + |> Kernel.<>(".epub") + end) + end + + defp print_version do + IO.puts("BUPE v#{BUPE.version()}") + end + + defp print_usage do + IO.puts(~S""" + Usage: + bupe EPUB_TITLE [OPTIONS] + + Examples: + bupe "Ode to Food" -p egg.xhtml -p bacon.xhtml + + Options: + EPUB_TITLE EPUB title + -p, --page Path to a page (e.g. XHTML) file that will be copied + to the EPUB file. May be given multiple times + --language Identify the primary language of the EPUB document, its + value must be a valid [BCP 47](https://tools.ietf.org/html/bcp47) + language tag, default: "en" + -l, --logo Path to the image logo of the EPUB document + -v, --version Print BUPE version + -o, --output Path to output EPUB document. + -h, --help Print this usage + + """) + end +end diff --git a/mix.exs b/mix.exs index 9365ff0..0575870 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule BUPE.Mixfile do use Mix.Project - @version "0.5.2-dev" + @version "0.6.0" def project do [ @@ -19,11 +19,8 @@ defmodule BUPE.Mixfile do docs: docs(), package: package(), deps: deps(), - dialyzer: [ - plt_add_apps: [:mix, :ex_unit, :xmerl], - check_plt: true, - flags: [:error_handling, :race_conditions, :underspecs] - ] + dialyzer: dialyzer(), + escript: escript() ] end @@ -34,6 +31,18 @@ defmodule BUPE.Mixfile do [applications: []] end + def dialyzer do + [ + plt_add_apps: [:mix, :ex_unit, :xmerl], + check_plt: true, + flags: [:error_handling, :race_conditions, :underspecs] + ] + end + + def escript do + [main_module: BUPE.CLI] + end + defp deps do [ {:ex_doc, "~> 0.19", only: :dev}, diff --git a/test/bupe/cli_test.exs b/test/bupe/cli_test.exs new file mode 100644 index 0000000..525039c --- /dev/null +++ b/test/bupe/cli_test.exs @@ -0,0 +1,55 @@ +defmodule BUPE.CLITest do + use ExUnit.Case, async: true + + alias BUPE.CLI + + import ExUnit.CaptureIO + + defp builder(_config, name, _opts) do + {:ok, name} + end + + test "minimum command-line options" do + fun = fn -> + CLI.main(["sample", "--page", "egg.xhtml"], &builder/3) + end + + assert "EPUB successfully generated:\nsample.epub\n" == capture_io(fun) + end + + test "at least one page is required" do + fun = fn -> + CLI.main(["sample"]) + end + + assert catch_exit(capture_io(fun)) == {:shutdown, 1} + end + + test "too few arguments" do + fun = fn -> + CLI.main(["--page", "egg.xhtml"]) + end + + assert catch_exit(capture_io(fun)) == {:shutdown, 1} + end + + test "version" do + assert capture_io(fn -> + CLI.main(["--version"]) + end) == "BUPE v#{BUPE.version()}\n" + + assert capture_io(fn -> + CLI.main(["-v"]) + end) == "BUPE v#{BUPE.version()}\n" + end + + test "help" do + assert capture_io(fn -> + CLI.main(["--help"]) + end) =~ "Usage:\n" + + assert capture_io(fn -> + CLI.main(["-h"]) + end) =~ "Usage:\n" + end +end