295 lines
5.9 KiB
Go
295 lines
5.9 KiB
Go
/*
|
|
Copyright © 2022 Sameer Rahmani <lxsameer@gnu.org>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package core
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"strings"
|
|
)
|
|
|
|
type Location struct {
|
|
Start int
|
|
End int
|
|
}
|
|
|
|
type RawCommentBlock struct {
|
|
LineNo int
|
|
Lines []string
|
|
File *File
|
|
}
|
|
|
|
type File struct {
|
|
Path string
|
|
Blocks []RawCommentBlock
|
|
Lines []string
|
|
}
|
|
|
|
type Block struct {
|
|
BlockType string // Like TODO, NOTE, etc
|
|
FilePath *string
|
|
Tags []string
|
|
Desc string
|
|
WrappedCode string
|
|
Loc Location
|
|
}
|
|
type FileRawBlock struct {
|
|
Block RawCommentBlock
|
|
Lang *Lang
|
|
}
|
|
|
|
type CommentStack struct {
|
|
storage []*Block
|
|
}
|
|
|
|
func (s *CommentStack) Push(block *Block) {
|
|
s.storage = append(s.storage, block)
|
|
}
|
|
|
|
func (s *CommentStack) Pop() *Block {
|
|
l := len(s.storage)
|
|
if l == 0 {
|
|
return nil
|
|
}
|
|
item := s.storage[l-1]
|
|
s.storage = s.storage[:l-1]
|
|
return item
|
|
}
|
|
|
|
func (b *RawCommentBlock) append(txt string) {
|
|
b.Lines = append(b.Lines, txt)
|
|
}
|
|
|
|
func (b *RawCommentBlock) empty() bool {
|
|
return len(b.Lines) == 0
|
|
}
|
|
|
|
func FindCommentBlocks(s *State, file string, output chan FileRawBlock, w *sync.WaitGroup) {
|
|
fullPath := path.Join(s.ProjectRoot, file)
|
|
readFile, err := os.Open(fullPath)
|
|
|
|
if err != nil {
|
|
s.ErrChannel <- err
|
|
close(output)
|
|
w.Done()
|
|
|
|
return
|
|
}
|
|
|
|
ext := filepath.Ext(file)
|
|
lang := s.GetLangForExt(ext)
|
|
|
|
if lang == nil {
|
|
close(output)
|
|
w.Done()
|
|
// log.Warnf("don't know about the language for extension '%s'", ext)
|
|
return
|
|
}
|
|
|
|
scanner := bufio.NewScanner(readFile)
|
|
|
|
scanner.Split(bufio.ScanLines)
|
|
|
|
curFile := File{
|
|
Path: file,
|
|
Blocks: []RawCommentBlock{},
|
|
Lines: []string{},
|
|
}
|
|
|
|
var line = 1
|
|
block := RawCommentBlock{0, []string{}, &curFile}
|
|
|
|
for scanner.Scan() {
|
|
rawText := scanner.Text()
|
|
curFile.Lines = append(curFile.Lines, rawText)
|
|
|
|
txt := strings.TrimSpace(rawText)
|
|
|
|
if strings.HasPrefix(txt, *lang.CommentBlockMarker) {
|
|
if block.empty() {
|
|
block.LineNo = line
|
|
}
|
|
block.append(txt)
|
|
} else {
|
|
if !block.empty() {
|
|
curFile.Blocks = append(curFile.Blocks, block)
|
|
output <- FileRawBlock{block, lang}
|
|
}
|
|
|
|
block = RawCommentBlock{0, []string{}, &curFile}
|
|
}
|
|
line++
|
|
}
|
|
|
|
if err = readFile.Close(); err != nil {
|
|
|
|
close(output)
|
|
w.Done()
|
|
s.ErrChannel <- err
|
|
return
|
|
}
|
|
|
|
w.Done()
|
|
close(output)
|
|
}
|
|
|
|
func getBlockFirstLine(lang *Lang, block RawCommentBlock) *string {
|
|
txt := strings.TrimLeft(block.Lines[0], *lang.CommentBlockMarker)
|
|
txt = strings.TrimSpace(txt)
|
|
return &txt
|
|
}
|
|
|
|
func JoinComments(state *State, commentMarker string, b RawCommentBlock) *string {
|
|
commentLines := b.Lines
|
|
firstLine := state.TrimTypeAndTags(&commentLines[0])
|
|
result := []string{}
|
|
|
|
if firstLine != nil {
|
|
result = append(result, *firstLine)
|
|
}
|
|
|
|
if len(commentLines) > 1 {
|
|
for _, line := range (commentLines)[1:] {
|
|
trimmed := strings.TrimSpace(strings.TrimLeft(line, commentMarker))
|
|
result = append(result, trimmed)
|
|
|
|
}
|
|
}
|
|
|
|
txt := strings.Join(result, "\n")
|
|
return &txt
|
|
}
|
|
|
|
func processCommentBlocks(s *State, file chan FileRawBlock, output chan Block, w *sync.WaitGroup) {
|
|
defer close(output)
|
|
defer w.Done()
|
|
|
|
// To keep track of opening and closing tags
|
|
var stack CommentStack
|
|
|
|
for fileBlock := range file {
|
|
|
|
block := fileBlock.Block
|
|
lang := fileBlock.Lang
|
|
|
|
if block.empty() {
|
|
continue
|
|
}
|
|
|
|
txt := getBlockFirstLine(lang, block)
|
|
type_ := s.getCommentType(*txt)
|
|
|
|
if type_ == nil {
|
|
type_, isClosing := s.detectClosingBlock(txt)
|
|
if !isClosing {
|
|
// A regular comment block, skipping
|
|
continue
|
|
}
|
|
|
|
lastOpenBlock := stack.Pop()
|
|
|
|
if lastOpenBlock == nil {
|
|
// e := fmt.Errorf(
|
|
// "closing comment with no Opening at '%s:%d'",
|
|
// block.File.Path, block.LineNo,
|
|
// )
|
|
// s.ErrChannel <- e
|
|
return
|
|
}
|
|
|
|
if type_ == &lastOpenBlock.BlockType {
|
|
wrappedCode := []string{}
|
|
for i := lastOpenBlock.Loc.End; i < block.LineNo; i++ {
|
|
ithLine := (block.File.Lines)[i]
|
|
wrappedCode = append(wrappedCode, ithLine)
|
|
}
|
|
lastOpenBlock.WrappedCode = strings.Join(wrappedCode, "\n")
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
tags := s.getTags(*txt)
|
|
|
|
processedBlock := Block{
|
|
BlockType: *type_,
|
|
FilePath: &block.File.Path,
|
|
Tags: tags,
|
|
Desc: *JoinComments(s, *lang.CommentBlockMarker, block),
|
|
WrappedCode: "",
|
|
Loc: Location{block.LineNo, len(block.Lines) + block.LineNo},
|
|
}
|
|
|
|
stack.Push(&processedBlock)
|
|
output <- processedBlock
|
|
}
|
|
}
|
|
|
|
func PrintResult(state *State, res chan Block, w *sync.WaitGroup) {
|
|
for b := range res {
|
|
tags := strings.Join(b.Tags, ",")
|
|
fmt.Printf("* %s at %s:%d | Tags: %s\n%s\n%s\n\n",
|
|
b.BlockType, *b.FilePath, b.Loc.Start,
|
|
tags,
|
|
b.Desc,
|
|
b.WrappedCode)
|
|
}
|
|
w.Done()
|
|
}
|
|
|
|
func ProcessFiles(state *State, files chan string) error {
|
|
var workers sync.WaitGroup
|
|
|
|
for file := range files {
|
|
fileBlocks := make(chan FileRawBlock, 10)
|
|
processed := make(chan Block, 100)
|
|
|
|
if file == "utils/bazel/zlib.bzl" {
|
|
println("file: ", file)
|
|
}
|
|
workers.Add(3)
|
|
go FindCommentBlocks(state, file, fileBlocks, &workers)
|
|
go processCommentBlocks(state, fileBlocks, processed, &workers)
|
|
go PrintResult(state, processed, &workers)
|
|
}
|
|
|
|
controller := make(chan bool)
|
|
go func() {
|
|
workers.Wait()
|
|
close(controller)
|
|
}()
|
|
|
|
select {
|
|
case <-controller:
|
|
println("Wait done")
|
|
case <-time.After(time.Second * 20):
|
|
println("timeout")
|
|
}
|
|
state.WaitGroup.Done()
|
|
return nil
|
|
|
|
}
|