summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'company-ebuild.el')
-rw-r--r--company-ebuild.el269
1 files changed, 269 insertions, 0 deletions
diff --git a/company-ebuild.el b/company-ebuild.el
new file mode 100644
index 0000000..2fbfedc
--- /dev/null
+++ b/company-ebuild.el
@@ -0,0 +1,269 @@
+;;; company-ebuild.el --- Company backend for editing Ebuild files -*- lexical-binding: t -*-
+
+
+
+;; Copyright 2022 Gentoo Authors
+
+
+;; This file is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 2 of the License, or
+;; (at your option) any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+
+;; Authors: Maciej Barć <xgqt@gentoo.org>
+;; Created: 16 Aug 2022
+;; Version: 0.0.0
+;; Keywords: languages
+;; Homepage: https://gitweb.gentoo.org/proj/company-ebuild.git
+;; Package-Requires: ((emacs "25.1"))
+;; SPDX-License-Identifier: GPL-2.0-or-later
+
+
+
+;;; Commentary:
+
+
+;; Company backend for editing Ebuild files.
+
+
+
+;;; Code:
+
+
+(require 'cl-lib)
+
+(require 'company)
+(require 'ebuild-mode)
+
+(require 'company-ebuild-keywords)
+
+
+(defconst company-ebuild-version "0.0.0"
+ "Company-Ebuild version.")
+
+
+(defun company-ebuild--annotation (candidate)
+ "Return annotation for CANDIDATE."
+ (cond
+ ((member candidate company-ebuild--constant-keywords-architectures)
+ " architecture")
+ ((member candidate company-ebuild--constant-keywords-restrict)
+ " restrict")
+ ((member candidate company-ebuild--constant-keywords-phases)
+ " phase")
+ ((member candidate company-ebuild--constant-keywords-sandbox)
+ " sandbox")
+ ((member candidate company-ebuild--constant-keywords-doc)
+ " doc")
+ ((member candidate company-ebuild--constant-keywords-variables-predefined)
+ " variable (predefined)")
+ ((member candidate company-ebuild--constant-keywords-variables-ebuild-defined)
+ " variable (ebuild-defined)")
+ ((member candidate company-ebuild--constant-keywords-variables-dependencies)
+ " variable (dependencies)")
+ ((member candidate company-ebuild--constant-keywords-variables-user-environment)
+ " variable (user-environment)")
+ ((member candidate company-ebuild--dynamic-keywords-eclasses)
+ " eclass")
+ ((or (member candidate company-ebuild--constant-keywords-functions)
+ (member candidate company-ebuild--dynamic-keywords-functions))
+ " function")
+ ((member candidate company-ebuild--dynamic-keywords-variables)
+ " variable (eclass)")
+ ((member candidate company-ebuild--dynamic-keywords-use-flags)
+ " USE flag")
+ ((member candidate company-ebuild--dynamic-keywords-packages)
+ " package")
+ ((member candidate company-ebuild--dynamic-keywords-licenses)
+ " license")
+ ((executable-find candidate)
+ " executable")
+ ;; TODO: Complete any string that already appears in current buffer.
+ (t
+ "")))
+
+(defun company-ebuild--packages ()
+ "Return a list of all available packages.
+
+Uses the \"qsearch\" tool to get the packages."
+ (let ((qsearch
+ (executable-find "qsearch"))
+ (qsearch-formats
+ '("%{CATEGORY}/%{PN}"
+ "%{CATEGORY}/%{PN}-%{PV}"
+ "%{CATEGORY}/%{PN}-%{PV}:%{SLOT}"
+ "%{CATEGORY}/%{PN}-%{PV}:%{SLOT}::%{REPO}")))
+ (cond
+ (qsearch
+ (mapcan (lambda (qsearch-format)
+ (let ((qlist-result
+ (shell-command-to-string
+ (format "%s --all --format \"%s\" --name-only --nocolor"
+ qsearch
+ qsearch-format))))
+ (split-string qlist-result "\n" t)))
+ qsearch-formats))
+ (t
+ nil))))
+
+(defun company-ebuild--get-tags (file-path tag-name)
+ "Return all tags with TAG-NAME from file at FILE-PATH.
+
+For example:
+\(company-ebuild--get-tags \"/gentoo/eclass/edo.eclass\" \"FUNCTION\")"
+ (let ((tag
+ (concat "# @" tag-name ": "))
+ (file-lines
+ (with-temp-buffer
+ (insert-file-contents file-path)
+ (split-string (buffer-string) "\n" t))))
+ ;; Hack with `mapcan' - doing both filter and map.
+ (mapcan (lambda (line)
+ (cond
+ ((string-match-p (concat tag ".*") line)
+ (list (replace-regexp-in-string tag "" line)))
+ (t
+ nil)))
+ file-lines)))
+
+(defun company-ebuild--find-repo-root (file-path)
+ "Return the root directory of current Ebuild repository.
+
+FILE-PATH is the location from which we start searching for repository root."
+ (locate-dominating-file file-path "profiles/repo_name"))
+
+(defun company-ebuild--find-eclass-files (file-path)
+ "Return found Eclass files.
+
+FILE-PATH is the location from which we start searching for Eclass files."
+ (let ((repo-root
+ (company-ebuild--find-repo-root file-path)))
+ (and repo-root
+ (directory-files
+ (expand-file-name "eclass" repo-root) t ".*\\.eclass" t))))
+
+(defun company-ebuild--regenerate-dynamic-keywords-eclasses ()
+ "Set new content of the ‘company-ebuild--dynamic-keywords’ Eclass variables."
+ (let ((repo-root
+ (and buffer-file-name
+ (company-ebuild--find-repo-root buffer-file-name))))
+ (when repo-root
+ (let ((eclass-files
+ (company-ebuild--find-eclass-files repo-root)))
+ (setq company-ebuild--dynamic-keywords-eclasses
+ (apply #'append
+ (mapcar (lambda (f)
+ (mapcar (lambda (s)
+ (replace-regexp-in-string "\\.eclass"
+ ""
+ s))
+ (company-ebuild--get-tags f "ECLASS")))
+ eclass-files)))
+ (setq company-ebuild--dynamic-keywords-variables
+ (apply #'append
+ (mapcar (lambda (f)
+ (company-ebuild--get-tags f "ECLASS_VARIABLE"))
+ eclass-files)))
+ (setq company-ebuild--dynamic-keywords-functions
+ (apply #'append
+ (mapcar (lambda (f)
+ (company-ebuild--get-tags f "FUNCTION"))
+ eclass-files)))))))
+
+(defun company-ebuild--regenerate-dynamic-keywords-use-flags ()
+ "Set new content of the ‘company-ebuild--dynamic-keywords-use-flags’ variable."
+ (let ((repo-root
+ (and buffer-file-name
+ (company-ebuild--find-repo-root buffer-file-name)))
+ (awk-format
+ "awk -F - '{ print $1 }' %s/profiles/use.desc"))
+ (when repo-root
+ (setq company-ebuild--dynamic-keywords-use-flags
+ (let ((awk-result
+ (shell-command-to-string (format awk-format repo-root))))
+ (mapcan (lambda (line)
+ (cond
+ ((not (string-prefix-p "#" line))
+ (list line))
+ (t
+ nil)))
+ (split-string awk-result "\n" t)))))))
+
+(defun company-ebuild--regenerate-dynamic-keywords-packages ()
+ "Set new content of the ‘company-ebuild--dynamic-keywords-packages’ variable."
+ (setq company-ebuild--dynamic-keywords-packages
+ (company-ebuild--packages)))
+
+(defun company-ebuild--regenerate-dynamic-keywords-licenses ()
+ "Set new content of the ‘company-ebuild--dynamic-keywords-licenses’ variable."
+ (let ((repo-root
+ (and buffer-file-name
+ (company-ebuild--find-repo-root buffer-file-name))))
+ (when repo-root
+ (setq company-ebuild--dynamic-keywords-licenses
+ (directory-files (expand-file-name "licenses" repo-root))))))
+
+(defun company-ebuild--regenerate-dynamic-keywords ()
+ "Regenerate dynamic keywords."
+ (company-ebuild--regenerate-dynamic-keywords-eclasses)
+ (company-ebuild--regenerate-dynamic-keywords-use-flags)
+ (company-ebuild--regenerate-dynamic-keywords-packages)
+ (company-ebuild--regenerate-dynamic-keywords-licenses))
+
+
+;;;###autoload
+(defun company-ebuild (command &optional arg &rest ignored)
+ "Company backend for editing Ebuild files.
+
+COMMAND, ARG and IGNORED are for Company.
+COMMAND is matched with `cl-case'.
+ARG is the completion argument for annotation and candidates."
+ (interactive (list 'interactive))
+ (cl-case command
+ (interactive
+ (company-begin-backend 'company-ebuild))
+ (prefix
+ (and (eq major-mode 'ebuild-mode) (company-grab-symbol)))
+ (annotation
+ (company-ebuild--annotation arg))
+ (candidates
+ ;; FIXME: Can not insert the "/" character.
+ (cl-remove-if-not (lambda (candidate)
+ (string-prefix-p arg candidate t))
+ (append company-ebuild--constant-keywords
+ (company-ebuild--dynamic-keywords)
+ (company-ebuild--executables arg))))))
+
+;;;###autoload
+(defun company-ebuild-setup ()
+ "Setup for Company-Ebuild."
+ ;; Force-enable `company-mode'.
+ (when (null company-mode)
+ (company-mode +1))
+ ;; Regenerate dynamic keywords.
+ (company-ebuild--regenerate-dynamic-keywords)
+ ;; Add the `company-ebuild' backend.
+ (cond
+ ((fboundp 'company-yasnippet)
+ (add-to-list 'company-backends '(company-ebuild :with company-yasnippet)))
+ (t
+ (add-to-list 'company-backends 'company-ebuild))))
+
+;;;###autoload
+(add-hook 'ebuild-mode-hook 'company-ebuild-setup)
+
+
+(provide 'company-ebuild)
+
+
+
+;;; company-ebuild.el ends here