elisp

package
v0.0.0-...-9ce4fc3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 23, 2024 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package elisp provides a backend for Emacs Lisp using Cask.

Index

Constants

This section is empty.

Variables

View Source
var ElispBackend = api.LanguageBackend{
	Name:             "elisp-cask",
	Specfile:         "Cask",
	Lockfile:         "packages.txt",
	IsAvailable:      elispCaskIsAvailable,
	FilenamePatterns: elispPatterns,
	Quirks:           api.QuirksNotReproducible,
	GetPackageDir: func() string {
		return ".cask"
	},
	Search: func(query string) []api.PkgInfo {
		tmpdir, err := os.MkdirTemp("", "elpa")
		if err != nil {
			util.DieIO("%s", err)
		}
		defer os.RemoveAll(tmpdir)

		code := fmt.Sprintf(
			"(eval '(progn %s) t)", util.GetResource("/elisp/elpa-search.el"),
		)
		code = strings.Replace(code, "~", "`", -1)
		outputB := util.GetCmdOutput([]string{
			"emacs", "-Q", "--batch", "--eval", code,
			tmpdir, "search", query,
		})
		var results []api.PkgInfo
		if err := json.Unmarshal(outputB, &results); err != nil {
			util.DieProtocol("%s", err)
		}
		return results
	},
	Info: func(name api.PkgName) api.PkgInfo {
		tmpdir, err := os.MkdirTemp("", "elpa")
		if err != nil {
			util.DieIO("%s", err)
		}
		defer os.RemoveAll(tmpdir)

		code := fmt.Sprintf(
			"(eval '(progn %s) t)", util.GetResource("/elisp/elpa-search.el"),
		)
		code = strings.Replace(code, "~", "`", -1)
		outputB := util.GetCmdOutput([]string{
			"emacs", "-Q", "--batch", "--eval", code,
			tmpdir, "info", string(name),
		})
		var info api.PkgInfo
		if err := json.Unmarshal(outputB, &info); err != nil {
			util.DieProtocol("%s", err)
		}
		return info
	},
	Add: func(ctx context.Context, pkgs map[api.PkgName]api.PkgSpec, projectName string) {

		span, ctx := tracer.StartSpanFromContext(ctx, "elisp add")
		defer span.Finish()
		contentsB, err := os.ReadFile("Cask")
		var contents string
		if os.IsNotExist(err) {
			contents = `(source melpa)
(source gnu)
(source org)
`
		} else if err != nil {
			util.DieIO("Cask: %s", err)
		} else {
			contents = string(contentsB)
		}

		if len(contents) > 0 && contents[len(contents)-1] != '\n' {
			contents += "\n"
		}

		for name, spec := range pkgs {
			contents += fmt.Sprintf(`(depends-on "%s"`, name)
			if spec != "" {
				contents += fmt.Sprintf(" %s", spec)
			}
			contents += ")\n"
		}

		contentsB = []byte(contents)
		util.ProgressMsg("write Cask")
		util.TryWriteAtomic("Cask", contentsB)
	},
	Remove: func(ctx context.Context, pkgs map[api.PkgName]bool) {

		span, ctx := tracer.StartSpanFromContext(ctx, "elisp remove")
		defer span.Finish()
		contentsB, err := os.ReadFile("Cask")
		if err != nil {
			util.DieIO("Cask: %s", err)
		}
		contents := string(contentsB)

		for name := range pkgs {
			contents = regexp.MustCompile(
				fmt.Sprintf(
					`(?m)^ *\(depends-on +"%s".*\)\n?$`,
					regexp.QuoteMeta(string(name)),
				),
			).ReplaceAllLiteralString(contents, "")
		}

		contentsB = []byte(contents)
		util.ProgressMsg("write Cask")
		util.TryWriteAtomic("Cask", contentsB)
	},
	Install: func(ctx context.Context) {

		span, ctx := tracer.StartSpanFromContext(ctx, "cask install")
		defer span.Finish()
		util.RunCmd([]string{"cask", "install"})
		outputB := util.GetCmdOutput(
			[]string{"cask", "eval", util.GetResource(
				"/elisp/cask-list-installed.el",
			)},
		)
		util.ProgressMsg("write packages.txt")
		util.TryWriteAtomic("packages.txt", outputB)
	},
	ListSpecfile: func(mergeAllGroups bool) map[api.PkgName]api.PkgSpec {
		outputB := util.GetCmdOutput(
			[]string{"cask", "eval", util.GetResource(
				"/elisp/cask-list-specfile.el",
			)},
		)
		pkgs := map[api.PkgName]api.PkgSpec{}
		for _, line := range strings.Split(string(outputB), "\n") {
			if line == "" {
				continue
			}
			fields := strings.SplitN(line, "=", 2)
			if len(fields) != 2 {
				util.DieProtocol("unexpected output, expected name=spec: %s", line)
			}
			name := api.PkgName(fields[0])
			spec := api.PkgSpec(fields[1])
			pkgs[name] = spec
		}
		return pkgs
	},
	ListLockfile: func() map[api.PkgName]api.PkgVersion {
		contentsB, err := os.ReadFile("packages.txt")
		if err != nil {
			util.DieIO("packages.txt: %s", err)
		}
		contents := string(contentsB)
		r := regexp.MustCompile(`(.+)=(.+)`)
		pkgs := map[api.PkgName]api.PkgVersion{}
		for _, match := range r.FindAllStringSubmatch(contents, -1) {
			name := api.PkgName(match[1])
			version := api.PkgVersion(match[2])
			pkgs[name] = version
		}
		return pkgs
	},
	GuessRegexps: util.Regexps([]string{
		`\(\s*require\s*'\s*([^)[:space:]]+)[^)]*\)`,
	}),
	Guess: func(ctx context.Context) (map[string][]api.PkgName, bool) {

		span, ctx := tracer.StartSpanFromContext(ctx, "elisp guess")
		defer span.Finish()
		r := regexp.MustCompile(
			`\(\s*require\s*'\s*([^)[:space:]]+)[^)]*\)`,
		)
		required := map[string]bool{}
		for _, match := range util.SearchRecursive(r, elispPatterns) {
			required[match[1]] = true
		}

		if len(required) == 0 {
			return map[string][]api.PkgName{}, true
		}

		r = regexp.MustCompile(
			`\(\s*provide\s*'\s*([^)[:space:]]+)[^)]*\)`,
		)
		provided := map[string]bool{}
		for _, match := range util.SearchRecursive(r, elispPatterns) {
			provided[match[1]] = true
		}

		tempdir, err := os.MkdirTemp("", "epkgs")
		if err != nil {
			util.DieIO("%s", err)
		}
		defer os.RemoveAll(tempdir)

		url := "https://github.com/emacsmirror/epkgs/raw/master/epkg.sqlite"
		epkgs := filepath.Join(tempdir, "epkgs.sqlite")
		util.DownloadFile(epkgs, url)

		clauses := []string{}
		for feature := range required {
			if strings.ContainsAny(feature, `\'`) {
				continue
			}
			if provided[feature] {
				continue
			}
			clauses = append(clauses, fmt.Sprintf("feature = '%s'", feature))
		}
		if len(clauses) == 0 {
			return map[string][]api.PkgName{}, true
		}
		where := strings.Join(clauses, " OR ")
		query := fmt.Sprintf("SELECT package FROM provided PR WHERE (%s) "+
			"AND NOT EXISTS (SELECT 1 FROM builtin_libraries B "+
			"WHERE PR.feature = B.feature) "+
			"AND NOT EXISTS (SELECT 1 FROM packages PK "+
			"WHERE PR.package = PK.name AND PK.class = 'builtin');",
			where,
		)
		output := string(util.GetCmdOutput([]string{"sqlite3", epkgs, query}))

		r = regexp.MustCompile(`"(.+?)"`)
		names := map[string][]api.PkgName{}
		for _, match := range r.FindAllStringSubmatch(output, -1) {
			name := match[1]
			names[name] = []api.PkgName{api.PkgName(name)}
		}
		return names, true
	},
	InstallReplitNixSystemDependencies: nix.DefaultInstallReplitNixSystemDependencies,
}

ElispBackend is the UPM language backend for Emacs Lisp using Cask.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL