feat(landerctl): started custom cli tool; wrote config parser
							parent
							
								
									13b20715bf
								
							
						
					
					
						commit
						49c4c78242
					
				
							
								
								
									
										53
									
								
								landerctl
								
								
								
								
							
							
						
						
									
										53
									
								
								landerctl
								
								
								
								
							|  | @ -1,53 +0,0 @@ | ||||||
| #!/usr/bin/env sh |  | ||||||
| 
 |  | ||||||
| API_KEY=test |  | ||||||
| URL=http://localhost:18080 |  | ||||||
| 
 |  | ||||||
| if [[ "$2" != '-' ]]; then |  | ||||||
|     filename="$2" |  | ||||||
|     content_type="$(file --mime-type --brief $2)" |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| if [ "$1" = g ]; then |  | ||||||
|     exec curl -is "$URL/$2" |  |  | ||||||
|         sed -En 's/^[lL]ocation: (.*)/\1/p' |  | ||||||
| 
 |  | ||||||
| elif [ "$1" = s ] || [ "$1" = sl ]; then |  | ||||||
|     exec curl \ |  | ||||||
|         --fail \ |  | ||||||
|         -w "${URL}%header{location}" \ |  | ||||||
|         -XPOST \ |  | ||||||
|         -d "$2" \ |  | ||||||
|         -H "X-Api-Key: $API_KEY" \ |  | ||||||
|         "$URL/$1/$3" |  | ||||||
| 
 |  | ||||||
| elif [ "$1" = p ] || [ "$1" = pl ]; then |  | ||||||
|     exec curl \ |  | ||||||
|         --fail \ |  | ||||||
|         -w "${URL}%header{location}" \ |  | ||||||
|         -XPOST \ |  | ||||||
|         -H "X-Api-Key: $API_KEY" \ |  | ||||||
|         -H "X-Lander-Filename: ${filename}" \ |  | ||||||
|         --data-binary @"$2" \ |  | ||||||
|         "$URL/$1/$3" |  | ||||||
| 
 |  | ||||||
| elif [ "$1" = f ] || [ "$1" = fl ]; then |  | ||||||
|     exec curl \ |  | ||||||
|         --fail \ |  | ||||||
|         -v \ |  | ||||||
|         -w "${URL}%header{location}" \ |  | ||||||
|         -XPOST \ |  | ||||||
|         -H "X-Api-Key: $API_KEY" \ |  | ||||||
|         -H "X-Lander-Content-Type: ${content_type}" \ |  | ||||||
|         -H "X-Lander-Filename: ${filename}" \ |  | ||||||
|         -T "$2" \ |  | ||||||
|         "$URL/$1/$3" |  | ||||||
| 
 |  | ||||||
| elif [ "$1" = d ]; then |  | ||||||
|     exec curl \ |  | ||||||
|         --fail \ |  | ||||||
|         -XDELETE \ |  | ||||||
|         -H "X-Api-Key: $API_KEY" \ |  | ||||||
|         "$URL/$2" |  | ||||||
| fi |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | api_key = test | ||||||
|  | @ -0,0 +1,120 @@ | ||||||
|  | # https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ was a great
 | ||||||
|  | # base for this Makefile
 | ||||||
|  | 
 | ||||||
|  | -include config.mk | ||||||
|  | 
 | ||||||
|  | export CFLAGS | ||||||
|  | export LDFLAGS | ||||||
|  | 
 | ||||||
|  | BIN := $(BUILD_DIR)/$(BIN_FILENAME) | ||||||
|  | 
 | ||||||
|  | SRCS != find '$(SRC_DIR)' -iname '*.c' | ||||||
|  | 
 | ||||||
|  | SRCS_H != find include -iname '*.h' | ||||||
|  | 
 | ||||||
|  | OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.o) | ||||||
|  | DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) | ||||||
|  | 
 | ||||||
|  | _CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra -DLANDER_VERSION=\"$(VERSION)\" | ||||||
|  | _LDFLAGS := $(addprefix -L,$(LIB_DIRS)) $(addprefix -l,$(LIBS)) $(LDFLAGS) | ||||||
|  | 
 | ||||||
|  | .PHONY: all | ||||||
|  | all: $(BIN) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # =====COMPILATION=====
 | ||||||
|  | # Utility used by the CI to lint
 | ||||||
|  | .PHONY: objs | ||||||
|  | objs: $(OBJS) | ||||||
|  | 
 | ||||||
|  | $(BIN): $(OBJS) | ||||||
|  | 	$(CC) -o $@ $(OBJS) $(_LDFLAGS) | ||||||
|  | 
 | ||||||
