Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rm command and update docs #3

Merged
merged 4 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: mehran-prs

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
4.

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Environment (please complete the following information):**
- OS: [e.g. iOS]
- Arch: [e.g., amd64]

**Additional context**
Add any other context about the problem here.
20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: mehran-prs

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ jobs:
strategy:
matrix:
target:
- 'windows/amd64'
- 'windows/386'
- 'windows/arm64'
# - 'windows/amd64'
# - 'windows/386'
# - 'windows/arm64'
- 'linux/amd64'
- 'linux/386'
- 'linux/arm64'
Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2024 Mehran Poursadeghi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
205 changes: 203 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,208 @@
Snip helps you manage snippets in cmd.
<p align="center">
<img width="250" src="./docs/images/snip_logo.png" alt="snip logo"/>
</p>

<p align="center">
<a href="https://goreportcard.com/report/github.com/mehran-prs/snip">
<img src="https://goreportcard.com/badge/github.com/mehran-prs/snip">
</a>
<a href="https://godoc.org/github.com/mehran-prs/snip">
<img src="https://godoc.org/github.com/mehran-prs/snip?status.svg" alt="GoDoc">
</a>
<a href="https://codecov.io/gh/mehran-prs/snip">
<img src="https://codecov.io/gh/mehran-prs/snip/branch/main/graph/badge.svg" />
</a>
</p>


Snip is a simple and minimal command-line snippet manager.

### Features

- View your snippets on command line and also manage them (create, edit, delete) using your favorite editor.
- Command-line auto-completion for the snippets names (supports `bash`, `zsh`, `fish` and `powershell`).
- Seamlessly integration with `fzf` to provide fuzzy search.
- Syntax highlighting (using `bat` and `glow` by default) and Git integration

### Usage

#### View a snippet

