diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
similarity index 100%
rename from CODE_OF_CONDUCT.md
rename to .github/CODE_OF_CONDUCT.md
diff --git a/LICENSE b/LICENSE
index 902c7cc..ae88b75 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 infixint943
+Copyright (c) 2020 cp-tools
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index a7ccdcd..f2c317f 100644
--- a/README.md
+++ b/README.md
@@ -8,12 +8,12 @@
Don't forget to :star: the project if you liked it!
-
-
-
-
-
-
+
+
+
+
+
+
@@ -37,20 +37,20 @@
# Installation
-You may download the latest, compiled, binary files from [here](https://github.com/infixint943/cf/releases).
+You may download the latest, compiled, binary files from [here](https://github.com/cp-tools/cf/releases).
Place the executable in system **PATH** to invoke the tool from any directory.
Alternatively, you can also compile the tool from source.
```bash
-git clone https://github.com/infixint943/cf.git
+git clone https://github.com/cp-tools/cf.git
cd cf/
go build -ldflags "-s -w"
```
# Quick Start
-**Note:** For detailed documentation, please head to the [wiki](https://github.com/infixint943/cf/wiki) page.
+**Note:** For detailed documentation, please head to the [wiki](https://github.com/cp-tools/cf/wiki) page.
> Let's simulate participating in contest `4`. This tutorial assumes you have already configured your login details and added at least one template, through `cf config`
diff --git a/client/fetch.go b/client/fetch.go
index cbf1eb6..b9e0d97 100644
--- a/client/fetch.go
+++ b/client/fetch.go
@@ -2,7 +2,6 @@ package cln
import (
cfg "cf/config"
- pkg "cf/packages"
"bytes"
"fmt"
@@ -13,19 +12,24 @@ import (
"github.com/PuerkitoBio/goquery"
)
-// FindCountdown parses countdown (if exists) from countdown page
+/*
+FindCountdown parses and returns number of seconds remaining
+before contest begins. Returns 0 if countdown has already ended.
+Virtual contests (of the current user session) are supported too.
+
+If countdown page doesn't exsit, returns error ErrContestNotExists
+*/
func FindCountdown(contest string, link url.URL) (int64, error) {
// This implementation contains redirection prevention
c := cfg.Session.Client
- c.CheckRedirect = pkg.RedirectCheck
+ c.CheckRedirect = redirectCheck
link.Path = path.Join(link.Path, "countdown")
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return 0, err
} else if len(body) == 0 {
// such page doesn't exist
- err = fmt.Errorf("Contest %v doesn't exist", contest)
- return 0, err
+ return 0, ErrContestNotExists
}
doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))
@@ -36,11 +40,17 @@ func FindCountdown(contest string, link url.URL) (int64, error) {
return h*3600 + m*60 + s, nil
}
-// FetchProbs finds all problems present in the contest
+/*
+FetchProbs parses and returns problem code's of all problems in contest.
+Problem codes are returned in their lowercase versions. For example,
+A => a, F1 => f1, C2 => c2 etc.
+
+If contest dashboard doesn't exist, returns error ErrContestNotExists
+*/
func FetchProbs(contest string, link url.URL) ([]string, error) {
// no need of modifying link as it already points to dashboard
c := cfg.Session.Client
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return nil, err
}
@@ -54,11 +64,18 @@ func FetchProbs(contest string, link url.URL) ([]string, error) {
return probs, nil
}
-// FetchTests extracts test cases of the problem(s) in contest
-// Returns 2d slice mapping to input and output
-// If problem == "", fetch all problem test cases
-// else, only fetch of given problem.
-// fix for https://github.com/infixint943/cf/pull/2#issuecomment-626122011
+/*
+FetchTests parses test cases of problems in the contest and returns
+the sample inputs/outputs as a 2d slice of strings.
+
+If problem parameter is empty, returns test cases of ALL problems in contest.
+Samples are fetched from the contest's 'complete problemset' page.
+
+Otherwise, sample tests of only the specified problem is returned
+Here, samples are fetched from the (individual) problem's page
+
+If problems page doesn't exist, returns error ErrContestNotExists
+*/
func FetchTests(contest, problem string, link url.URL) ([][]string, [][]string, error) {
c := cfg.Session.Client
@@ -70,7 +87,7 @@ func FetchTests(contest, problem string, link url.URL) ([][]string, [][]string,
link.Path = path.Join(link.Path, "problem", problem)
}
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return nil, nil, err
}
diff --git a/client/langs.go b/client/langs.go
deleted file mode 100644
index 32d9985..0000000
--- a/client/langs.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package cln
-
-// LangID represents all available languages with id's
-var LangID = map[string]string{
- "GNU GCC C11 5.1.0": "43",
- "Clang++17 Diagnostics": "52",
- "GNU G++11 5.1.0": "42",
- "GNU G++14 6.4.0": "50",
- "GNU G++17 7.3.0": "54",
- "Microsoft Visual C++ 2010": "2",
- "Microsoft Visual C++ 2017": "59",
- "GNU G++17 9.2.0 (64 bit, msys 2)": "61",
- "C# Mono 5.18": "9",
- "D DMD32 v2.086.0": "28",
- "Go 1.12.6": "32",
- "Haskell GHC 8.6.3": "12",
- "Java 11.0.5": "60",
- "Java 1.8.0_162": "36",
- "Kotlin 1.3.10": "48",
- "OCaml 4.02.1": "19",
- "Delphi 7": "3",
- "Free Pascal 3.0.2": "4",
- "PascalABC.NET 3.4.2": "51",
- "Perl 5.20.1": "13",
- "PHP 7.2.13": "6",
- "Python 2.7.15": "7",
- "Python 3.7.2": "31",
- "PyPy 2.7 (7.2.0)": "40",
- "PyPy 3.6 (7.2.0)": "41",
- "Ruby 2.0.0p645": "8",
- "Rust 1.35.0": "49",
- "Scala 2.12.8": "20",
- "JavaScript V8 4.8.0": "34",
- "Node.js 9.4.0": "55",
- "ActiveTcl 8.5": "14",
- "Io-2008-01-07 (Win32)": "15",
- "Pike 7.8": "17",
- "Befunge": "18",
- "OpenCobol 1.0": "22",
- "Factor": "25",
- "Secret_171": "26",
- "Roco": "27",
- "Ada GNAT 4": "33",
- "Mysterious Language": "38",
- "FALSE": "39",
- "Picat 0.9": "44",
- "GNU C++11 5 ZIP": "45",
- "Java 8 ZIP": "46",
- "J": "47",
- "Microsoft Q#": "56",
- "Text": "57",
-}
-
-// LangExt corresponds to file extension of
-// given language source code
-var LangExt = map[string]string{
- "GNU C11": ".c",
- "Clang++17 Diagnostics": ".cpp",
- "GNU C++0x": ".cpp",
- "GNU C++": ".cpp",
- "GNU C++11": ".cpp",
- "GNU C++14": ".cpp",
- "GNU C++17": ".cpp",
- "MS C++": ".cpp",
- "MS C++ 2017": ".cpp",
- "GNU C++17 (64)": ".cpp",
- "Mono C#": ".cs",
- "D": ".d",
- "Go": ".go",
- "Haskell": ".hs",
- "Kotlin": ".kt",
- "Ocaml": ".ml",
- "Delphi": ".pas",
- "FPC": ".pas",
- "PascalABC.NET": ".pas",
- "Perl": ".pl",
- "PHP": ".php",
- "Python 2": ".py",
- "Python 3": ".py",
- "PyPy 2": ".py",
- "PyPy 3": ".py",
- "Ruby": ".rb",
- "Rust": ".rs",
- "JavaScript": ".js",
- "Node.js": ".js",
- "Q#": ".qs",
- "Java": ".java",
- "Java 6": ".java",
- "Java 7": ".java",
- "Java 8": ".java",
- "Java 9": ".java",
- "Java 10": ".java",
- "Java 11": ".java",
- "Tcl": ".tcl",
- "F#": ".fs",
- "Befunge": ".bf",
- "Pike": ".pike",
- "Io": ".io",
- "Factor": ".factor",
- "Cobol": ".cbl",
- "Secret_171": ".secret_171",
- "Ada": ".adb",
- "FALSE": ".f",
- "": ".txt",
-}
\ No newline at end of file
diff --git a/client/login.go b/client/login.go
index ccda941..0b983ac 100644
--- a/client/login.go
+++ b/client/login.go
@@ -2,10 +2,8 @@ package cln
import (
cfg "cf/config"
- pkg "cf/packages"
"encoding/hex"
- "fmt"
"net/url"
"path"
@@ -13,7 +11,16 @@ import (
"github.com/oleiade/serrure/aes"
)
-// Login tries logginging in with user creds
+/*
+Login attempts logging in to configured host domain
+with user credentials passed in the parameters.
+
+Returns true if login was successful (saves session to sessPath)
+and false if login failed due to wrong credentials.
+
+If login failed for any other reason (other than wrong creds)
+the respective hhtp error message is returned.
+*/
func Login(usr, passwd string) (bool, error) {
// instantiate http client, but remove
// past user sessions to prevent redirection
@@ -23,18 +30,18 @@ func Login(usr, passwd string) (bool, error) {
link, _ := url.Parse(cfg.Settings.Host)
link.Path = path.Join(link.Path, "enter")
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return false, err
}
// Hidden form data
- csrf := pkg.FindCsrf(body)
+ csrf := findCsrf(body)
ftaa := "yzo0kk4bhlbaw83g2q"
bfaa := "883b704dbe5c70e1e61de4d8aff2da32"
// Post form (aka login using creds)
- body, err = pkg.PostReqBody(&c, link.String(), url.Values{
+ body, err = postReqBody(&c, link.String(), url.Values{
"csrf_token": {csrf},
"action": {"enter"},
"ftaa": {ftaa},
@@ -48,7 +55,7 @@ func Login(usr, passwd string) (bool, error) {
return false, err
}
- usr = pkg.FindHandle(body)
+ usr = findHandle(body)
if usr != "" {
// create aes 256 encryption and encode as
// hex string and save to sessions.json
@@ -64,28 +71,38 @@ func Login(usr, passwd string) (bool, error) {
return (usr != ""), nil
}
-// LoggedInUsr checks and returns whether
-// current session is logged in
+/*
+LoggedInUsr returns handle of currently logged in user
+Session uses Session.Client data to pull homepage
+and extract the handle of the logged in user.
+Returns an empty string if no logged in user is found
+
+If http request failed, corresponding error is returned
+*/
func LoggedInUsr() (string, error) {
// fetch home page and check if logged in
c := cfg.Session.Client
link, _ := url.Parse(cfg.Settings.Host)
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return "", err
}
- return pkg.FindHandle(body), nil
+ return findHandle(body), nil
}
-// Relogin extracts handle/passwd from sessions.json
-// and log's in with the credentials and returns status
+/*
+Relogin extracts user handle / passwd from the Session struct
+and passes the credentials to function Login() to relogin again.
+Returns same return values of function Login()
+
+If password couldn't be decrypted, returns error ErrDecodePasswdFailed
+*/
func Relogin() (bool, error) {
// decode hex data of encrypted password
ciphertext, err := hex.DecodeString(cfg.Session.Passwd)
if err != nil {
- err := fmt.Errorf("Failed to decode password")
- return false, err
+ return false, ErrDecodePasswdFailed
}
usr := cfg.Session.Handle
dec := aes.NewAES256Decrypter(usr)
diff --git a/client/misc.go b/client/misc.go
new file mode 100644
index 0000000..17232a3
--- /dev/null
+++ b/client/misc.go
@@ -0,0 +1,178 @@
+package cln
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+
+ "github.com/PuerkitoBio/goquery"
+)
+
+// Some global variables
+var (
+ ErrContestNotExists = fmt.Errorf("Contest doesn't exist")
+ ErrDecodePasswdFailed = fmt.Errorf("Failed to decode password")
+ ErrUnequalSampleTests = fmt.Errorf("Unequal number of input/output test files")
+ ErrSampleTestsNotExists = fmt.Errorf("No test files found")
+
+ // LangID represents all available languages with id's
+ LangID = map[string]string{
+ "GNU GCC C11 5.1.0": "43",
+ "Clang++17 Diagnostics": "52",
+ "GNU G++11 5.1.0": "42",
+ "GNU G++14 6.4.0": "50",
+ "GNU G++17 7.3.0": "54",
+ "Microsoft Visual C++ 2010": "2",
+ "Microsoft Visual C++ 2017": "59",
+ "GNU G++17 9.2.0 (64 bit, msys 2)": "61",
+ "C# Mono 5.18": "9",
+ "D DMD32 v2.086.0": "28",
+ "Go 1.12.6": "32",
+ "Haskell GHC 8.6.3": "12",
+ "Java 11.0.5": "60",
+ "Java 1.8.0_162": "36",
+ "Kotlin 1.3.10": "48",
+ "OCaml 4.02.1": "19",
+ "Delphi 7": "3",
+ "Free Pascal 3.0.2": "4",
+ "PascalABC.NET 3.4.2": "51",
+ "Perl 5.20.1": "13",
+ "PHP 7.2.13": "6",
+ "Python 2.7.15": "7",
+ "Python 3.7.2": "31",
+ "PyPy 2.7 (7.2.0)": "40",
+ "PyPy 3.6 (7.2.0)": "41",
+ "Ruby 2.0.0p645": "8",
+ "Rust 1.35.0": "49",
+ "Scala 2.12.8": "20",
+ "JavaScript V8 4.8.0": "34",
+ "Node.js 9.4.0": "55",
+ "ActiveTcl 8.5": "14",
+ "Io-2008-01-07 (Win32)": "15",
+ "Pike 7.8": "17",
+ "Befunge": "18",
+ "OpenCobol 1.0": "22",
+ "Factor": "25",
+ "Secret_171": "26",
+ "Roco": "27",
+ "Ada GNAT 4": "33",
+ "Mysterious Language": "38",
+ "FALSE": "39",
+ "Picat 0.9": "44",
+ "GNU C++11 5 ZIP": "45",
+ "Java 8 ZIP": "46",
+ "J": "47",
+ "Microsoft Q#": "56",
+ "Text": "57",
+ }
+
+ // LangExt corresponds to file extension of
+ // given language source code
+ LangExt = map[string]string{
+ "GNU C11": ".c",
+ "Clang++17 Diagnostics": ".cpp",
+ "GNU C++0x": ".cpp",
+ "GNU C++": ".cpp",
+ "GNU C++11": ".cpp",
+ "GNU C++14": ".cpp",
+ "GNU C++17": ".cpp",
+ "MS C++": ".cpp",
+ "MS C++ 2017": ".cpp",
+ "GNU C++17 (64)": ".cpp",
+ "Mono C#": ".cs",
+ "D": ".d",
+ "Go": ".go",
+ "Haskell": ".hs",
+ "Kotlin": ".kt",
+ "Ocaml": ".ml",
+ "Delphi": ".pas",
+ "FPC": ".pas",
+ "PascalABC.NET": ".pas",
+ "Perl": ".pl",
+ "PHP": ".php",
+ "Python 2": ".py",
+ "Python 3": ".py",
+ "PyPy 2": ".py",
+ "PyPy 3": ".py",
+ "Ruby": ".rb",
+ "Rust": ".rs",
+ "JavaScript": ".js",
+ "Node.js": ".js",
+ "Q#": ".qs",
+ "Java": ".java",
+ "Java 6": ".java",
+ "Java 7": ".java",
+ "Java 8": ".java",
+ "Java 9": ".java",
+ "Java 10": ".java",
+ "Java 11": ".java",
+ "Tcl": ".tcl",
+ "F#": ".fs",
+ "Befunge": ".bf",
+ "Pike": ".pike",
+ "Io": ".io",
+ "Factor": ".factor",
+ "Cobol": ".cbl",
+ "Secret_171": ".secret_171",
+ "Ada": ".adb",
+ "FALSE": ".f",
+ "": ".txt",
+ }
+)
+
+func parseBody(resp *http.Response) ([]byte, error) {
+ defer resp.Body.Close()
+ return ioutil.ReadAll(resp.Body)
+}
+
+// getReqBody executes a GET request to url and returns the request body
+func getReqBody(client *http.Client, url string) ([]byte, error) {
+ resp, err := client.Get(url)
+ if err != nil {
+ return nil, err
+ }
+ return parseBody(resp)
+}
+
+// postReqBody executes a POST request (with values: data) to url and returns the request body
+func postReqBody(client *http.Client, url string, data url.Values) ([]byte, error) {
+ resp, err := client.PostForm(url, data)
+ if err != nil {
+ return nil, err
+ }
+ return parseBody(resp)
+}
+
+// findHandle scrapes handle from REQUEST body
+func findHandle(body []byte) string {
+ doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))
+ val := doc.Find("#header").Find("a[href^=\"/profile/\"]").Text()
+ return val
+}
+
+// findCsrf extracts Csrf from REQUEST body
+func findCsrf(body []byte) string {
+ doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))
+ val, _ := doc.Find(".csrf-token").Attr("data-csrf")
+ return val
+}
+
+// redirectCheck prevents redirection and returns requested page info
+func redirectCheck(req *http.Request, via []*http.Request) error {
+ return http.ErrUseLastResponse
+}
+
+// getText extracts text from particular html data
+func getText(sel *goquery.Selection, query string) string {
+ str := sel.Find(query).Text()
+ return strings.TrimSpace(str)
+}
+
+// getAttr extracts attribute valur of particular html data
+func getAttr(sel *goquery.Selection, query, attr string) string {
+ str := sel.Find(query).AttrOr(attr, "")
+ return strings.TrimSpace(str)
+}
diff --git a/client/pull.go b/client/pull.go
index 43dfba4..75d8ff3 100644
--- a/client/pull.go
+++ b/client/pull.go
@@ -2,7 +2,6 @@ package cln
import (
cfg "cf/config"
- pkg "cf/packages"
"bytes"
"fmt"
@@ -24,7 +23,17 @@ type (
}
)
-// FetchSubs pulls submissions matching criteria
+/*
+FetchSubs pulls submission information of each submission
+of handle matching contest and problem parameters passed.
+
+Set contest param to non-empty to filter submissions of particular contest.
+Similarly, setting problem param to non-empty filters problems matching criteria.
+
+Returns slice of struct `Sub` holding details of filtered submissions.
+
+Returns error is http request fails or API returns non-OK status.
+*/
func FetchSubs(contest, problem, handle string) ([]Sub, error) {
c := cfg.Session.Client
@@ -35,7 +44,7 @@ func FetchSubs(contest, problem, handle string) ([]Sub, error) {
q.Set("handle", handle)
link.RawQuery = q.Encode()
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return nil, err
}
@@ -46,7 +55,7 @@ func FetchSubs(contest, problem, handle string) ([]Sub, error) {
comm := gjson.GetBytes(body, "comment").String()
return nil, fmt.Errorf(comm)
}
- // is another submission to same problem considered
+ // is another AC submission to same problem considered
isParsed := make(map[string]bool)
var Subs []Sub
@@ -54,7 +63,6 @@ func FetchSubs(contest, problem, handle string) ([]Sub, error) {
// thanks to module tidwall/gjson for the awesome package
result := gjson.GetBytes(body, "result")
result.ForEach(func(key, value gjson.Result) bool {
- // check if result matches search criteria
// extract submission data
contID := value.Get("problem.contestId").String()
probID := value.Get("problem.index").String()
@@ -65,7 +73,7 @@ func FetchSubs(contest, problem, handle string) ([]Sub, error) {
sid := value.Get("id").String()
// ContestId+ProblemId => 1234c2
query := contID + probID
-
+ // check if result matches search criteria
if (contID == contest || contest == "") && (probID == problem || problem == "") &&
(verdict == "OK" && isParsed[query] == false) {
// create sub and fetch source code
@@ -86,7 +94,12 @@ func FetchSubs(contest, problem, handle string) ([]Sub, error) {
return Subs, nil
}
-// FetchSubSource fetches submission code of Sub
+/*
+FetchSubSource extracts source code of particular submission
+returns a string containing the source code of the submission.
+
+If fetching submission fails, http error message is returned.
+*/
func (sub *Sub) FetchSubSource() (string, error) {
// determine contest type (contest/gym)
contClass := "contest"
@@ -97,7 +110,7 @@ func (sub *Sub) FetchSubSource() (string, error) {
c := cfg.Session.Client
link, _ := url.Parse(cfg.Settings.Host)
link.Path = path.Join(link.Path, contClass, sub.Contest, "submission", sub.Sid)
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return "", err
}
diff --git a/client/submit.go b/client/submit.go
index 0fd2473..f340804 100644
--- a/client/submit.go
+++ b/client/submit.go
@@ -2,7 +2,6 @@ package cln
import (
cfg "cf/config"
- pkg "cf/packages"
"bytes"
"fmt"
@@ -13,30 +12,35 @@ import (
"github.com/PuerkitoBio/goquery"
)
-// Submit uploads form data and submits user code
+/*
+Submit reads contents of file and submits it to
+specified problem in contest. Returns nil is submission was successful.
+
+If submission fails (includes failure due to submission of same code)
+returns error message of cause of failed submission.
+*/
func Submit(contest, problem, langID, file string, link url.URL) error {
// form redirection prevention is removed while submitting
c := cfg.Session.Client
- c.CheckRedirect = pkg.RedirectCheck
+ c.CheckRedirect = redirectCheck
link.Path = path.Join(link.Path, "submit")
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return err
} else if len(body) == 0 {
// such page doesn't exist
- err = fmt.Errorf("Contest %v doesn't exist", contest)
- return err
+ return ErrContestNotExists
}
// read source file
data, _ := ioutil.ReadFile(file)
// hidden form data
- csrf := pkg.FindCsrf(body)
+ csrf := findCsrf(body)
ftaa := "yzo0kk4bhlbaw83g2q"
bfaa := "883b704dbe5c70e1e61de4d8aff2da32"
// post form data (remove redirection prevention)
c.CheckRedirect = nil
- body, err = pkg.PostReqBody(&c, link.String(), url.Values{
+ body, err = postReqBody(&c, link.String(), url.Values{
"csrf_token": {csrf},
"ftaa": {ftaa},
"bfaa": {bfaa},
diff --git a/client/test.go b/client/test.go
index a20e162..3384007 100644
--- a/client/test.go
+++ b/client/test.go
@@ -2,11 +2,9 @@ package cln
import (
cfg "cf/config"
- pkg "cf/packages"
"bytes"
"context"
- "fmt"
"io"
"io/ioutil"
"math/big"
@@ -16,9 +14,6 @@ import (
"strconv"
"strings"
"time"
-
- "github.com/fatih/color"
- "github.com/gosuri/uitable"
)
// FindTests finds all returns all sample input/output
@@ -51,11 +46,9 @@ func FindTests() ([]string, []string, error) {
// check for i/o count equality
// and existence of non-zero test files
if len(inp) != len(out) {
- err := fmt.Errorf("Unequal number of input/output test files")
- return nil, nil, err
+ return nil, nil, ErrUnequalSampleTests
} else if len(inp) == 0 {
- err := fmt.Errorf("No test files found")
- return nil, nil, err
+ return nil, nil, ErrSampleTestsNotExists
}
return inp, out, nil
}
@@ -157,38 +150,3 @@ func Validator(out, ans string, igCase bool, exp int) (string, string) {
// return formatted strings
return f(out), f(ans)
}
-
-// PrintDiff is run if outputs don't match
-// returns input data, and then the diff of => out vs ans
-func PrintDiff(inp, out, ans string) string {
- // variable to hold diff output
- var diff strings.Builder
- headerfmt := pkg.Blue.Add(color.Underline).SprintfFunc()
- // print input data
- fmt.Fprintln(&diff, headerfmt("Input"))
- fmt.Fprintln(&diff, inp)
-
- // break output into lines
- str1 := strings.Split(out, "\n")
- str2 := strings.Split(ans, "\n")
- // equalize string lengths
- if len(str1) < len(str2) {
- str1 = append(str1, make([]string, len(str2)-len(str1))...)
- } else {
- str2 = append(str2, make([]string, len(str1)-len(str2))...)
- }
-
- // print output diff data
- tbl := uitable.New()
- tbl.Separator = " | "
-
- tbl.AddRow(headerfmt("Actual Output"), headerfmt("Expected Output"))
- // iterate over every row of outputs
- for i := 0; i < len(str1); i++ {
- tbl.AddRow(str1[i], str2[i])
- }
- fmt.Fprintln(&diff, tbl)
- fmt.Fprintln(&diff)
-
- return diff.String()
-}
diff --git a/client/upgrade.go b/client/upgrade.go
index 798631e..c7a7e46 100644
--- a/client/upgrade.go
+++ b/client/upgrade.go
@@ -3,17 +3,42 @@ package cln
import (
"archive/tar"
"compress/gzip"
+ "fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
+ "runtime"
+
+ "github.com/blang/semver"
+ "github.com/tidwall/gjson"
)
+// FetchLatest determines latest version through API page of github
+func FetchLatest(owner, repo string) (semver.Version, string, error) {
+ // url link of github API to fetch latest version data from
+ link := fmt.Sprintf("https://api.github.com/repos/%v/%v/releases/latest", owner, repo)
+ resp, err := getReqBody(http.DefaultClient, link)
+ if err != nil {
+ return semver.Version{}, "", err
+ }
+ // gjson is used in pull too. So being used again here!
+ latest := gjson.GetBytes(resp, "tag_name").String()
+ lVers, err := semver.ParseTolerant(latest)
+ releaseNotes := gjson.GetBytes(resp, "body").String()
+
+ return lVers, releaseNotes, err
+}
+
// SelfUpgrade downloads latest release and overwrites current binary
// Copied from https://github.com/yitsushi/totp-cli/blob/master/command/update.go
-func SelfUpgrade(url string) error {
- resp, err := http.Get(url)
+func SelfUpgrade(owner, repo, vers string) error {
+ // compile link from passed parameters to fetch binary matching current build
+ link := fmt.Sprintf("https://github.com/%v/%v/releases/download/v%v/cf_%v_%v.tar.gz",
+ owner, repo, vers, runtime.GOOS, runtime.GOARCH)
+
+ resp, err := http.Get(link)
if err != nil {
return err
}
diff --git a/client/watch.go b/client/watch.go
index 58b0f1c..9cab4bd 100644
--- a/client/watch.go
+++ b/client/watch.go
@@ -2,10 +2,8 @@ package cln
import (
cfg "cf/config"
- pkg "cf/packages"
"bytes"
- "fmt"
"net/url"
"path"
"strings"
@@ -34,16 +32,15 @@ type (
func WatchSubmissions(contest, query string, link url.URL) ([]Submission, error) {
// This implementation contains redirection prevention
c := cfg.Session.Client
- c.CheckRedirect = pkg.RedirectCheck
+ c.CheckRedirect = redirectCheck
// fetch all submissions in contest
link.Path = path.Join(link.Path, "my")
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return nil, err
} else if len(body) == 0 {
// such page doesn't exist
- err = fmt.Errorf("Contest %v doesn't exist", contest)
- return nil, err
+ return nil, ErrContestNotExists
}
// to hold all submissions
var data []Submission
@@ -54,14 +51,14 @@ func WatchSubmissions(contest, query string, link url.URL) ([]Submission, error)
sel.Each(func(_ int, row *goquery.Selection) {
// select cell ...type(x) from row
data = append(data, Submission{
- ID: pkg.GetText(row, "td:nth-of-type(1)"),
- When: pkg.GetText(row, "td:nth-of-type(2)"),
- Name: pkg.GetText(row, "td:nth-of-type(4)"),
- Lang: pkg.GetText(row, "td:nth-of-type(5)"),
- Waiting: pkg.GetAttr(row, "td:nth-of-type(6)", "waiting"),
- Verdict: pkg.GetText(row, "td:nth-of-type(6)"),
- Time: pkg.GetText(row, "td:nth-of-type(7)"),
- Memory: pkg.GetText(row, "td:nth-of-type(8)"),
+ ID: getText(row, "td:nth-of-type(1)"),
+ When: getText(row, "td:nth-of-type(2)"),
+ Name: getText(row, "td:nth-of-type(4)"),
+ Lang: getText(row, "td:nth-of-type(5)"),
+ Waiting: getAttr(row, "td:nth-of-type(6)", "waiting"),
+ Verdict: getText(row, "td:nth-of-type(6)"),
+ Time: getText(row, "td:nth-of-type(7)"),
+ Memory: getText(row, "td:nth-of-type(8)"),
})
})
@@ -72,15 +69,14 @@ func WatchSubmissions(contest, query string, link url.URL) ([]Submission, error)
func WatchContest(contest string, link url.URL) ([]Problem, error) {
// This implementation contains redirection prevention
c := cfg.Session.Client
- c.CheckRedirect = pkg.RedirectCheck
+ c.CheckRedirect = redirectCheck
// fetch contest dashboard page
- body, err := pkg.GetReqBody(&c, link.String())
+ body, err := getReqBody(&c, link.String())
if err != nil {
return nil, err
} else if len(body) == 0 {
// such page doesn't exist
- err = fmt.Errorf("Contest %v doesn't exist", contest)
- return nil, err
+ return nil, ErrContestNotExists
}
// to hold all problems in contest
var data []Problem
@@ -89,9 +85,9 @@ func WatchContest(contest string, link url.URL) ([]Problem, error) {
doc.Find(".problems tr").Has("td").Each(func(_ int, row *goquery.Selection) {
data = append(data, Problem{
- ID: pkg.GetText(row, "td:nth-of-type(1)"),
- Name: pkg.GetText(row, "td:nth-of-type(2) a"),
- Count: pkg.GetText(row, "td:nth-of-type(4)"),
+ ID: getText(row, "td:nth-of-type(1)"),
+ Name: getText(row, "td:nth-of-type(2) a"),
+ Count: getText(row, "td:nth-of-type(4)"),
Status: row.AttrOr("class", ""),
})
})
diff --git a/cmd/config.go b/cmd/config.go
index 14025f3..33db9a9 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -3,7 +3,6 @@ package cmd
import (
cln "cf/client"
cfg "cf/config"
- pkg "cf/packages"
"fmt"
"net/url"
@@ -27,7 +26,7 @@ func (opt Opts) RunConfig() {
"Other misc preferences",
},
}, &choice, survey.WithValidator(survey.Required))
- pkg.PrintError(err, "")
+ PrintError(err, "")
switch choice {
case 0:
@@ -45,8 +44,8 @@ func (opt Opts) RunConfig() {
func login() {
// check if logged in user exists
if cfg.Session.Handle != "" {
- pkg.Log.Success("Current user: " + cfg.Session.Handle)
- pkg.Log.Warning("Current session will be overwritten")
+ Log.Success("Current user: " + cfg.Session.Handle)
+ Log.Warning("Current session will be overwritten")
}
// take input of username / password
creds := struct{ Usr, Passwd string }{}
@@ -61,19 +60,19 @@ func login() {
Validate: survey.Required,
},
}, &creds)
- pkg.PrintError(err, "")
+ PrintError(err, "")
// login and check login status
- pkg.Log.Info("Logging in")
+ Log.Info("Logging in")
flag, err := cln.Login(creds.Usr, creds.Passwd)
- pkg.PrintError(err, "Login failed")
+ PrintError(err, "Login failed")
// login was successful
if flag == true {
- pkg.Log.Success("Login successful")
- pkg.Log.Notice("Welcome " + cfg.Session.Handle)
+ Log.Success("Login successful")
+ Log.Notice("Welcome " + cfg.Session.Handle)
} else {
// login failed
- pkg.Log.Error("Login failed")
- pkg.Log.Notice("Check credentials and retry")
+ Log.Error("Login failed")
+ Log.Notice("Check credentials and retry")
}
return
}
@@ -83,7 +82,7 @@ func addTmplt() {
for name := range cln.LangID {
lName = append(lName, name)
}
- pkg.Log.Info("For detailed instructions, read https://github.com/infixint943/cf/wiki/Configuration")
+ Log.Info("For detailed instructions, read https://github.com/cp-tools/cf/wiki/Configuration")
tmplt := cfg.Template{}
err := survey.Ask([]*survey.Question{
{
@@ -164,7 +163,7 @@ func addTmplt() {
},
},
}, &tmplt)
- pkg.PrintError(err, "")
+ PrintError(err, "")
// set ext and langid values manually
tmplt.Ext = filepath.Ext(tmplt.Path)
tmplt.LangID = cln.LangID[tmplt.LangName]
@@ -172,7 +171,7 @@ func addTmplt() {
cfg.Templates = append(cfg.Templates, tmplt)
cfg.SaveTemplates()
- pkg.Log.Success("Template saved successfully")
+ Log.Success("Template saved successfully")
return
}
@@ -180,7 +179,7 @@ func remTmplt() {
// check if any templates are present
sz := len(cfg.Templates)
if sz == 0 {
- pkg.Log.Error("No configured template's exist")
+ Log.Error("No configured template's exist")
return
}
@@ -189,19 +188,19 @@ func remTmplt() {
Message: "Template you want to remove:",
Options: cfg.ListTmplts(cfg.Templates...),
}, &idx)
- pkg.PrintError(err, "")
+ PrintError(err, "")
// delete the template from the slice
// and reconfigure default template settings
cfg.Templates = append(cfg.Templates[:idx], cfg.Templates[idx+1:]...)
if cfg.Settings.DfltTmplt == idx {
- pkg.Log.Warning("Default template configurations reset")
+ Log.Warning("Default template configurations reset")
cfg.Settings.DfltTmplt = -1
cfg.Settings.GenOnFetch = false
cfg.SaveSettings()
}
cfg.SaveTemplates()
- pkg.Log.Success("Templated removed successfully")
+ Log.Success("Templated removed successfully")
return
}
@@ -217,7 +216,7 @@ func miscPrefs() {
"Set workspace name",
},
}, &choice)
- pkg.PrintError(err, "")
+ PrintError(err, "")
switch choice {
case 0:
@@ -227,7 +226,7 @@ func miscPrefs() {
Options: append([]string{"None"}, cfg.ListTmplts(cfg.Templates...)...),
}, &cfg.Settings.DfltTmplt)
cfg.Settings.DfltTmplt--
- pkg.PrintError(err, "")
+ PrintError(err, "")
case 1:
// set GenOnFetch
@@ -237,7 +236,7 @@ func miscPrefs() {
"Default template has to be configured for this feature to work",
Default: false,
}, &cfg.Settings.GenOnFetch)
- pkg.PrintError(err, "")
+ PrintError(err, "")
case 2:
// set host domain
@@ -250,7 +249,7 @@ func miscPrefs() {
_, err := url.ParseRequestURI(ans.(string))
return err
}))
- pkg.PrintError(err, "")
+ PrintError(err, "")
case 3:
// validate and set proxy
@@ -268,7 +267,7 @@ func miscPrefs() {
_, err := url.ParseRequestURI(ans.(string))
return err
}))
- pkg.PrintError(err, "")
+ PrintError(err, "")
case 4:
err := survey.AskOne(&survey.Input{
@@ -277,10 +276,10 @@ func miscPrefs() {
"A root directory will be created of this name, and all problems will be fetched here.\n" +
"Current configured workspace name: " + cfg.Settings.WSName,
}, &cfg.Settings.WSName, survey.WithValidator(survey.Required))
- pkg.PrintError(err, "")
+ PrintError(err, "")
}
cfg.SaveSettings()
- pkg.Log.Success("Configurations successfully set")
+ Log.Success("Configurations successfully set")
return
}
diff --git a/cmd/fetch.go b/cmd/fetch.go
index b37d53c..9dc155f 100644
--- a/cmd/fetch.go
+++ b/cmd/fetch.go
@@ -3,7 +3,6 @@ package cmd
import (
cln "cf/client"
cfg "cf/config"
- pkg "cf/packages"
"fmt"
"os"
@@ -15,37 +14,37 @@ import (
func (opt Opts) RunFetch() {
// check if contest id is present
if opt.contest == "" {
- pkg.Log.Error("No contest id found")
+ Log.Error("No contest id found")
return
}
// fetch countdown info
- pkg.Log.Info("Fetching details of " + opt.contClass + " " + opt.contest)
+ Log.Info("Fetching details of " + opt.contClass + " " + opt.contest)
dur, err := cln.FindCountdown(opt.contest, opt.link)
- pkg.PrintError(err, "Extraction of countdown failed")
+ PrintError(err, "Extraction of countdown failed")
// contest not yet started
// countdown till it starts
if dur > 0 {
- pkg.Log.Warning("Contest hasn't started")
- pkg.Log.Info("Launching countdown to start")
+ Log.Warning("Contest hasn't started")
+ Log.Info("Launching countdown to start")
startCountdown(dur)
// open problems page (once parsing is over)
// page will be opened only for live rounds
defer opt.RunOpen()
}
// Fetch ALL problems from contest page
- pkg.Log.Info("Fetching problems...")
+ Log.Info("Fetching problems...")
probs, err := cln.FetchProbs(opt.contest, opt.link)
- pkg.PrintError(err, "Extraction of contest problems failed")
+ PrintError(err, "Extraction of contest problems failed")
// Fetch all tests from problems page
splInp, splOut, err := cln.FetchTests(opt.contest, "", opt.link)
- pkg.PrintError(err, "Failed to extract sample tests")
+ PrintError(err, "Failed to extract sample tests")
// no sample tests found, try parsing from each problem
if len(splInp) == 0 {
- pkg.Log.Warning("Failed to fetch tests from problems page")
- pkg.Log.Info("Fetching from page of every problem")
- pkg.Log.Notice("Please be patient")
+ Log.Warning("Failed to fetch tests from problems page")
+ Log.Info("Fetching from page of every problem")
+ Log.Notice("Please be patient")
// iterate over all present problems
for _, prob := range probs {
// Problem isn't specified to be fetched
@@ -56,13 +55,13 @@ func (opt Opts) RunFetch() {
continue
}
probInp, probOut, err := cln.FetchTests(opt.contest, prob, opt.link)
- pkg.PrintError(err, "Failed to extract sample tests of "+prob)
+ PrintError(err, "Failed to extract sample tests of "+prob)
// append sample tests to slice
splInp = append(splInp, probInp...)
splOut = append(splOut, probOut...)
// if problem is pdf format (can't extract tests)
if len(probInp) == 0 {
- pkg.Log.Warning("Unable to extract test(s) - " + prob)
+ Log.Warning("Unable to extract test(s) - " + prob)
splInp = append(splInp, make([]string, 0))
splOut = append(splOut, make([]string, 0))
}
@@ -86,11 +85,11 @@ func (opt Opts) RunFetch() {
// create tests
for x := 0; x < len(splInp[i]); x++ {
// create input file (form x.in)
- pkg.CreateFile(splInp[i][x], fmt.Sprintf("%v/%d.in", path, x))
+ CreateFile(splInp[i][x], fmt.Sprintf("%v/%d.in", path, x))
// create output file (form x.ans)
- pkg.CreateFile(splOut[i][x], fmt.Sprintf("%v/%d.out", path, x))
+ CreateFile(splOut[i][x], fmt.Sprintf("%v/%d.out", path, x))
}
- pkg.Log.Success(fmt.Sprintf("Fetched %d test(s) - %v", len(splInp[i]), prob))
+ Log.Success(fmt.Sprintf("Fetched %d test(s) - %v", len(splInp[i]), prob))
// generate code files if specified
idx := cfg.Settings.DfltTmplt
if cfg.Settings.GenOnFetch == true && idx != -1 {
@@ -108,15 +107,15 @@ func (opt Opts) RunFetch() {
// startCountdown starts countdown of dur seconds
func startCountdown(dur int64) {
// run timer till it runs out
- pkg.LiveUI.Start()
+ LiveUI.Start()
for ; dur > 0; dur-- {
h := fmt.Sprintf("%d:", dur/(60*60))
m := fmt.Sprintf("0%d:", (dur/60)%60)
s := fmt.Sprintf("0%d", dur%60)
- pkg.LiveUI.Print(h + m[len(m)-3:] + s[len(s)-2:])
+ LiveUI.Print(h + m[len(m)-3:] + s[len(s)-2:])
time.Sleep(time.Second)
}
// remove timer data from screen
- pkg.LiveUI.Print()
+ LiveUI.Print()
return
}
diff --git a/cmd/gen.go b/cmd/gen.go
index 7d9e103..c2aca1f 100644
--- a/cmd/gen.go
+++ b/cmd/gen.go
@@ -2,7 +2,6 @@ package cmd
import (
cfg "cf/config"
- pkg "cf/packages"
"fmt"
"io/ioutil"
@@ -17,7 +16,7 @@ import (
func (opt Opts) RunGen() {
// check if any templates exist
if len(cfg.Templates) == 0 {
- pkg.Log.Error("No configured template's exist")
+ Log.Error("No configured template's exist")
return
}
// index of template config to use
@@ -30,7 +29,7 @@ func (opt Opts) RunGen() {
Message: "Select template to generate:",
Options: cfg.ListTmplts(cfg.Templates...),
}, &idx)
- pkg.PrintError(err, "")
+ PrintError(err, "")
}
// create template in current folder
// leaving path to "" creates file in curr directory
@@ -42,7 +41,7 @@ func (opt Opts) RunGen() {
func (opt Opts) GenCode(t *cfg.Template, path string) {
// read template code file
file, err := ioutil.ReadFile(t.Path)
- pkg.PrintError(err, "Failed to read template file")
+ PrintError(err, "Failed to read template file")
// clean template code (replace placeholders)
e := Env{
Contest: opt.contest,
@@ -62,11 +61,11 @@ func (opt Opts) GenCode(t *cfg.Template, path string) {
// check if file already exists
if _, err := os.Stat(filepath.Join(path, name)); os.IsNotExist(err) {
- pkg.CreateFile(source, filepath.Join(path, name))
- pkg.Log.Notice("File " + name + " generated")
+ CreateFile(source, filepath.Join(path, name))
+ Log.Notice("File " + name + " generated")
break
}
- pkg.Log.Warning("File " + name + " exists")
+ Log.Warning("File " + name + " exists")
}
return
}
diff --git a/cmd/misc.go b/cmd/misc.go
index de2db06..86e8437 100644
--- a/cmd/misc.go
+++ b/cmd/misc.go
@@ -2,7 +2,6 @@ package cmd
import (
cfg "cf/config"
- pkg "cf/packages"
"fmt"
"net/url"
@@ -15,11 +14,58 @@ import (
"time"
"github.com/AlecAivazis/survey/v2"
+ "github.com/fatih/color"
+ "github.com/k0kubun/go-ansi"
)
// Version of the current executable
const Version = "1.1.0"
+// Global Variables for different UI formatting
+var (
+ writer = os.Stderr
+ Green = color.New(color.FgGreen)
+ Blue = color.New(color.FgBlue)
+ Red = color.New(color.FgRed)
+ Yellow = color.New(color.FgYellow)
+
+ Log struct {
+ Success, Notice, Info, Error,
+ Warning func(text ...interface{})
+ }
+ // LiveUI to print live data to terminal
+ LiveUI struct {
+ count int
+ Start func()
+ Print func(text ...string)
+ }
+)
+
+func init() {
+ // Initialise colored text output
+ Log.Success = func(text ...interface{}) { Green.Fprintln(writer, text...) }
+ Log.Notice = func(text ...interface{}) { fmt.Fprintln(writer, text...) }
+ Log.Info = func(text ...interface{}) { Blue.Fprintln(writer, text...) }
+ Log.Error = func(text ...interface{}) { Red.Fprintln(writer, text...) }
+ Log.Warning = func(text ...interface{}) { Yellow.Fprintln(writer, text...) }
+
+ // Initialise Live rendering output
+ LiveUI.Start = func() { LiveUI.count = 0 }
+ LiveUI.Print = func(text ...string) {
+ // clear last count lines from terminal
+ for i := 0; i < LiveUI.count; i++ {
+ ansi.CursorPreviousLine(1)
+ ansi.EraseInLine(2)
+ }
+ // count number of lines in text
+ LiveUI.count = 1
+ for _, str := range text {
+ LiveUI.count += strings.Count(str, "\n")
+ fmt.Println(str)
+ }
+ }
+}
+
type (
// Opts is struct docopt binds flag data to
Opts struct {
@@ -216,7 +262,7 @@ func selSourceFile(files []string) (string, error) {
Message: "Source file:",
Options: files,
}, &file)
- pkg.PrintError(err, "")
+ PrintError(err, "")
return file, nil
}
@@ -237,7 +283,7 @@ func selTmpltConfig(tmplt []cfg.Template) (*cfg.Template, error) {
Message: "Template configuration:",
Options: cfg.ListTmplts(tmplt...),
}, &idx)
- pkg.PrintError(err, "")
+ PrintError(err, "")
return &tmplt[idx], nil
}
@@ -250,20 +296,43 @@ func prettyVerdict(verdict string) string {
switch {
case strings.HasPrefix(verdict, "TLE"):
- return pkg.Yellow.Sprint(verdict)
+ return Yellow.Sprint(verdict)
case strings.HasPrefix(verdict, "MLE"):
- return pkg.Red.Sprint(verdict)
+ return Red.Sprint(verdict)
case strings.HasPrefix(verdict, "WA"):
- return pkg.Red.Sprint(verdict)
+ return Red.Sprint(verdict)
case strings.HasPrefix(verdict, "Pretests passed"):
- return pkg.Green.Sprint(verdict)
+ return Green.Sprint(verdict)
case strings.HasPrefix(verdict, "Accepted"):
- return pkg.Green.Sprint(verdict)
+ return Green.Sprint(verdict)
default:
return verdict
}
}
+// PrintError outputs error (with custom message)
+// and exits the program execution (if err != nil)
+func PrintError(err error, desc string) {
+ if err != nil {
+ if desc != "" {
+ Log.Error(desc)
+ }
+ Log.Error(err.Error())
+ os.Exit(0)
+ }
+}
+
+// CreateFile copies data to dst (create if not exists)
+// Returns absolute path to destination file
+func CreateFile(data, dst string) string {
+ out, err := os.Create(dst)
+ PrintError(err, "File "+dst+" couldn't be created!")
+ defer out.Close()
+
+ out.WriteString(data)
+ return dst
+}
+
/*
Parsing structure of problems
-----------------------------
diff --git a/cmd/open.go b/cmd/open.go
index 62b5c4b..e8b5f78 100644
--- a/cmd/open.go
+++ b/cmd/open.go
@@ -1,8 +1,6 @@
package cmd
import (
- pkg "cf/packages"
-
"os/exec"
"path"
"runtime"
@@ -12,7 +10,7 @@ import (
func (opt Opts) RunOpen() {
// check if contest id is present
if opt.contest == "" {
- pkg.Log.Error("No contest id found")
+ Log.Error("No contest id found")
return
}
link := opt.link
diff --git a/cmd/pull.go b/cmd/pull.go
index 7b017e8..90e813b 100644
--- a/cmd/pull.go
+++ b/cmd/pull.go
@@ -2,7 +2,6 @@ package cmd
import (
cln "cf/client"
- pkg "cf/packages"
"fmt"
"os"
@@ -18,16 +17,16 @@ import (
// RunPull is called on running cf pull
func (opt Opts) RunPull() {
// fetch all submissions matching criteria
- pkg.Log.Success("Pulling submissions of: " + opt.Handle)
+ Log.Success("Pulling submissions of: " + opt.Handle)
// fetch all submissions matching criteria
subs, err := cln.FetchSubs(opt.contest, opt.problem, opt.Handle)
- pkg.PrintError(err, "Failed to extract submission status")
+ PrintError(err, "Failed to extract submission status")
for _, sub := range subs {
// fetch source code
source, err := sub.FetchSubSource()
if err != nil {
- pkg.Log.Error("Failed to pull source code:" + sub.Sid)
+ Log.Error("Failed to pull source code:" + sub.Sid)
continue
}
@@ -50,8 +49,8 @@ func (opt Opts) RunPull() {
cwd, _ := os.Getwd()
relPath := strings.TrimPrefix(filepath.Join(path, name), cwd)
- pkg.CreateFile(source, filepath.Join(path, name))
- pkg.Log.Success(fmt.Sprintf("Fetched %v %v to .%v",
+ CreateFile(source, filepath.Join(path, name))
+ Log.Success(fmt.Sprintf("Fetched %v %v to .%v",
sub.Contest, sub.Problem, relPath))
break
}
diff --git a/cmd/submit.go b/cmd/submit.go
index b9be8d0..f11ebab 100644
--- a/cmd/submit.go
+++ b/cmd/submit.go
@@ -3,7 +3,6 @@ package cmd
import (
cln "cf/client"
cfg "cf/config"
- pkg "cf/packages"
"time"
@@ -14,50 +13,50 @@ import (
func (opt Opts) RunSubmit() {
// check if problem id is present
if opt.problem == "" {
- pkg.Log.Error("No problem id found")
+ Log.Error("No problem id found")
return
}
// find code file to submit
file, err := selSourceFile(cln.FindSourceFiles(opt.File))
- pkg.PrintError(err, "Failed to select source file")
+ PrintError(err, "Failed to select source file")
// find template config to use
t, err := selTmpltConfig(cln.FindTmpltsConfig(file))
- pkg.PrintError(err, "Failed to select template configuration")
+ PrintError(err, "Failed to select template configuration")
// check login status
usr, err := cln.LoggedInUsr()
- pkg.PrintError(err, "Failed to check login status")
+ PrintError(err, "Failed to check login status")
if usr == "" {
// exit if no saved login configurations found
if cfg.Session.Handle == "" || cfg.Session.Passwd == "" {
- pkg.Log.Error("No login details configured")
- pkg.Log.Notice("Configure login details through cf config")
+ Log.Error("No login details configured")
+ Log.Notice("Configure login details through cf config")
return
}
// attempt relogin
- pkg.Log.Warning("No logged in user session found")
- pkg.Log.Info("Attempting relogin: " + cfg.Session.Handle)
+ Log.Warning("No logged in user session found")
+ Log.Info("Attempting relogin: " + cfg.Session.Handle)
status, err := cln.Relogin()
- pkg.PrintError(err, "Failed to login")
+ PrintError(err, "Failed to login")
if status == true {
// logged in successfully
- pkg.Log.Success("Login successful")
+ Log.Success("Login successful")
} else {
- pkg.Log.Error("Login failed")
- pkg.Log.Notice("Configure login details through 'cf config'")
+ Log.Error("Login failed")
+ Log.Notice("Configure login details through 'cf config'")
return
}
} else {
// output handle details of current user
// this is in else loop, since current user is already
// being displayed during relogin above
- pkg.Log.Notice("Current user: " + usr)
+ Log.Notice("Current user: " + usr)
}
// main submit code runs here
err = cln.Submit(opt.contest, opt.problem, t.LangID, file, opt.link)
- pkg.PrintError(err, "Failed to submit source code")
- pkg.Log.Success("Submitted")
+ PrintError(err, "Failed to submit source code")
+ Log.Success("Submitted")
// watch submission verdict
opt.watch()
@@ -66,14 +65,14 @@ func (opt Opts) RunSubmit() {
func (opt Opts) watch() {
// infinite loop till verdicts declared
- pkg.LiveUI.Start()
+ LiveUI.Start()
for query := opt.problem; ; {
// query param to fetch submitted code verdict and not latest verdict in prob
// fetch submission status from contest every second
start := time.Now()
data, err := cln.WatchSubmissions(opt.contest, query, opt.link)
- pkg.PrintError(err, "Failed to extract submissions in contest.")
+ PrintError(err, "Failed to extract submissions in contest.")
sub := data[0]
query = sub.ID
@@ -85,10 +84,10 @@ func (opt Opts) watch() {
if sub.Waiting == "false" {
tbl.AddRow("Memory:", sub.Memory)
tbl.AddRow("Time:", sub.Time)
- pkg.LiveUI.Print(tbl.String())
+ LiveUI.Print(tbl.String())
break
}
- pkg.LiveUI.Print(tbl.String())
+ LiveUI.Print(tbl.String())
// sleep for 1 second
time.Sleep(time.Second - time.Since(start))
}
diff --git a/cmd/test.go b/cmd/test.go
index 0a886ad..2ab4d32 100644
--- a/cmd/test.go
+++ b/cmd/test.go
@@ -3,21 +3,24 @@ package cmd
import (
cln "cf/client"
cfg "cf/config"
- pkg "cf/packages"
+ "fmt"
"os"
"os/exec"
"strings"
+
+ "github.com/fatih/color"
+ "github.com/gosuri/uitable"
)
// RunTest is called on running `cf test`
func (opt Opts) RunTest() {
// find code file to test
file, err := selSourceFile(cln.FindSourceFiles(opt.File))
- pkg.PrintError(err, "Failed to select source file")
+ PrintError(err, "Failed to select source file")
// find template configs to use
t, err := selTmpltConfig(cln.FindTmpltsConfig(file))
- pkg.PrintError(err, "Failed to select template configuration")
+ PrintError(err, "Failed to select template configuration")
// main testing starts here!!
e := Env{
@@ -32,10 +35,10 @@ func (opt Opts) RunTest() {
if t.PreScript != "" {
// replace placeholders in script
script := e.ReplPlaceholder(t.PreScript)
- pkg.Log.Notice(script)
+ Log.Notice(script)
// run script with timer of 20 secs
_, _, err := cln.ExecScript(script, "", 1e9)
- pkg.PrintError(err, "")
+ PrintError(err, "")
}
if opt.Custom == false {
@@ -50,10 +53,10 @@ func (opt Opts) RunTest() {
if t.PostScript != "" {
// replace placeholders in script
script := e.ReplPlaceholder(t.PostScript)
- pkg.Log.Notice(script)
+ Log.Notice(script)
// run script with timer of 20 secs
_, _, err := cln.ExecScript(script, "", 1e9)
- pkg.PrintError(err, "")
+ PrintError(err, "")
}
return
}
@@ -63,7 +66,7 @@ func (opt Opts) RunTest() {
func (opt Opts) tradJudge(t cfg.Template, e Env) {
// fetch test cases from current directory
inp, out, err := cln.FindTests()
- pkg.PrintError(err, "Failed to parse sample tests")
+ PrintError(err, "Failed to parse sample tests")
// run judge for each test file
for i := 0; i < len(inp); i++ {
@@ -76,21 +79,21 @@ func (opt Opts) tradJudge(t cfg.Template, e Env) {
switch {
case elapsed.Seconds() >= float64(opt.Tl):
// print TLE message (add support for custom time limit)
- pkg.Yellow.Printf("#%d: TLE .... %v\n", i, elapsed.String())
+ Yellow.Printf("#%d: TLE .... %v\n", i, elapsed.String())
case err != nil:
// print RTE message with error data
- pkg.Red.Printf("#%d: RTE .... %v\n", i, err.Error())
+ Red.Printf("#%d: RTE .... %v\n", i, err.Error())
case stdout != out[i]:
// print WA message and diff output
- pkg.Red.Printf("#%d: WA .... %v\n", i, elapsed.String())
- diff := cln.PrintDiff(inp[i], stdout, out[i])
- pkg.Log.Info(diff)
+ Red.Printf("#%d: WA .... %v\n", i, elapsed.String())
+ diff := printDiff(inp[i], stdout, out[i])
+ Log.Info(diff)
default:
// print AC message
- pkg.Green.Printf("#%d: AC .... %v\n", i, elapsed.String())
+ Green.Printf("#%d: AC .... %v\n", i, elapsed.String())
}
}
return
@@ -114,9 +117,44 @@ func (opt Opts) spclJudge(t cfg.Template, e Env) {
*/
// inform user that interactive judge has started
- pkg.Log.Success("-----Judge begins-----\n")
+ Log.Success("-----Judge begins-----\n")
cmd.Run()
- pkg.Log.Success("\n-----Judge closed-----")
+ Log.Success("\n-----Judge closed-----")
return
}
+
+// printDiff is run if outputs don't match
+// returns input data, and then the diff of => out vs ans
+func printDiff(inp, out, ans string) string {
+ // variable to hold diff output
+ var diff strings.Builder
+ headerfmt := Blue.Add(color.Underline).SprintfFunc()
+ // print input data
+ fmt.Fprintln(&diff, headerfmt("Input"))
+ fmt.Fprintln(&diff, inp)
+
+ // break output into lines
+ str1 := strings.Split(out, "\n")
+ str2 := strings.Split(ans, "\n")
+ // equalize string lengths
+ if len(str1) < len(str2) {
+ str1 = append(str1, make([]string, len(str2)-len(str1))...)
+ } else {
+ str2 = append(str2, make([]string, len(str1)-len(str2))...)
+ }
+
+ // print output diff data
+ tbl := uitable.New()
+ tbl.Separator = " | "
+
+ tbl.AddRow(headerfmt("Actual Output"), headerfmt("Expected Output"))
+ // iterate over every row of outputs
+ for i := 0; i < len(str1); i++ {
+ tbl.AddRow(str1[i], str2[i])
+ }
+ fmt.Fprintln(&diff, tbl)
+ fmt.Fprintln(&diff)
+
+ return diff.String()
+}
diff --git a/cmd/upgrade.go b/cmd/upgrade.go
index 55bcf7b..720fc2a 100644
--- a/cmd/upgrade.go
+++ b/cmd/upgrade.go
@@ -2,59 +2,42 @@ package cmd
import (
cln "cf/client"
- pkg "cf/packages"
-
- "fmt"
- "net/http"
- "runtime"
"github.com/AlecAivazis/survey/v2"
"github.com/blang/semver"
- "github.com/tidwall/gjson"
)
// RunUpgrade is called on running `cf upgrade`
func RunUpgrade() {
- // parse current version
- cVers := semver.MustParse(Version)
- // determine latest release version using github API
- link := "https://api.github.com/repos/infixint943/cf/releases/latest"
- resp, err := pkg.GetReqBody(&http.Client{}, link)
- pkg.PrintError(err, "Failed to fetch latest release")
-
- // check version of latest release from API resp
- latest := gjson.GetBytes(resp, "tag_name").String()
- lVers := semver.MustParse(latest[1:])
+ // parse current version from set version number
+ cVers, _ := semver.ParseTolerant(Version)
+ lVers, releaseNotes, err := cln.FetchLatest("cp-tools", "cf")
+ PrintError(err, "Failed to fetch latest release")
// check if current release is same as latest release
if cVers.GTE(lVers) {
- pkg.Log.Success(fmt.Sprintf("Current version (v%v) is the latest", cVers.String()))
+ Log.Success("Current version (v" + cVers.String() + ") is the latest")
return
}
// new release found (fetch and print release notes)
- releaseNotes := gjson.GetBytes(resp, "body").String()
- pkg.Log.Success(fmt.Sprintf("New release (v%v) found", lVers.String()))
- pkg.Log.Notice(releaseNotes)
- fmt.Println()
+ Log.Success("New release (v" + lVers.String() + ") found")
+ Log.Notice(releaseNotes, "\n")
prompt := true
err = survey.AskOne(&survey.Confirm{
- Message: fmt.Sprintf("Do you wish to upgrade from v%v to v%v?",
- cVers.String(), lVers.String()),
+ Message: "Upgrade from v" + cVers.String() + " to v" + lVers.String(),
Default: true,
}, &prompt)
- pkg.PrintError(err, "")
+ PrintError(err, "")
+
if prompt == false {
- pkg.Log.Info("Tool not upgraded")
+ Log.Info("Tool not upgraded")
return
}
- // url of tar file to download
- link = fmt.Sprintf("https://github.com/infixint943/cf/releases/download/%v/cf_%v_%v.tar.gz",
- latest, runtime.GOOS, runtime.GOARCH)
- pkg.Log.Info("Downloading update. Please wait.")
- err = cln.SelfUpgrade(link)
- pkg.PrintError(err, "Failed to update tool")
+ Log.Info("Downloading update. Please wait.")
+ err = cln.SelfUpgrade("cp-tools", "cf", lVers.String())
+ PrintError(err, "Failed to update tool")
- pkg.Log.Success("Successfully updated to v" + lVers.String())
+ Log.Success("Successfully updated to v" + lVers.String())
return
}
diff --git a/cmd/watch.go b/cmd/watch.go
index 350ba29..f683218 100644
--- a/cmd/watch.go
+++ b/cmd/watch.go
@@ -2,7 +2,6 @@ package cmd
import (
cln "cf/client"
- pkg "cf/packages"
"fmt"
"strings"
@@ -16,11 +15,11 @@ import (
func (opt Opts) RunWatch() {
// check if contest id is present
if opt.contest == "" {
- pkg.Log.Error("No contest id found")
+ Log.Error("No contest id found")
return
}
// header formatting for table
- headerfmt := pkg.Blue.Add(color.Underline).SprintfFunc()
+ headerfmt := Blue.Add(color.Underline).SprintfFunc()
if opt.SubCnt == 0 {
// submissions aren't specified to be parsed
@@ -28,7 +27,7 @@ func (opt Opts) RunWatch() {
// fetch contest solve status
data, err := cln.WatchContest(opt.contest, opt.link)
- pkg.PrintError(err, "Failed to extract contest solve status")
+ PrintError(err, "Failed to extract contest solve status")
// init table with header + color
tbl := uitable.New()
@@ -53,9 +52,9 @@ func (opt Opts) RunWatch() {
clean := func(status string) string {
switch status {
case "accepted-problem":
- return pkg.Green.Sprint("AC")
+ return Green.Sprint("AC")
case "rejected-problem":
- return pkg.Red.Sprint("RE")
+ return Red.Sprint("RE")
default:
return "NA"
}
@@ -67,13 +66,13 @@ func (opt Opts) RunWatch() {
} else {
// infinite loop till verdicts declared
- pkg.LiveUI.Start()
+ LiveUI.Start()
for {
// timer to fetch data in interval of 1 second
start := time.Now()
// fetch contest submission status
data, err := cln.WatchSubmissions(opt.contest, opt.problem, opt.link)
- pkg.PrintError(err, "Failed to extract submissions in contest")
+ PrintError(err, "Failed to extract submissions in contest")
// create new table
tbl := uitable.New()
@@ -97,7 +96,7 @@ func (opt Opts) RunWatch() {
isPending = true
}
}
- pkg.LiveUI.Print(tbl.String())
+ LiveUI.Print(tbl.String())
if isPending == false {
break
diff --git a/config/sessions.go b/config/sessions.go
index db5c231..f8e20f7 100644
--- a/config/sessions.go
+++ b/config/sessions.go
@@ -1,8 +1,6 @@
package cfg
import (
- pkg "cf/packages"
-
"encoding/json"
"io/ioutil"
"net/http"
@@ -12,34 +10,42 @@ import (
"github.com/infixint943/cookiejar"
)
-// Session holds cookies and other request header data
-// password is AES encrypted and stored securely
-var Session struct {
- Handle string `json:"handle"`
- Passwd string `json:"password"`
- Cookies *cookiejar.Jar `json:"cookies"`
- Client http.Client `json:"-"`
-}
+var (
+ // Session holds cookies and other request header data
+ // password is AES encrypted and stored securely
+ Session struct {
+ Handle string `json:"handle"`
+ Passwd string `json:"password"`
+ Cookies *cookiejar.Jar `json:"cookies"`
+ Client http.Client `json:"-"`
+ }
+ // path to sessions.json file
+ sessPath string
+)
-var sessPath string
+// set default values of Session struct
+func init() {
+ Session.Handle = ""
+ Session.Passwd = ""
+ Session.Cookies, _ = cookiejar.New(nil)
+ Session.Client = *http.DefaultClient
+}
// InitSession reads data from sessions.json
-func InitSession(path string) {
+func InitSession(path string) error {
// set sessions.json file path
sessPath = path
- Session.Handle = ""
- Session.Cookies, _ = cookiejar.New(nil)
- proxyURL := http.ProxyFromEnvironment
-
- file, err := ioutil.ReadFile(sessPath)
+ file, err := os.OpenFile(sessPath, os.O_RDWR|os.O_CREATE, 0666)
+ defer file.Close()
if err != nil {
- pkg.Log.Warning("File sessions.json doesn't exist")
- pkg.Log.Info("Creating sessions.json file...")
- SaveSession()
+ return err
}
- json.Unmarshal(file, &Session)
- // configure proxy if set
+
+ body, _ := ioutil.ReadAll(file)
+ json.Unmarshal(body, &Session)
+ // proxy configuration
+ proxyURL := http.ProxyFromEnvironment
if Settings.Proxy != "" {
proxy, _ := url.Parse(Settings.Proxy)
proxyURL = http.ProxyURL(proxy)
@@ -48,14 +54,19 @@ func InitSession(path string) {
// instantiate client with proxy configurations
Session.Client = http.Client{Jar: Session.Cookies,
Transport: &http.Transport{Proxy: proxyURL}}
+
+ return nil
}
// SaveSession saves the data to sessions.json
-func SaveSession() {
+func SaveSession() error {
// create sessions.json file and log err (if any)
- file, err := os.Create(sessPath)
- pkg.PrintError(err, "Failed to create sessions.json file")
+ file, err := os.OpenFile(sessPath, os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0666)
+ if err != nil {
+ return err
+ }
body, _ := json.MarshalIndent(Session, "", "\t")
file.Write(body)
+ return nil
}
diff --git a/config/settings.go b/config/settings.go
index 7e59677..4ff8d33 100644
--- a/config/settings.go
+++ b/config/settings.go
@@ -1,52 +1,58 @@
package cfg
import (
- pkg "cf/packages"
-
"encoding/json"
"io/ioutil"
"os"
)
-// Settings holds configured settings data of the tool
-var Settings struct {
- DfltTmplt int `json:"default_template"`
- GenOnFetch bool `json:"gen_on_fetch"`
- Host string `json:"host"`
- Proxy string `json:"proxy"`
- WSName string `json:"workspace_name"`
-}
+var (
+ // Settings holds configured settings data of the tool
+ Settings struct {
+ DfltTmplt int `json:"default_template"`
+ GenOnFetch bool `json:"gen_on_fetch"`
+ Host string `json:"host"`
+ Proxy string `json:"proxy"`
+ WSName string `json:"workspace_name"`
+ }
-var settPath string
+ settPath string
+)
func init() {
// initialise default values of Settings struct
Settings.DfltTmplt = -1
Settings.GenOnFetch = false
- Settings.Host = "https://codeforces/com"
+ Settings.Host = "https://codeforces.com"
Settings.Proxy = ""
Settings.WSName = "codeforces"
}
// InitSettings reads settings.json file
-func InitSettings(path string) {
+func InitSettings(path string) error {
// set settings.json file path
settPath = path
- file, err := ioutil.ReadFile(settPath)
+ file, err := os.OpenFile(settPath, os.O_RDWR|os.O_CREATE, 0666)
+ defer file.Close()
if err != nil {
- pkg.Log.Warning("File settings.json doesn't exist")
- pkg.Log.Info("Creating settings.json file")
- SaveSettings()
+ return err
}
- json.Unmarshal(file, &Settings)
+
+ body, _ := ioutil.ReadAll(file)
+ json.Unmarshal(body, &Settings)
+ return nil
}
// SaveSettings to settings.json file
-func SaveSettings() {
- file, err := os.Create(settPath)
- pkg.PrintError(err, "Failed to create settings.json file")
+func SaveSettings() error {
+ // create settings.json file
+ file, err := os.OpenFile(settPath, os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0666)
+ if err != nil {
+ return err
+ }
body, _ := json.MarshalIndent(Settings, "", "\t")
file.Write(body)
+ return nil
}
diff --git a/config/templates.go b/config/templates.go
index 2320931..af6321c 100644
--- a/config/templates.go
+++ b/config/templates.go
@@ -1,8 +1,6 @@
package cfg
import (
- pkg "cf/packages"
-
"encoding/json"
"io/ioutil"
"os"
@@ -20,32 +18,40 @@ type Template struct {
PostScript string `json:"post_script"`
}
-// Templates holds all configured templates of user
-var Templates []Template
-
-var tmpltPath string
+var (
+ // Templates holds all configured templates of user
+ Templates []Template
+ tmpltPath string
+)
// InitTemplates reads data from templates.json
-func InitTemplates(path string) {
+func InitTemplates(path string) error {
// set templates.json file path
tmpltPath = path
- file, err := ioutil.ReadFile(tmpltPath)
+ file, err := os.OpenFile(tmpltPath, os.O_RDWR|os.O_CREATE, 0666)
+ defer file.Close()
if err != nil {
- pkg.Log.Warning("File templates.json doesn't exist")
- pkg.Log.Info("Creating templates.json file...")
- SaveTemplates()
+ return err
}
- json.Unmarshal(file, &Templates)
+
+ body, _ := ioutil.ReadAll(file)
+ json.Unmarshal(body, &Templates)
+ return nil
+
}
// SaveTemplates to settings.json file
-func SaveTemplates() {
- file, err := os.Create(tmpltPath)
- pkg.PrintError(err, "Failed to create templates.json file")
+func SaveTemplates() error {
+ // create templates.json file
+ file, err := os.OpenFile(tmpltPath, os.O_TRUNC|os.O_RDWR|os.O_CREATE, 0666)
+ if err != nil {
+ return err
+ }
body, _ := json.MarshalIndent(Templates, "", "\t")
file.Write(body)
+ return nil
}
// ListTmplts returns an array of required template aliases
diff --git a/packages/misc.go b/packages/misc.go
deleted file mode 100644
index b4743fd..0000000
--- a/packages/misc.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package pkg
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/PuerkitoBio/goquery"
- "github.com/fatih/color"
- "github.com/k0kubun/go-ansi"
-)
-
-// Global Variables for different UI formatting
-var (
- writer = os.Stderr
- Green = color.New(color.FgGreen)
- Blue = color.New(color.FgBlue)
- Red = color.New(color.FgRed)
- Yellow = color.New(color.FgYellow)
-
- Log struct {
- Success, Notice, Info, Error,
- Warning func(text ...interface{})
- }
- // LiveUI to print live data to terminal
- LiveUI struct {
- count int
- Start func()
- Print func(text ...string)
- }
-)
-
-func init() {
- // Initialise colored text output
- Log.Success = func(text ...interface{}) { Green.Fprintln(writer, text...) }
- Log.Notice = func(text ...interface{}) { fmt.Fprintln(writer, text...) }
- Log.Info = func(text ...interface{}) { Blue.Fprintln(writer, text...) }
- Log.Error = func(text ...interface{}) { Red.Fprintln(writer, text...) }
- Log.Warning = func(text ...interface{}) { Yellow.Fprintln(writer, text...) }
-
- // Initialise Live rendering output
- LiveUI.Start = func() { LiveUI.count = 0 }
- LiveUI.Print = func(text ...string) {
- // clear last count lines from terminal
- for i := 0; i < LiveUI.count; i++ {
- ansi.CursorPreviousLine(1)
- ansi.EraseInLine(2)
- }
- // count number of lines in text
- LiveUI.count = 1
- for _, str := range text {
- LiveUI.count += strings.Count(str, "\n")
- fmt.Println(str)
- }
- }
-}
-
-// PrintError outputs error (with custom message)
-// and exits the program execution (if err != nil)
-func PrintError(err error, desc string) {
- if err != nil {
- if desc != "" {
- Log.Error(desc)
- }
- Log.Error(err.Error())
- os.Exit(0)
- }
-}
-
-// CreateFile copies data to dst (create if not exists)
-// Returns absolute path to destination file
-func CreateFile(data, dst string) string {
- out, err := os.Create(dst)
- PrintError(err, "File "+dst+" couldn't be created!")
- defer out.Close()
-
- out.WriteString(data)
- return dst
-}
-
-// GetText extracts text from particular html data
-func GetText(sel *goquery.Selection, query string) string {
- str := sel.Find(query).Text()
- return strings.TrimSpace(str)
-}
-
-// GetAttr extracts attribute valur of particular html data
-func GetAttr(sel *goquery.Selection, query, attr string) string {
- str := sel.Find(query).AttrOr(attr, "")
- return strings.TrimSpace(str)
-}
diff --git a/packages/requests.go b/packages/requests.go
deleted file mode 100644
index 7664713..0000000
--- a/packages/requests.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package pkg
-
-import (
- "bytes"
- "io/ioutil"
- "net/http"
- "net/url"
-
- "github.com/PuerkitoBio/goquery"
-)
-
-func parseBody(resp *http.Response) ([]byte, error) {
- defer resp.Body.Close()
- return ioutil.ReadAll(resp.Body)
-}
-
-// GetReqBody executes a GET request to url and returns the request body
-func GetReqBody(client *http.Client, url string) ([]byte, error) {
- resp, err := client.Get(url)
- if err != nil {
- return nil, err
- }
- return parseBody(resp)
-}
-
-// PostReqBody executes a POST request (with values: data) to url and returns the request body
-func PostReqBody(client *http.Client, url string, data url.Values) ([]byte, error) {
- resp, err := client.PostForm(url, data)
- if err != nil {
- return nil, err
- }
- return parseBody(resp)
-}
-
-// FindHandle scrapes handle from REQUEST body
-func FindHandle(body []byte) string {
- doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))
- val := doc.Find("#header").Find("a[href^=\"/profile/\"]").Text()
- return val
-}
-
-// FindCsrf extracts Csrf from REQUEST body
-func FindCsrf(body []byte) string {
- doc, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))
- val, _ := doc.Find(".csrf-token").Attr("data-csrf")
- return val
-}
-
-// RedirectCheck prevents redirection and returns requested page info
-func RedirectCheck(req *http.Request, via []*http.Request) error {
- return http.ErrUseLastResponse
-}