diff --git a/README.md b/README.md index dff27422..494e3dcc 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,7 @@ Flags: -s, --sort string column to sort by [files, name, lines, blanks, code, comments, complexity] (default "files") --sql-project string use supplied name as the project identifier for the current run. Only valid with the --format sql or sql-insert option -t, --trace enable trace output (not recommended when processing multiple files) + --uloc flip into uloc mode and count the number of unique lines of code -v, --verbose verbose output --version version for scc -w, --wide wider output with additional statistics (implies --complexity) diff --git a/SCC-OUTPUT-REPORT.html b/SCC-OUTPUT-REPORT.html index a102a716..5b3e403b 100644 --- a/SCC-OUTPUT-REPORT.html +++ b/SCC-OUTPUT-REPORT.html @@ -12,12 +12,12 @@ Go 30 - 9131 - 1434 - 445 - 7252 - 1485 - 389947 + 9219 + 1449 + 448 + 7322 + 1503 + 392214 Java 24 @@ -48,12 +48,12 @@ Markdown 10 - 1397 + 1398 332 0 - 1065 + 1066 0 - 57928 + 58030 YAML 8 @@ -467,7 +467,7 @@ 0 797 0 - 11575 + 11574 Hare 1 @@ -787,11 +787,11 @@ Total 191 - 93807 - 4516 - 5937 - 83354 - 2871 - 3132026 + 93896 + 4531 + 5940 + 83425 + 2889 + 3134394 \ No newline at end of file diff --git a/main.go b/main.go index 2c4a40b0..c14334f2 100644 --- a/main.go +++ b/main.go @@ -52,6 +52,12 @@ func main() { flags := rootCmd.PersistentFlags() + flags.BoolVar( + &processor.UlocMode, + "uloc", + false, + "flip into uloc mode and count the number of unique lines of code", + ) flags.BoolVar( &processor.DisableCheckBinary, "binary", diff --git a/processor/formatters.go b/processor/formatters.go index 25cdad20..336b6a53 100644 --- a/processor/formatters.go +++ b/processor/formatters.go @@ -673,6 +673,18 @@ create table t ( return str.String() } +// var tabularWideFormatBody = "%-33s %9d %9d %8d %9d %8d %10d %16.2f\n" +var tabularUlocFormatBody = "Total Unique Source Lines of Code (ULOC) %38d\n" + +func ulocDisplay(input int) string { + var str strings.Builder + str.WriteString(getTabularShortBreak()) + str.WriteString(fmt.Sprintf(tabularUlocFormatBody, input)) + str.WriteString(getTabularShortBreak()) + + return str.String() +} + func fileSummarize(input chan *FileJob) string { if FormatMulti != "" { return fileSummarizeMulti(input) diff --git a/processor/processor.go b/processor/processor.go index 7be2a6a5..94f37caf 100644 --- a/processor/processor.go +++ b/processor/processor.go @@ -102,6 +102,9 @@ var CountIgnore = false // DisableCheckBinary toggles checking for binary files using NUL bytes var DisableCheckBinary = false +// UlocMode toggles checking for binary files using NUL bytes +var UlocMode = false + // SortBy sets which column output in formatter should be sorted by var SortBy = "" @@ -610,13 +613,17 @@ func Process() { close(fileListQueue) }() - go fileProcessorWorker(fileListQueue, fileSummaryJobQueue) - - result := fileSummarize(fileSummaryJobQueue) - if FileOutput == "" { - fmt.Println(result) + if UlocMode { + fileProcessorWorkerUloc(fileListQueue, fileSummaryJobQueue) } else { - _ = os.WriteFile(FileOutput, []byte(result), 0644) - fmt.Println("results written to " + FileOutput) + go fileProcessorWorker(fileListQueue, fileSummaryJobQueue) + + result := fileSummarize(fileSummaryJobQueue) + if FileOutput == "" { + fmt.Println(result) + } else { + _ = os.WriteFile(FileOutput, []byte(result), 0644) + fmt.Println("results written to " + FileOutput) + } } } diff --git a/processor/workers.go b/processor/workers.go index e48fbcef..bed253c2 100644 --- a/processor/workers.go +++ b/processor/workers.go @@ -699,7 +699,70 @@ func fileProcessorWorker(input chan *FileJob, output chan *FileJob) { printDebug(fmt.Sprintf("milliseconds reading files into memory: %d", makeTimestampMilli()-startTime)) } }() +} + +// Processes files using ULOC - Unique lines of code +func fileProcessorWorkerUloc(input chan *FileJob, output chan *FileJob) { + var startTime int64 + var fileCount int64 + var gcEnabled int64 + var wg sync.WaitGroup + + var ulocMutex = sync.Mutex{} + uloc := map[string]struct{}{} + + for i := 0; i < FileProcessJobWorkers; i++ { + wg.Add(1) + go func() { + reader := NewFileReader() + + for job := range input { + atomic.CompareAndSwapInt64(&startTime, 0, makeTimestampMilli()) + + loc := job.Location + if job.Symlocation != "" { + loc = job.Symlocation + } + + fileStartTime := makeTimestampNano() + content, err := reader.ReadFile(loc, int(job.Bytes)) + atomic.AddInt64(&fileCount, 1) + + if atomic.LoadInt64(&gcEnabled) == 0 && atomic.LoadInt64(&fileCount) >= int64(GcFileCount) { + debug.SetGCPercent(gcPercent) + atomic.AddInt64(&gcEnabled, 1) + if Verbose { + printWarn("read file limit exceeded GC re-enabled") + } + } + + if Trace { + printTrace(fmt.Sprintf("nanoseconds read into memory: %s: %d", job.Location, makeTimestampNano()-fileStartTime)) + } + + if err == nil { + ulocMutex.Lock() + for _, l := range strings.Split(string(content), "\n") { + uloc[l] = struct{}{} + } + ulocMutex.Unlock() + } else { + if Verbose { + printWarn(fmt.Sprintf("error reading: %s %s", job.Location, err)) + } + } + } + + wg.Done() + }() + } + + wg.Wait() + if Debug { + printDebug(fmt.Sprintf("milliseconds reading files into memory: %d", makeTimestampMilli()-startTime)) + } + fmt.Println(ulocDisplay(len(uloc))) } // Process a single file