From 5d5d1876da8aa414f14e364a7984e59cd1575b2e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 21 Aug 2014 17:00:21 -0700 Subject: [PATCH] De-dup package name/constraint parsing code Now we can all any of them from even before uniload is ready in the tool I <3 symlinks --- .../package-version-parser.js | 117 +--------------- tools/package-version-parser.js | 126 ++++++++++++++++++ tools/utils.js | 62 +-------- 3 files changed, 131 insertions(+), 174 deletions(-) mode change 100644 => 120000 packages/package-version-parser/package-version-parser.js create mode 100644 tools/package-version-parser.js diff --git a/packages/package-version-parser/package-version-parser.js b/packages/package-version-parser/package-version-parser.js deleted file mode 100644 index 612837246f..0000000000 --- a/packages/package-version-parser/package-version-parser.js +++ /dev/null @@ -1,116 +0,0 @@ -var semver = Npm.require('semver'); - -PackageVersion = {}; - -// Conceptually we have three types of constraints: -// 1. "compatible-with" - A@x.y.z - constraints package A to version x.y.z or -// higher, as long as the version is backwards compatible with x.y.z. -// "pick A compatible with x.y.z" -// It is the default type. -// 2. "exactly" - A@=x.y.z - constraints package A only to version x.y.z and -// nothing else. -// "pick A exactly at x.y.z" -// 3. "at-least" - A@>=x.y.z - constraints package A to version x.y.z or higher. -// "pick A at least at x.y.z" -// This one is only used internally by the constraint solver --- end users -// shouldn't be allowed to specify it, and you need to specially request it -// with the "allowAtLeast" option. -PackageVersion.parseVersionConstraint = function (versionString, options) { - options = options || {}; - var versionDesc = { version: null, type: "compatible-with", - constraintString: versionString }; - - if (versionString === "none" || versionString === null) { - versionDesc.type = "at-least"; - versionDesc.version = "0.0.0"; - return versionDesc; - } - - if (versionString.charAt(0) === '=') { - versionDesc.type = "exactly"; - versionString = versionString.substr(1); - } else if (options.allowAtLeast && versionString.substr(0, 2) === '>=') { - versionDesc.type = "at-least"; - versionString = versionString.substr(2); - } - - // XXX check for a dash in the version in case of foo@1.2.3-rc0 - - if (! semver.valid(versionString)) { - throwVersionParserError( - "Version string must look like semver (eg '1.2.3'), not '" - + versionString + "'."); - } - - versionDesc.version = versionString; - - return versionDesc; -}; - -PackageVersion.parseConstraint = function (constraintString, options) { - if (typeof constraintString !== "string") - throw new TypeError("constraintString must be a string"); - options = options || {}; - - var splitted = constraintString.split('@'); - - var constraint = { name: "", version: null, - type: "compatible-with", constraintString: null }; - var name = splitted[0]; - var versionString = splitted[1]; - - if (splitted.length > 2) { - // throw error complaining about @ - PackageVersion.validatePackageName('a@'); - } - PackageVersion.validatePackageName(name); - - constraint.name = name; - - if (splitted.length === 2 && !versionString) { - throwVersionParserError( - "Version constraint for package '" + name + - "' cannot be empty; leave off the @ if you don't want to constrain " + - "the version."); - } - - if (versionString) { - _.extend(constraint, - PackageVersion.parseVersionConstraint(versionString, options)); - } - - return constraint; -}; - -// XXX duplicates code in utils.js, sigh. but we need to run -// this before we can load packages. -PackageVersion.validatePackageName = function (packageName, options) { - options = options || {}; - - var badChar = packageName.match(/[^a-z0-9:.\-]/); - if (badChar) { - if (options.detailedColonExplanation) { - throwVersionParserError( - "Bad character in package name: " + JSON.stringify(badChar[0]) + - ". Package names can only contain lowercase ASCII alphanumerics, " + - "dash, or dot. If you plan to publish a package, it must be " + - "prefixed with your Meteor developer username and a colon."); - } - throwVersionParserError( - "Package names can only contain lowercase ASCII alphanumerics, dash, " + - "dot, or colon, not " + JSON.stringify(badChar[0]) + "."); - } - if (!/[a-z]/.test(packageName)) { - throwVersionParserError("Package names must contain a lowercase ASCII letter."); - } - if (packageName[0] === '.') { - throwVersionParserError("Package names may not begin with a dot."); - } -}; -// XXX duplicates code in utils.js, sigh. but we need to run -// this before we can load packages. -var throwVersionParserError = function (message) { - var e = new Error(message); - e.versionParserError = true; - throw e; -}; diff --git a/packages/package-version-parser/package-version-parser.js b/packages/package-version-parser/package-version-parser.js new file mode 120000 index 0000000000..a4d2bd5bdd --- /dev/null +++ b/packages/package-version-parser/package-version-parser.js @@ -0,0 +1 @@ +../../tools/package-version-parser.js \ No newline at end of file diff --git a/tools/package-version-parser.js b/tools/package-version-parser.js new file mode 100644 index 0000000000..a45d873c07 --- /dev/null +++ b/tools/package-version-parser.js @@ -0,0 +1,126 @@ +// This file is in tools/package-version-parser.js and is symlinked into +// packages/package-version-parser/package-version-parser.js. It's part +// of both the tool and the package! We don't use uniload for it because +// it needs to be used as part of initializing the uniload catalog. + +var inTool = typeof Package === 'undefined'; + +var PV; +if (inTool) { + PV = exports; +} else { + PackageVersion = PV = {}; +} + +var semver = inTool ? require ('semver') : Npm.require('semver'); +var __ = inTool ? require('underscore') : _; + +// Conceptually we have three types of constraints: +// 1. "compatible-with" - A@x.y.z - constraints package A to version x.y.z or +// higher, as long as the version is backwards compatible with x.y.z. +// "pick A compatible with x.y.z" +// It is the default type. +// 2. "exactly" - A@=x.y.z - constraints package A only to version x.y.z and +// nothing else. +// "pick A exactly at x.y.z" +// 3. "at-least" - A@>=x.y.z - constraints package A to version x.y.z or higher. +// "pick A at least at x.y.z" +// This one is only used internally by the constraint solver --- end users +// shouldn't be allowed to specify it, and you need to specially request it +// with the "allowAtLeast" option. +PV.parseVersionConstraint = function (versionString, options) { + options = options || {}; + var versionDesc = { version: null, type: "compatible-with", + constraintString: versionString }; + + if (versionString === "none" || versionString === null) { + versionDesc.type = "at-least"; + versionDesc.version = "0.0.0"; + return versionDesc; + } + + if (versionString.charAt(0) === '=') { + versionDesc.type = "exactly"; + versionString = versionString.substr(1); + } else if (options.allowAtLeast && versionString.substr(0, 2) === '>=') { + versionDesc.type = "at-least"; + versionString = versionString.substr(2); + } + + // XXX check for a dash in the version in case of foo@1.2.3-rc0 + + if (! semver.valid(versionString)) { + throwVersionParserError( + "Version string must look like semver (eg '1.2.3'), not '" + + versionString + "'."); + } + + versionDesc.version = versionString; + + return versionDesc; +}; + +PV.parseConstraint = function (constraintString, options) { + if (typeof constraintString !== "string") + throw new TypeError("constraintString must be a string"); + options = options || {}; + + var splitted = constraintString.split('@'); + + var constraint = { name: "", version: null, + type: "compatible-with", constraintString: null }; + var name = splitted[0]; + var versionString = splitted[1]; + + if (splitted.length > 2) { + // throw error complaining about @ + PV.validatePackageName('a@'); + } + PV.validatePackageName(name); + + constraint.name = name; + + if (splitted.length === 2 && !versionString) { + throwVersionParserError( + "Version constraint for package '" + name + + "' cannot be empty; leave off the @ if you don't want to constrain " + + "the version."); + } + + if (versionString) { + __.extend(constraint, + PV.parseVersionConstraint(versionString, options)); + } + + return constraint; +}; + +PV.validatePackageName = function (packageName, options) { + options = options || {}; + + var badChar = packageName.match(/[^a-z0-9:.\-]/); + if (badChar) { + if (options.detailedColonExplanation) { + throwVersionParserError( + "Bad character in package name: " + JSON.stringify(badChar[0]) + + ". Package names can only contain lowercase ASCII alphanumerics, " + + "dash, or dot. If you plan to publish a package, it must be " + + "prefixed with your Meteor developer username and a colon."); + } + throwVersionParserError( + "Package names can only contain lowercase ASCII alphanumerics, dash, " + + "dot, or colon, not " + JSON.stringify(badChar[0]) + "."); + } + if (!/[a-z]/.test(packageName)) { + throwVersionParserError("Package names must contain a lowercase ASCII letter."); + } + if (packageName[0] === '.') { + throwVersionParserError("Package names may not begin with a dot."); + } +}; + +var throwVersionParserError = function (message) { + var e = new Error(message); + e.versionParserError = true; + throw e; +}; diff --git a/tools/utils.js b/tools/utils.js index 918802d901..fb26c20375 100644 --- a/tools/utils.js +++ b/tools/utils.js @@ -4,6 +4,7 @@ var _ = require('underscore'); var archinfo = require('./archinfo.js'); var files = require('./files.js'); var main = require('./main.js'); +var packageVersionParser = require('./package-version-parser.js'); var semver = require('semver'); var os = require('os'); var fs = require('fs'); @@ -140,26 +141,9 @@ exports.randomPort = function () { return 20000 + Math.floor(Math.random() * 10000); }; -(function () { - var PackageVersion = null; - - var maybeLoadPackageVersionParser = function () { - if (PackageVersion) - return; - var uniload = require('./uniload.js'); - var Package = uniload.load({packages: ['package-version-parser']}); - PackageVersion = Package['package-version-parser'].PackageVersion; - }; - - exports.parseVersionConstraint = function () { - maybeLoadPackageVersionParser(); - return PackageVersion.parseVersionConstraint.apply(this, arguments); - }; - exports.parseConstraint = function () { - maybeLoadPackageVersionParser(); - return PackageVersion.parseConstraint.apply(this, arguments); - }; -})(); +exports.parseVersionConstraint = packageVersionParser.parseVersionConstraint; +exports.parseConstraint = packageVersionParser.parseConstraint; +exports.validatePackageName = packageVersionParser.validatePackageName; // XXX should unify this with utils.parseConstraint exports.splitConstraint = function (constraint) { @@ -196,44 +180,6 @@ exports.dealConstraint = function (constraint, pkg) { // eg package-client.js) prints and throws the "exit with code 1" exception // on failure. - -// XXX duplicates code in package-version-parser.js, sigh. but we need to run -// this before we can load packages. -exports.validatePackageName = function (packageName, options) { - options = options || {}; - - var badChar = packageName.match(/[^a-z0-9:.\-]/); - if (badChar) { - if (options.detailedColonExplanation) { - throwVersionParserError( - "Bad character in package name: " + JSON.stringify(badChar[0]) + - ". Package names can only contain lowercase ASCII alphanumerics, " + - "dash, or dot. If you plan to publish a package, it must be " + - "prefixed with your Meteor developer username and a colon."); - } - throwVersionParserError( - "Package names can only contain lowercase ASCII alphanumerics, dash, " + - "dot, or colon, not " + JSON.stringify(badChar[0]) + "."); - } - if (!/[a-z]/.test(packageName)) { - throwVersionParserError("Package names must contain a lowercase ASCII letter."); - } - if (packageName[0] === '.') { - throwVersionParserError("Package names may not begin with a dot."); - } -}; -// XXX duplicates code in package-version-parser.js, sigh. but we need to run -// this before we can load packages. -var throwVersionParserError = function (message) { - var e = new Error(message); - e.versionParserError = true; - throw e; -}; - - -// -// Returns a bool saying whether it is valid or not. Use validatePackageName -// if you want a nice error message. exports.isValidPackageName = function (packageName) { try { exports.validatePackageName(packageName);