Compare commits

...

14 Commits

Author SHA1 Message Date
Lucien Greathouse
4772350968 Version 0.4.1 2018-04-01 23:39:19 -07:00
Lucien Greathouse
eabcc0bd1d Add root gitignore to ignore mkdocs generated site 2018-04-01 23:36:04 -07:00
Lucien Greathouse
3a3af6ab10 Introduce mkdocs documentation 2018-04-01 23:35:18 -07:00
Lucien Greathouse
9723622b66 Bump server to version 0.4.1, update dependencies 2018-04-01 23:30:33 -07:00
Lucien Greathouse
3b1d647acb Bump license year 2018-04-01 23:30:25 -07:00
Lucien Greathouse
6fa925a402 Merge plugin back into main repository (#49) 2018-04-01 23:22:04 -07:00
Lucien Greathouse
c8f837d726 Add dates to each release in CHANGELOG 2018-04-01 21:23:22 -07:00
Lucien Greathouse
4557396564 Improve README in slight ways
Closes #14.
2018-03-27 01:51:15 -07:00
Lucien Greathouse
d3d67d47e1 Add new plugin logo 2018-03-27 01:11:18 -07:00
Lucien Greathouse
42107e0715 Update changes again, one day we'll release 0.4.0 2018-03-27 00:57:20 -07:00
Lucien Greathouse
ed183e0805 Update CHANGES, 0.4.0 2018-03-27 00:55:52 -07:00
Lucien Greathouse
116be16392 Improve error message when a partition target doesn't exist.
Closes #46
2018-03-27 00:50:44 -07:00
Lucien Greathouse
2c188738e6 Document JSON syncing and fix README typos 2018-02-21 23:48:37 -08:00
Lucien Greathouse
ebffba9589 Add bi-directional syncing note from Quenty 2018-02-04 08:55:19 -08:00
62 changed files with 2051 additions and 216 deletions

View File

@@ -4,12 +4,6 @@ root = true
end_of_line = lf
charset = utf-8
[*.rs]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
[*.json]
indent_style = space
indent_size = 2

3
.gitignore vendored
View File

@@ -1,2 +1 @@
/target/
**/*.rs.bk
/site

15
.gitmodules vendored Normal file
View File

@@ -0,0 +1,15 @@
[submodule "plugin/modules/roact"]
path = plugin/modules/roact
url = https://github.com/Roblox/roact.git
[submodule "plugin/modules/rodux"]
path = plugin/modules/rodux
url = https://github.com/Roblox/rodux.git
[submodule "plugin/modules/roact-rodux"]
path = plugin/modules/roact-rodux
url = https://github.com/Roblox/roact-rodux.git
[submodule "plugin/modules/testez"]
path = plugin/modules/testez
url = https://github.com/Roblox/testez.git
[submodule "plugin/modules/lemur"]
path = plugin/modules/lemur
url = https://github.com/LPGhatguy/lemur.git

View File

@@ -1,5 +1,39 @@
language: rust
matrix:
include:
- language: python
env:
- LUA="lua=5.1"
rust:
- stable
- beta
before_install:
- pip install hererocks
- hererocks lua_install -r^ --$LUA
- export PATH=$PATH:$PWD/lua_install/bin
install:
- luarocks install luafilesystem
- luarocks install busted
- luarocks install luacov
- luarocks install luacov-coveralls
- luarocks install luacheck
script:
- cd plugin
- luacheck src
- lua -lluacov spec.lua
after_success:
- cd plugin
- luacov-coveralls -e $TRAVIS_BUILD_DIR/lua_install
- language: rust
rust: stable
script:
- cd server
- cargo test --verbose
- language: rust
rust: beta
script:
- cd server
- cargo test --verbose

View File

@@ -1,41 +1,50 @@
# Rojo Change Log
## Current Master (0.4.0)
* Began protocol version 1, which shifts more responsibility onto the server
## Current Master
*No changes*
## 0.4.1 (April 1, 2018)
* Merged plugin repository into main Rojo repository for easier tracking.
* Improved `RouteMap` object tracking; this should fix some cases of duplicated instances being synced into the tree.
## 0.4.0 (March 27, 2018)
* Protocol version 1, which shifts more responsibility onto the server
* This is a **major breaking** change!
* The server now has a content of 'filter plugins', which transform data at various stages in the pipeline
* The server now exposes Roblox instance objects instead of file contents, which lines up with how `rojo pack` will work, and paves the way for more robust syncing.
* Added `*.model.json` files, which let you embed small Roblox objects into your Rojo tree.
* Improved error messages in some cases ([#46](https://github.com/LPGhatguy/rojo/issues/46))
## 0.3.2
## 0.3.2 (December 20, 2017)
* Fixed `rojo serve` failing to correctly construct an absolute root path when passed as an argument
* Fixed intense CPU usage when running `rojo serve`
## 0.3.1
## 0.3.1 (December 14, 2017)
* Improved error reporting when invalid JSON is found in a `rojo.json` project
* These messages are passed on from Serde
## 0.3.0
## 0.3.0 (December 12, 2017)
* Factored out the plugin into a separate repository
* Fixed server when using a file as a partition
* Previously, trailing slashes were put on the end of a partition even if the read request was an empty string. This broke file reading on Windows when a partition pointed to a file instead of a directory!
* Started running automatic tests on Travis CI (#9)
## 0.2.3
## 0.2.3 (December 4, 2017)
* Plugin only release
* Tightened `init` file rules to only match script files
* Previously, Rojo would sometimes pick up the wrong file when syncing
## 0.2.2
## 0.2.2 (December 1, 2017)
* Plugin only release
* Fixed broken reconciliation behavior with `init` files
## 0.2.1
## 0.2.1 (December 1, 2017)
* Plugin only release
* Changes default port to 8000
## 0.2.0
## 0.2.0 (December 1, 2017)
* Support for `init.lua` like rbxfs and rbxpacker
* More robust syncing with a new reconciler
## 0.1.0
## 0.1.0 (November 29, 2017)
* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs)

View File

@@ -36,4 +36,14 @@ The plan is to have several built-in plugins that can be rearranged/configured i
* User passes a binary name (like `moonc`) that modifies file contents
## Roblox Studio Plugin
With the protocol version 1 change, the Roblox Studio plugin got a lot simpler. Notably, the plugin doesn't need to be aware of anything about the filesystem's semantics, which is super handy.
With the protocol version 1 change, the Roblox Studio plugin got a lot simpler. Notably, the plugin doesn't need to be aware of anything about the filesystem's semantics, which is super handy.
## Bi-directional syncing
Quenty laid out a good way to handle bi-directional syncing.
When receiving a change from the plugin:
1. Hash the new contents of the file, store it in a map from routes to hashes
2. Write the new file contents to the filesystem
3. Later down the line, receive a change event from the filesystem watcher
4. When receiving a change, if the item is in the hash map, read it and hash those contents
5. If the hash matches the last noted hash, discard the change, else continue as normal

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017 Lucien Greathouse
Copyright (c) 2018 Lucien Greathouse
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:

121
README.md
View File

@@ -1,5 +1,5 @@
<div align="center">
<img src="assets/rojo-logo.png" alt="Rojo" height="150" />
<img src="assets/rojo-logo.png" alt="Rojo" height="217" />
</div>
<div>&nbsp;</div>
@@ -8,47 +8,52 @@
<a href="https://travis-ci.org/LPGhatguy/rojo">
<img src="https://api.travis-ci.org/LPGhatguy/rojo.svg?branch=master" alt="Travis-CI Build Status" />
</a>
<img src="https://img.shields.io/badge/latest_version-0.4.1-brightgreen.svg" alt="Current server version" />
</div>
<hr />
**Rojo** is a flexible multi-tool designed for creating robust Roblox projects. It's in early development, but is still useful for many projects.
**Rojo** is a flexible multi-tool designed for creating robust Roblox projects.
It's designed for power users who want to use the **best tools available** for building games, libraries, and plugins.
This is the main Rojo repository, containing the binary and project server component. For the source for the Roblox plugin, [see the rojo-plugin repository](https://github.com/LPGhatguy/rojo-plugin).
The master branches of both respositories should always pass all tests and be functional, but are not suitable for production use!
## Features
Rojo has a number of desirable features *right now*:
Rojo lets you:
* Work on scripts from the filesystem, in your favorite editor
* Version your place, library, or plugin using Git or another VCS
* Sync JSON-format models from the filesystem into your game
Soon, Rojo will be able to:
Later this year, Rojo will be able to:
* Sync Roblox objects (including models) bi-directionally between the filesystem and Roblox Studio
* Create installation scripts for libraries to be used in standalone places
* Similar to [rbxpacker](https://github.com/LPGhatguy/rbxpacker), another one of my projects
* Add strongly-versioned dependencies to your project
* Sync `rbxmx` models between the filesystem and Roblox Studio
* Package projects into `rbxmx` files from the command line
## Installation
Rojo has two components:
* The command line tool, written in Rust
* The [Roblox Studio plugin](https://www.roblox.com/library/1211549683/Rojo), written in Lua
To install the command line tool, there are two options:
* Cargo, if you have Rust installed
* Use `cargo install rojo` -- Rojo will be available with the `rojo` command
* Download a pre-built Windows binary from [the GitHub releases page](https://github.com/LPGhatguy/rojo/releases)
* The server, a binary written in Rust
* The plugin, a Roblox Studio plugin written in Lua
## Usage
It's important that the plugin and server are compatible. The plugin will show errors in the Roblox Studio Output window if there is a version mismatch.
To install the server, either:
* If you have Rust installed, use `cargo install rojo`
* Or, download a pre-built Windows binary from [the GitHub releases page](https://github.com/LPGhatguy/rojo/releases)
To install the plugin, either:
* Install the plugin from the [Roblox plugin page](https://www.roblox.com/library/1211549683/Rojo).
* This gives you less control over what version you install -- you will always have the latest version.
* Or, download the latest release from [the GitHub releases section](https://github.com/LPGhatguy/rojo/releases) and install it into your Roblox plugins folder
* You can open this folder by clicking the "Plugins Folder" button from the Plugins toolbar in Roblox Studio
## Server Usage
For more help, use `rojo help`.
### New Project
Just create a new folder and tell Rojo to initialize it!
Create a new folder, then use `rojo init` inside that folder to initialize an empty project:
```sh
mkdir my-new-project
@@ -57,9 +62,9 @@ cd my-new-project
rojo init
```
Rojo will create an empty project in the directory.
Rojo will create an empty project file named `rojo.json` in the directory.
The default project looks like this:
The default project file is:
```json
{
@@ -70,7 +75,7 @@ The default project looks like this:
```
### Start Dev Server
To create a server that allows the Rojo Dev Plugin to access your project, use:
To start the Rojo dev server, use:
```sh
rojo serve
@@ -79,12 +84,14 @@ rojo serve
The tool will tell you whether it found an existing project. You should then be able to connect and use the project from within Roblox Studio!
### Migrating an Existing Roblox Project
**Coming soon!**
[Bi-directional script syncing](https://github.com/LPGhatguy/rojo/issues/5) is on the roadmap for Rojo this year, but isn't implemented.
In the mean-time, manually migrating scripts is probably the best route forward.
### Syncing into Roblox
In order to sync code into Roblox, you'll need to add one or more "partitions" to your configuration. A partition tells Rojo how to map directories to Roblox objects.
In order to sync code into Roblox, you'll need to add one or more *partitions* to your configuration. A partition tells Rojo how to map directories on your filesystem to Roblox objects.
Each entry in the partitions table has a unique name, a filesystem path, and the full name of the Roblox object to sync into.
Each entry in the `partitions` map has a unique name, a filesystem path, and the full name of the Roblox object to sync into.
For example, if you want to map your `src` directory to an object named `My Cool Game` in `ReplicatedStorage`, you could use this configuration:
@@ -103,12 +110,14 @@ For example, if you want to map your `src` directory to an object named `My Cool
The `path` parameter is relative to the project file.
The `target` starts at `game` and crawls down the tree. If any objects don't exist along the way, they'll be created as `Folder` instances.
The `target` parameter is a path to a Roblox object to link the partition to, starting at `game`. If any objects don't exist along the way, Rojo will try to create them.
Run `rojo serve` in the directory containing this project, then press the "Sync In" or "Toggle Polling" buttons in the Roblox Studio plugin to move code into your game.
**Any objects in a partition may be wiped away by Rojo after syncing! If this is not desired, use multiple, smaller partitions.**
Run `rojo serve` in the directory containing this project, then press the "Sync In" or "Toggle Polling" buttons in the Roblox Studio plugin to sync into your game.
### Sync Details
The structure of files and folders on the filesystem are preserved when syncing into game.
The structure of files and diectories on the filesystem are preserved when syncing into game.
Creation of Roblox instances follows a simple set of rules. The first rule that matches the file name is chosen:
@@ -117,9 +126,16 @@ Creation of Roblox instances follows a simple set of rules. The first rule that
| `*.server.lua` | `Script` | `Source` will contain the file's contents |
| `*.client.lua` | `LocalScript` | `Source` will contain the file's contents |
| `*.lua` | `ModuleScript` | `Source` will contain the file's contents |
| `*.model.json` | *Varies* | See the example below |
| `*` | `StringValue` | `Value` will contain the file's contents |
Any folders on the filesystem will turn into `Folder` objects unless they contain a file named `init.lua`, `init.server.lua`, or `init.client.lua`. Following the convention of Lua, those objects will instead be whatever the `init` file would turn into.
Any directories on the filesystem will turn into `Folder` objects.
Any directory containing one of these files will instead be a `ModuleScript`, `Script`, `LocalScript` containing the directory's contents:
* `init.lua`
* `init.server.lua`
* `init.client.lua`
For example, this file tree:
@@ -127,15 +143,49 @@ For example, this file tree:
* init.client.lua
* foo.lua
Will turn into this tree in Roblox:
Will turn into these instances in Roblox:
* `my-game` (`LocalScript` with source from `my-game/init.client.lua`)
* `foo` (`ModuleScript` with source from `my-game/foo.lua`)
`*.model.json` files are a way to represent simple Roblox instances on the filesystem until `rbxmx` and `rbxlx` support is implemented in Rojo.
This feature is intended for small instances, like `RemoteEvent` or `*Value` objects.
JSON Model files are strict, with every property being required. They look like this:
```json
{
"Name": "hello",
"ClassName": "Model",
"Children": [
{
"Name": "Some Part",
"ClassName": "Part",
"Children": [],
"Properties": {}
},
{
"Name": "Some StringValue",
"ClassName": "StringValue",
"Children": [],
"Properties": {
"Value": {
"Type": "String",
"Value": "Hello, world!"
}
}
}
],
"Properties": {}
}
```
## Inspiration
There are lots of other tools that sync scripts into Roblox, or otherwise work to improve the development flow outside of Roblox Studio.
There are lots of other tools that sync scripts into Roblox or provide other tools for working with Roblox places.
Here are a few, if you're looking for alternatives or supplements to Rojo:
* [Studio Bridge by Vocksel](https://github.com/vocksel/studio-bridge)
* [RbxRefresh by Osyris](https://github.com/osyrisrblx/RbxRefresh)
* [RbxSync by evaera](https://github.com/evaera/RbxSync)
@@ -143,15 +193,14 @@ Here are a few, if you're looking for alternatives or supplements to Rojo:
* [rbxmk by Anaminus](https://github.com/anaminus/rbxmk)
I also have a couple tools that Rojo intends to replace:
* [rbxfs](https://github.com/LPGhatguy/rbxfs), which has been deprecated by Rojo
* [rbxpacker](https://github.com/LPGhatguy/rbxpacker), which is still useful
## Contributing
Pull requests are welcome!
The `master` branch of both repositories have tests running on Travis for every commit and pull request. The test suite on `master` should always pass!
The Rojo and Rojo Plugin repositories should stay in sync with eachother, so that the current `master` of each repository can be used together.
All pull requests are run against a test suite on Travis CI. That test suite should always pass!
## License
Rojo is available under the terms of the MIT license. See [LICENSE.md](LICENSE.md) for details.

BIN
assets/rojo-plugin-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

6
docs/index.md Normal file
View File

@@ -0,0 +1,6 @@
# Home
This is the documentation home for Rojo.
**Rojo** is a flexible multi-tool designed for creating robust Roblox projects.
This documentation is a work in progress, and is incomplete.

3
docs/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
mkdocs
mkdocs-material
pymdown-extensions

20
mkdocs.yml Normal file
View File

@@ -0,0 +1,20 @@
site_name: Rojo Documentation
site_url: https://lpghatguy.github.io/rojo/
repo_name: LPGhatguy/rojo
repo_url: https://github.com/LPGhatguy/rojo
theme:
name: material
palette:
primary: 'Red'
accent: 'Red'
pages:
- Home: index.md
markdown_extensions:
- admonition
- codehilite:
guess_lang: false
- toc:
permalink: true

4
plugin/.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*.lua]
indent_style = tab
trim_trailing_whitespace = true
insert_final_newline = true

1
plugin/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/luacov.*

56
plugin/.luacheckrc Normal file
View File

@@ -0,0 +1,56 @@
stds.roblox = {
read_globals = {
game = {
other_fields = true,
},
-- Roblox globals
"script",
-- Extra functions
"tick", "warn", "spawn",
"wait", "settings", "typeof",
-- Types
"Vector2", "Vector3",
"Color3",
"UDim", "UDim2",
"Rect",
"CFrame",
"Enum",
"Instance",
}
}
stds.plugin = {
read_globals = {
"plugin",
}
}
stds.testez = {
read_globals = {
"describe",
"it", "itFOCUS", "itSKIP",
"FOCUS", "SKIP", "HACK_NO_XPCALL",
"expect",
}
}
ignore = {
"212", -- unused arguments
"421", -- shadowing local variable
"422", -- shadowing argument
"431", -- shadowing upvalue
"432", -- shadowing upvalue argument
}
std = "lua51+roblox"
files["**/*.server.lua"] = {
std = "+plugin",
}
files["**/*.spec.lua"] = {
std = "+testez",
}

