├── .gitignore ├── man └── rng.1.gz ├── include ├── error.h ├── assert.h ├── range.h ├── main.h ├── argv.h ├── common.h ├── mem.h ├── utils.h └── except.h ├── src ├── assert.c ├── error.c ├── argv.c ├── except.c ├── mem.c ├── main.c └── utils.c ├── tools ├── clean.sh ├── uninstall.sh └── install.sh ├── Makefile ├── LICENSE └── README.rst /.gitignore: -------------------------------------------------------------------------------- 1 | rng 2 | *.sw[po] 3 | -------------------------------------------------------------------------------- /man/rng.1.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickolasburr/rng/HEAD/man/rng.1.gz -------------------------------------------------------------------------------- /include/error.h: -------------------------------------------------------------------------------- 1 | /** 2 | * error.h 3 | * 4 | * Copyright (C) 2017 Nickolas Burr 5 | */ 6 | 7 | #ifndef RNG_ERROR_H 8 | #define RNG_ERROR_H 9 | 10 | int is_error(int, int); 11 | 12 | #endif /* RNG_ERROR_H */ 13 | -------------------------------------------------------------------------------- /src/assert.c: -------------------------------------------------------------------------------- 1 | /** 2 | * assert.c 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | #include "assert.h" 7 | 8 | const Except_T Assert_Failed = { 9 | "Assertion failed" 10 | }; 11 | 12 | void (assert)(int e) { 13 | assert(e); 14 | } 15 | -------------------------------------------------------------------------------- /tools/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | PROJDIR="$(dirname $PWD)" 6 | TARGET="rng" 7 | 8 | if test -f "$PROJDIR/$TARGET"; then 9 | rm -rf "$PROJDIR/$TARGET" 10 | fi 11 | 12 | if test -d "$PROJDIR/$TARGET.dSYM"; then 13 | rm -rf "$PROJDIR/$TARGET.dSYM" 14 | fi 15 | -------------------------------------------------------------------------------- /tools/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | PREFIX=/usr/local 6 | BINDIR=$PREFIX/bin 7 | 8 | RM="rm" 9 | RMFLAGS="-rf" 10 | 11 | TARGET="rng" 12 | MANPAGE="$TARGET.1.gz" 13 | MANDEST=$PREFIX/share/man/man1 14 | 15 | $RM $RMFLAGS $BINDIR/$TARGET $MANDEST/$MANPAGE 16 | -------------------------------------------------------------------------------- /include/assert.h: -------------------------------------------------------------------------------- 1 | /** 2 | * assert.h 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #undef assert 8 | 9 | #ifdef NDEBUG 10 | #define assert(e) ((void)0) 11 | #else 12 | #include "except.h" 13 | extern void assert(int e); 14 | #define assert(e) ((void)((e)||(RAISE(Assert_Failed),0))) 15 | #endif 16 | -------------------------------------------------------------------------------- /include/range.h: -------------------------------------------------------------------------------- 1 | /** 2 | * range.h 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #ifndef RNG_RANGE_H 8 | #define RNG_RANGE_H 9 | 10 | #include "common.h" 11 | #include "mem.h" 12 | 13 | typedef struct { 14 | unsigned int start; 15 | unsigned int end; 16 | } Range_T; 17 | 18 | #endif /* RNG_RANGE_H */ 19 | -------------------------------------------------------------------------------- /tools/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | PREFIX=/usr/local 6 | BINDIR=$PREFIX/bin 7 | 8 | TARGET="rng" 9 | MANPAGE="$TARGET.1.gz" 10 | MANDEST=$PREFIX/share/man/man1 11 | 12 | INSTALL=/usr/bin/install 13 | OPTIONS="-c" 14 | 15 | cd .. 16 | 17 | cp "man/$MANPAGE" "$MANDEST/$MANPAGE" 18 | 19 | $INSTALL $OPTIONS $TARGET $BINDIR/$TARGET 20 | -------------------------------------------------------------------------------- /include/main.h: -------------------------------------------------------------------------------- 1 | /** 2 | * main.h 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #ifndef RNG_MAIN_H 8 | #define RNG_MAIN_H 9 | 10 | #include "common.h" 11 | #include "assert.h" 12 | #include "except.h" 13 | #include "argv.h" 14 | #include "mem.h" 15 | #include "range.h" 16 | #include "utils.h" 17 | 18 | #endif /* RNG_MAIN_H */ 19 | -------------------------------------------------------------------------------- /src/error.c: -------------------------------------------------------------------------------- 1 | /** 2 | * error.c 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #include "error.h" 8 | 9 | /** 10 | * A more expressive way of determining if an 11 | * error was encountered by comparing a return 12 | * value against a known error code (e.g. -1). 13 | */ 14 | int is_error (int value, int code) { 15 | return (value == code); 16 | } 17 | -------------------------------------------------------------------------------- /include/argv.h: -------------------------------------------------------------------------------- 1 | /** 2 | * argv.h 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #ifndef RNG_ARGV_H 8 | #define RNG_ARGV_H 9 | 10 | #include "common.h" 11 | #include "error.h" 12 | #include "utils.h" 13 | 14 | #define NUM_OPTIONS 2 15 | #define OPT_DELIMIT "--" 16 | 17 | typedef struct { 18 | char *value; 19 | char *alias; 20 | char *desc; 21 | } option_t; 22 | 23 | static option_t options[NUM_OPTIONS]; 24 | void usage(void); 25 | 26 | #endif /* RNG_ARGV_H */ 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ### 2 | ### Makefile 3 | ### 4 | 5 | CC ?= gcc 6 | CFLAGS += -I$(INCLUDE) 7 | LDFLAGS += -pthread -lz 8 | TARGET ?= rng 9 | INSTALL ?= /usr/bin/install -c 10 | 11 | INCLUDE = include 12 | SOURCES = src 13 | TOOLS = tools 14 | 15 | CSFILES = $(wildcard $(SOURCES)/*.c) 16 | OBFILES = $(patsubst %.c,%.o,$(CSFILES)) 17 | 18 | .PHONY: all $(TARGET) clean install uninstall 19 | 20 | all: $(TARGET) 21 | 22 | $(TARGET): $(CSFILES) 23 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 24 | 25 | clean: 26 | @cd $(TOOLS) && ./clean.sh 27 | 28 | install: 29 | @cd $(TOOLS) && ./install.sh 30 | 31 | uninstall: 32 | @cd $(TOOLS) && ./uninstall.sh 33 | -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * common.h 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #ifndef RNG_COMMON_H 8 | #define RNG_COMMON_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef __APPLE__ 20 | #include 21 | #else 22 | #include 23 | #endif 24 | 25 | #define is_null(ptr) ((ptr) == ((void *) 0)) 26 | 27 | #define PROGNAME "rng" 28 | #define RNG_VERSION "1.0.4" 29 | #define _GNU_SOURCE 1 30 | 31 | #endif /* RNG_COMMON_H */ 32 | -------------------------------------------------------------------------------- /src/argv.c: -------------------------------------------------------------------------------- 1 | /** 2 | * argv.c 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #include "argv.h" 8 | 9 | static option_t options[] = { 10 | { 11 | "--help", 12 | "-h", 13 | "Show help information.", 14 | }, 15 | { 16 | "--version", 17 | "-v", 18 | "Show version number.", 19 | }, 20 | }; 21 | 22 | /** 23 | * Print usage information. 24 | */ 25 | void usage (void) { 26 | int index; 27 | 28 | fprintf( 29 | stdout, 30 | "Usage: rng [OPTIONS] [,] [FILE]\n\nOPTIONS:\n\n" 31 | ); 32 | 33 | for (index = 0; index < NUM_OPTIONS; index += 1) { 34 | option_t *option = &options[index]; 35 | fprintf( 36 | stdout, 37 | "%4s%s, %s: %-24s\n", 38 | "", 39 | option->alias, 40 | option->value, 41 | option->desc 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/except.c: -------------------------------------------------------------------------------- 1 | /** 2 | * except.c 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #include "common.h" 8 | #include "assert.h" 9 | #include "except.h" 10 | 11 | #define T Except_T 12 | 13 | Except_Frame *Except_stack = NULL; 14 | 15 | void Except_raise (const T *e, const char *file, int line) { 16 | Except_Frame *p = Except_stack; 17 | assert(e); 18 | 19 | if (is_null(p)) { 20 | fprintf(stderr, "Uncaught exception"); 21 | if (e->reason) { 22 | fprintf(stderr, " %s", e->reason); 23 | } else { 24 | fprintf(stderr, " at 0x%p", e); 25 | } 26 | 27 | if (file && line > 0) { 28 | fprintf(stderr, " raised at %s:%d\n", file, line); 29 | } 30 | 31 | fprintf(stderr, "aborting...\n"); 32 | fflush(stderr); 33 | abort(); 34 | } 35 | 36 | p->exception = e; 37 | p->file = file; 38 | p->line = line; 39 | 40 | Except_stack = Except_stack->prev; 41 | longjmp(p->env, Except_raised); 42 | } 43 | -------------------------------------------------------------------------------- /include/mem.h: -------------------------------------------------------------------------------- 1 | /** 2 | * mem.h 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #ifndef RNG_MEM_H 8 | #define RNG_MEM_H 9 | 10 | #include "common.h" 11 | #include "assert.h" 12 | #include "except.h" 13 | 14 | extern const Except_T Mem_Failed; 15 | 16 | extern void *Mem_alloc(long nbytes, const char *file, int line); 17 | extern void *Mem_calloc(long count, long nbytes, const char *file, int line); 18 | extern void Mem_free(void *ptr, const char *file, int line); 19 | extern void *Mem_resize(void *ptr, long nbytes, const char *file, int line); 20 | 21 | #define ALLOC(nbytes) Mem_alloc((nbytes), __FILE__, __LINE__) 22 | #define CALLOC(count, nbytes) Mem_calloc((count), (nbytes), __FILE__, __LINE__) 23 | #define NEW(p) ((p) = ALLOC((long)sizeof *(p))) 24 | #define NEW0(p) ((p) = CALLOC(1, (long)sizeof *(p))) 25 | #define FREE(ptr) ((void)(Mem_free((ptr), __FILE__, __LINE__), (ptr) = 0)) 26 | #define RESIZE(ptr, nbytes) ((ptr) = Mem_resize((ptr), (nbytes), __FILE__, __LINE__)) 27 | 28 | #endif /* RNG_MEM_H */ 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2018 Nickolas Burr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/utils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * utils.h 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #ifndef RNG_UTILS_H 8 | #define RNG_UTILS_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "common.h" 15 | #include "assert.h" 16 | #include "mem.h" 17 | 18 | /** 19 | * String utilities 20 | */ 21 | 22 | char *base_name(char *); 23 | int compare(char *, char *); 24 | char *concat(char *, char *); 25 | char *copy(char *, char *); 26 | char *dir_name(char *); 27 | int index_of(char *, char **, size_t); 28 | int in_array(char *, char **, size_t); 29 | int length(char *); 30 | 31 | /** 32 | * Filesystem utilities 33 | */ 34 | 35 | DIR *get_dir(int *, const char *); 36 | FILE *get_file(int *, const char *, const char *); 37 | 38 | FILE *open_pipe(int *, const char *, const char *); 39 | int close_pipe(FILE *); 40 | 41 | int is_dir(const char *); 42 | int is_file(const char *); 43 | int is_link(const char *); 44 | int is_sock(const char *); 45 | int is_fifo(const char *); 46 | int is_block(const char *); 47 | int is_char(const char *); 48 | 49 | /** 50 | * Type utilities 51 | */ 52 | 53 | int is_digit(int); 54 | int is_numeric(char *); 55 | 56 | #endif /* RNG_UTILS_H */ 57 | -------------------------------------------------------------------------------- /src/mem.c: -------------------------------------------------------------------------------- 1 | /** 2 | * mem.c 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #include "mem.h" 8 | 9 | const Except_T Mem_Failed = { 10 | "Allocation Failed" 11 | }; 12 | 13 | void *Mem_alloc (long nbytes, const char *file, int line) { 14 | void *ptr; 15 | 16 | assert(nbytes > 0); 17 | ptr = malloc(nbytes); 18 | 19 | if (is_null(ptr)) { 20 | if (is_null(file)) { 21 | RAISE(Mem_Failed); 22 | } else { 23 | Except_raise(&Mem_Failed, file, line); 24 | } 25 | } 26 | 27 | return ptr; 28 | } 29 | 30 | void *Mem_calloc (long count, long nbytes, const char *file, int line) { 31 | void *ptr; 32 | 33 | assert(count > 0); 34 | assert(nbytes > 0); 35 | ptr = calloc(count, nbytes); 36 | 37 | if (is_null(ptr)) { 38 | if (is_null(file)) { 39 | RAISE(Mem_Failed); 40 | } else { 41 | Except_raise(&Mem_Failed, file, line); 42 | } 43 | } 44 | 45 | return ptr; 46 | } 47 | 48 | void Mem_free (void *ptr, const char *file, int line) { 49 | if (ptr) { 50 | free(ptr); 51 | } 52 | } 53 | 54 | void *Mem_resize (void *ptr, long nbytes, const char *file, int line) { 55 | assert(ptr); 56 | assert(nbytes > 0); 57 | ptr = realloc(ptr, nbytes); 58 | 59 | if (is_null(ptr)) { 60 | if (is_null(file)) { 61 | RAISE(Mem_Failed); 62 | } else { 63 | Except_raise(&Mem_Failed, file, line); 64 | } 65 | } 66 | 67 | return ptr; 68 | } 69 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | rng 2 | === 3 | 4 | .. contents:: 5 | :local: 6 | 7 | Description 8 | ----------- 9 | 10 | ``rng`` streams input from a file or ``stdin`` to ``stdout``. 11 | 12 | It is the love child of ``sed -n ',p'`` and ``tail -n``. 13 | 14 | Installation 15 | ------------ 16 | 17 | You can install ``rng`` via Homebrew or manually. 18 | 19 | Homebrew 20 | ^^^^^^^^ 21 | 22 | .. code-block:: sh 23 | 24 | brew tap nickolasburr/pfa 25 | brew install rng 26 | 27 | Source 28 | ^^^^^^ 29 | 30 | .. code-block:: sh 31 | 32 | git clone https://github.com/nickolasburr/rng.git 33 | cd rng 34 | make 35 | make install 36 | 37 | By default, files are installed to ``/usr/local`` You can install elsewhere by passing ``PREFIX`` to ``make install``. 38 | 39 | .. code-block:: sh 40 | 41 | make install PREFIX=/opt 42 | 43 | Notes 44 | ----- 45 | 46 | Certain range values can be omitted if they can be properly implied. To get multiple ranges, use the colon (``:``) operator. 47 | 48 | Examples 49 | -------- 50 | 51 | Get lines 7-15 in ``Makefile``. 52 | 53 | .. code-block:: sh 54 | 55 | rng 7,15 Makefile 56 | 57 | Skip the first 4 lines of input from a pipe. 58 | 59 | .. code-block:: sh 60 | 61 | echo -e "First\nSecond\nThird\nFourth\nFifth" | rng 5 62 | 63 | Output lines 1-25 in ``Makefile``. 64 | 65 | .. code-block:: sh 66 | 67 | rng ,25 Makefile 68 | 69 | Get lines 5-10 and 17-22 in ``main.c``. 70 | 71 | .. code-block:: sh 72 | 73 | rng 5,10:17,22 main.c 74 | 75 | Output all lines except 11-19 in main.c 76 | 77 | .. code-block:: sh 78 | 79 | rng ,10:20, main.c 80 | 81 | Get lines 16-27 of a text document from the web. 82 | 83 | .. code-block:: sh 84 | 85 | curl -fsL https://www.w3.org/TR/2003/REC-PNG-20031110/iso_8859-1.txt | rng 16,27 86 | -------------------------------------------------------------------------------- /include/except.h: -------------------------------------------------------------------------------- 1 | /** 2 | * except.h 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #ifndef RNG_EXCEPT_H 8 | #define RNG_EXCEPT_H 9 | 10 | #include 11 | 12 | #define T Except_T 13 | 14 | typedef struct T { 15 | char *reason; 16 | } T; 17 | 18 | typedef struct Except_Frame Except_Frame; 19 | 20 | struct Except_Frame { 21 | Except_Frame *prev; 22 | jmp_buf env; 23 | const char *file; 24 | int line; 25 | const T *exception; 26 | }; 27 | 28 | enum { 29 | Except_entered = 0, 30 | Except_raised, 31 | Except_handled, 32 | Except_finalized 33 | }; 34 | 35 | #ifdef WIN32 36 | __declspec(thread) 37 | #endif 38 | 39 | extern Except_Frame *Except_stack; 40 | extern const Except_T Assert_Failed; 41 | 42 | void Except_raise(const T *e, const char *file, int line); 43 | 44 | #define RAISE(e) Except_raise(&(e), __FILE__, __LINE__) 45 | #define RERAISE Except_raise(Except_frame.exception, Except_frame.file, Except_frame.line) 46 | #define RETURN switch (Except_stack = Except_stack->prev,0) default: return 47 | #define TRY do { \ 48 | volatile int Except_flag; \ 49 | Except_Frame Except_frame; \ 50 | Except_frame.prev = Except_stack; \ 51 | Except_stack = &Except_frame; \ 52 | Except_flag = setjmp(Except_frame.env); \ 53 | if (Except_flag == Except_entered) { 54 | #define EXCEPT(e) \ 55 | if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \ 56 | } else if (Except_frame.exception == &(e)) { \ 57 | Except_flag = Except_handled; 58 | #define ELSE \ 59 | if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \ 60 | } else { \ 61 | Except_flag = Except_handled; 62 | #define FINALLY \ 63 | if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \ 64 | } { \ 65 | if (Except_flag == Except_entered) \ 66 | Except_flag = Except_finalized; 67 | #define END_TRY \ 68 | if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \ 69 | } if (Except_flag == Except_raised) RERAISE; \ 70 | } while (0) 71 | 72 | #undef T 73 | 74 | #endif /* RNG_EXCEPT_H */ 75 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * main.c 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #include "main.h" 8 | 9 | int main (int argc, char **argv) { 10 | int i, n; 11 | int start, end, count; 12 | int pairs, index, zindex; 13 | int opt_index, opt_value; 14 | size_t len; 15 | ssize_t bytes; 16 | FILE *stream = NULL; 17 | Range_T **ranges = NULL; 18 | Range_T *rng = NULL; 19 | char *ptr = NULL; 20 | char *range = NULL, 21 | *urange = NULL; 22 | char *line = NULL, 23 | *token = NULL; 24 | 25 | /** 26 | * Designated getopt long options. 27 | */ 28 | static struct option long_opts[] = { 29 | { "help", no_argument, 0, 'h' }, 30 | { "version", no_argument, 0, 'v' }, 31 | }; 32 | 33 | opt_index = 0; 34 | opt_value = 0; 35 | 36 | do { 37 | opt_value = getopt_long( 38 | argc, 39 | argv, 40 | "hv", 41 | long_opts, 42 | &opt_index 43 | ); 44 | 45 | if (opt_value == -1) { 46 | break; 47 | } 48 | 49 | switch (opt_value) { 50 | case 'h': 51 | usage(); 52 | exit(EXIT_SUCCESS); 53 | case 'v': 54 | fprintf(stdout, "%s\n", RNG_VERSION); 55 | exit(EXIT_SUCCESS); 56 | case '?': 57 | usage(); 58 | exit(EXIT_FAILURE); 59 | } 60 | } while (1); 61 | 62 | /** 63 | * At minimum, require a range argument. 64 | */ 65 | if (argc < 2) { 66 | fprintf( 67 | stderr, 68 | "%s: Invalid number of arguments\n", 69 | PROGNAME 70 | ); 71 | exit(EXIT_FAILURE); 72 | } 73 | 74 | i = 0; 75 | n = 1; 76 | 77 | /** 78 | * User-provided range[s]. 79 | */ 80 | urange = argv[1]; 81 | 82 | while (urange[i++]) { 83 | if (urange[i] == ':') { 84 | n++; 85 | } 86 | } 87 | 88 | pairs = 0; 89 | ranges = ALLOC(sizeof(*ranges) * n); 90 | 91 | for (index = 1; index < argc; index += 1) { 92 | /** 93 | * Use positional context to determine 94 | * the anticipated type of argument. 95 | */ 96 | switch (index) { 97 | case 1: 98 | /** 99 | * Get range[s] from ranges string. 100 | */ 101 | for ( 102 | range = strtok_r(argv[index], ":", &ptr); 103 | !is_null(range); 104 | range = strtok_r(NULL, ":", &ptr) 105 | ) { 106 | zindex = 0; 107 | rng = ALLOC(sizeof(rng)); 108 | 109 | /** 110 | * Default , values. 111 | */ 112 | rng->start = 0; 113 | rng->end = 0; 114 | 115 | /** 116 | * Split the range into tokens. 117 | */ 118 | while ((token = strsep(&range, ","))) { 119 | if (!is_numeric(token)) { 120 | fprintf( 121 | stderr, 122 | "%s: '%s' is not a valid range value.\n\n", 123 | PROGNAME, 124 | token 125 | ); 126 | usage(); 127 | 128 | goto on_error; 129 | } 130 | 131 | switch (zindex) { 132 | case 0: 133 | rng->start = (unsigned int) strtoul(token, NULL, 0); 134 | break; 135 | case 1: 136 | rng->end = (unsigned int) strtoul(token, NULL, 0); 137 | break; 138 | default: 139 | break; 140 | } 141 | 142 | zindex++; 143 | } 144 | 145 | ranges[pairs++] = rng; 146 | } 147 | 148 | break; 149 | case 2: 150 | if (!is_file(argv[index])) { 151 | fprintf( 152 | stderr, 153 | "%s: '%s' is not a valid filename.\n", 154 | PROGNAME, 155 | argv[index] 156 | ); 157 | 158 | goto on_error; 159 | } 160 | 161 | stream = fopen(argv[index], "r"); 162 | break; 163 | default: 164 | break; 165 | } 166 | } 167 | 168 | /** 169 | * In the absence of a filename, read from STDIN. 170 | */ 171 | if (is_null(stream)) { 172 | stream = stdin; 173 | } 174 | 175 | bytes = 0; 176 | count = 1; 177 | 178 | do { 179 | bytes = getline(&line, &len, stream); 180 | 181 | if (bytes == -1) { 182 | break; 183 | } 184 | 185 | for (index = 0; index < pairs; index += 1) { 186 | if ( 187 | count >= ranges[index]->start 188 | && (count <= ranges[index]->end || !ranges[index]->end) 189 | ) { 190 | fwrite(line, bytes, 1, stdout); 191 | fflush(stdout); 192 | } 193 | } 194 | 195 | count++; 196 | } while (1); 197 | 198 | /** 199 | * Run clean-up tasks. 200 | */ 201 | for (index = 0; index < pairs; index += 1) { 202 | FREE(ranges[index]); 203 | } 204 | 205 | FREE(ranges); 206 | return EXIT_SUCCESS; 207 | 208 | on_error: 209 | /** 210 | * Run clean-up tasks. 211 | */ 212 | for (index = 0; index < pairs; index += 1) { 213 | FREE(ranges[index]); 214 | } 215 | 216 | FREE(ranges); 217 | return EXIT_FAILURE; 218 | } 219 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | /** 2 | * utils.c 3 | * 4 | * Copyright (C) 2018 Nickolas Burr 5 | */ 6 | 7 | #include "utils.h" 8 | 9 | /** 10 | * 11 | * String utilities (syntactic sugar, mostly) 12 | * 13 | */ 14 | 15 | /** 16 | * GNU `basename` polyfill. 17 | */ 18 | char *base_name (char *path) { 19 | char *base = strrchr(path, '/'); 20 | 21 | return base ? (base + 1) : path; 22 | } 23 | 24 | /** 25 | * `strcmp` wrapper 26 | */ 27 | int compare (char *one, char *two) { 28 | return strcmp(one, two); 29 | }; 30 | 31 | /** 32 | * `strcat` wrapper 33 | */ 34 | char *concat (char *buf, char *str) { 35 | return strcat(buf, str); 36 | } 37 | 38 | /** 39 | * `strcpy` wrapper 40 | */ 41 | char *copy (char *buf, char *str) { 42 | return strcpy(buf, str); 43 | } 44 | 45 | /** 46 | * GNU `dirname` polyfill. 47 | */ 48 | char *dir_name (char *path) { 49 | static const char *dot = "."; 50 | char *pslash; 51 | 52 | /** 53 | * Get trailing slash. 54 | */ 55 | pslash = !is_null(path) 56 | ? strrchr(path, '/') 57 | : NULL; 58 | 59 | if (pslash == path) { 60 | ++pslash; 61 | } else if (!is_null(pslash) && pslash[1] == '\0') { 62 | pslash = memchr(path, pslash - path, '/'); 63 | } 64 | 65 | if (!is_null(pslash)) { 66 | pslash[0] = '\0'; 67 | } else { 68 | path = (char *) dot; 69 | } 70 | 71 | return path; 72 | } 73 | 74 | /** 75 | * Get index of element in array. 76 | */ 77 | int index_of (char *element, char **array, size_t size) { 78 | unsigned int i; 79 | 80 | for (i = 0; i < size; i += 1) { 81 | if (!compare(array[i], element)) { 82 | return i; 83 | } 84 | } 85 | 86 | return -1; 87 | } 88 | 89 | /** 90 | * Check if element exists in array. 91 | */ 92 | int in_array (char *element, char **array, size_t size) { 93 | unsigned int i; 94 | 95 | for (i = 0; i < size; i += 1) { 96 | if (!compare(array[i], element)) { 97 | return 1; 98 | } 99 | } 100 | 101 | return 0; 102 | } 103 | 104 | /** 105 | * `strlen` wrapper 106 | */ 107 | int length (char *str) { 108 | return strlen(str); 109 | } 110 | 111 | /** 112 | * 113 | * Filesystem utilities 114 | * 115 | */ 116 | 117 | /** 118 | * Get pointer to directory by its pathname. 119 | */ 120 | DIR *get_dir (int *error, const char *path) { 121 | DIR *dp; 122 | 123 | *error = 0; 124 | 125 | if (!(dp = opendir(path))) { 126 | *error = 1; 127 | } 128 | 129 | return dp; 130 | } 131 | 132 | /** 133 | * Get pointer to file by its pathname. 134 | */ 135 | FILE *get_file (int *error, const char *filename, const char *filemode) { 136 | FILE *fp = fopen(filename, filemode); 137 | 138 | *error = 0; 139 | 140 | if (is_null(fp)) { 141 | *error = 1; 142 | } 143 | 144 | return fp; 145 | } 146 | 147 | /** 148 | * Get pointer to pipe. 149 | */ 150 | FILE *open_pipe (int *error, const char *command, const char *pipemode) { 151 | FILE *fp = popen(command, pipemode); 152 | 153 | *error = 0; 154 | 155 | if (is_null(fp)) { 156 | *error = 1; 157 | } 158 | 159 | return fp; 160 | } 161 | 162 | /** 163 | * Close pointer to pipe. 164 | */ 165 | int close_pipe (FILE *fp) { 166 | return pclose(fp); 167 | } 168 | 169 | /** 170 | * Determine if pathname is a directory. 171 | * 172 | * @note Adapted from https://goo.gl/ZmWfbx 173 | */ 174 | int is_dir (const char *path) { 175 | struct dirent *de; 176 | int is_dir, error; 177 | DIR *dp = get_dir(&error, path); 178 | 179 | if (!error) { 180 | while ((de = readdir(dp))) { 181 | #ifdef _DIRENT_HAVE_D_TYPE 182 | if (de->d_type != DT_UNKNOWN && de->d_type != DT_LNK) { 183 | is_dir = (de->d_type == DT_DIR); 184 | } else 185 | #endif 186 | { 187 | struct stat st; 188 | 189 | stat(de->d_name, &st); 190 | is_dir = S_ISDIR(st.st_mode); 191 | } 192 | 193 | if (is_dir) { 194 | return 1; 195 | } 196 | } 197 | } 198 | 199 | return 0; 200 | } 201 | 202 | /** 203 | * Determine if pathname is a regular file. 204 | * 205 | * @note Adapted from https://goo.gl/ZmWfbx 206 | */ 207 | int is_file (const char *path) { 208 | struct stat st; 209 | 210 | if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) { 211 | return 1; 212 | } 213 | 214 | return 0; 215 | } 216 | 217 | /** 218 | * Determine if pathname is writable. 219 | */ 220 | int is_writable (const char *path) { 221 | if (!access(path, W_OK)) { 222 | return 1; 223 | } 224 | 225 | return 0; 226 | } 227 | 228 | /** 229 | * 230 | * Type utilities 231 | * 232 | */ 233 | 234 | /** 235 | * `isdigit` wrapper 236 | */ 237 | int is_digit (int c) { 238 | return isdigit(c); 239 | } 240 | 241 | /** 242 | * Determine if string is numeric. 243 | */ 244 | int is_numeric (char *str) { 245 | int index; 246 | 247 | index = 0; 248 | 249 | while (str[index] != '\0') { 250 | if (!is_digit(str[index])) { 251 | return 0; 252 | } 253 | 254 | index++; 255 | } 256 | 257 | return 1; 258 | } 259 | --------------------------------------------------------------------------------