diff --git a/.assets/tree.example.png b/.assets/tree.example.png new file mode 100644 index 0000000..a54a85d Binary files /dev/null and b/.assets/tree.example.png differ diff --git a/.assets/tree_showhidden.example.png b/.assets/tree_showhidden.example.png new file mode 100644 index 0000000..2cefde3 Binary files /dev/null and b/.assets/tree_showhidden.example.png differ diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..e068d77 --- /dev/null +++ b/.clang-format @@ -0,0 +1,31 @@ +BasedOnStyle : LLVM +AccessModifierOffset : -1 +AlignAfterOpenBracket : AlwaysBreak +AlignConsecutiveAssignments : true +AlignConsecutiveDeclarations : false +AlignConsecutiveMacros : true +AlignEscapedNewlines : Left +AlignOperands : true +AlignTrailingComments : true +AllowAllArgumentsOnNextLine : true +AllowAllConstructorInitializersOnNextLine : true +AllowAllParametersOfDeclarationOnNextLine : true +AllowShortBlocksOnASingleLine : true +AllowShortCaseLabelsOnASingleLine : true +AllowShortFunctionsOnASingleLine : false +AllowShortIfStatementsOnASingleLine : Never +AllowShortLambdasOnASingleLine : All +AlwaysBreakAfterReturnType : All +AlwaysBreakAfterDefinitionReturnType : All +AlwaysBreakTemplateDeclarations : Yes +KeepEmptyLinesAtTheStartOfBlocks : false +IndentWrappedFunctionNames : false +UseTab : Never +IndentWidth : 2 +TabWidth : 2 +ContinuationIndentWidth : 4 +BinPackArguments : true +BinPackParameters : true +ColumnLimit : 80 +Language : Cpp +Standard : Auto diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b83704 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +# - - - - - - - - - - - - - configuration - - - - - - - - - - - - - # + +BIN = treee +PREFIX = /usr/local + +LIBS = -lncurses + +EXTDIR = ext +INCDIR = include +SRCDIR = src + +BINDIR = .bin +OBJDIR = .obj +DEPDIR = .dep + +SRC = disp.c \ + file.c \ + main.c + +CFLAGS = -std=gnu99 \ + -g \ + -pedantic \ + -Wall \ + -I/usr/include \ + -I${INCDIR} \ + -I${EXTDIR} +LDFLAGS = ${LIBS} + +# - - - - - - - - - - - - - - - phony - - - - - - - - - - - - - - - - # + +all: ${BINDIR}/${BIN} + +run: ${BINDIR}/${BIN} + ./${BINDIR}/${BIN} + +install: ${BINDIR}/${BIN} + cp $< ${PREFIX}/bin + +uninstall: + rm -f ${PREFIX}/bin/${BIN} + +clean: + rm -rf ./${BINDIR} ./${OBJDIR} ./${DEPDIR} + +.PHONY: all run clean + +# - - - - - - - - - - - - - - - objects - - - - - - - - - - - - - - - # + +vpath %.c ${SRCDIR} + +OBJ = $(patsubst %.c,${OBJDIR}/%.o,$(SRC)) + +${OBJ}: | ${OBJDIR} + +${OBJDIR}: + @mkdir -p $@ + +${OBJDIR}/%.o: %.c + ${CC} ${CFLAGS} -c $< -o $@ + +# - - - - - - - - - - - - - - - binary - - - - - - - - - - - - - - - # + +${BINDIR}: + @mkdir -p $@ + +${BINDIR}/${BIN}: ${BINDIR} ${OBJ} ${INC} + ${CC} -o $@ ${OBJ} ${LDFLAGS} + @chmod 755 $@ + +# - - - - - - - - - - - - - - dependencies - - - - - - - - - - - - - - # + +$(patsubst %.c,${DEPDIR}/%.d,$(SRC)): | ${DEPDIR} + +${DEPDIR}: + @mkdir -p $@ + +${DEPDIR}/%.d: %.c + @${CC} ${CFLAGS} \ + -MF"$@" -MG -M -MP \ + -MT"$@" \ + -MT"$(patsubst %.c,${DEPDIR}/%.o,${<})" \ + "$<" + +include $(patsubst %.c,${DEPDIR}/%.d,$(SRC)) diff --git a/README.md b/README.md new file mode 100644 index 0000000..e70e888 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# treee + +treee is an interactive GNU/Linux `tree` clone. + +_This project is in a very early stage of development, but basic features are complete._ + +## Purpose + +I have found `tree` to be a really great addition to development. + +This program is similar to `tree` but is built with ncurses; +the working directory is scanned every second and updates the display. + +## Example + +![example](./.assets/tree.example.png) +![showhidden.example](./.assets/tree_showhidden.example.png) + +## Usage + +```bash +git clone https://github.com/jpcx/treee +cd treee +make +sudo make install + +treee + +# for other directories: +treee ../another_dir +``` + +## Controls + +`h`, `j`, `k`, `l` for motion + +_[hold shift for fast motion]_ + +press `.` to toggle hidden file display + +pres `q` to exit + +## TODO + +- color different file types differently +- add `/` control for search +- add more command parameters + - enable/disable colors + - exclude matching +- display more directory information + +## Contribution + +Contribution is welcome! Again, this is in an early stage of development. diff --git a/ext/str.h b/ext/str.h new file mode 100644 index 0000000..a62f582 --- /dev/null +++ b/ext/str.h @@ -0,0 +1,721 @@ +#ifndef STR_STR_H_INCLUDED +#define STR_STR_H_INCLUDED +/* ///////////////////////////////////////////////////////////////////////////// +// ___ +// ,--.'|_ str: C string management header [1.0.0] +// | | :,' __ ,-. Copyright (C) 2020 Justin Collier +// .--.--. : : ' : ,' ,'/ /| +// / / '.;__,' / ' | |' | - - - - - - - - - - - - - - - - - - - +// | : /`./| | | | | ,' +// | : ;_ :__,'| : ' : / This program is free software: you can +// \ \ `. ' : |__ | | ' redistribute it and/or modify it under the +// `----. \| | '.'|; : | terms of the GNU General Public License +// / /`--' /; : ;| , ; as published by the Free Software Foundation, +// '--'. / | , / ---' either version 3 of the License, or (at your +// `--'---' ---`-' option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the internalied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// / +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . /// +///////////////////////////////////////////////////////////////////////////// */ + +/* + Synopsis + -------- + +note: all functions are static inline + + - - - ~ ~ construction ~ ~ - - - + +str str_alloc (size_t cap) : create a str with capacity cap +str str_dup (const str s) : duplicate str storage (alloc) +str str_new (const char *s) : record length, allocate, copy +str str_sub (const char *s, size_t len) : copy up to len chars from s + + - - - ~ ~ properties ~ ~ - - - + +int str_avail (const str s, size_t cap) : true if capacity is available +size_t str_cap (const str s) : retrieve the capacity +char * str_end (const str s) : pointer to the null terminator +size_t str_len (const str s) : retrieve the length +void * str_mbegin (str s) : ptr to allocated memory begin +void * str_mend (str s) : ptr to allocated memory end +size_t str_msize (const str s) : size of allocated memory +str str_mstr (void *m) : str pointer from mbegin + + - - - ~ ~ manipulation ~ ~ - - - + +// concatenate // +void str_append (str *a, const char *b) : append to a +void str_append_ (str *a, const str b) +void str_prepend (str *b, const char *a) : prepend to b +void str_prepend_ (str *b, const str a) + +// transform // +void str_emplace (str *s, const char *ins, : overwrite at idx + size_t idx) +void str_emplace_ (str *s, const str ins, + size_t idx) +void str_insert (str *s, const char *ins, : insert before s[idx] + size_t idx) +void str_insert_ (str *s, const str ins, + size_t idx) + +// format // +void str_cpad (str *s, size_t len) : center pad to reach len +void str_lpad (str *s, size_t len) : left pad to reach len +void str_rpad (str *s, size_t len) : right pad to reach len +void str_trim (str *s) : trim leading and trailing + whitespace [no realloc] + +// manage // +void str_clear (str *s) : zero len, term [no realloc] +void str_fit (str *s, size_t min_cap) : resize if capacity < min_cap +void str_grow (str *s, size_t delta) : grow string capacity by delta +void str_realloc (str *s, size_t cap) : resize string [null if needed] +void str_shrink (str *s, size_t delta) : shrink cap [null if needed] +void str_shrinkfit (str *s) : shrink to len if cap > len + + - - - ~ ~ destruction ~ ~ - - - + +void str_free (str *s) : free owned string, nullify ptr + +*/ + +#ifdef __cplusplus +extern "C" { +#include +#include +#include +#else +#include +#include +#include +#endif + +/*.----------------------------------------------------------------------------, + / detail macros */ + +/* config */ + +/* */ /* clang-format off */ + +#ifndef STR_CONFIG_NAMESPACE +/** defines the prefix of all defined C symbols [default `str`] + * if necessary, `#define STR_CONFIG_NAMESPACE [name]` before inclusion */ +# define STR_CONFIG_NAMESPACE str +#else +# define STR_DETAIL_USING_CUSTOM_NAMESPACE +#endif + +#ifndef STR_CONFIG_MALLOC +# define STR_CONFIG_MALLOC malloc +#else +# define STR_DETAIL_USING_CUSTOM_MALLOC +#endif + +#ifndef STR_CONFIG_FREE +# define STR_CONFIG_FREE free +#else +# define STR_DETAIL_USING_CUSTOM_FREE +#endif + +/* preprocessor */ + +/** Cat. */ +#define STR_DETAIL_PP_CAT(a, b) STR_DETAIL_PP_CAT_X(a, b) +#define STR_DETAIL_PP_CAT_X(a, b) a##b + +/* project setup */ + +/** refers to a namespaced library function name [eg `str_free`] */ +#define STR_DETAIL_NS_FN(name) \ + STR_DETAIL_PP_CAT(STR_CONFIG_NAMESPACE, STR_DETAIL_PP_CAT(_, name)) + +#define STR_VERSION_MAJOR 1 +#define STR_VERSION_MINOR 0 +#define STR_VERSION_PATCH 0 + +/* thanks to cxong/tinydir for this inline definition logic */ +#ifdef _MSC_VER +# define STR_FUNCTION static __inline +#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L +# define STR_FUNCTION static __inline__ +#else +# define STR_FUNCTION static inline +#endif + +/** temporary definitions for project readability; undefined at end of file */ +#ifdef STR_DETAIL_USING_CUSTOM_NAMESPACE +# define str STR_CONFIG_NAMESPACE +# define str_alloc STR_DETAIL_NS_FN(alloc) +# define str_dup STR_DETAIL_NS_FN(dup) +# define str_new STR_DETAIL_NS_FN(new) +# define str_sub STR_DETAIL_NS_FN(sub) +# define str_avail STR_DETAIL_NS_FN(avail) +# define str_cap STR_DETAIL_NS_FN(cap) +# define str_end STR_DETAIL_NS_FN(end) +# define str_len STR_DETAIL_NS_FN(len) +# define str_mbegin STR_DETAIL_NS_FN(mbegin) +# define str_mend STR_DETAIL_NS_FN(mend) +# define str_msize STR_DETAIL_NS_FN(msize) +# define str_mstr STR_DETAIL_NS_FN(mstr) +# define str_append STR_DETAIL_NS_FN(append) +# define str_append_ STR_DETAIL_NS_FN(append_) +# define str_prepend STR_DETAIL_NS_FN(prepend) +# define str_prepend_ STR_DETAIL_NS_FN(prepend_) +# define str_emplace STR_DETAIL_NS_FN(emplace) +# define str_emplace_ STR_DETAIL_NS_FN(emplace_) +# define str_insert STR_DETAIL_NS_FN(insert) +# define str_insert_ STR_DETAIL_NS_FN(insert_) +# define str_cpad STR_DETAIL_NS_FN(cpad) +# define str_lpad STR_DETAIL_NS_FN(lpad) +# define str_rpad STR_DETAIL_NS_FN(rpad) +# define str_trim STR_DETAIL_NS_FN(trim) +# define str_clear STR_DETAIL_NS_FN(clear) +# define str_fit STR_DETAIL_NS_FN(fit) +# define str_grow STR_DETAIL_NS_FN(grow) +# define str_realloc STR_DETAIL_NS_FN(realloc) +# define str_shrink STR_DETAIL_NS_FN(shrink) +# define str_shrinkfit STR_DETAIL_NS_FN(shrinkfit) +# define str_free STR_DETAIL_NS_FN(free) +#endif + +/* */ /* clang-format on */ + +/* implementation helpers */ + +/** defines the size of the memory block given a capacity */ +#define STR_DETAIL_MEMORY_SIZE(cap) \ + sizeof(size_t) * 2 + sizeof(char) * (cap + 1) + +/** shifts a char* to the left by n */ +#define STR_DETAIL_SHIFT_LEFT(cstr, len, n) \ + { \ + size_t i_; \ + for (i_ = 0; i_ < len; ++i_) \ + (cstr)[i_ - n] = (cstr)[i_]; \ + } + +/** shifts a char* to the right by n */ +#define STR_DETAIL_SHIFT_RIGHT(cstr, len, n) \ + { \ + size_t i_; \ + for (i_ = len + 1; i_ > 0; --i_) \ + (cstr)[(i_ - 1) + n] = (cstr)[i_ - 1]; \ + } + +/** assigns len to its memory location */ +#define STR_DETAIL_SET_LEN(str, len) *(((size_t *)(str)) - 1) = len + +/** assigns cap to its memory location */ +#define STR_DETAIL_SET_CAP(str, cap) *(((size_t *)(str)) - 2) = cap + +/*.----------------------------------------------------------------------------, + / type alias */ + +typedef char *str; + +/*.----------------------------------------------------------------------------, + / declarations */ + +/* construction */ + +/** create a str with capacity cap */ +STR_FUNCTION str +str_alloc(size_t cap); +/** duplicate str storage (alloc) */ +STR_FUNCTION str +str_dup(const str s); +/** record length, allocate, copy */ +STR_FUNCTION str +str_new(const char *s); +/** copy up to len chars from s */ +STR_FUNCTION str +str_sub(const char *s, size_t len); + +/* properties */ + +/** true if capacity is available */ +STR_FUNCTION int +str_avail(const str s, size_t cap); +/** retrieve the capacity */ +STR_FUNCTION size_t +str_cap(const str s); +/** pointer to the null terminator */ +STR_FUNCTION char * +str_end(const str s); +/** retrieve the length */ +STR_FUNCTION size_t +str_len(const str s); +/** ptr to allocated memory begin */ +STR_FUNCTION void * +str_mbegin(str s); +/** ptr to allocated memory end */ +STR_FUNCTION void * +str_mend(str s); +/** size of allocated memory */ +STR_FUNCTION size_t +str_msize(const str s); +/** str pointer from mbegin */ +STR_FUNCTION str +str_mstr(void *m); + +/* manipulation */ + +/** append chars to a */ +STR_FUNCTION void +str_append(str *a, const char *b); +/** append str to a */ +STR_FUNCTION void +str_append_(str *a, const str b); +/** prepend chars to b */ +STR_FUNCTION void +str_prepend(str *b, const char *a); +/** prepend str to b */ +STR_FUNCTION void +str_prepend_(str *b, const str a); + +/** overwrite chars in a string */ +STR_FUNCTION void +str_emplace(str *a, const char *b, size_t idx); +/** overwrite chars in a string */ +STR_FUNCTION void +str_emplace_(str *a, const str b, size_t idx); +/** insert chars before s[idx] */ +STR_FUNCTION void +str_insert(str *s, const char *ins, size_t idx); +/** insert str before s[idx] */ +STR_FUNCTION void +str_insert_(str *s, const str ins, size_t idx); + +/** center pad to reach len */ +STR_FUNCTION void +str_cpad(str *s, size_t len); +/** left pad to reach len */ +STR_FUNCTION void +str_lpad(str *s, size_t len); +/** right pad to reach len */ +STR_FUNCTION void +str_rpad(str *s, size_t len); +/** trim leading and trailing whitespace */ +STR_FUNCTION void +str_trim(str *s); + +/** zero len, term [no realloc] */ +STR_FUNCTION void +str_clear(str *a); +/** resize if capacity < min_cap */ +STR_FUNCTION void +str_fit(str *a, size_t min_cap); +/** grow string capacity by delta */ +STR_FUNCTION void +str_grow(str *a, size_t delta); +/** resize string [null if needed] */ +STR_FUNCTION void +str_realloc(str *s, size_t cap); +/** shrink cap [null if needed] */ +STR_FUNCTION void +str_shrink(str *a, size_t delta); +/** resize to len */ +STR_FUNCTION void +str_shrinkfit(str *s); + +/* destruction */ + +/** free owned string, nullify ptr */ +STR_FUNCTION void +str_free(str *s); + +/*.----------------------------------------------------------------------------, + / definitions */ + +/* construction */ + +/** create a str with capacity cap */ +STR_FUNCTION str +str_alloc(size_t cap) { + void *o = STR_CONFIG_MALLOC(STR_DETAIL_MEMORY_SIZE(cap)); + if (o == NULL) + return NULL; + str s = str_mstr(o); + STR_DETAIL_SET_CAP(s, cap); + STR_DETAIL_SET_LEN(s, 0); + s[0] = '\0'; + return s; +} + +/** duplicate str storage (alloc) */ +STR_FUNCTION str +str_dup(const str s) { + void *o = STR_CONFIG_MALLOC(str_msize(s)); + if (o == NULL) + return NULL; + memcpy(o, str_mbegin(s), str_msize(s)); + return str_mstr(o); +} + +/** record length, allocate, copy */ +STR_FUNCTION str +str_new(const char *s) { + size_t len = strlen(s); + str v = str_alloc(len); + if (v == NULL) + return NULL; + strcpy(v, s); + STR_DETAIL_SET_LEN(v, len); + return v; +} + +/** copy up to len chars from s */ +STR_FUNCTION str +str_sub(const char *s, size_t len) { + str v = str_alloc(len); + if (v == NULL) + return NULL; + size_t i; + for (i = 0; i < len && s[i] != '\0'; ++i) + v[i] = s[i]; + v[i] = '\0'; + STR_DETAIL_SET_LEN(v, i); + return v; +} + +/* properties */ + +/** true if capacity is available */ +STR_FUNCTION int +str_avail(const str s, size_t cap) { + return str_cap(s) >= cap; +} + +/** retrieve the capacity */ +STR_FUNCTION size_t +str_cap(const str s) { + return *(((size_t *)s) - 2); +} + +/** pointer to the null terminator */ +STR_FUNCTION char * +str_end(const str s) { + return &s[str_len(s)]; +} + +/** retrieve the length */ +STR_FUNCTION size_t +str_len(const str s) { + return *(((size_t *)s) - 1); +} + +/** ptr to allocated memory begin */ +STR_FUNCTION void * +str_mbegin(const str s) { + return (size_t *)s - 2; +} + +/** ptr to allocated memory end */ +STR_FUNCTION void * +str_mend(const str s) { + return s + str_cap(s); +} + +/** size of allocated memory */ +STR_FUNCTION size_t +str_msize(const str s) { + return STR_DETAIL_MEMORY_SIZE(str_cap(s)); +} + +/** str pointer from mbegin */ +STR_FUNCTION str +str_mstr(void *m) { + return (str)((size_t *)(m) + 2); +} + +/* manipulation */ + +/** append chars to a */ +STR_FUNCTION void +str_append(str *a, const char *b) { + size_t alen = str_len(*a); + size_t blen = strlen(b); + str_fit(a, alen + blen); + strcpy(&(*a)[alen], b); + STR_DETAIL_SET_LEN(*a, alen + blen); +} + +/** append str to a */ +STR_FUNCTION void +str_append_(str *a, const str b) { + size_t alen = str_len(*a); + size_t blen = str_len(b); + str_fit(a, alen + blen); + strcpy(&(*a)[alen], b); + STR_DETAIL_SET_LEN(*a, alen + blen); +} + +/** prepend chars to b */ +STR_FUNCTION void +str_prepend(str *b, const char *a) { + size_t blen = str_len(*b); + size_t alen = strlen(a); + str_fit(b, alen + blen); + STR_DETAIL_SHIFT_RIGHT(*b, blen, alen); + memcpy((*b), a, alen); + STR_DETAIL_SET_LEN(*b, alen + blen); +} + +/** prepend str to b */ +STR_FUNCTION void +str_prepend_(str *b, const str a) { + size_t blen = str_len(*b); + size_t alen = str_len(a); + str_fit(b, alen + blen); + STR_DETAIL_SHIFT_RIGHT(*b, blen, alen); + memcpy((*b), a, alen); + STR_DETAIL_SET_LEN(*b, alen + blen); +} + +/** overwrite chars in a string */ +STR_FUNCTION void +str_emplace(str *s, const char *ins, size_t idx) { + size_t inslen = strlen(ins); + str_fit(s, idx + inslen); + memcpy(&(*s)[idx], ins, inslen); + if (idx + inslen > str_len(*s)) { + (*s)[idx + inslen] = '\0'; + STR_DETAIL_SET_LEN(*s, idx + inslen); + } +} + +/** overwrite chars in a string */ +STR_FUNCTION void +str_emplace_(str *s, const str ins, size_t idx) { + size_t inslen = str_len(ins); + str_fit(s, idx + inslen); + memcpy(&(*s)[idx], ins, inslen); + if (idx + inslen > str_len(*s)) { + (*s)[idx + inslen] = '\0'; + STR_DETAIL_SET_LEN(*s, idx + inslen); + } +} + +/** insert chars before s[idx] */ +STR_FUNCTION void +str_insert(str *s, const char *ins, size_t idx) { + size_t slen = str_len(*s); + size_t inslen = strlen(ins); + str_fit(s, slen + inslen); + STR_DETAIL_SHIFT_RIGHT(&(*s)[idx], slen - idx, inslen); + memcpy(&(*s)[idx], ins, inslen); + STR_DETAIL_SET_LEN(*s, slen + inslen); +} + +/** insert str before s[idx] */ +STR_FUNCTION void +str_insert_(str *s, const str ins, size_t idx) { + size_t slen = str_len(*s); + size_t inslen = str_len(ins); + str_fit(s, slen + inslen); + STR_DETAIL_SHIFT_RIGHT(&(*s)[idx], slen - idx, inslen); + memcpy(&(*s)[idx], ins, inslen); + STR_DETAIL_SET_LEN(*s, slen + inslen); +} + +/** center pad to reach len */ +STR_FUNCTION void +str_cpad(str *s, size_t len) { + size_t slen = str_len(*s); + if (slen < len) { + str_fit(s, len); + size_t mid = (len - slen) / 2; + STR_DETAIL_SHIFT_RIGHT(*s, slen, mid); + memset(*s, ' ', mid); + memset(&(*s)[mid + slen], ' ', len - mid); + (*s)[len] = '\0'; + STR_DETAIL_SET_LEN(*s, len); + } +} + +/** left pad to reach len */ +STR_FUNCTION void +str_lpad(str *s, size_t len) { + size_t slen = str_len(*s); + if (slen < len) { + str_fit(s, len); + STR_DETAIL_SHIFT_RIGHT(*s, slen, len - slen); + memset(*s, ' ', len - slen); + STR_DETAIL_SET_LEN(*s, len); + } +} + +/** right pad to reach len */ +STR_FUNCTION void +str_rpad(str *s, size_t len) { + size_t slen = str_len(*s); + if (slen < len) { + str_fit(s, len); + memset(&(*s)[slen], ' ', len - slen); + (*s)[len] = '\0'; + STR_DETAIL_SET_LEN(*s, len); + } +} + +/** trim leading and trailing whitespace */ +STR_FUNCTION void +str_trim(str *s) { + size_t slen = str_len(*s); + size_t beg; + size_t end; + for (beg = 0; beg < slen && isspace((*s)[beg]); ++beg) + ; + for (end = slen; end > 0 && isspace((*s)[end - 1]); --end) + ; + + if (beg > 0) + STR_DETAIL_SHIFT_LEFT(&(*s)[beg], end - beg, beg); + + if (beg > 0 || end < slen) { + (*s)[end - beg] = '\0'; + STR_DETAIL_SET_LEN(*s, end - beg); + } +} + +/** zero len, term [no realloc] */ +STR_FUNCTION void +str_clear(str *s) { + (*s)[0] = '\0'; + STR_DETAIL_SET_LEN(*s, 0); +} + +/** resize if capacity < min_cap */ +STR_FUNCTION void +str_fit(str *a, size_t min_cap) { + if (str_cap(*a) < min_cap) + str_realloc(a, min_cap); +} + +/** grow string capacity by delta */ +STR_FUNCTION void +str_grow(str *s, size_t delta) { + str_realloc(s, str_cap(*s) + delta); +} + +/** resize string [reallocates and null terminates] */ +STR_FUNCTION void +str_realloc(str *s, size_t cap) { + void *v = STR_CONFIG_MALLOC(STR_DETAIL_MEMORY_SIZE(cap)); + + if (v == NULL) + return; + + memcpy(v, str_mbegin(*s), STR_DETAIL_MEMORY_SIZE(cap)); + str_free(s); + + *s = str_mstr(v); + STR_DETAIL_SET_CAP(*s, cap); + + if (cap < str_len(*s)) { + (*s)[cap] = '\0'; + STR_DETAIL_SET_LEN(*s, cap); + } +} + +/** shrink cap [null if needed] */ +STR_FUNCTION void +str_shrink(str *s, size_t delta) { + str_realloc(s, str_cap(*s) - delta); +} + +/** shrink to len if cap > len */ +STR_FUNCTION void +str_shrinkfit(str *s) { + size_t slen = str_len(*s); + if (slen < str_cap(*s)) + str_realloc(s, slen); +} + +/* destruction */ + +/** free owned string, nullify ptr */ +STR_FUNCTION void +str_free(str *s) { + STR_CONFIG_FREE(str_mbegin(*s)); + *s = NULL; +} + +/* */ /* clang-format off */ + +#undef STR_DETAIL_PP_CAT +#undef STR_DETAIL_PP_CAT_X +#undef STR_DETAIL_NS_FN +#undef STR_FUNCTION + +#ifdef STR_DETAIL_USING_CUSTOM_NAMESPACE +# undef str +# undef str_alloc +# undef str_dup +# undef str_new +# undef str_sub +# undef str_avail +# undef str_cap +# undef str_end +# undef str_len +# undef str_mbegin +# undef str_mend +# undef str_msize +# undef str_mstr +# undef str_append +# undef str_append_ +# undef str_prepend +# undef str_prepend_ +# undef str_emplace +# undef str_emplace_ +# undef str_insert +# undef str_insert_ +# undef str_cpad +# undef str_lpad +# undef str_rpad +# undef str_trim +# undef str_clear +# undef str_fit +# undef str_grow +# undef str_realloc +# undef str_shrink +# undef str_shrinkfit +# undef str_free +#endif + +#ifdef STR_DETAIL_USING_CUSTOM_NAMESPACE +# undef STR_DETAIL_USING_CUSTOM_NAMESPACE +#else +# undef STR_CONFIG_NAMESPACE +#endif + +#ifdef STR_DETAIL_USING_CUSTOM_MALLOC +# undef STR_DETAIL_USING_CUSTOM_MALLOC +#else +# undef STR_CONFIG_MALLOC +#endif + +#ifdef STR_DETAIL_USING_CUSTOM_FREE +# undef STR_DETAIL_USING_CUSTOM_FREE +#else +# undef STR_CONFIG_FREE +#endif + +#undef STR_DETAIL_MEMORY_SIZE +#undef STR_DETAIL_SHIFT_RIGHT +#undef STR_DETAIL_SHIFT_LEFT +#undef STR_DETAIL_SET_LEN +#undef STR_DETAIL_SET_CAP +/* */ /* clang-format on */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/ext/tinydir.h b/ext/tinydir.h new file mode 100644 index 0000000..e08eb84 --- /dev/null +++ b/ext/tinydir.h @@ -0,0 +1,831 @@ +/* +Copyright (c) 2013-2019, tinydir authors: +- Cong Xu +- Lautis Sun +- Baudouin Feildel +- Andargor +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef TINYDIR_H +#define TINYDIR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if ((defined _UNICODE) && !(defined UNICODE)) +#define UNICODE +#endif + +#if ((defined UNICODE) && !(defined _UNICODE)) +#define _UNICODE +#endif + +#include +#include +#include +#ifdef _MSC_VER +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include +# pragma warning(push) +# pragma warning (disable : 4996) +#else +# include +# include +# include +# include +#endif +#ifdef __MINGW32__ +# include +#endif + + +/* types */ + +/* Windows UNICODE wide character support */ +#if defined _MSC_VER || defined __MINGW32__ +# define _tinydir_char_t TCHAR +# define TINYDIR_STRING(s) _TEXT(s) +# define _tinydir_strlen _tcslen +# define _tinydir_strcpy _tcscpy +# define _tinydir_strcat _tcscat +# define _tinydir_strcmp _tcscmp +# define _tinydir_strrchr _tcsrchr +# define _tinydir_strncmp _tcsncmp +#else +# define _tinydir_char_t char +# define TINYDIR_STRING(s) s +# define _tinydir_strlen strlen +# define _tinydir_strcpy strcpy +# define _tinydir_strcat strcat +# define _tinydir_strcmp strcmp +# define _tinydir_strrchr strrchr +# define _tinydir_strncmp strncmp +#endif + +#if (defined _MSC_VER || defined __MINGW32__) +# include +# define _TINYDIR_PATH_MAX MAX_PATH +#elif defined __linux__ +# include +# ifdef PATH_MAX +# define _TINYDIR_PATH_MAX PATH_MAX +# endif +#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +# include +# if defined(BSD) +# include +# ifdef PATH_MAX +# define _TINYDIR_PATH_MAX PATH_MAX +# endif +# endif +#endif + +#ifndef _TINYDIR_PATH_MAX +#define _TINYDIR_PATH_MAX 4096 +#endif + +#ifdef _MSC_VER +/* extra chars for the "\\*" mask */ +# define _TINYDIR_PATH_EXTRA 2 +#else +# define _TINYDIR_PATH_EXTRA 0 +#endif + +#define _TINYDIR_FILENAME_MAX 256 + +#if (defined _MSC_VER || defined __MINGW32__) +#define _TINYDIR_DRIVE_MAX 3 +#endif + +#ifdef _MSC_VER +# define _TINYDIR_FUNC static __inline +#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L +# define _TINYDIR_FUNC static __inline__ +#else +# define _TINYDIR_FUNC static inline +#endif + +/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */ +#ifdef TINYDIR_USE_READDIR_R + +/* readdir_r is a POSIX-only function, and may not be available under various + * environments/settings, e.g. MinGW. Use readdir fallback */ +#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\ + _POSIX_SOURCE +# define _TINYDIR_HAS_READDIR_R +#endif +#if _POSIX_C_SOURCE >= 200112L +# define _TINYDIR_HAS_FPATHCONF +# include +#endif +#if _BSD_SOURCE || _SVID_SOURCE || \ + (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) +# define _TINYDIR_HAS_DIRFD +# include +#endif +#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\ + defined _PC_NAME_MAX +# define _TINYDIR_USE_FPATHCONF +#endif +#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\ + !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX) +# define _TINYDIR_USE_READDIR +#endif + +/* Use readdir by default */ +#else +# define _TINYDIR_USE_READDIR +#endif + +/* MINGW32 has two versions of dirent, ASCII and UNICODE*/ +#ifndef _MSC_VER +#if (defined __MINGW32__) && (defined _UNICODE) +#define _TINYDIR_DIR _WDIR +#define _tinydir_dirent _wdirent +#define _tinydir_opendir _wopendir +#define _tinydir_readdir _wreaddir +#define _tinydir_closedir _wclosedir +#else +#define _TINYDIR_DIR DIR +#define _tinydir_dirent dirent +#define _tinydir_opendir opendir +#define _tinydir_readdir readdir +#define _tinydir_closedir closedir +#endif +#endif + +/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */ +#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE) +#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE) +#else +#error "Either define both alloc and free or none of them!" +#endif + +#if !defined(_TINYDIR_MALLOC) + #define _TINYDIR_MALLOC(_size) malloc(_size) + #define _TINYDIR_FREE(_ptr) free(_ptr) +#endif /* !defined(_TINYDIR_MALLOC) */ + +typedef struct tinydir_file +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + _tinydir_char_t name[_TINYDIR_FILENAME_MAX]; + _tinydir_char_t *extension; + int is_dir; + int is_reg; + +#ifndef _MSC_VER +#ifdef __MINGW32__ + struct _stat _s; +#else + struct stat _s; +#endif +#endif +} tinydir_file; + +typedef struct tinydir_dir +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + int has_next; + size_t n_files; + + tinydir_file *_files; +#ifdef _MSC_VER + HANDLE _h; + WIN32_FIND_DATA _f; +#else + _TINYDIR_DIR *_d; + struct _tinydir_dirent *_e; +#ifndef _TINYDIR_USE_READDIR + struct _tinydir_dirent *_ep; +#endif +#endif +} tinydir_dir; + + +/* declarations */ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path); +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path); +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir); + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir); +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); + +_TINYDIR_FUNC +int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path); +_TINYDIR_FUNC +void _tinydir_get_ext(tinydir_file *file); +_TINYDIR_FUNC +int _tinydir_file_cmp(const void *a, const void *b); +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR +_TINYDIR_FUNC +size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp); +#endif +#endif + + +/* definitions*/ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path) +{ +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR + int error; + int size; /* using int size */ +#endif +#else + _tinydir_char_t path_buf[_TINYDIR_PATH_MAX]; +#endif + _tinydir_char_t *pathp; + + if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* initialise dir */ + dir->_files = NULL; +#ifdef _MSC_VER + dir->_h = INVALID_HANDLE_VALUE; +#else + dir->_d = NULL; +#ifndef _TINYDIR_USE_READDIR + dir->_ep = NULL; +#endif +#endif + tinydir_close(dir); + + _tinydir_strcpy(dir->path, path); + /* Remove trailing slashes */ + pathp = &dir->path[_tinydir_strlen(dir->path) - 1]; + while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/'))) + { + *pathp = TINYDIR_STRING('\0'); + pathp++; + } +#ifdef _MSC_VER + _tinydir_strcpy(path_buf, dir->path); + _tinydir_strcat(path_buf, TINYDIR_STRING("\\*")); +#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) + dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0); +#else + dir->_h = FindFirstFile(path_buf, &dir->_f); +#endif + if (dir->_h == INVALID_HANDLE_VALUE) + { + errno = ENOENT; +#else + dir->_d = _tinydir_opendir(path); + if (dir->_d == NULL) + { +#endif + goto bail; + } + + /* read first file */ + dir->has_next = 1; +#ifndef _MSC_VER +#ifdef _TINYDIR_USE_READDIR + dir->_e = _tinydir_readdir(dir->_d); +#else + /* allocate dirent buffer for readdir_r */ + size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */ + if (size == -1) return -1; + dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size); + if (dir->_ep == NULL) return -1; + + error = readdir_r(dir->_d, dir->_ep, &dir->_e); + if (error != 0) return -1; +#endif + if (dir->_e == NULL) + { + dir->has_next = 0; + } +#endif + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path) +{ + /* Count the number of files first, to pre-allocate the files array */ + size_t n_files = 0; + if (tinydir_open(dir, path) == -1) + { + return -1; + } + while (dir->has_next) + { + n_files++; + if (tinydir_next(dir) == -1) + { + goto bail; + } + } + tinydir_close(dir); + + if (n_files == 0 || tinydir_open(dir, path) == -1) + { + return -1; + } + + dir->n_files = 0; + dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files); + if (dir->_files == NULL) + { + goto bail; + } + while (dir->has_next) + { + tinydir_file *p_file; + dir->n_files++; + + p_file = &dir->_files[dir->n_files - 1]; + if (tinydir_readfile(dir, p_file) == -1) + { + goto bail; + } + + if (tinydir_next(dir) == -1) + { + goto bail; + } + + /* Just in case the number of files has changed between the first and + second reads, terminate without writing into unallocated memory */ + if (dir->n_files == n_files) + { + break; + } + } + + qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir) +{ + if (dir == NULL) + { + return; + } + + memset(dir->path, 0, sizeof(dir->path)); + dir->has_next = 0; + dir->n_files = 0; + _TINYDIR_FREE(dir->_files); + dir->_files = NULL; +#ifdef _MSC_VER + if (dir->_h != INVALID_HANDLE_VALUE) + { + FindClose(dir->_h); + } + dir->_h = INVALID_HANDLE_VALUE; +#else + if (dir->_d) + { + _tinydir_closedir(dir->_d); + } + dir->_d = NULL; + dir->_e = NULL; +#ifndef _TINYDIR_USE_READDIR + _TINYDIR_FREE(dir->_ep); + dir->_ep = NULL; +#endif +#endif +} + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir) +{ + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (!dir->has_next) + { + errno = ENOENT; + return -1; + } + +#ifdef _MSC_VER + if (FindNextFile(dir->_h, &dir->_f) == 0) +#else +#ifdef _TINYDIR_USE_READDIR + dir->_e = _tinydir_readdir(dir->_d); +#else + if (dir->_ep == NULL) + { + return -1; + } + if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0) + { + return -1; + } +#endif + if (dir->_e == NULL) +#endif + { + dir->has_next = 0; +#ifdef _MSC_VER + if (GetLastError() != ERROR_SUCCESS && + GetLastError() != ERROR_NO_MORE_FILES) + { + tinydir_close(dir); + errno = EIO; + return -1; + } +#endif + } + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) +{ + const _tinydir_char_t *filename; + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } +#ifdef _MSC_VER + if (dir->_h == INVALID_HANDLE_VALUE) +#else + if (dir->_e == NULL) +#endif + { + errno = ENOENT; + return -1; + } + filename = +#ifdef _MSC_VER + dir->_f.cFileName; +#else + dir->_e->d_name; +#endif + if (_tinydir_strlen(dir->path) + + _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >= + _TINYDIR_PATH_MAX) + { + /* the path for the file will be too long */ + errno = ENAMETOOLONG; + return -1; + } + if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + _tinydir_strcpy(file->path, dir->path); + if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0) + _tinydir_strcat(file->path, TINYDIR_STRING("/")); + _tinydir_strcpy(file->name, filename); + _tinydir_strcat(file->path, filename); +#ifndef _MSC_VER +#ifdef __MINGW32__ + if (_tstat( +#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \ + || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \ + || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) + if (lstat( +#else + if (stat( +#endif + file->path, &file->_s) == -1) + { + return -1; + } +#endif + _tinydir_get_ext(file); + + file->is_dir = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); +#else + S_ISDIR(file->_s.st_mode); +#endif + file->is_reg = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || + ( + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && +#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && +#endif +#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && +#endif + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); +#else + S_ISREG(file->_s.st_mode); +#endif + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) +{ + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files) + { + errno = ENOENT; + return -1; + } + + memcpy(file, &dir->_files[i], sizeof(tinydir_file)); + _tinydir_get_ext(file); + + return 0; +} + +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files || !dir->_files[i].is_dir) + { + errno = ENOENT; + return -1; + } + + _tinydir_strcpy(path, dir->_files[i].path); + tinydir_close(dir); + if (tinydir_open_sorted(dir, path) == -1) + { + return -1; + } + + return 0; +} + +/* Open a single file given its path */ +_TINYDIR_FUNC +int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) +{ + tinydir_dir dir; + int result = 0; + int found = 0; + _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX]; + _tinydir_char_t *dir_name; + _tinydir_char_t *base_name; +#if (defined _MSC_VER || defined __MINGW32__) + _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; +#endif + + if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* Get the parent path */ +#if (defined _MSC_VER || defined __MINGW32__) +#if ((defined _MSC_VER) && (_MSC_VER >= 1400)) + errno = _tsplitpath_s( + path, + drive_buf, _TINYDIR_DRIVE_MAX, + dir_name_buf, _TINYDIR_FILENAME_MAX, + file_name_buf, _TINYDIR_FILENAME_MAX, + ext_buf, _TINYDIR_FILENAME_MAX); +#else + _tsplitpath( + path, + drive_buf, + dir_name_buf, + file_name_buf, + ext_buf); +#endif + + if (errno) + { + return -1; + } + +/* _splitpath_s not work fine with only filename and widechar support */ +#ifdef _UNICODE + if (drive_buf[0] == L'\xFEFE') + drive_buf[0] = '\0'; + if (dir_name_buf[0] == L'\xFEFE') + dir_name_buf[0] = '\0'; +#endif + + /* Emulate the behavior of dirname by returning "." for dir name if it's + empty */ + if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') + { + _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); + } + /* Concatenate the drive letter and dir name to form full dir name */ + _tinydir_strcat(drive_buf, dir_name_buf); + dir_name = drive_buf; + /* Concatenate the file name and extension to form base name */ + _tinydir_strcat(file_name_buf, ext_buf); + base_name = file_name_buf; +#else + _tinydir_strcpy(dir_name_buf, path); + dir_name = dirname(dir_name_buf); + _tinydir_strcpy(file_name_buf, path); + base_name = basename(file_name_buf); +#endif + + /* Special case: if the path is a root dir, open the parent dir as the file */ +#if (defined _MSC_VER || defined __MINGW32__) + if (_tinydir_strlen(base_name) == 0) +#else + if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) +#endif + { + memset(file, 0, sizeof * file); + file->is_dir = 1; + file->is_reg = 0; + _tinydir_strcpy(file->path, dir_name); + file->extension = file->path + _tinydir_strlen(file->path); + return 0; + } + + /* Open the parent directory */ + if (tinydir_open(&dir, dir_name) == -1) + { + return -1; + } + + /* Read through the parent directory and look for the file */ + while (dir.has_next) + { + if (tinydir_readfile(&dir, file) == -1) + { + result = -1; + goto bail; + } + if (_tinydir_strcmp(file->name, base_name) == 0) + { + /* File found */ + found = 1; + break; + } + tinydir_next(&dir); + } + if (!found) + { + result = -1; + errno = ENOENT; + } + +bail: + tinydir_close(&dir); + return result; +} + +_TINYDIR_FUNC +void _tinydir_get_ext(tinydir_file *file) +{ + _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.')); + if (period == NULL) + { + file->extension = &(file->name[_tinydir_strlen(file->name)]); + } + else + { + file->extension = period + 1; + } +} + +_TINYDIR_FUNC +int _tinydir_file_cmp(const void *a, const void *b) +{ + const tinydir_file *fa = (const tinydir_file *)a; + const tinydir_file *fb = (const tinydir_file *)b; + if (fa->is_dir != fb->is_dir) + { + return -(fa->is_dir - fb->is_dir); + } + return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); +} + +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR +/* +The following authored by Ben Hutchings +from https://womble.decadent.org.uk/readdir_r-advisory.html +*/ +/* Calculate the required buffer size (in bytes) for directory * +* entries read from the given directory handle. Return -1 if this * +* this cannot be done. * +* * +* This code does not trust values of NAME_MAX that are less than * +* 255, since some systems (including at least HP-UX) incorrectly * +* define it to be a smaller value. */ +_TINYDIR_FUNC +size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp) +{ + long name_max; + size_t name_end; + /* parameter may be unused */ + (void)dirp; + +#if defined _TINYDIR_USE_FPATHCONF + name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); + if (name_max == -1) +#if defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else + return (size_t)(-1); +#endif +#elif defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else +#error "buffer size for readdir_r cannot be determined" +#endif + name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1; + return (name_end > sizeof(struct _tinydir_dirent) ? + name_end : sizeof(struct _tinydir_dirent)); +} +#endif +#endif + +#ifdef __cplusplus +} +#endif + +# if defined (_MSC_VER) +# pragma warning(pop) +# endif + +#endif diff --git a/include/disp.h b/include/disp.h new file mode 100644 index 0000000..3e1d7b9 --- /dev/null +++ b/include/disp.h @@ -0,0 +1,46 @@ +#ifndef TREEE_DISP_H_INCLUDED +#define TREEE_DISP_H_INCLUDED +/* ///////////////////////////////////////////////////////////////////////////// +// _ +// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0] +// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier +// | |_| | | __/ __/ __/ +// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - - +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the internalied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// / +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . /// +///////////////////////////////////////////////////////////////////////////// */ + +#include "file.h" + +#define PAIR_CYAN 1 +#define PAIR_MAGENTA 2 +#define PAIR_GREEN 3 +#define PAIR_ERROR 4 + +typedef struct window { + long x; + long y; + int showhidden; +} view; + +void +printtree(file *node, long *ln, view *win, long *max_x); + +void +initcurses(); + +void +destroycurses(); + +#endif diff --git a/include/file.h b/include/file.h new file mode 100644 index 0000000..9767cb1 --- /dev/null +++ b/include/file.h @@ -0,0 +1,69 @@ +#ifndef TREEE_FILE_H_INCLUDED +#define TREEE_FILE_H_INCLUDED +/* ///////////////////////////////////////////////////////////////////////////// +// _ +// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0] +// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier +// | |_| | | __/ __/ __/ +// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - - +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the internalied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// / +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . /// +///////////////////////////////////////////////////////////////////////////// */ + +#include + +#include "str.h" +#include "tinydir.h" + +/*.----------------------------------------------------------------------------, + / file */ + +typedef struct file file; +struct file { + str path; + str name; + str symlink_target; + long level; + int is_dir; + int is_lnk; + int is_valid_lnk; + file *parent; + file *first_child; + file *next_sibling; +}; + +file +file_ctor( + file *parent, const char *filename, const int is_dir, const int is_lnk); + +file * +file_new( + file *parent, const char *filename, const int is_dir, const int is_lnk); + +void +file_insert(file *parent, file *newchild); + +void +file_destruct(file *f); + +void +file_attach(file *parent, tinydir_dir *dir); + +int +file_has_siblings(file *node); + +int +file_is_last_sibling(file *node); + +#endif diff --git a/src/disp.c b/src/disp.c new file mode 100644 index 0000000..0b43201 --- /dev/null +++ b/src/disp.c @@ -0,0 +1,129 @@ +/* ///////////////////////////////////////////////////////////////////////////// +// _ +// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0] +// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier +// | |_| | | __/ __/ __/ +// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - - +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the internalied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// / +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . /// +///////////////////////////////////////////////////////////////////////////// */ + +#include + +#include "disp.h" + +static void +mvaddstr_bounded(long y, long x, const char *text, size_t len, long *max_x) { + if (x >= 0) { + mvaddstr(y, x, text); + } else if ((-x + 2) < len) { + mvaddch(y, 0, '<'); + mvaddstr(y, 2, &text[-x + 2]); + } +} + +void +printtree(file *node, long *ln, view *vw, long *max_x) { + long basex = node->level * 2 - vw->x; + long basey = *ln - vw->y; + if (node->name[0] == '.' && node->name[1] != '\0' && !vw->showhidden) + return; + if (basey >= 0 && basey <= LINES && basex <= COLS) { + attron(COLOR_PAIR(PAIR_MAGENTA)); + if (file_has_siblings(node) && strcmp(node->path, ".") != 0) { + if (file_is_last_sibling(node)) { + mvaddch(basey, basex - 2, ACS_LLCORNER); // └ + } else { + mvaddch(basey, basex - 2, ACS_LTEE); // ├ + } + } else if (node->parent != NULL) + // only child + { + mvaddch(basey, basex - 2, ACS_LLCORNER); // └ + } + + { + int x = basex - 2; + for (file *f = node->parent; f != NULL; f = f->parent) { + x -= 2; + if (!file_is_last_sibling(f)) { + mvaddch(basey, x, ACS_VLINE); + } + } + } + + attroff(COLOR_PAIR(PAIR_MAGENTA)); + + if (node->is_dir && !node->is_lnk) { + attron(COLOR_PAIR(PAIR_GREEN)); + mvaddstr_bounded(basey, basex, node->name, str_len(node->name), max_x); + if (node->level * 2 + str_len(node->name) > *max_x) + *max_x = node->level * 2 + str_len(node->name); + attroff(COLOR_PAIR(PAIR_GREEN)); + } else { + if (node->is_lnk) { + attron(COLOR_PAIR(PAIR_CYAN)); + mvaddstr_bounded(basey, basex, node->name, str_len(node->name), max_x); + attroff(COLOR_PAIR(PAIR_CYAN)); + mvaddstr_bounded( + basey, basex + str_len(node->name) + 1, "->", 2, max_x); + if (!node->is_valid_lnk) { + attron(COLOR_PAIR(PAIR_ERROR)); + attron(A_BOLD); + } + mvaddstr_bounded( + basey, basex + str_len(node->name) + 4, node->symlink_target, + str_len(node->symlink_target), max_x); + if (!node->is_valid_lnk) { + attroff(COLOR_PAIR(PAIR_ERROR)); + attroff(A_BOLD); + } + if (node->level * 2 + str_len(node->name) + 4 + + str_len(node->symlink_target) > + *max_x) + *max_x = node->level * 2 + str_len(node->name) + 4 + + str_len(node->symlink_target); + } else { + mvaddstr_bounded(basey, basex, node->name, str_len(node->name), max_x); + if (node->level * 2 + str_len(node->name) > *max_x) + *max_x = node->level * 2 + str_len(node->name); + } + } + } + + ++(*ln); + for (file *f = node->first_child; f != NULL; f = f->next_sibling) { + printtree(f, ln, vw, max_x); + } +} + +void +initcurses() { + initscr(); + cbreak(); + noecho(); + clear(); + start_color(); + use_default_colors(); + init_pair(PAIR_CYAN, COLOR_CYAN, -1); + init_pair(PAIR_MAGENTA, COLOR_MAGENTA, -1); + init_pair(PAIR_GREEN, COLOR_GREEN, -1); + init_pair(PAIR_ERROR, COLOR_WHITE, COLOR_RED); +} + +void +destroycurses() { + clear(); + endwin(); +} diff --git a/src/file.c b/src/file.c new file mode 100644 index 0000000..4e05827 --- /dev/null +++ b/src/file.c @@ -0,0 +1,185 @@ +/* ///////////////////////////////////////////////////////////////////////////// +// _ +// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0] +// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier +// | |_| | | __/ __/ __/ +// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - - +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the internalied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// / +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . /// +///////////////////////////////////////////////////////////////////////////// */ + +#include "file.h" + +#include + +/*.----------------------------------------------------------------------------, + / helpers */ + +static int +alphacmp(const char *a, const char *b) { + int i = 0; + for (; a[i] != '\0' && b[i] != '\0'; ++i) { + /* if (a[i] > b[i]) */ + /* return 1; */ + /* else if (a[i] < b[i]) */ + /* return -1; */ + if (a[i] != b[i]) + return -1 * (a[i] < b[i]) + (a[i] > b[i]); + } + /* if (a[i] == b[i]) return 0; */ + /* else if (a[i] == '\0') return -1; */ + /* else return 1; */ + return (a[i] != b[i]) * (-2 * (a[i] == '\0')) + 1; +} + +/*.----------------------------------------------------------------------------, + / file */ + +file +file_ctor( + file *parent, const char *filename, const int is_dir, const int is_lnk) { + str path; + long level; + if (parent == NULL) { + path = str_new(filename); + level = 0; + } else { + path = str_alloc(str_len(parent->path) + strlen(filename) + 1); + str_append_(&path, parent->path); + str_append(&path, "/"); + str_append(&path, filename); + level = parent->level + 1; + } + + char buf[_TINYDIR_PATH_MAX]; + size_t len = readlink(path, buf, sizeof(buf) - 1); + if (len != -1) { + buf[len] = '\0'; + } + int is_valid_lnk = 0; + if (len != -1 && access(buf, F_OK) != -1) { + is_valid_lnk = 1; + } + + return (file){ + .path = path, + .name = str_new(filename), + .symlink_target = len != -1 ? str_new(buf) : str_new(""), + .level = level, + .is_dir = is_dir, + .is_lnk = len != -1, + .is_valid_lnk = is_valid_lnk, + .parent = parent, + .first_child = NULL, + .next_sibling = NULL, + }; +} + +file * +file_new( + file *parent, const char *filename, const int is_dir, const int is_lnk) { + file *o = malloc(sizeof(file)); + file obj = file_ctor(parent, filename, is_dir, is_lnk); + memcpy(o, &obj, sizeof(obj)); + return o; +} + +void +file_insert(file *parent, file *newchild) { + file *cur = parent->first_child; + newchild->parent = parent; + + if (cur == NULL) { + parent->first_child = newchild; + return; + } + + // if child name < first_child name, make it the first child and return + if (alphacmp(newchild->name, cur->name) < 0) { + newchild->next_sibling = cur; + parent->first_child = newchild; + return; + } + + // loop through all siblings until child is < the next sibling (or next + // sibling is NULL), insert between cur and next and return + for (; cur->next_sibling != NULL; cur = cur->next_sibling) { + if (alphacmp(newchild->name, cur->next_sibling->name) < 0) { + newchild->next_sibling = cur->next_sibling; + cur->next_sibling = newchild; + return; + } + } + + // next_sibling is NULL; set it to child + cur->next_sibling = newchild; +} + +// destroys all children and their siblings recursively +void +file_destruct(file *f) { + for (file *cur = f->first_child; cur != NULL; cur = cur->next_sibling) { + file_destruct(cur); + } + str_free(&f->path); + str_free(&f->name); + str_free(&f->symlink_target); + free(f); +} + +void +file_attach(file *parent, tinydir_dir *dir) { + while (dir->has_next) { + tinydir_file tdf; + tinydir_readfile(dir, &tdf); + + if (strcmp(tdf.name, ".") == 0 || strcmp(tdf.name, "..") == 0) { + tinydir_next(dir); + continue; // skip ./ and ../ + } + + file *next = + file_new(parent, tdf.name, tdf.is_dir, S_ISREG(tdf._s.st_mode)); + file_insert(parent, next); + + if (tdf.is_dir) { + tinydir_dir tdnext; + tinydir_open(&tdnext, next->path); + file_attach(next, &tdnext); + tinydir_close(&tdnext); + } + + tinydir_next(dir); + } +} + +int +file_has_siblings(file *node) { + file *parent = node->parent; + if (parent == NULL) + return 1; + return node != parent->first_child || node->next_sibling != NULL; +} + +int +file_is_last_sibling(file *node) { + file *parent = node->parent; + if (parent == NULL) + return 1; + file *lst; + for (lst = parent->first_child; lst->next_sibling != NULL; + lst = lst->next_sibling) + ; + return lst == node; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..60be85a --- /dev/null +++ b/src/main.c @@ -0,0 +1,132 @@ +/* ///////////////////////////////////////////////////////////////////////////// +// _ +// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0] +// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier +// | |_| | | __/ __/ __/ +// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - - +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the internalied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// / +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . /// +///////////////////////////////////////////////////////////////////////////// */ + +#include + +#include "disp.h" +#include "file.h" + +int +main(int argc, char *argv[]) { + initcurses(); + + const char *searchdir = (argc < 2 ? "." : argv[1]); + + view vw = { + .x = 0, + .y = 0, + .showhidden = 0, + }; + + while (1) { + clear(); + tinydir_dir dir; + tinydir_open(&dir, searchdir); + + file *root = file_new(NULL, searchdir, 1, 0); + + file_attach(root, &dir); + long lines = 0; + long max_x = 0; + printtree(root, &lines, &vw, &max_x); + if (max_x < vw.x + 3) { + if (max_x >= 3) + vw.x = max_x - 3; + else + vw.x = 0; + continue; + } + + cbreak(); + + timeout(1000); + int c = getch(); + + switch (c) { + case 'j': { + if (lines < LINES) + break; + if (vw.y + 1 < lines - LINES + 1) + vw.y += 1; + break; + } + case 'J': { + if (lines < LINES) + break; + if (vw.y + 10 < lines - LINES + 1) + vw.y += 10; + else if (vw.y + 10 >= lines - LINES + 1) + vw.y = lines - LINES; + break; + } + case 'k': { + if (vw.y - 1 >= 0) + vw.y -= 1; + break; + } + case 'K': { + if (vw.y - 10 >= 0) + vw.y -= 10; + else if (vw.y < 10) + vw.y = 0; + break; + } + case 'l': { + if (vw.x + 3 < max_x) + vw.x += 1; + break; + } + case 'L': { + if (vw.x + 12 < max_x) + vw.x += 10; + else if (vw.x + 12 >= max_x) + vw.x = max_x - 2; + break; + } + case 'h': { + if (vw.x - 1 >= 0) + vw.x -= 1; + break; + } + case 'H': { + if (vw.x - 10 >= 0) + vw.x -= 10; + else if (vw.x < 10) + vw.x = 0; + break; + } + case 'q': { + destroycurses(); + file_destruct(root); + tinydir_close(&dir); + return EXIT_SUCCESS; + break; + } + case '.': { + vw.showhidden = !vw.showhidden; + break; + } + } + + file_destruct(root); + tinydir_close(&dir); + } +}