github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/mods/bundled/epm.elv.go (about)

     1  package bundled
     2  
     3  const epmElv = `use re
     4  use str
     5  
     6  # Verbosity configuration
     7  debug-mode = $false
     8  
     9  # Configuration for common domains
    10  -default-domain-config = [
    11    &"github.com"= [
    12      &method= git
    13      &protocol= https
    14      &levels= 2
    15    ]
    16    &"bitbucket.org"= [
    17      &method= git
    18      &protocol= https
    19      &levels= 2
    20    ]
    21    &"gitlab.com"= [
    22      &method= git
    23      &protocol= https
    24      &levels= 2
    25    ]
    26  ]
    27  
    28  # Internal configuration
    29  -data-dir = ~/.elvish
    30  -lib-dir = $-data-dir/lib
    31  
    32  # General utility functions
    33  
    34  fn -debug [text]{
    35    if $debug-mode {
    36      print (styled '=> ' blue)
    37      echo $text
    38    }
    39  }
    40  
    41  fn -info [text]{
    42    print (styled '=> ' green)
    43    echo $text
    44  }
    45  
    46  fn -warn [text]{
    47    print (styled '=> ' yellow)
    48    echo $text
    49  }
    50  
    51  fn -error [text]{
    52    print (styled '=> ' red)
    53    echo $text
    54  }
    55  
    56  fn dest [pkg]{
    57    put $-lib-dir/$pkg
    58  }
    59  
    60  fn is-installed [pkg]{
    61    bool ?(test -e (dest $pkg))
    62  }
    63  
    64  fn -package-domain [pkg]{
    65    str:split &max=2 / $pkg | take 1
    66  }
    67  
    68  fn -package-without-domain [pkg]{
    69    str:split &max=2 / $pkg | drop 1 | str:join ''
    70  }
    71  
    72  # Merge two maps
    73  fn -merge [a b]{
    74    keys $b | each [k]{ a[$k] = $b[$k] }
    75    put $a
    76  }
    77  
    78  # Uppercase first letter of a string
    79  fn -first-upper [s]{
    80    put (echo $s[0] | tr '[:lower:]' '[:upper:]')$s[(count $s[0]):]
    81  }
    82  
    83  # Expand tilde at the beginning of a string to the home dir
    84  fn -tilde-expand [p]{
    85    re:replace "^~" $E:HOME $p
    86  }
    87  
    88  # Known method handlers. Each entry is indexed by method name (the
    89  # value of the "method" key in the domain configs), and must contain
    90  # two keys: install and upgrade, each one must be a closure that
    91  # receives two arguments: package name and the domain config entry
    92  #
    93  # - Method 'git' requires the key 'protocol' in the domain config,
    94  #   which has to be 'http' or 'https'
    95  # - Method 'rsync' requires the key 'location' in the domain config,
    96  #   which has to contain the directory where the domain files are
    97  #   stored. It can be any source location understood by the rsync
    98  #   command.
    99  -method-handler = [
   100    &git= [
   101      &src= [pkg dom-cfg]{
   102        put $dom-cfg[protocol]"://"$pkg
   103      }
   104  
   105      &install= [pkg dom-cfg]{
   106        dest = (dest $pkg)
   107        -info "Installing "$pkg
   108        mkdir -p $dest
   109        git clone ($-method-handler[git][src] $pkg $dom-cfg) $dest
   110      }
   111  
   112      &upgrade= [pkg dom-cfg]{
   113        dest = (dest $pkg)
   114        -info "Updating "$pkg
   115        try {
   116          git -C $dest pull
   117        } except _ {
   118            -error "Something failed, please check error above and retry."
   119        }
   120      }
   121    ]
   122  
   123    &rsync= [
   124      &src= [pkg dom-cfg]{
   125        put (-tilde-expand $dom-cfg[location])/(-package-without-domain $pkg)/
   126      }
   127  
   128      &install= [pkg dom-cfg]{
   129        dest = (dest $pkg)
   130        pkgd = (-package-without-domain $pkg)
   131        -info "Installing "$pkg
   132        rsync -av ($-method-handler[rsync][src] $pkg $dom-cfg) $dest
   133      }
   134  
   135      &upgrade= [pkg dom-cfg]{
   136        dest = (dest $pkg)
   137        pkgd = (-package-without-domain $pkg)
   138        if (not (is-installed $pkg)) {
   139          -error "Package "$pkg" is not installed."
   140          return
   141        }
   142        -info "Updating "$pkg
   143        rsync -av ($-method-handler[rsync][src] $pkg $dom-cfg) $dest
   144      }
   145    ]
   146  ]
   147  
   148  # Return the filename of the domain config file for the given domain
   149  # (regardless of whether it exists)
   150  fn -domain-config-file [dom]{
   151    put $-lib-dir/$dom/epm-domain.cfg
   152  }
   153  
   154  # Return the filename of the metadata file for the given package
   155  # (regardless of whether it exists)
   156  fn -package-metadata-file [pkg]{
   157    put (dest $pkg)/metadata.json
   158  }
   159  
   160  fn -write-domain-config [dom]{
   161    cfgfile = (-domain-config-file $dom)
   162    mkdir -p (dirname $cfgfile)
   163    if (has-key $-default-domain-config $dom) {
   164      put $-default-domain-config[$dom] | to-json > $cfgfile
   165    } else {
   166      -error "No default config exists for domain "$dom"."
   167    }
   168  }
   169  
   170  # Returns the domain config for a given domain parsed from JSON.
   171  # If the file does not exist but we have a built-in
   172  # definition, then we return the default. Otherwise we return $false,
   173  # so the result can always be checked with 'if'.
   174  fn -domain-config [dom]{
   175    cfgfile = (-domain-config-file $dom)
   176    cfg = $false
   177    if ?(test -f $cfgfile) {
   178      # If the config file exists, read it...
   179      cfg = (cat $cfgfile | from-json)
   180      -debug "Read domain config for "$dom": "(to-string $cfg)
   181    } else {
   182      # ...otherwise check if we have a default config for the domain, and save it
   183      if (has-key $-default-domain-config $dom) {
   184        cfg = $-default-domain-config[$dom]
   185        -debug "No existing config for "$dom", using the default: "(to-string $cfg)
   186      } else {
   187        -debug "No existing config for "$dom" and no default available."
   188      }
   189    }
   190    put $cfg
   191  }
   192  
   193  
   194  # Return the method by which a package is installed
   195  fn -package-method [pkg]{
   196    dom = (-package-domain $pkg)
   197    cfg = (-domain-config $dom)
   198    if $cfg {
   199      put $cfg[method]
   200    } else {
   201      put $false
   202    }
   203  }
   204  
   205  # Invoke package operations defined in $-method-handler above
   206  fn -package-op [pkg what]{
   207    dom = (-package-domain $pkg)
   208    cfg = (-domain-config $dom)
   209    if $cfg {
   210      method = $cfg[method]
   211      if (has-key $-method-handler $method) {
   212        if (has-key $-method-handler[$method] $what) {
   213          $-method-handler[$method][$what] $pkg $cfg
   214        } else {
   215          fail "Unknown operation '"$what"' for package "$pkg
   216        }
   217      } else {
   218        fail "Unknown method '"$method"', specified in in config file "(-domain-config-file $dom)
   219      }
   220    } else {
   221      -error "No config for domain '"$dom"'."
   222    }
   223  }
   224  
   225  # Uninstall a single package by removing its directory
   226  fn -uninstall-package [pkg]{
   227    if (not (is-installed $pkg)) {
   228      -error "Package "$pkg" is not installed."
   229      return
   230    }
   231    dest = (dest $pkg)
   232    -info "Removing package "$pkg
   233    rm -rf $dest
   234  }
   235  
   236  ######################################################################
   237  # Main user-facing functions
   238  
   239  # Read and parse the package metadata, if it exists
   240  fn metadata [pkg]{
   241    # Base metadata attributes
   242    res = [
   243      &name= $pkg
   244      &method= (-package-method $pkg)
   245      &src= (-package-op $pkg src)
   246      &dst= (dest $pkg)
   247      &installed= (is-installed $pkg)
   248    ]
   249    # Merge with package-specified attributes, if any
   250    file = (-package-metadata-file $pkg)
   251    if (and (is-installed $pkg) ?(test -f $file)) {
   252      res = (-merge (cat $file | from-json) $res)
   253    }
   254    put $res
   255  }
   256  
   257  # Print out information about a package
   258  fn query [pkg]{
   259    data = (metadata $pkg)
   260    special-keys = [name method installed src dst]
   261    echo (styled "Package "$data[name] cyan)
   262    if $data[installed] {
   263      echo (styled "Installed at "$data[dst] green)
   264    } else {
   265      echo (styled "Not installed" red)
   266    }
   267    echo (styled "Source:" blue) $data[method] $data[src]
   268    keys $data | each [key]{
   269      if (not (has-value $special-keys $key)) {
   270        val = $data[$key]
   271        if (eq (kind-of $val) list) {
   272          val = (str:join ", " $val)
   273        }
   274        echo (styled (-first-upper $key)":" blue) $val
   275      }
   276    }
   277  }
   278  
   279  # List installed packages
   280  fn installed {
   281    put $-lib-dir/*[nomatch-ok] | each [dir]{
   282      dom = (str:replace $-lib-dir/ '' $dir)
   283      cfg = (-domain-config $dom)
   284      # Only list domains for which we know the config, so that the user
   285      # can have his own non-package directories under ~/.elvish/lib
   286      # without conflicts.
   287      if $cfg {
   288        lvl = $cfg[levels]
   289        pat = '^\Q'$-lib-dir'/\E('(repeat (+ $lvl 1) '[^/]+' | str:join '/')')/$'
   290        put (each [d]{ re:find $pat $d } [ $-lib-dir/$dom/**[nomatch-ok]/ ] )[groups][1][text]
   291      }
   292    }
   293  }
   294  
   295  # epm:list is an alias for epm:installed
   296  fn list { installed }
   297  
   298  # Install and upgrade are method-specific, so we call the
   299  # corresponding functions using -package-op
   300  fn install [&silent-if-installed=$false @pkgs]{
   301    if (eq $pkgs []) {
   302      -error "You must specify at least one package."
   303      return
   304    }
   305    for pkg $pkgs {
   306      if (is-installed $pkg) {
   307        if (not $silent-if-installed) {
   308          -info "Package "$pkg" is already installed."
   309        }
   310      } else {
   311        -package-op $pkg install
   312        # Check if there are any dependencies to install
   313        metadata = (metadata $pkg)
   314        if (has-key $metadata dependencies) {
   315          deps = $metadata[dependencies]
   316          -info "Installing dependencies: "(str:join " " $deps)
   317          # If the installation of dependencies fails, uninstall the
   318          # target package (leave any already-installed dependencies in
   319          # place)
   320          try {
   321            install $@deps
   322          } except e {
   323            -error "Dependency installation failed. Uninstalling "$pkg", please check the errors above and try again."
   324            -uninstall-package $pkg
   325          }
   326        }
   327      }
   328    }
   329  }
   330  
   331  fn upgrade [@pkgs]{
   332    if (eq $pkgs []) {
   333      pkgs = [(installed)]
   334      -info 'Upgrading all installed packages'
   335    }
   336    for pkg $pkgs {
   337      if (not (is-installed $pkg)) {
   338        -error "Package "$pkg" is not installed."
   339      } else {
   340        -package-op $pkg upgrade
   341      }
   342    }
   343  }
   344  
   345  # Uninstall is the same for everyone, just remove the directory
   346  fn uninstall [@pkgs]{
   347    if (eq $pkgs []) {
   348      -error 'You must specify at least one package.'
   349      return
   350    }
   351    for pkg $pkgs {
   352      -uninstall-package $pkg
   353    }
   354  }`