From 79fa27d181388df18989a61d929748eb546b42af Mon Sep 17 00:00:00 2001 From: ge Date: Sat, 22 Feb 2025 01:55:36 +0300 Subject: [PATCH] init --- .editorconfig | 8 +++ .gitattributes | 8 +++ .gitignore | 24 +++++++++ README.md | 5 ++ completion | 69 ++++++++++++++++++++++++ src/help.txt | 23 ++++++++ src/main.v | 140 +++++++++++++++++++++++++++++++++++++++++++++++++ v.mod | 7 +++ 8 files changed, 284 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 completion create mode 100644 src/help.txt create mode 100644 src/main.v create mode 100644 v.mod diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..01072ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.v] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9a98968 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +* text=auto eol=lf +*.bat eol=crlf + +*.v linguist-language=V +*.vv linguist-language=V +*.vsh linguist-language=V +v.mod linguist-language=V +.vdocignore linguist-language=ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..965e59a --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Binaries for programs and plugins +main +nex +*.exe +*.exe~ +*.so +*.dylib +*.dll + +# Ignore binary output folders +bin/ + +# Ignore common editor/system specific metadata +.DS_Store +.idea/ +.vscode/ +*.iml + +# ENV +.env + +# vweb and database +*.db +*.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb45e43 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# n! + +`n!` (notes!, nex, nexclamation) is a text notes manager. + +Use it with [completion](completion) script. See [help](src/help.txt) for usage info. diff --git a/completion b/completion new file mode 100644 index 0000000..b7ed414 --- /dev/null +++ b/completion @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# n! (notes!, nex, nexclamation) completion script. + +NPATH="${NPATH:-$HOME/Documents}" + +_n_list_dirs() { + find "$NPATH" -type d -exec echo {}/ \; | sed -E "s%$NPATH/?%%g;/^$/d"; +} + +_n_list_files() { + find "$NPATH" -type f | sed -E "s%$NPATH/?%%g;/^$/d" +} + +_n_get_opts() { + local all_opts="$1" + + # Find matched opts. + local used_opts="$(echo "${COMP_WORDS[@]} $all_opts" \ + | tr ' ' '\n' | sort | uniq -d \ + )" + + if [ "$used_opts" ]; then + # Delete 'help' option. + all_opts="$(sed 's%help%%' <<< "$all_opts")" + # Delete opts if match. + for opt in $used_opts; do + all_opts="$(sed "s%$opt%%" <<< "$all_opts")" + done + fi + echo "$all_opts" +} + +_nexclamation() { + local cur prev + cur=${COMP_WORDS[COMP_CWORD]} + prev=${COMP_WORDS[COMP_CWORD-1]} + + case ${COMP_CWORD} in + 1) # Commands and options + COMPREPLY=($(compgen -W \ + "-v --version -h --help + q quick s search l last mkdir ls lsd rm i info + $(_n_list_dirs) $(_n_list_files)" -- ${cur})) + ;; + 2) # Subcommand completion + case ${prev} in + ls) COMPREPLY=($(compgen -W "$(_n_list_dirs)" -- ${cur})) + ;; + rm) COMPREPLY=($(compgen -W "-f --force + $(_n_list_dirs) $(_n_list_files)" -- ${cur})) + ;; + i|info) COMPREPLY=($(compgen -W "$(_n_list_files)" -- ${cur})) + ;; + *) COMPREPLY=() + ;; + esac;; + *) # Complete file and directory names + case ${COMP_WORDS[2]} in + *) + COMPREPLY=($(compgen -W \ + "$(_n_get_opts "$(_n_list_dirs) $(_n_list_files)")" -- ${cur})) + ;; + esac;; + esac +} + +complete -F _nexclamation nexclamation +complete -F _nexclamation nex +complete -F _nexclamation n! diff --git a/src/help.txt b/src/help.txt new file mode 100644 index 0000000..e761319 --- /dev/null +++ b/src/help.txt @@ -0,0 +1,23 @@ +Usage: nex [command] [FILE] + +Commands: + d, dir print notes path. + o, open open file via xdg-open. + ls print dir tree of notes path. + lsf print list of files in notes path. + lsd print list of directories in notes path. + s, search search in notes via 'grep --color=always -rni'. + g, grep search in notes via custom grep. + h, help print this help message and exit. + +Options: + -trace enable debug mode. + -no-notes-path do not append notes_path to filenames. + -no-chdir do not perform chdir on running commands. + -grep-raw do not add any arguments to grep_args. + -grep-no-color do not add '--color=always' argument to grep_args. + -grep-no-recursive do not add '-rn' arguments to grep_args. + +Environment: + NPATH path to directory with notes (required). + EDITOR text editor to use. diff --git a/src/main.v b/src/main.v new file mode 100644 index 0000000..881e00b --- /dev/null +++ b/src/main.v @@ -0,0 +1,140 @@ +module main + +import os +import os.cmdline + +const help = $embed_file('help.txt') + +struct Preferences { +mut: + trace bool + no_notes_path bool + no_chdir bool + grep_raw bool + grep_no_color bool + grep_no_recursive bool +} + +fn main() { + editor := os.getenv_opt('EDITOR') or { '/usr/bin/vim' } + mut notes_path := os.getenv('NPATH') + mut pref := Preferences{} + argv := arguments()[1..] + opts := cmdline.only_options(argv) + match true { + '-trace' in opts { + pref.trace = true + } + '-no-notes-path' in opts { + pref.no_notes_path = true + } + '-no-chdir' in opts { + pref.no_chdir = true + } + '-grep-raw' in opts { + pref.grep_raw = true + } + '-grep-no-color' in opts { + pref.grep_no_color = true + } + '-grep-no-recursive' in opts { + pref.grep_no_recursive = true + } + else {} + } + args := cmdline.only_non_options(argv) + if args.len == 0 { + os.chdir(notes_path)! + os.execvp(editor, []string{})! + return + } + arg := args[0] + if notes_path == '' && arg !in ['h', 'help'] { + eprintln('E: NPATH environment variable is not set') + eprintln('E: NPATH must contain path to notes dir') + exit(1) + } + match arg { + 'd', 'dir' { + println(notes_path) + return + } + 'o', 'open' { + if pref.no_notes_path { + os.execvp('xdg-open', [argv[0]])! + } + os.execvp('xdg-open', [os.join_path_single(notes_path, argv[0])])! + return + } + 's', 'search' { + if !pref.no_chdir { + os.chdir(notes_path)! + } + result := os.execute_or_exit("grep --color=always -rni '${argv[1]}'") + println(result.output.trim_space()) + return + } + 'g', 'grep' { + if !pref.no_chdir { + os.chdir(notes_path)! + } + mut pc := os.new_process('/usr/bin/grep') + mut grep_args := []string{} + if !pref.grep_raw { + if !pref.grep_no_color { + grep_args << '--color=always' + } + if !pref.grep_no_recursive { + grep_args << ['--recursive', '--line-number'] + } + } + if arg == 'g' { + grep_args << argv[argv.index('g') + 1..] + } + if arg == 'grep' { + grep_args << argv[argv.index('grep') + 1..] + } + if pref.trace { + eprintln('I: grep_args: ${grep_args}') + } + pc.set_args(grep_args) + pc.run() + pc.wait() + return + } + 'ls', 'lsf', 'lsd' { + mut dir := notes_path + if !pref.no_chdir { + os.chdir(notes_path)! + dir = '.' + } + cmd := match arg { + 'ls' { 'env LC_ALL=C tree -FC ${dir}' } + 'lsf' { 'find ${dir} -type f' } + 'lsd' { 'find ${dir} -type d' } + else { '' } + } + if pref.trace { + eprintln('I: command to execute: ${cmd}') + } + result := os.execute_or_exit(cmd) + println(result.output.trim_space()) + return + } + 'h', 'help' { + println(help.to_string().trim_space()) + return + } + else { + if arg.starts_with('-') { + eprintln('E: unrecognized option: ${arg}') + exit(2) + } + if pref.no_notes_path { + os.execvp(editor, [arg])! + } + os.execvp(editor, [os.join_path_single(notes_path, arg)])! + return + } + } +} diff --git a/v.mod b/v.mod new file mode 100644 index 0000000..9786a57 --- /dev/null +++ b/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'nex' + description: 'Text notes manager' + version: '0.0.1' + license: 'Unlicense' + dependencies: [] +}