├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── assets
├── borg_mascot.png
├── borg_mascot.svg
└── borg_mascot@2x.png
├── commands
├── common.go
├── config.go
├── edit.go
├── edit_test.go
├── link.go
├── login.go
├── new.go
├── query.go
└── worked.go
├── conf
└── conf.go
├── docs
└── README.md
├── main.go
├── makefile
├── snapcraft.yaml
└── types
└── types.go
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Go ###
3 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
4 | *.o
5 | *.a
6 | *.so
7 |
8 | # Folders
9 | _obj
10 | _test
11 |
12 | # Architecture specific extensions/prefixes
13 | *.[568vq]
14 | [568vq].out
15 |
16 | *.cgo1.go
17 | *.cgo2.c
18 | _cgo_defun.c
19 | _cgo_gotypes.go
20 | _cgo_export.*
21 |
22 | _testmain.go
23 |
24 | *.exe
25 | *.test
26 | *.prof
27 |
28 | # Output of the go coverage tool, specifically when used with LiteIDE
29 | *.out
30 |
31 | # external packages folder
32 | vendor/
33 |
34 | bower_components/
35 |
36 | #Atom ctags
37 | .tags*
38 |
39 | # Other
40 | daemon_linux_amd64
41 | /.idea/
42 | /borg
43 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - 1.6
4 | - tip
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | Copyright 2016 János Dobronszki.
179 |
180 | Licensed under the Apache License, Version 2.0 (the "License");
181 | you may not use this file except in compliance with the License.
182 | You may obtain a copy of the License at
183 |
184 | http://www.apache.org/licenses/LICENSE-2.0
185 |
186 | Unless required by applicable law or agreed to in writing, software
187 | distributed under the License is distributed on an "AS IS" BASIS,
188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189 | See the License for the specific language governing permissions and
190 | limitations under the License.
191 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | BORG – Search and save shell snippets without leaving your terminal
4 | ===
5 |  [](https://travis-ci.org/ok-borg/borg) [](https://goreportcard.com/report/github.com/ok-borg/borg) [](http://ok-b.org:1492)
6 |
7 | Borg was built out of the frustration of having to leave the terminal to search and click around for bash snippets.
8 | Glance over multiple snippets quickly with Borg's succinct output.
9 |
10 | PLEASE READ: The website (https://ok-b.org) is down, because I didn't have time to maintain it.
11 | You can host borg yourself, and we plan to resurrect the version hosted by us on 1backend (https://github.com/1backend/1backend).
12 | The ETA for this is a couple of months.
13 |
14 | ### Search
15 |
16 | ```
17 | borg "list only files"
18 | ```
19 |
20 | ```shell
21 | (1) Bash: How to list only files?
22 | [a] find . -maxdepth 1 -type f
23 | [b] ls -l | egrep -v '^d'
24 | ls -l | grep -v '^d'
25 |
26 | (2) List only common parent directories for files
27 | [a] # read a line into the variable "prefix", split at slashes
28 | IFS=/ read -a prefix
29 | # while there are more lines, one after another read them into "next",
30 | # also split at slashes
31 | while IFS=/ read -a next; do
32 | new_prefix=()
33 | # for all indexes in prefix
34 | for ((i=0; i < "${#prefix[@]}"; ++i)); do
35 | # if the word in the new line matches the old one
36 | if [[ "${prefix[i]}" == "${next[i]}" ]]; then
37 | ...
38 | ```
39 |
40 | Use `borg pipeto less` to pipe the results straight to `less` (or another program).
41 |
42 | Can't find what you are looking for? Be a good hacker and contribute your wisdom to the hive mind by [tweaking existing snippets and adding your own](https://github.com/ok-borg/borg/tree/master/docs).
43 |
44 | ### Install
45 |
46 | The following releases only let you search snippets. To add or edit snippets, install from source. Releases are coming soon.
47 |
48 | ```
49 | brew install borg
50 | ```
51 |
52 | For Linux, download [a release](https://github.com/ok-borg/borg/releases) manually:
53 |
54 | ```
55 | wget https://github.com/ok-borg/borg/releases/download/v0.0.3/borg_linux_amd64 -O /usr/local/bin/borg
56 | chmod 755 /usr/local/bin/borg
57 | ```
58 |
59 | Same for Mac:
60 |
61 | ```
62 | wget https://github.com/ok-borg/borg/releases/download/v0.0.3/borg_darwin_amd64 -O /usr/local/bin/borg
63 | chmod 755 /usr/local/bin/borg
64 | ```
65 |
66 | ### Rate results: `worked`
67 |
68 | When a result works for you, use the `worked` command to give feedback:
69 |
70 | ```
71 | borg worked 12
72 | ```
73 |
74 | This will rank the result higher for similar queries—especially helpful when a good result was buried in the search results.
75 |
76 | ### Advanced usage
77 |
78 | For more commands and their explanations, please see [advanced usage](https://github.com/ok-borg/borg/tree/master/docs).
79 |
80 | ### How does borg work?
81 |
82 | The client connects to a server at [ok-b.org](https://ok-b.org/). You can host your own server too (see daemon folder), though self-hosting will become less appealing once people start contributing their own content to the database.
83 |
84 | ### UI explanation
85 |
86 | - `()` denotes hits for your query
87 | - `[]` denotes snippets found for a given query
88 | - `...` under a `[]` means more lines to display (use the `-f` flag for full display, see more about usage below)
89 |
90 | ### Credits
91 |
92 | The borg mascot has been delivered to you by the amazing [Fabricio Rosa Marques](https://dribbble.com/fabric8).
93 |
94 | ### Community
95 |
96 | - Use the [dockerized borg client](https://github.com/juhofriman/borg-docker) if you don't want to install anything on your host!
97 |
--------------------------------------------------------------------------------
/assets/borg_mascot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ok-borg/borg/7b577fd417884a6c26eeb42b23d16a89f5a7dd45/assets/borg_mascot.png
--------------------------------------------------------------------------------
/assets/borg_mascot.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/borg_mascot@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ok-borg/borg/7b577fd417884a6c26eeb42b23d16a89f5a7dd45/assets/borg_mascot@2x.png
--------------------------------------------------------------------------------
/commands/common.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | var Commands map[string]Command = map[string]Command{}
4 |
5 | type Command struct {
6 | F func([]string) error
7 | Summary string
8 | }
9 |
--------------------------------------------------------------------------------
/commands/config.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/ok-borg/borg/conf"
7 | )
8 |
9 | func init() {
10 | var summary string = "Editor summary"
11 | Commands["editor"] = Command{
12 | F: Editor,
13 | Summary: summary,
14 | }
15 | Commands["pipeto"] = Command{
16 | F: PipeTo,
17 | Summary: "Pipe To summary",
18 | }
19 | }
20 |
21 | // Login saves a token acquired from the web page into the user config file
22 | func Editor(args []string) error {
23 | if len(args) < 2 {
24 | return errors.New("Please supply an editor. The default is vim, so if you are happy with that, do nothing.")
25 | }
26 | editor := args[1]
27 | conf, err := conf.Get()
28 | if err != nil {
29 | return err
30 | }
31 | conf.Editor = editor
32 | return conf.Save()
33 | }
34 |
35 | func PipeTo(args []string) error {
36 | if len(args) < 2 {
37 | return errors.New("Please supply a program to pipe to.")
38 | }
39 | pipeTo := args[1]
40 | conf, err := conf.Get()
41 | if err != nil {
42 | return err
43 | }
44 | conf.PipeTo = pipeTo
45 | return conf.Save()
46 | }
47 |
--------------------------------------------------------------------------------
/commands/edit.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io/ioutil"
9 | "net/http"
10 | "os"
11 | "os/exec"
12 | "regexp"
13 | "strconv"
14 | "strings"
15 | "text/template"
16 | "time"
17 |
18 | "github.com/ok-borg/borg/conf"
19 | "github.com/ok-borg/borg/types"
20 | )
21 |
22 | func init() {
23 | var summary string = "Edit summary"
24 | Commands["edit"] = Command{
25 | F: Edit,
26 | Summary: summary,
27 | }
28 | }
29 |
30 | func findIdFromQueryIndex(queryIndex int) (string, error) {
31 | bs, err := ioutil.ReadFile(conf.QueryFile)
32 | if err != nil {
33 | return "", err
34 | }
35 | m := map[string]interface{}{}
36 | err = json.Unmarshal(bs, &m)
37 | if err != nil {
38 | return "", err
39 | }
40 | li, ok := m["ids"].([]interface{})
41 | l := []string{}
42 | for _, v := range li {
43 | l = append(l, v.(string))
44 | }
45 | if !ok {
46 | return "", errors.New("Can't find ids in query history")
47 | }
48 | if len(l) <= queryIndex {
49 | return "", errors.New("Can't find index in ids")
50 | }
51 | return l[queryIndex], nil
52 | }
53 |
54 | // Edit a snippet based on index from last query results
55 | func Edit(args []string) error {
56 | if len(args) != 2 {
57 | return errors.New("Please supply a query index to edit.")
58 | }
59 | queryIndex := args[1]
60 | i, err := strconv.ParseInt(queryIndex, 10, 32)
61 | if err != nil {
62 | return err
63 | }
64 | id, err := findIdFromQueryIndex(int(i - 1))
65 | if err != nil {
66 | return err
67 | }
68 | s, err := getSnippet(id)
69 | if err != nil {
70 | return err
71 | }
72 | str := problemToText(s)
73 | c, err := conf.Get()
74 | if err != nil {
75 | return err
76 | }
77 | ioutil.WriteFile(conf.EditFile, []byte(str), 0755)
78 | cmd := exec.Command(c.Editor, conf.EditFile)
79 | cmd.Stdin = os.Stdin
80 | cmd.Stdout = os.Stdout
81 | cmd.Run()
82 | bs, err := ioutil.ReadFile(conf.EditFile)
83 | if err != nil {
84 | return err
85 | }
86 | p, err := textToProblem(string(bs))
87 | if err != nil {
88 | return err
89 | }
90 | p.Id = s.Id
91 | if *conf.D {
92 | fmt.Println(s)
93 | fmt.Println(p)
94 | return nil
95 | }
96 | return saveSnippet(p)
97 | }
98 |
99 | func getSnippet(id string) (*types.Problem, error) {
100 | req, err := http.NewRequest("GET", fmt.Sprintf("%v/v1/p/%v", host(), id), nil)
101 | if err != nil {
102 | return nil, fmt.Errorf("Failed to create request: %v", err)
103 | }
104 | c, err := conf.Get()
105 | if err != nil {
106 | return nil, err
107 | }
108 | req.Header.Add("authorization", c.Token)
109 | client := &http.Client{Timeout: time.Duration(10 * time.Second)}
110 | rsp, err := client.Do(req)
111 | if err != nil {
112 | return nil, fmt.Errorf("Error while making request: %v", err)
113 | }
114 | defer rsp.Body.Close()
115 | body, err := ioutil.ReadAll(rsp.Body)
116 | if err != nil {
117 | return nil, err
118 | }
119 | if rsp.StatusCode != 200 {
120 | return nil, fmt.Errorf("Status code: %v: %s", rsp.StatusCode, body)
121 | }
122 | ret := types.Problem{}
123 | return &ret, json.Unmarshal(body, &ret)
124 | }
125 |
126 | var tpl = `{{.Title}}
127 | {{range $index, $element := .Solutions}}
128 | [{{toChar $index}}]
129 | {{join $element.Body}}{{end}}
130 | `
131 |
132 | func problemToText(p *types.Problem) string {
133 | b := []byte{}
134 | buffer := bytes.NewBuffer(b)
135 | funcs := map[string]interface{}{
136 | "join": func(s []string) string {
137 | return strings.Join(s, "\n")
138 | },
139 | "toChar": toChar,
140 | }
141 | template.Must(template.New("edit").Funcs(funcs).Parse(tpl)).Execute(buffer, p)
142 | return string(buffer.Bytes())
143 | }
144 |
145 | // very primitive right now...
146 | func textToProblem(s string) (types.Problem, error) {
147 | ret := types.Problem{}
148 | lines := strings.Split(s, "\n")
149 | // wish i had a parser combinator library to do this...
150 | title := ""
151 | solutions := []types.Solution{}
152 | if len(lines) < 3 {
153 | return ret, errors.New("Edit is too short")
154 | }
155 | buf := []string{}
156 | for i, v := range lines {
157 | if i == 0 {
158 | title = v
159 | continue
160 | }
161 | if i == 1 && len(strings.TrimSpace(v)) == 0 { // skip the newline after the title
162 | continue
163 | }
164 | if i == len(lines)-1 || (len(strings.TrimSpace(v)) == 3 && string(v[0]) == "[" && regexp.MustCompile("^[a-z]$").Match([]byte{v[1]}) && string(v[2]) == "]") {
165 | if len(buf) > 0 {
166 | solutions = append(solutions, types.Solution{
167 | Body: []string{strings.Join(buf, "\n")},
168 | })
169 | buf = []string{}
170 | }
171 | continue
172 | }
173 | buf = append(buf, v)
174 | }
175 | ret.Title = title
176 | ret.Solutions = solutions
177 | return ret, nil
178 | }
179 |
--------------------------------------------------------------------------------
/commands/edit_test.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "github.com/ok-borg/borg/types"
5 | "reflect"
6 | "testing"
7 | )
8 |
9 | func TestTextToProblem(t *testing.T) {
10 | ps := []types.Problem{
11 | {
12 | Title: "title title",
13 | Solutions: []types.Solution{
14 | {
15 | Body: []string{
16 | "solution 1",
17 | },
18 | },
19 | },
20 | },
21 | {
22 | Title: "asdsadasd",
23 | Solutions: []types.Solution{
24 | {
25 | Body: []string{
26 | "a multiline snippet\ngood stuff",
27 | },
28 | },
29 | {
30 | Body: []string{
31 | "a multiline snippet again \n why not",
32 | },
33 | },
34 | },
35 | },
36 | }
37 | for _, v := range ps {
38 | text := problemToText(&v)
39 | p, err := textToProblem(text)
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | if !reflect.DeepEqual(v, p) {
44 | t.Fatal(v, p)
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/commands/link.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "strconv"
7 | )
8 |
9 | func init() {
10 | var summary string = "Link summary"
11 | Commands["link"] = Command{
12 | F: Link,
13 | Summary: summary,
14 | }
15 | }
16 |
17 | // Link prints the url to a query result
18 | func Link(args []string) error {
19 | if len(args) != 2 {
20 | return errors.New("Please supply a query index to generate the link.")
21 | }
22 | queryIndex := args[1]
23 | i, err := strconv.ParseInt(queryIndex, 10, 32)
24 | if err != nil {
25 | return err
26 | }
27 | id, err := findIdFromQueryIndex(int(i - 1))
28 | if err != nil {
29 | return err
30 | }
31 | fmt.Println(fmt.Sprintf("https://ok-b.org/t/%v/x", id))
32 | return nil
33 | }
34 |
--------------------------------------------------------------------------------
/commands/login.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/ok-borg/borg/conf"
7 | )
8 |
9 | func init() {
10 | var summary string = "Login summary"
11 | Commands["login"] = Command{
12 | F: Login,
13 | Summary: summary,
14 | }
15 | }
16 |
17 | // Login saves a token acquired from the web page into the user config file
18 | func Login(args []string) error {
19 | if len(args) != 2 {
20 | return errors.New("Please supply a github token to login with.")
21 | }
22 | token := args[1]
23 | if len(token) == 0 {
24 | return errors.New("Please supply a token. Don't have one? Go to https://ok-b.org and get it")
25 | }
26 | conf, err := conf.Get()
27 | if err != nil {
28 | return err
29 | }
30 | conf.Token = token
31 | return conf.Save()
32 | }
33 |
--------------------------------------------------------------------------------
/commands/new.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io/ioutil"
9 | "net/http"
10 | "os"
11 | "os/exec"
12 | "strings"
13 | "time"
14 |
15 | "github.com/ok-borg/borg/conf"
16 | "github.com/ok-borg/borg/types"
17 | )
18 |
19 | func init() {
20 | var summary string = "Save summary"
21 | Commands["new"] = Command{
22 | F: New,
23 | Summary: summary,
24 | }
25 | }
26 |
27 | func extractPost(s string) (string, string, error) {
28 | ss := strings.Split(s, "\n")
29 | if len(ss) < 3 {
30 | return "", "", errors.New("Content too short must be at least 3 lines")
31 | }
32 | title := strings.TrimSpace(ss[0])
33 | body := strings.TrimSpace(strings.Join(ss[1:], "\n"))
34 | return title, body, nil
35 | }
36 |
37 | // New saves a new snippet into the borg mind
38 | func New([]string) error {
39 | c, err := conf.Get()
40 | if err != nil {
41 | return err
42 | }
43 | cmd := exec.Command(c.Editor, conf.EditFile)
44 | cmd.Stdin = os.Stdin
45 | cmd.Stdout = os.Stdout
46 | cmd.Run()
47 | bs, err := ioutil.ReadFile(conf.EditFile)
48 | if err != nil {
49 | return err
50 | }
51 | t, b, err := extractPost(string(bs))
52 | if err != nil {
53 | return err
54 | }
55 | if len(t) == 0 || len(b) == 0 {
56 | return errors.New("Title or body is empty")
57 | }
58 | p := types.Problem{
59 | Title: t,
60 | Solutions: []types.Solution{
61 | {
62 | Body: []string{b},
63 | },
64 | },
65 | }
66 | return saveSnippet(p)
67 | }
68 |
69 | // POSTS or PUTS based on id existence
70 | func saveSnippet(p types.Problem) error {
71 | method := "POST"
72 | if len(p.Id) > 0 {
73 | method = "PUT"
74 | }
75 | bs, err := json.Marshal(p)
76 | if err != nil {
77 | return err
78 | }
79 | req, err := http.NewRequest(method, fmt.Sprintf("%v/v1/p", host()), bytes.NewReader(bs))
80 | if err != nil {
81 | return fmt.Errorf("Failed to create request: %v", err)
82 | }
83 | c, err := conf.Get()
84 | if err != nil {
85 | return err
86 | }
87 | req.Header.Add("authorization", c.Token)
88 | client := &http.Client{Timeout: time.Duration(10 * time.Second)}
89 | rsp, err := client.Do(req)
90 | if err != nil {
91 | return fmt.Errorf("Error while making request: %v", err)
92 | }
93 | defer rsp.Body.Close()
94 | body, err := ioutil.ReadAll(rsp.Body)
95 | if err != nil {
96 | return err
97 | }
98 | if rsp.StatusCode != 200 {
99 | return fmt.Errorf("Status code: %v: %s", rsp.StatusCode, body)
100 | }
101 | return nil
102 | }
103 |
--------------------------------------------------------------------------------
/commands/query.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "net/url"
10 | "os"
11 | "os/exec"
12 | "strings"
13 | "text/tabwriter"
14 | "time"
15 |
16 | "github.com/ok-borg/borg/conf"
17 | "github.com/ok-borg/borg/types"
18 | )
19 |
20 | // Query the borg server
21 | func Query(q string) error {
22 | c, err := conf.Get()
23 | if err != nil {
24 | return err
25 | }
26 | if len(c.PipeTo) > 0 && *conf.DontPipe == false {
27 | c1 := exec.Command("borg", "--dontpipe", `"`+q+`"`)
28 | c2 := exec.Command(c.PipeTo)
29 | c2.Stdin, _ = c1.StdoutPipe()
30 | c2.Stdout = os.Stdout
31 | if err = c2.Start(); err != nil {
32 | return err
33 | }
34 | if err = c1.Run(); err != nil {
35 | return err
36 | }
37 | return c2.Wait()
38 | }
39 | client := &http.Client{Timeout: time.Duration(10 * time.Second)}
40 | req, err := http.NewRequest("GET", fmt.Sprintf("%v/v1/query?l=%v&p=%v&q=%v", host(), *conf.L, *conf.P, url.QueryEscape(q)), nil)
41 | if err != nil {
42 | return fmt.Errorf("Failed to create request: %s", err.Error())
43 | }
44 | rsp, err := client.Do(req)
45 | if err != nil {
46 | return fmt.Errorf("Error while making request: %s", err.Error())
47 | }
48 | defer rsp.Body.Close()
49 | body, err := ioutil.ReadAll(rsp.Body)
50 | if err != nil {
51 | return err
52 | }
53 | if *conf.D {
54 | fmt.Println(fmt.Sprintf("json response: %v", string(body)))
55 | }
56 | problems := []types.Problem{}
57 | err = json.Unmarshal(body, &problems)
58 | if err != nil {
59 | return errors.New("Malformed response from server")
60 | }
61 | err = writeToFile(q, problems)
62 | if err != nil {
63 | fmt.Println(err)
64 | }
65 | renderQuery(problems)
66 | return nil
67 | }
68 |
69 | func renderQuery(problems []types.Problem) {
70 | const padding = 4
71 | w := tabwriter.NewWriter(os.Stdout, 0, 0, padding, ' ', tabwriter.AlignRight)
72 | for i, prob := range problems {
73 | if i > 0 {
74 | fmt.Fprintln(w, "")
75 | }
76 | fmt.Fprintln(w, fmt.Sprintf("(%v)", i+1), prob.Title)
77 | line := 0
78 | Loop:
79 | for x, sol := range prob.Solutions {
80 | fmt.Fprintf(w, "\t[%v]", toChar(x))
81 | for i, bodyPart := range sol.Body {
82 | if i > 0 {
83 | fmt.Fprintln(w, "\t\t", "")
84 | }
85 | bodyPartLines := strings.Split(bodyPart, "\n")
86 | for j, bodyPartLine := range bodyPartLines {
87 | t := "\t\t"
88 | if i == 0 && j == 0 {
89 | t = "\t"
90 | }
91 | if len(strings.TrimSpace(bodyPartLine)) == 0 {
92 | continue
93 | }
94 | fmt.Fprintln(w, t, strings.Trim(bodyPartLine, "\n"))
95 | line++
96 | if line == 10 && *conf.F == false {
97 | fmt.Fprintln(w, "\t", "...", "\t")
98 | break Loop
99 | }
100 | }
101 | }
102 | }
103 | }
104 | w.Flush()
105 | }
106 |
107 | func writeToFile(query string, ps []types.Problem) error {
108 | m := map[string]interface{}{
109 | "query": query,
110 | }
111 | ids := []string{}
112 | for _, v := range ps {
113 | ids = append(ids, v.Id)
114 | }
115 | m["ids"] = ids
116 | bs, err := json.Marshal(m)
117 | if err != nil {
118 | return err
119 | }
120 | return ioutil.WriteFile(conf.QueryFile, bs, 0755)
121 | }
122 |
123 | func host() string {
124 | return fmt.Sprintf("http://%v:9992", *conf.S)
125 | }
126 |
127 | func toChar(i int) string {
128 | return string('a' + i)
129 | }
130 |
--------------------------------------------------------------------------------
/commands/worked.go:
--------------------------------------------------------------------------------
1 | package commands
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io/ioutil"
9 | "net/http"
10 | "strconv"
11 | "time"
12 |
13 | "github.com/ok-borg/borg/conf"
14 | )
15 |
16 | func init() {
17 | var summary string = "Worked summary"
18 | Commands["worked"] = Command{
19 | F: Worked,
20 | Summary: summary,
21 | }
22 | }
23 |
24 | // Worked lets you mark a result as relevant one for a query
25 | func Worked(args []string) error {
26 | if len(args) != 2 {
27 | return errors.New("Please supply a query index.")
28 | }
29 | queryIndex := args[1]
30 | i, err := strconv.ParseInt(queryIndex, 10, 32)
31 | if err != nil {
32 | return err
33 | }
34 | id, err := findIdFromQueryIndex(int(i - 1))
35 | if err != nil {
36 | return err
37 | }
38 | query, err := getLastQuery()
39 | if err != nil {
40 | return err
41 | }
42 | return saveWorked(id, query)
43 | }
44 |
45 | func getLastQuery() (string, error) {
46 | bs, err := ioutil.ReadFile(conf.QueryFile)
47 | if err != nil {
48 | return "", err
49 | }
50 | m := map[string]interface{}{}
51 | err = json.Unmarshal(bs, &m)
52 | if err != nil {
53 | return "", err
54 | }
55 | s, ok := m["query"].(string)
56 | if !ok {
57 | return "", errors.New("Can't find last query")
58 | }
59 | return s, nil
60 | }
61 |
62 | func saveWorked(id, query string) error {
63 | bs, err := json.Marshal(map[string]string{
64 | "id": id,
65 | "query": query,
66 | })
67 | if err != nil {
68 | return err
69 | }
70 | req, err := http.NewRequest("POST", fmt.Sprintf("%v/v1/worked", host()), bytes.NewReader(bs))
71 | if err != nil {
72 | return fmt.Errorf("Failed to create request: %v", err)
73 | }
74 | c, err := conf.Get()
75 | if err != nil {
76 | return err
77 | }
78 | req.Header.Add("authorization", c.Token)
79 | client := &http.Client{Timeout: time.Duration(10 * time.Second)}
80 | rsp, err := client.Do(req)
81 | if err != nil {
82 | return fmt.Errorf("Error while making request: %v", err)
83 | }
84 | defer rsp.Body.Close()
85 | body, err := ioutil.ReadAll(rsp.Body)
86 | if err != nil {
87 | return err
88 | }
89 | if rsp.StatusCode != 200 {
90 | return fmt.Errorf("Status code: %v: %s", rsp.StatusCode, body)
91 | }
92 | return nil
93 | }
94 |
--------------------------------------------------------------------------------
/conf/conf.go:
--------------------------------------------------------------------------------
1 | package conf
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "path/filepath"
7 |
8 | flag "github.com/juju/gnuflag"
9 | "gopkg.in/yaml.v2"
10 | )
11 |
12 | var (
13 | // F flag prints full request
14 | F = flag.Bool("f", false, "Print full results, ie. no more '...'")
15 |
16 | // L flag limit results to a number
17 | L = flag.Int("l", 5, "Result list limit. Defaults to 5")
18 |
19 | // H flag specifies the host to connect to
20 | S = flag.String("s", "ok-b.org", "Server to connect to")
21 |
22 | H = flag.Bool("h", false, "Display help")
23 |
24 | Help = flag.Bool("help", false, "Display help, same as -h")
25 |
26 | // P flag enables private search
27 | P = flag.Bool("p", false, "Private search. Your search won't leave a trace. Pinky promise. Don't use this all the time if you want to see the search result relevancy improved")
28 |
29 | // D flag enables debug mode
30 | D = flag.Bool("d", false, "Debug mode")
31 | // DontPipe
32 | DontPipe = flag.Bool("dontpipe", false, "Flag for internal use - ignore this")
33 | // Version flag displays current version
34 | Version = flag.Bool("version", false, "Print version number")
35 | // V flag displays current version
36 | V = flag.Bool("v", false, "Print version number")
37 | )
38 | var (
39 | // EditFile borg edit file.
40 | EditFile string
41 | // ConfigFile borg config file.
42 | ConfigFile string
43 | // QueryFile borg query file.
44 | QueryFile string
45 | )
46 |
47 | func init() {
48 | borgDir := borgDir()
49 |
50 | EditFile = filepath.Join(borgDir, "edit")
51 | ConfigFile = filepath.Join(borgDir, "config.yml")
52 | QueryFile = filepath.Join(borgDir, "query")
53 |
54 | os.Mkdir(borgDir, os.ModePerm)
55 | os.Create(EditFile)
56 | if _, err := os.Stat(ConfigFile); os.IsNotExist(err) {
57 | os.Create(ConfigFile)
58 | }
59 | if _, err := os.Stat(QueryFile); os.IsNotExist(err) {
60 | os.Create(QueryFile)
61 | }
62 | }
63 |
64 | func borgDir() string {
65 | home := os.Getenv("HOME")
66 | if len(home) == 0 {
67 | panic("$HOME environment variable is not set")
68 | }
69 | dir := filepath.Join(home, ".borg")
70 | if _, err := os.Stat(dir); os.IsNotExist(err) {
71 | if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
72 | dir = filepath.Join(xdgConfigHome, "borg")
73 | } else {
74 | dir = filepath.Join(home, ".config")
75 | }
76 | }
77 | return dir
78 | }
79 |
80 | // Config file
81 | type Config struct {
82 | Token string
83 | DefaultTags []string
84 | Editor string
85 | PipeTo string
86 | }
87 |
88 | // Save config
89 | func (c Config) Save() error {
90 | bs, err := yaml.Marshal(c)
91 | if err != nil {
92 | return err
93 | }
94 | return ioutil.WriteFile(ConfigFile, bs, os.ModePerm)
95 | }
96 |
97 | // Get config
98 | func Get() (Config, error) {
99 | bs, err := ioutil.ReadFile(ConfigFile)
100 | if err != nil {
101 | panic(err)
102 | }
103 | c := &Config{}
104 | err = yaml.Unmarshal(bs, c)
105 | if err != nil {
106 | return *c, err
107 | }
108 | if len(c.Editor) == 0 {
109 | c.Editor = "vim"
110 | }
111 | return *c, nil
112 | }
113 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Advanced usage
2 |
3 | ### Add
4 |
5 | First obtain an oauth token by loggin in with github at [ok-b.org](http://ok-b.org).
6 |
7 | ```
8 | borg login my3XamPleT0k3n
9 | ```
10 |
11 | You are ready to save your own content
12 |
13 | ```
14 | borg new
15 | ```
16 |
17 | A vim window opens and lets you save your snippet. For example:
18 |
19 | ```
20 | How to grep for a file in current directory
21 |
22 | ls | grep mySearchTerm
23 | ```
24 |
25 | Save and exit vim.
26 |
27 | ### Edit
28 |
29 | Using our search example, typing `borg edit 1` will present you with an editor window containing:
30 |
31 | ```
32 | Find and delete .txt files in bash
33 |
34 | [a]
35 | find . -name "*.txt" | xargs rm
36 |
37 | [b]
38 | find . -name "*.txt" -exec rm {} \;
39 |
40 | [c]
41 | 10 $ find . -name "*.txt" -type f -delete
42 | ```
43 |
44 | Let's say you want to remove the second snippet because your don't like it. Modify it so it becomes:
45 |
46 | ```
47 | Find and delete .txt files in bash
48 |
49 | [a]
50 | find . -name "*.txt" | xargs rm
51 |
52 | [c]
53 | 10 $ find . -name "*.txt" -type f -delete
54 | ```
55 |
56 | Save and exit.
57 |
58 | (Do not care about the incorrect alphabetical order, it's ok)
59 |
60 | ### Who can add/edit what?
61 |
62 | Any logged in user can edit any content. We trust you with not being a vandal.
63 |
64 | ### Flags
65 |
66 | Borg supports gnu flags, so flags are supported both before and after the arguments, so all of the followings are valid:
67 |
68 | ```
69 | borg -l 30 -f "md5 Mac"
70 | borg "md5 Mac" -l30 -f
71 | borg -f "md5 Mac" -l30
72 | ```
73 |
74 | But what do they do?
75 |
76 | ```
77 | -f (= false)
78 | Print full results, ie. no more '...'
79 | -h (= "ok-b.org")
80 | Server to connect to
81 | -l (= 5)
82 | Result list limit. Defaults to 5
83 | -p (= false)
84 | Private search. Your search won't leave a trace. Pinky promise. Don't use this all the time if you want to see the search result relevancy improved
85 | ```
86 |
87 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "strings"
8 |
9 | "github.com/fatih/color"
10 | flag "github.com/juju/gnuflag"
11 | "github.com/ok-borg/borg/commands"
12 | "github.com/ok-borg/borg/conf"
13 | )
14 |
15 | var versionNumber, operatingSystem, architecture string
16 |
17 | func main() {
18 | flag.Parse(true)
19 | if *conf.Version || *conf.V {
20 | printVersion()
21 | return
22 | }
23 | if flag.NArg() == 0 {
24 | help()
25 | return
26 | }
27 |
28 | var err error
29 | if c, ok := commands.Commands[flag.Arg(0)]; !ok {
30 | err = commands.Query(strings.Join(flag.Args(), " "))
31 | } else {
32 | err = c.F(flag.Args())
33 | }
34 |
35 | if err != nil {
36 | fmt.Println(err)
37 | os.Exit(1)
38 | }
39 | }
40 |
41 | func help() {
42 | underline := color.New(color.Underline)
43 | green := color.New(color.FgGreen)
44 | blue := color.New(color.FgBlue)
45 |
46 | underline.Println("Usage:")
47 | fmt.Print("\t$ ")
48 | green.Println("borg \"your question\"\n")
49 | fmt.Print("\t$ ")
50 | green.Println("borg COMMAND\n")
51 | fmt.Print("\n\t BORG - Terminal based search for bash snippets\n\n")
52 | underline.Println("Commands:\n\n")
53 | for k, v := range commands.Commands {
54 | green.Printf("\t+ %-8s\t", k)
55 | fmt.Println(v.Summary)
56 | }
57 | // TODO: Display all the possible flags
58 | underline.Println("\nOptions:\n\n")
59 | // TODO: Replace --help so that it displays this usage instead
60 | blue.Printf("\t%-8s\t", "--help")
61 | fmt.Println("Show help")
62 | }
63 | func printVersion() {
64 | fmt.Printf("\tBorg version: %s (%s/%s)\n", versionNumber, operatingSystem, architecture)
65 | }
66 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | VERSION := $(shell git describe --tags)
2 | # This is just used in all, so as to have something in the arch and OS
3 | OS := $(shell uname -s)
4 | ARCH := $(shell uname -m)
5 |
6 | all:
7 | go get -d
8 | go build -ldflags "-X main.versionNumber=${VERSION} -X main.operatingSystem=${OS} -X main.architecture=${ARCH}"
9 | # TODO learn for loop in makefile
10 | release:
11 | go get -d
12 | GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=darwin -X main.architecture=amd64" -o borg_darwin_amd64
13 | GOOS=darwin GOARCH=386 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=darwin -X main.architecture=386" -o borg_darwin_386
14 | GOOS=freebsd GOARCH=386 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=freebsd -X main.architecture=386" -o borg_freebsd_386
15 | GOOS=freebsd GOARCH=amd64 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=freebsd -X main.architecture=amd64" -o borg_freebsd_amd64
16 | GOOS=freebsd GOARCH=arm go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=freebsd -X main.architecture=arm" -o borg_freebsd_arm
17 | GOOS=linux GOARCH=386 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=linux -X main.architecture=386" -o borg_linux_386
18 | GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=linux -X main.architecture=amd64" -o borg_linux_amd64
19 | GOOS=linux GOARCH=arm go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=linux -X main.architecture=arm" -o borg_linux_arm
20 | GOOS=netbsd GOARCH=386 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=netbsd -X main.architecture=386" -o borg_netbsd_386
21 | GOOS=netbsd GOARCH=amd64 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=netbsd -X main.architecture=amd64" -o borg_netbsd_amd64
22 | GOOS=netbsd GOARCH=arm go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=netbsd -X main.architecture=arm" -o borg_netbsd_arm
23 | GOOS=openbsd GOARCH=386 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=openbsd -X main.architecture=386" -o borg_openbsd_386
24 | GOOS=openbsd GOARCH=amd64 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=openbsd -X main.architecture=amd64" -o borg_openbsd_amd64
25 | GOOS=windows GOARCH=386 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=windows -X main.architecture=386" -o borg_windows_386
26 | GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -X main.versionNumber=${VERSION} -X main.operatingSystem=windows -X main.architecture=amd64" -o borg_windows_amd64
27 | # upx does not work on some arch/OS combos
28 | upx borg_*
29 | clean:
30 | rm borg*
31 |
--------------------------------------------------------------------------------
/snapcraft.yaml:
--------------------------------------------------------------------------------
1 | name: borg
2 | version: master
3 | summary: Search and save shell snippets without leaving your terminal
4 | description: |
5 | Borg was built out of the frustration of having to leave the terminal to
6 | search and click around for bash snippets. Borg's succint output also makes
7 | it easy to glance over multiple snippets quickly.
8 |
9 | grade: devel
10 | confinement: strict
11 |
12 | apps:
13 | borg:
14 | command: borg
15 | plugs: [network]
16 |
17 | parts:
18 | borg:
19 | source: .
20 | plugin: go
21 | go-importpath: github.com/ok-borg/borg
22 |
--------------------------------------------------------------------------------
/types/types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // Problem represents a result to a query. Might rename it to topic later
8 | type Problem struct {
9 | Id string `json:"Id"`
10 | Title string `json:"Title,omitempty"`
11 | Solutions []Solution `json:"Solutions,omitempty"`
12 | ImportMeta ImportMeta `json:"ImportMeta,omitempty"`
13 | CreatedBy string `json:"CreatedBy,omitempty"`
14 | Created time.Time `json:"Created,omitempty"`
15 | LastUpdatedBy string `json:"LastUpdatedBy,omitempty"`
16 | LastUpdated time.Time `json:"Updated,omitempty"`
17 | }
18 |
19 | // ImportMeta describes where the entry comes from if it comes from anywhere else than borg.
20 | type ImportMeta struct {
21 | Source int `json:"Source,omitempty"` // enum, 0 stackoverflow
22 | Id string `json:"Id,omitempty"`
23 | }
24 |
25 | // Solution is a snippet inside a `Problem`. Might rename it to snippet...
26 | type Solution struct {
27 | Body []string `json:"Body,omitempty"` // this was a mistake to make it a string - after db correction and refactoring should get rid of it
28 | Score int `json:"Score,omitempty"` // this has values in the DB but they are not being used for anything. should nuke it. editing an entry nukes it anyway
29 | }
30 |
31 | // Solutions is a helper type for sorting solutions based on score. Only used at bootstrapping
32 | type Solutions []Solution
33 |
34 | func (a Solutions) Len() int { return len(a) }
35 | func (a Solutions) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
36 | func (a Solutions) Less(i, j int) bool { return a[i].Score > a[j].Score }
37 |
--------------------------------------------------------------------------------