mirror of
https://github.com/tomasriveral/NoteWrapper.git
synced 2026-08-12 18:30:45 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a32f97d229 | ||
|
|
6555b7247e | ||
|
|
2b2a3455d2 | ||
|
|
d8a11bbc16 | ||
|
|
1d95983d5d | ||
|
|
30449ca473 | ||
|
|
7af79c76b1 | ||
|
|
27b8b310cb | ||
|
|
4d228fb911 | ||
|
|
34a2546990 | ||
|
|
7231741f2d | ||
|
|
e86d7e7e03 | ||
|
|
280c329339 | ||
|
|
f7c84136a5 | ||
|
|
f66371137a | ||
|
|
77b5148489 | ||
|
|
d537357802 | ||
|
|
503dddd9be | ||
|
|
fd77eaa4de | ||
|
|
d6fa89785f | ||
|
|
30616b1b6f | ||
|
|
004b536e05 | ||
|
|
c9ee6ca032 | ||
|
|
c8b5538e1c | ||
|
|
2a09d4ee46 | ||
|
|
52169ccbe4 | ||
|
|
31366aeb48 | ||
|
|
e370f98c5e | ||
|
|
5c4bd6ef16 | ||
|
|
6e6ddebb5e | ||
|
|
fcb853e36a | ||
|
|
91d6db6c74 | ||
|
|
833f8c1bd2 | ||
|
|
5328fdc91c | ||
|
|
f85a32f97f | ||
|
|
bc306ca630 | ||
|
|
89c686652e | ||
|
|
d1c19696ff | ||
|
|
8ee0096540 | ||
|
|
3fa0dbe0dd | ||
|
|
2d6787a91b | ||
|
|
2f688df3ec | ||
|
|
1f20eb6996 | ||
|
|
ebd1ea70e1 | ||
|
|
ffcc16c14f | ||
|
|
9aa8b075e9 | ||
|
|
9a865897ed | ||
|
|
5cdf27f7ed | ||
|
|
e98985fb5b | ||
|
|
dff2fb25aa | ||
|
|
56498b2732 | ||
|
|
73f892aff0 | ||
|
|
533afd0a4b | ||
|
|
986618a05b | ||
|
|
7bd39a08c6 | ||
|
|
37a0763e14 | ||
|
|
0bf3d4624e | ||
|
|
36189b4a9d | ||
|
|
feadf62632 | ||
|
|
a839b0da5b | ||
|
|
027038e49e | ||
|
|
0b2c499bd1 | ||
|
|
8720a17286 | ||
|
|
c46ff838c1 | ||
|
|
66ca150190 | ||
|
|
acd64a845b | ||
|
|
2a5b08b737 | ||
|
|
a142f92c79 | ||
|
|
4422a8d6fd | ||
|
|
fc9480e12e | ||
|
|
d1561ddc2e | ||
|
|
a33a94637f | ||
|
|
4d7c44c016 | ||
|
|
433aca0a58 | ||
|
|
f8bc4004ed | ||
|
|
bf32877f96 | ||
|
|
99e0f9c130 | ||
|
|
92caf3ffd7 | ||
|
|
a3ece38fc1 | ||
|
|
aa4fa234ff | ||
|
|
d5b5d07e64 | ||
|
|
3fb2a32b83 | ||
|
|
5c87ac0aa2 | ||
|
|
3233b330db | ||
|
|
b7ca3eca14 | ||
|
|
68991673c0 | ||
|
|
0d2ec12263 | ||
|
|
107519507f | ||
|
|
e2343dd401 | ||
|
|
08fffcb3d1 | ||
|
|
8cae6cf8ff | ||
|
|
495e8015bc | ||
|
|
d208ccaa20 | ||
|
|
ce4b5cd719 | ||
|
|
f6625e0ef1 | ||
|
|
ae168f92ce | ||
|
|
0eac451751 | ||
|
|
9d7e2efe17 | ||
|
|
0b92270e15 | ||
|
|
7db5bfb44a | ||
|
|
eceaf723f1 | ||
|
|
cb6ac9bce6 | ||
|
|
847f0dae5d | ||
|
|
411ed0939a | ||
|
|
83008d8da8 | ||
|
|
44f14803e0 | ||
|
|
3190da3cc5 | ||
|
|
adbc56fb36 | ||
|
|
9ad71fb4f5 | ||
|
|
5aee1f540c | ||
|
|
f131316fa7 | ||
|
|
7ced9e735f |
@@ -0,0 +1,3 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 210
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
name: Release PR
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release_pr:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Configure git
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
- uses: DeterminateSystems/nix-installer-action@v12
|
||||||
|
|
||||||
|
- name: Create release branch
|
||||||
|
run: |
|
||||||
|
git checkout -B release/${{ inputs.version }}
|
||||||
|
|
||||||
|
- name: Set version in flake.nix
|
||||||
|
run: |
|
||||||
|
sed -i "s/version = \".*\";/version = \"${{ inputs.version }}\";/" flake.nix
|
||||||
|
|
||||||
|
- name: Update flake inputs
|
||||||
|
run: nix flake update
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
git add flake.nix flake.lock
|
||||||
|
git commit -m "chore(release): ${{ inputs.version }}"
|
||||||
|
|
||||||
|
- name: Push branch
|
||||||
|
run: |
|
||||||
|
git push origin release/${{ inputs.version }}
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
run: |
|
||||||
|
gh pr create \
|
||||||
|
--base main \
|
||||||
|
--head release/${{ inputs.version }} \
|
||||||
|
--title "[Automatic] Release PR ${{ inputs.version }}" \
|
||||||
|
--body "Automated release PR.\nAfter the test's succesfull run, create manually a release with a new tag matching ${{ inputs.version }}"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install clang
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y clang-format
|
||||||
|
- name: Check format
|
||||||
|
run: |
|
||||||
|
clang-format --version
|
||||||
|
find src -name "*.c" -o -name "*.h" | xargs clang-format --dry-run --Werror
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get install -y \
|
||||||
|
gcc \
|
||||||
|
make \
|
||||||
|
pkg-config \
|
||||||
|
libcjson-dev \
|
||||||
|
libncurses-dev \
|
||||||
|
git \
|
||||||
|
neovim
|
||||||
|
- name: Prepare environnement
|
||||||
|
run: mkdir -p ~/Documents/Notes
|
||||||
|
- name: Build
|
||||||
|
run: make
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
export TERM=xterm
|
||||||
|
make test
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
name: Weekly Flake Update
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 3 * * 1" # every Monday at 03:00 UTC
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
flake_update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: DeterminateSystems/nix-installer-action@v12
|
||||||
|
|
||||||
|
- name: Set branch name (date)
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
echo "branch=flake-update/$(date +%Y-%m-%d)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create update branch
|
||||||
|
run: |
|
||||||
|
git checkout -B ${{ steps.vars.outputs.branch }}
|
||||||
|
|
||||||
|
- name: Update flake inputs
|
||||||
|
run: |
|
||||||
|
nix flake update
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add flake.lock
|
||||||
|
git commit -m "chore: weekly flake update" || echo "No changes to commit"
|
||||||
|
|
||||||
|
- name: Push branch
|
||||||
|
run: |
|
||||||
|
git push origin ${{ steps.vars.outputs.branch }} --force
|
||||||
|
|
||||||
|
- name: Install GitHub CLI
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y gh
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
run: |
|
||||||
|
gh pr create \
|
||||||
|
--base main \
|
||||||
|
--head ${{ steps.vars.outputs.branch }} \
|
||||||
|
--title "[Automated] Weekly flake update $(date +%Y-%m-%d)" \
|
||||||
|
--body "Automated weekly update of flake inputs for $(date +%Y-%m-%d)."
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -8,3 +8,4 @@ result
|
|||||||
massif.out.*
|
massif.out.*
|
||||||
*.out.*
|
*.out.*
|
||||||
vgcore.*
|
vgcore.*
|
||||||
|
.direnv/
|
||||||
|
|||||||
+20
-2
@@ -37,7 +37,7 @@ Format:
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
* `Editor (#1): add support for Microsoft Word`
|
* `Editor (#1): add support for Microsoft Word`
|
||||||
* `README.md: fix typos`
|
* `docs: fix typos in README.md`
|
||||||
* `Journal: improve entry parsing logic`
|
* `Journal: improve entry parsing logic`
|
||||||
|
|
||||||
Keep commit messages short and descriptive.
|
Keep commit messages short and descriptive.
|
||||||
@@ -58,7 +58,7 @@ To add support for a new editor, you must update both documentation and source c
|
|||||||
* Add the editor name to `supportedEditor[]`
|
* Add the editor name to `supportedEditor[]`
|
||||||
* Increment `numEditor`
|
* Increment `numEditor`
|
||||||
|
|
||||||
* If the editor executable name differs from its logical name (for example `neovim` → `nvim`), update `doesEditorExist()` in `src/utils.c`
|
* If the editor executable name differs from its logical name (for example `neovim` → `nvim`), update `isEditorValid()` in `src/utils.c`
|
||||||
|
|
||||||
* Modify `openEditor()` in `src/utils.c`:
|
* Modify `openEditor()` in `src/utils.c`:
|
||||||
|
|
||||||
@@ -67,6 +67,15 @@ To add support for a new editor, you must update both documentation and source c
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Changing default configuration
|
||||||
|
|
||||||
|
If you want to change the default configuration or add a new option:
|
||||||
|
1. Update [README's documentation](./README.md#configuration)
|
||||||
|
2. In `./src/utils.c`, update the function `initAppFilesAndDirs()` which creates a default configuration if `~/.config/notewrapper/config.json` or if neither `-c` nor `--config` is set.
|
||||||
|
3. In `./src/main.c` where the JSON is parsed, add the parsing logic. Do not forget the debugging information, errors when wrong type or when an important field is missing and add a default value if a less important field is missing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Before opening a pull request
|
## Before opening a pull request
|
||||||
|
|
||||||
Before submitting a pull request, ensure that:
|
Before submitting a pull request, ensure that:
|
||||||
@@ -76,3 +85,12 @@ Before submitting a pull request, ensure that:
|
|||||||
* `make`, or
|
* `make`, or
|
||||||
* `nix-build` (on NixOS)
|
* `nix-build` (on NixOS)
|
||||||
* There are **no warnings or errors**
|
* There are **no warnings or errors**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Continuous integration
|
||||||
|
|
||||||
|
All PR will need to pass these checks:
|
||||||
|
1. `clang-format`. Running `find src -name "*.c" -o -name "*.h" | xargs clang-format -i` will automatically apply those rules.
|
||||||
|
2. The program must build without warning or error.
|
||||||
|
3. The program must run for five seconds without crashing.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# NoteWrapper
|
# NoteWrapper
|
||||||
|
|
||||||
|
Note: This project fullfills all my usecases for now, so my main source for features ideas dried out. So if you have good ideas, feel free to open an issue ;)
|
||||||
|
|
||||||
[](https://deepwiki.com/tomasriveral/notewrapper)
|
[](https://deepwiki.com/tomasriveral/notewrapper)
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
@@ -21,8 +23,8 @@ The goal was to create a terminal-based interface for accessing vaults and notes
|
|||||||
* Standard Markdown files (no custom format)
|
* Standard Markdown files (no custom format)
|
||||||
* Journal support with flexible entry formats
|
* Journal support with flexible entry formats
|
||||||
* Real-time Markdown rendering via Vivify
|
* Real-time Markdown rendering via Vivify
|
||||||
* Backup support using `rsync`
|
* Backup different directories into different destinations
|
||||||
* Supports multiple editors (vim, neovim, nano)
|
* Supports multiple editors
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -40,16 +42,41 @@ Before building NoteWrapper, you must install the following dependencies:
|
|||||||
* `ripgrep`
|
* `ripgrep`
|
||||||
* `fzf`
|
* `fzf`
|
||||||
|
|
||||||
You must also have a [supported editor](#editor-support) installed:
|
You must also have a [supported editor (and their associated plugin if needed)](#editor-support) installed:
|
||||||
|
|
||||||
* `vim`
|
* `helix`
|
||||||
* `neovim`
|
* `jed`
|
||||||
|
* `kakoune`
|
||||||
|
* `micro`
|
||||||
* `nano`
|
* `nano`
|
||||||
|
* `neovim`
|
||||||
|
* `vi`
|
||||||
|
* `vim`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
|
### Nix flakes
|
||||||
|
|
||||||
|
Add this to your inputs in flake.nix:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
notewrapper = {
|
||||||
|
url = "github:tomasriveral/notewrapper";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
If your flake already inputs nixpkgs and/or flake-utils, you can follow it:
|
||||||
|
```nix
|
||||||
|
notewrapper = {
|
||||||
|
url = "github:tomasriveral/notewrapper;
|
||||||
|
inputs.nixpkgs.follow = "nixpkgs";
|
||||||
|
inputs.flake-utils.follow = "flake-utils";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building from source
|
||||||
|
|
||||||
1. Install all required dependencies listed above.
|
1. Install all required dependencies listed above.
|
||||||
2. Ensure you have a supported editor installed.
|
2. Ensure you have a supported editor installed.
|
||||||
3. Clone the repository:
|
3. Clone the repository:
|
||||||
@@ -73,7 +100,7 @@ nix-build
|
|||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Configure `./config.json` (it is automatically created on first launch if it does not already exist).
|
5. [Configure](#configuration) `~/.config/notewrapper/config.json` (it is automatically created on first launch if it does not already exist).
|
||||||
6. Run the program:
|
6. Run the program:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@@ -82,7 +109,7 @@ make
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Packaging
|
### Packaging
|
||||||
|
|
||||||
There is currently no official package available for NoteWrapper.
|
There is currently no official package available for NoteWrapper.
|
||||||
|
|
||||||
@@ -98,7 +125,6 @@ As a NixOS user, I will likely package it for **nixpkgs** in the future if the p
|
|||||||
Usage: notewrapper [options]
|
Usage: notewrapper [options]
|
||||||
Options:
|
Options:
|
||||||
-c, --config <path/to/config> Specify the config file.
|
-c, --config <path/to/config> Specify the config file.
|
||||||
-d, --directory <path/to/directory> Specify the vaults' directory.
|
|
||||||
-h, --help Display this message.
|
-h, --help Display this message.
|
||||||
-e, --editor Specify the editor to open.
|
-e, --editor Specify the editor to open.
|
||||||
-j, --jump Jump to the end of the file on opening.
|
-j, --jump Jump to the end of the file on opening.
|
||||||
@@ -139,11 +165,16 @@ The first two features depend on [Vivify's editor integration](https://github.co
|
|||||||
|
|
||||||
If your editor does not support these features, you can implement a plugin using [Vivify's API](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#editor-support).
|
If your editor does not support these features, you can implement a plugin using [Vivify's API](https://github.com/jannis-baum/Vivify?tab=readme-ov-file#editor-support).
|
||||||
|
|
||||||
| Editor | Bufferless | Cursor | Jump to end | Plugin required |
|
| Editor | Bufferless | Cursor | Jump to end | Aditional requirements |
|
||||||
| ------ | ---------- | ------ | ----------- | ------------------------------------------------------- |
|
| --------- | ----------- | ------- | ------------ | ---------------------------------------------------------|
|
||||||
| Neovim | ✅ | ✅ | ✅ | [vivify-vim](https://github.com/jannis-baum/vivify.vim) |
|
| [Helix](https://helix-editor.com/) | ❌ | ❌ | ✅ | — |
|
||||||
| Vim | ✅ | ✅ | ✅ | [vivify-vim](https://github.com/jannis-baum/vivify.vim) |
|
| [Jed](https://www.jedsoft.org/jed/) | ❌ | ❌ | ✅ | — |
|
||||||
| Nano | ❌ | ❌ | ✅ | — |
|
| [Kakoune](http://kakoune.org/) | ❌ | ❌ | ✅ | — |
|
||||||
|
| [Micro](https://micro-editor.github.io/) | ❌ | ✅ | ✅ | [micro-vivify](https://codeberg.org/gibbert/micro-vivify) and [modifications to your `init.lua`](./docs/micro.md)|
|
||||||
|
| [Nano](https://www.nano-editor.org/) | ❌ | ❌ | ✅ | — |
|
||||||
|
| [Neovim](https://neovim.io/) | ✅ | ✅ | ✅ | [vivify-vim](https://github.com/jannis-baum/vivify.vim) |
|
||||||
|
| [Vi](https://en.wikipedia.org/wiki/Vi_(text_editor)) | ❌ | ❌ | ✅ | |
|
||||||
|
| [Vim](https://www.vim.org/) | ✅ | ✅ | ✅ | [vivify-vim](https://github.com/jannis-baum/vivify.vim) |
|
||||||
|
|
||||||
[How to add support for another editor](./CONTRIBUTING.md#adding-editor-support)
|
[How to add support for another editor](./CONTRIBUTING.md#adding-editor-support)
|
||||||
|
|
||||||
@@ -155,7 +186,7 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"directory": "~/Documents/Notes/",
|
"directory": ["~/Documents/Notes/", "/other/paths/"],
|
||||||
"render": true,
|
"render": true,
|
||||||
"jumpToEndOfFileOnLaunch": true,
|
"jumpToEndOfFileOnLaunch": true,
|
||||||
"editor": "neovim",
|
"editor": "neovim",
|
||||||
@@ -164,7 +195,10 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
|
|||||||
"newLineOnOpening": true,
|
"newLineOnOpening": true,
|
||||||
"backup": {
|
"backup": {
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"directory": "/path/to/backup",
|
"directory": {
|
||||||
|
"~/Documents/Notes": "path/to/backup1",
|
||||||
|
"/other/paths/": "path/to/backup2"
|
||||||
|
},
|
||||||
"interval": "weekly",
|
"interval": "weekly",
|
||||||
"rsyncArgs": ["-Lqah", "--update"]
|
"rsyncArgs": ["-Lqah", "--update"]
|
||||||
}
|
}
|
||||||
@@ -173,18 +207,28 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
|
|||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
* `directory`: root directory containing all vaults
|
* `directory`: Array of directories containing the vaults.
|
||||||
* `render`: enable/disable Vivify rendering
|
* `render`: enable/disable Vivify rendering
|
||||||
* `jumpToEndOfFileOnLaunch`: move cursor to end of file on open
|
* `jumpToEndOfFileOnLaunch`: move cursor to end of file on open
|
||||||
* `editor`: selected editor (must be supported)
|
* `editor`: selected editor (must be supported). If not set, it defaults to `$EDITOR`.
|
||||||
* `journalRegex`: regex used to detect journal files
|
* `journalRegex`: regex used to detect journal files
|
||||||
* `dateEntry`: format for journal entries (see `strftime`)
|
* `dateEntry`: format for journal entries (see `strftime`)
|
||||||
* `newLineOnOpening`: add a newline when opening a note
|
* `newLineOnOpening`: add a newline when opening a note
|
||||||
* `backup.enable`: enable automatic backups using `rsync`
|
* `backup.enable`: enable automatic backups using `rsync`
|
||||||
* `backup.directory`: destination directory for backups
|
* `backup.directory`: backup's destination for each directory
|
||||||
* `backup.interval`: backup frequency (`daily`, `weekly`, `monthly`, or integer)
|
* `backup.interval`: backup frequency (`daily`, `weekly`, `monthly`, or integer)
|
||||||
* `backup.rsyncArgs`: arguments passed to `rsync`
|
* `backup.rsyncArgs`: arguments passed to `rsync`
|
||||||
|
|
||||||
|
Note:
|
||||||
|
* Directories must end with `/`.
|
||||||
|
* All directories used must exist.
|
||||||
|
* Elements of `directory` and `backup.directory` must match exactly.
|
||||||
|
* Directories set in `directory` but not in `backup.directory` won't be backed up.
|
||||||
|
* Directories set in `backup.directory` but not in `directory` will be ignored.
|
||||||
|
* `journalRegex` must match if the file name ends with `.md` and if it doesn't.
|
||||||
|
* It is recommended to keep `-q` or `--quiet` flag in `rsyncArgs` to avoid interference with `ncurses`.
|
||||||
|
* If `rsync` fails, you will see it inside `ncurses`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Vivify configuration
|
## Vivify configuration
|
||||||
@@ -199,5 +243,7 @@ It is recommended to use a browser different from your main one for rendering.
|
|||||||
## Planned features
|
## Planned features
|
||||||
|
|
||||||
* [ ] A converter between journal types
|
* [ ] A converter between journal types
|
||||||
* [ ] Support multiple vault directories
|
* [x] Support multiple vault directories
|
||||||
* [ ] Port NoteWrapper to other editors
|
* [ ] Port NoteWrapper to other editors (non-exhaustive list of planned ports: `emacs -nw`, `ad`, flow-control, `ee`, `amp`, `dte`, `cano`, `mle`, `zee`, `ptext`, `kibi`, `ox`, `ne`, `dit`, `zile`, `moe`, `joe`, `pico`, `vis`)
|
||||||
|
* [x] Default to $EDITOR
|
||||||
|
* [ ] Refactoring the source code
|
||||||
|
|||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"directory": "~/Documents/Notes/",
|
|
||||||
"render": true,
|
|
||||||
"jumpToEndOfFileOnLaunch": true,
|
|
||||||
"editor": "neovim",
|
|
||||||
"journalRegex": ".*journal.*",
|
|
||||||
"dateEntry": "# %Y %m %d %a",
|
|
||||||
"newLineOnOpening": true,
|
|
||||||
"backup": {
|
|
||||||
"enable": false,
|
|
||||||
"directory": "/path/to/backup",
|
|
||||||
"interval": "weekly"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-35
@@ -1,35 +0,0 @@
|
|||||||
{ pkgs ? import <nixpkgs> {} }:
|
|
||||||
|
|
||||||
pkgs.stdenv.mkDerivation {
|
|
||||||
pname = "notewrapper";
|
|
||||||
version = "1.0";
|
|
||||||
|
|
||||||
src = ./.;
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkgs.pkg-config
|
|
||||||
];
|
|
||||||
|
|
||||||
buildInputs = [
|
|
||||||
pkgs.ncurses
|
|
||||||
pkgs.cjson
|
|
||||||
pkgs.fzf
|
|
||||||
pkgs.gnused
|
|
||||||
pkgs.ripgrep
|
|
||||||
];
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
make
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/bin
|
|
||||||
cp notewrapper $out/bin/
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
|
||||||
description = " Journaling and notetaking TUI wrapper on top of neovim, vim and nano.";
|
|
||||||
license = licenses.gpl3Only;
|
|
||||||
platforms = platforms.linux;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
You must add the following code to your `init.lua`:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local buffer = import("micro/buffer")
|
||||||
|
local shell = import("micro/shell")
|
||||||
|
|
||||||
|
function vivifyOpen(buf)
|
||||||
|
local cursor = buf:GetActiveCursor()
|
||||||
|
local line = cursor.Loc.Y + 1
|
||||||
|
|
||||||
|
shell.ExecCommand("viv", string.format("%s:%d", buf.AbsPath, line))
|
||||||
|
end
|
||||||
|
|
||||||
|
function onBufferOpen(buf)
|
||||||
|
if os.getenv("MICRO_VIVIFY") ~= "1" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if buf.Type.Kind == buffer.BTDefault then
|
||||||
|
vivifyOpen(buf)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This enables Vivify to run automatically when a file is opened, effectively simulating “startup” behavior (since Micro does not provide a true startup command hook for this use case).
|
||||||
|
|
||||||
|
Big thanks to @Andriamanitra for answering [my questions](https://github.com/micro-editor/micro/discussions/4078).
|
||||||
|
|
||||||
Generated
+61
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779560665,
|
||||||
|
"narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
description = "NoteWrapper";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils}:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
in {
|
||||||
|
packages.default = pkgs.stdenv.mkDerivation (finalAttrs: {
|
||||||
|
pname = "notewrapper";
|
||||||
|
version = "v2.2.1";
|
||||||
|
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
pkg-config
|
||||||
|
gnumake
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
ncurses
|
||||||
|
cjson
|
||||||
|
vivify
|
||||||
|
rsync
|
||||||
|
ripgrep
|
||||||
|
fzf
|
||||||
|
gnused
|
||||||
|
];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
make VERSION=${finalAttrs.version}
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp notewrapper $out/bin/
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
cjson
|
||||||
|
ncurses
|
||||||
|
pkg-config
|
||||||
|
gdb
|
||||||
|
valgrind
|
||||||
|
clang-tools
|
||||||
|
gnumake
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
apps.default = {
|
||||||
|
type = "app";
|
||||||
|
program = "${self.packages.${system}.default}/bin/notewrapper";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
CC := gcc
|
CC := gcc
|
||||||
|
|
||||||
CFLAGS := -Wall -Wextra -O2 \
|
VERSION := $(shell git describe --tags --always --dirty)
|
||||||
$(shell pkg-config --cflags libcjson ncurses)
|
|
||||||
|
CFLAGS := -Wall -Wextra -Werror -O2 \
|
||||||
|
-DVERSION=\"$(VERSION)\" \
|
||||||
|
$(shell pkg-config --cflags libcjson ncurses)
|
||||||
|
|
||||||
LDFLAGS := $(shell pkg-config --libs libcjson ncurses)
|
LDFLAGS := $(shell pkg-config --libs libcjson ncurses)
|
||||||
|
|
||||||
@@ -20,3 +23,17 @@ run: $(TARGET)
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(TARGET)
|
rm -f $(TARGET)
|
||||||
|
|
||||||
|
test: $(TARGET)
|
||||||
|
@timeout 5s ./$(TARGET); \
|
||||||
|
status=$$?; \
|
||||||
|
if [ $$status -eq 124 ]; then \
|
||||||
|
echo "Program ran for 5 seconds without crashing ✅"; \
|
||||||
|
exit 0; \
|
||||||
|
elif [ $$status -ne 0 ]; then \
|
||||||
|
echo "Program crashed ❌"; \
|
||||||
|
exit 1; \
|
||||||
|
else \
|
||||||
|
echo "Program exited cleanly ✅"; \
|
||||||
|
exit 0; \
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
{ pkgs ? import <nixpkgs> {} }:
|
|
||||||
|
|
||||||
pkgs.mkShell {
|
|
||||||
buildInputs = [
|
|
||||||
pkgs.cjson
|
|
||||||
pkgs.ncurses
|
|
||||||
pkgs.pkg-config
|
|
||||||
pkgs.gdb
|
|
||||||
pkgs.valgrind
|
|
||||||
pkgs.kdePackages.kcachegrind
|
|
||||||
];
|
|
||||||
}
|
|
||||||
+535
-393
File diff suppressed because it is too large
Load Diff
+259
-179
@@ -1,16 +1,44 @@
|
|||||||
#include "notes.h"
|
#include "notes.h"
|
||||||
#include "ui.h"
|
#include "utils.h"
|
||||||
|
|
||||||
char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) {
|
char *getDirectoryFromVault(char *targetVault, char **vaultsArray, int vaultTotalNumber, int *vaultNumberPerDirectory, char **directoryArray, int directoryNumber, int shouldDebug) {
|
||||||
|
debug("Searching the vault %s inside all the directories...", targetVault);
|
||||||
|
debug("Here are how many vaults there is per directory:");
|
||||||
|
for (int i = 0; i < directoryNumber; i++) {
|
||||||
|
altDebug("%d for %s\n", vaultNumberPerDirectory[i], directoryArray[i]);
|
||||||
|
}
|
||||||
|
debug("Here are all the vaults in the order they will be searched:");
|
||||||
|
for (int i = 0; i < vaultTotalNumber; i++) {
|
||||||
|
altDebug("%s\n", vaultsArray[i]);
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < directoryNumber; i++) {
|
||||||
|
for (int j = 0; j < vaultNumberPerDirectory[i]; j++) {
|
||||||
|
if (strcmp(vaultsArray[index], targetVault) == 0) {
|
||||||
|
debug("%s found in %s. (index %d)", targetVault, directoryArray[i], index);
|
||||||
|
return directoryArray[i];
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error(1, "program", "the vault %s was not found", targetVault);
|
||||||
|
return "this makes the compiler and clangd happy. Who doesn't want GCC and clangd to be happy? "
|
||||||
|
"Such person would be a terrible monster... One must imagine GCC and clangd happy.";
|
||||||
|
}
|
||||||
|
|
||||||
|
char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) {
|
||||||
debug("Searching %s for journals", vault);
|
debug("Searching %s for journals", vault);
|
||||||
// originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
// originally from
|
||||||
|
// https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
||||||
struct dirent *vaultEntry;
|
struct dirent *vaultEntry;
|
||||||
char tempPath[PATH_MAX];
|
char tempPath[PATH_MAX];
|
||||||
snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, vault); // sets the full absolute path to fullPathEntry
|
snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault,
|
||||||
|
vault); // sets the full absolute path to fullPathEntry
|
||||||
DIR *vaultDirectory = opendir(tempPath);
|
DIR *vaultDirectory = opendir(tempPath);
|
||||||
error(vaultDirectory==NULL, "program", "Could not open directory %s", tempPath);
|
error(vaultDirectory == NULL, "program", "Could not open directory %s", tempPath);
|
||||||
char **journalsArray = NULL; // will contain all the notes
|
char **journalsArray = NULL; // will contain all the notes
|
||||||
int journalsCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc
|
int journalsCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc
|
||||||
|
|
||||||
// https://stackoverflow.com/a/1085120 for regex code
|
// https://stackoverflow.com/a/1085120 for regex code
|
||||||
regex_t regex;
|
regex_t regex;
|
||||||
@@ -23,20 +51,21 @@ char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex,
|
|||||||
// for readdir()
|
// for readdir()
|
||||||
debug("┌------------------------------------------\nDetected files and dirs from the vault");
|
debug("┌------------------------------------------\nDetected files and dirs from the vault");
|
||||||
while ((vaultEntry = readdir(vaultDirectory)) != NULL) { // we iterate over every entry from the dir. So files and dirs (. and .. included)
|
while ((vaultEntry = readdir(vaultDirectory)) != NULL) { // we iterate over every entry from the dir. So files and dirs (. and .. included)
|
||||||
altDebug("%s ", vaultEntry->d_name);
|
altDebug("%s ", vaultEntry->d_name);
|
||||||
if (vaultEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files)
|
if (vaultEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files)
|
||||||
regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0);
|
regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0);
|
||||||
if (!regexReturn) { // if the regex matches
|
if (!regexReturn) { // if the regex matches
|
||||||
altDebug("matched with the regex. It is a journal.\n");
|
altDebug("matched with the regex. It is a journal.\n");
|
||||||
journalsArray = realloc(journalsArray, (journalsCount + 1)*sizeof(char*)); // resize notesArray so that
|
journalsArray = realloc(journalsArray,
|
||||||
journalsArray[journalsCount] = strdup(vaultEntry->d_name); // copy the dir name into notesArray
|
(journalsCount + 1) * sizeof(char *)); // resize notesArray so that
|
||||||
journalsCount++;
|
journalsArray[journalsCount] = strdup(vaultEntry->d_name); // copy the dir name into notesArray
|
||||||
|
journalsCount++;
|
||||||
|
} else {
|
||||||
|
altDebug("did not matched with the regex. It is a note.\n");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
altDebug("did not matched with the regex. It is a note.\n");
|
altDebug(" was ignored\n");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
altDebug(" was ignored\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
altDebug("└ ------------------------------\n");
|
altDebug("└ ------------------------------\n");
|
||||||
// free's some used memory
|
// free's some used memory
|
||||||
@@ -45,42 +74,51 @@ char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex,
|
|||||||
return journalsArray;
|
return journalsArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
char** getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) {
|
char **getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug) {
|
||||||
// this function is inputed a path to a vault (which was selected before) and outpus all the suitable notes (so not the hidden ones)
|
// this function is inputed a path to a vault (which was selected before) and outpus all the
|
||||||
// originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
// suitable notes (so not the hidden ones) originally from
|
||||||
|
// https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
||||||
debug("Searching %s for notes", vault);
|
debug("Searching %s for notes", vault);
|
||||||
struct dirent *vaultEntry;
|
struct dirent *vaultEntry;
|
||||||
char tempPath[PATH_MAX];
|
char tempPath[PATH_MAX];
|
||||||
snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault, vault); // sets the full absolute path to fullPathEntry
|
snprintf(tempPath, sizeof(tempPath), "%s/%s", pathToVault,
|
||||||
|
vault); // sets the full absolute path to fullPathEntry
|
||||||
DIR *vaultDirectory = opendir(tempPath);
|
DIR *vaultDirectory = opendir(tempPath);
|
||||||
error(vaultDirectory==NULL, "program", "Could not open directory %s", tempPath);
|
error(vaultDirectory == NULL, "program", "Could not open directory %s", tempPath);
|
||||||
char **notesArray = NULL; // will contain all the notes
|
char **notesArray = NULL; // will contain all the notes
|
||||||
int notesCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc
|
int notesCount = 0; // we need to count how many notes there is to always readjust how many memory we alloc
|
||||||
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
||||||
// for readdir()
|
// for readdir()
|
||||||
debug("┌------------------------------\nDetected Files and dirs from the vault:");
|
debug("┌------------------------------\nDetected Files and dirs from the vault:");
|
||||||
while ((vaultEntry = readdir(vaultDirectory)) != NULL) {
|
while ((vaultEntry = readdir(vaultDirectory)) != NULL) {
|
||||||
char *entryName = vaultEntry->d_name;
|
char *entryName = vaultEntry->d_name;
|
||||||
int entryLenght = strlen(entryName);
|
int entryLenght = strlen(entryName);
|
||||||
altDebug("%s ", entryName);
|
altDebug("%s ", entryName);
|
||||||
// for the regex code https://stackoverflow.com/a/1085120
|
// for the regex code https://stackoverflow.com/a/1085120
|
||||||
regex_t regex;
|
regex_t regex;
|
||||||
int regexReturn;
|
int regexReturn;
|
||||||
// compiles the regex
|
// compiles the regex
|
||||||
regexReturn = regcomp(®ex, journalRegex, 0);
|
regexReturn = regcomp(®ex, journalRegex, 0);
|
||||||
if (entryName[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files)
|
if (entryName[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files)
|
||||||
char fullPathEntry[PATH_MAX]; // creates a string of size of the maximum path lenght
|
char fullPathEntry[PATH_MAX]; // creates a string of size of the maximum path lenght
|
||||||
snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s/%s", pathToVault, vault, entryName); // sets the full absolute path to fullPathEntry
|
snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s/%s", pathToVault, vault,
|
||||||
// check if it matches the regex. If it does not match regexReturn != 0.
|
entryName); // sets the full absolute path to fullPathEntry
|
||||||
regexReturn = regexec(®ex, entryName, 0, NULL, 0);
|
// check if it matches the regex. If it does not match regexReturn != 0.
|
||||||
struct stat metadataPathEntry;
|
regexReturn = regexec(®ex, entryName, 0, NULL, 0);
|
||||||
if (stat(fullPathEntry, &metadataPathEntry) == 0 && entryName[entryLenght - 3] == '.' && entryName[entryLenght - 2] == 'm' && entryName[entryLenght - 1] == 'd' && regexReturn && S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a file ending in .md and that does no match the regex
|
struct stat metadataPathEntry;
|
||||||
notesArray = realloc(notesArray, (notesCount + 1)*sizeof(char*)); // resize notesArray so that
|
if (stat(fullPathEntry, &metadataPathEntry) == 0 && entryName[entryLenght - 3] == '.' && entryName[entryLenght - 2] == 'm' && entryName[entryLenght - 1] == 'd' && regexReturn &&
|
||||||
notesArray[notesCount] = strdup(entryName); // copy the dir name into notesArray
|
S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a file ending in .md and
|
||||||
notesCount++;
|
// that does no match the regex
|
||||||
altDebug("did not match with the regex. It is a note.\n");
|
notesArray = realloc(notesArray, (notesCount + 1) * sizeof(char *)); // resize notesArray so that
|
||||||
} else if (!regexReturn) {altDebug("matched with the regex. It is a journal.\n");}
|
notesArray[notesCount] = strdup(entryName); // copy the dir name into notesArray
|
||||||
} else {altDebug("was ignored\n");}
|
notesCount++;
|
||||||
|
altDebug("did not match with the regex. It is a note.\n");
|
||||||
|
} else if (!regexReturn) {
|
||||||
|
altDebug("matched with the regex. It is a journal.\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
altDebug("was ignored\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
altDebug("└ ------------------------------\n");
|
altDebug("└ ------------------------------\n");
|
||||||
|
|
||||||
@@ -90,148 +128,190 @@ char** getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int
|
|||||||
return notesArray;
|
return notesArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
char **getVaultsFromDirectory(char *dirString, int *count, int shouldDebug) {
|
char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber, int *vaultsPerDirectoryNumber, int *count, int shouldDebug) {
|
||||||
// this function is inputed a path to a directory (which comes usually from the config file) and outpus all the suitable directories (so not the hidden ones) which will serve as separate vaults for notes
|
// to avoid having to work with a tree-dimensional array. We will use a 2d and
|
||||||
debug("Opening %s ", dirString);
|
// vaultsPerDirectoryNumber will indicate the width of the directories.
|
||||||
// originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
char **vaultsArray = NULL;
|
||||||
struct dirent *vaultsDirectoryEntry;
|
int nthVault = 0; // this is only used internally to set the string into the right place in
|
||||||
DIR *vaultsDirectory = opendir(dirString);
|
// directoryStringArray.
|
||||||
error(vaultsDirectory==NULL, "program", "Could not open directory %s", dirString);
|
int previousStartIndex = 0;
|
||||||
char **dirsArray = NULL; // will contain all the dirs/vaults
|
for (int i = 0; i < directoryNumber; i++) {
|
||||||
int dirsCount = 0; // we need to count how many dirs there is to always readjust how many memory we alloc
|
debug("Opening %s", directoryStringArray[i]);
|
||||||
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
// originally from
|
||||||
// for readdir()
|
// https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
||||||
debug("┌------------------------------\n Detected files and dirs from the directory:");
|
struct dirent *vaultsDirectoryEntry;
|
||||||
while ((vaultsDirectoryEntry = readdir(vaultsDirectory)) != NULL) {
|
DIR *vaultsDirectory = opendir(directoryStringArray[i]);
|
||||||
altDebug("%s\n", vaultsDirectoryEntry->d_name);
|
error(!vaultsDirectory, "program", "Could not open directory %s", directoryStringArray[i]);
|
||||||
if (vaultsDirectoryEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files)
|
vaultsPerDirectoryNumber[i] = 0;
|
||||||
char fullPathEntry[PATH_MAX]; // creates a string of size of the maximum path lenght
|
debug("┌------------------------------\n Detected files and dirs %s:", directoryStringArray[i]);
|
||||||
snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s", dirString, vaultsDirectoryEntry->d_name); // sets the full absolute path to fullPathEntry
|
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
||||||
|
// for readdir()
|
||||||
struct stat metadataPathEntry;
|
while ((vaultsDirectoryEntry = readdir(vaultsDirectory)) != NULL) {
|
||||||
if (stat(fullPathEntry, &metadataPathEntry) == 0 && S_ISDIR(metadataPathEntry.st_mode)) { // if this entry is a directory
|
char *entryName = vaultsDirectoryEntry->d_name; // gets the entry as a string value
|
||||||
dirsArray = realloc(dirsArray, (dirsCount + 1)*sizeof(char*)); // resize dirsArray so that
|
altDebug("%s", entryName);
|
||||||
dirsArray[dirsCount] = strdup(vaultsDirectoryEntry->d_name); // copy the dir name into dirsArray
|
if (entryName[0] != '.') { // if not hidden file/dir
|
||||||
dirsCount++;
|
char tempFullEntryPath[PATH_MAX]; // we recreate the full path to check it's
|
||||||
|
// proprieties
|
||||||
|
snprintf(tempFullEntryPath, PATH_MAX, "%s%s", directoryStringArray[i], entryName);
|
||||||
|
altDebug(" (%s)", tempFullEntryPath);
|
||||||
|
// checking the metadata to see if it is a dir
|
||||||
|
struct stat metadataEntry;
|
||||||
|
if (stat(tempFullEntryPath, &metadataEntry) == 0 && S_ISDIR(metadataEntry.st_mode)) { // get's the metadata (stat should return 0 if
|
||||||
|
// fails) and sees if it is a dir.
|
||||||
|
altDebug(" is a vault");
|
||||||
|
vaultsArray = realloc(vaultsArray, sizeof(char *) * (nthVault + 1)); // resize vaultsArray
|
||||||
|
error(vaultsArray == NULL, "program", "realloc failed");
|
||||||
|
vaultsPerDirectoryNumber[i]++; // it will be used later to know which vaults
|
||||||
|
// goes into which directory
|
||||||
|
vaultsArray[nthVault] = strdup(entryName); // we use strdup and not strcpy, because memory used with
|
||||||
|
// opendir and readdir will be closed.
|
||||||
|
error(vaultsArray[nthVault] == NULL, "program", "strdup failed");
|
||||||
|
nthVault++; // it is used immediatly to set the vault into directoryStringArray
|
||||||
|
} else {
|
||||||
|
altDebug(" is not a vault (not a dir.)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
altDebug(" is not a vault (hidden file/dir)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// we sort entries for the same vault. We can't sort them all. This would break the order.
|
||||||
|
qsort(vaultsArray + previousStartIndex, nthVault - previousStartIndex, sizeof(const char *),
|
||||||
|
compareString); // sorts the vaults alphabetically
|
||||||
|
closedir(vaultsDirectory);
|
||||||
|
previousStartIndex = nthVault;
|
||||||
}
|
}
|
||||||
altDebug("└------------------------------\n");
|
// we calculate once the total number of vaults to avoid recalculation every time we use it
|
||||||
|
*count = 0;
|
||||||
// free's some used memory
|
for (int i = 0; i < directoryNumber; i++) {
|
||||||
closedir(vaultsDirectory);
|
*count += vaultsPerDirectoryNumber[i];
|
||||||
*count = dirsCount;
|
}
|
||||||
return dirsArray;
|
return vaultsArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, int shouldDebug) {
|
char *updateJournal(char *path, char *journal, char *timeFormat, int *journalWasUpdated, int shouldDebug) {
|
||||||
path[PATH_MAX] = '\0'; // it assures it is a string (Most cases this does nothing). But rewriting at least one bytes make the compile happy. He doesn't want to return an unchanged input.
|
path[PATH_MAX] = '\0'; // it assures it is a string (Most cases this does nothing). But rewriting at least
|
||||||
debug("Handling the journal %s", path);
|
// one bytes make the compile happy. He doesn't want to return an unchanged input.
|
||||||
|
debug("Handling the journal %s", path);
|
||||||
|
|
||||||
char *date = getFormatedTime(timeFormat, shouldDebug);
|
char *date = getFormatedTime(timeFormat, shouldDebug);
|
||||||
debug("Formated time for the journal's entry is %s", date);
|
debug("Formated time for the journal's entry is %s", date);
|
||||||
char dateWithExtension[PATH_MAX];
|
char dateWithExtension[PATH_MAX];
|
||||||
snprintf(dateWithExtension, PATH_MAX, "%s.md", date);
|
snprintf(dateWithExtension, PATH_MAX, "%s.md", date);
|
||||||
sanitize(dateWithExtension);
|
sanitize(dateWithExtension);
|
||||||
debug("Sanitized date: %s\n(it might be used later for a file name if the journal is divided)", dateWithExtension);
|
debug("Sanitized date: %s\n(it might be used later for a file name if the journal is divided)", dateWithExtension);
|
||||||
struct stat metadata;
|
struct stat metadata;
|
||||||
error(stat(path, &metadata), "program", "stat() failed to get information about %s", path);
|
stat(path, &metadata);
|
||||||
if (S_ISREG(metadata.st_mode)) {
|
if (S_ISREG(metadata.st_mode)) {
|
||||||
debug("%s is a unified journal.", path);
|
debug("%s is a unified journal.", path);
|
||||||
if (!isStringInFile(path, date, shouldDebug)) { // if there is no entry for current date
|
if (!isStringInFile(path, date, shouldDebug)) { // if there is no entry for current date
|
||||||
appendToFile(path, date, shouldDebug);
|
appendToFile(path, date, shouldDebug);
|
||||||
*journalWasUpdated = 1;
|
*journalWasUpdated = 1;
|
||||||
} // if there is an entry do nothing
|
} // if there is an entry do nothing
|
||||||
} else if (S_ISDIR(metadata.st_mode)) {
|
} else if (S_ISDIR(metadata.st_mode)) {
|
||||||
debug("%s is a divided journal.", path);
|
debug("%s is a divided journal.", path);
|
||||||
struct dirent *dividedJournalEntry;
|
struct dirent *dividedJournalEntry;
|
||||||
// snprintf does not like to have the same variable as input and output so we use a buffer
|
|
||||||
char temp[PATH_MAX];
|
|
||||||
snprintf(temp, PATH_MAX, "%s/", path); // appends a /. For safety
|
|
||||||
strncpy(path, temp, PATH_MAX);
|
|
||||||
DIR *dividedJournalDirectory = opendir(path);
|
|
||||||
error(dividedJournalDirectory==NULL, "program", "Could not open directory %s", path);
|
|
||||||
/*char **entryArray = NULL; // will contain all the entries of the divided journal
|
|
||||||
// This time it will be different. We must add "invert" the extraOptions to the options so that the Create new entry in journal is on top
|
|
||||||
entryArray = realloc(entryArray, sizeof(char*));*/
|
|
||||||
// simpler to just malloc 2 and later realloc
|
|
||||||
const int extraOptions = 3;
|
|
||||||
char **entryArray = malloc(extraOptions*sizeof(char*));
|
|
||||||
char *createEntryMessage = malloc(PATH_MAX);
|
|
||||||
snprintf(createEntryMessage, PATH_MAX, "Create new entry for the journal %s", journal);
|
|
||||||
entryArray[0] = createEntryMessage;
|
|
||||||
entryArray[1] = "Open random entry";
|
|
||||||
entryArray[2] = "Search inside entries";
|
|
||||||
int entryCount = extraOptions; // we need to count how many dirs there is to always readjust how many memory we alloc
|
|
||||||
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
|
||||||
// for readdir()
|
|
||||||
debug("┌------------------------------\n Detected files and dirs from %s:", path);
|
|
||||||
// iterates over all the entries from the dir
|
|
||||||
while ((dividedJournalEntry = readdir(dividedJournalDirectory)) != NULL) {
|
|
||||||
altDebug("%s\n", dividedJournalEntry->d_name);
|
|
||||||
if (dividedJournalEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files)
|
|
||||||
char fullPathEntry[PATH_MAX]; // creates a string of size of the maximum path lenght
|
|
||||||
snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s", path, dividedJournalEntry->d_name); // sets the full absolute path to fullPathEntry
|
|
||||||
|
|
||||||
struct stat metadataPathEntry;
|
|
||||||
if (stat(fullPathEntry, &metadataPathEntry) == 0 && S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a directory
|
|
||||||
entryArray = realloc(entryArray, (entryCount + 1)*sizeof(char*));
|
|
||||||
entryArray[entryCount] = strdup(dividedJournalEntry->d_name); // copy the dir name into entryArray
|
|
||||||
entryCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
altDebug("└------------------------------\n");
|
|
||||||
// free's some used memory
|
|
||||||
closedir(dividedJournalDirectory);
|
|
||||||
qsort(entryArray + extraOptions, entryCount - extraOptions, sizeof(const char *), reverseCompareString); // sorts the journals entries alphabetically. // the + extraOptions - extraOptions is to not sort "Create new entry for the journal" or Open random entry which is the first element
|
|
||||||
|
|
||||||
// we must now select to create new entry or to enter in old one
|
|
||||||
char *selectedOption = ncursesSelect(entryArray, "Create new entry or acces old entry (Use arrows or WASD, Enter to select):", extraOptions, entryCount - extraOptions, " ", " ", "", shouldDebug); // the "Create new entry for the journal %s" will be the only options. All other will be extraOptions. This is made so that "Create [...] %s" will always be on top
|
|
||||||
debug("Selected option from journal entry selection: %s", selectedOption);
|
|
||||||
if (strcmp(selectedOption, createEntryMessage) == 0) { // create new entry
|
|
||||||
char temp[PATH_MAX];
|
|
||||||
error(strlen(path)+1+strlen(dateWithExtension)+1>PATH_MAX, "Error file path too long. %s/%s must not exceed PATH_MAX", path, dateWithExtension);
|
|
||||||
snprintf(temp, PATH_MAX, "%s/%s", path, dateWithExtension);
|
|
||||||
strncpy(path, temp, PATH_MAX);
|
|
||||||
if (!isStringInArray(dateWithExtension, (const char **)entryArray, entryCount)) { // it only creates it if it doesn't already exist
|
|
||||||
// if it does exist it will just pass the full path
|
|
||||||
debug("Creating entry %s inside %s", date, path);
|
|
||||||
FILE *file;
|
|
||||||
file = fopen(path, "w");
|
|
||||||
error(file == NULL, "program", "%s couldn't be created", path);
|
|
||||||
debug("Writing %s\\n to %s", date, path);
|
|
||||||
fprintf(file, "%s\n", date);
|
|
||||||
fclose(file);
|
|
||||||
free(createEntryMessage);
|
|
||||||
*journalWasUpdated = 1;
|
|
||||||
} else {
|
|
||||||
debug("Today's entry (%s) already exist. We won't create a new one.", dateWithExtension);
|
|
||||||
}
|
|
||||||
} else if (strcmp(selectedOption, entryArray[1]) == 0) { // if we choosed to open a random entry
|
|
||||||
// setting up the seed for rand()
|
|
||||||
srand(time(NULL)); // not totaly random, just pseudorandom
|
|
||||||
int randomEntry = extraOptions + (rand() % (entryCount - extraOptions)); // we get a random number between extraOptions and entryCount (so all journal entries and not extraOptions)
|
|
||||||
char temp[PATH_MAX];
|
|
||||||
snprintf(temp, PATH_MAX, "%s/%s", path, entryArray[randomEntry]); // reconstruct the path
|
|
||||||
strncpy(path, temp, PATH_MAX);
|
|
||||||
debug("Selected random entry %s. It's path is %s.", entryArray[randomEntry], path);
|
|
||||||
} else if (strcmp(selectedOption, entryArray[2]) == 0) { // if we choosed to search
|
|
||||||
char *temp = fzfSelect(path, "Input text to be searched", shouldDebug); // uses ripgrep and fzf to search inside the files
|
|
||||||
char *selectedOption = malloc(PATH_MAX); // we can't just use path as both input and output of snprintf
|
|
||||||
snprintf(selectedOption, PATH_MAX, "%s/%s", path, temp);
|
|
||||||
path = strdup(selectedOption);
|
|
||||||
free(selectedOption);
|
|
||||||
} else { // we just recreate the path to the selected entry
|
|
||||||
// snprintf does not like to have the same variable as input and output so we use a buffer
|
// snprintf does not like to have the same variable as input and output so we use a buffer
|
||||||
char temp[PATH_MAX];
|
char temp[PATH_MAX];
|
||||||
snprintf(temp, PATH_MAX, "%s/%s", path, selectedOption);
|
snprintf(temp, PATH_MAX, "%s/", path); // appends a /. For safety
|
||||||
strncpy(path, temp, PATH_MAX);
|
strncpy(path, temp, PATH_MAX);
|
||||||
debug("Returning path to the selected entry: %s", path);
|
DIR *dividedJournalDirectory = opendir(path);
|
||||||
}
|
error(dividedJournalDirectory == NULL, "program", "Could not open directory %s", path);
|
||||||
} else {
|
/*char **entryArray = NULL; // will contain all the entries of the divided journal
|
||||||
error(1, "program", "%s is not a file and not a directory.", path);
|
// This time it will be different. We must add "invert" the extraOptions to the options so
|
||||||
}
|
that the Create new entry in journal is on top entryArray = realloc(entryArray,
|
||||||
|
sizeof(char*));*/
|
||||||
|
// simpler to just malloc 2 and later realloc
|
||||||
|
const int extraOptions = 3;
|
||||||
|
char **entryArray = malloc(extraOptions * sizeof(char *));
|
||||||
|
char *createEntryMessage = malloc(PATH_MAX);
|
||||||
|
snprintf(createEntryMessage, PATH_MAX, "Create new entry for the journal %s", journal);
|
||||||
|
entryArray[0] = createEntryMessage;
|
||||||
|
entryArray[1] = "Open random entry";
|
||||||
|
entryArray[2] = "Search inside entries";
|
||||||
|
int entryCount = extraOptions; // we need to count how many dirs there is to always readjust
|
||||||
|
// how many memory we alloc
|
||||||
|
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
||||||
|
// for readdir()
|
||||||
|
debug("┌------------------------------\n Detected files and dirs from %s:", path);
|
||||||
|
// iterates over all the entries from the dir
|
||||||
|
while ((dividedJournalEntry = readdir(dividedJournalDirectory)) != NULL) {
|
||||||
|
altDebug("%s\n", dividedJournalEntry->d_name);
|
||||||
|
if (dividedJournalEntry->d_name[0] != '.') { // if the entry don't start with a dot (so hidden dirs and hidden files)
|
||||||
|
char fullPathEntry[PATH_MAX]; // creates a string of size of the maximum path lenght
|
||||||
|
snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s", path,
|
||||||
|
dividedJournalEntry->d_name); // sets the full absolute path to fullPathEntry
|
||||||
|
|
||||||
return path;
|
struct stat metadataPathEntry;
|
||||||
|
if (stat(fullPathEntry, &metadataPathEntry) == 0 && S_ISREG(metadataPathEntry.st_mode)) { // if this entry is a directory
|
||||||
|
entryArray = realloc(entryArray, (entryCount + 1) * sizeof(char *));
|
||||||
|
entryArray[entryCount] = strdup(dividedJournalEntry->d_name); // copy the dir name into entryArray
|
||||||
|
entryCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
altDebug("└------------------------------\n");
|
||||||
|
// free's some used memory
|
||||||
|
closedir(dividedJournalDirectory);
|
||||||
|
qsort(entryArray + extraOptions, entryCount - extraOptions, sizeof(const char *),
|
||||||
|
reverseCompareString); // sorts the journals entries alphabetically. // the +
|
||||||
|
// extraOptions - extraOptions is to not sort "Create new entry
|
||||||
|
// for the journal" or Open random entry which is the first
|
||||||
|
// element
|
||||||
|
|
||||||
|
// we must now select to create new entry or to enter in old one
|
||||||
|
char *selectedOption = ncursesSelect(entryArray, "Create new entry or acces old entry (Use arrows or WASD, Enter to select):", extraOptions, entryCount - extraOptions, " ", " ", "",
|
||||||
|
shouldDebug); // the "Create new entry for the journal %s" will be the only options. All
|
||||||
|
// other will be extraOptions. This is made so that "Create [...] %s" will
|
||||||
|
// always be on top
|
||||||
|
debug("Selected option from journal entry selection: %s", selectedOption);
|
||||||
|
if (strcmp(selectedOption, createEntryMessage) == 0) { // create new entry
|
||||||
|
char temp[PATH_MAX];
|
||||||
|
error(strlen(path) + 1 + strlen(dateWithExtension) + 1 > PATH_MAX, "Error file path too long. %s/%s must not exceed PATH_MAX", path, dateWithExtension);
|
||||||
|
snprintf(temp, PATH_MAX, "%s/%s", path, dateWithExtension);
|
||||||
|
strncpy(path, temp, PATH_MAX);
|
||||||
|
if (!isStringInArray(dateWithExtension, (const char **)entryArray,
|
||||||
|
entryCount)) { // it only creates it if it doesn't already exist
|
||||||
|
// if it does exist it will just pass the full path
|
||||||
|
debug("Creating entry %s inside %s", date, path);
|
||||||
|
FILE *file;
|
||||||
|
file = fopen(path, "w");
|
||||||
|
error(file == NULL, "program", "%s couldn't be created", path);
|
||||||
|
debug("Writing %s\\n to %s", date, path);
|
||||||
|
fprintf(file, "%s\n", date);
|
||||||
|
fclose(file);
|
||||||
|
free(createEntryMessage);
|
||||||
|
*journalWasUpdated = 1;
|
||||||
|
} else {
|
||||||
|
debug("Today's entry (%s) already exist. We won't create a new one.", dateWithExtension);
|
||||||
|
}
|
||||||
|
} else if (strcmp(selectedOption, entryArray[1]) == 0) { // if we choosed to open a random entry
|
||||||
|
// setting up the seed for rand()
|
||||||
|
srand(time(NULL)); // not totaly random, just pseudorandom
|
||||||
|
int randomEntry = extraOptions + (rand() % (entryCount - extraOptions)); // we get a random number between extraOptions and entryCount (so
|
||||||
|
// all journal entries and not extraOptions)
|
||||||
|
char temp[PATH_MAX];
|
||||||
|
snprintf(temp, PATH_MAX, "%s/%s", path,
|
||||||
|
entryArray[randomEntry]); // reconstruct the path
|
||||||
|
strncpy(path, temp, PATH_MAX);
|
||||||
|
debug("Selected random entry %s. It's path is %s.", entryArray[randomEntry], path);
|
||||||
|
} else if (strcmp(selectedOption, entryArray[2]) == 0) { // if we choosed to search
|
||||||
|
char *temp = fzfSelect(path, "Input text to be searched",
|
||||||
|
shouldDebug); // uses ripgrep and fzf to search inside the files
|
||||||
|
char *selectedOption = malloc(PATH_MAX); // we can't just use path as both input and output of snprintf
|
||||||
|
snprintf(selectedOption, PATH_MAX, "%s/%s", path, temp);
|
||||||
|
path = strdup(selectedOption);
|
||||||
|
free(selectedOption);
|
||||||
|
} else { // we just recreate the path to the selected entry
|
||||||
|
// snprintf does not like to have the same variable as input and output so we use a
|
||||||
|
// buffer
|
||||||
|
char temp[PATH_MAX];
|
||||||
|
snprintf(temp, PATH_MAX, "%s/%s", path, selectedOption);
|
||||||
|
strncpy(path, temp, PATH_MAX);
|
||||||
|
debug("Returning path to the selected entry: %s", path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error(1, "program", "%s is not a file and not a directory.", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -1,16 +1,24 @@
|
|||||||
#ifndef NOTES_H
|
#ifndef NOTES_H
|
||||||
#define NOTES_H
|
#define NOTES_H
|
||||||
#include "utils.h"
|
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
|
#include "utils.h"
|
||||||
|
// find which directory contains targetVault
|
||||||
|
char *getDirectoryFromVault(char *targetVault, char **vaultsArray, int vaultTotalNumber, int *vaultNumberPerDirectory, char **directoryArray, int directoryNumber, int shouldDebug);
|
||||||
// gets the journals from the vault.
|
// gets the journals from the vault.
|
||||||
//to distinguish journals from note we use regex.
|
// to distinguish journals from note we use regex.
|
||||||
//Note: we must handle them separatly as journals can either be a single file (which will treat specially) or a directory.
|
// Note: we must handle them separatly as journals can either be a single file (which will treat
|
||||||
|
// specially) or a directory.
|
||||||
char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug);
|
char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug);
|
||||||
// this function is inputed a path to a vault (which was selected before) and outpus all the suitable notes (so not the hidden ones).
|
// this function is inputed a path to a vault (which was selected before) and outpus all the
|
||||||
// journalRegex is the regex code for the journals. If a note matches this code, it is treated as a journal and it is not outputed from this function.
|
// suitable notes (so not the hidden ones). journalRegex is the regex code for the journals. If a
|
||||||
|
// note matches this code, it is treated as a journal and it is not outputed from this function.
|
||||||
char **getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug);
|
char **getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int *count, int shouldDebug);
|
||||||
// this function is inputed a path to a directory (which comes usually from the config file) and outpus all the suitable directories (so not the hidden ones) which will serve as separate vaults for notes.
|
// function gets as input an array of directoryNumber strings. Which are the directories the
|
||||||
char **getVaultsFromDirectory(char *dirString, int *count, int shouldDebug);
|
// function will search for vaults. it returns an array of vaults. the vaults are organized in order
|
||||||
|
// per directory. vaultsPerDirectoryNumber and count should be initiallized before calling the
|
||||||
|
// function and the address be inputed. count is the total number of vaults.
|
||||||
|
// vaultsPerDirectoryNumber gives how many vaults each directory has.
|
||||||
|
char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber, int *vaultsPerDirectoryNumber, int *count, int shouldDebug);
|
||||||
// path is the path to the file.
|
// path is the path to the file.
|
||||||
// journal is the name of the file.
|
// journal is the name of the file.
|
||||||
// journalWasUpdated will be set to 1 if a new entry was created
|
// journalWasUpdated will be set to 1 if a new entry was created
|
||||||
|
|||||||
@@ -1,160 +1,177 @@
|
|||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
void createNewVault(char *dirToVault, int bypass, char *bypassvalue, int shouldDebug) {
|
void createNewVault(char **directoriesArray, int directoryCount, char **vaultsArray, int vaultCount, int bypass, char *bypassvalue, int shouldDebug) {
|
||||||
int duplicateWarning = 0; // set to 1 later if the vault you tried to create already existed
|
int duplicateWarning = 0; // set to 1 later if the vault you tried to create already existed
|
||||||
int emptyWarning = 0; // set to 1 later if inpted empty name
|
int emptyWarning = 0; // set to 1 later if inpted empty name
|
||||||
input_screen:
|
input_screen:
|
||||||
char *vaultName = malloc(PATH_MAX);
|
// choose in which directory the vault will be created
|
||||||
if (!bypass) { // if won't bypass (if -v or --vault weren't set)
|
char *dirToVault = ncursesSelect(directoriesArray, "Select a directory, in which the vault will remain (Use arrows or WASD, Enter to select)", directoryCount, 0, "", "", " ", shouldDebug);
|
||||||
echo();
|
char *vaultName = malloc(PATH_MAX);
|
||||||
keypad(stdscr, FALSE);
|
if (!bypass) { // if won't bypass (if -v or --vault weren't set)
|
||||||
// color code from https://stackoverflow.com/a/73396575
|
echo();
|
||||||
start_color();
|
keypad(stdscr, FALSE);
|
||||||
clear();
|
// color code from https://stackoverflow.com/a/73396575
|
||||||
use_default_colors();
|
start_color();
|
||||||
init_pair(1, COLOR_WHITE, -1); // -1 is blank
|
clear();
|
||||||
init_pair(2, COLOR_RED, -1);
|
use_default_colors();
|
||||||
attron(COLOR_PAIR(1));
|
init_pair(1, COLOR_WHITE, -1); // -1 is blank
|
||||||
printw("Enter the name of the new vault: ");
|
init_pair(2, COLOR_RED, -1);
|
||||||
mvprintw(3, 0, "Unsafe characters such as \\ and / will be replaced by _");
|
attron(COLOR_PAIR(1));
|
||||||
attroff(COLOR_PAIR(1));
|
printw("Enter the name of the new vault: ");
|
||||||
if (duplicateWarning) {
|
mvprintw(3, 0, "Unsafe characters such as \\ and / will be replaced by _");
|
||||||
attron(COLOR_PAIR(2));
|
attroff(COLOR_PAIR(1));
|
||||||
mvprintw(4, 0, "A vault with this name already exists. Please input another name");
|
if (duplicateWarning) {
|
||||||
attroff(COLOR_PAIR(2));
|
attron(COLOR_PAIR(2));
|
||||||
|
mvprintw(4, 0, "A vault with this name already exists. Please input another name");
|
||||||
|
attroff(COLOR_PAIR(2));
|
||||||
|
}
|
||||||
|
if (emptyWarning) {
|
||||||
|
attron(COLOR_PAIR(2));
|
||||||
|
mvprintw(5, 0, "The vault's name must not be empty. Please input another name");
|
||||||
|
attroff(COLOR_PAIR(2));
|
||||||
|
}
|
||||||
|
move(1, 1); // replace cursor
|
||||||
|
wgetnstr(stdscr, vaultName, PATH_MAX - 1);
|
||||||
|
refresh();
|
||||||
|
endwin();
|
||||||
|
reset_shell_mode();
|
||||||
|
fflush(stdout);
|
||||||
|
fflush(stderr);
|
||||||
|
} else {
|
||||||
|
strncpy(vaultName, bypassvalue, PATH_MAX - 2); // -2 (and later -1) because indexing
|
||||||
|
vaultName[PATH_MAX - 1] = '\0';
|
||||||
}
|
}
|
||||||
if (emptyWarning) {
|
// check if no empty string
|
||||||
attron(COLOR_PAIR(2));
|
if (strcmp(vaultName, "") == 0) {
|
||||||
mvprintw(5, 0, "The vault's name must not be empty. Please input another name");
|
emptyWarning = 1;
|
||||||
attroff(COLOR_PAIR(2));
|
goto input_screen;
|
||||||
}
|
}
|
||||||
move(1, 1); // replace cursor
|
debug("Inputed vaultName=%s", vaultName);
|
||||||
wgetnstr(stdscr, vaultName, PATH_MAX-1);
|
sanitize(vaultName);
|
||||||
refresh();
|
debug("Sanitized vaultName=%s", vaultName);
|
||||||
endwin();
|
|
||||||
reset_shell_mode();
|
|
||||||
fflush(stdout);
|
|
||||||
fflush(stderr);
|
|
||||||
} else {
|
|
||||||
strncpy(vaultName, bypassvalue, PATH_MAX -2); // -2 (and later -1) because indexing
|
|
||||||
vaultName[PATH_MAX-1] = '\0';
|
|
||||||
}
|
|
||||||
// check if no empty string
|
|
||||||
if (strcmp(vaultName, "") == 0) {
|
|
||||||
emptyWarning = 1;
|
|
||||||
goto input_screen;
|
|
||||||
}
|
|
||||||
debug("Inputed vaultName=%s", vaultName);
|
|
||||||
sanitize(vaultName);
|
|
||||||
debug("Sanitized vaultName=%s", vaultName);
|
|
||||||
|
|
||||||
struct stat st = {0}; // https://stackoverflow.com/a/7430262
|
if (!isStringInArray(vaultName, (const char **)vaultsArray,
|
||||||
char vaultFullPath[PATH_MAX];
|
vaultCount)) { // if vault doesnt already exists. We avoid vaults with the
|
||||||
sprintf(vaultFullPath, "%s/%s/", dirToVault, vaultName);
|
// same name even if they are from different directories.
|
||||||
if (stat(vaultFullPath, &st) == -1) {
|
char vaultFullPath[PATH_MAX]; // recreating the full path
|
||||||
mkdir(vaultFullPath, 0744);
|
sprintf(vaultFullPath, "%s/%s/", dirToVault, vaultName);
|
||||||
} else {
|
|
||||||
// if stat(...) != -1 it means the vault already exist. We will go back to the input screen with a new message.
|
mkdir(vaultFullPath, 0744);
|
||||||
duplicateWarning = 1;
|
} else {
|
||||||
goto input_screen;
|
duplicateWarning = 1;
|
||||||
}
|
goto input_screen;
|
||||||
free(vaultName);
|
}
|
||||||
|
free(vaultName);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, char *journalRegex, int shouldDebug) {
|
char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, char *journalRegex, int shouldDebug) {
|
||||||
// input from user for the name
|
// input from user for the name
|
||||||
char *fileName = malloc(BUFFER_SIZE);
|
char *fileName = malloc(BUFFER_SIZE);
|
||||||
if (!bypass) { // if we don't bypass. (if -n or --note weren't set.)
|
if (!bypass) { // if we don't bypass. (if -n or --note weren't set.)
|
||||||
echo();
|
echo();
|
||||||
keypad(stdscr, FALSE);
|
keypad(stdscr, FALSE);
|
||||||
clear();
|
clear();
|
||||||
printw("Enter the name of the new note: ");
|
printw("Enter the name of the new note: ");
|
||||||
mvprintw(3, 0, "Unsafe characters such as \\, and / will be replaced by _");
|
mvprintw(3, 0, "Unsafe characters such as \\, and / will be replaced by _");
|
||||||
mvprintw(4, 0, "If the name matches with the regex for a journal (%s), it will create a journal instead of a note.", journalRegex);
|
mvprintw(4, 0,
|
||||||
move(1,1);
|
"If the name matches with the regex for a journal (%s), it will create a journal "
|
||||||
wgetnstr(stdscr, fileName, BUFFER_SIZE-4); //limits the buffer to prevent overflow (-4 to account indexing and from ".md" in case we need to add it later)
|
"instead of a note.",
|
||||||
refresh();
|
journalRegex);
|
||||||
endwin();
|
move(1, 1);
|
||||||
reset_shell_mode();
|
wgetnstr(stdscr, fileName,
|
||||||
fflush(stdout);
|
BUFFER_SIZE - 4); // limits the buffer to prevent overflow (-4 to account indexing
|
||||||
fflush(stderr);
|
// and from ".md" in case we need to add it later)
|
||||||
} else { // bypasses user input if we bypass is set to 1
|
refresh();
|
||||||
strncpy(fileName, bypassvalue, BUFFER_SIZE);
|
endwin();
|
||||||
fileName[BUFFER_SIZE-1] = '\0';
|
reset_shell_mode();
|
||||||
}
|
fflush(stdout);
|
||||||
error(strcmp(fileName, "") == 0, "user", "fileName is empty"); // replace this with a warning and add a warning if duplicate file and handle case where multiple warnings (if such case is possible)
|
fflush(stderr);
|
||||||
// check/sanitize the input
|
} else { // bypasses user input if we bypass is set to 1
|
||||||
debug("Inputed fileName=%s", fileName);
|
strncpy(fileName, bypassvalue, BUFFER_SIZE);
|
||||||
sanitize(fileName);
|
fileName[BUFFER_SIZE - 1] = '\0';
|
||||||
debug("Sanitized fileName=%s (We might append .md later", fileName);
|
|
||||||
// we trow an error if the file already exists (previous behaviour was overwriting the file)
|
|
||||||
char path[PATH_MAX];
|
|
||||||
snprintf(path, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName);
|
|
||||||
struct stat st;
|
|
||||||
error(stat(path, &st) == 0, "user", "%s already exists", fileName);
|
|
||||||
// if it matches with the journalRegex we treat it as a journal instead of a note
|
|
||||||
regex_t regex;
|
|
||||||
int regexReturn = regcomp(®ex, journalRegex, 0);
|
|
||||||
error(regexReturn, "program", "Regex compilation failed.");
|
|
||||||
regexReturn = regexec(®ex, fileName, 0, NULL, 0);
|
|
||||||
if (!regexReturn) {
|
|
||||||
debug("%s matches with %s treating it as a journal", fileName, journalRegex);
|
|
||||||
char **options = malloc(32); // the number of bytes is exactly what in the two strings
|
|
||||||
options[0] = "Divided journal";
|
|
||||||
options[1] = "Unified journal";
|
|
||||||
char *optionSelected = ncursesSelect(options, "Select which type of journal you want to create (Use arrows or WASD, Enter to select):", 2, 0, "", "", " ", shouldDebug);
|
|
||||||
debug("%s was selected to be a %s", fileName, optionSelected);
|
|
||||||
if (strcmp(optionSelected, options[0]) == 0) { // if it is a divided journal
|
|
||||||
char *fileFullPath = malloc(PATH_MAX);
|
|
||||||
snprintf(fileFullPath, PATH_MAX, "%s/%s/%s/", dirToVault, vaultFromDir, fileName);
|
|
||||||
struct stat st = {0};
|
|
||||||
if (stat(fileFullPath, &st) == -1) {
|
|
||||||
error(!mkdir(fileFullPath, 0744), "program", "mkdir failed");
|
|
||||||
} else {
|
|
||||||
error(1, "program", "%s could not be created", fileFullPath);
|
|
||||||
}
|
|
||||||
free(fileFullPath);
|
|
||||||
} else { // if it is a unified journal
|
|
||||||
char *fileFullPath = malloc(PATH_MAX);
|
|
||||||
snprintf(fileFullPath, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName);
|
|
||||||
int len = strlen(fileFullPath);
|
|
||||||
if (fileFullPath[len-3] != '.' || fileFullPath[len-2] != 'm' || fileFullPath[len-1] != 'd') { // there might be a cleaner way to do this
|
|
||||||
error(len > PATH_MAX - 3, "user", "%s is too big (greater than PATH_MAX-3) and we can't append .md", fileFullPath);
|
|
||||||
strncat(fileFullPath, ".md", PATH_MAX);
|
|
||||||
// we checked before if fileName didn't already exist.
|
|
||||||
// we must redo it as we add a .mode
|
|
||||||
struct stat st;
|
|
||||||
error(stat(fileFullPath, &st) == 0, "user", "%s already exists", fileFullPath);
|
|
||||||
}
|
|
||||||
FILE *filePointer;
|
|
||||||
filePointer = fopen(fileFullPath, "w"); // creates and opens the file
|
|
||||||
error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath);
|
|
||||||
fprintf(filePointer, "### %s\n", fileName);
|
|
||||||
fclose(filePointer); // closes the file so that nvim could open it
|
|
||||||
free(fileFullPath);
|
|
||||||
}
|
}
|
||||||
free(options); // they are useless now
|
error(strcmp(fileName, "") == 0, "user",
|
||||||
} else { // normal process for a note
|
"fileName is empty"); // replace this with a warning and add a warning if duplicate file and
|
||||||
debug("%s does not match with %s treating it as a note", fileName, journalRegex);
|
// handle case where multiple warnings (if such case is possible)
|
||||||
// if there is no .md add an .md
|
// check/sanitize the input
|
||||||
int len = strlen(fileName);
|
debug("Inputed fileName=%s", fileName);
|
||||||
if (fileName[len-3] != '.' || fileName[len-2] != 'm' || fileName[len-1] != 'd') { // there might be a cleaner way to do this
|
sanitize(fileName);
|
||||||
strcat(fileName, ".md"); // this should not cause an overflow issue as we get at most 252 chars (+'.'+'m'+'d'+'\0' makes it to 256) with wgetnstr
|
debug("Sanitized fileName=%s (We might append .md later", fileName);
|
||||||
|
// we trow an error if the file already exists (previous behaviour was overwriting the file)
|
||||||
|
char path[PATH_MAX];
|
||||||
|
snprintf(path, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName);
|
||||||
|
struct stat st;
|
||||||
|
error(stat(path, &st) == 0, "user", "%s already exists", fileName);
|
||||||
|
// if it matches with the journalRegex we treat it as a journal instead of a note
|
||||||
|
regex_t regex;
|
||||||
|
int regexReturn = regcomp(®ex, journalRegex, 0);
|
||||||
|
error(regexReturn, "program", "Regex compilation failed.");
|
||||||
|
regexReturn = regexec(®ex, fileName, 0, NULL, 0);
|
||||||
|
if (!regexReturn) {
|
||||||
|
debug("%s matches with %s treating it as a journal", fileName, journalRegex);
|
||||||
|
char **options = malloc(32); // the number of bytes is exactly what in the two strings
|
||||||
|
options[0] = "Divided journal";
|
||||||
|
options[1] = "Unified journal";
|
||||||
|
char *optionSelected = ncursesSelect(options,
|
||||||
|
"Select which type of journal you want to create "
|
||||||
|
"(Use arrows or WASD, Enter to select):",
|
||||||
|
2, 0, "", "", " ", shouldDebug);
|
||||||
|
debug("%s was selected to be a %s", fileName, optionSelected);
|
||||||
|
if (strcmp(optionSelected, options[0]) == 0) { // if it is a divided journal
|
||||||
|
char *fileFullPath = malloc(PATH_MAX);
|
||||||
|
snprintf(fileFullPath, PATH_MAX, "%s/%s/%s/", dirToVault, vaultFromDir, fileName);
|
||||||
|
struct stat st = {0};
|
||||||
|
if (stat(fileFullPath, &st) == -1) {
|
||||||
|
error(mkdir(fileFullPath, 0744), "program", "mkdir failed");
|
||||||
|
} else {
|
||||||
|
error(1, "program", "%s could not be created", fileFullPath);
|
||||||
|
}
|
||||||
|
free(fileFullPath);
|
||||||
|
} else { // if it is a unified journal
|
||||||
|
char *fileFullPath = malloc(PATH_MAX);
|
||||||
|
// we append .md directly to fileName and not only to fileFullPath to avoid passing the wrong fileName later.
|
||||||
|
int lenName = strlen(fileName);
|
||||||
|
if (fileName[lenName - 3] != '.' && fileName[lenName - 2] != 'm' && fileName[lenName - 2] != '1') {
|
||||||
|
strcat(fileName, ".md");
|
||||||
|
debug("We just appended .md to %s", fileName);
|
||||||
|
}
|
||||||
|
snprintf(fileFullPath, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName);
|
||||||
|
// we verify if the file doesn't already exist as we might just have changed the fileName and (by extension) the file path.
|
||||||
|
struct stat metadata;
|
||||||
|
error(stat(fileFullPath, &metadata) == 0, "user", "%s already exists", fileFullPath);
|
||||||
|
FILE *filePointer;
|
||||||
|
filePointer = fopen(fileFullPath, "w"); // creates and opens the file
|
||||||
|
error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath);
|
||||||
|
fprintf(filePointer, "### %s\n", fileName);
|
||||||
|
fclose(filePointer); // closes the file so that nvim could open it
|
||||||
|
free(fileFullPath);
|
||||||
|
}
|
||||||
|
free(options); // they are useless now
|
||||||
|
} else { // normal process for a note
|
||||||
|
debug("%s does not match with %s treating it as a note", fileName, journalRegex);
|
||||||
|
// if there is no .md add an .md
|
||||||
|
int len = strlen(fileName);
|
||||||
|
if (fileName[len - 3] != '.' || fileName[len - 2] != 'm' || fileName[len - 1] != 'd') { // there might be a cleaner way to do this
|
||||||
|
strcat(fileName, ".md"); // this should not cause an overflow issue as we get at most
|
||||||
|
// 252 chars (+'.'+'m'+'d'+'\0' makes it to 256) with wgetnstr
|
||||||
|
}
|
||||||
|
char *fileFullPath = malloc(PATH_MAX); // this dinamically allocated because we use it in
|
||||||
|
// the main function to call openEditor
|
||||||
|
sprintf(fileFullPath, "%s/%s/%s", dirToVault, vaultFromDir, fileName);
|
||||||
|
FILE *filePointer;
|
||||||
|
filePointer = fopen(fileFullPath, "w"); // creates and opens the file
|
||||||
|
error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath);
|
||||||
|
fprintf(filePointer, "### %s\n", fileName);
|
||||||
|
fclose(filePointer); // closes the file so that nvim could open it
|
||||||
|
free(fileFullPath);
|
||||||
}
|
}
|
||||||
char *fileFullPath = malloc(PATH_MAX); // this dinamically allocated because we use it in the main function to call openEditor
|
return fileName;
|
||||||
sprintf(fileFullPath, "%s/%s/%s", dirToVault, vaultFromDir, fileName);
|
|
||||||
FILE *filePointer;
|
|
||||||
filePointer = fopen(fileFullPath, "w"); // creates and opens the file
|
|
||||||
error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath);
|
|
||||||
fprintf(filePointer, "### %s\n", fileName);
|
|
||||||
fclose(filePointer); // closes the file so that nvim could open it
|
|
||||||
free(fileFullPath);
|
|
||||||
}
|
|
||||||
return fileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
char *fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
||||||
// we are gonna write each line of each files (with the filename and the line number to an index)
|
// we are gonna write each line of each files (with the filename and the line number to an
|
||||||
|
// index)
|
||||||
char command[CMD_BUFFER];
|
char command[CMD_BUFFER];
|
||||||
char indexFile[] = "/tmp/notewrapper_index_XXXXXX";
|
char indexFile[] = "/tmp/notewrapper_index_XXXXXX";
|
||||||
|
|
||||||
@@ -167,11 +184,7 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
|||||||
* Build index once into temp file
|
* Build index once into temp file
|
||||||
* format: file:line:content
|
* format: file:line:content
|
||||||
*/
|
*/
|
||||||
snprintf(command, sizeof(command),
|
snprintf(command, sizeof(command), "rg --line-number --no-heading --color=never . \"%s\" > %s", pathToFiles, indexFile);
|
||||||
"rg --line-number --no-heading --color=never . \"%s\" > %s",
|
|
||||||
pathToFiles,
|
|
||||||
indexFile
|
|
||||||
);
|
|
||||||
|
|
||||||
debug("INDEX CMD: %s", command);
|
debug("INDEX CMD: %s", command);
|
||||||
|
|
||||||
@@ -221,12 +234,11 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
|||||||
*/
|
*/
|
||||||
snprintf(command, sizeof(command),
|
snprintf(command, sizeof(command),
|
||||||
"cat %s | fzf --delimiter ':' --prompt='%s' "
|
"cat %s | fzf --delimiter ':' --prompt='%s' "
|
||||||
"--preview 'file={1}; line={2}; nl -ba \"$file\" | sed -n \"$((line-5)),$((line+5))p\" | sed \"$((line-$(($((line-5))))+1))s/^/\\x1b[31m-> \\x1b[0m/\"' "
|
"--preview 'file={1}; line={2}; nl -ba \"$file\" | sed -n \"$((line-5)),$((line+5))p\" | "
|
||||||
|
"sed \"$((line-$(($((line-5))))+1))s/^/\\x1b[31m-> \\x1b[0m/\"' "
|
||||||
"--preview-window=right:60%%:hidden "
|
"--preview-window=right:60%%:hidden "
|
||||||
"--bind 'change:show-preview'",
|
"--bind 'change:show-preview'",
|
||||||
indexFile,
|
indexFile, selectText ? selectText : "> ");
|
||||||
selectText ? selectText : "> "
|
|
||||||
);
|
|
||||||
debug("FZF CMD: %s", command);
|
debug("FZF CMD: %s", command);
|
||||||
|
|
||||||
FILE *fzfPipe = popen(command, "r");
|
FILE *fzfPipe = popen(command, "r");
|
||||||
@@ -249,8 +261,8 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
|||||||
char *token = strtok(result, "/");
|
char *token = strtok(result, "/");
|
||||||
result = NULL;
|
result = NULL;
|
||||||
while (token != NULL) {
|
while (token != NULL) {
|
||||||
result = token;
|
result = token;
|
||||||
token = strtok(NULL, "/"); //Passing NULL means “don’t start a new string, resume
|
token = strtok(NULL, "/"); // Passing NULL means “don’t start a new string, resume
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
@@ -260,81 +272,84 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int shouldDebug) {
|
char *ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText,
|
||||||
int highlight = 0; //curently highlighted option
|
int shouldDebug) { // TODO we should see how it handles larges strings (like large directories)
|
||||||
|
int highlight = 0; // curently highlighted option
|
||||||
int key;
|
int key;
|
||||||
|
|
||||||
cbreak(); // disable line buffering
|
cbreak(); // disable line buffering
|
||||||
noecho(); // don't echo key presses
|
noecho(); // don't echo key presses
|
||||||
keypad(stdscr, TRUE); // enable arrow keys
|
keypad(stdscr, TRUE); // enable arrow keys
|
||||||
start_color();
|
start_color();
|
||||||
curs_set(0); // sets the cursor to invisible. (We will emulate the cursor with a hightlight that go up and down).
|
curs_set(0); // sets the cursor to invisible. (We will emulate the cursor with a hightlight that
|
||||||
|
// go up and down).
|
||||||
use_default_colors();
|
use_default_colors();
|
||||||
init_pair(1, COLOR_WHITE, -1); // color for optionsNumber (so notes, vaults, etc.)
|
init_pair(1, COLOR_WHITE, -1); // color for optionsNumber (so notes, vaults, etc.)
|
||||||
init_pair(2, COLOR_BLUE, -1); // color for extraOptionsNumber (so settings, delete notes, create vault, etc.)
|
init_pair(2, COLOR_BLUE,
|
||||||
|
-1); // color for extraOptionsNumber (so settings, delete notes, create vault, etc.)
|
||||||
while (1) {
|
while (1) {
|
||||||
clear();
|
clear();
|
||||||
attron(COLOR_PAIR(1));
|
attron(COLOR_PAIR(1));
|
||||||
mvprintw(0,0, "%s", optionsText);
|
mvprintw(0, 0, "%s", optionsText);
|
||||||
int offset = 1;
|
int offset = 1;
|
||||||
if (strcmp(bottomText, "") != 0) {
|
if (strcmp(bottomText, "") != 0) {
|
||||||
mvprintw(offset, 1, "%s", bottomText);
|
mvprintw(offset, 1, "%s", bottomText);
|
||||||
offset++;
|
offset++;
|
||||||
}
|
|
||||||
// Print options with highlighting
|
|
||||||
for (int i = 0; i < optionsNumber; i++) {
|
|
||||||
if (i == highlight) {
|
|
||||||
attron(A_REVERSE);
|
|
||||||
} // highlight selected
|
|
||||||
mvprintw(i+offset, 2, "%s", options[i]);
|
|
||||||
if (i == highlight) {
|
|
||||||
attroff(A_REVERSE);
|
|
||||||
}
|
}
|
||||||
}
|
// Print options with highlighting
|
||||||
attroff(COLOR_PAIR(1));
|
for (int i = 0; i < optionsNumber; i++) {
|
||||||
if (strcmp(middleText, "") != 0) {
|
if (i == highlight) {
|
||||||
mvprintw(offset+optionsNumber, 1, "%s", middleText);
|
attron(A_REVERSE);
|
||||||
offset++;
|
} // highlight selected
|
||||||
}
|
mvprintw(i + offset, 2, "%s", options[i]);
|
||||||
// Printf extraOptions with highlighting
|
if (i == highlight) {
|
||||||
attron(COLOR_PAIR(2));
|
attroff(A_REVERSE);
|
||||||
for (int k = optionsNumber; k < optionsNumber + extraOptionsNumber; k++) {
|
}
|
||||||
if (k == highlight) {
|
|
||||||
attron(A_REVERSE);
|
|
||||||
}
|
}
|
||||||
mvprintw(k+offset, 2, "%s", options[k]);
|
attroff(COLOR_PAIR(1));
|
||||||
if (k == highlight) {
|
if (strcmp(middleText, "") != 0) {
|
||||||
attroff(A_REVERSE);
|
mvprintw(offset + optionsNumber, 1, "%s", middleText);
|
||||||
|
offset++;
|
||||||
}
|
}
|
||||||
}
|
// Printf extraOptions with highlighting
|
||||||
attroff(COLOR_PAIR(2));
|
attron(COLOR_PAIR(2));
|
||||||
attron(COLOR_PAIR(1));
|
for (int k = optionsNumber; k < optionsNumber + extraOptionsNumber; k++) {
|
||||||
mvprintw(offset+optionsNumber+extraOptionsNumber, 1, "%s", topText);
|
if (k == highlight) {
|
||||||
attroff(COLOR_PAIR(1));
|
attron(A_REVERSE);
|
||||||
key = getch(); //get some int which value correspond to some key being pressed
|
}
|
||||||
|
mvprintw(k + offset, 2, "%s", options[k]);
|
||||||
|
if (k == highlight) {
|
||||||
|
attroff(A_REVERSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attroff(COLOR_PAIR(2));
|
||||||
|
attron(COLOR_PAIR(1));
|
||||||
|
mvprintw(offset + optionsNumber + extraOptionsNumber, 1, "%s", topText);
|
||||||
|
attroff(COLOR_PAIR(1));
|
||||||
|
key = getch(); // get some int which value correspond to some key being pressed
|
||||||
|
|
||||||
switch(key) {
|
switch (key) {
|
||||||
case KEY_UP:
|
case KEY_UP:
|
||||||
case 'w':
|
case 'w':
|
||||||
case 'W':
|
case 'W':
|
||||||
highlight--;
|
highlight--;
|
||||||
if (highlight < 0) {
|
if (highlight < 0) {
|
||||||
highlight = optionsNumber + extraOptionsNumber - 1;// can't select the -1nth option
|
highlight = optionsNumber + extraOptionsNumber - 1; // can't select the -1nth option
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
case 'S':
|
case 'S':
|
||||||
case KEY_DOWN:
|
case KEY_DOWN:
|
||||||
highlight++;
|
highlight++;
|
||||||
if (highlight >= optionsNumber + extraOptionsNumber) {
|
if (highlight >= optionsNumber + extraOptionsNumber) {
|
||||||
highlight = 0;
|
highlight = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 10: //\n
|
case 10: //\n
|
||||||
goto end_loop;
|
goto end_loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end_loop:
|
end_loop:
|
||||||
endwin(); // end ncurses mode
|
endwin(); // end ncurses mode
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
debug("Selected option: %s", options[highlight]);
|
debug("Selected option: %s", options[highlight]);
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
#ifndef UI_H
|
#ifndef UI_H
|
||||||
#define UI_H
|
#define UI_H
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <regex.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ncurses.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <pwd.h>
|
#include <unistd.h>
|
||||||
#include <ctype.h>
|
#define CMD_BUFFER 4096 // for fzfSelect
|
||||||
#include <limits.h>
|
|
||||||
#include <regex.h>
|
|
||||||
#include "utils.h"
|
|
||||||
#define CMD_BUFFER 4096 // for fzfSelect
|
|
||||||
#define RESULT_BUFFER 1024 // for fzfSelect
|
#define RESULT_BUFFER 1024 // for fzfSelect
|
||||||
|
|
||||||
/*Uses ncurses to get an input from the user.
|
/*Uses ncurses to get an input from the user.
|
||||||
@@ -22,24 +22,23 @@ returns the path to the note.
|
|||||||
If you want to bypass the input TUI set bypass to 1 and bypassvalue to the note name
|
If you want to bypass the input TUI set bypass to 1 and bypassvalue to the note name
|
||||||
*/
|
*/
|
||||||
char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, char *journalRegex, int debug);
|
char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, char *journalRegex, int debug);
|
||||||
/*Uses ncurses to get an input from the user.
|
/*
|
||||||
|
Chooses in which directory it want to create the vault.
|
||||||
|
Uses ncurses to get an input from the user.
|
||||||
Creates a new vault with this input.
|
Creates a new vault with this input.
|
||||||
If vault already exists, prints a warning.
|
If vault already exists, prints a warning.
|
||||||
returns nothing. */
|
returns nothing. */
|
||||||
void createNewVault(char *dirToVault, int bypass, char *bypassvalue, int debug);
|
void createNewVault(char **directoriesArray, int directoryCount, char **vaultsArray, int vaultCount, int bypass, char *bypassvalue, int debug);
|
||||||
// uses fzf and ripgrep to search inside the files to choose between all the options.
|
// uses fzf and ripgrep to search inside the files to choose between all the options.
|
||||||
char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug);
|
char *fzfSelect(char *pathToFiles, char *selectText, int shouldDebug);
|
||||||
/*this function is used multiple times let the user select one options from many with ncurses in a TUI.
|
/*this function is used multiple times let the user select one options from many with ncurses in a
|
||||||
options is the array of strings with all the options.
|
TUI. options is the array of strings with all the options. optionsText is the text that will be
|
||||||
optionsText is the text that will be printed at the top (For example: "Please select ...").
|
printed at the top (For example: "Please select ..."). we distinguish options from extraOptions.
|
||||||
we distinguish options from extraOptions.
|
|
||||||
options could be the list of all notes or all vaults.
|
options could be the list of all notes or all vaults.
|
||||||
extraOptions are options that are special and have a special color (for ex: "Delete vault", "Settings", etc.).
|
extraOptions are options that are special and have a special color (for ex: "Delete vault",
|
||||||
note: extraOptions should be at the end of options.
|
"Settings", etc.). note: extraOptions should be at the end of options. topText is printed between
|
||||||
topText is printed between options text and the start of options.
|
options text and the start of options. middleText (usally \n) is printed between options and
|
||||||
middleText (usally \n) is printed between options and extraOptions.
|
extraOptions. bottomText is printed bellow. topText and middle must be exactly one line. If you want
|
||||||
bottomText is printed bellow.
|
empty lines use " " and not "". returns the selected option. */
|
||||||
topText and middle must be exactly one line. If you want empty lines use " " and not "".
|
char *ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int debug);
|
||||||
returns the selected option. */
|
|
||||||
char* ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int debug);
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+441
-207
@@ -1,8 +1,7 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
const char *supportedEditor[] = {"neovim", "vim", "nano"};
|
const char *supportedEditor[] = {"helix", "jed", "kakoune", "micro", "nano", "neovim", "vi", "vim"};
|
||||||
const int numEditors = 3;
|
const int numEditors = 8;
|
||||||
|
|
||||||
int compareString(const void *a, const void *b) {
|
int compareString(const void *a, const void *b) {
|
||||||
const char *str1 = *(const char **)a;
|
const char *str1 = *(const char **)a;
|
||||||
@@ -12,82 +11,90 @@ int compareString(const void *a, const void *b) {
|
|||||||
int reverseCompareString(const void *a, const void *b) {
|
int reverseCompareString(const void *a, const void *b) {
|
||||||
const char *str1 = *(const char **)a;
|
const char *str1 = *(const char **)a;
|
||||||
const char *str2 = *(const char **)b;
|
const char *str2 = *(const char **)b;
|
||||||
return -1*strcmp(str1, str2);
|
return -1 * strcmp(str1, str2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getCurrentTime(int *hour, int *minute, int *second) {
|
void getCurrentTime(int *hour, int *minute, int *second) {
|
||||||
time_t now = time(NULL); // Get current time in seconds since epoch
|
time_t now = time(NULL); // Get current time in seconds since epoch
|
||||||
struct tm *local = localtime(&now); // Convert to local time structure
|
struct tm *local = localtime(&now); // Convert to local time structure
|
||||||
|
|
||||||
*hour = local->tm_hour; // Extract hour
|
*hour = local->tm_hour; // Extract hour
|
||||||
*minute = local->tm_min; // Extract minutes
|
*minute = local->tm_min; // Extract minutes
|
||||||
*second = local->tm_sec; // Extract seconds
|
*second = local->tm_sec; // Extract seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
void _debug(const int d, const char *file, const int line, const char *function, const char *message, ...) { // use for formatted debug
|
void _debug(const int d, const char *file, const int line, const char *function, const char *message, ...) { // use for formatted debug
|
||||||
if (d) {
|
if (d) {
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
va_list args; //variadic function stuff
|
va_list args; // variadic function stuff
|
||||||
va_start(args, message);
|
va_start(args, message);
|
||||||
int h, m, s;
|
int h, m, s;
|
||||||
getCurrentTime(&h, &m, &s);
|
getCurrentTime(&h, &m, &s);
|
||||||
fprintf(stderr, "\e[0;32m[DEBUG -- %d:%d:%d] From file %s line %d function %s:\e[0m\n", h, m, s, file, line, function);
|
fprintf(stderr, "\e[0;32m[DEBUG -- %d:%d:%d] From file %s line %d function %s:\e[0m\n", h, m, s, file, line, function);
|
||||||
vfprintf(stderr, message, args);
|
vfprintf(stderr, message, args);
|
||||||
fprintf(stderr, "\e[0m\n");
|
fprintf(stderr, "\e[0m\n");
|
||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void _altDebug(const int d, const char *message, ...) { // use for less formal debuggin. (usefull if enumerating or making a list
|
void _altDebug(const int d, const char *message,
|
||||||
if (d) {
|
...) { // use for less formal debuggin. (usefull if enumerating or making a list
|
||||||
fflush(stdout);
|
if (d) {
|
||||||
fflush(stderr);
|
fflush(stdout);
|
||||||
va_list args;
|
fflush(stderr);
|
||||||
va_start(args, message);
|
va_list args;
|
||||||
vfprintf(stderr, message, args);
|
va_start(args, message);
|
||||||
va_end(args);
|
vfprintf(stderr, message, args);
|
||||||
}
|
va_end(args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _error(const int shouldDebug, const int condition, const char *type, const char *file, const int line, const char *function, const char *message, ...) { // used for formatted errors
|
void _error(const int shouldDebug, const int condition, const char *type, const char *file, const int line, const char *function, const char *message,
|
||||||
if (condition) {
|
...) { // used for formatted errors
|
||||||
int h, m, s;
|
if (condition) {
|
||||||
getCurrentTime(&h, &m, &s);
|
int h, m, s;
|
||||||
fprintf(stderr, "\e[0;31m[%s ERROR -- %d:%d:%d] From file %s line %d function %s:\n", type, h, m, s, file, line, function);
|
getCurrentTime(&h, &m, &s);
|
||||||
if (errno != 0) {
|
fprintf(stderr, "\e[0;31m[%s ERROR -- %d:%d:%d] From file %s line %d function %s:\n", type, h, m, s, file, line, function);
|
||||||
fprintf(stderr, " (System-level error message: %s)\n", strerror(errno));
|
if (errno != 0) {
|
||||||
} else {
|
fprintf(stderr, " (System-level error message: %s)\n", strerror(errno));
|
||||||
fprintf(stderr, " (No system-level error; issue is application-level)\n");
|
} else {
|
||||||
|
fprintf(stderr, " (No system-level error; issue is application-level)\n");
|
||||||
|
}
|
||||||
|
va_list args;
|
||||||
|
va_start(args, message);
|
||||||
|
vfprintf(stderr, message, args);
|
||||||
|
va_end(args);
|
||||||
|
if (!shouldDebug) {
|
||||||
|
fprintf(stderr, "\nRunning notewrapper -V might give you more information.");
|
||||||
|
}
|
||||||
|
fprintf(stderr, "\e[0m\n");
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
va_list args;
|
|
||||||
va_start(args, message);
|
|
||||||
vfprintf(stderr, message, args);
|
|
||||||
va_end(args);
|
|
||||||
if (!shouldDebug) {
|
|
||||||
fprintf(stderr, "\nRunning notewrapper -V might give you more information.");
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\e[0m\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void copyDir(const char *source, const char *destination, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug) {
|
static void copyDir(const char *source, const char *destination, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug) {
|
||||||
debug("Backuping... source: %s and destination: %s", source, destination);
|
debug("Backuping... source: %s and destination: %s", source, destination);
|
||||||
|
debug("Rsync has %d extra arguments", rsyncArgsNumber);
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
|
|
||||||
error(pid < 0, "program", "fork() faild. pid = %d", pid);
|
error(pid < 0, "program", "fork() faild. pid = %d", pid);
|
||||||
|
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
// Child process: execute rsync
|
// Child process: execute rsync
|
||||||
char **args = malloc((4 + rsyncArgsNumber)*sizeof(char*));
|
char **args = malloc((4 + rsyncArgsNumber) * sizeof(char *));
|
||||||
args[0] = "rsync";
|
args[0] = "rsync";
|
||||||
for (int i = 0; i < rsyncArgsNumber; i++) {
|
for (int i = 0; i < rsyncArgsNumber; i++) {
|
||||||
args[i+1] = (char *)rsyncArgs[i];
|
args[i + 1] = (char *)rsyncArgs[i];
|
||||||
if (i == rsyncArgsNumber-1) { // last loop
|
if (i == rsyncArgsNumber - 1) { // last loop
|
||||||
args[i+2] = (char*)source;
|
args[i + 2] = (char *)source;
|
||||||
args[i+3] = (char*)destination;
|
args[i + 3] = (char *)destination;
|
||||||
args[i+4] = NULL; // execvp expect last arg to be NULL
|
args[i + 4] = NULL; // execvp expect last arg to be NULL
|
||||||
}
|
debug("rsync command:");
|
||||||
|
for (int k = 0; k <= i + 4; k++) {
|
||||||
|
altDebug("%s ", args[k]);
|
||||||
|
}
|
||||||
|
altDebug("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
execvp("rsync", args);
|
execvp("rsync", args);
|
||||||
// If execvp returns, there was an error
|
// If execvp returns, there was an error
|
||||||
@@ -107,19 +114,24 @@ static void ensureDir(const char *path, const int shouldDebug) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void initAppFilesAndDirs(const char *home, const int shouldDebug) {
|
void initAppFilesAndDirs(const char *home, const int shouldDebug) {
|
||||||
|
// sometimes .cache and .config doesn't exist. Such as with github actions machines
|
||||||
char config_dir[PATH_MAX];
|
char config_dir[PATH_MAX];
|
||||||
char cache_dir[PATH_MAX];
|
char cache_dir[PATH_MAX];
|
||||||
|
|
||||||
snprintf(config_dir, sizeof(config_dir),
|
snprintf(config_dir, sizeof(config_dir), "%s/.config/", home);
|
||||||
"%s/.config/notewrapper", home);
|
|
||||||
|
|
||||||
snprintf(cache_dir, sizeof(cache_dir),
|
snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/", home);
|
||||||
"%s/.cache/notewrapper", home);
|
|
||||||
|
|
||||||
ensureDir(config_dir, shouldDebug);
|
ensureDir(config_dir, shouldDebug);
|
||||||
ensureDir(cache_dir, shouldDebug);
|
ensureDir(cache_dir, shouldDebug);
|
||||||
|
|
||||||
char config_file[PATH_MAX+12];
|
strncat(config_dir, "notewrapper/", PATH_MAX);
|
||||||
|
strncat(cache_dir, "notewrapper/", PATH_MAX);
|
||||||
|
|
||||||
|
ensureDir(config_dir, shouldDebug);
|
||||||
|
ensureDir(cache_dir, shouldDebug);
|
||||||
|
|
||||||
|
char config_file[PATH_MAX + 12];
|
||||||
snprintf(config_file, sizeof(config_file), "%s/config.json", config_dir);
|
snprintf(config_file, sizeof(config_file), "%s/config.json", config_dir);
|
||||||
|
|
||||||
FILE *f = fopen(config_file, "r");
|
FILE *f = fopen(config_file, "r");
|
||||||
@@ -130,28 +142,34 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) {
|
|||||||
debug("Creating default config file.");
|
debug("Creating default config file.");
|
||||||
FILE *w = fopen(config_file, "w");
|
FILE *w = fopen(config_file, "w");
|
||||||
error(!w, "program", "fopen failed opening %s");
|
error(!w, "program", "fopen failed opening %s");
|
||||||
fprintf(w,
|
fprintf(w, // TODO CHANGE default json
|
||||||
"{\n"
|
"{\n"
|
||||||
" \"directory\": \"~/Documents/Notes/\",\n"
|
" \"directory\": [\"~/Documents/\"],\n" // I personally use ~/Documents/Notes. But the
|
||||||
" \"render\": true,\n"
|
// dir doesn't exist most times. So for the
|
||||||
" \"jumpToEndOfFileOnLaunch\": true,\n"
|
// default user it is better to put ~/Documents
|
||||||
" \"editor\": \"neovim\",\n"
|
" \"render\": true,\n"
|
||||||
" \"journalRegex\": \".*journal.*\",\n"
|
" \"jumpToEndOfFileOnLaunch\": true,\n"
|
||||||
" \"dateEntry\": \"# %%Y %%m %%d %%a\",\n"
|
" \"editor\": \"neovim\",\n"
|
||||||
" \"newLineOnOpening\": true,\n"
|
" \"journalRegex\": \".*journal.*\",\n"
|
||||||
" \"backup\": {\n"
|
" \"dateEntry\": \"# %%Y %%m %%d %%a\",\n"
|
||||||
" \"enable\": false,\n"
|
" \"newLineOnOpening\": true,\n"
|
||||||
" \"directory\": \"/path/to/backup\",\n"
|
" \"backup\": {\n"
|
||||||
" \"interval\": \"weekly\",\n"
|
" \"enable\": false,\n"
|
||||||
" \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n"
|
" \"directory\": {\n"
|
||||||
" }\n"
|
" \"~/Documents/\": \"path/to/backup/\"\n"
|
||||||
"}\n");
|
" },\n"
|
||||||
|
" \"interval\": \"weekly\",\n"
|
||||||
|
" \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n"
|
||||||
|
" }\n"
|
||||||
|
"}\n");
|
||||||
|
|
||||||
fclose(w);
|
fclose(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber, const int shouldDebug) {
|
void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber,
|
||||||
|
const int forceBackup, int *backupMessage, const int shouldDebug) {
|
||||||
int shouldBackup = 0;
|
int shouldBackup = 0;
|
||||||
|
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
debug("Time since epoch is %ld", (long)now);
|
debug("Time since epoch is %ld", (long)now);
|
||||||
char cacheFilePATH[PATH_MAX];
|
char cacheFilePATH[PATH_MAX];
|
||||||
@@ -160,6 +178,7 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha
|
|||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(cacheFilePATH, &st) != 0) { // file does not exist
|
if (stat(cacheFilePATH, &st) != 0) { // file does not exist
|
||||||
shouldBackup = 1;
|
shouldBackup = 1;
|
||||||
|
debug("Creating (for the first time) %s", cacheFilePATH);
|
||||||
FILE *cacheFile = fopen(cacheFilePATH, "w");
|
FILE *cacheFile = fopen(cacheFilePATH, "w");
|
||||||
if (!cacheFile) {
|
if (!cacheFile) {
|
||||||
error(1, "program", "Failed to open cache file for writing: %s", cacheFilePATH);
|
error(1, "program", "Failed to open cache file for writing: %s", cacheFilePATH);
|
||||||
@@ -168,6 +187,7 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha
|
|||||||
fclose(cacheFile);
|
fclose(cacheFile);
|
||||||
} else {
|
} else {
|
||||||
FILE *cacheFile = fopen(cacheFilePATH, "r");
|
FILE *cacheFile = fopen(cacheFilePATH, "r");
|
||||||
|
debug("reading %s", cacheFilePATH);
|
||||||
if (!cacheFile) {
|
if (!cacheFile) {
|
||||||
error(1, "program", "Failed to open cache file for reading: %s", cacheFilePATH);
|
error(1, "program", "Failed to open cache file for reading: %s", cacheFilePATH);
|
||||||
}
|
}
|
||||||
@@ -183,8 +203,13 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha
|
|||||||
if (endptr == line || (*endptr != '\n' && *endptr != '\0')) {
|
if (endptr == line || (*endptr != '\n' && *endptr != '\0')) {
|
||||||
error(1, "program", "Invalid timestamp in cache file: %s", line);
|
error(1, "program", "Invalid timestamp in cache file: %s", line);
|
||||||
}
|
}
|
||||||
|
double deltaTime = difftime(now, lastBackupTime);
|
||||||
if (difftime(now, lastBackupTime) > interval) {
|
if (deltaTime > interval || forceBackup) {
|
||||||
|
if (!forceBackup) {
|
||||||
|
debug("(difftime) %f is greater than (interval) %d -> backuping...", deltaTime, interval);
|
||||||
|
} else {
|
||||||
|
debug("Backup was forced. backuping...");
|
||||||
|
}
|
||||||
shouldBackup = 1;
|
shouldBackup = 1;
|
||||||
cacheFile = fopen(cacheFilePATH, "w");
|
cacheFile = fopen(cacheFilePATH, "w");
|
||||||
if (!cacheFile) {
|
if (!cacheFile) {
|
||||||
@@ -192,25 +217,57 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha
|
|||||||
}
|
}
|
||||||
fprintf(cacheFile, "%ld\n", (long)now);
|
fprintf(cacheFile, "%ld\n", (long)now);
|
||||||
fclose(cacheFile);
|
fclose(cacheFile);
|
||||||
|
} else {
|
||||||
|
debug("(difftime) %f is smaller than (interval) %d -> no need to backup.", deltaTime, interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldBackup) {
|
if (shouldBackup) {
|
||||||
copyDir(pathOfVaults, pathOfBackup, rsyncArguments, rsyncArgumentsNumber, shouldDebug);
|
*backupMessage = 1;
|
||||||
|
debug("A backup is needed");
|
||||||
|
for (int i = 0; i < sourceNumber; i++) {
|
||||||
|
debug("%s", destinationDirectoryArray[i]);
|
||||||
|
if (destinationDirectoryArray[i]) { // if we don't want to backup it, it was set to NULL
|
||||||
|
copyDir(sourceDirectoryArray[i], destinationDirectoryArray[i], rsyncArguments, rsyncArgumentsNumber, shouldDebug);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int doesEditorExist (char *editorToCheck, int shouldDebug) { // Some exectuables have not exaclty the same name as the editor.
|
int isEditorValid(char *editorToCheck, int useDefaultEditor,
|
||||||
char *editor;
|
int shouldDebug) { // check if editor is supported and if it is installed
|
||||||
if (strcmp(editorToCheck, "neovim") == 0) {
|
// check if supported
|
||||||
editor = strdup("nvim"); // we must use strdup and not just copy as we would have modified editorToOpen in main
|
if (useDefaultEditor) { // we use a different error message if it defaulted to $EDITOR
|
||||||
|
error(!isStringInArray(editorToCheck, supportedEditor, numEditors), "user",
|
||||||
|
"%s is not a supported editor.\n(defaulted to $EDITOR as neither \"editor\" was set "
|
||||||
|
"in the configuration file nor -e/--editor <editor> was set.)\n See "
|
||||||
|
"https://github.com/tomasriveral/NoteWrapper#editor-support for a list of supported "
|
||||||
|
"editors.",
|
||||||
|
editorToCheck);
|
||||||
|
} else {
|
||||||
|
error(!isStringInArray(editorToCheck, supportedEditor, numEditors), "user",
|
||||||
|
"%s is not a supported editor.\n See "
|
||||||
|
"https://github.com/tomasriveral/NoteWrapper#editor-support for a list of supported "
|
||||||
|
"editors.",
|
||||||
|
editorToCheck);
|
||||||
}
|
}
|
||||||
else {
|
// check if installed
|
||||||
editor = strdup(editorToCheck);
|
char *editor;
|
||||||
|
if (strcmp(editorToCheck, "neovim") == 0) { // some executables are not name the same as the project
|
||||||
|
editor = strdup("nvim"); // we must use strdup and not just copy as we would have modified
|
||||||
|
// editorToOpen in main
|
||||||
|
} else if (strcmp(editorToCheck, "helix") == 0) {
|
||||||
|
editor = strdup("hx");
|
||||||
|
} else if (strcmp(editorToCheck, "kakoune") == 0) {
|
||||||
|
editor = strdup("kak");
|
||||||
|
} else {
|
||||||
|
editor = strdup(editorToCheck);
|
||||||
}
|
}
|
||||||
char *path_env = getenv("PATH");
|
char *path_env = getenv("PATH");
|
||||||
error(!path_env, "program", "getenv(\"PATH\") failed to get your path. NoteWrapper is unable to check if your desired editor is installed\n");
|
error(!path_env, "program",
|
||||||
debug("Your PATH is %s\n", path_env);
|
"getenv(\"PATH\") failed to get your path. NoteWrapper is unable to check if your "
|
||||||
|
"desired editor is installed\n");
|
||||||
|
debug("Your PATH is %s", path_env);
|
||||||
char *paths = strdup(path_env); // duplicate because strtok modifies the string
|
char *paths = strdup(path_env); // duplicate because strtok modifies the string
|
||||||
char *dir = strtok(paths, ":");
|
char *dir = strtok(paths, ":");
|
||||||
while (dir) {
|
while (dir) {
|
||||||
@@ -230,43 +287,46 @@ int doesEditorExist (char *editorToCheck, int shouldDebug) { // Some exectua
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *getFormatedTime(char *format, int shouldDebug) {
|
char *getFormatedTime(char *format, int shouldDebug) {
|
||||||
debug("Inputed time format is %s", format);
|
debug("Inputed time format is %s", format);
|
||||||
time_t t = time(NULL);
|
time_t t = time(NULL);
|
||||||
struct tm *tm = localtime(&t);
|
struct tm *tm = localtime(&t);
|
||||||
char *buf = malloc(BUFFER_SIZE);
|
char *buf = malloc(BUFFER_SIZE);
|
||||||
strftime(buf, BUFFER_SIZE, format, tm);
|
strftime(buf, BUFFER_SIZE, format, tm);
|
||||||
debug("Formated time is %s", buf);
|
debug("Formated time is %s", buf);
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
int isStringInArray(const char *string, const char **array, const int len) {
|
int isStringInArray(const char *string, const char **array, const int len) {
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
if (strcmp(string, array[i]) == 0) {
|
if (strcmp(string, array[i]) == 0) {
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return 0;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int isStringInFile(const char *path, const char *string, const int shouldDebug) {
|
int isStringInFile(const char *path, const char *string, const int shouldDebug) {
|
||||||
FILE *file = fopen(path, "r");
|
FILE *file = fopen(path, "r");
|
||||||
error(file == NULL, "program", "While searching for \"%s\", we could not open file %s", string, path);
|
error(file == NULL, "program", "While searching for \"%s\", we could not open file %s", string, path);
|
||||||
char buf[BUFFER_SIZE];
|
char buf[BUFFER_SIZE];
|
||||||
|
|
||||||
while (fgets(buf, BUFFER_SIZE, file) != NULL) {
|
while (fgets(buf, BUFFER_SIZE, file) != NULL) {
|
||||||
if (strstr(buf, string) != NULL) {
|
if (strstr(buf, string) != NULL) {
|
||||||
debug("%s contains the substring %s", buf, string);
|
debug("%s contains the substring %s", buf, string);
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
debug("The string %s is not inside %s", string, path);
|
||||||
debug("The string %s is not inside %s", string, path);
|
return 0;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
void appendToFile(const char *path, const char *string, const int shouldDebug) {
|
void appendToFile(const char *path, const char *string, const int shouldDebug) {
|
||||||
FILE *file = fopen(path, "r");
|
FILE *file = fopen(path, "r");
|
||||||
char lastLine[1024] = {0};
|
char lastLine[1024] = {0};
|
||||||
error(file == NULL, "program", "could not open %s", path);
|
error(file == NULL, "program", "could not open %s", path);
|
||||||
char buffer[BUFFER_SIZE]; // it does not matter if we have a small buffer. string is relatively small (most time \n or the name of the file). So it is under BUFFER_SIZE. If the last line is more than BUFFER_SIZE. It can't be equal to string
|
char buffer[BUFFER_SIZE]; // it does not matter if we have a small buffer. string is relatively
|
||||||
|
// small (most time \n or the name of the file). So it is under
|
||||||
|
// BUFFER_SIZE. If the last line is more than BUFFER_SIZE. It can't be
|
||||||
|
// equal to string
|
||||||
|
|
||||||
// Read file line by line to get the last one
|
// Read file line by line to get the last one
|
||||||
while (fgets(buffer, sizeof(buffer), file) != NULL) {
|
while (fgets(buffer, sizeof(buffer), file) != NULL) {
|
||||||
@@ -293,17 +353,17 @@ void appendToFile(const char *path, const char *string, const int shouldDebug) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sanitize(char *string) {
|
void sanitize(char *string) {
|
||||||
size_t stringLenght = strlen(string);
|
size_t stringLenght = strlen(string);
|
||||||
for (size_t i = 0; i < stringLenght; i++) {
|
for (size_t i = 0; i < stringLenght; i++) {
|
||||||
if ((!isalnum((unsigned char)string[i]) && strchr("~/\\:*?\"\'|!$[]{}<>\n\r\t", string[i]))) { // replace unwanted chars by '_'
|
if ((!isalnum((unsigned char)string[i]) && strchr("~/\\:*?\"\'|!$[]{}<>\n\r\t", string[i]))) { // replace unwanted chars by '_'
|
||||||
string[i] = '_';
|
string[i] = '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// removes any leading '.
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < stringLenght && string[i] == '.') {
|
||||||
|
string[i++] = '_';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// removes any leading '.
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < stringLenght && string[i] == '.') {
|
|
||||||
string[i++] = '_';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// both functions are from https://stackoverflow.com/a/5467788
|
// both functions are from https://stackoverflow.com/a/5467788
|
||||||
@@ -311,119 +371,293 @@ void sanitize(char *string) {
|
|||||||
// remove() can't delete directories with files
|
// remove() can't delete directories with files
|
||||||
// so it walks the file tree and deletes it's content before removing the directory
|
// so it walks the file tree and deletes it's content before removing the directory
|
||||||
int unlink_cb(const char *filePath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
|
int unlink_cb(const char *filePath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
|
||||||
(void)sb;
|
(void)sb;
|
||||||
(void)typeflag;
|
(void)typeflag;
|
||||||
(void)ftwbuf;
|
(void)ftwbuf;
|
||||||
|
|
||||||
int shouldDebug = 1; //normally shouldDebug should be passed to the function. However, it's to difficult here and the best it to set it to 1.
|
int shouldDebug = 1; // normally shouldDebug should be passed to the function. However, it's to
|
||||||
|
// difficult here and the best it to set it to 1.
|
||||||
|
|
||||||
// some sanity checks to be sure that we don't delete something we shouldn't
|
// some sanity checks to be sure that we don't delete something we shouldn't
|
||||||
error(strcmp(filePath, "") == 0, "program", "filePath is empty. Refusing to delete directory");
|
error(strcmp(filePath, "") == 0, "program", "filePath is empty. Refusing to delete directory");
|
||||||
error(filePath[0] == '.', "program", "%s starts with \".\". For security reasons, use absolute path and not relative path.", filePath);
|
error(filePath[0] == '.', "program", "%s starts with \".\". For security reasons, use absolute path and not relative path.", filePath);
|
||||||
|
|
||||||
int rv = remove(filePath);
|
int rv = remove(filePath);
|
||||||
error(rv, "program", "remove() failed to delete %s", filePath);
|
error(rv, "program", "remove() failed to delete %s", filePath);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rmrf(char *path, int shouldDebug) {
|
int rmrf(char *path, int shouldDebug) {
|
||||||
error(strcmp(path, "/") == 0, "program", "Refusing to delete root directory");
|
error(strcmp(path, "/") == 0, "program", "Refusing to delete root directory");
|
||||||
return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
|
return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int openEditor(char *path, char *editor, int render, int shouldJumpToEndOfFile, int shouldDebug) {
|
int openEditor(char *path, char *editor, int render, int shouldJumpToEndOfFile, int shouldDebug) {
|
||||||
|
|
||||||
// this ensures that ncurses won't affect the editor behaviour
|
// this ensures that ncurses won't affect the editor behaviour
|
||||||
pid_t editor_pid = fork();
|
pid_t editor_pid = fork();
|
||||||
error(editor_pid < 0, "program", "fork() failed.");
|
error(editor_pid < 0, "program", "fork() failed.");
|
||||||
|
|
||||||
if (editor_pid == 0) {
|
// instead of reusing part of the codes, for any new editor copy an example and adapt it. This
|
||||||
// =========================
|
// is in case we need a custom fix for an editor.
|
||||||
// CHILD: launch editor
|
|
||||||
// =========================
|
|
||||||
|
|
||||||
// ---- NEOVIM / VIM ----
|
if (editor_pid == 0) {
|
||||||
if (strcmp(editor, "neovim") == 0 || strcmp(editor, "vim") == 0) {
|
// =========================
|
||||||
const char *bin = (strcmp(editor, "neovim") == 0) ? "nvim" : "vim";
|
// CHILD: launch editor
|
||||||
|
// =========================
|
||||||
|
|
||||||
if (render) {
|
// ---- NEOVIM / VIM ----
|
||||||
if (shouldJumpToEndOfFile) {
|
if (strcmp(editor, "neovim") == 0 || strcmp(editor, "vim") == 0) {
|
||||||
debug("Running %s +:$ +:Vivify %s", bin, path);
|
const char *bin = (strcmp(editor, "neovim") == 0) ? "nvim" : "vim";
|
||||||
execlp(bin, bin, "+:$", "+:Vivify", path, NULL);
|
|
||||||
} else {
|
|
||||||
debug("Running %s +:Vivify %s", bin, path);
|
|
||||||
execlp(bin, bin, "+:Vivify", path, NULL);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (shouldJumpToEndOfFile) {
|
|
||||||
debug("Running %s +:$ %s", bin, path);
|
|
||||||
execlp(bin, bin, "+:$", path, NULL);
|
|
||||||
} else {
|
|
||||||
debug("Running %s %s", bin, path);
|
|
||||||
execlp(bin, bin, path, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error(1, "program", "execlp() failed.");
|
if (render) {
|
||||||
}
|
if (shouldJumpToEndOfFile) {
|
||||||
|
debug("Running %s +:$ +:Vivify %s", bin, path);
|
||||||
|
execlp(bin, bin, "+:$", "+:Vivify", path, NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running %s +:Vivify %s", bin, path);
|
||||||
|
execlp(bin, bin, "+:Vivify", path, NULL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
debug("Running %s +:$ %s", bin, path);
|
||||||
|
execlp(bin, bin, "+:$", path, NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running %s %s", bin, path);
|
||||||
|
execlp(bin, bin, path, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- NANO ----
|
error(1, "program", "execlp() failed.");
|
||||||
else if (strcmp(editor, "nano") == 0) {
|
// ---- MICRO ----
|
||||||
|
} else if (strcmp(editor, "micro") == 0) {
|
||||||
|
|
||||||
// If render enabled → spawn viv in parallel
|
if (render) {
|
||||||
if (render) {
|
if (shouldJumpToEndOfFile) {
|
||||||
debug("Running the editor...");
|
debug("Running MICRO_VIVIFY=1 micro %s +99999", path);
|
||||||
pid_t viv_pid = fork();
|
setenv("MICRO_VIVIFY", "1", 1);
|
||||||
error(viv_pid < 0, "program", "fork() failed.");
|
execlp("micro", "micro", path, "+999999", NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running MICRO_VIVIFY=1 micro %s", path);
|
||||||
|
setenv("MICRO_VIVIFY", "1", 1);
|
||||||
|
execlp("micro", "micro", path, NULL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
debug("Running micro %s +999999", path);
|
||||||
|
execlp("micro", "micro", path, "+999999", NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running micro %s", path);
|
||||||
|
execlp("micro", "micro", path, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (viv_pid == 0) {
|
error(1, "program", "execlp() failed.");
|
||||||
// GRANDCHILD → viv
|
|
||||||
|
|
||||||
|
|
||||||
char viv_path[PATH_MAX];
|
|
||||||
strncpy(viv_path, path, PATH_MAX - 1);
|
|
||||||
viv_path[PATH_MAX - 1] = '\0';
|
|
||||||
|
|
||||||
if (shouldJumpToEndOfFile) {
|
|
||||||
strncat(viv_path, ":99999",
|
|
||||||
PATH_MAX - strlen(viv_path) - 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug("Running viv %s", viv_path);
|
// ---- NANO ----
|
||||||
execlp("viv", "viv", viv_path, NULL);
|
else if (strcmp(editor, "nano") == 0) {
|
||||||
error(1, "program", "execlp() failed.");
|
|
||||||
}
|
|
||||||
// IMPORTANT: do NOT wait for viv
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now run nano (this replaces the child process)
|
// If render enabled → spawn viv in parallel
|
||||||
if (shouldJumpToEndOfFile) {
|
if (render) {
|
||||||
debug("Running nano + %s", path);
|
debug("Running Vivify...");
|
||||||
execlp("nano", "nano", "+", path, NULL);
|
pid_t viv_pid = fork();
|
||||||
} else {
|
error(viv_pid < 0, "program", "fork() failed.");
|
||||||
debug("Running nano %s", path);
|
|
||||||
execlp("nano", "nano", path, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
error(1, "program", "execlp() failed.");
|
if (viv_pid == 0) {
|
||||||
}
|
// GRANDCHILD → viv
|
||||||
|
|
||||||
// ---- UNKNOWN EDITOR ----
|
char viv_path[PATH_MAX];
|
||||||
else {
|
strncpy(viv_path, path, PATH_MAX - 1);
|
||||||
error(1, "program", "Unknown editor.");
|
viv_path[PATH_MAX - 1] = '\0';
|
||||||
}
|
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Running viv %s", viv_path);
|
||||||
|
execlp("viv", "viv", viv_path, NULL);
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
}
|
||||||
|
// IMPORTANT: do NOT wait for viv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now run nano (this replaces the child process)
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
debug("Running nano + %s", path);
|
||||||
|
execlp("nano", "nano", "+", path, NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running nano %s", path);
|
||||||
|
execlp("nano", "nano", path, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
// ---- HELIX ----
|
||||||
|
} else if (strcmp(editor, "helix") == 0) {
|
||||||
|
|
||||||
|
// If render enabled → spawn viv in parallel
|
||||||
|
if (render) {
|
||||||
|
debug("Running Vivify...");
|
||||||
|
pid_t viv_pid = fork();
|
||||||
|
error(viv_pid < 0, "program", "fork() failed.");
|
||||||
|
|
||||||
|
if (viv_pid == 0) {
|
||||||
|
// GRANDCHILD → viv
|
||||||
|
|
||||||
|
char viv_path[PATH_MAX];
|
||||||
|
strncpy(viv_path, path, PATH_MAX - 1);
|
||||||
|
viv_path[PATH_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Running viv %s", viv_path);
|
||||||
|
execlp("viv", "viv", viv_path, NULL);
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
}
|
||||||
|
// IMPORTANT: do NOT wait for viv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now run helix (this replaces the child process)
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
strncat(path, ":99999", PATH_MAX);
|
||||||
|
debug("Running hx %s", path);
|
||||||
|
execlp("hx", "hx", path, NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running hx %s", path);
|
||||||
|
execlp("hx", "hx", path, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
}
|
||||||
|
// ---- kakoune ----
|
||||||
|
else if (strcmp(editor, "kakoune") == 0) {
|
||||||
|
|
||||||
|
// If render enabled → spawn viv in parallel
|
||||||
|
if (render) {
|
||||||
|
debug("Running Vivify...");
|
||||||
|
pid_t viv_pid = fork();
|
||||||
|
error(viv_pid < 0, "program", "fork() failed.");
|
||||||
|
|
||||||
|
if (viv_pid == 0) {
|
||||||
|
// GRANDCHILD → viv
|
||||||
|
|
||||||
|
char viv_path[PATH_MAX];
|
||||||
|
strncpy(viv_path, path, PATH_MAX - 1);
|
||||||
|
viv_path[PATH_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Running viv %s", viv_path);
|
||||||
|
execlp("viv", "viv", viv_path, NULL);
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
}
|
||||||
|
// IMPORTANT: do NOT wait for viv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now run nano (this replaces the child process)
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
debug("Running kak + %s", path);
|
||||||
|
execlp("kak", "kak", "+", path, NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running kak %s", path);
|
||||||
|
execlp("kak", "kak", path, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
// ---- VI ----
|
||||||
|
} else if (strcmp(editor, "vi") == 0) {
|
||||||
|
|
||||||
|
// If render enabled → spawn viv in parallel
|
||||||
|
if (render) {
|
||||||
|
debug("Running Vivify...");
|
||||||
|
pid_t viv_pid = fork();
|
||||||
|
error(viv_pid < 0, "program", "fork() failed.");
|
||||||
|
|
||||||
|
if (viv_pid == 0) {
|
||||||
|
// GRANDCHILD → viv
|
||||||
|
|
||||||
|
char viv_path[PATH_MAX];
|
||||||
|
strncpy(viv_path, path, PATH_MAX - 1);
|
||||||
|
viv_path[PATH_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Running viv %s", viv_path);
|
||||||
|
execlp("viv", "viv", viv_path, NULL);
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
}
|
||||||
|
// IMPORTANT: do NOT wait for viv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now run vi (this replaces the child process)
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
debug("Running vi +:$ %s", path);
|
||||||
|
execlp("vi", "vi", "+:$", path, NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running vi %s", path);
|
||||||
|
execlp("vi", "vi", path, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
// ---- Jed ----
|
||||||
|
} else if (strcmp(editor, "jed") == 0) {
|
||||||
|
|
||||||
|
// If render enabled → spawn viv in parallel
|
||||||
|
if (render) {
|
||||||
|
debug("Running Vivify...");
|
||||||
|
pid_t viv_pid = fork();
|
||||||
|
error(viv_pid < 0, "program", "fork() failed.");
|
||||||
|
|
||||||
|
if (viv_pid == 0) {
|
||||||
|
// GRANDCHILD → viv
|
||||||
|
|
||||||
|
char viv_path[PATH_MAX];
|
||||||
|
strncpy(viv_path, path, PATH_MAX - 1);
|
||||||
|
viv_path[PATH_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Running viv %s", viv_path);
|
||||||
|
execlp("viv", "viv", viv_path, NULL);
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
}
|
||||||
|
// IMPORTANT: do NOT wait for viv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now run jed (this replaces the child process)
|
||||||
|
if (shouldJumpToEndOfFile) {
|
||||||
|
debug("Running jed %s -g 99999999", path);
|
||||||
|
execlp("jed", "jed", path, "-g", "99999999", NULL);
|
||||||
|
} else {
|
||||||
|
debug("Running jed %s", path);
|
||||||
|
execlp("jed", "jed", path, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(1, "program", "execlp() failed.");
|
||||||
|
// ---- UNKNOWN EDITOR ----
|
||||||
|
} else {
|
||||||
|
error(1, "program", "Unknown editor.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// PARENT: wait ONLY editor
|
// PARENT: wait ONLY editor
|
||||||
// =========================
|
// =========================
|
||||||
int status;
|
int status;
|
||||||
while (waitpid(editor_pid, &status, 0) == -1) { // we can't just use waitpid(). Because when resizing the terminal, waitpid() is returned so we loop to see if there is not a problem
|
while (waitpid(editor_pid, &status, 0) == -1) { // we can't just use waitpid(). Because when resizing the terminal, waitpid() is
|
||||||
if (errno != EINTR) {
|
// returned so we loop to see if there is not a problem
|
||||||
perror("waitpid");
|
if (errno != EINTR) {
|
||||||
break;
|
perror("waitpid");
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-31
@@ -1,52 +1,53 @@
|
|||||||
#ifndef UTILS_H
|
#ifndef UTILS_H
|
||||||
#define UTILS_H
|
#define UTILS_H
|
||||||
|
|
||||||
|
#include <cjson/cJSON.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ftw.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <regex.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <ftw.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <limits.h>
|
|
||||||
#include <cjson/cJSON.h>
|
|
||||||
#include <regex.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <ftw.h>
|
#include <unistd.h>
|
||||||
#define BUFFER_SIZE 256 //standard buffer size.
|
#ifndef VERSION
|
||||||
// the three supported values are "daily" "weekly" and "monthly" (for months we use the avarage lenght of a month in a non leap year). All values were calculated on my loyal TI-30 ECO RS
|
#define VERSION "dev"
|
||||||
|
#endif
|
||||||
|
#define BUFFER_SIZE 256 // standard buffer size.
|
||||||
|
// the three supported values are "daily" "weekly" and "monthly" (for months we use the avarage
|
||||||
|
// lenght of a month in a non leap year). All values were calculated on my loyal TI-30 ECO RS
|
||||||
#define DAILY 86400
|
#define DAILY 86400
|
||||||
#define WEEKLY 604800
|
#define WEEKLY 604800
|
||||||
#define MONTHLY 2628000 // calculated from the average lenght of a non leap year
|
#define MONTHLY 2628000 // calculated from the average lenght of a non leap year
|
||||||
#define debug(message, ...) \
|
#define debug(message, ...) _debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||||
_debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
#define altDebug(message, ...) _altDebug(shouldDebug, message, ##__VA_ARGS__)
|
||||||
#define altDebug(message, ...) \
|
#define error(condition, type, message, ...) _error(shouldDebug, condition, type, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||||
_altDebug(shouldDebug, message, ##__VA_ARGS__)
|
|
||||||
#define error(condition, type, message, ...) \
|
|
||||||
_error(shouldDebug, condition, type, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
|
||||||
// you must edit this two values if you want to add suport for an editor
|
// you must edit this two values if you want to add suport for an editor
|
||||||
extern const char *supportedEditor[]; // array of supported editors
|
extern const char *supportedEditor[]; // array of supported editors
|
||||||
extern const int numEditors; // number of supported editors
|
extern const int numEditors; // number of supported editors
|
||||||
|
|
||||||
|
// compares two strings alphabetically.
|
||||||
//compares two strings alphabetically.
|
// this function is used for qsort
|
||||||
//this function is used for qsort
|
|
||||||
int compareString(const void *a, const void *b);
|
int compareString(const void *a, const void *b);
|
||||||
// compares two strings in reversed alphabetical order
|
// compares two strings in reversed alphabetical order
|
||||||
// this function is used for qsort
|
// this function is used for qsort
|
||||||
int reverseCompareString(const void *a, const void *b);
|
int reverseCompareString(const void *a, const void *b);
|
||||||
|
// checks if editor is supported and if it installed.
|
||||||
// this basically checks all the dirs from your path for the editor. This is a safety check.
|
// this basically checks all the dirs from your path for the editor. This is a safety check.
|
||||||
// If the executable from an editor is not the editor name (for example neovim and nvim), you must handle at the start of the function.
|
// If the executable from an editor is not the editor name (for example neovim and nvim), you must
|
||||||
int doesEditorExist(char *editorToCheck, int debug);
|
// handle at the start of the function. This can return an error and stop the program.
|
||||||
|
int isEditorValid(char *editorToCheck, int useDefaultEditor, int debug);
|
||||||
// please use the macro debug instead of _debug.
|
// please use the macro debug instead of _debug.
|
||||||
//formated debugging.
|
// formated debugging.
|
||||||
void _debug(const int d, const char *file, const int line, const char *function, const char *message, ...);
|
void _debug(const int d, const char *file, const int line, const char *function, const char *message, ...);
|
||||||
//use for less formal debuggin. (usefull if enumerating or making a list).
|
// use for less formal debuggin. (usefull if enumerating or making a list).
|
||||||
void _altDebug(const int d, const char *message, ...);
|
void _altDebug(const int d, const char *message, ...);
|
||||||
// formated error.
|
// formated error.
|
||||||
void _error(const int shouldDebug, const int condition, const char *type, const char *file, const int line, const char *function, const char *message, ...);
|
void _error(const int shouldDebug, const int condition, const char *type, const char *file, const int line, const char *function, const char *message, ...);
|
||||||
@@ -63,8 +64,8 @@ void appendToFile(const char *path, const char *string, const int shouldDebug);
|
|||||||
// replace unwanted chars by '_'.
|
// replace unwanted chars by '_'.
|
||||||
// '.' is replaced if it is only the first two chars
|
// '.' is replaced if it is only the first two chars
|
||||||
void sanitize(char *string);
|
void sanitize(char *string);
|
||||||
//from https://stackoverflow.com/a/5467788.
|
// from https://stackoverflow.com/a/5467788.
|
||||||
//deletes an entire directory. Use with parsimony and carefullness.
|
// deletes an entire directory. Use with parsimony and carefullness.
|
||||||
int rmrf(char *path, int shouldDebug);
|
int rmrf(char *path, int shouldDebug);
|
||||||
// Inputs are the path to the file, the editor to open and some rendering option.
|
// Inputs are the path to the file, the editor to open and some rendering option.
|
||||||
// render: if we render the .md file with Vivify.
|
// render: if we render the .md file with Vivify.
|
||||||
@@ -77,6 +78,11 @@ int openEditor(char *path, char *editor, int render, int shouldJumpToEndOfFile,
|
|||||||
char *getFormatedTime(char *format, int shouldDebug);
|
char *getFormatedTime(char *format, int shouldDebug);
|
||||||
/* Caclulates if we need to do another backup (by reading ~/.cache/NoteWrapper/backupTime.txt.
|
/* Caclulates if we need to do another backup (by reading ~/.cache/NoteWrapper/backupTime.txt.
|
||||||
If need launches in the background rsync to do the backuping.
|
If need launches in the background rsync to do the backuping.
|
||||||
rsyncArgs is the array of arguments to be passed to rsync. Do not inclue destination or source. It will be added in the function.*/
|
Each pair of source/destination will launch one rsync process.
|
||||||
void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug);
|
If a pair don't need to be backed up, the destination should be set to NULL.
|
||||||
|
rsyncArgs is the array of arguments to be passed to rsync. Do not inclue destination or source. It
|
||||||
|
will be added in the function.
|
||||||
|
backupMessage will be set to 1 if a backup was launched.*/
|
||||||
|
void handleBackups(char **sourceDirectoryArray, const int sourceDirectoryNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber,
|
||||||
|
const int forceBackup, int *backupMessage, const int shouldDebug);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user