/* Copyright © 2022 Sameer Rahmani 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 . */ 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 }