quran-go

Read Qur'an right in the terminal.
git clone http://git.hanabi.in/repos/quran-go.git
Log | Files | Refs | README | LICENSE

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+-
MPLAN | 7++++---
Dmain.go | 346-------------------------------------------------------------------------------
Mmakefile | 6+++++-
Asrc/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) + } +}