commit 400428b2328c12b4953c348522ebc07a26fa68cd
parent f46c7a21a14cf9490c7ad95cb301df1280515348
Author: Agastya Chandrakant <me@hanabi.in>
Date: Sun, 1 May 2022 13:42:16 +0530
Add a flag to decorate chapters, or verse ranges.
add bin to gitignore
Diffstat:
M | .gitignore | | | 2 | +- |
M | PLAN | | | 7 | ++++--- |
D | main.go | | | 346 | ------------------------------------------------------------------------------- |
M | makefile | | | 6 | +++++- |
A | src/main.go | | | 365 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 375 insertions(+), 351 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,2 @@
**/*.DS_Store
-quran
+bin/**/*
diff --git a/PLAN b/PLAN
@@ -5,7 +5,8 @@ Available commands
+ quran 2
+ quran 2:10
+ quran 2:10-11
-+ quran 2:10-11 -lang=ur
++ quran [-list-trans]
++ quran [-decorate] [-delay <num>] [-trans <num>] chap[:verse1[-verse2]]
Convention
==========
@@ -15,5 +16,5 @@ In the source code (even in the comments), "verse" is used in place of "aayat" s
Features
========
-+ Should I support 2:14- to print all verses from 14 till the end of the chapter?
-+ Add `quran about|details|list-translation|help'
++ Should I support 2:14- to print all verses from 14 till the end of the chapter? -- I have never seen anyone use it like that. And definitely not me -- we put end verse for a reason, for staying withing context, so not doing it as of now.
++ Add `quran about|details|list-translation|help' -- as sub-commands, xor flags? I love sub-commands, but haven't used it much (it has more code, perhaps -- let's find out!) -- Come on! -decorate is a flag, list-translation is *not* a flag, it is a sub-command, segregate accordingly!
diff --git a/main.go b/main.go
@@ -1,346 +0,0 @@
-package main
-
-import (
- "encoding/json"
- "flag"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "regexp"
- "sort"
- "strconv"
- "time"
-)
-
-const DEBUG = true
-const API = "https://api.quran.com/api/v4"
-
-func main() {
-
- input, trans, timeDelay, shouldListTrans := handleInputFlags()
-
- if shouldListTrans {
- if err := printTranslations(); err != nil {
- printerr(err)
- }
- } else {
-
- if err, chap, ver1, ver2 := parseInput(input); err != nil {
- printerr(err)
- } else if err, transAuthor := checkTrans(trans); err != nil {
- printerr(err)
- } else {
- if err, maxVerse := checkVerseRange(chap, ver1, ver2); err != nil {
- printerr(err)
- } else {
- if ver1 == 0 {
- // Print whole chapter, ie from 1-maxVerse
- for verse := t_verse(1); verse <= maxVerse; verse++ {
- printVerse(trans, chap, verse, timeDelay)
- }
- } else if ver1 < ver2 {
- // Print the range of verses, if from ver1 to ver2.
- // @TODO check if < or <= ?
- for verse := ver1; verse <= ver2; verse++ {
- printVerse(trans, chap, verse, timeDelay)
- }
- } else if ver1 == ver2 {
- // Print just one verse, if just ver 1.
- printVerse(trans, chap, ver1, timeDelay)
- }
- printRange(transAuthor, chap, ver1, ver2, maxVerse)
- }
- }
- }
-}
-
-func printRange(transAuthor string, chap t_chap, ver1, ver2, maxVerse t_verse) {
- res := fmt.Sprintf("\t--Qur'an %d:", chap)
- // if "1:0-0" || "1:3-3" || "1:3-7"
- if ver1 == 0 { // "1:0-0"
- res += fmt.Sprintf("%d-%d", 1, maxVerse)
- } else if ver1 == ver2 { // "1:3-3"
- res += fmt.Sprintf("%d", ver1)
- } else { // "1:3-7"
- res += fmt.Sprintf("%d:%d", ver1, ver2)
- }
-
- res += fmt.Sprintf(", %s translation.\n", transAuthor)
-
- fmt.Println(res)
-}
-
-func checkTrans(trans t_trans) (err error, transAuthor string) {
- var transList TranslationList
- if err = quranHttpGet(API+"/resources/translations", &transList); err != nil {
- return
- }
- for _, elem := range transList.Translations {
- if trans == t_trans(elem.ID) {
- transAuthor = elem.AuthorName
- return
- }
- }
- err = fmt.Errorf("%d is not a valid translation, please use `quran list-trans' to see available translations.", trans)
- return
-}
-
-type TranslationList struct {
- Translations []Translations `json:"translations"`
-}
-type TranslatedName struct {
- Name string `json:"name"`
- LanguageName string `json:"language_name"`
-}
-type Translations struct {
- ID int `json:"id"`
- Name string `json:"name"`
- AuthorName string `json:"author_name"`
- Slug string `json:"slug"`
- LanguageName string `json:"language_name"`
- TranslatedName TranslatedName `json:"translated_name"`
-}
-
-func printVerse(trans t_trans, chap t_chap, verse t_verse, timeDelay float64) {
- uri := fmt.Sprintf("%s/translations/%d/by_ayah/%d:%d", API, trans, chap, verse)
- var verseData Verse
- time.Sleep(time.Duration(timeDelay) * time.Second) // Rate-limit to minimise server load.
- if err := quranHttpGet(uri, &verseData); err != nil {
- printerr(err)
- } else {
- formattedAayat := formatAayat(verseData.Translations[0].Text)
- fmt.Println(formattedAayat)
- }
-}
-
-func formatAayat(aayat string) (formatAayat string) {
- reg := regexp.MustCompile(`<sup foot_note=\d+>\d+<\/sup>`)
- formatAayat = reg.ReplaceAllString(aayat, "")
- return
-}
-
-type Verse struct {
- Translations []struct {
- ID int `json:"id"`
- ResourceID int `json:"resource_id"`
- Text string `json:"text"`
- } `json:"translations"`
- Pagination struct {
- PerPage int `json:"per_page"`
- CurrentPage int `json:"current_page"`
- NextPage interface{} `json:"next_page"`
- TotalPages int `json:"total_pages"`
- TotalRecords int `json:"total_records"`
- } `json:"pagination"`
-}
-
-type t_trans int
-
-func checkVerseRange(chap t_chap, ver1, ver2 t_verse) (err error, maxVerse t_verse) {
- if err = checkChapRange(chap); err != nil {
- return
- }
- maxVerse, err = getChapMaxVerse(chap)
- if err != nil {
- return
- }
- if ver2 > maxVerse {
- err = fmt.Errorf("Verse for the chapter %d is out of range. That chapter has verses only till %d.\n", chap, maxVerse)
- return
- }
- return
-}
-
-func getChapMaxVerse(chap t_chap) (maxVerse t_verse, err error) {
- var chapDetails ChapterDetails
- uri := fmt.Sprintf("%s/chapters/%d", API, chap)
- err = quranHttpGet(uri, &chapDetails)
- maxVerse = t_verse(chapDetails.VersesCount)
- return
-}
-
-type ChapterDetails struct {
- Chapter `json:"chapter"`
-}
-
-type Chapter struct {
- ID int `json:"id"`
- RevelationPlace string `json:"revelation_place"`
- RevelationOrder int `json:"revelation_order"`
- BismillahPre bool `json:"bismillah_pre"`
- NameSimple string `json:"name_simple"`
- NameComplex string `json:"name_complex"`
- NameArabic string `json:"name_arabic"`
- VersesCount int `json:"verses_count"`
- Pages []int `json:"pages"`
- TranslatedName TranslatedName `json:"translated_name"`
-}
-
-type t_chap uint
-type t_verse uint
-
-func checkChapRange(chap t_chap) (err error) {
- maxChap, err := getMaxChap()
- if err != nil {
- return err
- }
-
- if chap < 1 || chap > maxChap {
- err = fmt.Errorf("Chapter is out of range.\n")
- }
- return
-}
-
-func quranHttpGet(endpoint string, st interface{}) (err error) {
-
- client := &http.Client{}
- req, err := http.NewRequest(http.MethodGet, endpoint, nil)
- if err != nil {
- return
- }
-
- req.Header.Set("User-Agent", "git.hanabi.in/repos/quran-go.git v2022-04-26")
-
- resp, err := client.Do(req)
- if err != nil {
- return
- }
-
- defer resp.Body.Close()
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return
- }
-
- err = json.Unmarshal(body, &st)
- if err != nil {
- return
- }
-
- return
-}
-
-func getMaxChap() (res t_chap, err error) {
- var chapList ChaptersList
- if err = quranHttpGet(API+"/chapters", &chapList); err != nil {
- return
- }
- chaps := chapList.Chapters
- res = t_chap(chaps[len(chaps)-1].ID)
- return
-}
-
-type ChaptersList struct {
- Chapters []Chapter `json:"chapters"`
-}
-
-func handleInputFlags() (input string, trans t_trans, timeDelay float64, shouldListTrans bool) {
- var transInt int
-
- flag.IntVar(&transInt, "trans", 131, "Which translation to use.")
- flag.Float64Var(&timeDelay, "delay", 0.2, "Minimum delay in seconds between fetching of two aayah.")
- flag.BoolVar(&shouldListTrans, "list-trans", false, "Print list of available translations.")
-
- flag.Parse()
-
- input = flag.Arg(0)
- trans = t_trans(transInt)
- return
-}
-
-func printHelp() {
- // @TODO
- debug("printing help")
-}
-
-func printTranslations() (err error) {
-
- var transList TranslationList
- if err := quranHttpGet(API+"/resources/translations", &transList); err != nil {
- return err
- }
-
- trans := transList.Translations
- sort.Slice(trans, func(a, b int) bool {
- return trans[a].ID < trans[b].ID
- })
-
- fmt.Println("Available translations:\n")
-
- for _, elem := range transList.Translations {
- fmt.Printf("%d\t(%s)\t\t%s\n", elem.ID, elem.LanguageName, elem.Name)
- }
-
- return err
-
-}
-
-func parseInput(input string) (err error, chapter t_chap, verse1, verse2 t_verse) {
-
- if len(os.Args) == 1 {
- printHelp()
- os.Exit(0)
- }
-
- // Group 1, 3, 5 = Chapter:Verse1-Verse2
- re := regexp.MustCompile(`^(\d)+(:(\d+)(-(\d+))?)?$`)
- isValid := re.Match([]byte(input))
- if !isValid {
- err = fmt.Errorf("Expected in the form `quran [-lang=code] chapter[:verse1[-verse2]]'\n")
- return
- }
-
- res := re.FindAllStringSubmatch(input, -1)
-
- ch, err := strconv.ParseUint(res[0][1], 10, 0)
- if err != nil {
- return
- }
- chapter = t_chap(ch)
-
- if res[0][3] == res[0][5] && len(res[0][3]) > 0 {
- err = fmt.Errorf("Verse 2 should not be same as Verse 1.\n")
- return
- }
-
- if res[0][3] == "" {
- res[0][3] = "0"
- }
- ve1, err := strconv.ParseUint(res[0][3], 10, 0)
- verse1 = t_verse(ve1)
-
- if err != nil {
- return
- }
-
- if res[0][5] == "" {
- res[0][5] = res[0][3]
- }
-
- ve2, err := strconv.ParseUint(res[0][5], 10, 0)
- verse2 = t_verse(ve2)
-
- if err != nil {
- return
- }
-
- if verse2 < verse1 {
- err = fmt.Errorf("Verse 2 must be bigger than Verse 1.\n")
- }
-
- return
-}
-
-func printerr(err error) {
- fmt.Fprint(os.Stderr, err.Error()+"\n")
- os.Exit(1)
-}
-
-func debug(s interface{}) {
- if DEBUG {
- fmt.Print("[DEBUG]: ")
- fmt.Println(s)
- }
-}
diff --git a/makefile b/makefile
@@ -1,2 +1,6 @@
build:
- go run -o quran main.go
+ mkdir -p bin && go build -o bin/quran src/main.go
+clean:
+ rm -rf bin
+install:
+ make build && mv bin/quran /usr/local/bin && make clean
diff --git a/src/main.go b/src/main.go
@@ -0,0 +1,365 @@
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "regexp"
+ "sort"
+ "strconv"
+ "time"
+)
+
+const DEBUG = true
+const API = "https://api.quran.com/api/v4"
+
+func main() {
+
+ input, trans, timeDelay, shouldListTrans, shouldDecorate := handleInputFlags()
+
+ if shouldListTrans {
+ if err := printTranslations(); err != nil {
+ printerr(err)
+ }
+ } else {
+
+ if err, chap, ver1, ver2 := parseInput(input); err != nil {
+ printerr(err)
+ } else if err, transAuthor := checkTrans(trans); err != nil {
+ printerr(err)
+ } else {
+ if err, maxVerse := checkVerseRange(chap, ver1, ver2); err != nil {
+ printerr(err)
+ } else {
+ if ver1 == 0 {
+ // Print whole chapter, ie from 1-maxVerse
+ decorateChapterHead(shouldDecorate, chap)
+ for verse := t_verse(1); verse <= maxVerse; verse++ {
+ printVerse(trans, chap, verse, timeDelay)
+ }
+ } else if ver1 < ver2 {
+ // Print the range of verses, if from ver1 to ver2.
+ // @TODO check if < or <= ?
+ for verse := ver1; verse <= ver2; verse++ {
+ printVerse(trans, chap, verse, timeDelay)
+ }
+ } else if ver1 == ver2 {
+ // Print just one verse, if just ver 1.
+ printVerse(trans, chap, ver1, timeDelay)
+ }
+ decorateEnd(shouldDecorate, transAuthor, chap, ver1, ver2, maxVerse)
+ }
+ }
+ }
+}
+
+func decorateChapterHead(shouldDecorate bool, chap t_chap) (err error) {
+ if shouldDecorate {
+ if chapDetails, err := getChapDetails(chap); err != nil {
+ return err
+ } else {
+ fmt.Printf("\tChapter %d (%s)\n\n", chapDetails.ID, chapDetails.NameSimple)
+ }
+ }
+ return err
+}
+
+func decorateEnd(shouldDecorate bool, transAuthor string, chap t_chap, ver1, ver2, maxVerse t_verse) {
+ if shouldDecorate {
+ printRange(transAuthor, chap, ver1, ver2, maxVerse)
+ }
+}
+
+func printRange(transAuthor string, chap t_chap, ver1, ver2, maxVerse t_verse) {
+ res := fmt.Sprintf("\t--Qur'an %d:", chap)
+ // if "1:0-0" || "1:3-3" || "1:3-7"
+ if ver1 == 0 { // "1:0-0"
+ res += fmt.Sprintf("%d-%d", 1, maxVerse)
+ } else if ver1 == ver2 { // "1:3-3"
+ res += fmt.Sprintf("%d", ver1)
+ } else { // "1:3-7"
+ res += fmt.Sprintf("%d:%d", ver1, ver2)
+ }
+
+ res += fmt.Sprintf(", %s translation.\n", transAuthor)
+
+ fmt.Println(res)
+}
+
+func checkTrans(trans t_trans) (err error, transAuthor string) {
+ var transList TranslationList
+ if err = quranHttpGet(API+"/resources/translations", &transList); err != nil {
+ return
+ }
+ for _, elem := range transList.Translations {
+ if trans == t_trans(elem.ID) {
+ transAuthor = elem.AuthorName
+ return
+ }
+ }
+ err = fmt.Errorf("%d is not a valid translation, please use `quran list-trans' to see available translations.", trans)
+ return
+}
+
+type TranslationList struct {
+ Translations []Translations `json:"translations"`
+}
+type TranslatedName struct {
+ Name string `json:"name"`
+ LanguageName string `json:"language_name"`
+}
+type Translations struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ AuthorName string `json:"author_name"`
+ Slug string `json:"slug"`
+ LanguageName string `json:"language_name"`
+ TranslatedName TranslatedName `json:"translated_name"`
+}
+
+func printVerse(trans t_trans, chap t_chap, verse t_verse, timeDelay float64) {
+ uri := fmt.Sprintf("%s/translations/%d/by_ayah/%d:%d", API, trans, chap, verse)
+ var verseData Verse
+ time.Sleep(time.Duration(timeDelay) * time.Second) // Rate-limit to minimise server load.
+ if err := quranHttpGet(uri, &verseData); err != nil {
+ printerr(err)
+ } else {
+ formattedAayat := formatAayat(verseData.Translations[0].Text)
+ fmt.Println(formattedAayat)
+ }
+}
+
+func formatAayat(aayat string) (formattedAayat string) {
+ reg := regexp.MustCompile(`<sup foot_note=\d+>\d+<\/sup>`)
+ formattedAayat = reg.ReplaceAllString(aayat, "")
+ return
+}
+
+type Verse struct {
+ Translations []struct {
+ ID int `json:"id"`
+ ResourceID int `json:"resource_id"`
+ Text string `json:"text"`
+ } `json:"translations"`
+ Pagination struct {
+ PerPage int `json:"per_page"`
+ CurrentPage int `json:"current_page"`
+ NextPage interface{} `json:"next_page"`
+ TotalPages int `json:"total_pages"`
+ TotalRecords int `json:"total_records"`
+ } `json:"pagination"`
+}
+
+type t_trans int
+
+func checkVerseRange(chap t_chap, ver1, ver2 t_verse) (err error, maxVerse t_verse) {
+ if err = checkChapRange(chap); err != nil {
+ return
+ }
+ chapDetails, err := getChapDetails(chap)
+ maxVerse = t_verse(chapDetails.VersesCount)
+ if err != nil {
+ return
+ }
+ if ver2 > maxVerse {
+ err = fmt.Errorf("Verse for the chapter %d is out of range. That chapter has verses only till %d.\n", chap, maxVerse)
+ return
+ }
+ return
+}
+
+func getChapDetails(chap t_chap) (chapDetails ChapterDetails, err error) {
+
+ uri := fmt.Sprintf("%s/chapters/%d", API, chap)
+ err = quranHttpGet(uri, &chapDetails)
+ return
+}
+
+type ChapterDetails struct {
+ Chapter `json:"chapter"`
+}
+
+type Chapter struct {
+ ID int `json:"id"`
+ RevelationPlace string `json:"revelation_place"`
+ RevelationOrder int `json:"revelation_order"`
+ BismillahPre bool `json:"bismillah_pre"`
+ NameSimple string `json:"name_simple"`
+ NameComplex string `json:"name_complex"`
+ NameArabic string `json:"name_arabic"`
+ VersesCount int `json:"verses_count"`
+ Pages []int `json:"pages"`
+ TranslatedName TranslatedName `json:"translated_name"`
+}
+
+type t_chap uint
+type t_verse uint
+
+func checkChapRange(chap t_chap) (err error) {
+ maxChap, err := getMaxChap()
+ if err != nil {
+ return err
+ }
+
+ if chap < 1 || chap > maxChap {
+ err = fmt.Errorf("Chapter is out of range.\n")
+ }
+ return
+}
+
+func quranHttpGet(endpoint string, st interface{}) (err error) {
+
+ client := &http.Client{}
+ req, err := http.NewRequest(http.MethodGet, endpoint, nil)
+ if err != nil {
+ return
+ }
+
+ req.Header.Set("User-Agent", "git.hanabi.in/repos/quran-go.git v2022-04-26")
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return
+ }
+
+ defer resp.Body.Close()
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+
+ err = json.Unmarshal(body, &st)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func getMaxChap() (res t_chap, err error) {
+ var chapList ChaptersList
+ if err = quranHttpGet(API+"/chapters", &chapList); err != nil {
+ return
+ }
+ chaps := chapList.Chapters
+ res = t_chap(chaps[len(chaps)-1].ID)
+ return
+}
+
+type ChaptersList struct {
+ Chapters []Chapter `json:"chapters"`
+}
+
+func handleInputFlags() (input string, trans t_trans, timeDelay float64, shouldListTrans, shouldDecorate bool) {
+ var transInt int
+
+ flag.IntVar(&transInt, "trans", 131, "Which translation to use.")
+ flag.Float64Var(&timeDelay, "delay", 0.2, "Minimum delay in seconds between fetching of two aayah.")
+ flag.BoolVar(&shouldListTrans, "list-trans", false, "Print list of available translations.")
+ flag.BoolVar(&shouldDecorate, "decorate", false, "Print list of available translations.")
+
+ flag.Parse()
+
+ input = flag.Arg(0)
+ trans = t_trans(transInt)
+ return
+}
+
+func printHelp() {
+ // @TODO
+ debug("printing help")
+}
+
+func printTranslations() (err error) {
+
+ var transList TranslationList
+ if err := quranHttpGet(API+"/resources/translations", &transList); err != nil {
+ return err
+ }
+
+ trans := transList.Translations
+ sort.Slice(trans, func(a, b int) bool {
+ return trans[a].ID < trans[b].ID
+ })
+
+ fmt.Println("Available translations:\n")
+
+ for _, elem := range transList.Translations {
+ fmt.Printf("%d\t(%s)\t\t%s\n", elem.ID, elem.LanguageName, elem.Name)
+ }
+
+ return err
+
+}
+
+func parseInput(input string) (err error, chapter t_chap, verse1, verse2 t_verse) {
+
+ if len(os.Args) == 1 {
+ printHelp()
+ os.Exit(0)
+ }
+
+ // Group 1, 3, 5 = Chapter:Verse1-Verse2
+ re := regexp.MustCompile(`^(\d)+(:(\d+)(-(\d+))?)?$`)
+ isValid := re.Match([]byte(input))
+ if !isValid {
+ err = fmt.Errorf("Expected in the form `quran [-lang=code] chapter[:verse1[-verse2]]'\n")
+ return
+ }
+
+ res := re.FindAllStringSubmatch(input, -1)
+
+ ch, err := strconv.ParseUint(res[0][1], 10, 0)
+ if err != nil {
+ return
+ }
+ chapter = t_chap(ch)
+
+ if res[0][3] == res[0][5] && len(res[0][3]) > 0 {
+ err = fmt.Errorf("Verse 2 should not be same as Verse 1.\n")
+ return
+ }
+
+ if res[0][3] == "" {
+ res[0][3] = "0"
+ }
+ ve1, err := strconv.ParseUint(res[0][3], 10, 0)
+ verse1 = t_verse(ve1)
+
+ if err != nil {
+ return
+ }
+
+ if res[0][5] == "" {
+ res[0][5] = res[0][3]
+ }
+
+ ve2, err := strconv.ParseUint(res[0][5], 10, 0)
+ verse2 = t_verse(ve2)
+
+ if err != nil {
+ return
+ }
+
+ if verse2 < verse1 {
+ err = fmt.Errorf("Verse 2 must be bigger than Verse 1.\n")
+ }
+
+ return
+}
+
+func printerr(err error) {
+ fmt.Fprint(os.Stderr, err.Error()+"\n")
+ os.Exit(1)
+}
+
+func debug(s interface{}) {
+ if DEBUG {
+ fmt.Print("[DEBUG]: ")
+ fmt.Println(s)
+ }
+}