workouts
+!bash-completion/workouts
workouts.db
workouts*/
workouts*.tar.gz
AM_CPPFLAGS = \
- -I$(top_builddir)/include/ \
- -I$(top_srcdir)/include/
+ -Wall \
+ -Werror
bin_PROGRAMS = workouts
workouts_SOURCES = \
src/ls/head.c \
src/ls/last.c \
src/ls/ls.c \
+ src/ls/name.c \
src/ls/single.c \
src/ls/recent.c \
src/ls/workout.c \
include/update.h \
include/usage.h
-SUBDIRS = . test/integration test/unit
+bashcompletiondir = $(datadir)/bash-completion/completions
+dist_bashcompletion_DATA = bash-completion/workouts
+
+SUBDIRS = . test/integration test/unit
--- /dev/null
+_workouts() {
+ IFS=$'\n'
+ COMPREPLY=()
+ local commands=$'add\nls\nrm\ntoggle\nupdate'
+
+ declare -A options
+ options[--attribute]="--attribute"
+ options[--attr]="--attr"
+ options[-a]="-a"
+ options[--help]="--help"
+ options[--homedir]="--homedir"
+ options[-d]="-d"
+ options[--quiet]="--quiet"
+ options[--recent]="--recent"
+ options[-l]="-l"
+ options[--rows]="--rows"
+ options[-r]="-r"
+ options[--verbose]="--verbose"
+ options[-v]="-v"
+ options[--workout]="--workout"
+ options[-w]="-w"
+
+ local ls_options=$'--filter\n-f\n--last-done\n--name-only'
+
+ local type="default"
+ local utility="workouts"
+
+ # search for type (only bother with latest; rest will be discarded)
+ for arg in "${COMP_WORDS[@]}"; do
+ case "$arg" in
+ --attribute|--attr|-a) type="attribute" ;;
+ --recent|-l) type="recent" ;;
+ --workout|-w) type="workout" ;;
+ add) utility="add" ;;
+ ls) utility="ls" ;;
+ rm) utility="rm" ;;
+ toggle) utility="toggle" ;;
+ update) utility="update" ;;
+ esac
+ unset options[$arg]
+ done
+
+ opts=
+ for val in "${options[@]}"; do
+ opts+="$val"
+ opts+=$'\n'
+ done
+
+ case "$3" in
+ add)
+ # don't need completions for attributes
+ if [[ "$type" == "recent" ]]; then
+ COMPREPLY+=($(workouts -w ls --name-only | grep "$2"))
+ fi
+ # don't need completions for workouts
+ ;;
+ --homedir|-d)
+ compopt -o nospace
+ COMPREPLY+=($(compgen -o dirnames -- "$2"))
+ ;;
+ --filter|-f)
+ local count=($(workouts -a ls | wc -l))
+ local template
+ for (( i = 0; i< "$count"; i++ )); do
+ template+="x"
+ done
+ COMPREPLY+=("$template")
+ ;;
+ ls)
+ case "$type" in
+ # don't need completions for attributes
+ # don't need completions for recent
+ workout|default)
+ COMPREPLY+=($(compgen -W "${ls_options}" -- "$2"))
+ COMPREPLY+=($(workouts -w ls --name-only | grep -- "$2"))
+ ;;
+ esac
+ ;;
+ rm)
+ case "$type" in
+ attribute)
+ COMPREPLY+=($(workouts --attribute ls | grep "$2"))
+ ;;
+ recent|default)
+ COMPREPLY+=($(workouts -w ls --name-only | grep "$2"))
+ ;;
+ workout)
+ COMPREPLY+=($(workouts -w ls --name-only | grep "$2"))
+ ;;
+ esac
+ ;;
+ toggle)
+ case "$type" in
+ # don't need completions for attributes
+ # don't need completions for recent
+ workout|default)
+ COMPREPLY+=($(workouts -w ls --name-only | grep "$2"))
+ esac
+ ;;
+ update)
+ case "$type" in
+ attribute)
+ COMPREPLY+=($(workouts --attribute ls | grep "$2"))
+ ;;
+ recent|default)
+ COMPREPLY+=($(workouts -w ls --name-only | grep "$2"))
+ ;;
+ workout)
+ COMPREPLY+=($(workouts -w ls --name-only | grep "$2"))
+ ;;
+ esac
+ ;;
+ workouts)
+ COMPREPLY+=($(compgen -W "${commands}" -- "$2"))
+ COMPREPLY+=($(compgen -W "${opts}" -- "$2"))
+ ;;
+ *)
+ if [[ "$utility" == "workouts" ]]; then
+ COMPREPLY+=($(compgen -W "${commands}" -- "$2"))
+ COMPREPLY+=($(compgen -W "${opts}" -- "$2"))
+ fi
+ ;;
+ esac
+}
+
+complete -F _workouts workouts
# Store build files not in main directory
AC_CONFIG_AUX_DIR([build-aux])
-AM_INIT_AUTOMAKE([foreign subdir-objects nostdinc -Wall -Werror])
+AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Werror])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADERS([include/config.h])
AC_MSG_CHECKING([if debugging])
if test x$enable_debug != xno; then
AC_MSG_RESULT(yes)
- CFLAGS="-ggdb -O0"
+ CFLAGS="-ggdb3 -O0"
else
AC_MSG_RESULT(no)
fi
#include<opt.h>
+#define DB_FILENAME "workouts.db"
#define EMPTY_STRING ""
// attributes
int attribute_count();
#define ATTRIBUTE_DELETE_SQL "DELETE FROM `" ATTRIBUTE_TABLE_NAME_SQL "` WHERE `name` = ?;"
-int attribute_delete(char*);
+int attribute_delete(const char*);
#define ATTRIBUTE_GET_SQL "SELECT `name` FROM " ATTRIBUTE_TABLE_NAME_SQL " WHERE 1 ORDER BY `order` ASC;"
int attribute_get(void (*)(const unsigned char*));
-int attribute_index(char*);
+int attribute_index(const char*);
void attribute_index_helper(const unsigned char*);
#define ATTRIBUTE_INSERT_SQL "INSERT INTO " ATTRIBUTE_TABLE_NAME_SQL " (`name`,`order`) VALUES (?,(SELECT MAX(`order`)+1 FROM `" ATTRIBUTE_TABLE_NAME_SQL "`));"
-int attribute_insert(char*);
-int attribute_parse(char*,int*,int*);
-unsigned int attribute_template(char*,int);
+int attribute_insert(const char*);
+int attribute_parse(const char*,int*,int*);
+unsigned int attribute_template(const char*,int);
#define ATTRIBUTE_UPDATE_SQL "UPDATE `" ATTRIBUTE_TABLE_NAME_SQL "` SET `name` = ? WHERE `name` = ?;"
-int attribute_update(char*,char*);
+int attribute_update(const char*,const char*);
// recent
#define RECENT_TABLE_NAME_SQL "recent"
");"
#define RECENT_DELETE_SQL "DELETE FROM `" RECENT_TABLE_NAME_SQL "` WHERE `name` = ? AND `date` = ?;"
-int recent_delete(char*,char*);
+int recent_delete(const char*,const char*);
#define RECENT_GET_BASE_SQL "SELECT name, date FROM `" RECENT_TABLE_NAME_SQL "`"
#define RECENT_GET_SQL RECENT_GET_BASE_SQL " ORDER BY date(`date`) DESC,`name` ASC LIMIT ?;"
#define RECENT_GET_SEARCH_SQL RECENT_GET_BASE_SQL " WHERE name LIKE ('%' || ? || '%') ORDER BY date(`date`) DESC,`name` ASC LIMIT ?;"
-int recent_get(char*,int,void(*)(const unsigned char*,const unsigned char*));
+int recent_get(const char*,int,void(*)(const unsigned char*,const unsigned char*));
#define RECENT_INSERT_SQL "INSERT INTO " RECENT_TABLE_NAME_SQL " (name,date) VALUES (?,?);"
-int recent_insert(char*,char*);
+int recent_insert(const char*,const char*);
#define RECENT_UPDATE_SQL "UPDATE `" RECENT_TABLE_NAME_SQL "` SET date = ? WHERE date = ? AND name = ?;"
-int recent_update(char*,char*,char*);
+int recent_update(const char*,const char*,const char*);
// workouts
#define WORKOUT_TABLE_NAME_SQL "workouts"
");"
#define WORKOUT_DELETE_SQL "DELETE FROM `" WORKOUT_TABLE_NAME_SQL "` WHERE `name` = ?;"
-int workout_delete(char*);
+int workout_delete(const char*);
#define WORKOUT_GET_BASE_SQL "SELECT " \
"`" WORKOUT_TABLE_NAME_SQL "`.name, " \
#define WORKOUT_GET_SQL WORKOUT_GET_BASE_SQL " WHERE (attributes & ?1) = ?1 AND (~attributes & ?2) = ?2 ORDER BY `last` DESC, `" WORKOUT_TABLE_NAME_SQL "`.name ASC LIMIT ?;"
#define WORKOUT_GET_SEARCH_SQL WORKOUT_GET_BASE_SQL " WHERE `" WORKOUT_TABLE_NAME_SQL "`.name LIKE ('%' || ? || '%') AND (attributes & ?2) = ?2 AND (~attributes & ?3) = ?3 ORDER BY `last` DESC, `" WORKOUT_TABLE_NAME_SQL "`.name ASC LIMIT ?;"
-int workout_get(char*,char*,int,void (*)(const unsigned char*,int,const unsigned char*));
+int workout_get(const char*,const char*,int,void (*)(const unsigned char*,int,const unsigned char*));
#define WORKOUT_INSERT_SQL "INSERT INTO " WORKOUT_TABLE_NAME_SQL " (name,attributes) VALUES (?,?);"
-int workout_insert(char*, unsigned int);
+int workout_insert(const char*, unsigned int);
-int workout_toggle(char*,char*);
+int workout_toggle(const char*,const char*);
void workout_toggle_helper(const unsigned char*,int,const unsigned char*);
#define WORKOUT_UPDATE_NAME_SQL "UPDATE `" WORKOUT_TABLE_NAME_SQL "` SET name = ? WHERE name = ?;"
#define WORKOUT_UPDATE_ATTRIBUTES_SQL "UPDATE `" WORKOUT_TABLE_NAME_SQL "` SET attributes = ? WHERE name = ?;"
-int workout_update(char*,char*,int);
+int workout_update(const char*,const char*,int);
#define WORKOUT_SHIFT_ATTRIBUTE_FLAGS_SQL "UPDATE `" WORKOUT_TABLE_NAME_SQL "` SET attributes = (((attributes<<(?3 - ?1))&?2)>>(?3 - ?1)) + ((attributes>>(?1+1))<<?1);"
#define DATE_MESSAGE_INVALID_DATE "invalid date given\n"
-int date_parse(char*,char*,size_t);
+int date_parse(const char*,char*,size_t);
#endif
#define LS_GENERATE_LAST_DONE_INVALID_DATE_OUTPUT "unexpected output\n"
#define LS_RECENT_COMMAND_FAILED "command failed\n"
#define LS_PRINT_SINGLE_ATTRIBUTE_GET_FAILED "arribute get failed unexpectedly\n"
+#define LS_MESSAGE_INVALID_FORMAT "invalid format\n"
+#define LS_MESSAGE_INVALID_FILTER "invalid filter; wrong number of attributes given (%d != %d)\n"
+
+#define WORKOUT_PRINT_ATTRIBUTE_HEADER "%-30.30s\t"
+#define WORKOUT_PRINT_ATTRIBUTE_LABEL "Attributes:"
+#define WORKOUT_PRINT_ATTRIBUTE_END "\n"
struct ls_helper {
int i;
time_t now;
};
+enum ls_format {
+ LS_FORMAT_LAST_DONE,
+ LS_FORMAT_NAME_ONLY,
+ LS_FORMAT_WITH_ATTRIBUTES,
+ LS_FORMAT_DEFAULT = LS_FORMAT_WITH_ATTRIBUTES
+};
+
extern struct ls_helper helper;
-void generate_last_done(const unsigned char*,int, const unsigned char*);
int last_done();
int ls(int,char**);
int ls_attribute();
+int ls_name_only(const char*,const char*);
int ls_recent(int,char**);
-int ls_single(char*,char*);
+int ls_single(const char*,const char*);
int ls_workout(int,char**);
-void print_attribute(const unsigned char*);
-void print_last_done();
-void print_recent(const unsigned char*,const unsigned char*);
-void print_single(const unsigned char*,int,const unsigned char*);
void print_header(const unsigned char*);
-void print_workout(const unsigned char*,int,const unsigned char*);
#endif
#ifndef __OPT_H_
#define __OPT_H_
-#include<dirent.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
+#include<unistd.h>
#include<log.h>
// global options
struct options {
- char *db_location;
- char *homedir;
int rows;
enum log_level verbose;
enum workout_data_type target;
extern struct options global_opts;
// specific option setters
-int opt_set_homedir(char*);
+int opt_set_homedir(const char*);
void opt_set_log_level(enum log_level);
void opt_set_rows(int);
void opt_set_target(enum workout_data_type);
-#endif
\ No newline at end of file
+#endif
return add_recent(argc,argv);
case WORKOUT_DATA_TYPE_WORKOUT:
return add_workout(argc,argv);
+ default:
+ return EXIT_FAILURE;
}
}
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_prepare_v2(db_p,ATTRIBUTE_COUNT_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
return -1;
}
-int attribute_delete(char *name) {
+int attribute_delete(const char *name) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
int i, index, count, mask;
i--;
}
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_exec(db_p,"BEGIN;",NULL,NULL,NULL)!=SQLITE_OK) { goto cleanup; }
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_prepare_v2(db_p,ATTRIBUTE_GET_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
struct attribute_indexer {
int i;
int index;
- char *name;
+ const char *name;
};
struct attribute_indexer indexer = {
NULL,
};
-int attribute_index(char *name) {
+int attribute_index(const char *name) {
indexer.i = 0;
indexer.index = -1;
indexer.name = name;
indexer.i++;
}
-int attribute_insert(char *name) {
+int attribute_insert(const char *name) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_prepare_v2(db_p,ATTRIBUTE_INSERT_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_bind_text(stmt_p,1,name,-1,SQLITE_STATIC)!=SQLITE_OK) { goto cleanup; }
return -1;
}
-int attribute_parse(char *str, int *required, int *exclude) {
+int attribute_parse(const char *str, int *required, int *exclude) {
if(NULL==str) { return -1; }
if(NULL==required) { return -1; }
return 1;
}
-unsigned int attribute_template(char *p, int len) {
+unsigned int attribute_template(const char *p, int len) {
unsigned int attr_flags;
attr_flags = 0;
return attr_flags;
}
-int attribute_update(char *from, char *to) {
+int attribute_update(const char *from, const char *to) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_prepare_v2(db_p,ATTRIBUTE_UPDATE_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_bind_text(stmt_p,1,to,-1,SQLITE_STATIC)!=SQLITE_OK) { goto cleanup; }
#include<data.h>
-int recent_delete(char *workout, char *date) {
+int recent_delete(const char *workout, const char *date) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_prepare_v2(db_p,RECENT_DELETE_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_bind_text(stmt_p,1,workout,-1,SQLITE_STATIC)!=SQLITE_OK) { goto cleanup; }
return -1;
}
-int recent_get(char *term, int limit, void (*f)(const unsigned char*,const unsigned char*)) {
+int recent_get(const char *term, int limit, void (*f)(const unsigned char*,const unsigned char*)) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(NULL==term) {
if(sqlite3_prepare_v2(db_p,RECENT_GET_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
return -1;
}
-int recent_insert(char *workout, char *date) {
+int recent_insert(const char *workout, const char *date) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_exec(db_p,"PRAGMA foreign_keys = ON;",NULL,NULL,NULL)!=SQLITE_OK) { goto cleanup; }
return -1;
}
-int recent_update(char *workout, char *from_date, char *to_date) {
+int recent_update(const char *workout, const char *from_date, const char *to_date) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_prepare_v2(db_p,RECENT_UPDATE_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_bind_text(stmt_p,1,to_date,-1,SQLITE_STATIC)!=SQLITE_OK) { goto cleanup; }
int setup() {
sqlite3 *db_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_exec(db_p,CREATE_SCHEMA_SQL,NULL,NULL,NULL)!=SQLITE_OK) { goto cleanup; }
cleanup:
if(db_p!=NULL) { sqlite3_close_v2(db_p); }
return -1;
-}
\ No newline at end of file
+}
#include<data.h>
-int workout_delete(char *name) {
+int workout_delete(const char *name) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_exec(db_p,"PRAGMA foreign_keys = ON;",NULL,NULL,NULL)!=SQLITE_OK) { goto cleanup; }
return -1;
}
-int workout_get(char *term, char *filter, int limit, void (*print_row)(const unsigned char*,int,const unsigned char*)) {
+int workout_get(const char *term, const char *filter, int limit, void (*print_row)(const unsigned char*,int,const unsigned char*)) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
if(attribute_parse(filter,&required,&exclude)<0) { return -1; }
}
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(NULL==term) {
if(sqlite3_prepare_v2(db_p,WORKOUT_GET_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
return -1;
}
-int workout_insert(char *name, unsigned int flags) {
+int workout_insert(const char *name, unsigned int flags) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_prepare_v2(db_p,WORKOUT_INSERT_SQL,-1,&stmt_p,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_bind_text(stmt_p,1,name,-1,SQLITE_STATIC)!=SQLITE_OK) { goto cleanup; }
}
struct workout_toggler {
- char *name;
+ const char *name;
int attributes;
};
0
};
-int workout_toggle(char *name, char *attribute) {
+int workout_toggle(const char *name, const char *attribute) {
int index = attribute_index(attribute);
if(index<0) { return -1; }
}
}
-int workout_update(char *old, char *name, int flags) {
+int workout_update(const char *old, const char *name, int flags) {
sqlite3 *db_p = NULL;
sqlite3_stmt *stmt_p = NULL;
- if(sqlite3_open_v2(global_opts.db_location,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
+ if(sqlite3_open_v2(DB_FILENAME,&db_p,SQLITE_OPEN_READWRITE,NULL)!=SQLITE_OK) { goto cleanup; }
if(sqlite3_exec(db_p,"PRAGMA foreign_keys = ON;",NULL,NULL,NULL)!=SQLITE_OK) { goto cleanup; }
#include<date.h>
-int date_parse(char *str, char *buf, size_t len) {
+int date_parse(const char *str, char *buf, size_t len) {
assert(DATE_BUF_LEN==len);
if(NULL==str) {
#include<default.h>
struct options global_opts = {
- NULL, /* db_location */
- NULL, /* homedir */
-1, /* rows */
- 0 /* verbose */
+ 0, /* verbose */
+ 0 /* target */
};
int defaults() {
- char *p;
-
- // homedir
- p = getenv("WORKOUTS_HOME");
- if(p==NULL) {
- p = getenv("HOME");
- if(NULL==p) {
- log_err("HOME or WORKOUTS_HOME env variable must be defined\n");
- return -1;
- }
- }
-
- if(opt_set_homedir(p)<0) {
- log_err("HOME or WORKOUTS_HOME env value invalid\n");
- return -1;
- }
-
opt_set_rows(-1);
opt_set_log_level(LOG_LEVEL_DEFAULT);
opt_set_target(WORKOUT_DATA_TYPE_DEFAULT);
return 0;
-}
\ No newline at end of file
+}
#include<ls.h>
+static void print_attribute(const unsigned char*);
+
int ls_attribute() {
if(attribute_get(&print_attribute)<0) { return EXIT_FAILURE; }
#define ATTRIBUTE_PRINT_FORMAT "%s\n"
-void print_attribute(const unsigned char *name) {
+static void print_attribute(const unsigned char *name) {
printf(ATTRIBUTE_PRINT_FORMAT,name);
}
#include<ls.h>
-int last_done(char *search_term, char *filter_p) {
+static void generate_last_done(const unsigned char*,int, const unsigned char*);
+static void print_last_done();
+
+int last_done(const char *search_term, const char *filter_p) {
int i;
+ printf(WORKOUT_PRINT_ATTRIBUTE_HEADER,WORKOUT_PRINT_ATTRIBUTE_LABEL);
+ if(attribute_get(&print_header)<0) { return EXIT_FAILURE; }
+ printf(WORKOUT_PRINT_ATTRIBUTE_END);
+
helper.days_ago = malloc(helper.attr_count*sizeof(int));
if(NULL==helper.days_ago) { return EXIT_FAILURE; }
for(i=0;i<helper.attr_count;i++) {
helper.days_ago[i] = INT_MAX;
}
- time(&helper.now);
if(workout_get(
search_term, /* term */
global_opts.rows, /* limit */
&generate_last_done /* print_function */
)<0) {
+ free(helper.days_ago);
return EXIT_FAILURE;
}
print_last_done();
free(helper.days_ago);
- return EXIT_FAILURE;
+ return EXIT_SUCCESS;
}
-void generate_last_done(const unsigned char *workout, int flags, const unsigned char *date) {
+static void generate_last_done(const unsigned char *workout, int flags, const unsigned char *date) {
int i, YY, MM, DD, diff;
struct tm when;
if(date!=NULL) {
for(i=0;i<helper.attr_count;i++) {
- if(flags&1>0) {
+ if((flags&1)>0) {
memset(&when,0,sizeof(struct tm));
- if(sscanf(date,"%d-%d-%d",&YY,&MM,&DD)!=3) {
+ if(sscanf((const char*)date,"%d-%d-%d",&YY,&MM,&DD)!=3) {
log_err(LS_GENERATE_LAST_DONE_INVALID_DATE_OUTPUT);
}
#define LAST_DONE_PRINT_ATTRIBUTE "%d\t"
#define LAST_DONE_PRINT_SUGGESTED_LABEL "Suggested filter: "
-void print_last_done() {
+static void print_last_done() {
int i, max = 0;
printf(LAST_DONE_PRINT_HEADER,LAST_DONE_PRINT_LABEL);
for(i=0;i<helper.attr_count;i++) {
#include<ls.h>
-struct ls_helper helper = {
- 0,
- 0
-};
+struct ls_helper helper;
+
+static void helper_init();
int ls(int argc, char **argv) {
+ helper_init();
+
switch(global_opts.target) {
case WORKOUT_DATA_TYPE_ATTRIBUTE:
return ls_attribute();
case WORKOUT_DATA_TYPE_DEFAULT:
case WORKOUT_DATA_TYPE_WORKOUT:
return ls_workout(argc,argv);
+ default:
+ return EXIT_FAILURE;
}
}
+
+static void helper_init() {
+ helper.i = 0;
+ helper.attr_count = 0;
+ helper.days_ago = NULL;
+ time(&helper.now);
+}
--- /dev/null
+#include<ls.h>
+
+static void print_workout_name(const unsigned char*,int,const unsigned char*);
+
+int ls_name_only(const char *search_term, const char *filter_p) {
+ if(workout_get(
+ search_term, /* term */
+ filter_p, /* filter */
+ global_opts.rows, /* limit */
+ &print_workout_name /* print function */
+ )<0) { return EXIT_FAILURE; }
+
+ return EXIT_SUCCESS;
+}
+
+#define WORKOUT_PRINT_NAME_ONLY "%s\n"
+
+static void print_workout_name(const unsigned char *name, int attr_flags, const unsigned char *last) {
+ printf(WORKOUT_PRINT_NAME_ONLY,name);
+}
#include<ls.h>
+static void print_recent(const unsigned char*,const unsigned char*);
+
int ls_recent(int argc, char **argv) {
if(recent_get(
(argc>=2)?argv[1]:NULL, /* term */
#define RECENT_PRINT_FORMAT "%-30.30s\t%s\n"
-void print_recent(const unsigned char *workout, const unsigned char *date) {
+static void print_recent(const unsigned char *workout, const unsigned char *date) {
printf(RECENT_PRINT_FORMAT,workout,date);
}
#include<ls.h>
-int ls_single(char *search_term, char *filter_p) {
+static void print_single(const unsigned char*,int,const unsigned char*);
+
+int ls_single(const char *search_term, const char *filter_p) {
if(workout_get(
search_term, /* term */
filter_p, /* filter */
#define WORKOUT_PRINT_SINGLE_LAST_DONE "[Last done: %s]\n"
#define WORKOUT_PRINT_SINGLE_LAST_NULL "[Last done: N/A]\n"
-void print_single(const unsigned char *name, int attr_flags, const unsigned char *last) {
+static void print_single(const unsigned char *name, int attr_flags, const unsigned char *last) {
int i;
- printf(WORKOUT_PRINT_SINGLE_ATTRIBUTE_HEADER,strlen(name),WORKOUT_PRINT_SINGLE_ATTRIBUTE_LABEL);
+ printf(WORKOUT_PRINT_SINGLE_ATTRIBUTE_HEADER,(int)strlen((const char*)name),WORKOUT_PRINT_SINGLE_ATTRIBUTE_LABEL);
if(attribute_get(&print_header)<0) {
log_err(LS_PRINT_SINGLE_ATTRIBUTE_GET_FAILED);
return;
}
printf(WORKOUT_PRINT_SINGLE_ATTRIBUTE_END);
- printf(WORKOUT_PRINT_SINGLE_NAME,strlen(WORKOUT_PRINT_SINGLE_ATTRIBUTE_LABEL),name);
+ printf(WORKOUT_PRINT_SINGLE_NAME,(int)strlen(WORKOUT_PRINT_SINGLE_ATTRIBUTE_LABEL),name);
for(i=0;i<helper.attr_count;i++) {
- printf(WORKOUT_PRINT_SINGLE_ATTRIBUTE,(attr_flags&1>0)?"yes":"no");
+ printf(WORKOUT_PRINT_SINGLE_ATTRIBUTE,((attr_flags&1)>0)?"yes":"no");
attr_flags >>= 1;
}
#include<ls.h>
-#define WORKOUT_PRINT_ATTRIBUTE_HEADER "%-30.30s\t"
-#define WORKOUT_PRINT_ATTRIBUTE_LABEL "Attributes:"
-#define WORKOUT_PRINT_ATTRIBUTE_END "\n"
+static int ls_with_attributes(const char*,const char*);
+static void print_workout(const unsigned char*,int,const unsigned char*);
static struct option ls_long_options[] = {
{"filter", required_argument, 0, 'f'},
{"last-done", no_argument, 0, 1},
+ {"name-only", no_argument, 0, 2},
{0,0,0,0}
};
* x: can have attribute
*/
- int c, j, last_done_flag = 0;
+ int c;
char *filter_p = NULL;
char *search_term = NULL;
+ enum ls_format format = LS_FORMAT_DEFAULT;
// reset optind; see `man getopt.3` NOTES section for justification
optind = 0;
return EXIT_FAILURE;
break;
- case 1:
- last_done_flag = 1;
+ case 1: // --last-done
+ format = LS_FORMAT_LAST_DONE;
+ break;
+ case 2: // --name-only
+ format = LS_FORMAT_NAME_ONLY;
break;
case 'f':
filter_p = optarg;
+ {
+ int count;
+ if((count = attribute_count())<0) { return EXIT_FAILURE; }
+ if(strlen(filter_p)!=count) {
+ log_err(LS_MESSAGE_INVALID_FILTER,strlen(filter_p),count);
+ return EXIT_FAILURE;
+ }
+ }
break;
case '?':
default:
search_term = argv[optind];
}
- if((global_opts.rows==1)&&(!last_done_flag)) {
- return ls_single(search_term,filter_p);
+ switch(format) {
+ case LS_FORMAT_LAST_DONE:
+ return last_done(search_term, filter_p);
+ case LS_FORMAT_NAME_ONLY:
+ return ls_name_only(search_term,filter_p);
+ case LS_FORMAT_WITH_ATTRIBUTES:
+ if(global_opts.rows==1) {
+ return ls_single(search_term,filter_p);
+ } else {
+ return ls_with_attributes(search_term,filter_p);
+ }
+ break;
+ default:
+ log_err(LS_MESSAGE_INVALID_FORMAT);
+ return EXIT_FAILURE;
}
+}
+static int ls_with_attributes(const char *search_term, const char *filter_p) {
printf(WORKOUT_PRINT_ATTRIBUTE_HEADER,WORKOUT_PRINT_ATTRIBUTE_LABEL);
- if(attribute_get(&print_header)<0) { return -1; }
+ if(attribute_get(&print_header)<0) { return EXIT_FAILURE; }
printf(WORKOUT_PRINT_ATTRIBUTE_END);
- if(last_done_flag) { return last_done(search_term, filter_p); }
-
if(workout_get(
search_term, /* term */
filter_p, /* filter */
#define WORKOUT_PRINT_LAST_DONE "[Last done: %s]\n"
#define WORKOUT_PRINT_LAST_NULL "[Last done: N/A]\n"
-void print_workout(const unsigned char *name, int attr_flags, const unsigned char *last) {
+static void print_workout(const unsigned char *name, int attr_flags, const unsigned char *last) {
int i;
printf(WORKOUT_PRINT_NAME,name);
for(i=0;i<helper.attr_count;i++) {
- printf(WORKOUT_PRINT_ATTRIBUTE,(attr_flags&1>0)?"yes":"no");
+ printf(WORKOUT_PRINT_ATTRIBUTE,((attr_flags&1)>0)?"yes":"no");
attr_flags >>= 1;
}
printf((NULL==last)?WORKOUT_PRINT_LAST_NULL:WORKOUT_PRINT_LAST_DONE,last);
#include<opt.h>
-#define DB_FILENAME "workouts.db"
-#define DB_FILENAME_LENGTH 11
-
-int opt_set_homedir(char *to_set) {
- char *homedir, *db_location;
- int len = strlen(to_set);
- if(len<1) { return -1; }
-
- homedir = malloc(sizeof(char)*(len+1));
- if(NULL==homedir) { return -1; }
-
- db_location = malloc(sizeof(char)*(len+DB_FILENAME_LENGTH+2));
- if(NULL==db_location) { return -1; }
-
- strcpy(homedir,to_set);
- strcpy(db_location,homedir);
-
- for(int i=0;;i++) {
- if(db_location[i]=='\0') {
- if(db_location[i-1]!='/') {
- strcat(db_location,"/");
- }
- break;
- }
- }
-
- strcat(db_location,DB_FILENAME);
-
- DIR *dir = opendir(homedir);
- if(NULL==dir) {
+int opt_set_homedir(const char *to_set) {
+ if(chdir(to_set)!=0) {
+ perror("chdir");
return -1;
}
- closedir(dir);
-
- if(global_opts.homedir!=NULL) { free(global_opts.homedir); }
- if(global_opts.db_location!=NULL) { free(global_opts.db_location); }
- global_opts.homedir = homedir;
- global_opts.db_location = db_location;
-
return 1;
}
void opt_set_rows(int to_set) {
global_opts.rows = to_set;
-}
\ No newline at end of file
+}
return rm_recent(argc,argv);
case WORKOUT_DATA_TYPE_WORKOUT:
return rm_workout(argc,argv);
+ default:
+ return EXIT_FAILURE;
}
}
log_msg(RM_MESSAGE_WORKOUT_DELETED,argv[1]);
return EXIT_SUCCESS;
-}
\ No newline at end of file
+}
return update_recent(argc,argv);
case WORKOUT_DATA_TYPE_WORKOUT:
return update_workout(argc,argv);
+ default:
+ return EXIT_FAILURE;
}
}
void usage() {
log_err("Usage:\n");
log_err("\tworkouts [options] add [!name] [date] [attribute filter]\n");
- log_err("\tworkouts [options] ls [--filter {attribute filter}] [--last-done] [search term]\n");
+ log_err("\tworkouts [options] ls [--filter {attribute filter}] [--last-done] [--name-only] [search term]\n");
log_err("\tworkouts [options] rm [!name] [date]\n");
log_err("\tworkouts [options] toggle [!workout name] [!attr]\n");
log_err("\tworkouts [options] update [workout name] [!from] [!to]\n");
{
"name": "workouts-integration-tests",
"version": "0.0.0",
- "lockfileVersion": 1,
+ "lockfileVersion": 2,
"requires": true,
+ "packages": {
+ "": {
+ "name": "workouts-integration-tests",
+ "version": "0.0.0",
+ "license": "UNLICENSED",
+ "devDependencies": {
+ "mocha": "^8.4.0"
+ }
+ },
+ "node_modules/@ungap/promise-all-settled": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz",
+ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==",
+ "dev": true
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
+ "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "3.0.0",
+ "picomatch": "2.3.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "1.0.2",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "node_modules/camelcase": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
+ "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "4.3.0",
+ "supports-color": "7.2.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
+ "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
+ "dev": true,
+ "dependencies": {
+ "anymatch": "3.1.2",
+ "braces": "3.0.2",
+ "fsevents": "2.3.2",
+ "glob-parent": "5.1.2",
+ "is-binary-path": "2.1.0",
+ "is-glob": "4.0.1",
+ "normalize-path": "3.0.0",
+ "readdirp": "3.5.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "4.2.2",
+ "strip-ansi": "6.0.0",
+ "wrap-ansi": "7.0.0"
+ }
+ },
+ "node_modules/cliui/node_modules/ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "8.0.0",
+ "is-fullwidth-code-point": "3.0.0",
+ "strip-ansi": "6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+ "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/debug/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "6.0.0",
+ "path-exists": "4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true,
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.4",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/growl": {
+ "version": "1.10.5",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.x"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz",
+ "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/log-symbols": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
+ "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "4.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "1.1.11"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mocha": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz",
+ "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==",
+ "dev": true,
+ "dependencies": {
+ "@ungap/promise-all-settled": "1.1.2",
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.1",
+ "debug": "4.3.1",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.1.6",
+ "growl": "1.10.5",
+ "he": "1.2.0",
+ "js-yaml": "4.0.0",
+ "log-symbols": "4.0.0",
+ "minimatch": "3.0.4",
+ "ms": "2.1.3",
+ "nanoid": "3.1.20",
+ "serialize-javascript": "5.0.1",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "which": "2.0.2",
+ "wide-align": "1.1.3",
+ "workerpool": "6.1.0",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha"
+ },
+ "engines": {
+ "node": ">= 10.12.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.1.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
+ "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
+ "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "2.3.0"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ },
+ "node_modules/serialize-javascript": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
+ "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "2.1.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "dependencies": {
+ "is-fullwidth-code-point": "2.0.0",
+ "strip-ansi": "4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "2.1.1"
+ }
+ },
+ "node_modules/workerpool": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz",
+ "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "4.3.0",
+ "string-width": "4.2.2",
+ "strip-ansi": "6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "8.0.0",
+ "is-fullwidth-code-point": "3.0.0",
+ "strip-ansi": "6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "7.0.4",
+ "escalade": "3.1.1",
+ "get-caller-file": "2.0.5",
+ "require-directory": "2.1.1",
+ "string-width": "4.2.2",
+ "y18n": "5.0.8",
+ "yargs-parser": "20.2.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "6.2.0",
+ "decamelize": "4.0.0",
+ "flat": "5.0.2",
+ "is-plain-obj": "2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs/node_modules/ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
+ "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "8.0.0",
+ "is-fullwidth-code-point": "3.0.0",
+ "strip-ansi": "6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ }
+ },
"dependencies": {
"@ungap/promise-all-settled": {
"version": "1.1.2",
const assert = require('assert');
const {cwd} = require('process');
+const path = require('path');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
it('should print usage options when run with --help option', async() => {
let usage = `Usage:\n`;
usage += `\tworkouts [options] add [!name] [date] [attribute filter]\n`;
- usage += `\tworkouts [options] ls [--filter {attribute filter}] [--last-done] [search term]\n`;
+ usage += `\tworkouts [options] ls [--filter {attribute filter}] [--last-done] [--name-only] [search term]\n`;
usage += `\tworkouts [options] rm [!name] [date]\n`;
usage += `\tworkouts [options] toggle [!workout name] [!attr]\n`;
usage += `\tworkouts [options] update [workout name] [!from] [!to]\n`;
});
it(`should throw when given option --homedir which doesn't exist`, async() => {
- await assert.rejects(async() => await exec(`${workouts} --homedir=${pwd}/doesnt_exist`));
- await assert.rejects(async() => await exec(`${workouts} -d ${pwd}/doesnt_exist`));
+ const baddir = path.join(cwd(),'doesnt_exist');
+ await assert.rejects(async() => await exec(`${workouts} --homedir=${baddir}`),(err) => {
+ assert.strictEqual(err.code,1);
+ assert.strictEqual(err.stderr,`chdir: No such file or directory\n`);
+ return true;
+ });
+ await assert.rejects(async() => await exec(`${workouts} -d ${baddir}`),(err) => {
+ assert.strictEqual(err.code,1);
+ assert.strictEqual(err.stderr,`chdir: No such file or directory\n`);
+ return true;
+ });
});
after(async() => {
const workouts = `../../workouts -d ${cwd()}`;
+ // helper methods for setting dates
+ const now = new Date();
+ const daysAgo = (offset) => {
+ const date = new Date();
+ date.setDate(now.getDate() - offset);
+ const y = date.getFullYear();
+ const m = (date.getMonth()+1).toString().padStart(2,'0');
+ const d = (date.getDate()).toString().padStart(2,'0');
+ return `${y}-${m}-${d}`;
+ };
+
const recent = [
- {name:"workout1",date:"2020-07-10"},
- {name:"apple",date:"2020-07-09"},
- {name:"test",date:"2020-07-09"},
- {name:"workout2",date:"2020-07-09"}
+ {name:"workout1",date:daysAgo(0)},
+ {name:"apple",date:daysAgo(1)},
+ {name:"test",date:daysAgo(1)},
+ {name:"workout2",date:daysAgo(2)},
+ {name:"workout4",date:daysAgo(10)},
+ {name:"workout3",date:daysAgo(20)}
];
before(async() => {
beforeEach(async() => {
await assert.doesNotReject(async() => await exec(`${workouts} --attr add lower`));
await assert.doesNotReject(async() => await exec(`${workouts} --attr add upper`));
-
- await assert.doesNotReject(async() => await exec(`${workouts} --workout add workout1 01`));
- await assert.doesNotReject(async() => await exec(`${workouts} --workout add workout2 10`));
- await assert.doesNotReject(async() => await exec(`${workouts} --workout add test 00`));
- await assert.doesNotReject(async() => await exec(`${workouts} --workout add apple 11`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --attr add cardio`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --attr add back`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --attr add core`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --attr add other`));
+
+ await assert.doesNotReject(async() => await exec(`${workouts} --workout add workout1 010000`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --workout add workout2 100000`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --workout add test 001000`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --workout add apple 110000`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --workout add workout3 110011`));
+ await assert.doesNotReject(async() => await exec(`${workouts} --workout add workout4 001100`));
for(const i of recent) {
await assert.doesNotReject(async() => await exec(`${workouts} --recent add ${i.name} ${i.date}`));
describe('ls_attribute', () => {
it('should successfully list all attributes in order they were added', async() => {
- const attributes = ["lower","upper"];
+ const attributes = ["lower","upper", "cardio", "back", "core", "other"];
await assert.doesNotReject(async() => {
const {stdout,stderr} = await exec(`${workouts} --attr ls`);
it('should success filter recent workouts', async() => {
const {stdout,stderr} = await exec(`${workouts} --recent ls test`);
- const expected = `test`.padEnd(30,' ') + `\t2020-07-09\n`;
+ const expected = `test`.padEnd(30,' ') + `\t${daysAgo(1)}\n`;
assert.strictEqual(stdout,expected);
});
});
const {stdout,stderr} = await exec(`${workouts}`);
let expected = `Attributes:`.padEnd(30,' ');
- expected += `\tlower\tupper\t\n`;
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
expected += `workout1`.padEnd(30,' ');
- expected += `\tno\tyes\t[Last done: 2020-07-10]\n`;
+ expected += `\tno\tyes\tno\tno\tno\tno\t[Last done: ${daysAgo(0)}]\n`;
expected += `apple`.padEnd(30,' ');
- expected += `\tyes\tyes\t[Last done: 2020-07-09]\n`;
+ expected += `\tyes\tyes\tno\tno\tno\tno\t[Last done: ${daysAgo(1)}]\n`;
expected += `test`.padEnd(30,' ');
- expected += `\tno\tno\t[Last done: 2020-07-09]\n`;
+ expected += `\tno\tno\tyes\tno\tno\tno\t[Last done: ${daysAgo(1)}]\n`;
expected += `workout2`.padEnd(30,' ');
- expected += `\tyes\tno\t[Last done: 2020-07-09]\n`;
+ expected += `\tyes\tno\tno\tno\tno\tno\t[Last done: ${daysAgo(2)}]\n`;
+ expected += `workout4`.padEnd(30,' ');
+ expected += `\tno\tno\tyes\tyes\tno\tno\t[Last done: ${daysAgo(10)}]\n`;
+ expected += `workout3`.padEnd(30,' ');
+ expected += `\tyes\tyes\tno\tno\tyes\tyes\t[Last done: ${daysAgo(20)}]\n`;
assert.strictEqual(stdout,expected);
});
const {stdout,stderr} = await exec(`${workouts} --rows=1 ls`);
let expected = `Attributes:`;
- expected += `\tlower\tupper\t\n`;
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
expected += `workout1 `;
- expected += `\tno\tyes\t[Last done: 2020-07-10]\n`;
+ expected += `\tno\tyes\tno\tno\tno\tno\t[Last done: ${daysAgo(0)}]\n`;
assert.strictEqual(stdout,expected);
const res = await exec(`${workouts} --workout update workout2 workoutworkoutworkoutworkoutworkout`);
const res2 = await exec(`${workouts} --rows=1 ls workoutworkout`);
expected = `Attributes: `;
- expected += `\tlower\tupper\t\n`;
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
expected += `workoutworkoutworkoutworkoutworkout`;
- expected += `\tyes\tno\t[Last done: 2020-07-09]\n`;
+ expected += `\tyes\tno\tno\tno\tno\tno\t[Last done: ${daysAgo(2)}]\n`;
assert.strictEqual(res2.stdout,expected);
});
});
const {stdout,stderr} = await exec(`${workouts} ls workout`);
let expected = `Attributes:`.padEnd(30,' ');
- expected += `\tlower\tupper\t\n`;
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
expected += `workout1`.padEnd(30,' ');
- expected += `\tno\tyes\t[Last done: 2020-07-10]\n`;
+ expected += `\tno\tyes\tno\tno\tno\tno\t[Last done: ${daysAgo(0)}]\n`;
expected += `workout2`.padEnd(30,' ');
- expected += `\tyes\tno\t[Last done: 2020-07-09]\n`;
+ expected += `\tyes\tno\tno\tno\tno\tno\t[Last done: ${daysAgo(2)}]\n`;
+ expected += `workout4`.padEnd(30,' ');
+ expected += `\tno\tno\tyes\tyes\tno\tno\t[Last done: ${daysAgo(10)}]\n`;
+ expected += `workout3`.padEnd(30,' ');
+ expected += `\tyes\tyes\tno\tno\tyes\tyes\t[Last done: ${daysAgo(20)}]\n`;
assert.strictEqual(stdout,expected);
});
it('should reject when `ls` term is not given and attempting to filter', async() => {
await assert.rejects(async() => {
- await exec(`${workouts} --filter 01`);
+ await exec(`${workouts} --filter 010000`);
});
});
+
+ it('should throw when given a filter with the wrong amount of attributes', async() => {
+ let num = 1;
+ const err = (err) => {
+ assert.strictEqual(err.code,1);
+ if(num===6) { num++; }
+ assert.strictEqual(err.stderr,`invalid filter; wrong number of attributes given (${num++} != 6)\n`);
+ return true;
+ };
+
+ await assert.rejects(async() => {
+ await exec(`${workouts} ls --filter 0`);
+ },err);
+ await assert.rejects(async() => {
+ await exec(`${workouts} ls --filter 01`);
+ },err);
+
+ await assert.rejects(async() => {
+ await exec(`${workouts} ls --filter 010`);
+ },err);
+ await assert.rejects(async() => {
+ await exec(`${workouts} ls --filter 0100`);
+ },err);
+ await assert.rejects(async() => {
+ await exec(`${workouts} ls --filter 01000`);
+ },err);
+ await assert.rejects(async() => {
+ await exec(`${workouts} ls --filter 0100000`);
+ },err);
+ });
it('should successfully filter workouts by attribute', async() => {
await assert.doesNotReject(async() => {
- const {stdout,stderr} = await exec(`${workouts} ls --filter 10`);
+ const {stdout,stderr} = await exec(`${workouts} ls --filter 100000`);
let expected = `Attributes:`.padEnd(30,' ');
- expected += `\tlower\tupper\t\n`;
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
expected += `workout2`.padEnd(30,' ');
- expected += `\tyes\tno\t[Last done: 2020-07-09]\n`;
+ expected += `\tyes\tno\tno\tno\tno\tno\t[Last done: ${daysAgo(2)}]\n`;
assert.strictEqual(stdout,expected);
});
await assert.doesNotReject(async() => {
- const {stdout,stderr} = await exec(`${workouts} ls --filter 01`);
+ const {stdout,stderr} = await exec(`${workouts} ls --filter 010000`);
let expected = `Attributes:`.padEnd(30,' ');
- expected += `\tlower\tupper\t\n`;
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
expected += `workout1`.padEnd(30,' ');
- expected += `\tno\tyes\t[Last done: 2020-07-10]\n`;
+ expected += `\tno\tyes\tno\tno\tno\tno\t[Last done: ${daysAgo(0)}]\n`;
assert.strictEqual(stdout,expected);
});
await assert.doesNotReject(async() => {
- const {stdout,stderr} = await exec(`${workouts} ls --filter 1x`);
+ const {stdout,stderr} = await exec(`${workouts} ls --filter 1xxxxx`);
let expected = `Attributes:`.padEnd(30,' ');
- expected += `\tlower\tupper\t\n`;
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
expected += `apple`.padEnd(30,' ');
- expected += `\tyes\tyes\t[Last done: 2020-07-09]\n`;
+ expected += `\tyes\tyes\tno\tno\tno\tno\t[Last done: ${daysAgo(1)}]\n`;
expected += `workout2`.padEnd(30,' ');
- expected += `\tyes\tno\t[Last done: 2020-07-09]\n`;
+ expected += `\tyes\tno\tno\tno\tno\tno\t[Last done: ${daysAgo(2)}]\n`;
+ expected += `workout3`.padEnd(30,' ');
+ expected += `\tyes\tyes\tno\tno\tyes\tyes\t[Last done: ${daysAgo(20)}]\n`;
assert.strictEqual(stdout,expected);
});
await assert.doesNotReject(async() => {
- const {stdout,stderr} = await exec(`${workouts} ls --filter 00`);
+ const {stdout,stderr} = await exec(`${workouts} ls --filter 00xxxx`);
let expected = `Attributes:`.padEnd(30,' ');
- expected += `\tlower\tupper\t\n`;
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
expected += `test`.padEnd(30,' ');
- expected += `\tno\tno\t[Last done: 2020-07-09]\n`;
+ expected += `\tno\tno\tyes\tno\tno\tno\t[Last done: ${daysAgo(1)}]\n`;
+ expected += `workout4`.padEnd(30,' ');
+ expected += `\tno\tno\tyes\tyes\tno\tno\t[Last done: ${daysAgo(10)}]\n`;
+
+ assert.strictEqual(stdout,expected);
+ });
+ });
+
+ it('should successfully generate last done array when given --last-done option', async() => {
+ await assert.doesNotReject(async() => {
+ const {stdout,stderr} = await exec(`${workouts} ls --last-done`);
+ let expected = `Attributes:`.padEnd(30,' ');
+ expected += `\tlower\tupper\tcardio\tback\tcore\tother\t\n`;
+ expected += `Last Done:`.padEnd(30,' ');
+ expected += `\t1\t0\t1\t10\t20\t20\t\n`;
+ expected += `Suggested filter: xxxx11\n`;
+
+ assert.strictEqual(stdout,expected);
+ });
+ });
+
+
+ it('should successfully print only workout names when given --name-only option', async() => {
+ await assert.doesNotReject(async() => {
+ const {stdout,stderr} = await exec(`${workouts} ls --name-only`);
+ let expected = `workout1\n`;
+ expected += `apple\n`;
+ expected += `test\n`;
+ expected += `workout2\n`;
+ expected += `workout4\n`;
+ expected += `workout3\n`;
+
+ assert.strictEqual(stdout,expected);
+ });
+
+ await assert.doesNotReject(async() => {
+ const {stdout,stderr} = await exec(`${workouts} ls --name-only workout`);
+ let expected = `workout1\n`;
+ expected += `workout2\n`;
+ expected += `workout4\n`;
+ expected += `workout3\n`;
+
+ assert.strictEqual(stdout,expected);
+ });
+
+ await assert.doesNotReject(async() => {
+ const {stdout,stderr} = await exec(`${workouts} ls --name-only -f 11x0xx`);
+ let expected = `apple\n`;
+ expected += `workout3\n`;
assert.strictEqual(stdout,expected);
});
AM_CPPFLAGS = \
- -I$(top_builddir)/include/ \
- -I$(top_builddir)/test/unit/ \
- -I$(top_srcdir)/include/ \
- -I$(top_srcdir)/test/unit/
+ -Wall \
+ -Werror
EXTRA_DIST = \
test_utils.h \
data.attr.tests.h \
data.recent.tests.h \
data.workout.tests.h \
- ls.tests.h \
toggle.tests.h \
update.tests.h
common_SOURCES = test_utils.c
-TEST_SRC_DIR = $(top_srcdir)/src
-
-common_SOURCES += $(TEST_SRC_DIR)/default.c $(TEST_SRC_DIR)/log.c
-common_SOURCES += $(TEST_SRC_DIR)/data/setup.c
-common_SOURCES += $(TEST_SRC_DIR)/opt/homedir.c $(TEST_SRC_DIR)/opt/loglvl.c $(TEST_SRC_DIR)/opt/rows.c $(TEST_SRC_DIR)/opt/target.c
+common_SOURCES += $(top_srcdir)/src/default.c $(top_srcdir)/src/log.c
+common_SOURCES += $(top_srcdir)/src/data/setup.c
+common_SOURCES += $(top_srcdir)/src/opt/homedir.c
+common_SOURCES += $(top_srcdir)/src/opt/loglvl.c
+common_SOURCES += $(top_srcdir)/src/opt/rows.c
+common_SOURCES += $(top_srcdir)/src/opt/target.c
add_tests_SOURCES = \
$(common_SOURCES) \
add.tests.c \
- $(TEST_SRC_DIR)/add.c \
- $(TEST_SRC_DIR)/data/attr.c \
- $(TEST_SRC_DIR)/data/recent.c \
- $(TEST_SRC_DIR)/data/workout.c \
- $(TEST_SRC_DIR)/date.c \
- $(TEST_SRC_DIR)/usage.c
+ $(top_srcdir)/src/add.c \
+ $(top_srcdir)/src/data/attr.c \
+ $(top_srcdir)/src/data/recent.c \
+ $(top_srcdir)/src/data/workout.c \
+ $(top_srcdir)/src/date.c \
+ $(top_srcdir)/src/usage.c
data_attr_tests_SOURCES = \
$(common_SOURCES) \
data.attr.tests.c \
- $(TEST_SRC_DIR)/data/attr.c \
- $(TEST_SRC_DIR)/data/workout.c
+ $(top_srcdir)/src/data/attr.c \
+ $(top_srcdir)/src/data/workout.c
data_recent_tests_SOURCES = \
$(common_SOURCES) \
data.recent.tests.c \
- $(TEST_SRC_DIR)/data/recent.c \
- $(TEST_SRC_DIR)/data/workout.c \
- $(TEST_SRC_DIR)/data/attr.c
+ $(top_srcdir)/src/data/recent.c \
+ $(top_srcdir)/src/data/workout.c \
+ $(top_srcdir)/src/data/attr.c
data_workout_tests_SOURCES = \
$(common_SOURCES) \
data.workout.tests.c \
- $(TEST_SRC_DIR)/data/workout.c \
- $(TEST_SRC_DIR)/data/attr.c \
- $(TEST_SRC_DIR)/data/recent.c
+ $(top_srcdir)/src/data/workout.c \
+ $(top_srcdir)/src/data/attr.c \
+ $(top_srcdir)/src/data/recent.c
ls_tests_SOURCES = \
$(common_SOURCES) \
ls.tests.c \
- $(TEST_SRC_DIR)/data/attr.c \
- $(TEST_SRC_DIR)/data/recent.c \
- $(TEST_SRC_DIR)/data/workout.c \
- $(TEST_SRC_DIR)/ls/attr.c \
- $(TEST_SRC_DIR)/ls/head.c \
- $(TEST_SRC_DIR)/ls/last.c \
- $(TEST_SRC_DIR)/ls/ls.c \
- $(TEST_SRC_DIR)/ls/recent.c \
- $(TEST_SRC_DIR)/ls/single.c \
- $(TEST_SRC_DIR)/ls/workout.c \
- $(TEST_SRC_DIR)/usage.c
+ $(top_srcdir)/src/data/attr.c \
+ $(top_srcdir)/src/data/recent.c \
+ $(top_srcdir)/src/data/workout.c \
+ $(top_srcdir)/src/ls/attr.c \
+ $(top_srcdir)/src/ls/head.c \
+ $(top_srcdir)/src/ls/last.c \
+ $(top_srcdir)/src/ls/ls.c \
+ $(top_srcdir)/src/ls/name.c \
+ $(top_srcdir)/src/ls/recent.c \
+ $(top_srcdir)/src/ls/single.c \
+ $(top_srcdir)/src/ls/workout.c \
+ $(top_srcdir)/src/usage.c
toggle_tests_SOURCES = \
$(common_SOURCES) \
toggle.tests.c \
- $(TEST_SRC_DIR)/data/attr.c \
- $(TEST_SRC_DIR)/data/workout.c \
- $(TEST_SRC_DIR)/toggle.c \
- $(TEST_SRC_DIR)/usage.c
+ $(top_srcdir)/src/data/attr.c \
+ $(top_srcdir)/src/data/workout.c \
+ $(top_srcdir)/src/toggle.c \
+ $(top_srcdir)/src/usage.c
update_tests_SOURCES = \
$(common_SOURCES) \
update.tests.c \
- $(TEST_SRC_DIR)/data/attr.c \
- $(TEST_SRC_DIR)/data/recent.c \
- $(TEST_SRC_DIR)/data/workout.c \
- $(TEST_SRC_DIR)/date.c \
- $(TEST_SRC_DIR)/update.c
+ $(top_srcdir)/src/data/attr.c \
+ $(top_srcdir)/src/data/recent.c \
+ $(top_srcdir)/src/data/workout.c \
+ $(top_srcdir)/src/date.c \
+ $(top_srcdir)/src/update.c
add_recent_basic_test();
add_workout_basic_test();
- clean();
+ clean_env();
return EXIT_SUCCESS;
}
assert(EXIT_FAILURE==add(3,argv));
reset_env();
-}
\ No newline at end of file
+}
attribute_insert_test();
attribute_parse_test();
- clean();
+ clean_env();
return EXIT_SUCCESS;
}
recent_get_test();
recent_insert_test();
- clean();
+ clean_env();
return EXIT_SUCCESS;
}
workout_toggle_test();
workout_update_test();
- clean();
+ clean_env();
return EXIT_SUCCESS;
}
-#include<ls.tests.h>
+#include<test_utils.h>
+
+#include<ls.h>
+
+static void daysAgo(int,char*,int);
+static void ls_attribute_basic_test();
+static void ls_recent_basic_test();
+static void ls_workout_basic_test();
+static void ls_workout_last_done_test();
+static void ls_workout_name_only_test();
int main() {
setup_env();
ls_attribute_basic_test();
ls_recent_basic_test();
ls_workout_basic_test();
+ ls_workout_last_done_test();
+ ls_workout_name_only_test();
- clean();
+ clean_env();
return EXIT_SUCCESS;
}
-void ls_attribute_basic_test() {
+#define DATE_BUF_LEN 11
+
+static void daysAgo(int days,char *buf, int buflen) {
+ time_t t = time(NULL);
+ struct tm now = *localtime(&t);
+ now.tm_mday -= days;
+ assert(strftime(buf,buflen,"&Y-%m-%d",&now)!=0);
+}
+
+static void ls_attribute_basic_test() {
assert(EXIT_SUCCESS==ls_attribute());
}
-void ls_recent_basic_test() {
+static void ls_recent_basic_test() {
char *opts[] = {
"ls",
"test workout",
assert(EXIT_SUCCESS==ls_recent(2,opts));
}
-void ls_workout_basic_test() {
+static void ls_workout_basic_test() {
global_opts.target = WORKOUT_DATA_TYPE_WORKOUT;
char *bad_opt[] = {
reset_env();
}
+
+static void ls_workout_last_done_test() {
+ char timebuf[DATE_BUF_LEN];
+
+ char *args[] = {
+ "ls",
+ "--last-done",
+ "P90X",
+ NULL
+ };
+
+ assert(attribute_insert("test")==1);
+ assert(attribute_insert("test2")==1);
+ assert(attribute_insert("test3")==1);
+ assert(attribute_insert("test4")==1);
+ assert(attribute_insert("test5")==1);
+ assert(attribute_insert("test6")==1);
+
+ assert(workout_insert("P90X - workout 1",37)==1);
+ assert(workout_insert("Rushfit - workout 2",24)==1);
+ assert(workout_insert("Hard Core - workout 3",2)==1);
+
+ daysAgo(1,timebuf,DATE_BUF_LEN);
+ assert(recent_insert("P90X - workout 1",timebuf)==1);
+ daysAgo(2,timebuf,DATE_BUF_LEN);
+ assert(recent_insert("Rushfit - workout 2",timebuf)==1);
+ daysAgo(3,timebuf,DATE_BUF_LEN);
+ assert(recent_insert("Hard Core - workout 3",timebuf)==1);
+
+ assert(EXIT_SUCCESS==ls(1,args));
+ assert(EXIT_SUCCESS==ls(2,args));
+ assert(EXIT_SUCCESS==ls(3,args));
+
+ reset_env();
+}
+
+static void ls_workout_name_only_test() {
+ char *args[] = {
+ "ls",
+ "--name-only",
+ "P90X",
+ NULL
+ };
+
+ assert(attribute_insert("test")==1);
+ assert(attribute_insert("test2")==1);
+ assert(attribute_insert("test3")==1);
+ assert(attribute_insert("test4")==1);
+ assert(attribute_insert("test5")==1);
+ assert(attribute_insert("test6")==1);
+
+ assert(workout_insert("P90X - workout 1",37)==1);
+ assert(workout_insert("Rushfit - workout 2",24)==1);
+ assert(workout_insert("Hard Core - workout 3",2)==1);
+
+ assert(EXIT_SUCCESS==ls(1,args));
+ assert(EXIT_SUCCESS==ls(2,args));
+ assert(EXIT_SUCCESS==ls(3,args));
+
+ reset_env();
+}
+++ /dev/null
-#ifndef __LS_TESTS_H_
-#define __LS_TESTS_H_
-
-#include<test_utils.h>
-
-#include<ls.h>
-
-int main();
-void ls_attribute_basic_test();
-void ls_recent_basic_test();
-void ls_workout_basic_test();
-
-#endif
\ No newline at end of file
#include<test_utils.h>
-void clean() {
- free(global_opts.db_location);
- free(global_opts.homedir);
+void clean_env() {
+ assert((remove(DB_FILENAME)==0)||(errno==ENOENT));
}
void reset_env() {
- assert((remove(global_opts.db_location)==0)||(errno==ENOENT));
+ clean_env();
setup();
}
#include<data.h>
#include<default.h>
-void clean();
+void clean_env();
void setup_env();
void reset_env();
toggle_basic_test();
- clean();
+ clean_env();
return EXIT_SUCCESS;
}
assert(EXIT_SUCCESS==toggle(3,argv));
reset_env();
-}
\ No newline at end of file
+}
update_recent_basic_test();
update_workout_basic_test();
- clean();
+ clean_env();
return EXIT_SUCCESS;
}