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