app = angular.module "plunker", [
"ui.bootstrap"
"ui.compat"
"plunker.catalogue"
]
app.directive "plunkerPackageBlock", [ () ->
restrict: "E"
replace: true
scope:
'package': "="
template: """
<div class="plunker-package-block">
<div class="package-header">
<h4>
<ul class="package-meta inline pull-right">
<li><i class="icon-download"></i><span ng-bind="package.bumps"></span></li>
</ul>
<a ng-href="packages/{{package.name}}" ng-bind="package.name"></a>
<ul class="package-versions inline">
<li ng-repeat="version in package.versions | limitTo:3">
<a class="label" ng-href="packages/{{package.name}}/versions/{{version.semver}}" ng-bind="version.semver"></a>
</li>
<li class="dropdown" ng-show="package.versions.length > 3">
<a class="more dropdown-toggle" ng-href="spackages/{{package.name}}/versions">More...</a>
<ul class="dropdown-menu">
<li ng-repeat="version in package.versions | limitTo:3 - package.versions.length">
<a ng-href="packages/{{package.name}}/versions/{{version.semver}}" ng-bind="version.semver"></a>
</li>
</ul>
</li>
</ul>
</h4>
</div>
<p class="package-description" ng-bind="package.description"></p>
</div>
"""
link: ($scope, $el, attrs) ->
]
app.config ["$stateProvider", "$routeProvider", ($stateProvider, $routeProvider) ->
$stateProvider.state "catalogue",
abstract: true
url: ""
template: """
<div class="plunker-catalogue">
<form class="plunker-searchbar" ng-submit="search()">
<a class="btn pull-left" href=""><i class="icon-home"></i></a>
<div class="plunker-searchbox input-append">
<div class="input-wrapper">
<input class="" type="text" ng-model="query" placeholder="Search packages..." />
</div>
<button class="btn"><i class="icon-search"></i></button>
</div>
</form>
<div ui-view></div>
</div>
"""
controller: ["$scope", "$state", "catalogue", ($scope, $state, catalogue) ->
$scope.search = (query) ->
$state.transitionTo("catalogue.search", query: $scope.query)
]
$stateProvider.state "catalogue.popular",
url: "/catalogue/popular"
template: """
<div>
<div class="sub-header">
<h3>
<small class="pull-right"><a href="packages/create">Add new...</a></small>
Popular packages
</h3>
</div>
<plunker-package-block package="package" ng-repeat="package in popular"></plunker-package-block>
</div>
"""
controller: ["$scope", "$state", "catalogue", ($scope, $state, catalogue) ->
$scope.popular = catalogue.findAll()
]
$stateProvider.state "catalogue.search",
url: "/catalogue/search/:query"
template: """
<div>
<div class="sub-header">
<h3>Search results: <span ng-bind="search"></span></h3>
</div>
<plunker-package-block package="package" ng-repeat="package in results"></plunker-package-block>
<p ng-hide="!results || results.length">No results found for {{search}}.</p>
<p ng-hide="results && !results.loading">Searching for {{search}}.</p>
</div>
"""
controller: ["$scope", "$state", "$stateParams", "catalogue", ($scope, $state, $stateParams, catalogue) ->
$scope.search = $stateParams.query
$scope.results = catalogue.search($stateParams.query)
]
$stateProvider.state "catalogue.create",
url: "/packages/create"
templateUrl: "partials/package/edit.html"
controller: ["$scope", "$state", "$stateParams", "catalogue", ($scope, $state, $stateParams, catalogue) ->
$scope.prefix = "Create new package"
$scope.editing = {}
$scope.cancel = -> $state.transitionTo("catalogue.popular")
$scope.save = ->
catalogue.create($scope.editing).then (pkg) ->
console.log "Created package", pkg
]
$stateProvider.state "catalogue.package",
url: "/packages/:name"
template: """
<div ui-view>
</div>
"""
resolve:
pkg: ["$stateParams", "catalogue", ($stateParams, catalogue) ->
catalogue.findOne(name: $stateParams.name)
]
controller: ["$scope", "$state", "$stateParams", "pkg", ($scope, $state, $stateParams, pkg) ->
console.log "Setting package to", pkg
$scope.package = pkg
unless pkg.versionCount or pkg.versions?.length
return $state.transitionTo("catalogue.package.createVersion", name: pkg.name)
#$scope.currentVersion = null
if $state.is("catalogue.package")
return $state.transitionTo("catalogue.package.version.detail", name: pkg.name, semver: pkg.getMatchingVersion().semver) unless $scope.currentVersion
]
$stateProvider.state "catalogue.package.createVersion",
url: "/versions/create"
template: """
<div class="sub-header">
<h3>{{prefix}}<a ng-href="packages/{{package.name}}/edit" ng-bind="package.name"></a></h3>
</div>
<version-editor save="save(editing)" editing="editing" package="package"></version-editor>
"""
controller: ["$scope", "$state", "$stateParams", "pkg", ($scope, $state, $stateParams, pkg) ->
$scope.package = pkg
$scope.prefix = "Adding version: "
$scope.editing =
scripts: []
styles: []
dependencies: []
$scope.save = ->
$scope.package.addVersion($scope.editing).then ->
$state.transitionTo("catalogue.package.version.detail", name: $scope.package.name, semver: $scope.editing.semver)
]
$stateProvider.state "catalogue.package.version",
abstract: true
url: "/versions/:semver"
template: """
<div>
<div class="sub-header">
<div class="package-version-toggle dropdown pull-right">
<div class="dropdown-toggle"><span ng-bind="currentVersion.semver"></span><span class="caret"></span></div>
<ul class="dropdown-menu">
<li ng-class="{active: version == currentVersion}" ng-repeat="version in package.versions">
<a ng-click="transitionTo(package, version)" ng-bind="version.semver"></a>
</li>
</ul>
</div>
<h3>{{prefix}}<a ng-href="packages/{{package.name}}/edit" ng-bind="package.name"></a></h3>
</div>
<div ui-view></div>
</div>
"""
controller: ["$scope", "$state", "$stateParams", "pkg", ($scope, $state, $stateParams, pkg) ->
$scope.package = pkg
$scope.currentVersion = $scope.package.getMatchingVersion($stateParams.semver)
$scope.transitionTo = (pkg, version) ->
$state.transitionTo($state.current.name, name: pkg.name, semver: version.semver)
]
$stateProvider.state "catalogue.package.version.detail",
url: ""
template: """
<p ng-bind="package.description"></p>
<ul class="package-meta inline">
<li ng-show="package.homepage"><i class="icon-link"></i><a ng-href="{{package.homepage}}">Website</a></li>
<li ng-show="package.documentation"><i class="icon-info-sign"></i><a ng-href="{{package.documentation}}">Docs</a></li>
</ul>
<details class="package-scripts" ng-show="currentVersion.scripts.length">
<summary>Scripts <span ng-bind-template="({{currentVersion.scripts.length}})"></span></summary>
<ol>
<li ng-repeat="url in currentVersion.scripts"><code ng-bind="url"></code></li>
</ol>
</details>
<details class="package-scripts" ng-show="currentVersion.styles.length">
<summary>Stylesheets <span ng-bind-template="({{currentVersion.styles.length}})"></span></summary>
<ol>
<li ng-repeat="url in currentVersion.styles"><code ng-bind="url"></code></li>
</ol>
</details>
<details class="package-scripts" ng-show="currentVersion.dependencies.length">
<summary>Stylesheets <span ng-bind-template="({{currentVersion.dependencies.length}})"></span></summary>
<ol>
<li ng-repeat="dependency in currentVersion.dependencies"><strong ng-bind="dependency.name"></strong> - <code ng-bind="dependency.semver"></code></li>
</ol>
</details>
<ul class="package-ops inline pull-right">
<li>
<button class="btn btn-small" tooltip="Add the selected package and its dependencies to your active plunk">
<i class="icon-magic"></i>
Add
</button>
</li>
<li>
<a class="btn btn-small btn-primary" ng-href="packages/{{package.name}}/edit" tooltip="Edit this package">
<i class="icon-pencil"></i>
Edit
</a>
</li>
</ul>
"""
controller: ["$scope", "$state", "$stateParams", ($scope, $state, $stateParams) ->
console.log "$stateParams", $stateParams
]
$stateProvider.state "catalogue.package.edit",
url: "/edit"
templateUrl: "partials/package/edit.html"
controller: ["$scope", "$state", "$stateParams", ($scope, $state, $stateParams) ->
$scope.prefix = "Edit: "
$scope.currentVersion = $scope.package.getMatchingVersion($stateParams.semver)
$scope.editing = angular.copy($scope.package)
$scope.cancel = ->
$state.transitionTo("catalogue.package.version.detail", $scope.package)
$scope.save = ->
$scope.package.save($scope.editing).then (pkg) ->
console.log "Package saved", pkg
$state.transitionTo("catalogue.package.version.detail", $scope.package)
]
$stateProvider.state "catalogue.package.version.edit",
url: "/edit"
template: """
<version-editor save="save(editing)" editing="editing"></version-editor>
"""
controller: ["$scope", "$state", "$stateParams", ($scope, $state, $stateParams) ->
$scope.prefix = "Edit: "
$scope.currentVersion = $scope.package.getMatchingVersion($stateParams.semver)
$scope.editing = angular.copy($scope.currentVersion)
]
]
app.config ["$locationProvider", "$routeProvider", ($locationProvider, $routeProvider) ->
$locationProvider.html5Mode(true)
$routeProvider.when "/",
controller: ["$state", ($state) ->
$state.transitionTo("catalogue.popular")
]
]
app.run ["$templateCache", ($templateCache) ->
$templateCache.put "partials/package/edit.html", """
<div class="sub-header">
<h3>{{prefix}}<a ng-href="packages/{{package.name}}/edit" ng-bind="package.name"></a></h3>
</div>
<form class="package-editor" ng-submit="save()">
<label>Package name:
<input ng-disabled="package.name" ng-model="editing.name" placeholder="package_name" required>
</label>
<label>Description:
<textarea ng-model="editing.description" placeholder="Package description..."></textarea>
</label>
<label>Website:
<input ng-model="editing.homepage" placeholder="http://homepage">
</label>
<label>Documentation:
<input ng-model="editing.documentation" placeholder="http://documentation">
</label>
<div ng-show="package.name">
<label>Versions:</label>
<ul class="" ng-show="package.name">
<li ng-repeat="version in editing.versions">
<a ng-href="packages/{{package.name}}/versions/{{version.semver}}/edit" ng-bind="version.semver"></a>
</li>
<li class="add-new">
<a ng-href="packages/{{package.name}}/versions/create">Add new...</a>
</li>
</ul>
</div>
<div>
<button class="btn btn-primary">Save</button>
<button class="btn" type="button" ng-click="cancel()">Cancel</button>
</div>
</form>
"""
]
app.directive "versionEditor", [ () ->
restrict: "E"
replace: true
scope:
editing: "="
package: "="
save: "&"
template: """
<form name="versionEditor" class="version-editor" ng-submit="save()">
<label>Semver:
<input ng-model="editing.semver" placeholder="0.0.1" semver required>
</label>
<label>Scripts:</label>
<ul class="">
<li ng-repeat="url in editing.scripts">
<ul class="inline pull-right">
<li><a ng-disabled="$first" ng-click="moveUp(editing.scripts, $index)"><i class="icon-arrow-up"></i></a></li>
<li><a ng-disabled="$last" ng-click="moveDown(editing.scripts, $index)"><i class="icon-arrow-down"></i></a></li>
<li><a ng-click="remove('script', editing.scripts, $index)"><i class="icon-trash"></i></a></li>
</ul>
<a ng-click="editUrl('script', editing.scripts, $index)" ng-bind="url"></a>
</li>
<li class="add-new">
<a ng-click="addNewElement('script', editing.scripts)">Add new...</a>
</li>
</ul>
<label>Styles:</label>
<ul class="">
<li ng-repeat="url in editing.styles">
<ul class="inline pull-right">
<li><a ng-disabled="$first" ng-click="moveUp(editing.styles, $index)"><i class="icon-arrow-up"></i></a></li>
<li><a ng-disabled="$last" ng-click="moveDown(editing.styles, $index)"><i class="icon-arrow-down"></i></a></li>
<li><a ng-click="remove('script', editing.styles, $index)"><i class="icon-trash"></i></a></li>
</ul>
<a ng-click="editUrl('script', editing.styles, $index)" ng-bind="url"></a>
</li>
<li class="add-new">
<a ng-click="addNewElement('script', editing.styles)">Add new...</a>
</li>
</ul>
<label>Dependencies:</label>
<ul class="">
<li ng-repeat="dep in editing.dependencies">
<ul class="inline pull-right">
<li><a ng-disabled="$first" ng-click="moveUp(editing.dependencies, $index)"><i class="icon-arrow-up"></i></a></li>
<li><a ng-disabled="$last" ng-click="moveDown(editing.dependencies, $index)"><i class="icon-arrow-down"></i></a></li>
<li><a ng-click="remove('dependency', editing.dependencies, $index)"><i class="icon-trash"></i></a></li>
</ul>
<a ng-click="editDependency('dependency', editing.dependencies, $index)" ng-bind="dep"></a>
</li>
<li class="add-new">
<a ng-click="addNewElement('dependency', editing.dependencies)">Add new...</a>
</li>
</ul>
<div>
<button ng-disabled="!versionEditor.$valid" class="btn btn-primary">Save</button>
<a class="btn" ng-href="packages/{{package.name}}/edit">Cancel</a>
</div>
</form>
"""
link: ($scope, $el, attrs) ->
$scope.editUrl = (type, list, index) ->
value = prompt("Enter the updated #{type}:", list[index])
if value then list[index] = value
$scope.addNewElement = (type, list) ->
value = prompt("Enter the new #{type}:")
if value then list.push(value)
$scope.moveUp = (list, index) ->
if index
prev = index - 1
[list[prev], list[index]] = [list[index], list[prev]]
$scope.moveDown = (list, index) ->
if index < list.length - 1
next = index + 1
[list[next], list[index]] = [list[index], list[next]]
$scope.remove = (type, list, index) ->
if confirm("Are you sure that you would like to remove the #{type} #{list[index]}?")
list.splice(index, 1)
]
app.directive "loading", [ () ->
]
app.run ["$rootScope", ($rootScope) ->
]
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8">
<title>AngularJS Plunker</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
<link href="//gregoryloucas.github.io/Fontstrap/assets/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<script src="http://code.angularjs.org/1.1.4/angular.js"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.3.0.min.js" type="text/javascript" charset="utf-8"></script>
<script src="http://angular-ui.github.io/ui-router/build/angular-ui-states.js"></script>
<script src="semver.js"></script>
<script src="app.js"></script>
<script src="catalogue.js"></script>
</head>
<body class="container" ui-view></body>
</html>
/* Put your css in here */
.plunker-searchbar {
background-color: #ccc;
margin: 0 -20px;
padding: 4px 20px;
}
h3 {
font-size: 16px;
line-height: 20px;
border-bottom: 1px solid #999;
}
details {
overflow-x: hidden;
}
.plunker-searchbox {
display: block;
position: relative;
height: 30px;
margin: 0;
margin-left: 44px;
button {
width: 40px;
position: absolute;
right: 0px;
}
.input-wrapper {
position: absolute;
display: block;
right: 39px;
left: 0;
top: 0;
bottom: 0;
input {
width: 100%;
height: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
}
}
.plunker-package-block {
h4 {
margin: 4px 0;
font-size: 14px;
}
.package-description {
font-size: 12px;
}
.package-versions {
display: inline-block;
margin-bottom: 0;
.label, .more {
font-size: 9px;
}
}
.package-meta {
font-size: 11px;
}
}
.package-editor {
label>textarea, label>input,.in-place>input {
width: 100%;
display: block;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
}
.ng-invalid {
border-color: red;
}
module = angular.module "plunker.catalogue", []
module.factory "catalogue", ["$http", ($http) ->
apiUrl = "http://api.plnkr.co"
identityMap = {}
class Package
constructor: (data = {}, options = {}) ->
if data.name and instance = identityMap[data.name]
instance.update(data)
return instance
pkg = @
angular.copy(data, pkg)
if options.serverState
pkg.$serverState = angular.copy(data)
identityMap[data.name] = pkg
else unless pkg.$serverState
pkg.then = -> pkg.refresh().then
update: (data = {}) ->
pkg = @
angular.extend pkg, data
pkg
save: (data = {}, options = {}) ->
pkg = @
throw new Error("Attempting to save a package without a primary key") unless pkg.name
options.cache = false
payload =
description: data.description
homepage: data.homepage
documentation: data.documentation
request = $http.post("#{apiUrl}/catalogue/packages/#{pkg.name}", payload, options).then (response) ->
pkg.update(response.data)
delete pkg.then
pkg
pkg.then = request.then.bind(request)
pkg
addVersion: (data = {}, options = {}) ->
pkg = @
throw new Error("Attempting to add a version without a primary key") unless pkg.name
options.cache = false
payload = {}
payload.scripts = angular.copy(data.scripts) if data.scripts.length
payload.styles = angular.copy(data.styles) if data.styles.length
request = $http.post("#{apiUrl}/catalogue/packages/#{pkg.name}/versions", payload, options).then (response) ->
pkg.update(response.data)
delete pkg.then
pkg
pkg.then = request.then.bind(request)
pkg
bump: (options = {}) ->
pkg = @
throw new Error("Attempting to refresh a package without a primary key") unless pkg.name
options.cache = false
request = $http.post("#{apiUrl}/catalogue/packages/#{pkg.name}/bump", {}, options).then (response) ->
pkg.update(response.data)
delete pkg.then
pkg
pkg.then = request.then.bind(request)
pkg
refresh: (options = {})->
pkg = @
throw new Error("Attempting to refresh a package without a primary key") unless pkg.name
options.cache = false
request = $http.get("#{apiUrl}/catalogue/packages/#{pkg.name}", options).then (response) ->
pkg.update(response.data)
delete pkg.then
pkg
pkg.then = request.then.bind(request)
pkg
# TODO: This does nothing useful
getMatchingVersion: (range = "*") ->
bestMatch = null
for version in @versions when semver.satisfies(version.semver, range) and (not bestMatch or semver.gt(version.semver, bestMatch.semver))
bestMatch = version
bestMatch
# Coffee-Script will implicitly return the object below
# Return an array of trending plunks
findOne: (data = {}) -> new Package(data)
search: (query) -> @findAll(params: query: query)
findAll: (options = {}) ->
options.cache = false
options.url ||= "#{apiUrl}/catalogue/packages"
packages = []
packages.loading = true
packages.refresh = ->
request = $http.get(options.url, options).then (response) ->
packages.length = 0
packages.push(new Package(json, serverState: true)) for json in response.data
delete packages.loading
delete packages.then
packages
packages.then = request.then.bind(request)
packages
packages.then = -> packages.refresh().then
packages
create: (data, options = {}) ->
options.cache ?= true
options.url ||= "#{apiUrl}/catalogue/packages"
request = $http.post(options.url, data, options).then (response) ->
new Package(response.data, $serverState: response.data)
, (err) -> new Error("Failed to create package" + err.message)
]
module.config ["$httpProvider", ($httpProvider) ->
$httpProvider.defaults.headers.common['Authorization'] = "token 51782caaa65dee000000000a"
]
;(function (exports) { // nothing in here is node-specific.
// See http://semver.org/
// This implementation is a *hair* less strict in that it allows
// v1.2.3 things, and also tags that don't begin with a char.
var semver = "\\s*[v=]*\\s*([0-9]+)" // major
+ "\\.([0-9]+)" // minor
+ "\\.([0-9]+)" // patch
+ "(-[0-9]+-?)?" // build
+ "([a-zA-Z-+][a-zA-Z0-9-\.:]*)?" // tag
, exprComparator = "^((<|>)?=?)\s*("+semver+")$|^$"
, xRangePlain = "[v=]*([0-9]+|x|X|\\*)"
+ "(?:\\.([0-9]+|x|X|\\*)"
+ "(?:\\.([0-9]+|x|X|\\*)"
+ "([a-zA-Z-][a-zA-Z0-9-\.:]*)?)?)?"
, xRange = "((?:<|>)=?)?\\s*" + xRangePlain
, exprLoneSpermy = "(?:~>?)"
, exprSpermy = exprLoneSpermy + xRange
, expressions = exports.expressions =
{ parse : new RegExp("^\\s*"+semver+"\\s*$")
, parsePackage : new RegExp("^\\s*([^\/]+)[-@](" +semver+")\\s*$")
, parseRange : new RegExp(
"^\\s*(" + semver + ")\\s+-\\s+(" + semver + ")\\s*$")
, validComparator : new RegExp("^"+exprComparator+"$")
, parseXRange : new RegExp("^"+xRange+"$")
, parseSpermy : new RegExp("^"+exprSpermy+"$")
}
Object.getOwnPropertyNames(expressions).forEach(function (i) {
exports[i] = function (str) {
return ("" + (str || "")).match(expressions[i])
}
})
exports.rangeReplace = ">=$1 <=$7"
exports.clean = clean
exports.compare = compare
exports.rcompare = rcompare
exports.satisfies = satisfies
exports.gt = gt
exports.gte = gte
exports.lt = lt
exports.lte = lte
exports.eq = eq
exports.neq = neq
exports.cmp = cmp
exports.inc = inc
exports.valid = valid
exports.validPackage = validPackage
exports.validRange = validRange
exports.maxSatisfying = maxSatisfying
exports.replaceStars = replaceStars
exports.toComparators = toComparators
function stringify (version) {
var v = version
return [v[1]||'', v[2]||'', v[3]||''].join(".") + (v[4]||'') + (v[5]||'')
}
function clean (version) {
version = exports.parse(version)
if (!version) return version
return stringify(version)
}
function valid (version) {
if (typeof version !== "string") return null
return exports.parse(version) && version.trim().replace(/^[v=]+/, '')
}
function validPackage (version) {
if (typeof version !== "string") return null
return version.match(expressions.parsePackage) && version.trim()
}
// range can be one of:
// "1.0.3 - 2.0.0" range, inclusive, like ">=1.0.3 <=2.0.0"
// ">1.0.2" like 1.0.3 - 9999.9999.9999
// ">=1.0.2" like 1.0.2 - 9999.9999.9999
// "<2.0.0" like 0.0.0 - 1.9999.9999
// ">1.0.2 <2.0.0" like 1.0.3 - 1.9999.9999
var starExpression = /(<|>)?=?\s*\*/g
, starReplace = ""
, compTrimExpression = new RegExp("((<|>)?=|<|>)\\s*("
+semver+"|"+xRangePlain+")", "g")
, compTrimReplace = "$1$3"
function toComparators (range) {
var ret = (range || "").trim()
.replace(expressions.parseRange, exports.rangeReplace)
.replace(compTrimExpression, compTrimReplace)
.split(/\s+/)
.join(" ")
.split("||")
.map(function (orchunk) {
return orchunk
.replace(new RegExp("(" + exprLoneSpermy + ")\\s+"), "$1")
.split(" ")
.map(replaceXRanges)
.map(replaceSpermies)
.map(replaceStars)
.join(" ").trim()
})
.map(function (orchunk) {
return orchunk
.trim()
.split(/\s+/)
.filter(function (c) { return c.match(expressions.validComparator) })
})
.filter(function (c) { return c.length })
return ret
}
function replaceStars (stars) {
return stars.trim().replace(starExpression, starReplace)
}
// "2.x","2.x.x" --> ">=2.0.0- <2.1.0-"
// "2.3.x" --> ">=2.3.0- <2.4.0-"
function replaceXRanges (ranges) {
return ranges.split(/\s+/)
.map(replaceXRange)
.join(" ")
}
function replaceXRange (version) {
return version.trim().replace(expressions.parseXRange,
function (v, gtlt, M, m, p, t) {
var anyX = !M || M.toLowerCase() === "x" || M === "*"
|| !m || m.toLowerCase() === "x" || m === "*"
|| !p || p.toLowerCase() === "x" || p === "*"
, ret = v
if (gtlt && anyX) {
// just replace x'es with zeroes
;(!M || M === "*" || M.toLowerCase() === "x") && (M = 0)
;(!m || m === "*" || m.toLowerCase() === "x") && (m = 0)
;(!p || p === "*" || p.toLowerCase() === "x") && (p = 0)
ret = gtlt + M+"."+m+"."+p+"-"
} else if (!M || M === "*" || M.toLowerCase() === "x") {
ret = "*" // allow any
} else if (!m || m === "*" || m.toLowerCase() === "x") {
// append "-" onto the version, otherwise
// "1.x.x" matches "2.0.0beta", since the tag
// *lowers* the version value
ret = ">="+M+".0.0- <"+(+M+1)+".0.0-"
} else if (!p || p === "*" || p.toLowerCase() === "x") {
ret = ">="+M+"."+m+".0- <"+M+"."+(+m+1)+".0-"
}
return ret
})
}
// ~, ~> --> * (any, kinda silly)
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0
function replaceSpermies (version) {
return version.trim().replace(expressions.parseSpermy,
function (v, gtlt, M, m, p, t) {
if (gtlt) throw new Error(
"Using '"+gtlt+"' with ~ makes no sense. Don't do it.")
if (!M || M.toLowerCase() === "x") {
return ""
}
// ~1 == >=1.0.0- <2.0.0-
if (!m || m.toLowerCase() === "x") {
return ">="+M+".0.0- <"+(+M+1)+".0.0-"
}
// ~1.2 == >=1.2.0- <1.3.0-
if (!p || p.toLowerCase() === "x") {
return ">="+M+"."+m+".0- <"+M+"."+(+m+1)+".0-"
}
// ~1.2.3 == >=1.2.3- <1.3.0-
t = t || "-"
return ">="+M+"."+m+"."+p+t+" <"+M+"."+(+m+1)+".0-"
})
}
function validRange (range) {
range = replaceStars(range)
var c = toComparators(range)
return (c.length === 0)
? null
: c.map(function (c) { return c.join(" ") }).join("||")
}
// returns the highest satisfying version in the list, or null
function maxSatisfying (versions, range) {
return versions
.filter(function (v) { return satisfies(v, range) })
.sort(compare)
.pop() || null
}
function satisfies (version, range) {
version = valid(version)
if (!version) return false
range = toComparators(range)
for (var i = 0, l = range.length ; i < l ; i ++) {
var ok = false
for (var j = 0, ll = range[i].length ; j < ll ; j ++) {
var r = range[i][j]
, gtlt = r.charAt(0) === ">" ? gt
: r.charAt(0) === "<" ? lt
: false
, eq = r.charAt(!!gtlt) === "="
, sub = (!!eq) + (!!gtlt)
if (!gtlt) eq = true
r = r.substr(sub)
r = (r === "") ? r : valid(r)
ok = (r === "") || (eq && r === version) || (gtlt && gtlt(version, r))
if (!ok) break
}
if (ok) return true
}
return false
}
// return v1 > v2 ? 1 : -1
function compare (v1, v2) {
var g = gt(v1, v2)
return g === null ? 0 : g ? 1 : -1
}
function rcompare (v1, v2) {
return compare(v2, v1)
}
function lt (v1, v2) { return gt(v2, v1) }
function gte (v1, v2) { return !lt(v1, v2) }
function lte (v1, v2) { return !gt(v1, v2) }
function eq (v1, v2) { return gt(v1, v2) === null }
function neq (v1, v2) { return gt(v1, v2) !== null }
function cmp (v1, c, v2) {
switch (c) {
case ">": return gt(v1, v2)
case "<": return lt(v1, v2)
case ">=": return gte(v1, v2)
case "<=": return lte(v1, v2)
case "==": return eq(v1, v2)
case "!=": return neq(v1, v2)
case "===": return v1 === v2
case "!==": return v1 !== v2
default: throw new Error("Y U NO USE VALID COMPARATOR!? "+c)
}
}
// return v1 > v2
function num (v) {
return v === undefined ? -1 : parseInt((v||"0").replace(/[^0-9]+/g, ''), 10)
}
function gt (v1, v2) {
v1 = exports.parse(v1)
v2 = exports.parse(v2)
if (!v1 || !v2) return false
for (var i = 1; i < 5; i ++) {
v1[i] = num(v1[i])
v2[i] = num(v2[i])
if (v1[i] > v2[i]) return true
else if (v1[i] !== v2[i]) return false
}
// no tag is > than any tag, or use lexicographical order.
var tag1 = v1[5] || ""
, tag2 = v2[5] || ""
// kludge: null means they were equal. falsey, and detectable.
// embarrassingly overclever, though, I know.
return tag1 === tag2 ? null
: !tag1 ? true
: !tag2 ? false
: tag1 > tag2
}
function inc (version, release) {
version = exports.parse(version)
if (!version) return null
var parsedIndexLookup =
{ 'major': 1
, 'minor': 2
, 'patch': 3
, 'build': 4 }
var incIndex = parsedIndexLookup[release]
if (incIndex === undefined) return null
var current = num(version[incIndex])
version[incIndex] = current === -1 ? 1 : current + 1
for (var i = incIndex + 1; i < 5; i ++) {
if (num(version[i]) !== -1) version[i] = "0"
}
if (version[4]) version[4] = "-" + version[4]
version[5] = ""
return stringify(version)
}
})(typeof exports === "object" ? exports : semver = {})