├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── README.md ├── dict └── dict.go ├── go.mod ├── go.sum ├── main.go └── utils └── xdg.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # others 15 | dist 16 | tango 17 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: tango 2 | before: 3 | hooks: 4 | - go mod tidy 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | - darwin 11 | ldflags: 12 | - -s -w -X main.version=v{{ .Version }} -X main.gitCommit={{ .ShortCommit }} -X main.buildDate={{ .Date }} 13 | archives: 14 | - replacements: 15 | darwin: Darwin 16 | linux: Linux 17 | windows: Windows 18 | 386: i386 19 | amd64: x86_64 20 | brews: 21 | - tap: 22 | owner: masakichi 23 | name: homebrew-tap 24 | folder: Formula 25 | license: "MIT" 26 | homepage: "https://github.com/masakichi/tango" 27 | description: "Tango(単語) is a local command-line Japanese dictionary tool using yomichan's dictionary files." 28 | checksum: 29 | name_template: 'checksums.txt' 30 | snapshot: 31 | name_template: "{{ incpatch .Version }}-dev" 32 | changelog: 33 | sort: asc 34 | filters: 35 | exclude: 36 | - '^docs:' 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Gimo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tango(単語) 2 | 3 | Tango(単語) is a local command-line Japanese dictionary tool using yomichan's dictionary files. 4 | 5 |

6 | tango 単語 preview 7 |