8
plugin/.luacov Normal file
View File

@@ -0,0 +1,8 @@
return {
include = {
"^src",
},
exclude = {
"%.spec$",
},
}

1
plugin/modules/lemur Submodule

Submodule plugin/modules/lemur added at 852c71b897

1
plugin/modules/roact Submodule

Submodule plugin/modules/roact added at bbb0663161

1
plugin/modules/rodux Submodule

Submodule plugin/modules/rodux added at b8ba486335

1
plugin/modules/testez Submodule

Submodule plugin/modules/testez added at 442b71926d

30
plugin/rojo.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "rojo",
"servePort": 8000,
"partitions": {
"plugin": {
"path": "src",
"target": "ReplicatedStorage.Rojo.plugin"
},
"modules/roact": {
"path": "modules/roact/lib",
"target": "ReplicatedStorage.Rojo.modules.Roact"
},
"modules/rodux": {
"path": "modules/rodux/lib",
"target": "ReplicatedStorage.Rojo.modules.Rodux"
},
"modules/roact-rodux": {
"path": "modules/roact-rodux/lib",
"target": "ReplicatedStorage.Rojo.modules.RoactRodux"
},
"modules/testez": {
"path": "modules/testez/lib",
"target": "ReplicatedStorage.TestEZ"
},
"tests": {
"path": "tests",
"target": "TestService"
}
}
}

