Commit 6155e42c authored by platypro's avatar platypro

Revamped argument handling, a parsing library is no longer used.

The program now uses a command-subcommand system. run with --help for more info.
Optional arguments are defined, but not used yet.
Signed-off-by: platypro's avatarAeden McClain <dev@platypro.net>
parent 89b2a0d9
......@@ -12,4 +12,4 @@ set(APP_DATA_PATH ${CMAKE_INSTALL_FULL_DATAROOTDIR}/${CMAKE_PROJECT_NAME})
set(APP_VAR_PATH ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/${CMAKE_PROJECT_NAME})
add_subdirectory(deps)
add_subdirectory(backend)
add_subdirectory(src)
/* Aeden McClain (c) 2019
* web: https://www.platypro.net
* email: dev@platypro.net
* License info at bottom.
*
* This file is a part of QuizGrind.
*/
#include "common.h"
#include "quizgrind.h"
#include <unistd.h>
#include <argp.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include "pcg_basic.h"
#include "pdf.h"
#include "keys.h"
#define OPT_PATH_EX ((1<<4) | 0)
#define OPT_PATH_PS ((1<<4) | 1)
#define OPT_PATH_SC ((1<<4) | 2)
#define OPT_DAEMON_HTTP 'd'
#define OPT_OUTPUT_FILE 'f'
#define OPT_OUTPUT_SFILE 's'
#define OPT_OUTPUT_TYPE 't'
#define OPT_INPUT_PSET 'p'
#define OPT_INPUT_GENMODE 'g'
#define OPT_NUMQUESTIONS 'n'
#if _QUIZGRIND_BUILD_PDF
#define OPT_PDF_HEADERS ((2<<4) | 0)
#define OPT_PDF_MARGIN ((2<<4) | 1)
#define OPT_PDF_WIDTH ((2<<4) | 2)
#define OPT_PDF_HEIGHT ((2<<4) | 3)
#endif /* _QUIZGRIND_BUILD_PDF */
PG_STATE* gstate = NULL;
void cleanup(PG_STATE* state)
{
daemon_stop(state);
exercise_cleanup(state);
script_cleanup(state);
}
void interrupt(int signal)
{
if(gstate)
cleanup(gstate);
exit(0);
}
bool gen_json(PG_STATE* state, PROBLEMSET* pset)
{
FILE *sf = NULL, *f = fopen(state->gen_file, "w");
if(!f) return false;
WJWriter writer = WJWOpenFILEDocument(true, f);
WJWriter solwriter = writer;
QUIZ* quiz = exercise_mkQuiz(state, pset, QTYPE_NORMAL, state->gen_num);
QUESTION* q = NULL;
if(state->gen_sfile)
{
sf = fopen(state->gen_sfile, "w");
if(!sf) return false;
solwriter = WJWOpenFILEDocument(true, sf);
}
WJWOpenArray(NULL, writer);
while((q = exercise_mkQuestion(state, quiz)))
{
WJWOpenObject(NULL, writer);
WJWString(KEY_CALCULATOR, q->eref->calculator, true, writer);
WJWString(KEY_NAME, q->eref->name, true, writer);
exercise_writeJSONWidgets(state, q, writer);
exercise_writeJSONSolutions(state, q, state->gen_sfile ? solwriter : writer);
WJEWriteDocument(q->hints, writer, KEYLIST(KEY_HINT));
WJWCloseObject(writer);
exercise_destroyQuestion(q);
}
WJWCloseArray(writer);
WJWCloseDocument(writer);
exercise_destroyQuiz(quiz);
if(sf) fclose(sf);
fclose(f);
return true;
}
bool handle_file(PG_STATE* state)
{
char* setPath = exercise_loadOne(state, state->gen_pset);
if(!setPath)
{
printf("Problem set at path %s not found!", setPath);
return false;
}
PROBLEMSET* pset = exercise_getSet(state, setPath);
switch(state->gen_type)
{
case GTYPE_JSON: return gen_json(state, pset);
#ifdef _QUIZGRIND_BUILD_PDF
case GTYPE_PDF: return gen_pdf(state, pset);
#endif
}
exercise_cleanup(state);
return true;
}
error_t arg_parser (int key, char *arg, struct argp_state *s)
{
PG_STATE* state = s->input;
switch(key)
{
case OPT_PATH_EX:
state->path_exercise = arg;
break;
case OPT_PATH_PS:
state->path_pset = arg;
break;
case OPT_DAEMON_HTTP:
state->daemon.type |= DAEMON_HTTP;
break;
case OPT_OUTPUT_FILE:
state->gen_file = arg;
break;
case OPT_OUTPUT_SFILE:
state->gen_sfile = arg;
break;
case OPT_INPUT_GENMODE:
state->gen_mode = exercise_toGenMode(arg);
break;
case OPT_INPUT_PSET:
state->gen_pset = arg;
break;
case OPT_OUTPUT_TYPE:
if(!strcmp(arg, "json"))
state->gen_type = GTYPE_JSON;
#ifdef _QUIZGRIND_BUILD_PDF
if(!strcmp(arg, "pdf"))
state->gen_type = GTYPE_PDF;
#endif
break;
case OPT_NUMQUESTIONS:
state->gen_num = strtoul(arg, 0, 0);
break;
#ifdef _QUIZGRIND_BUILD_PDF
case OPT_PDF_HEADERS:
state->pdf.headers = arg;
break;
case OPT_PDF_HEIGHT:
state->pdf.pageHeight = strtod(arg, NULL);
break;
case OPT_PDF_WIDTH:
state->pdf.pageWidth = strtod(arg, NULL);
break;
case OPT_PDF_MARGIN:
state->pdf.margin = strtod(arg, NULL);
break;
#endif /* _QUIZGRIND_BUILD_PDF */
default:
break;
}
return 0;
}
struct argp_option args[] = {
{"exercise-path", OPT_PATH_EX, "PATH", 0, "Path to find exercises", 0},
{"pset-path" , OPT_PATH_PS, "PATH", 0, "Path to find problem sets", 0},
// Daemon options
{NULL, 0, NULL , OPTION_DOC, "Daemons: These override any file operations", 0},
{"http-daemon" , OPT_DAEMON_HTTP, NULL, 0, "Run HTTP daemon. Overrides any file options.", 0},
// File options
{NULL, 0, NULL , OPTION_DOC, "File operations", 1},
{NULL , OPT_OUTPUT_FILE, "FILE", 0, "Output file", 1},
{NULL , OPT_OUTPUT_SFILE, "FILE", 0, "Solution output. If not set, added to regular output file.", 1},
{NULL , OPT_OUTPUT_TYPE, "TYPE", 0, "Output type", 1},
{NULL , OPT_INPUT_GENMODE, "MODE", 0, "Question generation type (random, ordered, unordered)", 0},
{NULL , OPT_INPUT_PSET, "FILE", 0, "Problem set to generate", 1},
{NULL , OPT_NUMQUESTIONS, "NUM", 0, "Number of questions", 1},
#ifdef _QUIZGRIND_BUILD_PDF
// PDF options
{NULL, 0, NULL , OPTION_DOC, "PDF options", 2},
{"pdf-margin" , OPT_PDF_MARGIN, NULL, 0, "Page margin (in centimeters)", 2},
{"pdf-width" , OPT_PDF_WIDTH, NULL, 0, "Page height (in centimeters)", 2},
{"pdf-height" , OPT_PDF_HEIGHT, NULL, 0, "Page width (in centimeters)", 2},
{"pdf-headers" , OPT_PDF_HEADERS, NULL, 0, "Header fields (comma seperated).", 2},
#endif /* _QUIZGRIND_BUILD_PDF */
{0,0,0,0,0}
};
struct argp argsettings = {
.options = args,
.parser = arg_parser,
.doc = "A quiz generator",
.children = NULL,
.help_filter = NULL,
.argp_domain = NULL
};
size_t fileWriter(char *data, size_t length, void *userdata)
{
return fwrite(data, sizeof(uint8_t), length, (FILE*)userdata);
}
int main(int argc, char* argv[])
{
PG_STATE state = {0};
signal(SIGINT, interrupt);
gstate = &state;
state.path_exercise = APP_EXERCISE_PATH;
state.path_pset = APP_PROBSET_PATH;
state.gen_type = GTYPE_JSON;
state.gen_num = 10;
#ifdef _QUIZGRIND_BUILD_PDF
state.pdf.headers = DEFAULT_PDF_HEADERS;
state.pdf.margin = DEFAULT_PDF_MARGIN;
state.pdf.pageHeight = DEFAULT_PDF_PAGE_HEIGHT;
state.pdf.pageWidth = DEFAULT_PDF_PAGE_WIDTH;
#endif /* _QUIZGRIND_BUILD_PDF */
time_t t;
srand((unsigned) time(&t));
argp_parse (&argsettings, argc, argv, 0, NULL, &state);
script_init(&state);
if(state.daemon.type)
daemon_run(&state);
else if(state.gen_file && state.gen_pset)
handle_file(&state);
else argp_help(&argsettings, stdout, ARGP_HELP_STD_HELP, NULL);
script_cleanup(&state);
exercise_cleanup(&state);
return 0;
}
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
SRCDIR = ${PWD}
BINDIR = ${PWD}/../bin/frontend.web
INSTALLDIR = ${PWD}/../bin/install/share/quizgrind/www/
BINDIR = ${PWD}/bin/frontend.web
INSTALLDIR = ${PWD}/bin/install
WEBPACK = webpack
......@@ -12,10 +12,15 @@ build:
${WEBPACK} --output-path=${BINDIR}
install:
make build
mkdir -p ${BINDIR}
mkdir -p ${INSTALLDIR}
cp -r ${SRCDIR}/media ${INSTALLDIR}
cp ${SRCDIR}/index.php ${INSTALLDIR}/index.php
cp ${SRCDIR}/style.css ${INSTALLDIR}
cp ${SRCDIR}/index.php ${SRCDIR}/style.css ${INSTALLDIR}
cp -r ${BINDIR}/bundle.js ${INSTALLDIR}/index.js
clean:
rm -r ${INSTALLDIR}/media
rm ${INSTALLDIR}/index.js
rm ${INSTALLDIR}/index.php
rm ${INSTALLDIR}/style.css
rm ${BINDIR}/bundle.js
\ No newline at end of file
......@@ -5,7 +5,7 @@
"main": "index.js",
"scripts": {
"build": "make build",
"install": "make install"
"install": "make all"
},
"repository": "https://gitlab.com/platypro/quizgrind",
"author": "Aeden McClain",
......
......@@ -5,21 +5,26 @@ set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR} ${CMAKE_INSTALL_PREFIX}/lib
configure_file("paths.h.in" "${CMAKE_CURRENT_BINARY_DIR}/paths.h")
set(SRCS
pcg_basic.c
quizgrind.c
exercise.c
script.c
widget.c
args.c
gen/gen.c
backend/exercise.c
backend/script.c
daemon/daemon.c
daemon/http.c
pcg_basic.c
# Widget sources
../content/widget/choiceInput/backend.c
../content/widget/imageView/backend.c
../content/widget/lineInput/backend.c
../content/widget/numberInput/backend.c
../content/widget/textInput/backend.c
../content/widget/textView/backend.c
widget/widget.c
widget/choiceInput/backend.c
widget/imageView/backend.c
widget/lineInput/backend.c
widget/numberInput/backend.c
widget/textInput/backend.c
widget/textView/backend.c
${DEPS_SRC}
)
......@@ -30,7 +35,7 @@ IF(QUIZGRIND_BUILD_PDF)
pkg_search_module(RSVG librsvg-2.0)
pkg_search_module(PANGO pango)
pkg_search_module(PANGOCAIRO pangocairo)
set(SRCS ${SRCS} pdf.c)
set(SRCS ${SRCS} gen/pdf.c)
add_definitions(-D_QUIZGRIND_BUILD_PDF)
ENDIF()
......
#include "common.h"
#include "args.h"
#include <stdio.h>
#include <string.h>
#include <alloca.h>
#include <getopt.h>
void args_show_short(struct option_info* opts)
{
while(opts->id)
{
if(opts->name)
printf("[--%s", opts->name);
else printf("[-%c", opts->id);
if(opts->flags & ARGS_ARGUMENT)
{
putchar(opts->name ? '=' : ' ');
if(opts->flags & ARGS_OPTIONAL)
putchar('[');
printf("%s", opts->argname);
if(opts->flags & ARGS_OPTIONAL)
putchar(']');
}
printf("] ");
opts++;
}
}
void args_show_long(struct option_info* opts)
{
while(opts->id)
{
int yat = 0;
if(opts->name)
yat += printf(" --%s", opts->name);
else
yat += printf(" -%c", opts->id);
printf("%*c%s\n", 22 - yat, ' ', opts->description);
opts++;
}
}
char** args_parse(struct option_info* opts,
args_parser parser,
void* udata,
int* argc, char** argv)
{
argv++;
(*argc)--;
while(*argc)
{
char *mark = *argv;
struct option_info* opt;
if(*mark == '-')
{
mark++;
} else return argv;
if(*mark == '-')
{
mark++;
int optlen = 0;
// Long option
while(*mark)
{
if(*(mark+optlen) == '='
|| *(mark+optlen) == '\000')
break;
optlen++;
}
opt = opts;
while(opt->id)
{
if(opt->name && !strncmp(mark, opt->name, optlen)) break;
opt++;
}
if(opt && opt->flags & ARGS_ARGUMENT)
{
if(*(mark+optlen) == '\000' && *argc > 1)
{
if(!parser(opt->id, argv[1], udata)) return argv;
argv++;
(*argc)--;
}
else if(!parser(opt->id, mark + optlen + 1, udata)) return argv;
}
else
if(!parser(opt->id, NULL, udata)) return argv;
}
else
{
//Parse options
while(*mark)
{
opt = opts;
while(opt->id)
{
if(opt->id == *mark) break;
opt++;
}
mark++;
if(opt && opt->flags & ARGS_ARGUMENT)
{
if(*mark == '\000' && *argc > 1)
{
if(!parser(opt->id, argv[1], udata)) return argv;
argv++;
(*argc)--;
}
else if(!parser(opt->id, mark, udata)) return argv;
break;
} else if(!parser(opt->id, NULL, udata)) return argv;
}
}
argv++;
(*argc)--;
}
return NULL;
}
#ifndef INCLUDE_ARGS_H
#define INCLUDE_ARGS_H
#define ARGS_LONGONLY(opt) (opt + 128)
#define ARGS_ARGUMENT 1
#define ARGS_OPTIONAL 2
struct option_info
{
uint32_t flags;
int id;
char* name;
char* argname;
char* description;
};
typedef bool (*args_parser) (int id, char* arg, void* udata);
extern void args_show_short(struct option_info* opt_info);
extern void args_show_long(struct option_info* opt_info);
extern char** args_parse(struct option_info* opts,
args_parser parser,
void* udata,
int* argc, char** argv);
#endif /* INCLUDE_ARGS_H */
......@@ -21,7 +21,7 @@
#include <errno.h>
#include <pmustache/template.h>
#include <pmustache/provider.wje.h>
#include <widget.h>
#include <widget/widget.h>
#include "pcg_basic.h"
#include "quizgrind.h"
......@@ -29,11 +29,11 @@
#include "script.h"
#include "keys.h"
char* exercise_loadResource(char* basepath, char* file)
char* exercise_loadResource(char* basepath, char* file, char* ext)
{
size_t fpath_len = strlen(basepath) + strlen(file) + 2;
size_t fpath_len = strlen(basepath) + strlen(file) + (ext ? strlen(ext) : 0) + 2;
char* fpath = malloc(fpath_len);
snprintf(fpath, fpath_len, "%s/%s", basepath, file);
snprintf(fpath, fpath_len, "%s/%s%s", basepath, file, ext);
return fpath;
}
......@@ -175,7 +175,7 @@ char* exercise_load(PG_STATE* state, char* filename)
snprintf(exerPath, exerPath_len, "%s/%s", state->path_exercise, exerId);
char* file = exercise_loadResource(exerPath, "exercise.json");
char* file = exercise_loadResource(exerPath, "exercise", ".json");
FILE* f = fopen(file, "r");
if(!f)
{
......@@ -276,7 +276,7 @@ char* exercise_load(PG_STATE* state, char* filename)
if(exer->staticType != STATTYPE_STATIC)
{
char* scriptPath = exercise_loadResource(exerPath, "script.lua");
char* scriptPath = exercise_loadResource(exerPath, "script", ".lua");
// script_load frees scriptPath
if(!scriptPath || !script_load(state, exerId, scriptPath))
{ WJRCloseDocument(exer_rdr); return NULL; }
......@@ -323,7 +323,7 @@ bool exercise_loadAll(PG_STATE* state)
{
if(dir->d_type == DT_REG)
{
char* fpath = exercise_loadResource(state->path_pset, dir->d_name);
char* fpath = exercise_loadResource(state->path_pset, dir->d_name, NULL);
if(!fpath) return false;
exercise_load(state, fpath);
free(fpath);
......@@ -335,7 +335,7 @@ bool exercise_loadAll(PG_STATE* state)
char* exercise_loadOne(PG_STATE* state, char* set)
{
char* path = exercise_loadResource(state->path_pset, set);
char* path = exercise_loadResource(state->path_pset, set, ".json");
if(!path) return false;
set = exercise_load(state, path);
free(path);
......
......@@ -12,6 +12,8 @@
#include <wjelement.h>
#include <pmustache/template.h>
#include "backend/script.h"
struct PG_State;
struct Widget;
......@@ -102,6 +104,7 @@ typedef struct TemplateContainer
typedef struct PG_Exercise
{
PG_SCRIPT script;
struct ProblemSet* problemSets;
struct Exercise* stash;
TEMPLATECONTAINER* templates;
......
......@@ -13,7 +13,7 @@
#include <libgen.h>
#include <dirent.h>
#include <stdlib.h>
#include <widget.h>
#include <widget/widget.h>
#include <pmustache/provider.wje.h>
#include "quizgrind.h"
......@@ -280,12 +280,12 @@ void script_init(PG_STATE* script)
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, LUA_KEY_SCRIPTS);
script->script.L = L;
script->exercise.script.L = L;
}
uint8_t script_load(PG_STATE* scr, char* id, char* path)
{
lua_State* L = scr->script.L;
lua_State* L = scr->exercise.script.L;
bool result = true;
//Load the function into the function table
......@@ -309,7 +309,7 @@ uint8_t script_load(PG_STATE* scr, char* id, char* path)
bool script_unload(PG_STATE* scr, char* id)
{
lua_State* L = scr->script.L;
lua_State* L = scr->exercise.script.L;
bool result = true;
//Free the function into the function table
......@@ -324,7 +324,7 @@ bool script_unload(PG_STATE* scr, char* id)
bool script_genQuiz(PG_STATE* scr, QUESTION* question, EXERCISEREF* exercise)
{
bool result = true;
lua_State* L = scr->script.L;
lua_State* L = scr->exercise.script.L;
//Push the question (for C callbacks)
lua_pushlightuserdata(L, question);
lua_setfield(L, LUA_REGISTRYINDEX, LUA_KEY_QUESTION);
......@@ -362,7 +362,7 @@ bool script_genQuiz(PG_STATE* scr, QUESTION* question, EXERCISEREF* exercise)
void script_cleanup(PG_STATE* state)
{
lua_close(state->script.L);
lua_close(state->exercise.script.L);
}
/* This program is free software: you can redistribute it and/or modify
......
......@@ -16,6 +16,16 @@
#include "daemon/http.h"
#include "quizgrind.h"
#include "args.h"
#define OPT_HELP ARGS_LONGONLY(0)
struct option_info daemon_opts[] =
{
{0, OPT_HELP, "help", NULL, "Show help and exit"},
{0}
};
bool daemon_stop(PG_STATE* state)
{
if(state->daemon.type & DAEMON_HTTP)
......@@ -24,11 +34,25 @@ bool daemon_stop(PG_STATE* state)
return true;
}
bool daemon_run(PG_STATE* state)
bool daemon_parse_arg(int id, char* arg, void* udata)
{
if(state->daemon.type & DAEMON_HTTP)
daemon_http_start(state);
else return false;
if(id == OPT_HELP)
{
puts("QuizGrind HTTP Daemon\n"
"Usage: quizgrind daemon\n\n"
"This command takes no arguments.");
exit(0);
}
return true;
}
int daemon_run(PG_STATE* state, int argc, char** argv)
{
args_parse(daemon_opts,
daemon_parse_arg,
state,
&argc, argv);
daemon_http_start(state);
printf("Daemon mode enabled\n");
struct termios term;
char c;
......@@ -70,7 +94,7 @@ bool daemon_run(PG_STATE* state)
}
}
daemon_stop(state);
return true;
return 0;
}
/* This program is free software: you can redistribute it and/or modify
......
......@@ -11,16 +11,20 @@
#include <termios.h>
#include "daemon/http.h"
struct PG_State;
#define DAEMON_HTTP (1<<0)
typedef struct PG_Daemon {
PG_HTTP http;
struct termios oldt;
uint8_t type;
} PG_DAEMON;
extern bool daemon_run(struct PG_State* state);
extern int daemon_run(struct PG_State* state, int argc, char** argv);
extern bool daemon_stop(struct PG_State* state);
#endif /* INCLUDE_DAEMON_H */
......
......@@ -24,10 +24,10 @@
USER** getUser(PG_STATE* state, UID_T uid)
{
pthread_mutex_lock(&state->http.userMutex);
pthread_mutex_lock(&state->daemon.http.userMutex);
//Search for user
uint8_t basebucket = (uid & 0xFF);
USER** bucket = &state->http.users[basebucket];
USER** bucket = &state->daemon.http.users[basebucket];
USER** result = NULL;