8 | 9 | ## Install 10 | 11 | If you use Homebrew you can: 12 | 13 | ```bash 14 | brew install masakichi/tap/tango 15 | ``` 16 | 17 | For Go 1.16+ 18 | 19 | ```bash 20 | go install github.com/masakichi/tango@latest 21 | ``` 22 | 23 | Or use `go get` 24 | 25 | ```bash 26 | GO111MODULE=on go get github.com/masakichi/tango 27 | ``` 28 | 29 | ## Configuration 30 | 31 | After install tango, you will need to import dictionaries as you wish. 32 | 33 | Dictionariy zip files can be found in yomichan's homepage: https://foosoft.net/projects/yomichan/index.html#dictionaries 34 | 35 | You can directly import the downloaded dictionary zip file by calling `tango -import path_to_dictionary.zip`. 36 | 37 | ## Usage 38 | 39 | ``` 40 | $ tango -h 41 | 42 | tango: A CLI Japanese Dictionary Tool 43 | Usage: tango 単語 44 | 45 | Other Commands of tango: 46 | -import string 47 | import yomichan's dictionary zip file 48 | -list 49 | list all dictionaries 50 | -version 51 | print tango app version 52 | ``` 53 | -------------------------------------------------------------------------------- /dict/dict.go: -------------------------------------------------------------------------------- 1 | package dict 2 | 3 | import ( 4 | "archive/zip" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | // https://github.com/FooSoft/yomichan-import/blob/5fe039e5f66ccad397f97a44a9f406a5a68a9438/common.go 15 | //type dbTerm struct { 16 | // Expression string 17 | // Reading string 18 | // DefinitionTags []string 19 | // Rules []string 20 | // Score int 21 | // Glossary []string 22 | // Sequence int 23 | // TermTags []string 24 | //} 25 | type Term struct { 26 | Expression string 27 | Reading string 28 | Glossaries []string 29 | Dict string 30 | } 31 | 32 | type Dict struct { 33 | ID int 34 | Title string `json:"title"` 35 | Format int `json:"format"` 36 | Revision string `json:"revision"` 37 | } 38 | 39 | func (t *Term) UnmarshalJSON(data []byte) error { 40 | var v []interface{} 41 | if err := json.Unmarshal(data, &v); err != nil { 42 | return err 43 | } 44 | t.Expression = v[0].(string) 45 | t.Reading = v[1].(string) 46 | //t.Glossaries = v[5].([]string) 47 | t.Glossaries = func(gs []interface{}) []string { 48 | result := []string{} 49 | for _, g := range gs { 50 | result = append(result, g.(string)) 51 | } 52 | return result 53 | }(v[5].([]interface{})) 54 | return nil 55 | } 56 | 57 | func InitDictDB(db *sql.DB) { 58 | db.Exec(`CREATE TABLE IF NOT EXISTS terms (id INTEGER PRIMARY KEY, expression TEXT, reading TEXT, glossaries TEXT, dict_id INTEGER)`) 59 | db.Exec(`CREATE INDEX IF NOT EXISTS idx_expr ON terms (expression)`) 60 | db.Exec(`CREATE INDEX IF NOT EXISTS idx_read ON terms (reading)`) 61 | db.Exec(`CREATE TABLE IF NOT EXISTS dicts (id INTEGER PRIMARY KEY, title TEXT, format INTEGER, revision TEXT)`) 62 | } 63 | 64 | func ImportDictDB(db *sql.DB, dictName string) { 65 | r, err := zip.OpenReader(dictName) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | defer r.Close() 70 | var dict Dict 71 | for _, f := range r.File { 72 | if f.Name == "index.json" { 73 | rc, _ := f.Open() 74 | content, _ := ioutil.ReadAll(rc) 75 | json.Unmarshal(content, &dict) 76 | rc.Close() 77 | } 78 | } 79 | if &dict == nil { 80 | log.Fatal("bad dict") 81 | } 82 | 83 | var dictTitle string 84 | row := db.QueryRow(`SELECT title FROM dicts WHERE title = ?`, dict.Title) 85 | row.Scan(&dictTitle) 86 | if dictTitle != "" { 87 | log.Fatal("dict already exist") 88 | } 89 | 90 | tx, _ := db.Begin() 91 | 92 | rv, err := tx.Exec( 93 | `INSERT INTO dicts (title, format, revision) VALUES (?,?,?)`, 94 | dict.Title, dict.Format, dict.Revision, 95 | ) 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | dictID, _ := rv.LastInsertId() 100 | 101 | termList := []Term{} 102 | for _, f := range r.File { 103 | if !strings.HasPrefix(f.Name, "term_bank_") { 104 | continue 105 | } 106 | rc, _ := f.Open() 107 | content, _ := ioutil.ReadAll(rc) 108 | 109 | var v []Term 110 | json.Unmarshal(content, &v) 111 | termList = append(termList, v...) 112 | rc.Close() 113 | } 114 | canCommit := true 115 | for _, t := range termList { 116 | glossaries, _ := json.Marshal(t.Glossaries) 117 | _, err := tx.Exec( 118 | `INSERT INTO terms (expression, reading, glossaries, dict_id) VALUES (?,?,?,?)`, 119 | t.Expression, t.Reading, string(glossaries), dictID, 120 | ) 121 | if err != nil { 122 | fmt.Println(err) 123 | canCommit = false 124 | break 125 | } 126 | } 127 | if canCommit { 128 | tx.Commit() 129 | fmt.Println("import done") 130 | } else { 131 | tx.Rollback() 132 | } 133 | } 134 | 135 | func getAllDicts(db *sql.DB) ([]Dict, error) { 136 | rows, err := db.Query( 137 | `SELECT id, title, format, revision 138 | FROM dicts 139 | ORDER BY id ASC`, 140 | ) 141 | if err != nil { 142 | return nil, err 143 | } 144 | var result []Dict 145 | for rows.Next() { 146 | var d Dict 147 | rows.Scan(&d.ID, &d.Title, &d.Format, &d.Revision) 148 | result = append(result, d) 149 | } 150 | return result, nil 151 | } 152 | 153 | func defineWord(db *sql.DB, w string) ([]Term, error) { 154 | rows, err := db.Query( 155 | `SELECT expression, reading, glossaries, d.title 156 | FROM terms t 157 | LEFT JOIN dicts d on t.dict_id = d.id 158 | WHERE expression = ? OR reading = ?`, 159 | w, w, 160 | ) 161 | if err != nil { 162 | return nil, err 163 | } 164 | var result []Term 165 | for rows.Next() { 166 | var t Term 167 | var glossaries string 168 | rows.Scan(&t.Expression, &t.Reading, &glossaries, &t.Dict) 169 | json.Unmarshal([]byte(glossaries), &t.Glossaries) 170 | result = append(result, t) 171 | } 172 | return result, nil 173 | } 174 | 175 | func PrintTerms(db *sql.DB) { 176 | terms, err := defineWord(db, os.Args[1]) 177 | if err != nil { 178 | log.Fatal(err) 179 | } 180 | for _, t := range terms { 181 | fmt.Printf("%s(%s)\n[%s]\n", t.Expression, t.Reading, t.Dict) 182 | fmt.Println(strings.TrimSuffix(strings.Join(t.Glossaries, "\n"), "\n") + "\n") 183 | } 184 | } 185 | 186 | func PrintDicts(db *sql.DB) { 187 | dicts, err := getAllDicts(db) 188 | if err != nil { 189 | log.Fatal(err) 190 | } 191 | for _, d := range dicts { 192 | fmt.Printf("[%d] %s Format: %d, Revision: %s\n", d.ID, d.Title, d.Format, d.Revision) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/masakichi/tango 2 | 3 | go 1.17 4 | 5 | require modernc.org/sqlite v1.13.1 6 | 7 | require ( 8 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 9 | github.com/mattn/go-isatty v0.0.12 // indirect 10 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect 11 | golang.org/x/mod v0.3.0 // indirect 12 | golang.org/x/sys v0.0.0-20210902050250-f475640dd07b // indirect 13 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect 14 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 15 | lukechampine.com/uint128 v1.1.1 // indirect 16 | modernc.org/cc/v3 v3.34.0 // indirect 17 | modernc.org/ccgo/v3 v3.11.2 // indirect 18 | modernc.org/libc v1.11.3 // indirect 19 | modernc.org/mathutil v1.4.1 // indirect 20 | modernc.org/memory v1.0.5 // indirect 21 | modernc.org/opt v0.1.1 // indirect 22 | modernc.org/strutil v1.1.1 // indirect 23 | modernc.org/token v1.0.0 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 2 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 3 | github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= 4 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 6 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 7 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 8 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 9 | github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= 10 | github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 11 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= 12 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 13 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 14 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 15 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 16 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 17 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 18 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 19 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 20 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 21 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 22 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 23 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 24 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 25 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 26 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20210902050250-f475640dd07b h1:S7hKs0Flbq0bbc9xgYt4stIEG1zNDFqyrPwAX2Wj/sE= 30 | golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 32 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 33 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 34 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 35 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs= 36 | golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 37 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 38 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 39 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 40 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 41 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 42 | lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= 43 | lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= 44 | modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 45 | modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 46 | modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 47 | modernc.org/cc/v3 v3.34.0 h1:dFhZc/HKR3qp92sYQxKRRaDMz+sr1bwcFD+m7LSCrAs= 48 | modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= 49 | modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= 50 | modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= 51 | modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= 52 | modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag= 53 | modernc.org/ccgo/v3 v3.11.2 h1:gqa8PQ2v7SjrhHCgxUO5dzoAJWSLAveJqZTNkPCN0kc= 54 | modernc.org/ccgo/v3 v3.11.2/go.mod h1:6kii3AptTDI+nUrM9RFBoIEUEisSWCbdczD9ZwQH2FE= 55 | modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= 56 | modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= 57 | modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= 58 | modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= 59 | modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= 60 | modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= 61 | modernc.org/libc v1.11.3 h1:q//spBhqp23lC/if8/o8hlyET57P8mCZqrqftzT2WmY= 62 | modernc.org/libc v1.11.3/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= 63 | modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 64 | modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 65 | modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 66 | modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= 67 | modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 68 | modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= 69 | modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= 70 | modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= 71 | modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= 72 | modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 73 | modernc.org/sqlite v1.13.1 h1:s/qk6VTTVyQIyhVNWa50whBBcI3+2oREbx85t227iOo= 74 | modernc.org/sqlite v1.13.1/go.mod h1:2qO/6jZJrcQaxFUHxOwa6Q6WfiGSsiVj6GXX0Ker+Jg= 75 | modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= 76 | modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= 77 | modernc.org/tcl v1.5.9 h1:DZMfR+RDJRhcrmMEMTJgVIX+Wf5qhfVX0llI0rsc20w= 78 | modernc.org/tcl v1.5.9/go.mod h1:bcwjvBJ2u0exY6K35eAmxXBBij5kXb1dHlAWmfhqThE= 79 | modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= 80 | modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 81 | modernc.org/z v1.1.2 h1:IjjzDsIFbl0wuF2KfwvdyUAJVwxD4iwZ6akLNiDoClM= 82 | modernc.org/z v1.1.2/go.mod h1:sj9T1AGBG0dm6SCVzldPOHWrif6XBpooJtbttMn1+Js= 83 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | 13 | "github.com/masakichi/tango/dict" 14 | "github.com/masakichi/tango/utils" 15 | _ "modernc.org/sqlite" 16 | ) 17 | 18 | var ( 19 | appName = "tango" 20 | version = "1.2.0-dev" 21 | gitCommit string 22 | buildDate string 23 | ) 24 | 25 | var ( 26 | dataDir = utils.GetDataDir(appName) 27 | databasePath = filepath.Join(dataDir, "tango.db") 28 | ) 29 | 30 | func initDatabase() *sql.DB { 31 | if _, err := os.Stat(dataDir); os.IsNotExist(err) { 32 | os.MkdirAll(dataDir, os.ModePerm) 33 | } 34 | db, err := sql.Open("sqlite", databasePath) 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | if _, err := os.Stat(databasePath); os.IsNotExist(err) { 39 | dict.InitDictDB(db) 40 | } 41 | return db 42 | } 43 | 44 | func main() { 45 | 46 | listFlag := flag.Bool("list", false, "list all dictionaries") 47 | importFlag := flag.String("import", "", "import yomichan's dictionary zip file") 48 | versionFlag := flag.Bool("version", false, "print tango app version") 49 | flag.Usage = func() { 50 | fmt.Fprintf(os.Stderr, "%s: A CLI Japanese Dictionary Tool\n", appName) 51 | fmt.Fprintf(os.Stderr, "Usage: %s %s\n\n", os.Args[0], "単語") 52 | fmt.Fprintf(os.Stderr, "Other Commands of %s:\n", os.Args[0]) 53 | flag.PrintDefaults() 54 | } 55 | 56 | flag.Parse() 57 | 58 | if len(os.Args) < 2 { 59 | flag.Usage() 60 | return 61 | } 62 | 63 | db := initDatabase() 64 | defer db.Close() 65 | 66 | if !strings.HasPrefix(os.Args[1], "-") { 67 | dict.PrintTerms(db) 68 | return 69 | } 70 | 71 | if *importFlag != "" { 72 | dict.ImportDictDB(db, *importFlag) 73 | return 74 | } 75 | 76 | if *listFlag { 77 | dict.PrintDicts(db) 78 | return 79 | } 80 | 81 | if *versionFlag { 82 | if gitCommit != "" { 83 | version += "-" + gitCommit 84 | } 85 | if buildDate == "" { 86 | buildDate = "unknown" 87 | } 88 | osArch := runtime.GOOS + "/" + runtime.GOARCH 89 | versionString := fmt.Sprintf("%s %s %s BuildDate=%s", 90 | appName, version, osArch, buildDate) 91 | fmt.Println(versionString) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /utils/xdg.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | func GetDataDir(name string) string { 9 | var basedir string 10 | if env := os.Getenv("XDG_DATA_HOME"); env != "" { 11 | basedir = env 12 | } else { 13 | basedir = filepath.Join(os.Getenv("HOME"), ".local", "share") 14 | } 15 | return filepath.Join(basedir, name) 16 | } 17 | --------------------------------------------------------------------------------