| /* |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package cmd |
| |
| import ( |
| "context" |
| "fmt" |
| "path" |
| "path/filepath" |
| |
| "github.com/apache/camel-k/pkg/util/modeline" |
| "github.com/pkg/errors" |
| "github.com/spf13/cobra" |
| "github.com/spf13/pflag" |
| ) |
| |
| const ( |
| runCmdName = "run" |
| localCmdName = "local" |
| runCmdSourcesArgs = "source" |
| ) |
| |
| var ( |
| nonRunOptions = map[string]bool{ |
| "language": true, // language is a marker modeline option for other tools |
| } |
| disallowedOptions = map[string]bool{ |
| "dev": true, |
| "wait": true, |
| "logs": true, |
| "sync": true, |
| } |
| |
| // file options must be considered relative to the source files they belong to |
| fileOptions = map[string]bool{ |
| "resource": true, |
| "config": true, |
| "open-api": true, |
| "property-file": true, |
| } |
| ) |
| |
| // NewKamelWithModelineCommand --- |
| func NewKamelWithModelineCommand(ctx context.Context, osArgs []string) (*cobra.Command, []string, error) { |
| originalFlags := osArgs[1:] |
| rootCmd, flags, err := createKamelWithModelineCommand(ctx, originalFlags) |
| if err != nil { |
| fmt.Printf("Error: %s\n", err.Error()) |
| return rootCmd, flags, err |
| } |
| if len(originalFlags) != len(flags) { |
| // Give a feedback about the actual command that is run |
| fmt.Fprintln(rootCmd.OutOrStdout(), "Modeline options have been loaded from source files") |
| fmt.Fprint(rootCmd.OutOrStdout(), "Full command: kamel ") |
| for _, a := range flags { |
| fmt.Fprintf(rootCmd.OutOrStdout(), "%s ", a) |
| } |
| fmt.Fprintln(rootCmd.OutOrStdout()) |
| } |
| return rootCmd, flags, nil |
| } |
| |
| func createKamelWithModelineCommand(ctx context.Context, args []string) (*cobra.Command, []string, error) { |
| rootCmd, err := NewKamelCommand(ctx) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| target, flags, err := rootCmd.Find(args) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| if target.Name() != runCmdName { |
| return rootCmd, args, nil |
| } |
| |
| err = target.ParseFlags(flags) |
| if err == pflag.ErrHelp { |
| return rootCmd, args, nil |
| } else if err != nil { |
| return nil, nil, err |
| } |
| |
| fg := target.Flags() |
| |
| // Only the run command has source flag (for now). Remove condition when |
| // local run also supports source. |
| additionalSources := make([]string, 0) |
| if target.Name() == runCmdName && target.Parent().Name() != localCmdName { |
| additionalSources, err = fg.GetStringArray(runCmdSourcesArgs) |
| if err != nil { |
| return nil, nil, err |
| } |
| } |
| |
| files := make([]string, 0, len(fg.Args())+len(additionalSources)) |
| files = append(files, fg.Args()...) |
| files = append(files, additionalSources...) |
| |
| opts, err := extractModelineOptions(ctx, files) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "cannot read sources") |
| } |
| |
| // filter out in place non-run options |
| nOpts := 0 |
| for _, o := range opts { |
| if !nonRunOptions[o.Name] { |
| opts[nOpts] = o |
| nOpts++ |
| } |
| } |
| |
| opts = opts[:nOpts] |
| |
| for _, o := range opts { |
| prefix := "-" |
| if len(o.Name) > 1 { |
| prefix = "--" |
| } |
| // Using the k=v syntax to avoid issues with booleans |
| if len(o.Value) > 0 { |
| args = append(args, fmt.Sprintf("%s%s=%s", prefix, o.Name, o.Value)) |
| } else { |
| args = append(args, fmt.Sprintf("%s%s", prefix, o.Name)) |
| } |
| } |
| |
| // Recreating the command as it's dirty |
| rootCmd, err = NewKamelCommand(ctx) |
| if err != nil { |
| return nil, nil, err |
| } |
| rootCmd.SetArgs(args) |
| |
| return rootCmd, args, nil |
| } |
| |
| func extractModelineOptions(ctx context.Context, sources []string) ([]modeline.Option, error) { |
| opts := make([]modeline.Option, 0) |
| |
| resolvedSources, err := ResolveSources(ctx, sources, false) |
| if err != nil { |
| return opts, errors.Wrap(err, "cannot read sources") |
| } |
| |
| for _, resolvedSource := range resolvedSources { |
| ops, err := extractModelineOptionsFromSource(resolvedSource) |
| if err != nil { |
| return opts, err |
| } |
| |
| opts = append(opts, ops...) |
| } |
| |
| return opts, nil |
| } |
| |
| func extractModelineOptionsFromSource(resolvedSource Source) ([]modeline.Option, error) { |
| ops, err := modeline.Parse(resolvedSource.Location, resolvedSource.Content) |
| if err != nil { |
| return ops, errors.Wrapf(err, "cannot process file %s", resolvedSource.Location) |
| } |
| for i, o := range ops { |
| if disallowedOptions[o.Name] { |
| return ops, fmt.Errorf("option %q is disallowed in modeline", o.Name) |
| } |
| |
| if fileOptions[o.Name] && resolvedSource.Local { |
| baseDir := filepath.Dir(resolvedSource.Origin) |
| refPath := o.Value |
| if !filepath.IsAbs(refPath) { |
| full := path.Join(baseDir, refPath) |
| o.Value = full |
| ops[i] = o |
| } |
| } |
| } |
| |
| return ops, nil |
| } |