Skip to content

Commit ecfc2fa

Browse files
author
Amr Metwally
committed
Implemented base functionality
1 parent 70dbaa7 commit ecfc2fa

File tree

14 files changed

+466
-7
lines changed

14 files changed

+466
-7
lines changed

README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,44 @@
11
# Browser tab groups
22

3+
Save and open **links** from the command line with ease. A tap group is a collection of links (urls) that belong together.
4+
e.g `work` tap group would contain links for work.
5+
e.g `uni` tap group would contain links for uni etc.
6+
37
## Features
48

59
1. Group `urls` with a label
10+
- use case: for work i want to quickly open `[Gitlab, Jira, Github, ...]`
11+
- use case: for a given issue i want to quickly open its `[jira link, bitbucket pr, bitbucket branch, ...]`
12+
- use case: for uni i want to quickly open `[Moodle, Web mailer, ...]`
613
1. Open a group of `urls` from the cli in the browser
714
1. Open a single `url` from a group of `urls`
15+
- use case: for a given issue i saved its urls `[Bitbucket, Jira, Github, ...]` but want to quickly open only its `Jira link` without the rest of urls because i don't need them right now.
816
1. Remove a group of `urls`
17+
- use case: after being done with a ticket. i want to remove all of its saved links
918

1019
## Usage
1120

12-
1. `bt list` to list all saved tab groups
13-
1. `bt add <tap group> <url>` to add the `url` to the tap group `tap group`
14-
1. `bt open <tap group>` to open all `urls` in the tap group `tap group` in the browser
15-
1. `bt open <tap group> <url matching string>` to open the url(s) that _fuzzy match_ `url matching string` in the browser
21+
1. `br` will print the usage
22+
1. `br list` to list all saved tab groups
23+
1. `br add <tap group> <url>` to add the `url` to the tap group `tap group`
24+
1. `br open <tap group>` to open all `urls` in the tap group `tap group` in the browser
25+
1. `br open <tap group> <url matching string>` to open the url(s) that _fuzzy match_ `url matching string` in the browser
26+
27+
## Workflow looks like this
28+
29+
1. `br add work https://enerxess.atlassian.net/jira/your-work`
30+
1. `br add work https://bitbucket.org/exseffi`
31+
1. `br add uni https://webmail.tu-dortmund.de/roundcubemail/`
32+
1. `br ls`
33+
34+
```
35+
uni:
36+
https://webmail.tu-dortmund.de/roundcubemail/
37+
38+
work:
39+
https://enerxess.atlassian.net/jira/your-work
40+
https://bitbucket.org/exseffi
41+
```
42+
43+
1. `br open work` would open the two links under the `work` group in the browser
44+
1. `br open work bit` would open the link for **bitbucket** because it uses `fuzzy finding` to filter for links based on the user's input

browser/browser.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package browser
2+
3+
import (
4+
"os/exec"
5+
"runtime"
6+
)
7+
8+
type Browser interface {
9+
//OpenLink opens a link in the browser
10+
OpenLink(link string) error
11+
12+
//OpenLinks opens a link in the browser
13+
OpenLinks(links []string) error
14+
}
15+
16+
// browser is the internal implementation for the Browser interface
17+
type browser struct {
18+
}
19+
20+
// OpenLink opens a link in the browser
21+
func (br *browser) OpenLink(link string) error {
22+
23+
var args []string
24+
switch runtime.GOOS {
25+
case "darwin":
26+
args = []string{"open"}
27+
case "windows":
28+
args = []string{"cmd", "/c", "start"}
29+
default:
30+
args = []string{"xdg-open"}
31+
}
32+
cmd := exec.Command(args[0], append(args[1:], link)...)
33+
return cmd.Start()
34+
}
35+
36+
// OpenLinks opens all links in the browser
37+
func (br *browser) OpenLinks(links []string) error {
38+
39+
for _, link := range links {
40+
err := br.OpenLink(link)
41+
if err != nil {
42+
return err
43+
}
44+
45+
}
46+
return nil
47+
}
48+
49+
func NewBrowser() Browser {
50+
return &browser{}
51+
}

cmd/add.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cmd
2+
3+
import (
4+
"io"
5+
"os"
6+
7+
"github.com/magdyamr542/browser-tab-groups/configManager"
8+
)
9+
10+
// Adding a new tap group
11+
type AddCmd struct {
12+
TapGroup string `arg:"" name:"tap group" help:"the tap group to add the url to"`
13+
Url string `arg:"" name:"url" help:"the url to add"`
14+
}
15+
16+
func (add *AddCmd) Run() error {
17+
jsonCmg, err := configManager.NewJsonConfigManager()
18+
if err != nil {
19+
return err
20+
}
21+
return addUrlToTapGroup(os.Stdout, jsonCmg, add.TapGroup, add.Url)
22+
}
23+
24+
// addUrlToTapGroup adds the given url to the given tap group
25+
func addUrlToTapGroup(outputW io.Writer, cm configManager.ConfigManager, tapGroup, url string) error {
26+
err := cm.AddUrl(url, tapGroup)
27+
if err != nil {
28+
return err
29+
}
30+
outputW.Write([]byte("url added"))
31+
return nil
32+
}

