changeset 157:2260eaac02ae

add puppet mode
author Jordi Gutiérrez Hermoso <jordigh@octave.org>
date Thu, 18 May 2017 11:33:58 -0400
parents c745e2cc79ee
children c09e8544288c
files elpa/epl-0.8/epl-autoloads.el elpa/epl-0.8/epl-pkg.el elpa/epl-0.8/epl.el elpa/pkg-info-0.6/pkg-info-autoloads.el elpa/pkg-info-0.6/pkg-info-pkg.el elpa/pkg-info-0.6/pkg-info.el elpa/puppet-mode-0.3/puppet-mode-autoloads.el elpa/puppet-mode-0.3/puppet-mode-pkg.el elpa/puppet-mode-0.3/puppet-mode.el elpa/puppet-mode-readme.txt
diffstat 10 files changed, 2087 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/elpa/epl-0.8/epl-autoloads.el
@@ -0,0 +1,15 @@
+;;; epl-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+
+;;;### (autoloads nil nil ("epl.el") (22782 38539 334113 509000))
+
+;;;***
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; epl-autoloads.el ends here
new file mode 100644
--- /dev/null
+++ b/elpa/epl-0.8/epl-pkg.el
@@ -0,0 +1,1 @@
+(define-package "epl" "0.8" "Emacs Package Library" '((cl-lib "0.3")) :url "http://github.com/cask/epl" :keywords '("convenience"))
new file mode 100644
--- /dev/null
+++ b/elpa/epl-0.8/epl.el
@@ -0,0 +1,695 @@
+;;; epl.el --- Emacs Package Library -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2013-2015 Sebastian Wiesner
+;; Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2015 Free Software
+
+;; Author: Sebastian Wiesner <swiesner@lunaryorn.com>
+;; Maintainer: Johan Andersson <johan.rejeep@gmail.com>
+;;     Sebastian Wiesner <swiesner@lunaryorn.com>
+;; Version: 0.8
+;; Package-Version: 0.8
+;; Package-Requires: ((cl-lib "0.3"))
+;; Keywords: convenience
+;; URL: http://github.com/cask/epl
+
+;; This file is NOT part of GNU Emacs.
+
+;; This program 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 3 of the License, or
+;; (at your option) any later version.
+
+;; This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; A package management library for Emacs, based on package.el.
+
+;; The purpose of this library is to wrap all the quirks and hassle of
+;; package.el into a sane API.
+
+;; The following functions comprise the public interface of this library:
+
+;;; Package directory selection
+
+;; `epl-package-dir' gets the directory of packages.
+
+;; `epl-default-package-dir' gets the default package directory.
+
+;; `epl-change-package-dir' changes the directory of packages.
+
+;;; Package system management
+
+;; `epl-initialize' initializes the package system and activates all
+;; packages.
+
+;; `epl-reset' resets the package system.
+
+;; `epl-refresh' refreshes all package archives.
+
+;; `epl-add-archive' adds a new package archive.
+
+;;; Package objects
+
+;; Struct `epl-requirement' describes a requirement of a package with `name' and
+;; `version' slots.
+
+;; `epl-requirement-version-string' gets a requirement version as string.
+
+;; Struct `epl-package' describes an installed or installable package with a
+;; `name' and some internal `description'.
+
+;; `epl-package-version' gets the version of a package.
+
+;; `epl-package-version-string' gets the version of a package as string.
+
+;; `epl-package-summary' gets the summary of a package.
+
+;; `epl-package-requirements' gets the requirements of a package.
+
+;; `epl-package-directory' gets the installation directory of a package.
+
+;; `epl-package-from-buffer' creates a package object for the package contained
+;; in the current buffer.
+
+;; `epl-package-from-file' creates a package object for a package file, either
+;; plain lisp or tarball.
+
+;; `epl-package-from-descriptor-file' creates a package object for a package
+;; description (i.e. *-pkg.el) file.
+
+;;; Package database access
+
+;; `epl-package-installed-p' determines whether a package is installed, either
+;; built-in or explicitly installed.
+
+;; `epl-package-outdated-p' determines whether a package is outdated, that is,
+;; whether a package with a higher version number is available.
+
+;; `epl-built-in-packages', `epl-installed-packages', `epl-outdated-packages'
+;; and `epl-available-packages' get all packages built-in, installed, outdated,
+;; or available for installation respectively.
+
+;; `epl-find-built-in-package', `epl-find-installed-packages' and
+;; `epl-find-available-packages' find built-in, installed and available packages
+;; by name.
+
+;; `epl-find-upgrades' finds all upgradable packages.
+
+;; `epl-built-in-p' return true if package is built-in to Emacs.
+
+;;; Package operations
+
+;; `epl-install-file' installs a package file.
+
+;; `epl-package-install' installs a package.
+
+;; `epl-package-delete' deletes a package.
+
+;; `epl-upgrade' upgrades packages.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'package)
+
+
+(unless (fboundp #'define-error)
+  ;; `define-error' for 24.3 and earlier, copied from subr.el
+  (defun define-error (name message &optional parent)
+    "Define NAME as a new error signal.
+MESSAGE is a string that will be output to the echo area if such an error
+is signaled without being caught by a `condition-case'.
+PARENT is either a signal or a list of signals from which it inherits.
+Defaults to `error'."
+    (unless parent (setq parent 'error))
+    (let ((conditions
+           (if (consp parent)
+               (apply #'append
+                      (mapcar (lambda (parent)
+                                (cons parent
+                                      (or (get parent 'error-conditions)
+                                          (error "Unknown signal `%s'" parent))))
+                              parent))
+             (cons parent (get parent 'error-conditions)))))
+      (put name 'error-conditions
+           (delete-dups (copy-sequence (cons name conditions))))
+      (when message (put name 'error-message message)))))
+
+(defsubst epl--package-desc-p (package)
+  "Whether PACKAGE is a `package-desc' object.
+
+Like `package-desc-p', but return nil, if `package-desc-p' is not
+defined as function."
+  (and (fboundp 'package-desc-p) (package-desc-p package)))
+
+
+;;; EPL errors
+(define-error 'epl-error "EPL error")
+
+(define-error 'epl-invalid-package "Invalid EPL package" 'epl-error)
+
+(define-error 'epl-invalid-package-file "Invalid EPL package file"
+  'epl-invalid-package)
+
+
+;;; Package directory
+(defun epl-package-dir ()
+  "Get the directory of packages."
+  package-user-dir)
+
+(defun epl-default-package-dir ()
+  "Get the default directory of packages."
+  (eval (car (get 'package-user-dir 'standard-value))))
+
+(defun epl-change-package-dir (directory)
+  "Change the directory of packages to DIRECTORY."
+  (setq package-user-dir directory)
+  (epl-initialize))
+
+
+;;; Package system management
+(defvar epl--load-path-before-initialize nil
+  "Remember the load path for `epl-reset'.")
+
+(defun epl-initialize (&optional no-activate)
+  "Load Emacs Lisp packages and activate them.
+
+With NO-ACTIVATE non-nil, do not activate packages."
+  (setq epl--load-path-before-initialize load-path)
+  (package-initialize no-activate))
+
+(defalias 'epl-refresh 'package-refresh-contents)
+
+(defun epl-add-archive (name url)
+  "Add a package archive with NAME and URL."
+  (add-to-list 'package-archives (cons name url)))
+
+(defun epl-reset ()
+  "Reset the package system.
+
+Clear the list of installed and available packages, the list of
+package archives and reset the package directory."
+  (setq package-alist nil
+        package-archives nil
+        package-archive-contents nil
+        load-path epl--load-path-before-initialize)
+  (when (boundp 'package-obsolete-alist) ; Legacy package.el
+    (setq package-obsolete-alist nil))
+  (epl-change-package-dir (epl-default-package-dir)))
+
+
+;;; Package structures
+(cl-defstruct (epl-requirement
+               (:constructor epl-requirement-create))
+  "Structure describing a requirement.
+
+Slots:
+
+`name' The name of the required package, as symbol.
+
+`version' The version of the required package, as version list."
+  name
+  version)
+
+(defun epl-requirement-version-string (requirement)
+  "The version of a REQUIREMENT, as string."
+  (package-version-join (epl-requirement-version requirement)))
+
+(cl-defstruct (epl-package (:constructor epl-package-create))
+  "Structure representing a package.
+
+Slots:
+
+`name' The package name, as symbol.
+
+`description' The package description.
+
+The format package description varies between package.el
+variants.  For `package-desc' variants, it is simply the
+corresponding `package-desc' object.  For legacy variants, it is
+a vector `[VERSION REQS DOCSTRING]'.
+
+Do not access `description' directly, but instead use the
+`epl-package' accessors."
+  name
+  description)
+
+(defmacro epl-package-as-description (var &rest body)
+  "Cast VAR to a package description in BODY.
+
+VAR is a symbol, bound to an `epl-package' object.  This macro
+casts this object to the `description' object, and binds the
+description to VAR in BODY."
+  (declare (indent 1))
+  (unless (symbolp var)
+    (signal 'wrong-type-argument (list #'symbolp var)))
+  `(if (epl-package-p ,var)
+       (let ((,var (epl-package-description ,var)))
+         ,@body)
+     (signal 'wrong-type-argument (list #'epl-package-p ,var))))
+
+(defsubst epl-package--package-desc-p (package)
+  "Whether the description of PACKAGE is a `package-desc'."
+  (epl--package-desc-p (epl-package-description package)))
+
+(defun epl-package-version (package)
+  "Get the version of PACKAGE, as version list."
+  (epl-package-as-description package
+    (cond
+     ((fboundp 'package-desc-version) (package-desc-version package))
+     ;; Legacy
+     ((fboundp 'package-desc-vers)
+      (let ((version (package-desc-vers package)))
+        (if (listp version) version (version-to-list version))))
+     (:else (error "Cannot get version from %S" package)))))
+
+(defun epl-package-version-string (package)
+  "Get the version from a PACKAGE, as string."
+  (package-version-join (epl-package-version package)))
+
+(defun epl-package-summary (package)
+  "Get the summary of PACKAGE, as string."
+  (epl-package-as-description package
+    (cond
+     ((fboundp 'package-desc-summary) (package-desc-summary package))
+     ((fboundp 'package-desc-doc) (package-desc-doc package)) ; Legacy
+     (:else (error "Cannot get summary from %S" package)))))
+
+(defsubst epl-requirement--from-req (req)
+  "Create a `epl-requirement' from a `package-desc' REQ."
+  (let  ((version (cadr req)))
+    (epl-requirement-create :name (car req)
+                            :version (if (listp version) version
+                                       (version-to-list version)))))
+
+(defun epl-package-requirements (package)
+  "Get the requirements of PACKAGE.
+
+The requirements are a list of `epl-requirement' objects."
+  (epl-package-as-description package
+    (mapcar #'epl-requirement--from-req (package-desc-reqs package))))
+
+(defun epl-package-directory (package)
+  "Get the directory PACKAGE is installed to.
+
+Return the absolute path of the installation directory of
+PACKAGE, or nil, if PACKAGE is not installed."
+  (cond
+   ((fboundp 'package-desc-dir)
+    (package-desc-dir (epl-package-description package)))
+   ((fboundp 'package--dir)
+    (package--dir (symbol-name (epl-package-name package))
+                  (epl-package-version-string package)))
+   (:else (error "Cannot get package directory from %S" package))))
+
+(defun epl-package-->= (pkg1 pkg2)
+  "Determine whether PKG1 is before PKG2 by version."
+  (not (version-list-< (epl-package-version pkg1)
+                       (epl-package-version pkg2))))
+
+(defun epl-package--from-package-desc (package-desc)
+  "Create an `epl-package' from a PACKAGE-DESC.
+
+PACKAGE-DESC is a `package-desc' object, from recent package.el
+variants."
+  (if (and (fboundp 'package-desc-name)
+           (epl--package-desc-p package-desc))
+      (epl-package-create :name (package-desc-name package-desc)
+                          :description package-desc)
+    (signal 'wrong-type-argument (list 'epl--package-desc-p package-desc))))
+
+(defun epl-package--parse-info (info)
+  "Parse a package.el INFO."
+  (if (epl--package-desc-p info)
+      (epl-package--from-package-desc info)
+    ;; For legacy package.el, info is a vector [NAME REQUIRES DESCRIPTION
+    ;; VERSION COMMENTARY].  We need to re-shape this vector into the
+    ;; `package-alist' format [VERSION REQUIRES DESCRIPTION] to attach it to the
+    ;; new `epl-package'.
+    (let ((name (intern (aref info 0)))
+          (info (vector (aref info 3) (aref info 1) (aref info 2))))
+      (epl-package-create :name name :description info))))
+
+(defun epl-package-from-buffer (&optional buffer)
+  "Create an `epl-package' object from BUFFER.
+
+BUFFER defaults to the current buffer.
+
+Signal `epl-invalid-package' if the buffer does not contain a
+valid package file."
+  (let ((info (with-current-buffer (or buffer (current-buffer))
+                (condition-case err
+                    (package-buffer-info)
+                  (error (signal 'epl-invalid-package (cdr err)))))))
+    (epl-package--parse-info info)))
+
+(defun epl-package-from-lisp-file (file-name)
+  "Parse the package headers the file at FILE-NAME.
+
+Return an `epl-package' object with the header metadata."
+  (with-temp-buffer
+    (insert-file-contents file-name)
+    (condition-case err
+        (epl-package-from-buffer (current-buffer))
+      ;; Attach file names to invalid package errors
+      (epl-invalid-package
+       (signal 'epl-invalid-package-file (cons file-name (cdr err))))
+      ;; Forward other errors
+      (error (signal (car err) (cdr err))))))
+
+(defun epl-package-from-tar-file (file-name)
+  "Parse the package tarball at FILE-NAME.
+
+Return a `epl-package' object with the meta data of the tarball
+package in FILE-NAME."
+  (condition-case nil
+      ;; In legacy package.el, `package-tar-file-info' takes the name of the tar
+      ;; file to parse as argument.  In modern package.el, it has no arguments
+      ;; and works on the current buffer.  Hence, we just try to call the legacy
+      ;; version, and if that fails because of a mismatch between formal and
+      ;; actual arguments, we use the modern approach.  To avoid spurious
+      ;; signature warnings by the byte compiler, we suppress warnings when
+      ;; calling the function.
+      (epl-package--parse-info (with-no-warnings
+                                 (package-tar-file-info file-name)))
+    (wrong-number-of-arguments
+     (with-temp-buffer
+       (insert-file-contents-literally file-name)
+       ;; Switch to `tar-mode' to enable extraction of the file.  Modern
+       ;; `package-tar-file-info' relies on `tar-mode', and signals an error if
+       ;; called in a buffer with a different mode.
+       (tar-mode)
+       (epl-package--parse-info (with-no-warnings
+                                  (package-tar-file-info)))))))
+
+(defun epl-package-from-file (file-name)
+  "Parse the package at FILE-NAME.
+
+Return an `epl-package' object with the meta data of the package
+at FILE-NAME."
+  (if (string-match-p (rx ".tar" string-end) file-name)
+      (epl-package-from-tar-file file-name)
+    (epl-package-from-lisp-file file-name)))
+
+(defun epl-package--parse-descriptor-requirement (requirement)
+  "Parse a REQUIREMENT in a package descriptor."
+  ;; This function is only called on legacy package.el.  On package-desc
+  ;; package.el, we just let package.el do the work.
+  (cl-destructuring-bind (name version-string) requirement
+    (list name (version-to-list version-string))))
+
+(defun epl-package-from-descriptor-file (descriptor-file)
+  "Load a `epl-package' from a package DESCRIPTOR-FILE.
+
+A package descriptor is a file defining a new package.  Its name
+typically ends with -pkg.el."
+  (with-temp-buffer
+    (insert-file-contents descriptor-file)
+    (goto-char (point-min))
+    (let ((sexp (read (current-buffer))))
+      (unless (eq (car sexp) 'define-package)
+        (error "%S is no valid package descriptor" descriptor-file))
+      (if (and (fboundp 'package-desc-from-define)
+               (fboundp 'package-desc-name))
+          ;; In Emacs snapshot, we can conveniently call a function to parse the
+          ;; descriptor
+          (let ((desc (apply #'package-desc-from-define (cdr sexp))))
+            (epl-package-create :name (package-desc-name desc)
+                                :description desc))
+        ;; In legacy package.el, we must manually deconstruct the descriptor,
+        ;; because the load function has eval's the descriptor and has a lot of
+        ;; global side-effects.
+        (cl-destructuring-bind
+            (name version-string summary requirements) (cdr sexp)
+          (epl-package-create
+           :name (intern name)
+           :description
+           (vector (version-to-list version-string)
+                   (mapcar #'epl-package--parse-descriptor-requirement
+                           ;; Strip the leading `quote' from the package list
+                           (cadr requirements))
+                   summary)))))))
+
+
+;;; Package database access
+(defun epl-package-installed-p (package)
+  "Determine whether a PACKAGE is installed.
+
+PACKAGE is either a package name as symbol, or a package object."
+  (let ((name (if (epl-package-p package)
+                  (epl-package-name package)
+                package))
+        (version (when (epl-package-p package)
+                   (epl-package-version package))))
+    (package-installed-p name version)))
+
+(defun epl--parse-built-in-entry (entry)
+  "Parse an ENTRY from the list of built-in packages.
+
+Return the corresponding `epl-package' object."
+  (if (fboundp 'package--from-builtin)
+      ;; In package-desc package.el, convert the built-in package to a
+      ;; `package-desc' and convert that to an `epl-package'
+      (epl-package--from-package-desc (package--from-builtin entry))
+    (epl-package-create :name (car entry) :description (cdr entry))))
+
+(defun epl-built-in-packages ()
+  "Get all built-in packages.
+
+Return a list of `epl-package' objects."
+  ;; This looks mighty strange, but it's the only way to force package.el to
+  ;; build the list of built-in packages.  Without this, `package--builtins'
+  ;; might be empty.
+  (package-built-in-p 'foo)
+  (mapcar #'epl--parse-built-in-entry package--builtins))
+
+(defun epl-find-built-in-package (name)
+  "Find a built-in package with NAME.
+
+NAME is a package name, as symbol.
+
+Return the built-in package as `epl-package' object, or nil if
+there is no built-in package with NAME."
+  (when (package-built-in-p name)
+    ;; We must call `package-built-in-p' *before* inspecting
+    ;; `package--builtins', because otherwise `package--builtins' might be
+    ;; empty.
+    (epl--parse-built-in-entry (assq name package--builtins))))
+
+(defun epl-package-outdated-p (package)
+  "Determine whether a PACKAGE is outdated.
+
+A package is outdated, if there is an available package with a
+higher version.
+
+PACKAGE is either a package name as symbol, or a package object.
+In the former case, test the installed or built-in package with
+the highest version number, in the later case, test the package
+object itself.
+
+Return t, if the package is outdated, or nil otherwise."
+  (let* ((package (if (epl-package-p package)
+                      package
+                    (or (car (epl-find-installed-packages package))
+                        (epl-find-built-in-package package))))
+         (available (car (epl-find-available-packages
+                          (epl-package-name package)))))
+    (and package available (version-list-< (epl-package-version package)
+                                           (epl-package-version available)))))
+
+(defun epl--parse-package-list-entry (entry)
+  "Parse a list of packages from ENTRY.
+
+ENTRY is a single entry in a package list, e.g. `package-alist',
+`package-archive-contents', etc.  Typically it is a cons cell,
+but the exact format varies between package.el versions.  This
+function tries to parse all known variants.
+
+Return a list of `epl-package' objects parsed from ENTRY."
+  (let ((descriptions (cdr entry)))
+    (cond
+     ((listp descriptions)
+      (sort (mapcar #'epl-package--from-package-desc descriptions)
+            #'epl-package-->=))
+     ;; Legacy package.el has just a single package in an entry, which is a
+     ;; standard description vector
+     ((vectorp descriptions)
+      (list (epl-package-create :name (car entry)
+                                :description descriptions)))
+     (:else (error "Cannot parse entry %S" entry)))))
+
+(defun epl-installed-packages ()
+  "Get all installed packages.
+
+Return a list of package objects."
+  (apply #'append (mapcar #'epl--parse-package-list-entry package-alist)))
+
+(defsubst epl--filter-outdated-packages (packages)
+  "Filter outdated packages from PACKAGES."
+  (let (res)
+    (dolist (package packages)
+      (when (epl-package-outdated-p package)
+        (push package res)))
+    (nreverse res)))
+
+(defun epl-outdated-packages ()
+  "Get all outdated packages, as in `epl-package-outdated-p'.
+
+Return a list of package objects."
+  (epl--filter-outdated-packages (epl-installed-packages)))
+
+(defsubst epl--find-package-in-list (name list)
+  "Find a package by NAME in a package LIST.
+
+Return a list of corresponding `epl-package' objects."
+  (let ((entry (assq name list)))
+    (when entry
+      (epl--parse-package-list-entry entry))))
+
+(defun epl-find-installed-package (name)
+  "Find the latest installed package by NAME.
+
+NAME is a package name, as symbol.
+
+Return the installed package with the highest version number as
+`epl-package' object, or nil, if no package with NAME is
+installed."
+  (car (epl-find-installed-packages name)))
+(make-obsolete 'epl-find-installed-package 'epl-find-installed-packages "0.7")
+
+(defun epl-find-installed-packages (name)
+  "Find all installed packages by NAME.
+
+NAME is a package name, as symbol.
+
+Return a list of all installed packages with NAME, sorted by
+version number in descending order.  Return nil, if there are no
+packages with NAME."
+  (epl--find-package-in-list name package-alist))
+
+(defun epl-available-packages ()
+  "Get all packages available for installation.
+
+Return a list of package objects."
+  (apply #'append (mapcar #'epl--parse-package-list-entry
+                          package-archive-contents)))
+
+(defun epl-find-available-packages (name)
+  "Find available packages for NAME.
+
+NAME is a package name, as symbol.
+
+Return a list of available packages for NAME, sorted by version
+number in descending order.  Return nil, if there are no packages
+for NAME."
+  (epl--find-package-in-list name package-archive-contents))
+
+(cl-defstruct (epl-upgrade
+               (:constructor epl-upgrade-create))
+  "Structure describing an upgradable package.
+Slots:
+
+`installed' The installed package
+
+`available' The package available for installation."
+  installed
+  available)
+
+(defun epl-find-upgrades (&optional packages)
+  "Find all upgradable PACKAGES.
+
+PACKAGES is a list of package objects to upgrade, defaulting to
+all installed packages.
+
+Return a list of `epl-upgrade' objects describing all upgradable
+packages."
+  (let ((packages (or packages (epl-installed-packages)))
+        upgrades)
+    (dolist (pkg packages)
+      (let* ((version (epl-package-version pkg))
+             (name (epl-package-name pkg))
+             ;; Find the latest available package for NAME
+             (available-pkg (car (epl-find-available-packages name)))
+             (available-version (when available-pkg
+                                  (epl-package-version available-pkg))))
+        (when (and available-version (version-list-< version available-version))
+          (push (epl-upgrade-create :installed pkg
+                                    :available available-pkg)
+                upgrades))))
+    (nreverse upgrades)))
+
+(defalias 'epl-built-in-p 'package-built-in-p)
+
+
+;;; Package operations
+
+(defalias 'epl-install-file 'package-install-file)
+
+(defun epl-package-install (package &optional force)
+  "Install a PACKAGE.
+
+PACKAGE is a `epl-package' object.  If FORCE is given and
+non-nil, install PACKAGE, even if it is already installed."
+  (when (or force (not (epl-package-installed-p package)))
+    (if (epl-package--package-desc-p package)
+        (package-install (epl-package-description package))
+      ;; The legacy API installs by name.  We have no control over versioning,
+      ;; etc.
+      (package-install (epl-package-name package)))))
+
+(defun epl-package-delete (package)
+  "Delete a PACKAGE.
+
+PACKAGE is a `epl-package' object to delete."
+  ;; package-delete allows for packages being trashed instead of fully deleted.
+  ;; Let's prevent his silly behavior
+  (let ((delete-by-moving-to-trash nil))
+    ;; The byte compiler will warn us that we are calling `package-delete' with
+    ;; the wrong number of arguments, since it can't infer that we guarantee to
+    ;; always call the correct version.  Thus we suppress all warnings when
+    ;; calling `package-delete'.  I wish there was a more granular way to
+    ;; disable just that specific warning, but it is what it is.
+    (if (epl-package--package-desc-p package)
+        (with-no-warnings
+          (package-delete (epl-package-description package)))
+      ;; The legacy API deletes by name (as string!) and version instead by
+      ;; descriptor.  Hence `package-delete' takes two arguments.  For some
+      ;; insane reason, the arguments are strings here!
+      (let ((name (symbol-name (epl-package-name package)))
+            (version (epl-package-version-string package)))
+        (with-no-warnings
+          (package-delete name version))
+        ;; Legacy package.el does not remove the deleted package
+        ;; from the `package-alist', so we do it manually here.
+        (let ((pkg (assq (epl-package-name package) package-alist)))
+          (when pkg
+            (setq package-alist (delq pkg package-alist))))))))
+
+(defun epl-upgrade (&optional packages preserve-obsolete)
+  "Upgrade PACKAGES.
+
+PACKAGES is a list of package objects to upgrade, defaulting to
+all installed packages.
+
+The old versions of the updated packages are deleted, unless
+PRESERVE-OBSOLETE is non-nil.
+
+Return a list of all performed upgrades, as a list of
+`epl-upgrade' objects."
+  (let ((upgrades (epl-find-upgrades packages)))
+    (dolist (upgrade upgrades)
+      (epl-package-install (epl-upgrade-available upgrade) 'force)
+      (unless preserve-obsolete
+        (epl-package-delete (epl-upgrade-installed upgrade))))
+    upgrades))
+
+(provide 'epl)
+
+;;; epl.el ends here
new file mode 100644
--- /dev/null
+++ b/elpa/pkg-info-0.6/pkg-info-autoloads.el
@@ -0,0 +1,122 @@
+;;; pkg-info-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+
+;;;### (autoloads nil "pkg-info" "pkg-info.el" (22782 38550 605214
+;;;;;;  495000))
+;;; Generated autoloads from pkg-info.el
+
+(autoload 'pkg-info-library-original-version "pkg-info" "\
+Get the original version in the header of LIBRARY.
+
+The original version is stored in the X-Original-Version header.
+This header is added by the MELPA package archive to preserve
+upstream version numbers.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version from the header of LIBRARY as list.  Signal an
+error if the LIBRARY was not found or had no X-Original-Version
+header.
+
+See Info node `(elisp)Library Headers' for more information
+about library headers.
+
+\(fn LIBRARY &optional SHOW)" t nil)
+
+(autoload 'pkg-info-library-version "pkg-info" "\
+Get the version in the header of LIBRARY.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version from the header of LIBRARY as list.  Signal an
+error if the LIBRARY was not found or had no proper header.
+
+See Info node `(elisp)Library Headers' for more information
+about library headers.
+
+\(fn LIBRARY &optional SHOW)" t nil)
+
+(autoload 'pkg-info-defining-library-original-version "pkg-info" "\
+Get the original version of the library defining FUNCTION.
+
+The original version is stored in the X-Original-Version header.
+This header is added by the MELPA package archive to preserve
+upstream version numbers.
+
+If SHOW is non-nil, show the version in mini-buffer.
+
+This function is mainly intended to find the version of a major
+or minor mode, i.e.
+
+   (pkg-info-defining-library-version 'flycheck-mode)
+
+Return the version of the library defining FUNCTION.  Signal an
+error if FUNCTION is not a valid function, if its defining
+library was not found, or if the library had no proper version
+header.
+
+\(fn FUNCTION &optional SHOW)" t nil)
+
+(autoload 'pkg-info-defining-library-version "pkg-info" "\
+Get the version of the library defining FUNCTION.
+
+If SHOW is non-nil, show the version in mini-buffer.
+
+This function is mainly intended to find the version of a major
+or minor mode, i.e.
+
+   (pkg-info-defining-library-version 'flycheck-mode)
+
+Return the version of the library defining FUNCTION.  Signal an
+error if FUNCTION is not a valid function, if its defining
+library was not found, or if the library had no proper version
+header.
+
+\(fn FUNCTION &optional SHOW)" t nil)
+
+(autoload 'pkg-info-package-version "pkg-info" "\
+Get the version of an installed PACKAGE.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version as list, or nil if PACKAGE is not installed.
+
+\(fn PACKAGE &optional SHOW)" t nil)
+
+(autoload 'pkg-info-version-info "pkg-info" "\
+Obtain complete version info for LIBRARY and PACKAGE.
+
+LIBRARY is a symbol denoting a named feature, or a library name
+as string.  PACKAGE is a symbol denoting an ELPA package.  If
+omitted or nil, default to LIBRARY.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+When called interactively, prompt for LIBRARY.  When called
+interactively with prefix argument, prompt for PACKAGE as well.
+
+Return a string with complete version information for LIBRARY.
+This version information contains the version from the headers of
+LIBRARY, and the version of the installed PACKAGE, the LIBRARY is
+part of.  If PACKAGE is not installed, or if the PACKAGE version
+is the same as the LIBRARY version, do not include a package
+version.
+
+\(fn LIBRARY &optional PACKAGE SHOW)" t nil)
+
+;;;***
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; pkg-info-autoloads.el ends here
new file mode 100644
--- /dev/null
+++ b/elpa/pkg-info-0.6/pkg-info-pkg.el
@@ -0,0 +1,1 @@
+(define-package "pkg-info" "0.6" "Information about packages" '((epl "0.8")) :url "https://github.com/lunaryorn/pkg-info.el" :keywords '("convenience"))
new file mode 100644
--- /dev/null
+++ b/elpa/pkg-info-0.6/pkg-info.el
@@ -0,0 +1,331 @@
+;;; pkg-info.el --- Information about packages       -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2013-2015  Sebastian Wiesner <swiesner@lunaryorn.com>
+
+;; Author: Sebastian Wiesner <swiesner@lunaryorn.com>
+;; URL: https://github.com/lunaryorn/pkg-info.el
+;; Package-Version: 0.6
+;; Keywords: convenience
+;; Version: 0.6
+;; Package-Requires: ((epl "0.8"))
+
+;; This file is not part of GNU Emacs.
+
+;; This program 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 3 of the License, or
+;; (at your option) any later version.
+
+;; This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library extracts information from installed packages.
+
+;;;; Functions:
+
+;; `pkg-info-library-version' extracts the version from the header of a library.
+;;
+;; `pkg-info-defining-library-version' extracts the version from the header of a
+;;  library defining a function.
+;;
+;; `pkg-info-package-version' gets the version of an installed package.
+;;
+;; `pkg-info-format-version' formats a version list as human readable string.
+;;
+;; `pkg-info-version-info' returns complete version information for a specific
+;; package.
+;;
+;; `pkg-info-get-melpa-recipe' gets the MELPA recipe for a package.
+;;
+;; `pkg-info-get-melpa-fetcher' gets the fetcher used to build a package on
+;; MELPA.
+;;
+;; `pkg-info-wiki-package-p' determines whether a package was build from
+;; EmacsWiki on MELPA.
+
+;;; Code:
+
+(require 'epl)
+
+(require 'lisp-mnt)
+(require 'find-func)
+(require 'json)                         ; `json-read'
+(require 'url-http)                     ; `url-http-parse-response'
+
+(defvar url-http-end-of-headers)
+
+
+;;; Version information
+(defun pkg-info-format-version (version)
+  "Format VERSION as human-readable string.
+
+Return a human-readable string representing VERSION."
+  ;; XXX: Find a better, more flexible way of formatting?
+  (package-version-join version))
+
+(defsubst pkg-info--show-version-and-return (version show)
+  "Show and return VERSION.
+
+When SHOW is non-nil, show VERSION in minibuffer.
+
+Return VERSION."
+  (when show
+    (message (if (listp version) (pkg-info-format-version version) version)))
+  version)
+
+(defun pkg-info--read-library ()
+  "Read a library from minibuffer."
+  (completing-read "Load library: "
+                   (apply-partially 'locate-file-completion-table
+                                    load-path
+                                    (get-load-suffixes))))
+
+(defun pkg-info--read-function ()
+  "Read a function name from minibuffer."
+  (let ((input (completing-read "Function: " obarray #'boundp :require-match)))
+    (if (string= input "") nil (intern input))))
+
+(defun pkg-info--read-package ()
+  "Read a package name from minibuffer."
+  (let* ((installed (epl-installed-packages))
+         (names (sort (mapcar (lambda (pkg)
+                                (symbol-name (epl-package-name pkg)))
+                              installed)
+                      #'string<))
+         (default (car names)))
+    (completing-read "Installed package: " names nil 'require-match
+                     nil nil default)))
+
+(defun pkg-info-library-source (library)
+  "Get the source file of LIBRARY.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+Return the source file of LIBRARY as string."
+  (find-library-name (if (symbolp library) (symbol-name library) library)))
+
+(defun pkg-info-defining-library (function)
+  "Get the source file of the library defining FUNCTION.
+
+FUNCTION is a function symbol.
+
+Return the file name of the library as string.  Signal an error
+if the library does not exist, or if the definition of FUNCTION
+was not found."
+  (unless (functionp function)
+    (signal 'wrong-type-argument (list 'functionp function)))
+  (let ((library (symbol-file function 'defun)))
+    (unless library
+      (error "Can't find definition of %s" function))
+    library))
+
+(defun pkg-info-x-original-version (file)
+  "Read the X-Original-Version header from FILE.
+
+Return the value as version list, or return nil if FILE lacks
+this header.  Signal an error, if the value of the header is not
+a valid version."
+  (let ((version-str (with-temp-buffer
+                       (insert-file-contents file)
+                       (lm-header "X-Original-Version"))))
+    (when version-str
+      (version-to-list version-str))))
+
+;;;###autoload
+(defun pkg-info-library-original-version (library &optional show)
+  "Get the original version in the header of LIBRARY.
+
+The original version is stored in the X-Original-Version header.
+This header is added by the MELPA package archive to preserve
+upstream version numbers.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version from the header of LIBRARY as list.  Signal an
+error if the LIBRARY was not found or had no X-Original-Version
+header.
+
+See Info node `(elisp)Library Headers' for more information
+about library headers."
+  (interactive (list (pkg-info--read-library) t))
+  (let ((version (pkg-info-x-original-version
+                  (pkg-info-library-source library))))
+    (if version
+        (pkg-info--show-version-and-return version show)
+      (error "Library %s has no original version" library))))
+
+;;;###autoload
+(defun pkg-info-library-version (library &optional show)
+  "Get the version in the header of LIBRARY.
+
+LIBRARY is either a symbol denoting a named feature, or a library
+name as string.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version from the header of LIBRARY as list.  Signal an
+error if the LIBRARY was not found or had no proper header.
+
+See Info node `(elisp)Library Headers' for more information
+about library headers."
+  (interactive (list (pkg-info--read-library) t))
+  (let* ((source (pkg-info-library-source library))
+         (version (epl-package-version (epl-package-from-file source))))
+    (pkg-info--show-version-and-return version show)))
+
+;;;###autoload
+(defun pkg-info-defining-library-original-version (function &optional show)
+  "Get the original version of the library defining FUNCTION.
+
+The original version is stored in the X-Original-Version header.
+This header is added by the MELPA package archive to preserve
+upstream version numbers.
+
+If SHOW is non-nil, show the version in mini-buffer.
+
+This function is mainly intended to find the version of a major
+or minor mode, i.e.
+
+   (pkg-info-defining-library-version 'flycheck-mode)
+
+Return the version of the library defining FUNCTION.  Signal an
+error if FUNCTION is not a valid function, if its defining
+library was not found, or if the library had no proper version
+header."
+  (interactive (list (pkg-info--read-function) t))
+  (pkg-info-library-original-version (pkg-info-defining-library function) show))
+
+;;;###autoload
+(defun pkg-info-defining-library-version (function &optional show)
+  "Get the version of the library defining FUNCTION.
+
+If SHOW is non-nil, show the version in mini-buffer.
+
+This function is mainly intended to find the version of a major
+or minor mode, i.e.
+
+   (pkg-info-defining-library-version 'flycheck-mode)
+
+Return the version of the library defining FUNCTION.  Signal an
+error if FUNCTION is not a valid function, if its defining
+library was not found, or if the library had no proper version
+header."
+  (interactive (list (pkg-info--read-function) t))
+  (pkg-info-library-version (pkg-info-defining-library function) show))
+
+;;;###autoload
+(defun pkg-info-package-version (package &optional show)
+  "Get the version of an installed PACKAGE.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+Return the version as list, or nil if PACKAGE is not installed."
+  (interactive (list (pkg-info--read-package) t))
+  (let* ((name (if (stringp package) (intern package) package))
+         (package (car (epl-find-installed-packages name))))
+    (unless package
+      (error "Can't find installed package %s" name))
+    (pkg-info--show-version-and-return (epl-package-version package) show)))
+
+;;;###autoload
+(defun pkg-info-version-info (library &optional package show)
+  "Obtain complete version info for LIBRARY and PACKAGE.
+
+LIBRARY is a symbol denoting a named feature, or a library name
+as string.  PACKAGE is a symbol denoting an ELPA package.  If
+omitted or nil, default to LIBRARY.
+
+If SHOW is non-nil, show the version in the minibuffer.
+
+When called interactively, prompt for LIBRARY.  When called
+interactively with prefix argument, prompt for PACKAGE as well.
+
+Return a string with complete version information for LIBRARY.
+This version information contains the version from the headers of
+LIBRARY, and the version of the installed PACKAGE, the LIBRARY is
+part of.  If PACKAGE is not installed, or if the PACKAGE version
+is the same as the LIBRARY version, do not include a package
+version."
+  (interactive (list (pkg-info--read-library)
+                     (when current-prefix-arg
+                       (pkg-info--read-package))
+                     t))
+  (let* ((package (or package (if (stringp library) (intern library) library)))
+         (orig-version (condition-case nil
+                           (pkg-info-library-original-version library)
+                         (error nil)))
+         ;; If we have X-Original-Version, we assume that MELPA replaced the
+         ;; library version with its generated version, so we use the
+         ;; X-Original-Version header instead, and ignore the library version
+         ;; header
+         (lib-version (or orig-version (pkg-info-library-version library)))
+         (pkg-version (condition-case nil
+                          (pkg-info-package-version package)
+                        (error nil)))
+         (version (if (and pkg-version
+                           (not (version-list-= lib-version pkg-version)))
+                      (format "%s (package: %s)"
+                              (pkg-info-format-version lib-version)
+                              (pkg-info-format-version pkg-version))
+                    (pkg-info-format-version lib-version))))
+    (pkg-info--show-version-and-return version show)))
+
+(defconst pkg-info-melpa-recipe-url "http://melpa.org/recipes.json"
+  "The URL from which to fetch MELPA recipes.")
+
+(defvar pkg-info-melpa-recipes nil
+  "An alist of MELPA recipes.")
+
+(defun pkg-info-retrieve-melpa-recipes ()
+  "Retrieve MELPA recipes from MELPA archive."
+  (let ((buffer (url-retrieve-synchronously pkg-info-melpa-recipe-url)))
+    (with-current-buffer buffer
+      (unwind-protect
+          (let ((response-code (url-http-parse-response)))
+            (unless (equal response-code 200)
+              (error "Failed to retrieve MELPA recipes from %s (code %s)"
+                     pkg-info-melpa-recipe-url response-code))
+            (goto-char url-http-end-of-headers)
+            (json-read))
+        (when (and buffer (buffer-live-p buffer))
+          (kill-buffer buffer))))))
+
+(defun pkg-info-get-melpa-recipes ()
+  "Get MELPA recipes."
+  (setq pkg-info-melpa-recipes
+        (or pkg-info-melpa-recipes
+            (pkg-info-retrieve-melpa-recipes))))
+
+(defun pkg-info-get-melpa-recipe (package)
+  "Get the MELPA recipe for PACKAGE.
+
+Return nil if PACKAGE is not on MELPA."
+  (cdr (assq package (pkg-info-get-melpa-recipes))))
+
+(defun pkg-info-get-melpa-fetcher (package)
+  "Get the MELPA fetcher for PACKAGE."
+  (cdr (assq 'fetcher (pkg-info-get-melpa-recipe package))))
+
+(defun pkg-info-wiki-package-p (package)
+  "Determine whether PACKAGE is build from the EmacsWiki."
+  (equal (pkg-info-get-melpa-fetcher package) "wiki"))
+
+(provide 'pkg-info)
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; coding: utf-8
+;; End:
+
+;;; pkg-info.el ends here
new file mode 100644
--- /dev/null
+++ b/elpa/puppet-mode-0.3/puppet-mode-autoloads.el
@@ -0,0 +1,24 @@
+;;; puppet-mode-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (or (file-name-directory #$) (car load-path)))
+
+;;;### (autoloads nil "puppet-mode" "puppet-mode.el" (22782 38552
+;;;;;;  233246 702000))
+;;; Generated autoloads from puppet-mode.el
+
+(autoload 'puppet-mode "puppet-mode" "\
+
+
+\(fn)" t nil)
+
+(add-to-list 'auto-mode-alist '("\\.pp\\'" . puppet-mode))
+
+;;;***
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; puppet-mode-autoloads.el ends here
new file mode 100644
--- /dev/null
+++ b/elpa/puppet-mode-0.3/puppet-mode-pkg.el
@@ -0,0 +1,1 @@
+(define-package "puppet-mode" "0.3" "Major mode for Puppet manifests" '((emacs "24.1") (cl-lib "0.5") (pkg-info "0.4")) :url "https://github.com/lunaryorn/puppet-mode" :keywords '("languages"))
new file mode 100644
--- /dev/null
+++ b/elpa/puppet-mode-0.3/puppet-mode.el
@@ -0,0 +1,896 @@
+;;; puppet-mode.el --- Major mode for Puppet manifests  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2013, 2014  Sebastian Wiesner <lunaryorn@gmail.com>
+;; Copyright (C) 2013, 2014  Bozhidar Batsov <bozhidar@batsov.com>
+;; Copyright (C) 2011  Puppet Labs Inc
+
+;; Author: Bozhidar Batsov <bozhidar@batsov.com>
+;;     Sebastian Wiesner <lunaryorn@gmail.com>
+;;     Russ Allbery <rra@stanford.edu>
+;; Maintainer: Bozhidar Batsov <bozhidar@batsov.com>
+;;     Sebastian Wiesner <lunaryorn@gmail.com>
+;; URL: https://github.com/lunaryorn/puppet-mode
+;; Package-Version: 0.3
+;; Keywords: languages
+;; Version: 0.3
+;; Package-Requires: ((emacs "24.1") (cl-lib "0.5") (pkg-info "0.4"))
+
+;; This file is not part of GNU Emacs.
+
+;; This program 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 3 of the License, or
+;; (at your option) any later version.
+
+;; This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;; This file incorporates work covered by the following copyright and
+;; permission notice:
+
+;;   Licensed under the Apache License, Version 2.0 (the "License"); you may not
+;;   use this file except in compliance with the License.  You may obtain a copy
+;;   of the License at
+;;
+;;       http://www.apache.org/licenses/LICENSE-2.0
+;;
+;;   Unless required by applicable law or agreed to in writing, software
+;;   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+;;   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+;;   License for the specific language governing permissions and limitations
+;;   under the License.
+
+;;; Commentary:
+
+;; Major mode for Puppet manifests
+
+;;; Code:
+
+
+;;;; Compatibility
+(eval-and-compile
+  ;; `defvar-local' for Emacs 24.2 and below
+  (unless (fboundp 'defvar-local)
+    (defmacro defvar-local (var val &optional docstring)
+      "Define VAR as a buffer-local variable with default value VAL.
+Like `defvar' but additionally marks the variable as being automatically
+buffer-local wherever it is set."
+      (declare (debug defvar) (doc-string 3))
+      `(progn
+         (defvar ,var ,val ,docstring)
+         (make-variable-buffer-local ',var))))
+
+  ;; `setq-local' for Emacs 24.2 and below
+  (unless (fboundp 'setq-local)
+    (defmacro setq-local (var val)
+      "Set variable VAR to value VAL in current buffer."
+      `(set (make-local-variable ',var) ,val))))
+
+
+;;;; Requirements
+(require 'pkg-info)
+
+(require 'cl-lib)
+(require 'rx)
+(require 'align)
+
+
+;;;; Customization
+(defgroup puppet nil
+  "Puppet mastering in Emacs"
+  :prefix "puppet-"
+  :group 'languages
+  :link '(url-link :tag "Github" "https://github.com/lunaryorn/puppet-mode")
+  :link '(emacs-commentary-link :tag "Commentary" "puppet-mode"))
+
+(defcustom puppet-indent-level 2
+  "Indentation of Puppet statements."
+  :type 'integer
+  :group 'puppet
+  :safe 'integerp)
+
+(defcustom puppet-include-indent 2
+  "Indentation of continued Puppet include statements."
+  :type 'integer
+  :group 'puppet
+  :safe 'integerp)
+
+(defcustom puppet-indent-tabs-mode nil
+  "Indentation can insert tabs in puppet mode if this is non-nil."
+  :type 'boolean
+  :group 'puppet
+  :safe 'booleanp)
+
+(defcustom puppet-comment-column 32
+  "Indentation column of comments."
+  :type 'integer
+  :group 'puppet
+  :safe 'integerp)
+
+(defcustom puppet-fontify-variables-in-comments nil
+  "When non-nil, fontify variable references in comments."
+  :type 'boolean
+  :group 'puppet
+  :safe 'booleanp
+  :package-version '(puppet-mode . "0.3"))
+
+(defcustom puppet-validate-command "puppet parser validate --color=false"
+  "Command to validate the syntax of a Puppet manifest."
+  :type 'string
+  :group 'puppet)
+
+(defcustom puppet-lint-command
+  (concat
+   "puppet-lint --with-context "
+   "--log-format \"%{path}:%{linenumber}: %{kind}: %{message} (%{check})\"")
+  "Command to lint a Puppet manifest."
+  :type 'string
+  :group 'puppet
+  :package-version '(puppet-mode . "0.3"))
+
+(defcustom puppet-apply-command "puppet apply --verbose --noop"
+  "Command to apply a Puppet manifest."
+  :type 'string
+  :group 'puppet
+  :package-version '(puppet-mode . "0.3"))
+
+(defface puppet-regular-expression-literal
+  '((t :inherit font-lock-constant-face))
+  "Face for regular expression literals in Puppet."
+  :group 'puppet
+  :package-version '(puppet-mode . "0.3"))
+
+(defface puppet-escape-sequence
+  '((t :inherit font-lock-constant-face))
+  "Face for escape sequences in double-quoted strings-consed literals in Puppet."
+  :group 'puppet
+  :package-version '(puppet-mode . "0.3"))
+
+
+;;;; Version information
+(defun puppet-version (&optional show-version)
+  "Get the Puppet Mode version as string.
+
+If called interactively or if SHOW-VERSION is non-nil, show the
+version in the echo area and the messages buffer.
+
+The returned string includes both, the version from package.el
+and the library version, if both a present and different.
+
+If the version number could not be determined, signal an error,
+if called interactively, or if SHOW-VERSION is non-nil, otherwise
+just return nil."
+  (interactive (list t))
+  (let ((version (pkg-info-version-info 'puppet-mode)))
+    (when show-version
+      (message "Puppet Mode version: %s" version))
+    version))
+
+
+;;;; Utilities
+
+(defun puppet-syntax-context (&optional pos)
+  "Determine the syntax context at POS, defaulting to point.
+
+Return nil, if there is no special context at POS, or one of
+
+`comment'
+     POS is inside a comment
+
+`single-quoted'
+     POS is inside a single-quoted string
+
+`double-quoted'
+     POS is inside a double-quoted string"
+  (let ((state (save-excursion (syntax-ppss pos))))
+    (if (nth 4 state)
+        'comment
+      (cl-case (nth 3 state)
+        (?\' 'single-quoted)
+        (?\" 'double-quoted)))))
+
+(defun puppet-in-string-or-comment-p (&optional pos)
+  "Determine whether POS is inside a string or comment."
+  (not (null (puppet-syntax-context pos))))
+
+
+;;;; Specialized rx
+
+(eval-when-compile
+  (defconst puppet-rx-constituents
+    `(
+      ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_reserved.html#reserved-words
+      (keyword . ,(rx symbol-start
+                      (or "and" "case" "class" "default" "define" "else" "elsif"
+                          "false" "if" "in" "import" "inherits" "node" "or"
+                          "true" "undef" "unless")
+                      symbol-end))
+      ;; http://docs.puppetlabs.com/references/latest/function.html
+      (builtin-function . ,(rx symbol-start
+                               (or "alert" "collect" "contain"
+                                   "create_resources" "crit" "debug" "defined"
+                                   "each" "emerg" "err" "extlookup" "fail"
+                                   "file" "filter" "fqdn_rand" "generate"
+                                   "hiera" "hiera_array" "hiera_hash"
+                                   "hiera_include" "include" "info"
+                                   "inline_template" "lookup" "map" "md5"
+                                   "notice" "realize" "reduce" "regsubst"
+                                   "require" "search" "select" "sha1"
+                                   "shellquote" "slice" "split" "sprintf" "tag"
+                                   "tagged" "template" "versioncmp" "warning")
+                               symbol-end))
+      ;; http://docs.puppetlabs.com/references/latest/type.html
+      (builtin-type . ,(rx symbol-start
+                           (or "augeas" "computer" "cron" "exec" "file"
+                               "filebucket" "group" "host" "interface" "k5login"
+                               "macauthorization" "mailalias" "maillist" "mcx"
+                               "mount" "nagios_command" "nagios_contact"
+                               "nagios_contactgroup" "nagios_host"
+                               "nagios_hostdependency" "nagios_hostescalation"
+                               "nagios_hostextinfo" "nagios_hostgroup"
+                               "nagios_service" "nagios_servicedependency"
+                               "nagios_serviceescalation" "nagios_serviceextinfo"
+                               "nagios_servicegroup" "nagios_timeperiod" "notify"
+                               "package" "resources" "router" "schedule"
+                               "scheduled_task" "selboolean" "selmodule"
+                               "service" "ssh_authorized_key" "sshkey" "stage"
+                               "tidy" "user" "vlan" "yumrepo" "zfs" "zone"
+                               "zpool")
+                           symbol-end))
+      ;; http://docs.puppetlabs.com/references/stable/metaparameter.html.
+      ;; Strictly speaking, this is no meta parameter, but it's so common that
+      ;; it got a mention in the docs, see
+      ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_resources.html#ensure,
+      ;; so we'll consider it as metaparameter anyway
+      (builtin-metaparam . ,(rx symbol-start
+                                (or "alias" "audit" "before" "loglevel" "noop"
+                                    "notify" "require" "schedule" "stage"
+                                    "subscribe" "tag"
+                                    ;; Because it's so common and important
+                                    "ensure")
+                                symbol-end))
+      ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_reserved.html#classes-and-types
+      (resource-name . ,(rx symbol-start
+                            ;; Optional top-level scope
+                            (optional (any "a-z")
+                                      (zero-or-more (any "a-z" "0-9" "_")))
+                            ;; Nested sub-scopes
+                            (zero-or-more "::"
+                                          (any "a-z")
+                                          (zero-or-more (any "a-z" "0-9" "_")))
+                            symbol-end))
+      (cap-resource-name . ,(rx symbol-start
+                                ;; Optional top-level scope
+                                (optional (any "A-Z")
+                                          (zero-or-more (any "a-z" "0-9" "_")))
+                                ;; Nested sub-scopes
+                                (zero-or-more "::"
+                                              (any "A-Z")
+                                              (zero-or-more (any "a-z" "0-9" "_")))
+                                symbol-end))
+      ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_reserved.html#variables
+      (simple-variable-name . ,(rx symbol-start
+                                   (one-or-more (any "A-Z" "a-z" "0-9" "_"))
+                                   symbol-end))
+      (variable-name . ,(rx symbol-start
+                            ;; The optional scope designation
+                            (optional
+                             (optional (any "a-z")
+                                       (zero-or-more (any "A-Z" "a-z" "0-9" "_")))
+                             (zero-or-more "::"
+                                           (any "a-z")
+                                           (zero-or-more (any "A-Z" "a-z" "0-9" "_")))
+                             "::")
+                            ;; The final variable name
+                            (one-or-more (any "A-Z" "a-z" "0-9" "_"))
+                            symbol-end))
+      ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_datatypes.html#double-quoted-strings
+      (dq-escape . ,(rx (or line-start (not (any "\\")))
+                        (zero-or-more "\\\\")
+                        ;; We do not include \n and \', because these are
+                        ;; available in single-quoted strings as well
+                        (group "\\" (any ?\" ?$ ?n ?r ?t ?s)))))
+    "Additional special sexps for `puppet-rx'")
+
+  (defmacro puppet-rx (&rest sexps)
+    "Specialized `rx' variant for Puppet Mode.
+
+In addition to the standard forms of `rx', the following forms
+are available:
+
+`keyword'
+     Any valid Puppet keyword
+
+`builtin-function'
+     Any built-in Puppet function
+
+`builtin-type'
+     Any built-in Puppet type
+
+`builtin-metaparam'
+     Any built-in meta-parameter, and `ensure'
+
+`resource-name'
+     Any valid resource name, including scopes
+
+`cap-resource-name'
+     Any capitalized resource name, including capitalized scopes
+
+`simple-variable-name'
+     Any variable name without scopes, without leading dollar sign
+
+`variable-name'
+     Any variable name including scopes, without a leading dollar sign
+
+`dq-escape'
+     Special escape sequences for double-quoted strings"
+    (let ((rx-constituents (append puppet-rx-constituents rx-constituents)))
+      (cond ((null sexps)
+             (error "No regexp"))
+            ((cdr sexps)
+             (rx-to-string `(and ,@sexps) t))
+            (t
+             (rx-to-string (car sexps) t))))))
+
+
+;;;; Checking
+
+(defvar-local puppet-last-validate-command nil
+  "The last command used for validation.")
+
+(defvar-local puppet-last-lint-command nil
+  "The last command used for linting.")
+
+;; This variable is intentionally not buffer-local, since you typically only
+;; apply top-level manifests, but not class or type definitions.
+(defvar puppet-last-apply-command nil
+  "The last command used to apply a manifest.")
+
+(defun puppet-run-check-command (command buffer-name-template)
+  "Run COMMAND to check the current buffer."
+  (save-some-buffers (not compilation-ask-about-save) nil)
+  (compilation-start command nil (lambda (_)
+                                   (format buffer-name-template command))))
+
+(defun puppet-read-command (prompt previous-command default-command)
+  "Read a command from minibuffer with PROMPT."
+  (let ((filename (or (buffer-file-name) "")))
+    (read-string prompt (or previous-command
+                            (concat default-command " "
+                                    (shell-quote-argument filename))))))
+
+(defun puppet-validate (command)
+  "Validate the syntax of the current buffer with COMMAND.
+
+When called interactively, prompt for COMMAND."
+  (interactive (list (puppet-read-command "Validate command: "
+                                          puppet-last-validate-command
+                                          puppet-validate-command)))
+  (setq puppet-last-validate-command command)
+  (puppet-run-check-command command "*Puppet Validate: %s*"))
+
+(defun puppet-lint (command)
+  "Lint the current buffer with COMMAND.
+
+When called interactively, prompt for COMMAND."
+  (interactive (list (puppet-read-command "Lint command: "
+                                          puppet-last-lint-command
+                                          puppet-lint-command)))
+  (setq puppet-last-lint-command command)
+  (puppet-run-check-command command "*Puppet Lint: %s*"))
+
+(defun puppet-apply (command)
+  "Apply the current manifest with COMMAND.
+
+When called interactively, prompt for COMMAND."
+  (interactive (list (puppet-read-command "Apply command: "
+                                          puppet-last-apply-command
+                                          puppet-apply-command)))
+  (setq puppet-last-apply-command command)
+  (puppet-run-check-command command "*Puppet Apply: %s*"))
+
+
+;;;; Navigation
+;; TODO: Check which of these are still needed for SMIE
+
+(defun puppet-beginning-of-defun-function (&optional arg)
+  "Move to the ARG'th beginning of a block."
+  (let* ((arg (or arg 1))
+         (search (if (< arg 0) #'search-forward #'search-backward))
+         (steps (abs arg)))
+    (while (> steps 0)
+      (let ((pos (funcall search "{" nil 'no-error)))
+        ;; Skip over strings and comments
+        (while (and pos (puppet-in-string-or-comment-p pos))
+          (setq pos (funcall search "{" nil 'no-error)))
+        (if pos
+            (cl-decf steps)
+          ;; Drop out of outer loop
+          (setq steps 0))))
+    (when (< arg 0)
+      (backward-char))))
+
+
+;;;; Indentation code
+(defun puppet-block-indent ()
+  "If point is in a block, return the indentation of the first line of that
+block (the line containing the opening brace).  Used to set the indentation
+of the closing brace of a block."
+  (save-excursion
+    (beginning-of-defun)
+    (current-indentation)))
+
+(defun puppet-in-array ()
+  "If point is in an array, return the position of the opening '[' of
+that array, else return nil."
+  (save-excursion
+    (save-match-data
+      (let ((opoint (point))
+            (apoint (search-backward "[" nil t)))
+        (when apoint
+          ;; This is a bit of a hack and doesn't allow for strings.  We really
+          ;; want to parse by sexps at some point.
+          (let ((close-brackets (count-matches "]" apoint opoint))
+                (open-brackets 0))
+            (while (and apoint (> close-brackets open-brackets))
+              (setq apoint (search-backward "[" nil t))
+              (when apoint
+                (setq close-brackets (count-matches "]" apoint opoint))
+                (setq open-brackets (1+ open-brackets)))))
+          apoint)))))
+
+(defun puppet-in-include ()
+  "If point is in a continued list of include statements, return the position
+of the initial include plus puppet-include-indent."
+  (save-excursion
+    (save-match-data
+      (let ((include-column nil)
+            (not-found t))
+        (while not-found
+          (forward-line -1)
+          (cond
+           ((bobp)
+            (setq not-found nil))
+           ((looking-at "^\\s-*include\\s-+.*,\\s-*$")
+            (setq include-column
+                  (+ (current-indentation) puppet-include-indent))
+            (setq not-found nil))
+           ((not (looking-at ".*,\\s-*$"))
+            (setq not-found nil))))
+        include-column))))
+
+(defun puppet-indent-line ()
+  "Indent current line as puppet code."
+  (interactive)
+  (beginning-of-line)
+  (if (bobp)
+      (indent-line-to 0)                ; First line is always non-indented
+    (let ((not-indented t)
+          (array-start (puppet-in-array))
+          (include-start (puppet-in-include))
+          (block-indent (puppet-block-indent))
+          cur-indent)
+      (cond
+       (array-start
+        ;; This line probably starts with an element from an array.
+        ;; Indent the line to the same indentation as the first
+        ;; element in that array.  That is, this...
+        ;;
+        ;;    exec {
+        ;;      "add_puppetmaster_mongrel_startup_links":
+        ;;      command => "string1",
+        ;;      creates => [ "string2", "string3",
+        ;;      "string4", "string5",
+        ;;      "string6", "string7",
+        ;;      "string3" ],
+        ;;      refreshonly => true,
+        ;;    }
+        ;;
+        ;; ...should instead look like this:
+        ;;
+        ;;    exec {
+        ;;      "add_puppetmaster_mongrel_startup_links":
+        ;;      command => "string1",
+        ;;      creates => [ "string2", "string3",
+        ;;                   "string4", "string5",
+        ;;                   "string6", "string7",
+        ;;                   "string8" ],
+        ;;      refreshonly => true,
+        ;;    }
+        (save-excursion
+          (goto-char array-start)
+          (forward-char 1)
+          (re-search-forward "\\S-")
+          (forward-char -1)
+          (setq cur-indent (current-column))))
+       (include-start
+        (setq cur-indent include-start))
+       ((and (looking-at "^\\s-*},?\\s-*$") block-indent)
+        ;; This line contains a closing brace or a closing brace followed by a
+        ;; comma and we're at the inner block, so we should indent it matching
+        ;; the indentation of the opening brace of the block.
+        (setq cur-indent block-indent))
+       (t
+        ;; Otherwise, we did not start on a block-ending-only line.
+        (save-excursion
+          ;; Iterate backwards until we find an indentation hint
+          (while not-indented
+            (forward-line -1)
+            (cond
+             ;; Comment lines are ignored unless we're at the start of the
+             ;; buffer.
+             ((eq (puppet-syntax-context) 'comment)
+              (if (bobp)
+                  (setq not-indented nil)))
+
+             ;; Brace or paren on a line by itself will already be indented to
+             ;; the right level, so we can cheat and stop there.
+             ((looking-at "^\\s-*[\)}]\\s-*")
+              (setq cur-indent (current-indentation))
+              (setq not-indented nil))
+
+             ;; Brace (possibly followed by a comma) or paren not on a line by
+             ;; itself will be indented one level too much, but don't catch
+             ;; cases where the block is started and closed on the same line.
+             ((looking-at "^[^\n\({]*[\)}],?\\s-*$")
+              (setq cur-indent (- (current-indentation) puppet-indent-level))
+              (setq not-indented nil))
+
+             ;; Indent by one level more than the start of our block.  We lose
+             ;; if there is more than one block opened and closed on the same
+             ;; line but it's still unbalanced; hopefully people don't do that.
+             ((looking-at "^.*{[^\n}]*$")
+              (setq cur-indent (+ (current-indentation) puppet-indent-level))
+              (setq not-indented nil))
+
+             ;; Indent by one level if the line ends with an open paren.
+             ((looking-at "^.*\(\\s-*$")
+              (setq cur-indent (+ (current-indentation) puppet-indent-level))
+              (setq not-indented nil))
+
+             ;; Semicolon ends a block for a resource when multiple resources
+             ;; are defined in the same block, but try not to get the case of
+             ;; a complete resource on a single line wrong.
+             ((looking-at "^\\([^'\":\n]\\|\"[^\n\"]*\"\\|'[^\n']*'\\)*;\\s-*$")
+              (setq cur-indent (- (current-indentation) puppet-indent-level))
+              (setq not-indented nil))
+
+             ;; Indent an extra level after : since it introduces a resource.
+             ((looking-at "^.*:\\s-*$")
+              (setq cur-indent (+ (current-indentation) puppet-indent-level))
+              (setq not-indented nil))
+
+             ;; Start of buffer.
+             ((bobp)
+              (setq not-indented nil)))))
+
+        ;; If this line contains only a closing paren, we should lose one
+        ;; level of indentation.
+        (if (looking-at "^\\s-*\)\\s-*$")
+            (setq cur-indent (- cur-indent puppet-indent-level)))))
+
+      ;; We've figured out the indentation, so do it.
+      (if (and cur-indent (> cur-indent 0))
+          (indent-line-to cur-indent)
+        (indent-line-to 0)))))
+
+
+;;;; Font locking
+
+(defvar puppet-mode-syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?\' "\"'"  table)
+    (modify-syntax-entry ?\" "\"\"" table)
+    ;; C-style comments.  Yes, Puppet has these!
+    (modify-syntax-entry ?/ ". 14b" table)
+    (modify-syntax-entry ?* ". 23b" table)
+    (modify-syntax-entry ?#  "<"    table)
+    (modify-syntax-entry ?\n ">"    table)
+    (modify-syntax-entry ?\\ "\\"   table)
+    (modify-syntax-entry ?$  "'"    table)
+    (modify-syntax-entry ?-  "."    table)
+    (modify-syntax-entry ?\( "()"   table)
+    (modify-syntax-entry ?\) ")("   table)
+    (modify-syntax-entry ?\{ "(}"   table)
+    (modify-syntax-entry ?\} "){"   table)
+    (modify-syntax-entry ?\[ "(]"   table)
+    (modify-syntax-entry ?\] ")["   table)
+    table)
+  "Syntax table in use in `puppet-mode' buffers.")
+
+(defvar puppet-font-lock-keywords
+  `(
+    ;; Regular expression literals
+    (, (rx (group "/"
+                  (zero-or-more
+                   (or (not (any "/" "\\" "\n")) ; Not the end of a regexp
+                       (and "\\" not-newline)))  ; Any escaped character
+                  "/")) 1 'puppet-regular-expression-literal)
+    ;; Keywords
+    (,(puppet-rx keyword) 0 font-lock-keyword-face)
+    ;; Variables
+    (,(puppet-rx "$" variable-name) 0 font-lock-variable-name-face)
+    ;; Type declarations
+    (,(puppet-rx symbol-start (or "class" "define" "node") symbol-end
+                 (one-or-more space)
+                 (group resource-name))
+     1 font-lock-type-face)
+    ;; Resource usage, see
+    ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_resources.html
+    (,(puppet-rx symbol-start
+                 (group (repeat 0 2 "@") ; Virtual and exported resources
+                        resource-name)
+                 (zero-or-more space) "{")
+     1 font-lock-type-face)
+    ;; Resource defaults, see
+    ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_defaults.html
+    (,(puppet-rx (group cap-resource-name) (zero-or-more space) "{")
+     1 font-lock-type-face)
+    ;; Resource references, see
+    ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_datatypes.html#resource-references
+    (,(puppet-rx (group cap-resource-name) (zero-or-more space) "[")
+     1 font-lock-type-face)
+    ;; Resource collectors, see
+    ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_collectors.html
+    (,(puppet-rx (group cap-resource-name) (zero-or-more space)
+                 (optional "<")         ; Exported collector
+                 "<|")
+     1 font-lock-type-face)
+    ;; Negation
+    ("!" 0 font-lock-negation-char-face)
+    ;; Builtin meta parameters
+    (,(puppet-rx (group builtin-metaparam) (zero-or-more space) "=>")
+     1 font-lock-builtin-face)
+    ;; Built-in functions
+    (,(puppet-rx builtin-function) 0 font-lock-builtin-face)
+    ;; Variable expansions in strings and comments
+    (puppet-match-valid-expansion 1 font-lock-variable-name-face t)
+    (puppet-match-invalid-expansion 1 font-lock-warning-face t)
+    ;; Escape sequences in strings
+    (puppet-match-valid-escape 1 'puppet-escape-sequence t)
+    )
+  "Font lock keywords for Puppet Mode.")
+
+(defun puppet-match-property (property context limit)
+  "Match a PROPERTY in CONTEXT before LIMIT.
+
+PROPERTY is the text property to look for.  CONTEXT is one of
+`single-quoted', `double-quoted' or `comment', or a list with any
+of these symbols.  The expansion will only match if it is in any
+given CONTEXT."
+  (when (symbolp context)
+    (setq context (list context)))
+  (let* ((pos (next-single-char-property-change (point) property nil limit)))
+    (when (and pos (> pos (point)))
+      (goto-char pos)
+      (let* ((value (get-text-property pos property)))
+        (if (memq (car value) context)
+            (progn (set-match-data (cdr value)) t)
+          (puppet-match-property property context limit))))))
+
+(defun puppet-match-valid-expansion (limit)
+  "Match a valid expansion before LIMIT.
+
+A valid expansion is a variable expansion in a double-quoted
+string."
+  (let ((valid-contexts '(double-quoted)))
+    (when puppet-fontify-variables-in-comments
+      (push 'comment valid-contexts))
+    (puppet-match-property 'puppet-expansion valid-contexts limit)))
+
+(defun puppet-match-invalid-expansion (limit)
+  "Match an invalid expansion before LIMIT.
+
+An invalid expansion is a variable expansion in a single-quoted
+string."
+  (puppet-match-property 'puppet-expansion 'single-quoted limit))
+
+(defun puppet-match-valid-escape (limit)
+  "Match a valid escape sequence before LIMIT."
+  (puppet-match-property 'puppet-escape 'double-quoted limit))
+
+(defun puppet-syntax-propertize-match (property)
+  "Propertize a match with PROPERTY.
+
+When in a special syntax context, add PROPERTY to the first
+character of the first group of the current `match-data'.  The
+value of PROPERTY is `(CONTEXT . MATCH-DATA)', where CONTEXT is
+one of nil, `single-quoted', `double-quoted' or `comment' and
+denotes the surrounding context, and MATCH-DATA is the original
+match data from propertization."
+  (let* ((beg (match-beginning 1))
+         (context (puppet-syntax-context)))
+    (when context
+      (put-text-property beg (1+ beg) property
+                         (cons context (match-data))))))
+
+(defun puppet-syntax-propertize-function (start end)
+  "Propertize text between START and END.
+
+Used as `syntax-propertize-function' in Puppet Mode."
+  (let ((case-fold-search nil))
+    (goto-char start)
+    (remove-text-properties start end '(puppet-expansion puppet-escape))
+    (funcall
+     (syntax-propertize-rules
+      ;; Find escape sequences and variable expansions
+      ((puppet-rx dq-escape)
+       (1 (ignore (puppet-syntax-propertize-match 'puppet-escape))))
+      ((puppet-rx (or line-start (not (any "\\")))
+                  (zero-or-more "\\\\")
+                  (group "$" (or (and "{" variable-name "}") variable-name)))
+       (1 (ignore (puppet-syntax-propertize-match 'puppet-expansion)))))
+     start end)))
+
+
+;;;; Alignment
+
+;; Configure alignment
+(add-to-list 'align-sq-string-modes 'puppet-mode)
+(add-to-list 'align-dq-string-modes 'puppet-mode)
+(add-to-list 'align-open-comment-modes 'puppet-mode)
+
+(defconst puppet-mode-align-rules
+  '((puppet-resource-arrow
+     (regexp . "\\(\\s-*\\)=>\\(\\s-*\\)")
+     (group  . (1 2))
+     (modes  . '(puppet-mode))))
+  "Align rules for Puppet Mode.")
+
+(defun puppet-align-block ()
+  "Align the current block."
+  (interactive)
+  (save-excursion
+    (beginning-of-defun)
+    (let ((beg (point)))
+      (end-of-defun)
+      (align beg (point)))))
+
+
+;;;; Imenu
+
+(defun puppet-imenu-collect-entries (pattern)
+  "Collect all index entries matching PATTERN.
+
+The first matching group of PATTERN is used as title and position
+for each entry."
+  (goto-char (point-min))
+  (let ((case-fold-search nil)
+        entries)
+    (while (re-search-forward pattern nil 'no-error)
+      (let ((context (save-excursion (syntax-ppss (match-beginning 0)))))
+        ;; Skip this match if it's inside a string or comment
+        (unless (or (nth 3 context) (nth 4 context))
+          (push (cons (match-string 1) (match-beginning 1)) entries))))
+    (nreverse entries)))
+
+(defun puppet-imenu-create-index ()
+  "Create an IMenu index for the current buffer."
+  (let ((case-fold-search nil)
+        ;; Variable assignments
+        (variables (puppet-imenu-collect-entries
+                    (puppet-rx (group "$" simple-variable-name)
+                               (zero-or-more space) "=")))
+        ;; Resource defaults
+        (defaults (puppet-imenu-collect-entries
+                   (puppet-rx cap-resource-name (zero-or-more space) "{")))
+        ;; Nodes, classes and defines
+        (nodes (puppet-imenu-collect-entries
+                (puppet-rx symbol-start "node" symbol-end
+                           (one-or-more space) resource-name)))
+        (classes (puppet-imenu-collect-entries
+                  (puppet-rx symbol-start "class" symbol-end
+                             (one-or-more space) resource-name)))
+        (defines (puppet-imenu-collect-entries
+                  (puppet-rx symbol-start "define" symbol-end
+                             (one-or-more space) resource-name)))
+        resources)
+    ;; Resources are a little more complicated since we need to extract the type
+    ;; and the name
+    (goto-char (point-min))
+    (while (re-search-forward
+            (puppet-rx symbol-start
+                       (group (repeat 0 2 "@") ; Virtual and exported resources
+                              resource-name)
+                       (zero-or-more space) "{"
+                       ;; FIXME: Support condensed forms
+                       (zero-or-more space)
+                       (group (one-or-more not-newline)) ":")
+            nil 'no-error)
+      ;; FIXME: Doesn't work for any condensed forms, see
+      ;; http://docs.puppetlabs.com/puppet/latest/reference/lang_resources.html#condensed-forms
+      ;; We probably need to be more clever here
+      (push (cons (concat (match-string 1) " " (match-string 2))
+                  (match-beginning 1))
+            resources))
+    (let (index
+          ;; Keep this in reversed order, for `push'
+          (parts (list (cons "Variables" (nreverse variables))
+                       (cons "Defaults" (nreverse defaults))
+                       (cons "Definitions" (nreverse defines))
+                       (cons "Classes" (nreverse classes))
+                       (cons "Nodes" (nreverse nodes)))))
+      (dolist (part parts)
+        (when (cdr part)
+          (push part index)))
+      (append index (nreverse resources)))))
+
+
+;;;; Major mode definition
+
+(defvar puppet-mode-map
+  (let ((map (make-sparse-keymap)))
+    ;; Editing
+    (define-key map (kbd "C-c C-a") #'puppet-align-block)
+    ;; Navigation
+    (define-key map (kbd "C-c C-j") #'imenu)
+    ;; Apply manifests
+    (define-key map (kbd "C-c C-c") #'puppet-apply)
+    ;; Linting and validation
+    (define-key map (kbd "C-c C-v") #'puppet-validate)
+    (define-key map (kbd "C-c C-l") #'puppet-lint)
+    ;; The menu bar
+    (easy-menu-define puppet-menu map "Puppet Mode menu"
+      `("Puppet"
+        :help "Puppet-specific Features"
+        ["Align the current block" puppet-align-block
+         :help "Align parameters in the current block"]
+        "-"
+        ["Jump to resource/variable" imenu
+         :help "Jump to a resource or variable"]
+        "-"
+        ["Apply manifest" puppet-apply :help "Apply a Puppet manifest"]
+        "-"
+        ["Validate file syntax" puppet-validate
+         :help "Validate the syntax of this file"]
+        ["Lint file" puppet-lint
+         :help "Check the file for semantic issues"]))
+    map)
+  "Key map for Puppet Mode buffers.")
+
+;;;###autoload
+(define-derived-mode puppet-mode prog-mode "Puppet" ()
+  "Major mode for editing Puppet manifests.
+
+\\{puppet-mode-map}"
+  ;; Misc variables
+  (setq-local require-final-newline t)
+  ;; Comment setup
+  (setq-local comment-start "# ")
+  (setq-local comment-start-skip "#+ *")
+  (setq-local comment-use-syntax t)
+  (setq-local comment-end "")
+  (setq-local comment-auto-fill-only-comments t)
+  (setq comment-column puppet-comment-column)
+  ;; Navigation (TODO: Will we still need this with SMIE?)
+  (setq-local beginning-of-defun-function #'puppet-beginning-of-defun-function)
+  ;; Indentation
+  (setq-local indent-line-function 'puppet-indent-line)
+  (setq indent-tabs-mode puppet-indent-tabs-mode)
+  ;; Paragaphs
+  (setq-local paragraph-ignore-fill-prefix t)
+  (setq-local paragraph-start "\f\\|[ \t]*$\\|#$")
+  (setq-local paragraph-separate "\\([ \t\f]*\\|#\\)$")
+  ;; Font locking
+  (setq font-lock-defaults '((puppet-font-lock-keywords) nil nil))
+  (setq-local syntax-propertize-function #'puppet-syntax-propertize-function)
+  ;; Alignment
+  (setq align-mode-rules-list puppet-mode-align-rules)
+  ;; IMenu
+  (setq imenu-create-index-function #'puppet-imenu-create-index))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.pp\\'" . puppet-mode))
+
+(provide 'puppet-mode)
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; End:
+
+;;; puppet-mode.el ends here
new file mode 100644
--- /dev/null
+++ b/elpa/puppet-mode-readme.txt
@@ -0,0 +1,1 @@
+Major mode for Puppet manifests