Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func main() {
mode, _ := cmd.Flags().GetString("mode")
verbose, _ := cmd.Flags().GetBool("verbose")
appstore, _ := cmd.Flags().GetBool("appstore")
filter, _ := cmd.Flags().GetString("filter")

log := logrus.New()
if verbose {
Expand All @@ -35,13 +36,14 @@ func main() {
}

mpr.SetLogger(log)
mpr.ExportModel(inputDirectory, outputDirectory, raw, mode, appstore)
mpr.ExportModel(inputDirectory, outputDirectory, raw, mode, appstore, filter)
},
}

cmdExportModel.Flags().StringP("input", "i", ".", "Path to directory or mpr file to export. If it's a directory, all mpr files will be exported")
cmdExportModel.Flags().StringP("output", "o", "modelsource", "Path to directory to write the yaml files. If it doesn't exist, it will be created")
cmdExportModel.Flags().StringP("mode", "m", "basic", "Export mode. Valid options: basic, advanced")
cmdExportModel.Flags().StringP("filter", "f", "", "Regex pattern to filter units by name. Only units with names matching the pattern will be exported")
cmdExportModel.Flags().Bool("raw", false, "If set, the output yaml will include all attributes as they are in the model. Otherwise, only the relevant attributes are included. You should never need this. Only useful when you are developing new functionalities for this tool.")
cmdExportModel.Flags().Bool("appstore", false, "If set, appstore modules will be included in the output")
cmdExportModel.Flags().Bool("verbose", false, "Turn on for debug logs")
Expand Down
6 changes: 3 additions & 3 deletions mpr/microflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func TestMPRMicroflow(t *testing.T) {
t.Run("microflow-simple", func(t *testing.T) {
if err := exportUnits("./../resources/app-mpr-v1/App.mpr", "./../tmp", true, "advanced"); err != nil {
if err := exportUnits("./../resources/app-mpr-v1/App.mpr", "./../tmp", true, "advanced", ""); err != nil {
t.Errorf("Failed to export units from MPR file")
}

Expand Down Expand Up @@ -42,7 +42,7 @@ func TestMPRMicroflow(t *testing.T) {
}
})
t.Run("microflow-with-split", func(t *testing.T) {
if err := exportUnits("./../resources/app-mpr-v1/App.mpr", "./../tmp", true, "advanced"); err != nil {
if err := exportUnits("./../resources/app-mpr-v1/App.mpr", "./../tmp", true, "advanced", ""); err != nil {
t.Errorf("Failed to export units from MPR file")
}

Expand Down Expand Up @@ -71,7 +71,7 @@ func TestMPRMicroflow(t *testing.T) {
}
})
t.Run("microflow-split-then-merge", func(t *testing.T) {
if err := exportUnits("./../resources/app-mpr-v1/App.mpr", "./../tmp", true, "advanced"); err != nil {
if err := exportUnits("./../resources/app-mpr-v1/App.mpr", "./../tmp", true, "advanced", ""); err != nil {
t.Errorf("Failed to export units from MPR file")
}

Expand Down
34 changes: 30 additions & 4 deletions mpr/mpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"os"
"path/filepath"
"regexp"
"strings"

"gopkg.in/yaml.v3"
Expand All @@ -30,7 +31,7 @@ const (
MaxComponentLength = 100
)

func ExportModel(inputDirectory string, outputDirectory string, raw bool, mode string, appstore bool) error {
func ExportModel(inputDirectory string, outputDirectory string, raw bool, mode string, appstore bool, filter string) error {

// create tmp directory in user tmp directory
tmpDir := filepath.Join(os.TempDir(), "mxlint")
Expand Down Expand Up @@ -63,8 +64,10 @@ func ExportModel(inputDirectory string, outputDirectory string, raw bool, mode s
return fmt.Errorf("error exporting metadata: %v", err)
}

if err := exportUnits(inputDirectory, tmpDir, raw, mode); err != nil {
return fmt.Errorf("error exporting units: %v", err)
if filter != "^Metadata$" {
if err := exportUnits(inputDirectory, tmpDir, raw, mode, filter); err != nil {
return fmt.Errorf("error exporting units: %v", err)
}
}

// remove output directory if it exists
Expand Down Expand Up @@ -452,7 +455,7 @@ func getMxDocuments(units []MxUnit, folders []MxFolder, mode string) ([]MxDocume
return documents, nil
}

func exportUnits(inputDirectory string, outputDirectory string, raw bool, mode string) error {
func exportUnits(inputDirectory string, outputDirectory string, raw bool, mode string, filter string) error {
log.Debugf("Exporting units from %s to %s", inputDirectory, outputDirectory)

units, err := getMxUnits(inputDirectory)
Expand All @@ -469,7 +472,25 @@ func exportUnits(inputDirectory string, outputDirectory string, raw bool, mode s
return fmt.Errorf("error getting documents: %v", err)
}

// Compile the filter regex if provided
var filterRegex *regexp.Regexp
if filter != "" {
filterRegex, err = regexp.Compile(filter)
if err != nil {
return fmt.Errorf("invalid filter regex pattern: %v", err)
}
log.Infof("Applying filter: %s", filter)
}

exportedCount := 0
for _, document := range documents {
// Apply filter if provided
if filterRegex != nil {
if !filterRegex.MatchString(document.Name) {
log.Debugf("Skipping document '%s' (does not match filter)", document.Name)
continue
}
}
// write document
// Sanitize the document path to handle invalid characters
sanitizedPath := sanitizePath(document.Path)
Expand Down Expand Up @@ -510,6 +531,11 @@ func exportUnits(inputDirectory string, outputDirectory string, raw bool, mode s
log.Errorf("Error writing file: %v", err)
return err
}
exportedCount++
}

if filterRegex != nil {
log.Infof("Exported %d documents matching filter (out of %d total)", exportedCount, len(documents))
}

return nil
Expand Down
87 changes: 85 additions & 2 deletions mpr/mpr_v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestMPRMetadata(t *testing.T) {

func TestMPRUnits(t *testing.T) {
t.Run("single-mpr", func(t *testing.T) {
if err := exportUnits("./../resources/app-mpr-v1", "./../tmp", false, "basic"); err != nil {
if err := exportUnits("./../resources/app-mpr-v1", "./../tmp", false, "basic", ""); err != nil {
t.Errorf("Failed to export units from MPR file")
}
})
Expand All @@ -51,7 +51,7 @@ func TestMPRUnits(t *testing.T) {
func TestIDAttributesExclusion(t *testing.T) {
t.Run("verify-id-attributes-excluded", func(t *testing.T) {
// Export units with ID attributes excluded
if err := exportUnits("./../resources/app-mpr-v1", "./../tmp", false, "basic"); err != nil {
if err := exportUnits("./../resources/app-mpr-v1", "./../tmp", false, "basic", ""); err != nil {
t.Errorf("Failed to export units from MPR file: %v", err)
return
}
Expand Down Expand Up @@ -98,3 +98,86 @@ func TestIDAttributesExclusion(t *testing.T) {
}
})
}

func TestFilterMetadataOnly(t *testing.T) {
t.Run("filter-metadata-exact-match", func(t *testing.T) {
// Clean up test directory
testDir := "./../tmp-filter-metadata"
os.RemoveAll(testDir)
defer os.RemoveAll(testDir)

// Export with filter ^Metadata$
// According to the code, when filter is "^Metadata$", only metadata is exported, no units
if err := ExportModel("./../resources/app-mpr-v1", testDir, false, "basic", false, "^Metadata$"); err != nil {
t.Errorf("Failed to export with Metadata filter: %v", err)
return
}

// Check that Metadata.yaml exists
metadataPath := filepath.Join(testDir, "Metadata.yaml")
if _, err := os.Stat(metadataPath); os.IsNotExist(err) {
t.Errorf("Metadata.yaml was not created")
return
}

// Check that no other files/directories were created (since filter is ^Metadata$ and units are skipped)
entries, err := os.ReadDir(testDir)
if err != nil {
t.Errorf("Failed to read test directory: %v", err)
return
}

// Should only have Metadata.yaml
if len(entries) != 1 {
t.Errorf("Expected only Metadata.yaml, but found %d entries", len(entries))
return
}

if entries[0].Name() != "Metadata.yaml" {
t.Errorf("Expected Metadata.yaml, but found %s", entries[0].Name())
}
})
}

func TestFilterConstantPattern(t *testing.T) {
t.Run("filter-constant-pattern", func(t *testing.T) {
// Clean up test directory
testDir := "./../tmp-filter-constant"
os.RemoveAll(testDir)
defer os.RemoveAll(testDir)

// Export with filter ^Constant.*
// This pattern won't match any documents in the test data, so we should get only metadata
if err := ExportModel("./../resources/app-mpr-v1", testDir, false, "basic", false, "^Constant.*"); err != nil {
t.Errorf("Failed to export with Constant filter: %v", err)
return
}

// Check that Metadata.yaml exists (always exported)
metadataPath := filepath.Join(testDir, "Metadata.yaml")
if _, err := os.Stat(metadataPath); os.IsNotExist(err) {
t.Errorf("Metadata.yaml was not created")
return
}

// Check that no module directories were created (since no documents match the filter)
entries, err := os.ReadDir(testDir)
if err != nil {
t.Errorf("Failed to read test directory: %v", err)
return
}

// Should only have Metadata.yaml since no documents match ^Constant.*
if len(entries) != 1 {
t.Errorf("Expected only Metadata.yaml when no documents match filter, but found %d entries", len(entries))
for _, entry := range entries {
t.Logf("Found entry: %s", entry.Name())
}
return
}

if entries[0].Name() != "Metadata.yaml" {
t.Errorf("Expected Metadata.yaml, but found %s", entries[0].Name())
}
})
}
2 changes: 1 addition & 1 deletion mpr/mpr_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestMPRV2Metadata(t *testing.T) {

func TestMPRV2Units(t *testing.T) {
t.Run("single-mpr", func(t *testing.T) {
if err := exportUnits("./../resources/app-mpr-v2", "./../tmp", false, "basic"); err != nil {
if err := exportUnits("./../resources/app-mpr-v2", "./../tmp", false, "basic", ""); err != nil {
t.Errorf("Failed to export units from MPR file")
}
})
Expand Down
2 changes: 1 addition & 1 deletion serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func runServe(cmd *cobra.Command, args []string) {
}()

log.Infof("Running export-model and lint")
err := mpr.ExportModel(inputDirectory, outputDirectory, false, mode, false)
err := mpr.ExportModel(inputDirectory, outputDirectory, false, mode, false, "")
if err != nil {
log.Warningf("Export failed: %s", err)
resultMutex.Lock()
Expand Down
Loading