Skip to content

Commit

Permalink
feat: export report card
Browse files Browse the repository at this point in the history
  • Loading branch information
Lutonite committed Feb 19, 2024
1 parent 5546b0e commit 310cf8d
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 19 deletions.
140 changes: 140 additions & 0 deletions cmd/report-card.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package cmd

import (
"encoding/json"
"fmt"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"lutonite.dev/gaps-cli/gaps"
"lutonite.dev/gaps-cli/parser"
"lutonite.dev/gaps-cli/util"
"os"
"strconv"
)

type ReportCardCmdOpts struct {
format string
}

var (
reportCardOpts = &ReportCardCmdOpts{}
reportCardCmd = &cobra.Command{
Use: "report-card",
Short: "Allows to consult your report card",
RunE: func(cmd *cobra.Command, args []string) error {
cfg := buildTokenClientConfiguration()

action := gaps.NewReportCardAction(cfg)
reports, err := action.FetchReportCard()
util.CheckErr(err)

if len(reports) == 0 {
log.Error("No reports found for the given parameters")
return nil
}

if reportCardOpts.format == "json" {
return json.NewEncoder(os.Stdout).Encode(reports)
}

reportCardOpts.PrintReportCardTable(reports)
return nil
},
}
)

func init() {
reportCardCmd.Flags().StringVarP(&reportCardOpts.format, "format", "o", "table", "Output format (table, json)")

rootCmd.AddCommand(reportCardCmd)
}

func (g *ReportCardCmdOpts) PrintReportCardTable(moduleReports []*parser.ModuleReport) {
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.Style().Options.SeparateRows = true
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: true, Align: text.AlignCenter, AlignHeader: text.AlignCenter},
{Number: 3, AutoMerge: true},
{Number: 4, Align: text.AlignCenter, AlignHeader: text.AlignCenter},
{Number: 5, Align: text.AlignCenter, AlignHeader: text.AlignCenter, AlignFooter: text.AlignRight},
{Number: 6, Align: text.AlignCenter, AlignHeader: text.AlignCenter, AlignFooter: text.AlignCenter},
})

t.AppendHeader(table.Row{"Module", "Credits", "Class", "Category", "Weight", "Grade"})

for _, module := range moduleReports {
moduleDesc := fmt.Sprintf("%s (%s)", module.Name, module.Identifier)
if module.Year > 0 {
moduleDesc += fmt.Sprintf(" - %d-%d", module.Year, module.Year+1)
}

for _, group := range module.Classes {
groupDesc := fmt.Sprintf("%s (%s)", group.Name, group.Identifier)
for _, grade := range group.Grades {
t.AppendRow(table.Row{
moduleDesc,
module.Credits,
groupDesc,
grade.Name,
fmt.Sprintf("%d%%", grade.Weight),
grade.Grade,
})
}

if group.Mean != "" {
t.AppendRow(table.Row{
moduleDesc,
module.Credits,
"",
"",
"",
fmt.Sprintf("%s (W: %d)", group.Mean, group.Weight),
}, table.RowConfig{AutoMerge: true})
}
}

situation := text.Colors{text.FgGreen}.Sprint(module.Situation)
t.AppendRow(table.Row{
moduleDesc,
module.Credits,
situation,
situation,
situation,
text.Colors{text.Bold, text.FgBlue}.Sprint(module.GlobalGrade),
}, table.RowConfig{AutoMerge: true})

t.AppendSeparator()
}

t.AppendFooter(table.Row{
"",
"",
"",
"",
"WEIGHTED GPA",
fmt.Sprintf("%.2f", computeGpa(moduleReports)),
}, table.RowConfig{AutoMerge: true})

t.Render()
}

func computeGpa(grades []*parser.ModuleReport) float64 {
var totalCredits uint
var totalPoints float64

for _, module := range grades {
if module.Situation != "Réussite" {
continue
}

totalCredits += module.Credits
gradeNumeric, _ := strconv.ParseFloat(module.GlobalGrade, 64)
totalPoints += float64(module.Credits) * gradeNumeric
}

return totalPoints / float64(totalCredits)
}
42 changes: 42 additions & 0 deletions gaps/report-card.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package gaps

import (
"fmt"
"golang.org/x/net/html/charset"
"lutonite.dev/gaps-cli/parser"
)

type ReportCardAction struct {
cfg *TokenClientConfiguration
}

func NewReportCardAction(config *TokenClientConfiguration) *ReportCardAction {
return &ReportCardAction{
cfg: config,
}
}

func (a *ReportCardAction) FetchReportCard() ([]*parser.ModuleReport, error) {
req, err := a.cfg.buildRequest("GET", fmt.Sprintf("/consultation/notes/bulletin.php?id=%d", a.cfg.studentId))
if err != nil {
return nil, err
}

res, err := a.cfg.doForm(req, nil)
if err != nil {
return nil, err
}

defer res.Body.Close()
utfBody, err := charset.NewReader(res.Body, "iso-8859-1")
if err != nil {
return nil, err
}

pres, err := parser.FromResponseBody(utfBody)
if err != nil {
return nil, err
}

return pres.ReportCard()
}
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ require (
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.15.0
github.com/zalando/go-keyring v0.2.3
golang.org/x/term v0.5.0
golang.org/x/net v0.7.0
golang.org/x/term v0.16.0
)

require (
Expand All @@ -22,7 +23,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
Expand All @@ -31,8 +32,7 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,9 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
Expand Down Expand Up @@ -346,12 +347,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
22 changes: 11 additions & 11 deletions parser/grades.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ type gradesParser struct {
}

const (
classHeader gradeRowType = iota
groupHeader
gradeRow
unknownRow
gradesClassHeader gradeRowType = iota
gradesGroupHeader
gradesGradeRow
gradesUnknownRow = -1
)

func (s *Parser) Grades() ([]*ClassGrades, error) {
Expand All @@ -64,15 +64,15 @@ func (p gradesParser) parse() ([]*ClassGrades, error) {
var globalErr error
p.doc.Find("table.displayArray tbody tr").Each(func(i int, s *goquery.Selection) {
switch p.getRowType(s) {
case classHeader:
case gradesClassHeader:
class, err := p.parseClassHeader(s)
if err != nil {
globalErr = err
return
}

classes = append(classes, class)
case groupHeader:
case gradesGroupHeader:
group, err := p.parseGroupHeader(s)
if err != nil {
globalErr = err
Expand All @@ -82,7 +82,7 @@ func (p gradesParser) parse() ([]*ClassGrades, error) {
classOff := len(classes) - 1

classes[classOff].GradeGroups = append(classes[classOff].GradeGroups, group)
case gradeRow:
case gradesGradeRow:
grade, err := p.parseGradeRow(s)
if err != nil {
globalErr = err
Expand Down Expand Up @@ -111,13 +111,13 @@ func (p gradesParser) parse() ([]*ClassGrades, error) {

func (p gradesParser) getRowType(row *goquery.Selection) gradeRowType {
if row.Has("td.bigheader").Length() > 0 {
return classHeader
return gradesClassHeader
} else if row.Has("td[rowspan]").Length() > 0 {
return groupHeader
return gradesGroupHeader
} else if row.Find("td").Size() == 5 {
return gradeRow
return gradesGradeRow
} else {
return unknownRow
return gradesUnknownRow
}
}

Expand Down
Loading

0 comments on commit 310cf8d

Please sign in to comment.