├── .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 | ![cruft guaranteed](https://img.shields.io/badge/cruft-guaranteed-green.svg) [![Travis CI](https://api.travis-ci.org/ok-borg/borg.svg?branch=master)](https://travis-ci.org/ok-borg/borg) [![Go Report Card](https://goreportcard.com/badge/github.com/ok-borg/borg)](https://goreportcard.com/report/github.com/ok-borg/borg) [![Slack Status](http://ok-b.org:1492/badge.svg)](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 | Zeichenfläche 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 | --------------------------------------------------------------------------------