mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-21 05:06:29 +00:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fc497f95e | ||
|
|
52eea667a7 | ||
|
|
c2f7e268ff | ||
|
|
31e5c558ab | ||
|
|
7a7ac9550d | ||
|
|
4d0fdf0dfd | ||
|
|
b448e8007e | ||
|
|
bad0e67266 | ||
|
|
3dee3dd627 | ||
|
|
4772350968 | ||
|
|
eabcc0bd1d | ||
|
|
3a3af6ab10 | ||
|
|
9723622b66 | ||
|
|
3b1d647acb | ||
|
|
6fa925a402 | ||
|
|
c8f837d726 | ||
|
|
4557396564 | ||
|
|
d3d67d47e1 | ||
|
|
42107e0715 | ||
|
|
ed183e0805 | ||
|
|
116be16392 | ||
|
|
2c188738e6 | ||
|
|
ebffba9589 | ||
|
|
ab644c3dfa | ||
|
|
c6cdd8a815 | ||
|
|
d99df59d9b | ||
|
|
c5f8247543 | ||
|
|
72557c9d23 | ||
|
|
1a1b6d923f | ||
|
|
27cf2c8740 | ||
|
|
c08a598d3f | ||
|
|
1318842c36 | ||
|
|
86d7d033d7 | ||
|
|
2df1dfa1cb | ||
|
|
78a1c658d8 | ||
|
|
f52f43fe90 | ||
|
|
58b244b7e9 | ||
|
|
d8bcbee463 | ||
|
|
f00152a9ac | ||
|
|
9720c56765 | ||
|
|
13ce04abb2 | ||
|
|
ab22b55b84 | ||
|
|
73117edbe7 | ||
|
|
d7e2a3542c | ||
|
|
fe240ed577 | ||
|
|
5e98cbe68f | ||
|
|
7a372dc50c | ||
|
|
958b6660be | ||
|
|
e731811911 | ||
|
|
66144cef2f | ||
|
|
13925f5879 | ||
|
|
68ba3fee6c | ||
|
|
95581dbaa6 | ||
|
|
aaaf3ba0b9 | ||
|
|
b885cae086 | ||
|
|
0f78eb933a | ||
|
|
6ee9a48e20 | ||
|
|
f90c51e923 | ||
|
|
6472a2cbce | ||
|
|
c75cbebbf0 | ||
|
|
2e340ff78c | ||
|
|
5a20646c57 | ||
|
|
199ebda689 | ||
|
|
ae6ca6fb23 | ||
|
|
12bfcd7b66 | ||
|
|
d365bc076e | ||
|
|
67ac6b7cec | ||
|
|
01325c8c7e | ||
|
|
21e9625c36 | ||
|
|
5bf1f11190 | ||
|
|
b4e31ea35d | ||
|
|
7c6fe38346 | ||
|
|
f89d491f29 | ||
|
|
59b2401c2c | ||
|
|
b74ba141d1 | ||
|
|
b60f06aa88 | ||
|
|
f00bcc6d7e | ||
|
|
d5b41e2bd4 | ||
|
|
c9a53debc3 | ||
|
|
edd45ca02e | ||
|
|
f88cb67210 | ||
|
|
302c6cf663 | ||
|
|
7995b6eca4 | ||
|
|
fd7e737c20 | ||
|
|
68b3d56619 | ||
|
|
059ff1777b | ||
|
|
dd16cadb4c | ||
|
|
551f75f39c | ||
|
|
23ae0bc186 | ||
|
|
713a199419 | ||
|
|
4dc705ee45 | ||
|
|
fe4678fdc5 | ||
|
|
97682108aa | ||
|
|
23d4f45ac9 | ||
|
|
9fd6799f93 | ||
|
|
5898780e8e | ||
|
|
1ad20421e9 | ||
|
|
4ff9033916 | ||
|
|
37bb0d1aa9 | ||
|
|
7042680a0a |
@@ -4,17 +4,6 @@ root = true
|
|||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|
||||||
[*.lua]
|
|
||||||
indent_style = tab
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.rs]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.json]
|
[*.json]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
/target/
|
/site
|
||||||
**/*.rs.bk
|
|
||||||
|
|||||||
27
.gitmodules
vendored
27
.gitmodules
vendored
@@ -1,12 +1,15 @@
|
|||||||
[submodule "modules/Roact"]
|
[submodule "plugin/modules/roact"]
|
||||||
path = modules/Roact
|
path = plugin/modules/roact
|
||||||
url = https://github.com/Roblox/Roact.git
|
url = https://github.com/Roblox/roact.git
|
||||||
[submodule "modules/Rodux"]
|
[submodule "plugin/modules/rodux"]
|
||||||
path = modules/Rodux
|
path = plugin/modules/rodux
|
||||||
url = https://github.com/Roblox/Rodux.git
|
url = https://github.com/Roblox/rodux.git
|
||||||
[submodule "modules/RoactRodux"]
|
[submodule "plugin/modules/roact-rodux"]
|
||||||
path = modules/RoactRodux
|
path = plugin/modules/roact-rodux
|
||||||
url = https://github.com/Roblox/RoactRodux.git
|
url = https://github.com/Roblox/roact-rodux.git
|
||||||
[submodule "modules/TestEZ"]
|
[submodule "plugin/modules/testez"]
|
||||||
path = modules/TestEZ
|
path = plugin/modules/testez
|
||||||
url = https://github.com/Roblox/TestEZ.git
|
url = https://github.com/Roblox/testez.git
|
||||||
|
[submodule "plugin/modules/lemur"]
|
||||||
|
path = plugin/modules/lemur
|
||||||
|
url = https://github.com/LPGhatguy/lemur.git
|
||||||
|
|||||||
39
.travis.yml
Normal file
39
.travis.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- language: python
|
||||||
|
env:
|
||||||
|
- LUA="lua=5.1"
|
||||||
|
|
||||||
|
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
|
||||||
47
CHANGES.md
47
CHANGES.md
@@ -1,24 +1,59 @@
|
|||||||
# Rojo Change Log
|
# Rojo Change Log
|
||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
* *No changes*
|
*No changes*
|
||||||
|
|
||||||
## 0.2.3
|
## 0.4.3 (April 7, 2018)
|
||||||
|
* Plugin now automatically selects `HttpService` if it determines that HTTP isn't enabled ([#58](https://github.com/LPGhatguy/rojo/pull/58))
|
||||||
|
* Plugin now has much more robust handling and will wipe all state when the server changes.
|
||||||
|
* This should fix issues that would otherwise be solved by restarting Roblox Studio.
|
||||||
|
|
||||||
|
## 0.4.2 (April 4, 2018)
|
||||||
|
* Fixed final case of duplicated instance insertion, caused by reconciled instances not being inserted into `RouteMap`.
|
||||||
|
* The reconciler is still not a perfect solution, especially if script instances get moved around without being destroyed. I don't think this can be fixed before a big refactor.
|
||||||
|
|
||||||
|
## 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 (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 (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 (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 (December 4, 2017)
|
||||||
* Plugin only release
|
* Plugin only release
|
||||||
* Tightened `init` file rules to only match script files
|
* Tightened `init` file rules to only match script files
|
||||||
* Previously, Rojo would sometimes pick up the wrong file when syncing
|
* Previously, Rojo would sometimes pick up the wrong file when syncing
|
||||||
|
|
||||||
## 0.2.2
|
## 0.2.2 (December 1, 2017)
|
||||||
* Plugin only release
|
* Plugin only release
|
||||||
* Fixed broken reconciliation behavior with `init` files
|
* Fixed broken reconciliation behavior with `init` files
|
||||||
|
|
||||||
## 0.2.1
|
## 0.2.1 (December 1, 2017)
|
||||||
* Plugin only release
|
* Plugin only release
|
||||||
* Changes default port to 8000
|
* Changes default port to 8000
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0 (December 1, 2017)
|
||||||
* Support for `init.lua` like rbxfs and rbxpacker
|
* Support for `init.lua` like rbxfs and rbxpacker
|
||||||
* More robust syncing with a new reconciler
|
* 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)
|
* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs)
|
||||||
49
DESIGN.md
Normal file
49
DESIGN.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Rojo Design - Protocol Version 1
|
||||||
|
This is a super rough draft that I'm trying to use to lay out some of my thoughts.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### POST `/read`
|
||||||
|
Accepts a `Vec<Route>` of items to read.
|
||||||
|
|
||||||
|
Returns `Vec<Option<RbxInstance>>`, in the same order as the request.
|
||||||
|
|
||||||
|
### POST `/write`
|
||||||
|
Accepts a `Vec<{ Route, RbxInstance }>` of items to write.
|
||||||
|
|
||||||
|
I imagine that the `Name` attribute of the top-level `RbxInstance` would be ignored in favor of the route name?
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
The `rojo serve` command uses three major components:
|
||||||
|
* A Virtual Filesystem (VFS), which exposes the filesystem as `VfsItem` objects
|
||||||
|
* A VFS watcher, which tracks changes to the filesystem and logs them
|
||||||
|
* An HTTP API, which exposes an interface to the Roblox Studio plugin
|
||||||
|
|
||||||
|
### Transform Plugins
|
||||||
|
Transform plugins (or filter plugins?) can interject in three places:
|
||||||
|
* Transform a `VfsItem` that's being read into an `RbxInstance` in the VFS
|
||||||
|
* Transform an `RbxInstance` that's being written into a `VfsItem` in the VFS
|
||||||
|
* Transform a file change into paths that need to be updated in the VFS watcher
|
||||||
|
|
||||||
|
The plan is to have several built-in plugins that can be rearranged/configured in project settings:
|
||||||
|
|
||||||
|
* Base plugin
|
||||||
|
* Transforms all unhandled files to/from StringValue objects
|
||||||
|
* Script plugin
|
||||||
|
* Transforms `*.lua` files to their appropriate file types
|
||||||
|
* JSON/rbxmx/rbxlx model plugin
|
||||||
|
* External binary plugin
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
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:
|
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:
|
||||||
|
|
||||||
|
|||||||
129
README.md
129
README.md
@@ -1,45 +1,59 @@
|
|||||||
<h1 align="center">Rojo</h1>
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://travis-ci.org/LPGhatguy/Rojo">
|
<img src="assets/rojo-logo.png" alt="Rojo" height="217" />
|
||||||
<img src="https://api.travis-ci.org/LPGhatguy/Rojo.svg?branch=master" alt="Travis-CI Build Status" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div> </div>
|
<div> </div>
|
||||||
|
|
||||||
Rojo is a flexible multi-tool designed for creating robust Roblox projects. It's in early development, but is still useful for many projects.
|
<div align="center">
|
||||||
|
<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.3-brightgreen.svg" alt="Current server version" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
**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.
|
It's designed for power users who want to use the **best tools available** for building games, libraries, and plugins.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
Rojo lets you:
|
||||||
Rojo has a number of desirable features *right now*:
|
|
||||||
|
|
||||||
* Work on scripts from the filesystem, in your favorite editor
|
* Work on scripts from the filesystem, in your favorite editor
|
||||||
* Version your place, library, or plugin using Git or another VCS
|
* 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
|
* Sync `rbxmx` models between the filesystem and Roblox Studio
|
||||||
* Create installation scripts for libraries to be used in standalone places
|
* Package projects into `rbxmx` files from the command line
|
||||||
* Similar to [rbxpacker](https://github.com/LPGhatguy/rbxpacker), another one of my projects
|
|
||||||
* Add strongly-versioned dependencies to your project
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Rojo has two components:
|
Rojo has two components:
|
||||||
* The command line tool, written in Rust
|
|
||||||
* The [Roblox Studio plugin](https://www.roblox.com/library/1211549683/Rojo-v0-0-0), written in Lua
|
|
||||||
|
|
||||||
To install the command line tool, there are two options:
|
* The server, a binary written in Rust
|
||||||
* Cargo, if you have Rust installed
|
* The plugin, a Roblox Studio plugin written in Lua
|
||||||
* 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)
|
|
||||||
|
|
||||||
## 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`.
|
For more help, use `rojo help`.
|
||||||
|
|
||||||
### New Project
|
### 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
|
```sh
|
||||||
mkdir my-new-project
|
mkdir my-new-project
|
||||||
@@ -48,9 +62,9 @@ cd my-new-project
|
|||||||
rojo init
|
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
|
```json
|
||||||
{
|
{
|
||||||
@@ -61,7 +75,7 @@ The default project looks like this:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Start Dev Server
|
### 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
|
```sh
|
||||||
rojo serve
|
rojo serve
|
||||||
@@ -70,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!
|
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
|
### 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
|
### 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:
|
For example, if you want to map your `src` directory to an object named `My Cool Game` in `ReplicatedStorage`, you could use this configuration:
|
||||||
|
|
||||||
@@ -94,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 `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
|
### 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:
|
Creation of Roblox instances follows a simple set of rules. The first rule that matches the file name is chosen:
|
||||||
|
|
||||||
@@ -108,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 |
|
| `*.server.lua` | `Script` | `Source` will contain the file's contents |
|
||||||
| `*.client.lua` | `LocalScript` | `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 |
|
| `*.lua` | `ModuleScript` | `Source` will contain the file's contents |
|
||||||
|
| `*.model.json` | *Varies* | See the example below |
|
||||||
| `*` | `StringValue` | `Value` will contain the file's contents |
|
| `*` | `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:
|
For example, this file tree:
|
||||||
|
|
||||||
@@ -118,15 +143,49 @@ For example, this file tree:
|
|||||||
* init.client.lua
|
* init.client.lua
|
||||||
* foo.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`)
|
* `my-game` (`LocalScript` with source from `my-game/init.client.lua`)
|
||||||
* `foo` (`ModuleScript` with source from `my-game/foo.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
|
## 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:
|
Here are a few, if you're looking for alternatives or supplements to Rojo:
|
||||||
|
|
||||||
* [Studio Bridge by Vocksel](https://github.com/vocksel/studio-bridge)
|
* [Studio Bridge by Vocksel](https://github.com/vocksel/studio-bridge)
|
||||||
* [RbxRefresh by Osyris](https://github.com/osyrisrblx/RbxRefresh)
|
* [RbxRefresh by Osyris](https://github.com/osyrisrblx/RbxRefresh)
|
||||||
* [RbxSync by evaera](https://github.com/evaera/RbxSync)
|
* [RbxSync by evaera](https://github.com/evaera/RbxSync)
|
||||||
@@ -134,8 +193,14 @@ Here are a few, if you're looking for alternatives or supplements to Rojo:
|
|||||||
* [rbxmk by Anaminus](https://github.com/anaminus/rbxmk)
|
* [rbxmk by Anaminus](https://github.com/anaminus/rbxmk)
|
||||||
|
|
||||||
I also have a couple tools that Rojo intends to replace:
|
I also have a couple tools that Rojo intends to replace:
|
||||||
|
|
||||||
* [rbxfs](https://github.com/LPGhatguy/rbxfs), which has been deprecated by Rojo
|
* [rbxfs](https://github.com/LPGhatguy/rbxfs), which has been deprecated by Rojo
|
||||||
* [rbxpacker](https://github.com/LPGhatguy/rbxpacker), which is still useful
|
* [rbxpacker](https://github.com/LPGhatguy/rbxpacker), which is still useful
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
Pull requests are welcome!
|
||||||
|
|
||||||
|
All pull requests are run against a test suite on Travis CI. That test suite should always pass!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Rojo is available under the terms of the MIT license. See [LICENSE.md](LICENSE.md) for details.
|
Rojo is available under the terms of the MIT license. See [LICENSE.md](LICENSE.md) for details.
|
||||||
BIN
assets/rojo-logo.png
Normal file
BIN
assets/rojo-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/rojo-plugin-logo.png
Normal file
BIN
assets/rojo-plugin-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
6
docs/index.md
Normal file
6
docs/index.md
Normal 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
3
docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mkdocs
|
||||||
|
mkdocs-material
|
||||||
|
pymdown-extensions
|
||||||
20
mkdocs.yml
Normal file
20
mkdocs.yml
Normal 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
|
||||||
Submodule modules/Roact deleted from 7cce62b130
Submodule modules/RoactRodux deleted from 43c4f347fe
Submodule modules/Rodux deleted from 6c573259ab
Submodule modules/TestEZ deleted from 9945f562e5
4
plugin/.editorconfig
Normal file
4
plugin/.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[*.lua]
|
||||||
|
indent_style = tab
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
1
plugin/.gitignore
vendored
Normal file
1
plugin/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/luacov.*
|
||||||
@@ -9,7 +9,7 @@ stds.roblox = {
|
|||||||
|
|
||||||
-- Extra functions
|
-- Extra functions
|
||||||
"tick", "warn", "spawn",
|
"tick", "warn", "spawn",
|
||||||
"wait", "settings",
|
"wait", "settings", "typeof",
|
||||||
|
|
||||||
-- Types
|
-- Types
|
||||||
"Vector2", "Vector3",
|
"Vector2", "Vector3",
|
||||||
|
|||||||
8
plugin/.luacov
Normal file
8
plugin/.luacov
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
return {
|
||||||
|
include = {
|
||||||
|
"^src",
|
||||||
|
},
|
||||||
|
exclude = {
|
||||||
|
"%.spec$",
|
||||||
|
},
|
||||||
|
}
|
||||||
1
plugin/modules/lemur
Submodule
1
plugin/modules/lemur
Submodule
Submodule plugin/modules/lemur added at 852c71b897
1
plugin/modules/roact
Submodule
1
plugin/modules/roact
Submodule
Submodule plugin/modules/roact added at bbb0663161
1
plugin/modules/roact-rodux
Submodule
1
plugin/modules/roact-rodux
Submodule
Submodule plugin/modules/roact-rodux added at 97fbfee90a
1
plugin/modules/rodux
Submodule
1
plugin/modules/rodux
Submodule
Submodule plugin/modules/rodux added at b8ba486335
1
plugin/modules/testez
Submodule
1
plugin/modules/testez
Submodule
Submodule plugin/modules/testez added at 442b71926d
30
plugin/rojo.json
Normal file
30
plugin/rojo.json
Normal 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
69
plugin/spec.lua
Normal 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
|
||||||
112
plugin/src/Api.lua
Normal file
112
plugin/src/Api.lua
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
local HttpService = game:GetService("HttpService")
|
||||||
|
|
||||||
|
local Config = require(script.Parent.Config)
|
||||||
|
local Promise = require(script.Parent.Promise)
|
||||||
|
local Version = require(script.Parent.Version)
|
||||||
|
|
||||||
|
local Api = {}
|
||||||
|
Api.__index = Api
|
||||||
|
|
||||||
|
Api.Error = {
|
||||||
|
ServerIdMismatch = "ServerIdMismatch",
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(Api.Error, {
|
||||||
|
__index = function(_, key)
|
||||||
|
error("Invalid API.Error name " .. key, 2)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Api.connect(Http) -> Promise<Api>
|
||||||
|
|
||||||
|
Create a new Api using the given HTTP implementation.
|
||||||
|
|
||||||
|
Attempting to invoke methods on an invalid conext will throw errors!
|
||||||
|
]]
|
||||||
|
function Api.connect(http)
|
||||||
|
local context = {
|
||||||
|
http = http,
|
||||||
|
serverId = nil,
|
||||||
|
currentTime = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(context, Api)
|
||||||
|
|
||||||
|
return context:_start()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Api:_start()
|
||||||
|
return self.http:get("/")
|
||||||
|
:andThen(function(response)
|
||||||
|
response = response:json()
|
||||||
|
|
||||||
|
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.expectedApiVersionString,
|
||||||
|
response.serverVersion, response.protocolVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
return Promise.reject(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.serverId = response.serverId
|
||||||
|
self.currentTime = response.currentTime
|
||||||
|
|
||||||
|
return self
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Api:getInfo()
|
||||||
|
return self.http:get("/")
|
||||||
|
:andThen(function(response)
|
||||||
|
response = response:json()
|
||||||
|
|
||||||
|
if response.serverId ~= self.serverId then
|
||||||
|
return Promise.reject(Api.Error.ServerIdMismatch)
|
||||||
|
end
|
||||||
|
|
||||||
|
return response
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Api:read(paths)
|
||||||
|
local body = HttpService:JSONEncode(paths)
|
||||||
|
|
||||||
|
return self.http:post("/read", body)
|
||||||
|
:andThen(function(response)
|
||||||
|
response = response:json()
|
||||||
|
|
||||||
|
if response.serverId ~= self.serverId then
|
||||||
|
return Promise.reject(Api.Error.ServerIdMismatch)
|
||||||
|
end
|
||||||
|
|
||||||
|
return response.items
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Api:getChanges()
|
||||||
|
local url = ("/changes/%f"):format(self.currentTime)
|
||||||
|
|
||||||
|
return self.http:get(url)
|
||||||
|
:andThen(function(response)
|
||||||
|
response = response:json()
|
||||||
|
|
||||||
|
if response.serverId ~= self.serverId then
|
||||||
|
return Promise.reject(Api.Error.ServerIdMismatch)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.currentTime = response.currentTime
|
||||||
|
|
||||||
|
return response.changes
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Api
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
return {
|
return {
|
||||||
pollingRate = 0.3,
|
pollingRate = 0.2,
|
||||||
version = "v0.2.3",
|
version = {0, 4, 3},
|
||||||
|
expectedServerVersionString = "0.4.x",
|
||||||
|
protocolVersion = 1,
|
||||||
dev = false,
|
dev = false,
|
||||||
}
|
}
|
||||||
|
|||||||
7
plugin/src/Config.spec.lua
Normal file
7
plugin/src/Config.spec.lua
Normal 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
|
||||||
@@ -52,6 +52,9 @@ end
|
|||||||
|
|
||||||
function HttpError:report()
|
function HttpError:report()
|
||||||
warn(self.message)
|
warn(self.message)
|
||||||
|
if self.type == HttpError.Error.HttpNotEnabled then
|
||||||
|
game:GetService("Selection"):Set{game:GetService("HttpService")}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return HttpError
|
return HttpError
|
||||||
|
|||||||
@@ -4,16 +4,51 @@ end
|
|||||||
|
|
||||||
local Plugin = require(script.Parent.Plugin)
|
local Plugin = require(script.Parent.Plugin)
|
||||||
local Config = require(script.Parent.Config)
|
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()
|
||||||
|
-- When developing Rojo, there's no use in doing version checks
|
||||||
|
if Config.dev then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
plugin:SetSetting("LastRojoVersion", Config.version)
|
||||||
|
end
|
||||||
|
|
||||||
local function main()
|
local function main()
|
||||||
local pluginInstance = Plugin.new()
|
local pluginInstance = Plugin.new()
|
||||||
|
|
||||||
local displayedVersion = Config.dev and "DEV" or Config.version
|
local displayedVersion = Config.dev and "DEV" or Version.display(Config.version)
|
||||||
|
|
||||||
local toolbar = plugin:CreateToolbar("Rojo Plugin " .. displayedVersion)
|
local toolbar = plugin:CreateToolbar("Rojo Plugin " .. displayedVersion)
|
||||||
|
|
||||||
toolbar:CreateButton("Test Connection", "Connect to Rojo Server", "")
|
toolbar:CreateButton("Test Connection", "Connect to Rojo Server", "")
|
||||||
.Click:Connect(function()
|
.Click:Connect(function()
|
||||||
|
checkUpgrade()
|
||||||
|
|
||||||
pluginInstance:connect()
|
pluginInstance:connect()
|
||||||
:catch(function(err)
|
:catch(function(err)
|
||||||
warn(err)
|
warn(err)
|
||||||
@@ -22,6 +57,8 @@ local function main()
|
|||||||
|
|
||||||
toolbar:CreateButton("Sync In", "Sync into Roblox Studio", "")
|
toolbar:CreateButton("Sync In", "Sync into Roblox Studio", "")
|
||||||
.Click:Connect(function()
|
.Click:Connect(function()
|
||||||
|
checkUpgrade()
|
||||||
|
|
||||||
pluginInstance:syncIn()
|
pluginInstance:syncIn()
|
||||||
:catch(function(err)
|
:catch(function(err)
|
||||||
warn(err)
|
warn(err)
|
||||||
@@ -30,12 +67,12 @@ local function main()
|
|||||||
|
|
||||||
toolbar:CreateButton("Toggle Polling", "Poll server for changes", "")
|
toolbar:CreateButton("Toggle Polling", "Poll server for changes", "")
|
||||||
.Click:Connect(function()
|
.Click:Connect(function()
|
||||||
spawn(function()
|
checkUpgrade()
|
||||||
pluginInstance:togglePolling()
|
|
||||||
:catch(function(err)
|
pluginInstance:togglePolling()
|
||||||
warn(err)
|
:catch(function(err)
|
||||||
end)
|
warn(err)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
local Config = require(script.Parent.Config)
|
local Config = require(script.Parent.Config)
|
||||||
local Http = require(script.Parent.Http)
|
local Http = require(script.Parent.Http)
|
||||||
local Server = require(script.Parent.Server)
|
local Api = require(script.Parent.Api)
|
||||||
local Promise = require(script.Parent.Promise)
|
local Promise = require(script.Parent.Promise)
|
||||||
local Reconciler = require(script.Parent.Reconciler)
|
local Reconciler = require(script.Parent.Reconciler)
|
||||||
|
|
||||||
@@ -23,13 +23,14 @@ function Plugin.new()
|
|||||||
|
|
||||||
local remote = ("http://%s:%d"):format(address, port)
|
local remote = ("http://%s:%d"):format(address, port)
|
||||||
|
|
||||||
local foop = {
|
local self = {
|
||||||
_http = Http.new(remote),
|
_http = Http.new(remote),
|
||||||
_server = nil,
|
_reconciler = Reconciler.new(),
|
||||||
|
_api = nil,
|
||||||
_polling = false,
|
_polling = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
setmetatable(foop, Plugin)
|
setmetatable(self, Plugin)
|
||||||
|
|
||||||
do
|
do
|
||||||
local screenGui = Instance.new("ScreenGui")
|
local screenGui = Instance.new("ScreenGui")
|
||||||
@@ -50,43 +51,64 @@ function Plugin.new()
|
|||||||
label.Position = UDim2.new(0, 0, 0, 0)
|
label.Position = UDim2.new(0, 0, 0, 0)
|
||||||
label.Parent = screenGui
|
label.Parent = screenGui
|
||||||
|
|
||||||
foop._label = screenGui
|
self._label = screenGui
|
||||||
end
|
end
|
||||||
|
|
||||||
return foop
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function Plugin:server()
|
--[[
|
||||||
if not self._server then
|
Clears all state and issues a notice to the user that the plugin has
|
||||||
self._server = Server.connect(self._http)
|
restarted.
|
||||||
|
]]
|
||||||
|
function Plugin:restart()
|
||||||
|
warn("The server has changed since the last request, reloading plugin...")
|
||||||
|
|
||||||
|
self._reconciler:clear()
|
||||||
|
self._api = nil
|
||||||
|
self._polling = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function Plugin:api()
|
||||||
|
if not self._api then
|
||||||
|
self._api = Api.connect(self._http)
|
||||||
:catch(function(err)
|
:catch(function(err)
|
||||||
self._server = nil
|
self._api = nil
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return self._server
|
return self._api
|
||||||
end
|
end
|
||||||
|
|
||||||
function Plugin:connect()
|
function Plugin:connect()
|
||||||
print("Testing connection...")
|
print("Testing connection...")
|
||||||
|
|
||||||
return self:server()
|
return self:api()
|
||||||
:andThen(function(server)
|
:andThen(function(api)
|
||||||
return server:getInfo()
|
local ok, info = api:getInfo():await()
|
||||||
end)
|
|
||||||
:andThen(function(result)
|
if not ok then
|
||||||
|
return Promise.reject(info)
|
||||||
|
end
|
||||||
|
|
||||||
print("Server found!")
|
print("Server found!")
|
||||||
print("Protocol version:", result.protocolVersion)
|
print("Protocol version:", info.protocolVersion)
|
||||||
print("Server version:", result.serverVersion)
|
print("Server version:", info.serverVersion)
|
||||||
|
end)
|
||||||
|
:catch(function(err)
|
||||||
|
if err == Api.Error.ServerIdMismatch then
|
||||||
|
self:restart()
|
||||||
|
return self:connect()
|
||||||
|
else
|
||||||
|
return Promise.reject(err)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Plugin:togglePolling()
|
function Plugin:togglePolling()
|
||||||
if self._polling then
|
if self._polling then
|
||||||
self:stopPolling()
|
return self:stopPolling()
|
||||||
|
|
||||||
return Promise.resolve(nil)
|
|
||||||
else
|
else
|
||||||
return self:startPolling()
|
return self:startPolling()
|
||||||
end
|
end
|
||||||
@@ -94,31 +116,51 @@ end
|
|||||||
|
|
||||||
function Plugin:stopPolling()
|
function Plugin:stopPolling()
|
||||||
if not self._polling then
|
if not self._polling then
|
||||||
return
|
return Promise.resolve(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
print("Stopped polling.")
|
print("Stopped polling.")
|
||||||
|
|
||||||
self._polling = false
|
self._polling = false
|
||||||
self._label.Enabled = false
|
self._label.Enabled = false
|
||||||
|
|
||||||
|
return Promise.resolve(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Plugin:_pull(server, project, routes)
|
function Plugin:_pull(api, project, routes)
|
||||||
local items = server:read(routes):await()
|
return api:read(routes)
|
||||||
|
:andThen(function(items)
|
||||||
|
for index = 1, #routes do
|
||||||
|
local itemRoute = routes[index]
|
||||||
|
local partitionName = itemRoute[1]
|
||||||
|
local partition = project.partitions[partitionName]
|
||||||
|
local item = items[index]
|
||||||
|
|
||||||
for index = 1, #routes do
|
local partitionRoute = collectMatch(partition.target, "[^.]+")
|
||||||
local route = routes[index]
|
|
||||||
local partitionName = route[1]
|
|
||||||
local partition = project.partitions[partitionName]
|
|
||||||
local item = items[index]
|
|
||||||
|
|
||||||
local fullRoute = collectMatch(partition.target, "[^.]+")
|
-- If the item route's length was 1, we need to rename the instance to
|
||||||
for i = 2, #route do
|
-- line up with the partition's root object name.
|
||||||
table.insert(fullRoute, routes[index][i])
|
--
|
||||||
end
|
-- This is a HACK!
|
||||||
|
if #itemRoute == 1 then
|
||||||
|
if item then
|
||||||
|
local objectName = partition.target:match("[^.]+$")
|
||||||
|
item.Name = objectName
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
Reconciler.reconcileRoute(fullRoute, item)
|
local fullRoute = {}
|
||||||
end
|
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Plugin:startPolling()
|
function Plugin:startPolling()
|
||||||
@@ -131,48 +173,81 @@ function Plugin:startPolling()
|
|||||||
self._polling = true
|
self._polling = true
|
||||||
self._label.Enabled = true
|
self._label.Enabled = true
|
||||||
|
|
||||||
return self:server()
|
return self:api()
|
||||||
:andThen(function(server)
|
:andThen(function(api)
|
||||||
self:syncIn():await()
|
local syncOk, result = self:syncIn():await()
|
||||||
|
|
||||||
local project = server:getInfo():await().project
|
if not syncOk then
|
||||||
|
return Promise.reject(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
local infoOk, info = api:getInfo():await()
|
||||||
|
|
||||||
|
if not infoOk then
|
||||||
|
return Promise.reject(info)
|
||||||
|
end
|
||||||
|
|
||||||
while self._polling do
|
while self._polling do
|
||||||
local changes = server:getChanges():await()
|
local changes = api:getChanges():await()
|
||||||
|
|
||||||
local routes = {}
|
if #changes > 0 then
|
||||||
|
local routes = {}
|
||||||
|
|
||||||
for _, change in ipairs(changes) do
|
for _, change in ipairs(changes) do
|
||||||
table.insert(routes, change.route)
|
table.insert(routes, change.route)
|
||||||
|
end
|
||||||
|
|
||||||
|
local pullOk, pullResult = self:_pull(api, info.project, routes):await()
|
||||||
|
|
||||||
|
if not pullOk then
|
||||||
|
return Promise.reject(pullResult)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self:_pull(server, project, routes)
|
|
||||||
|
|
||||||
wait(Config.pollingRate)
|
wait(Config.pollingRate)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
:catch(function()
|
:catch(function(err)
|
||||||
self:stopPolling()
|
self:stopPolling()
|
||||||
|
|
||||||
|
if err == Api.Error.ServerIdMismatch then
|
||||||
|
self:restart()
|
||||||
|
return self:startPolling()
|
||||||
|
else
|
||||||
|
return Promise.reject(err)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Plugin:syncIn()
|
function Plugin:syncIn()
|
||||||
print("Syncing from server...")
|
print("Syncing from server...")
|
||||||
|
|
||||||
return self:server()
|
return self:api()
|
||||||
:andThen(function(server)
|
:andThen(function(api)
|
||||||
local project = server:getInfo():await().project
|
local ok, info = api:getInfo():await()
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
return Promise.reject(info)
|
||||||
|
end
|
||||||
|
|
||||||
local routes = {}
|
local routes = {}
|
||||||
|
|
||||||
for name in pairs(project.partitions) do
|
for name in pairs(info.project.partitions) do
|
||||||
table.insert(routes, {name})
|
table.insert(routes, {name})
|
||||||
end
|
end
|
||||||
|
|
||||||
self:_pull(server, project, routes)
|
self:_pull(api, info.project, routes)
|
||||||
|
|
||||||
print("Sync successful!")
|
print("Sync successful!")
|
||||||
end)
|
end)
|
||||||
|
:catch(function(err)
|
||||||
|
if err == Api.Error.ServerIdMismatch then
|
||||||
|
self:restart()
|
||||||
|
return self:syncIn()
|
||||||
|
else
|
||||||
|
return Promise.reject(err)
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Plugin
|
return Plugin
|
||||||
|
|||||||
@@ -221,15 +221,11 @@ function Promise:await()
|
|||||||
local ok = bindable.Event:Wait()
|
local ok = bindable.Event:Wait()
|
||||||
bindable:Destroy()
|
bindable:Destroy()
|
||||||
|
|
||||||
if not ok then
|
return ok, unpack(result)
|
||||||
error(tostring(result[1]), 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
return unpack(result)
|
|
||||||
elseif self._status == Promise.Status.Resolved then
|
elseif self._status == Promise.Status.Resolved then
|
||||||
return unpack(self._value)
|
return true, unpack(self._value)
|
||||||
elseif self._status == Promise.Status.Rejected then
|
elseif self._status == Promise.Status.Rejected then
|
||||||
error(tostring(self._value[1]), 2)
|
return false, unpack(self._value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -242,7 +238,10 @@ function Promise:_resolve(...)
|
|||||||
if Promise.is((...)) then
|
if Promise.is((...)) then
|
||||||
-- Without this warning, arguments sometimes mysteriously disappear
|
-- Without this warning, arguments sometimes mysteriously disappear
|
||||||
if select("#", ...) > 1 then
|
if select("#", ...) > 1 then
|
||||||
local message = ("When returning a Promise from andThen, extra arguments are discarded! See:\n\n%s"):format(
|
local message = (
|
||||||
|
"When returning a Promise from andThen, extra arguments are " ..
|
||||||
|
"discarded! See:\n\n%s"
|
||||||
|
):format(
|
||||||
self._source
|
self._source
|
||||||
)
|
)
|
||||||
warn(message)
|
warn(message)
|
||||||
|
|||||||
@@ -67,4 +67,196 @@ return function()
|
|||||||
expect(promise._value[1]:find("hahah")).to.be.ok()
|
expect(promise._value[1]:find("hahah")).to.be.ok()
|
||||||
end)
|
end)
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -1,264 +1,214 @@
|
|||||||
local Reconciler = {}
|
local RouteMap = require(script.Parent.RouteMap)
|
||||||
|
|
||||||
--[[
|
local function classEqual(rbx, className)
|
||||||
The set of file names that should pass as init files
|
if className == "*" then
|
||||||
These files usurp their parents.
|
return true
|
||||||
]]
|
|
||||||
local initNames = {
|
|
||||||
["init.lua"] = true,
|
|
||||||
["init.server.lua"] = true,
|
|
||||||
["init.client.lua"] = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local function isInit(item, itemFileName)
|
|
||||||
if item and item.type == "dir" then
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return initNames[itemFileName] or false
|
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
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Determines if the given VFS item has an init file. Yields information about
|
Attempts to match up Roblox instances and object specifiers for
|
||||||
the file.
|
reconciliation.
|
||||||
]]
|
|
||||||
local function findInit(item)
|
|
||||||
if item.type ~= "dir" then
|
|
||||||
return nil, nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for childFileName, childItem in pairs(item.children) do
|
An object is considered a match if they have the same Name and ClassName.
|
||||||
if isInit(childItem, childFileName) then
|
|
||||||
return childItem, childFileName
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
local Reconciler = {}
|
||||||
Given a VFS item, returns a Name and ClassName for a corresponding Roblox
|
Reconciler.__index = Reconciler
|
||||||
instance.
|
|
||||||
|
|
||||||
Doesn't take into account init files.
|
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.
|
||||||
]]
|
]]
|
||||||
local function itemToName(item, fileName)
|
function Reconciler:_reconcileChildren(rbx, item)
|
||||||
if item and item.type == "dir" then
|
local visited = {}
|
||||||
return fileName, "Folder"
|
local rbxChildren = rbx:GetChildren()
|
||||||
elseif item and item.type == "file" or not item then
|
|
||||||
if fileName:find("%.server%.lua$") then
|
-- Reconcile any children that were added or updated
|
||||||
return fileName:match("^(.-)%.server%.lua$"), "Script"
|
while true do
|
||||||
elseif fileName:find("%.client%.lua$") then
|
local itemChild, rbxChild = findNextChildPair(item.Children, rbxChildren, visited)
|
||||||
return fileName:match("^(.-)%.client%.lua$"), "LocalScript"
|
|
||||||
elseif fileName:find("%.lua") then
|
if not itemChild then
|
||||||
return fileName:match("^(.-)%.lua$"), "ModuleScript"
|
break
|
||||||
else
|
|
||||||
return fileName, "StringValue"
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
error("unknown item type " .. tostring(item.type))
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Given a VFS item, assigns all relevant values (except Name!) to a Roblox
|
Construct a new Roblox object from the given item.
|
||||||
instance.
|
|
||||||
]]
|
]]
|
||||||
local function setValues(rbx, item, fileName)
|
function Reconciler:_reify(item)
|
||||||
local _, className = itemToName(item, fileName)
|
local className = item.ClassName
|
||||||
|
|
||||||
if className:find("Script") then
|
-- "*" represents a match of any class. It reifies as a folder!
|
||||||
rbx.Source = item.contents
|
if className == "*" then
|
||||||
else
|
className = "Folder"
|
||||||
rbx.Value = item.contents
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Reconciler._reifyShallow(item, fileName)
|
|
||||||
if item.type == "dir" then
|
|
||||||
local initItem, initFileName = findInit(item)
|
|
||||||
|
|
||||||
if initItem then
|
|
||||||
local rbx = Reconciler._reify(initItem, initFileName)
|
|
||||||
rbx.Name = fileName
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
else
|
|
||||||
local rbx = Instance.new("Folder")
|
|
||||||
rbx.Name = fileName
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
end
|
|
||||||
elseif item.type == "file" then
|
|
||||||
local objectName, className = itemToName(item, fileName)
|
|
||||||
|
|
||||||
local rbx = Instance.new(className)
|
|
||||||
rbx.Name = objectName
|
|
||||||
|
|
||||||
setValues(rbx, item, fileName)
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
else
|
|
||||||
error("unknown item type " .. tostring(item.type))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
Construct a new Roblox instance tree that corresponds to the given VFS item.
|
|
||||||
]]
|
|
||||||
function Reconciler._reify(item, fileName, parent)
|
|
||||||
local rbx = Reconciler._reifyShallow(item, fileName)
|
|
||||||
|
|
||||||
if item.type == "dir" then
|
|
||||||
for childFileName, childItem in pairs(item.children) do
|
|
||||||
if not isInit(childItem, childFileName) then
|
|
||||||
local childRbx = Reconciler._reify(childItem, childFileName)
|
|
||||||
childRbx.Parent = rbx
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
rbx.Parent = parent
|
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
|
return rbx
|
||||||
end
|
end
|
||||||
|
|
||||||
function Reconciler.reconcile(rbx, item, fileName, parent)
|
--[[
|
||||||
-- Item was deleted!
|
Clears any state that the Reconciler has, effectively restarting it.
|
||||||
if not item then
|
]]
|
||||||
if isInit(item, fileName) then
|
function Reconciler:clear()
|
||||||
if not parent then
|
self._routeMap:clear()
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Un-usurp parent!
|
|
||||||
local newParent = Instance.new("Folder")
|
|
||||||
newParent.Name = parent.Name
|
|
||||||
|
|
||||||
for _, child in ipairs(parent:GetChildren()) do
|
|
||||||
child.Parent = newParent
|
|
||||||
end
|
|
||||||
|
|
||||||
newParent.Parent = parent.Parent
|
|
||||||
parent:Destroy()
|
|
||||||
|
|
||||||
return
|
|
||||||
else
|
|
||||||
if rbx then
|
|
||||||
rbx:Destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if item.type == "dir" then
|
|
||||||
-- Folder was created!
|
|
||||||
if not rbx then
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
|
|
||||||
local initItem, initFileName = findInit(item)
|
|
||||||
|
|
||||||
if initItem then
|
|
||||||
local _, initClassName = itemToName(initItem, initFileName)
|
|
||||||
|
|
||||||
if rbx.ClassName == initClassName then
|
|
||||||
setValues(rbx, initItem, initFileName)
|
|
||||||
else
|
|
||||||
rbx:Destroy()
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if rbx.ClassName ~= "Folder" then
|
|
||||||
rbx:Destroy()
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local visitedChildren = {}
|
|
||||||
|
|
||||||
for childFileName, childItem in pairs(item.children) do
|
|
||||||
if not isInit(childItem, childFileName) then
|
|
||||||
local childName = itemToName(childItem, childFileName)
|
|
||||||
|
|
||||||
visitedChildren[childName] = true
|
|
||||||
|
|
||||||
Reconciler.reconcile(rbx:FindFirstChild(childName), childItem, childFileName, rbx)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, childRbx in ipairs(rbx:GetChildren()) do
|
|
||||||
-- Child was deleted!
|
|
||||||
if not visitedChildren[childRbx.Name] then
|
|
||||||
childRbx:Destroy()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
elseif item.type == "file" then
|
|
||||||
if isInit(item, fileName) then
|
|
||||||
-- Usurp our container!
|
|
||||||
local _, className = itemToName(item, fileName)
|
|
||||||
|
|
||||||
if parent.ClassName == className then
|
|
||||||
rbx = parent
|
|
||||||
else
|
|
||||||
rbx = Reconciler._reify(item, fileName, parent.Parent)
|
|
||||||
rbx.Name = parent.Name
|
|
||||||
|
|
||||||
for _, child in ipairs(parent:GetChildren()) do
|
|
||||||
child.Parent = rbx
|
|
||||||
end
|
|
||||||
|
|
||||||
parent:Destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
setValues(rbx, item, fileName)
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
else
|
|
||||||
if not rbx then
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
|
|
||||||
local _, className = itemToName(item, fileName)
|
|
||||||
|
|
||||||
if rbx.ClassName ~= className then
|
|
||||||
rbx:Destroy()
|
|
||||||
return Reconciler._reify(item, fileName, parent)
|
|
||||||
end
|
|
||||||
|
|
||||||
setValues(rbx, item, fileName)
|
|
||||||
|
|
||||||
return rbx
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error("unknown item type " .. tostring(item.type))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Reconciler.reconcileRoute(route, item)
|
--[[
|
||||||
local location = game
|
Apply the changes represented by the given item to a Roblox object that's a
|
||||||
|
child of the given instance.
|
||||||
for i = 1, #route - 1 do
|
]]
|
||||||
local piece = route[i]
|
function Reconciler:reconcile(rbx, item)
|
||||||
local newLocation = location:FindFirstChild(piece)
|
-- Item was deleted
|
||||||
|
if not item then
|
||||||
if not newLocation then
|
if rbx then
|
||||||
newLocation = Instance.new("Folder")
|
rbx:Destroy()
|
||||||
newLocation.Name = piece
|
|
||||||
newLocation.Parent = location
|
|
||||||
end
|
end
|
||||||
|
|
||||||
location = newLocation
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local fileName = route[#route]
|
-- Item was created!
|
||||||
|
if not rbx then
|
||||||
|
return self:_reify(item)
|
||||||
|
end
|
||||||
|
|
||||||
local name = itemToName(item, fileName)
|
-- Item changed type!
|
||||||
local rbx = location:FindFirstChild(name)
|
if not classEqual(rbx, item.ClassName) then
|
||||||
Reconciler.reconcile(rbx, item, fileName, location)
|
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)
|
||||||
|
|
||||||
|
if item.Route then
|
||||||
|
self._routeMap:insert(item.Route, rbx)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
return Reconciler
|
return Reconciler
|
||||||
|
|||||||
103
plugin/src/RouteMap.lua
Normal file
103
plugin/src/RouteMap.lua
Normal 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 _, connection in pairs(self._connectionsByRbx) do
|
||||||
|
connection: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
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
40
plugin/src/Version.lua
Normal 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
|
||||||
28
plugin/src/Version.spec.lua
Normal file
28
plugin/src/Version.spec.lua
Normal 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
|
||||||
2
plugin/tests/runTests.server.lua
Normal file
2
plugin/tests/runTests.server.lua
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
local TestEZ = require(game.ReplicatedStorage.TestEZ)
|
||||||
|
TestEZ.TestBootstrap:run(game.ReplicatedStorage.Rojo.plugin)
|
||||||
26
rojo.json
26
rojo.json
@@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "rojo",
|
|
||||||
"servePort": 8000,
|
|
||||||
"partitions": {
|
|
||||||
"plugin": {
|
|
||||||
"path": "plugin/src",
|
|
||||||
"target": "ReplicatedStorage.Rojo"
|
|
||||||
},
|
|
||||||
"modules/Roact": {
|
|
||||||
"path": "modules/Roact/lib",
|
|
||||||
"target": "ReplicatedStorage.Rojo.modules.Roact"
|
|
||||||
},
|
|
||||||
"modules/Rodux": {
|
|
||||||
"path": "modules/Rodux/lib",
|
|
||||||
"target": "ReplicatedStorage.Rojo.modules.Rodux"
|
|
||||||
},
|
|
||||||
"modules/RoactRodux": {
|
|
||||||
"path": "modules/RoactRodux/lib",
|
|
||||||
"target": "ReplicatedStorage.Rojo.modules.RoactRodux"
|
|
||||||
},
|
|
||||||
"modules/TestEZ": {
|
|
||||||
"path": "modules/TestEZ/lib",
|
|
||||||
"target": "ReplicatedStorage.TestEZ"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5
server/.editorconfig
Normal file
5
server/.editorconfig
Normal 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
2
server/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
479
Cargo.lock → server/Cargo.lock
generated
479
Cargo.lock → server/Cargo.lock
generated
@@ -1,16 +1,3 @@
|
|||||||
[root]
|
|
||||||
name = "rojo"
|
|
||||||
version = "0.2.0"
|
|
||||||
dependencies = [
|
|
||||||
"clap 2.28.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.18 (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.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_derive 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_json 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -20,9 +7,20 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "aho-corasick"
|
||||||
version = "0.10.2"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ansi_term"
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "ascii"
|
name = "ascii"
|
||||||
@@ -31,13 +29,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.3"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -61,7 +58,7 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
"gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -70,7 +67,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brotli-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"brotli-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -85,7 +82,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.3"
|
version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -98,8 +95,8 @@ name = "chrono"
|
|||||||
version = "0.2.25"
|
version = "0.2.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num 0.1.40 (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.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -109,13 +106,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.28.0"
|
version = "2.31.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term 0.10.2 (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.3 (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)",
|
"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)",
|
"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)",
|
"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)",
|
"vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -188,18 +185,18 @@ name = "env_logger"
|
|||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filetime"
|
name = "filetime"
|
||||||
version = "0.1.14"
|
version = "0.1.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (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.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -207,7 +204,7 @@ name = "flate2"
|
|||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.33 (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)",
|
"miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -218,7 +215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -226,24 +223,22 @@ name = "fsevent-sys"
|
|||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuchsia-zircon"
|
name = "fuchsia-zircon"
|
||||||
version = "0.2.1"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuchsia-zircon-sys"
|
name = "fuchsia-zircon-sys"
|
||||||
version = "0.2.0"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
|
||||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gcc"
|
name = "gcc"
|
||||||
@@ -265,12 +260,12 @@ name = "inotify"
|
|||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.3.4"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -282,15 +277,31 @@ dependencies = [
|
|||||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.33"
|
version = "0.2.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.3.8"
|
version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.1"
|
||||||
|
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)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matches"
|
name = "matches"
|
||||||
@@ -302,7 +313,15 @@ name = "memchr"
|
|||||||
version = "0.1.11"
|
version = "0.1.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -310,7 +329,7 @@ name = "mime"
|
|||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -329,8 +348,8 @@ name = "miniz-sys"
|
|||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc 1.0.3 (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.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -339,13 +358,13 @@ version = "0.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (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.8 (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)",
|
"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)",
|
"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)",
|
"slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
"time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -355,7 +374,7 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
@@ -367,24 +386,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"buf_redux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"buf_redux 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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 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)",
|
"mime_guess 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.18 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "net2"
|
name = "net2"
|
||||||
version = "0.2.31"
|
version = "0.2.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.4 (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)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -393,7 +410,7 @@ version = "0.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -402,47 +419,47 @@ version = "4.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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 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)",
|
"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)",
|
"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)",
|
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (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)",
|
"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)",
|
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.1.40"
|
version = "0.1.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-integer 0.1.35 (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.34 (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.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.35"
|
version = "0.1.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-iter"
|
name = "num-iter"
|
||||||
version = "0.1.34"
|
version = "0.1.35"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-integer 0.1.35 (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.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.1.40"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -473,7 +490,7 @@ version = "0.7.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
"phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -485,22 +502,44 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "proc-macro2"
|
||||||
version = "0.3.15"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.3.18"
|
version = "0.3.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.1.32"
|
version = "0.1.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -508,7 +547,7 @@ name = "redox_termios"
|
|||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
"redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -523,11 +562,54 @@ dependencies = [
|
|||||||
"utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
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.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)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
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.3"
|
||||||
|
dependencies = [
|
||||||
|
"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.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.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]]
|
[[package]]
|
||||||
name = "rouille"
|
name = "rouille"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -535,16 +617,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"brotli2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"multipart 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.18 (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)",
|
"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)",
|
"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)",
|
"term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"time 0.1.38 (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)",
|
"tiny_http 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -554,11 +636,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.0"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -566,42 +647,43 @@ name = "serde"
|
|||||||
version = "0.6.15"
|
version = "0.6.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.21"
|
version = "1.0.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.21"
|
version = "1.0.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_derive_internals 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"syn 0.11.11 (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]]
|
[[package]]
|
||||||
name = "serde_derive_internals"
|
name = "serde_derive_internals"
|
||||||
version = "0.17.0"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"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)",
|
||||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.6"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num-traits 0.1.40 (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.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -621,33 +703,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.6.0"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.11.11"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
"proc-macro2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"synom 0.11.3 (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.0.4 (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 = "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)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempdir"
|
name = "tempdir"
|
||||||
version = "0.3.5"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.3.18 (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]]
|
[[package]]
|
||||||
@@ -664,8 +739,8 @@ name = "termion"
|
|||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.33 (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.32 (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)",
|
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -683,7 +758,7 @@ version = "2.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.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.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -695,30 +770,42 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "thread_local"
|
||||||
version = "0.1.38"
|
version = "0.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
]
|
||||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"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]]
|
[[package]]
|
||||||
name = "tiny_http"
|
name = "tiny_http"
|
||||||
version = "0.5.8"
|
version = "0.5.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.8 (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)",
|
"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]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -739,9 +826,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.0.4"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unreachable"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "0.2.38"
|
version = "0.2.38"
|
||||||
@@ -754,7 +849,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "1.6.0"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@@ -767,12 +862,17 @@ name = "utf8-ranges"
|
|||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-ranges"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "0.1.18"
|
version = "0.1.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand 0.3.18 (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)",
|
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -781,12 +881,18 @@ name = "vec_map"
|
|||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "void"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.0.1"
|
version = "2.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"same-file 1.0.0 (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]]
|
[[package]]
|
||||||
@@ -794,11 +900,30 @@ name = "winapi"
|
|||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"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]]
|
[[package]]
|
||||||
name = "winapi-build"
|
name = "winapi-build"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ws2_32-sys"
|
name = "ws2_32-sys"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -810,9 +935,10 @@ dependencies = [
|
|||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
|
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
|
||||||
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
|
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
||||||
|
"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 ascii 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae7d751998c189c1d4468cf0a39bb2eae052a9c58d50ebb3b9591ee3813ad50"
|
||||||
"checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860"
|
"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.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 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"
|
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
|
||||||
@@ -820,11 +946,11 @@ dependencies = [
|
|||||||
"checksum brotli2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ea9d0bbab1235017a09226b079ed733bca4bf9ecb6b6102bd01aac79ea082dca"
|
"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 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 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 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 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 chunked_transfer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "498d20a7aaf62625b9bf26e637cf7736417cde1d0c99f1d04d1170229a85cf87"
|
||||||
"checksum clap 2.28.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc34bf7d5d66268b466b9852bca925ec1d2650654dab4da081e63fd230145c2e"
|
"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 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 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"
|
"checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
|
||||||
@@ -834,77 +960,92 @@ dependencies = [
|
|||||||
"checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
|
"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 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 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 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 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"
|
"checksum fsevent-sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a772d36c338d07a032d5375a36f15f9a7043bf0cb8ce7cee658e037c6032874"
|
||||||
"checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159"
|
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||||
"checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82"
|
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||||
"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
|
"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 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 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 kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||||
"checksum libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "5ba3df4dcb460b9dfbd070d41c94c19209620c191b0340b929ce748a2bcd42d2"
|
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
||||||
"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"
|
"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"
|
"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
|
||||||
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
|
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
|
||||||
|
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
||||||
"checksum mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ec0c2f4d901bf1d4a2192a40b4b570ae3b19c51243e549defc1de741940aa787"
|
"checksum mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ec0c2f4d901bf1d4a2192a40b4b570ae3b19c51243e549defc1de741940aa787"
|
||||||
"checksum mime_guess 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "422acd80644209a8c8c66a20514840d8c092eb1eab2898ca7c548cc1d64c8998"
|
"checksum mime_guess 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "422acd80644209a8c8c66a20514840d8c092eb1eab2898ca7c548cc1d64c8998"
|
||||||
"checksum miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "609ce024854aeb19a0ef7567d348aaa5a746b32fb72e336df7fcc16869d7e2b4"
|
"checksum miniz-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "609ce024854aeb19a0ef7567d348aaa5a746b32fb72e336df7fcc16869d7e2b4"
|
||||||
"checksum mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a637d1ca14eacae06296a008fa7ad955347e34efcb5891cfd8ba05491a37907e"
|
"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 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 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 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 notify 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5c3812da3098f210a0bb440f9c008471a031aa4c1de07a264fdd75456c95a4eb"
|
||||||
"checksum num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "a311b77ebdc5dd4cf6449d81e4135d9f0e3b153839ac90e648a8ef538f923525"
|
"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
|
||||||
"checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba"
|
"checksum num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f8d26da319fb45674985c78f1d1caf99aa4941f785d384a2ae36d0740bc3e2fe"
|
||||||
"checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01"
|
"checksum num-iter 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "4b226df12c5a59b63569dd57fafb926d91b385dfce33d8074a412411b689d593"
|
||||||
"checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0"
|
"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 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 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_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_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 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 proc-macro2 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "388d7ea47318c5ccdeb9ba6312cee7d3f65dd2804be8580a170fce410d50b786"
|
||||||
"checksum rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "6475140dfd8655aeb72e1fd4b7a1cc1c202be65d71669476e392fe62532b9edd"
|
"checksum quote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0ff51282f28dc1b53fd154298feaa2e77c5ea0dba68e1fd8b03b72fbe13d2a"
|
||||||
"checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0"
|
"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 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.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
|
||||||
|
"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.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
|
||||||
|
"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 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 rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||||
"checksum same-file 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "70a18720d745fb9ca6a041b37cb36d0b21066006b6cff8b5b360142d4b81fb60"
|
"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 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97b18e9e53de541f11e497357d6c5eaeb39f0cb9c8734e274abe4935f6991fa"
|
||||||
"checksum serde 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6eda663e865517ee783b0891a3f6eb3a253e0b0dabb46418969ee9635beadd9e"
|
"checksum serde 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "d3bcee660dcde8f52c3765dd9ca5ee36b4bf35470a738eb0bd5a8752b0389645"
|
||||||
"checksum serde_derive 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)" = "652bc323d694dc925829725ec6c890156d8e70ae5202919869cb00fe2eff3788"
|
"checksum serde_derive 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "f1711ab8b208541fa8de00425f6a577d90f27bb60724d2bb5fd911314af9668f"
|
||||||
"checksum serde_derive_internals 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32f1926285523b2db55df263d2aa4eb69ddcfa7a7eade6430323637866b513ab"
|
"checksum serde_derive_internals 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "89b340a48245bc03ddba31d0ff1709c118df90edc6adabaca4aac77aea181cce"
|
||||||
"checksum serde_json 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e4586746d1974a030c48919731ecffd0ed28d0c40749d0d18d43b3a7d6c9b20e"
|
"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 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 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 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 strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
"checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
|
||||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
|
||||||
"checksum term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281"
|
"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 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"
|
"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
|
||||||
"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
|
"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
|
||||||
"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
|
"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
|
||||||
"checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520"
|
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
||||||
"checksum tiny_http 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "016f040cfc9b5be610de3619eaaa57017fa0b0b678187327bde75fc146e2a41f"
|
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
|
||||||
|
"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-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-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-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 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 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 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 vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c"
|
||||||
"checksum walkdir 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b6d201f4f8998a837196b6de9c73e35af14c992cbb92c4ab641d2c2dce52de"
|
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||||
|
"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.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||||
|
"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-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||||
|
"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"
|
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "0.2.0"
|
version = "0.4.3"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
description = "A tool to create robust Roblox projects"
|
description = "A tool to create robust Roblox projects"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -18,3 +18,5 @@ serde_derive = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
notify = "4.0.0"
|
notify = "4.0.0"
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
|
regex = "0.2"
|
||||||
|
lazy_static = "1.0"
|
||||||
97
server/src/bin.rs
Normal file
97
server/src/bin.rs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
#[macro_use] extern crate rouille;
|
||||||
|
#[macro_use] extern crate clap;
|
||||||
|
#[macro_use] extern crate lazy_static;
|
||||||
|
extern crate notify;
|
||||||
|
extern crate rand;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate regex;
|
||||||
|
|
||||||
|
pub mod web;
|
||||||
|
pub mod core;
|
||||||
|
pub mod project;
|
||||||
|
pub mod pathext;
|
||||||
|
pub mod vfs;
|
||||||
|
pub mod rbx;
|
||||||
|
pub mod plugin;
|
||||||
|
pub mod plugins;
|
||||||
|
pub mod commands;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
use pathext::canonicalish;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let matches = clap_app!(rojo =>
|
||||||
|
(version: env!("CARGO_PKG_VERSION"))
|
||||||
|
(author: env!("CARGO_PKG_AUTHORS"))
|
||||||
|
(about: env!("CARGO_PKG_DESCRIPTION"))
|
||||||
|
|
||||||
|
(@subcommand init =>
|
||||||
|
(about: "Creates a new Rojo project")
|
||||||
|
(@arg PATH: "Path to the place to create the project. Defaults to the current directory.")
|
||||||
|
)
|
||||||
|
|
||||||
|
(@subcommand serve =>
|
||||||
|
(about: "Serves the project's files for use with the Rojo Studio plugin.")
|
||||||
|
(@arg PROJECT: "Path to the project to serve. Defaults to the current directory.")
|
||||||
|
(@arg port: --port +takes_value "The port to listen on. Defaults to 8000.")
|
||||||
|
)
|
||||||
|
|
||||||
|
(@subcommand pack =>
|
||||||
|
(about: "Packs the project into a GUI installer bundle. NOT YET IMPLEMENTED!")
|
||||||
|
(@arg PROJECT: "Path to the project to pack. Defaults to the current directory.")
|
||||||
|
)
|
||||||
|
|
||||||
|
(@arg verbose: --verbose "Enable extended logging.")
|
||||||
|
).get_matches();
|
||||||
|
|
||||||
|
let verbose = match matches.occurrences_of("verbose") {
|
||||||
|
0 => false,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
match matches.subcommand() {
|
||||||
|
("init", sub_matches) => {
|
||||||
|
let sub_matches = sub_matches.unwrap();
|
||||||
|
let project_path = Path::new(sub_matches.value_of("PATH").unwrap_or("."));
|
||||||
|
let full_path = canonicalish(project_path);
|
||||||
|
|
||||||
|
commands::init(&full_path);
|
||||||
|
},
|
||||||
|
("serve", sub_matches) => {
|
||||||
|
let sub_matches = sub_matches.unwrap();
|
||||||
|
|
||||||
|
let project_path = match sub_matches.value_of("PROJECT") {
|
||||||
|
Some(v) => canonicalish(PathBuf::from(v)),
|
||||||
|
None => std::env::current_dir().unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let port = {
|
||||||
|
match sub_matches.value_of("port") {
|
||||||
|
Some(source) => match source.parse::<u64>() {
|
||||||
|
Ok(value) => Some(value),
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("Invalid port '{}'", source);
|
||||||
|
process::exit(1);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commands::serve(&project_path, verbose, port);
|
||||||
|
},
|
||||||
|
("pack", _) => {
|
||||||
|
eprintln!("'rojo pack' is not yet implemented!");
|
||||||
|
process::exit(1);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
eprintln!("Please specify a subcommand!");
|
||||||
|
eprintln!("Try 'rojo help' for information.");
|
||||||
|
process::exit(1);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
16
server/src/commands/init.rs
Normal file
16
server/src/commands/init.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
use project::Project;
|
||||||
|
|
||||||
|
pub fn init(project_path: &PathBuf) {
|
||||||
|
match Project::init(project_path) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Created new empty project at {}", project_path.display());
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to create new project.\n{}", e);
|
||||||
|
process::exit(1);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
5
server/src/commands/mod.rs
Normal file
5
server/src/commands/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod serve;
|
||||||
|
mod init;
|
||||||
|
|
||||||
|
pub use self::serve::*;
|
||||||
|
pub use self::init::*;
|
||||||
99
server/src/commands/serve.rs
Normal file
99
server/src/commands/serve.rs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
use rand;
|
||||||
|
|
||||||
|
use project::{Project, ProjectLoadError};
|
||||||
|
use plugin::{PluginChain};
|
||||||
|
use plugins::{DefaultPlugin, JsonModelPlugin, ScriptPlugin};
|
||||||
|
use vfs::{VfsSession, VfsWatcher};
|
||||||
|
use web;
|
||||||
|
|
||||||
|
pub fn serve(project_path: &PathBuf, verbose: bool, port: Option<u64>) {
|
||||||
|
let server_id = rand::random::<u64>();
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
println!("Attempting to locate project at {}...", project_path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
let project = match Project::load(project_path) {
|
||||||
|
Ok(v) => {
|
||||||
|
println!("Using project from {}", project_path.display());
|
||||||
|
v
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
match err {
|
||||||
|
ProjectLoadError::InvalidJson(serde_err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Found invalid JSON!\nProject in: {}\nError: {}",
|
||||||
|
project_path.display(),
|
||||||
|
serde_err,
|
||||||
|
);
|
||||||
|
|
||||||
|
process::exit(1);
|
||||||
|
},
|
||||||
|
ProjectLoadError::FailedToOpen | ProjectLoadError::FailedToRead => {
|
||||||
|
eprintln!("Found project file, but failed to read it!");
|
||||||
|
eprintln!(
|
||||||
|
"Check the permissions of the project file at\n{}",
|
||||||
|
project_path.display(),
|
||||||
|
);
|
||||||
|
|
||||||
|
process::exit(1);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// Any other error is fine; use the default project.
|
||||||
|
println!("Found no project file, using default project...");
|
||||||
|
Project::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let web_config = web::WebConfig {
|
||||||
|
verbose,
|
||||||
|
port: port.unwrap_or(project.serve_port),
|
||||||
|
server_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref PLUGIN_CHAIN: PluginChain = PluginChain::new(vec![
|
||||||
|
Box::new(ScriptPlugin::new()),
|
||||||
|
Box::new(JsonModelPlugin::new()),
|
||||||
|
Box::new(DefaultPlugin::new()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let vfs = {
|
||||||
|
let mut vfs = VfsSession::new(&PLUGIN_CHAIN);
|
||||||
|
|
||||||
|
for (name, project_partition) in &project.partitions {
|
||||||
|
let path = {
|
||||||
|
let given_path = Path::new(&project_partition.path);
|
||||||
|
|
||||||
|
if given_path.is_absolute() {
|
||||||
|
given_path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
project_path.join(given_path)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vfs.insert_partition(name, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Arc::new(Mutex::new(vfs))
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Server listening on port {}", web_config.port);
|
||||||
|
|
||||||
|
{
|
||||||
|
let vfs = vfs.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
VfsWatcher::new(vfs).start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
web::start(web_config, project.clone(), &PLUGIN_CHAIN, vfs.clone());
|
||||||
|
}
|
||||||
1
server/src/core.rs
Normal file
1
server/src/core.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub type Route = Vec<String>;
|
||||||
@@ -31,9 +31,26 @@ fn test_path_to_route() {
|
|||||||
assert_eq!(path_to_route(root, value), result);
|
assert_eq!(path_to_route(root, value), result);
|
||||||
}
|
}
|
||||||
|
|
||||||
t(Path::new("/a/b/c"), Path::new("/a/b/c/d"), Some(vec!["d".to_string()]));
|
t(
|
||||||
|
Path::new("/a/b/c"),
|
||||||
|
Path::new("/a/b/c/d"),
|
||||||
|
Some(vec!["d".to_string()]),
|
||||||
|
);
|
||||||
t(Path::new("/a/b"), Path::new("a"), None);
|
t(Path::new("/a/b"), Path::new("a"), None);
|
||||||
t(Path::new("C:\\foo"), Path::new("C:\\foo\\bar\\baz"), Some(vec!["bar".to_string(), "baz".to_string()]));
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn test_path_to_route_windows() {
|
||||||
|
fn t(root: &Path, value: &Path, result: Option<Vec<String>>) {
|
||||||
|
assert_eq!(path_to_route(root, value), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
t(
|
||||||
|
Path::new("C:\\foo"),
|
||||||
|
Path::new("C:\\foo\\bar\\baz"),
|
||||||
|
Some(vec!["bar".to_string(), "baz".to_string()]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns the path into an absolute one, using the current working directory if
|
/// Turns the path into an absolute one, using the current working directory if
|
||||||
82
server/src/plugin.rs
Normal file
82
server/src/plugin.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use rbx::RbxInstance;
|
||||||
|
use vfs::VfsItem;
|
||||||
|
use core::Route;
|
||||||
|
|
||||||
|
pub enum TransformFileResult {
|
||||||
|
Value(Option<RbxInstance>),
|
||||||
|
Pass,
|
||||||
|
|
||||||
|
// TODO: Error case
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RbxChangeResult {
|
||||||
|
Write(Option<VfsItem>),
|
||||||
|
Pass,
|
||||||
|
|
||||||
|
// TODO: Error case
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum FileChangeResult {
|
||||||
|
MarkChanged(Option<Vec<Route>>),
|
||||||
|
Pass,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Plugin {
|
||||||
|
/// Invoked when a file is read from the filesystem and needs to be turned
|
||||||
|
/// into a Roblox instance.
|
||||||
|
fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult;
|
||||||
|
|
||||||
|
/// Invoked when a Roblox Instance change is reported by the Roblox Studio
|
||||||
|
/// plugin and needs to be turned into a file to save.
|
||||||
|
fn handle_rbx_change(&self, route: &Route, rbx_item: &RbxInstance) -> RbxChangeResult;
|
||||||
|
|
||||||
|
/// Invoked when a file changes on the filesystem. The result defines what
|
||||||
|
/// routes are marked as needing to be refreshed.
|
||||||
|
fn handle_file_change(&self, route: &Route) -> FileChangeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of plugins that are composed in order.
|
||||||
|
pub struct PluginChain {
|
||||||
|
plugins: Vec<Box<Plugin + Send + Sync>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PluginChain {
|
||||||
|
pub fn new(plugins: Vec<Box<Plugin + Send + Sync>>) -> PluginChain {
|
||||||
|
PluginChain {
|
||||||
|
plugins,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_file(&self, vfs_item: &VfsItem) -> Option<RbxInstance> {
|
||||||
|
for plugin in &self.plugins {
|
||||||
|
match plugin.transform_file(self, vfs_item) {
|
||||||
|
TransformFileResult::Value(rbx_item) => return rbx_item,
|
||||||
|
TransformFileResult::Pass => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_rbx_change(&self, route: &Route, rbx_item: &RbxInstance) -> Option<VfsItem> {
|
||||||
|
for plugin in &self.plugins {
|
||||||
|
match plugin.handle_rbx_change(route, rbx_item) {
|
||||||
|
RbxChangeResult::Write(vfs_item) => return vfs_item,
|
||||||
|
RbxChangeResult::Pass => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_file_change(&self, route: &Route) -> Option<Vec<Route>> {
|
||||||
|
for plugin in &self.plugins {
|
||||||
|
match plugin.handle_file_change(route) {
|
||||||
|
FileChangeResult::MarkChanged(changes) => return changes,
|
||||||
|
FileChangeResult::Pass => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
67
server/src/plugins/default_plugin.rs
Normal file
67
server/src/plugins/default_plugin.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use core::Route;
|
||||||
|
use plugin::{Plugin, PluginChain, TransformFileResult, RbxChangeResult, FileChangeResult};
|
||||||
|
use rbx::{RbxInstance, RbxValue};
|
||||||
|
use vfs::VfsItem;
|
||||||
|
|
||||||
|
/// A plugin with simple transforms:
|
||||||
|
/// * Directories become Folder instances
|
||||||
|
/// * Files become StringValue objects with 'Value' as their contents
|
||||||
|
pub struct DefaultPlugin;
|
||||||
|
|
||||||
|
impl DefaultPlugin {
|
||||||
|
pub fn new() -> DefaultPlugin {
|
||||||
|
DefaultPlugin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for DefaultPlugin {
|
||||||
|
fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult {
|
||||||
|
match vfs_item {
|
||||||
|
&VfsItem::File { ref contents, .. } => {
|
||||||
|
let mut properties = HashMap::new();
|
||||||
|
|
||||||
|
properties.insert("Value".to_string(), RbxValue::String {
|
||||||
|
value: contents.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
TransformFileResult::Value(Some(RbxInstance {
|
||||||
|
name: vfs_item.name().clone(),
|
||||||
|
class_name: "StringValue".to_string(),
|
||||||
|
children: Vec::new(),
|
||||||
|
properties,
|
||||||
|
route: Some(vfs_item.route().to_vec()),
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
&VfsItem::Dir { ref children, .. } => {
|
||||||
|
let mut rbx_children = Vec::new();
|
||||||
|
|
||||||
|
for (_, child_item) in children {
|
||||||
|
match plugins.transform_file(child_item) {
|
||||||
|
Some(rbx_item) => {
|
||||||
|
rbx_children.push(rbx_item);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TransformFileResult::Value(Some(RbxInstance {
|
||||||
|
name: vfs_item.name().clone(),
|
||||||
|
class_name: "*".to_string(),
|
||||||
|
children: rbx_children,
|
||||||
|
properties: HashMap::new(),
|
||||||
|
route: Some(vfs_item.route().to_vec()),
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_file_change(&self, route: &Route) -> FileChangeResult {
|
||||||
|
FileChangeResult::MarkChanged(Some(vec![route.clone()]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_rbx_change(&self, _route: &Route, _rbx_item: &RbxInstance) -> RbxChangeResult {
|
||||||
|
RbxChangeResult::Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
55
server/src/plugins/json_model_plugin.rs
Normal file
55
server/src/plugins/json_model_plugin.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
use core::Route;
|
||||||
|
use plugin::{Plugin, PluginChain, TransformFileResult, RbxChangeResult, FileChangeResult};
|
||||||
|
use rbx::RbxInstance;
|
||||||
|
use vfs::VfsItem;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref JSON_MODEL_PATTERN: Regex = Regex::new(r"^(.*?)\.model\.json$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JsonModelPlugin;
|
||||||
|
|
||||||
|
impl JsonModelPlugin {
|
||||||
|
pub fn new() -> JsonModelPlugin {
|
||||||
|
JsonModelPlugin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for JsonModelPlugin {
|
||||||
|
fn transform_file(&self, _plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult {
|
||||||
|
match vfs_item {
|
||||||
|
&VfsItem::File { ref contents, .. } => {
|
||||||
|
let rbx_name = match JSON_MODEL_PATTERN.captures(vfs_item.name()) {
|
||||||
|
Some(captures) => captures.get(1).unwrap().as_str().to_string(),
|
||||||
|
None => return TransformFileResult::Pass,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rbx_item: RbxInstance = match serde_json::from_str(contents) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Unable to parse JSON Model File named {}: {}", vfs_item.name(), e);
|
||||||
|
|
||||||
|
return TransformFileResult::Pass; // This should be an error in the future!
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
rbx_item.route = Some(vfs_item.route().to_vec());
|
||||||
|
rbx_item.name = rbx_name;
|
||||||
|
|
||||||
|
TransformFileResult::Value(Some(rbx_item))
|
||||||
|
},
|
||||||
|
&VfsItem::Dir { .. } => TransformFileResult::Pass,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_file_change(&self, _route: &Route) -> FileChangeResult {
|
||||||
|
FileChangeResult::Pass
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_rbx_change(&self, _route: &Route, _rbx_item: &RbxInstance) -> RbxChangeResult {
|
||||||
|
RbxChangeResult::Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
7
server/src/plugins/mod.rs
Normal file
7
server/src/plugins/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod default_plugin;
|
||||||
|
mod script_plugin;
|
||||||
|
mod json_model_plugin;
|
||||||
|
|
||||||
|
pub use self::default_plugin::*;
|
||||||
|
pub use self::script_plugin::*;
|
||||||
|
pub use self::json_model_plugin::*;
|
||||||
124
server/src/plugins/script_plugin.rs
Normal file
124
server/src/plugins/script_plugin.rs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use core::Route;
|
||||||
|
use plugin::{Plugin, PluginChain, TransformFileResult, RbxChangeResult, FileChangeResult};
|
||||||
|
use rbx::{RbxInstance, RbxValue};
|
||||||
|
use vfs::VfsItem;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref SERVER_PATTERN: Regex = Regex::new(r"^(.*?)\.server\.lua$").unwrap();
|
||||||
|
static ref CLIENT_PATTERN: Regex = Regex::new(r"^(.*?)\.client\.lua$").unwrap();
|
||||||
|
static ref MODULE_PATTERN: Regex = Regex::new(r"^(.*?)\.lua$").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
static SERVER_INIT: &'static str = "init.server.lua";
|
||||||
|
static CLIENT_INIT: &'static str = "init.client.lua";
|
||||||
|
static MODULE_INIT: &'static str = "init.lua";
|
||||||
|
|
||||||
|
pub struct ScriptPlugin;
|
||||||
|
|
||||||
|
impl ScriptPlugin {
|
||||||
|
pub fn new() -> ScriptPlugin {
|
||||||
|
ScriptPlugin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for ScriptPlugin {
|
||||||
|
fn transform_file(&self, plugins: &PluginChain, vfs_item: &VfsItem) -> TransformFileResult {
|
||||||
|
match vfs_item {
|
||||||
|
&VfsItem::File { ref contents, .. } => {
|
||||||
|
let name = vfs_item.name();
|
||||||
|
|
||||||
|
let (class_name, rbx_name) = {
|
||||||
|
if let Some(captures) = SERVER_PATTERN.captures(name) {
|
||||||
|
("Script".to_string(), captures.get(1).unwrap().as_str().to_string())
|
||||||
|
} else if let Some(captures) = CLIENT_PATTERN.captures(name) {
|
||||||
|
("LocalScript".to_string(), captures.get(1).unwrap().as_str().to_string())
|
||||||
|
} else if let Some(captures) = MODULE_PATTERN.captures(name) {
|
||||||
|
("ModuleScript".to_string(), captures.get(1).unwrap().as_str().to_string())
|
||||||
|
} else {
|
||||||
|
return TransformFileResult::Pass;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut properties = HashMap::new();
|
||||||
|
|
||||||
|
properties.insert("Source".to_string(), RbxValue::String {
|
||||||
|
value: contents.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
TransformFileResult::Value(Some(RbxInstance {
|
||||||
|
name: rbx_name,
|
||||||
|
class_name: class_name,
|
||||||
|
children: Vec::new(),
|
||||||
|
properties,
|
||||||
|
route: Some(vfs_item.route().to_vec()),
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
&VfsItem::Dir { ref children, .. } => {
|
||||||
|
let init_item = {
|
||||||
|
let maybe_item = children.get(SERVER_INIT)
|
||||||
|
.or(children.get(CLIENT_INIT))
|
||||||
|
.or(children.get(MODULE_INIT));
|
||||||
|
|
||||||
|
match maybe_item {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return TransformFileResult::Pass,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rbx_item = match self.transform_file(plugins, init_item) {
|
||||||
|
TransformFileResult::Value(Some(item)) => item,
|
||||||
|
_ => {
|
||||||
|
eprintln!("Inconsistency detected in ScriptPlugin!");
|
||||||
|
return TransformFileResult::Pass;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
rbx_item.name.clear();
|
||||||
|
rbx_item.name.push_str(vfs_item.name());
|
||||||
|
|
||||||
|
for (child_name, child_item) in children {
|
||||||
|
if child_name == init_item.name() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match plugins.transform_file(child_item) {
|
||||||
|
Some(child_rbx_item) => {
|
||||||
|
rbx_item.children.push(child_rbx_item);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TransformFileResult::Value(Some(rbx_item))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_file_change(&self, route: &Route) -> FileChangeResult {
|
||||||
|
let leaf = match route.last() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return FileChangeResult::Pass,
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_init = leaf == SERVER_INIT
|
||||||
|
|| leaf == CLIENT_INIT
|
||||||
|
|| leaf == MODULE_INIT;
|
||||||
|
|
||||||
|
if is_init {
|
||||||
|
let mut changed = route.clone();
|
||||||
|
changed.pop();
|
||||||
|
|
||||||
|
FileChangeResult::MarkChanged(Some(vec![changed]))
|
||||||
|
} else {
|
||||||
|
FileChangeResult::Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_rbx_change(&self, _route: &Route, _rbx_item: &RbxInstance) -> RbxChangeResult {
|
||||||
|
RbxChangeResult::Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ pub enum ProjectLoadError {
|
|||||||
DidNotExist,
|
DidNotExist,
|
||||||
FailedToOpen,
|
FailedToOpen,
|
||||||
FailedToRead,
|
FailedToRead,
|
||||||
Invalid,
|
InvalidJson(serde_json::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -44,10 +44,19 @@ impl fmt::Display for ProjectInitError {
|
|||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ProjectPartition {
|
pub struct ProjectPartition {
|
||||||
|
/// A slash-separated path to a file or folder, relative to the project's
|
||||||
|
/// directory.
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
|
||||||
|
/// A dot-separated route to a Roblox instance, relative to game.
|
||||||
pub target: String,
|
pub target: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a project configured by a user for use with Rojo. Holds anything
|
||||||
|
/// that can be configured with `rojo.json`.
|
||||||
|
///
|
||||||
|
/// In the future, this object will hold dependency information and other handy
|
||||||
|
/// configurables
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
@@ -57,6 +66,7 @@ pub struct Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
|
/// Creates a new empty Project object with the given name.
|
||||||
pub fn new<T: Into<String>>(name: T) -> Project {
|
pub fn new<T: Into<String>>(name: T) -> Project {
|
||||||
Project {
|
Project {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
@@ -64,10 +74,12 @@ impl Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes a new project inside the given folder path.
|
||||||
pub fn init<T: AsRef<Path>>(location: T) -> Result<Project, ProjectInitError> {
|
pub fn init<T: AsRef<Path>>(location: T) -> Result<Project, ProjectInitError> {
|
||||||
let location = location.as_ref();
|
let location = location.as_ref();
|
||||||
let package_path = location.join(PROJECT_FILENAME);
|
let package_path = location.join(PROJECT_FILENAME);
|
||||||
|
|
||||||
|
// We abort if the project file already exists.
|
||||||
match fs::metadata(&package_path) {
|
match fs::metadata(&package_path) {
|
||||||
Ok(_) => return Err(ProjectInitError::AlreadyExists),
|
Ok(_) => return Err(ProjectInitError::AlreadyExists),
|
||||||
Err(_) => {},
|
Err(_) => {},
|
||||||
@@ -78,11 +90,14 @@ impl Project {
|
|||||||
Err(_) => return Err(ProjectInitError::FailedToCreate),
|
Err(_) => return Err(ProjectInitError::FailedToCreate),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Try to give the project a meaningful name.
|
||||||
|
// If we can't, we'll just fall back to a default.
|
||||||
let name = match location.file_name() {
|
let name = match location.file_name() {
|
||||||
Some(v) => v.to_string_lossy().into_owned(),
|
Some(v) => v.to_string_lossy().into_owned(),
|
||||||
None => "new-project".to_string(),
|
None => "new-project".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Configure the project with all of the values we know so far.
|
||||||
let project = Project::new(name);
|
let project = Project::new(name);
|
||||||
let serialized = serde_json::to_string_pretty(&project).unwrap();
|
let serialized = serde_json::to_string_pretty(&project).unwrap();
|
||||||
|
|
||||||
@@ -94,6 +109,8 @@ impl Project {
|
|||||||
Ok(project)
|
Ok(project)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to load a project from the file named PROJECT_FILENAME from the
|
||||||
|
/// given folder.
|
||||||
pub fn load<T: AsRef<Path>>(location: T) -> Result<Project, ProjectLoadError> {
|
pub fn load<T: AsRef<Path>>(location: T) -> Result<Project, ProjectLoadError> {
|
||||||
let package_path = location.as_ref().join(Path::new(PROJECT_FILENAME));
|
let package_path = location.as_ref().join(Path::new(PROJECT_FILENAME));
|
||||||
|
|
||||||
@@ -116,10 +133,11 @@ impl Project {
|
|||||||
|
|
||||||
match serde_json::from_str(&contents) {
|
match serde_json::from_str(&contents) {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
Err(_) => return Err(ProjectLoadError::Invalid),
|
Err(e) => return Err(ProjectLoadError::InvalidJson(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves the given project file to the given folder with the appropriate name.
|
||||||
pub fn save<T: AsRef<Path>>(&self, location: T) -> Result<(), ProjectSaveError> {
|
pub fn save<T: AsRef<Path>>(&self, location: T) -> Result<(), ProjectSaveError> {
|
||||||
let package_path = location.as_ref().join(Path::new(PROJECT_FILENAME));
|
let package_path = location.as_ref().join(Path::new(PROJECT_FILENAME));
|
||||||
|
|
||||||
@@ -139,7 +157,7 @@ impl Project {
|
|||||||
impl Default for Project {
|
impl Default for Project {
|
||||||
fn default() -> Project {
|
fn default() -> Project {
|
||||||
Project {
|
Project {
|
||||||
name: "some-project".to_string(),
|
name: "new-project".to_string(),
|
||||||
serve_port: 8000,
|
serve_port: 8000,
|
||||||
partitions: HashMap::new(),
|
partitions: HashMap::new(),
|
||||||
}
|
}
|
||||||
34
server/src/rbx.rs
Normal file
34
server/src/rbx.rs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Represents data about a Roblox instance
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct RbxInstance {
|
||||||
|
pub name: String,
|
||||||
|
pub class_name: String,
|
||||||
|
pub children: Vec<RbxInstance>,
|
||||||
|
pub properties: HashMap<String, RbxValue>,
|
||||||
|
|
||||||
|
/// The route that this instance was generated from, if there was one.
|
||||||
|
pub route: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Any kind value that can be used by Roblox
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase", tag = "Type")]
|
||||||
|
pub enum RbxValue {
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
String {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
Bool {
|
||||||
|
value: bool,
|
||||||
|
},
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
Number {
|
||||||
|
value: f64,
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: Compound types like Vector3
|
||||||
|
}
|
||||||
7
server/src/vfs/mod.rs
Normal file
7
server/src/vfs/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod vfs_session;
|
||||||
|
mod vfs_item;
|
||||||
|
mod vfs_watcher;
|
||||||
|
|
||||||
|
pub use self::vfs_session::*;
|
||||||
|
pub use self::vfs_item::*;
|
||||||
|
pub use self::vfs_watcher::*;
|
||||||
31
server/src/vfs/vfs_item.rs
Normal file
31
server/src/vfs/vfs_item.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// A VfsItem represents either a file or directory as it came from the filesystem.
|
||||||
|
///
|
||||||
|
/// The interface here is intentionally simplified to make it easier to traverse
|
||||||
|
/// files that have been read into memory.
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase", tag = "type")]
|
||||||
|
pub enum VfsItem {
|
||||||
|
File {
|
||||||
|
route: Vec<String>,
|
||||||
|
contents: String,
|
||||||
|
},
|
||||||
|
Dir {
|
||||||
|
route: Vec<String>,
|
||||||
|
children: HashMap<String, VfsItem>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfsItem {
|
||||||
|
pub fn name(&self) -> &String {
|
||||||
|
self.route().last().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn route(&self) -> &[String] {
|
||||||
|
match self {
|
||||||
|
&VfsItem::File { ref route, .. } => route,
|
||||||
|
&VfsItem::Dir { ref route, .. } => route,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,30 @@
|
|||||||
use std::borrow::Borrow;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use core::Config;
|
use plugin::PluginChain;
|
||||||
|
use vfs::VfsItem;
|
||||||
|
|
||||||
/// Represents a virtual layer over multiple parts of the filesystem.
|
/// Represents a virtual layer over multiple parts of the filesystem.
|
||||||
///
|
///
|
||||||
/// Paths in this system are represented as slices of strings, and are always
|
/// Paths in this system are represented as slices of strings, and are always
|
||||||
/// relative to a partition, which is an absolute path into the real filesystem.
|
/// relative to a partition, which is an absolute path into the real filesystem.
|
||||||
pub struct Vfs {
|
pub struct VfsSession {
|
||||||
/// Contains all of the partitions mounted by the Vfs.
|
/// Contains all of the partitions mounted by the Vfs.
|
||||||
///
|
///
|
||||||
/// These must be absolute paths!
|
/// These must be absolute paths!
|
||||||
pub partitions: HashMap<String, PathBuf>,
|
partitions: HashMap<String, PathBuf>,
|
||||||
|
|
||||||
/// When the Vfs was initialized; used for change tracking.
|
|
||||||
pub start_time: Instant,
|
|
||||||
|
|
||||||
/// A chronologically-sorted list of routes that changed since the Vfs was
|
/// A chronologically-sorted list of routes that changed since the Vfs was
|
||||||
/// created, along with a timestamp denoting when.
|
/// created, along with a timestamp denoting when.
|
||||||
pub change_history: Vec<VfsChange>,
|
change_history: Vec<VfsChange>,
|
||||||
|
|
||||||
config: Config,
|
/// When the Vfs was initialized; used for change tracking.
|
||||||
|
start_time: Instant,
|
||||||
|
|
||||||
|
plugin_chain: &'static PluginChain,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
@@ -34,26 +34,31 @@ pub struct VfsChange {
|
|||||||
route: Vec<String>,
|
route: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
impl VfsSession {
|
||||||
#[serde(rename_all = "camelCase", tag = "type")]
|
pub fn new(plugin_chain: &'static PluginChain) -> VfsSession {
|
||||||
pub enum VfsItem {
|
VfsSession {
|
||||||
File { contents: String },
|
|
||||||
Dir { children: HashMap<String, VfsItem> },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vfs {
|
|
||||||
pub fn new(config: Config) -> Vfs {
|
|
||||||
Vfs {
|
|
||||||
partitions: HashMap::new(),
|
partitions: HashMap::new(),
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
change_history: Vec::new(),
|
change_history: Vec::new(),
|
||||||
config,
|
plugin_chain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn route_to_path<R: Borrow<str>>(&self, route: &[R]) -> Option<PathBuf> {
|
pub fn get_partitions(&self) -> &HashMap<String, PathBuf> {
|
||||||
|
&self.partitions
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_partition<P: Into<PathBuf>>(&mut self, name: &str, path: P) {
|
||||||
|
let path = path.into();
|
||||||
|
|
||||||
|
assert!(path.is_absolute());
|
||||||
|
|
||||||
|
self.partitions.insert(name.to_string(), path.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn route_to_path(&self, route: &[String]) -> Option<PathBuf> {
|
||||||
let (partition_name, rest) = match route.split_first() {
|
let (partition_name, rest) = match route.split_first() {
|
||||||
Some((first, rest)) => (first.borrow(), rest),
|
Some((first, rest)) => (first, rest),
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,7 +67,12 @@ impl Vfs {
|
|||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let full_path = {
|
// It's possible that the partition points to a file if `rest` is empty.
|
||||||
|
// Joining "" onto a path will put a trailing slash on, which causes
|
||||||
|
// file reads to fail.
|
||||||
|
let full_path = if rest.is_empty() {
|
||||||
|
partition.clone()
|
||||||
|
} else {
|
||||||
let joined = rest.join("/");
|
let joined = rest.join("/");
|
||||||
let relative = Path::new(&joined);
|
let relative = Path::new(&joined);
|
||||||
|
|
||||||
@@ -72,7 +82,8 @@ impl Vfs {
|
|||||||
Some(full_path)
|
Some(full_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_dir<P: AsRef<Path>>(&self, path: P) -> Result<VfsItem, ()> {
|
fn read_dir<P: AsRef<Path>>(&self, route: &[String], path: P) -> Result<VfsItem, ()> {
|
||||||
|
let path = path.as_ref();
|
||||||
let reader = match fs::read_dir(path) {
|
let reader = match fs::read_dir(path) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => return Err(()),
|
Err(_) => return Err(()),
|
||||||
@@ -87,11 +98,13 @@ impl Vfs {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
let name = path.file_name().unwrap().to_string_lossy().into_owned();
|
||||||
|
|
||||||
match self.read_path(&path) {
|
let mut child_route = route.iter().cloned().collect::<Vec<_>>();
|
||||||
|
child_route.push(name.clone());
|
||||||
|
|
||||||
|
match self.read_path(&child_route, &path) {
|
||||||
Ok(child_item) => {
|
Ok(child_item) => {
|
||||||
let name = path.file_name().unwrap().to_string_lossy().into_owned();
|
|
||||||
|
|
||||||
children.insert(name, child_item);
|
children.insert(name, child_item);
|
||||||
},
|
},
|
||||||
Err(_) => {},
|
Err(_) => {},
|
||||||
@@ -99,11 +112,13 @@ impl Vfs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(VfsItem::Dir {
|
Ok(VfsItem::Dir {
|
||||||
|
route: route.iter().cloned().collect::<Vec<_>>(),
|
||||||
children,
|
children,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file<P: AsRef<Path>>(&self, path: P) -> Result<VfsItem, ()> {
|
fn read_file<P: AsRef<Path>>(&self, route: &[String], path: P) -> Result<VfsItem, ()> {
|
||||||
|
let path = path.as_ref();
|
||||||
let mut file = match File::open(path) {
|
let mut file = match File::open(path) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => return Err(()),
|
Err(_) => return Err(()),
|
||||||
@@ -117,11 +132,12 @@ impl Vfs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(VfsItem::File {
|
Ok(VfsItem::File {
|
||||||
|
route: route.iter().cloned().collect::<Vec<_>>(),
|
||||||
contents,
|
contents,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_path<P: AsRef<Path>>(&self, path: P) -> Result<VfsItem, ()> {
|
fn read_path<P: AsRef<Path>>(&self, route: &[String], path: P) -> Result<VfsItem, ()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
let metadata = match fs::metadata(path) {
|
let metadata = match fs::metadata(path) {
|
||||||
@@ -130,31 +146,38 @@ impl Vfs {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if metadata.is_dir() {
|
if metadata.is_dir() {
|
||||||
self.read_dir(path)
|
self.read_dir(route, path)
|
||||||
} else if metadata.is_file() {
|
} else if metadata.is_file() {
|
||||||
self.read_file(path)
|
self.read_file(route, path)
|
||||||
} else {
|
} else {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current time, used for logging timestamps for file changes.
|
||||||
pub fn current_time(&self) -> f64 {
|
pub fn current_time(&self) -> f64 {
|
||||||
let elapsed = self.start_time.elapsed();
|
let elapsed = self.start_time.elapsed();
|
||||||
|
|
||||||
elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9
|
elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a new change to the filesystem at the given timestamp and VFS
|
||||||
|
/// route.
|
||||||
pub fn add_change(&mut self, timestamp: f64, route: Vec<String>) {
|
pub fn add_change(&mut self, timestamp: f64, route: Vec<String>) {
|
||||||
if self.config.verbose {
|
match self.plugin_chain.handle_file_change(&route) {
|
||||||
println!("Added change {:?}", route);
|
Some(routes) => {
|
||||||
|
for route in routes {
|
||||||
|
self.change_history.push(VfsChange {
|
||||||
|
timestamp,
|
||||||
|
route,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.change_history.push(VfsChange {
|
|
||||||
timestamp,
|
|
||||||
route,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collect a list of changes that occured since the given timestamp.
|
||||||
pub fn changes_since(&self, timestamp: f64) -> &[VfsChange] {
|
pub fn changes_since(&self, timestamp: f64) -> &[VfsChange] {
|
||||||
let mut marker: Option<usize> = None;
|
let mut marker: Option<usize> = None;
|
||||||
|
|
||||||
@@ -173,18 +196,19 @@ impl Vfs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read<R: Borrow<str>>(&self, route: &[R]) -> Result<VfsItem, ()> {
|
/// Read an item from the filesystem using the given VFS route.
|
||||||
|
pub fn read(&self, route: &[String]) -> Result<VfsItem, ()> {
|
||||||
match self.route_to_path(route) {
|
match self.route_to_path(route) {
|
||||||
Some(path) => self.read_path(&path),
|
Some(path) => self.read_path(route, &path),
|
||||||
None => Err(()),
|
None => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<R: Borrow<str>>(&self, _route: &[R], _item: VfsItem) -> Result<(), ()> {
|
pub fn write(&self, _route: &[String], _item: VfsItem) -> Result<(), ()> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete<R: Borrow<str>>(&self, _route: &[R]) -> Result<(), ()> {
|
pub fn delete(&self, _route: &[String]) -> Result<(), ()> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
108
server/src/vfs/vfs_watcher.rs
Normal file
108
server/src/vfs/vfs_watcher.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
|
|
||||||
|
use pathext::path_to_route;
|
||||||
|
use vfs::VfsSession;
|
||||||
|
|
||||||
|
/// An object that registers watchers on the real filesystem and relays those
|
||||||
|
/// changes to the virtual filesystem layer.
|
||||||
|
pub struct VfsWatcher {
|
||||||
|
vfs: Arc<Mutex<VfsSession>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfsWatcher {
|
||||||
|
pub fn new(vfs: Arc<Mutex<VfsSession>>) -> VfsWatcher {
|
||||||
|
VfsWatcher {
|
||||||
|
vfs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_watcher(
|
||||||
|
vfs: Arc<Mutex<VfsSession>>,
|
||||||
|
rx: mpsc::Receiver<DebouncedEvent>,
|
||||||
|
partition_name: String,
|
||||||
|
root_path: PathBuf,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
let event = rx.recv().unwrap();
|
||||||
|
|
||||||
|
let mut vfs = vfs.lock().unwrap();
|
||||||
|
let current_time = vfs.current_time();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
DebouncedEvent::Write(ref change_path) |
|
||||||
|
DebouncedEvent::Create(ref change_path) |
|
||||||
|
DebouncedEvent::Remove(ref change_path) => {
|
||||||
|
if let Some(mut route) = path_to_route(&root_path, change_path) {
|
||||||
|
route.insert(0, partition_name.clone());
|
||||||
|
|
||||||
|
vfs.add_change(current_time, route);
|
||||||
|
} else {
|
||||||
|
eprintln!("Failed to get route from {}", change_path.display());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DebouncedEvent::Rename(ref from_change, ref to_change) => {
|
||||||
|
if let Some(mut route) = path_to_route(&root_path, from_change) {
|
||||||
|
route.insert(0, partition_name.clone());
|
||||||
|
|
||||||
|
vfs.add_change(current_time, route);
|
||||||
|
} else {
|
||||||
|
eprintln!("Failed to get route from {}", from_change.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mut route) = path_to_route(&root_path, to_change) {
|
||||||
|
route.insert(0, partition_name.clone());
|
||||||
|
|
||||||
|
vfs.add_change(current_time, route);
|
||||||
|
} else {
|
||||||
|
eprintln!("Failed to get route from {}", to_change.display());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(self) {
|
||||||
|
let mut watchers = Vec::new();
|
||||||
|
|
||||||
|
// Create an extra scope so that `vfs` gets dropped and unlocked
|
||||||
|
{
|
||||||
|
let vfs = self.vfs.lock().unwrap();
|
||||||
|
|
||||||
|
for (ref partition_name, ref root_path) in vfs.get_partitions() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1))
|
||||||
|
.expect("Unable to create watcher! This is a bug in Rojo.");
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
{
|
||||||
|
let partition_name = partition_name.to_string();
|
||||||
|
let root_path = root_path.to_path_buf();
|
||||||
|
let vfs = self.vfs.clone();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
Self::start_watcher(vfs, rx, partition_name, root_path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
thread::park();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
225
server/src/web.rs
Normal file
225
server/src/web.rs
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use rouille;
|
||||||
|
use serde;
|
||||||
|
use serde_json;
|
||||||
|
|
||||||
|
use project::Project;
|
||||||
|
use vfs::{VfsSession, VfsChange};
|
||||||
|
use rbx::RbxInstance;
|
||||||
|
use plugin::PluginChain;
|
||||||
|
|
||||||
|
static MAX_BODY_SIZE: usize = 25 * 1024 * 1024; // 25 MiB
|
||||||
|
|
||||||
|
/// The set of configuration the web server needs to start.
|
||||||
|
pub struct WebConfig {
|
||||||
|
pub port: u64,
|
||||||
|
pub verbose: bool,
|
||||||
|
pub server_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ServerInfo<'a> {
|
||||||
|
server_version: &'static str,
|
||||||
|
protocol_version: u64,
|
||||||
|
server_id: &'a str,
|
||||||
|
project: &'a Project,
|
||||||
|
current_time: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ReadResult<'a> {
|
||||||
|
items: Vec<Option<RbxInstance>>,
|
||||||
|
server_id: &'a str,
|
||||||
|
current_time: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ChangesResult<'a> {
|
||||||
|
changes: &'a [VfsChange],
|
||||||
|
server_id: &'a str,
|
||||||
|
current_time: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct WriteSpecifier {
|
||||||
|
route: String,
|
||||||
|
item: RbxInstance,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json<T: serde::Serialize>(value: T) -> rouille::Response {
|
||||||
|
let data = serde_json::to_string(&value).unwrap();
|
||||||
|
rouille::Response::from_data("application/json", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pulls text that may be JSON out of a Rouille Request object.
|
||||||
|
///
|
||||||
|
/// Doesn't do any actual parsing -- all this method does is verify the content
|
||||||
|
/// type of the request and read the request's body.
|
||||||
|
fn read_json_text(request: &rouille::Request) -> Option<String> {
|
||||||
|
// Bail out if the request body isn't marked as JSON
|
||||||
|
match request.header("Content-Type") {
|
||||||
|
Some(header) => if !header.starts_with("application/json") {
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
None => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = match request.data() {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allocate a buffer and read up to MAX_BODY_SIZE+1 bytes into it.
|
||||||
|
let mut out = Vec::new();
|
||||||
|
match body.take(MAX_BODY_SIZE.saturating_add(1) as u64).read_to_end(&mut out) {
|
||||||
|
Ok(_) => {},
|
||||||
|
Err(_) => return None,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the body was too big (MAX_BODY_SIZE+1), we abort instead of trying to
|
||||||
|
// process it.
|
||||||
|
if out.len() > MAX_BODY_SIZE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed = match String::from_utf8(out) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the body out of a Rouille Request and attempts to turn it into JSON.
|
||||||
|
fn read_json<T>(request: &rouille::Request) -> Option<T>
|
||||||
|
where
|
||||||
|
T: serde::de::DeserializeOwned,
|
||||||
|
{
|
||||||
|
let body = match read_json_text(&request) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let parsed = match serde_json::from_str(&body) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Change return type to some sort of Result
|
||||||
|
|
||||||
|
Some(parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the Rojo web server and park our current thread.
|
||||||
|
pub fn start(config: WebConfig, project: Project, plugin_chain: &'static PluginChain, vfs: Arc<Mutex<VfsSession>>) {
|
||||||
|
let address = format!("localhost:{}", config.port);
|
||||||
|
|
||||||
|
let server_id = config.server_id.to_string();
|
||||||
|
|
||||||
|
rouille::start_server(address, move |request| {
|
||||||
|
router!(request,
|
||||||
|
(GET) (/) => {
|
||||||
|
// Get a summary of information about the server.
|
||||||
|
|
||||||
|
let current_time = {
|
||||||
|
let vfs = vfs.lock().unwrap();
|
||||||
|
|
||||||
|
vfs.current_time()
|
||||||
|
};
|
||||||
|
|
||||||
|
json(ServerInfo {
|
||||||
|
server_version: env!("CARGO_PKG_VERSION"),
|
||||||
|
protocol_version: 1,
|
||||||
|
server_id: &server_id,
|
||||||
|
project: &project,
|
||||||
|
current_time,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
(GET) (/changes/{ last_time: f64 }) => {
|
||||||
|
// Get the list of changes since the given time.
|
||||||
|
|
||||||
|
let vfs = vfs.lock().unwrap();
|
||||||
|
let current_time = vfs.current_time();
|
||||||
|
let changes = vfs.changes_since(last_time);
|
||||||
|
|
||||||
|
json(ChangesResult {
|
||||||
|
changes,
|
||||||
|
server_id: &server_id,
|
||||||
|
current_time,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
(POST) (/read) => {
|
||||||
|
// Read some instances from the server according to a JSON
|
||||||
|
// format body.
|
||||||
|
|
||||||
|
let read_request: Vec<Vec<String>> = match read_json(&request) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return rouille::Response::empty_400(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read the files off of the filesystem that the client
|
||||||
|
// requested.
|
||||||
|
let (items, current_time) = {
|
||||||
|
let vfs = vfs.lock().unwrap();
|
||||||
|
|
||||||
|
let current_time = vfs.current_time();
|
||||||
|
|
||||||
|
let mut items = Vec::new();
|
||||||
|
|
||||||
|
for route in &read_request {
|
||||||
|
match vfs.read(&route) {
|
||||||
|
Ok(v) => items.push(Some(v)),
|
||||||
|
Err(_) => items.push(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(items, current_time)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Transform all of our VfsItem objects into Roblox instances
|
||||||
|
// the client can use.
|
||||||
|
let rbx_items = items
|
||||||
|
.iter()
|
||||||
|
.map(|item| {
|
||||||
|
match *item {
|
||||||
|
Some(ref item) => plugin_chain.transform_file(item),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if config.verbose {
|
||||||
|
println!("Got read request: {:?}", read_request);
|
||||||
|
println!("Responding with:\n\t{:?}", rbx_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
json(ReadResult {
|
||||||
|
server_id: &server_id,
|
||||||
|
items: rbx_items,
|
||||||
|
current_time,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
(POST) (/write) => {
|
||||||
|
// Not yet implemented.
|
||||||
|
|
||||||
|
let _write_request: Vec<WriteSpecifier> = match read_json(&request) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return rouille::Response::empty_400(),
|
||||||
|
};
|
||||||
|
|
||||||
|
rouille::Response::empty_404()
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => rouille::Response::empty_404()
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
184
src/bin.rs
184
src/bin.rs
@@ -1,184 +0,0 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate rouille;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
extern crate notify;
|
|
||||||
extern crate rand;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
pub mod web;
|
|
||||||
pub mod core;
|
|
||||||
pub mod project;
|
|
||||||
pub mod pathext;
|
|
||||||
pub mod vfs;
|
|
||||||
pub mod vfs_watch;
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use core::Config;
|
|
||||||
use pathext::canonicalish;
|
|
||||||
use project::Project;
|
|
||||||
use vfs::Vfs;
|
|
||||||
use vfs_watch::VfsWatcher;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let matches = clap_app!(rojo =>
|
|
||||||
(version: env!("CARGO_PKG_VERSION"))
|
|
||||||
(author: env!("CARGO_PKG_AUTHORS"))
|
|
||||||
(about: env!("CARGO_PKG_DESCRIPTION"))
|
|
||||||
|
|
||||||
(@subcommand init =>
|
|
||||||
(about: "Creates a new Rojo project")
|
|
||||||
(@arg PATH: "Path to the place to create the project. Defaults to the current directory.")
|
|
||||||
)
|
|
||||||
|
|
||||||
(@subcommand serve =>
|
|
||||||
(about: "Serves the project's files for use with the Rojo Studio plugin.")
|
|
||||||
(@arg PROJECT: "Path to the project to serve. Defaults to the current directory.")
|
|
||||||
(@arg port: --port +takes_value "The port to listen on. Defaults to 8000.")
|
|
||||||
)
|
|
||||||
|
|
||||||
(@subcommand pack =>
|
|
||||||
(about: "Packs the project into a GUI installer bundle. NOT YET IMPLEMENTED!")
|
|
||||||
(@arg PROJECT: "Path to the project to pack. Defaults to the current directory.")
|
|
||||||
)
|
|
||||||
|
|
||||||
(@arg verbose: --verbose "Enable extended logging.")
|
|
||||||
).get_matches();
|
|
||||||
|
|
||||||
let verbose = match matches.occurrences_of("verbose") {
|
|
||||||
0 => false,
|
|
||||||
_ => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let server_id = rand::random::<u64>();
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
println!("Server ID: {}", server_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
match matches.subcommand() {
|
|
||||||
("init", sub_matches) => {
|
|
||||||
let sub_matches = sub_matches.unwrap();
|
|
||||||
let project_path = Path::new(sub_matches.value_of("PATH").unwrap_or("."));
|
|
||||||
let full_path = canonicalish(project_path);
|
|
||||||
|
|
||||||
match Project::init(&full_path) {
|
|
||||||
Ok(_) => {
|
|
||||||
println!("Created new empty project at {}", full_path.display());
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Failed to create new project.\n{}", e);
|
|
||||||
std::process::exit(1);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
("serve", sub_matches) => {
|
|
||||||
let sub_matches = sub_matches.unwrap();
|
|
||||||
|
|
||||||
let project_path = match sub_matches.value_of("PROJECT") {
|
|
||||||
Some(v) => PathBuf::from(v),
|
|
||||||
None => std::env::current_dir().unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
println!("Attempting to locate project at {}", project_path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
let project = match Project::load(&project_path) {
|
|
||||||
Ok(v) => {
|
|
||||||
println!("Using project from {}", project_path.display());
|
|
||||||
v
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
println!("Using default project...");
|
|
||||||
Project::default()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let port = {
|
|
||||||
match sub_matches.value_of("port") {
|
|
||||||
Some(source) => match source.parse::<u64>() {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(_) => {
|
|
||||||
eprintln!("Invalid port '{}'", source);
|
|
||||||
std::process::exit(1);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
None => project.serve_port,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let config = Config {
|
|
||||||
port,
|
|
||||||
verbose,
|
|
||||||
server_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
println!("Loading VFS...");
|
|
||||||
}
|
|
||||||
|
|
||||||
let vfs = {
|
|
||||||
let mut vfs = Vfs::new(config.clone());
|
|
||||||
|
|
||||||
for (name, project_partition) in &project.partitions {
|
|
||||||
let path = {
|
|
||||||
let given_path = Path::new(&project_partition.path);
|
|
||||||
|
|
||||||
if given_path.is_absolute() {
|
|
||||||
given_path.to_path_buf()
|
|
||||||
} else {
|
|
||||||
project_path.join(given_path)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
println!(
|
|
||||||
"Partition '{}': {} @ {}",
|
|
||||||
name,
|
|
||||||
project_partition.target,
|
|
||||||
project_partition.path
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
vfs.partitions.insert(name.clone(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Arc::new(Mutex::new(vfs))
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
let vfs = vfs.clone();
|
|
||||||
let config = config.clone();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
VfsWatcher::new(config, vfs).start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
web::start(config.clone(), project.clone(), vfs.clone());
|
|
||||||
|
|
||||||
println!("Server listening on port {}", port);
|
|
||||||
|
|
||||||
loop {}
|
|
||||||
},
|
|
||||||
("pack", _) => {
|
|
||||||
eprintln!("'rojo pack' is not yet implemented!");
|
|
||||||
std::process::exit(1);
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
eprintln!("Please specify a subcommand!");
|
|
||||||
eprintln!("Try 'rojo help' for information.");
|
|
||||||
std::process::exit(1);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Config {
|
|
||||||
pub port: u64,
|
|
||||||
pub verbose: bool,
|
|
||||||
pub server_id: u64,
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
use std::sync::{mpsc, Arc, Mutex};
|
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
|
||||||
|
|
||||||
use vfs::Vfs;
|
|
||||||
use pathext::path_to_route;
|
|
||||||
use core::Config;
|
|
||||||
|
|
||||||
pub struct VfsWatcher {
|
|
||||||
vfs: Arc<Mutex<Vfs>>,
|
|
||||||
watchers: Vec<RecommendedWatcher>,
|
|
||||||
config: Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VfsWatcher {
|
|
||||||
pub fn new(config: Config, vfs: Arc<Mutex<Vfs>>) -> VfsWatcher {
|
|
||||||
VfsWatcher {
|
|
||||||
vfs,
|
|
||||||
watchers: Vec::new(),
|
|
||||||
config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(mut self) {
|
|
||||||
{
|
|
||||||
let outer_vfs = self.vfs.lock().unwrap();
|
|
||||||
|
|
||||||
for (partition_name, root_path) in &outer_vfs.partitions {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
let partition_name = partition_name.clone();
|
|
||||||
let root_path = root_path.clone();
|
|
||||||
|
|
||||||
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1))
|
|
||||||
.expect("Unable to create watcher!");
|
|
||||||
|
|
||||||
watcher
|
|
||||||
.watch(&root_path, RecursiveMode::Recursive)
|
|
||||||
.expect("Unable to watch path!");
|
|
||||||
|
|
||||||
self.watchers.push(watcher);
|
|
||||||
|
|
||||||
{
|
|
||||||
let vfs = self.vfs.clone();
|
|
||||||
let config = self.config.clone();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
let event = rx.recv().unwrap();
|
|
||||||
let mut vfs = vfs.lock().unwrap();
|
|
||||||
let current_time = vfs.current_time();
|
|
||||||
|
|
||||||
if config.verbose {
|
|
||||||
println!("FS event {:?}", event);
|
|
||||||
}
|
|
||||||
|
|
||||||
match event {
|
|
||||||
DebouncedEvent::Write(ref change_path) |
|
|
||||||
DebouncedEvent::Create(ref change_path) |
|
|
||||||
DebouncedEvent::Remove(ref change_path) => {
|
|
||||||
if let Some(mut route) = path_to_route(&root_path, change_path) {
|
|
||||||
route.insert(0, partition_name.clone());
|
|
||||||
|
|
||||||
vfs.add_change(current_time, route);
|
|
||||||
} else {
|
|
||||||
println!("Failed to get route from {}", change_path.display());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DebouncedEvent::Rename(ref from_change, ref to_change) => {
|
|
||||||
if let Some(mut route) = path_to_route(&root_path, from_change) {
|
|
||||||
route.insert(0, partition_name.clone());
|
|
||||||
|
|
||||||
vfs.add_change(current_time, route);
|
|
||||||
} else {
|
|
||||||
println!("Failed to get route from {}", from_change.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mut route) = path_to_route(&root_path, to_change) {
|
|
||||||
route.insert(0, partition_name.clone());
|
|
||||||
|
|
||||||
vfs.add_change(current_time, route);
|
|
||||||
} else {
|
|
||||||
println!("Failed to get route from {}", to_change.display());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
170
src/web.rs
170
src/web.rs
@@ -1,170 +0,0 @@
|
|||||||
use std::io::Read;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use rouille;
|
|
||||||
use serde;
|
|
||||||
use serde_json;
|
|
||||||
|
|
||||||
use core::Config;
|
|
||||||
use project::Project;
|
|
||||||
use vfs::{Vfs, VfsItem, VfsChange};
|
|
||||||
|
|
||||||
static MAX_BODY_SIZE: usize = 25 * 1024 * 1025; // 25 MiB
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ServerInfo<'a> {
|
|
||||||
server_version: &'static str,
|
|
||||||
protocol_version: u64,
|
|
||||||
server_id: &'a str,
|
|
||||||
project: &'a Project,
|
|
||||||
current_time: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ReadResult<'a> {
|
|
||||||
items: Vec<Option<VfsItem>>,
|
|
||||||
server_id: &'a str,
|
|
||||||
current_time: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct ChangesResult<'a> {
|
|
||||||
changes: &'a [VfsChange],
|
|
||||||
server_id: &'a str,
|
|
||||||
current_time: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json<T: serde::Serialize>(value: T) -> rouille::Response {
|
|
||||||
let data = serde_json::to_string(&value).unwrap();
|
|
||||||
rouille::Response::from_data("application/json", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_json_text(request: &rouille::Request) -> Option<String> {
|
|
||||||
match request.header("Content-Type") {
|
|
||||||
Some(header) => if !header.starts_with("application/json") {
|
|
||||||
return None;
|
|
||||||
},
|
|
||||||
None => return None,
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = match request.data() {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
match body.take(MAX_BODY_SIZE.saturating_add(1) as u64)
|
|
||||||
.read_to_end(&mut out)
|
|
||||||
{
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(_) => return None,
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.len() > MAX_BODY_SIZE {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parsed = match String::from_utf8(out) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_json<T>(request: &rouille::Request) -> Option<T>
|
|
||||||
where
|
|
||||||
T: serde::de::DeserializeOwned,
|
|
||||||
{
|
|
||||||
let body = match read_json_text(&request) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let parsed = match serde_json::from_str(&body) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(parsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(config: Config, project: Project, vfs: Arc<Mutex<Vfs>>) {
|
|
||||||
let address = format!("localhost:{}", config.port);
|
|
||||||
|
|
||||||
let server_id = config.server_id.to_string();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
rouille::start_server(address, move |request| {
|
|
||||||
router!(request,
|
|
||||||
(GET) (/) => {
|
|
||||||
let current_time = {
|
|
||||||
let vfs = vfs.lock().unwrap();
|
|
||||||
|
|
||||||
vfs.current_time()
|
|
||||||
};
|
|
||||||
|
|
||||||
json(ServerInfo {
|
|
||||||
server_version: env!("CARGO_PKG_VERSION"),
|
|
||||||
protocol_version: 0,
|
|
||||||
server_id: &server_id,
|
|
||||||
project: &project,
|
|
||||||
current_time,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
(GET) (/changes/{ last_time: f64 }) => {
|
|
||||||
let vfs = vfs.lock().unwrap();
|
|
||||||
let current_time = vfs.current_time();
|
|
||||||
let changes = vfs.changes_since(last_time);
|
|
||||||
|
|
||||||
json(ChangesResult {
|
|
||||||
changes,
|
|
||||||
server_id: &server_id,
|
|
||||||
current_time,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
(POST) (/read) => {
|
|
||||||
let read_request: Vec<Vec<String>> = match read_json(&request) {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return rouille::Response::empty_400(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let (items, current_time) = {
|
|
||||||
let vfs = vfs.lock().unwrap();
|
|
||||||
|
|
||||||
let current_time = vfs.current_time();
|
|
||||||
|
|
||||||
let mut items = Vec::new();
|
|
||||||
|
|
||||||
for route in &read_request {
|
|
||||||
match vfs.read(&route) {
|
|
||||||
Ok(v) => items.push(Some(v)),
|
|
||||||
Err(_) => items.push(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(items, current_time)
|
|
||||||
};
|
|
||||||
|
|
||||||
json(ReadResult {
|
|
||||||
server_id: &server_id,
|
|
||||||
items,
|
|
||||||
current_time,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
(POST) (/write) => {
|
|
||||||
rouille::Response::empty_404()
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => rouille::Response::empty_404()
|
|
||||||
)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"partitions": {
|
"partitions": {
|
||||||
"src": {
|
"src": {
|
||||||
"path": "src",
|
"path": "src",
|
||||||
"target": "ReplicatedStorage.TestProject"
|
"target": "ReplicatedFirst"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
24
test-project/src/hello.model.json
Normal file
24
test-project/src/hello.model.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user