diff --git a/file.go b/file.go index 7bd2b1b..21a7630 100644 --- a/file.go +++ b/file.go @@ -1,5 +1,14 @@ package main +import ( + "bufio" + "crypto/md5" + "fmt" + "io/ioutil" + "os" + "strings" +) + type ClocFile struct { Code int32 `xml:"code,attr"` Comments int32 `xml:"comment,attr"` @@ -19,3 +28,117 @@ func (cf ClocFiles) Swap(i, j int) { func (cf ClocFiles) Less(i, j int) bool { return cf[i].Code > cf[j].Code } + +var fileCache map[string]struct{} + +func analyzeFile(filename string, language *Language) *ClocFile { + clocFile := &ClocFile{ + Name: filename, + } + + fp, err := os.Open(filename) + if err != nil { + return clocFile // ignore error + } + defer fp.Close() + + isFirstLine := true + isInComments := false + isInCommentsSame := false + scanner := bufio.NewScanner(fp) + for scanner.Scan() { + lineOrg := scanner.Text() + line := strings.TrimSpace(lineOrg) + + if len(strings.TrimSpace(line)) == 0 { + clocFile.Blanks += 1 + if opts.Debug { + fmt.Printf("[BLNK,cd:%d,cm:%d,bk:%d,iscm:%v] %s\n", + clocFile.Code, clocFile.Comments, clocFile.Blanks, isInComments, lineOrg) + } + continue + } + + if language.multi_line != "" { + if strings.HasPrefix(line, language.multi_line) { + isInComments = true + } else if containComments(line, language.multi_line, language.multi_line_end) { + isInComments = true + clocFile.Code += 1 + } + } + + if isInComments { + if language.multi_line == language.multi_line_end { + if strings.Count(line, language.multi_line_end) == 2 { + isInComments = false + isInCommentsSame = false + } else if strings.HasPrefix(line, language.multi_line_end) { + if isInCommentsSame { + isInComments = false + } + isInCommentsSame = !isInCommentsSame + } + } else { + if strings.Contains(line, language.multi_line_end) { + isInComments = false + } + } + clocFile.Comments += 1 + if opts.Debug { + fmt.Printf("[COMM,cd:%d,cm:%d,bk:%d,iscm:%v,iscms:%v] %s\n", + clocFile.Code, clocFile.Comments, clocFile.Blanks, isInComments, isInCommentsSame, lineOrg) + } + continue + } + + // shebang line is 'code' + if isFirstLine && strings.HasPrefix(line, "#!") { + clocFile.Code += 1 + isFirstLine = false + if opts.Debug { + fmt.Printf("[CODE,cd:%d,cm:%d,bk:%d,iscm:%v] %s\n", + clocFile.Code, clocFile.Comments, clocFile.Blanks, isInComments, lineOrg) + } + continue + } + + if language.line_comment != "" { + single_comments := strings.Split(language.line_comment, ",") + isSingleComment := false + for _, single_comment := range single_comments { + if strings.HasPrefix(line, single_comment) { + clocFile.Comments += 1 + isSingleComment = true + break + } + } + if isSingleComment { + if opts.Debug { + fmt.Printf("[COMM,cd:%d,cm:%d,bk:%d,iscm:%v] %s\n", + clocFile.Code, clocFile.Comments, clocFile.Blanks, isInComments, lineOrg) + } + continue + } + } + + clocFile.Code += 1 + if opts.Debug { + fmt.Printf("[CODE,cd:%d,cm:%d,bk:%d,iscm:%v] %s\n", + clocFile.Code, clocFile.Comments, clocFile.Blanks, isInComments, lineOrg) + } + } + + // uniq file detect & ignore + // FIXME: not used, now + if ret, err := fp.Seek(0, 0); ret != 0 || err != nil { + panic(err) + } + if d, err := ioutil.ReadAll(fp); err == nil { + hash := md5.Sum(d) + c := fmt.Sprintf("%x", hash) + fileCache[c] = struct{}{} + } + + return clocFile +} diff --git a/main.go b/main.go index 7349c11..4f139f5 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,7 @@ package main import ( - "bufio" - "crypto/md5" "fmt" - "io/ioutil" "os" "regexp" "sort" @@ -100,7 +97,7 @@ func main() { polly := NewLanguage("Polly", "") perl := NewLanguage("Perl", "#", ":=", ":=cut") protobuf := NewLanguage("Protocol Buffers", "//", "", "") - python := NewLanguage("Python", "#", "'''", "'''") + python := NewLanguage("Python", "#", "\"\"\"", "\"\"\"") r := NewLanguage("R", "#", "", "") ruby := NewLanguage("Ruby", "#", ":=begin", ":=end") ruby_html := NewLanguage("Ruby HTML", "") @@ -209,7 +206,7 @@ func main() { } clocFiles := make(map[string]*ClocFile, num) - fileCache := make(map[string]bool) + fileCache = make(map[string]struct{}) for _, language := range languages { if language.printed { @@ -217,81 +214,7 @@ func main() { } for _, file := range language.files { - clocFiles[file] = &ClocFile{ - Name: file, - } - isInComments := false - - func() { - fp, err := os.Open(file) - if err != nil { - return // ignore error - } - defer fp.Close() - - isFirstLine := true - scanner := bufio.NewScanner(fp) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - - if len(strings.TrimSpace(line)) == 0 { - clocFiles[file].Blanks += 1 - continue - } - - if language.multi_line != "" { - if strings.HasPrefix(line, language.multi_line) { - isInComments = true - } else if containComments(line, language.multi_line, language.multi_line_end) { - isInComments = true - clocFiles[file].Code += 1 - } - } - - if isInComments { - if strings.Contains(line, language.multi_line_end) { - isInComments = false - } - clocFiles[file].Comments += 1 - continue - } - - // shebang line is 'code' - if isFirstLine && strings.HasPrefix(line, "#!") { - clocFiles[file].Code += 1 - isFirstLine = false - continue - } - - if language.line_comment != "" { - single_comments := strings.Split(language.line_comment, ",") - isSingleComment := false - for _, single_comment := range single_comments { - if strings.HasPrefix(line, single_comment) { - clocFiles[file].Comments += 1 - isSingleComment = true - break - } - } - if isSingleComment { - continue - } - } - - clocFiles[file].Code += 1 - } - - // uniq file detect & ignore - // FIXME: not used, now - if ret, err := fp.Seek(0, 0); ret != 0 || err != nil { - panic(err) - } - if d, err := ioutil.ReadAll(fp); err == nil { - hash := md5.Sum(d) - c := fmt.Sprintf("%x", hash) - fileCache[c] = true - } - }() + clocFiles[file] = analyzeFile(file, language) language.code += clocFiles[file].Code language.comments += clocFiles[file].Comments diff --git a/option.go b/option.go index 574bec1..99bb3f7 100644 --- a/option.go +++ b/option.go @@ -8,6 +8,7 @@ type Options struct { OutputType string `long:"output-type" default:"default" description:"output type [values: default,cloc-xml,sloccount]"` ExcludeExt string `long:"exclude-ext" description:"exclude file name extensions (separated commas)"` NotMatchDir string `long:"not-match-d" description:"exclude dir name (regex)"` + Debug bool `long:"debug" description:"dump debug log for developer"` } const OutputTypeDefault string = "default"