Document everything and fix out-of-bounds memory error
This commit is contained in:
parent
60023c259e
commit
472261e15a
2 changed files with 76 additions and 46 deletions
1
esv.d
1
esv.d
|
@ -82,7 +82,6 @@ run(string[] args)
|
||||||
ushort lines;
|
ushort lines;
|
||||||
string apiKey;
|
string apiKey;
|
||||||
string configPath;
|
string configPath;
|
||||||
string configEnvVar;
|
|
||||||
string pager;
|
string pager;
|
||||||
string verses;
|
string verses;
|
||||||
Ini iniData;
|
Ini iniData;
|
||||||
|
|
121
esvapi.d
121
esvapi.d
|
@ -30,13 +30,17 @@ import std.stdio : File;
|
||||||
import std.string : capitalize, tr, wrap;
|
import std.string : capitalize, tr, wrap;
|
||||||
import std.net.curl : HTTP;
|
import std.net.curl : HTTP;
|
||||||
|
|
||||||
|
/** Indentation style to use when formatting passages. */
|
||||||
enum ESVIndent
|
enum ESVIndent
|
||||||
{
|
{
|
||||||
SPACE,
|
SPACE,
|
||||||
TAB
|
TAB
|
||||||
}
|
}
|
||||||
|
|
||||||
const enum string ESVAPI_URL = "https://api.esv.org/v3/passage";
|
/** Default URL to use when sending API requests. */
|
||||||
|
enum string ESVAPI_URL = "https://api.esv.org/v3/passage";
|
||||||
|
|
||||||
|
/** Constant array of all books in the Bible. */
|
||||||
const string[] BIBLE_BOOKS = [
|
const string[] BIBLE_BOOKS = [
|
||||||
/* Old Testament */
|
/* Old Testament */
|
||||||
"Genesis",
|
"Genesis",
|
||||||
|
@ -108,7 +112,8 @@ const string[] BIBLE_BOOKS = [
|
||||||
"Jude",
|
"Jude",
|
||||||
"Revelation"
|
"Revelation"
|
||||||
];
|
];
|
||||||
/* All boolean API parameters */
|
|
||||||
|
/** All allowed API parameters (for text passages). */
|
||||||
const string[] ESVAPI_PARAMETERS = [
|
const string[] ESVAPI_PARAMETERS = [
|
||||||
"include-passage-references",
|
"include-passage-references",
|
||||||
"include-verse-numbers",
|
"include-verse-numbers",
|
||||||
|
@ -131,7 +136,7 @@ const string[] ESVAPI_PARAMETERS = [
|
||||||
"indent-using",
|
"indent-using",
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Returns true if the argument book is a valid book of the Bible.
|
* Returns true if the argument book is a valid book of the Bible.
|
||||||
* Otherwise, returns false.
|
* Otherwise, returns false.
|
||||||
*/
|
*/
|
||||||
|
@ -144,27 +149,32 @@ bookValid(in char[] book) nothrow @safe
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/*
|
/**
|
||||||
* Returns true if the argument book is a valid verse format.
|
* Returns true if the argument verse is a valid verse format.
|
||||||
* Otherwise, returns false.
|
* Otherwise, returns false.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
verseValid(in char[] verse) @safe
|
verseValid(in char[] verse) @safe
|
||||||
{
|
{
|
||||||
bool
|
foreach (string re; [
|
||||||
vMatch(in string re) @safe
|
"^\\d{1,3}$",
|
||||||
{
|
"^\\d{1,3}-\\d{1,3}$",
|
||||||
return !verse.matchAll(regex(re)).empty;
|
"^\\d{1,3}:\\d{1,3}$",
|
||||||
|
"^\\d{1,3}:\\d{1,3}-\\d{1,3}$"
|
||||||
|
]) {
|
||||||
|
if (!verse.matchAll(regex(re)).empty)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (vMatch("^\\d{1,3}$") ||
|
|
||||||
vMatch("^\\d{1,3}-\\d{1,3}$") ||
|
|
||||||
vMatch("^\\d{1,3}:\\d{1,3}$") ||
|
|
||||||
vMatch("^\\d{1,3}:\\d{1,3}-\\d{1,3}$"))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ESV API object containing the authentication key,
|
||||||
|
* the API URL, any parameters to use when contacting the
|
||||||
|
* API as well as the temporary directory to use when
|
||||||
|
* fetching audio passages.
|
||||||
|
*/
|
||||||
class ESVApi
|
class ESVApi
|
||||||
{
|
{
|
||||||
protected {
|
protected {
|
||||||
|
@ -173,10 +183,25 @@ class ESVApi
|
||||||
string _url;
|
string _url;
|
||||||
}
|
}
|
||||||
|
|
||||||
string extraParameters;
|
/**
|
||||||
int delegate(size_t, size_t, size_t, size_t) onProgress;
|
* Structure that contains associative arrays for each of
|
||||||
|
* the possible parameter types. Specify API parameters here.
|
||||||
|
*/
|
||||||
ESVApiOptions opts;
|
ESVApiOptions opts;
|
||||||
|
/**
|
||||||
|
* Any additional parameters to append to the request.
|
||||||
|
* Must start with an ampersand ('&').
|
||||||
|
*/
|
||||||
|
string extraParameters;
|
||||||
|
/**
|
||||||
|
* Callback function that is called whenever progress is made
|
||||||
|
* on a request.
|
||||||
|
*/
|
||||||
|
int delegate(size_t, size_t, size_t, size_t) onProgress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an ESVApi object using the given authentication key.
|
||||||
|
*/
|
||||||
this(string key, string tmpName = "esv")
|
this(string key, string tmpName = "esv")
|
||||||
{
|
{
|
||||||
_key = key;
|
_key = key;
|
||||||
|
@ -184,14 +209,14 @@ class ESVApi
|
||||||
_url = ESVAPI_URL;
|
_url = ESVAPI_URL;
|
||||||
opts = ESVApiOptions(true);
|
opts = ESVApiOptions(true);
|
||||||
extraParameters = "";
|
extraParameters = "";
|
||||||
onProgress = (size_t dlTotal, size_t dlNow,
|
onProgress = delegate int (size_t dlTotal, size_t dlNow,
|
||||||
size_t ulTotal, size_t ulNow)
|
size_t ulTotal, size_t ulNow)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Returns the API authentication key that was given when the object
|
* Returns the API authentication key that was given when the object
|
||||||
* was constructed. This authentication key cannot be changed.
|
* was constructed. This authentication key cannot be changed.
|
||||||
*/
|
*/
|
||||||
|
@ -200,7 +225,7 @@ class ESVApi
|
||||||
{
|
{
|
||||||
return _key;
|
return _key;
|
||||||
}
|
}
|
||||||
/*
|
/**
|
||||||
* Returns the subdirectory used to store temporary audio passages.
|
* Returns the subdirectory used to store temporary audio passages.
|
||||||
*/
|
*/
|
||||||
@property string
|
@property string
|
||||||
|
@ -208,7 +233,7 @@ class ESVApi
|
||||||
{
|
{
|
||||||
return _tmp;
|
return _tmp;
|
||||||
}
|
}
|
||||||
/*
|
/**
|
||||||
* Returns the API URL currently in use.
|
* Returns the API URL currently in use.
|
||||||
*/
|
*/
|
||||||
@property string
|
@property string
|
||||||
|
@ -216,7 +241,7 @@ class ESVApi
|
||||||
{
|
{
|
||||||
return _url;
|
return _url;
|
||||||
}
|
}
|
||||||
/*
|
/**
|
||||||
* Sets the API URL currently in use to the given url argument.
|
* Sets the API URL currently in use to the given url argument.
|
||||||
*/
|
*/
|
||||||
@property void
|
@property void
|
||||||
|
@ -225,7 +250,7 @@ class ESVApi
|
||||||
{
|
{
|
||||||
_url = url;
|
_url = url;
|
||||||
}
|
}
|
||||||
/*
|
/**
|
||||||
* Requests the passage in text format from the API and returns it.
|
* Requests the passage in text format from the API and returns it.
|
||||||
* The (case-insensitive) name of the book being searched are
|
* The (case-insensitive) name of the book being searched are
|
||||||
* contained in the argument book. The verse(s) being looked up are
|
* contained in the argument book. The verse(s) being looked up are
|
||||||
|
@ -239,18 +264,17 @@ class ESVApi
|
||||||
in (verseValid(verse), "Invalid verse format")
|
in (verseValid(verse), "Invalid verse format")
|
||||||
{
|
{
|
||||||
char[] params, response;
|
char[] params, response;
|
||||||
HTTP request;
|
|
||||||
|
|
||||||
params = [];
|
params = [];
|
||||||
|
|
||||||
{
|
{
|
||||||
void *o;
|
|
||||||
string[] parambuf;
|
string[] parambuf;
|
||||||
|
|
||||||
void
|
void
|
||||||
addParams(R)(R item)
|
addParams(R)(R item)
|
||||||
{
|
{
|
||||||
parambuf[parambuf.length] =
|
parambuf.length++;
|
||||||
|
parambuf[parambuf.length - 1] =
|
||||||
format!"&%s=%s"(item.key, item.value);
|
format!"&%s=%s"(item.key, item.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +286,7 @@ class ESVApi
|
||||||
foreach (item; opts.b.byKeyValue())
|
foreach (item; opts.b.byKeyValue())
|
||||||
addParams(item);
|
addParams(item);
|
||||||
|
|
||||||
parambuf[parambuf.length] =
|
parambuf[parambuf.length - 1] =
|
||||||
format!"&indent-using=%s"(
|
format!"&indent-using=%s"(
|
||||||
opts.indent_using == ESVIndent.TAB ? "tab" : "space");
|
opts.indent_using == ESVIndent.TAB ? "tab" : "space");
|
||||||
|
|
||||||
|
@ -279,7 +303,7 @@ class ESVApi
|
||||||
verse) ~ params ~ extraParameters);
|
verse) ~ params ~ extraParameters);
|
||||||
return response.parseJSON()["passages"][0].str;
|
return response.parseJSON()["passages"][0].str;
|
||||||
}
|
}
|
||||||
/*
|
/**
|
||||||
* Requests an audio track of the verse(s) from the API and
|
* Requests an audio track of the verse(s) from the API and
|
||||||
* returns a file path containing an MP3 sound track.
|
* returns a file path containing an MP3 sound track.
|
||||||
* The (case-insensitive) name of the book being searched are
|
* The (case-insensitive) name of the book being searched are
|
||||||
|
@ -293,21 +317,18 @@ class ESVApi
|
||||||
in (bookValid(book), "Invalid book")
|
in (bookValid(book), "Invalid book")
|
||||||
in (verseValid(verse), "Invalid verse format")
|
in (verseValid(verse), "Invalid verse format")
|
||||||
{
|
{
|
||||||
char[] response;
|
|
||||||
File tmpFile;
|
File tmpFile;
|
||||||
HTTP request;
|
|
||||||
|
|
||||||
response = makeRequest(format!"audio/?q=%s+%s"(
|
tmpFile = File(_tmp, "w");
|
||||||
|
tmpFile.write(makeRequest(format!"audio/?q=%s+%s"(
|
||||||
book
|
book
|
||||||
.capitalize()
|
.capitalize()
|
||||||
.tr(" ", "+"),
|
.tr(" ", "+"),
|
||||||
verse)
|
verse)
|
||||||
);
|
));
|
||||||
tmpFile = File(_tmp, "w");
|
|
||||||
tmpFile.write(response);
|
|
||||||
return _tmp;
|
return _tmp;
|
||||||
}
|
}
|
||||||
/*
|
/**
|
||||||
* Requests a passage search for the given query.
|
* Requests a passage search for the given query.
|
||||||
* Returns a string containing JSON data representing
|
* Returns a string containing JSON data representing
|
||||||
* the results of the search.
|
* the results of the search.
|
||||||
|
@ -317,22 +338,17 @@ class ESVApi
|
||||||
char[]
|
char[]
|
||||||
search(in string query)
|
search(in string query)
|
||||||
{
|
{
|
||||||
char[] response;
|
return makeRequest("search/?q=" ~ query.tr(" ", "+"));
|
||||||
HTTP request;
|
|
||||||
JSONValue json;
|
|
||||||
|
|
||||||
response = makeRequest("search/?q=" ~ query.tr(" ", "+"));
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
/*
|
/**
|
||||||
* Calls search() and formats the results nicely as plain text.
|
* Calls search() and formats the results nicely as plain text.
|
||||||
*/
|
*/
|
||||||
char[]
|
char[]
|
||||||
searchFormat(alias fmt = "\033[1m%s\033[0m\n %s\n")
|
searchFormat(alias fmt = "\033[1m%s\033[0m\n %s\n")
|
||||||
(in string query, int lineLength = 0) /* 0 means default */
|
(in string query, int lineLength = 0) /* 0 means default */
|
||||||
{
|
{
|
||||||
JSONValue resp;
|
|
||||||
char[] layout;
|
char[] layout;
|
||||||
|
JSONValue resp;
|
||||||
|
|
||||||
resp = parseJSON(search(query));
|
resp = parseJSON(search(query));
|
||||||
layout = [];
|
layout = [];
|
||||||
|
@ -375,12 +391,22 @@ class ESVApi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure containing parameters passed to the ESV API.
|
||||||
|
*/
|
||||||
struct ESVApiOptions
|
struct ESVApiOptions
|
||||||
{
|
{
|
||||||
|
/** Boolean options */
|
||||||
bool[string] b;
|
bool[string] b;
|
||||||
|
/** Integer options */
|
||||||
int[string] i;
|
int[string] i;
|
||||||
|
/** Indentation style to use when formatting text passages. */
|
||||||
ESVIndent indent_using;
|
ESVIndent indent_using;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If initialise is true, initialise an ESVApiOptions
|
||||||
|
* structure with the default values.
|
||||||
|
*/
|
||||||
this(bool initialise) nothrow @safe
|
this(bool initialise) nothrow @safe
|
||||||
{
|
{
|
||||||
if (!initialise)
|
if (!initialise)
|
||||||
|
@ -408,6 +434,11 @@ struct ESVApiOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown on API errors.
|
||||||
|
* Currently only used when there is no search results
|
||||||
|
* following a call of searchFormat.
|
||||||
|
*/
|
||||||
class ESVException : Exception
|
class ESVException : Exception
|
||||||
{
|
{
|
||||||
mixin basicExceptionCtors;
|
mixin basicExceptionCtors;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue