GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 7dc2ce...59facb )
by Fedir
02:26
created

main.main   F

Complexity

Conditions 11

Size

Total Lines 105
Code Lines 94

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 13.5412

Importance

Changes 0
Metric Value
cc 11
eloc 94
nop 0
dl 0
loc 105
rs 3.9763
c 0
b 0
f 0
ccs 21
cts 29
cp 0.7241
crap 13.5412

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like main.main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// Copyright 2018 Fedir RYKHTIK. All rights reserved.
2
// Use of this source code is governed by the GNU GPL 3.0
3
// license that can be found in the LICENSE file.
4
5
// ghstat - statistical multi-criteria decision-making comparator for Github's projects.
6
package main
7
8
import (
9
	"encoding/csv"
10
	"encoding/json"
11
	"flag"
12
	"fmt"
13
	"log"
14
	"os"
15
	"path/filepath"
16
	"strings"
17
	"time"
18
19
	"github.com/fedir/ghstat/github"
20
	"github.com/fedir/ghstat/httpcache"
21
	"github.com/fedir/ghstat/timing"
22
)
23
24
func main() {
25 1
	var (
26
		clearHTTPCache         = flag.Bool("cc", false, "Clear HTTP cache")
27
		clearHTTPCacheDryRun   = flag.Bool("ccdr", false, "Clear HTTP cache (dry run)")
28
		debug                  = flag.Bool("d", false, "Debug mode")
29
		resultFileSavePath     = flag.String("f", "", "File path where result CSV file will be saved")
30
		rateLimitCheck         = flag.Bool("l", false, "Rate limit check")
31
		repositoriesKeysManual = flag.String("r", "", "Repositories keys")
32
		tmpFolder              = flag.String("t", "test_data", "Clear HTTP cache (dry run)")
33
		repositoriesKeys       = []string{}
34
	)
35 1
	flag.Parse()
36 1
	if *clearHTTPCache || *clearHTTPCacheDryRun {
37
		clearHTTPCacheFolder(*tmpFolder, *clearHTTPCacheDryRun)
38
		os.Exit(0)
39
	}
40 1
	if *rateLimitCheck {
41
		checkAndPrintRateLimit()
42
		os.Exit(0)
43
	}
44 1
	if *rateLimitCheck {
45
		checkAndPrintRateLimit()
46
		os.Exit(0)
47
	}
48 1
	if *repositoriesKeysManual != "" {
49
		repositoriesKeys = strings.Split(*repositoriesKeysManual, ",")
50
	} else {
51 1
		repositoriesKeys = []string{
52
			"astaxie/beego",
53
			"gohugoio/hugo",
54
			"gin-gonic/gin",
55
			"labstack/echo",
56
			"revel/revel",
57
			"gobuffalo/buffalo",
58
			"go-chi/chi",
59
			"kataras/iris",
60
			"zenazn/goji",
61
		}
62
	}
63 1
	csvFilePath := ""
64 1
	if *resultFileSavePath != "" {
65
		csvFilePath = *resultFileSavePath
66
	} else {
67 1
		csvFilePath = "result.csv"
68
	}
69 1
	var ghData = [][]string{}
70 1
	headers := []string{
71
		"Name",
72
		"URL",
73
		"Author",
74
		"License",
75
		"Author's followers",
76
		"Top 10 contributors followers",
77
		"Created at",
78
		"Age in days",
79
		"Total commits",
80
		"Total additions",
81
		"Total deletions",
82
		"Total code changes",
83
		"Last commit date",
84
		"Commits/day",
85
		"Medium commit size",
86
		"Total releases",
87
		"Stargazers",
88
		"Forks",
89
		"Contributors",
90
		"Active forkers, %",
91
		"Open issues",
92
		"Total issues",
93
		"Issue/day",
94
		"Closed issues, %",
95
		"Place",
96
	}
97 1
	ghDataColumnIndexes := map[string]int{
98
		"nameColumn":                       0,
99
		"urlColumn":                        1,
100
		"authorColumn":                     2,
101
		"licenseColumn":                    3,
102
		"authorsFollowersColumn":           4,
103
		"top10ContributorsFollowersColumn": 5,
104
		"ageColumn":                        7,
105
		"totalCommitsColumn":               8,
106
		"totalAdditionsColumn":             9,
107
		"totalDeletionsColumn":             10,
108
		"totalCodeChangesColumn":           11,
109
		"lastCommitDateColumn":             12,
110
		"commitsByDayColumn":               13,
111
		"mediCommitSizeColumn":             14,
112
		"totalTagsColumn":                  15,
113
		"stargazersColumn":                 16,
114
		"activeForkersColumn":              19,
115
		"issuesByDayColumn":                22,
116
		"closedIssuesPercentageColumn":     23,
117
		"totalPointsColumnIndex":           24,
118
	}
119 1
	dataChan := make(chan []string, len(repositoriesKeys))
120 1
	for _, rKey := range repositoriesKeys {
121 1
		go fillRepositoryStatistics(rKey, *tmpFolder, *debug, dataChan)
122
	}
123 1
	for range repositoriesKeys {
124 1
		ghData = append(ghData, <-dataChan)
125
	}
126 1
	greetings := rateGhData(ghData, ghDataColumnIndexes)
127 1
	fmt.Println(greetings)
128 1
	writeCsv(csvFilePath, headers, ghData)
129
}
130
131
func fillRepositoryStatistics(rKey string, tmpFolder string, debug bool, dataChan chan []string) {
132 1
	repositoryData := github.GetRepositoryStatistics(rKey, tmpFolder, debug)
133 1
	repositoryAge := int(time.Since(repositoryData.CreatedAt).Seconds() / 86400)
134 1
	authorLogin, lastCommitDate := github.GetRepositoryCommitsData(rKey, tmpFolder, debug)
135 1
	authorFollowers := 0
136 1
	if authorLogin != "" {
137 1
		authorFollowers = github.GetUserFollowers(authorLogin, tmpFolder, debug)
138
	}
139 1
	closedIssues := github.GetRepositoryClosedIssues(rKey, tmpFolder, debug)
140 1
	topContributorsFollowers, totalContributors := github.GetRepositoryContributors(rKey, tmpFolder, debug)
141 1
	totalTags := github.GetRepositoryTagsNumber(rKey, tmpFolder, debug)
142 1
	activeForkersPercentage := github.GetActiveForkersPercentage(totalContributors, repositoryData.Forks)
143 1
	issueByDay := github.GetIssueByDay(closedIssues+repositoryData.OpenIssues, repositoryAge)
144 1
	closedIssuesPercentage := github.GetClosedIssuesPercentage(repositoryData.OpenIssues, int(closedIssues))
145 1
	contributionStatistics := github.GetContributionStatistics(rKey, tmpFolder, debug)
146 1
	commitsByDay := github.GetCommitsByDay(contributionStatistics.TotalCommits, repositoryAge)
147 1
	ghProjectData := []string{
148
		repositoryData.FullName,
149
		fmt.Sprintf("https://github.com/%s", repositoryData.FullName),
150
		fmt.Sprintf("%s", func(a string) string {
151 1
			if a == "" {
152 1
				a = "[Account removed]"
153
			}
154 1
			return a
155
		}(authorLogin)),
156
		fmt.Sprintf("%s", func(l string) string {
157 1
			if l == "" {
158 1
				l = "[Unknown]"
159
			}
160 1
			return l
161
		}(repositoryData.License.SPDXID)),
162
		fmt.Sprintf("%d", authorFollowers),
163
		fmt.Sprintf("%d", topContributorsFollowers),
164
		fmt.Sprintf("%d/%02d", repositoryData.CreatedAt.Year(), repositoryData.CreatedAt.Month()),
165
		fmt.Sprintf("%d", repositoryAge),
166
		fmt.Sprintf("%d", contributionStatistics.TotalCommits),
167
		fmt.Sprintf("%d", contributionStatistics.TotalAdditions),
168
		fmt.Sprintf("%d", contributionStatistics.TotalDeletions),
169
		fmt.Sprintf("%d", contributionStatistics.TotalCodeChanges),
170
		fmt.Sprintf(lastCommitDate.Format("2006-01-02 15:04:05")),
0 ignored issues
show
introduced by
can't check non-constant format in call to Sprintf
Loading history...
171
		fmt.Sprintf("%.4f", commitsByDay),
172
		fmt.Sprintf("%d", contributionStatistics.MediumCommitSize),
173
		fmt.Sprintf("%d", totalTags),
174
		fmt.Sprintf("%d", repositoryData.Watchers),
175
		fmt.Sprintf("%d", repositoryData.Forks),
176
		fmt.Sprintf("%d", totalContributors),
177
		fmt.Sprintf("%.2f", activeForkersPercentage),
178
		fmt.Sprintf("%d", repositoryData.OpenIssues),
179
		fmt.Sprintf("%d", closedIssues+repositoryData.OpenIssues),
180
		fmt.Sprintf("%.4f", issueByDay),
181
		fmt.Sprintf("%.2f", closedIssuesPercentage),
182
		"0",
183
	}
184 1
	dataChan <- ghProjectData
185
}
186
187
func clearHTTPCacheFolder(tmpFolderPath string, dryRun bool) error {
188
	d, err := os.Open(tmpFolderPath)
189
	if err != nil {
190
		log.Fatalf("Could not open %s", tmpFolderPath)
191
	}
192
	defer d.Close()
193
	names, err := d.Readdirnames(-1)
194
	if err != nil {
195
		log.Fatalf("Could not read from %s", tmpFolderPath)
196
	}
197
	for _, name := range names {
198
		fp := filepath.Join(tmpFolderPath, name)
199
		if dryRun {
200
			fmt.Printf("Deleted %s\n", fp)
201
		} else {
202
			err = os.RemoveAll(fp)
203
			if err != nil {
204
				log.Fatalf("Could not remove %s", fp)
205
			}
206
			fmt.Printf("Deleted %s\n", fp)
207
		}
208
	}
209
	return nil
210
}
211
212
func checkAndPrintRateLimit() {
213
	type RateLimits struct {
214
		Resources struct {
215
			Core struct {
216
				Limit     int `json:"limit"`
217
				Remaining int `json:"remaining"`
218
				Reset     int `json:"reset"`
219
			} `json:"core"`
220
			Search struct {
221
				Limit     int `json:"limit"`
222
				Remaining int `json:"remaining"`
223
				Reset     int `json:"reset"`
224
			} `json:"search"`
225
			GraphQL struct {
226
				Limit     int `json:"limit"`
227
				Remaining int `json:"remaining"`
228
				Reset     int `json:"reset"`
229
			} `json:"graphql"`
230
		} `json:"resources"`
231
		Rate struct {
232
			Limit     int `json:"limit"`
233
			Remaining int `json:"remaining"`
234
			Reset     int `json:"reset"`
235
		} `json:"rate"`
236
	}
237
	url := "https://api.github.com/rate_limit"
238
	resp, statusCode, err := httpcache.MakeHTTPRequest(url)
239
	if err != nil {
240
		log.Fatalf("Error during checking rate limit : %d %v#", statusCode, err)
241
	}
242
	jsonResponse, _, _ := httpcache.ReadResp(resp)
243
	rateLimits := RateLimits{}
244
	json.Unmarshal(jsonResponse, &rateLimits)
245
	fmt.Printf("Core: %d/%d (reset in %d minutes)\n", rateLimits.Resources.Core.Remaining, rateLimits.Resources.Core.Limit, timing.GetRelativeTime(rateLimits.Resources.Core.Reset))
246
	fmt.Printf("Search: %d/%d (reset in %d minutes)\n", rateLimits.Resources.Search.Remaining, rateLimits.Resources.Search.Limit, timing.GetRelativeTime(rateLimits.Resources.Search.Reset))
247
	fmt.Printf("GraphQL: %d/%d (reset in %d minutes)\n", rateLimits.Resources.GraphQL.Remaining, rateLimits.Resources.GraphQL.Limit, timing.GetRelativeTime(rateLimits.Resources.GraphQL.Reset))
248
	fmt.Printf("Rate: %d/%d (reset in %d minutes)\n", rateLimits.Rate.Remaining, rateLimits.Rate.Limit, timing.GetRelativeTime(rateLimits.Rate.Reset))
249
}
250
251
func writeCsv(csvFilePath string, headers []string, ghData [][]string) {
252 1
	file, err := os.Create(csvFilePath)
253 1
	if err != nil {
254
		log.Fatal("Cannot create file", err)
255
	}
256 1
	defer file.Close()
257 1
	writer := csv.NewWriter(file)
258 1
	defer writer.Flush()
259 1
	err = writer.Write(headers)
260 1
	if err != nil {
261
		log.Fatal("Cannot write to file", err)
262
	}
263 1
	for _, value := range ghData {
264 1
		err := writer.Write(value)
265 1
		if err != nil {
266
			log.Fatal("Cannot write to file", err)
267
		}
268
	}
269
}
270