First commit

This commit is contained in:
2021-03-07 05:58:59 +01:00
commit 8204c6b556
18475 changed files with 3309357 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
uf-content
deploy
bin
test
deploy_key
www/bower_components

View File

@@ -0,0 +1,23 @@
# Compiled files
*.exe
*.n
*.zip
test/libraries/vsc/
dump/
deploy/
repo_integration_tests/
# run.n is in the repo
!/run.n
# Runtime files
*.tmp
*.db
# IDE files
.idea
deploy_key
# Bower
www/bower_components

View File

@@ -0,0 +1,100 @@
language: haxe
env:
global:
# deploy_key_decrypt
- secure: "cWYcbB7z97sW4yZtz9qcYrEhFZktNjQMOraPm7Vy0FsCMqb4r314Fk8rJze+yDbOKGdsIz6VNHE8nlsQN3EgGiVFJnF8xv6GAlfHPuaVomP2vO7W0A+l7xVy2/326MQJP8JoMgdebDi71yxKeEhYA+mVliI9qql9yfD2YOBmilQ="
matrix:
include:
# haxe development + neko master
- os: linux
language: generic
sudo: required
env:
- TRAVIS_HAXE_VERSION=development
before_install:
- sudo add-apt-repository ppa:haxe/snapshots -y
- sudo apt-get update
- sudo apt-get install haxe=1:3.3* neko-dev libapache2-mod-neko -y
- mkdir ~/haxelib && haxelib setup ~/haxelib
# haxe 3.2.1 + neko master
- os: linux
language: generic
sudo: required
env:
- TRAVIS_HAXE_VERSION=3.2.1
- DEPLOY=1
before_install:
- sudo add-apt-repository ppa:haxe/snapshots -y
- sudo add-apt-repository ppa:haxe/releases -y
- sudo apt-get update
- sudo apt-get install haxe=1:3.2.1* neko-dev libapache2-mod-neko -y
- mkdir ~/haxelib && haxelib setup ~/haxelib
# haxe 3.1.3 + neko 2.0.0
- os: linux
language: haxe
haxe: 3.1.3
sudo: required
env:
- USE_DOCKER=1
services: [docker]
before_install:
- sudo apt-get update
- sudo apt-get -o Dpkg::Options::=--force-confnew install docker-engine -y
- docker --version
# https://docs.travis-ci.com/user/docker/#Using-Docker-Compose
- sudo rm /usr/local/bin/docker-compose
- curl -L https://github.com/docker/compose/releases/download/1.6.2/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
# Mac
- os: osx
language: haxe
haxe: development
- os: osx
language: haxe
haxe: 3.2.1
- os: osx
language: haxe
haxe: 3.1.3
install:
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then
brew update;
fi
- npm -g install bower
- yes | haxelib install all
- yes | haxelib install tora
# We need https://github.com/HaxeFoundation/hscript/commit/bd8c016bbadc93c2db8deb75d7056703ac3e3ff8
- if [ "${TRAVIS_HAXE_VERSION}" = "development" ]; then
haxelib git hscript https://github.com/HaxeFoundation/hscript.git;
fi
- haxelib list
- haxelib run tora &
script:
# run our CI script
- haxe ci.hxml
# Deploy to lib.haxe.org
- if [ -n "$deploy_key_decrypt" ] && [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$TRAVIS_BRANCH" = "master" ] && [ -n "$DEPLOY" ]; then
openssl aes-256-cbc -k "$deploy_key_decrypt" -in deploy_key.enc -out deploy_key -d;
chmod 600 deploy_key;
eval `ssh-agent -s`;
ssh-add deploy_key;
scp haxelib@lib.haxe.org:/data/haxelib/www/.htaccess www/;
scp haxelib@lib.haxe.org:/data/haxelib/www/dbconfig.json www/;
pushd www;
bower install;
popd;
git config --global user.name "TravisCI";
git config --global user.email "travisci@haxe.org";
haxelib run ufront deploy;
fi
cache:
directories:
- deploy
addons:
ssh_known_hosts: lib.haxe.org

View File

@@ -0,0 +1,3 @@
{
"haxe.buildFile": "client.hxml"
}

View File

@@ -0,0 +1,18 @@
{
"version": "0.1.0",
"command": "haxe",
"args": ["client.hxml"],
"problemMatcher": {
"owner": "haxe",
"pattern": {
"regexp": "^(.+):(\\d+): (?:lines \\d+-(\\d+)|character(?:s (\\d+)-| )(\\d+)) : (?:(Warning) : )?(.*)$",
"file": 1,
"line": 2,
"endLine": 3,
"column": 4,
"endColumn": 5,
"severity": 6,
"message": 7
}
}
}

View File

@@ -0,0 +1,15 @@
## next version (2016-??-??)
- New haxelib self-updating mechanism (#172, #293)
- Haxelib new version notification (#282)
- Partial download resume support (#133)
- Respect `-notimeout` for uploading and downloading files (#235)
- `haxelib run` now sets `HAXELIB_RUN_NAME` environment variable to the library name (#293)
- Fixed order of library versions in `haxelib list` (#83)
- Merged `upgrade` and `update` commands (#188)
- Deprecated now redundant commands: `local`, `selfupdate` (#288)
- Fixed suggested repository path on Linux (#242)
- Suggested repository path on OSX is now `/usr/local/lib/haxe/lib` (#250)
- `haxelib install <file>.hxml` now checks hxml files recursively (#200)
- Git/Hg checkouts don't set dev mode unless subdir is specified now (#263)
- Tons of smaller fixes, cleanups and optimizations

View File

@@ -0,0 +1,35 @@
# Build haxelib server as a Docker container.
# Note that it doesn't contain a MySQL database,
# which need to be launched seperately. See test/docker-compose.yml on how to launch one.
FROM andyli/tora
# apt-get dependencies of bower
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
npm \
nodejs-legacy \
git \
&& rm -r /var/lib/apt/lists/*
RUN npm -g install bower
COPY server*.hxml /src/
WORKDIR /src
RUN haxelib setup /haxelib
RUN haxelib install all --always
COPY www /src/www/
COPY src /src/src/
RUN rm -rf /var/www/html
RUN ln -s /src/www /var/www/html
WORKDIR /src/www
RUN bower install --allow-root
WORKDIR /src
RUN haxe server.hxml

View File

@@ -0,0 +1,86 @@
[![TravisCI Build Status](https://travis-ci.org/HaxeFoundation/haxelib.svg?branch=development)](https://travis-ci.org/HaxeFoundation/haxelib)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/HaxeFoundation/haxelib?branch=development&svg=true)](https://ci.appveyor.com/project/HaxeFoundation/haxelib)
# Haxelib: library manager for Haxe
Haxelib is a library management tool shipped with the [Haxe Toolkit](http://haxe.org/).
It allows searching, installing, upgrading and removing libraries from the [haxelib repository](http://lib.haxe.org/) as well as submitting libraries to it.
For more documentation, please refer to http://lib.haxe.org/documentation/
## Development info
### Running the haxelib server for development
The server has to be compiled with Haxe 3.2.1+. It can be run in Apache using mod_neko / mod_tora.
Currently using [Docker](https://www.docker.com/) is the simpliest way to build and run the server. It doesn't require setting up Apache or MySQL since everything is included in the container.
To start, run:
```
docker-compose -f test/docker-compose.yml up
```
The command above will copy the server source code and website resources into a container, compile it, and then start Apache to serve it. To view the website, visit `http://$(docker-machine ip):2000/` (Windows and Mac) or `http://localhost:2000/` (Linux).
To stop the server, run:
```
docker-compose -f test/docker-compose.yml down
```
If we modify any of the server source code or website resources, we need to stop the server and then rebuild the image by running the command as follows:
```
docker-compose -f test/docker-compose.yml build
```
To run haxelib client with this local server, prepend the arguments, `-R $SERVER_URL`, to each of the haxelib commands, e.g.:
```
neko bin/haxelib.n -R http://$(docker-machine ip):2000/ search foo
```
To run integration tests with the local development server:
```
# prepare the test files
haxe prepare_tests.hxml
# set `HAXELIB_SERVER` and `HAXELIB_SERVER_PORT`
# note that `HAXELIB_SERVER` is also used for the tests to connect to the MySQL server for resetting database
export HAXELIB_SERVER=$(docker-machine ip)
export HAXELIB_SERVER_PORT=2000
# run the tests
haxe integration_tests.hxml
```
Note that the integration tests will reset the server database before and after each test.
### About this repo
Build files:
* client.hxml: Build the current haxelib client.
* client_tests.hxml: Build and run the client tests.
* client_legacy.hxml: Build the haxelib client that works with Haxe 2.x.
* server.hxml: Build the new website, and the Haxe remoting API.
* server_tests.hxml: Build and run the new website tests.
* server_each.hxml: Libraries and configs used by server.hxml and server_tests.hxml.
* server_legacy.hxml: Build the legacy website.
* integration_tests.hxml: Build and run tests that test haxelib client and server together.
* package.hxml: Package the client as package.zip for submitting to the lib.haxe.org as [haxelib](http://lib.haxe.org/p/haxelib/).
* prepare_tests.hxml: Package the test libs.
* ci.hxml: Used by our CIs, TravisCI and AppVeyor.
Folders:
* /src/: Source code for the haxelib tool and the website, including legacy versions.
* /bin/: The compile target for building the haxelib client, legacy client, and others.
* /www/: The compile target (and supporting files) for the haxelib website (including legacy server)
* /test/: Source code and files for testings.
Other files:
* schema.json: JSON schema of haxelib.json.
* deploy.json: Deploy configuration used by `haxelib run ufront deploy` for pushing the haxelib website to lib.haxe.org.
* deploy_key.enc: Encrypted ssh private key for logging in to lib.haxe.org. Used by TravisCI.
* Dockerfile: Docker build file for building an image that runs the haxelib server.

View File

@@ -0,0 +1,19 @@
version: "{build}"
services:
- mysql
install:
- npm -g install bower
- cinst haxe -y
- cinst nssm -y
- RefreshEnv
- haxelib setup haxelib_repo
- haxelib install all --always
- haxelib install tora
- haxelib list
build: off
test_script:
- haxe ci.hxml

View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -0,0 +1,2 @@
-cp test
-x RunCi

View File

@@ -0,0 +1,3 @@
-cp src
-neko run.n
-main haxelib.client.Main

View File

@@ -0,0 +1,3 @@
-cp src
-neko bin/legacyhaxelib.n
-main legacyhaxelib.Main

View File

@@ -0,0 +1,6 @@
-cp src
-cp test
-main HaxelibTests
-debug
-neko bin/test.n
-cmd neko bin/test.n

View File

@@ -0,0 +1,42 @@
{
"files": [
"api/",
"css/",
"documentation-files/",
"img/",
"js/",
"view/",
"dbconfig.json",
"favicon.ico",
"index.n",
"tasks.n",
".htaccess"
],
"targets": [
{
"name": "lib.haxe.org",
"gitRepo": "ssh://haxelib@lib.haxe.org/data/haxelib/haxelibwebsite.git",
"defines": ["deploy"],
"hxmls": ["server"],
"debug": false,
"servers": [{
"name": "lib.haxe.org",
"user": "haxelib",
"host": "lib.haxe.org",
"port": 22,
"remoteDir": "/data/haxelib/www/"
}]
}
],
"hooks": {
"beforeBuild": [],
"afterBuild": [],
"beforeCopy": [],
"afterCopy": [],
"beforePush": [],
"afterPush": [],
"beforePull": [],
"afterPull": ["touch index.n"],
"afterComplete": []
}
}

Binary file not shown.

View File

@@ -0,0 +1,11 @@
{
"name": "haxelib",
"url" : "http://haxe.org/haxelib",
"license": "GPL",
"tags": ["haxelib", "core"],
"description": "The haxelib client",
"classPath": "src",
"version": "3.3.0",
"releasenote": "Initial release for Haxe 3.3",
"contributors": ["back2dos", "ncannasse", "jason", "Simn", "nadako"]
}

View File

@@ -0,0 +1,5 @@
-cp src
-cp test
-main IntegrationTests
-neko bin/integration_tests.n
-cmd neko bin/integration_tests.n

2
hGameTest/node_modules/haxe/downloads/haxelib/package.hxml generated vendored Executable file
View File

@@ -0,0 +1,2 @@
-cp src
--run Package

View File

@@ -0,0 +1,2 @@
-cp test
--run Prepare

BIN
hGameTest/node_modules/haxe/downloads/haxelib/run.n generated vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,93 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "A haxelib project",
"type": "object",
"properties": {
"name": { "$ref": "#/definitions/projectName" },
"url": {
"description": "Project's website",
"type": "string",
"format": "uri"
},
"description": {
"description": "Short description of the project",
"type": "string"
},
"license": {
"description": "Open source license under which the project is licensed",
"enum": ["GPL", "LGPL", "BSD", "Public", "MIT", "Apache"]
},
"version": { "$ref": "#/definitions/semver" },
"classPath": {
"description": "Folder in the package which contains the source files for this project",
"type": "string"
},
"contributors": {
"description": "List of project contributors that are allowed to upload to haxelib",
"type": "array",
"items": { "$ref": "#/definitions/userName" },
"minItems": 1,
"uniqueItems": true
},
"tags": {
"description": "List of tags for easier finding the project on haxelib",
"type": "array",
"items": { "$ref": "#/definitions/haxelibTag" },
"uniqueItems": true
},
"dependencies": {
"type": "object",
"description": "Project's dependencies",
"patternProperties": {
"^[A-Za-z0-9_.-]{3,}$": { "$ref": "#/definitions/dependencyVersion" }
},
"additionalProperties": false
},
"releasenote": {
"description": "Short description of changes made in this version",
"type": "string"
}
},
"additionalProperties": false,
"required": ["name", "license", "releasenote", "contributors", "version"],
"definitions": {
"userName": {
"description": "The name of a user",
"type": "string",
"minLength": 3,
"pattern": "^[A-Za-z0-9_.-]{3,}$"
},
"projectName": {
"description": "The name of a haxelib project",
"type": "string",
"minLength": 3,
"pattern": "^[A-Za-z0-9_.-]{3,}$",
"not": {
"anyOf": [
{"enum": ["haxe", "all"]},
{"pattern": "\\.(zip|hxml)$"}
]
}
},
"haxelibTag": {
"description": "A keyword or term associated with a haxelib project",
"type": "string",
"minLength": 2,
"pattern": "^[A-Za-z0-9_.-]{2,}$"
},
"semver": {
"type": "string",
"description": "Project's version",
"pattern": "^(\\d|[1-9]\\d*)\\.(\\d|[1-9]\\d*)\\.(\\d|[1-9]\\d*)(-(alpha|beta|rc)(\\.(\\d|[1-9]\\d*))?)?$"
},
"dependencyVersion": {
"oneOf": [
{ "$ref": "#/definitions/semver" },
{
"type": "string",
"maxLength": 0
}
]
}
}
}

View File

@@ -0,0 +1,19 @@
# The new haxelib website (on the same 3.0 repo)
server_each.hxml
-main website.Server
-neko www/index.n
--next
server_each.hxml
-lib ufront-uftasks
-main website.Tasks
-neko www/tasks.n
--next
-cp src
-neko www/api/3.0/index.n
-main haxelib.server.Repo
-dce no
-D haxelib_api

View File

@@ -0,0 +1,11 @@
-cp src
-lib ufront
-lib ufront-mail:1.0.0-rc.4
-lib ufront-ufadmin
-lib erazor:1.0.1
-lib markdown:1.0.0
-lib cleversort
-D server
-D getter_support
# https://github.com/HaxeFoundation/haxe/issues/4903
--macro keep('StringBuf')

View File

@@ -0,0 +1,6 @@
-cp src
-neko www/legacy/index.n
-main legacyhaxelib.Site
-lib hx2compat
-D haxelib_site
-dce no

View File

@@ -0,0 +1,10 @@
server_each.hxml
-cp test
# Now the testing libs
-lib buddy:0.18.1
-lib mockatoo
-lib utest
--no-inline
-main WebsiteTests
-neko bin/websitetest.n
-cmd neko bin/websitetest.n

View File

@@ -0,0 +1,69 @@
import haxe.crypto.Crc32;
import haxe.zip.Entry;
import haxe.zip.Writer;
import haxe.zip.Tools;
import sys.io.File;
import sys.FileSystem;
import haxelib.client.Main.VERSION;
import haxelib.Data.Infos;
class Package {
static var outPath = "package.zip";
static function main() {
checkVersion();
var entries = new List<Entry>();
function add(path:String, ?target:String) {
if (!FileSystem.exists(path))
throw 'Invalid path: $path';
if (target == null)
target = path;
if (FileSystem.isDirectory(path)) {
for (item in FileSystem.readDirectory(path))
add(path + "/" + item, target + "/" + item);
} else {
Sys.println("Adding " + target);
var bytes = File.getBytes(path);
var entry:Entry = {
fileName: target,
fileSize: bytes.length,
fileTime: FileSystem.stat(path).mtime,
compressed: false,
dataSize: 0,
data: bytes,
crc32: Crc32.make(bytes),
}
Tools.compress(entry, 9);
entries.add(entry);
}
}
for (file in FileSystem.readDirectory("src/haxelib"))
if (file != "server")
add('src/haxelib/$file');
add("haxelib.json");
add("run.n");
add("README.md");
Sys.println("Saving to " + outPath);
var out = File.write(outPath, true);
var writer = new Writer(out);
writer.write(entries);
out.close();
}
@:access(haxelib.client.Main.VERSION)
static function checkVersion() {
var json:Infos = haxe.Json.parse(sys.io.File.getContent("haxelib.json"));
if (json.version != VERSION) {
Sys.println('Error: Version in haxelib.json (${json.version}) does not match version in haxelib.client.Main.VERSION field ($VERSION)');
Sys.exit(1);
}
}
}

View File

@@ -0,0 +1,324 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib;
import haxe.ds.Option;
import haxe.zip.Reader;
import haxe.zip.Entry;
import haxe.Json;
import haxelib.Validator;
using StringTools;
typedef UserInfos = {
var name : String;
var fullname : String;
var email : String;
var projects : Array<String>;
}
typedef VersionInfos = {
var date : String;
var name : SemVer;//TODO: this should eventually be called `number`
var downloads : Int;
var comments : String;
}
typedef ProjectInfos = {
var name : String;
var desc : String;
var website : String;
var owner : String;
var license : String;
var curversion : String;
var downloads : Int;
var versions : Array<VersionInfos>;
var tags : List<String>;
}
abstract DependencyVersion(String) to String from SemVer {
inline function new(s:String)
this = s;
@:to function toValidatable():Validatable
return
if (this == '') { validate: function () return None }
else @:privateAccess new SemVer(this);
static public function isValid(s:String)
return new DependencyVersion(s).toValidatable().validate() == None;
static public var DEFAULT(default, null) = new DependencyVersion('');
}
abstract Dependencies(Dynamic<DependencyVersion>) from Dynamic<DependencyVersion> {
@:to public function toArray():Array<Dependency> {
var fields = Reflect.fields(this);
fields.sort(Reflect.compare);
return [for (f in fields) {
name: f,
version: (Reflect.field(this, f) : DependencyVersion),
}];
}
public inline function iterator()
return toArray().iterator();
}
@:enum abstract DependencyType(String) {
var Haxelib = null;
var Git = 'git';
var Mercurial = 'hg';
}
typedef Dependency = {
name : String,
?version : DependencyVersion,
?type: DependencyType,
?url: String,
?subDir: String,
?branch: String,
}
typedef Infos = {
// IMPORTANT: if you change this or its fields types,
// make sure to update `schema.json` file accordingly,
// and make an update PR to https://github.com/SchemaStore/schemastore
var name : ProjectName;
@:optional var url : String;
@:optional var description : String;
var license : License;
var version : SemVer;
@:optional var classPath : String;
var releasenote : String;
@:requires('Specify at least one contributor' => _.length > 0)
var contributors : Array<String>;
@:optional var tags : Array<String>;
@:optional var dependencies : Dependencies;
@:optional var main:String;
}
@:enum abstract License(String) to String {
var Gpl = 'GPL';
var Lgpl = 'LGPL';
var Mit = 'MIT';
var Bsd = 'BSD';
var Public = 'Public';
var Apache = 'Apache';
}
abstract ProjectName(String) to String {
static var RESERVED_NAMES = ["haxe", "all"];
static var RESERVED_EXTENSIONS = ['.zip', '.hxml'];
inline function new(s:String)
this = s;
@:to function toValidatable():Validatable
return {
validate:
function ():Option<String> {
for (r in rules)
if (!r.check(this))
return Some(r.msg.replace('%VALUE', '`' + Json.stringify(this) + '`'));
return None;
}
}
static var rules = {//using an array because order might matter
var a = new Array<{ msg: String, check:String->Bool }>();
function add(m, r)
a.push( { msg: m, check: r } );
add("%VALUE is not a String", Std.is.bind(_, String));
add("%VALUE is too short", function (s) return s.length >= 3);
add("%VALUE contains invalid characters", Data.alphanum.match);
add("%VALUE is a reserved name", function(s) return RESERVED_NAMES.indexOf(s.toLowerCase()) == -1);
add("%VALUE ends with a reserved suffix", function(s) {
s = s.toLowerCase();
for (ext in RESERVED_EXTENSIONS)
if (s.endsWith(ext)) return false;
return true;
});
a;
}
public function validate()
return toValidatable().validate();
static public function ofString(s:String)
return switch new ProjectName(s) {
case _.toValidatable().validate() => Some(e): throw e;
case v: v;
}
static public var DEFAULT(default, null) = new ProjectName('unknown');
}
class Data {
public static var JSON = "haxelib.json";
public static var DOCXML = "haxedoc.xml";
public static var REPOSITORY = "files/3.0";
public static var alphanum = ~/^[A-Za-z0-9_.-]+$/;
public static function safe( name : String ) {
if( !alphanum.match(name) )
throw "Invalid parameter : "+name;
return name.split(".").join(",");
}
public static function unsafe( name : String ) {
return name.split(",").join(".");
}
public static function fileName( lib : String, ver : String ) {
return safe(lib)+"-"+safe(ver)+".zip";
}
static public function getLatest(info:ProjectInfos, ?preview:SemVer.Preview->Bool):Null<SemVer> {
if (info.versions.length == 0) return null;
if (preview == null)
preview = function (p) return p == null;
var versions = info.versions.copy();
versions.sort(function (a, b) return -SemVer.compare(a.name, b.name));
for (v in versions)
if (preview(v.name.preview)) return v.name;
return versions[0].name;
}
/**
Return the directory that contains *haxelib.json*.
If it is at the root, `""`.
If it is in a folder, the path including a trailing slash is returned.
*/
public static function locateBasePath( zip : List<Entry> ):String {
var f = getJson(zip);
return f.fileName.substr(0, f.fileName.length - JSON.length);
}
static function getJson(zip:List<Entry>)
return switch topmost(zip, fileNamed(JSON)) {
case Some(f): f;
default: throw 'No $JSON found';
}
static function fileNamed(name:String)
return function (f:Entry) return f.fileName.endsWith(name);
static function topmost(zip:List<Entry>, predicate:Entry->Bool):Option<Entry> {
var best:Entry = null,
bestDepth = 0xFFFF;
for (f in zip)
if (predicate(f)) {
var depth = f.fileName.replace('\\', '/').split('/').length;//TODO: consider Path.normalize
if ((depth == bestDepth && f.fileName < best.fileName) || depth < bestDepth) {
best = f;
bestDepth = depth;
}
}
return
if (best == null)
None;
else
Some(best);
}
public static function readDoc( zip : List<Entry> ) : Null<String>
return switch topmost(zip, fileNamed(DOCXML)) {
case Some(f): Reader.unzip(f).toString();
case None: null;
}
public static function readInfos( zip : List<Entry>, check : Bool ) : Infos
return readData(Reader.unzip(getJson(zip)).toString(), check);
public static function checkClassPath( zip : List<Entry>, infos : Infos ) {
if ( infos.classPath != "" ) {
var basePath = Data.locateBasePath(zip);
var cp = basePath + infos.classPath;
for( f in zip ) {
if( StringTools.startsWith(f.fileName,cp) )
return;
}
throw 'Class path `${infos.classPath}` not found';
}
}
public static function readData( jsondata: String, check : Bool ) : Infos {
var doc:Infos =
try Json.parse(jsondata)
catch ( e : Dynamic )
if (check)
throw 'JSON parse error: $e';
else {
name : ProjectName.DEFAULT,
url : '',
version : SemVer.DEFAULT,
releasenote: 'No haxelib.json found',
license: Mit,
description: 'No haxelib.json found',
contributors: [],
}
if (check)
Validator.validate(doc);
else {
if (!doc.version.valid)
doc.version = SemVer.DEFAULT;
}
//TODO: we have really weird ways to go about nullability and defaults
if (doc.dependencies == null)
doc.dependencies = {};
for (dep in doc.dependencies)
if (!DependencyVersion.isValid(dep.version))
Reflect.setField(doc.dependencies, dep.name, DependencyVersion.DEFAULT);//TODO: this is pure evil
if (doc.classPath == null)
doc.classPath = '';
if (doc.name.validate() != None)
doc.name = ProjectName.DEFAULT;
if (doc.description == null)
doc.description = '';
if (doc.tags == null)
doc.tags = [];
if (doc.url == null)
doc.url = '';
return doc;
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib;
import haxe.ds.Option;
using Std;
enum Preview {
ALPHA;
BETA;
RC;
}
abstract SemVer(String) to String {
public var major(get, never):Int;
public var minor(get, never):Int;
public var patch(get, never):Int;
public var preview(get, never):Null<Preview>;
public var previewNum(get, never):Null<Int>;
public var data(get, never):SemVerData;
public var valid(get, never):Bool;
inline function new(s) this = s;
static public function compare(a:SemVer, b:SemVer) {
function toArray(data:SemVerData)
return [
data.major,
data.minor,
data.patch,
if (data.preview == null) 100 else data.preview.getIndex(),
if (data.previewNum == null) -1 else data.previewNum
];
var a = toArray(a.data),
b = toArray(b.data);
for (i in 0...a.length)
switch Reflect.compare(a[i], b[i]) {
case 0:
case v: return v;
}
return 0;
}
@:to public function toValidatable():Validator.Validatable
return {
validate:
function ():Option<String>
return
try {
get_data();
None;
}
catch (e:Dynamic)
Some(Std.string(e))
}
inline function get_major()
return data.major;
inline function get_minor()
return data.minor;
inline function get_patch()
return data.patch;
inline function get_preview()
return data.preview;
inline function get_previewNum()
return data.previewNum;
inline function get_valid()
return isValid(this);
@:op(a > b) static inline function gt(a:SemVer, b:SemVer)
return compare(a, b) == 1;
@:op(a >= b) static inline function gteq(a:SemVer, b:SemVer)
return compare(a, b) != -1;
@:op(a < b) static inline function lt(a:SemVer, b:SemVer)
return compare(a, b) == -1;
@:op(a <= b) static inline function lteq(a:SemVer, b:SemVer)
return compare(a, b) != 1;
@:op(a == b) static inline function eq(a:SemVer, b:SemVer)
return compare(a, b) == 0;
@:op(a != b) static inline function neq(a:SemVer, b:SemVer)
return compare(a, b) != 0;
static var FORMAT = ~/^(\d|[1-9]\d*)\.(\d|[1-9]\d*)\.(\d|[1-9]\d*)(-(alpha|beta|rc)(\.(\d|[1-9]\d*))?)?$/;
static var cache = new Map();
@:to function get_data():SemVerData {
if (!cache.exists(this))
cache[this] = getData();
return cache[this];
}
@:from static function fromData(data:SemVerData)
return
new SemVer(
data.major + '.' + data.minor + '.' + data.patch +
if (data.preview == null) ''
else '-' + data.preview.getName().toLowerCase() +
if (data.previewNum == null) '';
else '.' + data.previewNum
);
function getData():SemVerData
return
if (valid) {//RAPTORS: This query will already cause the matching.
major: FORMAT.matched(1).parseInt(),
minor: FORMAT.matched(2).parseInt(),
patch: FORMAT.matched(3).parseInt(),
preview:
switch FORMAT.matched(5) {
case 'alpha': ALPHA;
case 'beta': BETA;
case 'rc': RC;
case v if (v == null): null;
case v: throw 'unrecognized preview tag $v';
},
previewNum:
switch FORMAT.matched(7) {
case null: null;
case v: v.parseInt();
}
}
else
throw '$this is not a valid version string';//TODO: include some URL for reference
static public function isValid(s:String)
return Std.is(s, String) && FORMAT.match(s.toLowerCase());
static public function ofString(s:String) {
var ret = new SemVer(s);
ret.getData();
return ret;
}
static public var DEFAULT(default, null) = new SemVer('0.0.0');
}
typedef SemVerData = {
major:Int,
minor:Int,
patch:Int,
preview:Null<Preview>,
previewNum:Null<Int>,
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib;
import haxelib.Data;
interface SiteApi {
public function search( word : String ) : List<{ id : Int, name : String }>;
public function infos( project : String ) : ProjectInfos;
public function getLatestVersion( project : String ) : SemVer;
public function user( name : String ) : UserInfos;
public function register( name : String, pass : String, mail : String, fullname : String ) : Bool;
public function isNewUser( name : String ) : Bool;
public function checkDeveloper( prj : String, user : String ) : Void;
public function checkPassword( user : String, pass : String ) : Bool;
public function getSubmitId() : String;
public function processSubmit( id : String, user : String, pass : String ) : String;
public function postInstall( project : String, version : String):Void;
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib;
import haxe.ds.Option;
#if macro
import haxe.macro.Expr;
import haxe.macro.Type;
import haxe.macro.Context;
using haxe.macro.Tools;
#end
typedef Validatable = {
function validate():Option<String>;
}
class Validator {
#if macro
static var ARG = 'v';
var pos:Position;
var IARG:Expr;
function new(pos) {
this.pos = pos;
IARG = macro @:pos(pos) $i{ARG};
}
function doCheck(t:Type, e:Expr) {
var ct = t.toComplexType();
return
macro @:pos (function ($ARG : $ct) ${makeCheck(t)})($e);
}
function isAtom(s:String)
return switch s {
case 'String', 'Int', 'Bool', 'Float': true;
default: false;
}
function enforce(type:String)
return
macro @:pos(pos) if (!Std.is($i{ARG}, $i{type})) throw '$type expected';
function rename(e:Expr)
return switch e {
case macro $i{name} if (name == '_'): IARG;
default: e.map(rename);
}
function makeCheck(t:Type):Expr
return
switch Context.follow(t) {
case TAnonymous(_.get().fields => fields):
var block:Array<Expr> = [
for (f in fields)
if (f.kind.match(FVar(AccNormal, _)))
{
var name = f.name;
var rec = doCheck(f.type, macro @:pos(pos) $IARG.$name);
if (f.meta.has(':requires')) {
var body = [];
for (m in f.meta.get())
if (m.name == ':requires')
for (p in m.params)
switch p {
case macro $msg => $p:
body.push(rename(
macro @:pos(pos) if (!$p) throw $msg
));
default:
Context.error('Should be "<message>" =>" <condition>', p.pos);
}
//{
//p = rename(p);
//cond = macro @:pos(pos) $p && $cond;
//}
var t = f.type.toComplexType();
rec = macro @:pos(pos) {
$rec;
(function($ARG : $t) $b{body})($IARG.$name);
}
}
if (f.meta.has(':optional')) {
rec = macro @:pos(pos) if (Reflect.hasField($IARG, $v{name}) && $IARG.$name != null) $rec;
}
else
rec = macro @:pos(pos)
if (!Reflect.hasField($IARG, $v{name}))
throw ("missing field " + $v{name});
else
$rec;
rec;
}
];
block.unshift(
macro @:pos(pos) if (!Reflect.isObject($IARG)) throw 'object expected'
);
macro @:pos(pos) $b{block};
case _.toString() => atom if (isAtom(atom)):
enforce(atom);
case TInst(_.get().module => 'Array', [p]):
macro @:pos(pos) {
${enforce('Array')};
for ($IARG in $IARG)
${doCheck(p, IARG)};
}
case TAbstract(_.get() => { from: [ { t: t, field: null } ] }, _):
makeCheck(t);
case TAbstract(_.get() => a, _) if (a.meta.has(':enum')):
var name = a.module + '.' + a.name;
var options:Array<Expr> = [
for (f in a.impl.get().statics.get())
if (f.kind.match(FVar(_, _)))
macro @:pos(pos) $p{(name+'.'+f.name).split('.')}
];
macro if (!Lambda.has($a { options }, $IARG)) throw 'Invalid value ' + $IARG + ' for ' + $v { a.name };
case TAbstract(_.get() => a, _):
macro @:pos(pos) switch ($IARG : haxelib.Validator.Validatable).validate() {
case Some(e): throw e;
case None:
}
case TDynamic(k):
var checker = makeCheck(k);
var ct = k.toComplexType();
macro @:pos(pos) {
if (!Reflect.isObject($i{ARG})) throw 'object expected';
for (f in Reflect.fields($i{ARG})) {
var $ARG:$ct = Reflect.field($i{ARG}, f);
$checker;
}
}
case v:
throw t.toString();
}
#end
macro static public function validate(e:Expr)
return
new Validator(e.pos).doCheck(Context.typeof(e), e);
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib.client;
class Cli {
public static var defaultAnswer:Null<Bool>;
public static function ask(question:String):Bool {
if (defaultAnswer != null)
return defaultAnswer;
while (true) {
Sys.print(question + " [y/n/a] ? ");
try {
switch (Sys.stdin().readLine()) {
case "n": return false;
case "y": return true;
case "a": return defaultAnswer = true;
}
} catch (e:haxe.io.Eof) {
Sys.println("n");
return false;
}
}
return false;
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib.client;
import haxe.Json;
class ConvertXml {
public static function convert(inXml:String) {
// Set up the default JSON structure
var json = {
"name": "",
"url" : "",
"license": "",
"tags": [],
"description": "",
"version": "0.0.1",
"releasenote": "",
"contributors": [],
"dependencies": {}
};
// Parse the XML and set the JSON
var xml = Xml.parse(inXml);
var project = xml.firstChild();
json.name = project.get("name");
json.license = project.get("license");
json.url = project.get("url");
for (node in project) {
switch (node.nodeType) {
case #if (haxe_ver >= 3.2) Element #else Xml.Element #end:
switch (node.nodeName) {
case "tag":
json.tags.push(node.get("v"));
case "user":
json.contributors.push(node.get("name"));
case "version":
json.version = node.get("name");
json.releasenote = node.firstChild().toString();
case "description":
json.description = node.firstChild().toString();
case "depends":
var name = node.get("name");
var version = node.get("version");
if (version == null) version = "";
Reflect.setField(json.dependencies, name, version);
default:
}
default:
}
}
return json;
}
public static function prettyPrint(json:Dynamic, indent="") {
var sb = new StringBuf();
sb.add("{\n");
var firstRun = true;
for (f in Reflect.fields(json)) {
if (!firstRun) sb.add(",\n");
firstRun = false;
var value = switch (f) {
case "dependencies":
var d = Reflect.field(json, f);
prettyPrint(d, indent + " ");
default:
Json.stringify(Reflect.field(json, f));
}
sb.add(indent+' "$f": $value');
}
sb.add('\n$indent}');
return sb.toString();
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib.client;
import haxe.io.Path;
import sys.FileSystem;
using StringTools;
class FsUtils {
static var IS_WINDOWS = (Sys.systemName() == "Windows");
//recursively follow symlink
public static function realPath(path:String):String {
var proc = new sys.io.Process('readlink', [path.endsWith("\n") ? path.substr(0, path.length-1) : path]);
var ret = switch (proc.stdout.readAll().toString()) {
case "": //it is not a symlink
path;
case targetPath:
if (targetPath.startsWith("/")) {
realPath(targetPath);
} else {
realPath(new Path(path).dir + "/" + targetPath);
}
}
proc.close();
return ret;
}
public static function isSamePath(a:String, b:String):Bool {
a = Path.normalize(a);
b = Path.normalize(b);
if (IS_WINDOWS) { // paths are case-insensitive on Windows
a = a.toLowerCase();
b = b.toLowerCase();
}
return a == b;
}
public static function safeDir(dir:String, checkWritable = false):Bool {
if (FileSystem.exists(dir)) {
if (!FileSystem.isDirectory(dir))
throw 'A file is preventing $dir to be created';
if (checkWritable) {
var checkFile = dir+"/haxelib_writecheck.txt";
try {
sys.io.File.saveContent(checkFile, "This is a temporary file created by Haxelib to check if directory is writable. You can safely delete it!");
} catch (_:Dynamic) {
throw '$dir exists but is not writeable, chmod it';
}
FileSystem.deleteFile(checkFile);
}
return false;
} else {
try {
FileSystem.createDirectory(dir);
return true;
} catch (_:Dynamic) {
throw 'You don\'t have enough user rights to create the directory $dir';
}
}
}
public static function deleteRec(dir:String):Bool {
if (!FileSystem.exists(dir))
return false;
for (p in FileSystem.readDirectory(dir)) {
var path = Path.join([dir, p]);
if (isBrokenSymlink(path)) {
safeDelete(path);
} else if (FileSystem.isDirectory(path)) {
if (!IS_WINDOWS) {
// try to delete it as a file first - in case of path
// being a symlink, it will success
if (!safeDelete(path))
deleteRec(path);
} else {
deleteRec(path);
}
} else {
safeDelete(path);
}
}
FileSystem.deleteDirectory(dir);
return true;
}
static function safeDelete(file:String):Bool {
try {
FileSystem.deleteFile(file);
return true;
} catch (e:Dynamic) {
if (IS_WINDOWS) {
try {
Sys.command("attrib", ["-R", file]);
FileSystem.deleteFile(file);
return true;
} catch (_:Dynamic) {
}
}
return false;
}
}
static function isBrokenSymlink(path:String):Bool {
// TODO: figure out what this method actually does :)
var errors = 0;
try FileSystem.isDirectory(path) catch (error:String) if (error == "std@sys_file_type") errors++;
try FileSystem.fullPath(path) catch (error:String) if (error == "std@file_full_path") errors++;
return errors == 2;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,378 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib.client;
import sys.FileSystem;
using haxelib.client.Vcs;
interface IVcs {
var name(default, null):String;
var directory(default, null):String;
var executable(default, null):String;
var available(get, null):Bool;
var settings(default, null):Settings;
/**
Clone repo into `libPath`.
**/
function clone(libPath:String, vcsPath:String, ?branch:String, ?version:String):Void;
/**
Update to HEAD repo contains in CWD or CWD/`Vcs.directory`.
CWD must be like "...haxelib-repo/lib/git" for Git.
Returns `true` if update successful.
**/
function update(libName:String):Bool;
}
@:enum abstract VcsID(String) to String {
var Hg = "hg";
var Git = "git";
}
enum VcsError {
VcsUnavailable(vcs:Vcs);
CantCloneRepo(vcs:Vcs, repo:String, ?stderr:String);
CantCheckoutBranch(vcs:Vcs, branch:String, stderr:String);
CantCheckoutVersion(vcs:Vcs, version:String, stderr:String);
}
typedef Settings = {
@:optional var flat:Bool;
@:optional var debug:Bool;
@:optional var quiet:Bool;
}
class Vcs implements IVcs {
static var reg:Map<VcsID, Vcs>;
public var name(default, null):String;
public var directory(default, null):String;
public var executable(default, null):String;
public var settings(default, null):Settings;
public var available(get, null):Bool;
var availabilityChecked = false;
var executableSearched = false;
public static function initialize(settings:Settings) {
if (reg == null) {
reg = [
VcsID.Git => new Git(settings),
VcsID.Hg => new Mercurial(settings)
];
} else {
if (reg.get(VcsID.Git) == null)
reg.set(VcsID.Git, new Git(settings));
if (reg.get(VcsID.Hg) == null)
reg.set(VcsID.Hg, new Mercurial(settings));
}
}
function new(executable:String, directory:String, name:String, settings:Settings) {
this.name = name;
this.directory = directory;
this.executable = executable;
this.settings = {
flat: settings.flat != null ? settings.flat : false,
debug: settings.debug != null ? settings.debug : false,
quiet: settings.quiet != null ? settings.quiet : false
}
if (settings.debug) {
this.settings.quiet = false;
}
}
public static function get(id:VcsID, settings:Settings):Null<Vcs> {
initialize(settings);
return reg.get(id);
}
static function set(id:VcsID, vcs:Vcs, settings:Settings, ?rewrite:Bool):Void {
initialize(settings);
var existing = reg.get(id) != null;
if (!existing || rewrite)
reg.set(id, vcs);
}
public static function getVcsForDevLib(libPath:String, settings:Settings):Null<Vcs> {
initialize(settings);
for (k in reg.keys()) {
if (FileSystem.exists(libPath + "/" + k) && FileSystem.isDirectory(libPath + "/" + k))
return reg.get(k);
}
return null;
}
function sure(commandResult:{code:Int, out:String}):Void {
switch (commandResult) {
case {code: 0}: //pass
case {code: code, out:out}:
if (!settings.debug)
Sys.stderr().writeString(out);
Sys.exit(code);
}
}
function command(cmd:String, args:Array<String>):{
code: Int,
out: String
} {
var p = try {
new sys.io.Process(cmd, args);
} catch(e:Dynamic) {
return {
code: -1,
out: Std.string(e)
}
}
var out = p.stdout.readAll().toString();
var err = p.stderr.readAll().toString();
if (settings.debug && out != "")
Sys.println(out);
if (settings.debug && err != "")
Sys.stderr().writeString(err);
var code = p.exitCode();
var ret = {
code: code,
out: code == 0 ? out : err
};
p.close();
return ret;
}
function searchExecutable():Void {
executableSearched = true;
}
function checkExecutable():Bool {
available = (executable != null) && try command(executable, []).code == 0 catch(_:Dynamic) false;
availabilityChecked = true;
if (!available && !executableSearched)
searchExecutable();
return available;
}
@:final function get_available():Bool {
if (!availabilityChecked)
checkExecutable();
return available;
}
public function clone(libPath:String, vcsPath:String, ?branch:String, ?version:String):Void {
throw "This method must be overriden.";
}
public function update(libName:String):Bool {
throw "This method must be overriden.";
}
}
class Git extends Vcs {
public function new(settings:Settings)
super("git", "git", "Git", settings);
override function checkExecutable():Bool {
// with `help` cmd because without any cmd `git` can return exit-code = 1.
available = (executable != null) && try command(executable, ["help"]).code == 0 catch(_:Dynamic) false;
availabilityChecked = true;
if (!available && !executableSearched)
searchExecutable();
return available;
}
override function searchExecutable():Void {
super.searchExecutable();
if (available)
return;
// if we have already msys git/cmd in our PATH
var match = ~/(.*)git([\\|\/])cmd$/;
for (path in Sys.getEnv("PATH").split(";")) {
if (match.match(path.toLowerCase())) {
var newPath = match.matched(1) + executable + match.matched(2) + "bin";
Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + newPath);
}
}
if (checkExecutable())
return;
// look at a few default paths
for (path in ["C:\\Program Files (x86)\\Git\\bin", "C:\\Progra~1\\Git\\bin"]) {
if (FileSystem.exists(path)) {
Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + path);
if (checkExecutable())
return;
}
}
}
override public function update(libName:String):Bool {
if (
command(executable, ["diff", "--exit-code"]).code != 0
||
command(executable, ["diff", "--cached", "--exit-code"]).code != 0
) {
if (Cli.ask("Reset changes to " + libName + " " + name + " repo so we can pull latest version?")) {
sure(command(executable, ["reset", "--hard"]));
} else {
if (!settings.quiet)
Sys.println(name + " repo left untouched");
return false;
}
}
var code = command(executable, ["pull"]).code;
// But if before we pulled specified branch/tag/rev => then possibly currently we haxe "HEAD detached at ..".
if (code != 0) {
// get parent-branch:
var branch = command(executable, ["show-branch"]).out;
var regx = ~/\[([^]]*)\]/;
if (regx.match(branch))
branch = regx.matched(1);
sure(command(executable, ["checkout", branch, "--force"]));
sure(command(executable, ["pull"]));
}
return true;
}
override public function clone(libPath:String, url:String, ?branch:String, ?version:String):Void {
var oldCwd = Sys.getCwd();
var vcsArgs = ["clone", url, libPath];
if (settings == null || !settings.flat)
vcsArgs.push('--recursive');
//TODO: move to Vcs.run(vcsArgs)
//TODO: use settings.quiet
if (command(executable, vcsArgs).code != 0)
throw VcsError.CantCloneRepo(this, url/*, ret.out*/);
Sys.setCwd(libPath);
if (version != null) {
var ret = command(executable, ["checkout", "tags/" + version]);
if (ret.code != 0)
throw VcsError.CantCheckoutVersion(this, version, ret.out);
} else if (branch != null) {
var ret = command(executable, ["checkout", branch]);
if (ret.code != 0)
throw VcsError.CantCheckoutBranch(this, branch, ret.out);
}
// return prev. cwd:
Sys.setCwd(oldCwd);
}
}
class Mercurial extends Vcs {
public function new(settings:Settings)
super("hg", "hg", "Mercurial", settings);
override function searchExecutable():Void {
super.searchExecutable();
if (available)
return;
// if we have already msys git/cmd in our PATH
var match = ~/(.*)hg([\\|\/])cmd$/;
for(path in Sys.getEnv("PATH").split(";")) {
if(match.match(path.toLowerCase())) {
var newPath = match.matched(1) + executable + match.matched(2) + "bin";
Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + newPath);
}
}
checkExecutable();
}
override public function update(libName:String):Bool {
command(executable, ["pull"]);
var summary = command(executable, ["summary"]).out;
var diff = command(executable, ["diff", "-U", "2", "--git", "--subrepos"]);
var status = command(executable, ["status"]);
// get new pulled changesets:
// (and search num of sets)
summary = summary.substr(0, summary.length - 1);
summary = summary.substr(summary.lastIndexOf("\n") + 1);
// we don't know any about locale then taking only Digit-exising:s
var changed = ~/(\d)/.match(summary);
if (changed && !settings.quiet)
// print new pulled changesets:
Sys.println(summary);
if (diff.code + status.code + diff.out.length + status.out.length != 0) {
if (!settings.quiet)
Sys.println(diff.out);
if (Cli.ask("Reset changes to " + libName + " " + name + " repo so we can update to latest version?")) {
sure(command(executable, ["update", "--clean"]));
} else {
changed = false;
if (!settings.quiet)
Sys.println(name + " repo left untouched");
}
} else if (changed) {
sure(command(executable, ["update"]));
}
return changed;
}
override public function clone(libPath:String, url:String, ?branch:String, ?version:String):Void {
var vcsArgs = ["clone", url, libPath];
if (branch != null) {
vcsArgs.push("--branch");
vcsArgs.push(branch);
}
if (version != null) {
vcsArgs.push("--rev");
vcsArgs.push(version);
}
if (command(executable, vcsArgs).code != 0)
throw VcsError.CantCloneRepo(this, url/*, ret.out*/);
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib.server;
class Paths {
static var RELATIVE_ROOT =
#if haxelib_api
'../../';
#else
'';
#end
//TODO: these should be inline or read-only or whatever
static public var CWD = neko.Web.getCwd() + RELATIVE_ROOT;
static public var DB_CONFIG = CWD + "dbconfig.json";
static public var DB_FILE = CWD + "haxelib.db";
static public var TMP_DIR = CWD + "tmp";
static public var REP_DIR = CWD + Data.REPOSITORY;
}

View File

@@ -0,0 +1,361 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib.server;
import haxe.io.Bytes;
import neko.Web;
import haxelib.Data;
import haxelib.SemVer;
import haxelib.server.Paths.*;
import haxelib.server.SiteDb;
class Repo implements SiteApi {
static function run() {
if( !sys.FileSystem.exists(TMP_DIR) )
sys.FileSystem.createDirectory(TMP_DIR);
if( !sys.FileSystem.exists(REP_DIR) )
sys.FileSystem.createDirectory(REP_DIR);
var ctx = new haxe.remoting.Context();
ctx.addObject("api", new Repo());
if( haxe.remoting.HttpConnection.handleRequest(ctx) )
return;
else
throw "Invalid remoting call";
}
public function new() {}
public function search( word : String ) : List<{ id : Int, name : String }> {
return Project.containing(word);
}
public function infos( project : String ) : ProjectInfos {
var p = Project.manager.select($name == project);
if( p == null )
throw "No such Project : "+project;
var vl = Version.manager.search($project == p.id);
var sumDownloads = function(version:Version, total:Int) return total += version.downloads;
var totalDownloads = Lambda.fold(vl, sumDownloads, 0);
return {
name : p.name,
curversion : if( p.versionObj == null ) null else p.versionObj.toSemver(),
desc : p.description,
versions:
[for ( v in vl ) {
name : v.toSemver(),
comments : v.comments,
downloads : v.downloads,
date : v.date
}],
owner : p.ownerObj.name,
website : p.website,
license : p.license,
downloads : totalDownloads,
tags : Tag.manager.search($project == p.id).map(function(t) return t.tag),
};
}
public function getLatestVersion( project : String ) : SemVer {
var p = Project.manager.select($name == project);
if( p == null )
throw "No such Project : "+project;
var vl = Version.manager.unsafeObjects('SELECT * FROM Version WHERE project = ${p.id} ORDER BY major DESC, minor DESC, patch DESC, ifnull(preview, 100) DESC, previewNum DESC LIMIT 1', false);
return vl.first().toSemver();
}
public function user( name : String ) : UserInfos {
var u = User.manager.search($name == name).first();
if( u == null )
throw "No such user : "+name;
var pl = Project.manager.search($owner == u.id);
var projects = new Array();
for( p in pl )
projects.push(p.name);
return {
name : u.name,
fullname : u.fullname,
email : u.email,
projects : projects,
};
}
public function register( name : String, pass : String, mail : String, fullname : String ) : Bool {
if( name.length < 3 )
throw "User name must be at least 3 characters";
if( !Data.alphanum.match(name) )
throw "Invalid user name, please use alphanumeric characters";
if( User.manager.count($name == name) > 0 )
throw 'User name "$name" is already taken';
var u = new User();
u.name = name;
u.pass = pass;
u.email = mail;
u.fullname = fullname;
u.insert();
return null;
}
public function isNewUser( name : String ) : Bool {
return User.manager.select($name == name) == null;
}
public function checkDeveloper( prj : String, user : String ) : Void {
var p = Project.manager.search({ name : prj }).first();
if( p == null )
return;
for( d in Developer.manager.search({ project : p.id }) )
if( d.userObj.name == user )
return;
throw "User '"+user+"' is not a developer of project '"+prj+"'";
}
public function checkPassword( user : String, pass : String ) : Bool {
var u = User.manager.search({ name : user }).first();
return u != null && u.pass == pass;
}
public function getSubmitId() : String {
return Std.string(Std.random(100000000));
}
public function processSubmit( id : String, user : String, pass : String ) : String {
var path = TMP_DIR+"/"+Std.parseInt(id)+".tmp";
var file = try sys.io.File.read(path,true) catch( e : Dynamic ) throw "Invalid file id #"+id;
var zip = try haxe.zip.Reader.readZip(file) catch( e : Dynamic ) { file.close(); neko.Lib.rethrow(e); };
file.close();
var infos = Data.readInfos(zip,true);
var u = User.manager.search({ name : user }).first();
if( u == null || u.pass != pass )
throw "Invalid username or password";
var devs = infos.contributors.map(function(user) {
var u = User.manager.search({ name : user }).first();
if( u == null )
throw "Unknown user '"+user+"'";
return u;
});
var tags = Lambda.array(infos.tags);
tags.sort(Reflect.compare);
var p = Project.manager.search({ name : infos.name }).first();
// create project if needed
if( p == null ) {
p = new Project();
p.name = infos.name;
p.description = infos.description;
p.website = infos.url;
p.license = infos.license;
p.ownerObj = u;
p.insert();
for( u in devs ) {
var d = new Developer();
d.userObj = u;
d.projectObj = p;
d.insert();
}
for( tag in tags ) {
var t = new Tag();
t.tag = tag;
t.projectObj = p;
t.insert();
}
}
// check submit rights
var pdevs = Developer.manager.search({ project : p.id });
var isdev = false;
for( d in pdevs )
if( d.userObj.id == u.id ) {
isdev = true;
break;
}
if( !isdev )
throw "You are not a developer of this project";
var otags = Tag.manager.search({ project : p.id });
var curtags = otags.map(function(t) return t.tag).join(":");
var devsChanged = (pdevs.length != devs.length);
if (!devsChanged) { // same length, check whether elements are the same
for (d in pdevs) {
var exists = Lambda.exists(devs, function(u) return u.id == d.userObj.id);
if (!exists) {
devsChanged = true;
break;
}
}
}
// update public infos
if( devsChanged || infos.description != p.description || p.website != infos.url || p.license != infos.license || tags.join(":") != curtags ) {
if( u.id != p.ownerObj.id )
throw "Only project owner can modify project infos";
p.description = infos.description;
p.website = infos.url;
p.license = infos.license;
p.update();
if( devsChanged ) {
for( d in pdevs )
d.delete();
for( u in devs ) {
var d = new Developer();
d.userObj = u;
d.projectObj = p;
d.insert();
}
}
if( tags.join(":") != curtags ) {
for( t in otags )
t.delete();
for( tag in tags ) {
var t = new Tag();
t.tag = tag;
t.projectObj = p;
t.insert();
}
}
}
// look for current version
var current = null;
for( v in Version.manager.search({ project : p.id }) )
if( v.name == infos.version ) {
current = v;
break;
}
// update documentation
var doc = null;
var docXML = Data.readDoc(zip);
if( docXML != null ) {
try {
var p = new haxe.rtti.XmlParser();
p.process(Xml.parse(docXML).firstElement(),null);
p.sort();
var roots = new Array();
for( x in p.root )
switch( x ) {
case TPackage(name,_,_):
switch( name ) {
case "flash","sys","cs","java","haxe","js","neko","cpp","php","python": // don't include haXe core types
default: roots.push(x);
}
default:
// don't include haXe root types
}
var s = new haxe.Serializer();
s.useEnumIndex = true;
s.useCache = true;
s.serialize(roots);
doc = s.toString();
} catch ( e:Dynamic ) {
// If documentation can't be generated, ignore it.
}
}
// update file
var target = REP_DIR + "/" + Data.fileName(p.name, infos.version);
if (sys.FileSystem.exists(target))
sys.FileSystem.deleteFile(target);
sys.FileSystem.rename(path,target);
var semVer = SemVer.ofString(infos.version);
// update existing version
if( current != null ) {
current.documentation = doc;
current.comments = infos.releasenote;
current.update();
return "Version "+current.name+" (id#"+current.id+") updated";
}
// add new version
var v = new Version();
v.projectObj = p;
v.major = semVer.major;
v.minor = semVer.minor;
v.patch = semVer.patch;
v.preview = semVer.preview;
v.previewNum = semVer.previewNum;
v.comments = infos.releasenote;
v.downloads = 0;
v.date = Date.now().toString();
v.documentation = doc;
v.insert();
p.versionObj = v;
p.update();
return "Version " + v.toSemver() + " (id#" + v.id + ") added";
}
public function postInstall( project : String, version : String ) {
var p = Project.manager.select($name == project);
if( p == null )
throw "No such Project : " + project;
var version = SemVer.ofString(version);
// don't use macro select because of
// https://github.com/HaxeFoundation/haxe/issues/4931
// and https://github.com/HaxeFoundation/haxe/issues/4932
var v = Version.manager.dynamicSearch({
project: p.id,
major: version.major,
minor: version.minor,
patch: version.patch,
preview: if (version.preview == null) null else version.preview.getIndex(),
previewNum: version.previewNum
}).first();
if( v == null )
throw "No such Version : " + version;
v.downloads++;
v.update();
p.downloads++;
p.update();
}
static function main() {
var error = null;
SiteDb.init();
try {
run();
} catch( e : Dynamic ) {
error = { e : e };
}
SiteDb.cleanup();
if( error != null )
neko.Lib.rethrow(error.e);
}
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C)2005-2016 Haxe Foundation
*
* 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.
*/
package haxelib.server;
import sys.db.*;
import sys.db.Types;
import haxelib.server.Paths.*;
class User extends Object {
public var id : SId;
public var name : String;
public var fullname : String;
public var email : String;
public var pass : String;
}
class Project extends Object {
public var id : SId;
public var name : String;
public var description : String;
public var website : String;
public var license : String;
public var downloads : Int = 0;
@:relation(owner) public var ownerObj : User;
@:relation(version) public var versionObj : SNull<Version>;
static public function containing( word:String ) : List<{ id: Int, name: String }> {
var ret = new List();
word = '%$word%';
for (project in manager.search($name.like(word) || $description.like(word)))
ret.push( { id: project.id, name: project.name } );
return ret;
}
static public function allByName() {
//TODO: Propose SPOD patch to support manager.search(true, { orderBy: name.toLowerCase() } );
return manager.unsafeObjects('SELECT * FROM Project ORDER BY LOWER(name)', false);
}
}
class Tag extends Object {
public var id : SId;
public var tag : String;
@:relation(project) public var projectObj : Project;
static public function topTags( n : Int ) : List<{ tag:String, count: Int }> {
return cast Manager.cnx.request("SELECT tag, COUNT(*) as count FROM Tag GROUP BY tag ORDER BY count DESC LIMIT " + n).results();
}
}
class Version extends Object {
public var id : SId;
@:relation(project) public var projectObj : Project;
public var major : Int;
public var minor : Int;
public var patch : Int;
public var preview : SNull<SEnum<SemVer.Preview>>;
public var previewNum : SNull<Int>;
@:skip public var name(get, never):String;
function get_name():String return toSemver();
public function toSemver():SemVer {
return {
major: this.major,
minor: this.minor,
patch: this.patch,
preview: this.preview,
previewNum: this.previewNum,
}
}
public var date : String; // sqlite does not have a proper 'date' type
public var comments : String;
public var downloads : Int;
public var documentation : SNull<String>;
static public function latest( n : Int ) {
return manager.search(1 == 1, { orderBy: -date, limit: n } );
}
static public function byProject( p : Project ) {
return manager.search($project == p.id, { orderBy: -date } );
}
}
@:id(user,project)
class Developer extends Object {
@:relation(user) public var userObj : User;
@:relation(project) public var projectObj : Project;
}
class SiteDb {
static var db : Connection;
//TODO: this whole configuration business is rather messy to say the least
static public function init() {
db =
if (sys.FileSystem.exists(DB_CONFIG))
Mysql.connect(haxe.Json.parse(sys.io.File.getContent(DB_CONFIG)));
else
Sqlite.open(DB_FILE);
Manager.cnx = db;
Manager.initialize();
var managers:Array<Manager<Dynamic>> = [
User.manager,
Project.manager,
Tag.manager,
Version.manager,
Developer.manager
];
for (m in managers)
if (!TableCreate.exists(m))
TableCreate.create(m);
}
static public function cleanup() {
db.close();
Manager.cleanup();
}
}

View File

@@ -0,0 +1,10 @@
# Set index.n to come before index.php
DirectoryIndex index.n
# Enable rewrite
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.n/$1

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C)2005-2012 Haxe Foundation
*
* 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.
*/
package legacyhaxelib;
import haxe.zip.Reader;
import haxe.zip.Entry;
import haxe.xml.Check;
typedef UserInfos = {
var name : String;
var fullname : String;
var email : String;
var projects : Array<String>;
}
typedef VersionInfos = {
var date : String;
var name : String;
var comments : String;
}
typedef ProjectInfos = {
var name : String;
var desc : String;
var website : String;
var owner : String;
var license : String;
var curversion : String;
var versions : Array<VersionInfos>;
var tags : List<String>;
}
typedef XmlInfos = {
var project : String;
var website : String;
var desc : String;
var license : String;
var version : String;
var versionComments : String;
var developers : List<String>;
var tags : List<String>;
var dependencies : List<{ project : String, version : String }>;
}
class Data {
public static var XML = "haxelib.xml";
public static var DOCXML = "haxedoc.xml";
public static var REPOSITORY = "files";
public static var alphanum = ~/^[A-Za-z0-9_.-]+$/;
static var LICENSES = ["GPL","LGPL","BSD","Public","MIT"];
static function requiredAttribute( x : Xml, name ) {
var v = x.get(name);
if( v == null )
throw "Missing required attribute '"+name+"' in node "+x.nodeName;
return v;
}
static function requiredNode( x : Xml, name ) {
var v = x.elementsNamed(name).next();
if( v == null )
throw "Missing required node '"+name+"' in node "+x.nodeName;
return v;
}
static function requiredText( x : Xml ) {
var v = x.firstChild();
if( v == null || (v.nodeType != Xml.PCData && v.nodeType != Xml.CData) )
throw "Missing required text in node "+x.nodeName;
return v.nodeValue;
}
public static function safe( name : String ) {
if( !alphanum.match(name) )
throw "Invalid parameter : "+name;
return name.split(".").join(",");
}
public static function unsafe( name : String ) {
return name.split(",").join(".");
}
public static function fileName( lib : String, ver : String ) {
return safe(lib)+"-"+safe(ver)+".zip";
}
public static function readDoc( zip : List<Entry> ) : String {
for( f in zip )
if( StringTools.endsWith(f.fileName,DOCXML) )
return Reader.unzip(f).toString();
return null;
}
public static function readInfos( zip : List<Entry>, check : Bool ) : XmlInfos {
var xmldata = null;
for( f in zip )
if( StringTools.endsWith(f.fileName,XML) ) {
xmldata = Reader.unzip(f).toString();
break;
}
if( xmldata == null )
throw XML+" not found in package";
return readData(xmldata,check);
}
static function doCheck( doc : Xml ) {
var sname = Att("name",FReg(alphanum));
var schema = RNode(
"project",
[ sname, Att("url"), Att("license",FEnum(LICENSES)) ],
RList([
RMulti( RNode("user",[sname]), true ),
RMulti( RNode("tag",[Att("v",FReg(alphanum))]) ),
RNode("description",[],RData()),
RNode("version",[sname],RData()),
RMulti( RNode("depends",[sname,Att("version",FReg(alphanum),"")]) ),
])
);
haxe.xml.Check.checkDocument(doc,schema);
}
public static function readData( xmldata : String, check : Bool ) : XmlInfos {
var doc = Xml.parse(xmldata);
if( check )
doCheck(doc);
var p = new haxe.xml.Fast(doc).node.project;
var project = p.att.name;
if( project.length < 3 )
throw "Project name must contain at least 3 characters";
var tags = new List();
for( t in p.nodes.tag )
tags.add(t.att.v.toLowerCase());
var devs = new List();
for( d in p.nodes.user )
devs.add(d.att.name);
var deps = new List();
for( d in p.nodes.depends )
deps.add({ project : d.att.name, version : if( d.has.version ) d.att.version else "" });
return {
project : project,
website : p.att.url,
desc : p.node.description.innerData,
version : p.node.version.att.name,
versionComments : p.node.version.innerData,
license : p.att.license,
tags : tags,
developers : devs,
dependencies : deps,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,266 @@
/*
* Copyright (C)2005-2012 Haxe Foundation
*
* 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.
*/
package legacyhaxelib;
#if haxelib_site
import legacyhaxelib.SiteDb;
#end
import haxe.rtti.CType;
class Site {
static var db : sys.db.Connection;
static var CWD = neko.Web.getCwd();
static var DB_FILE = CWD+"haxelib.db";
public static var TMP_DIR = CWD+"../tmp";
public static var REP_DIR = CWD+"../"+Data.REPOSITORY;
static function setup() {
SiteDb.create(db);
}
static function initDatabase() {
db = neko.db.Sqlite.open(DB_FILE);
neko.db.Manager.cnx = db;
neko.db.Manager.initialize();
}
static function run() {
if( !sys.FileSystem.exists(TMP_DIR) )
sys.FileSystem.createDirectory(TMP_DIR);
if( !sys.FileSystem.exists(REP_DIR) )
sys.FileSystem.createDirectory(REP_DIR);
var ctx = new haxe.remoting.Context();
ctx.addObject("api",new SiteApi(db));
if( haxe.remoting.HttpConnection.handleRequest(ctx) )
return;
if( Sys.args()[0] == "setup" ) {
setup();
neko.Lib.print("Setup done\n");
return;
}
var file = null;
var sid = null;
var bytes = 0;
neko.Web.parseMultipart(function(p,filename) {
if( p == "file" ) {
sid = Std.parseInt(filename);
file = sys.io.File.write(TMP_DIR+"/"+sid+".tmp",true);
} else
throw p+" not accepted";
},function(data,pos,len) {
bytes += len;
file.writeFullBytes(data,pos,len);
});
if( file != null ) {
file.close();
neko.Lib.print("File #"+sid+" accepted : "+bytes+" bytes written");
return;
}
display();
}
static function display() {
var data = sys.io.File.getContent(CWD + "website.mtt");
var page = new haxe.Template(data);
var ctx : Dynamic = {};
var macros = {
download : function( res, p, v ) {
return "/"+Data.REPOSITORY+"/"+Data.fileName(res(p).name,res(v).name);
}
};
if( fillContent(ctx) )
neko.Lib.print( page.execute(ctx,macros) );
}
static function fillContent( ctx : Dynamic ) {
var uri = neko.Web.getURI().split("/");
var error = function(msg) { ctx.error = StringTools.htmlEscape(msg); return true; }
if( uri[0] == "" )
uri.shift();
var domain = "http://" + neko.Web.getHostName();
var act = uri.shift();
if( act == "legacy" ) {
act = uri.shift();
ctx.basehref = domain + "/legacy/";
}
else
ctx.basehref = domain + "/";
if( act == null || act == "" || act == "index.n" )
act = "index";
ctx.menuTags = Tag.manager.topTags(10);
switch( act ) {
case "p":
var name = uri.shift();
var p = Project.manager.search({ name : name }).first();
if( p == null )
return error("Unknown project '"+name+"'");
ctx.p = p;
ctx.owner = p.owner;
ctx.version = p.version;
ctx.versions = Version.manager.byProject(p);
var tags = Tag.manager.search({ project : p.id });
if( !tags.isEmpty() ) ctx.tags = tags;
case "u":
var name = uri.shift();
var u = User.manager.search({ name : name }).first();
if( u == null )
return error("Unknown user '"+name+"'");
ctx.u = u;
ctx.uprojects = Developer.manager.search({ user : u.id }).map(function(d:Developer) { return d.project; });
case "t":
var tag = uri.shift();
ctx.tag = StringTools.htmlEscape(tag);
ctx.tprojects = Tag.manager.search({ tag : tag }).map(function(t) return t.project);
case "d":
var name = uri.shift();
var p = Project.manager.search({ name : name }).first();
if( p == null )
return error("Unknown project '"+name+"'");
var version = uri.shift();
var v;
if( version == null ) {
v = p.version;
version = v.name;
} else {
v = Version.manager.search({ project : p.id, name : version }).first();
if( v == null ) return error("Unknown version '"+version+"'");
}
if( v.documentation == null )
return error("Project "+p.name+" version "+version+" has no documentation");
var root : TypeRoot = haxe.Unserializer.run(v.documentation);
var buf = new StringBuf();
// var html = new tools.haxedoc.HtmlPrinter("/d/"+p.name+"/"+version+"/","","");
// html.output = function(str) buf.add(str);
// var path = uri.join(".").toLowerCase().split(".");
// if( path.length == 1 && path[0] == "" )
// path = [];
// if( path.length == 0 ) {
// ctx.index = true;
// html.process(TPackage("root","root",root));
// } else {
// var cl = html.find(root,path,0);
// if( cl == null ) {
// // we most likely clicked on a class which is part of the haxe core documentation
// neko.Web.redirect("http://haxe.org/api/"+path.join("/"));
// return false;
// }
// html.process(cl);
// }
// ctx.p = p;
// ctx.v = v;
// ctx.content = buf.toString();
case "index":
var vl = Version.manager.latest(10);
for( v in vl ) {
var p = v.project; // fetch
}
ctx.versions = vl;
case "all":
ctx.projects = Project.manager.allByName();
case "search":
var v = neko.Web.getParams().get("v");
var p = Project.manager.search({ name : v }).first();
if( p != null ) {
neko.Web.redirect("/legacy/p/"+p.name);
return false;
}
if( Tag.manager.count({ tag : v }) > 0 ) {
neko.Web.redirect("/legacy/t/"+v);
return false;
}
ctx.projects = Project.manager.containing(v).map(function(p) return Project.manager.get(p.id));
ctx.act_all = true;
ctx.search = StringTools.htmlEscape(v);
case "rss":
neko.Web.setHeader("Content-Type", "text/xml; charset=UTF-8");
neko.Lib.println('<?xml version="1.0" encoding="UTF-8"?>');
neko.Lib.print(buildRss().toString());
return false;
default:
ctx.error = "Unknown action : "+act;
return true;
}
Reflect.setField(ctx,"act_"+act,true);
return true;
}
static function buildRss() : Xml {
var createChild = function(root:Xml, name:String){
var c = Xml.createElement(name);
root.addChild(c);
return c;
}
var createChildWithContent = function(root:Xml, name:String, content:String){
var e = Xml.createElement(name);
var c = Xml.createPCData(if (content != null) content else "");
e.addChild(c);
root.addChild(e);
return e;
}
var createChildWithCdata = function(root:Xml, name:String, content:String){
var e = Xml.createElement(name);
var c = Xml.createCData(if (content != null) content else "");
e.addChild(c);
root.addChild(e);
return e;
}
Sys.setTimeLocale("en_US.UTF8");
var url = "http://"+neko.Web.getClientHeader("Host");
var rss = Xml.createElement("rss");
rss.set("version","2.0");
var channel = createChild(rss, "channel");
createChildWithContent(channel, "title", "haxe-libs");
createChildWithContent(channel, "link", url);
createChildWithContent(channel, "description", "lib.haxe.org RSS");
createChildWithContent(channel, "generator", "haxe");
createChildWithContent(channel, "language", "en");
for (v in Version.manager.latest(10)){
var project = v.project;
var item = createChild(channel, "item");
createChildWithContent(item, "title", StringTools.htmlEscape(project.name+" "+v.name));
createChildWithContent(item, "link", url+"/p/"+project.name);
createChildWithContent(item, "guid", url+"/p/"+project.name+"?v="+v.id);
var date = DateTools.format(Date.fromString(v.date), "%a, %e %b %Y %H:%M:%S %z");
createChildWithContent(item, "pubDate", date);
createChildWithContent(item, "author", project.owner.name);
createChildWithContent(item, "description", StringTools.htmlEscape(v.comments));
}
return rss;
}
static function main() {
var error = null;
initDatabase();
try {
run();
} catch( e : Dynamic ) {
error = { e : e };
}
db.close();
neko.db.Manager.cleanup();
if( error != null )
neko.Lib.rethrow(error.e);
}
}

View File

@@ -0,0 +1,287 @@
/*
* Copyright (C)2005-2012 Haxe Foundation
*
* 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.
*/
package legacyhaxelib;
import legacyhaxelib.Data;
#if haxelib_site
import legacyhaxelib.SiteDb;
#end
class SiteApi {
var db : sys.db.Connection;
public function new( db ) {
this.db = db;
}
public function search( word : String ) : List<{ id : Int, name : String }> {
return Project.manager.containing(word);
}
public function infos( project : String ) : ProjectInfos {
var p = Project.manager.search({ name : project }).first();
if( p == null )
throw "No such Project : "+project;
var vl = Version.manager.search({ project : p.id });
var versions = new Array();
for( v in vl )
versions.push({ name : v.name, comments : v.comments, date : v.date });
return {
name : p.name,
curversion : if( p.version == null ) null else p.version.name,
desc : p.description,
versions : versions,
owner : p.owner.name,
website : p.website,
license : p.license,
tags : Tag.manager.search({ project : p.id }).map(function(t) return t.tag),
};
}
public function user( name : String ) : UserInfos {
var u = User.manager.search({ name : name }).first();
if( u == null )
throw "No such user : "+name;
var pl = Project.manager.search({ owner : u.id });
var projects = new Array();
for( p in pl )
projects.push(p.name);
return {
name : u.name,
fullname : u.fullname,
email : u.email,
projects : projects,
};
}
public function register( name : String, pass : String, mail : String, fullname : String ) : Bool {
if( !Data.alphanum.match(name) )
throw "Invalid user name, please use alphanumeric characters";
if( name.length < 3 )
throw "User name must be at least 3 characters";
var u = new User();
u.name = name;
u.pass = pass;
u.email = mail;
u.fullname = fullname;
u.insert();
return null;
}
public function isNewUser( name : String ) : Bool {
return User.manager.search({ name : name }).first() == null;
}
public function checkDeveloper( prj : String, user : String ) : Void {
var p = Project.manager.search({ name : prj }).first();
if( p == null )
return;
for( d in Developer.manager.search({ project : p.id }) )
if( d.user.name == user )
return;
throw "User '"+user+"' is not a developer of project '"+prj+"'";
}
public function checkPassword( user : String, pass : String ) : Bool {
var u = User.manager.search({ name : user }).first();
return u != null && u.pass == pass;
}
public function getSubmitId() : String {
return Std.string(Std.random(100000000));
}
public function processSubmit( id : String, user : String, pass : String ) : String {
var path = Site.TMP_DIR+"/"+Std.parseInt(id)+".tmp";
var file = try sys.io.File.read(path,true) catch( e : Dynamic ) throw "Invalid file id #"+id;
var zip = try haxe.zip.Reader.readZip(file) catch( e : Dynamic ) { file.close(); neko.Lib.rethrow(e); };
file.close();
var infos = Data.readInfos(zip,true);
var u = User.manager.search({ name : user }).first();
if( u == null || u.pass != pass )
throw "Invalid username or password";
var devs = infos.developers.map(function(user) {
var u = User.manager.search({ name : user }).first();
if( u == null )
throw "Unknown user '"+user+"'";
return u;
});
var tags = Lambda.array(infos.tags);
tags.sort(Reflect.compare);
var p = Project.manager.search({ name : infos.project }).first();
// create project if needed
if( p == null ) {
p = new Project();
p.name = infos.project;
p.description = infos.desc;
p.website = infos.website;
p.license = infos.license;
p.downloads = 0;
p.owner = u;
p.insert();
for( u in devs ) {
var d = new Developer();
d.user = u;
d.project = p;
d.insert();
}
for( tag in tags ) {
var t = new Tag();
t.tag = tag;
t.project = p;
t.insert();
}
}
// check submit rights
var pdevs = Developer.manager.search({ project : p.id });
var isdev = false;
for( d in pdevs )
if( d.user.id == u.id ) {
isdev = true;
break;
}
if( !isdev )
throw "You are not a developer of this project";
var otags = Tag.manager.search({ project : p.id });
var curtags = otags.map(function(t) return t.tag).join(":");
// update public infos
if( infos.desc != p.description || p.website != infos.website || p.license != infos.license || pdevs.length != devs.length || tags.join(":") != curtags ) {
if( u.id != p.owner.id )
throw "Only project owner can modify project infos";
p.description = infos.desc;
p.website = infos.website;
p.license = infos.license;
p.update();
if( pdevs.length != devs.length ) {
for( d in pdevs )
d.delete();
for( u in devs ) {
var d = new Developer();
d.user = u;
d.project = p;
d.insert();
}
}
if( tags.join(":") != curtags ) {
for( t in otags )
t.delete();
for( tag in tags ) {
var t = new Tag();
t.tag = tag;
t.project = p;
t.insert();
}
}
}
// look for current version
var current = null;
for( v in Version.manager.search({ project : p.id }) )
if( v.name == infos.version ) {
current = v;
break;
}
// update documentation
var doc = null;
var docXML = Data.readDoc(zip);
if( docXML != null ) {
try {
var p = new haxe.rtti.XmlParser();
var firstElm = Xml.parse(docXML).firstElement();
p.process(firstElm, null);
throw "get to here before doc";
p.process(Xml.parse(docXML).firstElement(),null);
p.sort();
var roots = new Array();
for( x in p.root )
switch( x ) {
case TPackage(name,_,_):
switch( name ) {
case "flash","flash8","sys","cs","java","flash9","haxe","js","neko","cpp","php","tools": // don't include haXe core types
default: roots.push(x);
}
default:
// don't include haXe root types
}
var s = new haxe.Serializer();
s.useEnumIndex = true;
s.useCache = true;
s.serialize(roots);
doc = s.toString();
} catch ( e:Dynamic ) {
// If documentation can't be generated, ignore it.
}
}
// update file
var target = Site.REP_DIR+"/"+Data.fileName(p.name,infos.version);
if( current != null ) sys.FileSystem.deleteFile(target);
sys.FileSystem.rename(path,target);
// update existing version
if( current != null ) {
current.documentation = doc;
current.comments = infos.versionComments;
current.update();
return "Version "+current.name+" (id#"+current.id+") updated";
}
// add new version
var v = new Version();
v.project = p;
v.name = infos.version;
v.comments = infos.versionComments;
v.downloads = 0;
v.date = Date.now().toString();
v.documentation = doc;
v.insert();
p.version = v;
p.update();
return "Version "+v.name+" (id#"+v.id+") added";
}
public function postInstall( project : String, version : String ) {
var p = Project.manager.search({ name : project }).first();
if( p == null )
throw "No such Project : "+project;
var v = Version.manager.search({ project : p.id, name : version }).first();
if( v == null )
throw "No such Version : "+version;
v.downloads++;
v.update();
p.downloads++;
p.update();
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (C)2005-2012 Haxe Foundation
*
* 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.
*/
package legacyhaxelib;
class User extends neko.db.Object {
public static var manager = new neko.db.Manager<User>(User);
public var id : Int;
public var name : String;
public var fullname : String;
public var email : String;
public var pass : String;
}
class Project extends neko.db.Object {
static function RELATIONS() {
return [
{ key : "owner", prop : "owner", manager : User.manager },
{ key : "version", prop : "version", manager : Version.manager },
];
}
public static var manager = new ProjectManager(Project);
public var id : Int;
public var name : String;
public var description : String;
public var website : String;
public var license : String;
public var downloads : Int;
public var owner(dynamic,dynamic) : User;
public var version(dynamic,dynamic) : Version;
}
class Tag extends neko.db.Object {
static function RELATIONS() {
return [
{ key : "project", prop : "project", manager : Project.manager },
];
}
public static var manager = new TagManager(Tag);
public var id : Int;
public var tag : String;
public var project(dynamic,dynamic) : Project;
}
class Version extends neko.db.Object {
static function RELATIONS() {
return [{ key : "project", prop : "project", manager : Project.manager }];
}
public static var manager = new VersionManager(Version);
public var id : Int;
public var project(dynamic,dynamic) : Project;
public var name : String;
public var date : String; // sqlite does not have a proper 'date' type
public var comments : String;
public var downloads : Int;
public var documentation : Null<String>;
}
class Developer extends neko.db.Object {
static var TABLE_IDS = ["user","project"];
static function RELATIONS() {
return [
{ key : "user", prop : "user", manager : User.manager },
{ key : "project", prop : "project", manager : Project.manager },
];
}
public static var manager = new neko.db.Manager<Developer>(Developer);
public var user(dynamic,dynamic) : User;
public var project(dynamic,dynamic) : Project;
}
class ProjectManager extends neko.db.Manager<Project> {
public function containing( word ) : List<{ id : Int, name : String }> {
word = quote("%"+word+"%");
return results("SELECT id, name FROM Project WHERE name LIKE "+word+" OR description LIKE "+word);
}
public function allByName() {
return objects("SELECT * FROM Project ORDER BY name COLLATE NOCASE",false);
}
}
class VersionManager extends neko.db.Manager<Version> {
public function latest( n : Int ) {
return objects("SELECT * FROM Version ORDER BY date DESC LIMIT "+n,false);
}
public function byProject( p : Project ) {
return objects("SELECT * FROM Version WHERE project = "+p.id+" ORDER BY date DESC",false);
}
}
class TagManager extends neko.db.Manager<Tag> {
public function topTags( n : Int ) {
return results("SELECT tag, COUNT(*) as count FROM Tag GROUP BY tag ORDER BY count DESC LIMIT "+n);
}
}
class SiteDb {
public static function create( db : sys.db.Connection ) {
db.request("DROP TABLE IF EXISTS User");
db.request("
CREATE TABLE User (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name VARCHAR(16) NOT NULL UNIQUE,
fullname VARCHAR(50) NOT NULL,
pass VARCHAR(32) NOT NULL,
email VARCHAR(50) NOT NULL
)
");
db.request("DROP TABLE IF EXISTS Project");
db.request("
CREATE TABLE Project (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
owner INTEGER NOT NULL,
name VARCHAR(32) NOT NULL UNIQUE,
license VARCHAR(20) NOT NULL,
description TEXT NOT NULL,
website VARCHAR(100) NOT NULL,
version INT,
downloads INT NOT NULL
)
");
db.request("DROP TABLE IF EXISTS Version");
db.request("
CREATE TABLE Version (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
project INTEGER NOT NULL,
downloads INTEGER NOT NULL,
date VARCHAR(19) NOT NULL,
name VARCHAR(32) NOT NULL,
comments TEXT NOT NULL,
documentation TEXT NULL
)
");
db.request("DROP TABLE IF EXISTS Developer");
db.request("
CREATE TABLE Developer (
user INTEGER NOT NULL,
project INTEGER NOT NULL
)
");
db.request("DROP TABLE IF EXISTS Tag");
db.request("
CREATE TABLE Tag (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
tag VARCHAR(32) NOT NULL,
project INTEGER NOT NULL
)
");
db.request("DROP INDEX IF EXISTS TagSearch");
db.request("CREATE INDEX TagSearch ON Tag(tag)");
}
}

View File

@@ -0,0 +1,212 @@
/* structure */
body {
margin : 0;
padding : 0;
text-align: center;
font-family : "trebuchet ms",sans-serif;
font-size : 10.5pt;
background : #FFFFFF url("http://haxe.org/img/haxe/bg_main.gif") repeat-x;
}
.page {
margin: 10px auto 10px auto;
padding : 5px;
width : 750px;
background-color : #FFFFFF;
border : 1px solid #AAA;
-moz-border-radius : 10px;
}
.menu {
text-align : left;
float : left;
width : 150px;
}
.content {
text-align : left;
float : left;
width : 600px;
}
.clear {
clear : both;
}
/* style */
a, a:visited {
color : #C35700;
text-decoration : none;
}
a:hover {
text-decoration : underline;
}
h1 {
text-align : center;
margin-top : 20px;
}
h1 a, h1 a:hover, h1 a:visited {
color : #C35700;
text-decoration : none;
}
.content p {
text-align : justify;
margin : 0px;
padding : 0px 10px 0px 10px;
}
.menu ul {
list-style : none;
margin : 5px;
padding : 0px;
}
.versions .date, .versions .project, .versions .name {
display : inline;
}
.versions ul, .projects ul {
list-style : circle;
margin : 25px;
padding : 0px;
}
.date {
color : #555;
font-size : 12px;
}
.versions .name {
font-weight : bold;
}
.versions .download {
float : right;
margin-top : -22px;
margin-right : 30px;
}
.download {
padding : 2px 4px 2px 4px;
background-color : #eee;
display : inline;
}
.download a {
color : #555;
font-size : 12px;
text-decoration : none;
}
.download a:visited {
color : #555;
}
.versions .comments {
margin-right : 40px;
margin-left : 10px;
margin-bottom : 5px;
text-align : justify;
}
.pinfos .description {
padding : 10px;
}
.pinfos .download {
margin : 200px;
}
.label {
color : #555;
width : 80px;
float : left;
}
.tags a {
margin-right : 5px;
}
form {
margin-left : 5px;
margin-top : 5px;
}
input {
width : 80px;
}
/* documentation */
.api .title {
font-size: 35;
font-weight: bold;
text-align: center;
background-color : #FFD473;
color : white;
}
.api ul.entry {
list-style-type: disc;
margin-left : 30px;
padding-left : 0px;
}
.api .package_content {
display : none;
}
.api a {
text-decoration : none;
}
.api a:hover {
text-decoration : underline;
}
.api a.package {
color : black;
}
.api .index {
}
.api .kwd {
color : #09598A;
font-weight : bold;
}
.api .classname {
font-size : 30;
font-weight : bold;
}
.api .classdoc {
border : 1px dashed #666;
margin-left : 20px;
margin-right : 20px;
padding : 5 5 5 5;
}
.api .importmod, .api .extends, .api .implements, .api .typedef, .api .platforms {
color : #777;
}
.api dd {
margin-top : 10px;
font-size : 12pt;
color : #444;
}
.api dt {
margin-left : 20px;
margin-bottom : 5px;
text-align : left;
}

View File

@@ -0,0 +1,3 @@
-neko haxelib.n
-main legacyhaxelib.Main
-cmd "nekotools boot haxelib.n"

View File

@@ -0,0 +1,2 @@
#!/bin/sh
exec haxe --run legacyhaxelib.Main "$@"

View File

@@ -0,0 +1,5 @@
-neko index.n
-main legacyhaxelib.Site
-dce no
-D haxelib-site
-lib hx2compat

View File

@@ -0,0 +1,180 @@
<html>
<head>
<title>lib.haxe.org</title>
<base href="::basehref::" />
<link type="application/rss+xml" rel="alternate" title="RSS feed" href="rss"/>
<link href="haxelib.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<h1><a href="">(Legacy Haxelib) lib.haxe.org</a></h1>
<div class="page">
<div class="menu">
<div class="title">Search :</div>
<form action="::basehref::search" method="POST">
<input name="v"/>
</form>
<div class="title">Top tags :</div>
<ul>
::foreach menuTags::
<li><a href="t/::tag::">::tag::</a> (::count::)</li>
::end::
</ul>
</div>
<div class="content">
::if error::
<div class="error">
::error::
</div>
::elseif act_index::
<h2>Welcome</h2>
<p>
This website is listing all the libraries available through the <code>haxelib</code> haXe package manager.
Please visit <a href="http://haxe.org/haxelib">the haxelib page</a> on haXe website to learn more about haxelib.
</p>
<h2>Latest releases</h2>
<div class="versions">
<ul>
::foreach versions::
<li>
<div class="date">::date:: </div>
<div class="project"><a href="p/::(__project.name)::">::(__project.name)::</a> </div>
<div class="name">::name:: </div>
<div class="download"><a href="$$download(__project,__current__)">Download</a></div>
<div class="comments">::comments::</div>
</li>
::end::
</ul>
</div>
<a href="all">Browse Projects</a>
::elseif act_p::
<h2>::(p.name)::</h2>
<div class="pinfos">
<div class="description">::(p.description)::</div>
::if tags::<div class="tags"><div class="label">Tags</div> ::foreach tags::<a href="t/::tag::">::tag::</a>::end::</div>::end::
<div class="url"><div class="label">Website</div> <a href="::(p.website)::">::(p.website)::</a></div>
<div class="version"><div class="label">Version</div> ::(version.name)::</div>
<div class="owner"><div class="label">Owner</div> <a href="u/::(owner.name)::">::(owner.name)::</a></div>
<div class="license"><div class="label">License</div> ::(p.license)::</div>
::if (version.documentation)::<div class="doc"><a href="d/::(p.name)::">Documentation</a></div>::end::
<div class="download"><a href="$$download(p,version)">Download</a></div>
</div>
<h2>History</h2>
<div class="versions">
<ul>
::foreach versions::
<li>
<div class="date">::date:: </div>
<div class="name">::name:: </div>
<div class="download"><a href="$$download(p,__current__)">Download</a></div>
<div class="comments">::comments::</div>
</li>
::end::
</ul>
</div>
::elseif act_u::
<h2>::(u.name)::</h2>
<div class="uinfos">
<div class="name"><div class="label">Name</div> ::(u.fullname)::</div>
<div class="email"><div class="label">Email</div> ::(u.email)::</div>
</div>
<h2>Projects</h2>
<div class="projects">
<ul>
::foreach uprojects::
<li><a href="p/::name::">::name::</a></li>
::end::
</ul>
</div>
::elseif act_t::
<h2>Tag ::tag::</h2>
<p>
Here's the list of projects using this tag :
</p>
<div class="projects">
<ul>
::foreach tprojects::
<li>
<a href="p/::name::">::name::</a>
<div class="description">::description::</div>
</li>
::end::
</ul>
</div>
::elseif act_all::
<h2>::if search::Search Results for '::search::'::else::All Projects::end:::</h2>
<div class="projects">
<ul>
::foreach projects::
<li>
<a href="p/::name::">::name::</a>
<div class="description">::description::</div>
</li>
::end::
</ul>
</div>
::elseif act_d::
<h2>::(p.name):: ::(v.name):: Documentation</h2>
<script type="text/javascript">
function toggle(id) {
var e = document.getElementById(id);
e.isopen = !e.isopen;
e.style.display = e.isopen?"block":"none";
return false;
}
</script>
<div class="api">
::content::
</div>
::else::
<p>
No content for this action
</p>
::end::
</div>
<div class="clear"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,97 @@
package website;
import website.controller.*;
import ufront.mailer.*;
import ufront.MVC;
import sys.db.*;
import haxelib.server.SiteDb;
import haxelib.server.Paths.*;
class Server {
static var ufApp:UfrontApplication;
static function main() {
// this is a temporary fix from https://github.com/dpeek/haxe-markdown/pull/26
@:privateAccess markdown.BlockParser.TableSyntax.TABLE_PATTERN = new EReg('^(.+?:?\\|:?)+(.+)$', '');
ufApp = new UfrontApplication({
indexController: HomeController,
templatingEngines: [TemplatingEngines.erazor],
defaultLayout: "layout.html",
logFile: "logs/haxelib.log",
sessionImplementation: VoidSession,
authImplementation: NobodyAuthHandler,
contentDirectory: "../uf-content/",
requestMiddleware: [],
responseMiddleware: [],
});
ufApp.injector.map( String, "documentationPath" ).toValue( neko.Web.getCwd()+"documentation-files/" );
ufApp.injector.map( UFCacheConnectionSync ).toClass( DBCacheConnection );
ufApp.injector.map( UFCacheConnection ).toClass( DBCacheConnection );
// var cacheMiddleware = new RequestCacheMiddleware();
// ufApp.addRequestMiddleware( cacheMiddleware, true ).addResponseMiddleware( cacheMiddleware, true );
// If we're on neko, and using the module cache, next time jump straight to the main request.
#if (neko && !debug)
neko.Web.cacheModule(run);
#end
// Execute the main request.
run();
}
static function run() {
var wasUpload = handleHaxelibUpload();
if ( wasUpload==false ) {
SiteDb.init();
ufApp.executeRequest();
SiteDb.cleanup();
}
}
static function handleHaxelibUpload():Bool {
if( !sys.FileSystem.exists(TMP_DIR) )
sys.FileSystem.createDirectory(TMP_DIR);
if( !sys.FileSystem.exists(REP_DIR) )
sys.FileSystem.createDirectory(REP_DIR);
var file = null;
var sid = null;
var bytes = 0;
//RAPTORS: the whole handling for nekotools is seriously evil
if (Sys.executablePath().indexOf('nekotools') == -1)
neko.Web.parseMultipart(function(p,filename) {
if( p == "file" ) {
sid = Std.parseInt(filename);
file = sys.io.File.write(TMP_DIR+"/"+sid+".tmp",true);
} else
throw p+" not accepted";
},function(data,pos,len) {
bytes += len;
file.writeFullBytes(data,pos,len);
});
else {
var post = neko.Web.getPostData();
if (post != null) {
var index = post.indexOf('PK');
if (index == -1)
throw 'Invalid Zip - or so I claim';
var start = post.substr(0, index);
var data = post.substr(index);
sid = Std.parseInt(start.split('filename="').pop());
file = sys.io.File.write(TMP_DIR + "/" + sid + ".tmp", true);
bytes = data.length;//thank got neko does not use utf8
file.writeString(data);
}
}
if( file != null ) {
file.close();
neko.Lib.print("File #"+sid+" accepted : "+bytes+" bytes written");
return true;
}
return false;
}
}

View File

@@ -0,0 +1,58 @@
package website;
import haxelib.server.SiteDb;
import mcli.Dispatch;
import ufront.api.UFApi;
import ufront.tasks.UFTaskSet;
import ufront.auth.*;
import ufront.auth.tasks.EasyAuthTasks;
import ufront.auth.EasyAuth;
import ufront.web.session.*;
import website.tasks.*;
using ufront.core.InjectionTools;
class Tasks extends UFTaskSet
{
/**
The CLI runner.
**/
static function main() {
// Only access the command line runner from the command line, not the web.
if ( !neko.Web.isModNeko ) {
// This is an auth system that lets you do anything regardless of permissions, handy for CLI tools
var auth = new EasyAuthAdminMode();
var tasks = new Tasks();
tasks.injector.map( UFHttpSession ).toValue( new VoidSession() );
tasks.injector.map( UFAuthHandler ).toValue( auth );
tasks.injector.map( EasyAuth ).toValue( auth );
tasks.injector.map( String, "contentDirectory" ).toValue( "../uf-content" );
tasks.useCLILogging( "log/twl-webapp.log" );
// Inject our APIs
for ( api in CompileTime.getAllClasses(UFApi) )
tasks.injector.mapRuntimeTypeOf( api );
SiteDb.init();
tasks.execute( Sys.args() );
SiteDb.cleanup();
}
}
/**
Easyauth task set
@alias a
**/
public function auth( d:Dispatch ) {
executeSubTasks( d, EasyAuthTasks );
}
/**
DBCache task set
@alias c
**/
public function cache( d:Dispatch ) {
executeSubTasks( d, HaxelibCacheTasks );
}
}

View File

@@ -0,0 +1,22 @@
package website.api;
import sys.FileSystem;
import ufront.web.HttpError;
import ufront.api.UFApi;
import sys.io.File;
using tink.CoreApi;
class DocumentationApi extends UFApi {
@inject("documentationPath") public var docPath:String;
public function getDocumentationHTML( page:String ):Outcome<String,Error> {
if ( page==null )
page = "index";
var markdownFile = docPath+page+'.md';
var markdown =
try File.getContent( markdownFile )
catch(e:Dynamic) return Failure( new Error(404,'Documentation page $page not found: $e') );
var html = Markdown.markdownToHtml( markdown );
return Success( html );
}
}

View File

@@ -0,0 +1,231 @@
package website.api;
import haxe.io.Bytes;
import haxe.zip.Entry;
import haxe.zip.Reader;
import haxelib.Data;
import haxelib.server.Repo;
import ufront.api.UFApi;
import ufront.cache.UFCache;
import ufront.web.HttpError;
import website.model.SiteDb;
import haxe.ds.Option;
using tink.CoreApi;
using StringTools;
using haxe.io.Path;
class ProjectApi extends UFApi {
public static var cacheNames = {
info: 'haxelib_zip_cache_info',
dirListing: 'haxelib_zip_cache_dir_list',
fileBytes: 'haxelib_zip_cache_file_content'
};
// TODO: inject the repo directory instead.
@inject("scriptDirectory") public var scriptDir:String;
@inject public var cacheCnx:UFCacheConnectionSync;
/** Extensions that should be loaded as a text file. **/
public static var textExtensions:Array<String> = ["md","txt","hx","hxml","json","xml","htaccess"];
public static var imgExtensions:Array<String> = ["jpg","jpeg","gif","png"];
/**
Load the ProjectInfos for the given project.
This contains basic metadata and is loaded from the database (though it is set via a haxelib.json file during project upload).
**/
public function projectInfo( projectName:String ):Outcome<ProjectInfos,Error> {
try {
var info = new Repo().infos(projectName);
return Success( info );
}
catch ( e:Dynamic ) {
var error =
if ( Std.is(e,String) && StringTools.startsWith(e,"No such Project") ) HttpError.pageNotFound();
else Error.withData('Failed to get info for project $projectName',e);
return Failure( error );
}
}
/**
Given a path, load either the file or the directory listing, and take a guess at the content type.
This will use `this.cacheCnx` to cache results, to prevent us having to load the zip file each time.
**/
public function getInfoForPath( projectName:String, version:String, path:String ):Outcome<FileInformation,Error> {
try {
var pathWithSlash = path.addTrailingSlash();
var cache = cacheCnx.getNamespaceSync( cacheNames.info );
var fileInfo = cache.getOrSetSync( '$projectName:$version:$pathWithSlash', function() {
var extension = path.extension();
var zip = getZipEntries(projectName,version);
var fileInfo = null;
for ( entry in zip ) {
if ( entry.fileName==path ) {
// Exact match! It's a file, not a directory. Now, check the type and load it.
fileInfo =
if ( textExtensions.indexOf(extension)>-1 )
Text( Reader.unzip(entry).toString(), extension );
else if ( imgExtensions.indexOf(extension)>-1 )
Image( Reader.unzip(entry), extension );
else
Binary( entry.fileSize );
break;
}
else if ( entry.fileName==pathWithSlash || pathWithSlash=="/" ) {
// It's a directory, so get the listing of files and sub directories.
var dirListing = getDirListing( zip, path );
fileInfo = Directory( dirListing.dirs, dirListing.files );
break;
}
}
if (fileInfo == null) {
// If it's still null, handle one more case: there's no zip entry for the requested directory,
// but there are entries for files in it. Get listing of that directory and if it's not empty,
// return it.
var dirListing = getDirListing( zip, path );
if (dirListing.dirs.length > 0 || dirListing.files.length > 0)
fileInfo = Directory( dirListing.dirs, dirListing.files );
}
return fileInfo;
}).sure();
return
if ( fileInfo!=null ) Success( fileInfo );
else Failure( HttpError.pageNotFound() );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to get file information for $path in $projectName ($version)',e) );
}
/**
Fetch a list of files in a directory in the zip file.
This will use `this.cacheCnx` to cache results, to prevent us having to load the zip file each time.
Returns a Success with two arrays, containing a) sub directories, and b) files. Names are relative to the dirPath, not absolute to the zip.
Returns a Failure with an error if one is encountered.
**/
public function getDirListingFromZip( projectName:String, version:String, dirPath:String ):Outcome<{ dirs:Array<String>, files:Array<String> },Error> {
try {
var cache = cacheCnx.getNamespaceSync( cacheNames.dirListing );
var listing = cache.getOrSetSync( '$projectName:$version:$dirPath', function() {
var zip = getZipEntries(projectName,version);
return getDirListing(zip,dirPath);
}).sure();
return Success( listing );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to read directory $dirPath from $projectName ($version) zip',e) );
}
/**
Read text content from a file in a project's zip file.
Returns Success(Some(contents)) if the file was read successfully.
Returns Success(None) if the file was not found in the zip file.
Returns Failure if an error was encountered.
**/
public function readContentFromZip( projectName:String, version:String, filename:String, ?caseSensitive:Bool=true ):Outcome<Option<String>,Error> {
// This is the same as readBytesFromZip, but we transform the result to turn Bytes into a String.
return readBytesFromZip( projectName, version, filename, caseSensitive ).map(function (outcome) return switch outcome {
case Some(bytes): Some( bytes.toString() );
case None: None;
});
}
/**
Read the raw bytes from a file in a project's zip file.
Returns Success(Some(bytes)) if the file was read successfully.
Returns Success(None) if the file was not found in the zip file.
Returns Failure if an error was encountered.
**/
public function readBytesFromZip( projectName:String, version:String, filename:String, ?caseSensitive:Bool=true ):Outcome<Option<Bytes>,Error> {
try {
var cache = cacheCnx.getNamespaceSync( cacheNames.fileBytes );
var bytes = cache.getOrSetSync( '$projectName:$version:$filename:$caseSensitive', function() {
var zip = getZipEntries( projectName, version );
return getBytesFromFile( zip, filename, caseSensitive );
}).sure();
return
if ( bytes!=null ) Success( Some(bytes) );
else Success( None );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to read $filename from $projectName ($version) zip: $e',e) );
}
/**
Get the path to the zip file (relative to the script directory).
**/
public function getZipFilePath( project:String, version:String ):String {
var fileName = Data.fileName(project, version);
#if deploy
// On the haxe.org server, we want to access the repo from the old website.
// When we change the websites over we should probably move these over too.
return '../www/files/3.0/$fileName';
#else
return 'files/3.0/$fileName';
#end
}
//
// Private API
//
/** Get a list of entries in a zip file. **/
function getZipEntries( projectName:String, version:String ):List<Entry> {
var path = scriptDir+getZipFilePath( projectName, version );
var file = try sys.io.File.read(path,true) catch( e : Dynamic ) throw 'Invalid zip file $path: $e';
var zip = try haxe.zip.Reader.readZip(file) catch( e : Dynamic ) { file.close(); neko.Lib.rethrow(e); };
file.close();
return zip;
}
/** Attempt to extract the bytes of a file within a zip file. Will return null if the file was not found. **/
function getBytesFromFile( zip:List<Entry>, filename:String, caseSensitive:Bool ):Null<Bytes> {
var file = null;
for( f in zip ) {
var sameName = f.fileName==filename || (caseSensitive==false && f.fileName.toLowerCase()==filename.toLowerCase());
if ( sameName )
return Reader.unzip( f );
}
return null;
}
/** Get a directory listing from the zip entries. If the directory doesn't exist the file listings will just be empty. **/
function getDirListing( zip:List<Entry>, dirPath:String ):{ dirs:Array<String>, files:Array<String> } {
dirPath = dirPath.addTrailingSlash();
var subdirectories = [];
var files = [];
for( f in zip ) {
var fileInsideDirectory = (f.fileName.startsWith(dirPath) && f.fileName.length>dirPath.length) || dirPath=="/";
if ( fileInsideDirectory ) {
var remainingName =
if ( dirPath=="/" ) f.fileName;
else f.fileName.substr( dirPath.length );
if ( remainingName.indexOf('/')==-1 ) {
// This is a file in this directory. Add it if we don't have it already.
if ( files.indexOf(remainingName)==-1 )
files.push( remainingName );
}
else {
// This is a file in a subdirectory. Add it if we don't have it already.
var subdirName = remainingName.substr( 0, remainingName.indexOf('/') );
if ( subdirectories.indexOf(subdirName)==-1 )
subdirectories.push( subdirName );
}
}
}
return { dirs:subdirectories, files:files };
}
}
/**
A description of a file found in a Haxelib zip file.
**/
enum FileInformation {
/** A text file, with it's String content and extension. **/
Text( content:String, extension:String );
/** A text file, with it's Bytes content and extension. **/
Image( content:Bytes, extension:String );
/** A binary file that we can't display, together with it's size. **/
Binary( size:Int );
/** A directory listing, with separate arrays for subdirs and files. **/
Directory( subdirs:Array<String>, files:Array<String> );
}

View File

@@ -0,0 +1,73 @@
package website.api;
import ufront.api.UFApi;
import website.model.SiteDb;
using tink.CoreApi;
using Lambda;
using CleverSort;
class ProjectListApi extends UFApi {
public function all():Outcome<Array<Project>,Error> {
try {
var all = Project.manager.search(1 == 1,{ orderBy: [-downloads,name] });
return Success( all.array() );
}
catch ( e:Dynamic ) return Failure( Error.withData("Failed to get list of all projects",e) );
}
public function byUser( username:String ):Outcome<Array<Project>,Error> {
try {
var user = User.manager.select( $name==username );
if ( user==null )
return Failure( new Error(404,'User $username not found') );
var joins = Developer.manager.search( $user==user.id );
// TODO: It would be better to do a single query on all IDs, rather than a single query for each project.
// Unfortunately, the current set up of the "Developer" model doesn't give us access to the ID.
var theirProjects = [ for (j in joins) j.projectObj ];
return Success( theirProjects );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to get list of projects belong to $username',e) );
}
public function getTagList( num:Int ):Outcome<Array<{ tag:String, count: Int }>,Error> {
try {
// TODO, see if we can return more useful info, maybe including the most popular projects in this tag etc.
return Success( Tag.topTags(num).array() );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to get list of tags',e) );
}
public function byTag( tag:String ):Outcome<Array<Project>,Error> {
try {
var tagJoins = Tag.manager.search( $tag==tag );
// TODO: It would be better to do a single query on all IDs, rather than a single query for each project.
// Unfortunately, the current set up of the "Tag" model doesn't give us access to the ID.
var projects = [for (t in tagJoins) t.projectObj];
projects.cleverSort( -_.downloads );
return Success( projects );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to get list of projects with tag $tag',e) );
}
public function search( word:String ):Outcome<Array<Project>,Error> {
try {
// TODO: We should match other things too. Tags & users especially. Also release notes, docs, readmes.
word = '%$word%';
var searchResults = Project.manager.search($name.like(word) || $description.like(word));
return Success( searchResults.array() );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to search for projects with "$word" in the name or description',e) );
}
public function latest( n:Int ):Outcome<Array<{ v:Version, p:Project }>,Error> {
try {
var latestVersions = Version.latest( n );
var l = new List();
// TODO: again, we are performing many queries here. Need to access project ID somehow.
var versionsAndProjects = [for (v in latestVersions) { v:v, p:v.projectObj }];
return Success( versionsAndProjects );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to get most recent $n projects',e) );
}
}

View File

@@ -0,0 +1,69 @@
package website.api;
import haxe.crypto.Md5;
import website.model.SiteDb;
import ufront.web.HttpError;
import ufront.api.UFApi;
import haxe.Utf8;
using tink.CoreApi;
using CleverSort;
using Lambda;
class UserApi extends UFApi {
@inject("documentationPath") public var docPath:String;
/**
Given a username, return that user object and a
**/
public function getUserProfile( username:String ):Outcome<Pair<User,Array<Project>>,Error> {
try {
var user = User.manager.select( $name==username );
if ( user==null )
return Failure( HttpError.pageNotFound() );
var projectJoins = Developer.manager.search( $user==user.id );
var projects = [for (j in projectJoins) j.projectObj];
projects.cleverSort( -_.downloads );
return Success( new Pair(user,projects) );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to fetch user profile for $username', e) );
}
/**
**/
public function getUserList():Outcome<Array<{ user:User, emailHash:String, projects:Array<Project>, totalDownloads:Int }>,Error> {
try {
// Fetch all the objects we are going to be using:
var allUsers = User.manager.all();
var allProjects = Project.manager.all();
var joins = Developer.manager.all();
// Collate them into a map, tally the downloads
var map = new Map();
for ( u in allUsers ) {
var hash = Md5.encode( u.email );
var obj = { user:u, emailHash:hash, projects:[], totalDownloads:0 };
map.set( u.id, obj );
}
for ( j in joins ) {
var obj = map[j.userObj.id];
var project = j.projectObj;
if ( project==null ) throw 'How is it null? ${j}';
obj.projects.push( project );
obj.totalDownloads += project.downloads;
}
// Sort the projects on each user, and then the users
var arr = [];
for ( obj in map ) {
obj.projects.cleverSort( -_.downloads, _.name );
arr.push( obj );
}
arr.cleverSort( -_.totalDownloads, _.user.fullname );
return Success( arr );
}
catch ( e:Dynamic ) return Failure( Error.withData('Failed to get list of all users',e) );
}
}

View File

@@ -0,0 +1,36 @@
package website.controller;
import ufront.web.Controller;
import ufront.web.result.*;
import website.api.DocumentationApi;
import ufront.core.OrderedStringMap;
using tink.CoreApi;
@cacheRequest
class DocumentationController extends Controller {
@inject public var api:DocumentationApi;
@:route("/$page")
public function documentationPage( ?page:String ) {
var html = api.getDocumentationHTML( page ).sure();
var documentationPages = getDocumentationPages();
var docTitle =
if ( page==null ) documentationPages.get('/documentation/');
else documentationPages.get('/documentation/$page/');
return new ViewResult({
title: '$docTitle - Haxelib Documentation',
content: html,
});
}
public static function getDocumentationPages():OrderedStringMap<String> {
var pages = new OrderedStringMap();
pages.set( "/documentation/", "Getting Started" );
pages.set( "/documentation/using-haxelib/", "Using Haxelib" );
pages.set( "/documentation/creating-a-haxelib-package/", "Creating a Haxelib" );
// pages.set( "/documentation/faq/", "FAQ" );
// pages.set( "/documentation/api/", "API" );
return pages;
}
}

View File

@@ -0,0 +1,173 @@
package website.controller;
import ufront.MVC;
import ufront.ufadmin.controller.*;
import website.api.ProjectListApi;
import website.model.SiteDb;
using StringTools;
using tink.CoreApi;
using CleverSort;
class HomeController extends Controller {
@inject public var projectListApi:ProjectListApi;
// Perform init() after dependency injection has occured.
@inject public function init( ctx:HttpContext ) {
// All MVC actions come through HomeController (our index controller) first, so this is a good place to set global template variables.
var r = ctx.request;
var url = 'http://'+r.hostName+r.uri;
if ( r.queryString!="" ) {
url += '?'+r.queryString;
}
ViewResult.globalValues.set( "useWrapper", true );
ViewResult.globalValues.set( "pageUrl", url );
ViewResult.globalValues.set( "currentPage", r.uri );
ViewResult.globalValues.set( "todaysDate", Date.now() );
ViewResult.globalValues.set( "documentationPages", DocumentationController.getDocumentationPages() );
ViewResult.globalValues.set( "description", "Haxe is an open source toolkit based on a modern, high level, strictly typed programming language." );
ViewResult.globalValues.set( "searchTerm", ctx.session.get('searchTerm') );
}
@:route("/")
public function homepage() {
var latestProjects = projectListApi.latest( 10 ).sure();
var tags = projectListApi.getTagList( 10 ).sure();
return new ViewResult({
title: "Haxelib - the Haxe package manager",
description: "Haxelib is a tool that enables sharing libraries and code in the Haxe ecosystem.",
pageUrl: context.request.uri,
latestProjects: latestProjects,
tags: tags,
exampleCode: CompileTime.readFile( "website/homepage-example.txt" ),
useWrapper: false,
});
}
@:route("/p/*")
public var projectController:ProjectController;
@:route("/u/*")
public var userController:UserController;
@:route("/rss/")
public var rssController:RSSController;
@:route("/documentation/*")
public var documentationController:DocumentationController;
@cacheRequest
@:route("/t/")
public function tagList():ViewResult {
var tagList = projectListApi.getTagList( 50 ).sure();
// Build a tag cloud.
var least = null,
most = null,
minSize = 10,
maxSize = 140;
for (t in tagList) {
if ( least==null || t.count<least )
least = t.count;
if ( most==null || t.count>most )
most = t.count;
}
function fontSizeForCount( count:Int ) {
var countRange = most - least;
var sizeRange = maxSize-minSize;
var pos = (count - least) / countRange;
return minSize + pos*sizeRange;
}
var tagCloud = [for (t in tagList) { tag:t.tag, size:fontSizeForCount(t.count) }];
tagCloud.cleverSort( _.tag );
return new ViewResult({
title: 'Haxelib Tags',
description: 'The 50 most popular tags for projects on Haxelib, sorted by the number of projects',
tags: tagList,
tagCloud: tagCloud,
});
}
// TODO: get ufront-mvc to support `/t/$tagName` and `/t/$tagName.json` as different routes.
@cacheRequest
@:route("/t/$tagName")
public function tag( tagName:String ):ActionResult {
if ( tagName.endsWith(".json") ) {
return tagJson( tagName.substr(0,tagName.length-5) );
}
else {
var list = prepareProjectList( projectListApi.byTag( tagName ).sure() );
return new ViewResult({
title: 'Tag: $tagName',
icon: 'fa-tag',
description: 'A list of all projects on Haxelib with the tag "$tagName"',
projects: list,
}, "projectList.html");
}
}
@cacheRequest
@:route("/all")
public function all() {
var list = prepareProjectList( projectListApi.all().sure() );
return new ViewResult({
title: 'All Haxelibs',
icon: 'fa-star',
description: 'A list of every project uploaded on haxelib, sorted by popularity',
projects: list,
}, "projectList.html");
}
@:route("/search")
public function search( ?args:{ v:String } ) {
var result = new ViewResult();
if ( args.v==null || args.v.length==0 ) {
result.setVars({
title: 'Search Haxelib',
description: 'Search Haxelib project names and descriptions',
searchTerm: "",
projects: null
});
}
else {
context.session.set( 'searchTerm', args.v );
var list = prepareProjectList( projectListApi.search( args.v ).sure() );
result.setVars({
title: 'Search for "${args.v}"',
description: 'Haxelib projects that match the search word "${args.v}"',
projects: list,
searchTerm: args.v,
});
}
return result;
}
static function prepareProjectList( list:Array<Project> ):Array<{ name:String, author:String, description:String, version:String, downloads:Int }> {
return [for (p in list) {
name: p.name,
author: p.ownerObj.name,
description: p.description,
version: p.versionObj.toSemver(),
downloads: p.downloads
}];
}
@:route("/search.json")
public function searchJson( args:{ v:String } )
return new JsonResult( projectListApi.search(args.v).sure() );
@cacheRequest
@:route("/all.json")
public function allJson()
return new JsonResult( projectListApi.all().sure() );
@:route("/ufadmin/*")
public function ufadmin() {
return executeSubController( UFAdminHomeController );
}
public function tagJson( tagName:String )
return new JsonResult( projectListApi.byTag(tagName).sure() );
}

View File

@@ -0,0 +1,182 @@
package website.controller;
import haxelib.SemVer;
import ufront.MVC;
import website.api.ProjectApi;
import haxe.ds.Option;
import markdown.AST;
import Markdown;
using tink.CoreApi;
using haxe.io.Path;
using CleverSort;
using Lambda;
using DateTools;
class ProjectController extends Controller {
@inject public var projectApi:ProjectApi;
@:route("/$projectName")
public function project( projectName:String ) {
var info = projectApi.projectInfo( projectName ).sure();
return version( projectName );
}
@:route("/$projectName/versions/")
public function versionList( projectName:String ) {
var info = projectApi.projectInfo( projectName ).sure();
info.versions.sort(function(v1, v2) return SemVer.compare(v2.name, v1.name));
return new ViewResult({
title: 'All versions of $projectName',
project: projectName,
allVersions: info.versions,
info: info,
});
}
@:route("/$projectName/$semver")
public function version( projectName:String, ?semver:String ) {
var info = projectApi.projectInfo( projectName ).sure();
if ( semver==null )
semver = info.curversion;
var currentVersion = info.versions.find( function(v) return v.name==semver );
if ( currentVersion==null )
throw HttpError.pageNotFound();
var downloadUrl = '/p/$projectName/$semver/download/';
var readmeHTML = switch projectApi.readContentFromZip( projectName, semver, "README.md" ) {
case Success(Some(readme)): markdownToHtml(readme, '/p/$projectName/$semver/raw-files/');
case Success(None): ""; // No README.
case Failure(err):
ufError( err.message );
ufError( err.toString() );
"";
}
return new ViewResult({
title: '$projectName ($semver)',
project: projectName,
allVersions: info.versions,
version: semver,
versionDate: Date.fromString(currentVersion.date).format('%F'),
info: info,
downloadUrl: downloadUrl,
readme: readmeHTML,
}, "version.html");
}
function markdownToHtml(markdown:String, prefix:String) {
// this function is basically a copy of Markdown.markdownToHtml
// default md->html rendering function, but it adds a filter that
// fixes relative URLs in IMG tags
try {
var imgSrcfixer = new MarkdownImgRelativeSrcFixer(prefix);
var document = new Document();
var lines = ~/(\r\n|\r)/g.replace(markdown, '\n').split("\n");
document.parseRefLinks(lines);
var blocks = document.parseLines(lines);
for (block in blocks) block.accept(imgSrcfixer); // fix relative image links
return Markdown.renderHtml(blocks);
} catch (e:Dynamic) {
return '<pre>$e</pre>';
}
}
@:route("/$projectName/$semver/download/")
public function download( projectName:String, semver:String ) {
var zipFile = projectApi.getZipFilePath( projectName, semver );
return new DirectFilePathResult( context.request.scriptDirectory+zipFile );
}
@cacheRequest
@:route("/$projectName/$semver/doc/$typePath")
public function docs( projectName:String, semver:String, ?typePath:String ) {
return new ViewResult({
title: 'View project $projectName docs for $typePath',
});
}
@cacheRequest
@:route("/$projectName/$semver/files/*")
public function file( projectName:String, semver:String, rest:Array<String> ) {
var filePath = rest.join("/");
var downloadLink = baseUri+'$projectName/$semver/raw-files/$filePath';
var info = projectApi.projectInfo( projectName ).sure();
var data:TemplateData = {
title: 'Viewing $filePath on $projectName:$semver',
project: projectName,
info: info,
version: semver,
fileParts: rest,
filePath: filePath,
downloadLink: downloadLink,
type: "download",
};
switch projectApi.getInfoForPath( projectName, semver, filePath ).sure() {
case Directory(dirs,files):
data["type"] = "directory";
data["dirListing"] = dirs;
data["fileListing"] = files;
data["currentDir"] = baseUri+'$projectName/$semver/files/$filePath'.removeTrailingSlashes();
case Text(str,ext):
if ( ["md","mdown","markdown"].indexOf(ext)>-1 ) {
str = Markdown.markdownToHtml( str );
data["type"] = "markdown";
}
else {
data["type"] = "text";
}
data["fileContent"] = str;
data["extension"] = ext;
data["highlightLanguage"] = ext;
case Image(bytes,ext):
data["filename"] = rest[rest.length-1];
data["type"] = "img";
case Binary(size):
data["filename"] = rest[rest.length-1];
var sizeInKb = Math.round(size/1024*10) / 10;
data["size"] = sizeInKb + "kb";
}
var vr = new ViewResult( data );
vr.helpers["extensionAllowed"] = function(file:String) return ProjectApi.textExtensions.has(file.extension().toLowerCase());
return vr;
}
// TODO: write some tests...
@:route("/$projectName/$semver/raw-files/*")
public function downloadFile( projectName:String, semver:String, rest:Array<String> ) {
var filename = rest[ rest.length-1 ];
var filePath = rest.join("/");
switch projectApi.readBytesFromZip( projectName, semver, filePath, true ).sure() {
case Some(bytes):
return new BytesResult( bytes, null, filename );
case None:
throw HttpError.pageNotFound();
}
}
}
private class MarkdownImgRelativeSrcFixer implements NodeVisitor {
static var ABSOLUTE_URL_RE = ~/^(https?:\/)?\//;
var prefix:String;
public function new(prefix:String) {
this.prefix = prefix;
}
public function visitElementBefore(element:ElementNode):Bool {
if (element.tag == "img") {
var url = element.attributes["src"];
if (!ABSOLUTE_URL_RE.match(url))
element.attributes["src"] = prefix + url;
}
return element.children != null;
}
public function visitText(text:TextNode):Void {}
public function visitElementAfter(element:ElementNode):Void {}
}

View File

@@ -0,0 +1,86 @@
package website.controller;
import haxelib.server.SiteDb;
import ufront.web.Controller;
import ufront.web.result.ContentResult;
import website.api.ProjectListApi;
using StringTools;
using DateTools;
using tink.CoreApi;
class RSSController extends Controller {
@inject public var projectListApi:ProjectListApi;
@:route("/")
public function rss( ?args:{ number:Int } ) {
var number = (args.number!=null) ? args.number : 20;
var releases = projectListApi.latest( number ).sure();
var rss = buildRss( releases );
var content = '<?xml version="1.0" encoding="UTF-8"?>'+rss.toString();
return new ContentResult( content, "text/xml" );
}
function buildRss( releases:Array<{v:Version, p:Project}> ):Xml {
// Helpers for building the XML
var createChild = function(root:Xml, name:String){
var c = Xml.createElement( name );
root.addChild( c );
return c;
}
var createChildWithContent = function(root:Xml, name:String, content:String){
var e = Xml.createElement( name );
var c = Xml.createPCData( if (content != null) content else "" );
e.addChild( c );
root.addChild( e );
return e;
}
var createChildWithCdata = function(root:Xml, name:String, content:String){
var e = Xml.createElement( name );
var c = Xml.createCData( if (content != null) content else "" );
e.addChild( c );
root.addChild( e );
return e;
}
// Set some variables we'll use.
Sys.setTimeLocale( "en_US.UTF8" );
var hostName = context.request.hostName;
var url = "http://"+hostName;
var num = releases.length;
// Create the RSS document and headers.
var rss = Xml.createElement( "rss" );
rss.set( "version", "2.0" );
rss.set( "xmlns:atom", "http://www.w3.org/2005/Atom" );
rss.set( "xmlns:dc", "http://purl.org/dc/elements/1.1/" );
var channel = createChild( rss, "channel" );
var link = createChild( channel, "atom:link" );
link.set( "href", 'http://$hostName/rss/' );
link.set( "rel", "self" );
link.set( "type", "application/rss+xml" );
createChildWithContent( channel, "title", 'Latest Haxelib Releases ($hostName)' );
createChildWithContent( channel, "link", url );
createChildWithContent( channel, "description", 'The latest $num haxelib releases on $hostName' );
createChildWithContent( channel, "generator", "haxe" );
createChildWithContent( channel, "language", "en" );
// Create the various RSS entries.
for ( release in releases ) {
var version = release.v;
var project = release.p;
var item = createChild(channel, "item");
var title = '${project.name} ${version.toSemver()}';
var description = '<p>${version.comments.htmlEscape()}</p><hr/><p>${project.description.htmlEscape()}</p>';
createChildWithContent( item, "title", title.htmlEscape() );
createChildWithContent( item, "link", url+"/p/"+project.name );
createChildWithContent( item, "guid", url+"/p/"+project.name+"?v="+version.id );
var date = Date.fromString( version.date ).format( "%a, %e %b %Y %H:%M:%S %z" );
createChildWithContent( item, "pubDate", date );
createChildWithContent( item, "dc:creator", project.ownerObj.name );
createChildWithContent( item, "description", description );
}
return rss;
}
}

View File

@@ -0,0 +1,35 @@
package website.controller;
import haxe.crypto.Md5;
import ufront.web.Controller;
import ufront.web.result.*;
import website.api.UserApi;
using tink.CoreApi;
@cacheRequest
class UserController extends Controller {
@inject public var api:UserApi;
@:route("/$username")
public function profile( username:String ) {
var data = api.getUserProfile( username ).sure();
var user = data.a;
return new ViewResult({
title: '$username (${user.fullname}) on Haxelib',
user: user,
projects: data.b,
emailHash: Md5.encode( user.email ),
});
}
@:route("/")
public function list() {
var userList = api.getUserList().sure();
return new ViewResult({
title: 'Haxelib Contributors',
list: userList
});
}
// Future: edit your own profile. Especially password resets.
}

View File

@@ -0,0 +1,12 @@
haxelib install actuate # Install `actuate` library
haxelib install actuate 1.8.1 # Install a specific version
haxe -lib actuate -main Test -js test.js # Use `actuate` library in your Haxe build
haxelib list # List all of your installed libraries
haxelib list openfl # List your installed libraries that have "openfl" in the name
haxelib install actuate.zip # Install a library from a zip file
haxelib install test.hxml # Install all the libs listed in a hxml
haxelib install all # Install all the libs in the hxml files in the current directory
haxelib submit actuate.zip # Use Haxelib to share your library with others!

View File

@@ -0,0 +1,7 @@
package website.model;
typedef Developer = haxelib.server.SiteDb.Developer;
typedef Project = haxelib.server.SiteDb.Project;
typedef Tag = haxelib.server.SiteDb.Tag;
typedef User = haxelib.server.SiteDb.User;
typedef Version = haxelib.server.SiteDb.Version;

View File

@@ -0,0 +1,29 @@
package website.tasks;
import ufront.tasks.UFTaskSet;
import ufront.cache.DBCache;
import ufront.cache.RequestCacheMiddleware;
import website.api.ProjectApi;
class HaxelibCacheTasks extends UFTaskSet {
@:skip @inject public var api:DBCacheApi;
/** Set up the cache table. **/
public function setup():Void api.setup();
/** Clear every cached item. **/
public function clearAll():Void api.clearAll();
/** Clear all cached pages. **/
public function clearPageCache() api.clearNamespace( RequestCacheMiddleware.namespace );
/** Clear a projects zip file cache entries, and also the page caches for that project. **/
public function clearZipCache( project:String, version:String ) {
var namespaces = ProjectApi.cacheNames;
var prefix = '$project:$version:%';
api.clearItemLike( namespaces.info, prefix );
api.clearItemLike( namespaces.dirListing, prefix );
api.clearItemLike( namespaces.fileBytes, prefix );
api.clearItemLike( RequestCacheMiddleware.namespace, '/p/$project/%' );
}
}

View File

@@ -0,0 +1,59 @@
import haxelib.client.Vcs.VcsID;
import haxe.unit.TestRunner;
import sys.*;
import sys.io.*;
import tests.*;
using StringTools;
class HaxelibTests {
public static function runCommand(cmd:String, args:Array<String>):Void
{
Sys.println('Command: $cmd $args');
var exitCode = Sys.command(cmd, args);
Sys.println('Command exited with $exitCode: $cmd $args');
if(exitCode != 0)
Sys.exit(exitCode);
}
static function cmdSucceed(cmd:String, ?args:Array<String>):Bool {
var p = try {
new Process(cmd, args);
} catch(e:Dynamic) {
return false;
}
var exitCode = p.exitCode();
p.close();
return exitCode == 0;
}
static function main():Void {
var r = new TestRunner();
r.add(new TestSemVer());
r.add(new TestData());
r.add(new TestRemoveSymlinks());
r.add(new TestRemoveSymlinksBroken());
var isCI = Sys.getEnv("CI") != null;
if (isCI || cmdSucceed("hg", ["version"])) {
// Hg impl. suports tags & revs. Here "78edb4b" is a first revision "initial import" at that repo:
r.add(new TestHg());
} else {
Sys.println("hg not found.");
}
if (isCI || cmdSucceed("git", ["version"])) {
// Git impl. suports only tags. Here "0.9.2" is a first revision too ("initial import"):
r.add(new TestGit());
} else {
Sys.println("git not found.");
}
r.add(new TestVcsNotFound());
var success = r.run();
Sys.exit(success ? 0 : 1);
}
}

View File

@@ -0,0 +1,174 @@
import haxe.unit.*;
import haxe.*;
import haxe.io.*;
import sys.*;
import sys.io.*;
import haxelib.*;
using StringTools;
using IntegrationTests;
class IntegrationTests extends TestBase {
var haxelibBin:String = Path.join([Sys.getCwd(), "run.n"]);
public var server(default, null):String = switch (Sys.getEnv("HAXELIB_SERVER")) {
case null:
"localhost";
case url:
url;
};
public var serverPort(default, null) = switch (Sys.getEnv("HAXELIB_SERVER_PORT")) {
case null:
2000;
case port:
Std.parseInt(port);
};
public var serverUrl(get, null):String;
function get_serverUrl() return serverUrl != null ? serverUrl : serverUrl = 'http://${server}:${serverPort}/';
static var originalRepo(default, never) = {
var p = new Process("haxelib", ["config"]);
var repo = Path.normalize(p.stdout.readLine());
p.close();
repo;
};
static public var repo(default, never) = "repo_integration_tests";
static public var bar(default, never) = {
user: "Bar",
email: "bar@haxe.org",
fullname: "Bar",
pw: "barpassword",
};
static public var foo(default, never) = {
user: "Foo",
email: "foo@haxe.org",
fullname: "Foo",
pw: "foopassword",
};
public var clientVer(get, null):SemVer;
var clientVer_inited = false;
function get_clientVer() {
return if (clientVer_inited)
clientVer;
else {
clientVer = {
var r = haxelib(["version"]).result();
if (r.code == 0)
SemVer.ofString(r.out.trim());
else if (r.out.indexOf("3.1.0-rc.4") >= 0)
SemVer.ofString("3.1.0-rc.4");
else
throw "unknown version";
};
clientVer_inited = true;
clientVer;
}
}
function haxelib(args:Array<String>, ?input:String):Process {
var p = #if system_haxelib
new Process("haxelib", ["-R", serverUrl].concat(args));
#else
new Process("neko", [haxelibBin, "-R", serverUrl].concat(args));
#end
if (input != null) {
p.stdin.writeString(input);
p.stdin.close();
}
return p;
}
function assertSuccess(r:{out:String, err:String, code:Int}, ?pos:haxe.PosInfos):Void {
if (r.code != 0) {
throw r;
}
assertEquals(0, r.code, pos);
}
function assertNoError(f:Void->Void):Void {
f();
assertTrue(true);
}
var dbConfig:Dynamic = Json.parse(File.getContent("www/dbconfig.json"));
var dbCnx:sys.db.Connection;
function resetDB():Void {
var db = dbConfig.database;
dbCnx.request('DROP DATABASE IF EXISTS ${db};');
dbCnx.request('CREATE DATABASE ${db};');
var filesPath = "www/files/3.0";
for (item in FileSystem.readDirectory(filesPath)) {
if (item.endsWith(".zip")) {
FileSystem.deleteFile(Path.join([filesPath, item]));
}
}
var tmpPath = "tmp";
for (item in FileSystem.readDirectory(filesPath)) {
if (item.endsWith(".tmp")) {
FileSystem.deleteFile(Path.join([tmpPath, item]));
}
}
}
override function setup():Void {
super.setup();
dbCnx = sys.db.Mysql.connect({
user: dbConfig.user,
pass: dbConfig.pass,
host: server,
port: dbConfig.port,
database: dbConfig.database,
});
resetDB();
deleteDirectory(repo);
haxelibSetup(repo);
}
override function tearDown():Void {
haxelibSetup(originalRepo);
deleteDirectory(repo);
resetDB();
dbCnx.close();
super.tearDown();
}
static public function result(p:Process):{out:String, err:String, code:Int} {
var out = p.stdout.readAll().toString();
var err = p.stderr.readAll().toString();
var code = p.exitCode();
p.close();
return {out:out, err:err, code:code};
}
static public function haxelibSetup(path:String):Void {
var p = new Process("haxelib", ["setup", path]);
if (p.exitCode() != 0)
throw "unable to set haxelib repo to " + path;
p.close();
}
static function main():Void {
var prevDir = Sys.getCwd();
var runner = new TestRunner();
runner.add(new tests.integration.TestEmpty());
runner.add(new tests.integration.TestSimple());
runner.add(new tests.integration.TestUpgrade());
runner.add(new tests.integration.TestUpdate());
runner.add(new tests.integration.TestList());
runner.add(new tests.integration.TestSet());
runner.add(new tests.integration.TestInfo());
runner.add(new tests.integration.TestUser());
runner.add(new tests.integration.TestDev());
var success = runner.run();
if (!success) {
Sys.exit(1);
}
}
}

View File

@@ -0,0 +1,52 @@
import sys.*;
import sys.io.*;
import haxe.io.*;
using StringTools;
class Prepare {
static function zipDir(dir:String, outPath:String):Void {
var entries = new List<haxe.zip.Entry>();
function add(path:String, target:String) {
if (!FileSystem.exists(path))
throw 'Invalid path: $path';
if (FileSystem.isDirectory(path)) {
for (item in FileSystem.readDirectory(path))
add(path + "/" + item, target == "" ? item : target + "/" + item);
} else {
var bytes = File.getBytes(path);
var entry:haxe.zip.Entry = {
fileName: target,
fileSize: bytes.length,
fileTime: FileSystem.stat(path).mtime,
compressed: false,
dataSize: 0,
data: bytes,
crc32: haxe.crypto.Crc32.make(bytes),
}
haxe.zip.Tools.compress(entry, 9);
entries.add(entry);
}
}
add(dir, "");
var out = File.write(outPath, true);
var writer = new haxe.zip.Writer(out);
writer.write(entries);
out.close();
}
static function main():Void {
/*
(re)package the dummy libraries
*/
var libsPath = "test/libraries";
for (item in FileSystem.readDirectory(libsPath)) {
var path = Path.join([libsPath, item]);
if (FileSystem.isDirectory(path)) {
zipDir(path, 'test/libraries/${item}.zip');
}
}
}
}

View File

@@ -0,0 +1,329 @@
import Sys.*;
import haxe.*;
import haxe.io.*;
import sys.FileSystem.*;
import sys.io.File.*;
class RunCi {
static function successMsg(msg:String):Void {
Sys.println('\x1b[32m' + msg + '\x1b[0m');
}
static function failMsg(msg:String):Void {
Sys.println('\x1b[31m' + msg + '\x1b[0m');
}
static function infoMsg(msg:String):Void {
Sys.println('\x1b[36m' + msg + '\x1b[0m');
}
/**
Run a command using `Sys.command()`.
If the command exits with non-zero code, exit the whole script with the same code.
If `useRetry` is `true`, the command will be re-run if it exits with non-zero code (3 trials).
It is useful for running network-dependent commands.
*/
static function runCommand(cmd:String, ?args:Array<String>, useRetry:Bool = false):Void {
var trials = useRetry ? 3 : 1;
var exitCode:Int = 1;
var cmdStr = cmd + (args != null ? ' $args' : '');
while (trials-->0) {
Sys.println("Command: " + cmdStr);
var t = Timer.stamp();
exitCode = Sys.command(cmd, args);
var dt = Math.round(Timer.stamp() - t);
if (exitCode == 0)
successMsg('Command exited with $exitCode in ${dt}s: $cmdStr');
else
failMsg('Command exited with $exitCode in ${dt}s: $cmdStr');
if (exitCode == 0) {
return;
} else if (trials > 0) {
Sys.println('Command will be re-run...');
}
}
Sys.exit(exitCode);
}
static function download(url:String, saveAs:String):Void {
infoMsg('download $url as $saveAs');
runCommand("curl", ["-fSLk", url, "-o", saveAs, "-A", "Mozilla/4.0"]);
}
static function compileServer():Void {
runCommand("haxe", ["server.hxml"]);
}
static function compileLegacyServer():Void {
runCommand("haxe", ["server_legacy.hxml"]);
}
static function compileClient():Void {
runCommand("haxe", ["client.hxml"]);
}
static function compileLegacyClient():Void {
runCommand("haxe", ["client_legacy.hxml"]);
}
static function testClient():Void {
runCommand("haxe", ["client_tests.hxml"]);
}
static function testServer():Void {
runCommand("haxe", ["server_tests.hxml"]);
}
static function setupLocalServer():Void {
var ndllPath = getEnv("NEKOPATH");
if (ndllPath == null) ndllPath = "/usr/lib/neko";
var DocumentRoot = Path.join([getCwd(), "www"]);
var dbConfigPath = Path.join(["www", "dbconfig.json"]);
var dbConfig = Json.parse(getContent(dbConfigPath));
// update dbConfig.host to be "localhost"
saveContent(dbConfigPath, Json.stringify({
user: dbConfig.user,
pass: dbConfig.pass,
host: "localhost",
database: dbConfig.database,
}));
function writeApacheConf(confPath:String):Void {
var hasModNeko = switch (systemName()) {
case "Windows":
false;
case _:
var p = new sys.io.Process("apachectl", ["-M"]);
var out = p.stdout.readAll().toString();
var has = out.indexOf("neko_module") >= 0;
p.close();
has;
}
var confContent =
(
if (systemName() == "Windows")
"LoadModule rewrite_module modules/mod_rewrite.so\n"
else
""
) +
(
if (hasModNeko)
""
else
'LoadModule neko_module ${Path.join([ndllPath, "mod_neko2.ndll"])}\n'
) +
'LoadModule tora_module ${Path.join([ndllPath, "mod_tora2.ndll"])}
AddHandler tora-handler .n
Listen 2000
<VirtualHost *:2000>
DocumentRoot "$DocumentRoot"
</VirtualHost>
<Directory "$DocumentRoot">
Options Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
';
var confOut = if (exists(confPath))
append(confPath);
else
write(confPath);
confOut.writeString(confContent);
confOut.flush();
confOut.close();
}
function configDb():Void {
var isAppVeyor = getEnv("APPVEYOR") != null;
var user = if (isAppVeyor)
// https://www.appveyor.com/docs/services-databases#mysql
{ user: "root", pass: "Password12!" };
else
{ user: "root", pass: "" };
var cnx = sys.db.Mysql.connect({
user: user.user,
pass: user.pass,
host: "localhost",
port: 3306,
database: "",
});
cnx.request('create user \'${dbConfig.user}\'@\'localhost\' identified by \'${dbConfig.pass}\';');
cnx.request('create database ${dbConfig.database};');
cnx.request('grant all on ${dbConfig.database}.* to \'${dbConfig.user}\'@\'localhost\';');
cnx.close();
}
switch (systemName()) {
case "Windows":
configDb();
download("https://www.apachelounge.com/download/win32/binaries/httpd-2.2.31-win32.zip", "bin/httpd.zip");
runCommand("7z", ["x", "bin\\httpd.zip", "-obin\\httpd"]);
writeApacheConf("bin\\httpd\\Apache2\\conf\\httpd.conf");
rename("bin\\httpd\\Apache2", "c:\\Apache2");
var serviceName = "HaxelibApache";
var httpd = "c:\\Apache2\\bin\\httpd.exe";
runCommand(httpd, ["-k", "install", "-n", serviceName]);
runCommand(httpd, ["-n", serviceName, "-t"]);
runCommand(httpd, ["-k", "start", "-n", serviceName]);
var toraPath = {
var p = new sys.io.Process("haxelib", ["path", "tora"]);
var path = p.stdout.readLine();
p.close();
path;
}
runCommand("nssm", ["install", "tora", Path.join([getEnv("NEKOPATH"), "neko.exe"]), Path.join([toraPath, "run.n"])]);
runCommand("nssm", ["start", "tora"]);
Sys.sleep(2.5);
case "Mac":
runCommand("brew", ["install", "homebrew/apache/httpd22", "mysql"]);
runCommand("mysql.server", ["start"]);
configDb();
runCommand("apachectl", ["start"]);
Sys.sleep(2.5);
writeApacheConf("/usr/local/etc/apache2/2.2/httpd.conf");
Sys.sleep(2.5);
runCommand("apachectl", ["restart"]);
Sys.sleep(2.5);
case "Linux":
configDb();
runCommand("sudo", ["apt-get", "install", "apache2"]);
writeApacheConf("haxelib.conf");
runCommand("sudo", ["ln", "-s", Path.join([Sys.getCwd(), "haxelib.conf"]), "/etc/apache2/conf.d/haxelib.conf"]);
runCommand("sudo", ["a2enmod", "rewrite"]);
runCommand("sudo", ["service", "apache2", "restart"]);
Sys.sleep(2.5);
case name:
throw "System not supported: " + name;
}
Sys.setCwd("www");
runCommand("bower", ["install"]);
Sys.setCwd("..");
Sys.putEnv("HAXELIB_SERVER", "localhost");
Sys.putEnv("HAXELIB_SERVER_PORT", "2000");
}
static function runWithDockerServer(test:Void->Void):Void {
var server = switch (systemName()) {
case "Linux":
"localhost";
case _:
var p = new sys.io.Process("docker-machine", ["ip"]);
var ip = p.stdout.readLine();
p.close();
ip;
}
var serverPort = 2000;
runCommand("docker-compose", ["-f", "test/docker-compose.yml", "up", "-d"]);
Sys.putEnv("HAXELIB_SERVER", server);
Sys.putEnv("HAXELIB_SERVER_PORT", Std.string(serverPort));
infoMsg("waiting for server to start...");
var url = 'http://${server}:${serverPort}/';
var t = Timer.stamp();
while (true) {
var isUp = try {
var response = haxe.Http.requestUrl(url);
!StringTools.startsWith(response, "Error");
} catch (e:Dynamic) {
false;
}
if (isUp) {
break;
}
if (Timer.stamp() - t > 120) {
throw "server is not reachable...";
}
Sys.sleep(10.0);
// Sys.command("curl", ["-s", "-o", "/dev/null", "-w", "%{http_code}", url]);
}
infoMsg("server started");
test();
runCommand("docker-compose", ["-f", "test/docker-compose.yml", "down"]);
}
static function integrationTests():Void {
function test():Void {
switch (Sys.getEnv("TRAVIS_HAXE_VERSION")) {
case null, "development":
runCommand("haxe", ["integration_tests.hxml"]);
case "3.1.3":
runCommand("haxe", ["integration_tests.hxml", "-D", "system_haxelib"]);
case _:
runCommand("haxe", ["integration_tests.hxml"]);
runCommand("haxe", ["integration_tests.hxml", "-D", "system_haxelib"]);
}
}
if (Sys.getEnv("CI") != null && Sys.getEnv("USE_DOCKER") == null) {
setupLocalServer();
test();
} else {
runWithDockerServer(test);
}
}
static function installDotNet11():Void {
// This is a msvcr71.dll in my own dropbox. If you want to obtain one, you probably shouldn't use my file.
// Instead, install .Net Framework 1.1 from the link as follows
// https://www.microsoft.com/en-us/download/details.aspx?id=26
download("https://dl.dropboxusercontent.com/u/2661116/msvcr71.dll", Path.join([getEnv("NEKOPATH"), "msvcr71.dll"]));
}
static function main():Void {
// Note that package.zip output is also used by client tests, so it has to be run before that.
runCommand("haxe", ["package.hxml"]);
runCommand("haxe", ["prepare_tests.hxml"]);
compileLegacyClient();
compileLegacyServer();
// the server can only be compiled with haxe 3.2+
// haxe 3.1.3 bundles haxelib client 3.1.0-rc.4, which is not upgradable to later haxelib
// so there is no need to test the client either
#if (haxe_ver >= 3.2)
compileClient();
testClient();
compileServer();
if (systemName() == "Windows") {
// The Neko 2.0 Windows binary archive is missing "msvcr71.dll", which is a dependency of "sqlite.ndll".
// https://github.com/HaxeFoundation/haxe/issues/2008#issuecomment-176849497
installDotNet11();
}
testServer();
#end
// integration test
switch (systemName()) {
case "Windows", "Linux":
integrationTests();
case "Mac":
#if (haxe_ver >= 3.2)
integrationTests();
#end
case _:
throw "Unknown system";
}
}
}

View File

@@ -0,0 +1,33 @@
import sys.*;
import sys.io.*;
import haxe.io.*;
import haxe.unit.*;
class TestBase extends TestCase {
static var haxelibPath = FileSystem.fullPath("run.n");
public function runHaxelib(args:Array<String>) {
var p = new Process("neko", [haxelibPath].concat(args));
var stdout = p.stdout.readAll().toString();
var stderr = p.stderr.readAll().toString();
var exitCode = p.exitCode();
p.close();
return {
stdout: stdout,
stderr: stderr,
exitCode: exitCode
}
}
public function deleteDirectory(dir:String):Void {
if (!FileSystem.exists(dir)) return;
var exitCode = switch (Sys.systemName()) {
case "Windows":
Sys.command("rmdir", ["/S", "/Q", StringTools.replace(FileSystem.fullPath(dir), "/", "\\")]);
case _:
Sys.command("rm", ["-rf", dir]);
}
if (exitCode != 0) {
throw 'unable to delete $dir';
}
}
}

View File

@@ -0,0 +1,44 @@
package;
import website.api.ProjectListApi;
import ufront.app.UfrontApplication;
import ufront.mailer.*;
import ufront.auth.EasyAuth;
import ufront.view.TemplatingEngines;
import twl.webapp.*;
import twl.*;
import buddy.*;
@:build(buddy.GenerateMain.withSuites([
website.controller.DocumentationControllerTest,
website.controller.HomeControllerTest,
// website.controller.ProjectControllerTest,
website.controller.RSSControllerTest,
// website.controller.UserControllerTest,
]))
class WebsiteTests {
static var ufApp:UfrontApplication;
public static function getTestApp():UfrontApplication {
if ( ufApp==null ) {
// Create a UfrontApplication suitable for unit testing.
ufApp = new UfrontApplication({
indexController: website.controller.HomeController,
errorHandlers: [],
disableBrowserTrace: true,
contentDirectory: "../uf-content/",
templatingEngines: [TemplatingEngines.erazor],
viewPath: "www/view/",
defaultLayout: "layout.html",
});
// Different injections for our test suite.
ufApp.injector.map( UFMailer ).toSingleton( TestMailer );
ufApp.injector.map( EasyAuth ).toValue( new EasyAuthAdminMode() );
ufApp.injector.map( String, "documentationPath" ).toValue( "www/documentation-files/" );
haxelib.server.SiteDb.init();
}
return ufApp;
}
}

View File

@@ -0,0 +1,21 @@
version: '2'
services:
web:
image: andyli/tora
ports:
- "2000:80"
volumes:
- ../www:/var/www/html
dbHost:
image: mariadb
ports:
- "3306:3306"
environment:
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
- MYSQL_DATABASE=haxelib
- MYSQL_USER=dbUser
- MYSQL_PASSWORD=dbPass
volumes:
- database:/var/lib/mysql
volumes:
database: {}

View File

@@ -0,0 +1,19 @@
version: '2'
services:
web:
build: ..
ports:
- "2000:80"
dbHost:
image: mariadb
ports:
- "3306:3306"
environment:
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
- MYSQL_DATABASE=haxelib
- MYSQL_USER=dbUser
- MYSQL_PASSWORD=dbPass
volumes:
- database:/var/lib/mysql
volumes:
database: {}

View File

@@ -0,0 +1,11 @@
{
"name": "UseCp",
"url" : "http://example.org",
"license": "GPL",
"tags": [],
"description": "This project use a deep class path.",
"version": "0.0.1",
"releasenote": "Initial release, everything is working correctly",
"classPath": "lib/src",
"contributors": ["Bar"]
}

View File

@@ -0,0 +1,9 @@
package bar;
class Bar {
static function new() {
trace("new Bar");
}
}

View File

@@ -0,0 +1,9 @@
package bar;
class Bar {
static function new() {
trace("new Bar");
}
}

View File

@@ -0,0 +1,10 @@
{
"name": "Bar",
"url" : "http://example.org",
"license": "GPL",
"tags": ["bar", "test"],
"description": "This project is an example of an haxelib project",
"version": "1.0.0",
"releasenote": "Initial release, everything is working correctly",
"contributors": ["Bar"]
}

View File

@@ -0,0 +1,7 @@
<project name="Bar" url="http://example.org" license="GPL">
<user name="Bar"/>
<tag v="bar"/>
<tag v="test"/>
<description>This project is an example of an haxelib project</description>
<version name="1.0.0">Initial release, everything is working correctly</version>
</project>

View File

@@ -0,0 +1,9 @@
package bar;
class Bar {
static function new() {
trace("new Bar");
}
}

View File

@@ -0,0 +1,10 @@
{
"name": "Bar",
"url" : "http://example.org",
"license": "MIT",
"tags": ["bar", "version2"],
"description": "This project is an example of an haxelib project",
"version": "2.0.0",
"releasenote": "Version 2.",
"contributors": ["Bar"]
}

View File

@@ -0,0 +1,7 @@
<project name="Bar" url="http://example.org" license="GPL">
<user name="Bar"/>
<tag v="bar"/>
<tag v="test"/>
<description>This project is an example of an haxelib project</description>
<version name="1.0.0">Initial release, everything is working correctly</version>
</project>

View File

@@ -0,0 +1,9 @@
package bar;
class Bar {
static function new() {
trace("new Bar");
}
}

View File

@@ -0,0 +1,10 @@
{
"name": "Deep",
"url" : "http://example.org",
"license": "Public",
"tags": ["deep", "test"],
"description": "This project's zip contains a folder that holds the lib.",
"version": "1.0.0",
"releasenote": "N/A",
"contributors": ["DeepAuthor", "AnotherGuy"]
}

View File

@@ -0,0 +1,9 @@
package foo;
class Foo {
public function new() {
trace("new Foo");
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "Foo",
"url" : "http://example.org",
"license": "GPL",
"tags": ["foo", "test"],
"description": "This project is an example of an haxelib project",
"version": "0.1.0-alpha.0",
"releasenote": "Initial release, everything is working correctly",
"dependencies": {
"Bar": ""
},
"contributors": ["Foo"]
}

View File

@@ -0,0 +1,7 @@
<project name="Foo" url="http://example.org" license="GPL">
<user name="Foo"/>
<tag v="foo"/>
<tag v="test"/>
<description>This project is an example of an haxelib project</description>
<version name="1.0.0-alpha.0">Initial release, everything is working correctly</version>
</project>

View File

@@ -0,0 +1,282 @@
package tests;
import haxe.ds.StringMap;
import haxe.Json;
import haxe.io.*;
import haxe.zip.*;
import sys.io.*;
import haxelib.Data;
using StringTools;
class TestData extends TestBase {
public function testSafe() {
assertEquals( "abc", checkSafe("abc") );
assertEquals( "bean,hx", checkSafe("bean.hx") );
assertEquals( "1,2,3", checkSafe("1.2.3") );
assertEquals( "_-,123", checkSafe("_-.123") );
assertEquals( "invalid", checkSafe("1,2") );
assertEquals( "invalid", checkSafe("space ") );
assertEquals( "invalid", checkSafe("\t") );
assertEquals( "invalid", checkSafe("\n") );
assertEquals( "invalid", checkSafe("") );
}
function checkSafe( str:String ) {
return try {
Data.safe( str );
} catch (e:String) "invalid";
}
public function testUnsafe() {
assertEquals( "abc", Data.unsafe("abc") );
assertEquals( "1.2.3", Data.unsafe("1,2,3") );
assertEquals( "", Data.unsafe("") );
}
public function testFileName() {
assertEquals( "lib-1,2,3.zip", checkFileName("lib","1.2.3") );
assertEquals( "lib-1,2,3-rc,3.zip", checkFileName("lib","1.2.3-rc.3") );
assertEquals( "invalid", checkFileName("lib",",") );
assertEquals( "invalid", checkFileName(",","version") );
assertEquals( "invalid", checkFileName("","version") );
}
function checkFileName( lib, ver ) {
return try {
Data.fileName( lib, ver );
} catch (e:String) "invalid";
}
public function testLocateBasePath() {
var zip = Reader.readZip(new BytesInput(File.getBytes("package.zip")));
assertEquals( "", Data.locateBasePath(zip) );
var zip = Reader.readZip(new BytesInput(File.getBytes("test/libraries/libDeep.zip")));
assertEquals( "libDeep/", Data.locateBasePath(zip) );
}
public function testReadDoc() {
var zip = Reader.readZip(new BytesInput(File.getBytes("package.zip")));
assertEquals( null, Data.readDoc(zip) );
//TODO
}
public function testReadInfos() {
var zip = Reader.readZip(new BytesInput(File.getBytes("package.zip")));
var info = Data.readInfos(zip, true);
assertEquals( "haxelib", info.name );
assertEquals( "GPL", info.license );
var zip = Reader.readZip(new BytesInput(File.getBytes("test/libraries/libDeep.zip")));
var info = Data.readInfos(zip, true);
assertEquals( "Deep", info.name );
assertEquals( "http://example.org", info.url );
assertEquals( "Public", info.license );
assertEquals( "deep, test", info.tags.join(", ") );
assertEquals( "This project's zip contains a folder that holds the lib.", info.description );
assertEquals( "1.0.0", info.version );
assertEquals( "N/A", info.releasenote );
assertEquals( "DeepAuthor, AnotherGuy", info.contributors.join(", ") );
}
public function testCheckClassPath() {
var zip = Reader.readZip(new BytesInput(File.getBytes("package.zip")));
var info = Data.readInfos(zip, true);
var ok:Dynamic = try {
Data.checkClassPath(zip,info);
true;
} catch (e:Dynamic) {
e;
}
assertEquals( ok, true );
var zip = Reader.readZip(new BytesInput(File.getBytes("test/libraries/libDeep.zip")));
var info = Data.readInfos(zip, true);
var ok:Dynamic = try {
Data.checkClassPath(zip,info);
true;
} catch (e:Dynamic) {
e;
}
assertEquals( ok, true );
}
public function testReadDataWithCheck() {
assertFalse( readDataOkay("bad json") );
assertTrue( readDataOkay(getJsonInfos()) );
// Names
assertFalse( readDataOkay("{}") );
assertFalse( readDataOkay(getJsonInfos({ name: null })) );
assertFalse( readDataOkay(getJsonInfos({ name: '' })) );
assertFalse( readDataOkay(getJsonInfos({ name: 'haxe' })) );
assertFalse( readDataOkay(getJsonInfos({ name: 'haXe' })) );
assertFalse( readDataOkay(getJsonInfos({ name: 'all' })) );
assertFalse( readDataOkay(getJsonInfos({ name: 'something.zip' })) );
assertFalse( readDataOkay(getJsonInfos({ name: 'something.hxml' })) );
assertFalse( readDataOkay(getJsonInfos({ name: '12' })) );
assertTrue( readDataOkay(getJsonInfos({ name: 'mylib' })) );
assertFalse( readDataOkay(getJsonInfos([ "name" ])) ); // remove the field altogether
// Description (optional)
assertTrue( readDataOkay(getJsonInfos({ description: 'Some Description' })) );
assertTrue( readDataOkay(getJsonInfos({ description: '' })) );
assertTrue( readDataOkay(getJsonInfos({ description: null })) );
// Licence
assertTrue( readDataOkay(getJsonInfos({ license: 'BSD' })) );
assertFalse( readDataOkay(getJsonInfos({ license: null })) );
assertFalse( readDataOkay(getJsonInfos({ license: '' })) );
assertFalse( readDataOkay(getJsonInfos({ license: 'CustomLicence' })) );
assertFalse( readDataOkay(getJsonInfos([ "license" ])) ); // remove the field altogether
// Contibutors
assertFalse( readDataOkay(getJsonInfos({ contributors: [] })) );
assertFalse( readDataOkay(getJsonInfos({ contributors: null })) );
assertFalse( readDataOkay(getJsonInfos({ contributors: "jason" })) );
assertTrue( readDataOkay(getJsonInfos({ contributors: ["jason"] })) );
assertTrue( readDataOkay(getJsonInfos({ contributors: ["jason","juraj"] })) );
assertFalse( readDataOkay(getJsonInfos([ "contributors" ])) ); // remove the field altogether
// Version
assertTrue( readDataOkay(getJsonInfos({ version: "0.1.2-rc.0" })) );
assertFalse( readDataOkay(getJsonInfos({ version: "non-semver" })) );
assertFalse( readDataOkay(getJsonInfos({ version: 0 })) );
assertFalse( readDataOkay(getJsonInfos({ version: null })) );
assertFalse( readDataOkay(getJsonInfos([ "version" ])) ); // remove the field altogether
// Tags (optional)
assertTrue( readDataOkay(getJsonInfos({ tags: ["tag1","tag2"] })) );
assertTrue( readDataOkay(getJsonInfos({ tags: null })) );
assertFalse( readDataOkay(getJsonInfos({ tags: "mytag" })) );
// Dependencies (optional)
assertTrue( readDataOkay(getJsonInfos({ dependencies: null })) );
assertTrue( readDataOkay(getJsonInfos({ dependencies: { somelib: "" } })) );
assertTrue( readDataOkay(getJsonInfos({ dependencies: { somelib:"1.3.0" } }) ));
assertFalse( readDataOkay(getJsonInfos({ dependencies: { somelib: "nonsemver" }})) );
assertFalse( readDataOkay(getJsonInfos({ dependencies: { somelib: 0 } })) );
assertFalse( readDataOkay(getJsonInfos({ dependencies: "somelib" })) );
// ReleaseNote
assertTrue( readDataOkay(getJsonInfos({ releasenote: "release" })) );
assertFalse( readDataOkay(getJsonInfos({ releasenote: ["some","note"] })) );
assertFalse( readDataOkay(getJsonInfos({ releasenote: null })) );
assertFalse( readDataOkay(getJsonInfos([ "releasenote" ])) ); // remove the field altogether
// ClassPath
assertTrue( readDataOkay(getJsonInfos({ classPath: 'src/' })) );
assertTrue( readDataOkay(getJsonInfos({ classPath: '' })) );
assertTrue( readDataOkay(getJsonInfos({ classPath: null })) );
assertFalse( readDataOkay(getJsonInfos({ classPath: ["src","othersrc"] })) );
}
public function testReadDataWithoutCheck() {
assertEquals( ProjectName.DEFAULT, Data.readData("bad json",false).name );
assertEquals( "0.0.0", Data.readData("bad json",false).version );
assertEquals( "mylib", Data.readData(getJsonInfos(),false).name );
assertEquals( "0.1.2", Data.readData(getJsonInfos(),false).version );
// Names
assertEquals( ProjectName.DEFAULT, Data.readData("{}",false).name );
assertEquals( ProjectName.DEFAULT, Data.readData(getJsonInfos({ name: null }),false).name );
assertEquals( ProjectName.DEFAULT, Data.readData(getJsonInfos({ name: '' }),false).name );
assertEquals( "mylib", Data.readData(getJsonInfos({ name: 'mylib' }),false).name );
assertEquals( ProjectName.DEFAULT, Data.readData(getJsonInfos([ "name" ]), false).name ); // remove the field altogether
/*
// Description (optional)
assertEquals( "Some Description", Data.readData(getJsonInfos({ description: 'Some Description' }),false).description );
assertEquals( "", Data.readData(getJsonInfos({ description: '' }),false).description );
assertEquals( "", Data.readData(getJsonInfos({ description: null }),false).description );
assertEquals( "", Data.readData(getJsonInfos([ "description" ]),false).description ); // remove the field altogether
// Licence
assertEquals( "BSD", Data.readData(getJsonInfos({ license: 'BSD' }),false).license );
assertEquals( "Unknown", Data.readData(getJsonInfos({ license: null }),false).license );
assertEquals( "Unknown", Data.readData(getJsonInfos({ license: '' }),false).license );
assertEquals( "CustomLicence", Data.readData(getJsonInfos({ license: 'CustomLicence' }),false).license );
assertEquals( "Unknown", Data.readData(getJsonInfos([ "license" ]),false).license ); // remove the field altogether
// Contibutors
assertEquals( 0, Data.readData(getJsonInfos({ contributors: [] }),false).contributors.length );
assertEquals( 0, Data.readData(getJsonInfos({ contributors: null }),false).contributors.length );
assertEquals( 0, Data.readData(getJsonInfos({ contributors: "jason" }),false).contributors.length );
assertEquals( 1, Data.readData(getJsonInfos({ contributors: ["jason"] }),false).contributors.length );
assertEquals( 2, Data.readData(getJsonInfos({ contributors: ["jason","juraj"] }),false).contributors.length );
assertEquals( 0, Data.readData(getJsonInfos([ "contributors" ]),false).contributors.length ); // remove the field altogether
*/
// Version
assertEquals( "0.1.2-rc.0", Data.readData(getJsonInfos({ version: "0.1.2-rc.0" }),false).version );
assertEquals( "0.0.0", Data.readData(getJsonInfos({ version: "non-semver" }),false).version );
assertEquals( "0.0.0", Data.readData(getJsonInfos({ version: 0 }),false).version );
assertEquals( "0.0.0", Data.readData(getJsonInfos({ version: null }),false).version );
assertEquals( "0.0.0", Data.readData(getJsonInfos([ "version" ]),false).version ); // remove the field altogether
/*
// Tags (optional)
assertEquals( 2, Data.readData(getJsonInfos({ tags: ["tag1","tag2"] }),false).tags.length );
assertEquals( 0, Data.readData(getJsonInfos({ tags: null }),false).tags.length );
assertEquals( 0, Data.readData(getJsonInfos({ tags: "mytag" }),false).tags.length );
*/
// Dependencies (optional)
assertEquals( 0, Data.readData(getJsonInfos({ dependencies: null }),false).dependencies.toArray().length );
assertEquals( "somelib", Data.readData(getJsonInfos({ dependencies: { somelib:"" } }),false).dependencies.toArray()[0].name );
assertEquals( "", Data.readData(getJsonInfos({ dependencies: { somelib:"" } }),false).dependencies.toArray()[0].version );
assertEquals( "1.3.0", Data.readData(getJsonInfos({ dependencies: { somelib:"1.3.0" } }),false).dependencies.toArray()[0].version );
assertEquals( "", Data.readData(getJsonInfos({ dependencies: { somelib:"nonsemver" } }),false).dependencies.toArray()[0].version );
assertEquals( "", Data.readData(getJsonInfos({ dependencies: { somelib:null } }),false).dependencies.toArray()[0].version );
assertEquals( "", Data.readData(getJsonInfos( { dependencies: { somelib:0 } } ), false).dependencies.toArray()[0].version );
/*
// ReleaseNote
assertEquals( "release", Data.readData(getJsonInfos({ releasenote: "release" }),false).releasenote );
assertEquals( "", Data.readData(getJsonInfos({ releasenote: null }),false).releasenote );
assertEquals( "", Data.readData(getJsonInfos([ "releasenote" ]),false).releasenote ); // remove the field altogether
*/
// ClassPath
assertEquals( "src", Data.readData(getJsonInfos({ classPath: 'src' }), false).classPath );
assertEquals( "", Data.readData(getJsonInfos({ classPath: '' }), false).classPath );
assertEquals( "", Data.readData(getJsonInfos({ classPath: null }), false).classPath );
}
function readDataOkay( json ) {
try {
Data.readData( json,true );
return true;
}
catch (e:String) {
return false;
}
}
function getJsonInfos( ?remove:Array<String>, ?change:Dynamic ) {
var infos = {
name: "mylib",
license: "MIT",
contributors: ["jason"],
version: "0.1.2",
releasenote: ""
};
if (change != null) {
for ( name in Reflect.fields(change) ) {
var value = Reflect.field( change, name );
Reflect.setField( infos, name, value );
}
}
if (remove != null) {
for ( f in remove )
Reflect.deleteField( infos, f );
}
return Json.stringify(infos);
}
}

View File

@@ -0,0 +1,9 @@
package tests;
import haxelib.client.Vcs;
class TestGit extends TestVcs {
public function new():Void {
super(VcsID.Git, "Git", "https://github.com/fzzr-/hx.signal.git", "0.9.2");
}
}

View File

@@ -0,0 +1,9 @@
package tests;
import haxelib.client.Vcs;
class TestHg extends TestVcs {
public function new():Void {
super(VcsID.Hg, "Mercurial", "https://bitbucket.org/fzzr/hx.signal", "78edb4b");
}
}

View File

@@ -0,0 +1,55 @@
package tests;
import sys.FileSystem;
import sys.io.*;
import haxe.io.Path;
import haxe.unit.TestCase;
class TestRemoveSymlinks extends TestBase
{
//----------- properties, fields ------------//
static var REPO = "haxelib-repo";
var lib:String = "symlinks";
var repo:String = null;
var origRepo:String;
//--------------- constructor ---------------//
public function new()
{
super();
this.repo = Path.join([Sys.getCwd(), "test", REPO]);
}
//--------------- initialize ----------------//
override public function setup():Void
{
origRepo = ~/\r?\n/.split(runHaxelib(["config"]).stdout)[0];
origRepo = Path.normalize(origRepo);
var libzip = Path.join([Sys.getCwd(), "test", "libraries", lib + ".zip"]);
if (runHaxelib(["setup", repo]).exitCode != 0) {
throw "haxelib setup failed";
}
if (runHaxelib(["local", libzip]).exitCode != 0) {
throw "haxelib local failed";
}
}
override public function tearDown():Void {
if (runHaxelib(["setup", origRepo]).exitCode != 0) {
throw "haxelib setup failed";
}
deleteDirectory(repo);
}
//----------------- tests -------------------//
public function testRemoveLibWithSymlinks():Void
{
var code = runHaxelib(["remove", lib]).exitCode;
assertEquals(code, 0);
assertFalse(FileSystem.exists(Path.join([repo, lib])));
}
}

View File

@@ -0,0 +1,9 @@
package tests;
class TestRemoveSymlinksBroken extends TestRemoveSymlinks
{
public function new():Void {
super();
this.lib = "symlinks-broken";
}
}

View File

@@ -0,0 +1,72 @@
package tests;
import haxelib.SemVer;
class TestSemVer extends TestBase {
static function make(major, minor, patch, ?preview, ?previewNum):SemVer {
return {
major : major,
minor : minor,
patch : patch,
preview : preview,
previewNum : previewNum
};
}
public function testToString() {
assertEquals( "0.1.2", make(0,1,2) );
// Release Tags
assertEquals( "0.1.2-alpha", make(0,1,2,ALPHA) );
assertEquals( "0.1.2-beta", make(0,1,2,BETA) );
assertEquals( "0.1.2-rc", make(0,1,2,RC) );
// Release Tag Versions
assertEquals( "0.1.2-alpha.0", make(0,1,2,ALPHA,0) );
assertEquals( "0.1.2-beta.0", make(0,1,2,BETA,0) );
assertEquals( "0.1.2-rc.0", make(0,1,2,RC,0) );
// Weird input
assertEquals( "0.1.2", make(0,1,2,null,0) );
// Multiple characters
assertEquals( "100.200.300-rc.400", make(100,200,300,RC,400) );
}
public function testOfString() {
// Normal
assertEquals( "0.1.2", (SemVer.ofString("0.1.2").data : SemVer));
assertEquals( "100.50.200", (SemVer.ofString("100.50.200").data : SemVer));
// Release tags
assertEquals( "0.1.2-alpha", (SemVer.ofString("0.1.2-ALPHA").data : SemVer));
assertEquals( "0.1.2-alpha", (SemVer.ofString("0.1.2-alpha").data : SemVer));
assertEquals( "0.1.2-beta", (SemVer.ofString("0.1.2-beta").data : SemVer));
assertEquals( "0.1.2-rc", (SemVer.ofString("0.1.2-rc").data : SemVer));
assertEquals( "0.1.2-rc.1", (SemVer.ofString("0.1.2-rc.1").data : SemVer));
}
public function testOfStringInvalid() {
assertEquals( "invalid", parseInvalid(null) );
assertEquals( "invalid", parseInvalid("") );
assertEquals( "invalid", parseInvalid("1") );
assertEquals( "invalid", parseInvalid("1.1") );
assertEquals( "invalid", parseInvalid("1.2.a") );
assertEquals( "invalid", parseInvalid("a.b.c") );
assertEquals( "invalid", parseInvalid("1.2.3-") );
assertEquals( "invalid", parseInvalid("1.2.3-rc.") );
assertEquals( "invalid", parseInvalid("1.2.3--rc.1") );
assertEquals( "invalid", parseInvalid("1.2.3-othertag") );
assertEquals( "invalid", parseInvalid("1.2.3-othertag.1") );
assertEquals( "invalid", parseInvalid("10.050.02"));
assertEquals( "invalid", parseInvalid("10.50.2-rc.01"));
}
function parseInvalid( str:String ) {
return try {
(SemVer.ofString( str ) : String);
} catch (e:String) {
"invalid";
}
}
}

View File

@@ -0,0 +1,246 @@
package tests;
import sys.io.*;
import sys.FileSystem;
import haxe.io.*;
import haxe.unit.TestCase;
import haxelib.client.Cli;
import haxelib.client.Vcs;
class TestVcs extends TestBase
{
//----------- properties, fields ------------//
static inline var REPO_ROOT = "test/libraries";
static inline var REPO_DIR = "vcs";
static var CWD:String = null;
var id:VcsID = null;
var vcsName:String = null;
var url:String = null;
var rev:String = null;
var counter:Int = 0;
//--------------- constructor ---------------//
public function new(id:VcsID, vcsName:String, url:String, ?rev:String)
{
super();
this.id = id;
this.url = url;
this.rev = rev;
this.vcsName = vcsName;
CWD = Sys.getCwd();
counter = 0;
}
//--------------- initialize ----------------//
override public function setup():Void
{
Sys.setCwd(Path.join([CWD, REPO_ROOT]));
if(FileSystem.exists(REPO_DIR)) {
deleteDirectory(REPO_DIR);
}
FileSystem.createDirectory(REPO_DIR);
Sys.setCwd(REPO_DIR);
}
override public function tearDown():Void
{
// restore original CWD:
Sys.setCwd(CWD);
deleteDirectory(Path.join([CWD, REPO_ROOT, REPO_DIR]));
}
//----------------- tests -------------------//
public function testGetVcs():Void
{
assertTrue(Vcs.get(id, {quiet: true}) != null);
assertTrue(Vcs.get(id, {quiet: true}).name == vcsName);
}
public function testAvailable():Void
{
assertTrue(getVcs().available);
}
// --------------- clone --------------- //
public function testGetVcsByDir():Void
{
var vcs = getVcs();
testCloneSimple();
assertEquals(vcs, Vcs.get(id, {quiet: true}));
}
public function testCloneSimple():Void
{
var vcs = getVcs();
var dir = vcs.directory + counter++;
vcs.clone(dir, url);
assertTrue(FileSystem.exists(dir));
assertTrue(FileSystem.isDirectory(dir));
assertTrue(FileSystem.exists('$dir/.${vcs.directory}'));
assertTrue(FileSystem.isDirectory('$dir/.${vcs.directory}'));
}
public function testCloneBranch():Void
{
var vcs = getVcs();
var dir = vcs.directory + counter++;
vcs.clone(dir, url, "develop");
assertTrue(FileSystem.exists(dir));
assertTrue(FileSystem.isDirectory(dir));
assertTrue(FileSystem.exists('$dir/.${vcs.directory}'));
assertTrue(FileSystem.isDirectory('$dir/.${vcs.directory}'));
}
public function testCloneBranchTag_0_9_2():Void
{
var vcs = getVcs();
var dir = vcs.directory + counter++;
vcs.clone(dir, url, "develop", "0.9.2");
Sys.sleep(3);
assertTrue(FileSystem.exists(dir));
assertTrue(FileSystem.exists('$dir/.${vcs.directory}'));
// if that repo "README.md" was added in tag/rev.: "0.9.3"
assertFalse(FileSystem.exists(dir + "/README.md"));
}
public function testCloneBranchTag_0_9_3():Void
{
var vcs = getVcs();
var dir = vcs.directory + counter++;
vcs.clone(dir, url, "develop", "0.9.3");
assertTrue(FileSystem.exists(dir));
assertTrue(FileSystem.exists('$dir/.${vcs.directory}'));
// if that repo "README.md" was added in tag/rev.: "0.9.3"
assertTrue(FileSystem.exists(dir + "/README.md"));
}
public function testCloneBranchRev():Void
{
var vcs = getVcs();
var dir = vcs.directory + counter++;
vcs.clone(dir, url, "develop", rev);
assertTrue(FileSystem.exists(dir));
assertTrue(FileSystem.exists('$dir/.${vcs.directory}'));
// if that repo "README.md" was added in tag/rev.: "0.9.3"
assertFalse(FileSystem.exists(dir + "/README.md"));
}
// --------------- update --------------- //
public function testUpdateBranchTag_0_9_2__toLatest():Void
{
var vcs = getVcs();
var dir = vcs.directory + counter;// increment will do in `testCloneBranchTag_0_9_2`
testCloneBranchTag_0_9_2();
assertFalse(FileSystem.exists("README.md"));
// save CWD:
var cwd = Sys.getCwd();
Sys.setCwd(cwd + dir);
assertTrue(FileSystem.exists("." + vcs.directory));
// in this case `libName` can get any value:
vcs.update("LIBNAME");
// Now we get actual version (0.9.3 or newer) with README.md.
assertTrue(FileSystem.exists("README.md"));
// restore CWD:
Sys.setCwd(cwd);
}
public function testUpdateBranchTag_0_9_2__toLatest__afterUserChanges_withReset():Void
{
var vcs = getVcs();
var dir = vcs.directory + counter;// increment will do in `testCloneBranchTag_0_9_2`
testCloneBranchTag_0_9_2();
assertFalse(FileSystem.exists("README.md"));
// save CWD:
var cwd = Sys.getCwd();
Sys.setCwd(cwd + dir);
// creating user-changes:
FileSystem.deleteFile("build.hxml");
File.saveContent("file", "new file \"file\" with content");
//Hack: set the default answer:
Cli.defaultAnswer = true;
// update to HEAD:
// in this case `libName` can get any value:
vcs.update("LIBNAME");
// Now we get actual version (0.9.3 or newer) with README.md.
assertTrue(FileSystem.exists("README.md"));
// restore CWD:
Sys.setCwd(cwd);
}
public function testUpdateBranchTag_0_9_2__toLatest__afterUserChanges_withoutReset():Void
{
var vcs = getVcs();
var dir = vcs.directory + counter;// increment will do in `testCloneBranchTag_0_9_2`
testCloneBranchTag_0_9_2();
assertFalse(FileSystem.exists("README.md"));
// save CWD:
var cwd = Sys.getCwd();
Sys.setCwd(cwd + dir);
// creating user-changes:
FileSystem.deleteFile("build.hxml");
File.saveContent("file", "new file \"file\" with content");
//Hack: set the default answer:
Cli.defaultAnswer = false;
// update to HEAD:
// in this case `libName` can get any value:
vcs.update("LIBNAME");
// We get no reset and update:
assertTrue(FileSystem.exists("file"));
assertFalse(FileSystem.exists("build.hxml"));
assertFalse(FileSystem.exists("README.md"));
// restore CWD:
Sys.setCwd(cwd);
}
//----------------- tools -------------------//
inline function getVcs():Vcs
{
return Vcs.get(id, {quiet: true});
}
}

View File

@@ -0,0 +1,183 @@
package tests;
import sys.FileSystem;
import haxe.io.*;
import haxelib.client.Vcs;
class TestVcsNotFound extends TestBase
{
//----------- properties, fields ------------//
static inline var REPO_ROOT = "test/libraries";
static inline var REPO_DIR = "vcs-no";
static var CWD:String = null;
//--------------- constructor ---------------//
public function new()
{
super();
CWD = Sys.getCwd();
}
//--------------- initialize ----------------//
override public function setup():Void
{
Sys.setCwd(Path.join([CWD, REPO_ROOT]));
if(FileSystem.exists(REPO_DIR)) {
deleteDirectory(REPO_DIR);
}
FileSystem.createDirectory(REPO_DIR);
Sys.setCwd(REPO_DIR);
}
override public function tearDown():Void
{
// restore original CWD & PATH:
Sys.setCwd(CWD);
deleteDirectory(Path.join([CWD, REPO_ROOT, REPO_DIR]));
}
//----------------- tests -------------------//
public function testAvailableHg():Void
{
assertFalse(getHg().available);
}
public function testAvailableGit():Void
{
assertFalse(getGit().available);
}
public function testCloneHg():Void
{
var vcs = getHg();
try
{
vcs.clone(vcs.directory, "https://bitbucket.org/fzzr/hx.signal");
assertFalse(true);
}
catch(error:VcsError)
{
switch(error)
{
case VcsError.CantCloneRepo(_, repo, stderr): assertTrue(true);
default: assertFalse(true);
}
}
}
public function testCloneGit():Void
{
var vcs = getGit();
try
{
vcs.clone(vcs.directory, "https://github.com/fzzr-/hx.signal.git");
assertFalse(true);
}
catch(error:VcsError)
{
switch(error)
{
case VcsError.CantCloneRepo(_, repo, stderr): assertTrue(true);
default: assertFalse(true);
}
}
}
//----------------- tools -------------------//
inline function getHg():Vcs
{
return new WrongHg({quiet: true});
}
inline function getGit():Vcs
{
return new WrongGit({quiet: true});
}
}
class WrongHg extends Mercurial
{
public function new(settings:Settings)
{
super(settings);
this.directory = "no-hg";
this.executable = "no-hg";
this.name = "Mercurial-not-found";
}
// copy of Mercurial.searchExecutablebut have a one change - regexp.
override private function searchExecutable():Void
{
super.searchExecutable();
if(available)
return;
// if we have already msys git/cmd in our PATH
var match = ~/(.*)no-hg-no([\\|\/])cmd$/;
for(path in Sys.getEnv("PATH").split(";"))
{
if(match.match(path.toLowerCase()))
{
var newPath = match.matched(1) + executable + match.matched(2) + "bin";
Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + newPath);
}
}
checkExecutable();
}
}
class WrongGit extends Git
{
public function new(settings:Settings)
{
super(settings);
this.directory = "no-git";
this.executable = "no-git";
this.name = "Git-not-found";
}
// copy of Mercurial.searchExecutablebut have a one change - regexp.
override private function searchExecutable():Void
{
super.searchExecutable();
if(available)
return;
// if we have already msys git/cmd in our PATH
var match = ~/(.*)no-git-no([\\|\/])cmd$/;
for(path in Sys.getEnv("PATH").split(";"))
{
if(match.match(path.toLowerCase()))
{
var newPath = match.matched(1) + executable + match.matched(2) + "bin";
Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + newPath);
}
}
if(checkExecutable())
return;
// look at a few default paths
for(path in ["C:\\Program Files (x86)\\Git\\bin", "C:\\Progra~1\\Git\\bin"])
if(FileSystem.exists(path))
{
Sys.putEnv("PATH", Sys.getEnv("PATH") + ";" + path);
if(checkExecutable())
return;
}
}
}

View File

@@ -0,0 +1,136 @@
package tests.integration;
import haxelib.*;
import IntegrationTests.*;
using IntegrationTests;
import haxe.io.*;
class TestDev extends IntegrationTests {
function testDev():Void {
{
var r = haxelib(["dev", "Bar", "test/libraries/libBar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertTrue(r.out.indexOf("Bar") >= 0);
assertSuccess(r);
}
{
var r = haxelib(["path", "Bar"]).result();
var out = ~/\r?\n/g.split(r.out);
assertEquals(
Path.addTrailingSlash(Path.normalize(sys.FileSystem.fullPath("test/libraries/libBar"))),
Path.addTrailingSlash(Path.normalize(out[0]))
);
if (clientVer > SemVer.ofString("3.1.0-rc.4"))
assertEquals("-D Bar=1.0.0", out[1]);
else
assertEquals("-D Bar", out[1]);
assertSuccess(r);
}
{
var r = haxelib(["remove", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") == -1);
}
}
function testWrongPath():Void {
{
var r = haxelib(["dev", "Bar", "test/libraries/libBar_not_exist"]).result();
// assertTrue(r.code != 0); //TODO
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
if (clientVer > SemVer.ofString("3.1.0-rc.4"))
assertTrue(r.out.indexOf("Bar") == -1);
}
}
function testNoHaxelibJson():Void {
{
var r = haxelib(["dev", "Bar", "bin"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
}
{
var r = haxelib(["path", "Bar"]).result();
var out = ~/\r?\n/g.split(r.out);
assertEquals(
Path.addTrailingSlash(Path.normalize(sys.FileSystem.fullPath("bin"))),
Path.addTrailingSlash(Path.normalize(out[0]))
);
if (clientVer > SemVer.ofString("3.1.0-rc.4"))
assertEquals("-D Bar=0.0.0", out[1]);
else
assertEquals("-D Bar", out[1]);
assertSuccess(r);
}
{
var r = haxelib(["remove", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") == -1);
}
}
function testClassPath():Void {
{
var r = haxelib(["dev", "UseCp", "test/libraries/UseCp"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "UseCp"]).result();
assertTrue(r.out.indexOf("UseCp") >= 0);
assertSuccess(r);
}
{
var r = haxelib(["path", "UseCp"]).result();
var out = ~/\r?\n/g.split(r.out);
assertEquals(
Path.addTrailingSlash(Path.normalize(sys.FileSystem.fullPath("test/libraries/UseCp/lib/src"))),
Path.addTrailingSlash(Path.normalize(out[0]))
);
if (clientVer > SemVer.ofString("3.1.0-rc.4"))
assertEquals("-D UseCp=0.0.1", out[1]);
else
assertEquals("-D UseCp", out[1]);
assertSuccess(r);
}
{
var r = haxelib(["remove", "UseCp"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "UseCp"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("UseCp") == -1);
}
}
}

View File

@@ -0,0 +1,78 @@
package tests.integration;
import haxe.*;
import haxe.io.*;
import haxelib.*;
import IntegrationTests.*;
using IntegrationTests;
class TestEmpty extends IntegrationTests {
function testEmpty():Void {
// the initial local and remote repos are empty
var installResult = haxelib(["install", "foo"]).result();
assertTrue(installResult.code != 0);
var upgradeResult = haxelib(["upgrade"]).result();
assertSuccess(upgradeResult);
var updateResult = haxelib(["update", "foo"]).result();
// assertTrue(updateResult.code != 0);
var removeResult = haxelib(["remove", "foo"]).result();
assertTrue(removeResult.code != 0);
var upgradeResult = haxelib(["list"]).result();
assertSuccess(upgradeResult);
var removeResult = haxelib(["set", "foo", "0.0"], "y\n").result();
assertTrue(removeResult.code != 0);
var searchResult = haxelib(["search", "foo"]).result();
assertSuccess(searchResult);
assertTrue(searchResult.out.indexOf("0") >= 0);
var infoResult = haxelib(["info", "foo"]).result();
assertTrue(infoResult.code != 0);
var userResult = haxelib(["user", "foo"]).result();
assertTrue(userResult.code != 0);
var configResult = haxelib(["config"]).result();
assertSuccess(configResult);
var pathResult = haxelib(["path", "foo"]).result();
assertTrue(pathResult.code != 0);
if (clientVer > SemVer.ofString("3.1.0-rc.4")) {
var versionResult = haxelib(["version"]).result();
assertSuccess(versionResult);
var helpResult = haxelib(["help"]).result();
assertSuccess(helpResult);
}
}
function testWebsite():Void {
// home page
assertNoError(function() Http.requestUrl(serverUrl));
// Haxelib Tags
assertNoError(function() Http.requestUrl(Path.join([serverUrl, "t"])));
// All Haxelibs
assertNoError(function() Http.requestUrl(Path.join([serverUrl, "all"])));
// Haxelib Contributors
assertNoError(function() Http.requestUrl(Path.join([serverUrl, "u"])));
// docs
assertNoError(function() Http.requestUrl(Path.join([serverUrl, "documentation"])));
assertNoError(function() Http.requestUrl(Path.join([serverUrl, "documentation/using-haxelib"])));
assertNoError(function() Http.requestUrl(Path.join([serverUrl, "documentation/creating-a-haxelib-package/"])));
assertNoError(function() Http.requestUrl(Path.join([serverUrl, "documentation/creating-a-haxelib-package/"])));
// RSS feed
assertNoError(function() Http.requestUrl(Path.join([serverUrl, "rss"])));
}
}

View File

@@ -0,0 +1,66 @@
package tests.integration;
import IntegrationTests.*;
using IntegrationTests;
class TestInfo extends IntegrationTests {
function test():Void {
{
var r = haxelib(["register", bar.user, bar.email, bar.fullname, bar.pw, bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libBar.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["info", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
// license
assertTrue(r.out.indexOf("GPL") >= 0);
// tags
assertTrue(r.out.indexOf("bar") >= 0);
assertTrue(r.out.indexOf("test") >= 0);
// versions
assertTrue(r.out.indexOf("1.0.0") >= 0);
}
{
var r = haxelib(["submit", "test/libraries/libBar2.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["info", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
// license
assertTrue(r.out.indexOf("MIT") >= 0);
// tags
assertTrue(r.out.indexOf("bar") >= 0);
assertTrue(r.out.indexOf("test") == -1);
assertTrue(r.out.indexOf("version2") >= 0);
// versions
assertTrue(r.out.indexOf("1.0.0") >= 0);
assertTrue(r.out.indexOf("2.0.0") >= 0);
}
}
function testNotExist():Void {
{
var r = haxelib(["info", "Bar"]).result();
assertTrue(r.code != 0);
}
}
}

View File

@@ -0,0 +1,116 @@
package tests.integration;
import haxelib.*;
import IntegrationTests.*;
using IntegrationTests;
class TestList extends IntegrationTests {
function test():Void {
{
var r = haxelib(["list"]).result();
assertSuccess(r);
}
{
var r = haxelib(["register", bar.user, bar.email, bar.fullname, bar.pw, bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libBar.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["install", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
assertTrue(r.out.indexOf("[1.0.0]") >= 0);
}
{
var r = haxelib(["submit", "test/libraries/libBar2.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["update", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
var pos1 = r.out.indexOf("1.0.0");
var pos2 = r.out.indexOf("2.0.0");
assertTrue(pos1 >= 0);
assertTrue(pos2 >= 0);
if (SemVer.compare(clientVer, SemVer.ofString("3.3.0")) >= 0) {
assertTrue(pos2 >= pos1);
}
}
}
/**
Make sure the version list is in ascending order, independent of the installation order.
**/
function testInstallNewThenOld():Void {
{
var r = haxelib(["register", bar.user, bar.email, bar.fullname, bar.pw, bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libBar.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libBar2.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["install", "Bar", "2.0.0"]).result();
assertSuccess(r);
}
{
var r = haxelib(["install", "Bar", "1.0.0"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
var pos1 = r.out.indexOf("1.0.0");
var pos2 = r.out.indexOf("2.0.0");
assertTrue(pos1 >= 0);
assertTrue(pos2 >= 0);
if (SemVer.compare(clientVer, SemVer.ofString("3.3.0")) >= 0) {
assertTrue(pos2 >= pos1);
}
}
}
}

View File

@@ -0,0 +1,77 @@
package tests.integration;
import IntegrationTests.*;
using IntegrationTests;
using StringTools;
class TestSet extends IntegrationTests {
function test():Void {
{
var r = haxelib(["register", bar.user, bar.email, bar.fullname, bar.pw, bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libBar.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libBar2.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["search", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
}
{
var r = haxelib(["install", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("[2.0.0]") >= 0);
}
{
var r = haxelib(["install", "Bar", "1.0.0"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("[2.0.0]") >= 0);
assertTrue(r.out.indexOf("1.0.0") >= 0);
}
{
var r = haxelib(["set", "Bar", "1.0.0"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("[1.0.0]") >= 0);
assertTrue(r.out.indexOf("2.0.0") >= 0);
}
{
var r = haxelib(["set", "Bar", "2.0.0"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("1.0.0") >= 0);
assertTrue(r.out.indexOf("[2.0.0]") >= 0);
}
}
}

View File

@@ -0,0 +1,91 @@
package tests.integration;
import IntegrationTests.*;
using IntegrationTests;
class TestSimple extends IntegrationTests {
function testNormal():Void {
{
var r = haxelib(["register", bar.user, bar.email, bar.fullname, bar.pw, bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libBar.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["search", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
}
{
var r = haxelib(["register", foo.user, foo.email, foo.fullname, foo.pw, foo.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libFoo.zip", foo.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["search", "Foo"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Foo") >= 0);
}
{
var r = haxelib(["install", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["install", "Foo"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertTrue(r.out.indexOf("Bar") >= 0);
assertSuccess(r);
}
{
var r = haxelib(["list", "Foo"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Foo") >= 0);
}
{
var r = haxelib(["list"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Foo") >= 0);
assertTrue(r.out.indexOf("Bar") >= 0);
}
{
var r = haxelib(["remove", "Foo"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Foo"]).result();
assertTrue(r.out.indexOf("Foo") < 0);
assertSuccess(r);
}
{
var r = haxelib(["remove", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") < 0);
}
}
}

View File

@@ -0,0 +1,69 @@
package tests.integration;
import IntegrationTests.*;
using IntegrationTests;
using StringTools;
class TestUpdate extends IntegrationTests {
function test():Void {
{
var r = haxelib(["register", bar.user, bar.email, bar.fullname, bar.pw, bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["submit", "test/libraries/libBar.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["search", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") >= 0);
}
{
var r = haxelib(["install", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["update", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("[1.0.0]") >= 0);
}
{
var r = haxelib(["submit", "test/libraries/libBar2.zip", bar.pw]).result();
assertSuccess(r);
}
{
var r = haxelib(["update", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("[2.0.0]") >= 0);
assertTrue(r.out.indexOf("1.0.0") >= 0);
}
{
var r = haxelib(["remove", "Bar"]).result();
assertSuccess(r);
}
{
var r = haxelib(["list", "Bar"]).result();
assertSuccess(r);
assertTrue(r.out.indexOf("Bar") < 0);
}
}
}

Some files were not shown because too many files have changed in this diff Show More