Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
simendsjo committed Mar 11, 2024
1 parent 89a88d4 commit 5880851
Show file tree
Hide file tree
Showing 10 changed files with 679 additions and 0 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
on: [push]
jobs:
tests:
strategy:
matrix:
os:
- ubuntu-latest
# - macos-latest
# - windows-latest
lisp:
- sbcl-bin
# - 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
qlfile-template: |
dist ultralisp http://dist.ultralisp.org
- uses: 40ants/run-tests@v2
with:
asdf-system: sijo-version/tests
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
*.FASL
*.fas
*.fasl
*.lisp-temp
*.dfsl
*.pfsl
*.d64fsl
*.p64fsl
*.lx64fsl
*.lx32fsl
*.dx64fsl
*.dx32fsl
*.fx64fsl
*.fx32fsl
*.sx64fsl
*.sx32fsl
*.wx64fsl
*.wx32fsl
22 changes: 22 additions & 0 deletions CHANGELOG.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
* Changelog
All notable changes to this project will be documented in this file.

The format is based on [[https://keepachangelog.com/en/1.1.0][Keep a Changelog]], and this project *DOES NOT* adhere to [[https://semver.org/spec/v2.0.0.html][Semantic
Versioning]].

** 0.1.0 - 2024-03-11
*** Added
- ~version~ :: construct a semantic version
- ~version-file-line-0~ :: fetches the first line from the =VERSION= file
- ~version-file-line-1~ :: fetches the second line from the =VERSION= file
- ~system-version~ :: the =:version= field from system we're calculating
the version from
- ~current-time~ :: current time
- ~git-non-main-branch~ :: git branch if it's the non-main/non-master branch
- ~git-current-branch~ :: current git branch
- ~git-current-commit~ :: current git commit
*** Changed
*** Deprecated
*** Removed
*** Fixed
*** Security
192 changes: 192 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
Semantic version for your systems. Opinionated defaults, but flexible.

See [[file:CHANGELOG.org][CHANGELOG]].

* 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' ('if as first element) :: call `%eval-spec' on the second element. If
true, return `%eval-spec' of the third element, otherwise return `%eval-spec'
of the fourth element.
- `list' ('when as first element) :: call `%eval-spec' on the second element. If
true, return `%eval-spec' of the third element, otherwise return nil.
- `list' ('unless as first element) :: call `%eval-spec' on the second element. If
false, return `%eval-spec' of the third element, otherwise return nil.
- `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
=pre-release= 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))
50 changes: 50 additions & 0 deletions src/sequence.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
;; 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 sequence &key (test #'eql))
(unless sequence
(return-from starts-with nil))
(let ((prefix-length (length prefix)))
(if (> prefix-length (length sequence))
(return-from starts-with nil))
(eql 0 (search prefix sequence :test test :end2 prefix-length))))

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

0 comments on commit 5880851

Please sign in to comment.