diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8b9825d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.c] +indent_size = 4 + +[*.{markdown,md}] +trim_trailing_whitespace = false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5aa0ad2..2cfcde0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,24 +1,28 @@ - ## What does it do? - + ## Why the change? - + ## How can this be tested? - + ## Where to start code review? - + ## Relevant tickets? - + ## Questions? + +### Checklist + +- [ ] I have checked there aren't any existing PRs open to fix this issue/add this feature. +- [ ] I have compiled the code with `make` and tested the change in an active installation with `sudo make install`. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 641b340..09a5f1e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,9 +12,7 @@ jobs: name: Brew Formula steps: - - name: Checkout - uses: actions/checkout@v2 - + - uses: actions/checkout@v4 - name: Update Homebrew Formula uses: dawidd6/action-homebrew-bump-formula@v3 with: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index ebbeb25..0000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: 'Manage stale issues' - -on: - schedule: - - cron: '0 0 * * *' - -jobs: - stale: - runs-on: ubuntu-latest - - steps: - - - name: Stale Bot - uses: actions/stale@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - # Number of days of inactivity before an issue or PR is considered stale - days-before-stale: 15 - - # Number of days of inactivity before a stale issue or PR is closed - days-before-close: 30 - - # Issues or PRs with these labels will never be considered stale - exempt-pr-labels: up for grabs - - # Label to use when marking an issue or PR as stale - stale-pr-label: waiting - - # Comment to post when marking an issue or PR as stale - stale-pr-message: | - Hi all! This thread has not had any recent activity. - Are there any updates? Thanks! - - # Comment to post when closing a stale issue or PR - close-pr-message: | - Hi everyone. - This thread is being closed as there was no response to the previous prompt. - However, please leave a comment whenever you're ready to resume, - so the thread can be reopened. Thanks again! diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0c8cac2..2d0c3ee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,8 +14,7 @@ jobs: steps: - - name: Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -34,5 +33,11 @@ jobs: - name: Compile run: make V=1 DEBUG= CC=${{ matrix.compiler }} LD=${{ matrix.compiler }} - - name: Test + - name: Test printing tldr version run: ./tldr --version + + - name: Test printing tldr list + run: ./tldr --list + + - name: Test printing tldr page + run: ./tldr tar diff --git a/.gitignore b/.gitignore index 8a8e9a8..48cc010 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ infer-out *.i*86 *.x86_64 *.hex + +# VSCode +.vscode diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f6633..a14b542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,36 +7,107 @@ Deprecated features will be kept for any following maintenance release, and will be removed after two major releases. ## [Unreleased] + +## v1.6.0 - 2023-05-09 + +### Added + +- Add support for auto updating local data if too old + +### Fixed + +- Fix make finding lib directories with M1 Homebrew + +## v1.5.0 - 2022-07-03 + +### Added + +- Add fish completion for `--list`, `--linux`, `--osx`, and `--sunos` flags + +### Fixed + +- Fix typo of "database" in the usage output +- Fix fish completion not escaping characters +- Make fish completion reflect actual usage of `tldr` better + +## v1.4.3 - 2022-04-11 + +### Fixed + +- Fix segfault on trying to update cache +- Fix segfault when failing to open cache directory +- Bubble error code appropriately when using `--list` + +## v1.4.2 - 2021-11-13 + +### Fixed + +- Fix version not being updated in source code + +## v1.4.1 - 2021-11-12 + +### Fixed + +- Fix wrong directory name when extracting files from zip cache download + +## v1.4.0 - 2021-11-07 + +### Added + - Add fish completion (see README.md for details) +- Improve output messages for various options +- Add `--list option` +- Add Windows as available platform +- Allow using `TLDR_CACHE_DIR` env var to specify cache directory +- Add `--linux`, `--osx`, and `--sunos` flags as shorthand for `--platform=` +- Add yum support to the `./deps.sh` script +### Fixed + +- Do not check last cache update date when updating cache +- Add yes flag to apt install in ./deps.sh script +- Add blank line at end of output +- Remove `-ansi` flag when compiling +- Use `tldr` instead of `tldr-pages` as name for cache folder + +### Miscellaneous + +- Rename repository from `tldr-cpp-client` to `tldr-c-client` +- Move from Travis-CI to GitHub actions ## v1.3.0 - 2016-09-09 + ### Added + - Initial release in core homebrew repository - Add zsh / bash completion (see README.md for details) ### Fixed + - Fix compiling on old Linux distributions ## v1.2.0 - 2016-04-06 + ### Added + - Download local database at first run - Add SunOS as supported tldr platform - Add --clear-cache option, to clear the local database - ## v1.1.0 - 2016-01-18 + ### Added + - Error messages ### Fixed -- Typos +- Typos ## v1.0.0 - 2015-12-30 -- Initial release +- Initial release - diff --git a/Makefile b/Makefile index 56b9493..730aff5 100644 --- a/Makefile +++ b/Makefile @@ -37,27 +37,43 @@ endif HAS_GIT := $(shell type git > /dev/null 2>&1 && echo "1" || echo "0") IS_GITREPO := $(shell [ -d .git ] && echo "1" || echo "0") ifeq (0,$(filter 0,$(HAS_GIT) $(IS_GITREPO))) -VER := v1.3.0 +VER := v1.6.0 else VER := $(shell git describe --tags --always --dirty) endif -# Preprocessor Flags +# Set Flags ALL_CPPFLAGS := $(CPPFLAGS) -DVERSION='"$(VER)"' +ALL_LDFLAGS := $(LDFLAGS) -L/usr/lib +ALL_LDLIBS := -lc -lm -lcurl -lzip ALL_CPPFLAGS += -D_GNU_SOURCE ALL_CPPFLAGS += $(shell pkg-config --cflags libzip) + ALL_CPPFLAGS += -I/usr/include ALL_CPPFLAGS += -I/usr/local/include -ALL_CPPFLAGS += -I/usr/local/opt/curl/include -ALL_CPPFLAGS += -I/usr/local/opt/libzip/include - -# Linker Flags -ALL_LDFLAGS := $(LDFLAGS) -L/usr/lib ALL_LDFLAGS += -L/usr/local/lib -ALL_LDFLAGS += -L/usr/local/opt/curl/lib -ALL_LDFLAGS += -L/usr/local/opt/libzip/lib -ALL_LDLIBS := -lc -lm -lcurl -lzip +ifneq (,$(wildcard /opt/homebrew/.*)) + ALL_CCPFLAGS += -I/opt/homebrew/include + ALL_CPPFLAGS += -I/opt/homebrew/lib +endif +ifneq (,$(wildcard /usr/local/opt/curl/.*)) + ALL_CPPFLAGS += -I/usr/local/opt/curl/include + ALL_LDFLAGS += -L/usr/local/opt/curl/lib +endif +ifneq (,$(wildcard /opt/homebrew/opt/curl/.)) + ALL_CPPFLAGS += -I/opt/homebrew/opt/curl/include + ALL_LDFLAGS += -L/opt/homebrew/opt/curl/lib +endif + +ifneq (,$(wildcard /usr/local/opt/libzip/.)) + ALL_CPPFLAGS += -I/usr/local/opt/libzip/include + ALL_LDFLAGS += -L/usr/local/opt/libzip/lib +endif +ifneq (,$(wildcard /opt/homebrew/opt/libzip/.)) + ALL_CPPFLAGS += -I/opt/homebrew/opt/libzip/include + ALL_LDFLAGS += -L/opt/homebrew/opt/libzip/lib +endif # Source, Binaries, Dependencies SRC := $(shell find $(SRCDIR) -type f -name '*.c') @@ -126,6 +142,10 @@ install: all $(MANSRC) $(INSTALL) -d $(MANPATH) $(INSTALL) $(MANSRC) $(MANPATH) +uninstall: + $(RM) $(PREFIX)/bin/$(TARGET) + $(RM) $(MANPATH)/$(TARGET).1 + clean: $(RM) $(OBJ) $(DEP) $(BIN) $(RMDIR) $(OBJDIR) $(BINDIR) 2> /dev/null; true diff --git a/README.md b/README.md index 3622534..a9a1aef 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,42 @@ # tldr c client -[![Build Status](https://img.shields.io/github/workflow/status/tldr-pages/tldr-c-client/Tests.svg?style=flat-square)](https://github.com/tldr-pages/tldr-c-client/actions) +[![Build Status](https://img.shields.io/github/actions/workflow/status/tldr-pages/tldr-c-client/.github/workflows/tests.yml?branch=main)](https://github.com/tldr-pages/tldr-c-client/actions) A command line client for tldr, written in plain ISO C90. - ## Installing -On OS X, the client can be installed through [homebrew](http://brew.sh/). +On OS X, the client can be installed through [Homebrew](http://brew.sh/). -``` -# To install latest development version +```shell +# To install the latest development version brew install tldr --HEAD # To install the latest stable release brew install tldr ``` -To build the latest version from source: +On Arch Linux, the client can be installed through [the AUR](https://aur.archlinux.org/packages/tldr-git) using an AUR helper such as yay. + +```shell +yay -S tldr-git ``` + +To build the latest version from the source: + +```shell git clone https://github.com/tldr-pages/tldr-c-client.git cd tldr-c-client -./deps.sh # install dependencies +sudo ./deps.sh # install dependencies make # build tldr -make install # install tldr +sudo make install # install tldr +``` + +To remove the version installed from the source: + +```shell +sudo make uninstall ``` The default prefix for installation is `/usr/local/bin`. @@ -34,25 +46,24 @@ The default prefix for installation is `/usr/local/bin`. Building the `tldr` client is pretty straightforward. -#### Requirements +### Requirements - `clang`/`gcc` - `libcurl` (`brew install curl` / `apt-get install libcurl-dev` / `apt-get install libcurl4-openssl-dev`) - `libzip` (`brew install libzip` / `apt-get install libzip-dev`) - `pkg-config` (`brew install pkg-config` / `apt-get install pkg-config`) -#### Compiling +### Compiling The [`Makefile`](https://github.com/tldr-pages/tldr-c-client/blob/master/Makefile) -in the root directory has all you need for builing the project. +in the root directory has all you need for building the project. Just call `make` and `tldr` will build itself. -``` +```shell make ``` - ## Autocompletion Autocompletion is supported for `bash`, `zsh`, and `fish` and can be added by sourcing @@ -61,40 +72,46 @@ the correct autocompletion file. The files `autocomplete.zsh`, `autocomplete.bash`, and `autocomplete.fish` can be found in the `autocomplete` folder in the root of the repository. -#### Installation +### Installation -To install the autocompletion, just move the script for your shell to a an easy -to access directory (like your home directory), and source it in your `.bashrc` or `.zshrc`. +To install the autocompletion, just move the script for your shell to an easy +to access the directory (like your home directory), and source it in your `.bashrc` or `.zshrc`. Example for zsh: -``` +```shell mv autocomplete/complete.zsh ~/.tldr.complete echo "source ~/.tldr.complete" >> ~/.zshrc ``` - ## Usage -``` -usage: ./tldr [-v] [OPTION]... SEARCH +```shell +usage: tldr [-v] [OPTION]... SEARCH available commands: - -v print verbose output - --version print version and exit - -h, --help print this help and exit - -u, --update update local database - -c, --clear-cache clear local database - -p, --platform=PLATFORM select platform, supported are linux / osx / sunos / common + -v print verbose output + --version print version and exit + -h, --help print this help and exit + -u, --update update local database + -c, --clear-cache clear local database + -l, --list list all entries in the local database + -p, --platform=PLATFORM select platform, supported are linux / osx / sunos / windows / common + --linux show command page for Linux + --osx show command page for OSX + --sunos show command page for SunOS -r, --render=PATH render a local page for testing purposes + -C, --color force color display ``` +## Configuration + +To prevent `tldr` from automatically updating its database, set the environment variable `TLDR_AUTO_UPDATE_DISABLED`. ## Contributing Please read the [CONTRIBUTING.md](https://github.com/tldr-pages/tldr-c-client/blob/master/CONTRIBUTING.md) for details. - ## License The MIT License (MIT) - see [LICENSE](https://github.com/tldr-pages/tldr-c-client/blob/master/LICENSE) for details. diff --git a/autocomplete/complete.bash b/autocomplete/complete.bash index 1a6097c..a97a46e 100644 --- a/autocomplete/complete.bash +++ b/autocomplete/complete.bash @@ -6,7 +6,7 @@ _tldr_get_files() { local ret - local files="$(find $HOME/.tldrc/tldr-master/pages/$1 -name '*.md' -exec basename {} .md \;)" + local files="$(find $HOME/.tldrc/tldr/pages/$1 -name '*.md' -exec basename {} .md \;)" IFS=$'\n\t' for f in $files; do @@ -23,7 +23,7 @@ _tldr_complete() { elif [ "$word" = "--" ]; then cmpl=$(echo $'--version\n--help\n--update\n--clear-cache\n--platform\n--render' | sort) else - if [ -d "$HOME/.tldrc/tldr-master/pages" ]; then + if [ -d "$HOME/.tldrc/tldr/pages" ]; then local platform="$(uname)" cmpl="$(_tldr_get_files common | sort | uniq)" if [ "$platform" = "Darwin" ]; then diff --git a/autocomplete/complete.fish b/autocomplete/complete.fish index 6d74c85..9fecd98 100644 --- a/autocomplete/complete.fish +++ b/autocomplete/complete.fish @@ -1,19 +1,51 @@ -complete -c tldr -xf -s v -d "print verbose output" -complete -c tldr -xf -l version -d "print version and exit" -complete -c tldr -xf -s h -l help -d "print this help and exit" -complete -c tldr -xf -s u -l update -d "update local database" -complete -c tldr -xf -s c -l clear-cache -d "clear local database" -complete -c tldr -rf -f -s p -l platform -a "linux osx sunos common" -d "select platform, supported are linux / osx / sunos / common" -complete -c tldr -r -s r -l render -a PATH -d "render a local page for testing purpose" +function __tldr_not_contain_standalone_opt + __fish_not_contain_opt -s v + and __fish_not_contain_opt version + and __fish_not_contain_opt -s l list + and __fish_not_contain_opt -s h help + and __fish_not_contain_opt -s u update + and __fish_not_contain_opt -s c clear-cache +end + +function __tldr_no_os_choice_opt + __fish_not_contain_opt linux osx sunos + and __fish_not_contain_opt -s r render + and __tldr_not_contain_standalone_opt +end + +function __tldr_no_os_choice_opt_nor_p + __tldr_no_os_choice_opt + and __fish_not_contain_opt -s p platform +end + +function __tldr_positional + __fish_use_subcommand + and __tldr_not_contain_standalone_opt +end + +function __tldr_positional_no_os + __tldr_positional + and __fish_not_contain_opt linux osx sunos + and __fish_not_contain_opt -s p platform +end + +complete -c tldr -f -n "__fish_not_contain_opt -s v" -s v -d "print verbose output" +complete -c tldr -f -n __fish_is_first_arg -l version -d "print version and exit" +complete -c tldr -f -n __fish_is_first_arg -s l -l list -d "list all entries in the local database" +complete -c tldr -f -n __fish_is_first_arg -s h -l help -d "print help and exit" +complete -c tldr -f -n __fish_is_first_arg -s u -l update -d "update local database" +complete -c tldr -f -n __fish_is_first_arg -s c -l clear-cache -d "clear local database" +complete -c tldr -x -n __tldr_no_os_choice_opt -s p -l platform -d "select platform" -a "linux osx sunos common" +complete -c tldr -f -n __tldr_no_os_choice_opt_nor_p -l linux -d "show command page for Linux" +complete -c tldr -f -n __tldr_no_os_choice_opt_nor_p -l osx -d "show command page for macOS" +complete -c tldr -f -n __tldr_no_os_choice_opt_nor_p -l sunos -d "show command page for SunOS" +complete -c tldr -rF -n __tldr_positional_no_os -s r -l render -d "render a local page for testing purposes" function __tldr_get_files - set -l files (basename -s .md (find $HOME/.tldrc/tldr-master/pages/$argv[1] -name '*.md')) - for f in $files - echo $f - end + basename -s .md (find $HOME/.tldrc/tldr/pages/$argv[1] -name '*.md') | string escape end -if test -d "$HOME/.tldrc/tldr-master/pages" +if test -d "$HOME/.tldrc/tldr/pages" set -l cmpl (__tldr_get_files common) switch (uname) @@ -25,7 +57,9 @@ if test -d "$HOME/.tldrc/tldr-master/pages" set cmpl $cmpl (__tldr_get_files sunos) end - complete -c tldr -a (echo $cmpl | sort | uniq) + complete -c tldr -f -a "$cmpl" -n __tldr_positional end +complete -c tldr -f + functions -e __tldr_get_files diff --git a/autocomplete/complete.zsh b/autocomplete/complete.zsh index fb04525..fb57d09 100644 --- a/autocomplete/complete.zsh +++ b/autocomplete/complete.zsh @@ -8,7 +8,7 @@ _tldr_get_files() { local ret - local files="$(find $HOME/.tldrc/tldr-master/pages/$1 -name '*.md' -exec basename {} .md \;)" + local files="$(find $HOME/.tldrc/tldr/pages/$1 -name '*.md' -exec basename -s .md {} +)" IFS=$'\n\t' for f in $files; do @@ -24,7 +24,7 @@ _tldr_complete() { elif [ "$word" = "--" ]; then cmpl=$(echo $'--version\n--help\n--update\n--clear-cache\n--platform\n--render' | sort) else - if [ -d "$HOME/.tldrc/tldr-master/pages" ]; then + if [ -d "$HOME/.tldrc/tldr/pages" ]; then local platform="$(uname)" cmpl="$(_tldr_get_files common | sort | uniq)" if [ "$platform" = "Darwin" ]; then diff --git a/src/local.c b/src/local.c index bd8a1f8..bf4cc9a 100644 --- a/src/local.c +++ b/src/local.c @@ -52,13 +52,16 @@ check_localdate(void) curtime = time(NULL); oldtime = strtol(buffer, NULL, 10); difftime = curtime - oldtime; - if (difftime > (60 * 60 * 24 * 7 * 2)) { + if (difftime > (60 * 60 * 24 * 7 * 2) && !getenv(PREVENT_UPDATE_ENV_VARIABLE)) { /* *INDENT-OFF* */ - fprintf(stdout, "%s", ANSI_BOLD_ON); - fprintf(stdout, "%s", ANSI_COLOR_CODE_FG); - fprintf(stdout, "Local data is older than two weeks, use --update to update it.\n\n"); - fprintf(stdout, "%s", ANSI_COLOR_RESET_FG); - fprintf(stdout, "%s", ANSI_BOLD_OFF); + fprintf(stdout, "Local database is older than two weeks, attempting to update it...\n"); + fprintf(stdout, "To prevent automatic updates, set the environment variable %s.\n", PREVENT_UPDATE_ENV_VARIABLE); + if (update_localdb(0)) { + fprintf(stdout, "%s%s", ANSI_BOLD_ON, ANSI_COLOR_CODE_FG); + fprintf(stdout, "Failed to update local database.\n"); + fprintf(stdout, "%s%s", ANSI_COLOR_RESET_FG, ANSI_BOLD_OFF); + } + fprintf(stdout, "\n"); /* *INDENT-ON* */ } @@ -137,87 +140,102 @@ int update_localdb(int verbose) { struct stat s; - char tmp[STRBUFSIZ]; - char outpath[STRBUFSIZ]; - char outfile[STRBUFSIZ]; - char outhome[STRBUFSIZ]; + char tldr_home[STRBUFSIZ]; /* $HOME/TLDR_HOME */ + char tldr_home_db[STRBUFSIZ]; /* $HOME/TLDR_HOME/TLDR_DIR */ + char temp_dir[STRBUFSIZ]; /* $HOME/TLDR_HOME/TMP_DIR */ + char zip_archive_path[STRBUFSIZ]; /* $HOME/TLDR_HOME/TMP_DIR/TMP_FILE */ + char extracted_contents[STRBUFSIZ]; /* $HOME/TLDR_HOME/TMP_DIR/TLDR_ZIP_DIR */ char const *homedir; size_t outlen; - outlen = 0; - if (sstrncat(outfile, &outlen, STRBUFSIZ, TMP_DIR, TMP_DIR_LEN)) - return 1; - if (mkdtemp(outfile) == NULL) { - fprintf(stderr, "Error: Creating Directory: %s\n", outfile); + homedir = gethome(); + if (homedir == NULL) { + fprintf(stderr, "Error: HOME not existent\n"); return 1; } + /* Create TLDR_HOME if non-existent */ outlen = 0; - - /* it's guaranteed, that outfile is only TMP_DIR_LEN long */ - if (sstrncat(outpath, &outlen, STRBUFSIZ, outfile, TMP_DIR_LEN)) + if (sstrncat(tldr_home, &outlen, STRBUFSIZ, homedir, strlen(homedir))) { return 1; - - outlen = TMP_DIR_LEN; - if (sstrncat(outfile, &outlen, STRBUFSIZ, TMP_FILE, TMP_FILE_LEN)) + } + if (sstrncat(tldr_home, &outlen, STRBUFSIZ, TLDR_HOME, TLDR_HOME_LEN)) { return 1; - - if (download_file(ZIP_URL, outfile, verbose)) { - fprintf(stderr, "Error: Downloading File: %s\n", ZIP_URL); + } + if (mkdir(tldr_home, 0755) > 0 && errno != EEXIST) { + fprintf(stderr, "Error: Could not create directory: %s\n", tldr_home); return 1; } - if (unzip(outfile, outpath)) { - rm(outpath, 0); + /* Set up the temp directory */ + outlen = 0; + if (sstrncat(temp_dir, &outlen, STRBUFSIZ, tldr_home, strlen(tldr_home))) { + return 1; + } + if (sstrncat(temp_dir, &outlen, STRBUFSIZ, TMP_DIR, TMP_DIR_LEN)) { + return 1; + } + if (mkdir(temp_dir, 0755) > 0 && errno != EEXIST) { + fprintf(stderr, "Error: Could not create directory: %s\n", temp_dir); return 1; } + /* The update zip file will be downloaded to $HOME/TLDR_HOME/TMP_DIR/TMP_FILE */ outlen = 0; - if (sstrncat(tmp, &outlen, STRBUFSIZ, outpath, strlen(outpath))) + if (sstrncat(zip_archive_path, &outlen, STRBUFSIZ, temp_dir, strlen(temp_dir))) { return 1; - if (sstrncat(tmp, &outlen, STRBUFSIZ, TLDR_DIR, TLDR_DIR_LEN)) + } + if (sstrncat(zip_archive_path, &outlen, STRBUFSIZ, TMP_FILE, TMP_FILE_LEN)) { return 1; + } - homedir = gethome(); - if (homedir == NULL) { - fprintf(stderr, "Error: HOME not existant\n"); + /* Download and unzip the file */ + if (download_file(ZIP_URL, zip_archive_path, verbose)) { + fprintf(stderr, "Error: Downloading file: %s\n", ZIP_URL); + return 1; + } + + if (unzip(zip_archive_path, temp_dir)) { + rm(temp_dir, 0); return 1; } outlen = 0; - if (sstrncat(outhome, &outlen, STRBUFSIZ, homedir, strlen(homedir))) { + if (sstrncat(extracted_contents, &outlen, STRBUFSIZ, temp_dir, strlen(temp_dir))) { return 1; } - if (sstrncat(outhome, &outlen, STRBUFSIZ, TLDR_HOME, TLDR_HOME_LEN)) { + if (sstrncat(extracted_contents, &outlen, STRBUFSIZ, TLDR_ZIP_DIR, TLDR_ZIP_DIR_LEN)) { return 1; } - if (mkdir(outhome, 0755) > 0 && errno != EEXIST) { - fprintf(stderr, "Error: Could Not Create Directory: %s\n", outhome); - rm(outpath, 0); + /* tldr_home_db is where we want to move the update contents */ + outlen = 0; + if (sstrncat(tldr_home_db, &outlen, STRBUFSIZ, tldr_home, strlen(tldr_home))) { return 1; } - - if (sstrncat(outhome, &outlen, STRBUFSIZ, TLDR_DIR, TLDR_DIR_LEN)) + if (sstrncat(tldr_home_db, &outlen, STRBUFSIZ, TLDR_DIR, TLDR_HOME_LEN)) { return 1; - if (sstrncat(outhome, &outlen, STRBUFSIZ, "/", 1)) + } + if (sstrncat(tldr_home_db, &outlen, STRBUFSIZ, "/", 1)) { return 1; + } - if ((stat(outhome, &s) == 0) && (S_ISDIR(s.st_mode))) { - if (rm(outhome, 0)) { - fprintf(stderr, "Error: Could Not Remove: %s\n", outhome); + /* Remove the old database */ + if ((stat(tldr_home_db, &s) == 0) && (S_ISDIR(s.st_mode))) { + if (rm(tldr_home_db, 0)) { + fprintf(stderr, "Error: Could not remove the old database: %s\n", tldr_home_db); return 1; } } - if (rename(tmp, outhome)) { - fprintf(stderr, "Error: Could Not Rename: %s to %s\n", tmp, outhome); - rm(outpath, 0); + if (rename(extracted_contents, tldr_home_db)) { + fprintf(stderr, "Error: Could not rename: %s to %s\n", extracted_contents, tldr_home_db); + rm(temp_dir, 0); return 1; } - if (rm(outpath, 0)) { - fprintf(stderr, "Error: Could Not Remove: %s\n", outpath); + if (rm(temp_dir, 0)) { + fprintf(stderr, "Error: Could not remove: %s\n", temp_dir); return 1; } diff --git a/src/parser.c b/src/parser.c index 8af10ef..9443ab2 100644 --- a/src/parser.c +++ b/src/parser.c @@ -59,7 +59,7 @@ construct_path(char *buf, size_t buflen, char const *home, char const *input, } int -parse_tldrpage(char const *input) +parse_tldrpage(char const *input, int color_enabled) { char c; int i, len; @@ -74,40 +74,49 @@ parse_tldrpage(char const *input) switch (c) { case '>': start = i; - fprintf(stdout, "%s", ANSI_COLOR_EXPLANATION_FG); + if (color_enabled) + fprintf(stdout, "%s", ANSI_COLOR_EXPLANATION_FG); continue; case '-': start = i; - fprintf(stdout, "%s", ANSI_COLOR_COMMENT_FG); + if (color_enabled) + fprintf(stdout, "%s", ANSI_COLOR_COMMENT_FG); continue; case '`': start = i; - fprintf(stdout, "%s", ANSI_COLOR_CODE_FG); + if (color_enabled) + fprintf(stdout, "%s", ANSI_COLOR_CODE_FG); fprintf(stdout, " "); continue; case '#': start = i; - fprintf(stdout, "%s", ANSI_BOLD_ON); - fprintf(stdout, "%s", ANSI_COLOR_TITLE_FG); + if (color_enabled) { + fprintf(stdout, "%s", ANSI_BOLD_ON); + fprintf(stdout, "%s", ANSI_COLOR_TITLE_FG); + } continue; } } else if (start > -1) { if (input[i] == '{' && input[i + 1] == '{') { fprintf(stdout, "%.*s", i - (start + 1), input + (start + 1)); - fprintf(stdout, "%s", ANSI_BOLD_OFF); - fprintf(stdout, "%s", ANSI_COLOR_RESET_FG); - fprintf(stdout, "%s", ANSI_COLOR_CODE_PLACEHOLDER_FG); + if (color_enabled) { + fprintf(stdout, "%s", ANSI_BOLD_OFF); + fprintf(stdout, "%s", ANSI_COLOR_RESET_FG); + fprintf(stdout, "%s", ANSI_COLOR_CODE_PLACEHOLDER_FG); + } start = i; for (i = i + 1; i < len; i++) { if (input[i] == '}' && input[i + 1] == '}') { fprintf(stdout, "%.*s", i - (start + 2), input + (start + 2)); - fprintf(stdout, "%s", ANSI_COLOR_RESET_FG); - fprintf(stdout, "%s", ANSI_COLOR_CODE_FG); + if (color_enabled) { + fprintf(stdout, "%s", ANSI_COLOR_RESET_FG); + fprintf(stdout, "%s", ANSI_COLOR_CODE_FG); + } start = i + 1; break; } @@ -127,9 +136,10 @@ parse_tldrpage(char const *input) } else { fprintf(stdout, "%.*s\n", i - (start + 2), input + (start + 2)); } - - fprintf(stdout, "%s", ANSI_BOLD_OFF); - fprintf(stdout, "%s", ANSI_COLOR_RESET_FG); + if (color_enabled) { + fprintf(stdout, "%s", ANSI_BOLD_OFF); + fprintf(stdout, "%s", ANSI_COLOR_RESET_FG); + } fprintf(stdout, "\n"); start = -1; } @@ -140,7 +150,7 @@ parse_tldrpage(char const *input) } int -print_tldrpage(char const *input, char const *poverride) +print_tldrpage(char const *input, char const *poverride, int color_enabled) { char *output; char url[URLBUFSIZ]; @@ -178,7 +188,7 @@ print_tldrpage(char const *input, char const *poverride) construct_path(url, URLBUFSIZ, homedir, input, platform); if (stat(url, &sb) == 0 && S_ISREG(sb.st_mode)) { if (!get_file_content(url, &output, 0)) { - parse_tldrpage(output); + parse_tldrpage(output, color_enabled); free(output); return 0; } @@ -186,7 +196,7 @@ print_tldrpage(char const *input, char const *poverride) construct_path(url, URLBUFSIZ, homedir, input, "common"); if (stat(url, &sb) == 0 && S_ISREG(sb.st_mode)) { if (!get_file_content(url, &output, 0)) { - parse_tldrpage(output); + parse_tldrpage(output, color_enabled); free(output); return 0; } @@ -194,6 +204,9 @@ print_tldrpage(char const *input, char const *poverride) } } + if (getenv(PREVENT_UPDATE_ENV_VARIABLE)) + return 1; + construct_url(url, URLBUFSIZ, input, platform); /* make clang's static analyzer happy */ @@ -206,9 +219,10 @@ print_tldrpage(char const *input, char const *poverride) return 1; } - parse_tldrpage(output); + parse_tldrpage(output, color_enabled); free(output); + return 0; } @@ -245,13 +259,12 @@ print_tldrlist(char const *poverride) return 1; if (strcmp(platform, "common") != 0) { - parse_tldrlist(directory, platform); + if (parse_tldrlist(directory, platform)) + return 1; fprintf(stdout, "\n"); } - parse_tldrlist(directory, "common"); - - return 0; + return parse_tldrlist(directory, "common"); } int @@ -268,12 +281,16 @@ parse_tldrlist(char const *path, char const *platform) if (sstrncat(fullpath, &len, STRBUFSIZ, platform, strlen(platform))) return 1; + directory = opendir(fullpath); + if (directory == NULL) { + fprintf(stderr, "Can't open cache directory."); + return 1; + } + fprintf(stdout, "%s", ANSI_BOLD_ON); fprintf(stdout, "Pages for %s\n", platform); fprintf(stdout, "%s", ANSI_BOLD_OFF); - directory = opendir(fullpath); - while((entry = readdir(directory))) { len = strlen(entry->d_name); if (strcmp(entry->d_name + (len - 3), ".md") != 0) @@ -286,11 +303,11 @@ parse_tldrlist(char const *path, char const *platform) } int -print_localpage(char const *path) +print_localpage(char const *path, int color_enabled) { char *output = NULL; if (!get_file_content(path, &output, 0)) { - parse_tldrpage(output); + parse_tldrpage(output, color_enabled); free(output); return 0; } diff --git a/src/tldr.c b/src/tldr.c index 5a834ab..f21411d 100644 --- a/src/tldr.c +++ b/src/tldr.c @@ -12,17 +12,18 @@ #include #include #include +#include //for isatty -#define VERSION_TAG "v1.3.0" +#define VERSION_TAG "v1.6.0" #ifndef VERSION - #define VERSION_PRETTY "" +#define VERSION_PRETTY "" #else - #define VERSION_PRETTY VERSION +#define VERSION_PRETTY VERSION #endif /* Help and usage */ -void print_version (char const *arg); -void print_usage (char const *arg); +void print_version(char const *arg); +void print_usage(char const *arg); /* getopt */ static int help_flag; @@ -33,25 +34,24 @@ static int clear_flag; static int platform_flag; static int list_flag; static int render_flag; +static int color_flag; static char pbuf[STRBUFSIZ]; static struct option long_options[] = { - { "help", no_argument, &help_flag, 1 }, - { "version", no_argument, &version_flag, 1 }, - { "verbose", no_argument, &verbose_flag, 1 }, - - { "update", no_argument, &update_flag, 1 }, - { "clear-cache", no_argument, &clear_flag, 1 }, - { "platform", required_argument, 0, 'p' }, - { "linux", no_argument, 0, 'p' }, - { "osx", no_argument, 0, 'p' }, - { "sunos", no_argument, 0, 'p' }, - { "list", no_argument, &list_flag, 'l'}, - { "render", required_argument, 0, 'r'}, {0, 0, 0, 0 } -}; - -int -main(int argc, char **argv) -{ + {"help", no_argument, &help_flag, 1}, + {"version", no_argument, &version_flag, 1}, + {"verbose", no_argument, &verbose_flag, 1}, + {"update", no_argument, &update_flag, 1}, + {"clear-cache", no_argument, &clear_flag, 1}, + {"platform", required_argument, 0, 'p'}, + {"linux", no_argument, 0, 'p'}, + {"osx", no_argument, 0, 'p'}, + {"sunos", no_argument, 0, 'p'}, + {"list", no_argument, &list_flag, 'l'}, + {"render", required_argument, 0, 'r'}, + {"color", no_argument, &color_flag, 'C'}, + {0, 0, 0, 0}}; + +int main(int argc, char **argv) { int c; int missing_arg; int option_index; @@ -61,6 +61,13 @@ main(int argc, char **argv) return EXIT_FAILURE; } + char *no_color = getenv("NO_COLOR"); + if (no_color == NULL || no_color[0] == '\0') { + color_flag = isatty(fileno(stdout)); + } else { + color_flag = 0; + } + while (1) { option_index = 0; c = getopt_long_only(argc, argv, "v", long_options, &option_index); @@ -79,12 +86,12 @@ main(int argc, char **argv) break; case '?': - /* do not set help flag, only show getopt error */ + /* do not set the help flag, only show getopt error */ /* help_flag = 1; */ break; case 'p': { - const char* platform_name = long_options[option_index].name; + const char *platform_name = long_options[option_index].name; if (strcmp(platform_name, "platform") == 0) { size_t len = strlen(optarg); if (len > STRBUFSIZ) @@ -108,6 +115,10 @@ main(int argc, char **argv) render_flag = 1; } break; + case 'C': + color_flag = 1; + break; + default: abort(); } @@ -117,7 +128,7 @@ main(int argc, char **argv) check_localdate(); } - /* show help, if platform was supplied, but no further argument */ + /* show help, if the platform was supplied, but no further argument */ missing_arg = (platform_flag && !list_flag && (optind == argc)); if (help_flag || missing_arg) { print_usage(argv[0]); @@ -150,7 +161,7 @@ main(int argc, char **argv) return EXIT_SUCCESS; } if (render_flag) { - if (print_localpage(pbuf)) + if (print_localpage(pbuf, 1)) return EXIT_FAILURE; return EXIT_SUCCESS; } @@ -162,7 +173,7 @@ main(int argc, char **argv) sum = 0; while (optind < argc) { len = strlen(argv[optind]); - if (sum+len >= 4096) + if (sum + len >= 4096) exit(EXIT_FAILURE); memcpy(buf + sum, argv[optind], len); memcpy(buf + sum + len, "-", 1); @@ -174,9 +185,13 @@ main(int argc, char **argv) if (!has_localdb()) update_localdb(verbose_flag); - if (print_tldrpage(buf, pbuf[0] != 0 ? pbuf : NULL)) { + if (print_tldrpage(buf, pbuf[0] != 0 ? pbuf : NULL, color_flag)) { fprintf(stdout, "This page doesn't exist yet!\n"); fprintf(stdout, "Submit new pages here: https://github.com/tldr-pages/tldr\n"); + if (getenv(PREVENT_UPDATE_ENV_VARIABLE)) { + fprintf(stdout, "Checking the online database was skipped because automatic updates are disabled.\n"); + fprintf(stdout, "You could try updating the local database manually with: tldr --update\n"); + } return EXIT_FAILURE; } } @@ -184,22 +199,18 @@ main(int argc, char **argv) return EXIT_SUCCESS; } -void -print_version(char const *arg) -{ +void print_version(char const *arg) { /* *INDENT-OFF* */ if (strcmp("", VERSION_PRETTY) == 0) - fprintf(stdout, "%s %s\n", arg, VERSION_TAG); + fprintf(stdout, "%s %s\n", arg, VERSION_TAG); else - fprintf(stdout, "%s %s (%s)\n", arg, VERSION_TAG, VERSION_PRETTY);; + fprintf(stdout, "%s %s (%s)\n", arg, VERSION_TAG, VERSION_PRETTY);; fprintf(stdout, "Copyright (C) 2016 Arvid Gerstmann\n"); fprintf(stdout, "Source available at https://github.com/tldr-pages/tldr-c-client\n"); /* *INDENT-ON* */ } -void -print_usage(char const *arg) -{ +void print_usage(char const *arg) { char const *out = "usage: %s [-v] [OPTION]... SEARCH\n\n"; /* *INDENT-OFF* */ @@ -210,7 +221,7 @@ print_usage(char const *arg) fprintf(stdout, " %-20s %-30s\n", "-h, --help", "print this help and exit"); fprintf(stdout, " %-20s %-30s\n", "-u, --update", "update local database"); fprintf(stdout, " %-20s %-30s\n", "-c, --clear-cache", "clear local database"); - fprintf(stdout, " %-20s %-30s\n", "-l, --list", "list all entries in the local databse"); + fprintf(stdout, " %-20s %-30s\n", "-l, --list", "list all entries in the local database"); fprintf(stdout, " %-20s %-30s\n", "-p, --platform=PLATFORM", "select platform, supported are linux / osx / sunos / windows / common"); fprintf(stdout, " %-20s %-30s\n", "--linux", "show command page for Linux"); @@ -218,6 +229,6 @@ print_usage(char const *arg) fprintf(stdout, " %-20s %-30s\n", "--sunos", "show command page for SunOS"); fprintf(stdout, " %-20s %-30s\n", "-r, --render=PATH", "render a local page for testing purposes"); + fprintf(stdout, " %-20s %-30s\n", "-C, --color", "force color display"); /* *INDENT-ON* */ } - diff --git a/src/tldr.h b/src/tldr.h index 57c42f7..74d0e22 100644 --- a/src/tldr.h +++ b/src/tldr.h @@ -14,19 +14,23 @@ #define STRBUFSIZ 512 #define URLBUFSIZ 1024 -#define BASE_URL "https://raw.github.com/tldr-pages/tldr/master/pages" +#define BASE_URL "https://raw.github.com/tldr-pages/tldr/main/pages" #define BASE_URL_LEN (sizeof(BASE_URL) - 1) -#define ZIP_URL "https://github.com/tldr-pages/tldr/archive/master.zip" +#define ZIP_URL "https://github.com/tldr-pages/tldr/archive/main.zip" #define ZIP_URL_LEN (sizeof(ZIP_URL_LEN) - 1) -#define TMP_DIR "/tmp/tldrXXXXXX" +/* Relative to TLDR_HOME */ +#define TMP_DIR "/tmp" #define TMP_DIR_LEN (sizeof(TMP_DIR) - 1) -#define TMP_FILE "/master.zip" +#define TMP_FILE "/main.zip" #define TMP_FILE_LEN (sizeof(TMP_FILE) - 1) -#define TLDR_DIR "/tldr-master" +#define TLDR_ZIP_DIR "/tldr-main" +#define TLDR_ZIP_DIR_LEN (sizeof(TLDR_ZIP_DIR) - 1) + +#define TLDR_DIR "/tldr" #define TLDR_DIR_LEN (sizeof(TLDR_DIR) - 1) #define TLDR_HOME "/.tldrc" @@ -35,9 +39,11 @@ #define TLDR_DATE "/.tldrc/date" #define TLDR_DATE_LEN (sizeof(TLDR_DATE) - 1) -#define TLDR_EXT "/.tldrc/tldr-master/pages/" +#define TLDR_EXT "/.tldrc/tldr/pages/" #define TLDR_EXT_LEN (sizeof(TLDR_EXT) - 1) +#define PREVENT_UPDATE_ENV_VARIABLE "TLDR_AUTO_UPDATE_DISABLED" + #define ANSI_COLOR_RESET_FG "\x1b[39m" #define ANSI_COLOR_TITLE_FG "\x1b[39m" #define ANSI_COLOR_EXPLANATION_FG "\x1b[39m" @@ -66,11 +72,12 @@ int construct_url (char *buf, size_t buflen, char const *platform); int construct_path (char *buf, size_t buflen, char const *home, char const *input, char const *platform); -int parse_tldrpage (char const *input); -int print_tldrpage (char const *input, char const *platform); +int parse_tldrpage (char const *input, int color_enabled); +int print_tldrpage (char const *input, char const *platform, + int color_enabled); int print_tldrlist (char const *platform); int parse_tldrlist (char const *path, char const *platform); -int print_localpage (char const *path); +int print_localpage (char const *path, int color_enabled); /* utils.c */ #define RMOPT_IGNORE_NOFILE (0x1)