69
plugin/spec.lua Normal file
View File

@@ -0,0 +1,69 @@
--[[
Loads our library and all of its dependencies, then runs tests using TestEZ.
]]
-- If you add any dependencies, add them to this table so they'll be loaded!
local LOAD_MODULES = {
{"src", "Plugin"},
{"modules/testez/lib", "TestEZ"},
}
-- This makes sure we can load Lemur and other libraries that depend on init.lua
package.path = package.path .. ";?/init.lua"
-- If this fails, make sure you've run `lua bin/install-dependencies.lua` first!
local lemur = require("modules.lemur")
--[[
Collapses ModuleScripts named 'init' into their parent folders.
This is the same result as the collapsing mechanism from Rojo.
]]
local function collapse(root)
local init = root:FindFirstChild("init")
if init then
init.Name = root.Name
init.Parent = root.Parent
for _, child in ipairs(root:GetChildren()) do
child.Parent = init
end
root:Destroy()
root = init
end
for _, child in ipairs(root:GetChildren()) do
if child:IsA("Folder") then
collapse(child)
end
end
return root
end
-- Create a virtual Roblox tree
local habitat = lemur.Habitat.new()
-- We'll put all of our library code and dependencies here
local Root = lemur.Instance.new("Folder")
Root.Name = "Root"
-- Load all of the modules specified above
for _, module in ipairs(LOAD_MODULES) do
local container = lemur.Instance.new("Folder", Root)
container.Name = module[2]
habitat:loadFromFs(module[1], container)
end
collapse(Root)
-- Load TestEZ and run our tests
local TestEZ = habitat:require(Root.TestEZ)
local results = TestEZ.TestBootstrap:run(Root.Plugin, TestEZ.Reporters.TextReporter)
-- Did something go wrong?
if results.failureCount > 0 then
os.exit(1)
end

7
plugin/src/Config.lua Normal file
View File

@@ -0,0 +1,7 @@
return {
pollingRate = 0.2,
version = {0, 4, 1},
expectedServerVersionString = "0.4.x",
protocolVersion = 1,
dev = false,
}

View File

@@ -0,0 +1,7 @@
return function()
local Config = require(script.Parent.Config)
it("should have 'dev' disabled", function()
expect(Config.dev).to.equal(false)
end)
end

67
plugin/src/Http.lua Normal file
View File

@@ -0,0 +1,67 @@
local HttpService = game:GetService("HttpService")
local HTTP_DEBUG = false
local Promise = require(script.Parent.Promise)
local HttpError = require(script.Parent.HttpError)
local HttpResponse = require(script.Parent.HttpResponse)
local function dprint(...)
if HTTP_DEBUG then
print(...)
end
end
local Http = {}
Http.__index = Http
function Http.new(baseUrl)
assert(type(baseUrl) == "string", "Http.new needs a baseUrl!")
local http = {
baseUrl = baseUrl
}
setmetatable(http, Http)
return http
end
function Http:get(endpoint)
dprint("\nGET", endpoint)
return Promise.new(function(resolve, reject)
spawn(function()
local ok, result = pcall(function()
return HttpService:GetAsync(self.baseUrl .. endpoint, true)
end)
if ok then
dprint("\t", result, "\n")
resolve(HttpResponse.new(result))
else
reject(HttpError.fromErrorString(result))
end
end)
end)
end
function Http:post(endpoint, body)
dprint("\nPOST", endpoint)
dprint(body)
return Promise.new(function(resolve, reject)
spawn(function()
local ok, result = pcall(function()
return HttpService:PostAsync(self.baseUrl .. endpoint, body)
end)
if ok then
dprint("\t", result, "\n")
resolve(HttpResponse.new(result))
else
reject(HttpError.fromErrorString(result))
end
end)
end)
end
return Http

57
plugin/src/HttpError.lua Normal file
View File

