mirror of
https://github.com/tomasriveral/NoteWrapper.git
synced 2026-08-12 18:30:45 +02:00
Compare commits
68
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34a2546990 | ||
|
|
e86d7e7e03 | ||
|
|
f66371137a | ||
|
|
503dddd9be | ||
|
|
004b536e05 | ||
|
|
c8b5538e1c | ||
|
|
52169ccbe4 | ||
|
|
31366aeb48 | ||
|
|
5c4bd6ef16 | ||
|
|
fcb853e36a | ||
|
|
833f8c1bd2 | ||
|
|
5328fdc91c | ||
|
|
d1c19696ff | ||
|
|
3fa0dbe0dd | ||
|
|
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 |
@@ -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.*
|
||||
*.out.*
|
||||
vgcore.*
|
||||
.direnv/
|
||||
|
||||
+20
-2
@@ -37,7 +37,7 @@ Format:
|
||||
Examples:
|
||||
|
||||
* `Editor (#1): add support for Microsoft Word`
|
||||
* `README.md: fix typos`
|
||||
* `docs: fix typos in README.md`
|
||||
* `Journal: improve entry parsing logic`
|
||||
|
||||
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[]`
|
||||
* 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`:
|
||||
|
||||
@@ -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 submitting a pull request, ensure that:
|
||||
@@ -76,3 +85,12 @@ Before submitting a pull request, ensure that:
|
||||
* `make`, or
|
||||
* `nix-build` (on NixOS)
|
||||
* 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
|
||||
|
||||
Note: This project fullfills all my usecase 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)
|
||||
|
||||
## Why?
|
||||
@@ -21,7 +23,7 @@ The goal was to create a terminal-based interface for accessing vaults and notes
|
||||
* Standard Markdown files (no custom format)
|
||||
* Journal support with flexible entry formats
|
||||
* Real-time Markdown rendering via Vivify
|
||||
* Backup support using `rsync`
|
||||
* Backup different directories into different destinations
|
||||
* Supports multiple editors
|
||||
|
||||
---
|
||||
@@ -43,6 +45,7 @@ Before building NoteWrapper, you must install the following dependencies:
|
||||
You must also have a [supported editor (and their associated plugin if needed)](#editor-support) installed:
|
||||
|
||||
* `helix`
|
||||
* `jed`
|
||||
* `kakoune`
|
||||
* `micro`
|
||||
* `nano`
|
||||
@@ -54,6 +57,26 @@ You must also have a [supported editor (and their associated plugin if needed)](
|
||||
|
||||
## 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.
|
||||
2. Ensure you have a supported editor installed.
|
||||
3. Clone the repository:
|
||||
@@ -77,7 +100,7 @@ nix-build
|
||||
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:
|
||||
|
||||
```shell
|
||||
@@ -86,7 +109,7 @@ make
|
||||
|
||||
---
|
||||
|
||||
## Packaging
|
||||
### Packaging
|
||||
|
||||
There is currently no official package available for NoteWrapper.
|
||||
|
||||
@@ -102,7 +125,6 @@ As a NixOS user, I will likely package it for **nixpkgs** in the future if the p
|
||||
Usage: notewrapper [options]
|
||||
Options:
|
||||
-c, --config <path/to/config> Specify the config file.
|
||||
-d, --directory <path/to/directory> Specify the vaults' directory.
|
||||
-h, --help Display this message.
|
||||
-e, --editor Specify the editor to open.
|
||||
-j, --jump Jump to the end of the file on opening.
|
||||
@@ -145,13 +167,14 @@ If your editor does not support these features, you can implement a plugin using
|
||||
|
||||
| Editor | Bufferless | Cursor | Jump to end | Aditional requirements |
|
||||
| --------- | ----------- | ------- | ------------ | ---------------------------------------------------------|
|
||||
| Helix | ❌ | ❌ | ✅ | — |
|
||||
| Kakoune | ❌ | ❌ | ✅ | — |
|
||||
| Micro | ❌ | ✅ | ✅ | [micro-vivify](https://codeberg.org/gibbert/micro-vivify) and [modifications to your `init.lua`](./docs/micro.md)|
|
||||
| Nano | ❌ | ❌ | ✅ | — |
|
||||
| Neovim | ✅ | ✅ | ✅ | [vivify-vim](https://github.com/jannis-baum/vivify.vim) |
|
||||
| Vi | ❌ | ❌ | ✅ | |
|
||||
| Vim | ✅ | ✅ | ✅ | [vivify-vim](https://github.com/jannis-baum/vivify.vim) |
|
||||
| [Helix](https://helix-editor.com/) | ❌ | ❌ | ✅ | — |
|
||||
| [Jed](https://www.jedsoft.org/jed/) | ❌ | ❌ | ✅ | — |
|
||||
| [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)
|
||||
|
||||
@@ -163,7 +186,7 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
|
||||
|
||||
```json
|
||||
{
|
||||
"directory": "~/Documents/Notes/",
|
||||
"directory": ["~/Documents/Notes/", "/other/paths/"],
|
||||
"render": true,
|
||||
"jumpToEndOfFileOnLaunch": true,
|
||||
"editor": "neovim",
|
||||
@@ -172,7 +195,10 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
|
||||
"newLineOnOpening": true,
|
||||
"backup": {
|
||||
"enable": false,
|
||||
"directory": "/path/to/backup",
|
||||
"directory": {
|
||||
"~/Documents/Notes": "path/to/backup1",
|
||||
"/other/paths/": "path/to/backup2"
|
||||
},
|
||||
"interval": "weekly",
|
||||
"rsyncArgs": ["-Lqah", "--update"]
|
||||
}
|
||||
@@ -181,18 +207,28 @@ Edit `~/.config/notewrapper/config.json`. If it does not exist, it will be creat
|
||||
|
||||
### Fields
|
||||
|
||||
* `directory`: root directory containing all vaults
|
||||
* `directory`: Array of directories containing the vaults.
|
||||
* `render`: enable/disable Vivify rendering
|
||||
* `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
|
||||
* `dateEntry`: format for journal entries (see `strftime`)
|
||||
* `newLineOnOpening`: add a newline when opening a note
|
||||
* `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.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
|
||||
@@ -207,6 +243,7 @@ It is recommended to use a browser different from your main one for rendering.
|
||||
## Planned features
|
||||
|
||||
* [ ] A converter between journal types
|
||||
* [ ] Support multiple vault directories
|
||||
* [ ] Port NoteWrapper to other editors (non-exhaustive list of planned ports: `emacs -nw`, `jed`, `ad`, flow-control, `ee`, `amp`, `dte`, `cano`, `mle`, `zee`, `ptext`, `kibi`, `ox`, `ne`, `dit`, `zile`, `moe`, `joe`, `pico`, `vis`)
|
||||
* [ ] Default to $EDITOR
|
||||
* [x] Support multiple vault directories
|
||||
* [ ] 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;
|
||||
};
|
||||
}
|
||||
Generated
+78
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"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": 1779508470,
|
||||
"narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "29916453413845e54a65b8a1cf996842300cd299",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"notewrapper": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1779721342,
|
||||
"narHash": "sha256-QbRzgD4HoUUemaVCIZ2VfiW+SUjzFY2rL51CLOxQLvk=",
|
||||
"owner": "tomasriveral",
|
||||
"repo": "NoteWrapper",
|
||||
"rev": "e370f98c5ebc3c037dec4e7e35f26230724b70d3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "tomasriveral",
|
||||
"repo": "NoteWrapper",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"notewrapper": "notewrapper"
|
||||
}
|
||||
},
|
||||
"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,66 @@
|
||||
{
|
||||
description = "NoteWrapper";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
notewrapper = {
|
||||
url = "github:tomasriveral/NoteWrapper";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, notewrapper }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in {
|
||||
packages.default = pkgs.stdenv.mkDerivation (finalAttrs: {
|
||||
pname = "notewrapper";
|
||||
version = "v2.1";
|
||||
|
||||
src = notewrapper;
|
||||
|
||||
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
|
||||
|
||||
CFLAGS := -Wall -Wextra -O2 \
|
||||
$(shell pkg-config --cflags libcjson ncurses)
|
||||
VERSION := $(shell git describe --tags --always --dirty)
|
||||
|
||||
CFLAGS := -Wall -Wextra -Werror -O2 \
|
||||
-DVERSION=\"$(VERSION)\" \
|
||||
$(shell pkg-config --cflags libcjson ncurses)
|
||||
|
||||
LDFLAGS := $(shell pkg-config --libs libcjson ncurses)
|
||||
|
||||
@@ -20,3 +23,17 @@ run: $(TARGET)
|
||||
|
||||
clean:
|
||||
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
-396
File diff suppressed because it is too large
Load Diff
+259
-179
@@ -1,16 +1,44 @@
|
||||
#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);
|
||||
// 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;
|
||||
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);
|
||||
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
|
||||
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
|
||||
regex_t regex;
|
||||
@@ -23,20 +51,21 @@ char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex,
|
||||
// for readdir()
|
||||
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)
|
||||
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)
|
||||
regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0);
|
||||
if (!regexReturn) { // if the regex matches
|
||||
altDebug("matched with the regex. It is a journal.\n");
|
||||
journalsArray = realloc(journalsArray, (journalsCount + 1)*sizeof(char*)); // resize notesArray so that
|
||||
journalsArray[journalsCount] = strdup(vaultEntry->d_name); // copy the dir name into notesArray
|
||||
journalsCount++;
|
||||
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)
|
||||
regexReturn = regexec(®ex, vaultEntry->d_name, 0, NULL, 0);
|
||||
if (!regexReturn) { // if the regex matches
|
||||
altDebug("matched with the regex. It is a journal.\n");
|
||||
journalsArray = realloc(journalsArray,
|
||||
(journalsCount + 1) * sizeof(char *)); // resize notesArray so that
|
||||
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 {
|
||||
altDebug("did not matched with the regex. It is a note.\n");
|
||||
altDebug(" was ignored\n");
|
||||
}
|
||||
} else {
|
||||
altDebug(" was ignored\n");
|
||||
}
|
||||
}
|
||||
altDebug("└ ------------------------------\n");
|
||||
// free's some used memory
|
||||
@@ -45,42 +74,51 @@ char **getJournalsFromVault(char *pathToVault, char *vault, char *journalRegex,
|
||||
return journalsArray;
|
||||
}
|
||||
|
||||
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)
|
||||
// originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
||||
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) originally from
|
||||
// https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
||||
debug("Searching %s for notes", vault);
|
||||
struct dirent *vaultEntry;
|
||||
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);
|
||||
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
|
||||
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
|
||||
// for readdir()
|
||||
debug("┌------------------------------\nDetected Files and dirs from the vault:");
|
||||
while ((vaultEntry = readdir(vaultDirectory)) != NULL) {
|
||||
char *entryName = vaultEntry->d_name;
|
||||
int entryLenght = strlen(entryName);
|
||||
altDebug("%s ", entryName);
|
||||
// for the regex code https://stackoverflow.com/a/1085120
|
||||
regex_t regex;
|
||||
int regexReturn;
|
||||
// compiles the regex
|
||||
regexReturn = regcomp(®ex, journalRegex, 0);
|
||||
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
|
||||
snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s/%s", pathToVault, vault, entryName); // sets the full absolute path to fullPathEntry
|
||||
// check if it matches the regex. If it does not match regexReturn != 0.
|
||||
regexReturn = regexec(®ex, entryName, 0, NULL, 0);
|
||||
struct stat metadataPathEntry;
|
||||
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
|
||||
notesArray = realloc(notesArray, (notesCount + 1)*sizeof(char*)); // resize notesArray so that
|
||||
notesArray[notesCount] = strdup(entryName); // copy the dir name into notesArray
|
||||
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");}
|
||||
char *entryName = vaultEntry->d_name;
|
||||
int entryLenght = strlen(entryName);
|
||||
altDebug("%s ", entryName);
|
||||
// for the regex code https://stackoverflow.com/a/1085120
|
||||
regex_t regex;
|
||||
int regexReturn;
|
||||
// compiles the regex
|
||||
regexReturn = regcomp(®ex, journalRegex, 0);
|
||||
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
|
||||
snprintf(fullPathEntry, sizeof(fullPathEntry), "%s/%s/%s", pathToVault, vault,
|
||||
entryName); // sets the full absolute path to fullPathEntry
|
||||
// check if it matches the regex. If it does not match regexReturn != 0.
|
||||
regexReturn = regexec(®ex, entryName, 0, NULL, 0);
|
||||
struct stat metadataPathEntry;
|
||||
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
|
||||
notesArray = realloc(notesArray, (notesCount + 1) * sizeof(char *)); // resize notesArray so that
|
||||
notesArray[notesCount] = strdup(entryName); // copy the dir name into notesArray
|
||||
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");
|
||||
|
||||
@@ -90,148 +128,190 @@ char** getNotesFromVault(char *pathToVault, char *vault, char *journalRegex, int
|
||||
return notesArray;
|
||||
}
|
||||
|
||||
char **getVaultsFromDirectory(char *dirString, 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
|
||||
debug("Opening %s ", dirString);
|
||||
// originally from https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
||||
struct dirent *vaultsDirectoryEntry;
|
||||
DIR *vaultsDirectory = opendir(dirString);
|
||||
error(vaultsDirectory==NULL, "program", "Could not open directory %s", dirString);
|
||||
char **dirsArray = NULL; // will contain all the dirs/vaults
|
||||
int dirsCount = 0; // 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 the directory:");
|
||||
while ((vaultsDirectoryEntry = readdir(vaultsDirectory)) != NULL) {
|
||||
altDebug("%s\n", vaultsDirectoryEntry->d_name);
|
||||
if (vaultsDirectoryEntry->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", dirString, vaultsDirectoryEntry->d_name); // sets the full absolute path to fullPathEntry
|
||||
|
||||
struct stat metadataPathEntry;
|
||||
if (stat(fullPathEntry, &metadataPathEntry) == 0 && S_ISDIR(metadataPathEntry.st_mode)) { // if this entry is a directory
|
||||
dirsArray = realloc(dirsArray, (dirsCount + 1)*sizeof(char*)); // resize dirsArray so that
|
||||
dirsArray[dirsCount] = strdup(vaultsDirectoryEntry->d_name); // copy the dir name into dirsArray
|
||||
dirsCount++;
|
||||
char **getVaultsFromDirectories(char **directoryStringArray, int directoryNumber, int *vaultsPerDirectoryNumber, int *count, int shouldDebug) {
|
||||
// to avoid having to work with a tree-dimensional array. We will use a 2d and
|
||||
// vaultsPerDirectoryNumber will indicate the width of the directories.
|
||||
char **vaultsArray = NULL;
|
||||
int nthVault = 0; // this is only used internally to set the string into the right place in
|
||||
// directoryStringArray.
|
||||
int previousStartIndex = 0;
|
||||
for (int i = 0; i < directoryNumber; i++) {
|
||||
debug("Opening %s", directoryStringArray[i]);
|
||||
// originally from
|
||||
// https://www.geeksforgeeks.org/c/c-program-list-files-sub-directories-directory/
|
||||
struct dirent *vaultsDirectoryEntry;
|
||||
DIR *vaultsDirectory = opendir(directoryStringArray[i]);
|
||||
error(!vaultsDirectory, "program", "Could not open directory %s", directoryStringArray[i]);
|
||||
vaultsPerDirectoryNumber[i] = 0;
|
||||
debug("┌------------------------------\n Detected files and dirs %s:", directoryStringArray[i]);
|
||||
// Refer https://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
|
||||
// for readdir()
|
||||
while ((vaultsDirectoryEntry = readdir(vaultsDirectory)) != NULL) {
|
||||
char *entryName = vaultsDirectoryEntry->d_name; // gets the entry as a string value
|
||||
altDebug("%s", entryName);
|
||||
if (entryName[0] != '.') { // if not hidden file/dir
|
||||
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");
|
||||
|
||||
// free's some used memory
|
||||
closedir(vaultsDirectory);
|
||||
*count = dirsCount;
|
||||
return dirsArray;
|
||||
// we calculate once the total number of vaults to avoid recalculation every time we use it
|
||||
*count = 0;
|
||||
for (int i = 0; i < directoryNumber; i++) {
|
||||
*count += vaultsPerDirectoryNumber[i];
|
||||
}
|
||||
return vaultsArray;
|
||||
}
|
||||
|
||||
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.
|
||||
debug("Handling the journal %s", path);
|
||||
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.
|
||||
debug("Handling the journal %s", path);
|
||||
|
||||
char *date = getFormatedTime(timeFormat, shouldDebug);
|
||||
debug("Formated time for the journal's entry is %s", date);
|
||||
char dateWithExtension[PATH_MAX];
|
||||
snprintf(dateWithExtension, PATH_MAX, "%s.md", date);
|
||||
sanitize(dateWithExtension);
|
||||
debug("Sanitized date: %s\n(it might be used later for a file name if the journal is divided)", dateWithExtension);
|
||||
struct stat metadata;
|
||||
error(stat(path, &metadata), "program", "stat() failed to get information about %s", path);
|
||||
if (S_ISREG(metadata.st_mode)) {
|
||||
debug("%s is a unified journal.", path);
|
||||
if (!isStringInFile(path, date, shouldDebug)) { // if there is no entry for current date
|
||||
appendToFile(path, date, shouldDebug);
|
||||
*journalWasUpdated = 1;
|
||||
} // if there is an entry do nothing
|
||||
} else if (S_ISDIR(metadata.st_mode)) {
|
||||
debug("%s is a divided journal.", path);
|
||||
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
|
||||
char *date = getFormatedTime(timeFormat, shouldDebug);
|
||||
debug("Formated time for the journal's entry is %s", date);
|
||||
char dateWithExtension[PATH_MAX];
|
||||
snprintf(dateWithExtension, PATH_MAX, "%s.md", date);
|
||||
sanitize(dateWithExtension);
|
||||
debug("Sanitized date: %s\n(it might be used later for a file name if the journal is divided)", dateWithExtension);
|
||||
struct stat metadata;
|
||||
stat(path, &metadata);
|
||||
if (S_ISREG(metadata.st_mode)) {
|
||||
debug("%s is a unified journal.", path);
|
||||
if (!isStringInFile(path, date, shouldDebug)) { // if there is no entry for current date
|
||||
appendToFile(path, date, shouldDebug);
|
||||
*journalWasUpdated = 1;
|
||||
} // if there is an entry do nothing
|
||||
} else if (S_ISDIR(metadata.st_mode)) {
|
||||
debug("%s is a divided journal.", path);
|
||||
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/%s", path, selectedOption);
|
||||
snprintf(temp, PATH_MAX, "%s/", path); // appends a /. For safety
|
||||
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);
|
||||
}
|
||||
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
|
||||
|
||||
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
|
||||
#define NOTES_H
|
||||
#include "utils.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.
|
||||
//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.
|
||||
// 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.
|
||||
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).
|
||||
// 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.
|
||||
// this function is inputed a path to a vault (which was selected before) and outpus all the
|
||||
// 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);
|
||||
// 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.
|
||||
char **getVaultsFromDirectory(char *dirString, int *count, int shouldDebug);
|
||||
// function gets as input an array of directoryNumber strings. Which are the directories the
|
||||
// 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.
|
||||
// journal is the name of the file.
|
||||
// journalWasUpdated will be set to 1 if a new entry was created
|
||||
|
||||
@@ -1,160 +1,177 @@
|
||||
#include "ui.h"
|
||||
#include "utils.h"
|
||||
|
||||
void createNewVault(char *dirToVault, int bypass, char *bypassvalue, int shouldDebug) {
|
||||
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
|
||||
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 emptyWarning = 0; // set to 1 later if inpted empty name
|
||||
input_screen:
|
||||
char *vaultName = malloc(PATH_MAX);
|
||||
if (!bypass) { // if won't bypass (if -v or --vault weren't set)
|
||||
echo();
|
||||
keypad(stdscr, FALSE);
|
||||
// color code from https://stackoverflow.com/a/73396575
|
||||
start_color();
|
||||
clear();
|
||||
use_default_colors();
|
||||
init_pair(1, COLOR_WHITE, -1); // -1 is blank
|
||||
init_pair(2, COLOR_RED, -1);
|
||||
attron(COLOR_PAIR(1));
|
||||
printw("Enter the name of the new vault: ");
|
||||
mvprintw(3, 0, "Unsafe characters such as \\ and / will be replaced by _");
|
||||
attroff(COLOR_PAIR(1));
|
||||
if (duplicateWarning) {
|
||||
attron(COLOR_PAIR(2));
|
||||
mvprintw(4, 0, "A vault with this name already exists. Please input another name");
|
||||
attroff(COLOR_PAIR(2));
|
||||
// choose in which directory the vault will be created
|
||||
char *dirToVault = ncursesSelect(directoriesArray, "Select a directory, in which the vault will remain (Use arrows or WASD, Enter to select)", directoryCount, 0, "", "", " ", shouldDebug);
|
||||
char *vaultName = malloc(PATH_MAX);
|
||||
if (!bypass) { // if won't bypass (if -v or --vault weren't set)
|
||||
echo();
|
||||
keypad(stdscr, FALSE);
|
||||
// color code from https://stackoverflow.com/a/73396575
|
||||
start_color();
|
||||
clear();
|
||||
use_default_colors();
|
||||
init_pair(1, COLOR_WHITE, -1); // -1 is blank
|
||||
init_pair(2, COLOR_RED, -1);
|
||||
attron(COLOR_PAIR(1));
|
||||
printw("Enter the name of the new vault: ");
|
||||
mvprintw(3, 0, "Unsafe characters such as \\ and / will be replaced by _");
|
||||
attroff(COLOR_PAIR(1));
|
||||
if (duplicateWarning) {
|
||||
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) {
|
||||
attron(COLOR_PAIR(2));
|
||||
mvprintw(5, 0, "The vault's name must not be empty. Please input another name");
|
||||
attroff(COLOR_PAIR(2));
|
||||
// check if no empty string
|
||||
if (strcmp(vaultName, "") == 0) {
|
||||
emptyWarning = 1;
|
||||
goto input_screen;
|
||||
}
|
||||
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';
|
||||
}
|
||||
// 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);
|
||||
debug("Inputed vaultName=%s", vaultName);
|
||||
sanitize(vaultName);
|
||||
debug("Sanitized vaultName=%s", vaultName);
|
||||
|
||||
struct stat st = {0}; // https://stackoverflow.com/a/7430262
|
||||
char vaultFullPath[PATH_MAX];
|
||||
sprintf(vaultFullPath, "%s/%s/", dirToVault, vaultName);
|
||||
if (stat(vaultFullPath, &st) == -1) {
|
||||
mkdir(vaultFullPath, 0744);
|
||||
} else {
|
||||
// if stat(...) != -1 it means the vault already exist. We will go back to the input screen with a new message.
|
||||
duplicateWarning = 1;
|
||||
goto input_screen;
|
||||
}
|
||||
free(vaultName);
|
||||
if (!isStringInArray(vaultName, (const char **)vaultsArray,
|
||||
vaultCount)) { // if vault doesnt already exists. We avoid vaults with the
|
||||
// same name even if they are from different directories.
|
||||
char vaultFullPath[PATH_MAX]; // recreating the full path
|
||||
sprintf(vaultFullPath, "%s/%s/", dirToVault, vaultName);
|
||||
|
||||
mkdir(vaultFullPath, 0744);
|
||||
} else {
|
||||
duplicateWarning = 1;
|
||||
goto input_screen;
|
||||
}
|
||||
free(vaultName);
|
||||
}
|
||||
|
||||
char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, char *journalRegex, int shouldDebug) {
|
||||
// input from user for the name
|
||||
char *fileName = malloc(BUFFER_SIZE);
|
||||
if (!bypass) { // if we don't bypass. (if -n or --note weren't set.)
|
||||
echo();
|
||||
keypad(stdscr, FALSE);
|
||||
clear();
|
||||
printw("Enter the name of the new note: ");
|
||||
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);
|
||||
move(1,1);
|
||||
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)
|
||||
refresh();
|
||||
endwin();
|
||||
reset_shell_mode();
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
} else { // bypasses user input if we bypass is set to 1
|
||||
strncpy(fileName, bypassvalue, BUFFER_SIZE);
|
||||
fileName[BUFFER_SIZE-1] = '\0';
|
||||
}
|
||||
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)
|
||||
// check/sanitize the input
|
||||
debug("Inputed fileName=%s", fileName);
|
||||
sanitize(fileName);
|
||||
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);
|
||||
// input from user for the name
|
||||
char *fileName = malloc(BUFFER_SIZE);
|
||||
if (!bypass) { // if we don't bypass. (if -n or --note weren't set.)
|
||||
echo();
|
||||
keypad(stdscr, FALSE);
|
||||
clear();
|
||||
printw("Enter the name of the new note: ");
|
||||
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);
|
||||
move(1, 1);
|
||||
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)
|
||||
refresh();
|
||||
endwin();
|
||||
reset_shell_mode();
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
} else { // bypasses user input if we bypass is set to 1
|
||||
strncpy(fileName, bypassvalue, BUFFER_SIZE);
|
||||
fileName[BUFFER_SIZE - 1] = '\0';
|
||||
}
|
||||
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
|
||||
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)
|
||||
// check/sanitize the input
|
||||
debug("Inputed fileName=%s", fileName);
|
||||
sanitize(fileName);
|
||||
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
|
||||
} 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
|
||||
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;
|
||||
return fileName;
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
char command[CMD_BUFFER];
|
||||
char indexFile[] = "/tmp/notewrapper_index_XXXXXX";
|
||||
|
||||
@@ -167,11 +184,7 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
||||
* Build index once into temp file
|
||||
* format: file:line:content
|
||||
*/
|
||||
snprintf(command, sizeof(command),
|
||||
"rg --line-number --no-heading --color=never . \"%s\" > %s",
|
||||
pathToFiles,
|
||||
indexFile
|
||||
);
|
||||
snprintf(command, sizeof(command), "rg --line-number --no-heading --color=never . \"%s\" > %s", pathToFiles, indexFile);
|
||||
|
||||
debug("INDEX CMD: %s", command);
|
||||
|
||||
@@ -221,12 +234,11 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
||||
*/
|
||||
snprintf(command, sizeof(command),
|
||||
"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 "
|
||||
"--bind 'change:show-preview'",
|
||||
indexFile,
|
||||
selectText ? selectText : "> "
|
||||
);
|
||||
indexFile, selectText ? selectText : "> ");
|
||||
debug("FZF CMD: %s", command);
|
||||
|
||||
FILE *fzfPipe = popen(command, "r");
|
||||
@@ -249,8 +261,8 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
||||
char *token = strtok(result, "/");
|
||||
result = NULL;
|
||||
while (token != NULL) {
|
||||
result = token;
|
||||
token = strtok(NULL, "/"); //Passing NULL means “don’t start a new string, resume
|
||||
result = token;
|
||||
token = strtok(NULL, "/"); // Passing NULL means “don’t start a new string, resume
|
||||
}
|
||||
|
||||
// clean up
|
||||
@@ -260,81 +272,84 @@ char* fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) {
|
||||
return result;
|
||||
}
|
||||
|
||||
char* ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int shouldDebug) {
|
||||
int highlight = 0; //curently highlighted option
|
||||
char *ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText,
|
||||
int shouldDebug) { // TODO we should see how it handles larges strings (like large directories)
|
||||
int highlight = 0; // curently highlighted option
|
||||
int key;
|
||||
|
||||
cbreak(); // disable line buffering
|
||||
noecho(); // don't echo key presses
|
||||
keypad(stdscr, TRUE); // enable arrow keys
|
||||
cbreak(); // disable line buffering
|
||||
noecho(); // don't echo key presses
|
||||
keypad(stdscr, TRUE); // enable arrow keys
|
||||
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();
|
||||
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) {
|
||||
clear();
|
||||
attron(COLOR_PAIR(1));
|
||||
mvprintw(0,0, "%s", optionsText);
|
||||
int offset = 1;
|
||||
if (strcmp(bottomText, "") != 0) {
|
||||
mvprintw(offset, 1, "%s", bottomText);
|
||||
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);
|
||||
clear();
|
||||
attron(COLOR_PAIR(1));
|
||||
mvprintw(0, 0, "%s", optionsText);
|
||||
int offset = 1;
|
||||
if (strcmp(bottomText, "") != 0) {
|
||||
mvprintw(offset, 1, "%s", bottomText);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
attroff(COLOR_PAIR(1));
|
||||
if (strcmp(middleText, "") != 0) {
|
||||
mvprintw(offset+optionsNumber, 1, "%s", middleText);
|
||||
offset++;
|
||||
}
|
||||
// Printf extraOptions with highlighting
|
||||
attron(COLOR_PAIR(2));
|
||||
for (int k = optionsNumber; k < optionsNumber + extraOptionsNumber; k++) {
|
||||
if (k == highlight) {
|
||||
attron(A_REVERSE);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
mvprintw(k+offset, 2, "%s", options[k]);
|
||||
if (k == highlight) {
|
||||
attroff(A_REVERSE);
|
||||
attroff(COLOR_PAIR(1));
|
||||
if (strcmp(middleText, "") != 0) {
|
||||
mvprintw(offset + optionsNumber, 1, "%s", middleText);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
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
|
||||
// Printf extraOptions with highlighting
|
||||
attron(COLOR_PAIR(2));
|
||||
for (int k = optionsNumber; k < optionsNumber + extraOptionsNumber; k++) {
|
||||
if (k == highlight) {
|
||||
attron(A_REVERSE);
|
||||
}
|
||||
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 'w':
|
||||
case 'W':
|
||||
highlight--;
|
||||
if (highlight < 0) {
|
||||
highlight = optionsNumber + extraOptionsNumber - 1;// can't select the -1nth option
|
||||
}
|
||||
break;
|
||||
highlight--;
|
||||
if (highlight < 0) {
|
||||
highlight = optionsNumber + extraOptionsNumber - 1; // can't select the -1nth option
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
case 'S':
|
||||
case KEY_DOWN:
|
||||
highlight++;
|
||||
if (highlight >= optionsNumber + extraOptionsNumber) {
|
||||
highlight = 0;
|
||||
}
|
||||
break;
|
||||
highlight++;
|
||||
if (highlight >= optionsNumber + extraOptionsNumber) {
|
||||
highlight = 0;
|
||||
}
|
||||
break;
|
||||
case 10: //\n
|
||||
goto end_loop;
|
||||
}
|
||||
goto end_loop;
|
||||
}
|
||||
}
|
||||
end_loop:
|
||||
end_loop:
|
||||
endwin(); // end ncurses mode
|
||||
fflush(stderr);
|
||||
debug("Selected option: %s", options[highlight]);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#ifndef 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ncurses.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <pwd.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <regex.h>
|
||||
#include "utils.h"
|
||||
#define CMD_BUFFER 4096 // for fzfSelect
|
||||
#include <unistd.h>
|
||||
#define CMD_BUFFER 4096 // for fzfSelect
|
||||
#define RESULT_BUFFER 1024 // for fzfSelect
|
||||
|
||||
/*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
|
||||
*/
|
||||
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.
|
||||
If vault already exists, prints a warning.
|
||||
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.
|
||||
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.
|
||||
options is the array of strings with all the options.
|
||||
optionsText is the text that will be printed at the top (For example: "Please select ...").
|
||||
we distinguish options from extraOptions.
|
||||
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. options is the array of strings with all the options. optionsText is the text that will be
|
||||
printed at the top (For example: "Please select ..."). we distinguish options from extraOptions.
|
||||
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.).
|
||||
note: extraOptions should be at the end of options.
|
||||
topText is printed between options text and the start of options.
|
||||
middleText (usally \n) is printed between options and extraOptions.
|
||||
bottomText is printed bellow.
|
||||
topText and middle must be exactly one line. If you want empty lines use " " and not "".
|
||||
returns the selected option. */
|
||||
char* ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int debug);
|
||||
extraOptions are options that are special and have a special color (for ex: "Delete vault",
|
||||
"Settings", etc.). note: extraOptions should be at the end of options. topText is printed between
|
||||
options text and the start of options. middleText (usally \n) is printed between options and
|
||||
extraOptions. bottomText is printed bellow. topText and middle must be exactly one line. If you want
|
||||
empty lines use " " and not "". returns the selected option. */
|
||||
char *ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int debug);
|
||||
#endif
|
||||
|
||||
+425
-343
@@ -1,8 +1,7 @@
|
||||
#include "utils.h"
|
||||
#include "string.h"
|
||||
|
||||
const char *supportedEditor[] = {"helix", "kakoune", "micro", "nano", "neovim", "vi", "vim"};
|
||||
const int numEditors = 7;
|
||||
const char *supportedEditor[] = {"helix", "jed", "kakoune", "micro", "nano", "neovim", "vi", "vim"};
|
||||
const int numEditors = 8;
|
||||
|
||||
int compareString(const void *a, const void *b) {
|
||||
const char *str1 = *(const char **)a;
|
||||
@@ -12,63 +11,65 @@ int compareString(const void *a, const void *b) {
|
||||
int reverseCompareString(const void *a, const void *b) {
|
||||
const char *str1 = *(const char **)a;
|
||||
const char *str2 = *(const char **)b;
|
||||
return -1*strcmp(str1, str2);
|
||||
return -1 * strcmp(str1, str2);
|
||||
}
|
||||
|
||||
void getCurrentTime(int *hour, int *minute, int *second) {
|
||||
time_t now = time(NULL); // Get current time in seconds since epoch
|
||||
struct tm *local = localtime(&now); // Convert to local time structure
|
||||
time_t now = time(NULL); // Get current time in seconds since epoch
|
||||
struct tm *local = localtime(&now); // Convert to local time structure
|
||||
|
||||
*hour = local->tm_hour; // Extract hour
|
||||
*minute = local->tm_min; // Extract minutes
|
||||
*second = local->tm_sec; // Extract seconds
|
||||
*hour = local->tm_hour; // Extract hour
|
||||
*minute = local->tm_min; // Extract minutes
|
||||
*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
|
||||
if (d) {
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
va_list args; //variadic function stuff
|
||||
va_start(args, message);
|
||||
int 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);
|
||||
vfprintf(stderr, message, args);
|
||||
fprintf(stderr, "\e[0m\n");
|
||||
va_end(args);
|
||||
}
|
||||
if (d) {
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
va_list args; // variadic function stuff
|
||||
va_start(args, message);
|
||||
int 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);
|
||||
vfprintf(stderr, message, args);
|
||||
fprintf(stderr, "\e[0m\n");
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
void _altDebug(const int d, const char *message, ...) { // use for less formal debuggin. (usefull if enumerating or making a list
|
||||
if (d) {
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
vfprintf(stderr, message, args);
|
||||
va_end(args);
|
||||
}
|
||||
void _altDebug(const int d, const char *message,
|
||||
...) { // use for less formal debuggin. (usefull if enumerating or making a list
|
||||
if (d) {
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
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
|
||||
if (condition) {
|
||||
int h, m, s;
|
||||
getCurrentTime(&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);
|
||||
if (errno != 0) {
|
||||
fprintf(stderr, " (System-level error message: %s)\n", strerror(errno));
|
||||
} else {
|
||||
fprintf(stderr, " (No system-level error; issue is application-level)\n");
|
||||
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
|
||||
if (condition) {
|
||||
int h, m, s;
|
||||
getCurrentTime(&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);
|
||||
if (errno != 0) {
|
||||
fprintf(stderr, " (System-level error message: %s)\n", strerror(errno));
|
||||
} 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) {
|
||||
@@ -80,20 +81,20 @@ static void copyDir(const char *source, const char *destination, const char **rs
|
||||
|
||||
if (pid == 0) {
|
||||
// Child process: execute rsync
|
||||
char **args = malloc((4 + rsyncArgsNumber)*sizeof(char*));
|
||||
char **args = malloc((4 + rsyncArgsNumber) * sizeof(char *));
|
||||
args[0] = "rsync";
|
||||
for (int i = 0; i < rsyncArgsNumber; i++) {
|
||||
args[i+1] = (char *)rsyncArgs[i];
|
||||
if (i == rsyncArgsNumber-1) { // last loop
|
||||
args[i+2] = (char*)source;
|
||||
args[i+3] = (char*)destination;
|
||||
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]);
|
||||
args[i + 1] = (char *)rsyncArgs[i];
|
||||
if (i == rsyncArgsNumber - 1) { // last loop
|
||||
args[i + 2] = (char *)source;
|
||||
args[i + 3] = (char *)destination;
|
||||
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");
|
||||
}
|
||||
altDebug("\n");
|
||||
}
|
||||
}
|
||||
execvp("rsync", args);
|
||||
// If execvp returns, there was an error
|
||||
@@ -113,19 +114,24 @@ static void ensureDir(const char *path, 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 cache_dir[PATH_MAX];
|
||||
|
||||
snprintf(config_dir, sizeof(config_dir),
|
||||
"%s/.config/notewrapper", home);
|
||||
snprintf(config_dir, sizeof(config_dir), "%s/.config/", home);
|
||||
|
||||
snprintf(cache_dir, sizeof(cache_dir),
|
||||
"%s/.cache/notewrapper", home);
|
||||
snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/", home);
|
||||
|
||||
ensureDir(config_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);
|
||||
|
||||
FILE *f = fopen(config_file, "r");
|
||||
@@ -136,28 +142,34 @@ void initAppFilesAndDirs(const char *home, const int shouldDebug) {
|
||||
debug("Creating default config file.");
|
||||
FILE *w = fopen(config_file, "w");
|
||||
error(!w, "program", "fopen failed opening %s");
|
||||
fprintf(w,
|
||||
"{\n"
|
||||
" \"directory\": \"~/Documents/Notes/\",\n"
|
||||
" \"render\": true,\n"
|
||||
" \"jumpToEndOfFileOnLaunch\": true,\n"
|
||||
" \"editor\": \"neovim\",\n"
|
||||
" \"journalRegex\": \".*journal.*\",\n"
|
||||
" \"dateEntry\": \"# %%Y %%m %%d %%a\",\n"
|
||||
" \"newLineOnOpening\": true,\n"
|
||||
" \"backup\": {\n"
|
||||
" \"enable\": false,\n"
|
||||
" \"directory\": \"/path/to/backup\",\n"
|
||||
" \"interval\": \"weekly\",\n"
|
||||
" \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
fprintf(w, // TODO CHANGE default json
|
||||
"{\n"
|
||||
" \"directory\": [\"~/Documents/\"],\n" // I personally use ~/Documents/Notes. But the
|
||||
// dir doesn't exist most times. So for the
|
||||
// default user it is better to put ~/Documents
|
||||
" \"render\": true,\n"
|
||||
" \"jumpToEndOfFileOnLaunch\": true,\n"
|
||||
" \"editor\": \"neovim\",\n"
|
||||
" \"journalRegex\": \".*journal.*\",\n"
|
||||
" \"dateEntry\": \"# %%Y %%m %%d %%a\",\n"
|
||||
" \"newLineOnOpening\": true,\n"
|
||||
" \"backup\": {\n"
|
||||
" \"enable\": false,\n"
|
||||
" \"directory\": {\n"
|
||||
" \"~/Documents/\": \"path/to/backup/\"\n"
|
||||
" },\n"
|
||||
" \"interval\": \"weekly\",\n"
|
||||
" \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
|
||||
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;
|
||||
|
||||
time_t now = time(NULL);
|
||||
debug("Time since epoch is %ld", (long)now);
|
||||
char cacheFilePATH[PATH_MAX];
|
||||
@@ -166,6 +178,7 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha
|
||||
struct stat st;
|
||||
if (stat(cacheFilePATH, &st) != 0) { // file does not exist
|
||||
shouldBackup = 1;
|
||||
debug("Creating (for the first time) %s", cacheFilePATH);
|
||||
FILE *cacheFile = fopen(cacheFilePATH, "w");
|
||||
if (!cacheFile) {
|
||||
error(1, "program", "Failed to open cache file for writing: %s", cacheFilePATH);
|
||||
@@ -174,6 +187,7 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha
|
||||
fclose(cacheFile);
|
||||
} else {
|
||||
FILE *cacheFile = fopen(cacheFilePATH, "r");
|
||||
debug("reading %s", cacheFilePATH);
|
||||
if (!cacheFile) {
|
||||
error(1, "program", "Failed to open cache file for reading: %s", cacheFilePATH);
|
||||
}
|
||||
@@ -189,8 +203,13 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha
|
||||
if (endptr == line || (*endptr != '\n' && *endptr != '\0')) {
|
||||
error(1, "program", "Invalid timestamp in cache file: %s", line);
|
||||
}
|
||||
|
||||
if (difftime(now, lastBackupTime) > interval) {
|
||||
double deltaTime = difftime(now, lastBackupTime);
|
||||
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;
|
||||
cacheFile = fopen(cacheFilePATH, "w");
|
||||
if (!cacheFile) {
|
||||
@@ -198,28 +217,57 @@ void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const cha
|
||||
}
|
||||
fprintf(cacheFile, "%ld\n", (long)now);
|
||||
fclose(cacheFile);
|
||||
} else {
|
||||
debug("(difftime) %f is smaller than (interval) %d -> no need to backup.", deltaTime, interval);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
char *editor;
|
||||
if (strcmp(editorToCheck, "neovim") == 0) {
|
||||
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");
|
||||
int isEditorValid(char *editorToCheck, int useDefaultEditor,
|
||||
int shouldDebug) { // check if editor is supported and if it is installed
|
||||
// check if supported
|
||||
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 {
|
||||
editor = strdup(editorToCheck);
|
||||
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);
|
||||
}
|
||||
// check if installed
|
||||
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");
|
||||
error(!path_env, "program", "getenv(\"PATH\") failed to get your path. NoteWrapper is unable to check if your desired editor is installed\n");
|
||||
debug("Your PATH is %s\n", path_env);
|
||||
error(!path_env, "program",
|
||||
"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 *dir = strtok(paths, ":");
|
||||
while (dir) {
|
||||
@@ -239,43 +287,46 @@ int doesEditorExist (char *editorToCheck, int shouldDebug) { // Some exectua
|
||||
}
|
||||
|
||||
char *getFormatedTime(char *format, int shouldDebug) {
|
||||
debug("Inputed time format is %s", format);
|
||||
time_t t = time(NULL);
|
||||
struct tm *tm = localtime(&t);
|
||||
char *buf = malloc(BUFFER_SIZE);
|
||||
strftime(buf, BUFFER_SIZE, format, tm);
|
||||
debug("Formated time is %s", buf);
|
||||
return buf;
|
||||
debug("Inputed time format is %s", format);
|
||||
time_t t = time(NULL);
|
||||
struct tm *tm = localtime(&t);
|
||||
char *buf = malloc(BUFFER_SIZE);
|
||||
strftime(buf, BUFFER_SIZE, format, tm);
|
||||
debug("Formated time is %s", buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int isStringInArray(const char *string, const char **array, const int len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (strcmp(string, array[i]) == 0) {
|
||||
return 1;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (strcmp(string, array[i]) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isStringInFile(const char *path, const char *string, const int shouldDebug) {
|
||||
FILE *file = fopen(path, "r");
|
||||
error(file == NULL, "program", "While searching for \"%s\", we could not open file %s", string, path);
|
||||
char buf[BUFFER_SIZE];
|
||||
FILE *file = fopen(path, "r");
|
||||
error(file == NULL, "program", "While searching for \"%s\", we could not open file %s", string, path);
|
||||
char buf[BUFFER_SIZE];
|
||||
|
||||
while (fgets(buf, BUFFER_SIZE, file) != NULL) {
|
||||
if (strstr(buf, string) != NULL) {
|
||||
debug("%s contains the substring %s", buf, string);
|
||||
return 1;
|
||||
while (fgets(buf, BUFFER_SIZE, file) != NULL) {
|
||||
if (strstr(buf, string) != NULL) {
|
||||
debug("%s contains the substring %s", buf, string);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug("The string %s is not inside %s", string, path);
|
||||
return 0;
|
||||
debug("The string %s is not inside %s", string, path);
|
||||
return 0;
|
||||
}
|
||||
void appendToFile(const char *path, const char *string, const int shouldDebug) {
|
||||
FILE *file = fopen(path, "r");
|
||||
char lastLine[1024] = {0};
|
||||
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
|
||||
while (fgets(buffer, sizeof(buffer), file) != NULL) {
|
||||
@@ -302,17 +353,17 @@ void appendToFile(const char *path, const char *string, const int shouldDebug) {
|
||||
}
|
||||
|
||||
void sanitize(char *string) {
|
||||
size_t stringLenght = strlen(string);
|
||||
for (size_t i = 0; i < stringLenght; i++) {
|
||||
if ((!isalnum((unsigned char)string[i]) && strchr("~/\\:*?\"\'|!$[]{}<>\n\r\t", string[i]))) { // replace unwanted chars by '_'
|
||||
string[i] = '_';
|
||||
size_t stringLenght = strlen(string);
|
||||
for (size_t i = 0; i < stringLenght; i++) {
|
||||
if ((!isalnum((unsigned char)string[i]) && strchr("~/\\:*?\"\'|!$[]{}<>\n\r\t", string[i]))) { // replace unwanted chars by '_'
|
||||
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
|
||||
@@ -320,262 +371,293 @@ void sanitize(char *string) {
|
||||
// remove() can't delete directories with files
|
||||
// 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) {
|
||||
(void)sb;
|
||||
(void)typeflag;
|
||||
(void)ftwbuf;
|
||||
(void)sb;
|
||||
(void)typeflag;
|
||||
(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
|
||||
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);
|
||||
// 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(filePath[0] == '.', "program", "%s starts with \".\". For security reasons, use absolute path and not relative path.", filePath);
|
||||
|
||||
int rv = remove(filePath);
|
||||
error(rv, "program", "remove() failed to delete %s", filePath);
|
||||
return rv;
|
||||
int rv = remove(filePath);
|
||||
error(rv, "program", "remove() failed to delete %s", filePath);
|
||||
return rv;
|
||||
}
|
||||
|
||||
int rmrf(char *path, int shouldDebug) {
|
||||
error(strcmp(path, "/") == 0, "program", "Refusing to delete root directory");
|
||||
return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
|
||||
error(strcmp(path, "/") == 0, "program", "Refusing to delete root directory");
|
||||
return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
|
||||
}
|
||||
|
||||
|
||||
int openEditor(char *path, char *editor, int render, int shouldJumpToEndOfFile, int shouldDebug) {
|
||||
|
||||
// this ensures that ncurses won't affect the editor behaviour
|
||||
pid_t editor_pid = fork();
|
||||
error(editor_pid < 0, "program", "fork() failed.");
|
||||
// this ensures that ncurses won't affect the editor behaviour
|
||||
pid_t editor_pid = fork();
|
||||
error(editor_pid < 0, "program", "fork() failed.");
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
|
||||
if (editor_pid == 0) {
|
||||
// =========================
|
||||
// CHILD: launch editor
|
||||
// =========================
|
||||
if (editor_pid == 0) {
|
||||
// =========================
|
||||
// CHILD: launch editor
|
||||
// =========================
|
||||
|
||||
// ---- NEOVIM / VIM ----
|
||||
if (strcmp(editor, "neovim") == 0 || strcmp(editor, "vim") == 0) {
|
||||
const char *bin = (strcmp(editor, "neovim") == 0) ? "nvim" : "vim";
|
||||
// ---- NEOVIM / VIM ----
|
||||
if (strcmp(editor, "neovim") == 0 || strcmp(editor, "vim") == 0) {
|
||||
const char *bin = (strcmp(editor, "neovim") == 0) ? "nvim" : "vim";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
error(1, "program", "execlp() failed.");
|
||||
// ---- MICRO ----
|
||||
} else if (strcmp(editor, "micro") == 0) {
|
||||
error(1, "program", "execlp() failed.");
|
||||
// ---- MICRO ----
|
||||
} else if (strcmp(editor, "micro") == 0) {
|
||||
|
||||
if (render) {
|
||||
if (shouldJumpToEndOfFile) {
|
||||
debug("Running MICRO_VIVIFY=1 micro %s +99999", path);
|
||||
setenv("MICRO_VIVIFY", "1", 1);
|
||||
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 (render) {
|
||||
if (shouldJumpToEndOfFile) {
|
||||
debug("Running MICRO_VIVIFY=1 micro %s +99999", path);
|
||||
setenv("MICRO_VIVIFY", "1", 1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
error(1, "program", "execlp() failed.");
|
||||
}
|
||||
|
||||
// ---- NANO ----
|
||||
else if (strcmp(editor, "nano") == 0) {
|
||||
|
||||
// If render enabled → spawn viv in parallel
|
||||
if (render) {
|
||||
debug("Running the editor...");
|
||||
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);
|
||||
error(1, "program", "execlp() failed.");
|
||||
}
|
||||
|
||||
debug("Running viv %s", viv_path);
|
||||
execlp("viv", "viv", viv_path, NULL);
|
||||
error(1, "program", "execlp() failed.");
|
||||
}
|
||||
// IMPORTANT: do NOT wait for viv
|
||||
}
|
||||
// ---- NANO ----
|
||||
else if (strcmp(editor, "nano") == 0) {
|
||||
|
||||
// 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);
|
||||
}
|
||||
// If render enabled → spawn viv in parallel
|
||||
if (render) {
|
||||
debug("Running Vivify...");
|
||||
pid_t viv_pid = fork();
|
||||
error(viv_pid < 0, "program", "fork() failed.");
|
||||
|
||||
error(1, "program", "execlp() failed.");
|
||||
// ---- HELIX ----
|
||||
} else if (strcmp(editor, "helix") == 0) {
|
||||
if (viv_pid == 0) {
|
||||
// GRANDCHILD → viv
|
||||
|
||||
// If render enabled → spawn viv in parallel
|
||||
if (render) {
|
||||
debug("Running the editor...");
|
||||
pid_t viv_pid = fork();
|
||||
error(viv_pid < 0, "program", "fork() failed.");
|
||||
char viv_path[PATH_MAX];
|
||||
strncpy(viv_path, path, PATH_MAX - 1);
|
||||
viv_path[PATH_MAX - 1] = '\0';
|
||||
|
||||
if (viv_pid == 0) {
|
||||
// GRANDCHILD → viv
|
||||
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
|
||||
}
|
||||
|
||||
char viv_path[PATH_MAX];
|
||||
strncpy(viv_path, path, PATH_MAX - 1);
|
||||
viv_path[PATH_MAX - 1] = '\0';
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (shouldJumpToEndOfFile) {
|
||||
strncat(viv_path, ":99999",
|
||||
PATH_MAX - strlen(viv_path) - 1);
|
||||
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) {
|
||||
|
||||
debug("Running viv %s", viv_path);
|
||||
execlp("viv", "viv", viv_path, NULL);
|
||||
error(1, "program", "execlp() failed.");
|
||||
}
|
||||
// IMPORTANT: do NOT wait for viv
|
||||
}
|
||||
// If render enabled → spawn viv in parallel
|
||||
if (render) {
|
||||
debug("Running Vivify...");
|
||||
pid_t viv_pid = fork();
|
||||
error(viv_pid < 0, "program", "fork() failed.");
|
||||
|
||||
// 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);
|
||||
}
|
||||
if (viv_pid == 0) {
|
||||
// GRANDCHILD → viv
|
||||
|
||||
error(1, "program", "execlp() failed.");
|
||||
}
|
||||
// ---- kakoune ----
|
||||
else if (strcmp(editor, "kakoune") == 0) {
|
||||
char viv_path[PATH_MAX];
|
||||
strncpy(viv_path, path, PATH_MAX - 1);
|
||||
viv_path[PATH_MAX - 1] = '\0';
|
||||
|
||||
// If render enabled → spawn viv in parallel
|
||||
if (render) {
|
||||
debug("Running the editor...");
|
||||
pid_t viv_pid = fork();
|
||||
error(viv_pid < 0, "program", "fork() failed.");
|
||||
if (shouldJumpToEndOfFile) {
|
||||
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
||||
}
|
||||
|
||||
if (viv_pid == 0) {
|
||||
// GRANDCHILD → viv
|
||||
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);
|
||||
}
|
||||
|
||||
char viv_path[PATH_MAX];
|
||||
strncpy(viv_path, path, PATH_MAX - 1);
|
||||
viv_path[PATH_MAX - 1] = '\0';
|
||||
error(1, "program", "execlp() failed.");
|
||||
// ---- VI ----
|
||||
} else if (strcmp(editor, "vi") == 0) {
|
||||
|
||||
if (shouldJumpToEndOfFile) {
|
||||
strncat(viv_path, ":99999",
|
||||
PATH_MAX - strlen(viv_path) - 1);
|
||||
// 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.");
|
||||
}
|
||||
|
||||
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 the editor...");
|
||||
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.");
|
||||
// ---- UNKNOWN EDITOR ----
|
||||
} else {
|
||||
error(1, "program", "Unknown editor.");
|
||||
}
|
||||
}
|
||||
|
||||
// =========================
|
||||
// PARENT: wait ONLY editor
|
||||
// =========================
|
||||
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
|
||||
if (errno != EINTR) {
|
||||
perror("waitpid");
|
||||
break;
|
||||
}
|
||||
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
|
||||
if (errno != EINTR) {
|
||||
perror("waitpid");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+37
-31
@@ -1,52 +1,53 @@
|
||||
#ifndef 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.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 <limits.h>
|
||||
#include <cjson/cJSON.h>
|
||||
#include <regex.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <ftw.h>
|
||||
#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
|
||||
#include <unistd.h>
|
||||
#ifndef VERSION
|
||||
#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 WEEKLY 604800
|
||||
#define MONTHLY 2628000 // calculated from the average lenght of a non leap year
|
||||
#define debug(message, ...) \
|
||||
_debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||
#define altDebug(message, ...) \
|
||||
_altDebug(shouldDebug, message, ##__VA_ARGS__)
|
||||
#define error(condition, type, message, ...) \
|
||||
_error(shouldDebug, condition, type, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||
#define debug(message, ...) _debug(shouldDebug, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__)
|
||||
#define altDebug(message, ...) _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
|
||||
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.
|
||||
//this function is used for qsort
|
||||
// compares two strings alphabetically.
|
||||
// this function is used for qsort
|
||||
int compareString(const void *a, const void *b);
|
||||
// compares two strings in reversed alphabetical order
|
||||
// this function is used for qsort
|
||||
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.
|
||||
// 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.
|
||||
int doesEditorExist(char *editorToCheck, int debug);
|
||||
// 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. 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.
|
||||
//formated debugging.
|
||||
// formated debugging.
|
||||
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, ...);
|
||||
// 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, ...);
|
||||
@@ -63,8 +64,8 @@ void appendToFile(const char *path, const char *string, const int shouldDebug);
|
||||
// replace unwanted chars by '_'.
|
||||
// '.' is replaced if it is only the first two chars
|
||||
void sanitize(char *string);
|
||||
//from https://stackoverflow.com/a/5467788.
|
||||
//deletes an entire directory. Use with parsimony and carefullness.
|
||||
// from https://stackoverflow.com/a/5467788.
|
||||
// deletes an entire directory. Use with parsimony and carefullness.
|
||||
int rmrf(char *path, int shouldDebug);
|
||||
// Inputs are the path to the file, the editor to open and some rendering option.
|
||||
// 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);
|
||||
/* 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.
|
||||
rsyncArgs is the array of arguments to be passed to rsync. Do not inclue destination or source. It will be added in the function.*/
|
||||
void handleBackups(const char *pathOfVaults, const char *pathOfBackup, const char *homeDir, const int interval, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug);
|
||||
Each pair of source/destination will launch one rsync process.
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user