nt

Unnamed repository; edit this file 'description' to name the repository.
git clone git://edryd.org/nt
Log | Files | Refs | LICENSE

commit 7d61b0086b50d196ea3f2dda7e2c38ec4ece78e8
Author: Ed van Bruggen <edvb54@gmail.com>
Date:   Sat, 11 Mar 2017 11:50:38 -0800

Initial commit

Diffstat:
.gitignore | 21+++++++++++++++++++++
LICENSE | 18++++++++++++++++++
Makefile | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
README.md | 41+++++++++++++++++++++++++++++++++++++++++
arg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 1+
config.mk | 21+++++++++++++++++++++
nt.1 | 38++++++++++++++++++++++++++++++++++++++
nt.c | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
util.c | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
util.h | 16++++++++++++++++
11 files changed, 667 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,21 @@ +# swap files +*~ +*.swp +*.swo +\#*\# + +# compiled files +nt +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.o +*.obj +*.so +*.so.* + +# misc +tags +config.h diff --git a/LICENSE b/LICENSE @@ -0,0 +1,18 @@ +zlib License + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/Makefile b/Makefile @@ -0,0 +1,66 @@ +# nt - create notes +# See LICENSE file for copyright and license details. + +include config.mk + +EXE = nt +SRC = $(wildcard *.c) +OBJ = $(SRC:.c=.o) + +all: options $(EXE) + +options: + @echo $(EXE) build options: + @echo "CFLAGS = $(CFLAGS)" + @echo "LDFLAGS = $(LDFLAGS)" + +.o: + @echo $(LD) $@ + @$(LD) -o $@ $< $(LDFLAGS) + +.c.o: + @echo $(CC) $< + @$(CC) -c -o $@ $< $(CFLAGS) + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +$(EXE): $(OBJ) + @echo $(CC) -o $@ + @$(CC) -o $@ $(OBJ) $(LDFLAGS) + +clean: + @echo -n cleaning ... + @rm -f $(OBJ) $(EXE) + @echo \ done + +install: all + @echo -n installing executable file to $(DESTDIR)$(PREFIX)/bin ... + @mkdir -p $(DESTDIR)$(PREFIX)/bin + @cp -f $(EXE) $(DESTDIR)$(PREFIX)/bin + @chmod 755 $(DESTDIR)$(PREFIX)/bin/$(EXE) + @echo \ done + @echo -n installing manual page to $(DESTDIR)$(MANPREFIX)/man1 ... + @mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + @sed "s/VERSION/$(VERSION)/g" < $(EXE).1 > $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @echo \ done + +uninstall: + @echo -n removing executable file from $(DESTDIR)$(PREFIX)/bin ... + @rm -f $(DESTDIR)$(PREFIX)/bin/$(EXE) + @echo \ done + @echo -n removing manual page from $(DESTDIR)$(MANPREFIX)/man1 ... + @rm -f $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1 + @echo \ done + +update-man: + @echo -n updating man page $(EXE).1 ... + @cat README.md | sed "s/# $(EXE)/# $(EXE) 1\n\n##NAME\n\n$(EXE) /" | \ + md2man-roff | sed "s/\\[la\]/\</" | sed "s/\\[ra\]/\>/" > $(EXE).1 + @echo \ done + +.PHONY: all options clean install uninstall update-man diff --git a/README.md b/README.md @@ -0,0 +1,41 @@ +# nt - simple note taker + +## SYNOPSIS + +`nt` [-lv] [-f *FILE*] [-d *NOTE*] [-s *SEARCH*] [-n *NUM* | -*NUM*] [*NOTE* ...] + +## DESCRIPTION + +Simple note taker for the command line. + +## OPTIONS + +-v + Print version info and exit + +-f *FILE* + Load *FILE* instead of default one + +-l + List notes + +-n *NUM* + List last *NUM* notes + +-s *SEARCH* + Search for *SEARCH* in notes + +-d *NOTE* + Delete *NOTE* from notes + +## AUTHOR + +Ed van Bruggen <edvb54@gmail.com> + +## SEE ALSO + +View source code at: <https://gitlab.com/edvb/nt> + +## LICENSE + +zlib License diff --git a/arg.h b/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF() (brk_ = 1, atoi(argv[0])) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.def.h b/config.def.h @@ -0,0 +1 @@ +char *fname = "todo"; diff --git a/config.mk b/config.mk @@ -0,0 +1,21 @@ +# nt version number +VERSION = 0.0.0 + +### Change the variables below for your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +# includes and libraries +INCS = -Iinclude +LIBS = + +# flags +CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=600 +CFLAGS = -g -std=c99 -pedantic -Wall ${INCS} ${CPPFLAGS} +LDFLAGS = -g ${LIBS} + +# compiler and linker +CC = gcc + diff --git a/nt.1 b/nt.1 @@ -0,0 +1,38 @@ +.TH nt 1 +.SH NAME +.PP +nt \- simple note taker +.SH SYNOPSIS +.PP +\fB\fCnt\fR [\-lv] [\-f \fIFILE\fP] [\-d \fINOTE\fP] [\-s \fISEARCH\fP] [\-n \fINUM\fP | \-\fINUM\fP] [\fINOTE\fP ...] +.SH DESCRIPTION +.PP +Simple note taker for the command line. +.SH OPTIONS +.PP +\-v + Print version info and exit +.PP +\-f \fIFILE\fP + Load \fIFILE\fP instead of default one +.PP +\-l + List notes +.PP +\-n \fINUM\fP + List last \fINUM\fP notes +.PP +\-s \fISEARCH\fP + Search for \fISEARCH\fP in notes +.PP +\-d \fINOTE\fP + Delete \fINOTE\fP from notes +.SH AUTHOR +.PP +Ed van Bruggen \<edvb54@gmail.com\> +.SH SEE ALSO +.PP +View source code at: \<https://gitlab.com/edvb/nt\> +.SH LICENSE +.PP +zlib License diff --git a/nt.c b/nt.c @@ -0,0 +1,247 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "arg.h" +#include "util.h" + +/* macros */ +#define MAX_SUB 2048 + +/* typedefs */ + +/* functions */ +char *trimwhitespace(char *str); + +void nt_new(void); +void nt_del(void); +void nt_search(void); +void nt_list_all(void); +void nt_list_n(int n); + +void run(void); +void cleanup(void); +void usage(void); + +/* varibles */ +char *argv0; + +FILE *fp; +char *sub; +char **notes; +int notec; + +char mode = 0; +int lsnum; + +#include "config.h" + +/* remove tailing or leading whitespace from str */ +char * +trimwhitespace(char *str) +{ + char *end; + + /* trim leading space */ + while(isspace((unsigned char)*str)) str++; + + if(*str == 0) /* all spaces? */ + return str; + + /* trim trailing space */ + end = str + strlen(str) - 1; + while(end > str && isspace((unsigned char)*end)) end--; + + /* write new null terminator */ + *(end+1) = 0; + + return str; +} + +/* create a new note */ +void +nt_new(void) +{ + if (sub == NULL) usage(); + + fprintf(fp, "%s\n", sub); +} + +/* delete oldest matching note from notes */ +void +nt_del(void) +{ + if (sub == NULL) usage(); + + int i, found = 0; + FILE *tmpfp; + char *tmpfpext = "part"; + char *tmpfpname = ecalloc(strlen(fname)+strlen(tmpfpext)+1, sizeof(char)); + + strcpy(tmpfpname, fname); + strcat(tmpfpname, "."); + strcat(tmpfpname, tmpfpext); + tmpfp = fopen(tmpfpname, "w"); + + for (i = 0; i < notec; i++) + if (strcmp(notes[i], sub) != 0 || found) + fprintf(tmpfp, "%s\n", notes[i]); + else + found = 1; + if (!found) + die("%s: delete: '%s' not found", argv0, sub); + + fclose(tmpfp); + remove(fname); + rename(tmpfpname, fname); +} + +/* search notes for given note */ +void +nt_search() +{ + if (sub == NULL) usage(); + + int i, found = 0; + for (i = 0; i < notec; i++) + if (strstr(notes[i], sub)) { + printf("%s\n", notes[i]); + found = 1; + } + if (!found) + die("%s: search: '%s' not found", argv0, sub); +} + +/* print out entire file */ +void +nt_list_all(void) +{ + int i; + for (i = 0; i < notec; i++) + printf("%s\n", notes[i]); +} + +/* diplay n most recent subjects in file */ +void +nt_list_n(int n) +{ + int i = notec; + n = n > i ? -1 : i-n-1; + for (i--; i > n; i--) + printf("%s\n", notes[i]); +} + +/* handle options and create notes */ +void +run(void) +{ + switch (mode) { + case 'd': + nt_del(); + break; + case 'l': + nt_list_all(); + exit(0); + case 'n': + nt_list_n(lsnum); + exit(0); + case 's': + nt_search(); + break; + default: + nt_new(); + } +} + +/* load fname, set notec, populate notes, allocate sub */ +void +setup(void) +{ + int c, i = 0, last = 0; + + /* open file */ + fp = fopen(fname, "ab+"); + + /* get number of notec */ + while ((c = fgetc(fp)) != EOF) { + if (c == '\n' && last != '\n') + ++notec; + last = c; + } + rewind(fp); + + /* copy file fp to notes */ + notes = ecalloc(notec+1, sizeof(char*)); + do + notes[i] = ecalloc(MAX_SUB, sizeof(char)); + while (fscanf(fp, "%2048[^\n]\n", notes[i++]) != EOF); + rewind(fp); + + sub = ecalloc(MAX_SUB, sizeof(char)); +} + +/* close fname, free memory */ +void +cleanup(void) +{ + fclose(fp); + for (int j = 0; j < notec+1; j++) + free(notes[j]); + free(notes); + free(sub); +} + +void +usage(void) +{ + die("usage: %s [-lv] [-f FILE] [-d NOTE] [-s SEARCH] [-n NUM | -NUM] [NOTE ...]", argv0); +} + +int +main(int argc, char *argv[]) +{ + ARGBEGIN { + case 'f': + fname = EARGF(usage()); + break; + case 'd': + mode = 'd'; + break; + case 'l': + mode = 'l'; + break; + case 'n': + mode = 'n'; + lsnum = atoi(EARGF(usage())); + break; + ARGNUM: + mode = 'n'; + lsnum = ARGNUMF(); + break; + case 's': + mode = 's'; + break; + case 'v': + printf("%s v%s\n", argv0, VERSION); + return 0; + default: + usage(); + } ARGEND; + + setup(); + + if (argc <= 0 && (!mode || mode == 'd' || mode == 's')) + fgets(sub, MAX_SUB, stdin); + else if (argc > 0) + sub = strconcat(argv, argc); + trimwhitespace(sub); + + run(); + + cleanup(); + + return 0; + +} diff --git a/util.c b/util.c @@ -0,0 +1,135 @@ +/* See LICENSE file for copyright and license details. */ +#include <assert.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + die("calloc: out of memory"); + + return p; +} + +void * +emalloc(size_t size) +{ + void *p; + + if (!(p = malloc(size))) + die("malloc: out of memory"); + + return p; +} + +void * +erealloc(void *p, size_t size) +{ + if (!(p = realloc(p, size))) + die("realloc: out of memory"); + + return p; +} + +char * +estrdup(char *s) +{ + if ((s = strdup(s)) == NULL) + die("strdup: out of memory"); + + return s; +} + +char * +strconcat(char **s, int c) +{ + if (s == NULL) die("strconcat: given null pointer"); + + int len, i; + char *ret; + + for (i = 0; i < c; i++) + len += strlen(s[i]) + 1; + ret = ecalloc(len, sizeof(char)); + + strcpy(ret, s[0]); + strcat(ret, " "); + for (i = 1; i < c; i++) { + strcat(ret, s[i]); + strcat(ret, " "); + } + + return ret; +} + +/* split string s into muiltple strings by a_delim */ +char ** +strsplit(const char *s, const char a_delim, int *size) +{ + if (s == NULL) die("strsplit: given null pointer"); + + char **ret = 0; + size_t count = 0; + char *last_delim = 0; + char delim[2] = { a_delim, 0 }; /* converet a_delim into string for strtok */ + char *a_str = ecalloc(strlen(s)+1, sizeof(char)); + strcpy(a_str, s); + + /* count number of elements that will be extracted. */ + for (char *tmp = a_str; *tmp; tmp++) + if (a_delim == *tmp) { + count++; + last_delim = tmp; + } + + /* add space for trailing token. */ + count += last_delim < (a_str + strlen(a_str) - 1); + + /* add space for terminating null string so caller + * knows where the list of returned strings ends. */ + count++; + + ret = ecalloc(count, sizeof(char*)); + + if (ret) { + size_t idx = 0; + char *token = strtok(a_str, delim); + + while (token) { + assert(idx < count); + *(ret + idx++) = estrdup(token); + token = strtok(0, delim); + } + assert(idx == count - 1); + *(ret + idx) = 0; + } + + free(a_str); + *size = count - 1; + return ret; +} + +void +die(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/util.h b/util.h @@ -0,0 +1,16 @@ +/* See LICENSE file for copyright and license details. */ + +#define MAX(A, B) ((A) > (B) ? (A) : (B)) +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) +#define LEN(X) (sizeof(X) / sizeof((X)[0])) + +void *ecalloc(size_t nmemb, size_t size); +void *emalloc(size_t size); +void *erealloc(void *p, size_t size); +char *estrdup(char *s); + +char *strconcat(char **s, int c); +char **strsplit(const char *s, const char a_delim, int *size); + +void die(const char *errstr, ...);