@@ -0,0 +1,57 @@
local HttpError = {}
HttpError.__index = HttpError
HttpError.Error = {
HttpNotEnabled = {
message = "Rojo requires HTTP access, which is not enabled.\n" ..
"Check your game settings, located in the 'Home' tab of Studio.",
},
ConnectFailed = {
message = "Rojo plugin couldn't connect to the Rojo server.\n" ..
"Make sure the server is running -- use 'Rojo serve' to run it!",
},
Unknown = {
message = "Rojo encountered an unknown error: {{message}}",
},
}
function HttpError.new(type, extraMessage)
extraMessage = extraMessage or ""
local message = type.message:gsub("{{message}}", extraMessage)
local err = {
type = type,
message = message,
}
setmetatable(err, HttpError)
return err
end
function HttpError:__tostring()
return self.message
end
--[[
This method shouldn't have to exist. Ugh.
]]
function HttpError.fromErrorString(err)
err = err:lower()
if err:find("^http requests are not enabled") then
return HttpError.new(HttpError.Error.HttpNotEnabled)
end
if err:find("^curl error") then
return HttpError.new(HttpError.Error.ConnectFailed)
end
return HttpError.new(HttpError.Error.Unknown, err)
end
function HttpError:report()
warn(self.message)
end
return HttpError

View File

@@ -0,0 +1,20 @@
local HttpService = game:GetService("HttpService")
local HttpResponse = {}
HttpResponse.__index = HttpResponse
function HttpResponse.new(body)
local response = {
body = body,
}
setmetatable(response, HttpResponse)
return response
end
function HttpResponse:json()
return HttpService:JSONDecode(self.body)
end
return HttpResponse

View File

@@ -0,0 +1,79 @@
if not plugin then
return
end
local Plugin = require(script.Parent.Plugin)
local Config = require(script.Parent.Config)
local Version = require(script.Parent.Version)
--[[
Check if the user is using a newer version of Rojo than last time. If they
are, show them a reminder to make sure they check their server version.
]]
local function checkUpgrade()
local lastVersion = plugin:GetSetting("LastRojoVersion")
if lastVersion then
local wasUpgraded = Version.compare(Config.version, lastVersion) == 1
if wasUpgraded then
local message = (
"\nRojo detected an upgrade from version %s to version %s." ..
"\nMake sure you have also upgraded your server!" ..
"\n\nRojo version %s is intended for use with server version %s.\n"
):format(
Version.display(lastVersion), Version.display(Config.version),
Version.display(Config.version), Config.expectedServerVersionString
)
print(message)
end
end
-- When developing Rojo, there's no use in storing that version number.
if not Config.dev then
plugin:SetSetting("LastRojoVersion", Config.version)
end
end
local function main()
local pluginInstance = Plugin.new()
local displayedVersion = Config.dev and "DEV" or Version.display(Config.version)
local toolbar = plugin:CreateToolbar("Rojo Plugin " .. displayedVersion)
toolbar:CreateButton("Test Connection", "Connect to Rojo Server", "")
.Click:Connect(function()
checkUpgrade()
pluginInstance:connect()
:catch(function(err)
warn(err)
end)
end)
toolbar:CreateButton("Sync In", "Sync into Roblox Studio", "")
.Click:Connect(function()
checkUpgrade()
pluginInstance:syncIn()
:catch(function(err)
warn(err)
end)
end)
toolbar:CreateButton("Toggle Polling", "Poll server for changes", "")
.Click:Connect(function()
checkUpgrade()
spawn(function()
pluginInstance:togglePolling()
:catch(function(err)
warn(err)
end)
end)
end)
end
main()

198
plugin/src/Plugin.lua Normal file
View File

@@ -0,0 +1,198 @@
local Config = require(script.Parent.Config)
local Http = require(script.Parent.Http)
local Server = require(script.Parent.Server)
local Promise = require(script.Parent.Promise)
local Reconciler = require(script.Parent.Reconciler)
local function collectMatch(source, pattern)
local result = {}
for match in source:gmatch(pattern) do
table.insert(result, match)
end
return result
end
local Plugin = {}
Plugin.__index = Plugin
function Plugin.new()
local address = "localhost"
local port = Config.dev and 8001 or 8000
local remote = ("http://%s:%d"):format(address, port)
local self = {
_http = Http.new(remote),
_reconciler = Reconciler.new(),
_server = nil,
_polling = false,
}
setmetatable(self, Plugin)
do
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "Rojo UI"
screenGui.Parent = game.CoreGui
screenGui.DisplayOrder = -1
screenGui.Enabled = false
local label = Instance.new("TextLabel")
label.Font = Enum.Font.SourceSans
label.TextSize = 20
label.Text = "Rojo polling..."
label.BackgroundColor3 = Color3.fromRGB(31, 31, 31)
label.BackgroundTransparency = 0.5
label.BorderSizePixel = 0
label.TextColor3 = Color3.new(1, 1, 1)
label.Size = UDim2.new(0, 120, 0, 28)
label.Position = UDim2.new(0, 0, 0, 0)
label.Parent = screenGui
self._label = screenGui
end
return self
end
function Plugin:server()
if not self._server then
self._server = Server.connect(self._http)
:catch(function(err)
self._server = nil
return Promise.reject(err)
end)
end
return self._server
end
function Plugin:connect()
print("Testing connection...")
return self:server()
:andThen(function(server)
return server:getInfo()
end)
:andThen(function(result)
print("Server found!")
print("Protocol version:", result.protocolVersion)
print("Server version:", result.serverVersion)
end)
end
function Plugin:togglePolling()
if self._polling then
self:stopPolling()
return Promise.resolve(nil)
else
return self:startPolling()
end
end
function Plugin:stopPolling()
if not self._polling then
return
end
print("Stopped polling.")
self._polling = false
self._label.Enabled = false
end
function Plugin:_pull(server, project, routes)
local items = server:read(routes):await()
for index = 1, #routes do
local itemRoute = routes[index]
local partitionName = itemRoute[1]
local partition = project.partitions[partitionName]
local item = items[index]
local partitionRoute = collectMatch(partition.target, "[^.]+")
-- If the item route's length was 1, we need to rename the instance to
-- line up with the partition's root object name.
--
-- This is a HACK!
if #itemRoute == 1 then
if item then
local objectName = partition.target:match("[^.]+$")
item.Name = objectName
end
end
local fullRoute = {}
for _, piece in ipairs(partitionRoute) do
table.insert(fullRoute, piece)
end
for i = 2, #itemRoute do
table.insert(fullRoute, itemRoute[i])
end
self._reconciler:reconcileRoute(fullRoute, item, itemRoute)
end
end
function Plugin:startPolling()
if self._polling then
return
end
print("Starting to poll...")
self._polling = true
self._label.Enabled = true
return self:server()
:andThen(function(server)
self:syncIn():await()
local project = server:getInfo():await().project
while self._polling do
local changes = server:getChanges():await()
if #changes > 0 then
local routes = {}
for _, change in ipairs(changes) do
table.insert(routes, change.route)
end
self:_pull(server, project, routes)
end
wait(Config.pollingRate)
end
end)
:catch(function()
self:stopPolling()
end)
end
function Plugin:syncIn()
print("Syncing from server...")
return self:server()
:andThen(function(server)
local project = server:getInfo():await().project
local routes = {}
for name in pairs(project.partitions) do
table.insert(routes, {name})
end
self:_pull(server, project, routes)
print("Sync successful!")
end)
end
return Plugin

311
plugin/src/Promise.lua Normal file
View File