|  | $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c | ||||||
|  | 	mkdir -p $(dir $@) | ||||||
|  | 	$(CC) $(_CFLAGS) -c $< -o $@ | ||||||
|  | 
 | ||||||
|  | $(BUILD_DIR)/$(THIRDPARTY_DIR)/%.c.o: $(THIRDPARTY_DIR)/%.c | ||||||
|  | 	mkdir -p $(dir $@) | ||||||
|  | 	$(CC) $(_CFLAGS) -c $< -o $@ | ||||||
|  | 
 | ||||||
|  | # =====TESTING=====
 | ||||||
|  | .PHONY: run | ||||||
|  | run: $(BIN) | ||||||
|  | 	LANDER_API_KEY=test \
 | ||||||
|  | 		LANDER_DATA_DIR=data \
 | ||||||
|  | 		'$(BUILD_DIR)/$(BIN_FILENAME)' | ||||||
|  | 
 | ||||||
|  | .PHONY: valgrind | ||||||
|  | valgrind: $(BIN) | ||||||
|  | 	LANDER_API_KEY=test \
 | ||||||
|  | 		LANDER_DATA_DIR=data \
 | ||||||
|  | 		valgrind '$(BUILD_DIR)/$(BIN_FILENAME)' | ||||||
|  | 
 | ||||||
|  | .PHONY: test | ||||||
|  | test: $(TARGETS_TEST) | ||||||
|  | 
 | ||||||
|  | .PHONY: test-mem | ||||||
|  | test-mem: $(TARGETS_MEM_TEST) | ||||||
|  | 
 | ||||||
|  | .PHONY: $(TARGETS_TEST) | ||||||
|  | $(TARGETS_TEST): test-%: % | ||||||
|  | 	./$^ | ||||||
|  | 
 | ||||||
|  | .PHONY: $(TARGETS_MEM_TEST) | ||||||
|  | $(TARGETS_MEM_TEST): test-mem-%: % | ||||||
|  | 	valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^ | ||||||
|  | 
 | ||||||
|  | .PHONY: build-test | ||||||
|  | build-test: $(BINS_TEST) | ||||||
|  | 
 | ||||||
|  | $(BINS_TEST): %: %.c.o | ||||||
|  | 	$(CC) \
 | ||||||
|  | 		$^ -o $@ | ||||||
|  | 
 | ||||||
|  | # Along with the include directory, each test includes $(TEST_DIR) (which
 | ||||||
|  | # contains the acutest.h header file), and the src directory of the module it's
 | ||||||
|  | # testing. This allows tests to access internal methods, which aren't publicly
 | ||||||
|  | # exposed.
 | ||||||
|  | $(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c | ||||||
|  | 	mkdir -p $(dir $@) | ||||||
|  | 	$(CC) $(_CFLAGS) -I$(TEST_DIR) \
 | ||||||
|  | 		-I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \
 | ||||||
|  | 		-c $< -o $@ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # =====MAINTENANCE=====
 | ||||||
|  | .PHONY: lint | ||||||
|  | lint: | ||||||
|  | 	clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) | ||||||
|  | 
 | ||||||
|  | .PHONY: fmt | ||||||
|  | fmt: | ||||||
|  | 	clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) | ||||||
|  | 
 | ||||||
|  | .PHONY: check | ||||||
|  | check: | ||||||
|  | 	mkdir -p $(BUILD_DIR)/cppcheck | ||||||
|  | 	cppcheck \
 | ||||||
|  | 		$(addprefix -I,$(INC_DIRS)) \
 | ||||||
|  | 		--cppcheck-build-dir=$(BUILD_DIR)/cppcheck \
 | ||||||
|  | 		--project=compile_commands.json \
 | ||||||
|  | 		--error-exitcode=1 \
 | ||||||
|  | 		--enable=warning,style \
 | ||||||
|  | 		--inline-suppr \
 | ||||||
|  | 		--check-level=exhaustive \
 | ||||||
|  | 		--quiet \
 | ||||||
|  | 		-j$(shell nproc) | ||||||
|  | 
 | ||||||
|  | .PHONY: clean | ||||||
|  | clean: | ||||||
|  | 	rm -rf $(BUILD_DIR) | ||||||
|  | 
 | ||||||
|  | .PHONY: bear | ||||||
|  | bear: clean | ||||||
|  | 	bear -- make | ||||||
|  | 	bear --append -- make build-test | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Make make aware of the .d files
 | ||||||