cmd/ls.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"github.com/magdyamr542/browser-tab-groups/configManager"
9+
)
10+
11+
// Listing all tap groups with their urls
12+
type LsCmd struct{}
13+
14+
func (ls *LsCmd) Run() error {
15+
16+
jsonCmg, err := configManager.NewJsonConfigManager()
17+
if err != nil {
18+
return err
19+
}
20+
return listTapGroups(os.Stdout, jsonCmg)
21+
}
22+
23+
// listTapGroups lists all tap groups
24+
func listTapGroups(outputW io.Writer, cm configManager.ConfigManager) error {
25+
cfg, err := cm.GetConfig()
26+
if err != nil {
27+
return err
28+
}
29+
30+
i := 0
31+
for groupName, urls := range cfg {
32+
entry := fmt.Sprintf("%v:\n", groupName)
33+
outputW.Write([]byte(entry))
34+
for i := range urls {
35+
outputW.Write([]byte(fmt.Sprintf(" %v\n", urls[i])))
36+
}
37+
i += 1
38+
if i < len(cfg) {
39+
outputW.Write([]byte("\n"))
40+
}
41+
}
42+
return nil
43+
}

cmd/open.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"io"
6+
"os"
7+
"strings"
8+
9+
"github.com/magdyamr542/browser-tab-groups/browser"
10+
"github.com/magdyamr542/browser-tab-groups/configManager"
11+
"github.com/magdyamr542/browser-tab-groups/helpers"
12+
13+
"github.com/lithammer/fuzzysearch/fuzzy"
14+
)
15+
16+
// Adding a new tap group
17+
type OpenCmd struct {
18+
TapGroup string `arg:"" name:"tap group" help:"the tap group to add the url to"`
19+
UrlLike string `arg:"" optional:"" name:"url part" help:"a part of the url to be use with fuzzy matching"`
20+
}
21+
22+
func (open *OpenCmd) Run() error {
23+
jsonCmg, err := configManager.NewJsonConfigManager()
24+
if err != nil {
25+
return err
26+
}
27+
return openTapGroup(os.Stdout, open.TapGroup, open.UrlLike, jsonCmg, browser.NewBrowser())
28+
}
29+
30+
// AddUrlToTapGroup adds the given url to the given tap group
31+
func openTapGroup(outputW io.Writer, tapGroup string, urlLike string, cm configManager.ConfigManager, br browser.Browser) error {
32+
urls, err := cm.GetUrls(tapGroup)
33+
if err != nil {
34+
return err
35+
}
36+
if len(urls) == 0 {
37+
return errors.New("the given tap group does not have urls")
38+
}
39+
40+
if len(strings.TrimSpace(urlLike)) > 0 {
41+
urlLikeLower := strings.ToLower(urlLike)
42+
urls = helpers.Filter(urls, func(url string) bool {
43+
return fuzzy.Match(urlLikeLower, strings.ToLower(url))
44+
})
45+
46+
if len(urls) == 0 {
47+
return errors.New("no matches found in the given tap group")
48+
}
49+
}
50+
51+
return br.OpenLinks(urls)
52+
}

cmd/rm.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cmd
2+
3+
import (
4+
"io"
5+
"os"
6+
7+
"github.com/magdyamr542/browser-tab-groups/configManager"
8+
)
9+
10+
// Removing a tap group
11+
type RmCmd struct {
12+
TapGroup string `arg:"" name:"tap group" help:"the tap group to remove"`
13+
}
14+
15+
func (rm *RmCmd) Run() error {
16+
17+
jsonCmg, err := configManager.NewJsonConfigManager()
18+
if err != nil {
19+
return err
20+
}
21+
return removeTapGroup(os.Stdout, jsonCmg, rm.TapGroup)
22+
}
23+
24+
// removeTapGroup removes a saved tap group
25+
func removeTapGroup(outputW io.Writer, cm configManager.ConfigManager, tapGroup string) error {
26+
err := cm.RemoveTapGroup(tapGroup)
27+
if err != nil {
28+
return err
29+
}
30+
outputW.Write([]byte("removed"))
31+
return nil
32+
}

configManager/configManager.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package configManager
2+
3+
type ConfigManager interface {
4+
//GetConfig gets the config instance. this should be a map from a tap group to the list of urls
5+
GetConfig() (map[string][]string, error)
6+
7+
// AddUrl adds the given url to the given tap group. the tap group is created if it does not exist
8+
AddUrl(url string, tapGroup string) error
9+
10+
// GetUrls gets the urls for the given tap group
11+
GetUrls(tapGroup string) ([]string, error)
12+
13+
// RemoveTapGroup removes all urls saved in the given tap group
14+
RemoveTapGroup(tapGroup string) error
15+
}

0 commit comments

Comments
 (0)