From 6af2f86d3e7cdd5468c57a12ca912e73f2936a4b Mon Sep 17 00:00:00 2001 From: Mike Schinkel Date: Wed, 18 Aug 2021 05:02:23 -0400 Subject: [PATCH 1/4] Code Review and Refactored --- CODE_REVIEW.md | 48 ++ {internal/app => app/lpm}/VERSION | 0 app/lpm/main.go | 16 + app/populator/module.go | 218 +++++++ app/populator/modules.go | 58 ++ app/populator/populator.go | 50 ++ cmd/lpm/darwin.go | 51 -- cmd/lpm/windows.go | 50 -- cmd/populator/Populator.go | 168 ------ go.mod | 6 +- go.sum | 9 +- internal/app/App.go | 95 --- internal/app/commands/add.go | 74 --- internal/app/commands/install.go | 51 -- internal/app/commands/list.go | 41 -- internal/app/commands/remove.go | 53 -- internal/app/commands/root.go | 74 --- internal/app/commands/search.go | 46 -- internal/app/commands/update.go | 63 -- internal/app/dependencies/Dependencies.go | 71 --- .../app/dependencies/dependencies_test.go | 72 --- internal/app/errors/errors.go | 13 - internal/app/packages/Packages.go | 54 -- internal/app/packages/Version.go | 97 --- internal/app/utils/Http.go | 31 - pkg/lpm/add_cmd.go | 112 ++++ pkg/lpm/cli_args.go | 12 + pkg/lpm/context.go | 560 ++++++++++++++++++ pkg/lpm/darwin.go | 12 + pkg/lpm/dependencies.go | 117 ++++ .../dependencies => pkg/lpm}/dependency.go | 11 +- pkg/lpm/embeds/VERSION | 1 + .../app => pkg/lpm/embeds}/packages.json | 0 pkg/lpm/http.go | 38 ++ pkg/lpm/install_cmd.go | 105 ++++ pkg/lpm/list_cmd.go | 57 ++ pkg/lpm/lpm.go | 12 + .../packages/Package.go => pkg/lpm/package.go | 19 +- pkg/lpm/packages.go | 63 ++ pkg/lpm/remove_cmd.go | 84 +++ pkg/lpm/root_cmd.go | 46 ++ pkg/lpm/search_cmd.go | 52 ++ pkg/lpm/update_cmd.go | 59 ++ pkg/lpm/version.go | 154 +++++ pkg/lpm/windows.go | 11 + tests/endtoend/update.yml | 2 +- tests/pkg/lpm/dependencies_test.go | 114 ++++ .../pkg/lpm}/dependency_test.go | 15 +- .../pkg/lpm}/package_test.go | 37 +- .../pkg/lpm}/packages_test.go | 31 +- .../pkg/lpm}/version_test.go | 155 +++-- 51 files changed, 2145 insertions(+), 1243 deletions(-) create mode 100644 CODE_REVIEW.md rename {internal/app => app/lpm}/VERSION (100%) create mode 100644 app/lpm/main.go create mode 100644 app/populator/module.go create mode 100644 app/populator/modules.go create mode 100644 app/populator/populator.go delete mode 100644 cmd/lpm/darwin.go delete mode 100644 cmd/lpm/windows.go delete mode 100644 cmd/populator/Populator.go delete mode 100644 internal/app/App.go delete mode 100644 internal/app/commands/add.go delete mode 100644 internal/app/commands/install.go delete mode 100644 internal/app/commands/list.go delete mode 100644 internal/app/commands/remove.go delete mode 100644 internal/app/commands/root.go delete mode 100644 internal/app/commands/search.go delete mode 100644 internal/app/commands/update.go delete mode 100644 internal/app/dependencies/Dependencies.go delete mode 100644 internal/app/dependencies/dependencies_test.go delete mode 100644 internal/app/errors/errors.go delete mode 100644 internal/app/packages/Packages.go delete mode 100644 internal/app/packages/Version.go delete mode 100644 internal/app/utils/Http.go create mode 100644 pkg/lpm/add_cmd.go create mode 100644 pkg/lpm/cli_args.go create mode 100644 pkg/lpm/context.go create mode 100644 pkg/lpm/darwin.go create mode 100644 pkg/lpm/dependencies.go rename {internal/app/dependencies => pkg/lpm}/dependency.go (65%) create mode 100644 pkg/lpm/embeds/VERSION rename {internal/app => pkg/lpm/embeds}/packages.json (100%) create mode 100644 pkg/lpm/http.go create mode 100644 pkg/lpm/install_cmd.go create mode 100644 pkg/lpm/list_cmd.go create mode 100644 pkg/lpm/lpm.go rename internal/app/packages/Package.go => pkg/lpm/package.go (68%) create mode 100644 pkg/lpm/packages.go create mode 100644 pkg/lpm/remove_cmd.go create mode 100644 pkg/lpm/root_cmd.go create mode 100644 pkg/lpm/search_cmd.go create mode 100644 pkg/lpm/update_cmd.go create mode 100644 pkg/lpm/version.go create mode 100644 pkg/lpm/windows.go create mode 100644 tests/pkg/lpm/dependencies_test.go rename {internal/app/dependencies => tests/pkg/lpm}/dependency_test.go (77%) rename {internal/app/packages => tests/pkg/lpm}/package_test.go (82%) rename {internal/app/packages => tests/pkg/lpm}/packages_test.go (88%) rename {internal/app/packages => tests/pkg/lpm}/version_test.go (54%) diff --git a/CODE_REVIEW.md b/CODE_REVIEW.md new file mode 100644 index 0000000..2f86fe1 --- /dev/null +++ b/CODE_REVIEW.md @@ -0,0 +1,48 @@ +# Notes +Here are my notes in reviewing/refactoring this code: + +1. Idiomatic Go is a thing. Embrace it. + +2. All in all, your code was really, really good. My code review/refactoring is taking you from 80% to 90%, with some of my review/refactors are based on my personal style that AFAIK does not conflict with idiomatic Go but really helps with readability and maintainability. _(I can't take you to 100% because I'm just not that good; I doubt almost anyone is.)_ + +3. One of the first things I think developers new to Go do is see packages and immediately start envisioning how their namespaces will allow logical code organization within an applications. And then these developers start creating a lot of packages with generic names. I absolutely did that. Unfortunately however I found through painful outcomes that using packages to organize code within an application is not a smart approach. Generic names often conflict with packages names in the Go standard library, and it is very easy to create cyclical dependencies across your packages which then requires you to work very hard to create internal interfaces you would otherwise never need just to get your over-organized code to compile. In reality your packages are rarely actually independent because one so often cannot really standalone without the functionality of the others. + +4. You had a lot of code in your commands that could really be re-envisioned as methods for your structs which really simplify the code in your commands and allows for reuse of functionality now or down the road. + +5. So for any given application or library package it is better — ignoring the required `main` package — to stick with one, or a very small number of packages, where the `main()` func is in the `main()` package — if you are writing an application — and the rest of code should be in a library named after your application, e.g. `lpm` in your case. I reorganized your code in my fork to this effect. + +6. Your two main files (`darwin.go` and `windows.go`) were almost exactly the same except for one expression difference. I refactored that difference into `utils` and I made your `main.go` file simple and easy to grok. + +7. Your functions should _(almost)_ always return an error in Go when an error occurs that your func cannot handle itself. Just failing to terminal is not a good strategy. You will also be able to add a lot more information so you user can figure out what happened. And if you ever want to use the same packages for microservices you will thank me. + +8. So when possible, if you want to fail on an error then bubble up that error while wrapping it with more info then handle at the root, in a CLI. Don't just fail deep in the middle of code because you may later want to reuse that code and if you do it won't be robust and will require lots of rearchitecture. + +9. When using text to describe error messages start with lower case so multiple messages can be composed without it looking weird. Also don't use periods to end error messages. + +10. Don't create an empty instance to be able to call a method. Just define as a func. That's idiomatic in Go and acceptable. If for want an instance for dependency injection but still want to call it explictly elsewhere, create both a method and a func that creates an instance to be able to call the method. So don't write `util.HTTPUtil{}.Get()`. Just write `util.HttpGet()` + +11. I refactored out `else{}` statements. [Embrace the happy-path](https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea88). + +12. It's more idiomatic in Go to use fmt.Sprintf() than string concatenation. + +13. My personal style is to use `goto end` rather than early `return` because it provides numerous benefits. Unfortunately in Go it also requires all declarations be above the `goto`. But then explict declarations help the reader of your code, so I don't think that's really a bad thing. Also doing it this way causes the developer to have to work harder to write longer methods, so this approach encourages more modularity. + +14. `errors` is a well-known package name in Go; you probably should not use it for your own package name. + +15. For cross-platform paths, use `filepath.FromSlash()` and `filepath.ToSlash()` + +16. I use GoLand from JetBrains as my editor/IDE _(which I cannot praise too much.)_ Thus I added `//goland:noinspection` in a few places because in those places it flagged that it reasonably thought were errors that were not really errors, such as not capturing the return value of a `.Close()` in a `defer` statement. + +17. In `Dependencies` you use pointer and non-pointer receivers (`d`) in your methods. Better to be consistent and use the same. I know it has less immutability benefit to use pointers, but mixing can cause people who have to use your packages to struggle. If you need immutability code for it explicitly, such as how `append()` works in Go. + +18. You can define types such as 'ClasspathFiles' so you can type `[]fs.FileInfo` in just one place and so that when you use them only values that are meant to be used for Classpath Files can be used for Classpath files, vs. some other slice of files. + +19. Your configuration in App variables makes it harder to write unit tests, from experience. Every times I have started with variables for config I ultimately end up having to refactor to using a struct with methods. + +20. Things that run within `func init()` should not be of the nature to throw errors. It will make debugging edge cases much harder. Do things that might throw errors in the main flow of your program. + +21. It appears that `Dependency` is a `map[string]string` but it looks like it never has more than a single key and a single value. Why not a simple struct? + +22. Test in the same package has too much access to internals. Better to create a separate `test` package + +23. In general I have tried to move away from calculating things in init() functions and instead do so on demand in methods, e.g. see `Context.GetManifestFilepath()` vs. `app.FileLocation`. \ No newline at end of file diff --git a/internal/app/VERSION b/app/lpm/VERSION similarity index 100% rename from internal/app/VERSION rename to app/lpm/VERSION diff --git a/app/lpm/main.go b/app/lpm/main.go new file mode 100644 index 0000000..968ea65 --- /dev/null +++ b/app/lpm/main.go @@ -0,0 +1,16 @@ +package main + +import ( + _ "embed" // Embed Import for Package Files + "os" + "package-manager/pkg/lpm" +) + +func main() { + + err := lpm.Execute("/") + if err != nil { + lpm.ShowUserError(err) + os.Exit(1) + } +} diff --git a/app/populator/module.go b/app/populator/module.go new file mode 100644 index 0000000..d664d26 --- /dev/null +++ b/app/populator/module.go @@ -0,0 +1,218 @@ +package main + +import ( + "fmt" + "github.com/gocolly/colly/v2" + "github.com/hashicorp/go-version" + "package-manager/pkg/lpm" + "sort" + "strings" +) + +type Module struct { + name string + category string + url string + includeSuffix string + excludeSuffix string + filePrefix string +} + +func (m Module) GetJarPath(tag string) string { + return fmt.Sprintf("%s/%s/%s%s.jar", + m.url, + tag, + m.filePrefix, + tag) +} + +func (m Module) GetSha1Path(tag string) string { + return fmt.Sprintf("%s.sha1", m.GetJarPath(tag)) +} + +func (m Module) getCheckSum(tag string, algo lpm.ChecksumAlgorithm) (cs string, err error) { + var url string + var _sha []byte + var sha string + + switch algo { + case lpm.Sha1Algorithm: + url = m.GetSha1Path(tag) + default: + err = fmt.Errorf("invalid checksum algorithm '%s'", string(algo)) + goto end + } + _sha, err = lpm.HttpGet(url) + if err != nil { + err = fmt.Errorf("unable to retrieve %s: %w", url, err) + goto end + } + sha = string(_sha) + if strings.Contains(sha, "html") { + sha = "" + } + if 40 > len(sha) { + goto end + } + cs = sha[0:40] //Get first 40 character of SHA1 only + +end: + return cs, err +} + +func (m Module) onAHref(f *colly.HTMLElement, vs []string) []string { + var text string + + if strings.Contains(f.Text, "../") { + goto end + } + + if strings.Contains(f.Text, "maven-metadata.") { + goto end + } + + switch true { + + case m.HasNoSuffixes(): + text = strings.TrimSuffix(f.Text, "/") + + case m.HasExcludeSuffix(): + if m.ContainsExcludeSuffix(f.Text) { + goto end + } + text = strings.TrimSuffix(f.Text, "/") + + case m.HasIncludeSuffix(): + if !m.ContainsIncludeSuffix(f.Text) { + goto end + } + text = strings.TrimSuffix(f.Text, m.includeSuffix+"/") + + case m.HasBothSuffixes(): + if m.ContainsExcludeSuffix(f.Text) { + goto end + } + if !m.ContainsIncludeSuffix(f.Text) { + goto end + } + text = strings.TrimSuffix(f.Text, m.includeSuffix+"/") + + } + + vs = append(vs, text) +end: + return vs +} + +func (m Module) ContainsIncludeSuffix(s string) bool { + // @TODO Using Contains below seems like they could matching + // false positives. Regex would probably better, but I + // do not know the exact format to look for. + return strings.Contains(s, m.includeSuffix) +} +func (m Module) ContainsExcludeSuffix(s string) bool { + // @TODO Using Contains below seems like they could matching + // false positives. Regex would probably better, but I + // do not know the exact format to look for. + return strings.Contains(s, m.excludeSuffix) +} +func (m Module) HasNoSuffixes() bool { + return m.excludeSuffix == "" && m.includeSuffix != "" +} +func (m Module) HasExcludeSuffix() bool { + return m.excludeSuffix != "" && m.includeSuffix == "" +} +func (m Module) HasIncludeSuffix() bool { + return m.includeSuffix != "" && m.excludeSuffix == "" +} +func (m Module) HasBothSuffixes() bool { + return m.excludeSuffix != "" && m.includeSuffix != "" +} + +func (m Module) retrieveVersionsViaHttp() (versions []*version.Version, err error) { + var versionsRaw []string + + // Get Versions from Root package site + c := colly.NewCollector() + // Find and visit all links + c.OnHTML("a[href]", func(f *colly.HTMLElement) { + versionsRaw = m.onAHref(f, versionsRaw) + }) + + err = c.Visit(m.url) + if err != nil { + err = fmt.Errorf("unable to visit '%s': %w", m.url, err) + goto end + } + + // Make Sorted Versions + versions = make([]*version.Version, len(versionsRaw)) + for i, raw := range versionsRaw { + v, _ := version.NewVersion(raw) + versions[i] = v + } + sort.Sort(version.Collection(versions)) + +end: + return versions, err + +} + +func (m Module) getNewVersion(p lpm.Package, tag string) (ver lpm.Version, err error) { + + ver.Tag = tag + + if m.includeSuffix != "" { + tag += m.includeSuffix + } + + if m.filePrefix == "" { + m.filePrefix = p.Name + } + + ver.Path = m.GetJarPath(tag) + + ver.Algorithm = lpm.Sha1Algorithm + ver.CheckSum, err = m.getCheckSum(tag, ver.Algorithm) + return ver, err +} + +func (m Module) getNewVersions(p lpm.Package) (lpm.Package, error) { + var vs []*version.Version + var ver lpm.Version + var err error + + // Retrieve the version by URL + vs, err = m.retrieveVersionsViaHttp() + if err != nil { + err = fmt.Errorf("unable to retrieve vs for package '%s': %w", + p.Name, + err) + goto end + } + + //Look for new versions + for _, v := range vs { + tag := v.Original() + + pv := p.GetVersion(tag) + + if pv.Tag != "" { + // if remote version is already in package manifest skip it + continue + } + + ver, err = m.getNewVersion(p, v.Original()) + + if err != nil { + // Older vs might have bad version patterns + // ending up with a missing sha. Don't add them. + continue + } + + p.Versions = append(p.Versions, ver) + + } +end: + return p, err +} diff --git a/app/populator/modules.go b/app/populator/modules.go new file mode 100644 index 0000000..a942fad --- /dev/null +++ b/app/populator/modules.go @@ -0,0 +1,58 @@ +package main + +var modules Modules + +type Modules []Module + +func (es Modules) getByName(n string) Module { + var m Module + for _, e := range es { + if e.name == n { + m = e + break + } + } + return m +} + +func init() { + modules = []Module{ + {"postgresql", "driver", "https://repo1.maven.org/maven2/org/postgresql/postgresql", "", ".jre", ""}, + {"mssql", "driver", "https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc", ".jre11", ".jre11-preview", "mssql-jdbc-"}, + {"mariadb", "driver", "https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client", "", "", "mariadb-java-client-"}, + {"h2", "driver", "https://repo1.maven.org/maven2/com/h2database/h2", "", "", ""}, + {"db2", "driver", "https://repo1.maven.org/maven2/com/ibm/db2/jcc", "", "db2", "jcc-"}, + {"snowflake", "driver", "https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc", "", "", "snowflake-jdbc-"}, + {"sybase", "driver", "https://repo1.maven.org/maven2/net/sf/squirrel-sql/plugins/sybase", "", "", ""}, + {"firebird", "driver", "https://repo1.maven.org/maven2/net/sf/squirrel-sql/plugins/firebird", "", "", ""}, + {"sqlite", "driver", "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc", "", "", "sqlite-jdbc-"}, + {"oracle", "driver", "https://repo1.maven.org/maven2/com/oracle/ojdbc/ojdbc8", "", "", "ojdbc8-"}, + {"mysql", "driver", "https://repo1.maven.org/maven2/mysql/mysql-connector-java", "", "", "mysql-connector-java-"}, + {"liquibase-cache", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-cache", "", "", ""}, + {"liquibase-cassandra", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-cassandra", "", "", ""}, + {"liquibase-cosmosdb", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-cosmosdb", "", "", ""}, + {"liquibase-db2i", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-db2i", "", "", ""}, + {"liquibase-filechangelog", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-filechangelog", "", "", ""}, + {"liquibase-hanadb", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-hanadb", "", "", ""}, + {"liquibase-hibernate5", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-hibernate5", "", "", ""}, + {"liquibase-maxdb", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-maxdb", "", "", ""}, + {"liquibase-modify-column", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-modify-column", "", "", ""}, + {"liquibase-mongodb", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-mongodb", "", "", ""}, + {"liquibase-mssql", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-mssql", "", "", ""}, + {"liquibase-neo4j", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-neo4j", "", "", ""}, + {"liquibase-oracle", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-oracle", "", "", ""}, + {"liquibase-percona", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-percona", "", "", ""}, + {"liquibase-postgresql", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-postgresql", "", "", ""}, + {"liquibase-redshift", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-redshift", "", "", ""}, + {"liquibase-snowflake", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-snowflake", "", "", ""}, + {"liquibase-sqlfire", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-sqlfire", "", "", ""}, + {"liquibase-teradata", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-teradata", "", "", ""}, + {"liquibase-verticaDatabase", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-verticaDatabase", "", "", ""}, + //"liquibase-compat", + //"liquibase-javalogger", + //"liquibase-nochangeloglock", + //"liquibase-nochangelogupdate", + //"liquibase-sequencetable", + //"liquibase-vertica", + } +} diff --git a/app/populator/populator.go b/app/populator/populator.go new file mode 100644 index 0000000..c6be6ec --- /dev/null +++ b/app/populator/populator.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "os" + "package-manager/pkg/lpm" +) + +func main() { + + var newPacks = make(lpm.Packages, 0) + var pack lpm.Package + var err error + + // @TODO Verify '/' is the right thing here... + cfg := lpm.NewContext("/") + err = cfg.Initialize() + + // read packages from embedded file + packs, err := cfg.UnmarshalPackages(lpm.PackagesJSON) + if err != nil { + goto end + } + for _, p := range packs { + m := modules.getByName(p.Name) + if m.name == "" { + newPacks = append(newPacks, p) + continue + } + pack, err = m.getNewVersions(p) + if err != nil { + err = fmt.Errorf("failed to get versions for %s: %w", + m.name, + err) + goto end + } + // Get new versions for a package + newPacks = append(newPacks, pack) + } + + //WriteManifest all packages back to manifest. + err = cfg.WritePackages(newPacks) + +end: + if err != nil { + fmt.Printf("ERROR %s\n", err.Error()) + os.Exit(1) + } + +} diff --git a/cmd/lpm/darwin.go b/cmd/lpm/darwin.go deleted file mode 100644 index 19c88fb..0000000 --- a/cmd/lpm/darwin.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/exec" - "package-manager/internal/app/commands" - "path/filepath" - "strings" -) - -func main() { - - var liquibasehome string - - if _, ok := os.LookupEnv("LIQUIBASE_HOME"); ok { - liquibasehome = os.Getenv("LIQUIBASE_HOME") - } else { - // Find Liquibase Command - out, err := exec.Command("which", "liquibase").CombinedOutput() - if err != nil { - fmt.Println("Unable to locate Liquibase.") - os.Exit(1) - } - - // Determine if Command is Symlink - loc := strings.TrimRight(string(out), "\n") - fi, err := os.Lstat(loc) - if err != nil { - log.Fatal(err) - } - - if fi.Mode()&os.ModeSymlink != 0 { - link, err := os.Readlink(loc) - if err != nil { - log.Fatal(err) - } - // Is Symlink - liquibasehome, _ = filepath.Split(link) - } else { - // Not Symlink - liquibasehome, _ = filepath.Split(loc) - } - } - - if !strings.HasSuffix(liquibasehome, "/") { - liquibasehome = liquibasehome + "/" - } - commands.Execute(liquibasehome, "/") -} \ No newline at end of file diff --git a/cmd/lpm/windows.go b/cmd/lpm/windows.go deleted file mode 100644 index f10b108..0000000 --- a/cmd/lpm/windows.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "os/exec" - "package-manager/internal/app/commands" - "path/filepath" - "strings" -) - -func main() { - - var liquibasehome string - - if _, ok := os.LookupEnv("LIQUIBASE_HOME"); ok { - liquibasehome = os.Getenv("LIQUIBASE_HOME") - } else { - // Find Liquibase Command - out, err := exec.Command("where", "liquibase").CombinedOutput() - if err != nil { - fmt.Println("Unable to locate Liquibase.") - os.Exit(1) - } - - // Determine if Command is Symlink - loc := strings.TrimRight(strings.Split(string(out), "\n")[0], "\r") - fi, err := os.Lstat(loc) - if err != nil { - log.Fatal(err) - } - - if fi.Mode()&os.ModeSymlink != 0 { - link, err := os.Readlink(loc) - if err != nil { - log.Fatal(err) - } - // Is Symlink - liquibasehome, _ = filepath.Split(link) - } else { - // Not Symlink - liquibasehome, _ = filepath.Split(loc) - } - } - if !strings.HasSuffix(liquibasehome, "\\") { - liquibasehome = liquibasehome + "\\" - } - commands.Execute(liquibasehome, "\\") -} \ No newline at end of file diff --git a/cmd/populator/Populator.go b/cmd/populator/Populator.go deleted file mode 100644 index 6f28052..0000000 --- a/cmd/populator/Populator.go +++ /dev/null @@ -1,168 +0,0 @@ -package main - -import ( - "github.com/gocolly/colly/v2" - "github.com/hashicorp/go-version" - "package-manager/internal/app" - "package-manager/internal/app/packages" - "package-manager/internal/app/utils" - "sort" - "strings" -) - -type module struct { - name string - category string - url string - includeSuffix string - excludeSuffix string - filePrefix string -} -type modules []module -var mods modules - -func init() { - mods = []module{ - {"postgresql", "driver", "https://repo1.maven.org/maven2/org/postgresql/postgresql", "", ".jre", ""}, - {"mssql", "driver", "https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc",".jre11",".jre11-preview", "mssql-jdbc-"}, - {"mariadb", "driver", "https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client","","", "mariadb-java-client-"}, - {"h2", "driver", "https://repo1.maven.org/maven2/com/h2database/h2","","", ""}, - {"db2", "driver", "https://repo1.maven.org/maven2/com/ibm/db2/jcc","","db2", "jcc-"}, - {"snowflake", "driver", "https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc","","", "snowflake-jdbc-"}, - {"sybase", "driver", "https://repo1.maven.org/maven2/net/sf/squirrel-sql/plugins/sybase","","", ""}, - {"firebird", "driver", "https://repo1.maven.org/maven2/net/sf/squirrel-sql/plugins/firebird","","", ""}, - {"sqlite", "driver", "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc","","", "sqlite-jdbc-"}, - {"oracle", "driver", "https://repo1.maven.org/maven2/com/oracle/ojdbc/ojdbc8","","", "ojdbc8-"}, - {"mysql", "driver", "https://repo1.maven.org/maven2/mysql/mysql-connector-java","","", "mysql-connector-java-"}, - {"liquibase-cache", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-cache","","", ""}, - {"liquibase-cassandra", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-cassandra","","", ""}, - {"liquibase-cosmosdb", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-cosmosdb","","", ""}, - {"liquibase-db2i", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-db2i","","", ""}, - {"liquibase-filechangelog", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-filechangelog","","", ""}, - {"liquibase-hanadb", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-hanadb","","", ""}, - {"liquibase-hibernate5", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-hibernate5","","", ""}, - {"liquibase-maxdb", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-maxdb","","", ""}, - {"liquibase-modify-column", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-modify-column","","", ""}, - {"liquibase-mongodb", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-mongodb","","", ""}, - {"liquibase-mssql", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-mssql","","", ""}, - {"liquibase-neo4j", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-neo4j","","", ""}, - {"liquibase-oracle", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-oracle","","", ""}, - {"liquibase-percona", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-percona","","", ""}, - {"liquibase-postgresql", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-postgresql","","", ""}, - {"liquibase-redshift", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-redshift","","", ""}, - {"liquibase-snowflake", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-snowflake","","", ""}, - {"liquibase-sqlfire", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-sqlfire","","", ""}, - {"liquibase-teradata", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-teradata","","", ""}, - {"liquibase-verticaDatabase", "extension", "https://repo1.maven.org/maven2/org/liquibase/ext/liquibase-verticaDatabase","","", ""}, - //"liquibase-compat", - //"liquibase-javalogger", - //"liquibase-nochangeloglock", - //"liquibase-nochangelogupdate", - //"liquibase-sequencetable", - //"liquibase-vertica", - } -} - -func (es modules) getByName(n string) module { - var r module - for _, e := range es { - if e.name == n { - r = e - } - } - return r -} - -func getNewVersions(m module, p packages.Package) packages.Package { - var versionsRaw []string - - // Get Versions from Root package site - c := colly.NewCollector() - // Find and visit all links - c.OnHTML("a[href]", func(f *colly.HTMLElement) { - if !strings.Contains(f.Text, "../") && !strings.Contains(f.Text, "maven-metadata.") { - if m.excludeSuffix != "" && m.includeSuffix == "" { - if !strings.Contains(f.Text, m.excludeSuffix) { - versionsRaw = append(versionsRaw, strings.TrimSuffix(f.Text, "/")) - } - } - if m.excludeSuffix == "" && m.includeSuffix != "" { - if strings.Contains(f.Text, m.includeSuffix) { - versionsRaw = append(versionsRaw, strings.TrimSuffix(f.Text, m.includeSuffix + "/")) - } - } - if m.excludeSuffix != "" && m.includeSuffix != "" { - if strings.Contains(f.Text, m.includeSuffix) && !strings.Contains(f.Text, m.excludeSuffix) { - versionsRaw = append(versionsRaw, strings.TrimSuffix(f.Text, m.includeSuffix + "/")) - } - } - if m.excludeSuffix == "" && m.includeSuffix == "" { - versionsRaw = append(versionsRaw, strings.TrimSuffix(f.Text, "/")) - } - } - }) - c.Visit(m.url) - - // Sort Versions - versions := make([]*version.Version, len(versionsRaw)) - for i, raw := range versionsRaw { - v, _ := version.NewVersion(raw) - versions[i] = v - } - sort.Sort(version.Collection(versions)) - - //Look for new versions - for _, v := range versions { - var ver packages.Version - ver.Tag = v.Original() - pv := p.GetVersion(ver.Tag) - if pv.Tag != "" { - // if remote version is already in package manifest skip it - continue - } - - var tag string - if m.includeSuffix != "" { - tag = ver.Tag + m.includeSuffix - } else { - tag = ver.Tag - } - - if m.filePrefix != "" { - ver.Path = m.url + "/" + tag + "/" + m.filePrefix + tag + ".jar" - } else { - ver.Path = m.url + "/" + tag + "/" + p.Name + "-" + tag + ".jar" - } - ver.Algorithm = "SHA1" - sha := string(utils.HTTPUtil{}.Get(ver.Path + ".sha1")) - if strings.Contains(sha, "html") { - sha = "" - } - ver.CheckSum = sha[0:40] //Get first 40 character of SHA1 only - - // Older versions might have bad version patters ending up with a missing sha. Don't add them. - if ver.CheckSum != "" { - p.Versions = append(p.Versions, ver) - } - } - return p -} - -func main(){ - var newPacks = packages.Packages{} - - // read packages from embedded file - packs := app.LoadPackages(app.PackagesJSON) - for _, p := range packs { - m := mods.getByName(p.Name) - if m.name != "" { - // Get new versions for a package - newPacks = append(newPacks, getNewVersions(m, p)) - } else { - newPacks = append(newPacks, p) - } - } - - //Write all packages back to manifest. - app.WritePackages(newPacks) -} \ No newline at end of file diff --git a/go.mod b/go.mod index 3cc3c22..f118d22 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,11 @@ require ( github.com/PuerkitoBio/goquery v1.7.1 // indirect github.com/antchfx/xmlquery v1.3.6 // indirect github.com/antchfx/xpath v1.2.0 // indirect - github.com/gocolly/colly/v2 v2.1.0 // indirect + github.com/gocolly/colly/v2 v2.1.0 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/hashicorp/go-version v1.3.0 // indirect + github.com/hashicorp/go-version v1.3.0 github.com/spf13/cobra v1.2.1 github.com/temoto/robotstxt v1.1.2 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/net v0.0.0-20210716203947-853a461950ff // indirect - golang.org/x/tools v0.1.5 // indirect google.golang.org/protobuf v1.27.1 // indirect ) diff --git a/go.sum b/go.sum index 9364c26..639dd98 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,7 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -139,6 +140,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -219,6 +221,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -247,6 +250,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= @@ -300,7 +304,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -485,11 +488,10 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -605,6 +607,7 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/app/App.go b/internal/app/App.go deleted file mode 100644 index d14437e..0000000 --- a/internal/app/App.go +++ /dev/null @@ -1,95 +0,0 @@ -package app - -import ( - _ "embed" // Embed Import for Package Files - "encoding/json" - "io/fs" - "io/ioutil" - "os" - "package-manager/internal/app/errors" - "package-manager/internal/app/packages" -) - -//go:embed "VERSION" -var version string - -//PackagesJSON is embedded for first time run -//go:embed "packages.json" -var PackagesJSON []byte - -//PackageFile exported for overwrite -var PackageFile = "packages.json" -//Classpath exported for overwrite -var Classpath string -//ClasspathFiles exported for overwrite -var ClasspathFiles []fs.FileInfo - -//Version output from embedded file -func Version() string { - return version -} - -//SetClasspath to switch between global and local modules -func SetClasspath(global bool, globalpath string, globalpathFiles []fs.FileInfo) { - if global { - Classpath = globalpath - ClasspathFiles = globalpathFiles - } else { - pwd, err := os.Getwd() - if err != nil { - errors.Exit(err.Error(), 1) - } - - //TODO rework this order to prevent creating an empty directory before add/install - // probably separate into separate methods - Classpath = pwd + "/liquibase_libs/" - os.Mkdir(Classpath, 0775) - if err != nil { - errors.Exit(err.Error(), 1) - } - ClasspathFiles, err = ioutil.ReadDir(Classpath) - if err != nil { - errors.Exit(err.Error(), 1) - } - } -} - -//PackagesInClassPath is the packages.json file in global classpath -func PackagesInClassPath(cp string) bool { - _, err := os.Stat(cp + PackageFile) - return err == nil -} - -//CopyPackagesToClassPath install packages.json to global classpath -func CopyPackagesToClassPath(cp string, p []byte) { - err := ioutil.WriteFile(cp + PackageFile, p, 0664) - if err != nil { - errors.Exit(err.Error(), 1) - } -} - -//LoadPackages get packages from bytes from file -func LoadPackages(b []byte) packages.Packages { - var e packages.Packages - err := json.Unmarshal(b, &e) - if err != nil { - errors.Exit(err.Error(), 1) - } - return e -} - -//WritePackages write packages back to file -func WritePackages(p packages.Packages) { - b, err := json.MarshalIndent(p, "", " ") - if err != nil { - errors.Exit(err.Error(), 1) - } - pwd, err := os.Getwd() - if err != nil { - errors.Exit(err.Error(), 1) - } - err = ioutil.WriteFile(pwd + "/internal/app/packages.json", b, 0664) - if err != nil { - errors.Exit(err.Error(), 1) - } -} \ No newline at end of file diff --git a/internal/app/commands/add.go b/internal/app/commands/add.go deleted file mode 100644 index b68ddbf..0000000 --- a/internal/app/commands/add.go +++ /dev/null @@ -1,74 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/spf13/cobra" - "package-manager/internal/app" - "package-manager/internal/app/dependencies" - "package-manager/internal/app/errors" - "package-manager/internal/app/packages" - "strings" -) - -// addCmd represents the add command -var addCmd = &cobra.Command{ - Use: "add [PACKAGE]...", - Short: "Add Packages", - Args: cobra.ArbitraryArgs, - Run: func(cmd *cobra.Command, args []string) { - - d := dependencies.Dependencies{} - if !global { - d.Read() - } - - for _, name := range args { - var p packages.Package - var v packages.Version - if strings.Contains(name, "@") { - p = packs.GetByName(strings.Split(name, "@")[0]) - v = p.GetVersion(strings.Split(name, "@")[1]) - if v.Tag == "" { - errors.Exit("Version '"+strings.Split(name, "@")[1]+"' not available.", 1) - } - } else { - p = packs.GetByName(name) - v = p.GetLatestVersion() - } - if p.Name == "" { - errors.Exit("Package '"+name+"' not found.", 1) - } - if v.InClassPath(app.ClasspathFiles) { - errors.Exit(name+" is already installed.", 1) - } - if !v.PathIsHTTP() { - v.CopyToClassPath(app.Classpath) - } else { - v.DownloadToClassPath(app.Classpath) - } - fmt.Println(v.GetFilename() + " successfully installed in classpath.") - d.Dependencies = append(d.Dependencies, dependencies.Dependency{p.Name: v.Tag}) - } - - if !global { - //Add package to local manifest - if !d.FileExists() { - d.CreateFile() - } - d.Write() - - // Output helper for JAVA_OPTS - //TODO Test this on windows - p := "-cp liquibase_libs/*:" + globalpath + "*:" + liquibaseHome + "liquibase.jar" - fmt.Println() - fmt.Println("---------- IMPORTANT ----------") - fmt.Println("Add the following JAVA_OPTS to your CLI:") - fmt.Println("export JAVA_OPTS=\"" + p + "\"") - } - }, -} - -func init() { - rootCmd.AddCommand(addCmd) - addCmd.Flags().BoolVarP(&global, "global", "g", false, "add package globally") -} diff --git a/internal/app/commands/install.go b/internal/app/commands/install.go deleted file mode 100644 index 3eb2c59..0000000 --- a/internal/app/commands/install.go +++ /dev/null @@ -1,51 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/spf13/cobra" - "package-manager/internal/app" - "package-manager/internal/app/dependencies" - "package-manager/internal/app/errors" -) - -// installCmd represents the install command -var installCmd = &cobra.Command{ - Use: "install", - Short: "Install Packages from liquibase.json", - Run: func(cmd *cobra.Command, args []string) { - - if global { - errors.Exit("Can not install packages from liquibase.json globally", 1) - } - - d := dependencies.Dependencies{} - d.Read() - - for _, dep := range d.Dependencies { - p := packs.GetByName(dep.GetName()) - v := p.GetVersion(dep.GetVersion()) - - if v.InClassPath(app.ClasspathFiles) { - errors.Exit(p.Name+" is already installed.", 1) - } - if !v.PathIsHTTP() { - v.CopyToClassPath(app.Classpath) - } else { - v.DownloadToClassPath(app.Classpath) - } - fmt.Println(v.GetFilename() + " successfully installed in classpath.") - } - - // Output helper for JAVA_OPTS - // TODO Test this on windows - p := "-cp liquibase_libs/*:" + globalpath + "*:" + liquibaseHome + "liquibase.jar" - fmt.Println() - fmt.Println("---------- IMPORTANT ----------") - fmt.Println("Add the following JAVA_OPTS to your CLI:") - fmt.Println("export JAVA_OPTS=\"" + p + "\"") - }, -} - -func init() { - rootCmd.AddCommand(installCmd) -} \ No newline at end of file diff --git a/internal/app/commands/list.go b/internal/app/commands/list.go deleted file mode 100644 index 6134be9..0000000 --- a/internal/app/commands/list.go +++ /dev/null @@ -1,41 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/spf13/cobra" - "os" - "package-manager/internal/app" - "package-manager/internal/app/packages" -) - -// listCmd represents the list command -var listCmd = &cobra.Command{ - Use: "list", - Short: "List Installed Packages", - Aliases: []string{"ls"}, - Run: func(cmd *cobra.Command, args []string) { - - // Collect installed packages - var installed packages.Packages - for _, e := range packs { - v := e.GetInstalledVersion(app.ClasspathFiles) - if v.InClassPath(app.ClasspathFiles) { - installed = append(installed, e) - } - } - - // Format output - fmt.Println(app.Classpath) - if len(installed) == 0 { - os.Exit(1) - } - for _, out := range installed.Display(app.ClasspathFiles) { - fmt.Println(out) - } - }, -} - -func init() { - rootCmd.AddCommand(listCmd) - listCmd.Flags().BoolVarP(&global, "global", "g", false, "list global packages") -} diff --git a/internal/app/commands/remove.go b/internal/app/commands/remove.go deleted file mode 100644 index 46fcf78..0000000 --- a/internal/app/commands/remove.go +++ /dev/null @@ -1,53 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/spf13/cobra" - "os" - "package-manager/internal/app" - "package-manager/internal/app/dependencies" - "package-manager/internal/app/errors" -) - -// removeCmd represents the install command -var removeCmd = &cobra.Command{ - Use: "remove [PACKAGE]...", - Short: "Removes Package", - Aliases: []string{"rm"}, - Args: cobra.ArbitraryArgs, - Run: func(cmd *cobra.Command, args []string) { - - d := dependencies.Dependencies{} - if !global { - d.Read() - } - - // Remove Each Package - for _, name := range args { - p := packs.GetByName(name) - v := p.GetInstalledVersion(app.ClasspathFiles) - if p.Name == "" { - errors.Exit("Package '" + name + "' not found.", 1) - } - if !v.InClassPath(app.ClasspathFiles) { - errors.Exit(name + " is not installed.", 1) - } - err := os.Remove(app.Classpath + v.GetFilename()) - if err != nil { - errors.Exit("Unable to delete " + v.GetFilename() + " from classpath.", 1) - } - fmt.Println(v.GetFilename() + " successfully uninstalled from classpath.") - if !global{ - d.Remove(p.Name) - } - } - if !global{ - d.Write() - } - }, -} - -func init() { - rootCmd.AddCommand(removeCmd) - removeCmd.Flags().BoolVarP(&global, "global", "g", false, "remove package globally") -} diff --git a/internal/app/commands/root.go b/internal/app/commands/root.go deleted file mode 100644 index 94a2290..0000000 --- a/internal/app/commands/root.go +++ /dev/null @@ -1,74 +0,0 @@ -package commands - -import ( - "github.com/spf13/cobra" - "io/fs" - "io/ioutil" - "os" - "package-manager/internal/app" - "package-manager/internal/app/errors" - "package-manager/internal/app/packages" -) - -var ( - category string - liquibaseHome string - globalpath string - globalpathFiles []fs.FileInfo - packs packages.Packages - global bool -) - -var rootCmd = &cobra.Command{ - Use: "lpm", - Short: "Liquibase Package Manager", - Long: `Easily manage external dependencies for Database Development. -Search for, install, and uninstall liquibase drivers, extensions, and utilities.`, -} - -//Execute main entry point for CLI -func Execute(cp string, s string) { - var err error - liquibaseHome = cp - globalpath = liquibaseHome + "lib" + s - globalpathFiles, err = ioutil.ReadDir(globalpath) - if err != nil { - errors.Exit(err.Error(), 1) - } - if err := rootCmd.Execute(); err != nil { - errors.Exit(err.Error(), 1) - } -} - -func init() { - cobra.OnInitialize(initConfig) - - //Global params - //rootCmd.CompletionOptions.DisableDefaultCmd = true - rootCmd.PersistentFlags().StringVar(&category, "category","", "extension, driver, or utility") - rootCmd.Version = app.Version() - rootCmd.SetVersionTemplate("{{with .Name}}{{printf \"%s \" .}}{{end}}{{with .Short}}{{printf \"(%s) \" .}}{{end}}{{printf \"version %s\" .Version}}\n") -} - -func initConfig() { - //Install Embedded Package File - if !app.PackagesInClassPath(globalpath) { - app.CopyPackagesToClassPath(globalpath, app.PackagesJSON) - } - - //Get Bytes from Package File - jsonFile, err := os.Open(globalpath + app.PackageFile) - if err != nil { - errors.Exit(err.Error(), 1) - } - b, err := ioutil.ReadAll(jsonFile) - - //Load Bytes to Packages - packs = app.LoadPackages(b) - if category != "" { - packs = packs.FilterByCategory(category) - } - - // Set global vs local classpath - app.SetClasspath(global, globalpath, globalpathFiles) -} \ No newline at end of file diff --git a/internal/app/commands/search.go b/internal/app/commands/search.go deleted file mode 100644 index 070ba72..0000000 --- a/internal/app/commands/search.go +++ /dev/null @@ -1,46 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/spf13/cobra" - "os" - "package-manager/internal/app" - "package-manager/internal/app/packages" - "strings" -) - -// searchCmd represents the install command -var searchCmd = &cobra.Command{ - Use: "search [PACKAGE]", - Short: "Search for Packages", - - Run: func(cmd *cobra.Command, args []string) { - var name string - if len(args) > 0 { - name = args[0] - if len(name) < 3 { - fmt.Println("Minimum of 3 characters required for search.") - os.Exit(1) - } - } else { - name = "" - } - var found packages.Packages - for _, p := range packs { - if strings.Contains(p.Name, name) || name == "" { - found = append(found, p) - } - } - if len(found) == 0 { - fmt.Println("No results found.") - os.Exit(1) - } - for _, out := range found.Display(app.ClasspathFiles) { - fmt.Println(out) - } - }, -} - -func init() { - rootCmd.AddCommand(searchCmd) -} diff --git a/internal/app/commands/update.go b/internal/app/commands/update.go deleted file mode 100644 index 61eb576..0000000 --- a/internal/app/commands/update.go +++ /dev/null @@ -1,63 +0,0 @@ -package commands - -import ( - "fmt" - "github.com/spf13/cobra" - "io/ioutil" - "os" - "package-manager/internal/app" - "package-manager/internal/app/errors" - "package-manager/internal/app/packages" - "package-manager/internal/app/utils" - "reflect" - "strings" -) - -var ( - path string -) - -// updateCmd represents the update command -var updateCmd = &cobra.Command{ - Use: "update", - Short: "Updates the Package Manifest", - - Run: func(cmd *cobra.Command, args []string) { - var bytes []byte - if strings.HasPrefix(path, "http") { - // Update Package from Remote URL - bytes = utils.HTTPUtil{}.Get(path) - } else { - // Update Packages from Local File - file, err := os.Open(path) - if err != nil { - errors.Exit(err.Error(), 1) - } - bytes, err = ioutil.ReadAll(file) - if err != nil { - errors.Exit(err.Error(), 1) - } - } - //Verify bytes are valid - p := app.LoadPackages(bytes) - if reflect.TypeOf(p) != reflect.TypeOf(packages.Packages{}) { - errors.Exit("Unable to validate package contents.", 1) - } - if p.GetByName("postgres").Name == "postgres" { - errors.Exit("Unable to validate package contents.", 1) - } - app.CopyPackagesToClassPath(globalpath, bytes) - fmt.Println("Package manifest updated from " + path) - }, -} - -func init() { - rootCmd.AddCommand(updateCmd) - updateCmd.Flags().StringVarP( - &path, - "path", - "p", - "https://raw.githubusercontent.com/liquibase/liquibase-package-manager/master/internal/app/packages.json", - "path to new packages.json manifest", - ) -} diff --git a/internal/app/dependencies/Dependencies.go b/internal/app/dependencies/Dependencies.go deleted file mode 100644 index b78b612..0000000 --- a/internal/app/dependencies/Dependencies.go +++ /dev/null @@ -1,71 +0,0 @@ -package dependencies - -import ( - "encoding/json" - "io/ioutil" - "os" - "package-manager/internal/app/errors" -) - -//FileLocation exported for testing overwrite -var FileLocation string - -func init() { - pwd, err := os.Getwd() - if err != nil { - errors.Exit(err.Error(), 1) - } - FileLocation = pwd + "/liquibase.json" -} - -//Dependencies main wrapper for liquibase.json objects -type Dependencies struct { - Dependencies []Dependency `json:"dependencies"` -} - -//CreateFile init liquibase.json file in pwd -func (d Dependencies) CreateFile() { - file, err := os.Create(FileLocation) - if err != nil { - errors.Exit(err.Error(), 1) - } - defer file.Close() - d.Write() -} - -//Write dump contents to liquibase.json -func (d Dependencies) Write() { - file, err := json.MarshalIndent(d, "", " ") - if err != nil { - errors.Exit(err.Error(), 1) - } - err = ioutil.WriteFile(FileLocation, file, 0664) - if err != nil { - errors.Exit(err.Error(), 1) - } -} - -//Read get contents from liquibase.json -func (d *Dependencies) Read() { - file, _ := os.Open(FileLocation) - defer file.Close() - decoder := json.NewDecoder(file) - for decoder.More() { - decoder.Decode(d) - } -} - -//FileExists does the liquibase.json file exist -func (d Dependencies) FileExists() bool { - _, err := os.Stat(FileLocation) - return err == nil -} - -//Remove remove specific dependency from group -func (d *Dependencies) Remove(n string) { - for i, m := range d.Dependencies { - if m.GetName() == n { - d.Dependencies = append(d.Dependencies[:i], d.Dependencies[i+1:]...) - } - } -} \ No newline at end of file diff --git a/internal/app/dependencies/dependencies_test.go b/internal/app/dependencies/dependencies_test.go deleted file mode 100644 index 0de5d3f..0000000 --- a/internal/app/dependencies/dependencies_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package dependencies - -import ( - "io/ioutil" - "os/exec" - "path/filepath" - "reflect" - "strings" - "testing" -) - -var d Dependencies - -func init() { - rootPath, _ := exec.Command("git", "rev-parse", "--show-toplevel").Output() - FileLocation = strings.TrimRight(string(rootPath), "\n") + "/tests/mocks/liquibase.json" - d = Dependencies{} -} - -func TestDependencies_CreateFile(t *testing.T) { - d.CreateFile() - _, file := filepath.Split(FileLocation) - if file != "liquibase.json" { - t.Fatalf("Expected %s but got %s", "liquibase.json", file ) - } -} - -func TestDependencies_FileExists(t *testing.T) { - if d.FileExists() != true { - t.Fatalf( "Unable to verify liquibase.json file exists." ) - } -} - -func TestDependencies_Write(t *testing.T) { - d.Dependencies = append(d.Dependencies, Dependency{"package": "tag"}) - d.Write() - - file, _ := ioutil.ReadFile(FileLocation) - content := `{ - "dependencies": [ - { - "package": "tag" - } - ] -}` - if string(file) != content { - t.Fatalf( "Unable to verify liquibase.json json contents." ) - } -} - -func TestDependencies_Read(t *testing.T) { - dd := Dependencies{} - dd.Read() - if reflect.TypeOf(dd.Dependencies[0]) != reflect.TypeOf(Dependency{}) { - t.Fatalf("Unable to load Dendency from file") - } - for k, v := range dd.Dependencies[0] { - if k != "package" { - t.Fatalf("Invalid Key") - } - if v != "tag" { - t.Fatalf("Invalid Value") - } - } -} - -func TestDependencies_Remove(t *testing.T) { - d.Remove("package") - if len(d.Dependencies) != 0 { - t.Fatalf( "Unable to remove dependency" ) - } -} diff --git a/internal/app/errors/errors.go b/internal/app/errors/errors.go deleted file mode 100644 index d9a6e81..0000000 --- a/internal/app/errors/errors.go +++ /dev/null @@ -1,13 +0,0 @@ -package errors - -import ( - "fmt" - "os" -) - -//Exit graceful exit with message -func Exit(message string, code int) { - fmt.Println(message) - os.Exit(code) -} - diff --git a/internal/app/packages/Packages.go b/internal/app/packages/Packages.go deleted file mode 100644 index dc03c3c..0000000 --- a/internal/app/packages/Packages.go +++ /dev/null @@ -1,54 +0,0 @@ -package packages - -import ( - "fmt" - "io/fs" -) - -//Packages type -type Packages []Package - -//GetByName individual package from packages -func (ps Packages) GetByName(n string) Package { - var r Package - for _, p := range ps { - if p.Name == n { - r = p - } - } - return r -} - -//FilterByCategory get packages by catetory -func (ps Packages) FilterByCategory(c string) Packages { - var r Packages - for _, p := range ps { - if p.Category == c { - r = append(r, p) - } - } - return r -} - -//Display generate display table for packages -func (ps Packages) Display(files []fs.FileInfo) []string { - var r []string - var prefix string - r = append(r, fmt.Sprintf("%-4s %-38s %s", " ", "Package", "Category")) - for i, p := range ps { - if (i+1) == len(ps) { - prefix = "└──" - } else { - prefix = "├──" - } - var v string - tag := p.GetInstalledVersion(files).Tag - if tag != "" { - v = "@" + tag - } else { - v = tag - } - r = append(r, fmt.Sprintf("%-4s %-38s %s", prefix, p.Name + v, p.Category)) - } - return r -} \ No newline at end of file diff --git a/internal/app/packages/Version.go b/internal/app/packages/Version.go deleted file mode 100644 index 4920881..0000000 --- a/internal/app/packages/Version.go +++ /dev/null @@ -1,97 +0,0 @@ -package packages - -import ( - "bytes" - "crypto/sha1" - "crypto/sha256" - "fmt" - "io" - "io/fs" - "io/ioutil" - "os" - "package-manager/internal/app/errors" - "package-manager/internal/app/utils" - "path/filepath" - "strings" -) - -//Version struct -type Version struct { - Tag string `json:"tag"` - Path string `json:"path"` - Algorithm string `json:"algorithm"` - CheckSum string `json:"checksum"` -} - -//GetFilename from version -func (v Version) GetFilename() string { - _, f := filepath.Split(v.Path) - return f -} - -//InClassPath version is installed in classpath -func (v Version) InClassPath(files []fs.FileInfo) bool { - r := false - for _, f := range files { - if f.Name() == v.GetFilename() { - r = true - } - } - return r -} - -//PathIsHTTP remote or local file path -func (v Version) PathIsHTTP() bool { - return strings.HasPrefix(v.Path, "http") -} - -//CopyToClassPath install local version to classpath -func (v Version) CopyToClassPath(cp string) { - source, err := os.Open(v.Path) - if err != nil { - errors.Exit("Unable to open " + v.Path, 1) - } - defer source.Close() - b, err := ioutil.ReadAll(source) - if err != nil { - errors.Exit(err.Error(), 1) - } - writeToDestination(cp + v.GetFilename(), b, v.GetFilename()) -} - -func writeToDestination(d string, b []byte, f string) { - destination, err := os.Create(d) - if err != nil { - errors.Exit("Unable to access classpath located at " + d, 1) - } - defer destination.Close() - _, err = io.Copy(destination, bytes.NewReader(b)) - if err != nil { - errors.Exit("Unable to install " + f + " in classpath.", 1) - } -} - -func (v Version) calcChecksum(b []byte) string { - var r string - switch v.Algorithm { - case "SHA1": - r = fmt.Sprintf("%x", sha1.Sum(b)) - case "SHA256": - r = fmt.Sprintf("%x", sha256.Sum256(b)) - default: - errors.Exit("Unknown Algorithm.", 1) - } - return r -} - -//DownloadToClassPath install remote version to classpath -func (v Version) DownloadToClassPath(cp string) { - body := utils.HTTPUtil{}.Get(v.Path) - sha := v.calcChecksum(body) - if sha == v.CheckSum { - fmt.Println("Checksum verified. Installing " + v.GetFilename() + " to " + cp) - } else { - errors.Exit("Checksum validation failed. Aborting download.", 1) - } - writeToDestination(cp + v.GetFilename(), body, v.GetFilename()) -} \ No newline at end of file diff --git a/internal/app/utils/Http.go b/internal/app/utils/Http.go deleted file mode 100644 index cf43011..0000000 --- a/internal/app/utils/Http.go +++ /dev/null @@ -1,31 +0,0 @@ -package utils - -import ( - "io/ioutil" - "net/http" - "package-manager/internal/app/errors" -) - -//HTTPUtil struct -type HTTPUtil struct {} - -//Get contencts from URL as bytes -func (h HTTPUtil) Get(url string) []byte { - client := http.Client{ - CheckRedirect: func(r *http.Request, via []*http.Request) error { - r.URL.Opaque = r.URL.Path - return nil - }, - } - r, err := client.Get(url) - if err != nil { - errors.Exit("Unable to download from " + url, 1) - - } - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - errors.Exit(err.Error(), 1) - } - return body -} diff --git a/pkg/lpm/add_cmd.go b/pkg/lpm/add_cmd.go new file mode 100644 index 0000000..1596d5e --- /dev/null +++ b/pkg/lpm/add_cmd.go @@ -0,0 +1,112 @@ +package lpm + +import ( + "fmt" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(addCmd) + addCmd.Flags().BoolVarP( + &cliArgs.Global, + "global", + "g", + false, + "add package globally") +} + +// addCmd represents the add command +var addCmd = &cobra.Command{ + Use: "add [PACKAGE]...", + Short: "Add Packages", + Args: cobra.ArbitraryArgs, + Run: func(cmd *cobra.Command, args []string) { + var p Package + var v Version + var err error + + ctx := ContextFromCobraCommand(cmd) + + dd := NewDependencies() + + if ctx.FileSource == LocalFiles { + err = dd.ReadManifest(ctx) + } + if err != nil { + ctx.Error("unable to read local classpath files") + goto end + } + + for _, name := range args { + + err = maybeAddPackage(ctx, name) + if err != nil { + ctx.Error("unable to add package %s; %w", name, err) + continue + } + + dd.Append(NewDependency(p.Name, v.Tag)) + + fmt.Printf("%s successfully installed in classpath.\n", + v.GetFilename()) + + } + + if ctx.FileSource == LocalFiles { + //Add package to local manifest + if !dd.FileExists(ctx) { + err = dd.CreateManifestFile(ctx) + } + if err != nil { + ctx.Error("unable to create local manifest %s", ctx.GetManifestFilepath()) + goto end + } + err = dd.WriteManifest(ctx) + if err != nil { + ctx.Error("unable to write to local manifest %s", ctx.GetManifestFilepath()) + goto end + } + + } + + // Output helper for JAVA_OPTS + ctx.PrintJavaOptsHelper() + end: + }, +} + +func maybeAddPackage(ctx *Context, name string) error { + var cp string + var msg string + var files ClasspathFiles + + p, v, err := ctx.GetPackageAndVersion(name) + + if p.Name == "" { + msg = fmt.Sprintf("package '%s' not found", name) + goto end + } + + files, cp, err = ctx.GetClasspathFiles() + if err != nil { + msg = fmt.Sprintf("unable to get classpath files") + goto end + } + + if files.VersionExists(v) { + msg = fmt.Sprintf("%s is already installed", name) + goto end + } + + err = v.CopyFilesToClassPath(cp) + if err != nil { + msg = fmt.Sprintf("unable to copy %s file to classpath", name) + goto end + } + +end: + if msg != "" { + err = fmt.Errorf("%s when attempting to add", msg) + } + return err +} diff --git a/pkg/lpm/cli_args.go b/pkg/lpm/cli_args.go new file mode 100644 index 0000000..adc6fa4 --- /dev/null +++ b/pkg/lpm/cli_args.go @@ -0,0 +1,12 @@ +package lpm + +type CliArgs struct { + Category string + Global bool +} + +var cliArgs CliArgs + +func init() { + cliArgs = CliArgs{} +} diff --git a/pkg/lpm/context.go b/pkg/lpm/context.go new file mode 100644 index 0000000..81f3b69 --- /dev/null +++ b/pkg/lpm/context.go @@ -0,0 +1,560 @@ +package lpm + +import ( + "context" + "encoding/json" + "fmt" + "github.com/spf13/cobra" + "io/fs" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" +) + +type FileSource byte + +const GlobalFiles FileSource = 'g' +const LocalFiles FileSource = 'l' + +const PackageFilePerms = 0664 +const DirectoryPerms = 0775 +const DefaultPackageFile = "packages.json" + +// ClasspathFiles represents either Global or Local Classpath ClasspathFiles +type ClasspathFiles []fs.FileInfo + +func (files ClasspathFiles) VersionExists(v Version) bool { + r := false + for _, f := range files { + if f.Name() == v.GetFilename() { + r = true + break + } + } + return r +} + +// Context contains +type Context struct { + context.Context + + WorkingDir string + + manifestFilepath string + FileSource FileSource + Category string + HomeDir string + Path string + + //PackageFile to use + //ex: package.json + PackageFile string + + // bytes for storing JSON read from file + packageBytes []byte + + classpath string + + classpathFiles ClasspathFiles + + packages Packages + + errors []error +} + +func (ctx *Context) GetPackageByName(n string) Package { + return ctx.packages.GetByName(n) +} + +// PrintJavaOptsHelper +// TODO Test this on windows +func (ctx *Context) PrintJavaOptsHelper() { + jo := fmt.Sprintf("-cp liquibase_libs%s*:%s*:%sliquibase.jar", + string(os.PathSeparator), + ctx.GetGlobalClasspath(), + ctx.HomeDir) + fmt.Println() + fmt.Println("---------- IMPORTANT ----------") + fmt.Println("Run the following JAVA_OPTS command:") + fmt.Printf("\n\texport JAVA_OPTS=\"%s\"", jo) +} + +func ContextFromCobraCommand(cmd *cobra.Command) *Context { + ctx, ok := cmd.Context().(*Context) + if !ok { + // When !ok it is a programming error. So okay to just exit + fmt.Printf("Unable to assert *app.Context from *cobra.Context:\n") + os.Exit(1) + } + return ctx +} + +func NewContext(path string) *Context { + return &Context{ + Context: context.Background(), + PackageFile: DefaultPackageFile, + classpathFiles: make(ClasspathFiles, 0), + errors: make([]error, 0), + Path: path, + } +} + +type ContextArgs struct { + Path string + WorkingDir string +} + +// NewInitializedContext creates a new context and initializes it, +// with optional arguments and returning an error on failure +func NewInitializedContext(args *ContextArgs) (ctx *Context, err error) { + ctx = NewContext(ctx.Path) + err = ctx.Initialize() + if args.WorkingDir != "" { + ctx.WorkingDir = args.WorkingDir + } + return ctx, err +} + +// GetManifestFilepath gets filepath for `liquibase.json` manifest file +func (ctx *Context) GetManifestFilepath() string { + if ctx.manifestFilepath != "" { + goto end + } + ctx.manifestFilepath = fmt.Sprintf("%s%sliquibase.json", + string(os.PathSeparator), + ctx.WorkingDir) +end: + return ctx.manifestFilepath +} + +// GetGlobalPackageFilepath gets the global class filepath +func (ctx *Context) GetGlobalPackageFilepath() string { + return ctx.GetClassPackageFilepath(ctx.GetGlobalClasspath()) +} + +// GetLocalPackageFilepath gets the local class filepath +func (ctx *Context) GetLocalPackageFilepath() (pfp string, err error) { + var cp string + cp, err = ctx.GetLocalClasspath() + if err != nil { + err = fmt.Errorf("unable to get local package filepath; %w", err) + goto end + } + pfp = ctx.GetClassPackageFilepath(cp) +end: + return pfp, err +} + +// GetClassPackageFilepath gets the class filepath for classpath specified +func (ctx *Context) GetClassPackageFilepath(path string) string { + return fmt.Sprintf("%s%s", + path, + ctx.PackageFile) +} + +func (ctx *Context) GetGlobalClasspath() string { + return fmt.Sprintf("%slib%s", + ctx.HomeDir, + ctx.Path) +} + +func (ctx *Context) maybeCreateInitialPackageFile() (err error) { + if ctx.PackagesInClassPath() { + goto end + } + // TODO Should this be copying to GLOBAL classpath, or current classpath? + err = ctx.CopyPackageBytesToClassPath(PackagesJSON) + if err != nil { + err = fmt.Errorf("unable to create initial packages.json in %s; %w", + ctx.GetGlobalClasspath(), + err) + goto end + } +end: + return err +} + +// Initialize ensure the Context object has required values +func (ctx *Context) Initialize() (err error) { + + ctx.HomeDir, err = GetHomeDir() + if err != nil { + err = fmt.Errorf("unable to get home directory for lbm; %w", err) + goto end + } + + ctx.WorkingDir, err = GetWorkingDir() + if err != nil { + err = fmt.Errorf("unable to get current working directory for lbm; %w", err) + goto end + } + + //Install Embedded Package File + err = ctx.maybeCreateInitialPackageFile() + if err != nil { + goto end + } + + //Load Packages + //@TODO Should this be loaded from Global Package Filepath, or + // loaded from Local? + err = ctx.LoadPackages(ctx.GetGlobalPackageFilepath()) + + // Set global vs local classpath + ctx.FileSource = LocalFiles + +end: + + return err + +} + +// GetLocalClasspath returns the current working directory +// with the path `/liquibase_libs/` added. +// +func (ctx *Context) GetLocalClasspath() (cp string, err error) { + + var pwd string + pwd, err = os.Getwd() + if err != nil { + err = fmt.Errorf("unable to get working directory for local classpath; %w", + err) + goto end + } + + cp = fmt.Sprintf("%s%sliquibase_libs%s", + string(os.PathSeparator), + pwd, + string(os.PathSeparator)) + +end: + + return cp, err +} + +//GetFiles returns list of files +func (ctx *Context) GetFiles(path string) (files ClasspathFiles, err error) { + err = os.Mkdir(path, DirectoryPerms) + if err != nil { + err = fmt.Errorf("unable to make directory %s; %w", + path, + err) + goto end + } + + files, err = ioutil.ReadDir(path) + if err != nil { + err = fmt.Errorf("unable to list files in directory %s; %w", + path, + err) + goto end + } +end: + return files, err +} + +//GetClasspath returns global or local depending on FileSource +func (ctx *Context) GetClasspath() (cp string, err error) { + + switch ctx.FileSource { + + case GlobalFiles: + cp = ctx.GetGlobalClasspath() + + case LocalFiles: + cp, err = ctx.GetLocalClasspath() + if err != nil { + err = fmt.Errorf("unable to get local classpath; %w", + err) + } + } + + return cp, err +} + +//GetClasspathFiles returns global or local files depending on FileSource +// @TODO consider catching the files to a private instance variable on 1st call +func (ctx *Context) GetClasspathFiles() (files ClasspathFiles, cp string, err error) { + cp, err = ctx.GetClasspath() + if err != nil { + err = fmt.Errorf("unable to get classpath; %w", + err) + goto end + } + files, err = ctx.GetFiles(cp) + if err != nil { + err = fmt.Errorf("unable to get classpath files; %w", + err) + goto end + } + +end: + return files, cp, err +} + +//PackagesInClassPath is the packages.json file in global classpath +func (ctx *Context) PackagesInClassPath() bool { + // TODO Should this be Global classpath or current classpath? + _, err := os.Stat(ctx.GetGlobalClasspath()) + return err == nil +} + +//CopyPackageBytesToClassPath install packages.json to provided classpath +func (ctx *Context) copyPackagesToClassPath(p []byte, cp string) (err error) { + err = ioutil.WriteFile(cp, p, PackageFilePerms) + if err != nil { + err = fmt.Errorf("unable to write to %s; %w", cp, err) + } + return err +} + +//CopyPackageBytesToClassPath install packages.json to provided classpath +func (ctx *Context) CopyPackageBytesToClassPath(b []byte) (err error) { + var cp string + cp, err = ctx.GetClasspath() + if err != nil { + err = fmt.Errorf("unable to copy packages to classpath %s; %w", + cp, + err) + goto end + } + err = ctx.copyPackagesToClassPath(b, cp) +end: + return err +} + +//CopyPackagesToGlobalClassPath install packages.json to global classpath +func (ctx *Context) CopyPackagesToGlobalClassPath() (err error) { + return ctx.copyPackagesToClassPath( + ctx.packageBytes, + ctx.GetGlobalClasspath()) +} + +//UnmarshalPackages from bytes +func (ctx *Context) UnmarshalPackages(b []byte) (packs Packages, err error) { + packs = make(Packages, 0) + + //Unmarshal the JSON into array of Packages + err = json.Unmarshal(b, &packs) + if err != nil { + err = fmt.Errorf("unable to unmarshal packages from JSON; %w", + err) + goto end + } +end: + return packs, err +} + +//LoadPackages from filepath +func (ctx *Context) LoadPackages(fp string) (err error) { + // JSON Package File + var jpf *os.File + + // error action + var action string + + //Open the JSON Package File + jpf, err = os.Open(fp) + if err != nil { + action = "open" + goto end + } + + //Get Bytes from JSON Package File + ctx.packageBytes, err = ioutil.ReadAll(jpf) + if err != nil { + action = "read from" + goto end + } + + //Unmarshal the JSON into array of Packages + ctx.packages, err = ctx.UnmarshalPackages(ctx.packageBytes) + if err != nil { + action = "load packages from" + goto end + } + +end: + if action != "" { + err = fmt.Errorf("unable to %s %s; %w", + action, + jpf.Name(), + err) + } + return err +} + +//SearchPackages returns previously loaded packages filtered by `name` +func (ctx *Context) SearchPackages(name string) (p Packages) { + pp := make(Packages, 0) + if name == "" { + copy(pp, ctx.GetPackages()) + goto end + } + for _, p := range ctx.GetPackages() { + if !strings.Contains(p.Name, name) { + continue + } + pp = append(pp, p) + } +end: + return pp +} + +//GetPackages returns previously loaded packages +func (ctx *Context) GetPackages() (p Packages) { + return ctx.packages +} + +//GetFilteredPackages returns previously loaded packages filtered by category +func (ctx *Context) GetFilteredPackages() (p Packages) { + return ctx.GetPackages().FilterByCategory(ctx.Category) +} + +//WritePackages write packages back to file +func (ctx *Context) WritePackages(p Packages) (err error) { + var b []byte + var pwd string + var jf string + + b, err = json.MarshalIndent(p, "", " ") + if err != nil { + err = fmt.Errorf("unable to marshall JSON for packages; %w", err) + goto end + } + + pwd, err = os.Getwd() + if err != nil { + err = fmt.Errorf("unable to get working directory for packages; %w", err) + goto end + } + jf = fmt.Sprintf("%s/embeds/packages.json", pwd) + + // @see https://stackoverflow.com/a/9373342/102699 + jf = filepath.FromSlash(jf) + + err = ioutil.WriteFile(jf, b, PackageFilePerms) + if err != nil { + err = fmt.Errorf("unable to get working directory for packages; %w", err) + goto end + } +end: + return err +} + +// GetWorkingDir returns current working directory +func GetWorkingDir() (workdir string, err error) { + workdir, err = os.Getwd() + if err != nil { + err = fmt.Errorf("failed to get current working directory; %w", err) + } + return workdir, err +} + +// GetHomeDir returns the Liquibase home directory +func GetHomeDir() (homedir string, err error) { + var out []byte + var loc string + var fi os.FileInfo + var link string + + if _, ok := os.LookupEnv("LIQUIBASE_HOME"); ok { + homedir = os.Getenv("LIQUIBASE_HOME") + goto end + } + + out, err = exec.Command("which", "liquibase").CombinedOutput() + if err != nil { + // @see https://blog.golang.org/go1.13-errors#TOC_3.3. + err = fmt.Errorf("unable to locate Liquibase: %w", err) + goto end + } + + // Determine if Command is Symlink + loc = strings.TrimRight(string(out), "\n") + fi, err = os.Lstat(loc) + if err != nil { + err = fmt.Errorf("cannot stat %s: %w", loc, err) + goto end + } + + if fi.Mode()&os.ModeSymlink == 0 { + // Not Symlink + homedir, _ = filepath.Split(loc) + goto end + } + + link, err = os.Readlink(loc) + if err != nil { + err = fmt.Errorf("cannot read %s: %w", loc, err) + goto end + } + + // Is Symlink + homedir, _ = filepath.Split(link) + +end: + if !strings.HasSuffix(homedir, "/") { + homedir = homedir + "/" + } + return homedir, err + +} + +func (ctx *Context) GetPackageAndVersion(name string) (p Package, v Version, err error) { + var parts []string + if !strings.Contains(name, "@") { + p = ctx.GetPackageByName(name) + v = p.GetLatestVersion() + goto end + } + parts = strings.Split(name, "@") + if parts[0] == "" { + err = fmt.Errorf("no package name provided in '%s'", parts[0]) + goto end + } + p = ctx.GetPackageByName(parts[0]) + if parts[1] == "" { + err = fmt.Errorf("no VersionNumber name provided in '%s'", parts[1]) + goto end + } + v = p.GetVersion(parts[1]) + if v.Tag == "" { + err = fmt.Errorf("VersionNumber %s not available for package %s", + parts[1], + name) + goto end + } +end: + return p, v, err + +} + +func (ctx *Context) Error(params ...interface{}) { + var msg string + var err error + err, ok := params[1].(error) + if ok { + goto end + } + msg, ok = params[1].(string) + if !ok { + err = fmt.Errorf("error in parameters passed to ctx.Error(); first parameter must be of type `string` or `error`") + } + if len(params) == 1 { + err = fmt.Errorf(msg) + goto end + } + err = fmt.Errorf(msg, params[1:]...) +end: + ctx.errors = append(ctx.errors, err) +} + +// ShowUserError display any error to a user +// TODO Make the output generated more attractive +func ShowUserError(err error) { + fmt.Printf("%s\n", err) + +} diff --git a/pkg/lpm/darwin.go b/pkg/lpm/darwin.go new file mode 100644 index 0000000..e530c4b --- /dev/null +++ b/pkg/lpm/darwin.go @@ -0,0 +1,12 @@ +// +build darwin + +package lpm + +import ( + "strings" +) + +//goland:noinspection GoUnusedExportedFunction +func TrimCommandOutput(cmdout string) string { + return strings.TrimRight(string(cmdout), "\n") +} diff --git a/pkg/lpm/dependencies.go b/pkg/lpm/dependencies.go new file mode 100644 index 0000000..8bd38c9 --- /dev/null +++ b/pkg/lpm/dependencies.go @@ -0,0 +1,117 @@ +package lpm + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" +) + +//Dependencies main wrapper for liquibase.json objects +type Dependencies struct { + // @TODO Handle marshaling to JSON and unmarshalling from JSON + Dependencies []Dependency `json:"dependencies"` +} + +func (dd *Dependencies) Append(d Dependency) { + dd.Dependencies = append(dd.Dependencies, d) +} + +// NewDependencies returns a new instance of Dependencies +func NewDependencies() Dependencies { + return Dependencies{} +} + +//CreateManifestFile init liquibase.json file in pwd +// TODO Renamed to be more clear on what type of file was being created. +// Was I correct? Is it a Manifest file? +func (dd *Dependencies) CreateManifestFile(ctx *Context) (err error) { + file, err := os.Create(ctx.GetGlobalClasspath()) + if err != nil { + err = fmt.Errorf("unable to create dependency file %s; %w", + ctx.GetManifestFilepath(), + err) + goto end + } + //goland:noinspection GoUnhandledErrorResult + defer file.Close() + err = dd.WriteManifest(ctx) + if err != nil { + err = fmt.Errorf("unable to write dependency file %s; %w", + ctx.GetManifestFilepath(), + err) + goto end + } +end: + return err +} + +//WriteManifest dump contents to liquibase.json +// TODO Renamed to be more clear on what type of file was being written. +// Was I correct? Is it a Manifest file? +func (dd *Dependencies) WriteManifest(ctx *Context) (err error) { + var file []byte + file, err = json.MarshalIndent(dd, "", " ") + if err != nil { + err = fmt.Errorf("unable to marshal dependency JSON; %w", + err) + goto end + } + err = ioutil.WriteFile(ctx.GetManifestFilepath(), file, 0664) + if err != nil { + err = fmt.Errorf("unable to write dependency JSON to %s; %w", + ctx.GetManifestFilepath(), + err) + goto end + } +end: + return err +} + +//ReadManifest contents from liquibase.json into a Dependencies +// TODO Renamed to be more clear on what type of file was being read. +// Was I correct? Is it a Manifest file? +func (dd *Dependencies) ReadManifest(ctx *Context) (err error) { + var file *os.File + file, err = os.Open(ctx.GetManifestFilepath()) + var decoder *json.Decoder + + if err != nil { + err = fmt.Errorf("unable to read %s; %w", + ctx.GetManifestFilepath(), + err) + goto end + } + + //goland:noinspection GoUnhandledErrorResult + defer file.Close() + decoder = json.NewDecoder(file) + for decoder.More() { + err = decoder.Decode(dd) + if err != nil { + err = fmt.Errorf("unable to decode JSON in %s; %w", + ctx.GetManifestFilepath(), + err) + goto end + } + } + +end: + + return err +} + +//FileExists returns true if the liquibase.json file exist +func (dd *Dependencies) FileExists(ctx *Context) bool { + _, err := os.Stat(ctx.GetManifestFilepath()) + return err == nil +} + +//Remove specific dependency from group +func (dd *Dependencies) Remove(n string) { + for i, m := range dd.Dependencies { + if m.GetName() == n { + dd.Dependencies = append(dd.Dependencies[:i], dd.Dependencies[i+1:]...) + } + } +} diff --git a/internal/app/dependencies/dependency.go b/pkg/lpm/dependency.go similarity index 65% rename from internal/app/dependencies/dependency.go rename to pkg/lpm/dependency.go index 30871ef..faa7166 100644 --- a/internal/app/dependencies/dependency.go +++ b/pkg/lpm/dependency.go @@ -1,13 +1,19 @@ -package dependencies +package lpm //Dependency main "package":"tag" object type Dependency map[string]string +//NewDependency instantiates a Dependency from a pkgname and a tag +func NewDependency(pkgname, tag string) Dependency { + return Dependency{pkgname: tag} +} + //GetName get key from Dependency map func (d Dependency) GetName() string { var r string for k := range d { r = k + break } return r } @@ -17,6 +23,7 @@ func (d Dependency) GetVersion() string { var r string for _, v := range d { r = v + break } return r -} \ No newline at end of file +} diff --git a/pkg/lpm/embeds/VERSION b/pkg/lpm/embeds/VERSION new file mode 100644 index 0000000..6da28dd --- /dev/null +++ b/pkg/lpm/embeds/VERSION @@ -0,0 +1 @@ +0.1.1 \ No newline at end of file diff --git a/internal/app/packages.json b/pkg/lpm/embeds/packages.json similarity index 100% rename from internal/app/packages.json rename to pkg/lpm/embeds/packages.json diff --git a/pkg/lpm/http.go b/pkg/lpm/http.go new file mode 100644 index 0000000..ec22620 --- /dev/null +++ b/pkg/lpm/http.go @@ -0,0 +1,38 @@ +package lpm + +import ( + "fmt" + "io/ioutil" + "net/http" +) + +//HttpGet connects from URL as bytes +func HttpGet(url string) (body []byte, err error) { + + callback := func(r *http.Request, via []*http.Request) error { + r.URL.Opaque = r.URL.Path + return nil + } + + client := http.Client{ + CheckRedirect: callback, + } + + r, err := client.Get(url) + if err != nil { + err = fmt.Errorf("unable to download from "+url, 1) + goto end + + } + + //goland:noinspection GoUnhandledErrorResult + defer r.Body.Close() + body, err = ioutil.ReadAll(r.Body) + if err != nil { + err = fmt.Errorf("unable to read HTTP response body: %w", err) + goto end + } + +end: + return body, err +} diff --git a/pkg/lpm/install_cmd.go b/pkg/lpm/install_cmd.go new file mode 100644 index 0000000..8e34a30 --- /dev/null +++ b/pkg/lpm/install_cmd.go @@ -0,0 +1,105 @@ +package lpm + +import ( + "fmt" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(installCmd) +} + +// updateCmd represents the `install` command +var installCmd = &cobra.Command{ + Use: "install", + Short: "Install Packages from liquibase.json", + Run: func(cmd *cobra.Command, args []string) { + + var d Dependencies + var dep Dependency + var fn string + var files ClasspathFiles + var cp string + var err error + + ctx := ContextFromCobraCommand(cmd) + if ctx.FileSource == GlobalFiles { + ctx.Error("cannot install packages from liquibase.json globally") + goto end + } + + d = NewDependencies() + + err = d.ReadManifest(ctx) + if err != nil { + ctx.Error("unable to read dependencies file; %w", err) + goto end + } + + files, cp, err = ctx.GetClasspathFiles() + if err != nil { + ctx.Error("unable to get files for classpath '%s'; %w", cp, err) + goto end + } + + for _, dep = range d.Dependencies { + fn, err = maybeInstall(ctx, dep, cp, files) + if err != nil { + err = fmt.Errorf("unable to install dependency %s %s; %w", + dep.GetName(), + dep.GetVersion(), + err) + continue + } + fmt.Printf("%s successfully installed in classpath %s.\n", + fn, + cp) + } + + if err != nil { + ctx.Error(err) + goto end + } + + // Output helper for JAVA_OPTS + ctx.PrintJavaOptsHelper() + + end: + }, +} + +func maybeInstall(ctx *Context, dep Dependency, cp string, files ClasspathFiles) (fn string, err error) { + + p := ctx.GetPackageByName(dep.GetName()) + v := p.GetVersion(dep.GetVersion()) + fn = v.GetFilename() + + if files.VersionExists(v) { + ctx.Error("%s is already installed for %s", p.Name) + goto end + } + + err = CopyToClassPath(v, cp) + if err != nil { + err = fmt.Errorf("unable to copy ##WHAT?## to class path '%s' for VersionNumber '%s'; %w", + cp, + v.Tag, + err) + goto end + } + +end: + + return fn, err + +} + +func CopyToClassPath(v Version, cp string) (err error) { + if v.PathIsHTTP() { + err = v.DownloadToClassPath(cp) + goto end + } + err = v.CopyToClassPath(cp) +end: + return err +} diff --git a/pkg/lpm/list_cmd.go b/pkg/lpm/list_cmd.go new file mode 100644 index 0000000..f6d4225 --- /dev/null +++ b/pkg/lpm/list_cmd.go @@ -0,0 +1,57 @@ +package lpm + +import ( + "fmt" + "github.com/spf13/cobra" + "os" +) + +func init() { + rootCmd.AddCommand(listCmd) + // @TODO make an CliArgs struct for `global` and other CLI args + listCmd.Flags().BoolVarP(&cliArgs.Global, "global", "g", false, "list global packages") +} + +// listCmd represents the list command +var listCmd = &cobra.Command{ + Use: "list", + Short: "List Installed Packages", + Aliases: []string{"ls"}, + Run: func(cmd *cobra.Command, args []string) { + var files ClasspathFiles + var cp string + var err error + var p Package + var v Version + var out string + // Collect installed packages + var installed Packages + + ctx := ContextFromCobraCommand(cmd) + + files, cp, err = ctx.GetClasspathFiles() + if err != nil { + ctx.Error("unable to get files for classpath '%s'; %w", cp, err) + goto end + } + + for _, p = range ctx.GetPackages() { + v = p.GetInstalledVersion(files) + if !files.VersionExists(v) { + continue + } + installed = append(installed, p) + } + + // Format output + fmt.Println(cp) + if len(installed) == 0 { + os.Exit(1) + } + + for _, out = range installed.Display(files) { + fmt.Println(out) + } + end: + }, +} diff --git a/pkg/lpm/lpm.go b/pkg/lpm/lpm.go new file mode 100644 index 0000000..311ae81 --- /dev/null +++ b/pkg/lpm/lpm.go @@ -0,0 +1,12 @@ +package lpm + +import ( + _ "embed" // Embed Import for Package Files +) + +//go:embed "embeds/VERSION" +var VersionNumber string + +//PackagesJSON is embedded for first time run +//go:embed "embeds/packages.json" +var PackagesJSON []byte diff --git a/internal/app/packages/Package.go b/pkg/lpm/package.go similarity index 68% rename from internal/app/packages/Package.go rename to pkg/lpm/package.go index 74bd0b9..c1fa6e2 100644 --- a/internal/app/packages/Package.go +++ b/pkg/lpm/package.go @@ -1,14 +1,13 @@ -package packages +package lpm import ( "github.com/hashicorp/go-version" - "io/fs" ) //Package struct type Package struct { - Name string `json:"name"` - Category string `json:"category"` + Name string `json:"name"` + Category string `json:"category"` Versions []Version `json:"versions"` } @@ -17,33 +16,35 @@ func (p Package) GetLatestVersion() Version { var ver Version old, _ := version.NewVersion("0.0.0") for _, v := range p.Versions { - new, _ := version.NewVersion(v.Tag) - if old.LessThan(new) { - old = new + _new, _ := version.NewVersion(v.Tag) + if old.LessThan(_new) { + old = _new ver = v } } return ver } -//GetVersion from package by version name +//GetVersion from package by VersionNumber name func (p Package) GetVersion(v string) Version { var r Version for _, ver := range p.Versions { if ver.Tag == v { r = ver + break } } return r } //GetInstalledVersion from classpath files -func (p Package) GetInstalledVersion(files []fs.FileInfo) Version { +func (p Package) GetInstalledVersion(files ClasspathFiles) Version { var r Version for _, f := range files { for _, v := range p.Versions { if f.Name() == v.GetFilename() { r = v + break } } } diff --git a/pkg/lpm/packages.go b/pkg/lpm/packages.go new file mode 100644 index 0000000..22d25a1 --- /dev/null +++ b/pkg/lpm/packages.go @@ -0,0 +1,63 @@ +package lpm + +import ( + "fmt" +) + +//Packages type +type Packages []Package + +//GetByName individual package from packages +func (pp Packages) GetByName(n string) Package { + var r Package + for _, p := range pp { + if p.Name != n { + continue + } + r = p + break + } + return r +} + +//FilterByCategory get packages by category +func (pp Packages) FilterByCategory(c string) Packages { + var fpp Packages + for _, p := range pp { + if p.Category != c { + continue + } + fpp = append(fpp, p) + } + return fpp +} + +//Println generates display table for all packages +func (pp Packages) Println(files ClasspathFiles) { + for _, out := range pp.Display(files) { + fmt.Println(out) + } +} + +//Display generate display table for packages +func (pp Packages) Display(files ClasspathFiles) []string { + var r []string + var prefix string + r = append(r, fmt.Sprintf("%-4s %-38s %s", " ", "Package", "Category")) + for i, p := range pp { + if (i + 1) == len(pp) { + prefix = "└──" + } else { + prefix = "├──" + } + var v string + tag := p.GetInstalledVersion(files).Tag + if tag != "" { + v = "@" + tag + } else { + v = tag + } + r = append(r, fmt.Sprintf("%-4s %-38s %s", prefix, p.Name+v, p.Category)) + } + return r +} diff --git a/pkg/lpm/remove_cmd.go b/pkg/lpm/remove_cmd.go new file mode 100644 index 0000000..85e3797 --- /dev/null +++ b/pkg/lpm/remove_cmd.go @@ -0,0 +1,84 @@ +package lpm + +import ( + "fmt" + "github.com/spf13/cobra" + "os" +) + +func init() { + rootCmd.AddCommand(removeCmd) + removeCmd.Flags().BoolVarP( + &cliArgs.Global, + "global", + "g", + false, + "remove package globally") +} + +// removeCmd represents the `remove` command +var removeCmd = &cobra.Command{ + Use: "remove [PACKAGE]...", + Short: "Removes Package", + Aliases: []string{"rm"}, + Args: cobra.ArbitraryArgs, + Run: func(cmd *cobra.Command, args []string) { + var files ClasspathFiles + var cp string + var p Package + var v Version + var err error + + ctx := ContextFromCobraCommand(cmd) + + d := NewDependencies() + + if ctx.FileSource != GlobalFiles { + err = d.ReadManifest(ctx) + } + if err != nil { + goto end + } + + files, cp, err = ctx.GetClasspathFiles() + if err != nil { + ctx.Error("unable to get files for classpath '%s'; %w", cp, err) + goto end + } + + // Remove Each Package + for _, name := range args { + p = ctx.GetPackageByName(name) + v = p.GetInstalledVersion(files) + if p.Name == "" { + ctx.Error("package '%s' not found.", name) + } + if !v.InClassPath(files) { + ctx.Error("%s is not installed.", name) + } + err := os.Remove(cp + v.GetFilename()) + if err != nil { + ctx.Error("unable to remove %s from classpath %s", v.GetFilename(), cp) + continue + } + fmt.Printf("%s successfully uninstalled from classpath.\n", v.GetFilename()) + if ctx.FileSource != GlobalFiles { + d.Remove(p.Name) + } + } + + if ctx.FileSource != GlobalFiles { + err = d.WriteManifest(ctx) + } + if err != nil { + ctx.Error("unable to write manifest %s for classpath %s", + v.GetFilename(), + cp) + goto end + } + + end: + return + + }, +} diff --git a/pkg/lpm/root_cmd.go b/pkg/lpm/root_cmd.go new file mode 100644 index 0000000..66a9955 --- /dev/null +++ b/pkg/lpm/root_cmd.go @@ -0,0 +1,46 @@ +package lpm + +import ( + "fmt" + "github.com/spf13/cobra" +) + +const DefaultVersionTemplate = "" + + "{{with .Name}}{{printf \"%s \" .}}{{end}}" + + "{{with .Short}}{{printf \"(%s) \" .}}{{end}}" + + "{{printf \"version %s\" .Version}}\n" + +func init() { + //Global params + //rootCmd.CompletionOptions.DisableDefaultCmd = true + rootCmd.PersistentFlags().StringVar( + &cliArgs.Category, + "category", + "", + "extension, driver, or utility") + + rootCmd.Version = VersionNumber + + rootCmd.SetVersionTemplate(DefaultVersionTemplate) +} + +var rootCmd = &cobra.Command{ + Use: "lpm", + Short: "Liquibase Package Manager", + Long: `Easily manage external dependencies for Database Development. +Search for, install, and uninstall liquibase drivers, extensions, and utilities.`, +} + +//Execute main entry point for CLI from root +func Execute(path string) error { + ctx := NewContext(path) + err := ctx.Initialize() + if err != nil { + err = fmt.Errorf("unable to initialize Context when executing root command; %w", + err) + goto end + } + err = rootCmd.ExecuteContext(ctx) +end: + return err +} diff --git a/pkg/lpm/search_cmd.go b/pkg/lpm/search_cmd.go new file mode 100644 index 0000000..ac5188c --- /dev/null +++ b/pkg/lpm/search_cmd.go @@ -0,0 +1,52 @@ +package lpm + +import ( + "fmt" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(searchCmd) +} + +// searchCmd represents the `search` command +var searchCmd = &cobra.Command{ + Use: "search [PACKAGE]", + Short: "Search for Packages", + + Run: func(cmd *cobra.Command, args []string) { + var name string + var files ClasspathFiles + var cp string + var err error + var found Packages + + ctx := ContextFromCobraCommand(cmd) + + files, cp, err = ctx.GetClasspathFiles() + if err != nil { + ctx.Error("unable to get files for classpath '%s'; %w", cp, err) + goto end + } + + if len(args) > 0 { + name = args[0] + } + + if len(name) < 3 { + fmt.Println("Minimum of 3 characters required for search.") + goto end + } + + found = ctx.SearchPackages(name) + + if len(found) == 0 { + fmt.Println("No results found.") + goto end + } + + found.Println(files) + + end: + }, +} diff --git a/pkg/lpm/update_cmd.go b/pkg/lpm/update_cmd.go new file mode 100644 index 0000000..e9195a3 --- /dev/null +++ b/pkg/lpm/update_cmd.go @@ -0,0 +1,59 @@ +package lpm + +import ( + "fmt" + "github.com/spf13/cobra" +) + +var ( + path string +) + +func init() { + rootCmd.AddCommand(updateCmd) + updateCmd.Flags().StringVarP( + &path, + "path", + "p", + "https://raw.githubusercontent.com/liquibase/liquibase-package-manager/master/embeds/packages.json", + "path to new packages.json manifest", + ) +} + +// updateCmd represents the update command +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Updates the Package Manifest", + + Run: func(cmd *cobra.Command, args []string) { + var err error + + ctx := ContextFromCobraCommand(cmd) + + err = ctx.LoadPackages(path) + if err != nil { + err = fmt.Errorf("unable to load packages during update; %w", + err) + goto end + } + + if ctx.GetPackageByName("postgres").Name == "postgres" { + // TODO Why does postgres not validate? + goto end + } + + err = ctx.CopyPackagesToGlobalClassPath() + if err != nil { + err = fmt.Errorf("unable to copy packages to global classpath during update; %w", + err) + goto end + } + + fmt.Printf("Package manifest updated from %s\n", path) + + end: + if err != nil { + ctx.Error("unable to validate package contents; %w", err) + } + }, +} diff --git a/pkg/lpm/version.go b/pkg/lpm/version.go new file mode 100644 index 0000000..906daac --- /dev/null +++ b/pkg/lpm/version.go @@ -0,0 +1,154 @@ +package lpm + +import ( + "bytes" + "crypto/sha1" + "crypto/sha256" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +type ChecksumAlgorithm string + +const Sha1Algorithm ChecksumAlgorithm = "SHA1" + +//Version struct +type Version struct { + Tag string `json:"tag"` + Path string `json:"path"` + Algorithm ChecksumAlgorithm `json:"algorithm"` + CheckSum string `json:"checksum"` +} + +func (v Version) CopyFilesToClassPath(cp string) (err error) { + if !v.PathIsHTTP() { + err = v.CopyToClassPath(cp) + } else { + err = v.DownloadToClassPath(cp) + } + return err +} + +//GetFilename from VersionNumber +func (v Version) GetFilename() string { + _, f := filepath.Split(v.Path) + return f +} + +//InClassPath VersionNumber is installed in classpath +func (v Version) InClassPath(files ClasspathFiles) bool { + r := false + for _, f := range files { + if f.Name() == v.GetFilename() { + r = true + break + } + } + return r +} + +//PathIsHTTP remote or local file path +func (v Version) PathIsHTTP() bool { + return strings.HasPrefix(v.Path, "http") +} + +//CopyToClassPath install local VersionNumber to classpath +func (v Version) CopyToClassPath(cp string) error { + var b []byte + source, err := os.Open(v.Path) + if err != nil { + err = fmt.Errorf("unable to open %s when copying to classpath; %w", + v.Path, + err) + goto end + } + //goland:noinspection GoUnhandledErrorResult + defer source.Close() + b, err = ioutil.ReadAll(source) + if err != nil { + err = fmt.Errorf("unable to read from %s when copying to classpath; %w", + v.Path, + err) + goto end + } + err = writeToDestination(cp+v.GetFilename(), b) +end: + return err +} + +func writeToDestination(dest string, buf []byte) error { + destination, err := os.Create(dest) + if err != nil { + err = fmt.Errorf("unable to create file %s while writing to destination; %w", + dest, + err) + goto end + } + //goland:noinspection GoUnhandledErrorResult + defer destination.Close() + _, err = io.Copy(destination, bytes.NewReader(buf)) + if err != nil { + err = fmt.Errorf("unable to write contents to file %s; %w", + dest, + err) + goto end + } +end: + return err +} + +func (v Version) CalcChecksum(b []byte) (r string, err error) { + switch v.Algorithm { + case "SHA1": + r = fmt.Sprintf("%x", sha1.Sum(b)) + case "SHA256": + r = fmt.Sprintf("%x", sha256.Sum256(b)) + default: + err = fmt.Errorf("unknown checksum algorithm: '%s'", string(b)) + } + return r, err +} + +//DownloadToClassPath install remote VersionNumber to classpath +func (v Version) DownloadToClassPath(cp string) (err error) { + var body []byte + var sha, msg, fn, fp string + + body, err = HttpGet(v.Path) + if sha != v.CheckSum { + err = fmt.Errorf("failed to download class path %s; %w", cp, err) + goto end + } + + sha, err = v.CalcChecksum(body) + if err != nil { + msg = "unable to calculate checksum" + goto end + } + if sha != v.CheckSum { + msg = "checksum not valid" + goto end + } + fn = v.GetFilename() + fmt.Printf("Checksum verified. Installing %s to %s ", fn, cp) + + fp = fmt.Sprintf("%s%s", cp, fn) + err = writeToDestination(fp, body) + if err != nil { + msg = fmt.Sprintf("unable to write to destination %s", fp) + goto end + } + +end: + if msg != "" { + err = fmt.Errorf("%s when downloading class path %s; %w", + msg, + cp, + err) + } + return err +} diff --git a/pkg/lpm/windows.go b/pkg/lpm/windows.go new file mode 100644 index 0000000..cb9b961 --- /dev/null +++ b/pkg/lpm/windows.go @@ -0,0 +1,11 @@ +// +build windows + +package lpm + +import ( + "strings" +) + +func TrimCommandOutput(cmdout string) string { + return strings.TrimRight(strings.Split(string(cmdout), "\n")[0], "\r") +} diff --git a/tests/endtoend/update.yml b/tests/endtoend/update.yml index 80a7c17..ea95d0f 100644 --- a/tests/endtoend/update.yml +++ b/tests/endtoend/update.yml @@ -1,4 +1,4 @@ tests: "can update package json from remote": command: lpm update - stdout: Package manifest updated from https://raw.githubusercontent.com/liquibase/liquibase-package-manager/master/internal/app/packages.json \ No newline at end of file + stdout: Package manifest updated from https://raw.githubusercontent.com/liquibase/liquibase-package-manager/master/embeds/packages.json \ No newline at end of file diff --git a/tests/pkg/lpm/dependencies_test.go b/tests/pkg/lpm/dependencies_test.go new file mode 100644 index 0000000..733fff3 --- /dev/null +++ b/tests/pkg/lpm/dependencies_test.go @@ -0,0 +1,114 @@ +package test + +import ( + "io/ioutil" + "os/exec" + "package-manager/pkg/lpm" + "path/filepath" + "reflect" + "strings" + "testing" +) + +var rootPath []byte +var d lpm.Dependencies +var contextArgs *lpm.ContextArgs + +func init() { + rootPath, _ = exec.Command("git", "rev-parse", "--show-toplevel").Output() + contextArgs = &lpm.ContextArgs{ + Path: "/", + WorkingDir: strings.TrimRight(string(rootPath), "\n") + "/tests/mocks/liquibase.json", + } + d = lpm.Dependencies{} +} + +func TestDependencies_CreateFile(t *testing.T) { + ctx, err := lpm.NewInitializedContext(contextArgs) + if err != nil { + t.Fatalf(err.Error()) + } + err = d.CreateManifestFile(ctx) + if err != nil { + t.Fatalf(err.Error()) + } + _, file := filepath.Split(ctx.GetManifestFilepath()) + if file != "liquibase.json" { + t.Fatalf("Expected %s but got %s", "liquibase.json", file) + } +} + +func TestDependencies_FileExists(t *testing.T) { + ctx, err := lpm.NewInitializedContext(contextArgs) + if err != nil { + t.Fatalf(err.Error()) + } + if d.FileExists(ctx) != true { + t.Fatalf("Unable to verify liquibase.json file exists.") + } +} + +func TestDependencies_Write(t *testing.T) { + ctx, err := lpm.NewInitializedContext(contextArgs) + if err != nil { + t.Fatalf(err.Error()) + } + d.Append(lpm.NewDependency("package", "tag")) + d.Append(lpm.NewDependency("package", "tag2")) + d.Append(lpm.NewDependency("package2", "tag")) + d.Append(lpm.NewDependency("package2", "tag3")) + err = d.WriteManifest(ctx) + if err != nil { + t.Fatalf(err.Error()) + } + + file, _ := ioutil.ReadFile(ctx.GetManifestFilepath()) + content := `{ + "dependencies": [ + { + "package": "tag" + }, + { + "package": "tag2" + }, + { + "package2": "tag" + }, + { + "package2": "tag3" + }, + ] +}` + if string(file) != content { + t.Fatalf("Unable to verify liquibase.json json contents.") + } +} + +func TestDependencies_Read(t *testing.T) { + ctx, err := lpm.NewInitializedContext(contextArgs) + dd := lpm.NewDependencies() + err = dd.ReadManifest(ctx) + if err != nil { + t.Fatalf(err.Error()) + } + // TODO This seems strange. Shouldn't ReadManifest never return + // invalid types? If it can, it should be rewritten. + if reflect.TypeOf(dd.Dependencies[0]) != reflect.TypeOf(lpm.Dependency{}) { + t.Fatalf("Unable to load Dendency from file") + } + for k, v := range dd.Dependencies[0] { + if k != "package" { + t.Fatalf("Invalid Key") + } + if v != "tag" { + t.Fatalf("Invalid Value") + } + } +} + +func TestDependencies_Remove(t *testing.T) { + d.Remove("package") + if len(d.Dependencies) != 0 { + t.Fatalf("Unable to remove dependency") + } +} diff --git a/internal/app/dependencies/dependency_test.go b/tests/pkg/lpm/dependency_test.go similarity index 77% rename from internal/app/dependencies/dependency_test.go rename to tests/pkg/lpm/dependency_test.go index 05c070c..4b815ad 100644 --- a/internal/app/dependencies/dependency_test.go +++ b/tests/pkg/lpm/dependency_test.go @@ -1,16 +1,19 @@ -package dependencies +package test -import "testing" +import ( + "package-manager/pkg/lpm" + "testing" +) func TestDependency_GetName(t *testing.T) { tests := []struct { name string - d Dependency + d lpm.Dependency want string }{ { name: "Can Get Name", - d: Dependency{"package":"tag"}, + d: lpm.NewDependency("package", "tag"), want: "package", }, } @@ -26,12 +29,12 @@ func TestDependency_GetName(t *testing.T) { func TestDependency_GetVersion(t *testing.T) { tests := []struct { name string - d Dependency + d lpm.Dependency want string }{ { name: "Can Get Version", - d: Dependency{"package":"tag"}, + d: lpm.NewDependency("package", "tag"), want: "tag", }, } diff --git a/internal/app/packages/package_test.go b/tests/pkg/lpm/package_test.go similarity index 82% rename from internal/app/packages/package_test.go rename to tests/pkg/lpm/package_test.go index 8577996..3d0b184 100644 --- a/internal/app/packages/package_test.go +++ b/tests/pkg/lpm/package_test.go @@ -1,49 +1,50 @@ -package packages +package test import ( "io/fs" "io/ioutil" "os/exec" + "package-manager/pkg/lpm" "reflect" "strings" "testing" ) -var driver = Package{ +var driver = lpm.Package{ "driver", "driver", - []Version{driverV1, driverV2}, + []lpm.Version{driverV1, driverV2}, } -var extension = Package{ +var extension = lpm.Package{ "extension", "extension", - []Version{extensionV1, extensionV2}, + []lpm.Version{extensionV1, extensionV2}, } func TestPackage_GetLatestVersion(t *testing.T) { type fields struct { Name string Category string - Versions []Version + Versions []lpm.Version } tests := []struct { name string fields fields - want Version + want lpm.Version }{ { name: "Can Get Latest Version", fields: fields{ "test", "driver", - []Version{driverV1, driverV2}, + []lpm.Version{driverV1, driverV2}, }, want: driverV2, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := Package{ + p := lpm.Package{ Name: tt.fields.Name, Category: tt.fields.Category, Versions: tt.fields.Versions, @@ -59,7 +60,7 @@ func TestPackage_GetVersion(t *testing.T) { type fields struct { Name string Category string - Versions []Version + Versions []lpm.Version } type args struct { v string @@ -68,14 +69,14 @@ func TestPackage_GetVersion(t *testing.T) { name string fields fields args args - want Version + want lpm.Version }{ { name: "Can get Specific Version", fields: fields{ "test", "driver", - []Version{driverV1}, + []lpm.Version{driverV1}, }, args: args{"0.0.1"}, want: driverV1, @@ -83,7 +84,7 @@ func TestPackage_GetVersion(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := Package{ + p := lpm.Package{ Name: tt.fields.Name, Category: tt.fields.Category, Versions: tt.fields.Versions, @@ -99,7 +100,7 @@ func TestPackage_GetInstalledVersion(t *testing.T) { type fields struct { Name string Category string - Versions []Version + Versions []lpm.Version } type args struct { files []fs.FileInfo @@ -112,14 +113,14 @@ func TestPackage_GetInstalledVersion(t *testing.T) { name string fields fields args args - want Version + want lpm.Version }{ { name: "Can Get Installed Version", fields: fields{ "test", "driver", - []Version{driverV1, driverV2}, + []lpm.Version{driverV1, driverV2}, }, args: args{files}, want: driverV1, @@ -127,7 +128,7 @@ func TestPackage_GetInstalledVersion(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := Package{ + p := lpm.Package{ Name: tt.fields.Name, Category: tt.fields.Category, Versions: tt.fields.Versions, @@ -137,4 +138,4 @@ func TestPackage_GetInstalledVersion(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/internal/app/packages/packages_test.go b/tests/pkg/lpm/packages_test.go similarity index 88% rename from internal/app/packages/packages_test.go rename to tests/pkg/lpm/packages_test.go index 2754800..f8e825d 100644 --- a/internal/app/packages/packages_test.go +++ b/tests/pkg/lpm/packages_test.go @@ -1,15 +1,16 @@ -package packages +package test import ( "io/fs" "io/ioutil" "os/exec" + "package-manager/pkg/lpm" "reflect" "strings" "testing" ) -var ps = Packages{driver, extension} +var ps = lpm.Packages{driver, extension} func TestPackages_FilterByCategory(t *testing.T) { type args struct { @@ -17,21 +18,21 @@ func TestPackages_FilterByCategory(t *testing.T) { } tests := []struct { name string - ps Packages + ps lpm.Packages args args - want Packages + want lpm.Packages }{ { name: "Can Filter by Driver", - ps: ps, + ps: ps, args: args{"driver"}, - want: []Package{driver}, + want: []lpm.Package{driver}, }, { name: "Can Filter by Extension", - ps: ps, + ps: ps, args: args{"extension"}, - want: []Package{extension}, + want: []lpm.Package{extension}, }, } for _, tt := range tests { @@ -49,9 +50,9 @@ func TestPackages_GetByName(t *testing.T) { } tests := []struct { name string - ps Packages + ps lpm.Packages args args - want Package + want lpm.Package }{ { name: "Can Get Package (Driver) By Name", @@ -86,13 +87,13 @@ func TestPackages_Display(t *testing.T) { tests := []struct { name string - ps Packages + ps lpm.Packages args args want []string }{ { name: "Can Display Installed Formatted Lists", - ps: ps, + ps: ps, args: args{installed}, want: []string{ " Package Category", @@ -102,9 +103,9 @@ func TestPackages_Display(t *testing.T) { }, { name: "Can Display Uninstalled Formatted Lists", - ps: ps, + ps: ps, args: args{files}, - want : []string{ + want: []string{ " Package Category", "├── driver driver", "└── extension extension", @@ -118,4 +119,4 @@ func TestPackages_Display(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/internal/app/packages/version_test.go b/tests/pkg/lpm/version_test.go similarity index 54% rename from internal/app/packages/version_test.go rename to tests/pkg/lpm/version_test.go index 3b88818..193de79 100644 --- a/internal/app/packages/version_test.go +++ b/tests/pkg/lpm/version_test.go @@ -1,37 +1,38 @@ -package packages +package test import ( "io/fs" "io/ioutil" "os" "os/exec" + "package-manager/pkg/lpm" "strings" "testing" ) -var driverV1 = Version{ - "0.0.1", - "tests/mocks/files/driver-0.0.1.txt", - "SHA256", - "", +var driverV1 = lpm.Version{ + Tag: "0.0.1", + Path: "tests/mocks/files/driver-0.0.1.txt", + Algorithm: "SHA256", + CheckSum: "", } -var driverV2 = Version{ - "0.2.0", - "tests/mocks/files/driver-0.2.0.txt", - "SHA1", - "", +var driverV2 = lpm.Version{ + Tag: "0.2.0", + Path: "tests/mocks/files/driver-0.2.0.txt", + Algorithm: "SHA1", + CheckSum: "", } -var extensionV1 = Version{ - "0.0.2", - "tests/mocks/files/extension-0.0.2.txt", - "SHA1", - "", +var extensionV1 = lpm.Version{ + Tag: "0.0.2", + Path: "tests/mocks/files/extension-0.0.2.txt", + Algorithm: "SHA1", + CheckSum: "", } -var extensionV2 = Version{ - "1.0.0", - "tests/mocks/files/extension-1.0.0.txt", - "SHA1", - "", +var extensionV2 = lpm.Version{ + Tag: "1.0.0", + Path: "tests/mocks/files/extension-1.0.0.txt", + Algorithm: "SHA1", + CheckSum: "", } var testPath string @@ -42,39 +43,45 @@ func init() { } func TestVersion_CopyToClassPath(t *testing.T) { + var files []fs.FileInfo + extensionV1.Path = testPath + "/tests/mocks/files/extension-0.0.2.txt" - extensionV1.CopyToClassPath(testPath + "/tests/mocks/classpath/") - var files, _ = ioutil.ReadDir(testPath + "/tests/mocks/classpath/") + err := extensionV1.CopyToClassPath(testPath + "/tests/mocks/classpath/") + if err != nil { + t.Errorf(err.Error()) + goto end + } + + files, err = ioutil.ReadDir(testPath + "/tests/mocks/classpath/") + if err != nil { + t.Errorf(err.Error()) + goto end + } if files[1].Name() != "extension-0.0.2.txt" { t.Fatalf("Expected %s but got %s", "extension-0.0.2.txt", files[0].Name()) } t.Cleanup(func() { - os.Remove(testPath + "/tests/mocks/classpath/extension-0.0.2.txt") + _ = os.Remove(testPath + "/tests/mocks/classpath/extension-0.0.2.txt") }) +end: } func TestVersion_GetFilename(t *testing.T) { - type fields struct { - Tag string - Path string - Algorithm string - CheckSum string - } tests := []struct { - name string - version Version - want string + name string + version lpm.Version + want string }{ { - name: "Can Get Filename", + name: "Can Get Filename", version: driverV1, - want: "driver-0.0.1.txt", + want: "driver-0.0.1.txt", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - v := Version{ + v := lpm.Version{ Tag: tt.version.Tag, Path: tt.version.Path, Algorithm: tt.version.Algorithm, @@ -88,12 +95,6 @@ func TestVersion_GetFilename(t *testing.T) { } func TestVersion_InClassPath(t *testing.T) { - type fields struct { - Tag string - Path string - Algorithm string - CheckSum string - } type args struct { files []fs.FileInfo } @@ -101,21 +102,21 @@ func TestVersion_InClassPath(t *testing.T) { var files, _ = ioutil.ReadDir(testPath + "/tests/mocks/installed") tests := []struct { - name string - version Version - args args - want bool + name string + version lpm.Version + args args + want bool }{ { - name: "Can Determine if Package in Classpath", + name: "Can Determine if Package in Classpath", version: extensionV2, - args: args{files}, - want: true, + args: args{files}, + want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - v := Version{ + v := lpm.Version{ Tag: tt.version.Tag, Path: tt.version.Path, Algorithm: tt.version.Algorithm, @@ -129,26 +130,20 @@ func TestVersion_InClassPath(t *testing.T) { } func TestVersion_PathIsHttp(t *testing.T) { - type fields struct { - Tag string - Path string - Algorithm string - CheckSum string - } tests := []struct { - name string - version Version - want bool + name string + version lpm.Version + want bool }{ { - name: "Can Determine if Path is Http", + name: "Can Determine if Path is Http", version: driverV2, - want: false, + want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - v := Version{ + v := lpm.Version{ Tag: tt.version.Tag, Path: tt.version.Path, Algorithm: tt.version.Algorithm, @@ -162,44 +157,42 @@ func TestVersion_PathIsHttp(t *testing.T) { } func TestVersion_calcChecksum(t *testing.T) { - type fields struct { - Tag string - Path string - Algorithm string - CheckSum string - } type args struct { b []byte } tests := []struct { - name string - version Version - args args - want string + name string + version lpm.Version + args args + want string }{ { - name: "Can Calc Checksum SHA1", + name: "Can Calc Checksum SHA1", version: driverV2, - args: args{[]byte("DriverSHA1")}, - want: "70daefe06dd19c073920273e02cfc712951795ea", + args: args{[]byte("DriverSHA1")}, + want: "70daefe06dd19c073920273e02cfc712951795ea", }, { - name: "Can Calc Checksum SHA256", + name: "Can Calc Checksum SHA256", version: driverV1, - args: args{[]byte("DriverSHA256")}, - want: "94c74ea180983e2ec16451fed233c9f6d3d47572133cae84a0adc7c9fd7e1dd4", + args: args{[]byte("DriverSHA256")}, + want: "94c74ea180983e2ec16451fed233c9f6d3d47572133cae84a0adc7c9fd7e1dd4", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - v := Version{ + v := lpm.Version{ Tag: tt.version.Tag, Path: tt.version.Path, Algorithm: tt.version.Algorithm, CheckSum: tt.version.CheckSum, } - if got := v.calcChecksum(tt.args.b); got != tt.want { - t.Errorf("calcChecksum() = %v, want %v", got, tt.want) + got, err := v.CalcChecksum(tt.args.b) + if err != nil { + t.Errorf(err.Error()) + } + if got != tt.want { + t.Errorf("CalcChecksum() = %v, want %v", got, tt.want) } }) } From 24c9a099fffab966e64849a0451d5b6a0c6b1214 Mon Sep 17 00:00:00 2001 From: Mike Schinkel Date: Thu, 19 Aug 2021 02:37:11 -0400 Subject: [PATCH 2/4] Move to /cmd subdir and wired category and global flags In the initial refactor commit I had not wired up the category and global flags, and after discussion with @mcred decided to move commands into a subdirectory. --- app/lpm/main.go | 3 ++- pkg/lpm/cli_args.go | 8 ++++++-- pkg/lpm/{add_cmd.go => cmd/add.go} | 23 +++++++++++----------- pkg/lpm/{install_cmd.go => cmd/install.go} | 19 +++++++++--------- pkg/lpm/{list_cmd.go => cmd/list.go} | 15 +++++++------- pkg/lpm/cmd/readme.md | 1 + pkg/lpm/{remove_cmd.go => cmd/remove.go} | 21 ++++++++++---------- pkg/lpm/{root_cmd.go => cmd/root.go} | 9 +++++---- pkg/lpm/{search_cmd.go => cmd/search.go} | 9 +++++---- pkg/lpm/{update_cmd.go => cmd/update.go} | 5 +++-- pkg/lpm/context.go | 23 +++++++++++----------- pkg/lpm/file_source.go | 15 ++++++++++++++ pkg/lpm/{darwin.go => os_darwin.go} | 0 pkg/lpm/{windows.go => os_windows.go} | 0 14 files changed, 90 insertions(+), 61 deletions(-) rename pkg/lpm/{add_cmd.go => cmd/add.go} (82%) rename pkg/lpm/{install_cmd.go => cmd/install.go} (80%) rename pkg/lpm/{list_cmd.go => cmd/list.go} (76%) create mode 100644 pkg/lpm/cmd/readme.md rename pkg/lpm/{remove_cmd.go => cmd/remove.go} (80%) rename pkg/lpm/{root_cmd.go => cmd/root.go} (87%) rename pkg/lpm/{search_cmd.go => cmd/search.go} (84%) rename pkg/lpm/{update_cmd.go => cmd/update.go} (93%) create mode 100644 pkg/lpm/file_source.go rename pkg/lpm/{darwin.go => os_darwin.go} (100%) rename pkg/lpm/{windows.go => os_windows.go} (100%) diff --git a/app/lpm/main.go b/app/lpm/main.go index 968ea65..64612b3 100644 --- a/app/lpm/main.go +++ b/app/lpm/main.go @@ -4,11 +4,12 @@ import ( _ "embed" // Embed Import for Package Files "os" "package-manager/pkg/lpm" + "package-manager/pkg/lpm/cmd" ) func main() { - err := lpm.Execute("/") + err := cmd.Execute("/") if err != nil { lpm.ShowUserError(err) os.Exit(1) diff --git a/pkg/lpm/cli_args.go b/pkg/lpm/cli_args.go index adc6fa4..16cddb9 100644 --- a/pkg/lpm/cli_args.go +++ b/pkg/lpm/cli_args.go @@ -5,8 +5,12 @@ type CliArgs struct { Global bool } -var cliArgs CliArgs +var cliArgs *CliArgs func init() { - cliArgs = CliArgs{} + cliArgs = &CliArgs{} +} + +func GetCliArgs() *CliArgs { + return cliArgs } diff --git a/pkg/lpm/add_cmd.go b/pkg/lpm/cmd/add.go similarity index 82% rename from pkg/lpm/add_cmd.go rename to pkg/lpm/cmd/add.go index 1596d5e..0c531ce 100644 --- a/pkg/lpm/add_cmd.go +++ b/pkg/lpm/cmd/add.go @@ -1,14 +1,15 @@ -package lpm +package cmd import ( "fmt" "github.com/spf13/cobra" + "package-manager/pkg/lpm" ) func init() { rootCmd.AddCommand(addCmd) addCmd.Flags().BoolVarP( - &cliArgs.Global, + &lpm.GetCliArgs().Global, "global", "g", false, @@ -21,15 +22,15 @@ var addCmd = &cobra.Command{ Short: "Add Packages", Args: cobra.ArbitraryArgs, Run: func(cmd *cobra.Command, args []string) { - var p Package - var v Version + var p lpm.Package + var v lpm.Version var err error - ctx := ContextFromCobraCommand(cmd) + ctx := lpm.ContextFromCobraCommand(cmd) - dd := NewDependencies() + dd := lpm.NewDependencies() - if ctx.FileSource == LocalFiles { + if ctx.FileSource == lpm.LocalFiles { err = dd.ReadManifest(ctx) } if err != nil { @@ -45,14 +46,14 @@ var addCmd = &cobra.Command{ continue } - dd.Append(NewDependency(p.Name, v.Tag)) + dd.Append(lpm.NewDependency(p.Name, v.Tag)) fmt.Printf("%s successfully installed in classpath.\n", v.GetFilename()) } - if ctx.FileSource == LocalFiles { + if ctx.FileSource == lpm.LocalFiles { //Add package to local manifest if !dd.FileExists(ctx) { err = dd.CreateManifestFile(ctx) @@ -75,10 +76,10 @@ var addCmd = &cobra.Command{ }, } -func maybeAddPackage(ctx *Context, name string) error { +func maybeAddPackage(ctx *lpm.Context, name string) error { var cp string var msg string - var files ClasspathFiles + var files lpm.ClasspathFiles p, v, err := ctx.GetPackageAndVersion(name) diff --git a/pkg/lpm/install_cmd.go b/pkg/lpm/cmd/install.go similarity index 80% rename from pkg/lpm/install_cmd.go rename to pkg/lpm/cmd/install.go index 8e34a30..467cd93 100644 --- a/pkg/lpm/install_cmd.go +++ b/pkg/lpm/cmd/install.go @@ -1,8 +1,9 @@ -package lpm +package cmd import ( "fmt" "github.com/spf13/cobra" + "package-manager/pkg/lpm" ) func init() { @@ -15,20 +16,20 @@ var installCmd = &cobra.Command{ Short: "Install Packages from liquibase.json", Run: func(cmd *cobra.Command, args []string) { - var d Dependencies - var dep Dependency + var d lpm.Dependencies + var dep lpm.Dependency var fn string - var files ClasspathFiles + var files lpm.ClasspathFiles var cp string var err error - ctx := ContextFromCobraCommand(cmd) - if ctx.FileSource == GlobalFiles { + ctx := lpm.ContextFromCobraCommand(cmd) + if ctx.FileSource == lpm.GlobalFiles { ctx.Error("cannot install packages from liquibase.json globally") goto end } - d = NewDependencies() + d = lpm.NewDependencies() err = d.ReadManifest(ctx) if err != nil { @@ -68,7 +69,7 @@ var installCmd = &cobra.Command{ }, } -func maybeInstall(ctx *Context, dep Dependency, cp string, files ClasspathFiles) (fn string, err error) { +func maybeInstall(ctx *lpm.Context, dep lpm.Dependency, cp string, files lpm.ClasspathFiles) (fn string, err error) { p := ctx.GetPackageByName(dep.GetName()) v := p.GetVersion(dep.GetVersion()) @@ -94,7 +95,7 @@ end: } -func CopyToClassPath(v Version, cp string) (err error) { +func CopyToClassPath(v lpm.Version, cp string) (err error) { if v.PathIsHTTP() { err = v.DownloadToClassPath(cp) goto end diff --git a/pkg/lpm/list_cmd.go b/pkg/lpm/cmd/list.go similarity index 76% rename from pkg/lpm/list_cmd.go rename to pkg/lpm/cmd/list.go index f6d4225..7d9c1ef 100644 --- a/pkg/lpm/list_cmd.go +++ b/pkg/lpm/cmd/list.go @@ -1,15 +1,16 @@ -package lpm +package cmd import ( "fmt" "github.com/spf13/cobra" "os" + "package-manager/pkg/lpm" ) func init() { rootCmd.AddCommand(listCmd) // @TODO make an CliArgs struct for `global` and other CLI args - listCmd.Flags().BoolVarP(&cliArgs.Global, "global", "g", false, "list global packages") + listCmd.Flags().BoolVarP(&lpm.GetCliArgs().Global, "global", "g", false, "list global packages") } // listCmd represents the list command @@ -18,16 +19,16 @@ var listCmd = &cobra.Command{ Short: "List Installed Packages", Aliases: []string{"ls"}, Run: func(cmd *cobra.Command, args []string) { - var files ClasspathFiles + var files lpm.ClasspathFiles var cp string var err error - var p Package - var v Version + var p lpm.Package + var v lpm.Version var out string // Collect installed packages - var installed Packages + var installed lpm.Packages - ctx := ContextFromCobraCommand(cmd) + ctx := lpm.ContextFromCobraCommand(cmd) files, cp, err = ctx.GetClasspathFiles() if err != nil { diff --git a/pkg/lpm/cmd/readme.md b/pkg/lpm/cmd/readme.md new file mode 100644 index 0000000..bcab850 --- /dev/null +++ b/pkg/lpm/cmd/readme.md @@ -0,0 +1 @@ +See https://www.educative.io/edpresso/how-to-use-cobra-in-golang for directory conventions. \ No newline at end of file diff --git a/pkg/lpm/remove_cmd.go b/pkg/lpm/cmd/remove.go similarity index 80% rename from pkg/lpm/remove_cmd.go rename to pkg/lpm/cmd/remove.go index 85e3797..009355b 100644 --- a/pkg/lpm/remove_cmd.go +++ b/pkg/lpm/cmd/remove.go @@ -1,15 +1,16 @@ -package lpm +package cmd import ( "fmt" "github.com/spf13/cobra" "os" + "package-manager/pkg/lpm" ) func init() { rootCmd.AddCommand(removeCmd) removeCmd.Flags().BoolVarP( - &cliArgs.Global, + &lpm.GetCliArgs().Global, "global", "g", false, @@ -23,17 +24,17 @@ var removeCmd = &cobra.Command{ Aliases: []string{"rm"}, Args: cobra.ArbitraryArgs, Run: func(cmd *cobra.Command, args []string) { - var files ClasspathFiles + var files lpm.ClasspathFiles var cp string - var p Package - var v Version + var p lpm.Package + var v lpm.Version var err error - ctx := ContextFromCobraCommand(cmd) + ctx := lpm.ContextFromCobraCommand(cmd) - d := NewDependencies() + d := lpm.NewDependencies() - if ctx.FileSource != GlobalFiles { + if ctx.FileSource != lpm.GlobalFiles { err = d.ReadManifest(ctx) } if err != nil { @@ -62,12 +63,12 @@ var removeCmd = &cobra.Command{ continue } fmt.Printf("%s successfully uninstalled from classpath.\n", v.GetFilename()) - if ctx.FileSource != GlobalFiles { + if ctx.FileSource != lpm.GlobalFiles { d.Remove(p.Name) } } - if ctx.FileSource != GlobalFiles { + if ctx.FileSource != lpm.GlobalFiles { err = d.WriteManifest(ctx) } if err != nil { diff --git a/pkg/lpm/root_cmd.go b/pkg/lpm/cmd/root.go similarity index 87% rename from pkg/lpm/root_cmd.go rename to pkg/lpm/cmd/root.go index 66a9955..287a5b6 100644 --- a/pkg/lpm/root_cmd.go +++ b/pkg/lpm/cmd/root.go @@ -1,8 +1,9 @@ -package lpm +package cmd import ( "fmt" "github.com/spf13/cobra" + "package-manager/pkg/lpm" ) const DefaultVersionTemplate = "" + @@ -14,12 +15,12 @@ func init() { //Global params //rootCmd.CompletionOptions.DisableDefaultCmd = true rootCmd.PersistentFlags().StringVar( - &cliArgs.Category, + &lpm.GetCliArgs().Category, "category", "", "extension, driver, or utility") - rootCmd.Version = VersionNumber + rootCmd.Version = lpm.VersionNumber rootCmd.SetVersionTemplate(DefaultVersionTemplate) } @@ -33,7 +34,7 @@ Search for, install, and uninstall liquibase drivers, extensions, and utilities. //Execute main entry point for CLI from root func Execute(path string) error { - ctx := NewContext(path) + ctx := lpm.NewContext(path) err := ctx.Initialize() if err != nil { err = fmt.Errorf("unable to initialize Context when executing root command; %w", diff --git a/pkg/lpm/search_cmd.go b/pkg/lpm/cmd/search.go similarity index 84% rename from pkg/lpm/search_cmd.go rename to pkg/lpm/cmd/search.go index ac5188c..a5d2eba 100644 --- a/pkg/lpm/search_cmd.go +++ b/pkg/lpm/cmd/search.go @@ -1,8 +1,9 @@ -package lpm +package cmd import ( "fmt" "github.com/spf13/cobra" + "package-manager/pkg/lpm" ) func init() { @@ -16,12 +17,12 @@ var searchCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { var name string - var files ClasspathFiles + var files lpm.ClasspathFiles var cp string var err error - var found Packages + var found lpm.Packages - ctx := ContextFromCobraCommand(cmd) + ctx := lpm.ContextFromCobraCommand(cmd) files, cp, err = ctx.GetClasspathFiles() if err != nil { diff --git a/pkg/lpm/update_cmd.go b/pkg/lpm/cmd/update.go similarity index 93% rename from pkg/lpm/update_cmd.go rename to pkg/lpm/cmd/update.go index e9195a3..d19d7e1 100644 --- a/pkg/lpm/update_cmd.go +++ b/pkg/lpm/cmd/update.go @@ -1,8 +1,9 @@ -package lpm +package cmd import ( "fmt" "github.com/spf13/cobra" + "package-manager/pkg/lpm" ) var ( @@ -28,7 +29,7 @@ var updateCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { var err error - ctx := ContextFromCobraCommand(cmd) + ctx := lpm.ContextFromCobraCommand(cmd) err = ctx.LoadPackages(path) if err != nil { diff --git a/pkg/lpm/context.go b/pkg/lpm/context.go index 81f3b69..d710e28 100644 --- a/pkg/lpm/context.go +++ b/pkg/lpm/context.go @@ -13,11 +13,6 @@ import ( "strings" ) -type FileSource byte - -const GlobalFiles FileSource = 'g' -const LocalFiles FileSource = 'l' - const PackageFilePerms = 0664 const DirectoryPerms = 0775 const DefaultPackageFile = "packages.json" @@ -42,16 +37,20 @@ type Context struct { WorkingDir string - manifestFilepath string - FileSource FileSource - Category string - HomeDir string - Path string - //PackageFile to use //ex: package.json PackageFile string + FileSource FileSource + + Category string + + HomeDir string + + Path string + + manifestFilepath string + // bytes for storing JSON read from file packageBytes []byte @@ -94,6 +93,8 @@ func ContextFromCobraCommand(cmd *cobra.Command) *Context { func NewContext(path string) *Context { return &Context{ Context: context.Background(), + Category: GetCliArgs().Category, + FileSource: GetFileSource(), PackageFile: DefaultPackageFile, classpathFiles: make(ClasspathFiles, 0), errors: make([]error, 0), diff --git a/pkg/lpm/file_source.go b/pkg/lpm/file_source.go new file mode 100644 index 0000000..9096015 --- /dev/null +++ b/pkg/lpm/file_source.go @@ -0,0 +1,15 @@ +package lpm + +type FileSource byte + +const GlobalFiles FileSource = 'g' +const LocalFiles FileSource = 'l' + +var fileSources = map[bool]FileSource{ + true: GlobalFiles, + false: LocalFiles, +} + +func GetFileSource() (fs FileSource) { + return fileSources[cliArgs.Global] +} diff --git a/pkg/lpm/darwin.go b/pkg/lpm/os_darwin.go similarity index 100% rename from pkg/lpm/darwin.go rename to pkg/lpm/os_darwin.go diff --git a/pkg/lpm/windows.go b/pkg/lpm/os_windows.go similarity index 100% rename from pkg/lpm/windows.go rename to pkg/lpm/os_windows.go From 4e9dc02d9b56b990d106676debd9bfa7c58814aa Mon Sep 17 00:00:00 2001 From: Mike Schinkel Date: Thu, 19 Aug 2021 05:51:54 -0400 Subject: [PATCH 3/4] Add one missing error handler, renamed method Added a missing error handled in lpm.Initialize() and renamed lpm.ContextFromCobraCommand() to lpm.GetContextFromCommand(). --- app/populator/module.go | 1 + pkg/lpm/cmd/add.go | 2 +- pkg/lpm/cmd/install.go | 2 +- pkg/lpm/cmd/list.go | 2 +- pkg/lpm/cmd/remove.go | 2 +- pkg/lpm/cmd/search.go | 2 +- pkg/lpm/cmd/update.go | 2 +- pkg/lpm/context.go | 5 ++++- 8 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/populator/module.go b/app/populator/module.go index d664d26..fefdc7e 100644 --- a/app/populator/module.go +++ b/app/populator/module.go @@ -174,6 +174,7 @@ func (m Module) getNewVersion(p lpm.Package, tag string) (ver lpm.Version, err e ver.Algorithm = lpm.Sha1Algorithm ver.CheckSum, err = m.getCheckSum(tag, ver.Algorithm) + return ver, err } diff --git a/pkg/lpm/cmd/add.go b/pkg/lpm/cmd/add.go index 0c531ce..e3a213a 100644 --- a/pkg/lpm/cmd/add.go +++ b/pkg/lpm/cmd/add.go @@ -26,7 +26,7 @@ var addCmd = &cobra.Command{ var v lpm.Version var err error - ctx := lpm.ContextFromCobraCommand(cmd) + ctx := lpm.GetContextFromCommand(cmd) dd := lpm.NewDependencies() diff --git a/pkg/lpm/cmd/install.go b/pkg/lpm/cmd/install.go index 467cd93..62313fc 100644 --- a/pkg/lpm/cmd/install.go +++ b/pkg/lpm/cmd/install.go @@ -23,7 +23,7 @@ var installCmd = &cobra.Command{ var cp string var err error - ctx := lpm.ContextFromCobraCommand(cmd) + ctx := lpm.GetContextFromCommand(cmd) if ctx.FileSource == lpm.GlobalFiles { ctx.Error("cannot install packages from liquibase.json globally") goto end diff --git a/pkg/lpm/cmd/list.go b/pkg/lpm/cmd/list.go index 7d9c1ef..9464108 100644 --- a/pkg/lpm/cmd/list.go +++ b/pkg/lpm/cmd/list.go @@ -28,7 +28,7 @@ var listCmd = &cobra.Command{ // Collect installed packages var installed lpm.Packages - ctx := lpm.ContextFromCobraCommand(cmd) + ctx := lpm.GetContextFromCommand(cmd) files, cp, err = ctx.GetClasspathFiles() if err != nil { diff --git a/pkg/lpm/cmd/remove.go b/pkg/lpm/cmd/remove.go index 009355b..d972697 100644 --- a/pkg/lpm/cmd/remove.go +++ b/pkg/lpm/cmd/remove.go @@ -30,7 +30,7 @@ var removeCmd = &cobra.Command{ var v lpm.Version var err error - ctx := lpm.ContextFromCobraCommand(cmd) + ctx := lpm.GetContextFromCommand(cmd) d := lpm.NewDependencies() diff --git a/pkg/lpm/cmd/search.go b/pkg/lpm/cmd/search.go index a5d2eba..45add31 100644 --- a/pkg/lpm/cmd/search.go +++ b/pkg/lpm/cmd/search.go @@ -22,7 +22,7 @@ var searchCmd = &cobra.Command{ var err error var found lpm.Packages - ctx := lpm.ContextFromCobraCommand(cmd) + ctx := lpm.GetContextFromCommand(cmd) files, cp, err = ctx.GetClasspathFiles() if err != nil { diff --git a/pkg/lpm/cmd/update.go b/pkg/lpm/cmd/update.go index d19d7e1..642a145 100644 --- a/pkg/lpm/cmd/update.go +++ b/pkg/lpm/cmd/update.go @@ -29,7 +29,7 @@ var updateCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { var err error - ctx := lpm.ContextFromCobraCommand(cmd) + ctx := lpm.GetContextFromCommand(cmd) err = ctx.LoadPackages(path) if err != nil { diff --git a/pkg/lpm/context.go b/pkg/lpm/context.go index d710e28..7eb3dcc 100644 --- a/pkg/lpm/context.go +++ b/pkg/lpm/context.go @@ -80,7 +80,7 @@ func (ctx *Context) PrintJavaOptsHelper() { fmt.Printf("\n\texport JAVA_OPTS=\"%s\"", jo) } -func ContextFromCobraCommand(cmd *cobra.Command) *Context { +func GetContextFromCommand(cmd *cobra.Command) *Context { ctx, ok := cmd.Context().(*Context) if !ok { // When !ok it is a programming error. So okay to just exit @@ -202,6 +202,9 @@ func (ctx *Context) Initialize() (err error) { //@TODO Should this be loaded from Global Package Filepath, or // loaded from Local? err = ctx.LoadPackages(ctx.GetGlobalPackageFilepath()) + if err != nil { + goto end + } // Set global vs local classpath ctx.FileSource = LocalFiles From 4404adf72558a24de859f940bf8a8a2442255f95 Mon Sep 17 00:00:00 2001 From: Mike Schinkel Date: Mon, 23 Aug 2021 21:29:56 -0400 Subject: [PATCH 4/4] Make some Context properties private Changed WorkingDir, Category, HomeDir and Path to all be private as they are not used anywhere outside of Context and I didn't want Context to less cohesive than it needs to be. Also renamed GetFilteredPackages() to GetCategoryFilteredPackages() to be more accurate. --- pkg/lpm/context.go | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/pkg/lpm/context.go b/pkg/lpm/context.go index 7eb3dcc..79d8298 100644 --- a/pkg/lpm/context.go +++ b/pkg/lpm/context.go @@ -28,6 +28,7 @@ func (files ClasspathFiles) VersionExists(v Version) bool { break } } + return r } @@ -35,19 +36,19 @@ func (files ClasspathFiles) VersionExists(v Version) bool { type Context struct { context.Context - WorkingDir string - //PackageFile to use //ex: package.json PackageFile string FileSource FileSource - Category string + workdir string + + category string - HomeDir string + homedir string - Path string + path string manifestFilepath string @@ -73,7 +74,7 @@ func (ctx *Context) PrintJavaOptsHelper() { jo := fmt.Sprintf("-cp liquibase_libs%s*:%s*:%sliquibase.jar", string(os.PathSeparator), ctx.GetGlobalClasspath(), - ctx.HomeDir) + ctx.homedir) fmt.Println() fmt.Println("---------- IMPORTANT ----------") fmt.Println("Run the following JAVA_OPTS command:") @@ -93,12 +94,12 @@ func GetContextFromCommand(cmd *cobra.Command) *Context { func NewContext(path string) *Context { return &Context{ Context: context.Background(), - Category: GetCliArgs().Category, + category: GetCliArgs().Category, FileSource: GetFileSource(), PackageFile: DefaultPackageFile, classpathFiles: make(ClasspathFiles, 0), errors: make([]error, 0), - Path: path, + path: path, } } @@ -110,10 +111,10 @@ type ContextArgs struct { // NewInitializedContext creates a new context and initializes it, // with optional arguments and returning an error on failure func NewInitializedContext(args *ContextArgs) (ctx *Context, err error) { - ctx = NewContext(ctx.Path) + ctx = NewContext(ctx.path) err = ctx.Initialize() if args.WorkingDir != "" { - ctx.WorkingDir = args.WorkingDir + ctx.workdir = args.WorkingDir } return ctx, err } @@ -125,7 +126,7 @@ func (ctx *Context) GetManifestFilepath() string { } ctx.manifestFilepath = fmt.Sprintf("%s%sliquibase.json", string(os.PathSeparator), - ctx.WorkingDir) + ctx.workdir) end: return ctx.manifestFilepath } @@ -157,8 +158,8 @@ func (ctx *Context) GetClassPackageFilepath(path string) string { func (ctx *Context) GetGlobalClasspath() string { return fmt.Sprintf("%slib%s", - ctx.HomeDir, - ctx.Path) + ctx.homedir, + ctx.path) } func (ctx *Context) maybeCreateInitialPackageFile() (err error) { @@ -180,13 +181,13 @@ end: // Initialize ensure the Context object has required values func (ctx *Context) Initialize() (err error) { - ctx.HomeDir, err = GetHomeDir() + ctx.homedir, err = GetHomeDir() if err != nil { err = fmt.Errorf("unable to get home directory for lbm; %w", err) goto end } - ctx.WorkingDir, err = GetWorkingDir() + ctx.workdir, err = GetWorkingDir() if err != nil { err = fmt.Errorf("unable to get current working directory for lbm; %w", err) goto end @@ -412,9 +413,9 @@ func (ctx *Context) GetPackages() (p Packages) { return ctx.packages } -//GetFilteredPackages returns previously loaded packages filtered by category -func (ctx *Context) GetFilteredPackages() (p Packages) { - return ctx.GetPackages().FilterByCategory(ctx.Category) +//GetCategoryFilteredPackages returns previously loaded packages filtered by category +func (ctx *Context) GetCategoryFilteredPackages() (p Packages) { + return ctx.GetPackages().FilterByCategory(ctx.category) } //WritePackages write packages back to file