├── .gitignore ├── .gitmodules ├── bin └── update-wordlist ├── go.mod ├── go.sum ├── README.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | results 2 | output/ 3 | gonetia -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wordlists"] 2 | path = wordlists 3 | url = https://github.com/ashelkovnykov/urbit-wordlists 4 | -------------------------------------------------------------------------------- /bin/update-wordlist: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # This script updates the wordlist from ashelkovnykov/urbit-wordlists 3 | 4 | git submodule foreach git pull origin master 5 | 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module tomholford/gonetia 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/deelawn/urbit-gob v0.1.0 7 | github.com/manifoldco/promptui v0.9.0 8 | ) 9 | 10 | require ( 11 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect 12 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 2 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 3 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 5 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 6 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/deelawn/urbit-gob v0.1.0 h1:ByFIcHcQCE4owZiqEqKgnoC50PPHmkEBTwa22MUysnk= 10 | github.com/deelawn/urbit-gob v0.1.0/go.mod h1:NVvd94ZeWIrOjIF4eiM7sksbFvAyXIw9UVdF1xjlX0U= 11 | github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= 12 | github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 17 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 18 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= 19 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 22 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gonetia 2 | 3 | Gonetia is a simple command-line utility for generating a list of planet names 4 | issuable from an Urbit star. It is inspired by 5 | [Venetia](https://github.com/tylershuster/venetia), but written in Go instead 6 | of JS. 7 | 8 | ## Preview 9 | 10 | https://user-images.githubusercontent.com/16504501/190836787-212afe87-b2f9-4352-a552-0cadff6b1338.mp4 11 | 12 | ## Setup 13 | 14 | ### Prerequisites 15 | 16 | This project requires Go 1.18+. One option is to use [gvm](https://github.com/moovweb/gvm). Alternatively, on MacOS it is simple to install Go with [homebrew](https://brew.sh/). 17 | 18 | Once `brew` is installed, install Go like so: 19 | ``` 20 | brew install go 21 | ``` 22 | ### Instructions 23 | 24 | 1. Clone this repo 25 | 2. `git submodule update --init --recursive` 26 | 3. `go build` 27 | 28 | ## Usage 29 | 30 | Enter a star in patp format (e.g., `~marzod`) either as a command line argument, or in response to the interactive prompt. 31 | 32 | ### Command Line Argument 33 | 34 | ``` 35 | ./gonetia "~marzod" 36 | ``` 37 | 38 | ### Interactive Mode 39 | 40 | ```sh 41 | ./gonetia 42 | 43 | # Enter a star in patp format 44 | >Which star? (e.g., ~marzod): 45 | 46 | ~marzod 47 | ``` 48 | 49 | ### Output 50 | 51 | A [planet](https://developers.urbit.org/reference/glossary/planet) identity is a four-syllable name composed of two six-character segments, such as `~sampel-palnet`. 52 | 53 | The script uses [urbit-wordlists](https://github.com/ashelkovnykov/urbit-wordlists) and various strategies to generate output: 54 | 55 | - *AnyEnglish*: at least one segment matches `wordlists/name/english-single` or `wordlists/name/english-double`. 56 | - *OnlyEnglish*: both segments match `wordlists/name/english-single` or `wordlists/name/english-double`. 57 | - *AnyApprox*: at least one segment matches `wordlists/name/approx-single` or `wordlists/name/approx-double`. 58 | - *OnlyApprox*: both segments match any of the wordlists. 59 | - *Doubles*: both segments are identical (e.g., ~datnut-datnut) 60 | 61 | 62 | Output for each is written to `./output/[star]/[strategy]_planets.txt`, for 5 total files per run. 63 | 64 | ## Special Thanks 65 | 66 | - Thanks to @tylershuster for creating [Venetia](https://github.com/tylershuster/venetia) 67 | - Thanks to @deelawn for creating [urbit-gob](https://github.com/deelawn/urbit-gob) 68 | - Thanks to @ashelkovnykov for creating [urbit-wordlists](https://github.com/ashelkovnykov/urbit-wordlists) 69 | - Thanks to @michelleylai for feature ideas, feedback, testing, and proofreading docs 70 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "github.com/deelawn/urbit-gob/co" 8 | "github.com/manifoldco/promptui" 9 | "io/ioutil" 10 | "os" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | // available strategies 17 | type Strategy int 18 | 19 | const ( 20 | All Strategy = iota 21 | AnyApprox 22 | AnyEnglish 23 | OnlyApprox 24 | OnlyEnglish 25 | Doubles 26 | ) 27 | 28 | type StrategyPrompt struct { 29 | name string 30 | strategy Strategy 31 | } 32 | 33 | var strategyPrompts = []StrategyPrompt{ 34 | { 35 | "All", All, 36 | }, 37 | { 38 | "Any English or Slang", AnyApprox, 39 | }, 40 | { 41 | "Any English", AnyEnglish, 42 | }, 43 | { 44 | "Only English or Slang", OnlyApprox, 45 | }, 46 | { 47 | "Only English", OnlyEnglish, 48 | }, 49 | { 50 | "Doubles", Doubles, 51 | }, 52 | } 53 | 54 | // generates an English word map 55 | func generateWords(fileName string) map[string]bool { 56 | file, err := os.Open(fileName) 57 | if err != nil { 58 | fmt.Println(err) 59 | } 60 | defer file.Close() 61 | 62 | var words = make(map[string]bool) 63 | 64 | scanner := bufio.NewScanner(file) 65 | for scanner.Scan() { 66 | words[scanner.Text()] = true 67 | } 68 | 69 | if err := scanner.Err(); err != nil { 70 | fmt.Println(err) 71 | } 72 | 73 | return words 74 | } 75 | 76 | // loaded words globals 77 | var singleEnglishWords = generateWords("./wordlists/name/english-single.txt") 78 | var doubleEnglishWords = generateWords("./wordlists/name/english-double.txt") 79 | var singleApproxWords = generateWords("./wordlists/name/approx-single.txt") 80 | var doubleApproxWords = generateWords("./wordlists/name/approx-double.txt") 81 | 82 | // strategy filters 83 | func matchApprox(phoneme string) bool { 84 | return singleApproxWords[phoneme] || doubleApproxWords[phoneme] 85 | } 86 | 87 | func matchEnglish(phoneme string) bool { 88 | return singleEnglishWords[phoneme] || doubleEnglishWords[phoneme] 89 | } 90 | 91 | func anyApprox(planet string) bool { 92 | deSigged := strings.Replace(planet, "~", "", 1) 93 | parts := strings.Split(deSigged, "-") 94 | for i := 0; i < len(parts); i++ { 95 | part := parts[i] 96 | 97 | if matchApprox(part) { 98 | return true 99 | } 100 | } 101 | 102 | return false 103 | } 104 | 105 | func anyEnglish(planet string) bool { 106 | deSigged := strings.Replace(planet, "~", "", 1) 107 | parts := strings.Split(deSigged, "-") 108 | for i := 0; i < len(parts); i++ { 109 | part := parts[i] 110 | 111 | if matchEnglish(part) { 112 | return true 113 | } 114 | } 115 | 116 | return false 117 | } 118 | 119 | func onlyApprox(planet string) bool { 120 | deSigged := strings.Replace(planet, "~", "", 1) 121 | parts := strings.Split(deSigged, "-") 122 | 123 | return (matchApprox(parts[0]) || matchEnglish(parts[0])) && (matchApprox(parts[1]) || matchEnglish(parts[1])) 124 | } 125 | 126 | func onlyEnglish(planet string) bool { 127 | deSigged := strings.Replace(planet, "~", "", 1) 128 | parts := strings.Split(deSigged, "-") 129 | 130 | return matchEnglish(parts[0]) && matchEnglish(parts[1]) 131 | } 132 | 133 | func doubles(planet string) bool { 134 | deSigged := strings.Replace(planet, "~", "", 1) 135 | parts := strings.Split(deSigged, "-") 136 | 137 | return parts[0] == parts[1] 138 | } 139 | 140 | // generates list of planets under parent 141 | func makePlanets(parent string) []string { 142 | fmt.Sprintf("Making planet list for %s ...\n", parent) 143 | 144 | hex, err := co.Patq2Hex(parent) 145 | if err != nil { 146 | fmt.Println(err) 147 | } 148 | 149 | var planets = make([]string, 0) 150 | 151 | for i := 1; i <= 0xFFFF; i++ { 152 | base := strconv.FormatInt(int64(i), 16) 153 | s := fmt.Sprintf("%04s", base) + hex 154 | p, err := co.Hex2Patp(s) 155 | if err != nil { 156 | fmt.Println(err) 157 | } 158 | 159 | planets = append(planets, p) 160 | } 161 | 162 | sort.Slice(planets, func(i, j int) bool { 163 | return planets[i] < planets[j] 164 | }) 165 | 166 | return planets 167 | } 168 | 169 | func filterPlanets(planets []string, strategy Strategy) []string { 170 | var output = make([]string, 0) 171 | 172 | for i := 1; i < len(planets); i++ { 173 | p := planets[i] 174 | 175 | switch strategy { 176 | case AnyApprox: 177 | if anyApprox(p) { 178 | output = append(output, p) 179 | // doesn't check english, so only continue if match 180 | continue 181 | } 182 | 183 | case AnyEnglish: 184 | if anyEnglish(p) { 185 | output = append(output, p) 186 | } 187 | continue 188 | 189 | case OnlyApprox: 190 | if onlyApprox(p) { 191 | output = append(output, p) 192 | } 193 | continue 194 | 195 | case OnlyEnglish: 196 | if onlyEnglish(p) { 197 | output = append(output, p) 198 | } 199 | continue 200 | 201 | case Doubles: 202 | if doubles(p) { 203 | output = append(output, p) 204 | } 205 | continue 206 | 207 | default: 208 | output = append(output, p) 209 | } 210 | } 211 | 212 | return output 213 | } 214 | 215 | // input validation 216 | func validate(input string) error { 217 | // ensure valid patp 218 | isValid := co.IsValidPat(input) 219 | if !isValid { 220 | return errors.New("Invalid patp") 221 | } 222 | 223 | // ensure valid size 224 | size, err := co.Clan(input) 225 | if size != "star" || err != nil { 226 | return errors.New("Must be a star") 227 | } 228 | 229 | return nil 230 | } 231 | 232 | func writeResults(parent string, strategy string, results []string) error { 233 | fmt.Sprintf("Writing output for %s ...\n", strategy) 234 | 235 | pbytes := strings.Join(results, "\n") 236 | err := ioutil.WriteFile(fmt.Sprintf("./output/%s/%s_planets.txt", parent, strategy), []byte(pbytes), 0755) 237 | 238 | if err != nil { 239 | fmt.Println(err) 240 | } 241 | 242 | return nil 243 | } 244 | 245 | func main() { 246 | var parent string 247 | 248 | // get input from args... 249 | argLength := len(os.Args[1:]) 250 | if argLength > 0 { 251 | arg := os.Args[1] 252 | aErr := validate(arg) 253 | if aErr == nil { 254 | parent = arg 255 | } 256 | } 257 | 258 | // ... or prompt for parent 259 | if parent == "" { 260 | parentPrompt := promptui.Prompt{ 261 | Label: "Which star? (e.g., ~marzod)", 262 | Validate: validate, 263 | } 264 | 265 | var pErr error 266 | parent, pErr = parentPrompt.Run() 267 | 268 | if pErr != nil { 269 | fmt.Printf("bad input: %v\n", pErr) 270 | return 271 | } 272 | } 273 | 274 | // find planets 275 | planets := makePlanets(parent) 276 | 277 | // filter for each strategy 278 | anyApproxPlanets := filterPlanets(planets, AnyApprox) 279 | onlyApproxPlanets := filterPlanets(planets, OnlyApprox) 280 | anyEnglishPlanets := filterPlanets(planets, AnyEnglish) 281 | onlyEnglishPlanets := filterPlanets(planets, OnlyEnglish) 282 | doublesPlanets := filterPlanets(planets, Doubles) 283 | 284 | // write output 285 | _, oErr := os.Stat("output") 286 | if os.IsNotExist(oErr) { 287 | os.Mkdir("./output", 0755) 288 | } 289 | deSiggedParent := strings.Replace(parent, "~", "", 1) 290 | e := os.Mkdir(fmt.Sprintf("./output/%s", deSiggedParent), 0755) 291 | if e != nil { 292 | fmt.Println(e) 293 | } 294 | 295 | writeResults(deSiggedParent, "any_approx", anyApproxPlanets) 296 | writeResults(deSiggedParent, "only_approx", onlyApproxPlanets) 297 | writeResults(deSiggedParent, "any_english", anyEnglishPlanets) 298 | writeResults(deSiggedParent, "only_english", onlyEnglishPlanets) 299 | writeResults(deSiggedParent, "doubles", doublesPlanets) 300 | 301 | fmt.Println("Done :)") 302 | } 303 | --------------------------------------------------------------------------------