Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
simendsjo committed Mar 8, 2024
1 parent 89a88d4 commit 460b027
Show file tree
Hide file tree
Showing 9 changed files with 642 additions and 0 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
on: [push]
jobs:
tests:
strategy:
matrix:
os:
- ubuntu-latest
- macos-latest
# I cannot get windows working for any implementations
# - windows-latest
lisp:
- sbcl-bin
# The following doesn't work
# - ecl
# - ccl-bin
# - abcl-bin
# - clasp-bin
# - cmu-bin
# - clisp-head
runs-on: ${{ matrix.os }}
env:
LISP: ${{ matrix.lisp }}
steps:
- uses: actions/checkout@v4
- uses: 40ants/setup-lisp@v4
with:
asdf-system: sijo-version/tests
qlfile-template: |
dist ultralisp http://dist.ultralisp.org
- uses: 40ants/run-tests@v2
with:
asdf-system: sijo-version
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
*.FASL
*.fasl
*.lisp-temp
*.dfsl
*.pfsl
*.d64fsl
*.p64fsl
*.lx64fsl
*.lx32fsl
*.dx64fsl
*.dx32fsl
*.fx64fsl
*.fx32fsl
*.sx64fsl
*.sx32fsl
*.wx64fsl
*.wx32fsl
183 changes: 183 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
Semantic version for your systems. Opinionated defaults, but flexible.

* Installation
Clone repository
#+begin_src bash :eval never
git clone [email protected]:simendsjo/sijo-version.git ~/quicklisp/local-projects/sijo-version
#+end_src

* Use
Load library
#+begin_src lisp :exports code
(ql:quickload :sijo-version)
#+end_src

#+RESULTS:
| :SIJO-VERSION |

Call ~version~ to get a calculated semver version.

#+begin_src lisp :exports both :eval never
(sijo-version:version)
#+end_src

#+RESULTS:
: 0.1.0-0.dev.27+prototype.7aeedb4d32927e19a5e9ed406ac5735db0fd20dd.20240301145715Z

When supplying the values manually, those will be used instead

#+begin_src lisp :exports both
(sijo-version:version :version "1.2.3" :pre-release nil :build-metadata nil)
#+end_src

#+RESULTS:
: 1.2.3

#+begin_src lisp :exports both
(sijo-version:version :version "1.2.3" :pre-release "some-prerelease" :build-metadata nil)
#+end_src

#+RESULTS:
: 1.2.3-some-prerelease

#+begin_src lisp :exports both
(sijo-version:version :version "1.2.3" :pre-release "some-prerelease" :build-metadata "some-build-info")
#+end_src

#+RESULTS:
: 1.2.3-some-prerelease+some-build-info

* Documentation for ~version~

Warning: This is bound to get out of date, so look at the documentation in the source.

