diff --git a/cmd/main.go b/cmd/main.go index 68112fd..b64f8bb 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,3 +1,96 @@ package main // This file is our main entrypoint, and build point for AllPac + +import ( + "flag" + "fmt" + "os" + "pixelridgesoftworks.com/AllPac/pkg/packagemanager" +) + +func main() { + // Define flags for different commands + updateCmd := flag.NewFlagSet("update", flag.ExitOnError) + installCmd := flag.NewFlagSet("install", flag.ExitOnError) + uninstallCmd := flag.NewFlagSet("uninstall", flag.ExitOnError) + searchCmd := flag.NewFlagSet("search", flag.ExitOnError) + aurRebuildCmd := flag.NewFlagSet("rebuild", flag.ExitOnError) + aurCleanCmd := flag.NewFlagSet("clean-aur", flag.ExitOnError) + + if len(os.Args) < 2 { + fmt.Println("Expected 'update', 'install', 'uninstall', 'search', 'rebuild', or 'clean-aur' subcommands") + os.Exit(1) + } + + switch os.Args[1] { + case "update": + handleUpdate(updateCmd) + case "install": + handleInstall(installCmd) + case "uninstall": + handleUninstall(uninstallCmd) + case "search": + handleSearch(searchCmd) + case "rebuild": + handleRebuild(aurRebuildCmd) + case "clean-aur": + handleCleanAur(aurCleanCmd) + default: + fmt.Printf("Unknown subcommand: %s\n", os.Args[1]) + os.Exit(1) + } +} + +func handleUpdate(cmd *flag.FlagSet) { + everythingFlag := cmd.Bool("everything", false, "Update all packages on the system") + snapFlag := cmd.Bool("snap", false, "Update all Snap packages") + aurFlag := cmd.Bool("aur", false, "Update all AUR packages") + archFlag := cmd.Bool("arch", false, "Update all Arch packages") + flatsFlag := cmd.Bool("flats", false, "Update all Flatpak packages") + cmd.Parse(os.Args[2:]) + + if *everythingFlag { + // Call function to update all packages + packagemanager.UpdateAllPackages() + } else if *snapFlag { + // Call function to update Snap packages + packagemanager.UpdateSnapPackages() + } else if *aurFlag { + // Call function to update AUR packages + packagemanager.UpdateAURPackages() + } else if *archFlag { + // Call function to update Arch packages + packagemanager.UpdatePacmanPackages() + } else if *flatsFlag { + // Call function to update Flatpak packages + packagemanager.UpdateFlatpakPackages() + } else { + fmt.Println("No update option specified or unrecognized option") + } +} + +func handleInstall(cmd *flag.FlagSet) { + // Parse and handle the install command + // Use functions from install to install packages +} + +func handleUninstall(cmd *flag.FlagSet) { + // Parse and handle the uninstall command + // Use functions from packagemanager to uninstall packages +} + +func handleSearch(cmd *flag.FlagSet) { + // Parse and handle the search command + // Use functions from search to search for packages +} + +func handleRebuild(cmd *flag.FlagSet) { + // Parse and handle the search command + // Use functions from search to search for packages +} + +func handleCleanAur(cmd *flag.FlagSet) { + // Parse and handle the search command + // Use functions from search to search for packages +} diff --git a/pkg/packagemanager/all_updater.go b/pkg/packagemanager/all_updater.go new file mode 100644 index 0000000..30edfe4 --- /dev/null +++ b/pkg/packagemanager/all_updater.go @@ -0,0 +1,46 @@ +package packagemanager + +import ( + "fmt" + "sync" +) + +// UpdateAllPackages updates all packages on the system +func UpdateAllPackages() error { + pkgList, err := readPackageList() + if err != nil { + return fmt.Errorf("error reading package list: %v", err) + } + + var wg sync.WaitGroup + for pkgName, pkgInfo := range pkgList { + wg.Add(1) + go func(name string, info PackageInfo) { + defer wg.Done() + if err := checkAndUpdatePackage(name, info); err != nil { + fmt.Printf("Error updating package %s: %v\n", name, err) + } + }(pkgName, pkgInfo) + } + + wg.Wait() + fmt.Println("All packages have been updated.") + return nil +} + +// checkAndUpdatePackage checks if an update is available for the package and updates it +func checkAndUpdatePackage(name string, info PackageInfo) error { + // Implement logic to check for the latest version and update + return nil +} + +// functions to get the latest version for each package manager +func getLatestPacmanVersion(packageName string) (string, error) { + // Use SearchPacman to get the latest version + // Parse the output to extract the version + // Return the version + // ... + return "", nil +} + +// Similar implementations for getLatestAURVersion, getLatestSnapVersion, getLatestFlatpakVersion diff --git a/pkg/packagemanager/aur.go b/pkg/packagemanager/aur.go new file mode 100644 index 0000000..1682742 --- /dev/null +++ b/pkg/packagemanager/aur.go @@ -0,0 +1,46 @@ +package packagemanager + +import ( + "fmt" + "os/exec" +) + +// AURPackageInfo represents the package information from the AUR +type AURPackageInfo struct { + Version string `json:"Version"` + // Add other relevant fields +} + +// UpdateAURPackages updates specified AUR packages or all if no specific package is provided +func UpdateAURPackages(packageNames ...string) error { + pkgList, err := ReadPackageList() + if err != nil { + return fmt.Errorf("error reading package list: %v", err) + } + + for _, packageName := range packageNames { + aurInfo, err := fetchAURPackageInfo(packageName) + if err != nil { + return fmt.Errorf("error fetching AUR package info for %s: %v", packageName, err) + } + + installedInfo, ok := pkgList[packageName] + if !ok || installedInfo.Version != aurInfo.Version { + _, err := CloneAndInstallFromAUR("https://aur.archlinux.org/" + packageName + ".git", true) + if err != nil { + return fmt.Errorf("error updating AUR package %s: %v", packageName, err) + } + } + } + return nil +} + +// UninstallAURPackage uninstalls a specified AUR package +func UninstallAURPackage(packageName string) error { + // Uninstalling an AUR package is typically done with pacman + cmd := exec.Command("sudo", "pacman", "-Rns", "--noconfirm", packageName) + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("error uninstalling AUR package: %s, %v", output, err) + } + return nil +} diff --git a/pkg/packagemanager/flatpak.go b/pkg/packagemanager/flatpak.go index 8651f4b..8eceb48 100644 --- a/pkg/packagemanager/flatpak.go +++ b/pkg/packagemanager/flatpak.go @@ -5,6 +5,7 @@ package packagemanager import ( "os/exec" "fmt" + "strings" ) // UpdateFlatpakPackages updates specified Flatpak packages or all if no specific package is provided @@ -31,3 +32,24 @@ func UninstallFlatpakPackage(packageName string) error { } return nil } + +// GetVersionFromFlatpak gets the installed version of a Flatpak package +func GetVersionFromFlatpak(applicationID string) (string, error) { + cmd := exec.Command("flatpak", "info", applicationID) + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("error getting flatpak package info: %v", err) + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "Version:") { + parts := strings.Fields(line) + if len(parts) >= 2 { + return parts[1], nil + } + break + } + } + return "", fmt.Errorf("version not found for flatpak package: %s", applicationID) +} diff --git a/pkg/install/install.go b/pkg/packagemanager/install.go similarity index 56% rename from pkg/install/install.go rename to pkg/packagemanager/install.go index 78daa80..31ccd79 100644 --- a/pkg/install/install.go +++ b/pkg/packagemanager/install.go @@ -1,132 +1,86 @@ -package install +package packagemanager // This package is responsible for handling our actual install logic. We could have probably gotten away with // implementing this into the packagemanager package, but this seems like a better way // because this provides a single interface for all our install functions import ( - "encoding/json" + "fmt" "os" "os/exec" "os/user" "path/filepath" - "fmt" ) -// PackageList represents the mapping of installed packages to their sources -type PackageList map[string]string - -const pkgListFilename = "pkg.list" - -// getPkgListPath returns the file path for the package list -func getPkgListPath() (string, error) { - usr, err := user.Current() - if err != nil { - return "", fmt.Errorf("error getting current user: %v", err) - } - return filepath.Join(usr.HomeDir, ".allpac", pkgListFilename), nil -} - -// readPackageList reads the package list from the file -func readPackageList() (PackageList, error) { - pkgListPath, err := getPkgListPath() - if err != nil { - return nil, err - } - - file, err := os.Open(pkgListPath) - if err != nil { - if os.IsNotExist(err) { - return PackageList{}, nil // Return an empty list if file doesn't exist - } - return nil, fmt.Errorf("error opening package list file: %v", err) - } - defer file.Close() - - var pkgList PackageList - err = json.NewDecoder(file).Decode(&pkgList) - if err != nil { - return nil, fmt.Errorf("error decoding package list: %v", err) - } - - return pkgList, nil -} - -// writePackageList writes the package list to the file -func writePackageList(pkgList PackageList) error { - pkgListPath, err := getPkgListPath() - if err != nil { - return err - } - - file, err := os.Create(pkgListPath) - if err != nil { - return fmt.Errorf("error creating package list file: %v", err) - } - defer file.Close() - - err = json.NewEncoder(file).Encode(pkgList) - if err != nil { - return fmt.Errorf("error encoding package list: %v", err) - } - - return nil -} - -// logInstallation logs the package installation details -func LogInstallation(packageName, source string) error { - pkgList, err := readPackageList() - if err != nil { - return err - } - - pkgList[packageName] = source - - return writePackageList(pkgList) -} - -// InstallPackagePacman installs a package using Pacman +// InstallPackagePacman installs a package using Pacman and logs the installation func InstallPackagePacman(packageName string) error { cmd := exec.Command("sudo", "pacman", "-S", "--noconfirm", packageName) if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("error installing package with Pacman: %s, %v", output, err) } - if err := LogInstallation(packageName, "pacman"); err != nil { + + version, err := GetVersionFromPacman(packageName) + if err != nil { + return err + } + + if err := LogInstallation(packageName, "pacman", version); err != nil { return fmt.Errorf("error logging installation: %v", err) } return nil } -// InstallPackageSnap installs a package using Snap +// InstallPackageSnap installs a package using Snap and logs the installation func InstallPackageSnap(packageName string) error { cmd := exec.Command("sudo", "snap", "install", packageName) if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("error installing package with Snap: %s, %v", output, err) } - if err := LogInstallation(packageName, "snap"); err != nil { + + version, err := GetVersionFromSnap(packageName) + if err != nil { + return err + } + + if err := LogInstallation(packageName, "snap", version); err != nil { return fmt.Errorf("error logging installation: %v", err) } return nil } -// InstallPackageFlatpak installs a package using Flatpak +// InstallPackageFlatpak installs a package using Flatpak and logs the installation func InstallPackageFlatpak(packageName string) error { cmd := exec.Command("flatpak", "install", "-y", packageName) if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("error installing package with Flatpak: %s, %v", output, err) } - if err := LogInstallation(packageName, "flatpak"); err != nil { + + version, err := GetVersionFromFlatpak(packageName) + if err != nil { + return err + } + + if err := LogInstallation(packageName, "flatpak", version); err != nil { return fmt.Errorf("error logging installation: %v", err) } return nil } // cloneAndInstallFromAUR clones the given AUR repository and installs it -func CloneAndInstallFromAUR(repoURL string) error { +func CloneAndInstallFromAUR(repoURL string, skipConfirmation bool) (string, error) { + // Request root permissions + if !skipConfirmation && !requestRootPermissions() { + return "", fmt.Errorf("root permissions denied") + } + + // Confirm before proceeding with each step + if !skipConfirmation && !confirmAction("Do you want to download and build package from " + repoURL + "?") { + return "", fmt.Errorf("user aborted the action") + } // Get the current user's home directory usr, err := user.Current() if err != nil { - return fmt.Errorf("error getting current user: %v", err) + return "", fmt.Errorf("error getting current user: %v", err) } // Define the base directory for AllPac cache @@ -134,13 +88,13 @@ func CloneAndInstallFromAUR(repoURL string) error { // Ensure the base directory exists if err := os.MkdirAll(baseDir, 0755); err != nil { - return fmt.Errorf("error creating base directory: %v", err) + return "", fmt.Errorf("error creating base directory: %v", err) } // Clone the repository cmdGitClone := exec.Command("git", "clone", repoURL, baseDir) if output, err := cmdGitClone.CombinedOutput(); err != nil { - return fmt.Errorf("error cloning AUR repo: %s, %v", output, err) + return "", fmt.Errorf("error cloning AUR repo: %s, %v", output, err) } // Determine the name of the created directory (and the package name) @@ -149,29 +103,41 @@ func CloneAndInstallFromAUR(repoURL string) error { // Change directory to the cloned repository if err := os.Chdir(repoDir); err != nil { - return fmt.Errorf("error changing directory: %v", err) + return "", fmt.Errorf("error changing directory: %v", err) } // Build the package using makepkg cmdMakePkg := exec.Command("makepkg", "-si", "--noconfirm") if output, err := cmdMakePkg.CombinedOutput(); err != nil { - return fmt.Errorf("error building package with makepkg: %s, %v", output, err) + return "", fmt.Errorf("error building package with makepkg: %s, %v", output, err) } - // Log the installation - if err := LogInstallation(repoName, "aur"); err != nil { - return fmt.Errorf("error logging installation: %v", err) + // Extract the version from PKGBUILD + version, err := ExtractVersionFromPKGBUILD(repoDir) + if err != nil { + return "", fmt.Errorf("error extracting version from PKGBUILD: %v", err) } - return nil + // Confirm before installing + if !skipConfirmation && !confirmAction("Do you want to install the built package " + repoName + "?") { + return "", fmt.Errorf("user aborted the installation") + } + + if err := LogInstallation(repoName, "aur", version); err != nil { + return "", fmt.Errorf("error logging installation: %v", err) + } + + return version, nil } // InstallSnap installs Snap manually from the AUR func InstallSnap() error { - if err := CloneAndInstallFromAUR("https://aur.archlinux.org/snapd.git"); err != nil { + version, err := CloneAndInstallFromAUR("https://aur.archlinux.org/snapd.git", true) + if err != nil { return fmt.Errorf("error installing Snap: %v", err) } - if err := LogInstallation("snapd", "aur"); err != nil { + + if err := LogInstallation("snapd", "aur", version); err != nil { return fmt.Errorf("error logging installation: %v", err) } return nil diff --git a/pkg/packagemanager/installer_utils.go b/pkg/packagemanager/installer_utils.go new file mode 100644 index 0000000..086e6e2 --- /dev/null +++ b/pkg/packagemanager/installer_utils.go @@ -0,0 +1,140 @@ +package packagemanager + +import ( + "bufio" + "os" + "os/user" + "path/filepath" + "strings" + "fmt" + "encoding/json" +) + +// extractVersionFromPKGBUILD reads the PKGBUILD file and extracts the package version +func ExtractVersionFromPKGBUILD(repoDir string) (string, error) { + pkgbuildPath := filepath.Join(repoDir, "PKGBUILD") + file, err := os.Open(pkgbuildPath) + if err != nil { + return "", err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "pkgver=") { + return strings.TrimPrefix(line, "pkgver="), nil + } + } + + if err := scanner.Err(); err != nil { + return "", err + } + + return "", fmt.Errorf("pkgver not found in PKGBUILD") +} + +type PackageInfo struct { + Source string `json:"source"` + Version string `json:"version"` +} + +type PackageList map[string]PackageInfo + +const pkgListFilename = "pkg.list" + +// getPkgListPath returns the file path for the package list +func GetPkgListPath() (string, error) { + usr, err := user.Current() + if err != nil { + return "", fmt.Errorf("error getting current user: %v", err) + } + return filepath.Join(usr.HomeDir, ".allpac", pkgListFilename), nil +} + +// readPackageList reads the package list from the file +func ReadPackageList() (PackageList, error) { + pkgListPath, err := GetPkgListPath() + if err != nil { + return nil, err + } + + file, err := os.Open(pkgListPath) + if err != nil { + if os.IsNotExist(err) { + return PackageList{}, nil // Return an empty list if file doesn't exist + } + return nil, fmt.Errorf("error opening package list file: %v", err) + } + defer file.Close() + + var pkgList PackageList + err = json.NewDecoder(file).Decode(&pkgList) + if err != nil { + return nil, fmt.Errorf("error decoding package list: %v", err) + } + + return pkgList, nil +} + +// writePackageList writes the package list to the file +func writePackageList(pkgList PackageList) error { + pkgListPath, err := GetPkgListPath() + if err != nil { + return err + } + + file, err := os.Create(pkgListPath) + if err != nil { + return fmt.Errorf("error creating package list file: %v", err) + } + defer file.Close() + + err = json.NewEncoder(file).Encode(pkgList) + if err != nil { + return fmt.Errorf("error encoding package list: %v", err) + } + + return nil +} + +// logInstallation logs the package installation details +func LogInstallation(packageName, source, version string) error { + pkgList, err := readPackageList() + if err != nil { + return err + } + + pkgList[packageName] = PackageInfo{ + Source: source, + Version: version, + } + + return writePackageList(pkgList) +} + +// requestRootPermissions prompts the user for root permissions +func requestRootPermissions() bool { + fmt.Println("Root permissions are required to install AUR packages.") + return confirmAction("Do you want to continue with root permissions?") +} + +// confirmAction prompts the user with a yes/no question and returns true if the answer is yes +func confirmAction(question string) bool { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("%s [Y/n]: ", question) + response, err := reader.ReadString('\n') + if err != nil { + fmt.Println("Error reading response:", err) + return false + } + response = strings.ToLower(strings.TrimSpace(response)) + + if response == "y" || response == "yes" { + return true + } else if response == "n" || response == "no" { + return false + } + } +} diff --git a/pkg/packagemanager/pacman.go b/pkg/packagemanager/pacman.go index c0bdb42..0e669af 100644 --- a/pkg/packagemanager/pacman.go +++ b/pkg/packagemanager/pacman.go @@ -5,6 +5,7 @@ package packagemanager import ( "os/exec" "fmt" + "strings" ) // UpdatePacmanPackages updates specified Pacman packages or all if no specific package is provided @@ -31,3 +32,19 @@ func UninstallPacmanPackage(packageName string) error { } return nil } + +// getVersionFromPacman gets the installed version of a package using Pacman +func GetVersionFromPacman(packageName string) (string, error) { + cmd := exec.Command("pacman", "-Qi", packageName) + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("error getting package version: %v", err) + } + + for _, line := range strings.Split(string(output), "\n") { + if strings.HasPrefix(line, "Version") { + return strings.Fields(line)[2], nil + } + } + return "", fmt.Errorf("version not found for package: %s", packageName) +} diff --git a/pkg/search/search.go b/pkg/packagemanager/search.go similarity index 73% rename from pkg/search/search.go rename to pkg/packagemanager/search.go index d7dd81f..ee17708 100644 --- a/pkg/search/search.go +++ b/pkg/packagemanager/search.go @@ -1,9 +1,8 @@ -package search +package packagemanager // This package is responsible for searching various sources for the availability of the requested package import ( - "pixelridgesoftworks.com/AllPac/pkg/packagemanager" "encoding/json" "io/ioutil" "fmt" @@ -14,9 +13,6 @@ import ( "net/http" ) -// PackageList represents the mapping of installed packages to their sources -type PackageList map[string]string - // UninstallPackages uninstalls the provided packages func UninstallPackages(packageNames []string) error { pkgList, err := readPackageList() @@ -33,11 +29,11 @@ func UninstallPackages(packageNames []string) error { switch source { case "pacman": - err = packagemanager.UninstallPacmanPackage(packageName) + err = UninstallPacmanPackage(packageName) case "snap": - err = packagemanager.UninstallSnapPackage(packageName) + err = UninstallSnapPackage(packageName) case "flatpak": - err = packagemanager.UninstallFlatpakPackage(packageName) + err = UninstallFlatpakPackage(packageName) // Add cases for other package managers if necessary default: fmt.Printf("Unknown source for package %s\n", packageName) @@ -166,3 +162,52 @@ func parsePacmanOutput(output string) []string { return packages } + +// GetPacmanPackageVersion returns the version of a package in the Pacman repositories +func GetPacmanPackageVersion(packageName string) (string, error) { + searchResults, err := SearchPacman(packageName) + if err != nil { + return "", err + } + + for _, result := range searchResults { + if strings.Contains(result, packageName) { + return extractVersionFromPacmanResult(result), nil + } + } + + return "", fmt.Errorf("package %s not found in Pacman", packageName) +} + +// extractVersionFromPacmanResult extracts the version from a Pacman search result string +func extractVersionFromPacmanResult(result string) string { + // Assuming the result is in the format "packageName version description" + parts := strings.Fields(result) + if len(parts) >= 2 { + return parts[1] // The second element should be the version + } + return "" +} + +// fetchAURPackageInfo fetches package information from the AUR +func fetchAURPackageInfo(packageName string) (*AURPackageInfo, error) { + url := fmt.Sprintf("https://aur.archlinux.org/rpc/?v=5&type=info&arg[]=%s", packageName) + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var result struct { + Results []AURPackageInfo `json:"results"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, err + } + + if len(result.Results) == 0 { + return nil, fmt.Errorf("package %s not found in AUR", packageName) + } + + return &result.Results[0], nil +} diff --git a/pkg/packagemanager/snap.go b/pkg/packagemanager/snap.go index 84dd73e..dac74d4 100644 --- a/pkg/packagemanager/snap.go +++ b/pkg/packagemanager/snap.go @@ -5,6 +5,7 @@ package packagemanager import ( "os/exec" "fmt" + "strings" ) // UpdateSnapPackages updates specified Snap packages or all if no specific package is provided @@ -31,3 +32,24 @@ func UninstallSnapPackage(packageName string) error { } return nil } + +// GetVersionFromSnap gets the installed version of a Snap package +func GetVersionFromSnap(packageName string) (string, error) { + cmd := exec.Command("snap", "info", packageName) + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("error getting snap package info: %v", err) + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "installed:") { + parts := strings.Fields(line) + if len(parts) >= 2 { + return parts[1], nil + } + break + } + } + return "", fmt.Errorf("version not found for snap package: %s", packageName) +} diff --git a/pkg/toolcheck/toolcheck.go b/pkg/toolcheck/toolcheck.go index 582d325..1ea28c9 100644 --- a/pkg/toolcheck/toolcheck.go +++ b/pkg/toolcheck/toolcheck.go @@ -9,7 +9,7 @@ package toolcheck import ( "os/exec" "fmt" - "pixelridgesoftworks.com/AllPac/pkg/install" + "pixelridgesoftworks.com/AllPac/pkg/packagemanager" ) // isCommandAvailable checks if a command exists @@ -33,7 +33,7 @@ func EnsurePacman() error { // EnsureSnap ensures that Snap is installed and available func EnsureSnap() error { if !isCommandAvailable("snap") { - return install.InstallSnap() + return packagemanager.InstallSnap() } return nil } @@ -41,7 +41,7 @@ func EnsureSnap() error { // EnsureGit ensures that Git is installed and available func EnsureGit() error { if !isCommandAvailable("git") { - return install.InstallGit() + return packagemanager.InstallGit() } return nil } @@ -49,7 +49,7 @@ func EnsureGit() error { // EnsureBaseDevel ensures that the base-devel group is installed func EnsureBaseDevel() error { if !isCommandAvailable("make") { // 'make' is part of base-devel, this is the best method to check - return install.InstallBaseDevel() + return packagemanager.InstallBaseDevel() } return nil } @@ -57,7 +57,7 @@ func EnsureBaseDevel() error { // EnsureFlatpak ensures that Flatpak is installed and available func EnsureFlatpak() error { if !isCommandAvailable("flatpak") { - return install.InstallFlatpak() + return packagemanager.InstallFlatpak() } return nil }