github.com/developest/gtm-core@v1.0.4-0.20220111132249-cc80a3372c3f/report/query.go (about)

     1  // Copyright 2016 Michael Schenk. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package report
     6  
     7  import (
     8  	"fmt"
     9  	"path/filepath"
    10  	"sort"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/DEVELOPEST/gtm-core/note"
    15  	"github.com/DEVELOPEST/gtm-core/project"
    16  	"github.com/DEVELOPEST/gtm-core/scm"
    17  	"github.com/DEVELOPEST/gtm-core/util"
    18  )
    19  
    20  const (
    21  	defaultDateFormat = "Mon Jan 02 15:04:05 2006 MST"
    22  )
    23  
    24  func retrieveNotes(projects []ProjectCommits,
    25  	terminalOff, appOff, calcStats bool,
    26  	dateFormat, subdir string) commitNoteDetails {
    27  	notes := commitNoteDetails{}
    28  
    29  	if dateFormat == "" {
    30  		dateFormat = defaultDateFormat
    31  	}
    32  
    33  	for _, p := range projects {
    34  		for _, c := range p.Commits {
    35  			n, err := scm.ReadNote(c, project.NoteNameSpace, calcStats, p.Path)
    36  			if err != nil {
    37  				notes = append(notes, commitNoteDetail{})
    38  				continue
    39  			}
    40  
    41  			when := n.When.Format(dateFormat)
    42  
    43  			var commitNote note.CommitNote
    44  			commitNote, err = note.UnMarshal(n.Note)
    45  			if err != nil {
    46  				commitNote = note.CommitNote{}
    47  			}
    48  
    49  			if terminalOff {
    50  				commitNote = commitNote.FilterOutTerminal()
    51  			}
    52  			if appOff {
    53  				commitNote = commitNote.FilterOutApp()
    54  			}
    55  
    56  			if subdir != "" {
    57  				commitNote = commitNote.FilterOutSubdir(subdir)
    58  			}
    59  
    60  			id := n.ID
    61  			if len(id) > 7 {
    62  				id = id[:7]
    63  			}
    64  
    65  			message := strings.TrimPrefix(n.Message, n.Summary)
    66  			message = strings.TrimSpace(message)
    67  
    68  			notes = append(notes,
    69  				commitNoteDetail{
    70  					Author:     n.Author,
    71  					Date:       when,
    72  					When:       n.When,
    73  					Hash:       id,
    74  					Subject:    n.Summary,
    75  					Message:    message,
    76  					Note:       commitNote,
    77  					Project:    filepath.Base(p.Path),
    78  					LineAdd:    fmt.Sprintf("+%d", n.Stats.Insertions),
    79  					LineDel:    fmt.Sprintf("-%d", n.Stats.Deletions),
    80  					LineDiff:   fmt.Sprintf("%d", n.Stats.Insertions-n.Stats.Deletions),
    81  					ChangeRate: fmt.Sprintf("%.0f", n.Stats.ChangeRatePerHour(commitNote.Total())),
    82  				})
    83  		}
    84  	}
    85  	sort.Sort(notes)
    86  	return notes
    87  }
    88  
    89  type commitNoteDetails []commitNoteDetail
    90  
    91  func (c commitNoteDetails) Len() int           { return len(c) }
    92  func (c commitNoteDetails) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
    93  func (c commitNoteDetails) Less(i, j int) bool { return c[i].When.After(c[j].When) }
    94  
    95  func (c commitNoteDetails) Total() int {
    96  	t := 0
    97  	for i := range c {
    98  		t += c[i].Note.Total()
    99  	}
   100  	return t
   101  }
   102  
   103  type commitNoteDetail struct {
   104  	Author     string
   105  	Date       string
   106  	When       time.Time
   107  	Hash       string
   108  	Subject    string
   109  	Project    string
   110  	Message    string
   111  	Note       note.CommitNote
   112  	LineAdd    string
   113  	LineDel    string
   114  	LineDiff   string
   115  	ChangeRate string
   116  }
   117  
   118  func (c commitNoteDetails) files() fileEntries {
   119  	filesMap := map[string]fileEntry{}
   120  	for _, n := range c {
   121  		for _, f := range n.Note.Files {
   122  			if entry, ok := filesMap[f.SourceFile]; !ok {
   123  				filesMap[f.SourceFile] = fileEntry{Filename: f.SourceFile, Seconds: f.TimeSpent}
   124  			} else {
   125  				entry.add(f.TimeSpent)
   126  				filesMap[f.SourceFile] = entry
   127  			}
   128  		}
   129  	}
   130  
   131  	files := make(fileEntries, 0, len(filesMap))
   132  	for _, entry := range filesMap {
   133  		files = append(files, entry)
   134  	}
   135  	sort.Sort(sort.Reverse(files))
   136  	return files
   137  }
   138  
   139  type fileEntries []fileEntry
   140  
   141  func (f fileEntries) Len() int           { return len(f) }
   142  func (f fileEntries) Swap(i, j int)      { f[i], f[j] = f[j], f[i] }
   143  func (f fileEntries) Less(i, j int) bool { return f[i].Seconds < f[j].Seconds }
   144  
   145  func (f fileEntries) Duration() string {
   146  	return util.FormatDuration(f.Total())
   147  }
   148  
   149  func (f fileEntries) Total() int {
   150  	total := 0
   151  	for _, entry := range f {
   152  		total += entry.Seconds
   153  	}
   154  	return total
   155  }
   156  
   157  type fileEntry struct {
   158  	Filename string
   159  	Seconds  int
   160  }
   161  
   162  func (f *fileEntry) add(s int) {
   163  	f.Seconds += s
   164  }
   165  
   166  func (f *fileEntry) Duration() string {
   167  	return util.FormatDuration(f.Seconds)
   168  }
   169  
   170  func (f *fileEntry) IsTerminal() bool {
   171  	return f.Filename == ".gtm/terminal.app"
   172  }
   173  
   174  func (f *fileEntry) IsApp() bool {
   175  	return project.AppEventFileContentRegex.MatchString(f.Filename)
   176  }
   177  
   178  // GetAppName returns the name of the App
   179  func (f *fileEntry) GetAppName() string {
   180  	name := project.AppEventFileContentRegex.FindStringSubmatch(f.Filename)[1]
   181  	name = util.UcFirst(name)
   182  	return name
   183  }