aboutsummaryrefslogtreecommitdiff
path: root/main.go
blob: 7433ab624935eec9d5b80bcea300b3defccbb528 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
// Phelpsify is a tool to help assign prayer codes to prayers.
// It requests prayers from bahaiprayers.net via the API url.
// It reads already assigned prayer codes from rel/code.list.
// It reads the conversion from number to language code from rel/lang.csv.
// It writes the new prayer codes to rel/code.list.
// rel/code.list is structured as prayer code, comma, prayer ids from bahaiprayers.net all separated by commas per line.
// rel/lang.csv is a csv file with header id,iso,iso_type,name,english,flag_link,rtl.
// The tool is a command line tool that first asks which languages you want to complete.
// It then presents you a random prayer from those languages that doesn't have
// a prayer code yet. It will then help you find the prayer among the prayers that already have a prayer code using keyword based search.
// When a match is found, the id of the prayer will be added to the list after the prayer code.
// The tool then asks you if you want to add another prayer and repeat the process.
package main

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"strconv"
	"strings"
)

//BPNAPI is the API link of bahaiprayers.net
//It is used to get the list of prayers per language by numerical id from lang.csv
var BPNAPI = "https://bahaiprayers.net/api/prayer/prayersystembylanguage?languageid="

type BPNKind string

type Language struct {
	Id       int
	Iso      string
	IsoType  string
	Name     string
	English  string
	FlagLink string
	Rtl      bool
}

//BPNAPIOutput is the JSON structure of the API output
type BPNAPIOutput struct {
	ErrorMessage string
	IsInError    bool
	Version      int
	Prayers      []Prayer
	Tags         []struct {
		Id          int
		LanguageId  int
		Name        string
		Kind        BPNKind
		PrayerCount int
	}
	TagRelations []struct {
		Id          int
		PrayerId    int
		PrayerTagId int
		LanguageId  int
	}
	Urls      []interface{}
	Languages []struct {
		Id            int
		Name          string
		English       string
		IsLeftToRight bool
		FlagLink      string
	}
}

type Prayer struct {
	Id         int
	AuthorId   int
	LanguageId int
	Text       string
	Tags       []struct {
		Id   int
		Name string
		Kind BPNKind
	}
	Tagkind struct {
		Kind BPNKind
	}
	Urls []interface{}
}

func (p Prayer) Author() string {
	if p.AuthorId > 0 && p.AuthorId < 4 {
		return []string{"Báb", "Bahá'u'lláh", "Abdu'l-Bahá"}[p.AuthorId-1]
	}
	return "Unknown Author"
}

type PrayerCode struct {
	Code     string
	Language string
}

var Languages []Language
var CodeList map[string][]int
var PrayersWithCode map[PrayerCode]Prayer

// ReadLangCSV reads rel/lang.csv and puts it in Languages
// It does so by matching each CSV field to a struct field
func ReadLangCSV() error {
	file, err := os.Open("rel/lang.csv")
	if err != nil {
		return err
	}
	defer file.Close()
	reader := csv.NewReader(file)
	langCSV, err := reader.ReadAll()
	if err != nil {
		return err
	}
	for _, lang := range langCSV {
		var language Language
		language.Id, _ = strconv.Atoi(lang[0])
		language.Iso = lang[1]
		language.IsoType = lang[2]
		language.Name = lang[3]
		language.English = lang[4]
		language.FlagLink = lang[5]
		language.Rtl, _ = strconv.ParseBool(lang[6])
		Languages = append(Languages, language)
	}
	return nil
}

func ReadCodeList() error {
	file, err := os.Open("rel/code.list")
	if err != nil {
		return err
	}
	defer file.Close()
	CodeList = make(map[string][]int)
	reader := csv.NewReader(file)
	for {
		line, err := reader.Read()
		if err != nil {
			break
		}
		CodeList[line[0]] = make([]int, 0)
		for _, prayerIDstr := range line[1:] {
			prayerID, err := strconv.Atoi(prayerIDstr)
			if err != nil {
				return err
			}
			CodeList[line[0]] = append(CodeList[line[0]], prayerID)
		}
	}
	return nil
}