|  | -include $(DEPS) | ||||||
|  | @ -0,0 +1,22 @@ | ||||||
|  | VERSION := 0.2.0 | ||||||
|  | 
 | ||||||
|  | BIN_FILENAME = landerctl | ||||||
|  | 
 | ||||||
|  | BUILD_DIR      = build | ||||||
|  | SRC_DIR        = src | ||||||
|  | TEST_DIR       = test | ||||||
|  | 
 | ||||||
|  | INC_DIRS  = include | ||||||
|  | LIBS      = magic curl | ||||||
|  | LIB_DIRS  =  | ||||||
|  | 
 | ||||||
|  | # -MMD: generate a .d file for every source file. This file can be imported by
 | ||||||
|  | #  make and makes make aware that a header file has been changed, ensuring an
 | ||||||
|  | #  object file is also recompiled if only a header is changed.
 | ||||||
|  | # -MP: generate a dummy target for every header file (according to the  docs it
 | ||||||
|  | #  prevents some errors when removing header files)
 | ||||||
|  | CFLAGS = -MMD -MP -g | ||||||
|  | 
 | ||||||
|  | # When compiling release builds, these flags are better
 | ||||||
|  | # CLAGS = -O3
 | ||||||
|  | # LDFLAGS = -flto
 | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | #ifndef LANDERCTL | ||||||
|  | #define LANDERCTL | ||||||
|  | 
 | ||||||
|  | typedef struct landerctl_cfg { | ||||||
|  |   const char *api_key; | ||||||
|  | } landerctl_cfg; | ||||||
|  | 
 | ||||||
|  | typedef enum landerctl_cfg_err { | ||||||
|  |   landerctl_cfg_err_ok = 0, | ||||||
|  |   landerctl_cfg_err_not_found, | ||||||
|  |   landerctl_cfg_err_invalid, | ||||||
|  |   landerctl_cfg_err_incomplete, | ||||||
|  | } landerctl_cfg_err; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Try to parse the required config arguments from the config file | ||||||
|  |  * | ||||||
|  |  * @param out config to write values to. Existing values are overwritten | ||||||
|  |  * @param path path to config file | ||||||
|  |  */ | ||||||
|  | landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path); | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
|  | @ -0,0 +1,59 @@ | ||||||
|  | #include <regex.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
|  | #include "landerctl.h" | ||||||
|  | 
 | ||||||
|  | static const char cfg_line_regex_expr[] = "^([^ ]+) *= *([^ ]+)$"; | ||||||
|  | 
 | ||||||
