all: add #pkgconfig directive using the new vlib modules (#6673)
parent
cf21c63183
commit
36c5eab799
|
@ -0,0 +1,66 @@
|
||||||
|
v-pkgconfig
|
||||||
|
===========
|
||||||
|
|
||||||
|
This module implements the `pkg-config` tool as a library in pure V.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Simple API, but still not stable, but shouldnt change much
|
||||||
|
* Runs 2x faster than original pkg-config
|
||||||
|
* Commandline tool that aims to be compatible with `pkg-config`
|
||||||
|
* Resolve full path for `.pc` file given a name
|
||||||
|
* Recursively parse all the dependencies
|
||||||
|
* Find and replace all inner variables
|
||||||
|
|
||||||
|
Todo/Future/Wish:
|
||||||
|
|
||||||
|
* 100% compatibility with `pkg-config` options
|
||||||
|
* Integration with V, to support pkgconfig with `system()`
|
||||||
|
* Strictier pc parsing logic, with better error reporting
|
||||||
|
|
||||||
|
Example
|
||||||
|
-------
|
||||||
|
|
||||||
|
The commandline tool is available in `vlib/pkgconfig/bin/pkgconfig.v`
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./bin/pkgconfig -h
|
||||||
|
pkgconfig 0.2.0
|
||||||
|
-----------------------------------------------
|
||||||
|
Usage: pkgconfig [options] [ARGS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-V, --modversion show version of module
|
||||||
|
-d, --description show pkg module description
|
||||||
|
-h, --help show this help message
|
||||||
|
-D, --debug show debug information
|
||||||
|
-l, --list-all list all pkgmodules
|
||||||
|
-e, --exists return 0 if pkg exists
|
||||||
|
-V, --print-variables display variable names
|
||||||
|
-r, --print-requires display requires of the module
|
||||||
|
-a, --atleast-version <string>
|
||||||
|
return 0 if pkg version is at least the given one
|
||||||
|
--exact-version <string>
|
||||||
|
return 0 if pkg version is at least the given one
|
||||||
|
-v, --version show version of this tool
|
||||||
|
-c, --cflags output all pre-processor and compiler flags
|
||||||
|
-I, --cflags-only-I show only -I flags from CFLAGS
|
||||||
|
--cflags-only-other show cflags without -I
|
||||||
|
-s, --static show --libs for static linking
|
||||||
|
-l, --libs output all linker flags
|
||||||
|
--libs-only-l show only -l from ldflags
|
||||||
|
-L, --libs-only-L show only -L from ldflags
|
||||||
|
--libs-only-other show flags not containing -l or -L
|
||||||
|
$
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the API from the V repl
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> import pkgconfig
|
||||||
|
>>> opt := pkgconfig.Options{}
|
||||||
|
>>> mut pc := pkgconfig.load('r_core', opt) or { panic(err) }
|
||||||
|
>>> pc.libs
|
||||||
|
['-L/usr/local/lib', '-lr_core', '-lr_config', '-lr_util', '', '-ldl', '-lr_cons', '-lr_io', '-lr_socket', '-lr_hash', '-lr_crypto', '-lr_flag', '-lr_asm', '-lr_syscall', '-lr_lang', '-lr_parse', '-lr_reg', '-lr_debug', '-lr_anal', '-lr_search', '-lr_bp', '-lr_egg', '-lr_bin', '-lr_magic', '-lr_fs']
|
||||||
|
>>>
|
||||||
|
```
|
|
@ -0,0 +1,18 @@
|
||||||
|
module main
|
||||||
|
|
||||||
|
import pkgconfig
|
||||||
|
import os
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
mut m := pkgconfig.main(os.args[1..]) or {
|
||||||
|
eprintln(err)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
m.res = m.run() or {
|
||||||
|
eprintln(err)
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
if m.res != '' {
|
||||||
|
println(m.res)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
module pkgconfig
|
||||||
|
|
||||||
|
import flag
|
||||||
|
import strings
|
||||||
|
|
||||||
|
pub struct Main {
|
||||||
|
pub mut:
|
||||||
|
opt &MainOptions
|
||||||
|
res string
|
||||||
|
has_actions bool
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MainOptions {
|
||||||
|
modversion bool
|
||||||
|
description bool
|
||||||
|
help bool
|
||||||
|
debug bool
|
||||||
|
listall bool
|
||||||
|
exists bool
|
||||||
|
variables bool
|
||||||
|
requires bool
|
||||||
|
atleast string
|
||||||
|
atleastpc string
|
||||||
|
exactversion string
|
||||||
|
version bool
|
||||||
|
cflags bool
|
||||||
|
cflags_only_path bool
|
||||||
|
cflags_only_other bool
|
||||||
|
stat1c bool
|
||||||
|
libs bool
|
||||||
|
libs_only_link bool
|
||||||
|
libs_only_path bool
|
||||||
|
libs_only_other bool
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desc(mod string) ?string {
|
||||||
|
options := Options{
|
||||||
|
norecurse: true
|
||||||
|
}
|
||||||
|
mut pc := load(mod, options) or {
|
||||||
|
return error('cannot parse')
|
||||||
|
}
|
||||||
|
return pc.description
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(args []string) ?&Main {
|
||||||
|
mut fp := flag.new_flag_parser(args)
|
||||||
|
fp.application('pkgconfig')
|
||||||
|
fp.version(version)
|
||||||
|
mut m := &Main{
|
||||||
|
opt: parse_options(mut fp)
|
||||||
|
}
|
||||||
|
opt := m.opt
|
||||||
|
if opt.help {
|
||||||
|
m.res = fp.usage().replace('- ,', ' ')
|
||||||
|
} else if opt.version {
|
||||||
|
m.res = version
|
||||||
|
} else if opt.listall {
|
||||||
|
mut modules := list()
|
||||||
|
modules.sort()
|
||||||
|
if opt.description {
|
||||||
|
for mod in modules {
|
||||||
|
d := desc(mod) or {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pad := strings.repeat(` `, 20 - mod.len)
|
||||||
|
m.res += '$mod $pad $d\n'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.res = modules.join('\n')
|
||||||
|
}
|
||||||
|
} else if opt.args.len == 0 {
|
||||||
|
return error('No packages given')
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut m Main) run() ?string {
|
||||||
|
options := Options{
|
||||||
|
debug: m.opt.debug
|
||||||
|
}
|
||||||
|
// m.opt = options
|
||||||
|
opt := m.opt
|
||||||
|
mut pc := &PkgConfig(0)
|
||||||
|
mut res := m.res
|
||||||
|
for arg in opt.args {
|
||||||
|
mut pcdep := load(arg, options) or {
|
||||||
|
if !opt.exists {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if opt.description {
|
||||||
|
if res != '' {
|
||||||
|
res += '\n'
|
||||||
|
}
|
||||||
|
res += pcdep.description
|
||||||
|
}
|
||||||
|
if pc != 0 {
|
||||||
|
pc.extend(pcdep)
|
||||||
|
} else {
|
||||||
|
pc = pcdep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.exists {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
if opt.exactversion != '' {
|
||||||
|
if pc.version != opt.exactversion {
|
||||||
|
return error('version mismatch')
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
if opt.atleast != '' {
|
||||||
|
if pc.atleast(opt.atleast) {
|
||||||
|
return error('version mismatch')
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
if opt.atleastpc != '' {
|
||||||
|
if atleast(opt.atleastpc) {
|
||||||
|
return error('version mismatch')
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
if opt.variables {
|
||||||
|
for k, _ in pc.vars {
|
||||||
|
res += '$k\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.requires {
|
||||||
|
res += pc.requires.join('\n')
|
||||||
|
}
|
||||||
|
mut r := []string{}
|
||||||
|
if opt.cflags_only_path {
|
||||||
|
r << filter(pc.cflags, '-I', '')
|
||||||
|
}
|
||||||
|
if opt.cflags_only_other {
|
||||||
|
r << filter(pc.cflags, '-I', '-I')
|
||||||
|
}
|
||||||
|
if opt.cflags {
|
||||||
|
r << pc.cflags.join(' ')
|
||||||
|
}
|
||||||
|
if opt.libs_only_link {
|
||||||
|
r << filter(pc.libs, '-l', '')
|
||||||
|
}
|
||||||
|
if opt.libs_only_path {
|
||||||
|
r << filter(pc.libs, '-L', '')
|
||||||
|
}
|
||||||
|
if opt.libs_only_other {
|
||||||
|
r << filter(pc.libs, '-l', '-L')
|
||||||
|
}
|
||||||
|
if opt.libs {
|
||||||
|
if opt.stat1c {
|
||||||
|
r << pc.libs_private.join(' ')
|
||||||
|
} else {
|
||||||
|
r << pc.libs.join(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.modversion {
|
||||||
|
r << pc.version
|
||||||
|
}
|
||||||
|
return res + r.join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter(libs []string, prefix string, prefix2 string) string {
|
||||||
|
mut res := ''
|
||||||
|
if prefix2 != '' {
|
||||||
|
for lib in libs {
|
||||||
|
if !lib.starts_with(prefix) && !lib.starts_with(prefix2) {
|
||||||
|
res += ' $lib'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for lib in libs {
|
||||||
|
if lib.starts_with(prefix) {
|
||||||
|
res += ' $lib'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_options(mut fp flag.FlagParser) &MainOptions {
|
||||||
|
return &MainOptions{
|
||||||
|
description: fp.bool('description', `d`, false, 'show pkg module description')
|
||||||
|
modversion: fp.bool('modversion', `V`, false, 'show version of module')
|
||||||
|
help: fp.bool('help', `h`, false, 'show this help message')
|
||||||
|
debug: fp.bool('debug', `D`, false, 'show debug information')
|
||||||
|
listall: fp.bool('list-all', `l`, false, 'list all pkgmodules')
|
||||||
|
exists: fp.bool('exists', `e`, false, 'return 0 if pkg exists')
|
||||||
|
variables: fp.bool('print-variables', `V`, false, 'display variable names')
|
||||||
|
requires: fp.bool('print-requires', `r`, false, 'display requires of the module')
|
||||||
|
atleast: fp.string('atleast-version', `a`, '', 'return 0 if pkg version is at least the given one')
|
||||||
|
atleastpc: fp.string('atleast-pkgconfig-version', `A`, '', 'return 0 if pkgconfig version is at least the given one')
|
||||||
|
exactversion: fp.string('exact-version', ` `, '', 'return 0 if pkg version is at least the given one')
|
||||||
|
version: fp.bool('version', `v`, false, 'show version of this tool')
|
||||||
|
cflags: fp.bool('cflags', `c`, false, 'output all pre-processor and compiler flags')
|
||||||
|
cflags_only_path: fp.bool('cflags-only-I', `I`, false, 'show only -I flags from CFLAGS')
|
||||||
|
cflags_only_other: fp.bool('cflags-only-other', ` `, false, 'show cflags without -I')
|
||||||
|
stat1c: fp.bool('static', `s`, false, 'show --libs for static linking')
|
||||||
|
libs: fp.bool('libs', `l`, false, 'output all linker flags')
|
||||||
|
libs_only_link: fp.bool('libs-only-l', ` `, false, 'show only -l from ldflags')
|
||||||
|
libs_only_path: fp.bool('libs-only-L', `L`, false, 'show only -L from ldflags')
|
||||||
|
libs_only_other: fp.bool('libs-only-other', ` `, false, 'show flags not containing -l or -L')
|
||||||
|
args: fp.args
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,250 @@
|
||||||
|
module pkgconfig
|
||||||
|
|
||||||
|
import semver
|
||||||
|
import os
|
||||||
|
|
||||||
|
const (
|
||||||
|
default_paths = [
|
||||||
|
'/usr/local/lib/x86_64-linux-gnu/pkgconfig',
|
||||||
|
'/usr/local/lib/pkgconfig',
|
||||||
|
'/usr/local/share/pkgconfig',
|
||||||
|
'/usr/lib/x86_64-linux-gnu/pkgconfig',
|
||||||
|
'/usr/lib/pkgconfig',
|
||||||
|
'/usr/share/pkgconfig',
|
||||||
|
]
|
||||||
|
version = '0.2.0'
|
||||||
|
)
|
||||||
|
|
||||||
|
pub struct Options {
|
||||||
|
pub:
|
||||||
|
path string
|
||||||
|
debug bool
|
||||||
|
norecurse bool
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PkgConfig {
|
||||||
|
pub mut:
|
||||||
|
options Options
|
||||||
|
libs []string
|
||||||
|
libs_private []string
|
||||||
|
cflags []string
|
||||||
|
paths []string // TODO: move to options?
|
||||||
|
vars map[string]string
|
||||||
|
requires []string
|
||||||
|
version string
|
||||||
|
description string
|
||||||
|
name string
|
||||||
|
modname string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pc PkgConfig) filters(s string) []string {
|
||||||
|
r := pc.filter(s).split(' ')
|
||||||
|
mut res := []string{}
|
||||||
|
for a in r {
|
||||||
|
if a != '' {
|
||||||
|
res << a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pc PkgConfig) filter(s string) string {
|
||||||
|
mut r := s.trim_space()
|
||||||
|
for r.contains('\${') {
|
||||||
|
tok0 := r.index('\${') or {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mut tok1 := r[tok0..].index('}') or {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
tok1 += tok0
|
||||||
|
v := r[tok0 + 2..tok1]
|
||||||
|
r = r.replace('\${$v}', pc.vars[v])
|
||||||
|
}
|
||||||
|
return r.trim_space()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pc PkgConfig) setvar(line string) {
|
||||||
|
kv := line.trim_space().split('=')
|
||||||
|
if kv.len == 2 {
|
||||||
|
k := kv[0]
|
||||||
|
v := pc.filter(kv[1])
|
||||||
|
pc.vars[k] = pc.filter(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pc PkgConfig) parse(file string) bool {
|
||||||
|
data := os.read_file(file) or {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pc.options.debug {
|
||||||
|
eprintln(data)
|
||||||
|
}
|
||||||
|
lines := data.split('\n')
|
||||||
|
if pc.options.norecurse {
|
||||||
|
// 2x faster than original pkg-config for --list-all --description
|
||||||
|
// TODO: use different variable. norecurse have nothing to do with this
|
||||||
|
for line in lines {
|
||||||
|
if line.starts_with('Description: ') {
|
||||||
|
pc.description = pc.filter(line[13..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for line in lines {
|
||||||
|
if line.starts_with('#') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if line.contains('=') && !line.contains(' ') {
|
||||||
|
pc.setvar(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if line.starts_with('Description: ') {
|
||||||
|
pc.description = pc.filter(line[13..])
|
||||||
|
} else if line.starts_with('Name: ') {
|
||||||
|
pc.name = pc.filter(line[6..])
|
||||||
|
} else if line.starts_with('Version: ') {
|
||||||
|
pc.version = pc.filter(line[9..])
|
||||||
|
} else if line.starts_with('Requires: ') {
|
||||||
|
pc.requires = pc.filters(line[10..])
|
||||||
|
} else if line.starts_with('Cflags: ') {
|
||||||
|
pc.cflags = pc.filters(line[8..])
|
||||||
|
} else if line.starts_with('Libs: ') {
|
||||||
|
pc.libs = pc.filters(line[6..])
|
||||||
|
} else if line.starts_with('Libs.private: ') {
|
||||||
|
pc.libs_private = pc.filters(line[14..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pc PkgConfig) resolve(pkgname string) ?string {
|
||||||
|
if pc.paths.len == 0 {
|
||||||
|
pc.paths << '.'
|
||||||
|
}
|
||||||
|
for path in pc.paths {
|
||||||
|
file := '$path/${pkgname}.pc'
|
||||||
|
if os.exists(file) {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error('Cannot find "$pkgname" pkgconfig file')
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn atleast(v string) bool {
|
||||||
|
v0 := semver.from(version) or {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v1 := semver.from(v) or {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return v0.gt(v1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut pc PkgConfig) atleast(v string) bool {
|
||||||
|
v0 := semver.from(pc.version) or {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v1 := semver.from(v) or {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return v0.gt(v1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut pc PkgConfig) extend(pcdep &PkgConfig) ?string {
|
||||||
|
for flag in pcdep.cflags {
|
||||||
|
if pc.cflags.index(flag) == -1 {
|
||||||
|
pc.cflags << flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for lib in pcdep.libs {
|
||||||
|
if pc.libs.index(lib) == -1 {
|
||||||
|
pc.libs << lib
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for lib in pcdep.libs_private {
|
||||||
|
if pc.libs_private.index(lib) == -1 {
|
||||||
|
pc.libs_private << lib
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pc PkgConfig) load_requires() {
|
||||||
|
for dep in pc.requires {
|
||||||
|
mut pcdep := PkgConfig{
|
||||||
|
paths: pc.paths
|
||||||
|
}
|
||||||
|
depfile := pcdep.resolve(dep) or {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pcdep.parse(depfile)
|
||||||
|
pcdep.load_requires()
|
||||||
|
pc.extend(pcdep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pc PkgConfig) add_path(path string) {
|
||||||
|
p := if path.ends_with('/') { path[0..path.len - 1] } else { path }
|
||||||
|
if pc.paths.index(p) == -1 {
|
||||||
|
pc.paths << p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (mut pc PkgConfig) load_paths() {
|
||||||
|
for path in default_paths {
|
||||||
|
pc.add_path(path)
|
||||||
|
}
|
||||||
|
for path in pc.options.path.split(':') {
|
||||||
|
pc.add_path(path)
|
||||||
|
}
|
||||||
|
env_var := os.getenv('PKG_CONFIG_PATH')
|
||||||
|
if env_var != '' {
|
||||||
|
env_paths := env_var.trim_space().split(':')
|
||||||
|
for path in env_paths {
|
||||||
|
pc.add_path(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(pkgname string, options Options) ?&PkgConfig {
|
||||||
|
mut pc := &PkgConfig{
|
||||||
|
modname: pkgname
|
||||||
|
options: options
|
||||||
|
}
|
||||||
|
pc.load_paths()
|
||||||
|
file := pc.resolve(pkgname) or {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
|
pc.parse(file)
|
||||||
|
/*
|
||||||
|
if pc.name != pc.modname {
|
||||||
|
eprintln('Warning: modname and filename differ $pc.name $pc.modname')
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if !options.norecurse {
|
||||||
|
pc.load_requires()
|
||||||
|
}
|
||||||
|
return pc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list() []string {
|
||||||
|
mut pc := &PkgConfig{
|
||||||
|
options: Options{}
|
||||||
|
}
|
||||||
|
pc.load_paths()
|
||||||
|
mut modules := []string{}
|
||||||
|
for path in pc.paths {
|
||||||
|
files := os.ls(path) or {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for file in files {
|
||||||
|
if file.ends_with('.pc') {
|
||||||
|
name := file.replace('.pc', '')
|
||||||
|
if modules.index(name) == -1 {
|
||||||
|
modules << name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modules
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
Module {
|
||||||
|
name: 'pkgconfig'
|
||||||
|
author: 'pancake'
|
||||||
|
version: '0.2.0'
|
||||||
|
repo_url: 'https://github.com/trufae/v-pkgconfig'
|
||||||
|
vcs: 'git'
|
||||||
|
tags: ['system', 'compilers']
|
||||||
|
dependencies: ['semver']
|
||||||
|
description: 'V API implementing pkg-config logic'
|
||||||
|
license: 'MIT'
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 alexesprit
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,38 @@
|
||||||
|
# semver
|
||||||
|
|
||||||
|
A library for working with versions in [semver][semver] format.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```v
|
||||||
|
import semver
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
ver1 := semver.from('1.2.4') or {
|
||||||
|
println('Invalid version')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ver2 := semver.from('2.3.4') or {
|
||||||
|
println('Invalid version')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
println(ver1.gt(ver2))
|
||||||
|
println(ver2.gt(ver1))
|
||||||
|
println(ver1.satisfies('>=1.1.0 <2.0.0'))
|
||||||
|
println(ver2.satisfies('>=1.1.0 <2.0.0'))
|
||||||
|
println(ver2.satisfies('>=1.1.0 <2.0.0 || >2.2.0'))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
false
|
||||||
|
true
|
||||||
|
true
|
||||||
|
false
|
||||||
|
true
|
||||||
|
```
|
||||||
|
|
||||||
|
For more details see `semver.v` file.
|
||||||
|
|
||||||
|
[semver]: https://semver.org/
|
|
@ -0,0 +1,61 @@
|
||||||
|
module semver
|
||||||
|
|
||||||
|
// * Private functions.
|
||||||
|
[inline]
|
||||||
|
fn version_satisfies(ver Version, input string) bool {
|
||||||
|
range := parse_range(input) or {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return range.satisfies(ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_eq(v1 Version, v2 Version) bool {
|
||||||
|
return v1.major == v2.major &&
|
||||||
|
v1.minor == v2.minor && v1.patch == v2.patch && v1.prerelease == v2.prerelease
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_gt(v1 Version, v2 Version) bool {
|
||||||
|
if v1.major < v2.major {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v1.major > v2.major {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if v1.minor < v2.minor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v1.minor > v2.minor {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return v1.patch > v2.patch
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_lt(v1 Version, v2 Version) bool {
|
||||||
|
if v1.major > v2.major {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v1.major < v2.major {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if v1.minor > v2.minor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v1.minor < v2.minor {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return v1.patch < v2.patch
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_ge(v1 Version, v2 Version) bool {
|
||||||
|
if compare_eq(v1, v2) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return compare_gt(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_le(v1 Version, v2 Version) bool {
|
||||||
|
if compare_eq(v1, v2) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return compare_lt(v1, v2)
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
module semver
|
||||||
|
|
||||||
|
// * Private structs and functions.
|
||||||
|
struct RawVersion {
|
||||||
|
prerelease string
|
||||||
|
metadata string
|
||||||
|
mut:
|
||||||
|
raw_ints []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ver_major = 0
|
||||||
|
ver_minor = 1
|
||||||
|
ver_patch = 2
|
||||||
|
versions = [ver_major, ver_minor, ver_patch]
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Rewrite using regexps?
|
||||||
|
// /(\d+)\.(\d+)\.(\d+)(?:\-([0-9A-Za-z-.]+))?(?:\+([0-9A-Za-z-]+))?/
|
||||||
|
fn parse(input string) RawVersion {
|
||||||
|
mut raw_version := input
|
||||||
|
mut prerelease := ''
|
||||||
|
mut metadata := ''
|
||||||
|
plus_idx := raw_version.last_index('+') or {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
if plus_idx > 0 {
|
||||||
|
metadata = raw_version[(plus_idx + 1)..]
|
||||||
|
raw_version = raw_version[0..plus_idx]
|
||||||
|
}
|
||||||
|
hyphen_idx := raw_version.index('-') or {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
if hyphen_idx > 0 {
|
||||||
|
prerelease = raw_version[(hyphen_idx + 1)..]
|
||||||
|
raw_version = raw_version[0..hyphen_idx]
|
||||||
|
}
|
||||||
|
raw_ints := raw_version.split('.')
|
||||||
|
return RawVersion{
|
||||||
|
prerelease: prerelease
|
||||||
|
metadata: metadata
|
||||||
|
raw_ints: raw_ints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (ver RawVersion) is_valid() bool {
|
||||||
|
if ver.raw_ints.len != 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return is_valid_number(ver.raw_ints[ver_major]) && is_valid_number(ver.raw_ints[ver_minor]) &&
|
||||||
|
is_valid_number(ver.raw_ints[ver_patch]) && is_valid_string(ver.prerelease) && is_valid_string(ver.metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (ver RawVersion) is_missing(typ int) bool {
|
||||||
|
return typ >= ver.raw_ints.len - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (raw_ver RawVersion) coerce() ?Version {
|
||||||
|
ver := raw_ver.complete()
|
||||||
|
if !is_valid_number(ver.raw_ints[ver_major]) {
|
||||||
|
return error('Invalid major version: $ver.raw_ints[ver_major]')
|
||||||
|
}
|
||||||
|
return ver.to_version()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (raw_ver RawVersion) complete() RawVersion {
|
||||||
|
mut raw_ints := raw_ver.raw_ints
|
||||||
|
for raw_ints.len < 3 {
|
||||||
|
raw_ints << '0'
|
||||||
|
}
|
||||||
|
return RawVersion{
|
||||||
|
prerelease: raw_ver.prerelease
|
||||||
|
metadata: raw_ver.metadata
|
||||||
|
raw_ints: raw_ints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (raw_ver RawVersion) validate() ?Version {
|
||||||
|
if !raw_ver.is_valid() {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
return raw_ver.to_version()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (raw_ver RawVersion) to_version() Version {
|
||||||
|
return Version{raw_ver.raw_ints[ver_major].int(), raw_ver.raw_ints[ver_minor].int(), raw_ver.raw_ints[ver_patch].int(), raw_ver.prerelease, raw_ver.metadata}
|
||||||
|
}
|
|
@ -0,0 +1,245 @@
|
||||||
|
module semver
|
||||||
|
|
||||||
|
// * Private functions.
|
||||||
|
const (
|
||||||
|
comparator_sep = ' '
|
||||||
|
comparator_set_sep = ' || '
|
||||||
|
hyphen_range_sep = ' - '
|
||||||
|
x_range_symbols = 'Xx*'
|
||||||
|
)
|
||||||
|
|
||||||
|
enum Operator {
|
||||||
|
gt
|
||||||
|
lt
|
||||||
|
ge
|
||||||
|
le
|
||||||
|
eq
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Comparator {
|
||||||
|
ver Version
|
||||||
|
op Operator
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ComparatorSet {
|
||||||
|
comparators []Comparator
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Range {
|
||||||
|
comparator_sets []ComparatorSet
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (r Range) satisfies(ver Version) bool {
|
||||||
|
mut final_result := false
|
||||||
|
for set in r.comparator_sets {
|
||||||
|
final_result = final_result || set.satisfies(ver)
|
||||||
|
}
|
||||||
|
return final_result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (set ComparatorSet) satisfies(ver Version) bool {
|
||||||
|
for comp in set.comparators {
|
||||||
|
if !comp.satisfies(ver) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (c Comparator) satisfies(ver Version) bool {
|
||||||
|
return match c.op {
|
||||||
|
.gt { ver.gt(c.ver) }
|
||||||
|
.lt { ver.lt(c.ver) }
|
||||||
|
.ge { ver.ge(c.ver) }
|
||||||
|
.le { ver.le(c.ver) }
|
||||||
|
.eq { ver.eq(c.ver) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_range(input string) ?Range {
|
||||||
|
raw_comparator_sets := input.split(comparator_set_sep)
|
||||||
|
mut comparator_sets := []ComparatorSet{}
|
||||||
|
for raw_comp_set in raw_comparator_sets {
|
||||||
|
if can_expand(raw_comp_set) {
|
||||||
|
s := expand_comparator_set(raw_comp_set) or {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
|
comparator_sets << s
|
||||||
|
} else {
|
||||||
|
s := parse_comparator_set(raw_comp_set) or {
|
||||||
|
return error(err)
|
||||||
|
}
|
||||||
|
comparator_sets << s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Range{comparator_sets}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_comparator_set(input string) ?ComparatorSet {
|
||||||
|
raw_comparators := input.split(comparator_sep)
|
||||||
|
if raw_comparators.len > 2 {
|
||||||
|
return error('Invalid format of comparator set')
|
||||||
|
}
|
||||||
|
mut comparators := []Comparator{}
|
||||||
|
for raw_comp in raw_comparators {
|
||||||
|
c := parse_comparator(raw_comp) or {
|
||||||
|
return error('Invalid comparator: $raw_comp')
|
||||||
|
}
|
||||||
|
comparators << c
|
||||||
|
}
|
||||||
|
return ComparatorSet{comparators}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_comparator(input string) ?Comparator {
|
||||||
|
mut op := Operator.eq
|
||||||
|
mut raw_version := ''
|
||||||
|
if input.starts_with('>=') {
|
||||||
|
op = .ge
|
||||||
|
raw_version = input[2..]
|
||||||
|
} else if input.starts_with('<=') {
|
||||||
|
op = .le
|
||||||
|
raw_version = input[2..]
|
||||||
|
} else if input.starts_with('>') {
|
||||||
|
op = .gt
|
||||||
|
raw_version = input[1..]
|
||||||
|
} else if input.starts_with('<') {
|
||||||
|
op = .lt
|
||||||
|
raw_version = input[1..]
|
||||||
|
} else if input.starts_with('=') {
|
||||||
|
raw_version = input[1..]
|
||||||
|
} else {
|
||||||
|
raw_version = input
|
||||||
|
}
|
||||||
|
version := coerce_version(raw_version) or {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
return Comparator{version, op}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_xrange(input string) ?Version {
|
||||||
|
mut raw_ver := parse(input).complete()
|
||||||
|
for typ in versions {
|
||||||
|
if raw_ver.raw_ints[typ].index_any(x_range_symbols) == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
match typ {
|
||||||
|
ver_major {
|
||||||
|
raw_ver.raw_ints[ver_major] = '0'
|
||||||
|
raw_ver.raw_ints[ver_minor] = '0'
|
||||||
|
raw_ver.raw_ints[ver_patch] = '0'
|
||||||
|
}
|
||||||
|
ver_minor {
|
||||||
|
raw_ver.raw_ints[ver_minor] = '0'
|
||||||
|
raw_ver.raw_ints[ver_patch] = '0'
|
||||||
|
}
|
||||||
|
ver_patch {
|
||||||
|
raw_ver.raw_ints[ver_patch] = '0'
|
||||||
|
}
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !raw_ver.is_valid() {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
return raw_ver.to_version()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_expand(input string) bool {
|
||||||
|
return input[0] == `~` ||
|
||||||
|
input[0] == `^` || input.contains(hyphen_range_sep) || input.index_any(x_range_symbols) > -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_comparator_set(input string) ?ComparatorSet {
|
||||||
|
match input[0] {
|
||||||
|
`~` { return expand_tilda(input[1..]) }
|
||||||
|
`^` { return expand_caret(input[1..]) }
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
if input.contains(hyphen_range_sep) {
|
||||||
|
return expand_hyphen(input)
|
||||||
|
}
|
||||||
|
return expand_xrange(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_tilda(raw_version string) ?ComparatorSet {
|
||||||
|
min_ver := coerce_version(raw_version) or {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
mut max_ver := min_ver
|
||||||
|
if min_ver.minor == 0 && min_ver.patch == 0 {
|
||||||
|
max_ver = min_ver.increment(.major)
|
||||||
|
} else {
|
||||||
|
max_ver = min_ver.increment(.minor)
|
||||||
|
}
|
||||||
|
return make_comparator_set_ge_lt(min_ver, max_ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_caret(raw_version string) ?ComparatorSet {
|
||||||
|
min_ver := coerce_version(raw_version) or {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
mut max_ver := min_ver
|
||||||
|
if min_ver.major == 0 {
|
||||||
|
max_ver = min_ver.increment(.minor)
|
||||||
|
} else {
|
||||||
|
max_ver = min_ver.increment(.major)
|
||||||
|
}
|
||||||
|
return make_comparator_set_ge_lt(min_ver, max_ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_hyphen(raw_range string) ?ComparatorSet {
|
||||||
|
raw_versions := raw_range.split(hyphen_range_sep)
|
||||||
|
if raw_versions.len != 2 {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
min_ver := coerce_version(raw_versions[0]) or {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
raw_max_ver := parse(raw_versions[1])
|
||||||
|
if raw_max_ver.is_missing(ver_major) {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
mut max_ver := raw_max_ver.coerce() or {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
if raw_max_ver.is_missing(ver_minor) {
|
||||||
|
max_ver = max_ver.increment(.minor)
|
||||||
|
return make_comparator_set_ge_lt(min_ver, max_ver)
|
||||||
|
}
|
||||||
|
return make_comparator_set_ge_le(min_ver, max_ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_xrange(raw_range string) ?ComparatorSet {
|
||||||
|
min_ver := parse_xrange(raw_range) or {
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
if min_ver.major == 0 {
|
||||||
|
comparators := [
|
||||||
|
Comparator{min_ver, Operator.ge},
|
||||||
|
]
|
||||||
|
return ComparatorSet{comparators}
|
||||||
|
}
|
||||||
|
mut max_ver := min_ver
|
||||||
|
if min_ver.minor == 0 {
|
||||||
|
max_ver = min_ver.increment(.major)
|
||||||
|
} else {
|
||||||
|
max_ver = min_ver.increment(.minor)
|
||||||
|
}
|
||||||
|
return make_comparator_set_ge_lt(min_ver, max_ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_comparator_set_ge_lt(min Version, max Version) ComparatorSet {
|
||||||
|
comparators := [
|
||||||
|
Comparator{min, Operator.ge},
|
||||||
|
Comparator{max, Operator.lt},
|
||||||
|
]
|
||||||
|
return ComparatorSet{comparators}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_comparator_set_ge_le(min Version, max Version) ComparatorSet {
|
||||||
|
comparators := [
|
||||||
|
Comparator{min, Operator.ge},
|
||||||
|
Comparator{max, Operator.le},
|
||||||
|
]
|
||||||
|
return ComparatorSet{comparators}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// * Documentation: https://docs.npmjs.com/misc/semver
|
||||||
|
module semver
|
||||||
|
|
||||||
|
// * Structures.
|
||||||
|
// Structure representing version in semver format.
|
||||||
|
pub struct Version {
|
||||||
|
pub:
|
||||||
|
major int
|
||||||
|
minor int
|
||||||
|
patch int
|
||||||
|
prerelease string
|
||||||
|
metadata string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum representing type of version increment.
|
||||||
|
pub enum Increment {
|
||||||
|
major
|
||||||
|
minor
|
||||||
|
patch
|
||||||
|
}
|
||||||
|
|
||||||
|
// * Constructor.
|
||||||
|
// from returns Version structure parsed from input string.
|
||||||
|
pub fn from(input string) ?Version {
|
||||||
|
if input.len == 0 {
|
||||||
|
return error('Empty input')
|
||||||
|
}
|
||||||
|
raw_version := parse(input)
|
||||||
|
version := raw_version.validate() or {
|
||||||
|
return error('Invalid version format')
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
// build returns Version structure with given major, minor and patch versions.
|
||||||
|
pub fn build(major int, minor int, patch int) Version {
|
||||||
|
// TODO Check if versions are greater than zero.
|
||||||
|
return Version{major, minor, patch, '', ''}
|
||||||
|
}
|
||||||
|
|
||||||
|
// * Transformation.
|
||||||
|
// increment returns Version structure with incremented values.
|
||||||
|
pub fn (ver Version) increment(typ Increment) Version {
|
||||||
|
return increment_version(ver, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// * Comparison.
|
||||||
|
pub fn (ver Version) satisfies(input string) bool {
|
||||||
|
return version_satisfies(ver, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (v1 Version) eq(v2 Version) bool {
|
||||||
|
return compare_eq(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (v1 Version) gt(v2 Version) bool {
|
||||||
|
return compare_gt(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (v1 Version) lt(v2 Version) bool {
|
||||||
|
return compare_lt(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (v1 Version) ge(v2 Version) bool {
|
||||||
|
return compare_ge(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (v1 Version) le(v2 Version) bool {
|
||||||
|
return compare_le(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// * Utilites.
|
||||||
|
pub fn coerce(input string) ?Version {
|
||||||
|
ver := coerce_version(input) or {
|
||||||
|
return error('Invalid version: $input')
|
||||||
|
}
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_valid(input string) bool {
|
||||||
|
return is_version_valid(input)
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
import semver
|
||||||
|
|
||||||
|
struct TestVersion {
|
||||||
|
raw string
|
||||||
|
major int
|
||||||
|
minor int
|
||||||
|
patch int
|
||||||
|
prerelease string
|
||||||
|
metadata string
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestRange {
|
||||||
|
raw_version string
|
||||||
|
range_satisfied string
|
||||||
|
range_unsatisfied string
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestCoerce {
|
||||||
|
invalid string
|
||||||
|
valid string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
versions_to_test = [
|
||||||
|
TestVersion{'1.2.4', 1, 2, 4, '', ''},
|
||||||
|
TestVersion{'1.2.4-prerelease-1', 1, 2, 4, 'prerelease-1', ''},
|
||||||
|
TestVersion{'1.2.4+20191231', 1, 2, 4, '', '20191231'},
|
||||||
|
TestVersion{'1.2.4-prerelease-1+20191231', 1, 2, 4, 'prerelease-1', '20191231'},
|
||||||
|
TestVersion{'1.2.4+20191231-prerelease-1', 1, 2, 4, '', '20191231-prerelease-1'},
|
||||||
|
]
|
||||||
|
ranges_to_test = [
|
||||||
|
TestRange{'1.1.0', '1.1.0', '1.1.1'},
|
||||||
|
TestRange{'1.1.0', '=1.1.0', '=1.1.1'},
|
||||||
|
TestRange{'1.1.0', '>=1.0.0', '<1.1.0'},
|
||||||
|
TestRange{'1.1.0', '>=1.0.0 <=1.1.0', '>=1.0.0 <1.1.0'},
|
||||||
|
TestRange{'2.3.1', '>=1.0.0 <=1.1.0 || >2.0.0 <2.3.4', '>=1.0.0 <1.1.0'},
|
||||||
|
TestRange{'2.3.1', '>=1.0.0 <=1.1.0 || >2.0.0 <2.3.4', '>=1.0.0 <1.1.0 || >4.0.0 <5.0.0'},
|
||||||
|
TestRange{'2.3.1', '~2.3.0', '~2.4.0'},
|
||||||
|
TestRange{'3.0.0', '~3.0.0', '~4.0.0'},
|
||||||
|
TestRange{'2.3.1', '^2.0.0', '^2.4.0'},
|
||||||
|
TestRange{'0.3.1', '^0.3.0', '^2.4.0'},
|
||||||
|
TestRange{'0.0.4', '^0.0.1', '^0.1.0'},
|
||||||
|
TestRange{'2.3.4', '^0.0.1 || ^2.3.0', '^3.1.0 || ^4.2.0'},
|
||||||
|
TestRange{'2.3.4', '>2 || <3', '>3 || >4'},
|
||||||
|
TestRange{'2.3.4', '2.3.4 - 2.3.5', '2.5.1 - 2.8.3'},
|
||||||
|
TestRange{'2.3.4', '2.2 - 2.3', '2.4 - 2.8'},
|
||||||
|
TestRange{'2.3.4', '2.3.x', '2.4.x'},
|
||||||
|
TestRange{'2.3.4', '2.x', '3.x'},
|
||||||
|
TestRange{'2.3.4', '*', '3.x'},
|
||||||
|
]
|
||||||
|
coerce_to_test = [
|
||||||
|
TestCoerce{'1.2.0.4', '1.2.0'},
|
||||||
|
TestCoerce{'1.2.0', '1.2.0'},
|
||||||
|
TestCoerce{'1.2', '1.2.0'},
|
||||||
|
TestCoerce{'1', '1.0.0'},
|
||||||
|
TestCoerce{'1-alpha', '1.0.0-alpha'},
|
||||||
|
TestCoerce{'1+meta', '1.0.0+meta'},
|
||||||
|
TestCoerce{'1-alpha+meta', '1.0.0-alpha+meta'},
|
||||||
|
]
|
||||||
|
invalid_versions_to_test = [
|
||||||
|
'a.b.c',
|
||||||
|
'1.2',
|
||||||
|
'1.2.x',
|
||||||
|
'1.2.3.4',
|
||||||
|
'1.2.3-alpha@',
|
||||||
|
'1.2.3+meta%',
|
||||||
|
]
|
||||||
|
invalid_ranges_to_test = [
|
||||||
|
'^a',
|
||||||
|
'~b',
|
||||||
|
'a - c',
|
||||||
|
'>a',
|
||||||
|
'a',
|
||||||
|
'a.x',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
fn test_from() {
|
||||||
|
for item in versions_to_test {
|
||||||
|
ver := semver.from(item.raw) or {
|
||||||
|
assert false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert ver.major == item.major
|
||||||
|
assert ver.minor == item.minor
|
||||||
|
assert ver.patch == item.patch
|
||||||
|
assert ver.metadata == item.metadata
|
||||||
|
assert ver.prerelease == item.prerelease
|
||||||
|
}
|
||||||
|
for ver in invalid_versions_to_test {
|
||||||
|
semver.from(ver) or {
|
||||||
|
assert true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_increment() {
|
||||||
|
version1 := semver.build(1, 2, 3)
|
||||||
|
version1_inc := version1.increment(.major)
|
||||||
|
assert version1_inc.major == 2
|
||||||
|
assert version1_inc.minor == 0
|
||||||
|
assert version1_inc.patch == 0
|
||||||
|
version2_inc := version1.increment(.minor)
|
||||||
|
assert version2_inc.major == 1
|
||||||
|
assert version2_inc.minor == 3
|
||||||
|
assert version2_inc.patch == 0
|
||||||
|
version3_inc := version1.increment(.patch)
|
||||||
|
assert version3_inc.major == 1
|
||||||
|
assert version3_inc.minor == 2
|
||||||
|
assert version3_inc.patch == 4
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_compare() {
|
||||||
|
first := semver.build(1, 0, 0)
|
||||||
|
patch := semver.build(1, 0, 1)
|
||||||
|
minor := semver.build(1, 2, 3)
|
||||||
|
major := semver.build(2, 0, 0)
|
||||||
|
assert first.le(first)
|
||||||
|
assert first.ge(first)
|
||||||
|
assert !first.lt(first)
|
||||||
|
assert !first.gt(first)
|
||||||
|
assert patch.ge(first)
|
||||||
|
assert first.le(patch)
|
||||||
|
assert !first.ge(patch)
|
||||||
|
assert !patch.le(first)
|
||||||
|
assert patch.gt(first)
|
||||||
|
assert first.lt(patch)
|
||||||
|
assert !first.gt(patch)
|
||||||
|
assert !patch.lt(first)
|
||||||
|
assert minor.gt(patch)
|
||||||
|
assert patch.lt(minor)
|
||||||
|
assert !patch.gt(minor)
|
||||||
|
assert !minor.lt(patch)
|
||||||
|
assert major.gt(minor)
|
||||||
|
assert minor.lt(major)
|
||||||
|
assert !minor.gt(major)
|
||||||
|
assert !major.lt(minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_satisfies() {
|
||||||
|
for item in ranges_to_test {
|
||||||
|
ver := semver.from(item.raw_version) or {
|
||||||
|
assert false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert ver.satisfies(item.range_satisfied)
|
||||||
|
assert !ver.satisfies(item.range_unsatisfied)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_satisfies_invalid() {
|
||||||
|
ver := semver.from('1.0.0') or {
|
||||||
|
assert false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for item in invalid_ranges_to_test {
|
||||||
|
assert ver.satisfies(item) == false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_coerce() {
|
||||||
|
for item in coerce_to_test {
|
||||||
|
valid := semver.from(item.valid) or {
|
||||||
|
assert false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fixed := semver.coerce(item.invalid) or {
|
||||||
|
assert false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert fixed.eq(valid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_coerce_invalid() {
|
||||||
|
semver.coerce('a') or {
|
||||||
|
assert true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_is_valid() {
|
||||||
|
for item in versions_to_test {
|
||||||
|
assert semver.is_valid(item.raw)
|
||||||
|
}
|
||||||
|
for item in invalid_versions_to_test {
|
||||||
|
assert semver.is_valid(item) == false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
module semver
|
||||||
|
|
||||||
|
// * Private functions.
|
||||||
|
[inline]
|
||||||
|
fn is_version_valid(input string) bool {
|
||||||
|
raw_ver := parse(input)
|
||||||
|
return raw_ver.is_valid()
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn coerce_version(input string) ?Version {
|
||||||
|
raw_ver := parse(input)
|
||||||
|
ver := raw_ver.coerce() or {
|
||||||
|
return error('Invalid version: $input')
|
||||||
|
}
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
|
||||||
|
[inline]
|
||||||
|
fn increment_version(ver Version, typ Increment) Version {
|
||||||
|
mut major := ver.major
|
||||||
|
mut minor := ver.minor
|
||||||
|
mut patch := ver.patch
|
||||||
|
match typ {
|
||||||
|
.major {
|
||||||
|
major++
|
||||||
|
minor = 0
|
||||||
|
patch = 0
|
||||||
|
}
|
||||||
|
.minor {
|
||||||
|
minor++
|
||||||
|
patch = 0
|
||||||
|
}
|
||||||
|
.patch {
|
||||||
|
patch++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Version{major, minor, patch, ver.prerelease, ver.metadata}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_string(input string) bool {
|
||||||
|
for c in input {
|
||||||
|
if !(c.is_letter() || c.is_digit() || c == `.` || c == `-`) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_number(input string) bool {
|
||||||
|
for c in input {
|
||||||
|
if !c.is_digit() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
Module {
|
||||||
|
name: 'semver'
|
||||||
|
version: '0.3.0'
|
||||||
|
deps: []
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import v.token
|
||||||
import v.pref
|
import v.pref
|
||||||
import v.util
|
import v.util
|
||||||
import v.errors
|
import v.errors
|
||||||
|
import pkgconfig
|
||||||
|
|
||||||
const (
|
const (
|
||||||
max_nr_errors = 300
|
max_nr_errors = 300
|
||||||
|
@ -2477,6 +2478,20 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
|
||||||
c.error('including C files should use either `"header_file.h"` or `<header_file.h>` quoting',
|
c.error('including C files should use either `"header_file.h"` or `<header_file.h>` quoting',
|
||||||
node.pos)
|
node.pos)
|
||||||
}
|
}
|
||||||
|
} else if node.kind == 'pkgconfig' {
|
||||||
|
args := if node.main.contains('--') { node.main.split(' ') } else { '--cflags --libs $node.main'.split(' ') }
|
||||||
|
mut m := pkgconfig.main(args) or {
|
||||||
|
c.error(err, node.pos)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cflags := m.run() or {
|
||||||
|
c.error(err, node.pos)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.table.parse_cflag(cflags, c.mod, c.pref.compile_defines_all) or {
|
||||||
|
c.error(err, node.pos)
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if node.kind == 'flag' {
|
} else if node.kind == 'flag' {
|
||||||
// #flag linux -lm
|
// #flag linux -lm
|
||||||
mut flag := node.main
|
mut flag := node.main
|
||||||
|
@ -2498,7 +2513,8 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if node.kind != 'define' {
|
if node.kind != 'define' {
|
||||||
c.warn('expected `#include`, `#flag` or `#define` not $node.val', node.pos)
|
c.warn('expected `#define`, `#flag`, `#include` or `#pkgconfig` not $node.val',
|
||||||
|
node.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue