k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/gopherage/cmd/html/html.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package html
    18  
    19  import (
    20  	"fmt"
    21  	"html/template"
    22  	"io"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  
    27  	"github.com/spf13/cobra"
    28  )
    29  
    30  type flags struct {
    31  	OutputFile string
    32  }
    33  
    34  // MakeCommand returns a `diff` command.
    35  func MakeCommand() *cobra.Command {
    36  	flags := &flags{}
    37  	cmd := &cobra.Command{
    38  		Use:   "html [coverage...]",
    39  		Short: "Emits an HTML file to browse coverage files.",
    40  		Long: `Produces a self-contained HTML file that enables browsing the provided
    41  coverage files by directory. The resulting file can be distributed alone to
    42  produce the same rendering (but does currently require gstatic.com to be
    43  accessible).
    44  
    45  If multiple files are provided, they will all be
    46  shown in the generated HTML file, with the columns in the same order the files
    47  were listed. When there are multiples columns, each column will have an arrow
    48  indicating the change from the column immediately to its right.`,
    49  		Run: func(cmd *cobra.Command, args []string) {
    50  			run(flags, cmd, args)
    51  		},
    52  	}
    53  	cmd.Flags().StringVarP(&flags.OutputFile, "output", "o", "-", "output file")
    54  	return cmd
    55  }
    56  
    57  type coverageFile struct {
    58  	Path    string `json:"path"`
    59  	Content string `json:"content"`
    60  }
    61  
    62  func run(flags *flags, cmd *cobra.Command, args []string) {
    63  	if len(args) < 1 {
    64  		fmt.Println("Expected at least one coverage file.")
    65  		cmd.Usage()
    66  		os.Exit(2)
    67  	}
    68  
    69  	var resourceDir string
    70  	if exe, err := os.Executable(); err == nil {
    71  		resourceDir = path.Join(path.Dir(exe), "cmd/html/static")
    72  	} else {
    73  		resourceDir = "gopherage/cmd/html/static"
    74  	}
    75  	if _, err := os.Stat(resourceDir); os.IsNotExist(err) {
    76  		fmt.Fprintf(os.Stderr, "Resource directory %q does not exist. Are you trying to use go run? You must build this program.\n", resourceDir)
    77  		os.Exit(1)
    78  	}
    79  
    80  	tpl, err := template.ParseFiles(filepath.Join(resourceDir, "browser.html"))
    81  	if err != nil {
    82  		fmt.Fprintf(os.Stderr, "Couldn't read the HTML template: %v.", err)
    83  		os.Exit(1)
    84  	}
    85  	script, err := os.ReadFile(filepath.Join(resourceDir, "browser_bundle.es2015.js"))
    86  	if err != nil {
    87  		fmt.Fprintf(os.Stderr, "Couldn't read JavaScript: %v.", err)
    88  		os.Exit(1)
    89  	}
    90  
    91  	// If we're under bazel, move into BUILD_WORKING_DIRECTORY so that manual
    92  	// invocations of bazel run are less confusing.
    93  	if wd, ok := os.LookupEnv("BUILD_WORKING_DIRECTORY"); ok {
    94  		if err := os.Chdir(wd); err != nil {
    95  			fmt.Fprintf(os.Stderr, "Couldn't chdir into expected working directory.")
    96  			os.Exit(1)
    97  		}
    98  	}
    99  
   100  	var coverageFiles []coverageFile
   101  	for _, arg := range args {
   102  		var content []byte
   103  		var err error
   104  		if arg == "-" {
   105  			content, err = io.ReadAll(os.Stdin)
   106  		} else {
   107  			content, err = os.ReadFile(arg)
   108  		}
   109  		if err != nil {
   110  			fmt.Fprintf(os.Stderr, "Couldn't read coverage file: %v.", err)
   111  			os.Exit(1)
   112  		}
   113  		coverageFiles = append(coverageFiles, coverageFile{Path: arg, Content: string(content)})
   114  	}
   115  
   116  	outputPath := flags.OutputFile
   117  	var output io.Writer
   118  	if outputPath == "-" {
   119  		output = os.Stdout
   120  	} else {
   121  		f, err := os.Create(outputPath)
   122  		if err != nil {
   123  			fmt.Fprintf(os.Stderr, "Couldn't open output file: %v.", err)
   124  			os.Exit(1)
   125  		}
   126  		defer f.Close()
   127  		output = f
   128  	}
   129  
   130  	err = tpl.Execute(output, struct {
   131  		Script   template.JS
   132  		Coverage []coverageFile
   133  	}{template.JS(script), coverageFiles})
   134  	if err != nil {
   135  		fmt.Fprintf(os.Stderr, "Couldn't write output file: %v.", err)
   136  	}
   137  }