Leavitt/pkg/core/blocks.go

253 lines
5.2 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"
"strings"
"github.com/spf13/cobra"
)
type Location struct {
Start int
End int
}
type RawCommetBlock struct {
LineNo int
Lines []string
}
type FileRawBlocks struct {
Path *string
Blocks []RawCommetBlock
lines *[]string
}
type Block struct {
BlockType string // Like TODO, NOTE, etc
FilePath *string
Tags []string
Desc string
WrappedCode string
Loc Location
}
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 *RawCommetBlock) append(txt string) {
b.Lines = append(b.Lines, txt)
}
func (b *RawCommetBlock) empty() bool {
return len(b.Lines) == 0
}
func FindCommentBlocks(s *State, file *string) (*FileRawBlocks, error) {
fullPath := path.Join(s.ProjectRoot, *file)
readFile, err := os.Open(fullPath)
if err != nil {
return nil, err
}
defer readFile.Close()
ext := filepath.Ext(*file)
lang := s.GetLangForExt(ext)
if lang == nil {
println("[warn]: don't know about the language for extension '%s'", ext)
return nil, nil
}
scanner := bufio.NewScanner(readFile)
scanner.Split(bufio.ScanLines)
var commentblocks []RawCommetBlock
var line = 1
var block RawCommetBlock
var lines []string
for scanner.Scan() {
rawText := scanner.Text()
lines = append(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() {
commentblocks = append(commentblocks, block)
}
block = RawCommetBlock{}
}
line++
}
return &FileRawBlocks{
file,
commentblocks,
&lines,
}, nil
}
func getBlockFirstLine(lang *Lang, block RawCommetBlock) *string {
txt := strings.TrimLeft(block.Lines[0], *lang.CommentBlockMarker)
txt = strings.TrimSpace(txt)
return &txt
}
func JoinComments(state *State, commentMarker string, commentLines *[]string) *string {
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 *FileRawBlocks) (*[]Block, error) {
ext := filepath.Ext(*file.Path)
lang := s.GetLangForExt(ext)
if lang == nil {
println("[warn]: don't know about the language for extension '%s'", ext)
return nil, nil
}
// To keep track of opening and closing tags
var stack CommentStack
var blocks []Block
for _, block := range file.Blocks {
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'",
*file.Path, block.LineNo,
)
return nil, e
}
if type_ == &lastOpenBlock.BlockType {
wrappedCode := []string{}
for i := lastOpenBlock.Loc.End; i < block.LineNo; i++ {
wrappedCode = append(wrappedCode, (*file.lines)[i])
}
lastOpenBlock.WrappedCode = strings.Join(wrappedCode, "\n")
}
continue
}
tags := s.getTags(*txt)
processedBlock := Block{
BlockType: *type_,
FilePath: file.Path,
Tags: tags,
Desc: *JoinComments(s, *lang.CommentBlockMarker, &block.Lines),
WrappedCode: "",
Loc: Location{block.LineNo, len(block.Lines) + block.LineNo},
}
stack.Push(&processedBlock)
blocks = append(blocks, processedBlock)
}
return &blocks, nil
}
func ProcessFiles(state *State, files []string) error {
for _, file := range files {
blocks, err := FindCommentBlocks(state, &file)
cobra.CheckErr(err)
if blocks == nil {
continue
}
processedBlocks, err := processCommentBlocks(state, blocks)
cobra.CheckErr(err)
for _, b := range *processedBlocks {
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)
}
}
return nil
}