Find similar packages here.
- `, pkg.PackageName, pkg.PackageName, pkg.Description, strings.Join(pkg.Maintainers, ", "), pkg.Version, pkg.PackageName, pkg.PackageName, consts.PACSCRIPT_FILE_EXTENSION, pkg.PackageName), + `, pkg.PackageName, pkg.PackageName, pkg.Description, strings.Join(pkg.Maintainers, ", "), pkg.Version, pkg.PackageName, pkg.PackageName, consts.SRCINFO_FILE_EXTENSION, pkg.PackageName), } }, ) diff --git a/server/types/equals.go b/server/types/equals.go new file mode 100644 index 00000000..a64bf68e --- /dev/null +++ b/server/types/equals.go @@ -0,0 +1,5 @@ +package types + +type Equaller interface { + Equals(other Equaller) bool +} diff --git a/server/types/pac/parser/last_updated.go b/server/types/pac/parser/last_updated.go index dfd9e3b8..adf1bb8d 100644 --- a/server/types/pac/parser/last_updated.go +++ b/server/types/pac/parser/last_updated.go @@ -10,7 +10,6 @@ import ( "github.com/joomcode/errorx" "pacstall.dev/webserver/config" - "pacstall.dev/webserver/consts" "pacstall.dev/webserver/log" "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" @@ -31,8 +30,8 @@ func getPackageLastUpdatedTuples() ([]packageLastUpdatedTuple, error) { programsPath := path.Join(wordingDirectoryAbsolute, config.GitClonePath) script := fmt.Sprintf(` cd %v - for i in ./packages/*/*.%s; do echo $i; git log -1 --pretty=\"%%at\" $i; done - `, programsPath, consts.PACSCRIPT_FILE_EXTENSION) + for i in ./packages/*/*.pacscript; do echo $i; git log -1 --pretty=\"%%at\" $i; done + `, programsPath) outputBytes, err := pacsh.ExecBash(programsPath, "last_updated.sh", []byte(script)) if err != nil { @@ -52,7 +51,7 @@ func getPackageLastUpdatedTuples() ([]packageLastUpdatedTuple, error) { lastUpdatedString = lastUpdatedString[1 : len(lastUpdatedString)-1] packageNameWithExtension := path.Base(packagePath) - packageName := strings.TrimSuffix(packageNameWithExtension, "."+consts.PACSCRIPT_FILE_EXTENSION) + packageName := strings.TrimSuffix(packageNameWithExtension, ".pacscript") if packageName == "" || strings.HasPrefix(packageName, "-") { return nil, errorx.IllegalState.New("failed to parse package name from package path '%v'", packagePath) diff --git a/server/types/pac/parser/pacscript.go b/server/types/pac/parser/pacscript.go index 616ff5b0..82c3032b 100644 --- a/server/types/pac/parser/pacscript.go +++ b/server/types/pac/parser/pacscript.go @@ -1,66 +1,18 @@ package parser import ( - "fmt" - "strings" - "pacstall.dev/webserver/types/array" "pacstall.dev/webserver/types/pac" - "pacstall.dev/webserver/types/pac/parser/pacsh" ) -func removeDebianCheck(script string) string { - if !strings.Contains(script, "/etc/os-release)\" == \"debian\"") { - return script - } - - if strings.Index(script, "if") != 0 { - return script - } - - debianCheckEnd := strings.Index(script, "fi") - if debianCheckEnd == -1 { - return script - } - - return script[debianCheckEnd+len("fi"):] -} - -func buildCustomFormatScript(header []byte) []byte { - // TODO: remove after `preinstall` gets implemented - script := removeDebianCheck(string(header)) + "\n" - - script += "echo ''\n" - for _, bashName := range pacsh.PacscriptVars { - // If the variable is a function, then we replace it with the output of the function - script += fmt.Sprintf(` -if [[ "$(declare -F -p %v)" ]]; then - %v=$(%v) -fi -`, bashName, bashName, bashName) - } - - script = script + "\njo -p -- " - - for _, bashName := range pacsh.PacscriptVars { - script += fmt.Sprintf("-s %v=\"$%v\" ", bashName, bashName) - } - - for _, bashName := range pacsh.PacscriptArrays { - script += fmt.Sprintf("%v=$(jo -a ${%v[@]}) ", bashName, bashName) - } - - return []byte(script) -} - func computeRequiredBy(script *pac.Script, scripts []*pac.Script) { - pickBeforeColon := func(it *array.Iterator[string]) string { - return strings.Split(it.Value, ": ")[0] + pickName := func(it *array.Iterator[pac.ArchDistroString]) string { + return it.Value.Value } script.RequiredBy = make([]string, 0) for _, otherScript := range scripts { - otherScriptDependencies := array.Map(otherScript.PacstallDependencies, pickBeforeColon) + otherScriptDependencies := array.SwitchMap(otherScript.PacstallDependencies, pickName) if array.Contains(otherScriptDependencies, array.Is(script.PackageName)) { script.RequiredBy = append(script.RequiredBy, otherScript.PackageName) } diff --git a/server/types/pac/parser/pacsh/git_version.go b/server/types/pac/parser/pacsh/git_version.go new file mode 100644 index 00000000..c40d00f9 --- /dev/null +++ b/server/types/pac/parser/pacsh/git_version.go @@ -0,0 +1,27 @@ +package pacsh + +import ( + "github.com/joomcode/errorx" + "pacstall.dev/webserver/types/array" + "pacstall.dev/webserver/types/pac" + "pacstall.dev/webserver/types/pac/parser/pacsh/internal" +) + +func ApplyGitVersion(p *pac.Script) error { + sources := internal.NewGitSources(array.SwitchMap(p.Source, func(it *array.Iterator[pac.ArchDistroString]) string { + return it.Value.Value + })) + + version, err := sources.ParseGitPackageVersion() + if err != nil { + return errorx.Decorate(err, "failed to parse git version for package '%s'", p.PackageName) + } + + if p.Epoch != "" { + p.Version = p.Epoch + ":" + version + "-" + p.Release + } else { + p.Version = version + "-" + p.Release + } + + return nil +} diff --git a/server/types/pac/parser/pacsh/parse_pac_output.go b/server/types/pac/parser/pacsh/parse_pac_output.go index d6d590c8..21fd336f 100644 --- a/server/types/pac/parser/pacsh/parse_pac_output.go +++ b/server/types/pac/parser/pacsh/parse_pac_output.go @@ -1,178 +1,19 @@ package pacsh import ( - "encoding/json" - "strings" - - "github.com/joomcode/errorx" - "pacstall.dev/webserver/types/array" + "github.com/pacstall/go-srcinfo" "pacstall.dev/webserver/types/pac" - "pacstall.dev/webserver/types/pac/parser/pacsh/internal" ) -var ParsePacOutput = parseOutput -var PacscriptVars []string = []string{"pkgname", "pkgdesc", "gives", "hash", "pkgver"} -var PacscriptArrays []string = []string{"source", "arch", "maintainer", "depends", "conflicts", "breaks", "replaces", "makedepends", "optdepends", "pacdeps", "patch", "ppa", "repology"} - -type Stringable struct { - Data string -} - -func (s *Stringable) UnmarshalJSON(data []byte) error { - s.Data = string(data) - s.Data = strings.ReplaceAll(s.Data, "\"", "") - return nil -} - -func (s *Stringable) String() string { - return s.Data -} - -type StringableArrayWithComments struct { - Data []Stringable -} - -func (s StringableArrayWithComments) toStringArray() []string { - return array.SwitchMapPtr(s.Data, func(it *array.PtrIterator[Stringable]) string { - val := *it.Value - return val.String() - }) -} - -func (s *StringableArrayWithComments) UnmarshalJSON(data []byte) error { - err := json.Unmarshal(data, &s.Data) - if err != nil { - return err - } - - out := make([]string, 0) - item := "" - hasComments := false - for _, word := range s.Data { - strWord := word.String() - wordHasComment := strings.HasSuffix(strWord, ":") - - if wordHasComment { - hasComments = true - if item != "" { - out = append(out, strings.TrimSpace(item)) - } - item = strWord - } else if !hasComments { - out = append(out, strWord) - } else { - item += " " + strWord - } - - } - - if item != "" { - out = append(out, strings.TrimSpace(item)) - } - - s.Data = array.SwitchMap(out, func(it *array.Iterator[string]) Stringable { - return Stringable{it.Value} - }) - - return nil -} - -type pacscriptJsonStructure struct { - Pkgname string `json:"pkgname"` - Pkgdesc string `json:"pkgdesc"` - Gives *string `json:"gives"` - Hash *string `json:"hash"` - Pkgver *string `json:"pkgver"` - Source StringableArrayWithComments `json:"source"` - Maintainer StringableArrayWithComments `json:"maintainer"` - Depends StringableArrayWithComments `json:"depends"` - Conflicts StringableArrayWithComments `json:"conflicts"` - Arch StringableArrayWithComments `json:"arch"` - Breaks StringableArrayWithComments `json:"breaks"` - Replaces StringableArrayWithComments `json:"replaces"` - Makedepends StringableArrayWithComments `json:"makedepends"` - Optdepends StringableArrayWithComments `json:"optdepends"` - Pacdeps StringableArrayWithComments `json:"pacdeps"` - Patch StringableArrayWithComments `json:"patch"` - Ppa StringableArrayWithComments `json:"ppa"` - Repology StringableArrayWithComments `json:"repology"` -} - -var _GIT_VERSION = "git" -var _EMPTI_STR = "" - -func parseOutput(data []byte) (out pac.Script, err error) { - // remove prefixes if any - runeIndex := strings.IndexRune(string(data), '{') - if runeIndex >= 0 { - str := string(data) - str = str[runeIndex:] - data = []byte(str) - } - - var parsedContent pacscriptJsonStructure - err = json.Unmarshal(data, &parsedContent) +func ParsePacOutput(data []byte) (*pac.Script, error) { + out, err := srcinfo.Parse(string(data)) if err != nil { - return out, errorx.IllegalFormat.Wrap(err, "failed to deserialize json content '%v'", string(data)) - } - if parsedContent.Pkgver == nil { - if strings.HasSuffix(parsedContent.Pkgname, "-git") { - parsedContent.Pkgver = &_GIT_VERSION - } else { - return out, errorx.IllegalArgument.New("expected version to be non-empty") - } + return nil, err } - if parsedContent.Gives == nil { - parsedContent.Gives = &_EMPTI_STR - } + ps := pac.FromSrcInfo(*out) - out = pac.Script{ - PackageName: parsedContent.Pkgname, - Maintainers: parseMaintainers(parsedContent.Maintainer.toStringArray()), - Description: parsedContent.Pkgdesc, - Source: parsedContent.Source.toStringArray(), - Gives: *parsedContent.Gives, - Hash: parsedContent.Hash, - Version: *parsedContent.Pkgver, - RuntimeDependencies: parsedContent.Depends.toStringArray(), - BuildDependencies: parsedContent.Makedepends.toStringArray(), - OptionalDependencies: parsedContent.Optdepends.toStringArray(), - Conflicts: parsedContent.Conflicts.toStringArray(), - Replaces: parsedContent.Replaces.toStringArray(), - Breaks: parsedContent.Breaks.toStringArray(), - PacstallDependencies: parsedContent.Pacdeps.toStringArray(), - PPA: parsedContent.Ppa.toStringArray(), - Patch: parsedContent.Patch.toStringArray(), - RequiredBy: make([]string, 0), - Repology: parsedContent.Repology.toStringArray(), - LatestVersion: nil, - UpdateStatus: pac.UpdateStatus.Unknown, - } - - if pkgver, err := internal.NewGitSources(out.Source).ParseGitPackageVersion(); err == nil && pkgver != "" { - out.Version = pkgver - } - - if out.Hash != nil && len(*out.Hash) == 0 { - out.Hash = nil - } - - out.PrettyName = getPrettyName(out) - - return -} - -func parseMaintainers(maintainers []string) []string { - maintainersSplitByLA := strings.Split(strings.Join(maintainers, " "), ">") - - out := []string{} - for _, maintainer := range maintainersSplitByLA { - if len(maintainer) == 0 { - continue - } - out = append(out, strings.TrimSpace(maintainer)+">") - } + ps.PrettyName = getPrettyName(ps) - return out + return ps, nil } diff --git a/server/types/pac/parser/pacsh/pretty-name.go b/server/types/pac/parser/pacsh/pretty-name.go index 1bed2dd1..d1e12030 100644 --- a/server/types/pac/parser/pacsh/pretty-name.go +++ b/server/types/pac/parser/pacsh/pretty-name.go @@ -3,25 +3,19 @@ package pacsh import ( "strings" + "pacstall.dev/webserver/types" "pacstall.dev/webserver/types/pac" ) -var pacTypes = map[string]string{ - "-deb": "Debian Native", - "-git": "Source Code", - "-bin": "Precompiled", - "-app": "AppImage", -} - -func getPrettyName(p pac.Script) string { +func getPrettyName(p *pac.Script) string { name := "" if name == "" { name = p.PackageName } - for suffix := range pacTypes { - if strings.HasSuffix(name, suffix) { + for suffix := range types.PackageTypeSuffixToPackageTypeName { + if strings.HasSuffix(name, string(suffix)) { name = name[0 : len(name)-len(suffix)] } } diff --git a/server/types/pac/parser/pacsh/temp_dir_test.go b/server/types/pac/parser/pacsh/temp_dir_test.go deleted file mode 100644 index ce00df49..00000000 --- a/server/types/pac/parser/pacsh/temp_dir_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package pacsh - -import ( - "os" - "strings" - "testing" - "time" - - "pacstall.dev/webserver/types/array" -) - -func cleanup() { - statFile = os.Stat - removeAll = os.RemoveAll - makeDir = os.Mkdir - removeFile = os.Remove -} - -type testFileInfo struct { - name string - size int64 - mode os.FileMode - modTime time.Time - isDir bool -} - -func (t testFileInfo) Name() string { - return t.name -} - -func (t testFileInfo) Size() int64 { - return t.size -} - -func (t testFileInfo) Mode() os.FileMode { - return t.mode -} - -func (t testFileInfo) ModTime() time.Time { - return t.modTime -} - -func (t testFileInfo) IsDir() bool { - return t.isDir -} - -func (t testFileInfo) Sys() interface{} { - return nil -} - -func Test_CreateTempDirectory_NoExisting(t *testing.T) { - defer cleanup() - - makeDirCalled := 0 - removeDirCalled := 0 - statFileCalled := 0 - - statFile = func(path string) (os.FileInfo, error) { - if statFileCalled == 1 { - statFileCalled += 1 - return nil, os.ErrNotExist - } - - statFileCalled += 1 - name, _ := array.Last(strings.Split(path, "/")) - return testFileInfo{ - name: name, - size: 0, - mode: 0777, - modTime: time.Now(), - isDir: true, - }, nil - } - - makeDir = func(path string, perm os.FileMode) error { - makeDirCalled += 1 - return nil - } - - removeAll = func(path string) error { - removeDirCalled += 1 - return nil - } - - err := CreateTempDirectory("/tmp") - - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - if removeDirCalled != 1 { - t.Error("expected removeAll to be called 1 time but was called", removeDirCalled) - } - - if makeDirCalled != 1 { - t.Error("expected makeDir to be called 1 time but was called", makeDirCalled) - } - - if statFileCalled != 2 { - t.Error("expected statFile to be called 2 times but was called", statFileCalled) - } -} - -func Test_CreateTempDirectory_AlreadyExisting(t *testing.T) { - defer cleanup() - - makeDirCalled := 0 - removeDirCalled := 0 - statFileCalled := 0 - - statFile = func(path string) (os.FileInfo, error) { - if statFileCalled == 0 { - statFileCalled += 1 - return nil, os.ErrNotExist - } - - statFileCalled += 1 - name, _ := array.Last(strings.Split(path, "/")) - return testFileInfo{ - name: name, - size: 0, - mode: 0777, - modTime: time.Now(), - isDir: true, - }, nil - } - - makeDir = func(path string, perm os.FileMode) error { - makeDirCalled += 1 - return nil - } - - removeAll = func(path string) error { - removeDirCalled += 1 - return nil - } - - err := CreateTempDirectory("/tmp") - - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - if removeDirCalled != 0 { - t.Error("expected removeAll to be called 0 times but was called", removeDirCalled) - } - - if makeDirCalled != 1 { - t.Error("expected makeDir to be called 1 time but was called", makeDirCalled) - } - - if statFileCalled != 1 { - t.Error("expected statFile to be called 2 times but was called", statFileCalled) - } -} diff --git a/server/types/pac/parser/parallelism/channels/exhaust.go b/server/types/pac/parser/parallelism/channels/exhaust.go new file mode 100644 index 00000000..f9d63c09 --- /dev/null +++ b/server/types/pac/parser/parallelism/channels/exhaust.go @@ -0,0 +1,6 @@ +package channels + +func Exhaust[T any](in <-chan T) { + for range in { + } +} diff --git a/server/types/pac/parser/parse.go b/server/types/pac/parser/parse.go index e93a226d..26eca283 100644 --- a/server/types/pac/parser/parse.go +++ b/server/types/pac/parser/parse.go @@ -1,7 +1,6 @@ package parser import ( - "fmt" "os" "path" "strings" @@ -22,6 +21,7 @@ import ( ) const PACKAGE_LIST_FILE_NAME = "./packagelist" +const MAX_GIT_VERSION_CONCURRENCY = 5 func ParseAll() error { if err := git.RefreshPrograms(config.GitClonePath, config.GitURL, config.PacstallPrograms.Branch); err != nil { @@ -38,6 +38,8 @@ func ParseAll() error { return errorx.Decorate(err, "failed to parse pacscripts") } + log.Info("pacscript parsing done. computing dependency graph") + for _, script := range loadedPacscripts { computeRequiredBy(script, loadedPacscripts) } @@ -46,12 +48,25 @@ func ParseAll() error { return s1.PackageName < s2.PackageName }) + log.Info("dependency graph done. setting up git updated-at dates") + if err := setLastUpdatedAt(loadedPacscripts); err != nil { return errorx.Decorate(err, "failed to set last updated at") } + log.Info("updated-at dates done. fetching git versions") + + gitPacscripts := array.Filter(loadedPacscripts, func(it *array.Iterator[*pac.Script]) bool { + return strings.HasSuffix(it.Value.PackageName, string(types.PACKAGE_TYPE_SUFFIX_GIT)) + }) + + channels.Exhaust(batch.Run(MAX_GIT_VERSION_CONCURRENCY, gitPacscripts, func(p *pac.Script) (interface{}, error) { + err := pacsh.ApplyGitVersion(p) + return nil, err + })) + pacstore.Update(loadedPacscripts) - log.Info("successfully parsed %v (%v / %v) packages", types.Percent(float64(len(loadedPacscripts))/float64(len(pkgList))), len(loadedPacscripts), len(pkgList)) + log.Info("successfully loaded %v (%v / %v) packages", types.Percent(float64(len(loadedPacscripts))/float64(len(pkgList))), len(loadedPacscripts), len(pkgList)) return nil } @@ -81,45 +96,37 @@ func parsePacscriptFiles(names []string) ([]*pac.Script, error) { out, err := ParsePacscriptFile(config.GitClonePath, pacName) if config.Repology.Enabled { - if err := repology.Sync(&out); err != nil { - log.Debug("failed to sync %v with repology. Error: %v", pacName, err) + if err := repology.Sync(out); err != nil { + log.Debug("failed to sync %v with repology. Error: %+v", pacName, err) } } - return &out, err + return out, err }) return channels.ToSlice(outChan), nil } func readPacscriptFile(rootDir, name string) (scriptBytes []byte, fileName string, err error) { - fileName = fmt.Sprintf("%s.%s", name, consts.PACSCRIPT_FILE_EXTENSION) - scriptPath := path.Join(rootDir, "packages", name, fileName) + scriptPath := path.Join(rootDir, "packages", name, consts.SRCINFO_FILE_EXTENSION) scriptBytes, err = os.ReadFile(scriptPath) if err != nil { return nil, "", errorx.Decorate(err, "failed to read file '%v'", scriptPath) } - return scriptBytes, fileName, nil + return scriptBytes, consts.SRCINFO_FILE_EXTENSION, nil } -func ParsePacscriptFile(programsDirPath, name string) (pac.Script, error) { - pacshell, filename, err := readPacscriptFile(programsDirPath, name) - if err != nil { - return pac.Script{}, errorx.Decorate(err, "failed to read pacscript '%v'", name) - } - - pacshell = buildCustomFormatScript(pacshell) - - stdout, err := pacsh.ExecBash(config.TempDir, filename, pacshell) +func ParsePacscriptFile(programsDirPath, name string) (*pac.Script, error) { + srcInfoData, _, err := readPacscriptFile(programsDirPath, name) if err != nil { - return pac.Script{}, errorx.Decorate(err, "failed to execute pacscript '%v'", name) + return nil, errorx.Decorate(err, "failed to read pacscript '%v'", name) } - pacscript, err := pacsh.ParsePacOutput(stdout) + pacscript, err := pacsh.ParsePacOutput(srcInfoData) if err != nil { - return pac.Script{}, errorx.Decorate(err, "failed to parse pacscript '%v'", name) + return nil, errorx.Decorate(err, "failed to parse pacscript '%v'", name) } return pacscript, nil diff --git a/server/types/pac/parser/parse_test.go b/server/types/pac/parser/parse_test.go index 89fde1c4..984664ce 100644 --- a/server/types/pac/parser/parse_test.go +++ b/server/types/pac/parser/parse_test.go @@ -7,6 +7,7 @@ import ( "path" "testing" + "pacstall.dev/webserver/types" "pacstall.dev/webserver/types/pac" "pacstall.dev/webserver/types/pac/parser" "pacstall.dev/webserver/types/pac/parser/pacsh" @@ -29,7 +30,24 @@ func assertEquals(t *testing.T, what string, expected interface{}, actual interf } } -func assertArrayEquals(t *testing.T, what string, expected []string, actual []string) { +func assertArrayEquals[T types.Equaller](t *testing.T, what string, expected []T, actual []T) { + if len(actual) == len(expected) && len(actual) == 0 { + return + } + + if len(actual) != len(expected) { + t.Errorf("pacscript.%v expected len '%v', got len '%v' (expected '%#v', got '%#v')", what, len(expected), len(actual), expected, actual) + return + } + + for idx := range expected { + if !expected[idx].Equals(actual[idx]) { + t.Errorf("pacscript.%v[%v] expected '%#v', got '%#v'", what, idx, expected, actual) + } + } +} + +func assertStringArrayEquals(t *testing.T, what string, expected []string, actual []string) { if len(actual) == len(expected) && len(actual) == 0 { return } @@ -46,18 +64,11 @@ func assertArrayEquals(t *testing.T, what string, expected []string, actual []st } } -func assertPacscriptEquals(t *testing.T, expected pac.Script, actual pac.Script) { +func assertPacscriptEquals(t *testing.T, expected *pac.Script, actual *pac.Script) { assertEquals(t, "package name", expected.PackageName, actual.PackageName) - assertArrayEquals(t, "maintainers", expected.Maintainers, actual.Maintainers) + assertStringArrayEquals(t, "maintainers", expected.Maintainers, actual.Maintainers) assertEquals(t, "description", expected.Description, actual.Description) assertEquals(t, "gives", expected.Gives, actual.Gives) - if expected.Hash != nil && actual.Hash == nil { - t.Errorf("expected hash '%v', got nil", *expected.Hash) - } else if expected.Hash == nil && actual.Hash != nil { - t.Errorf("expected hash nil, got %v", *actual.Hash) - } else if expected.Hash != nil && actual.Hash != nil { - assertEquals(t, "hash", *expected.Hash, *actual.Hash) - } assertEquals(t, "version", expected.Version, actual.Version) assertArrayEquals(t, "breaks", expected.Breaks, actual.Breaks) assertArrayEquals(t, "conflicts", expected.Conflicts, actual.Conflicts) @@ -68,10 +79,8 @@ func assertPacscriptEquals(t *testing.T, expected pac.Script, actual pac.Script) assertArrayEquals(t, "build dependencies", expected.BuildDependencies, actual.BuildDependencies) assertArrayEquals(t, "optional dependencies", expected.OptionalDependencies, actual.OptionalDependencies) assertArrayEquals(t, "pacstall dependencies", expected.PacstallDependencies, actual.PacstallDependencies) - assertArrayEquals(t, "ppa", expected.PPA, actual.PPA) - assertArrayEquals(t, "patch", expected.Patch, actual.Patch) - assertArrayEquals(t, "required by", expected.RequiredBy, actual.RequiredBy) - assertArrayEquals(t, "repology", expected.Repology, actual.Repology) + assertStringArrayEquals(t, "required by", expected.RequiredBy, actual.RequiredBy) + assertStringArrayEquals(t, "repology", expected.Repology, actual.Repology) assertEquals(t, "update status", expected.UpdateStatus, actual.UpdateStatus) } @@ -121,7 +130,7 @@ func assertPacscriptMatchesSnapshot(t *testing.T, pkgname string) { return } - assertPacscriptEquals(t, *expected, actual) + assertPacscriptEquals(t, expected, actual) } func Test_PacscriptSnapshots(t *testing.T) { diff --git a/server/types/pac/parser/search.go b/server/types/pac/parser/search.go index 4e8c5776..f1b99407 100644 --- a/server/types/pac/parser/search.go +++ b/server/types/pac/parser/search.go @@ -37,7 +37,11 @@ func FilterPackages(packages []*pac.Script, filter, filterBy string) []*pac.Scri case "name": return filterByFunc(func(pi *pac.Script) bool { return strings.Contains(pi.PackageName, filter) || - strings.Contains(pi.Gives, filter) || + func(ps []pac.ArchDistroString, filter string) bool { + return array.Any(ps, func(it pac.ArchDistroString) bool { + return strings.Contains(it.Value, filter) + }) + }(pi.Gives, filter) || strings.Contains(pi.Description, filter) }) diff --git a/server/types/pac/script.go b/server/types/pac/script.go index b97f12da..96b43298 100644 --- a/server/types/pac/script.go +++ b/server/types/pac/script.go @@ -1,6 +1,13 @@ package pac -import "time" +import ( + "strings" + "time" + + "github.com/pacstall/go-srcinfo" + "pacstall.dev/webserver/types" + "pacstall.dev/webserver/types/array" +) type updateStatus struct { Unknown UpdateStatusValue @@ -20,27 +27,142 @@ var UpdateStatus = updateStatus{ type UpdateStatusValue = int +type ArchDistroString struct { + Arch string `json:"arch,omitempty"` + Distro string `json:"distro,omitempty"` + Value string `json:"value"` +} + +func (a ArchDistroString) Equals(b types.Equaller) bool { + other, ok := b.(ArchDistroString) + if !ok { + return false + } + + return a.Arch == other.Arch && a.Distro == other.Distro && a.Value == other.Value +} + type Script struct { - PrettyName string `json:"prettyName"` - Version string `json:"version"` - LatestVersion *string `json:"latestVersion"` - PackageName string `json:"packageName"` - Maintainers []string `json:"maintainers"` - Description string `json:"description"` - Source []string `json:"source"` - RuntimeDependencies []string `json:"runtimeDependencies"` - BuildDependencies []string `json:"buildDependencies"` - OptionalDependencies []string `json:"optionalDependencies"` - Conflicts []string `json:"conflicts"` - Breaks []string `json:"breaks"` - Gives string `json:"gives"` - Replaces []string `json:"replaces"` - Hash *string `json:"hash"` - PPA []string `json:"ppa"` - PacstallDependencies []string `json:"pacstallDependencies"` - Patch []string `json:"patch"` - Repology []string `json:"repology"` - RequiredBy []string `json:"requiredBy"` - LastUpdatedAt time.Time `json:"lastUpdatedAt"` - UpdateStatus int `json:"updateStatus"` // enum UpdateStatus + Architectures []string `json:"architectures"` + PrettyName string `json:"prettyName"` + Version string `json:"version"` + Release string `json:"release"` + LatestVersion *string `json:"latestVersion"` + PackageName string `json:"packageName"` + Maintainers []string `json:"maintainers"` + Description string `json:"description"` + Source []ArchDistroString `json:"source"` + RuntimeDependencies []ArchDistroString `json:"runtimeDependencies"` + BuildDependencies []ArchDistroString `json:"buildDependencies"` + OptionalDependencies []ArchDistroString `json:"optionalDependencies"` + CheckDependencies []ArchDistroString `json:"checkDependencies"` + Conflicts []ArchDistroString `json:"conflicts"` + Breaks []ArchDistroString `json:"breaks"` + Gives []ArchDistroString `json:"gives"` + Replaces []ArchDistroString `json:"replaces"` + Sha1Sums []ArchDistroString `json:"sha1sums"` + Sha224Sums []ArchDistroString `json:"sha224sums"` + Sha256Sums []ArchDistroString `json:"sha256sums"` + Sha384Sums []ArchDistroString `json:"sha384sums"` + Sha512Sums []ArchDistroString `json:"sha512sums"` + Md5Sums []ArchDistroString `json:"md5sums"` + Priority []ArchDistroString `json:"priority"` + Recommends []ArchDistroString `json:"recommends"` + Suggests []ArchDistroString `json:"suggests"` + PacstallDependencies []ArchDistroString `json:"pacstallDependencies"` + Enhances []ArchDistroString `json:"enhances"` + Repology []string `json:"repology"` + RequiredBy []string `json:"requiredBy"` + LastUpdatedAt time.Time `json:"lastUpdatedAt"` + UpdateStatus int `json:"updateStatus"` // enum UpdateStatus + Changelog string `json:"changelog"` + Backup []string `json:"backup"` + Compatible []string `json:"compatible"` + Incompatible []string `json:"incompatible"` + Epoch string `json:"epoch"` + Install string `json:"install"` + License []string `json:"license"` + Mask []string `json:"mask"` + NoExtract []string `json:"noExtract"` + ValidPGPKeys []string `json:"validPgpKeys"` + Groups []string `json:"groups"` +} + +func (p *Script) Type() types.PackageTypeName { + for suffix, name := range types.PackageTypeSuffixToPackageTypeName { + if strings.HasSuffix(p.PackageName, string(suffix)) { + return name + } + } + + return types.PackageTypeSuffixToPackageTypeName["-git"] +} + +func FromSrcInfo(info srcinfo.Srcinfo) *Script { + return &Script{ + Version: info.Version(), + Release: info.Pkgrel, + LatestVersion: nil, + PackageName: info.Packages[0].Pkgname, + Maintainers: info.Maintainer, + Description: info.Pkgdesc, + Source: toArchDistroStrings(info.Source), + RuntimeDependencies: toArchDistroStrings(info.Depends), + BuildDependencies: toArchDistroStrings(info.MakeDepends), + OptionalDependencies: toArchDistroStrings(info.OptDepends), + Conflicts: toArchDistroStrings(info.Conflicts), + Breaks: toArchDistroStrings(info.Breaks), + Gives: toArchDistroStrings(info.Gives), + Replaces: toArchDistroStrings(info.Replaces), + PacstallDependencies: toArchDistroStrings(info.Pacdeps), + Architectures: info.Arch, + Repology: info.Repology, + RequiredBy: []string{}, + Sha1Sums: toArchDistroStrings(info.SHA1Sums), + Sha224Sums: toArchDistroStrings(info.SHA224Sums), + Sha256Sums: toArchDistroStrings(info.SHA256Sums), + Sha384Sums: toArchDistroStrings(info.SHA384Sums), + Sha512Sums: toArchDistroStrings(info.SHA512Sums), + PrettyName: "", + Changelog: info.Changelog, + Backup: orEmptyArray(info.Backup), + Compatible: orEmptyArray(info.Compatible), + Incompatible: orEmptyArray(info.Incompatible), + Epoch: info.Epoch, + Install: info.Install, + License: orEmptyArray(info.License), + Mask: orEmptyArray(info.Mask), + NoExtract: orEmptyArray(info.NoExtract), + ValidPGPKeys: orEmptyArray(info.ValidPGPKeys), + Groups: orEmptyArray(info.Groups), + Enhances: toArchDistroStrings(info.Enhances), + CheckDependencies: toArchDistroStrings(info.CheckDepends), + Md5Sums: toArchDistroStrings(info.MD5Sums), + Priority: toArchDistroStrings(info.Priority), + Suggests: toArchDistroStrings(info.Suggests), + Recommends: toArchDistroStrings(info.Recommends), + UpdateStatus: UpdateStatus.Unknown, + } +} + +func toArchDistroStrings(ads []srcinfo.ArchDistroString) []ArchDistroString { + return array.SwitchMap(ads, func(it *array.Iterator[srcinfo.ArchDistroString]) ArchDistroString { + return toArchDistroString(it.Value) + }) +} + +func toArchDistroString(ads srcinfo.ArchDistroString) ArchDistroString { + return ArchDistroString{ + Arch: ads.Arch, + Distro: ads.Distro, + Value: ads.Value, + } +} + +func orEmptyArray[T interface{}](items []T) []T { + if items == nil { + return []T{} + } + + return items } diff --git a/server/types/pkgtype.go b/server/types/pkgtype.go new file mode 100644 index 00000000..765ce8dc --- /dev/null +++ b/server/types/pkgtype.go @@ -0,0 +1,26 @@ +package types + +type PackageTypeName string + +const ( + PACKAGE_TYPE_DEB PackageTypeName = "Debian Native" + PACKAGE_TYPE_GIT PackageTypeName = "Source Code" + PACKAGE_TYPE_BIN PackageTypeName = "Precompiled" + PACKAGE_TYPE_APP PackageTypeName = "AppImage" +) + +type PackageTypeSuffix string + +const ( + PACKAGE_TYPE_SUFFIX_DEB PackageTypeSuffix = "-deb" + PACKAGE_TYPE_SUFFIX_GIT PackageTypeSuffix = "-git" + PACKAGE_TYPE_SUFFIX_BIN PackageTypeSuffix = "-bin" + PACKAGE_TYPE_SUFFIX_APP PackageTypeSuffix = "-app" +) + +var PackageTypeSuffixToPackageTypeName = map[PackageTypeSuffix]PackageTypeName{ + PACKAGE_TYPE_SUFFIX_DEB: PACKAGE_TYPE_DEB, + PACKAGE_TYPE_SUFFIX_GIT: PACKAGE_TYPE_GIT, + PACKAGE_TYPE_SUFFIX_BIN: PACKAGE_TYPE_BIN, + PACKAGE_TYPE_SUFFIX_APP: PACKAGE_TYPE_APP, +}