|  | landerctl_cfg_err landerctl_cfg_parse(landerctl_cfg *out, const char *path) { | ||||||
|  |   FILE *f = fopen(path, "r"); | ||||||
|  | 
 | ||||||
|  |   if (f == NULL) { | ||||||
|  |     return landerctl_cfg_err_not_found; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   regex_t cfg_line_regex; | ||||||
|  |   regcomp(&cfg_line_regex, cfg_line_regex_expr, REG_EXTENDED); | ||||||
|  | 
 | ||||||
|  |   // Accept lines of at most 256 lines
 | ||||||
|  |   char line[256]; | ||||||
|  |   landerctl_cfg_err res = landerctl_cfg_err_incomplete; | ||||||
|  | 
 | ||||||
|  |   while (fgets(line, sizeof(line), f) != NULL) { | ||||||
|  |     // Last character might be a newline
 | ||||||
|  |     size_t len = strlen(line); | ||||||
|  | 
 | ||||||
|  |     if (line[len - 1] == '\n') { | ||||||
|  |       line[len - 1] = '\0'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     regmatch_t reg_groups[3]; | ||||||
|  | 
 | ||||||
|  |     if (regexec(&cfg_line_regex, line, 3, reg_groups, 0) != 0) { | ||||||
|  |       res = landerctl_cfg_err_not_found; | ||||||
|  | 
 | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // api_key is currently the only value we parse
 | ||||||
|  |     int key_len = reg_groups[1].rm_eo - reg_groups[1].rm_so; | ||||||
|  | 
 | ||||||
|  |     if ((strlen("api_key") == key_len) && | ||||||
|  |         (strncmp("api_key", &line[reg_groups[1].rm_so], key_len) == 0)) { | ||||||
|  |       int val_len = reg_groups[2].rm_eo - reg_groups[2].rm_so; | ||||||
|  |       char *buf = malloc(val_len + 1); | ||||||
|  |       strncpy(buf, &line[reg_groups[2].rm_so], val_len); | ||||||
|  | 
 | ||||||
|  |       out->api_key = buf; | ||||||
|  | 
 | ||||||
|  |       res = landerctl_cfg_err_ok; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   fclose(f); | ||||||
|  | 
 | ||||||
|  |   return res; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,87 @@ | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <sys/stat.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | 
 | ||||||
|  | #include <curl/curl.h> | ||||||
|  | #include <magic.h> | ||||||
|  | 
 | ||||||
|  | #include "landerctl.h" | ||||||
|  | 
 | ||||||
|  | const char default_cfg_path[] = ".landerrc"; | ||||||
|  | 
 | ||||||
|  | int main(int argc, char **argv) { | ||||||
|  |   landerctl_cfg cfg; | ||||||
|  |   char *err_msg = NULL; | ||||||
|  | 
 | ||||||
|  |   switch (landerctl_cfg_parse(&cfg, default_cfg_path)) { | ||||||
|  |   case landerctl_cfg_err_ok: | ||||||
|  |     break; | ||||||
|  |   case landerctl_cfg_err_not_found: | ||||||
|  |     err_msg = "Config file not found"; | ||||||
|  |     break; | ||||||
|  |   case landerctl_cfg_err_invalid: | ||||||
|  |     err_msg = "Invalid config file"; | ||||||
|  |     break; | ||||||
|  |   case landerctl_cfg_err_incomplete: | ||||||
|  |     err_msg = "Incomplete config file"; | ||||||
|  |     break; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (err_msg != NULL) { | ||||||
|  |     fprintf(stderr, "%s\n", err_msg); | ||||||
|  |     exit(1); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /* struct stat sb; */ | ||||||
|  | 
 | ||||||
|  |   /* stat(argv[1], &sb); */ | ||||||
|  | 
 | ||||||
|  |   /* printf("file size: %lu\n", sb.st_size); */ | ||||||
|  | 
 | ||||||
|  |   /* FILE *f = fopen(argv[1], "rb"); */ | ||||||
|  | 
 | ||||||
|  |   /* if (f == NULL) { */ | ||||||
|  |   /*   printf("Couldn't open file.\n"); */ | ||||||
|  |   /*   exit(1); */ | ||||||
|  |   /* } */ | ||||||
|  | 
 | ||||||
|  |   /* curl_global_init(CURL_GLOBAL_ALL); */ | ||||||
|  | 
 | ||||||
|  |   /* CURL *curl = curl_easy_init(); */ | ||||||
|  | 
 | ||||||
|  |   /* if (curl == NULL) { */ | ||||||
|  |   /*   exit(1); */ | ||||||
|  |   /* } */ | ||||||
|  | 
 | ||||||
|  |   /* curl_easy_setopt(curl, CURLOPT_URL, "http://localhost:18080/f/"); */ | ||||||
|  |   /* curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); */ | ||||||
|  |   /* curl_easy_setopt(curl, CURLOPT_READDATA, f); */ | ||||||
|  | 
 | ||||||
|  |   /* curl_off_t file_size = sb.st_size; */ | ||||||
|  |   /* /1* curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, file_size); *1/ */ | ||||||
|  |   /* curl_easy_setopt(curl, CURLOPT_POST, 1L); */ | ||||||
|  |   /* curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, file_size); */ | ||||||
|  | 
 | ||||||
|  |   /* magic_t cookie = magic_open(MAGIC_MIME_TYPE); */ | ||||||
|  |   /* magic_load(cookie, NULL); */ | ||||||
|  |   /* const char *mime_type = magic_file(cookie, argv[1]); */ | ||||||
|  | 
 | ||||||
|  |   /* char content_type_header[strlen(mime_type) + 24]; */ | ||||||
|  |   /* sprintf(content_type_header, "X-Lander-Content-Type: %s", mime_type); */ | ||||||
|  | 
 | ||||||
|  |   /* char content_length_header[32]; */ | ||||||
|  |   /* sprintf(content_length_header, "Content-Length: %lu", sb.st_size); */ | ||||||
|  | 
 | ||||||
|  |   /* struct curl_slist *list = NULL; */ | ||||||
|  |   /* list = curl_slist_append(list, content_type_header); */ | ||||||
|  |   /* list = curl_slist_append(list, content_length_header); */ | ||||||
|  |   /* list = curl_slist_append(list, "X-Api-Key: test"); */ | ||||||
|  | 
 | ||||||
|  |   /* curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); */ | ||||||
|  | 
 | ||||||
|  |   /* curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); */ | ||||||
|  |   /* curl_easy_perform(curl); */ | ||||||
|  | 
 | ||||||
|  |   /* curl_slist_free_all(list); */ | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue