Leavitt/pkg/core/blocks.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
}