github.com/yoheimuta/protolint@v0.49.8-0.20240515023657-4ecaebb7575d/internal/cmd/protocgenprotolint/cmd.go (about)

     1  package protocgenprotolint
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"google.golang.org/protobuf/types/pluginpb"
    13  
    14  	"github.com/yoheimuta/protolint/internal/cmd/subcmds"
    15  
    16  	"github.com/yoheimuta/protolint/internal/cmd/subcmds/lint"
    17  
    18  	"github.com/golang/protobuf/proto"
    19  	protogen "github.com/golang/protobuf/protoc-gen-go/plugin"
    20  
    21  	"github.com/yoheimuta/protolint/internal/osutil"
    22  )
    23  
    24  var (
    25  	version  = "master"
    26  	revision = "latest"
    27  )
    28  
    29  const (
    30  	subCmdVersion = "version"
    31  )
    32  
    33  // Do runs the command logic.
    34  func Do(
    35  	args []string,
    36  	stdin io.Reader,
    37  	stdout io.Writer,
    38  	stderr io.Writer,
    39  ) osutil.ExitCode {
    40  	if 0 < len(args) && args[0] == subCmdVersion {
    41  		return doVersion(stdout)
    42  	}
    43  
    44  	err := signalSupportProto3Optional()
    45  	if err != nil {
    46  		_, _ = fmt.Fprintln(stderr, err)
    47  		return osutil.ExitInternalFailure
    48  	}
    49  
    50  	subCmd, err := newSubCmd(stdin, stdout, stderr)
    51  	if err != nil {
    52  		_, _ = fmt.Fprintln(stderr, err)
    53  		return osutil.ExitInternalFailure
    54  	}
    55  	return subCmd.Run()
    56  }
    57  
    58  func signalSupportProto3Optional() error {
    59  	// supports proto3 field presence
    60  	// See https://github.com/protocolbuffers/protobuf/blob/cdc11c2d2d314ce0382fe0eaa715e5e0e1270438/docs/implementing_proto3_presence.md#signaling-that-your-code-generator-supports-proto3-optional
    61  	var supportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
    62  	data, err := proto.Marshal(&protogen.CodeGeneratorResponse{
    63  		SupportedFeatures: &supportedFeatures,
    64  	})
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	_, err = io.Copy(os.Stdout, bytes.NewReader(data))
    70  	if err != nil {
    71  		return err
    72  	}
    73  	return nil
    74  }
    75  
    76  func newSubCmd(
    77  	stdin io.Reader,
    78  	stdout io.Writer,
    79  	stderr io.Writer,
    80  ) (*lint.CmdLint, error) {
    81  	data, err := ioutil.ReadAll(stdin)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	var req protogen.CodeGeneratorRequest
    87  	err = proto.Unmarshal(data, &req)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	flags, err := newFlags(&req)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	subCmd, err := lint.NewCmdLint(
    98  		*flags,
    99  		stdout,
   100  		stderr,
   101  	)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	return subCmd, nil
   106  }
   107  
   108  func newFlags(
   109  	req *protogen.CodeGeneratorRequest,
   110  ) (*lint.Flags, error) {
   111  	flags, err := lint.NewFlags(req.FileToGenerate)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	var pf subcmds.PluginFlag
   117  	for _, p := range strings.Split(req.GetParameter(), ",") {
   118  		params := strings.SplitN(strings.TrimSpace(p), "=", 2)
   119  		switch params[0] {
   120  		case "":
   121  			continue
   122  		case "config_path":
   123  			if len(params) != 2 {
   124  				return nil, fmt.Errorf("config_path should be specified")
   125  			}
   126  			flags.ConfigDirPath = params[1]
   127  		case "config_dir_path":
   128  			if len(params) != 2 {
   129  				return nil, fmt.Errorf("config_dir_path should be specified")
   130  			}
   131  			flags.ConfigDirPath = params[1]
   132  		case "fix":
   133  			flags.FixMode = true
   134  		case "reporter":
   135  			if len(params) != 2 {
   136  				return nil, fmt.Errorf("reporter should be specified")
   137  			}
   138  			value := params[1]
   139  			r, err := lint.GetReporter(value)
   140  			if err != nil {
   141  				return nil, err
   142  			}
   143  			flags.Reporter = r
   144  		case "output_file":
   145  			if len(params) != 2 {
   146  				return nil, fmt.Errorf("output_file should be specified")
   147  			}
   148  			flags.OutputFilePath = params[1]
   149  		case "plugin":
   150  			if len(params) != 2 {
   151  				return nil, fmt.Errorf("plugin should be specified")
   152  			}
   153  			err = pf.Set(params[1])
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  		case "v":
   158  			flags.Verbose = true
   159  		case "proto_root":
   160  			if len(params) != 2 {
   161  				return nil, fmt.Errorf("proto_root should be specified")
   162  			}
   163  			for i, f := range flags.FilePaths {
   164  				flags.FilePaths[i] = filepath.Join(params[1], f)
   165  			}
   166  		default:
   167  			return nil, fmt.Errorf("unmatched parameter: %s", p)
   168  		}
   169  	}
   170  
   171  	plugins, err := pf.BuildPlugins(flags.Verbose)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	flags.Plugins = plugins
   176  
   177  	return &flags, nil
   178  }
   179  
   180  func doVersion(
   181  	stdout io.Writer,
   182  ) osutil.ExitCode {
   183  	_, _ = fmt.Fprintln(stdout, "protoc-gen-protolint version "+version+"("+revision+")")
   184  	return osutil.ExitSuccess
   185  }