@@ -0,0 +1,311 @@
--[[
An implementation of Promises similar to Promise/A+.
]]
local PROMISE_DEBUG = false
-- If promise debugging is on, use a version of pcall that warns on failure.
-- This is useful for finding errors that happen within Promise itself.
local wpcall
if PROMISE_DEBUG then
wpcall = function(f, ...)
local result = { pcall(f, ...) }
if not result[1] then
warn(result[2])
end
return unpack(result)
end
else
wpcall = pcall
end
--[[
Creates a function that invokes a callback with correct error handling and
resolution mechanisms.
]]
local function createAdvancer(callback, resolve, reject)
return function(...)
local result = { wpcall(callback, ...) }
local ok = table.remove(result, 1)
if ok then
resolve(unpack(result))
else
reject(unpack(result))
end
end
end
local function isEmpty(t)
return next(t) == nil
end
local Promise = {}
Promise.__index = Promise
Promise.Status = {
Started = "Started",
Resolved = "Resolved",
Rejected = "Rejected",
}
--[[
Constructs a new Promise with the given initializing callback.
This is generally only called when directly wrapping a non-promise API into
a promise-based version.
The callback will receive 'resolve' and 'reject' methods, used to start
invoking the promise chain.
For example:
local function get(url)
return Promise.new(function(resolve, reject)
spawn(function()
resolve(HttpService:GetAsync(url))
end)
end)
end
get("https://google.com")
:andThen(function(stuff)
print("Got some stuff!", stuff)
end)
]]
function Promise.new(callback)
local promise = {
-- Used to locate where a promise was created
_source = debug.traceback(),
-- A tag to identify us as a promise
_type = "Promise",
_status = Promise.Status.Started,
-- A table containing a list of all results, whether success or failure.
-- Only valid if _status is set to something besides Started
_value = nil,
-- If an error occurs with no observers, this will be set.
_unhandledRejection = false,
-- Queues representing functions we should invoke when we update!
_queuedResolve = {},
_queuedReject = {},
}
setmetatable(promise, Promise)
local function resolve(...)
promise:_resolve(...)
end
local function reject(...)
promise:_reject(...)
end
local ok, err = wpcall(callback, resolve, reject)
if not ok and promise._status == Promise.Status.Started then
reject(err)
end
return promise
end
--[[
Create a promise that represents the immediately resolved value.
]]
function Promise.resolve(value)
return Promise.new(function(resolve)
resolve(value)
end)
end
--[[
Create a promise that represents the immediately rejected value.
]]
function Promise.reject(value)
return Promise.new(function(_, reject)
reject(value)
end)
end
--[[
Returns a new promise that:
* is resolved when all input promises resolve
* is rejected if ANY input promises reject
]]
function Promise.all(...)
error("unimplemented", 2)
end
--[[
Is the given object a Promise instance?
]]
function Promise.is(object)
if type(object) ~= "table" then
return false
end
return object._type == "Promise"
end
--[[
Creates a new promise that receives the result of this promise.
The given callbacks are invoked depending on that result.
]]
function Promise:andThen(successHandler, failureHandler)
self._unhandledRejection = false
-- Create a new promise to follow this part of the chain
return Promise.new(function(resolve, reject)
-- Our default callbacks just pass values onto the next promise.
-- This lets success and failure cascade correctly!
local successCallback = resolve
if successHandler then
successCallback = createAdvancer(successHandler, resolve, reject)
end
local failureCallback = reject
if failureHandler then
failureCallback = createAdvancer(failureHandler, resolve, reject)
end
if self._status == Promise.Status.Started then
-- If we haven't resolved yet, put ourselves into the queue
table.insert(self._queuedResolve, successCallback)
table.insert(self._queuedReject, failureCallback)
elseif self._status == Promise.Status.Resolved then
-- This promise has already resolved! Trigger success immediately.
successCallback(unpack(self._value))
elseif self._status == Promise.Status.Rejected then
-- This promise died a terrible death! Trigger failure immediately.
failureCallback(unpack(self._value))
end
end)
end
--[[
Used to catch any errors that may have occurred in the promise.
]]
function Promise:catch(failureCallback)
return self:andThen(nil, failureCallback)
end
--[[
Yield until the promise is completed.
This matches the execution model of normal Roblox functions.
]]
function Promise:await()
self._unhandledRejection = false
if self._status == Promise.Status.Started then
local result
local bindable = Instance.new("BindableEvent")
self:andThen(function(...)
result = {...}
bindable:Fire(true)
end, function(...)
result = {...}
bindable:Fire(false)
end)
local ok = bindable.Event:Wait()
bindable:Destroy()
if not ok then
error(tostring(result[1]), 2)
end
return unpack(result)
elseif self._status == Promise.Status.Resolved then
return unpack(self._value)
elseif self._status == Promise.Status.Rejected then
error(tostring(self._value[1]), 2)
end
end
function Promise:_resolve(...)
if self._status ~= Promise.Status.Started then
return
end
-- If the resolved value was a Promise, we chain onto it!
if Promise.is((...)) then
-- Without this warning, arguments sometimes mysteriously disappear
if select("#", ...) > 1 then
local message = (
"When returning a Promise from andThen, extra arguments are " ..
"discarded! See:\n\n%s"
):format(
self._source
)
warn(message)
end
(...):andThen(function(...)
self:_resolve(...)
end, function(...)
self:_reject(...)
end)
return
end
self._status = Promise.Status.Resolved
self._value = {...}
-- We assume that these callbacks will not throw errors.
for _, callback in ipairs(self._queuedResolve) do
callback(...)
end
end
function Promise:_reject(...)
if self._status ~= Promise.Status.Started then
return
end
self._status = Promise.Status.Rejected
self._value = {...}
-- If there are any rejection handlers, call those!
if not isEmpty(self._queuedReject) then
-- We assume that these callbacks will not throw errors.
for _, callback in ipairs(self._queuedReject) do
callback(...)
end
else
-- At this point, no one was able to observe the error.
-- An error handler might still be attached if the error occurred
-- synchronously. We'll wait one tick, and if there are still no
-- observers, then we should put a message in the console.
self._unhandledRejection = true
local err = tostring((...))
spawn(function()
-- Someone observed the error, hooray!
if not self._unhandledRejection then
return
end
-- Build a reasonable message
local message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
err,
self._source
)
warn(message)
end)
end
end
return Promise

262
plugin/src/Promise.spec.lua Normal file
View File