#+begin_src lisp :exports results
(setf (cdr (assoc 'slynk:*string-elision-length* slynk:*slynk-pprint-bindings*)) nil)
(documentation 'sijo-version:version 'function)
#+end_src

#+RESULTS:
#+begin_example
Construct a semantic version (https://semver.org/)

Note that minimal effort is made validating or sanitizing the input, so the user
is able to construct an incorrect semantic version e.g. by supplying too many
components to :VERSION. Some sanitizing is done by dropping invalid characters.

Calling `version' will construct a default semantic version based on the VERSION
file, ./.git/HEAD and the current time.

If no VERSION file exists, the asdf components :VERSION will be used for the
version number.

If no version is found, 0.0.0 is used as the version.

The VERSION file should contain the version number in the first line, and an
optional pre-release tag in the second line. If no pre-release tag exist in the
file, the default is to add the git branch name iff it's not the main/master
branch.

VERSION, PRE-RELEASE and BUILD-METADATA is evaluated by `%eval-spec' into a
dotted string, and has the following semantics:
- `string' :: use as-is - it's the canonical form
- `list' ('or as first element) :: call `%eval-spec' on second element. If
non-nil, return it, otherwise evaluate next element. If there are no more
elements, return nil.
- `list' ('and as first element) :: call `%eval-spec' on subsequent elements,
remove nulls, and join with "-" as the separator.
- `list' :: call `%eval-spec' on each element, remove nulls, and join with "." as a separator.
- `function' :: call function pass result to `%eval-spec'
- `symbol' :: if `fboundp', call function and pass result to `%eval-spec'. If
`boundp' call `%eval-spec' on `symbol-value'. Otherwise call `%eval-spec' on
the `symbol-name'.
- `atom' :: convert to a string

ROOT is the root folder where the VERSION file and .git directory is located.
The value is evaluated by `%guess-root', and several different forms is
accepted:
- `pathname' :: use as-is - it's the canonical form
- `asdf:system' :: `asdf:system-relative-pathname' is used as the root
- `package' :: try to load a system with the same name as `package-name'. If no
system is found, `*default-pathname-defaults*' is used.
- `null' :: we guess given `*package*' instead
- `string' :: try to load a system, if missing interpret the input as a `pathname' instead
- `symbol' :: try to guess using the symbol as the system name

Examples:

`(version)'
"0.1+main.748e8897a233ddbc26a959bd14a97acb0ef5b895.20240301152751Z"

You can specify the system it should fetch information from directly using `:root'

`(version :root :sijo-version)'
"0.1+main.748e8897a233ddbc26a959bd14a97acb0ef5b895.20240301152751Z"

You can remove the use of extra build metadata

`(version :build-metadata nil)'
"0.1-some-prerelease"

You can specify the exact version and pre-release tag to use instead of looking in VERSION

>> (version :version "0.1" :pre-release nil :build-metadata nil)
"0.1"

>> (version :version "0.1" :pre-release "pre" :build-metadata nil)
"0.1-pre"

>> (version :version "0.1" :pre-release nil :build-metadata "build")
"0.1+build"

>> (version :version "0.1" :pre-release "pre" :build-metadata "build")
"0.1-pre+build"
#+end_example

* Troubleshooting
** Component "some-package" not found
~version~ tries to find out what the current system is by looking at
~(package-name *package*)~. If you're calling ~version~ from a package not named
the same as a system, it will fail.

#+begin_src lisp :exports both :eval never
(in-package :common-lisp-user)
(sijo-version:version)
#+end_src

#+RESULTS:
: Component "common-lisp-user" not found
: [Condition of type ASDF/FIND-COMPONENT:MISSING-COMPONENT]

In that case, you need to set ~:system~ yourself:

#+begin_src lisp :exports both :eval never
(in-package :common-lisp-user)
(sijo-version:version :system :my-system)
#+end_src

#+RESULTS:
: 0.1+main.7aeedb4d32927e19a5e9ed406ac5735db0fd20dd.20240301151319Z

** Using =VERSION= in ~defsystem~
ASDF can read use the version from your =VERSION= file directly with the
following syntax:

#+begin_src lisp :eval never
(defsystem :my-system
;; ...
:version (:read-file-line "VERSION" :at 0))
#+end_src

* Versioning
** =VERSION= file
The file should include =major.minor.patch= on the first line, and an optional
=prerelease= tag on the second line.

** Build information
The system assumes the project is using =git= by default, and will construct
build information as follows: =branch.commit.timestamp=

- =branch= is the current branch by reading =.git/HEAD=
- =commit= is the current commit in =.git/HEAD= or by following the ref there
- =timestamp= is a timestamp when calling the ~version~ function in the
=yyyyMMddHHmmssZ= format
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1
24 changes: 24 additions & 0 deletions sijo-version.asd
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(defsystem :sijo-version
:depends-on ()
:in-order-to ((test-op (test-op :sijo-version/tests)))
:version (:read-file-line "VERSION" :at 0)
:serial t
:pathname "src/"
:components ((:file "packages")
(:file "sequence")
(:file "version")))

(defsystem :sijo-version/tests
:depends-on (#:sijo-version
#:str
#:lisp-unit2
#:sijo-doctest)
:perform (test-op (o c)
(eval (read-from-string "
(lisp-unit2:with-summary ()
(lisp-unit2:run-tests
:package :sijo-version/tests
:name :sijo-version))")))
:serial t
:pathname "tests/"
:components ((:file "version")))
11 changes: 11 additions & 0 deletions src/packages.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(defpackage :sijo-version
(:use #:cl)
(:export #:version
;; Helpers for constructing the semver
#:version-file-line-0
#:version-file-line-1
#:system-version
#:current-time
#:git-non-main-branch
#:git-current-branch
#:git-current-commit))
46 changes: 46 additions & 0 deletions src/sequence.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
;; These functions is to avoid a dependency on the str library.

(in-package :sijo-version)

(defun split (needle haystack &aux result)
(when (eql 0 (length needle))
(return-from split (list haystack)))
(do ((pos (search needle haystack) (search needle haystack)))
((null pos) (if haystack
(nreverse (cons haystack result))
(nreverse result)))
(setf result (cons (subseq haystack 0 pos) result))
(setf haystack (subseq haystack (+ pos (length needle))))))

(defun intersperse (separator seq &aux result)
(do ((rest seq (subseq rest 1)))
((or (null rest) (eql 0 (length rest))) (nreverse result))
(push (elt rest 0) result)
(when (subseq rest 1)
(push separator result))))

(defun concat (sequence)
(let ((first (first sequence)))
(typecase first
(string (apply #'concatenate 'string sequence))
(array
;; It might have a specific length, but we want a variable length as the result
(apply #'concatenate (append (subseq (type-of first) 0 2) '((*))) sequence))
(t (apply #'concatenate (type-of first) sequence)))))

(defun join (separator sequence)
(concat (intersperse separator sequence)))

(defun remove-all (needle haystack)
(concat (split needle haystack)))

(defun replace-all (old new haystack)
(join new (split old haystack)))

(defun starts-with (prefix string &key (test #'eql))
(eql 0 (search prefix string :test test :end2 (length prefix))))

(defun without-prefix (prefix string &key (test #'eql))
(if (starts-with prefix string :test test)
(values (subseq string (length prefix)) t)
(values string nil)))
Loading

0 comments on commit 460b027

Please sign in to comment.