func WriteCodeList(codeList map[string][]int) error {
	file, err := os.Create("rel/code.list")
	if err != nil {
		return err
	}
	defer file.Close()
	writer := csv.NewWriter(file)
	for code, prayerIDs := range codeList {
		line := make([]string, 0)
		line = append(line, code)
		for _, prayerID := range prayerIDs {
			line = append(line, strconv.Itoa(prayerID))
		}
		writer.Write(line)
	}
	writer.Flush()
	return nil
}

func AskLanguages() []Language {
	// Ask "Which languages do you want to complete?"
	// The answer is a list of language codes that must be converted to numbers
	// using the conversion from lang.csv in Languages
	fmt.Print("Which languages do you want to complete? ")
	var languages []string
	fmt.Scanln(&languages)
	var outLangs []Language
	for _, language := range languages {
		for _, lang := range Languages {
			if lang.Iso == language || lang.English == language {
				outLangs = append(outLangs, lang)
			}
		}
	}
	return outLangs
}

func Code(p Prayer) string {
	// Get the prayer code for a prayer via CodeList
	// or return an empty string if it's not on the list
	for code, prayerIDs := range CodeList {
		for _, prayerID := range prayerIDs {
			if p.Id == prayerID {
				return code
			}
		}
	}
	return ""
}

func ReadPrayers(lang []Language) []Prayer {
	var prayers []Prayer
	for _, language := range lang {
		response, err := http.Get(BPNAPI + strconv.Itoa(language.Id))
		if err != nil {
			fmt.Println(err)
			return nil
		}
		defer response.Body.Close()
		var output BPNAPIOutput
		err = json.NewDecoder(response.Body).Decode(&output)
		if err != nil {
			fmt.Println(err)
			return nil
		}
		for _, prayer := range output.Prayers {
			if Code(prayer) == "" {
				prayers = append(prayers, prayer)
			}
		}
	}
	return prayers
}

func main() {
	err := ReadLangCSV()
	if err != nil {
		panic(err)
	}
	err = ReadCodeList()
	if err != nil {
		panic(err)
	}
	// Iterate over all languages and read in all prayers
	// with a language code to PrayersWithCode
	for _, language := range Languages {
		prayers := ReadPrayers([]Language{language})
		for _, prayer := range prayers {
			code := Code(prayer)
			if code != "" {
				prayerCode := PrayerCode{code, language.Iso}
				PrayersWithCode[prayerCode] = prayer
			}
		}
	}
	for {
		languages := AskLanguages()
		prayers := ReadPrayers(languages)
		// randomize the order of the prayers
		for i := len(prayers) - 1; i > 0; i-- {
			j := rand.Intn(i + 1)
			prayers[i], prayers[j] = prayers[j], prayers[i]
		}
		// pick the first prayer from the resulting list that
		// doesn't have a code in CodeList.
		var prayer Prayer
		var code string
		for _, p := range prayers {
			if Code(p) == "" {
				prayer = p
				break
			}
		}
		// Present the text, id and author of the prayer
		fmt.Println(prayer.Text)
		fmt.Println("ID:", prayer.Id)
		fmt.Println("Author:", prayer.Author())
		for code == "" {
			// Ask for a keyword
			fmt.Print("Input a keyword for this prayer: ")
			var keyword string
			fmt.Scanln(&keyword)
			var Matches []Prayer
			// Check for the prayer text of each prayer in
			// PrayersWithCode if there is a match with the keyword
			// and add it to Matches
			for _, prayer := range PrayersWithCode {
				if strings.Contains(prayer.Text, keyword) {
					Matches = append(Matches, prayer)
				}
			}
			// If there are no matches, ask again
			if len(Matches) == 0 {
				fmt.Println("No matches found.")
				continue
			}
			// Ask which of the matches to use
			fmt.Println("Which of the following matches?")
			for i, match := range Matches {
				fmt.Println(i+1, ":", match.Text)
				fmt.Print("Does this match? (y/n) ")
				var answer string
				fmt.Scanln(&answer)
				if answer == "y" {
					prayer = match
					code = Code(match)
					break
				}
			}
		}
		// Add the code to CodeList
		CodeList[code] = append(CodeList[code], prayer.Id)
		// Ask if the user wants to identify another prayer
		// or if they want to quit
		// To identify another prayer, continue
		// To quit, save the CodeList and quit
		fmt.Print("Identify another prayer? (y/n) ")
		var answer string
		fmt.Scanln(&answer)
		if answer == "n" {
			err = WriteCodeList(CodeList)
			if err != nil {
				panic(err)
			}
			break
		}
	}
}