Skip to content

Commit ae4f709

Browse files
committed
solutions
1 parent 98a79e0 commit ae4f709

File tree

82 files changed

+7690
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+7690
-0
lines changed

aug15/extra/dmitry-pokidov/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Category: Participating and exploring further (does it mean that I will support the tool in future if it needs that?)
2+
3+
The main package is in github.com/dooman87/gounexport/cmd folder (just was looking for other tools).
4+
5+
Also, I left my thoughts for you in README.md. I'm looking forward to write a blog post about this cool experience.
6+
7+
Thanks a lot for interesting task and hope to hear from you soon.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FROM golang
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Run in Docker #
2+
3+
```
4+
5+
docker build -t go-unexport .
6+
docker run -it --rm --name go-unexport go-unexport
7+
docker run -it --rm -v /home/dooman/Projects/go/:/go -p 6060:6060 --name gounexport golang
8+
9+
```
10+
11+
# Thoughts #
12+
13+
It was a Friday and I was actually thinking what to do on the weekend, because
14+
my wife was going with her friends to do some shopping. I was choosing between bike
15+
ride on ocean road or have a garage party with friends. Why did I open reddit?
16+
You guys did that weekend and next two :)
17+
18+
Well, I came with C/C++ and Java experience. My last 8 years was exclusively hold
19+
by JVM and Web(yeah, still need to be full stack and agile). This application is my first
20+
serious golang experience. There are some points from me:
21+
22+
* Easy to start. I supposed that it will be more like C. All this nightmare with
23+
compilers and shared libraries. But it was really comfy, like Java. Also, I developed
24+
using Docker and I think this way is the easiest. It's probably worth to *add link
25+
with docker images* to "Getting Started" guide on golang.org.
26+
27+
* Tools are awesome. I decided to try new editors. I tried Atom and Sublime. They
28+
are both good. I suppose, that the root of happiness is govet, gofmt, golint,...
29+
I really like that you choose Unix way to develop language environment. It's
30+
really brilliant idea.
31+
32+
* Docs are good. I didn't have any problems with starting develop gounexport tool.
33+
Examples are super handy and is awesome idea to store them in code make them compiled!
34+
35+
* go/types package. It's pretty staightforward and easy to use. I had a problem
36+
with importing builtin packages. I'm still thinking that I'm doing something
37+
wrong, because I don't understand why method definitions have names like
38+
(interface).Less instead of (sort.Interface).Less for builtin(compiled)
39+
types.
40+
41+
* Errors handling. To be honest, this one is not the best experience. I remember
42+
that it was a pain point in C, as well. Golang brought me back to that time.
43+
I know that try-catch is not really nice from performance point and not clear
44+
semantically, but I think it's more straightforward then process each error
45+
separately.
46+
Just, to illustrate, fs.ReplaceStringInFile:
47+
48+
```go
49+
sourceFile, err := os.OpenFile(file, os.O_RDWR, 0)
50+
if err != nil {
51+
return err
52+
}
53+
54+
var info os.FileInfo
55+
var restFile []byte
56+
seekTo := offset + len(from)
57+
58+
if info, err = sourceFile.Stat(); err != nil {
59+
goto closeAndReturn
60+
}
61+
62+
restFile = make([]byte, int(info.Size())-seekTo)
63+
if _, err = sourceFile.Seek(int64(seekTo), 0); err != nil {
64+
goto closeAndReturn
65+
}
66+
if _, err = sourceFile.Read(restFile); err != nil {
67+
goto closeAndReturn
68+
}
69+
//...5 more calls to truncate file and replace string
70+
71+
if err = sourceFile.Close(); err != nil {
72+
goto closeAndReturn
73+
}
74+
75+
closeAndReturn:
76+
closeErr := sourceFile.Close()
77+
if closeErr != nil && err == nil {
78+
err = closeErr
79+
}
80+
81+
return err
82+
```
83+
84+
* Tests. Good and easy. Included coverage is a nice bonus. Little bit miss
85+
mock framework. Maybe for next gochallange's it would be nice to *allow
86+
github.com/golang/mock/gomock* or other. Again, examples are excellent idea!
87+
88+
* Debugging. I didn't use debugger because it was *not in docker image (nice to have)*
89+
and I didn't want to spend time to setup it. Anyway it's not really neccessary
90+
for such application (one thread and not big enough).
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
//Package cmd provides command line interface to gounexport tool.
2+
//
3+
//Command requires package name. For, example:
4+
// gounexport github.com/dooman87/gounexport
5+
//
6+
//There are next supported flags:
7+
//
8+
// -exclude string
9+
// File with exlude patterns for objects that shouldn't be unexported.Each pattern should be started at new line. Default pattern is Test* to exclude tests methods.
10+
// -out string
11+
// Output file. If not set then stdout will be used
12+
// -rename
13+
// If set, then all defenitions that will be determined as unused will be renamed in files
14+
// -verbose
15+
// Turning on verbose mode
16+
//
17+
//Exclude flag is pointing to file with regular expressions to ignore
18+
//public unexported symbols. Each expression should be starterd
19+
//with a new line. It's a standard go/regexp package. For example,
20+
//below we are excluding all Test methods and everything from package
21+
//public/api/pack/:
22+
//
23+
// Test*
24+
// public/api/packag/*
25+
//
26+
//Use -rename flag carefully and check output before.
27+
//
28+
//BUG(d): The tool is not analyzing test files if package in the test file is not
29+
//the same as a base package. For instance, pack/pack_test.go is in package pack_test
30+
//instead of pack
31+
package main
32+
33+
import (
34+
"flag"
35+
"fmt"
36+
"io/ioutil"
37+
"os"
38+
"regexp"
39+
"sort"
40+
"strings"
41+
42+
"go/ast"
43+
"go/types"
44+
45+
"github.com/dooman87/gounexport"
46+
"github.com/dooman87/gounexport/fs"
47+
"github.com/dooman87/gounexport/util"
48+
)
49+
50+
type sortableDefinition struct {
51+
defs []*gounexport.Definition
52+
}
53+
54+
func (sortDefs *sortableDefinition) Len() int {
55+
return len(sortDefs.defs)
56+
}
57+
58+
func (sortDefs *sortableDefinition) Less(i int, j int) bool {
59+
iDef := sortDefs.defs[i]
60+
jDef := sortDefs.defs[j]
61+
if iDef.File != jDef.File {
62+
return iDef.File < jDef.File
63+
}
64+
if iDef.Line != jDef.Line {
65+
return iDef.Line < jDef.Line
66+
}
67+
return iDef.Col < jDef.Col
68+
}
69+
70+
func (sortDefs *sortableDefinition) Swap(i, j int) {
71+
temp := sortDefs.defs[i]
72+
sortDefs.defs[i] = sortDefs.defs[j]
73+
sortDefs.defs[j] = temp
74+
}
75+
76+
func main() {
77+
var err error
78+
79+
rename := flag.Bool("rename", false,
80+
"If set, then all defenitions "+
81+
"that will be determined as unused will be renamed in files")
82+
verbose := flag.Bool("verbose", false, "Turning on verbose mode")
83+
out := flag.String("out", "", "Output file. If not set then stdout will be used")
84+
exclude := flag.String("exclude", "",
85+
"File with exlude patterns for objects that shouldn't be unexported."+
86+
"Each pattern should be started at new line. Default pattern is Test* to exclude tests methods.")
87+
88+
flag.Parse()
89+
pkg := flag.Arg(0)
90+
91+
//Setup logging
92+
if *verbose {
93+
util.Level = "DEBUG"
94+
} else {
95+
util.Level = "ERROR"
96+
}
97+
98+
//Setup excludes
99+
defaultRegexp, _ := regexp.Compile("Test*")
100+
excludeRegexps := []*regexp.Regexp{defaultRegexp}
101+
if len(*exclude) > 0 {
102+
excludeRegexps, err = readExcludes(*exclude)
103+
if err != nil {
104+
util.Fatalf("error while setup logging: %v", err)
105+
}
106+
}
107+
108+
//Looking up for unused definitions, print them and rename
109+
if len(pkg) > 0 {
110+
unusedDefinitions, allDefinitions, err := getUnusedDefinitions(pkg, excludeRegexps)
111+
if err != nil {
112+
util.Fatalf("error while getting definitions: %v", err)
113+
}
114+
if err := printDefinitions(*out, unusedDefinitions); err != nil {
115+
util.Fatalf("error while printing result: %v", err)
116+
}
117+
if *rename {
118+
renameDefinitions(unusedDefinitions, allDefinitions)
119+
}
120+
} else {
121+
fmt.Printf("Usage: gounexport [OPTIONS] package\n")
122+
flag.PrintDefaults()
123+
}
124+
}
125+
126+
func readExcludes(file string) ([]*regexp.Regexp, error) {
127+
bytes, err := ioutil.ReadFile(file)
128+
if err != nil {
129+
return nil, err
130+
}
131+
132+
var result []*regexp.Regexp
133+
regexpStrings := strings.Split(string(bytes), "\n")
134+
for _, regexpS := range regexpStrings {
135+
if len(regexpS) == 0 {
136+
continue
137+
}
138+
var regex *regexp.Regexp
139+
regex, err = regexp.Compile(regexpS)
140+
if err != nil {
141+
return nil, err
142+
}
143+
result = append(result, regex)
144+
}
145+
return result, err
146+
}
147+
148+
func renameDefinitions(unused []*gounexport.Definition, allDefs map[string]*gounexport.Definition) {
149+
for _, def := range unused {
150+
gounexport.Unexport(def, allDefs, fs.ReplaceStringInFile)
151+
}
152+
}
153+
154+
func getUnusedDefinitions(pkg string, excludes []*regexp.Regexp) (
155+
[]*gounexport.Definition, map[string]*gounexport.Definition, error) {
156+
info := types.Info{
157+
Types: make(map[ast.Expr]types.TypeAndValue),
158+
Defs: make(map[*ast.Ident]types.Object),
159+
Uses: make(map[*ast.Ident]types.Object),
160+
}
161+
_, fset, err := gounexport.ParsePackage(pkg, &info)
162+
if err != nil {
163+
return nil, nil, err
164+
}
165+
defs := gounexport.GetDefinitions(&info, fset)
166+
return gounexport.GetDefenitionsToHide(pkg, defs, excludes), defs, nil
167+
}
168+
169+
func printDefinitions(filename string, defs []*gounexport.Definition) error {
170+
output := definitionsToString(defs)
171+
if len(filename) > 0 {
172+
if err := ioutil.WriteFile(filename, []byte(output), os.ModePerm); err != nil {
173+
return err
174+
}
175+
} else {
176+
fmt.Print(output)
177+
}
178+
return nil
179+
}
180+
181+
func definitionsToString(defs []*gounexport.Definition) string {
182+
sDef := new(sortableDefinition)
183+
sDef.defs = defs
184+
sort.Sort(sDef)
185+
186+
result := "-----------------------------------------------------\n"
187+
result += fmt.Sprintf("Found %d unused definitions\n", len(defs))
188+
for _, def := range defs {
189+
result += fmt.Sprintf("%s - %s:%d:%d\n", def.Name, def.File, def.Line, def.Col)
190+
for _, u := range def.Usages {
191+
result += fmt.Sprintf("\t%s:%d:%d\n", u.Pos.Filename, u.Pos.Line, u.Pos.Column)
192+
}
193+
}
194+
return result
195+
}

0 commit comments

Comments
 (0)