@@ -0,0 +1,262 @@
return function()
local Promise = require(script.Parent.Promise)
describe("Promise.new", function()
it("should instantiate with a callback", function()
local promise = Promise.new(function() end)
expect(promise).to.be.ok()
end)
it("should invoke the given callback with resolve and reject", function()
local callCount = 0
local resolveArg
local rejectArg
local promise = Promise.new(function(resolve, reject)
callCount = callCount + 1
resolveArg = resolve
rejectArg = reject
end)
expect(promise).to.be.ok()
expect(callCount).to.equal(1)
expect(resolveArg).to.be.a("function")
expect(rejectArg).to.be.a("function")
expect(promise._status).to.equal(Promise.Status.Started)
end)
it("should resolve promises on resolve()", function()
local callCount = 0
local promise = Promise.new(function(resolve)
callCount = callCount + 1
resolve()
end)
expect(promise).to.be.ok()
expect(callCount).to.equal(1)
expect(promise._status).to.equal(Promise.Status.Resolved)
end)
it("should reject promises on reject()", function()
local callCount = 0
local promise = Promise.new(function(resolve, reject)
callCount = callCount + 1
reject()
end)
expect(promise).to.be.ok()
expect(callCount).to.equal(1)
expect(promise._status).to.equal(Promise.Status.Rejected)
end)
it("should reject on error in callback", function()
local callCount = 0
local promise = Promise.new(function()
callCount = callCount + 1
error("hahah")
end)
expect(promise).to.be.ok()
expect(callCount).to.equal(1)
expect(promise._status).to.equal(Promise.Status.Rejected)
expect(promise._value[1]:find("hahah")).to.be.ok()
end)
end)
describe("Promise.resolve", function()
it("should immediately resolve with a value", function()
local promise = Promise.resolve(5)
expect(promise).to.be.ok()
expect(promise._status).to.equal(Promise.Status.Resolved)
expect(promise._value[1]).to.equal(5)
end)
it("should chain onto passed promises", function()
local promise = Promise.resolve(Promise.new(function(_, reject)
reject(7)
end))
expect(promise).to.be.ok()
expect(promise._status).to.equal(Promise.Status.Rejected)
expect(promise._value[1]).to.equal(7)
end)
end)
describe("Promise.reject", function()
it("should immediately reject with a value", function()
local promise = Promise.reject(6)
expect(promise).to.be.ok()
expect(promise._status).to.equal(Promise.Status.Rejected)
expect(promise._value[1]).to.equal(6)
end)
it("should pass a promise as-is as an error", function()
local innerPromise = Promise.new(function(resolve)
resolve(6)
end)
local promise = Promise.reject(innerPromise)
expect(promise).to.be.ok()
expect(promise._status).to.equal(Promise.Status.Rejected)
expect(promise._value[1]).to.equal(innerPromise)
end)
end)
describe("Promise:andThen", function()
it("should chain onto resolved promises", function()
local args
local argsLength
local callCount = 0
local badCallCount = 0
local promise = Promise.resolve(5)
local chained = promise
:andThen(function(...)
args = {...}
argsLength = select("#", ...)
callCount = callCount + 1
end, function()
badCallCount = badCallCount + 1
end)
expect(badCallCount).to.equal(0)
expect(callCount).to.equal(1)
expect(argsLength).to.equal(1)
expect(args[1]).to.equal(5)
expect(promise).to.be.ok()
expect(promise._status).to.equal(Promise.Status.Resolved)
expect(promise._value[1]).to.equal(5)
expect(chained).to.be.ok()
expect(chained).never.to.equal(promise)
expect(chained._status).to.equal(Promise.Status.Resolved)
expect(#chained._value).to.equal(0)
end)
it("should chain onto rejected promises", function()
local args
local argsLength
local callCount = 0
local badCallCount = 0
local promise = Promise.reject(5)
local chained = promise
:andThen(function(...)
badCallCount = badCallCount + 1
end, function(...)
args = {...}
argsLength = select("#", ...)
callCount = callCount + 1
end)
expect(badCallCount).to.equal(0)
expect(callCount).to.equal(1)
expect(argsLength).to.equal(1)
expect(args[1]).to.equal(5)
expect(promise).to.be.ok()
expect(promise._status).to.equal(Promise.Status.Rejected)
expect(promise._value[1]).to.equal(5)
expect(chained).to.be.ok()
expect(chained).never.to.equal(promise)
expect(chained._status).to.equal(Promise.Status.Resolved)
expect(#chained._value).to.equal(0)
end)
it("should chain onto asynchronously resolved promises", function()
local args
local argsLength
local callCount = 0
local badCallCount = 0
local startResolution
local promise = Promise.new(function(resolve)
startResolution = resolve
end)
local chained = promise
:andThen(function(...)
args = {...}
argsLength = select("#", ...)
callCount = callCount + 1
end, function()
badCallCount = badCallCount + 1
end)
expect(callCount).to.equal(0)
expect(badCallCount).to.equal(0)
startResolution(6)
expect(badCallCount).to.equal(0)
expect(callCount).to.equal(1)
expect(argsLength).to.equal(1)
expect(args[1]).to.equal(6)
expect(promise).to.be.ok()
expect(promise._status).to.equal(Promise.Status.Resolved)
expect(promise._value[1]).to.equal(6)
expect(chained).to.be.ok()
expect(chained).never.to.equal(promise)
expect(chained._status).to.equal(Promise.Status.Resolved)
expect(#chained._value).to.equal(0)
end)
it("should chain onto asynchronously rejected promises", function()
local args
local argsLength
local callCount = 0
local badCallCount = 0
local startResolution
local promise = Promise.new(function(_, reject)
startResolution = reject
end)
local chained = promise
:andThen(function()
badCallCount = badCallCount + 1
end, function(...)
args = {...}
argsLength = select("#", ...)
callCount = callCount + 1
end)
expect(callCount).to.equal(0)
expect(badCallCount).to.equal(0)
startResolution(6)
expect(badCallCount).to.equal(0)
expect(callCount).to.equal(1)
expect(argsLength).to.equal(1)
expect(args[1]).to.equal(6)
expect(promise).to.be.ok()
expect(promise._status).to.equal(Promise.Status.Rejected)
expect(promise._value[1]).to.equal(6)
expect(chained).to.be.ok()
expect(chained).never.to.equal(promise)
expect(chained._status).to.equal(Promise.Status.Resolved)
expect(#chained._value).to.equal(0)
end)
end)
end

203
plugin/src/Reconciler.lua Normal file
View File

@@ -0,0 +1,203 @@
local RouteMap = require(script.Parent.RouteMap)
local function classEqual(rbx, className)
if className == "*" then
return true
end
return rbx.ClassName == className
end
local function reparent(rbx, parent)
if rbx then
-- It's possible that 'rbx' is a service or some other object that we
-- can't change the parent of. That's the only reason why Parent would
-- fail except for rbx being previously destroyed!
pcall(function()
rbx.Parent = parent
end)
end
end
--[[
Attempts to match up Roblox instances and object specifiers for
reconciliation.
An object is considered a match if they have the same Name and ClassName.
primaryChildren and secondaryChildren can each be either a list of Roblox
instances or object specifiers. Since they share a common shape, switching
the two around isn't problematic!
visited is expected to be an empty table initially. It will be filled with
the set of children that have been visited so far.
]]
local function findNextChildPair(primaryChildren, secondaryChildren, visited)
for _, primaryChild in ipairs(primaryChildren) do
if not visited[primaryChild] then
visited[primaryChild] = true
for _, secondaryChild in ipairs(secondaryChildren) do
if primaryChild.ClassName == secondaryChild.ClassName and primaryChild.Name == secondaryChild.Name then
visited[secondaryChild] = true
return primaryChild, secondaryChild
end
end
return primaryChild, nil
end
end
return nil, nil
end
local Reconciler = {}
Reconciler.__index = Reconciler
function Reconciler.new()
local reconciler = {
_routeMap = RouteMap.new(),
}
setmetatable(reconciler, Reconciler)
return reconciler
end
--[[
A semi-smart algorithm that attempts to apply the given item's children to
an existing Roblox object.
]]
function Reconciler:_reconcileChildren(rbx, item)
local visited = {}
local rbxChildren = rbx:GetChildren()
-- Reconcile any children that were added or updated
while true do
local itemChild, rbxChild = findNextChildPair(item.Children, rbxChildren, visited)
if not itemChild then
break
end
reparent(self:reconcile(rbxChild, itemChild), rbx)
end
-- Reconcile any children that were deleted
while true do
local rbxChild, itemChild = findNextChildPair(rbxChildren, item.Children, visited)
if not rbxChild then
break
end
reparent(self:reconcile(rbxChild, itemChild), rbx)
end
end
--[[
Construct a new Roblox object from the given item.
]]
function Reconciler:_reify(item)
local className = item.ClassName
-- "*" represents a match of any class. It reifies as a folder!
if className == "*" then
className = "Folder"
end
local rbx = Instance.new(className)
rbx.Name = item.Name
for key, property in pairs(item.Properties) do
-- TODO: Check for compound types, like Vector3!
rbx[key] = property.Value
end
self:_reconcileChildren(rbx, item)
if item.Route then
self._routeMap:insert(item.Route, rbx)
end
return rbx
end
--[[
Apply the changes represented by the given item to a Roblox object that's a
child of the given instance.
]]
function Reconciler:reconcile(rbx, item)
-- Item was deleted
if not item then
if rbx then
rbx:Destroy()
end
return nil
end
-- Item was created!
if not rbx then
return self:_reify(item)
end
-- Item changed type!
if not classEqual(rbx, item.ClassName) then
rbx:Destroy()
rbx = self:_reify(item)
end
-- Apply all properties, Roblox will de-duplicate changes
for key, property in pairs(item.Properties) do
-- TODO: Transform property value based on property.Type
-- Right now, we assume that 'value' is primitive!
rbx[key] = property.Value
end
-- Use a dumb algorithm for reconciling children
self:_reconcileChildren(rbx, item)
return rbx
end
function Reconciler:reconcileRoute(route, item, itemRoute)
local parent
local rbx = game
for i = 1, #route do
local piece = route[i]
local child = rbx:FindFirstChild(piece)
-- We should get services instead of making folders here.
if rbx == game and not child then
local _
_, child = pcall(game.GetService, game, piece)
end
-- We don't want to create a folder if we're reaching our target item!
if not child and i ~= #route then
child = Instance.new("Folder")
child.Parent = rbx
child.Name = piece
end
parent = rbx
rbx = child
end
-- Let's check the route map!
if not rbx then
rbx = self._routeMap:get(itemRoute)
end
rbx = self:reconcile(rbx, item)
reparent(rbx, parent)
end
return Reconciler

103
plugin/src/RouteMap.lua Normal file
View File

@@ -0,0 +1,103 @@
--[[
A map from Route objects (given by the server) to Roblox instances (created
by the plugin).
]]
local function hashRoute(route)
return table.concat(route, "/")
end
local RouteMap = {}
RouteMap.__index = RouteMap
function RouteMap.new()
local self = {
_map = {},
_reverseMap = {},
_connectionsByRbx = {},
}
setmetatable(self, RouteMap)
return self
end
function RouteMap:insert(route, rbx)
local hashed = hashRoute(route)
self._map[hashed] = rbx
self._reverseMap[rbx] = hashed
self._connectionsByRbx[rbx] = rbx.AncestryChanged:Connect(function(_, parent)
if parent == nil then
self:removeByRbx(rbx)
end
end)
end
function RouteMap:get(route)
return self._map[hashRoute(route)]
end
function RouteMap:removeByRoute(route)
local hashedRoute = hashRoute(route)
local rbx = self._map[hashedRoute]
if rbx then
self._map[hashedRoute] = nil
self._reverseMap[rbx] = nil
self._connectionsByRbx[rbx] = nil
end
end
function RouteMap:removeByRbx(rbx)
local hashedRoute = self._reverseMap[rbx]
if hashedRoute then
self._map[hashedRoute] = nil
self._reverseMap[rbx] = nil
self._connectionsByRbx[rbx] = nil
end
end
function RouteMap:removeRbxDescendants(parentRbx)
for rbx in pairs(self._reverseMap) do
if rbx:IsDescendantOf(parentRbx) then
self:removeByRbx(rbx)
end
end
end
function RouteMap:clear()
self._map = {}
self._reverseMap = {}
for object in pairs(self._connectionsByRbx) do
object:Disconnect()
end
self._connectionsByRbx = {}
end
function RouteMap:visualize()
-- Log all of our keys so that the visualization has a stable order.
local keys = {}
for key in pairs(self._map) do
table.insert(keys, key)
end
table.sort(keys)
local buffer = {}
for _, key in ipairs(keys) do
local visualized = ("- %s: %s"):format(
key,
self._map[key]:GetFullName()
)
table.insert(buffer, visualized)
end
return table.concat(buffer, "\n")
end
return RouteMap

89
plugin/src/Server.lua Normal file
View File

@@ -0,0 +1,89 @@
local HttpService = game:GetService("HttpService")
local Config = require(script.Parent.Config)
local Promise = require(script.Parent.Promise)
local Version = require(script.Parent.Version)
local Server = {}
Server.__index = Server
--[[
Create a new Server using the given HTTP implementation and replacer.
If the context becomes invalid, `replacer` will be invoked with a new
context that should be suitable to replace this one.
Attempting to invoke methods on an invalid conext will throw errors!
]]
function Server.connect(http)
local context = {
http = http,
serverId = nil,
currentTime = 0,
}
setmetatable(context, Server)
return context:_start()
end
function Server:_start()
return self:getInfo()
:andThen(function(response)
if response.protocolVersion ~= Config.protocolVersion then
local message = (
"Found a Rojo dev server, but it's using a different protocol version, and is incompatible." ..
"\nMake sure you have matching versions of both the Rojo plugin and server!" ..
"\n\nYour client is version %s, with protocol version %s. It expects server version %s." ..
"\nYour server is version %s, with protocol version %s." ..
"\n\nGo to https://github.com/LPGhatguy/rojo for more details."
):format(
Version.display(Config.version), Config.protocolVersion,
Config.expectedServerVersionString,
response.serverVersion, response.protocolVersion
)
return Promise.reject(message)
end
self.serverId = response.serverId
self.currentTime = response.currentTime
return self
end)
end
function Server:getInfo()
return self.http:get("/")
:andThen(function(response)
response = response:json()
return response
end)
end
function Server:read(paths)
local body = HttpService:JSONEncode(paths)
return self.http:post("/read", body)
:andThen(function(response)
response = response:json()
return response.items
end)
end
function Server:getChanges()
local url = ("/changes/%f"):format(self.currentTime)
return self.http:get(url)
:andThen(function(response)
response = response:json()
self.currentTime = response.currentTime
return response.changes
end)
end
return Server

40
plugin/src/Version.lua Normal file
View File

@@ -0,0 +1,40 @@
local function compare(a, b)
if a > b then
return 1
elseif a < b then
return -1
end
return 0
end
local Version = {}
--[[
Compares two versions of the form {major, minor, revision}.
If a is newer than b, 1.
If a is older than b, -1.
If a and b are the same, 0.
]]
function Version.compare(a, b)
local major = compare(a[1], b[1])
local minor = compare(a[2] or 0, b[2] or 0)
local revision = compare(a[3] or 0, b[3] or 0)
if major ~= 0 then
return major
end
if minor ~= 0 then
return minor
end
return revision
end
function Version.display(version)
return table.concat(version, ".")
end
return Version

View File

@@ -0,0 +1,28 @@
return function()
local Version = require(script.Parent.Version)
it("should compare equal versions", function()
expect(Version.compare({1, 2, 3}, {1, 2, 3})).to.equal(0)
expect(Version.compare({0, 4, 0}, {0, 4})).to.equal(0)
expect(Version.compare({0, 0, 123}, {0, 0, 123})).to.equal(0)
expect(Version.compare({26}, {26})).to.equal(0)
expect(Version.compare({26, 42}, {26, 42})).to.equal(0)
expect(Version.compare({1, 0, 0}, {1})).to.equal(0)
end)
it("should compare newer, older versions", function()
expect(Version.compare({1}, {0})).to.equal(1)
expect(Version.compare({1, 1}, {1, 0})).to.equal(1)
end)
it("should compare different major versions", function()
expect(Version.compare({1, 3, 2}, {2, 2, 1})).to.equal(-1)
expect(Version.compare({1, 2}, {2, 1})).to.equal(-1)
expect(Version.compare({1}, {2})).to.equal(-1)
end)
it("should compare different minor versions", function()
expect(Version.compare({1, 2, 3}, {1, 3, 2})).to.equal(-1)
expect(Version.compare({50, 1}, {50, 2})).to.equal(-1)
end)
end

4
plugin/src/runTests.lua Normal file
View File

@@ -0,0 +1,4 @@
return function()
local TestEZ = require(script.Parent.Parent.TestEZ)
TestEZ.TestBootstrap:run(script.Parent)
end

View File

@@ -0,0 +1,2 @@
local TestEZ = require(game.ReplicatedStorage.TestEZ)
TestEZ.TestBootstrap:run(game.ReplicatedStorage.Rojo.plugin)

5
server/.editorconfig Normal file
View File

@@ -0,0 +1,5 @@
[*.rs]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true

2
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target/
**/*.rs.bk

View File

@@ -16,8 +16,11 @@ dependencies = [
[[package]]
name = "ansi_term"
version = "0.10.2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ascii"
@@ -26,12 +29,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "atty"
version = "0.2.6"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -55,7 +58,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -64,7 +67,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"brotli-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -79,7 +82,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.3"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -92,7 +95,7 @@ name = "chrono"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -103,13 +106,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.29.0"
version = "2.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -188,12 +191,12 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.1.14"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -201,7 +204,7 @@ name = "flate2"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -212,7 +215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -220,7 +223,7 @@ name = "fsevent-sys"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -257,12 +260,12 @@ name = "inotify"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.3.4"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -281,7 +284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.35"
version = "0.2.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -310,7 +313,7 @@ name = "memchr"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -318,7 +321,7 @@ name = "memchr"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -345,8 +348,8 @@ name = "miniz-sys"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -355,10 +358,10 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -371,7 +374,7 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
"net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -387,20 +390,18 @@ dependencies = [
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "net2"
version = "0.2.31"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -409,7 +410,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -418,47 +419,47 @@ version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
"fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num"
version = "0.1.41"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
"num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-integer"
version = "0.1.35"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-iter"
version = "0.1.34"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.1.41"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -489,7 +490,7 @@ version = "0.7.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -501,22 +502,44 @@ dependencies = [
]
[[package]]
name = "quote"
version = "0.3.15"
name = "proc-macro2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.3.19"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.34"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -524,7 +547,7 @@ name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -541,12 +564,12 @@ dependencies = [
[[package]]
name = "regex"
version = "0.2.5"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -558,22 +581,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "regex-syntax"
version = "0.4.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "remove_dir_all"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rojo"
version = "0.4.0"
version = "0.4.1"
dependencies = [
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rouille 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -583,16 +617,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"brotli2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
"multipart 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
"sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
"tiny_http 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tiny_http 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -602,10 +636,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "same-file"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -613,42 +647,43 @@ name = "serde"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde"
version = "1.0.27"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.27"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive_internals 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive_internals"
version = "0.19.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.9"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -668,33 +703,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.11.11"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synom"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tempdir"
version = "0.3.5"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"remove_dir_all 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -711,8 +739,8 @@ name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -730,7 +758,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -755,14 +783,14 @@ name = "time"
version = "0.1.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tiny_http"
version = "0.5.8"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -770,10 +798,14 @@ dependencies = [
"chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ucd-util"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-bidi"
version = "0.3.4"
@@ -794,7 +826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.0.4"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -817,7 +849,7 @@ dependencies = [
[[package]]
name = "url"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -840,7 +872,7 @@ name = "uuid"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -856,10 +888,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "2.0.1"
version = "2.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"same-file 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -869,11 +902,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -883,12 +916,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.3.2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.3.2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -903,9 +936,9 @@ dependencies = [
[metadata]
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae7d751998c189c1d4468cf0a39bb2eae052a9c58d50ebb3b9591ee3813ad50"
"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859"
"checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4"
"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
@@ -913,11 +946,11 @@ dependencies = [
"checksum brotli2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ea9d0bbab1235017a09226b079ed733bca4bf9ecb6b6102bd01aac79ea082dca"
"checksum buf_redux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b115bd9935c68b58f80ff867e1c46942c4aed79e78bcc8c2bc22d50f52bb9099"
"checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27"
"checksum cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b13a57efd6b30ecd6598ebdb302cca617930b5470647570468a65d12ef9719"
"checksum cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2b4911e4bdcb4100c7680e7e854ff38e23f1b34d4d9e079efae3da2801341ffc"
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00"
"checksum chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87"
"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
"checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
"checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
@@ -927,7 +960,7 @@ dependencies = [
"checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
"checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
"checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f"
"checksum filetime 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "aa75ec8f7927063335a9583e7fa87b0110bb888cf766dc01b54c0ff70d760c8e"
"checksum filetime 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "714653f3e34871534de23771ac7b26e999651a0a228f47beb324dfdf1dd4b10f"
"checksum flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "e6234dd4468ae5d1e2dbb06fe2b058696fdc50a339c68a393aefbf00bc81e423"
"checksum fsevent 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "c4bbbf71584aeed076100b5665ac14e3d85eeb31fdbb45fbd41ef9a682b5ec05"
"checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874"
@@ -936,10 +969,10 @@ dependencies = [
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
"checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c"
"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
"checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
"checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb"
"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
@@ -951,41 +984,43 @@ dependencies = [
"checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e"
"checksum miow 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3e690c5df6b2f60acd45d56378981e827ff8295562fc8d34f573deb267a59cd1"
"checksum multipart 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b68c9a0c757bd65893af529f7af6e7a71442e57ca6d9db1fa69b79e2f05f6b49"
"checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09"
"checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0"
"checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79"
"checksum notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5c3812da3098f210a0bb440f9c008471a031aa4c1de07a264fdd75456c95a4eb"
"checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca"
"checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba"
"checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01"
"checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070"
"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe"
"checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593"
"checksum num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dee092fcdf725aee04dd7da1d21debff559237d49ef1cb3e69bcb8ece44c7364"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
"checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f"
"checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
"checksum rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "9e7944d95d25ace8f377da3ac7068ce517e4c646754c43a1b1849177bbf72e59"
"checksum redox_syscall 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "df1a5c588807af3b0cbbfa2f1358f2d5ec6ad546858c1ccd30dfbb127021706b"
"checksum proc-macro2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "388d7ea47318c5ccdeb9ba6312cee7d3f65dd2804be8580a170fce410d50b786"
"checksum quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0ff51282f28dc1b53fd154298feaa2e77c5ea0dba68e1fd8b03b72fbe13d2a"
"checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5"
"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa"
"checksum regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "aec3f58d903a7d2a9dc2bf0e41a746f4530e0cab6b615494e058f67a3ef947fb"
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
"checksum regex-syntax 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2550876c31dc914696a6c2e01cbce8afba79a93c8ae979d2fe051c0230b3756"
"checksum remove_dir_all 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfc5b3ce5d5ea144bb04ebd093a9e14e9765bcfec866aecda9b6dec43b3d1e24"
"checksum rouille 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d6415f261a8775bef50e9fcfb14ed73209ce637f753f9d1c8c6122559e559001"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum same-file 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3257af0472da4b8b8902102a57bafffd9991f0f43772a8af6153d597e6e4ae2"
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
"checksum serde 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97b18e9e53de541f11e497357d6c5eaeb39f0cb9c8734e274abe4935f6991fa"
"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526"
"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0"
"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5"
"checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb"
"checksum serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "d3bcee660dcde8f52c3765dd9ca5ee36b4bf35470a738eb0bd5a8752b0389645"
"checksum serde_derive 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "f1711ab8b208541fa8de00425f6a577d90f27bb60724d2bb5fd911314af9668f"
"checksum serde_derive_internals 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "89b340a48245bc03ddba31d0ff1709c118df90edc6adabaca4aac77aea181cce"
"checksum serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "5c508584d9913df116b91505eec55610a2f5b16e9ed793c46e4d0152872b3e74"
"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
"checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e"
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
@@ -993,23 +1028,24 @@ dependencies = [
"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
"checksum tiny_http 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "016f040cfc9b5be610de3619eaaa57017fa0b0b678187327bde75fc146e2a41f"
"checksum tiny_http 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2f4d55c9a213880d1f0c89ded183f209c6e45b912ca6c7df6f93c163773572e1"
"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
"checksum url 0.2.38 (registry+https://github.com/rust-lang/crates.io-index)" = "cbaa8377a162d88e7d15db0cf110c8523453edcbc5bc66d2b6fffccffa34a068"
"checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2"
"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum uuid 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "78c590b5bd79ed10aad8fb75f078a59d8db445af6c743e55c4a53227fc01c13f"
"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum walkdir 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b6d201f4f8998a837196b6de9c73e35af14c992cbb92c4ab641d2c2dce52de"
"checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b09fb3b6f248ea4cd42c9a65113a847d612e17505d6ebd1f7357ad68a8bf8693"
"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum winapi-i686-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ec6667f60c23eca65c561e63a13d81b44234c2e38a6b6c959025ee907ec614cc"
"checksum winapi-x86_64-pc-windows-gnu 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98f12c52b2630cd05d2c3ffd8e008f7f48252c042b4871c72aed9dc733b96668"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"

View File

@@ -1,6 +1,6 @@
[package]
name = "rojo"
version = "0.4.0"
version = "0.4.1"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "A tool to create robust Roblox projects"
license = "MIT"

View File

@@ -78,11 +78,14 @@ impl VfsWatcher {
let (tx, rx) = mpsc::channel();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1))
.expect("Unable to create watcher!");
.expect("Unable to create watcher! This is a bug in Rojo.");
watcher
.watch(&root_path, RecursiveMode::Recursive)
.expect("Unable to watch path!");
match watcher.watch(&root_path, RecursiveMode::Recursive) {
Ok(_) => (),
Err(_) => {
panic!("Unable to watch partition {}, with path {}! Make sure that it's a file or directory.", partition_name, root_path.display());
},
}
watchers.push(watcher);