dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

cider-jar.el (5539B)


      1 ;;; cider-jar.el --- Jar functionality for Clojure -*- lexical-binding: t -*-
      2 
      3 ;; Copyright © 2022 Arne Brasseur
      4 ;;
      5 ;; Author: Arne Brasseur <arne@arnebrasseur.net>
      6 
      7 ;; This program is free software: you can redistribute it and/or modify
      8 ;; it under the terms of the GNU General Public License as published by
      9 ;; the Free Software Foundation, either version 3 of the License, or
     10 ;; (at your option) any later version.
     11 
     12 ;; This program is distributed in the hope that it will be useful,
     13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 ;; GNU General Public License for more details.
     16 
     17 ;; You should have received a copy of the GNU General Public License
     18 ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
     19 
     20 ;; This file is not part of GNU Emacs.
     21 
     22 ;;; Commentary:
     23 
     24 ;; Dealing with JAR (Java archive) files, which are really just zip files in
     25 ;; disguise.  In particular downloading and retrieving the cider-nrepl jar.
     26 
     27 ;;; Code:
     28 
     29 (require 'url)
     30 (require 'arc-mode)
     31 (require 'map)
     32 
     33 
     34 (defvar cider-jar-cache-dir (expand-file-name "cider-cache" user-emacs-directory)
     35   "Location where we store downloaded files for later use.")
     36 
     37 (defvar cider-jar-content-cache (make-hash-table :test #'equal)
     38   "Nested hash table of jar-path -> file-path -> bool.
     39 This provides an efficient check to see if a file exists in a jar or not.")
     40 
     41 (defun cider-jar-clojars-url (group artifact version)
     42   "URL to download a specific jar from Clojars.
     43 GROUP, ARTIFACT, and VERSION are the components of the Maven coordinates."
     44   (concat "https://repo.clojars.org/" group "/" artifact "/"
     45           version
     46           "/cider-nrepl-"
     47           version
     48           ".jar"))
     49 
     50 (defun cider-jar-find-or-fetch (group artifact version)
     51   "Download the given jar off clojars and cache it.
     52 
     53 GROUP, ARTIFACT, and VERSION are the components of the Maven coordinates.
     54 Returns the path to the jar."
     55   (let* ((m2-path (expand-file-name (concat "~/.m2/repository/" group "/" artifact "/" version "/" artifact "-" version ".jar")))
     56          (clojars-url (cider-jar-clojars-url group artifact version))
     57          (cache-path (expand-file-name (replace-regexp-in-string "https://" "" clojars-url) cider-jar-cache-dir)))
     58     (cond
     59      ((file-exists-p m2-path) m2-path)
     60      ((file-exists-p cache-path) cache-path)
     61      (t
     62       (make-directory (file-name-directory cache-path) t)
     63       (url-copy-file clojars-url cache-path)
     64       cache-path))))
     65 
     66 (defun cider-jar--archive-zip-summarize ()
     67   "Forked version of `archive-zip-summarize'.
     68 Only read the information we need, and be version independent."
     69   (goto-char (- (point-max) (- 22 18)))
     70   (search-backward-regexp "[P]K\005\006")
     71   (let ((p (archive-l-e (+ (point) 16) 4))
     72         files)
     73     (when (or (= p #xffffffff) (= p -1))
     74       ;; If the offset of end-of-central-directory is 0xFFFFFFFF, this
     75       ;; is a Zip64 extended ZIP file format, and we need to glean the
     76       ;; info from Zip64 records instead.
     77       ;;
     78       ;; First, find the Zip64 end-of-central-directory locator.
     79       (search-backward "PK\006\007")
     80       (setq p (+ (point-min)
     81                  (archive-l-e (+ (point) 8) 8)))
     82       (goto-char p)
     83       ;; We should be at Zip64 end-of-central-directory record now.
     84       (or (string= "PK\006\006" (buffer-substring p (+ p 4)))
     85           (error "Unrecognized ZIP file format"))
     86       ;; Offset to central directory:
     87       (setq p (archive-l-e (+ p 48) 8)))
     88     (setq p (+ p (point-min)))
     89     (while (string= "PK\001\002" (buffer-substring p (+ p 4)))
     90       (let* ((fnlen   (archive-l-e (+ p 28) 2))
     91              (exlen   (archive-l-e (+ p 30) 2))
     92              (fclen   (archive-l-e (+ p 32) 2))
     93              (efnname (let ((str (buffer-substring (+ p 46) (+ p 46 fnlen))))
     94                         (decode-coding-string
     95                          str archive-file-name-coding-system))))
     96         (setq files (cons efnname files)
     97               p (+ p 46 fnlen exlen fclen))))
     98     files))
     99 
    100 (defun cider-jar-contents (jarfile)
    101   "Get the list of filenames in a jar (or zip) file.
    102 JARFILE is the location of the archive."
    103   (with-temp-buffer
    104     (set-buffer-multibyte nil)
    105     (setq buffer-file-coding-system 'binary)
    106     (insert-file-contents-literally jarfile nil)
    107     (cider-jar--archive-zip-summarize)))
    108 
    109 (defun cider-jar-contents-cached (jarfile)
    110   "Like cider-jar-contents, but cached.
    111 
    112 Instead of returning a list of strings this returns a hash table of string
    113 keys and values `t`, for quick lookup.  JARFILE is the location of the
    114 archive."
    115   (let ((m (map-elt cider-jar-content-cache jarfile)))
    116     (if m
    117         m
    118       (let ((m (make-hash-table :test #'equal)))
    119         (seq-do (lambda (path)
    120                   (puthash path t m))
    121                 (cider-jar-contents jarfile))
    122         (puthash jarfile m cider-jar-content-cache)
    123         m))))
    124 
    125 (defun cider-jar-contains-p (jarfile name)
    126   "Does the JARFILE contain a file with the given NAME?"
    127   (map-elt (cider-jar-contents-cached jarfile) name))
    128 
    129 (defun cider-jar-retrieve-resource (jarfile name)
    130   "Extract a file NAME from a JARFILE as a string."
    131   (make-directory archive-tmpdir :make-parents)
    132   (when (cider-jar-contains-p jarfile name)
    133     (let ((default-directory archive-tmpdir))
    134       (with-temp-buffer
    135         (set-buffer-multibyte nil)
    136         (setq buffer-file-coding-system 'binary)
    137         (archive-zip-extract jarfile name)
    138         (buffer-substring-no-properties (point-min) (point-max))))))
    139 
    140 (provide 'cider-jar)
    141 ;;; cider-jar.el ends here