commit 2558e37fc5be4904a963cea119bb6c836217c27b
parent 400428b2328c12b4953c348522ebc07a26fa68cd
Author: Agastya Chandrakant <me@hanabi.in>
Date: Mon, 2 May 2022 03:40:49 +0530
Refactored everything, added README. Use semver.
Diffstat:
29 files changed, 806 insertions(+), 375 deletions(-)
diff --git a/PLAN b/PLAN
@@ -1,20 +0,0 @@
-Available commands
-==================
-
-+ quran
-+ quran 2
-+ quran 2:10
-+ quran 2:10-11
-+ quran [-list-trans]
-+ quran [-decorate] [-delay <num>] [-trans <num>] chap[:verse1[-verse2]]
-
-Convention
-==========
-
-In the source code (even in the comments), "verse" is used in place of "aayat" since the JSON response of quran.com API says verse. So staying consistent with it.
-
-Features
-========
-
-+ 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/README b/README
@@ -0,0 +1,90 @@
+ __ _ _ _ _ __ __ _ _ __
+ / _` | | | | '__/ _` | '_ \
+| (_| | |_| | | | (_| | | | |
+ \__, |\__,_|_| \__,_|_| |_|
+ |_|
+=============================
+
+
+quran fetches various quran translations right in the terminal.
+This is a successor of now dysfunct quran.py, but is not bug-compatible.
+
+Build and run
+=============
+Assuming you have the golang compiler, run the following command:
+
+$ git clone https://git.hanabi.in/reops/quran-go.git
+$ cd quran-go
+$ make # or `go build -o quran src/main.go`
+$ ./quran help
+
+I have moved the binary to /usr/local/bin so I can play directly -- depending on
+your $PATH, consider moving the binary there.
+Alternatively, run `make install`.
+
+Usage
+=====
+
+quran [-delay[=<millisecs>]] [-decorate] [-trans[=<translation-id>]] <command> [<args>]
+
+Flags
+-----
+ -decorate:
+ Specify if the chapters and verses should be decorated xor not. Defaults to false.
+ -delay <delay-ms>:
+ Specify in ms, the delay between fetching aayahs. I advice using at least
+ 500. Defaults to 1000.
+ -trans <translation-id>
+ Select which translation to use. Defaults to 131.
+
+Sub-commands
+------------
++ about: Shows description of the binary.
++ details: Shows details about where the verses are fetchef from.
++ version: Prints version of the software.
++ ls-translations: List all available translations.
+
+Arguments
+---------
+Argument is of the type <chapter[:verse1[-verse2]]>
+
++ `quran 1' will print the first chapter.
++ `quran 1:2' will print the chapter 1, verse 2.
++ `quran 1:3-5' will print verses of chapter 1 from 3 to 5.
+
+Source code
+===========
+The source code uses git for version control.
+
+Get the source code by running:
+$ git clone http://git.hanabi.in/repos/quran-go.git
+
+The source code has three branches important dev, prod and master.
++ dev: new features are added committed to dev.
++ prod: branch refers to the code ready to be used for production.
++ master -- git.hanabi.in makes use of stagit
+ <https://codemadness.org/stagit.html>. Stagit requires a master branch to
+ generate web-front-end, and hence, the branch exists.
+
+Additionally, the source code also has few tags.
++ One tag is called quran.py -- in fond memory of the initial script, which
+ holds special place in my memory.
++ Other tags being semantic versioning (semver) of the software release.
+
+I don't plan to overly complicate the software, please mind UNIX philosophy
+before requesting features.
+
+Please email patches to <me+git@hanabi.in>.
+
+Authors
+=======
++ acagastya (Implementation of the stuff -- which anyone can do.)
+
+`git blame` should suffice.
+
+License
+=======
+
+This source code is licened under the GNU AGPLv3 license. Read the LICENSE file
+to see what you can do with it, or read
+<https://www.gnu.org/licenses/agpl-3.0.en.html>
diff --git a/go.mod b/go.mod
@@ -0,0 +1,3 @@
+module git.hanabi.in/gitbox/quran-go
+
+go 1.18
diff --git a/src/cliinput/parse-flags.go b/src/cliinput/parse-flags.go
@@ -0,0 +1,31 @@
+package cliinput
+
+import (
+ "flag"
+
+ q "git.hanabi.in/gitbox/quran-go/src/quran-com"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Supply runtime input.
+func ParseFlags() (chapterVerseRange string, translationID t.Trans, delay t.Delay, shouldDecorate bool) {
+
+ var transInt int
+ var delayUint float64
+
+ flag.IntVar(&transInt, "trans", q.DefaultTrans, "Specify translation ID of which translation to show. Defaults to 131, corresponding to Dr. Mustafa Khattab, the Clear Quran.")
+
+ flag.Float64Var(&delayUint, "delay", q.DefaultDelay, "Minimum delay in milliseconds between fetching aayahs. Defaults to 1000.")
+
+ flag.BoolVar(&shouldDecorate, "decorate", q.DefaultDecorate, "Specify if the chapters, or verses should be decorated. Defaults to false.")
+
+ flag.Parse()
+
+ chapterVerseRange = flag.Arg(0)
+
+ translationID = t.Trans(transInt)
+ delay = t.Delay(delayUint)
+
+ return chapterVerseRange, translationID, delay, shouldDecorate
+
+}
diff --git a/src/display/decorate/chapter-head.go b/src/display/decorate/chapter-head.go
@@ -0,0 +1,22 @@
+package decorate
+
+import (
+ "fmt"
+
+ f "git.hanabi.in/gitbox/quran-go/src/fetch"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Decorate Chapter Head like "Chapter 1 (Al-Fātiĥah)"
+func ChapterHead(chapterID t.Chap) (err error) {
+
+ chapDetails, err := f.ChapterDetails(chapterID)
+ if err != nil {
+ return err
+ }
+
+ fmt.Printf("\n\tChapter %d (%s)\n\n", chapDetails.ID, chapDetails.NameComplex)
+
+ return
+
+}
diff --git a/src/display/decorate/end.go b/src/display/decorate/end.go
@@ -0,0 +1,25 @@
+package decorate
+
+import (
+ "fmt"
+
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Decorate end of printed verses like:
+// "--Qur'an 1:1-7"
+// "--Qur'an 1:2"
+// "--Qur'an 1:2-5"
+func End(transAuthor string, chapterID t.Chap, verse1, verse2 t.Verse) {
+
+ res := fmt.Sprintf("\n\t--Qur'an %d:%d", chapterID, verse1)
+
+ if verse1 < verse2 { // "1:3-3"
+ res += fmt.Sprintf("-%d", verse2)
+ }
+
+ res += fmt.Sprintf(", %s translation.\n", transAuthor)
+
+ fmt.Println(res)
+
+}
diff --git a/src/display/translations.go b/src/display/translations.go
@@ -0,0 +1,26 @@
+package display
+
+import (
+ "fmt"
+
+ f "git.hanabi.in/gitbox/quran-go/src/fetch"
+)
+
+// Print a list of all available translations from quran.com, sorted ASC by their ID.
+func Translations() (err error) {
+
+ translationList, err := f.AllTranslations()
+ if err != nil {
+ return err
+ }
+
+ fmt.Println("Available translations:\n")
+ fmt.Println("ID\t\ttLanguage\t\tTranslation name\n")
+
+ for _, elem := range translationList {
+ fmt.Printf("%d\t\t(%s)\t\t%s\n", elem.ID, elem.LanguageName, elem.Name)
+ }
+
+ return err
+
+}
diff --git a/src/display/verses.go b/src/display/verses.go
@@ -0,0 +1,44 @@
+package display
+
+import (
+ "fmt"
+
+ d "git.hanabi.in/gitbox/quran-go/src/display/decorate"
+ f "git.hanabi.in/gitbox/quran-go/src/fetch"
+ p "git.hanabi.in/gitbox/quran-go/src/print"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+ u "git.hanabi.in/gitbox/quran-go/src/utils"
+)
+
+// Display verses from verse1 to verse2.
+func Verses(chapter t.Chap, verse1, verse2 t.Verse, author string, translationID t.Trans, delay t.Delay, shouldDecorate bool) {
+
+ if shouldDecorate {
+ d.ChapterHead(chapter)
+ }
+
+ for verse := verse1; verse <= verse2; verse++ {
+ printVerse(translationID, chapter, verse, delay, shouldDecorate)
+ }
+
+ if shouldDecorate {
+ d.End(author, chapter, verse1, verse2)
+ }
+
+}
+
+// Print a single verse.
+func printVerse(transID t.Trans, chapID t.Chap, verse t.Verse, delay t.Delay, shouldDecorate bool) {
+ aayat, err := f.Aayat(transID, chapID, verse, delay)
+ if err != nil {
+ p.Err(err)
+ }
+
+ formattedAayat := u.FormatAayat(aayat)
+
+ if shouldDecorate {
+ fmt.Printf("(%d:%d) ", chapID, verse)
+ }
+
+ fmt.Println(formattedAayat)
+}
diff --git a/src/driver/default-behaviour.go b/src/driver/default-behaviour.go
@@ -0,0 +1,37 @@
+package driver
+
+import (
+ c "git.hanabi.in/gitbox/quran-go/src/cliinput"
+ d "git.hanabi.in/gitbox/quran-go/src/display"
+ f "git.hanabi.in/gitbox/quran-go/src/fetch"
+ p "git.hanabi.in/gitbox/quran-go/src/print"
+ u "git.hanabi.in/gitbox/quran-go/src/utils"
+)
+
+func defaultBehaviuor() {
+
+ chapterVerseRange, translationID, delay, shouldDecorate := c.ParseFlags()
+
+ chapter, verse1, verse2, err := u.ParseInput(chapterVerseRange)
+ if err != nil {
+ p.Err(err)
+ }
+
+ translationDetails, err := f.TranslationDetails(translationID)
+ if err != nil {
+ p.Err(err)
+ }
+ author := translationDetails.AuthorName
+
+ lastVerse, err := u.CheckVerseRange(chapter, verse1, verse2)
+ if err != nil {
+ p.Err(err)
+ }
+
+ if verse1 == 0 { // Print whole chapter.
+ verse1, verse2 = 1, lastVerse
+ }
+
+ d.Verses(chapter, verse1, verse2, author, translationID, delay, shouldDecorate)
+
+}
diff --git a/src/driver/run.go b/src/driver/run.go
@@ -0,0 +1,37 @@
+package driver
+
+import (
+ "os"
+
+ d "git.hanabi.in/gitbox/quran-go/src/display"
+ p "git.hanabi.in/gitbox/quran-go/src/print"
+)
+
+// Driver code.
+func Run() (err error) {
+
+ option := "help"
+ if len(os.Args) > 1 {
+ option = os.Args[1]
+ }
+
+ switch option {
+ case "about":
+ p.About()
+ case "details":
+ p.Details()
+ case "version":
+ p.Version()
+ case "ls-translations":
+ if err = d.Translations(); err != nil {
+ return err
+ }
+ case "help":
+ p.Help()
+ default:
+ defaultBehaviuor()
+ }
+
+ return err
+
+}
diff --git a/src/fetch/aayat.go b/src/fetch/aayat.go
@@ -0,0 +1,27 @@
+package fetch
+
+import (
+ "fmt"
+ "time"
+
+ q "git.hanabi.in/gitbox/quran-go/src/quran-com"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Fetch an aayah based on translation ID, chapter ID, verse, with delay; returns aayat, and error if any.
+func Aayat(transID t.Trans, chapterID t.Chap, verse t.Verse, delay t.Delay) (aayat string, err error) {
+
+ var verseData t.VerseData
+ uri := fmt.Sprintf("%s/translations/%d/by_ayah/%d:%d", q.API, transID, chapterID, verse)
+
+ time.Sleep(time.Duration(delay) * time.Millisecond)
+
+ if err = q.HttpGet(uri, &verseData); err != nil {
+ return aayat, err
+ }
+
+ aayat = verseData.Translations[0].Text
+
+ return aayat, err
+
+}
diff --git a/src/fetch/all-translations.go b/src/fetch/all-translations.go
@@ -0,0 +1,28 @@
+package fetch
+
+import (
+ "sort"
+
+ q "git.hanabi.in/gitbox/quran-go/src/quran-com"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Fetch all available translations of Qur'an sorted based on the ID provided by quran.com.
+func AllTranslations() (translationList []t.Translations, err error) {
+
+ var resp t.TranslationList
+ uri := q.API + "/resources/translations"
+
+ err = q.HttpGet(uri, &resp)
+ if err != nil {
+ return translationList, err
+ }
+
+ translationList = resp.Translations
+ sort.Slice(translationList, func(a, b int) bool {
+ return translationList[a].ID < translationList[b].ID
+ })
+
+ return translationList, err
+
+}
diff --git a/src/fetch/chapter-details.go b/src/fetch/chapter-details.go
@@ -0,0 +1,18 @@
+package fetch
+
+import (
+ "fmt"
+
+ q "git.hanabi.in/gitbox/quran-go/src/quran-com"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Fetch chapter details based on chapter ID.
+func ChapterDetails(chapterID t.Chap) (chapDetails t.ChapterDetails, err error) {
+
+ uri := fmt.Sprintf("%s/chapters/%d", q.API, chapterID)
+ err = q.HttpGet(uri, &chapDetails)
+
+ return chapDetails, err
+
+}
diff --git a/src/fetch/last-chap.go b/src/fetch/last-chap.go
@@ -0,0 +1,24 @@
+package fetch
+
+import (
+ q "git.hanabi.in/gitbox/quran-go/src/quran-com"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Fetch the ID of the last chapter in Qur'an.
+func LastChapter() (res t.Chap, err error) {
+
+ var chapterList t.ChaptersList
+ uri := q.API + "/chapters"
+
+ err = q.HttpGet(uri, &chapterList)
+ if err != nil {
+ return res, err
+ }
+
+ chapters := chapterList.Chapters
+ res = t.Chap(chapters[len(chapters)-1].ID)
+
+ return res, err
+
+}
diff --git a/src/fetch/translation-details.go b/src/fetch/translation-details.go
@@ -0,0 +1,46 @@
+package fetch
+
+import (
+ "fmt"
+
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Fetch translation details based on translation ID.
+func TranslationDetails(translationID t.Trans) (transDetails t.Translations, err error) {
+
+ translationList, err := AllTranslations()
+ if err != nil {
+ return transDetails, err
+ }
+
+ idx := searchTranslation(translationList, translationID)
+ if idx > -1 {
+ transDetails = translationList[idx]
+ } else {
+ err = fmt.Errorf("%d is not a valid translation, please use `quran ls-translations' to see available translations.", translationID)
+ }
+
+ return transDetails, err
+
+}
+
+// Binary search on translation list against transID. Returns index of found element, else -1.
+func searchTranslation(translationList []t.Translations, translationID t.Trans) (idx int) {
+ low, high := 0, len(translationList)-1
+ for low <= high {
+ mid := (low + high) >> 1
+ midElemID := t.Trans(translationList[mid].ID)
+
+ if midElemID == translationID {
+ return mid
+ } else {
+ if midElemID > translationID {
+ high = mid - 1
+ } else {
+ low = mid + 1
+ }
+ }
+ }
+ return -1
+}
diff --git a/src/main.go b/src/main.go
@@ -1,365 +1,14 @@
package main
import (
- "encoding/json"
- "flag"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "regexp"
- "sort"
- "strconv"
- "time"
+ dr "git.hanabi.in/gitbox/quran-go/src/driver"
+ p "git.hanabi.in/gitbox/quran-go/src/print"
)
-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
+ if err := dr.Run(); err != nil {
+ p.Err(err)
}
- 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)
- }
}
diff --git a/src/print/about.go b/src/print/about.go
@@ -0,0 +1,5 @@
+package print
+
+func About() {
+ p("`quran' prints Qur'an chapters and verses right in the terminal.")
+}
diff --git a/src/print/details.go b/src/print/details.go
@@ -0,0 +1,5 @@
+package print
+
+func Details() {
+ p("`quran' fetches verses and translation information from <quran.com>'s v4 API.")
+}
diff --git a/src/print/err.go b/src/print/err.go
@@ -0,0 +1,11 @@
+package print
+
+import (
+ "fmt"
+ "os"
+)
+
+func Err(err error) {
+ fmt.Fprintln(os.Stderr, err.Error())
+ os.Exit(1)
+}
diff --git a/src/print/fns.go b/src/print/fns.go
@@ -0,0 +1,19 @@
+package print
+
+import "fmt"
+
+func p(a string) (n int, err error) {
+ return fmt.Println(a)
+}
+func pt(a string) (n int, err error) {
+ return fmt.Println("\t", a)
+}
+func ptt(a string) (n int, err error) {
+ return fmt.Println("\t\t", a)
+}
+func ptn(a string) (n int, err error) {
+ return fmt.Println("\t", a, "\n\n")
+}
+func pttn(a string) (n int, err error) {
+ return fmt.Println("\t\t", a, "\n")
+}
diff --git a/src/print/help.go b/src/print/help.go
@@ -0,0 +1,64 @@
+package print
+
+func Help() {
+
+ p("QURAN\n\n")
+
+ p("NAME")
+ ptn("quran - fetch various quran translations right in the terminal.")
+
+ p("SYNOPSIS")
+ ptn("quran [-delay[=<millisecs>]] [-decorate] [-trans[=<translation-id>]] <command> [<args>]")
+
+ p("DESCRIPTION")
+ pt("`quran' fetches Qur'an translations right in the terminal.")
+ ptn("`quran' is not 'bug compatible' with `quran.py', as this software handles parsing input differently.")
+
+ p("OPTIONS")
+ pt("-decorate")
+ ptt("Specify if the chapters and verses should be decorated xor not.")
+ ptt("Defaults to false.")
+
+ pt("-delay <delay-ms>")
+ ptt("Specify <delay-ms> in milliseconds, the delay between fetching aayahs.")
+ ptt("I advice using at least 500.")
+ ptt("Default is 1000.") // Should the delay be *only* between verses? Or *any* API call?
+
+ pt("-trans <translation-id>")
+ ptt("Select which translation to use, for fetching the verses.")
+ pttn("Default id is 131, corresponding to Dr. Mustafa Khattab, the Clear Quran.")
+ pttn("Use `quran ls-translations' to see available translations.")
+
+ p("COMMANDS")
+ pt("about")
+ ptt("Prints basic description about `quran'.")
+
+ pt("details")
+ ptt("Prints information about the source of the data.")
+
+ pt("version")
+ ptt("Prints version of `quran'.")
+
+ pt("ls-translations")
+ pttn("Prints available translations, which can be specified with the `-trans' flag.")
+
+ p("ARGUMENTS")
+ ptn("Without specifying any commands, arguemnt is of the type <chapter:[verse1[-verse2]]>")
+
+ p("LICENSE")
+ pt("This software is licensed under GNU Affero General Public License v3.")
+ pt("Please see <https://www.gnu.org/licenses/agpl-3.0.txt>")
+ ptn("for what you are allowed to do with this software's source code.")
+
+ p("AUTHORS")
+ pt("quran was written by acagastya.")
+ ptn("Running `git blame' on the source code of this software can show the authors of project.")
+
+ p("REPORTING ERRORS")
+ pt("Please do NOT call errors as bugs. Any incorrect behaviour of the software is an ERROR.")
+ pt("Report issues to <me+git@hanabi.in>.")
+ ptn("Additionally, if you wish to submit changes/error fixes, please send a git-patch to the same email.")
+
+ p("Quran v1.0.0\t\tMay 01, 2022\t\tacagastya")
+
+}
diff --git a/src/print/version.go b/src/print/version.go
@@ -0,0 +1,5 @@
+package print
+
+func Version() {
+ p("quran v1.0.0")
+}
diff --git a/src/quran-com/consts.go b/src/quran-com/consts.go
@@ -0,0 +1,7 @@
+package qurancom
+
+const API = "https://api.quran.com/api/v4"
+
+const DefaultDecorate = false
+const DefaultDelay = 1000
+const DefaultTrans = 131
diff --git a/src/quran-com/http-get.go b/src/quran-com/http-get.go
@@ -0,0 +1,36 @@
+package qurancom
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+)
+
+// Custom HTTP client for GET method.
+func HttpGet(endpoint string, st interface{}) (err error) {
+
+ client := &http.Client{}
+
+ req, err := http.NewRequest(http.MethodGet, endpoint, nil)
+ if err != nil {
+ return err
+ }
+
+ req.Header.Set("User-Agent", "git.hanabi.in/quran-go v1.0.0")
+ resp, err := client.Do(req)
+
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+
+ err = json.Unmarshal(body, &st)
+
+ return err
+
+}
diff --git a/src/types/type.go b/src/types/type.go
@@ -0,0 +1,60 @@
+package types
+
+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"`
+}
+
+type VerseData 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 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 ChaptersList struct {
+ Chapters []Chapter `json:"chapters"`
+}
+
+type Chap uint64
+type Delay float64
+type Trans int // As of now, I have only seen integers, could be negative in future, for all I know.
+type Verse uint64
diff --git a/src/utils/check-verse-range.go b/src/utils/check-verse-range.go
@@ -0,0 +1,35 @@
+package utils
+
+import (
+ "fmt"
+
+ f "git.hanabi.in/gitbox/quran-go/src/fetch"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Check if chapter verse are within range, returns last verse of the chapter, and error if any,
+func CheckVerseRange(chapterID t.Chap, verse1, verse2 t.Verse) (lastVerse t.Verse, err error) {
+
+ res, err := isValidChapRange(chapterID)
+ if err != nil {
+ return lastVerse, err
+ }
+
+ if res == false {
+ err = fmt.Errorf("Chapter %d is out of range.\n", chapterID)
+ return lastVerse, err
+ }
+
+ chapDetails, err := f.ChapterDetails(chapterID)
+ if err != nil {
+ return lastVerse, err
+ }
+
+ if lastVerse = t.Verse(chapDetails.VersesCount); verse2 > lastVerse {
+ err = fmt.Errorf("Verse for the chapter %d is out of range. That chapter has verses only till %d.\n", chapterID, lastVerse)
+ return lastVerse, err
+ }
+
+ return lastVerse, err
+
+}
diff --git a/src/utils/format-aayat.go b/src/utils/format-aayat.go
@@ -0,0 +1,12 @@
+package utils
+
+import "regexp"
+
+// Regex fix an Aayah to remove things like <sup foot_note=76373>1</sup>
+func FormatAayat(aayat string) (formattedAayat string) {
+
+ reg := regexp.MustCompile(`<sup foot_note=\d+>\d+<\/sup>`)
+ formattedAayat = reg.ReplaceAllString(aayat, "")
+ return formattedAayat
+
+}
diff --git a/src/utils/is-valid-chapter-range.go b/src/utils/is-valid-chapter-range.go
@@ -0,0 +1,20 @@
+package utils
+
+import (
+ f "git.hanabi.in/gitbox/quran-go/src/fetch"
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Check if the chapter number is valid.
+func isValidChapRange(chapter t.Chap) (res bool, err error) {
+
+ if lastChapter, err := f.LastChapter(); err != nil {
+ return res, err
+ } else {
+ if 1 <= chapter && chapter <= lastChapter {
+ res = true
+ }
+ return res, err
+ }
+
+}
diff --git a/src/utils/parse-input.go b/src/utils/parse-input.go
@@ -0,0 +1,65 @@
+package utils
+
+import (
+ "fmt"
+ "regexp"
+ "strconv"
+
+ t "git.hanabi.in/gitbox/quran-go/src/types"
+)
+
+// Takes "1" or "1:2" or "1:2-5" and returns chapter, verse1, verse2 and error, if any.
+func ParseInput(input string) (chapter t.Chap, verse1, verse2 t.Verse, err error) {
+
+ // 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, 64)
+ 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, 64)
+ if err != nil {
+ return
+ }
+
+ verse1 = t.Verse(ve1)
+
+ if res[0][5] == "" {
+ res[0][5] = res[0][3]
+ }
+
+ ve2, err := strconv.ParseUint(res[0][5], 10, 64)
+ if err != nil {
+ return
+ }
+
+ verse2 = t.Verse(ve2)
+
+ if verse2 < verse1 {
+ err = fmt.Errorf("Verse 2 must be bigger than Verse 1.\n")
+ }
+
+ return
+
+}