~hristoast/openmw-validator

87cd8a2f32f199f4ef2a909fc0ef819d5092b197 — Hristos N. Triantafillou 28 days ago 16f8ae3
Represent more things as structs rather than strings
4 files changed, 95 insertions(+), 61 deletions(-)

M config.go
M files.go
M paths.go
M validations.go
M config.go => config.go +6 -5
@@ 30,6 30,7 @@ type gameConfig struct {
	emptyPaths     []string
	foundPaths     map[string]bool
	runCfg         *runConfig
	fullyReplaced  []*dataDir
}

// Check a single path and return all files within it that were replaced.


@@ 38,7 39,7 @@ func (g *gameConfig) checkSinglePath(path string) []*replacedFile {
	var replaced []*replacedFile

	for _, rf := range g.replacedFiles {
		if strings.HasPrefix(rf.dir, path) {
		if strings.HasPrefix(rf.dDir.path, path) {
			replaced = append(replaced, rf)
		}
	}


@@ 49,12 50,12 @@ func (g *gameConfig) checkSinglePath(path string) []*replacedFile {
// Helper method for storing details about when one file replaces another.
func (g *gameConfig) registerReplacement(new *dataFile, old *dataFile) {
	g.replacedFiles = append(g.replacedFiles, &replacedFile{
		dir:           old.path,
		path:          new.localPath,
		replacedByDir: strings.Replace(new.path, new.localPath, "", -1),
		dDir:       old.dDir,
		localPath:  new.localPath,
		replacedBy: new.dDir,
	})
	if !g.runCfg.noReplace {
		log.Printf("Replaced: %s", old.path)
		log.Printf("Replaced: %s", old.dDir.path)
	}
}


M files.go => files.go +28 -5
@@ 12,13 12,33 @@ type contentFile struct {
	order int
}

//TODO: plugin file validation is broken, it didn't notice when
//TODO: Ports Of Vvardenfell V1.6.ESP was configured and didn't exist.
//TODO: Clean_Ports Of Vvardenfell V1.6.ESP did exist.

// Represent a data file and the associated data path, "local path"
// (e.g. meshes/x/foo.nif), and the type of file it is (directory, file).
type dataFile struct {
	path      string
	dDir      *dataDir
	localPath string
}

// Helper method to detect if a particular `localPath` has already been stored.
func (d *dataFile) alreadySaved(files map[string]*dataFile) (*dataFile, bool) {
	if f, ok := files[d.localPath]; ok {
		return f, ok
	}
	return &dataFile{}, false
}

// Helper method to detect if a particular data path `path` has already been stored.
func (d *dataFile) alreadyFound(found map[string]bool) bool {
	if _, ok := found[d.dDir.path]; ok {
		return ok // true
	}
	return false
}

// Is the given `dataFile` a valid plugin file?
func (d *dataFile) isPlugin() bool {
	f := strings.ToLower(d.localPath)


@@ 28,9 48,9 @@ func (d *dataFile) isPlugin() bool {
// Strings representing data paths (e.g. `/meshes/x/foobar.nif`) that have been
// replaced, and the data dirs of the replaced and replacing files.
type replacedFile struct {
	dir           string
	path          string
	replacedByDir string
	localPath  string
	dDir       *dataDir
	replacedBy *dataDir
}

func checkContentFile(cf string, order int, gc *gameConfig) bool {


@@ 64,7 84,10 @@ func checkContentFile(cf string, order int, gc *gameConfig) bool {
		return false
	} else {
		gc.contentFiles = append(gc.contentFiles, &contentFile{
			file:  &dataFile{},
			file: &dataFile{
				dDir:      &dataDir{},
				localPath: "",
			},
			order: 0,
		})
	}

M paths.go => paths.go +31 -44
@@ 8,21 8,16 @@ import (
	"strings"
)

// Represents attributes related to a configured data path: `files` for strings
// representing full paths to files within a data path, `plugins` for strings
// representing found plugin files, `order` an int for the numerical load order
// of the data path, and `path` which a string that is the path itself.
// Represents attributes related to a configured data path.
type dataDir struct {
	files   []*dataFile
	plugins []*dataFile
	order   int
	path    string
	files    []*dataFile
	plugins  []*dataFile
	order    int
	path     string
	replaced int
}

// Given a string `d` that represents a data path root, a string `p` that represents a
// top-level data directory, and a struct pointer `dp` that represents tha `dataDir`
// object: do `filepath.Walk` on `p` and list out all files within it, recursively.
// func checkDataDirs(d string, p string, dp *dataDir, gc *gameConfig) error {
// Do `filepath.Walk` on a dataDir's paths and register found files as needed.
func (d *dataDir) checkDataDirs(gc *gameConfig) error {
	pathContent, err := ioutil.ReadDir(d.path)
	if err != nil {


@@ 45,7 40,7 @@ func (d *dataDir) checkDataDirs(gc *gameConfig) error {

					if !nfo.IsDir() && !ignoredFile(filePath) {
						df := &dataFile{
							path:      filePath,
							dDir:      d,
							localPath: localPath,
						}
						d.files = append(d.files, df)


@@ 53,9 48,10 @@ func (d *dataDir) checkDataDirs(gc *gameConfig) error {
						// Does gc.dataFiles already have this localPath stored as a key? If
						// so, check for an overwrite and log as needed.
						// THANKS: https://stackoverflow.com/a/2050629
						val, ok := alreadySaved(df.localPath, gc.dataFiles)
						oldf, ok := df.alreadySaved(gc.dataFiles)
						if ok {
							gc.registerReplacement(df, val)
							gc.registerReplacement(df, oldf)
							d.replaced += 1
						}
						gc.dataFiles[localPath] = df



@@ 71,17 67,18 @@ func (d *dataDir) checkDataDirs(gc *gameConfig) error {
			path := filepath.Join(d.path, pc.Name())
			if !ignoredFile(path) {
				df := &dataFile{
					path:      path,
					dDir:      d,
					localPath: pc.Name(),
				}
				val, ok := alreadySaved(df.localPath, gc.dataFiles)
				oldf, ok := df.alreadySaved(gc.dataFiles)
				if ok {
					gc.registerReplacement(df, val)
					gc.registerReplacement(df, oldf)
					d.replaced += 1
				}

				if df.isPlugin() {
					d.plugins = append(d.plugins, df)
					gc.foundPlugins = append(gc.foundPlugins, df.path)
					gc.foundPlugins = append(gc.foundPlugins, df.localPath)
				}

				gc.dataFiles[df.localPath] = df


@@ 91,28 88,17 @@ func (d *dataDir) checkDataDirs(gc *gameConfig) error {
	return err
}

// Helper method to detect if a particular data path `path` has already been stored.
func alreadyFound(path string, found map[string]bool) bool {
	if _, ok := found[path]; ok {
func (d *dataDir) alreadyFound(found map[string]bool) bool {
	if _, ok := found[d.path]; ok {
		return ok // true
	}
	return false
}

// Helper method to detect if a particular `localPath` has already been stored.
func alreadySaved(localPath string, files map[string]*dataFile) (*dataFile, bool) {
	if val, ok := files[localPath]; ok {
		return val, ok
	}
	return &dataFile{}, false
}

// Format a given string `dp` which is a `data=` entry from an openmw.cfg file
// as just the path with no extra quotes or anything. Check that it's a real
// thing, then scan it for files.
func checkDataPath(dp string, dataOrder int, gc *gameConfig) (*dataDir, error) {
// Verify that a dataDir is real, then scan it for files.
func (d *dataDir) check(dataOrder int, gc *gameConfig) error {
	// Format the path...
	p := strings.Replace(dp, "data=", "", -1)
	p := strings.Replace(d.path, "data=", "", -1)
	p = strings.TrimPrefix(p, "'")
	p = strings.TrimPrefix(p, "\"")
	p = strings.TrimSuffix(p, "\r")


@@ 123,7 109,7 @@ func checkDataPath(dp string, dataOrder int, gc *gameConfig) (*dataDir, error) {
		log.Printf("Checking: %v", p)
	}

	found := alreadyFound(p, gc.foundPaths)
	found := d.alreadyFound(gc.foundPaths)
	if found {
		log.Printf("Path already configured: %s", p)
		gc.duplicatePaths = append(gc.duplicatePaths, p)


@@ 134,16 120,17 @@ func checkDataPath(dp string, dataOrder int, gc *gameConfig) (*dataDir, error) {
	_, err := os.Stat(p)
	if os.IsNotExist(err) {
		gc.badPaths = append(gc.badPaths, p)
		return &dataDir{}, err
		return err
	}

	d := &dataDir{
		files:   []*dataFile{},
		plugins: []*dataFile{},
		order:   dataOrder,
		path:    p,
	dd := &dataDir{
		files:    []*dataFile{},
		plugins:  []*dataFile{},
		order:    dataOrder,
		path:     p,
		replaced: 0,
	}
	d.checkDataDirs(gc)
	dd.checkDataDirs(gc)

	return d, err
	return err
}

M validations.go => validations.go +30 -7
@@ 24,6 24,7 @@ func runValidations(runCfg *runConfig) {
		emptyPaths:     []string{},
		foundPaths:     map[string]bool{},
		runCfg:         runCfg,
		fullyReplaced:  []*dataDir{},
	}

	if len(d.contentFiles) == 0 || len(d.dataPaths) == 0 {


@@ 37,7 38,15 @@ func runValidations(runCfg *runConfig) {
		log.Println()
	}
	for _, p := range d.dataPaths {
		dp, err := checkDataPath(p, dataDirCount, g)
		dd := &dataDir{
			files:    []*dataFile{},
			plugins:  []*dataFile{},
			order:    0,
			path:     p,
			replaced: 0,
		}
		err := dd.check(dd.order, g)

		if err != nil {
			// Most likely this path didn't actually exist..
			if !runCfg.quiet {


@@ 46,7 55,7 @@ func runValidations(runCfg *runConfig) {

		} else {
			dataDirCount++
			g.dataDirs = append(g.dataDirs, dp)
			g.dataDirs = append(g.dataDirs, dd)
		}
	}



@@ 86,7 95,18 @@ func runValidations(runCfg *runConfig) {
		}

		if !runCfg.quiet {
			log.Printf("Data path #%d \"%s\" has %d %s and %d %s", p.order, p.path, fileCount, fileWord, pluginCount, pluginWord)

			fWord := "file"
			wWord := "was"
			if p.replaced == 0 || p.replaced > 1 {
				fWord += "s"
				wWord = "were"
			}
			log.Printf("Data path #%d \"%s\" has %d %s and %d %s. %d %s %s replaced.", p.order, p.path, fileCount, fileWord, pluginCount, pluginWord, p.replaced, fWord, wWord)

			if len(p.files) >= p.replaced {
				g.fullyReplaced = append(g.fullyReplaced, p)
			}
		}
	}



@@ 113,6 133,7 @@ func runValidations(runCfg *runConfig) {
	haveEmptyPaths := len(g.emptyPaths) > 0
	haveDupedPaths := dupeCount > 0
	haveBunkContent := len(g.bunkContent) > 0
	haveFullyReplaced := len(g.fullyReplaced) > 0

	if haveBadPaths {
		log.Printf("Bad data paths count: %v", len(g.badPaths))


@@ 154,13 175,13 @@ func runValidations(runCfg *runConfig) {
		log.Println()
	}

	if !haveBadPaths && !haveEmptyPaths && !haveDupedPaths && !haveBunkContent {
	//TODO: report on fully replaced

	if !haveBadPaths && !haveEmptyPaths && !haveDupedPaths && !haveBunkContent && !haveFullyReplaced {
		log.Println("Great Job! No problems detected.")
		log.Println()
	}

	//TODO: Report any paths that have been fully replaced

	if runCfg.checkPath != "" {
		var dd *dataDir
		for _, d := range g.dataDirs {


@@ 182,7 203,7 @@ func runValidations(runCfg *runConfig) {
					log.Printf("The following %d files in this path have been replaced:", len(replaced))
					log.Println()
					for _, r := range replaced {
						log.Printf("\"%s\" replaced by: \"%s\"", r.path, r.replacedByDir)
						log.Printf("\"%s\" replaced by: \"%s\"", r.localPath, r.replacedBy.path)
					}
					log.Println()
				}


@@ 201,6 222,8 @@ func runValidations(runCfg *runConfig) {

	}

	//TODO: Check groundcover files with content files
	//TODO: Report if a data path is totally replaced
	//TODO: Report unused content files (ones found in data paths but not configured as content files)
	//TODO: Very quiet mode, that just exits 0 or 1
	//TODO: Tests for everything