From 8537febfa74dddcbcbef493d6de8b0f38f614bc9 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 12 Sep 2020 00:40:51 +0100 Subject: [PATCH] initial design completed --- .gitignore | 17 + Dockerfile | 76 +- Makefile.am | 76 ++ README.md | 4 +- configure.ac | 72 + include/add.h | 22 + include/branch.h | 23 + include/cat.h | 12 + include/commit.h | 12 + include/default.h | 16 + include/edit.h | 17 + include/editor.h | 21 + include/exec.h | 17 + include/git.h | 68 + include/hash.h | 11 + include/log.h | 18 + include/ls.h | 13 + include/main.h | 22 +- include/opt.h | 25 + include/prepend.h | 10 + include/templ.h | 19 + src/add.c | 70 + src/branch.c | 115 ++ src/cat.c | 14 + src/commit.c | 44 + src/default.c | 78 ++ src/edit.c | 38 + src/editor.c | 35 + src/exec.c | 121 ++ src/git.c | 24 + src/hash.c | 9 + src/log.c | 14 + src/ls.c | 13 + src/main.c | 84 +- src/prepend.c | 23 + src/templ.c | 73 ++ templates/bug.template | 4 + test/integration/Makefile.am | 67 + test/integration/index.js | 1 + test/integration/package-lock.json | 1166 +++++++++++++++++ test/integration/package.json | 14 + .../integration/test/add.integration.tests.js | 79 ++ .../test/branch.pattern.integration.test.js | 19 + .../integration/test/cat.integration.tests.js | 46 + .../test/edit.integration.tests.js | 43 + test/integration/test/ls.integration.tests.js | 75 ++ test/integration/test/utils.js | 25 + test/unit/Makefile.am | 100 ++ test/unit/add.tests.c | 18 + test/unit/add.tests.h | 12 + test/unit/branch.tests.c | 44 + test/unit/branch.tests.h | 14 + test/unit/cat.tests.c | 19 + test/unit/cat.tests.h | 11 + test/unit/commit.tests.c | 30 + test/unit/commit.tests.h | 11 + test/unit/default.tests.c | 26 + test/unit/default.tests.h | 14 + test/unit/edit.tests.c | 21 + test/unit/edit.tests.h | 12 + test/unit/editor.tests.c | 21 + test/unit/editor.tests.h | 12 + test/unit/hash.tests.c | 20 + test/unit/hash.tests.h | 11 + test/unit/templ.tests.c | 25 + test/unit/templ.tests.h | 11 + test/unit/test_utils.c | 29 + test/unit/test_utils.h | 12 + 68 files changed, 3319 insertions(+), 19 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile.am create mode 100644 configure.ac create mode 100644 include/add.h create mode 100644 include/branch.h create mode 100644 include/cat.h create mode 100644 include/commit.h create mode 100644 include/default.h create mode 100644 include/edit.h create mode 100644 include/editor.h create mode 100644 include/exec.h create mode 100644 include/git.h create mode 100644 include/hash.h create mode 100644 include/log.h create mode 100644 include/ls.h create mode 100644 include/opt.h create mode 100644 include/prepend.h create mode 100644 include/templ.h create mode 100644 src/add.c create mode 100644 src/branch.c create mode 100644 src/cat.c create mode 100644 src/commit.c create mode 100644 src/default.c create mode 100644 src/edit.c create mode 100644 src/editor.c create mode 100644 src/exec.c create mode 100644 src/git.c create mode 100644 src/hash.c create mode 100644 src/log.c create mode 100644 src/ls.c create mode 100644 src/prepend.c create mode 100644 src/templ.c create mode 100644 templates/bug.template create mode 100644 test/integration/Makefile.am create mode 100644 test/integration/index.js create mode 100644 test/integration/package-lock.json create mode 100644 test/integration/package.json create mode 100644 test/integration/test/add.integration.tests.js create mode 100644 test/integration/test/branch.pattern.integration.test.js create mode 100644 test/integration/test/cat.integration.tests.js create mode 100644 test/integration/test/edit.integration.tests.js create mode 100644 test/integration/test/ls.integration.tests.js create mode 100644 test/integration/test/utils.js create mode 100644 test/unit/Makefile.am create mode 100644 test/unit/add.tests.c create mode 100644 test/unit/add.tests.h create mode 100644 test/unit/branch.tests.c create mode 100644 test/unit/branch.tests.h create mode 100644 test/unit/cat.tests.c create mode 100644 test/unit/cat.tests.h create mode 100644 test/unit/commit.tests.c create mode 100644 test/unit/commit.tests.h create mode 100644 test/unit/default.tests.c create mode 100644 test/unit/default.tests.h create mode 100644 test/unit/edit.tests.c create mode 100644 test/unit/edit.tests.h create mode 100644 test/unit/editor.tests.c create mode 100644 test/unit/editor.tests.h create mode 100644 test/unit/hash.tests.c create mode 100644 test/unit/hash.tests.h create mode 100644 test/unit/templ.tests.c create mode 100644 test/unit/templ.tests.h create mode 100644 test/unit/test_utils.c create mode 100644 test/unit/test_utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2ee2e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# autoconf/automake +Makefile +*.in +*.in~ +aclocal.m4 +autom4te.cache +autoscan.log +build-aux +config.log +config.status +configure +configure.scan +include/config.h +include/stamp-h1 +src/.deps +src/.dirstamp +test/unit/.deps \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8829d17..559c4cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,77 @@ -FROM debian:latest +FROM debian:latest as base RUN apt-get update -RUN apt-get install -y autoconf automake gcc make +RUN apt-get install -y git autoconf automake gcc make vim -WORKDIR /bugs +FROM base as unit-tester + +RUN apt-get install -y gdb vim valgrind + +FROM base as core + +WORKDIR /build + +COPY Makefile.am . +COPY configure.ac . + +COPY README.md . +COPY templates templates/ + +COPY test test/ COPY include include/ -COPY src src/ \ No newline at end of file +RUN autoreconf -vif + +COPY src src/ +RUN ./configure + +RUN make +RUN make distcheck +RUN make install + +FROM unit-tester + +WORKDIR /build +COPY --from=core /build /build + +COPY --from=core /usr/local/share/git-extra/templates/ /usr/local/share/git-extra/templates/ + +RUN ./configure +RUN make check || cat test/unit/test-suite.log + +FROM node:latest as integration-tester + +COPY --from=core /usr/local/bin/git-ex /usr/local/bin/git-ex +COPY --from=core /usr/local/share/git-extra/templates/ /usr/local/share/git-extra/templates/ + +USER node +WORKDIR /home/node + +COPY --from=core --chown=node:node /build/test/integration/index.js . +COPY --from=core --chown=node:node /build/test/integration/package.json . +COPY --from=core --chown=node:node /build/test/integration/package-lock.json . + +RUN npm install + +COPY --from=core --chown=node:node /build/test/integration/test/ test/ + +RUN npm test + +FROM core as final + +WORKDIR /root +RUN git init working + +# setup test repository; Docker image meant to be a sandbox +WORKDIR /root/working + +RUN echo "test" > test.txt +RUN git config --global core.editor vim + +RUN git config --local user.name "test" +RUN git config --local user.email "test@email" + +RUN git add -A +RUN git commit -a -m "..." + +ENTRYPOINT bash \ No newline at end of file diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..6334348 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,76 @@ +templatesdir=$(datadir)/git-extra/templates + +AM_CPPFLAGS = \ + -I$(top_builddir)/include/ \ + -I$(top_srcdir)/include/ \ + -DTEMPLATE_DIR='"$(templatesdir)"' + +bin_PROGRAMS = git-ex +git_ex_SOURCES = \ + src/add.c \ + src/branch.c \ + src/cat.c \ + src/commit.c \ + src/default.c \ + src/edit.c \ + src/editor.c \ + src/exec.c \ + src/git.c \ + src/hash.c \ + src/log.c \ + src/ls.c \ + src/main.c \ + src/prepend.c \ + src/templ.c +git_ex_SOURCES += \ + include/add.h \ + include/branch.h \ + include/cat.h \ + include/commit.h \ + include/default.h \ + include/edit.h \ + include/editor.h \ + include/exec.h \ + include/git.h \ + include/hash.h \ + include/log.h \ + include/ls.h \ + include/main.h \ + include/opt.h \ + include/prepend.h \ + include/templ.h + +dist_doc_DATA = $(top_srcdir)/README.md +dist_templates_DATA = \ + $(top_srcdir)/templates/bug.template + +SUBDIRS = test/integration test/unit + +if HAVE_DOCKER +IMAGE_NAME=git-extra +CONTAINER_NAME=git-extra-test +build: + docker build -t $(IMAGE_NAME) . + +run: build + docker run -d \ + --name $(CONTAINER_NAME) \ + --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ + $(IMAGE_NAME) + +start: build + -docker run -it --rm \ + --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ + $(IMAGE_NAME) + +stop: + docker stop $(CONTAINER_NAME) + docker rm $(CONTAINER_NAME); +else +build: missing-local +run: missing-local +start: missing-local +stop: missing-local +missing-local: + @echo "docker not found" +endif \ No newline at end of file diff --git a/README.md b/README.md index 6602990..ab80c6f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# Bug Tracker +# Git Extra -Git-integrated bug tracker. \ No newline at end of file +Minimal utility for storing notes/bugs/etc. in an extra branch within git. \ No newline at end of file diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..bc6f3f6 --- /dev/null +++ b/configure.ac @@ -0,0 +1,72 @@ +AC_PREREQ([2.69]) +AC_INIT([git-extra], [0.0.0]) + +# Store build files not in main directory +AC_CONFIG_AUX_DIR([build-aux]) + +AM_INIT_AUTOMAKE([foreign subdir-objects nostdinc -Wall -Werror]) + +AC_CONFIG_SRCDIR([src/main.c]) +AC_CONFIG_HEADERS([include/config.h]) + +AC_ARG_ENABLE([memcheck], + [AS_HELP_STRING([--disable-memcheck], + [disable valgrind (enabled by default)])], + [enable_memcheck=$enableval], + [enable_memcheck=yes]) + +AC_PATH_PROG([GIT], [git]) +AM_CONDITIONAL([HAVE_GIT], [test -n "$GIT"]) +AM_COND_IF([HAVE_GIT],,[AC_MSG_ERROR([Please install git before trying to build git-extra])]) + +AC_PATH_PROG([DOCKER], [docker]) +AM_CONDITIONAL([HAVE_DOCKER], [test -n "$DOCKER"]) + +AC_PATH_PROG([NODE], [node]) +AM_CONDITIONAL([HAVE_NODE], [test -n "$NODE"]) + +AC_PATH_PROG([VALGRIND], [valgrind]) +AM_CONDITIONAL([HAVE_VALGRIND], [test -n "$VALGRIND"]) + +dnl disable memcheck if valgrind not found +if test "x$enable_memcheck" = "xyes"; then + if test -z "$VALGRIND"; then + enable_memcheck=no + fi +fi + +AC_MSG_CHECKING([if memcheck should be enabled]) +if test x$enable_memcheck != xno; then + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +AM_CONDITIONAL([ENABLE_MEMCHECK],[test x$enable_memcheck = xyes]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_INSTALL + +# Checks for libraries. + +# Checks for header files. +AC_CHECK_HEADERS([stddef.h stdlib.h string.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_OFF_T +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T + +# Checks for library functions. +AC_FUNC_FORK +AC_FUNC_MALLOC +AC_CHECK_FUNCS([dup2 getcwd memset setenv]) + +AC_CONFIG_FILES([ + Makefile + test/integration/Makefile + test/unit/Makefile +]) +AC_OUTPUT diff --git a/include/add.h b/include/add.h new file mode 100644 index 0000000..b90df92 --- /dev/null +++ b/include/add.h @@ -0,0 +1,22 @@ +#ifndef __ADD_H_ +#define __ADD_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define ADD_MESSAGE_ADDED_FILE_INDEX_HASH "added %s to index with hash: %s\n" +#define ADD_MESSAGE_ALREADY_ADDED "%s already exists\n" +#define ADD_MESSAGE_ADDED_FILE "added %s\n" +#define ADD_MESSAGE_TEMP_FILENAME "temporary file %s created\n" + +int add(char*); +int add_to_index(char*,char*); + +#endif \ No newline at end of file diff --git a/include/branch.h b/include/branch.h new file mode 100644 index 0000000..a860dbf --- /dev/null +++ b/include/branch.h @@ -0,0 +1,23 @@ +#ifndef __BRANCH_H_ +#define __BRANCH_H_ + +#include +#include + +#include +#include +#include +#include +#include + +#define BRANCH_MISSING_MESSAGE "branch `%s` missing\n" +#define BRANCH_CREATED_MESSAGE "branch `%s` created\n" +#define BRANCH_RESET_MESSAGE "switch back to branch `%s` \n" +#define BRANCH_WORKING_BRANCH_ERROR_MESSAGE "failed to get current working branch; additional output might be printed above\n" + +int branch(char*,size_t); +int branch_create(); +int branch_exists(); +int branch_reset(); + +#endif \ No newline at end of file diff --git a/include/cat.h b/include/cat.h new file mode 100644 index 0000000..ef6e682 --- /dev/null +++ b/include/cat.h @@ -0,0 +1,12 @@ +#ifndef __CAT_H_ +#define __CAT_H_ + +#include + +#include +#include +#include + +int cat(char*); + +#endif \ No newline at end of file diff --git a/include/commit.h b/include/commit.h new file mode 100644 index 0000000..f78b643 --- /dev/null +++ b/include/commit.h @@ -0,0 +1,12 @@ +#ifndef __COMMIT_H_ +#define __COMMIT_H_ + +#include + +#include +#include +#include + +int commit(char*,size_t,char*); + +#endif \ No newline at end of file diff --git a/include/default.h b/include/default.h new file mode 100644 index 0000000..485853f --- /dev/null +++ b/include/default.h @@ -0,0 +1,16 @@ +#ifndef __DEFAULT_H_ +#define __DEFAULT_H_ + +#include +#include + +#include +#include +#include + +int defaults(); +void default_branch(); +int default_editor(); +int default_path_prefix(); + +#endif \ No newline at end of file diff --git a/include/edit.h b/include/edit.h new file mode 100644 index 0000000..ee1dff2 --- /dev/null +++ b/include/edit.h @@ -0,0 +1,17 @@ +#ifndef __EDIT_H_ +#define __EDIT_H_ + +#include +#include + +#include +#include +#include +#include + +#define EDIT_MESSAGE_EDITTED_FILE "%s saved\n" +#define EDIT_MESSAGE_TEMP_FILENAME "temporary file %s created\n" + +int edit(char*); + +#endif \ No newline at end of file diff --git a/include/editor.h b/include/editor.h new file mode 100644 index 0000000..5c3fa83 --- /dev/null +++ b/include/editor.h @@ -0,0 +1,21 @@ +#ifndef __EDITOR_H_ +#define __EDITOR_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define EDITOR_MESSAGE_MISSING_EDITOR "no editor or core.editor config option set; set EDITOR env variable or pass --editor option\n" +#define EDITOR_MESSAGE_NO_CHANGE "no change detected; aborting...\n" + +int editor(char*); + +#endif \ No newline at end of file diff --git a/include/exec.h b/include/exec.h new file mode 100644 index 0000000..6cc4eeb --- /dev/null +++ b/include/exec.h @@ -0,0 +1,17 @@ +#ifndef __EXEC_H_ +#define __EXEC_H_ + +#include +#include +#include +#include +#include + +#include + +#define EXEC_INVALID_RETURN_STATUS "%s invalid return status [%d]\n" + +int exec_cmd(char**,char*,char*,size_t); +int exec_cmd_with_stds(char**); + +#endif \ No newline at end of file diff --git a/include/git.h b/include/git.h new file mode 100644 index 0000000..7b14ff3 --- /dev/null +++ b/include/git.h @@ -0,0 +1,68 @@ +#ifndef __GIT_H_ +#define __GIT_H_ + +#include +#include + +#include +#include +#include + +#define HASH_BUF_LENGTH 41 + +#define LS_TREE_MODE_LENGTH 7 +#define LS_TREE_TYPE_LENGTH 5 +#define LS_TREE_PREFIX_LENGTH LS_TREE_MODE_LENGTH+LS_TREE_TYPE_LENGTH +#define LS_TREE_ENTRY_LENGTH HASH_BUF_LENGTH+LS_TREE_PREFIX_LENGTH + +#define GIT_COMMIT_DEFAULT_MESSAGE "updated using git-ex" +#define GIT_COMMIT_DEFAULT_MESSAGE_LENGTH 21 + +#define GIT_SYMBOLIC_REF_BRANCH_NAME_TOO_LARGE "consider making branch name shorter...\n" +#define GIT_UPDATE_REF_BRANCH_NAME_TOO_LARGE "consider making branch name shorter...\n" + +enum git_exec_flags { + GIT_EXEC_FLAG_DEFAULT = 0, + GIT_EXEC_FLAG_WITH_STDS = 1 +}; + +enum rev_parse_type { + REV_PARSE_BRANCH_NAME, + REV_PARSE_HASH, + REV_PARSE_TOP_LEVEL +}; + +enum hash_object_type { + HASH_OBJECT_DEFAULT, + HASH_OBJECT_ADD_TO_INDEX +}; + +#define git_branch(output,len) git(GIT_EXEC_FLAG_DEFAULT,NULL,output,len,"git","branch","--list",global_opts.branch,NULL) +#define git_cat_file(hash) git(GIT_EXEC_FLAG_WITH_STDS,NULL,NULL,0,"git","cat-file","-p",hash,NULL) +#define git_commit_tree(tree_hash,parent_hash,output,len) git(GIT_EXEC_FLAG_DEFAULT,NULL,output,len,"git","commit-tree","-m",(NULL!=global_opts.message)?global_opts.message:GIT_COMMIT_DEFAULT_MESSAGE,tree_hash,(NULL!=parent_hash)?"-p":NULL,parent_hash,NULL) +#define git_config(option,output,len) git(GIT_EXEC_FLAG_DEFAULT,NULL,output,len,"git","config","--get",option,NULL) +#define git_hash_object(filename,type,output,len) git(GIT_EXEC_FLAG_DEFAULT,NULL,output,len,"git","hash-object",filename,(HASH_OBJECT_ADD_TO_INDEX==type)?"-w":NULL,NULL) +#define git_ls_tree(path,output,len) \ + git( \ + (NULL==path)?GIT_EXEC_FLAG_WITH_STDS:GIT_EXEC_FLAG_DEFAULT, \ + NULL, \ + output, \ + len, \ + "git", \ + "ls-tree", \ + global_opts.branch, \ + (NULL==path)?"-r":path, \ + (NULL!=path)?NULL:(GIT_LS_TREE_OPTION_NAME_ONLY==global_opts.ls_options)?"--name-only":NULL, \ + NULL \ + ) +#define git_mktree(output,len) git(GIT_EXEC_FLAG_DEFAULT,"\n",output,len,"git","mktree","--batch",NULL) +#define git_reset() git(GIT_EXEC_FLAG_DEFAULT,NULL,NULL,0,"git","reset",NULL) +#define git_rev_parse(type,output,len) git(GIT_EXEC_FLAG_DEFAULT,NULL,output,len,"git","rev-parse",(REV_PARSE_TOP_LEVEL==type)?"--show-toplevel":"--verify",(REV_PARSE_TOP_LEVEL==type)?NULL:"HEAD",(REV_PARSE_BRANCH_NAME==type)?"--abbrev-ref":NULL,NULL) +#define git_symbolic_ref(branch) git(GIT_EXEC_FLAG_DEFAULT,NULL,NULL,0,"git","symbolic-ref","HEAD",branch,NULL) +#define git_update_index(file_hash,path) git(GIT_EXEC_FLAG_DEFAULT,NULL,NULL,0,"git","update-index","--add","--cacheinfo","100644",file_hash,path,NULL) +#define git_update_ref(commit_hash,branch) git(GIT_EXEC_FLAG_DEFAULT,NULL,NULL,0,"git","update-ref",branch,commit_hash,NULL) +#define git_write_tree(output,len) git(GIT_EXEC_FLAG_DEFAULT,NULL,output,len,"git","write-tree",NULL) + +int git(enum git_exec_flags,char*,char*,size_t,...); + +#endif \ No newline at end of file diff --git a/include/hash.h b/include/hash.h new file mode 100644 index 0000000..af13a15 --- /dev/null +++ b/include/hash.h @@ -0,0 +1,11 @@ +#ifndef __HASH_H_ +#define __HASH_H_ + +#include + +#include +#include + +int hash(char*,char*,int); + +#endif \ No newline at end of file diff --git a/include/log.h b/include/log.h new file mode 100644 index 0000000..544ab75 --- /dev/null +++ b/include/log.h @@ -0,0 +1,18 @@ +#ifndef __LOG_H_ +#define __LOG_H_ + +#include +#include + +extern int verbose_flag; + +enum log_level { + LOG_LEVEL_SILENT = -1, /* suppresses all output */ + LOG_LEVEL_ERRORS = 0, /* only prints errors */ + LOG_LEVEL_DEFAULT = 1, /* normal output */ + LOG_LEVEL_VERBOSE = 2 /* logging and debugging info */ +}; + +void log_message(enum log_level,const char*,...); + +#endif \ No newline at end of file diff --git a/include/ls.h b/include/ls.h new file mode 100644 index 0000000..9b3fac1 --- /dev/null +++ b/include/ls.h @@ -0,0 +1,13 @@ +#ifndef __LS_H_ +#define __LS_H_ + +#include + +#include +#include + +#define LS_MESSAGE_TREE_LEVEL "using ls_tree option: %d\n" + +int ls(int); + +#endif \ No newline at end of file diff --git a/include/main.h b/include/main.h index faa8bd0..163cf88 100644 --- a/include/main.h +++ b/include/main.h @@ -1,10 +1,26 @@ #ifndef __MAIN_H_ -#include -#include #include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MAIN_BRANCH_SET_MESSAGE "using branch `%s`\n" +#define MAIN_EDITOR_SET_MESSAGE "using %s as editor\n" +#define MAIN_UNKNOWN_SUBCOMMAND_MESSAGE "unknown command `git ex %s`\n" +#define MAIN_WRONG_NUMBER_ARGS "wrong number of arguments\n" -extern int verbose_flag; +#define MAIN_SUBCOMMAND_ADD "add" +#define MAIN_SUBCOMMAND_CAT "cat" +#define MAIN_SUBCOMMAND_EDIT "edit" +#define MAIN_SUBCOMMAND_LS "ls" int main(int,char**); diff --git a/include/opt.h b/include/opt.h new file mode 100644 index 0000000..8df5028 --- /dev/null +++ b/include/opt.h @@ -0,0 +1,25 @@ +#ifndef __OPT_H_ +#define __OPT_H_ + +// global options +#define WORKING_BRANCH_BUF_SIZE 30 + +enum ls_tree_options { + GIT_LS_TREE_OPTION_DEFAULT = 0, + GIT_LS_TREE_OPTION_FULL = 1, + GIT_LS_TREE_OPTION_NAME_ONLY = 2 +}; + +struct options { + char *branch; + char *editor; + char *message; + char *path_prefix; + char *template; + enum ls_tree_options ls_options; + char working_branch[WORKING_BRANCH_BUF_SIZE]; +}; + +extern struct options global_opts; + +#endif \ No newline at end of file diff --git a/include/prepend.h b/include/prepend.h new file mode 100644 index 0000000..65f4a27 --- /dev/null +++ b/include/prepend.h @@ -0,0 +1,10 @@ +#ifndef __PREPEND_H_ +#define __PREPEND_H_ + +#include +#include +#include + +char *prepend(char*,char*); + +#endif \ No newline at end of file diff --git a/include/templ.h b/include/templ.h new file mode 100644 index 0000000..75fd2c9 --- /dev/null +++ b/include/templ.h @@ -0,0 +1,19 @@ +#ifndef __TEMPL_H_ +#define __TEMPL_H_ + +#include +#include +#include +#include + +#include + +#define TEMPLATE_PREFIX "/" +#define TEMPLATE_PREFIX_LENGTH 1 +#define TEMPLATE_SUFFIX ".template" +#define TEMPLATE_SUFFIX_LENGTH 9 +#define TEMPLATE_EXTRA_LENGTH TEMPLATE_PREFIX_LENGTH+TEMPLATE_SUFFIX_LENGTH + +int template(int); + +#endif \ No newline at end of file diff --git a/src/add.c b/src/add.c new file mode 100644 index 0000000..199177e --- /dev/null +++ b/src/add.c @@ -0,0 +1,70 @@ +#include + +int add(char *path) { + char ls_tree_result[LS_TREE_ENTRY_LENGTH]; + + if(NULL==path) { return EXIT_FAILURE; } + + // verify not attempting to add same file again + if(branch_exists()>0) { + ls_tree_result[0] = 0; + if(git_ls_tree(path,ls_tree_result,LS_TREE_ENTRY_LENGTH)<0) { return EXIT_FAILURE; } + if(ls_tree_result[0]!=0) { + log_message(LOG_LEVEL_ERRORS,ADD_MESSAGE_ALREADY_ADDED,path); + return EXIT_FAILURE; + } + } + + char filename[] = "/tmp/XXXXXX"; + int fd = mkstemp(filename); + if(fd<0) { return EXIT_FAILURE; } + + log_message(LOG_LEVEL_VERBOSE,ADD_MESSAGE_TEMP_FILENAME,filename); + + if(NULL!=global_opts.template) { + if(template(fd)<0) { + close(fd); + return EXIT_FAILURE; + } + } + + close(fd); + + if(add_to_index(filename,path)<0) { return EXIT_FAILURE; } + + log_message(LOG_LEVEL_DEFAULT,ADD_MESSAGE_ADDED_FILE,path); + + return EXIT_SUCCESS; +} + +int add_to_index(char *tmp_filename, char *path) { + char file_hash[HASH_BUF_LENGTH]; + int i; + + if(editor(tmp_filename)<0) { goto cleanup; } + + // add tmp file to index + if(git_hash_object(tmp_filename,HASH_OBJECT_ADD_TO_INDEX,file_hash,HASH_BUF_LENGTH)<0) { goto cleanup; } + + log_message(LOG_LEVEL_VERBOSE,ADD_MESSAGE_ADDED_FILE_INDEX_HASH,path,file_hash); + + // delete file + if(remove(tmp_filename)!=0) { return -1; } + + if(NULL!=global_opts.path_prefix) { + char *tmp = malloc(sizeof(char)*(strlen(path)+strlen(global_opts.path_prefix)+1)); + if(NULL==tmp) { return -1; } + + strcpy(tmp,global_opts.path_prefix); + strcat(tmp,path); + + path = tmp; + } + + if(commit(file_hash,HASH_BUF_LENGTH,path)<0) { return -1; } + + return 1; + cleanup: + remove(tmp_filename); + return -1; +} \ No newline at end of file diff --git a/src/branch.c b/src/branch.c new file mode 100644 index 0000000..72ccf74 --- /dev/null +++ b/src/branch.c @@ -0,0 +1,115 @@ +#include + +int branch(char *commit_hash, size_t commit_hash_len) { + char *p; + int i; + + assert(commit_hash_len==HASH_BUF_LENGTH); + + if((i = branch_exists())<=0) { + log_message(LOG_LEVEL_VERBOSE,BRANCH_MISSING_MESSAGE,global_opts.branch); + if(i<0) { return -1; } + if(branch_create()<0) { return -1; } + } + + // store current branch into global_opts.working_branch + if(git_rev_parse(REV_PARSE_BRANCH_NAME,global_opts.working_branch,WORKING_BRANCH_BUF_SIZE)<0) { + log_message(LOG_LEVEL_ERRORS,BRANCH_WORKING_BRANCH_ERROR_MESSAGE); + return -1; + } + + // git rev-parse returns branch with \n at the end + while(i++ + +int cat(char *path) { + char ls_tree_result[LS_TREE_ENTRY_LENGTH]; + + if(NULL==path) { return EXIT_FAILURE; } + + // get reference to path in branch + if(git_ls_tree(path,ls_tree_result,LS_TREE_ENTRY_LENGTH)<0) { return EXIT_FAILURE; } + + if(git_cat_file(&(ls_tree_result[LS_TREE_PREFIX_LENGTH]))<0) { return EXIT_FAILURE; } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/commit.c b/src/commit.c new file mode 100644 index 0000000..3588858 --- /dev/null +++ b/src/commit.c @@ -0,0 +1,44 @@ +#include + +int commit(char *file_hash, size_t file_hash_len, char *path) { + char prev_commit_hash[HASH_BUF_LENGTH]; + char tree_hash[HASH_BUF_LENGTH]; + char commit_hash[HASH_BUF_LENGTH]; + char *p; + int i; + + assert(file_hash_len==HASH_BUF_LENGTH); + + if(branch(prev_commit_hash,HASH_BUF_LENGTH)<0) { return -1; } + + /* + * add object to tree from cache using git update-index + * and update extra branch's ref to point at new commit: + * 1. git update-index --add --cacheinfo 100644 [FILE_HASH] [PATH] + * 2. git write-tree + * 3. git commit-tree -m "[COMMIT_MESSAGE]" [HASH from (2)] -p [PARENT_COMMIT_HASH] + * 4. git update-ref refs/heads/[EXTRA_BRANCH_NAME] [HASH from (3)] + */ + + // 1.) + if(git_update_index(file_hash,path)<0) { goto reset; } + + // 2.) + if(git_write_tree(tree_hash,HASH_BUF_LENGTH)<0) { goto reset; } + + // 3.) + if(git_commit_tree(tree_hash,prev_commit_hash,commit_hash,HASH_BUF_LENGTH)<0) { goto reset; } + + // 4.) + p = prepend("refs/heads/",global_opts.branch); + if(NULL==p) { goto reset; } + if(git_update_ref(commit_hash,p)<0) { goto reset; } + free(p); + + if(branch_reset()<0) { return -1; } + + return 1; + reset: + if(branch_reset()<0) { return -1; } + return -1; +} \ No newline at end of file diff --git a/src/default.c b/src/default.c new file mode 100644 index 0000000..ae78ef6 --- /dev/null +++ b/src/default.c @@ -0,0 +1,78 @@ +#include + +struct options global_opts = {NULL,NULL,NULL,NULL,NULL,GIT_LS_TREE_OPTION_FULL}; + +int defaults() { + default_branch(); + if(default_editor()<0) { return -1; } + if(default_path_prefix()<0) { return -1; } + + return 1; +} + +void default_branch() { + global_opts.branch = getenv("GIT_EX_BRANCH"); + if(NULL==global_opts.branch) { + global_opts.branch = "extra"; + } +} + +int default_editor() { + /* + * editor can be found in multiple places: (ordered by priority) + * -passed in via option (handled in main.c (called after this) + * -EDITOR env variable + * -git config + */ + global_opts.editor = getenv("EDITOR"); + if(NULL==global_opts.editor) { + char tmp[100]; // should be no longer than this; copying to global_opts below + + if(git_config("core.editor",tmp,100)<0) { + /* + * will error out if core.editor + * not set, proceed + */ + return 1; + } + + int i = strlen(tmp); + if(i>0) { + global_opts.editor = malloc(sizeof(char)*(i+1)); + strcpy(global_opts.editor,tmp); + } + } + + return 1; +} + +int default_path_prefix() { + /* + * generate path prefix for when not working in git root directory + */ + char *cwd,*root; + int i,cwd_len,root_len; + + cwd = getcwd(NULL,0); + if(NULL==cwd) { return -1; } + + // root will be at least as long as cwd + cwd_len = strlen(cwd)+1; + root = malloc(sizeof(char)*cwd_len); + if(NULL==root) { return -1; } + + if(git_rev_parse(REV_PARSE_TOP_LEVEL,root,cwd_len)<0) { return -1; } + + cwd_len--; + root_len = strlen(root); + + if(root_len + +int edit(char *path) { + char file_hash[HASH_BUF_LENGTH]; + fpos_t pos; + int fd; + + if(NULL==path) { return EXIT_FAILURE; } + + char filename[] = "/tmp/XXXXXX"; + fd = mkstemp(filename); + close(fd); + + log_message(LOG_LEVEL_VERBOSE,EDIT_MESSAGE_TEMP_FILENAME,filename); + + // redirect stdout to tmp file + fd = dup(fileno(stdout)); + if(NULL==freopen(filename,"w",stdout)) { + perror("freopen() failed"); + return EXIT_FAILURE; + } + + fflush(stdout); + + if(cat(path)!=EXIT_SUCCESS) { return EXIT_FAILURE; } + + // reset stdout + while((dup2(fd,fileno(stdout))==-1)&&(errno==EINTR)) {} + close(fd); + clearerr(stdout); + fsetpos(stdout,&pos); /* for C9X */ + + if(add_to_index(filename,path)<0) { return EXIT_FAILURE; } + + log_message(LOG_LEVEL_DEFAULT,EDIT_MESSAGE_EDITTED_FILE,path); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/editor.c b/src/editor.c new file mode 100644 index 0000000..54bcee3 --- /dev/null +++ b/src/editor.c @@ -0,0 +1,35 @@ +#include + +int editor(char *filename) { + char pre_edit_hash[HASH_BUF_LENGTH]; + char post_edit_hash[HASH_BUF_LENGTH]; + + if(NULL==filename) { return -1; } + + if(NULL==global_opts.editor) { + log_message(LOG_LEVEL_ERRORS,EDITOR_MESSAGE_MISSING_EDITOR); + return -1; + } + + if(hash(filename,pre_edit_hash,HASH_BUF_LENGTH)<0) { return -1; } + + wordexp_t result; + + if(wordexp(global_opts.editor,&result,WRDE_NOCMD)!=0) { goto clean; } + if(wordexp(filename,&result,WRDE_APPEND)!=0) { goto clean; } + if(exec_cmd_with_stds(result.we_wordv)<0) { goto clean; } + + wordfree(&result); + + if(hash(filename,post_edit_hash,HASH_BUF_LENGTH)<0) { return -1; } + + if(memcmp(pre_edit_hash,post_edit_hash,HASH_BUF_LENGTH)==0) { + log_message(LOG_LEVEL_DEFAULT,EDITOR_MESSAGE_NO_CHANGE); + return -1; + } + + return 1; + clean: + wordfree(&result); + return -1; +} \ No newline at end of file diff --git a/src/exec.c b/src/exec.c new file mode 100644 index 0000000..b4922bf --- /dev/null +++ b/src/exec.c @@ -0,0 +1,121 @@ +#include + +int exec_cmd(char **argv, char *input, char *output, size_t output_len) { + int parent_to_child_fds[2]; + int child_to_parent_fds[2]; + + if(pipe(parent_to_child_fds)==-1) { + perror("pipe() failed"); + return -1; + } + + if(pipe(child_to_parent_fds)==-1) { + perror("pipe() failed"); + return -1; + } + + pid_t pid = fork(); + if(pid==-1) { + perror("fork() failed"); + return -1; + } else if(pid==0) { // child + int i = 0; + while(argv[i]!=NULL) { + log_message(LOG_LEVEL_VERBOSE,"%s ",argv[i]); + i++; + } + + while((dup2(parent_to_child_fds[0],STDIN_FILENO)==-1)&&(errno==EINTR)) {} + while((dup2(child_to_parent_fds[1],STDOUT_FILENO)==-1)&&(errno==EINTR)) {} + close(parent_to_child_fds[0]); + close(parent_to_child_fds[1]); + close(child_to_parent_fds[0]); + close(child_to_parent_fds[1]); + + execvp(argv[0],argv); + perror("execvp() failed"); + exit(1); + } + + close(child_to_parent_fds[1]); + + if(NULL!=input) { + while(1) { + ssize_t count = write(parent_to_child_fds[1],input,strlen(input)+1); + if(-1==count) { + if(EINTR==errno) { + // allow for retry if interrupted + continue; + } else { + perror("read() failed"); + return -1; + } + } + break; + } + } + + close(parent_to_child_fds[0]); + close(parent_to_child_fds[1]); + + int status = 0; + if(wait(&status)==-1) { + perror("wait() failed"); + return -1; + } + + if(status!=0) { + log_message(LOG_LEVEL_VERBOSE,EXEC_INVALID_RETURN_STATUS,argv[0],status); + return -1; + } + + if(NULL!=output) { + while(1) { + ssize_t count = read(child_to_parent_fds[0],output,output_len); + if(-1==count) { + if(EINTR==errno) { + // allow for retry if interrupted + continue; + } else { + perror("read() failed"); + return -1; + } + } + break; + } + + // remove final newline from output (if exists) + strtok(output,"\n"); + // make sure that output ends w/ '\0' + output[output_len-1] = '\0'; + } + + close(child_to_parent_fds[0]); + + return 1; +} + +int exec_cmd_with_stds(char **argv) { + pid_t pid = fork(); + if(pid==-1) { + perror("fork() failed"); + return -1; + } else if(pid==0) { // child + execvp(argv[0],argv); + perror("execvp() failed"); + exit(1); + } + + int status = 0; + if(wait(&status)==-1) { + perror("wait() failed"); + return -1; + } + + if(status!=0) { + log_message(LOG_LEVEL_VERBOSE,EXEC_INVALID_RETURN_STATUS,argv[0],status); + return -1; + } + + return 1; +} \ No newline at end of file diff --git a/src/git.c b/src/git.c new file mode 100644 index 0000000..57c402b --- /dev/null +++ b/src/git.c @@ -0,0 +1,24 @@ +#include + +char *argv[10]; + +int git(enum git_exec_flags flags, char *input, char *output, size_t output_len, ...) { + va_list args; + va_start(args,output_len); + int i; + + memset(argv,0,sizeof argv); + + i = 0; + while(1) { + argv[i] = va_arg(args,char*); + if(NULL==argv[i]) { break; } + i++; + } + va_end(args); + + if(flags&GIT_EXEC_FLAG_WITH_STDS) { + return exec_cmd_with_stds(argv); + } + return exec_cmd(argv,input,output,output_len); +} \ No newline at end of file diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..663844f --- /dev/null +++ b/src/hash.c @@ -0,0 +1,9 @@ +#include + +int hash(char *filename, char *buf, int buf_len) { + assert(buf_len==HASH_BUF_LENGTH); + + if(git_hash_object(filename,HASH_OBJECT_DEFAULT,buf,HASH_BUF_LENGTH)<0) { return -1; } + + return 1; +} \ No newline at end of file diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..69303e3 --- /dev/null +++ b/src/log.c @@ -0,0 +1,14 @@ +#include + +int verbose_flag = LOG_LEVEL_DEFAULT; + +void log_message(enum log_level level, const char *format,...) { + if(verbose_flag>=0) { + if(level<=verbose_flag) { + va_list args; + va_start(args,format); + vprintf(format,args); + va_end(args); + } + } +} \ No newline at end of file diff --git a/src/ls.c b/src/ls.c new file mode 100644 index 0000000..bf676c8 --- /dev/null +++ b/src/ls.c @@ -0,0 +1,13 @@ +#include + +int ls(int ls_flag) { + log_message(LOG_LEVEL_VERBOSE,"flag: %d\n",ls_flag); + global_opts.ls_options = (GIT_LS_TREE_OPTION_FULL==ls_flag)?GIT_LS_TREE_OPTION_FULL:GIT_LS_TREE_OPTION_NAME_ONLY; + + log_message(LOG_LEVEL_VERBOSE,LS_MESSAGE_TREE_LEVEL,ls_flag); + + // get list of files based on directory relative to git root + if(git_ls_tree(NULL,NULL,0)<0) { return EXIT_FAILURE; } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/src/main.c b/src/main.c index f482170..2e57811 100644 --- a/src/main.c +++ b/src/main.c @@ -1,40 +1,104 @@ #include -int verbose_flag; +/* + * main sets the following flags + * int verbose_flag (defined in log.c) + */ + +int ls_flag = GIT_LS_TREE_OPTION_DEFAULT; static struct option long_options[] = { - {"verbose", no_argument, &verbose_flag, 1}, - {"quiet", no_argument, &verbose_flag, 0}, + {"long", no_argument, &ls_flag, GIT_LS_TREE_OPTION_FULL}, + {"verbose", no_argument, &verbose_flag, LOG_LEVEL_VERBOSE}, + {"quiet", no_argument, &verbose_flag, LOG_LEVEL_SILENT}, + {"branch", required_argument, 0, 'b'}, + {"editor", required_argument, 0, 'e'}, + {"message", required_argument, 0, 'm'}, + {"template", required_argument, 0, 't'}, {0,0,0,0} }; int main(int argc, char **argv) { + if(defaults()<0) { return EXIT_FAILURE; } + int c; while(1) { int option_index = 0; - if((c = getopt_long(argc,argv,"vq",long_options,&option_index))==-1) { break; } + if((c = getopt_long(argc,argv,"vqm:b:e:lt:",long_options,&option_index))==-1) { break; } switch(c) { case 0: if(long_options[option_index].flag!=0) { break; } - printf("option %s",long_options[option_index].name); + log_message(LOG_LEVEL_VERBOSE,"option %s",long_options[option_index].name); if(optarg) { - printf(" with arg %s",optarg); + log_message(LOG_LEVEL_VERBOSE," with arg %s",optarg); } - printf("\n"); + log_message(LOG_LEVEL_VERBOSE,"\n"); + return EXIT_FAILURE; break; - case '?': + case 'b': + global_opts.branch = optarg; + break; + case 'e': + if(NULL!=global_opts.editor) { + free(global_opts.editor); + } + global_opts.editor = optarg; + break; + case 'l': + ls_flag = GIT_LS_TREE_OPTION_FULL; + break; + case 'm': + global_opts.message = optarg; break; + case 'q': + verbose_flag = LOG_LEVEL_ERRORS; + break; + case 't': + global_opts.template = optarg; + break; + case 'v': + verbose_flag = LOG_LEVEL_VERBOSE; + break; + case '?': default: return EXIT_FAILURE; } } - printf("verbose flag set\n"); + log_message(LOG_LEVEL_VERBOSE,MAIN_BRANCH_SET_MESSAGE,global_opts.branch); + log_message(LOG_LEVEL_VERBOSE,MAIN_EDITOR_SET_MESSAGE,global_opts.editor); + + if(optind + +char *prepend(char *prefix, char *str) { + char *p; + int len; + + assert(prefix!=NULL); + + if(NULL==str) { return NULL; } + + len = strlen(str); + len += strlen(prefix); + + //sanity check len - no way branch name should be this large + if(len>100) { return NULL; } + + p = malloc(sizeof(char)*(len)); + + strcpy(p,prefix); + strcat(p,str); + + return p; +} \ No newline at end of file diff --git a/src/templ.c b/src/templ.c new file mode 100644 index 0000000..e2e62e9 --- /dev/null +++ b/src/templ.c @@ -0,0 +1,73 @@ +#include + +int template(int tmp_fd) { + /* + * copy template to temp file (if exists) + * will treat global_opts.template as a full path first, + * then search [ADD_TEMPLATE_PREFIX].template (see add.h) + */ + + if(NULL==global_opts.template) { return 0; } + + FILE *fp = fopen(global_opts.template,"r"); + if(NULL==fp) { + int len = strlen(global_opts.template); + + /* + * this is defined using autotools and + * can be modified with ./configure. See + * templatesdir varible in top-level + * Makefile.am. + */ + char templatedir[] = TEMPLATE_DIR; + len += strlen(templatedir); + + char *fullname = malloc(sizeof(char)*(len+TEMPLATE_EXTRA_LENGTH+1)); + if(NULL==fullname) { return -1; } + + strcpy(fullname,TEMPLATE_DIR); + strcat(fullname,TEMPLATE_PREFIX); + strcat(fullname,global_opts.template); + strcat(fullname,TEMPLATE_SUFFIX); + + fp = fopen(fullname,"r"); + + free(fullname); + } + + if(fp==NULL) { return 0; } + + if(0!=fseek(fp,0,SEEK_END)) { + perror("fseek() failed"); + return -1; + } + + long size = ftell(fp); + if(size<0) { + perror("ftell() failed"); + return -1; + } + + rewind(fp); + + size_t count = size; + + int template_fd = fileno(fp); + off_t off = 0; + while(count>0) { + ssize_t sent = sendfile(tmp_fd,template_fd,&off,count); + if(sent<0) { + perror("sendfile() failed"); + fclose(fp); + return -1; + } + count -= sent; + } + + if(0!=fclose(fp)) { + perror("fclose() failed"); + return -1; + } + + return 1; +} \ No newline at end of file diff --git a/templates/bug.template b/templates/bug.template new file mode 100644 index 0000000..41ef75a --- /dev/null +++ b/templates/bug.template @@ -0,0 +1,4 @@ +Title: [TITLE] +Date: [DATE] +Description: [DESCRIPTION] +Asignee: [ASIGNEE] \ No newline at end of file diff --git a/test/integration/Makefile.am b/test/integration/Makefile.am new file mode 100644 index 0000000..d48a721 --- /dev/null +++ b/test/integration/Makefile.am @@ -0,0 +1,67 @@ +EXTRA_DIST = \ + index.js \ + package.json \ + package-lock.json \ + test/utils.js \ + test/ls.integration.tests.js \ + test/cat.integration.tests.js \ + test/edit.integration.tests.js \ + test/add.integration.tests.js \ + test/branch.pattern.integration.test.js + +if HAVE_DOCKER +PACKAGE_CONTAINER_NAME=git_extra_package_init + +package_init: + docker run -d --name $(PACKAGE_CONTAINER_NAME) node:latest tail -f /dev/null + docker cp package.json $(PACKAGE_CONTAINER_NAME):/home/node/ + docker cp package-lock.json $(PACKAGE_CONTAINER_NAME):/home/node/ + +package_extract: + docker cp $(PACKAGE_CONTAINER_NAME):/home/node/package.json ./package.json + docker cp $(PACKAGE_CONTAINER_NAME):/home/node/package-lock.json ./package-lock.json + $(MAKE) -s package_stop + +package_stop: + docker stop $(PACKAGE_CONTAINER_NAME) + docker rm $(PACKAGE_CONTAINER_NAME) + +outdated: package_init + -docker exec -it -w /home/node $$(docker container ls -q -f name=$(PACKAGE_CONTAINER_NAME)) npm outdated + $(MAKE) -s package_stop + +update: package_init + -docker exec -it -w /home/node $$(docker container ls -q -f name=$(PACKAGE_CONTAINER_NAME)) /bin/bash + $(MAKE) -s package_extract + +inspect: package_init + docker exec -it $$(docker container ls -q -f name=$(PACKAGE_CONTAINER_NAME)) /bin/bash + +else +build: missing-local +run: missing-local +extract: missing-local +start: missing-local +stop: missing-local +package_init: missing-local +package_extract: missing-local +package_stop: missing-local +outdated: missing-local +update: missing-local +inspect: missing-local +missing-local: + @echo "docker not found" +endif + +if HAVE_NODE +all-local: + npm install + +check-local: + npm run test +else +all-local: missing-local +check-local: missing-local +missing-local: + @echo "node not found" +endif \ No newline at end of file diff --git a/test/integration/index.js b/test/integration/index.js new file mode 100644 index 0000000..a14ab47 --- /dev/null +++ b/test/integration/index.js @@ -0,0 +1 @@ +console.log("Run `npm run test` to run integration tests"); \ No newline at end of file diff --git a/test/integration/package-lock.json b/test/integration/package-lock.json new file mode 100644 index 0000000..bd1556a --- /dev/null +++ b/test/integration/package-lock.json @@ -0,0 +1,1166 @@ +{ + "name": "git-extra-integration-tests", + "version": "0.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "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 + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "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, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array.prototype.map": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", + "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.4" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "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 + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "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, + "requires": { + "color-name": "~1.1.4" + } + }, + "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 + }, + "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 + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "requires": { + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "dev": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "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 + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "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, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "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 + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "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 + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "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 + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, + "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, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "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 + }, + "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 + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "dev": true + }, + "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 + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "iterate-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", + "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", + "dev": true + }, + "iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dev": true, + "requires": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + } + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "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, + "requires": { + "chalk": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz", + "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==", + "dev": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.2", + "debug": "4.1.1", + "diff": "4.0.2", + "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": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "object.assign": "4.1.0", + "promise.allsettled": "1.0.2", + "serialize-javascript": "4.0.0", + "strip-json-comments": "3.0.1", + "supports-color": "7.1.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.0", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.1" + } + }, + "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 + }, + "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 + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "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 + }, + "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 + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "promise.allsettled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", + "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", + "dev": true, + "requires": { + "array.prototype.map": "^1.0.1", + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "iterate-value": "^1.0.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "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 + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "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 + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "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, + "requires": { + "is-number": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "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, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", + "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", + "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "decamelize": "^1.2.0", + "flat": "^4.1.0", + "is-plain-obj": "^1.1.0", + "yargs": "^14.2.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "yargs-parser": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + } + } +} diff --git a/test/integration/package.json b/test/integration/package.json new file mode 100644 index 0000000..268a7a1 --- /dev/null +++ b/test/integration/package.json @@ -0,0 +1,14 @@ +{ + "name": "git-extra-integration-tests", + "version": "0.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "mocha" + }, + "author": "", + "license": "UNLICENSED", + "devDependencies": { + "mocha": "^8.1.3" + } +} diff --git a/test/integration/test/add.integration.tests.js b/test/integration/test/add.integration.tests.js new file mode 100644 index 0000000..13ed888 --- /dev/null +++ b/test/integration/test/add.integration.tests.js @@ -0,0 +1,79 @@ +const assert = require('assert'); +const util = require('util'); + +const exec = util.promisify(require('child_process').exec); +const fs_stat = util.promisify(require('fs').stat); +const fs_readFile = util.promisify(require('fs').readFile); + +const {reset_env,setup_env} = require('./utils.js'); + +describe('add integration tests', () => { + + /* + * This file should contain tests to test the full add functionality, + * from parsing command line options and their interactions with adding files. + * Should also test behavior of add which depends on state. + */ + + beforeEach(async() => { + await reset_env(); + await setup_env(); + }); + + it('should succeed when adding a new file', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex add hello.txt',{cwd:"/home/node/working"}); + assert.deepStrictEqual(res,{stdout:'added hello.txt\n',stderr:''}); + }); + }); + + it('should fail when not given a file', async() => { + await assert.rejects(async() => await exec('git ex add',{cwd:"/home/node/working"})); + }); + + it('should succeed when given a valid template and verify template was applied', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex add -t bug hello.txt',{cwd:"/home/node/working"}); + assert.deepStrictEqual(res,{stdout:'added hello.txt\n',stderr:''}); + + const expected = (await fs_readFile("/usr/local/share/git-extra/templates/bug.template","utf8"))+"hello\n"; + const written = (await exec('git ex cat hello.txt',{cwd:"/home/node/working"})).stdout; + + assert.deepStrictEqual(expected,written); + }); + }); + + it(`should succeed when given a template which doesn't exist`, async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex add -t templatewhichdoesnotexist hello.txt',{cwd:"/home/node/working"}); + assert.deepStrictEqual(res,{stdout:'added hello.txt\n',stderr:''}); + + const written = (await exec('git ex cat hello.txt',{cwd:"/home/node/working"})).stdout; + assert.deepStrictEqual("hello\n",written); + }); + }); + + it('should fail when attempting to add a file that has already been added', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex add hello.txt',{cwd:"/home/node/working"}); + assert.deepStrictEqual(res,{stdout:'added hello.txt\n',stderr:''}); + + await assert.rejects(async() => await exec('git ex add hello.txt',{cwd:"/home/node/working"})); + }); + }); + + it('should delete temporary file after adding', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex add -v hello.txt',{cwd:"/home/node/working"}); + const index = res.stdout.indexOf("temporary file "); + assert(index>-1); + const tmp_filename = res.stdout.substr(index+15,11); + await assert.rejects(async() => fs_stat(tmp_filename)); + }); + }); + + after(async() => { + await reset_env(); + }); + +}); \ No newline at end of file diff --git a/test/integration/test/branch.pattern.integration.test.js b/test/integration/test/branch.pattern.integration.test.js new file mode 100644 index 0000000..d0f6e8c --- /dev/null +++ b/test/integration/test/branch.pattern.integration.test.js @@ -0,0 +1,19 @@ +const assert = require('assert'); +const util = require('util'); + +const exec = util.promisify(require('child_process').exec); + +const {reset_env,setup_env} = require('./utils.js'); + +describe('branch pattern integration test', () => { + + before(async() => { + await reset_env(); + await setup_env(); + }); + + it('should fail when given a branch pattern instead of the full branch', async() => { + await assert.rejects(async() => await exec('git ex add -b mas* hello.txt',{cwd:"/home/node/working"})); + }); + +}); \ No newline at end of file diff --git a/test/integration/test/cat.integration.tests.js b/test/integration/test/cat.integration.tests.js new file mode 100644 index 0000000..bee78ab --- /dev/null +++ b/test/integration/test/cat.integration.tests.js @@ -0,0 +1,46 @@ +const assert = require('assert'); +const util = require('util'); + +const exec = util.promisify(require('child_process').exec); + +const {reset_env,setup_env} = require('./utils.js'); + +describe('cat integration tests', () => { + + /* + * This file should contain tests to test cat functionality + */ + + beforeEach(async() => { + await reset_env(); + await setup_env(); + await exec('git ex add hello.txt',{cwd:"/home/node/working"}); + }); + + it('should cat successfully', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex cat hello.txt',{cwd:"/home/node/working"}); + assert.deepStrictEqual(res,{stdout:'hello\n',stderr:''}); + }); + }); + + it(`should fail when given a file which doesn't exist`, async() => { + await assert.rejects(async() => await exec('git ex cat -b extra hello1.txt',{cwd:"/home/node/working"})); + }); + + it(`should fail when given a file which doesn't exist on the given branch`, async() => { + await assert.rejects(async() => await exec('git ex cat -b notarealbranch hello1.txt',{cwd:"/home/node/working"})); + }); + + it('should cat successfully on the current branch', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex cat -b master test.txt',{cwd:"/home/node/working"}); + assert.deepStrictEqual(res,{stdout:'test\n',stderr:''}); + }); + }); + + after(async() => { + await reset_env(); + }); + +}); \ No newline at end of file diff --git a/test/integration/test/edit.integration.tests.js b/test/integration/test/edit.integration.tests.js new file mode 100644 index 0000000..d27b9d4 --- /dev/null +++ b/test/integration/test/edit.integration.tests.js @@ -0,0 +1,43 @@ +const assert = require('assert'); +const util = require('util'); + +const exec = util.promisify(require('child_process').exec); + +const {reset_env,setup_env} = require('./utils.js'); + +describe('edit integration tests', () => { + + /* + * This file should contain tests to test the full edit functionality, + * from parsing command line options and their interactions with editting files. + * Should also test behavior of edit which depends on state. + */ + + beforeEach(async() => { + await reset_env(); + await setup_env(); + await exec('git ex add hello.txt',{cwd:"/home/node/working"}); + }); + + it('should succeed when editting a new file', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex edit hello.txt',{cwd:"/home/node/working"}); + assert.deepStrictEqual(res,{stdout:'hello.txt saved\n',stderr:''}); + }); + }); + + it('should delete temporary file after editting', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex edit -v hello.txt',{cwd:"/home/node/working"}); + const index = res.stdout.indexOf("temporary file "); + assert(index>-1); + const tmp_filename = res.stdout.substr(index+15,11); + await assert.rejects(async() => fs_stat(tmp_filename)); + }); + }); + + after(async() => { + await reset_env(); + }); + +}); \ No newline at end of file diff --git a/test/integration/test/ls.integration.tests.js b/test/integration/test/ls.integration.tests.js new file mode 100644 index 0000000..7e5e2c0 --- /dev/null +++ b/test/integration/test/ls.integration.tests.js @@ -0,0 +1,75 @@ +const assert = require('assert'); +const util = require('util'); + +const exec = util.promisify(require('child_process').exec); + +const {reset_env,setup_env} = require('./utils.js'); + +describe('ls integration tests', () => { + + /* + * This file should contain tests to test ls functionality. + */ + + beforeEach(async() => { + await reset_env(); + await setup_env(); + await exec('git ex add hello.txt',{cwd:"/home/node/working"}); + await exec('git ex add hello1.txt',{cwd:"/home/node/working"}); + await exec('mkdir test',{cwd:"/home/node/working"}); + await exec('git ex add test/hello.txt',{cwd:"/home/node/working"}); + await exec('mkdir -p test2/test',{cwd:"/home/node/working"}); + await exec('git ex add test2/test/hello.txt',{cwd:"/home/node/working"}); + }); + + it('should ls successfully', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex ls --long',{cwd:"/home/node/working"}); + + let expected = '100644 blob ce013625030ba8dba906f756967f9e9ca394464a\thello.txt\n'; + expected += '100644 blob ce013625030ba8dba906f756967f9e9ca394464a\thello1.txt\n'; + expected += '100644 blob ce013625030ba8dba906f756967f9e9ca394464a\ttest/hello.txt\n'; + expected += '100644 blob ce013625030ba8dba906f756967f9e9ca394464a\ttest2/test/hello.txt\n'; + + assert.deepStrictEqual(res,{stdout:expected,stderr:''}); + }); + }); + + it('should ls (only names) successfully', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex ls',{cwd:"/home/node/working"}); + + let expected = 'hello.txt\n'; + expected += 'hello1.txt\n'; + expected += 'test/hello.txt\n'; + expected += 'test2/test/hello.txt\n'; + assert.deepStrictEqual(res,{stdout:expected,stderr:''}); + }); + }); + + it('should ls inside a sub-directory successfully', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex ls --long',{cwd:"/home/node/working/test"}); + + const expected = '100644 blob ce013625030ba8dba906f756967f9e9ca394464a\thello.txt\n'; + assert.deepStrictEqual(res,{stdout:expected,stderr:''}); + }); + }); + + it('should ls properly when not given any arguments', async() => { + await assert.doesNotReject(async() => { + const res = await exec('git ex',{cwd:"/home/node/working"}); + + let expected = 'hello.txt\n'; + expected += 'hello1.txt\n'; + expected += 'test/hello.txt\n'; + expected += 'test2/test/hello.txt\n'; + assert.deepStrictEqual(res,{stdout:expected,stderr:''}); + }); + }); + + after(async() => { + await reset_env(); + }); + +}); \ No newline at end of file diff --git a/test/integration/test/utils.js b/test/integration/test/utils.js new file mode 100644 index 0000000..b4ae39c --- /dev/null +++ b/test/integration/test/utils.js @@ -0,0 +1,25 @@ +const assert = require('assert'); +const util = require('util'); + +const exec = util.promisify(require('child_process').exec); + +const workdir = "working"; + +const reset_env = async() => { + await assert.doesNotReject(async() => await exec(`rm -rf ${workdir}`,{cwd:"/home/node/"})); +}; + +const setup_env = async() => { + await assert.doesNotReject(async() => await exec(`git init ${workdir}`,{cwd:"/home/node/"})); + await assert.doesNotReject(async() => await exec('git config --local user.email "test@test.com"',{cwd:"/home/node/working"})); + await assert.doesNotReject(async() => await exec('git config --local user.name "test"',{cwd:"/home/node/working"})); + await assert.doesNotReject(async() => await exec(`git config --local core.editor "sh -c 'echo \\"hello\\" >> \\"\\$0\\"'"`,{cwd:"/home/node/working"})); + await assert.doesNotReject(async() => await exec(`echo "test" > test.txt`,{cwd:"/home/node/working"})); + await assert.doesNotReject(async() => await exec(`git add -A`,{cwd:"/home/node/working"})); + await assert.doesNotReject(async() => await exec(`git commit -a -m "..."`,{cwd:"/home/node/working"})); +}; + +module.exports = { + reset_env, + setup_env +}; \ No newline at end of file diff --git a/test/unit/Makefile.am b/test/unit/Makefile.am new file mode 100644 index 0000000..6fdee03 --- /dev/null +++ b/test/unit/Makefile.am @@ -0,0 +1,100 @@ +TEMPLATE_DIR=$(builddir)/ + +AM_CPPFLAGS = \ + -I$(top_builddir)/include/ \ + -I$(top_builddir)/test/unit/ \ + -I$(top_srcdir)/include/ \ + -I$(top_srcdir)/test/unit/ \ + -DTEMPLATE_DIR='"$(TEMPLATE_DIR)"' + +EXTRA_DIST = \ + add.tests.h \ + branch.tests.h \ + cat.tests.h \ + commit.tests.h \ + default.tests.h \ + editor.tests.h \ + edit.tests.h \ + hash.tests.h \ + templ.tests.h \ + test_utils.h + +check_PROGRAMS = add.tests branch.tests cat.tests commit.tests default.tests edit.tests editor.tests hash.tests templ.tests +TESTS = $(check_PROGRAMS) + +if ENABLE_MEMCHECK +LOG_COMPILER = $(VALGRIND) +AM_LOG_FLAGS = --leak-check=full -v +endif + +common_SOURCES = test_utils.c + +TEST_SRC_DIR = $(top_srcdir)/src + +common_SOURCES += \ + $(TEST_SRC_DIR)/default.c \ + $(TEST_SRC_DIR)/exec.c \ + $(TEST_SRC_DIR)/git.c \ + $(TEST_SRC_DIR)/log.c + +add_tests_SOURCES = \ + $(common_SOURCES) \ + add.tests.c \ + $(TEST_SRC_DIR)/add.c \ + $(TEST_SRC_DIR)/branch.c \ + $(TEST_SRC_DIR)/commit.c \ + $(TEST_SRC_DIR)/editor.c \ + $(TEST_SRC_DIR)/hash.c \ + $(TEST_SRC_DIR)/prepend.c \ + $(TEST_SRC_DIR)/templ.c + +branch_tests_SOURCES = \ + $(common_SOURCES) \ + branch.tests.c \ + $(TEST_SRC_DIR)/branch.c \ + $(TEST_SRC_DIR)/prepend.c + +cat_tests_SOURCES = \ + $(common_SOURCES) \ + cat.tests.c \ + $(TEST_SRC_DIR)/cat.c + +commit_tests_SOURCES = \ + $(common_SOURCES) \ + commit.tests.c \ + $(TEST_SRC_DIR)/branch.c \ + $(TEST_SRC_DIR)/commit.c \ + $(TEST_SRC_DIR)/prepend.c + +default_tests_SOURCES = \ + $(common_SOURCES) \ + default.tests.c + +edit_tests_SOURCES = \ + $(common_SOURCES) \ + edit.tests.c \ + $(TEST_SRC_DIR)/add.c \ + $(TEST_SRC_DIR)/branch.c \ + $(TEST_SRC_DIR)/cat.c \ + $(TEST_SRC_DIR)/commit.c \ + $(TEST_SRC_DIR)/edit.c \ + $(TEST_SRC_DIR)/editor.c \ + $(TEST_SRC_DIR)/hash.c \ + $(TEST_SRC_DIR)/prepend.c \ + $(TEST_SRC_DIR)/templ.c + +editor_tests_SOURCES = \ + $(common_SOURCES) \ + editor.tests.c \ + $(TEST_SRC_DIR)/editor.c \ + $(TEST_SRC_DIR)/hash.c + +hash_tests_SOURCES = \ + $(common_SOURCES) \ + hash.tests.c \ + $(TEST_SRC_DIR)/hash.c + +templ_tests_SOURCES = \ + $(common_SOURCES) \ + templ.tests.c \ + $(TEST_SRC_DIR)/templ.c \ No newline at end of file diff --git a/test/unit/add.tests.c b/test/unit/add.tests.c new file mode 100644 index 0000000..09d0402 --- /dev/null +++ b/test/unit/add.tests.c @@ -0,0 +1,18 @@ +#include + +int main() { + setup_env(); + + add_basic_test(); + + clean_env(); +} + +void add_basic_test() { + defaults(); + + assert(EXIT_FAILURE==add(NULL)); + assert(EXIT_SUCCESS==add("test.txt")); + assert(EXIT_FAILURE==add("test.txt")); + assert(EXIT_SUCCESS==add("test2.txt")); +} \ No newline at end of file diff --git a/test/unit/add.tests.h b/test/unit/add.tests.h new file mode 100644 index 0000000..5aa9ba0 --- /dev/null +++ b/test/unit/add.tests.h @@ -0,0 +1,12 @@ +#ifndef __ADD_TESTS_H_ +#define __ADD_TESTS_H_ + +#include + +#include +#include + +int main(); +void add_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/branch.tests.c b/test/unit/branch.tests.c new file mode 100644 index 0000000..8f5d695 --- /dev/null +++ b/test/unit/branch.tests.c @@ -0,0 +1,44 @@ +#include + +int main() { + setup_env(); + + branch_create_basic_test(); + reset_env(); + + branch_exists_basic_test(); + reset_env(); + + branch_basic_test(); + branch_reset_basic_test(); + + clean_env(); +} + +void branch_basic_test() { + global_opts.branch = "extra"; + + char got[HASH_BUF_LENGTH]; + + assert(1==branch(got,HASH_BUF_LENGTH)); +} + +void branch_create_basic_test() { + global_opts.branch = NULL; + assert(branch_create()<0); + + global_opts.branch = "test"; + assert(branch_create()==1); +} + +void branch_exists_basic_test() { + global_opts.branch = "master"; + assert(branch_exists()==1); + + global_opts.branch = "notarealbranch"; + assert(branch_exists()==0); +} + +void branch_reset_basic_test() { + assert(branch_reset()==1); +} \ No newline at end of file diff --git a/test/unit/branch.tests.h b/test/unit/branch.tests.h new file mode 100644 index 0000000..04d3c14 --- /dev/null +++ b/test/unit/branch.tests.h @@ -0,0 +1,14 @@ +#ifndef __BRANCH_TESTS_H_ +#define __BRANCH_TESTS_H_ + +#include + +#include + +int main(); +void branch_basic_test(); +void branch_create_basic_test(); +void branch_exists_basic_test(); +void branch_reset_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/cat.tests.c b/test/unit/cat.tests.c new file mode 100644 index 0000000..9bd542c --- /dev/null +++ b/test/unit/cat.tests.c @@ -0,0 +1,19 @@ +#include + +int main() { + setup_env(); + + cat_basic_test(); + + clean_env(); +} + +void cat_basic_test() { + assert(EXIT_FAILURE==cat(NULL)); + + global_opts.branch = "extra"; + assert(EXIT_FAILURE==cat("test.txt")); + + global_opts.branch = "master"; + assert(EXIT_SUCCESS==cat("test.txt")); +} \ No newline at end of file diff --git a/test/unit/cat.tests.h b/test/unit/cat.tests.h new file mode 100644 index 0000000..d1dbb20 --- /dev/null +++ b/test/unit/cat.tests.h @@ -0,0 +1,11 @@ +#ifndef __CAT_TESTS_H_ +#define __CAT_TESTS_H_ + +#include + +#include + +int main(); +void cat_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/commit.tests.c b/test/unit/commit.tests.c new file mode 100644 index 0000000..a6e15c1 --- /dev/null +++ b/test/unit/commit.tests.c @@ -0,0 +1,30 @@ +#include + +int main() { + setup_env(); + + commit_basic_test(); + + clean_env(); +} + +void commit_basic_test() { + assert(0==system("echo 'test2' > test2.txt")); + + global_opts.branch = "extra"; + + char file_hash[HASH_BUF_LENGTH]; + memset(file_hash,0,HASH_BUF_LENGTH); + + char **argv; + int i; + + assert(1==git_hash_object("test2.txt",HASH_OBJECT_ADD_TO_INDEX,file_hash,HASH_BUF_LENGTH)); + + char c = file_hash[0]; + file_hash[0]++; + assert(commit(file_hash,HASH_BUF_LENGTH,"hahahah")<0); + + file_hash[0] = c; + assert(1==commit(file_hash,HASH_BUF_LENGTH,"hahahah")); +} \ No newline at end of file diff --git a/test/unit/commit.tests.h b/test/unit/commit.tests.h new file mode 100644 index 0000000..2f683cf --- /dev/null +++ b/test/unit/commit.tests.h @@ -0,0 +1,11 @@ +#ifndef __COMMIT_TESTS_H_ +#define __COMMIT_TESTS_H_ + +#include + +#include + +int main(); +void commit_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/default.tests.c b/test/unit/default.tests.c new file mode 100644 index 0000000..93e326c --- /dev/null +++ b/test/unit/default.tests.c @@ -0,0 +1,26 @@ +#include + +int main() { + setup_env(); + + default_basic_test(); + default_editor_basic_test(); + + clean_env(); +} + +void default_basic_test() { + defaults(); + + assert(0==strcmp(global_opts.branch,"extra")); + assert(0==strcmp(global_opts.editor,"sh -c 'echo \"hello\" >> \"$0\"'")); + assert(NULL==global_opts.path_prefix); +} + +void default_editor_basic_test() { + assert(0==setenv("EDITOR","hahahahah",1)); + + assert(default_editor()==1); + + assert(0==strcmp(global_opts.editor,"hahahahah")); +} \ No newline at end of file diff --git a/test/unit/default.tests.h b/test/unit/default.tests.h new file mode 100644 index 0000000..ea22576 --- /dev/null +++ b/test/unit/default.tests.h @@ -0,0 +1,14 @@ +#ifndef __DEFAULT_TESTS_H_ +#define __DEFAULT_TESTS_H_ + +#include + +#include + +#include + +int main(); +void default_basic_test(); +void default_editor_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/edit.tests.c b/test/unit/edit.tests.c new file mode 100644 index 0000000..186266c --- /dev/null +++ b/test/unit/edit.tests.c @@ -0,0 +1,21 @@ +#include + +int main() { + setup_env(); + + edit_basic_test(); + + clean_env(); +} + +void edit_basic_test() { + defaults(); + + assert(EXIT_FAILURE==edit(NULL)); + assert(EXIT_FAILURE==edit("test.txt")); + + assert(EXIT_SUCCESS==add("test.txt")); + assert(EXIT_SUCCESS==edit("test.txt")); + + assert(EXIT_FAILURE==edit("test2.txt")); +} \ No newline at end of file diff --git a/test/unit/edit.tests.h b/test/unit/edit.tests.h new file mode 100644 index 0000000..2328a80 --- /dev/null +++ b/test/unit/edit.tests.h @@ -0,0 +1,12 @@ +#ifndef __EDIT_TESTS_H_ +#define __EDIT_TESTS_H_ + +#include + +#include +#include + +int main(); +void edit_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/editor.tests.c b/test/unit/editor.tests.c new file mode 100644 index 0000000..936a125 --- /dev/null +++ b/test/unit/editor.tests.c @@ -0,0 +1,21 @@ +#include + +int main() { + setup_env(); + + editor_basic_test(); + + clean_env(); +} + +void editor_basic_test() { + defaults(); + + assert(editor(NULL)<0); + assert(editor("filewhichdoesntexist.txt")<0); + + assert(editor("test.txt")>0); + + global_opts.editor = "sh -c 'echo \"$0\"'"; + assert(editor("test.txt")<0); +} \ No newline at end of file diff --git a/test/unit/editor.tests.h b/test/unit/editor.tests.h new file mode 100644 index 0000000..c979df9 --- /dev/null +++ b/test/unit/editor.tests.h @@ -0,0 +1,12 @@ +#ifndef __EDITOR_TESTS_H_ +#define __EDITOR_TESTS_H_ + +#include + +#include +#include + +int main(); +void editor_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/hash.tests.c b/test/unit/hash.tests.c new file mode 100644 index 0000000..788e45a --- /dev/null +++ b/test/unit/hash.tests.c @@ -0,0 +1,20 @@ +#include + +int main() { + setup_env(); + + hash_basic_test(); + + clean_env(); +} + +void hash_basic_test() { + char expected[HASH_BUF_LENGTH] = "9daeafb9864cf43055ae93beb0afd6c7d144bfa4"; + char got[HASH_BUF_LENGTH]; + + memset(got,0,HASH_BUF_LENGTH); + + assert(hash("test.txt",got,HASH_BUF_LENGTH)==1); + + assert(memcmp(expected,got,HASH_BUF_LENGTH)==0); +} \ No newline at end of file diff --git a/test/unit/hash.tests.h b/test/unit/hash.tests.h new file mode 100644 index 0000000..710d281 --- /dev/null +++ b/test/unit/hash.tests.h @@ -0,0 +1,11 @@ +#ifndef __HASH_TESTS_H_ +#define __HASH_TESTS_H_ + +#include + +#include + +int main(); +void hash_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/templ.tests.c b/test/unit/templ.tests.c new file mode 100644 index 0000000..cc69a74 --- /dev/null +++ b/test/unit/templ.tests.c @@ -0,0 +1,25 @@ +#include + +int main() { + setup_env(); + + template_basic_test(); + + clean_env(); +} + +void template_basic_test() { + char filename[] = "/tmp/XXXXXX"; + int fd = mkstemp(filename); + + assert(fd>=0); + + global_opts.template = NULL; + assert(template(fd)==0); + + global_opts.template = "notavalidtemplatename"; + assert(template(fd)==0); + + global_opts.template = "test.template"; + assert(template(fd)==1); +} \ No newline at end of file diff --git a/test/unit/templ.tests.h b/test/unit/templ.tests.h new file mode 100644 index 0000000..c502065 --- /dev/null +++ b/test/unit/templ.tests.h @@ -0,0 +1,11 @@ +#ifndef __TEMPL_TESTS_H_ +#define __TEMPL_TESTS_H_ + +#include + +#include + +int main(); +void template_basic_test(); + +#endif \ No newline at end of file diff --git a/test/unit/test_utils.c b/test/unit/test_utils.c new file mode 100644 index 0000000..1cf07a8 --- /dev/null +++ b/test/unit/test_utils.c @@ -0,0 +1,29 @@ +#include + +void clean_env() { + assert(chdir("..")==0); + assert(0==system("rm -rf working")); +} + +void reset_env() { + clean_env(); + setup_env(); +} + +void setup_env() { + // remove old test directory (if previous test failed) + assert(0==system("rm -rf working")); + + assert(0==system("git init working")); + + assert(chdir("working")==0); + + assert(0==system("git config --local user.email \"test@test.com\"")); + assert(0==system("git config --local user.name \"test@test.com\"")); + assert(0==system("git config --local core.editor \"sh -c 'echo \\\"hello\\\" >> \\\"\\$0\\\"'\"")); + + assert(0==system("echo \"template\n\n\nhahaha\" > test.template")); + assert(0==system("echo \"test\" > test.txt")); + assert(0==system("git add -A")); + assert(0==system("git commit -a -m \"...\"")); +} \ No newline at end of file diff --git a/test/unit/test_utils.h b/test/unit/test_utils.h new file mode 100644 index 0000000..5011cc8 --- /dev/null +++ b/test/unit/test_utils.h @@ -0,0 +1,12 @@ +#ifndef __TEST_UTILS_H_ +#define __TEST_UTILS_H_ + +#include +#include +#include + +void clean_env(); +void setup_env(); +void reset_env(); + +#endif \ No newline at end of file -- 2.30.2