- Run `snip {snippet_name}` to view a snippet.
- If you've
enabled [`fzf` shell integration](https://github.com/junegunn/fzf?tab=readme-ov-file#setting-up-shell-integration) in
you bash or zsh,
you can find snippets by fuzzy search. e.g., type `snip **` and pres tab.

![snip view snippets](docs/images/snip-view.gif)

#### Edit snippets (Create|Update|Delete)

- Run `snip edit` to open your snippets repository in your favorite editor.
- Run `snip edit {snippet_path}` to create|edit your snippet in your favorite editor.
- Run `snip rm {snippet_path}` to remove a snippet. (use `-r` flag to remove recursively)

![snip edit snippets](docs/images/snip-edit.gif)

### Sync snippets changes with your remote git repository

- Run `snip sync [optional commit message]` to pull and then push your snippets changes. This command runs the following
commands:

```bash
git pull origin
git add -A
git commit -m "{your_provided_message | default_message}"
git push origin
```

![snip sync snippets](docs/images/snip-sync.gif)

> [!NOTE]
> before running `git sync` for first time, you need to initialize git in your snippets directory and
> also set upstream of your default branch. something like the following commands:

```bash
cd $(snip dir)
git init
git remote add origin {your_repo_remote_path}
# Push your first commit to setup upstream branch
git add -A && git commit -m "Initial commit"
git push -u origin main
```

### Prerequisites

- [`bat`](https://github.com/sharkdp/bat) and [`glow`](https://github.com/charmbracelet/glow) to render your snippets. (
you can customize `snip` to use other tools to
render files)
- (optional): [`fzf`](https://github.com/junegunn/fzf) to enable fuzzy-search in auto-completion.
(currently `fzf` autocompletion feature is supported just in `bash` and `zsh`).

### Getting started

- [Install prerequisites](#prerequisites): Install `bat` and `glow` commands. and optionally(recommended) `fzf` if
you're
using bash or zsh.
- [Install the snip command](#installation).
- [Enable auto-completion](#setting-up-shell-integration)
- [Set `SNIP_DIR`](#customization) env variables in your shell config (e.g., `~/.zshrc`) to point to your snippets
directory
- [Use `snip`](#usage) :))

### Installation

Install using go:

```bash
go install -ldflags "-X main.Version=main -X main.Date=`date +'%FT%TZ%z'`" github.com/mehran-prs/snip@main
```

Or get pre-compiled executables [here](http://github.com/mehran-prs/snip/releases)

### Setting up shell integration

Add the following line to your shell configuration file.

* bash
```sh
# Set up snip regular and fuzzy completion
eval "$(snip completion bash)"
```
* zsh
```sh
# Set up snip regular and fuzzy completion
source <(snip completion zsh)
```
* fish
```fish
# Set up snip completion
snip completion fish | source
```

> [!NOTE]
> [fzf shell integration](https://github.com/junegunn/fzf?tab=readme-ov-file#setting-up-shell-integration) is a
> pre-requisite
> of snip fuzzy search in auto-completion.

### Customization

Set the following env variables to customize snip:

| Name | Default | Description |
|--------------------------|-----------------------------------------------------|---------------------------------------------------------------------------------|
| SNIP_DIR | `~/snippets` | The snippets directory. It must be absolute path |
| SNIP_FILE_VIEWER_CMD | `bat --style plain --paging never` | The tool which renders non-markdown files in cmd |
| SNIP_MARKDOWN_VIEWER_CMD | `glow` | The tool which renders markdown files in cmd |
| SNIP_EDITOR | Value of the `EDITOR` env variable, otherwise `vim` | The editor which snip uses to let you edit snippets |
| SNIP_GIT | `git` | The git command which it uses to sync snippets with your remote git repository |
| SNIP_EXCLUDE | `.git,.idea` | comma-separated list of directories that you want to exclude in auto-completion |
| SNIP_VERBOSE | "" | Enable verbose mode |
| SNIP_LOG_TMP_FILENAME | "" | Set a temporary log file. it's helpful in autocompletion debugging |

### Commands

```bash
Usage:
snip [command] [flags]
snip [command]

Available Commands:
completion Generate completion script
dir prints the snippets directory
edit Create|Edit the snippet in the editor
help Help about any command
rm Remove a snippet or directory
sync sync the snippets changes with your remote git repository
version Print the version and build information

Flags:
-h, --help help for snip
```

### Multi-tenancy (Advanced usage)

I like to have multiple instances of the `snip` command under different names for multiple repositories. for example
`snip` to manage my snippets, and `tasks` to manage my tasks.
We can do that by creating a soft-link to the `snip` command (other solutions like aliasing doesn't work
perfectly in auto-completion, at least for me :)) ),
for example to add the `tasks` command, follow these steps:

- Link `tasks` to the `snip` command:

```bash
ln -s $(whereis snip | awk '{print $2}') /usr/local/bin/tasks
```

- Update your shell config to set the tasks directory for the `tasks` command(as its snippets directory) and also
enable autocompletion for it:

```bash
# Set the tasks directory (change to your own tasks directory)
export TASKS_DIR=/path/to/my/tasks

# Enable shell auto-completion (in this example, enabled for zsh)
source <(tasks completion zsh)

```

> [!NOTE]
> You may wonder how the `tasks` command reads its directory path from `TASKS_DIR` env variable instead of `SNIP_DIR`,
> actually the `snip` tool reads env variables from `{APPNAME}_{ENV_NAME}` (in this case `TASKS_*`) and if
> it was empty then reads from `SNIP_{ENV_NAME}`.

## Contributing

1. Fork the repository
1. Clone your fork (`git clone https://github.com/<your_username>/snip && cd snip`)
1. Create your feature branch (`git checkout -b my-new-feature`)
1. Make changes and add them (`git add .`)
1. Commit your changes (`git commit -m 'Add some feature'`)
1. Push to the branch (`git push -u origin my-new-feature`)
1. Create new pull request

### Some helper commands for contributors

### Some commands
```bash
# Run tests
go test ./...
Expand Down
16 changes: 9 additions & 7 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"sync"
)

const prefix = "SNIP_"
const prefix = "SNIP" // Env prefix.

var cfgOnce sync.Once // Singleton config instance.
var Cfg *Config
Expand All @@ -26,11 +26,11 @@ type Config struct {
LogTmpFileName string
}

func loadConfig(appPrefix string) (err error) {
func loadConfig(globalPrefix string, appPrefix string) (err error) {
env := func(name string, def ...string) string {
return DefaultStr(
os.Getenv(strings.ToUpper(appPrefix+name)),
append([]string{os.Getenv(strings.ToUpper(prefix + name))}, def...)...,
os.Getenv(strings.ToUpper(appPrefix+"_"+name)),
append([]string{os.Getenv(strings.ToUpper(globalPrefix + "_" + name))}, def...)...,
)
}
cfgOnce.Do(func() {
Expand Down Expand Up @@ -71,7 +71,7 @@ func loadConfig(appPrefix string) (err error) {
}
})

Verbose("Config: ", fmt.Sprintf("%#v", Cfg))
Verbose("Prefix: ", prefix, " app_prefix: ", appPrefix, " Config: ", fmt.Sprintf("%#v", Cfg))
return
}

Expand All @@ -90,11 +90,13 @@ func (c *Config) ViewerCmd(fname string) *exec.Cmd {
func (c *Config) SnippetPath(name string) string {
fname := name
if !strings.HasPrefix(name, c.Dir) { // join the snippets dir with the file.
fname = path.Join(c.Dir, name)
fname = JoinPaths(c.Dir, name)
}

isDirName := len(fname) != 0 && fname[len(fname)-1] == os.PathSeparator

stat, err := os.Stat(fname)
if err == nil && !stat.IsDir() { // If file exists and is not a directory, return its name
if err == nil && (!stat.IsDir() || isDirName) { // If file exists and is not a directory, return its name
return fname
}

Expand Down
6 changes: 3 additions & 3 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestLoadDefaultConfig(t *testing.T) {
homeDir, err := os.UserHomeDir()
assertEqual(t, err, nil)

assertEqual(t, loadConfig("TEST_"), nil)
assertEqual(t, loadConfig("TEST", "TEST"), nil)
assertEqual(t, Cfg.Dir, path.Join(homeDir, "snippets"))
assertEqualSlice(t, Cfg.FileViewerCMD, []string{"bat", "--style", "plain", "--paging", "never"})
assertEqualSlice(t, Cfg.MarkdownViewerCMD, []string{"glow"})
Expand All @@ -53,7 +53,7 @@ func TestLoadConfig(t *testing.T) {
setEnv(t, "TEST_VERBOSE", "TRUE")
setEnv(t, "TEST_LOG_TMP_FILENAME", "abc.log")

assertEqual(t, loadConfig("TEST_"), nil)
assertEqual(t, loadConfig("TEST", "TEST"), nil)

assertEqual(t, Cfg.Dir, "/ab/c")
assertEqualSlice(t, Cfg.FileViewerCMD, []string{"touch", "a"})
Expand All @@ -70,7 +70,7 @@ func TestLoadConfigInheritance(t *testing.T) {
setEnv(t, "TEST_DIR", "/ab/c")
setEnv(t, "SNIP_DIR", "/ab/d")
setEnv(t, "SNIP_GIT", "abc")
assertEqual(t, loadConfig("TEST_"), nil)
assertEqual(t, loadConfig("TEST", "TEST"), nil)

assertEqual(t, Cfg.Dir, "/ab/c")
assertEqual(t, Cfg.Dir, "/ab/c")
Expand Down
Binary file added docs/images/snip-edit.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/snip-sync.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/snip-view.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/snip_logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/snip_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading