Files
gocryptotrader/cmd/documentation/documentation.go
Samuael A aeb4a87913 exchanges: Add Deribit exchange support (#1082)
* deribit implementation

* add ws impll

* cleanup

* Update deribit_wrapper.go

* Add missing endpoints

* Fix config file

* asset type update

* Update code structure

* Update authenticated private endpoints unit tests

* Updating websocket

* Updating websocket connection and subscription handling

* Finishing up adding subscription push data

* Adding websocket public endpoint

* Adding WS endpoints

* Adding websocket unit tests

* Minor clean-up

* Integrating websocket endpoints into the wrapper funcs

* Updating exchange documentations

* Fixing test issues

* Code cleaning-up

* fix test issues

* Updating validations and logic errors

* Updating wrapper issues

* fix test issues

* Slight test update

* Unit test and code structure update

* Update websocket tempos

* Slight update on code structure

* Minor update on unit tests

* Update depending on review comments

* Minor code fix and doc re-generating

* Update on Candlestick wrapper functions

* Minor updates

* minor unit test updates

* Minor updates on weboscket and unit tests

* minor linter fix

* codespell and rate limiter issues

* single linter issue fix

* adding rate limiter

* Add ratelimiter to websocket conn and overall code update

* fix websocket push data issue

* Implementing missing wrapper function

* Websocket fix

* Minor update on missing endpoint and other

* fixing websocket issues and cleaningup

* Minor tempo fix

* Minor linter issues

* unit test update

* Indexing error fix

* Websocket connection fix

* string formatting fix

* Small fix on unit tests

* fix minor json conversion issue

* websocket and documentation update

* websocket, wrapper and unit test updates

* Documentation and unit tests update

* Fix unit tests

* wrapper fix for new change

* Unit test fix

* timestamp conversion and unit tests update

* Minor instrument ID conversion fix

* instrument formats and unit test update

* formatting and unit test fix

* config update

* Updating websocket and adding the Spot support

* Add small unit test fix

* unit test and websocket handlers update

* Linter issues fix

* minor documentation and code update

* Minor fix

* added a wrapper func GetLatestFundingRates

* Types, wrapper update, and unit tests

* Minor config update

* fix wrapper unit tests

* Resolve all panic and wrapper test issues

* minor unit test fix

* fix issues and adding newly added endpoints

* updates and added remaining endpoints with unit tests

* Update unit tests using assert

* Added missing endpoints and unit tests

* Minor updates and clean-ups

* Resolve tradable pair fetching  panic

* Mutex fix

* Added Options assets test and minor fixes

* subscription mothod updated

* Remove misadded code lines

* resolve websocket

* Updating tests, types, endpoint methods and others

* Added GetFuturesContractDetails and minor fix

* fix linter issue

* revert change on candlestic time

* Added filters to candles

* minor unit test and wrapper fix

* Minor unit tests update

* cahnge param key for GetOrderMarginByID

* updating unit tests and resolve issues

* Update websocket unit tests

* Minor fix based on review

* Revert unit test change

* fix pair config issue

* Added missing wrapper functions

* Fix missing review changes

* Fix options request pair formatting

* fix AllExchangeWrappers test issue

* Changes with unit test and wrapper based on the review

* Fix to options reg-exp

* wrapper functions fix

* Update MaximumFundingRateHistory filter and minor fixes

* Fix besed on review comment

* Fix issues on review comment

* linter fix

* fix minor unit test issue

* Fix unit test issues

* Update trade order cancellation responses

* fix config files issue

* lint update config files

* Update unit tests

* Update return values and response handling

* added missing endpoint and fixes based on review comment

* toggle useTestNet back

* Update cancel by label and other fix

* fix forgotten cancel all response type

* update CancelResp type

* Fix unmarshaling error

* updated websocket orderbook load issue

* fix websocket lock and groups

* Change Items to Tranche and fix linter issues

* Fix orderbook issue

* Update unit tests offline error handling, and endpoints argument and  error handling

* Contributors documentation update and change error return type

* Updated unit tests based on review comment

* Update unit tests and removed password change endpoint

* Fix race condition

* Update on tests, test pairs, and wrapper config

* Update test tradable pairs loading

* Update unit tests, fix linter issues, and update wrapper functions

* remove credentials

* Update test and fix authentication method and few authenticated endpoints

* fix codespell issue

* group the repeated currency code check to a func

* added unit test for repeated pair check func

* Added a base coin and related updates

---------

Co-authored-by: E Sequeira <earncef@earncef.com>
2024-05-29 10:28:03 +10:00

644 lines
16 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"text/template"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/file"
"github.com/thrasher-corp/gocryptotrader/core"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
const (
// DefaultRepo is the main example repository
DefaultRepo = "https://api.github.com/repos/thrasher-corp/gocryptotrader"
// GithubAPIEndpoint allows the program to query your repository
// contributor list
GithubAPIEndpoint = "/contributors"
// LicenseFile defines a license file
LicenseFile = "LICENSE"
// ContributorFile defines contributor file
ContributorFile = "CONTRIBUTORS"
)
var (
// DefaultExcludedDirectories defines the basic directory exclusion list for GCT
DefaultExcludedDirectories = []string{".github",
".git",
"node_modules",
".vscode",
".idea",
"cmd_templates",
"common_templates",
"communications_templates",
"config_templates",
"currency_templates",
"events_templates",
"exchanges_templates",
"portfolio_templates",
"root_templates",
"sub_templates",
"testdata_templates",
"tools_templates",
"web_templates",
}
// global flag for verbosity
verbose bool
// current tool directory to specify working templates
toolDir string
// exposes root directory if outside of document tool directory
repoDir string
// is a broken down version of the documentation tool dir for cross platform
// checking
ref = []string{"gocryptotrader", "cmd", "documentation"}
engineFolder = "engine"
)
// Contributor defines an account associated with this code base by doing
// contributions
type Contributor struct {
Login string `json:"login"`
URL string `json:"html_url"`
Contributions int `json:"contributions"`
}
// Config defines the running config to deploy documentation across a github
// repository including exclusion lists for files and directories
type Config struct {
GithubRepo string `json:"githubRepo"`
Exclusions Exclusions `json:"exclusionList"`
RootReadme bool `json:"rootReadmeActive"`
LicenseFile bool `json:"licenseFileActive"`
ContributorFile bool `json:"contributorFileActive"`
}
// Exclusions defines the exclusion list so documents are not generated
type Exclusions struct {
Files []string `json:"Files"`
Directories []string `json:"Directories"`
}
// DocumentationDetails defines parameters to update documentation
type DocumentationDetails struct {
Directories []string
Tmpl *template.Template
Contributors []Contributor
Config *Config
}
// Attributes defines specific documentation attributes when a template is
// executed
type Attributes struct {
Name string
Contributors []Contributor
NameURL string
Year int
CapitalName string
DonationAddress string
}
func main() {
flag.BoolVar(&verbose, "v", false, "Verbose output")
flag.StringVar(&toolDir, "tooldir", "", "Pass in the documentation tool directory if outside tool folder")
flag.Parse()
wd, err := os.Getwd()
if err != nil {
fmt.Println("Documentation tool error cannot get working dir:", err)
os.Exit(1)
}
if strings.Contains(wd, filepath.Join(ref...)) {
rootDir := filepath.Dir(filepath.Dir(wd))
repoDir = rootDir
toolDir = wd
} else {
if toolDir == "" {
fmt.Println("Please set documentation tool directory via the tooldir flag if working outside of tool directory")
os.Exit(1)
}
repoDir = wd
}
fmt.Print(core.Banner)
fmt.Println("This will update and regenerate documentation for the different packages in your repo.")
fmt.Println()
if verbose {
fmt.Println("Fetching configuration...")
}
config, err := GetConfiguration()
if err != nil {
log.Fatalf("Documentation Generation Tool - GetConfiguration error %s",
err)
}
if verbose {
fmt.Println("Fetching project directory tree...")
}
dirList, err := GetProjectDirectoryTree(&config)
if err != nil {
log.Fatalf("Documentation Generation Tool - GetProjectDirectoryTree error %s",
err)
}
var contributors []Contributor
if config.ContributorFile {
if verbose {
fmt.Println("Fetching repository contributor list...")
}
contributors, err = GetContributorList(config.GithubRepo, verbose)
if err != nil {
log.Fatalf("Documentation Generation Tool - GetContributorList error %s",
err)
}
// Github API missing contributors
contributors = append(contributors, []Contributor{
{
Login: "andreygrehov",
URL: "https://github.com/andreygrehov",
Contributions: 2,
},
{
Login: "azhang",
URL: "https://github.com/azhang",
Contributions: 2,
},
{
Login: "bretep",
URL: "https://github.com/bretep",
Contributions: 2,
},
{
Login: "Christian-Achilli",
URL: "https://github.com/Christian-Achilli",
Contributions: 2,
},
{
Login: "cornelk",
URL: "https://github.com/cornelk",
Contributions: 2,
},
{
Login: "gam-phon",
URL: "https://github.com/gam-phon",
Contributions: 2,
},
{
Login: "herenow",
URL: "https://github.com/herenow",
Contributions: 2,
},
{
Login: "if1live",
URL: "https://github.com/if1live",
Contributions: 2,
},
{
Login: "lozdog245",
URL: "https://github.com/lozdog245",
Contributions: 2,
},
{
Login: "MarkDzulko",
URL: "https://github.com/MarkDzulko",
Contributions: 2,
},
{
Login: "mshogin",
URL: "https://github.com/mshogin",
Contributions: 2,
},
{
Login: "soxipy",
URL: "https://github.com/soxipy",
Contributions: 2,
},
{
Login: "tk42",
URL: "https://github.com/tk42",
Contributions: 2,
},
{
Login: "blombard",
URL: "https://github.com/blombard",
Contributions: 1,
},
{
Login: "cavapoo2",
URL: "https://github.com/cavapoo2",
Contributions: 1,
},
{
Login: "CodeLingoTeam",
URL: "https://github.com/CodeLingoTeam",
Contributions: 1,
},
{
Login: "CodeLingoBot",
URL: "https://github.com/CodeLingoBot",
Contributions: 1,
},
{
Login: "Daanikus",
URL: "https://github.com/Daanikus",
Contributions: 1,
},
{
Login: "daniel-cohen",
URL: "https://github.com/daniel-cohen",
Contributions: 1,
},
{
Login: "DirectX",
URL: "https://github.com/DirectX",
Contributions: 1,
},
{
Login: "frankzougc",
URL: "https://github.com/frankzougc",
Contributions: 1,
},
// idoall's contributors were forked and merged, so his contributions
// aren't automatically retrievable
{
Login: "idoall",
URL: "https://github.com/idoall",
Contributions: 1,
},
{
Login: "Jimexist",
URL: "https://github.com/Jimexist",
Contributions: 1,
},
{
Login: "lookfirst",
URL: "https://github.com/lookfirst",
Contributions: 1,
},
{
Login: "m1kola",
URL: "https://github.com/m1kola",
Contributions: 1,
},
{
Login: "mattkanwisher",
URL: "https://github.com/mattkanwisher",
Contributions: 1,
},
{
Login: "merkeld",
URL: "https://github.com/merkeld",
Contributions: 1,
},
{
Login: "mKurrels",
URL: "https://github.com/mKurrels",
Contributions: 1,
},
{
Login: "starit",
URL: "https://github.com/starit",
Contributions: 1,
},
{
Login: "zeldrinn",
URL: "https://github.com/zeldrinn",
Contributions: 1,
},
}...)
if verbose {
fmt.Println("Contributor List Fetched")
for i := range contributors {
fmt.Println(contributors[i].Login)
}
}
} else {
fmt.Println("Contributor list file disabled skipping fetching details")
}
if verbose {
fmt.Println("Fetching template files...")
}
tmpl, err := GetTemplateFiles()
if err != nil {
log.Fatalf("Documentation Generation Tool - GetTemplateFiles error %s",
err)
}
if verbose {
fmt.Println("All core systems fetched, updating documentation...")
}
UpdateDocumentation(DocumentationDetails{
dirList,
tmpl,
contributors,
&config})
fmt.Println("\nDocumentation Generation Tool - Finished")
}
// GetConfiguration retrieves the documentation configuration
func GetConfiguration() (Config, error) {
var c Config
configFilePath := filepath.Join(toolDir, "config.json")
if file.Exists(configFilePath) {
config, err := os.ReadFile(configFilePath)
if err != nil {
return c, err
}
err = json.Unmarshal(config, &c)
if err != nil {
return c, err
}
if c.GithubRepo == "" {
return c, errors.New("repository not set in config.json file, please change")
}
return c, nil
}
fmt.Println("Creating configuration file, please check to add a different github repository path and change preferences")
// Set default params for configuration
c.GithubRepo = DefaultRepo
c.ContributorFile = true
c.LicenseFile = true
c.RootReadme = true
c.Exclusions.Directories = DefaultExcludedDirectories
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return c, err
}
if err := os.WriteFile(configFilePath, data, file.DefaultPermissionOctal); err != nil {
return c, err
}
return c, nil
}
// IsExcluded returns if the file path is included in the exclusion list
func IsExcluded(path string, exclusion []string) bool {
for i := range exclusion {
if path == exclusion[i] {
return true
}
}
return false
}
// GetProjectDirectoryTree uses filepath walk functions to get each individual
// directory name and path to match templates with
func GetProjectDirectoryTree(c *Config) ([]string, error) {
var directoryData []string
if c.RootReadme { // Projects root README.md
directoryData = append(directoryData, repoDir)
}
if c.LicenseFile { // Standard license file
directoryData = append(directoryData, filepath.Join(repoDir, LicenseFile))
}
if c.ContributorFile { // Standard contributor file
directoryData = append(directoryData, filepath.Join(repoDir, ContributorFile))
}
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
// Bypass what is contained in config.json directory exclusion
if IsExcluded(info.Name(), c.Exclusions.Directories) {
if verbose {
fmt.Println("Excluding Directory:", info.Name())
}
return filepath.SkipDir
}
// Don't append parent directory
if strings.EqualFold(info.Name(), "..") {
return nil
}
directoryData = append(directoryData, path)
}
return nil
}
return directoryData, filepath.Walk(repoDir, walkFn)
}
// GetTemplateFiles parses and returns all template files in the documentation
// tree
func GetTemplateFiles() (*template.Template, error) {
tmpl := template.New("")
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
if path == "." || path == ".." {
return nil
}
var tmplExt *template.Template
tmplExt, err = tmpl.ParseGlob(filepath.Join(path, "*.tmpl"))
if err != nil {
fmt.Println(err)
if strings.Contains(err.Error(), "pattern matches no files") {
return nil
}
return err
}
tmpl = tmplExt
return filepath.SkipDir
}
return nil
}
return tmpl, filepath.Walk(toolDir, walkFn)
}
// GetContributorList fetches a list of contributors from the github api
// endpoint
func GetContributorList(repo string, verbose bool) ([]Contributor, error) {
contents, err := common.SendHTTPRequest(context.TODO(),
http.MethodGet,
repo+GithubAPIEndpoint,
nil,
nil,
verbose)
if err != nil {
return nil, err
}
var resp []Contributor
return resp, json.Unmarshal(contents, &resp)
}
// GetDocumentationAttributes returns specific attributes for a file template
func GetDocumentationAttributes(packageName string, contributors []Contributor) Attributes {
return Attributes{
Name: GetPackageName(packageName, false),
Contributors: contributors,
NameURL: GetGoDocURL(packageName),
Year: time.Now().Year(),
CapitalName: GetPackageName(packageName, true),
DonationAddress: core.BitcoinDonationAddress,
}
}
// GetPackageName returns the package name after cleaning path as a string
func GetPackageName(name string, capital bool) string {
newStrings := strings.Split(name, " ")
var i int
if len(newStrings) > 1 {
// retrieve the latest spacing to define the most childish package name
i = len(newStrings) - 1
}
if capital {
return strings.Replace(cases.Title(language.English).String(newStrings[i]), "_", " ", -1)
}
return newStrings[i]
}
// GetGoDocURL returns a string for godoc package names
func GetGoDocURL(name string) string {
if strings.Contains(name, " ") {
return strings.Join(strings.Split(name, " "), "/")
}
if name == "testdata" ||
name == "tools" ||
name == ContributorFile ||
name == LicenseFile {
return ""
}
return name
}
// UpdateDocumentation generates or updates readme/documentation files across
// the codebase
func UpdateDocumentation(details DocumentationDetails) {
for i := range details.Directories {
cutSet := details.Directories[i][len(repoDir):]
if cutSet != "" && cutSet[0] == os.PathSeparator {
cutSet = cutSet[1:]
}
data := strings.Split(cutSet, string(os.PathSeparator))
var temp []string
for x := range data {
if data[x] == ".." {
continue
}
if data[x] == "" {
break
}
temp = append(temp, data[x])
}
var name string
if len(temp) == 0 {
name = "root"
} else {
name = strings.Join(temp, " ")
}
if IsExcluded(name, details.Config.Exclusions.Files) {
if verbose {
fmt.Println("Excluding file:", name)
}
continue
}
if strings.Contains(name, engineFolder) {
d, err := os.ReadDir(details.Directories[i])
if err != nil {
fmt.Println("Excluding file:", err)
}
for x := range d {
nameSplit := strings.Split(d[x].Name(), ".go")
engineTemplateName := engineFolder + " " + nameSplit[0]
if details.Tmpl.Lookup(engineTemplateName) == nil {
fmt.Printf("Template not found for path %s create new template with {{define \"%s\" -}} TEMPLATE HERE {{end}}\n",
details.Directories[i],
name)
continue
}
err = runTemplate(details, filepath.Join(details.Directories[i], nameSplit[0]+".md"), engineTemplateName)
if err != nil {
fmt.Println(err)
}
}
continue
}
if details.Tmpl.Lookup(name) == nil {
fmt.Printf("Template not found for path %s create new template with {{define \"%s\" -}} TEMPLATE HERE {{end}}\n",
details.Directories[i],
name)
continue
}
var mainPath string
switch {
case name == LicenseFile || name == ContributorFile:
mainPath = details.Directories[i]
default:
mainPath = filepath.Join(details.Directories[i], "README.md")
}
if err := runTemplate(details, mainPath, name); err != nil {
log.Println(err)
continue
}
}
}
func runTemplate(details DocumentationDetails, mainPath, name string) error {
err := os.Remove(mainPath)
if err != nil && !(strings.Contains(err.Error(), "no such file or directory") ||
strings.Contains(err.Error(), "The system cannot find the file specified.")) {
return err
}
f, err := os.Create(mainPath)
if err != nil {
return err
}
defer func(f *os.File) {
err := f.Close()
if err != nil {
log.Printf("could not close file %s: %v", mainPath, err)
}
}(f)
attr := GetDocumentationAttributes(name, details.Contributors)
return details.Tmpl.ExecuteTemplate(f, name, attr)
}