From 1f859aef0a8201b5d0fd4d6669dde3925f3340a8 Mon Sep 17 00:00:00 2001 From: k1LoW Date: Thu, 4 Mar 2021 16:56:03 +0900 Subject: [PATCH] Add command `ndiag coverage` --- cmd/coverage.go | 82 ++++++++++++++++++++++++++++ coverage/coverage.go | 125 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 4 ++ go.sum | 3 ++ 4 files changed, 214 insertions(+) create mode 100644 cmd/coverage.go create mode 100644 coverage/coverage.go diff --git a/cmd/coverage.go b/cmd/coverage.go new file mode 100644 index 00000000..b2faee9e --- /dev/null +++ b/cmd/coverage.go @@ -0,0 +1,82 @@ +/* +Copyright © 2021 Ken'ichiro Oyama + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "fmt" + "os" + + "github.com/goccy/go-json" + "github.com/k1LoW/ndiag/coverage" + "github.com/labstack/gommon/color" + "github.com/mattn/go-runewidth" + "github.com/spf13/cobra" +) + +var cformat string + +// coverageCmd represents the coverage command +var coverageCmd = &cobra.Command{ + Use: "coverage", + Short: "measure document coverage", + Long: `measure document coverage.`, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, err := newConfig() + if err != nil { + return err + } + cover := coverage.Measure(cfg) + switch cformat { + case "json": + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + err := encoder.Encode(cover) + if err != nil { + return err + } + default: + max := runewidth.StringWidth("All Elements") + fmtName := fmt.Sprintf("%%-%ds", max) + fmt.Printf("%s %s\n", color.White(fmt.Sprintf(fmtName, "Elements"), color.B), color.White("Coverage", color.B)) + fmt.Printf("%s %g%% (%d/%d)\n", fmt.Sprintf(fmtName, "All Elements"), cover.Coverage, cover.Covered, cover.Total) + if cover.Views != nil { + fmt.Printf("%s %g%% (%d/%d)\n", fmt.Sprintf(fmtName, "Views"), cover.Views.Coverage, cover.Views.Covered, cover.Views.Total) + } + fmt.Printf("%s %g%% (%d/%d)\n", fmt.Sprintf(fmtName, "Nodes"), cover.Nodes.Coverage, cover.Nodes.Covered, cover.Nodes.Total) + fmt.Printf("%s %g%% (%d/%d)\n", fmt.Sprintf(fmtName, "Components"), cover.Components.Coverage, cover.Components.Covered, cover.Components.Total) + if cover.Relations != nil { + fmt.Printf("%s %g%% (%d/%d)\n", fmt.Sprintf(fmtName, "Relations"), cover.Relations.Coverage, cover.Relations.Covered, cover.Relations.Total) + } + fmt.Printf("%s %g%% (%d/%d)\n", fmt.Sprintf(fmtName, "Layers"), cover.Layers.Coverage, cover.Layers.Covered, cover.Layers.Total) + if cover.Labels != nil { + fmt.Printf("%s %g%% (%d/%d)\n", fmt.Sprintf(fmtName, "Labels"), cover.Labels.Coverage, cover.Labels.Covered, cover.Labels.Total) + } + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(coverageCmd) + coverageCmd.Flags().StringVarP(&configPath, "config", "c", "", "config file path") + coverageCmd.Flags().StringVarP(&cformat, "format", "t", "", "output format") +} diff --git a/coverage/coverage.go b/coverage/coverage.go new file mode 100644 index 00000000..09f40bd1 --- /dev/null +++ b/coverage/coverage.go @@ -0,0 +1,125 @@ +package coverage + +import ( + "math" + + "github.com/k1LoW/ndiag/config" +) + +type Coverage struct { + Name string + Coverage float64 + Views *CoverageByElement `json:"views,omitempty"` + Nodes *CoverageByElement + Components *CoverageByElement + Relations *CoverageByElement `json:"relations,omitempty"` + Layers *CoverageByElement + Labels *CoverageByElement `json:"labels,omitempty"` + Covered int + Total int +} + +type CoverageByElement struct { + Coverage float64 + Covered int + Total int +} + +// Measure coverage +func Measure(cfg *config.Config) *Coverage { + cover := &Coverage{ + Name: cfg.Name, + Nodes: &CoverageByElement{}, + Components: &CoverageByElement{}, + Layers: &CoverageByElement{}, + } + // index + cover.Total += 1 + if cfg.Desc != "" { + cover.Covered += 1 + } + + // views + if !cfg.HideViews { + cover.Views = &CoverageByElement{} + for _, v := range cfg.Views { + cover.Views.Total += 1 + if v.Desc != "" { + cover.Views.Covered += 1 + } + } + cover.Views.Coverage = round(float64(cover.Views.Covered) / float64(cover.Views.Total) * 100) + cover.Total += cover.Views.Total + cover.Covered += cover.Views.Covered + } + + // nodes + for _, n := range cfg.Nodes { + cover.Nodes.Total += 1 + if n.Desc != "" { + cover.Nodes.Covered += 1 + } + } + cover.Nodes.Coverage = round(float64(cover.Nodes.Covered) / float64(cover.Nodes.Total) * 100) + cover.Total += cover.Nodes.Total + cover.Covered += cover.Nodes.Covered + + // components + for _, c := range cfg.Components() { + cover.Components.Total += 1 + if c.Desc != "" { + cover.Components.Covered += 1 + } + } + cover.Components.Coverage = round(float64(cover.Components.Covered) / float64(cover.Components.Total) * 100) + cover.Total += cover.Components.Total + cover.Covered += cover.Views.Covered + + // relations + if !(cfg.HideViews && cfg.HideLabels) { + cover.Relations = &CoverageByElement{} + for _, r := range cfg.Relations { + cover.Relations.Total += 1 + if r.Desc != "" { + cover.Relations.Covered += 1 + } + } + cover.Components.Coverage = round(float64(cover.Components.Covered) / float64(cover.Components.Total) * 100) + cover.Total += cover.Components.Total + cover.Covered += cover.Components.Covered + } + + // layers + for _, l := range cfg.Layers() { + cover.Layers.Total += 1 + if l.Desc != "" { + cover.Layers.Covered += 1 + } + } + cover.Layers.Coverage = round(float64(cover.Layers.Covered) / float64(cover.Layers.Total) * 100) + cover.Total += cover.Layers.Total + cover.Covered += cover.Layers.Covered + + // labels + if !cfg.HideLabels { + cover.Labels = &CoverageByElement{} + for _, l := range cfg.Labels() { + cover.Labels.Total += 1 + if l.Desc != "" { + cover.Labels.Covered += 1 + } + } + cover.Labels.Coverage = round(float64(cover.Labels.Covered) / float64(cover.Labels.Total) * 100) + cover.Total += cover.Labels.Total + cover.Covered += cover.Labels.Covered + } + + cover.Coverage = round(float64(cover.Covered) / float64(cover.Total) * 100) + return cover +} + +func round(f float64) float64 { + places := 1 + shift := math.Pow(10, float64(places)) + return math.Floor(f*shift+.5) / shift +} diff --git a/go.mod b/go.mod index 17af1e8a..e9cc449f 100644 --- a/go.mod +++ b/go.mod @@ -7,15 +7,19 @@ require ( github.com/elliotchance/orderedmap v1.3.0 github.com/gobuffalo/packr/v2 v2.8.0 github.com/goccy/go-graphviz v0.0.8 + github.com/goccy/go-json v0.4.7 github.com/goccy/go-yaml v1.8.4 github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c github.com/k1LoW/glyph v0.5.1-0.20210105154244-06dafc0214e7 github.com/k1LoW/tbls v1.43.1 github.com/karrick/godirwalk v1.16.1 + github.com/labstack/gommon v0.3.0 + github.com/mattn/go-runewidth v0.0.9 github.com/muesli/gamut v0.2.0 github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 github.com/olekukonko/tablewriter v0.0.4 github.com/pasztorpisti/qs v0.0.0-20171216220353-8d6c33ee906c + github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.1.1 github.com/stoewer/go-strcase v1.2.0 ) diff --git a/go.sum b/go.sum index cd489ae4..d3a237d2 100644 --- a/go.sum +++ b/go.sum @@ -163,6 +163,8 @@ github.com/gobuffalo/packr/v2 v2.8.0/go.mod h1:PDk2k3vGevNE3SwVyVRgQCCXETC9SaONC github.com/goccy/go-graphviz v0.0.6/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk= github.com/goccy/go-graphviz v0.0.8 h1:hYQikvj368s8+rmfzFOZeiCXvSocGH7rfAyLTOy/7AM= github.com/goccy/go-graphviz v0.0.8/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk= +github.com/goccy/go-json v0.4.7 h1:xGUjaNfhpqhKAV2LoyNXihFLZ8ABSST8B+W+duHqkPI= +github.com/goccy/go-json v0.4.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.7.18/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y= github.com/goccy/go-yaml v1.8.4 h1:AOEdR7aQgbgwHznGe3BLkDQVujxCPUpHOZZcQcp8Y3M= github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= @@ -326,6 +328,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=