Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a78e9178a | ||
|
|
1659cf7a01 | ||
|
|
78d97e162c | ||
|
|
5d0aa1193f | ||
|
|
126040a87b | ||
|
|
2c408f4047 | ||
|
|
b53cda787a | ||
|
|
7b4455ed51 | ||
|
|
5b57025b0b | ||
|
|
ece454e6dd | ||
|
|
afa480b07d | ||
|
|
c9b695d533 | ||
|
|
71c77a09a6 | ||
|
|
d309a1359c | ||
|
|
b0bb486d9a | ||
|
|
2c7c3348cf | ||
|
|
4caac5e6cb |
@@ -1,2 +0,0 @@
|
||||
((nil . ((eglot-luau-rojo-project-path . "plugin.project.json")
|
||||
(eglot-luau-rojo-sourcemap-enabled . 't))))
|
||||
@@ -3,27 +3,16 @@ root = true
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{json,js,css}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
[*.{md,rs}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{rs,toml}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
|
||||
[*.snap]
|
||||
insert_final_newline = true
|
||||
|
||||
[*.lua]
|
||||
indent_style = tab
|
||||
|
||||
[*.luau]
|
||||
indent_style = tab
|
||||
@@ -1,2 +0,0 @@
|
||||
# stylua formatting
|
||||
0f8e1625d572a5fe0f7b5c08653ff92cc837d346
|
||||
1
.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
*.lua linguist-language=Luau
|
||||
23
.github/workflows/changelog.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: Changelog Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [assigned, opened, synchronize, reopened, labeled, unlabeled]
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Check Actions
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Changelog check
|
||||
uses: Zomzog/changelog-checker@v1.3.0
|
||||
with:
|
||||
fileName: CHANGELOG.md
|
||||
noChangelogLabel: skip changelog
|
||||
checkNotification: Simple
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
133
.github/workflows/ci.yml
vendored
@@ -1,133 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Test
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, windows-latest, macos-latest, windows-11-arm, ubuntu-22.04-arm]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Restore Rust Cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked --verbose
|
||||
|
||||
- name: Test
|
||||
run: cargo test --locked --verbose
|
||||
|
||||
- name: Save Rust Cache
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
msrv:
|
||||
name: Check MSRV
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@1.88.0
|
||||
|
||||
- name: Restore Rust Cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked --verbose
|
||||
|
||||
- name: Save Rust Cache
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
lint:
|
||||
name: Rustfmt, Clippy, Stylua, & Selene
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Restore Rust Cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Setup Rokit
|
||||
uses: CompeyDev/setup-rokit@v0.1.2
|
||||
with:
|
||||
version: 'v1.1.0'
|
||||
|
||||
- name: Stylua
|
||||
run: stylua --check plugin/src
|
||||
|
||||
- name: Selene
|
||||
run: selene plugin/src
|
||||
|
||||
- name: Rustfmt
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy
|
||||
|
||||
- name: Save Rust Cache
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
157
.github/workflows/release.yml
vendored
@@ -1,157 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Create Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create ${{ github.ref_name }} --draft --verify-tag --title ${{ github.ref_name }}
|
||||
|
||||
build-plugin:
|
||||
needs: ["create-release"]
|
||||
name: Build Roblox Studio Plugin
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Rokit
|
||||
uses: CompeyDev/setup-rokit@v0.1.2
|
||||
with:
|
||||
version: 'v1.1.0'
|
||||
|
||||
- name: Build Plugin
|
||||
run: rojo build plugin.project.json --output Rojo.rbxm
|
||||
|
||||
- name: Upload Plugin to Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload ${{ github.ref_name }} Rojo.rbxm
|
||||
|
||||
- name: Upload Plugin to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Rojo.rbxm
|
||||
path: Rojo.rbxm
|
||||
|
||||
- name: Upload Plugin to Roblox
|
||||
env:
|
||||
RBX_API_KEY: ${{ secrets.PLUGIN_UPLOAD_TOKEN }}
|
||||
RBX_UNIVERSE_ID: ${{ vars.PLUGIN_CI_PLACE_ID }}
|
||||
RBX_PLACE_ID: ${{ vars.PLUGIN_CI_UNIVERSE_ID }}
|
||||
run: lune run upload-plugin Rojo.rbxm
|
||||
|
||||
build:
|
||||
needs: ["create-release"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# https://doc.rust-lang.org/rustc/platform-support.html
|
||||
include:
|
||||
- host: linux
|
||||
os: ubuntu-22.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
label: linux-x86_64
|
||||
|
||||
- host: linux
|
||||
os: ubuntu-22.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
label: linux-aarch64
|
||||
|
||||
- host: windows
|
||||
os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
label: windows-x86_64
|
||||
|
||||
- host: windows
|
||||
os: windows-11-arm
|
||||
target: aarch64-pc-windows-msvc
|
||||
label: windows-aarch64
|
||||
|
||||
- host: macos
|
||||
os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
label: macos-x86_64
|
||||
|
||||
- host: macos
|
||||
os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
label: macos-aarch64
|
||||
|
||||
name: Build (${{ matrix.target }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
BIN: rojo
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- name: Restore Rust Cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Build Release
|
||||
run: cargo build --release --locked --verbose --target ${{ matrix.target }}
|
||||
|
||||
- name: Save Rust Cache
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Generate Artifact Name
|
||||
shell: bash
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
echo "ARTIFACT_NAME=$BIN-${TAG_NAME#v}-${{ matrix.label }}.zip" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Create Archive and Upload to Release
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mkdir staging
|
||||
|
||||
if [ "${{ matrix.host }}" = "windows" ]; then
|
||||
cp "target/${{ matrix.target }}/release/$BIN.exe" staging/
|
||||
cd staging
|
||||
7z a ../$ARTIFACT_NAME *
|
||||
else
|
||||
cp "target/${{ matrix.target }}/release/$BIN" staging/
|
||||
cd staging
|
||||
zip ../$ARTIFACT_NAME *
|
||||
fi
|
||||
|
||||
gh release upload ${{ github.ref_name }} ../$ARTIFACT_NAME
|
||||
|
||||
- name: Upload Archive to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: ${{ env.ARTIFACT_NAME }}
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
27
.gitignore
vendored
@@ -1,28 +1,9 @@
|
||||
# Rust output directory
|
||||
/target
|
||||
|
||||
# Headers for clibrojo
|
||||
/include
|
||||
|
||||
# Roblox model and place files in the root, used for debugging
|
||||
/scratch-project
|
||||
/server/failed-snapshots/
|
||||
**/*.rs.bk
|
||||
/*.rbxm
|
||||
/*.rbxmx
|
||||
/*.rbxl
|
||||
/*.rbxlx
|
||||
|
||||
# Sourcemap for the Rojo plugin (for better intellisense)
|
||||
/sourcemap.json
|
||||
|
||||
# Roblox Studio holds 'lock' files on places
|
||||
*.rbxl.lock
|
||||
*.rbxlx.lock
|
||||
|
||||
# Snapshot files from the 'insta' Rust crate
|
||||
**/*.snap.new
|
||||
|
||||
# Macos file system junk
|
||||
._*
|
||||
.DS_STORE
|
||||
|
||||
# JetBrains IDEs
|
||||
/.idea/
|
||||
**/*.snap.new
|
||||
40
.gitmodules
vendored
@@ -1,24 +1,18 @@
|
||||
[submodule "plugin/Packages/Roact"]
|
||||
path = plugin/Packages/Roact
|
||||
url = https://github.com/roblox/roact.git
|
||||
[submodule "plugin/Packages/Flipper"]
|
||||
path = plugin/Packages/Flipper
|
||||
url = https://github.com/reselim/flipper.git
|
||||
[submodule "plugin/Packages/Promise"]
|
||||
path = plugin/Packages/Promise
|
||||
url = https://github.com/evaera/roblox-lua-promise.git
|
||||
[submodule "plugin/Packages/t"]
|
||||
path = plugin/Packages/t
|
||||
[submodule "plugin/modules/roact"]
|
||||
path = plugin/modules/roact
|
||||
url = https://github.com/Roblox/roact.git
|
||||
[submodule "plugin/modules/testez"]
|
||||
path = plugin/modules/testez
|
||||
url = https://github.com/Roblox/testez.git
|
||||
[submodule "plugin/modules/lemur"]
|
||||
path = plugin/modules/lemur
|
||||
url = https://github.com/LPGhatguy/lemur.git
|
||||
[submodule "plugin/modules/promise"]
|
||||
path = plugin/modules/promise
|
||||
url = https://github.com/LPGhatguy/roblox-lua-promise.git
|
||||
[submodule "plugin/modules/t"]
|
||||
path = plugin/modules/t
|
||||
url = https://github.com/osyrisrblx/t.git
|
||||
[submodule "plugin/Packages/TestEZ"]
|
||||
path = plugin/Packages/TestEZ
|
||||
url = https://github.com/roblox/testez.git
|
||||
[submodule "plugin/Packages/Highlighter"]
|
||||
path = plugin/Packages/Highlighter
|
||||
url = https://github.com/boatbomber/highlighter.git
|
||||
[submodule "plugin/Packages/msgpack-luau"]
|
||||
path = plugin/Packages/msgpack-luau
|
||||
url = https://github.com/cipharius/msgpack-luau/
|
||||
[submodule ".lune/opencloud-execute"]
|
||||
path = .lune/opencloud-execute
|
||||
url = https://github.com/Dekkonot/opencloud-luau-execute-lune.git
|
||||
[submodule "plugin/modules/rbx-dom"]
|
||||
path = plugin/modules/rbx-dom
|
||||
url = http://github.com/rojo-rbx/rbx-dom
|
||||
@@ -1,8 +0,0 @@
|
||||
return {
|
||||
luau = {
|
||||
languagemode = "strict",
|
||||
aliases = {
|
||||
lune = "~/.lune/.typedefs/0.10.4/",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
local args: any = ...
|
||||
assert(args, "no arguments passed to script")
|
||||
|
||||
local input: buffer = args.BinaryInput
|
||||
|
||||
local AssetService = game:GetService("AssetService")
|
||||
local SerializationService = game:GetService("SerializationService")
|
||||
local EncodingService = game:GetService("EncodingService")
|
||||
|
||||
local input_hash: buffer = EncodingService:ComputeBufferHash(input, Enum.HashAlgorithm.Sha256)
|
||||
local hex_hash: { string } = table.create(buffer.len(input_hash))
|
||||
for i = 0, buffer.len(input_hash) - 1 do
|
||||
table.insert(hex_hash, string.format("%02x", buffer.readu8(input_hash, i)))
|
||||
end
|
||||
|
||||
print(`Deserializing plugin file (size: {buffer.len(input)} bytes, hash: {table.concat(hex_hash, "")})`)
|
||||
local plugin = SerializationService:DeserializeInstancesAsync(input)[1]
|
||||
|
||||
local UploadDetails = require(plugin.UploadDetails) :: any
|
||||
local PLUGIN_ID = UploadDetails.assetId
|
||||
local PLUGIN_NAME = UploadDetails.name
|
||||
local PLUGIN_DESCRIPTION = UploadDetails.description
|
||||
local PLUGIN_CREATOR_ID = UploadDetails.creatorId
|
||||
local PLUGIN_CREATOR_TYPE = UploadDetails.creatorType
|
||||
|
||||
assert(typeof(PLUGIN_ID) == "number", "UploadDetails did not contain a number field 'assetId'")
|
||||
assert(typeof(PLUGIN_NAME) == "string", "UploadDetails did not contain a string field 'name'")
|
||||
assert(typeof(PLUGIN_DESCRIPTION) == "string", "UploadDetails did not contain a string field 'description'")
|
||||
assert(typeof(PLUGIN_CREATOR_ID) == "number", "UploadDetails did not contain a number field 'creatorId'")
|
||||
assert(typeof(PLUGIN_CREATOR_TYPE) == "string", "UploadDetails did not contain a string field 'creatorType'")
|
||||
assert(
|
||||
Enum.AssetCreatorType:FromName(PLUGIN_CREATOR_TYPE) ~= nil,
|
||||
"UploadDetails field 'creatorType' was not a valid member of Enum.AssetCreatorType"
|
||||
)
|
||||
|
||||
print(`Uploading to {PLUGIN_ID}`)
|
||||
print(`Plugin Name: {PLUGIN_NAME}`)
|
||||
print(`Plugin Description: {PLUGIN_DESCRIPTION}`)
|
||||
|
||||
local result, version_or_err = AssetService:CreateAssetVersionAsync(plugin, Enum.AssetType.Plugin, PLUGIN_ID, {
|
||||
["Name"] = PLUGIN_NAME,
|
||||
["Description"] = PLUGIN_DESCRIPTION,
|
||||
["CreatorId"] = PLUGIN_CREATOR_ID,
|
||||
["CreatorType"] = Enum.AssetCreatorType:FromName(PLUGIN_CREATOR_TYPE),
|
||||
})
|
||||
|
||||
if result ~= Enum.CreateAssetResult.Success then
|
||||
error(`Plugin failed to upload because: {result.Name} - {version_or_err}`)
|
||||
end
|
||||
|
||||
print(`Plugin uploaded successfully. New version is {version_or_err}.`)
|
||||
@@ -1,78 +0,0 @@
|
||||
local fs = require("@lune/fs")
|
||||
local process = require("@lune/process")
|
||||
local stdio = require("@lune/stdio")
|
||||
|
||||
local luau_execute = require("./opencloud-execute")
|
||||
|
||||
local UNIVERSE_ID = process.env["RBX_UNIVERSE_ID"]
|
||||
local PLACE_ID = process.env["RBX_PLACE_ID"]
|
||||
|
||||
local version_string = fs.readFile("plugin/Version.txt")
|
||||
local versions = { string.match(version_string, "^v?(%d+)%.(%d+)%.(%d+)(.*)$") }
|
||||
if versions[4] ~= "" then
|
||||
print("This release is a pre-release. Skipping uploading plugin.")
|
||||
process.exit(0)
|
||||
end
|
||||
|
||||
local plugin_path = process.args[1]
|
||||
assert(
|
||||
typeof(plugin_path) == "string",
|
||||
"no plugin path provided, expected usage is `lune run upload-plugin [PATH TO RBXM]`."
|
||||
)
|
||||
|
||||
-- For local testing
|
||||
if process.env["CI"] ~= "true" then
|
||||
local rojo = process.exec("rojo", { "build", "plugin.project.json", "--output", plugin_path })
|
||||
if not rojo.ok then
|
||||
stdio.ewrite("plugin upload failed because: could not build plugin.rbxm\n\n")
|
||||
stdio.ewrite(rojo.stderr)
|
||||
stdio.ewrite("\n")
|
||||
process.exit(1)
|
||||
end
|
||||
else
|
||||
assert(fs.isFile(plugin_path), `Plugin file did not exist at {plugin_path}`)
|
||||
end
|
||||
local plugin_content = fs.readFile(plugin_path)
|
||||
|
||||
local engine_script = fs.readFile(".lune/scripts/plugin-upload.luau")
|
||||
|
||||
print("Creating task to upload plugin")
|
||||
local task = luau_execute.create_task_latest(UNIVERSE_ID, PLACE_ID, engine_script, 300, false, plugin_content)
|
||||
|
||||
print("Waiting for task to finish")
|
||||
local success = luau_execute.await_finish(task)
|
||||
if not success then
|
||||
local error = luau_execute.get_error(task)
|
||||
assert(error, "could not fetch error from task")
|
||||
stdio.ewrite("plugin upload failed because: task did not finish successfully\n\n")
|
||||
stdio.ewrite(error.code)
|
||||
stdio.ewrite("\n")
|
||||
stdio.ewrite(error.message)
|
||||
stdio.ewrite("\n")
|
||||
process.exit(1)
|
||||
end
|
||||
|
||||
print("Output from task:\n")
|
||||
for _, log in luau_execute.get_structured_logs(task) do
|
||||
if log.messageType == "ERROR" then
|
||||
stdio.write(stdio.color("red"))
|
||||
stdio.write(log.message)
|
||||
stdio.write("\n")
|
||||
stdio.write(stdio.color("reset"))
|
||||
elseif log.messageType == "INFO" then
|
||||
stdio.write(stdio.color("cyan"))
|
||||
stdio.write(log.message)
|
||||
stdio.write("\n")
|
||||
stdio.write(stdio.color("reset"))
|
||||
elseif log.messageType == "WARNING" then
|
||||
stdio.write(stdio.color("yellow"))
|
||||
stdio.write(log.message)
|
||||
stdio.write("\n")
|
||||
stdio.write(stdio.color("reset"))
|
||||
else
|
||||
stdio.write(stdio.color("reset"))
|
||||
stdio.write(log.message)
|
||||
stdio.write("\n")
|
||||
stdio.write(stdio.color("reset"))
|
||||
end
|
||||
end
|
||||
44
.travis.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
matrix:
|
||||
include:
|
||||
# Lua tests are currently disabled because of holes in Lemur that are pretty
|
||||
# tedious to fix. It should be fixed by either adding missing features to
|
||||
# Lemur or by migrating to a CI system based on real Roblox instead.
|
||||
|
||||
# - 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: 1.34.0
|
||||
cache: cargo
|
||||
|
||||
script:
|
||||
- cargo test --verbose
|
||||
|
||||
- language: rust
|
||||
rust: stable
|
||||
cache: cargo
|
||||
|
||||
script:
|
||||
- cargo test --verbose
|
||||
8
.vscode/extensions.json
vendored
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"JohnnyMorganz.luau-lsp",
|
||||
"JohnnyMorganz.stylua",
|
||||
"Kampfkarren.selene-vscode",
|
||||
"rust-lang.rust-analyzer"
|
||||
]
|
||||
}
|
||||
4
.vscode/settings.json
vendored
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"luau-lsp.sourcemap.rojoProjectFile": "plugin.project.json",
|
||||
"luau-lsp.sourcemap.autogenerate": true
|
||||
}
|
||||
1293
CHANGELOG.md
@@ -1,5 +1,5 @@
|
||||
# Contributing to the Rojo Project
|
||||
Rojo is a big project and can always use more help!
|
||||
Rojo is a big project and can always use more help! This guide covers all repositories underneath the [rojo-rbx organization on GitHub](https://github.com/rojo-rbx).
|
||||
|
||||
Some of the repositories covered are:
|
||||
|
||||
@@ -11,34 +11,10 @@ Some of the repositories covered are:
|
||||
## Code
|
||||
Code contributions are welcome for features and bugs that have been reported in the project's bug tracker. We want to make sure that no one wastes their time, so be sure to talk with maintainers about what changes would be accepted before doing any work!
|
||||
|
||||
You'll want these tools to work on Rojo:
|
||||
|
||||
* Latest stable Rust compiler
|
||||
* Rustfmt and Clippy are used for code formatting and linting.
|
||||
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
||||
* [Rokit](https://github.com/rojo-rbx/rokit)
|
||||
* [Luau Language Server](https://github.com/JohnnyMorganz/luau-lsp) (Only needed if working on the Studio plugin.)
|
||||
|
||||
When working on the Studio plugin, we recommend using this command to automatically rebuild the plugin when you save a change:
|
||||
|
||||
*(Make sure you've enabled the Studio setting to reload plugins on file change!)*
|
||||
|
||||
```bash
|
||||
bash scripts/watch-build-plugin.sh
|
||||
```
|
||||
|
||||
You can also run the plugin's unit tests with the following:
|
||||
|
||||
*(Make sure you have `run-in-roblox` installed first!)*
|
||||
|
||||
```bash
|
||||
bash scripts/unit-test-plugin.sh
|
||||
```
|
||||
|
||||
## Documentation
|
||||
Documentation impacts way more people than the individual lines of code we write.
|
||||
|
||||
If you find any problems in the documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
||||
If you find any problems in documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
||||
|
||||
## Bug Reports and Feature Requests
|
||||
Most of the tools around Rojo try to be clear when an issue is a bug. Even if they aren't, sometimes things don't work quite right.
|
||||
@@ -47,26 +23,23 @@ Sometimes there's something that Rojo doesn't do that it probably should.
|
||||
|
||||
Please file issues and we'll try to help figure out what the best way forward is.
|
||||
|
||||
## Local Development Gotchas
|
||||
|
||||
If your build fails with "Error: failed to open file `D:\code\rojo\plugin\modules\roact\src`" you need to update your Git submodules.
|
||||
Run the command and try building again: `git submodule update --init --recursive`.
|
||||
|
||||
## Pushing a Rojo Release
|
||||
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
||||
|
||||
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
||||
1. Bump server version in [`server/Cargo.toml`](server/Cargo.toml)
|
||||
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
||||
3. Run `cargo test` to update `Cargo.lock` and run tests
|
||||
3. Run `cargo test` to update `Cargo.lock` and double-check tests
|
||||
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
||||
5. Commit!
|
||||
* `git add . && git commit -m "Release vX.Y.Z"`
|
||||
6. Tag the commit
|
||||
* `git tag vX.Y.Z`
|
||||
6. Tag the commit with the version from `Cargo.toml` prepended with a v, like `v0.4.13`
|
||||
7. Build Windows release build of CLI
|
||||
* `cargo build --release`
|
||||
7. Publish the CLI
|
||||
* `cargo publish`
|
||||
8. Publish the Plugin
|
||||
* `cargo run -- upload plugin --asset_id 6415005344`
|
||||
8. Build and upload the plugin
|
||||
* `rojo build plugin -o Rojo.rbxm`
|
||||
* Upload `Rojo.rbxm` to Roblox.com, keep it for later
|
||||
9. Push commits and tags
|
||||
* `git push && git push --tags`
|
||||
10. Copy GitHub release content from previous release
|
||||
|
||||
3889
Cargo.lock
generated
135
Cargo.toml
@@ -1,133 +1,8 @@
|
||||
[package]
|
||||
name = "rojo"
|
||||
version = "7.7.0-rc.1"
|
||||
rust-version = "1.88"
|
||||
authors = [
|
||||
"Lucien Greathouse <me@lpghatguy.com>",
|
||||
"Micah Reid <git@dekkonot.com>",
|
||||
"Ken Loeffler <kenloef@gmail.com>",
|
||||
[workspace]
|
||||
members = [
|
||||
"server",
|
||||
"rojo-test",
|
||||
]
|
||||
description = "Enables professional-grade development tools for Roblox developers"
|
||||
license = "MPL-2.0"
|
||||
homepage = "https://rojo.space"
|
||||
documentation = "https://rojo.space/docs"
|
||||
repository = "https://github.com/rojo-rbx/rojo"
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
exclude = ["/test-projects/**"]
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Enable this feature to live-reload assets from the web UI.
|
||||
dev_live_assets = []
|
||||
|
||||
# Run Rojo with this feature to open a Tracy session.
|
||||
# Currently uses protocol v63, last supported in Tracy 0.9.1.
|
||||
profile-with-tracy = ["profiling/profile-with-tracy"]
|
||||
|
||||
[workspace]
|
||||
members = ["crates/*"]
|
||||
|
||||
[lib]
|
||||
name = "librojo"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "build"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
memofs = { version = "0.3.1", path = "crates/memofs" }
|
||||
|
||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||
# rbx_binary = { path = "../rbx-dom/rbx_binary", features = [
|
||||
# "unstable_text_format",
|
||||
# ] }
|
||||
# rbx_dom_weak = { path = "../rbx-dom/rbx_dom_weak" }
|
||||
# rbx_reflection = { path = "../rbx-dom/rbx_reflection" }
|
||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||
|
||||
rbx_binary = { version = "2.0.1", features = ["unstable_text_format"] }
|
||||
rbx_dom_weak = "4.1.0"
|
||||
rbx_reflection = "6.1.0"
|
||||
rbx_reflection_database = "2.0.2"
|
||||
rbx_xml = "2.0.1"
|
||||
|
||||
anyhow = "1.0.80"
|
||||
backtrace = "0.3.69"
|
||||
bincode = "1.3.3"
|
||||
crossbeam-channel = "0.5.12"
|
||||
csv = "1.3.0"
|
||||
env_logger = "0.9.3"
|
||||
fs-err = "2.11.0"
|
||||
futures = "0.3.30"
|
||||
globset = "0.4.14"
|
||||
humantime = "2.1.0"
|
||||
hyper = { version = "0.14.28", features = ["server", "tcp", "http1"] }
|
||||
hyper-tungstenite = "0.11.0"
|
||||
jod-thread = "0.1.2"
|
||||
log = "0.4.21"
|
||||
num_cpus = "1.16.0"
|
||||
opener = "0.5.2"
|
||||
rayon = "1.9.0"
|
||||
reqwest = { version = "0.11.24", default-features = false, features = [
|
||||
"blocking",
|
||||
"json",
|
||||
"rustls-tls",
|
||||
] }
|
||||
ritz = "0.1.0"
|
||||
roblox_install = "1.0.0"
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.145"
|
||||
jsonc-parser = { version = "0.27.0", features = ["serde"] }
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
toml = "0.5.11"
|
||||
termcolor = "1.4.1"
|
||||
thiserror = "1.0.57"
|
||||
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||
uuid = { version = "1.7.0", features = ["v4", "serde"] }
|
||||
clap = { version = "3.2.25", features = ["derive"] }
|
||||
profiling = "1.0.15"
|
||||
yaml-rust2 = "0.10.3"
|
||||
data-encoding = "2.8.0"
|
||||
pathdiff = "0.2.3"
|
||||
|
||||
blake3 = "1.5.0"
|
||||
float-cmp = "0.9.0"
|
||||
indexmap = { version = "2.10.0", features = ["serde"] }
|
||||
rmp-serde = "1.3.0"
|
||||
serde_bytes = "0.11.19"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.10.1"
|
||||
|
||||
[build-dependencies]
|
||||
memofs = { version = "0.3.0", path = "crates/memofs" }
|
||||
|
||||
embed-resource = "1.8.0"
|
||||
anyhow = "1.0.80"
|
||||
bincode = "1.3.3"
|
||||
fs-err = "2.11.0"
|
||||
maplit = "1.0.2"
|
||||
semver = "1.0.22"
|
||||
|
||||
[dev-dependencies]
|
||||
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
||||
|
||||
criterion = "0.3.6"
|
||||
insta = { version = "1.36.1", features = ["redactions", "yaml", "json"] }
|
||||
paste = "1.0.14"
|
||||
pretty_assertions = "1.4.0"
|
||||
serde_yaml = "0.8.26"
|
||||
tempfile = "3.10.1"
|
||||
walkdir = "2.5.0"
|
||||
opt-level = 1
|
||||
32
README.md
@@ -1,13 +1,24 @@
|
||||
<div align="center">
|
||||
<a href="https://rojo.space"><img src="assets/brand_images/logo-512.png" alt="Rojo" height="217" /></a>
|
||||
<a href="https://rojo.space">
|
||||
<img src="assets/logo-512.png" alt="Rojo" height="217" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div> </div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/rojo-rbx/rojo/actions"><img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" /></a>
|
||||
<a href="https://crates.io/crates/rojo"><img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" /></a>
|
||||
<a href="https://rojo.space/docs"><img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" /></a>
|
||||
<a href="https://travis-ci.org/rojo-rbx/rojo">
|
||||
<img src="https://api.travis-ci.org/rojo-rbx/rojo.svg?branch=master" alt="Travis-CI Build Status" />
|
||||
</a>
|
||||
<a href="https://crates.io/crates/rojo">
|
||||
<img src="https://img.shields.io/crates/v/rojo.svg?label=version" alt="Latest server version" />
|
||||
</a>
|
||||
<a href="https://rojo.space/docs/0.4.x">
|
||||
<img src="https://img.shields.io/badge/docs-0.4.x-brightgreen.svg" alt="Rojo 0.4.x Documentation" />
|
||||
</a>
|
||||
<a href="https://rojo.space/docs/0.5.x">
|
||||
<img src="https://img.shields.io/badge/docs-0.5.x-brightgreen.svg" alt="Rojo 0.5.x Documentation" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
@@ -26,21 +37,22 @@ Rojo enables:
|
||||
* Streaming `rbxmx` and `rbxm` models into your game in real time
|
||||
* Packaging and deploying your project to Roblox.com from the command line
|
||||
|
||||
In the future, Rojo will be able to:
|
||||
Soon, Rojo will be able to:
|
||||
|
||||
* Sync instances from Roblox Studio to the filesystem
|
||||
* Automatically convert your existing game to work with Rojo
|
||||
* Sync instances from Roblox Studio to the filesystem
|
||||
* Automatically manage your assets on Roblox.com, like images and sounds
|
||||
* Import custom instances like MoonScript code
|
||||
|
||||
## [Documentation](https://rojo.space/docs)
|
||||
Documentation is hosted in the [rojo.space repository](https://github.com/rojo-rbx/rojo.space).
|
||||
## [Documentation](https://rojo.space/docs/latest)
|
||||
If you find any mistakes, feel free to make changes in the [docs](https://github.com/rojo-rbx/rojo/tree/master/docs) folder of this repository and submit a pull request!
|
||||
|
||||
## Contributing
|
||||
Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions for helping work on Rojo!
|
||||
|
||||
Pull requests are welcome!
|
||||
|
||||
Rojo supports Rust 1.88 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||
Rojo supports Rust 1.34.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||
|
||||
## License
|
||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||
|
Before Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 269 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g transform="matrix(1,0,0,1,-54,-1)">
|
||||
<g id="Artboard3" transform="matrix(1.77778,0,0,1.45455,-42,-0.454545)">
|
||||
<rect x="54" y="1" width="9" height="11" style="fill:none;"/>
|
||||
<g transform="matrix(3.375,0,0,4.125,-3654,-2753.12)">
|
||||
<path d="M1099,670L1101,668" style="fill:none;stroke:white;stroke-width:0.5px;"/>
|
||||
<g transform="matrix(-1,0,0,1,2200,0)">
|
||||
<path d="M1099,670L1101,668" style="fill:none;stroke:white;stroke-width:0.5px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 249 B |
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g transform="matrix(1,0,0,1,-32,0)">
|
||||
<g id="Artboard2" transform="matrix(0.8,0,0,0.941176,6.4,0)">
|
||||
<rect x="32" y="0" width="20" height="17" style="fill:none;"/>
|
||||
<g transform="matrix(5,0,0,4.25,-5470.5,-2371.5)">
|
||||
<path d="M1101,560L1102,561L1104,559" style="fill:none;stroke:white;stroke-width:0.75px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 317 B |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 613 B |
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 60 27" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Artboard1" transform="matrix(0.952381,0,0,0.710526,-228.571,-156.316)">
|
||||
<rect x="240" y="220" width="63" height="38" style="fill:none;"/>
|
||||
<g transform="matrix(0.0789166,0,0,0.105779,211.848,170.749)">
|
||||
<g transform="matrix(340.635,0,0,340.635,376,753)">
|
||||
<path d="M0.302,-0.836L0.306,-0.836L0.302,-0.829L0.302,-0.814C0.333,-0.82 0.349,-0.828 0.349,-0.836L0.371,-0.833C0.408,-0.835 0.441,-0.836 0.472,-0.836L0.476,-0.836C0.524,-0.836 0.58,-0.81 0.646,-0.757C0.66,-0.734 0.668,-0.714 0.672,-0.695L0.672,-0.659C0.664,-0.609 0.65,-0.571 0.632,-0.546C0.621,-0.537 0.597,-0.507 0.56,-0.456L0.556,-0.456L0.556,-0.463L0.566,-0.478L0.56,-0.478C0.474,-0.405 0.378,-0.349 0.27,-0.311C0.243,-0.304 0.216,-0.3 0.19,-0.3C0.224,-0.26 0.287,-0.207 0.378,-0.141C0.387,-0.136 0.445,-0.099 0.552,-0.028C0.641,0.027 0.747,0.087 0.871,0.153L0.871,0.157L0.856,0.157C0.832,0.148 0.82,0.141 0.82,0.138L0.813,0.142L0.806,0.142C0.803,0.142 0.802,0.141 0.802,0.138C0.793,0.14 0.786,0.149 0.78,0.164C0.795,0.168 0.802,0.173 0.802,0.178C0.796,0.183 0.792,0.186 0.788,0.186L0.77,0.182L0.77,0.186C0.77,0.19 0.773,0.193 0.78,0.193L0.78,0.204L0.777,0.204C0.772,0.204 0.717,0.172 0.614,0.109C0.541,0.072 0.451,0.015 0.346,-0.061C0.311,-0.077 0.247,-0.124 0.153,-0.202L0.143,-0.202C0.129,-0.181 0.111,-0.129 0.088,-0.046C0.083,-0.027 0.059,0.001 0.016,0.037L0.008,0.037L0.001,0.026L0.001,0.022C0.001,0.017 0.005,0.006 0.012,-0.01L0.012,-0.014L0.008,-0.014C0.008,-0.007 -0.007,0.004 -0.035,0.019L-0.039,0.019L-0.039,0.012C0.046,-0.24 0.105,-0.404 0.139,-0.481C0.197,-0.614 0.226,-0.69 0.226,-0.709C0.213,-0.709 0.184,-0.693 0.139,-0.659L0.132,-0.659L0.135,-0.666L0.135,-0.673C0.122,-0.673 0.092,-0.652 0.045,-0.608L0.041,-0.608L0.041,-0.612L0.088,-0.666L0.096,-0.677L0.096,-0.681L0.088,-0.681L0.045,-0.645C0.04,-0.645 0.038,-0.647 0.038,-0.651C0.083,-0.701 0.138,-0.744 0.204,-0.778C0.265,-0.794 0.295,-0.812 0.295,-0.833L0.302,-0.836ZM0.632,-0.735L0.632,-0.731C0.632,-0.727 0.635,-0.724 0.639,-0.724L0.639,-0.728C0.639,-0.732 0.637,-0.735 0.632,-0.735ZM0.208,-0.387L0.211,-0.387C0.255,-0.396 0.277,-0.403 0.277,-0.409C0.274,-0.414 0.273,-0.417 0.273,-0.42C0.365,-0.451 0.427,-0.482 0.458,-0.514C0.461,-0.514 0.48,-0.534 0.516,-0.576L0.52,-0.576L0.52,-0.572C0.515,-0.564 0.512,-0.558 0.512,-0.554L0.516,-0.554C0.547,-0.578 0.563,-0.604 0.563,-0.633L0.563,-0.655C0.556,-0.655 0.552,-0.658 0.552,-0.663C0.564,-0.665 0.57,-0.671 0.57,-0.681C0.57,-0.693 0.546,-0.705 0.498,-0.717C0.459,-0.727 0.417,-0.731 0.371,-0.731C0.348,-0.731 0.325,-0.689 0.302,-0.604C0.292,-0.594 0.261,-0.522 0.208,-0.387ZM0.251,-0.695L0.251,-0.691C0.255,-0.691 0.258,-0.695 0.262,-0.702L0.262,-0.706C0.259,-0.706 0.255,-0.702 0.251,-0.695ZM0.255,-0.626L0.259,-0.626C0.266,-0.629 0.27,-0.636 0.27,-0.648C0.266,-0.648 0.261,-0.641 0.255,-0.626ZM0.596,-0.612L0.596,-0.604C0.599,-0.604 0.603,-0.608 0.606,-0.615L0.606,-0.619L0.603,-0.619C0.598,-0.618 0.596,-0.616 0.596,-0.612ZM0.204,-0.369L0.204,-0.365L0.211,-0.365C0.216,-0.368 0.22,-0.369 0.222,-0.369L0.222,-0.365C0.281,-0.376 0.316,-0.388 0.328,-0.401L0.324,-0.401C0.271,-0.389 0.231,-0.378 0.204,-0.369ZM0.19,-0.333L0.193,-0.333L0.259,-0.347L0.255,-0.354C0.212,-0.349 0.19,-0.341 0.19,-0.333ZM0.334,-0.108L0.334,-0.101C0.372,-0.072 0.395,-0.057 0.403,-0.057C0.394,-0.064 0.389,-0.07 0.389,-0.075L0.334,-0.108ZM0.44,-0.028C0.442,-0.019 0.449,-0.014 0.461,-0.014L0.465,-0.014L0.465,-0.018C0.459,-0.018 0.452,-0.021 0.443,-0.028L0.44,-0.028ZM0.675,0.102C0.694,0.119 0.723,0.136 0.762,0.153L0.766,0.146C0.736,0.128 0.708,0.113 0.683,0.102L0.675,0.102ZM0.875,0.16L0.886,0.16C0.89,0.161 0.893,0.163 0.893,0.167C0.888,0.167 0.886,0.171 0.886,0.178L0.878,0.178L0.882,0.171L0.882,0.167L0.875,0.167L0.875,0.16Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(340.635,0,0,340.635,588.398,753)">
|
||||
<path d="M0.631,-0.705C0.646,-0.705 0.661,-0.696 0.673,-0.678L0.646,-0.678C0.62,-0.678 0.587,-0.67 0.546,-0.653L0.546,-0.65L0.549,-0.65C0.607,-0.654 0.649,-0.656 0.677,-0.656L0.683,-0.656L0.683,-0.65L0.67,-0.65C0.668,-0.65 0.667,-0.651 0.667,-0.653L0.503,-0.629C0.503,-0.627 0.493,-0.624 0.473,-0.62L0.473,-0.614L0.519,-0.623L0.521,-0.623L0.521,-0.617C0.386,-0.581 0.318,-0.555 0.318,-0.538C0.252,-0.483 0.204,-0.431 0.175,-0.383C0.131,-0.304 0.108,-0.243 0.108,-0.201C0.108,-0.157 0.131,-0.125 0.175,-0.104C0.183,-0.102 0.19,-0.101 0.196,-0.101C0.286,-0.136 0.353,-0.196 0.397,-0.28C0.409,-0.318 0.419,-0.363 0.427,-0.417L0.434,-0.417L0.427,-0.359C0.433,-0.359 0.439,-0.374 0.443,-0.404C0.441,-0.412 0.439,-0.42 0.439,-0.429L0.439,-0.438L0.446,-0.438C0.448,-0.407 0.449,-0.386 0.449,-0.374C0.449,-0.358 0.444,-0.331 0.434,-0.292C0.445,-0.302 0.458,-0.335 0.473,-0.392C0.48,-0.447 0.486,-0.474 0.491,-0.474C0.491,-0.472 0.492,-0.471 0.494,-0.471C0.492,-0.469 0.49,-0.457 0.488,-0.435L0.488,-0.432L0.491,-0.432C0.499,-0.454 0.504,-0.48 0.506,-0.508C0.502,-0.516 0.5,-0.522 0.5,-0.526L0.506,-0.529C0.512,-0.529 0.518,-0.513 0.525,-0.48C0.525,-0.4 0.491,-0.297 0.424,-0.17C0.372,-0.093 0.305,-0.042 0.224,-0.019C0.188,-0.006 0.154,-0 0.121,-0C0.04,-0.022 -0.001,-0.083 -0.001,-0.183C-0.001,-0.296 0.045,-0.398 0.136,-0.489C0.165,-0.522 0.227,-0.568 0.321,-0.629C0.425,-0.68 0.529,-0.705 0.631,-0.705ZM0.482,-0.656L0.482,-0.653C0.499,-0.653 0.508,-0.652 0.509,-0.65C0.526,-0.659 0.536,-0.663 0.54,-0.663L0.54,-0.668L0.482,-0.656ZM0.099,-0.417C0.144,-0.458 0.179,-0.494 0.206,-0.523C0.23,-0.538 0.242,-0.55 0.242,-0.559C0.201,-0.535 0.156,-0.493 0.108,-0.432C0.102,-0.427 0.099,-0.422 0.099,-0.417ZM0.105,-0.261C0.108,-0.261 0.118,-0.284 0.136,-0.328C0.158,-0.378 0.203,-0.439 0.272,-0.511L0.272,-0.514C0.204,-0.463 0.167,-0.422 0.16,-0.392C0.142,-0.371 0.124,-0.328 0.105,-0.261ZM0.069,-0.334L0.069,-0.332L0.072,-0.332C0.116,-0.393 0.139,-0.429 0.139,-0.438C0.11,-0.413 0.086,-0.378 0.069,-0.334ZM0.467,-0.31L0.467,-0.307L0.47,-0.307C0.492,-0.371 0.503,-0.413 0.503,-0.432C0.49,-0.409 0.478,-0.369 0.467,-0.31ZM0.087,-0.404L0.087,-0.401C0.091,-0.401 0.093,-0.403 0.093,-0.407L0.093,-0.41C0.089,-0.41 0.087,-0.408 0.087,-0.404ZM0.063,-0.368C0.032,-0.297 0.017,-0.245 0.017,-0.213C0.021,-0.148 0.028,-0.107 0.039,-0.088L0.042,-0.088L0.042,-0.094C0.035,-0.107 0.032,-0.129 0.032,-0.158C0.032,-0.191 0.035,-0.227 0.042,-0.268L0.039,-0.273C0.045,-0.3 0.054,-0.331 0.066,-0.365L0.066,-0.368L0.063,-0.368ZM0.096,-0.24L0.096,-0.237C0.1,-0.237 0.103,-0.239 0.103,-0.243L0.103,-0.246C0.098,-0.245 0.096,-0.243 0.096,-0.24ZM0.397,-0.222L0.397,-0.219L0.4,-0.219C0.408,-0.232 0.412,-0.241 0.412,-0.243C0.407,-0.242 0.402,-0.235 0.397,-0.222ZM0.066,-0.24C0.06,-0.222 0.055,-0.202 0.051,-0.179C0.055,-0.138 0.061,-0.108 0.069,-0.088L0.072,-0.088L0.072,-0.094C0.066,-0.121 0.063,-0.139 0.063,-0.146L0.066,-0.152C0.064,-0.155 0.063,-0.16 0.063,-0.164C0.063,-0.169 0.064,-0.173 0.066,-0.176C0.064,-0.18 0.063,-0.183 0.063,-0.186C0.067,-0.218 0.069,-0.235 0.069,-0.237L0.069,-0.24L0.066,-0.24ZM0.294,-0.125L0.294,-0.122C0.3,-0.122 0.322,-0.143 0.357,-0.186L0.357,-0.188L0.354,-0.188C0.314,-0.151 0.294,-0.129 0.294,-0.125ZM0.379,-0.149C0.379,-0.146 0.36,-0.126 0.321,-0.088L0.324,-0.088C0.351,-0.112 0.37,-0.132 0.382,-0.146L0.385,-0.146L0.385,-0.149L0.379,-0.149ZM0.09,-0.104L0.09,-0.101C0.094,-0.08 0.104,-0.07 0.121,-0.07L0.13,-0.07L0.13,-0.073L0.09,-0.104ZM0.151,-0.055L0.188,-0.055L0.203,-0.058L0.203,-0.061L0.157,-0.061C0.153,-0.06 0.151,-0.058 0.151,-0.055ZM0.096,-0.058L0.096,-0.055C0.104,-0.045 0.113,-0.04 0.124,-0.04L0.13,-0.04L0.13,-0.042C0.127,-0.042 0.116,-0.048 0.096,-0.058Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(340.635,0,0,340.635,758.882,753)">
|
||||
<path d="M0.208,-0.7L0.223,-0.7C0.227,-0.7 0.229,-0.697 0.229,-0.691C0.294,-0.687 0.328,-0.684 0.331,-0.682C0.333,-0.682 0.333,-0.683 0.333,-0.685C0.364,-0.683 0.387,-0.682 0.401,-0.682L0.462,-0.682C0.466,-0.682 0.468,-0.684 0.468,-0.688L0.45,-0.688L0.45,-0.694L0.49,-0.694L0.49,-0.688L0.475,-0.688L0.475,-0.682L0.557,-0.682C0.564,-0.682 0.573,-0.681 0.584,-0.679C0.603,-0.681 0.612,-0.685 0.612,-0.691L0.658,-0.691C0.678,-0.691 0.688,-0.692 0.689,-0.694C0.706,-0.692 0.72,-0.691 0.731,-0.691L0.903,-0.691L0.921,-0.688L0.928,-0.691C1.035,-0.686 1.096,-0.681 1.111,-0.676L1.13,-0.682L1.13,-0.676L1.068,-0.667L1.068,-0.664C1.075,-0.664 1.078,-0.661 1.078,-0.657C1.078,-0.654 1.075,-0.651 1.068,-0.648C1.064,-0.65 1.061,-0.651 1.059,-0.651C1.059,-0.647 1.055,-0.645 1.047,-0.645L1.047,-0.642C1.048,-0.638 1.05,-0.636 1.053,-0.636L1.059,-0.636C1.072,-0.636 1.087,-0.635 1.105,-0.633C1.113,-0.635 1.12,-0.636 1.126,-0.636C1.139,-0.625 1.145,-0.619 1.145,-0.618C1.138,-0.613 1.132,-0.611 1.126,-0.611L1.099,-0.611C1.088,-0.611 1.079,-0.608 1.071,-0.602L1.029,-0.602C1.024,-0.602 1.02,-0.603 1.017,-0.605L1.01,-0.602C0.993,-0.604 0.98,-0.605 0.971,-0.605C0.962,-0.605 0.958,-0.604 0.958,-0.602L0.928,-0.605L0.695,-0.605C0.69,-0.605 0.686,-0.604 0.683,-0.602C0.679,-0.604 0.675,-0.605 0.67,-0.605L0.563,-0.605C0.555,-0.605 0.547,-0.604 0.539,-0.602C0.531,-0.604 0.523,-0.605 0.514,-0.605L0.453,-0.605C0.429,-0.578 0.401,-0.525 0.37,-0.446C0.37,-0.439 0.334,-0.358 0.263,-0.201L0.177,-0.021C0.185,-0.001 0.189,0.011 0.189,0.013C0.186,0.013 0.177,0.001 0.162,-0.023L0.156,-0.023L0.153,-0.008C0.159,-0.001 0.162,0.006 0.162,0.013L0.162,0.017L0.15,0.007L0.128,0.007C0.128,-0.005 0.108,-0.045 0.067,-0.112C0.012,-0.235 -0.028,-0.332 -0.052,-0.403L-0.052,-0.412C-0.048,-0.412 -0.046,-0.409 -0.046,-0.403L-0.043,-0.403C-0.039,-0.403 -0.037,-0.405 -0.037,-0.409L-0.046,-0.443L-0.046,-0.446L-0.04,-0.446L0.034,-0.293L0.037,-0.293L0.037,-0.296C0.008,-0.37 -0.006,-0.41 -0.006,-0.416L-0.006,-0.421C0.009,-0.398 0.037,-0.343 0.076,-0.256C0.118,-0.177 0.142,-0.135 0.147,-0.13C0.212,-0.285 0.285,-0.442 0.364,-0.602C0.36,-0.602 0.358,-0.604 0.358,-0.608L0.352,-0.605L0.333,-0.605C0.332,-0.605 0.331,-0.606 0.331,-0.608C0.327,-0.606 0.324,-0.605 0.321,-0.605L0.315,-0.608C0.313,-0.608 0.312,-0.607 0.312,-0.605L0.291,-0.608L0.263,-0.608C0.263,-0.615 0.261,-0.618 0.257,-0.618L0.254,-0.618C0.247,-0.618 0.215,-0.621 0.159,-0.626L0.159,-0.63L0.162,-0.636L0.156,-0.636L0.156,-0.639C0.156,-0.641 0.163,-0.646 0.177,-0.654L0.184,-0.654L0.189,-0.651C0.189,-0.653 0.191,-0.659 0.193,-0.669C0.188,-0.671 0.178,-0.674 0.162,-0.676L0.162,-0.682L0.184,-0.682C0.191,-0.682 0.199,-0.688 0.208,-0.7ZM0.291,-0.697L0.352,-0.697C0.356,-0.697 0.358,-0.694 0.358,-0.691L0.291,-0.691L0.291,-0.697ZM0.41,-0.694L0.419,-0.694L0.419,-0.688L0.41,-0.688L0.41,-0.694ZM0.505,-0.694L0.533,-0.694L0.533,-0.688L0.505,-0.688L0.505,-0.694ZM0.603,-0.694L0.603,-0.691C0.603,-0.687 0.601,-0.685 0.597,-0.685L0.563,-0.685L0.563,-0.691L0.588,-0.691L0.603,-0.694ZM0.202,-0.667L0.202,-0.664L0.239,-0.664L0.239,-0.667L0.202,-0.667ZM0.269,-0.339L0.266,-0.327L0.266,-0.324L0.269,-0.324L0.272,-0.336L0.272,-0.339L0.269,-0.339ZM0.257,-0.308C0.237,-0.266 0.211,-0.205 0.177,-0.125C0.195,-0.147 0.223,-0.207 0.26,-0.305L0.26,-0.308L0.257,-0.308Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(340.635,0,0,340.635,891.444,753)">
|
||||
<path d="M0.631,-0.705C0.646,-0.705 0.661,-0.696 0.673,-0.678L0.646,-0.678C0.62,-0.678 0.587,-0.67 0.546,-0.653L0.546,-0.65L0.549,-0.65C0.607,-0.654 0.649,-0.656 0.677,-0.656L0.683,-0.656L0.683,-0.65L0.67,-0.65C0.668,-0.65 0.667,-0.651 0.667,-0.653L0.503,-0.629C0.503,-0.627 0.493,-0.624 0.473,-0.62L0.473,-0.614L0.519,-0.623L0.521,-0.623L0.521,-0.617C0.386,-0.581 0.318,-0.555 0.318,-0.538C0.252,-0.483 0.204,-0.431 0.175,-0.383C0.131,-0.304 0.108,-0.243 0.108,-0.201C0.108,-0.157 0.131,-0.125 0.175,-0.104C0.183,-0.102 0.19,-0.101 0.196,-0.101C0.286,-0.136 0.353,-0.196 0.397,-0.28C0.409,-0.318 0.419,-0.363 0.427,-0.417L0.434,-0.417L0.427,-0.359C0.433,-0.359 0.439,-0.374 0.443,-0.404C0.441,-0.412 0.439,-0.42 0.439,-0.429L0.439,-0.438L0.446,-0.438C0.448,-0.407 0.449,-0.386 0.449,-0.374C0.449,-0.358 0.444,-0.331 0.434,-0.292C0.445,-0.302 0.458,-0.335 0.473,-0.392C0.48,-0.447 0.486,-0.474 0.491,-0.474C0.491,-0.472 0.492,-0.471 0.494,-0.471C0.492,-0.469 0.49,-0.457 0.488,-0.435L0.488,-0.432L0.491,-0.432C0.499,-0.454 0.504,-0.48 0.506,-0.508C0.502,-0.516 0.5,-0.522 0.5,-0.526L0.506,-0.529C0.512,-0.529 0.518,-0.513 0.525,-0.48C0.525,-0.4 0.491,-0.297 0.424,-0.17C0.372,-0.093 0.305,-0.042 0.224,-0.019C0.188,-0.006 0.154,-0 0.121,-0C0.04,-0.022 -0.001,-0.083 -0.001,-0.183C-0.001,-0.296 0.045,-0.398 0.136,-0.489C0.165,-0.522 0.227,-0.568 0.321,-0.629C0.425,-0.68 0.529,-0.705 0.631,-0.705ZM0.482,-0.656L0.482,-0.653C0.499,-0.653 0.508,-0.652 0.509,-0.65C0.526,-0.659 0.536,-0.663 0.54,-0.663L0.54,-0.668L0.482,-0.656ZM0.099,-0.417C0.144,-0.458 0.179,-0.494 0.206,-0.523C0.23,-0.538 0.242,-0.55 0.242,-0.559C0.201,-0.535 0.156,-0.493 0.108,-0.432C0.102,-0.427 0.099,-0.422 0.099,-0.417ZM0.105,-0.261C0.108,-0.261 0.118,-0.284 0.136,-0.328C0.158,-0.378 0.203,-0.439 0.272,-0.511L0.272,-0.514C0.204,-0.463 0.167,-0.422 0.16,-0.392C0.142,-0.371 0.124,-0.328 0.105,-0.261ZM0.069,-0.334L0.069,-0.332L0.072,-0.332C0.116,-0.393 0.139,-0.429 0.139,-0.438C0.11,-0.413 0.086,-0.378 0.069,-0.334ZM0.467,-0.31L0.467,-0.307L0.47,-0.307C0.492,-0.371 0.503,-0.413 0.503,-0.432C0.49,-0.409 0.478,-0.369 0.467,-0.31ZM0.087,-0.404L0.087,-0.401C0.091,-0.401 0.093,-0.403 0.093,-0.407L0.093,-0.41C0.089,-0.41 0.087,-0.408 0.087,-0.404ZM0.063,-0.368C0.032,-0.297 0.017,-0.245 0.017,-0.213C0.021,-0.148 0.028,-0.107 0.039,-0.088L0.042,-0.088L0.042,-0.094C0.035,-0.107 0.032,-0.129 0.032,-0.158C0.032,-0.191 0.035,-0.227 0.042,-0.268L0.039,-0.273C0.045,-0.3 0.054,-0.331 0.066,-0.365L0.066,-0.368L0.063,-0.368ZM0.096,-0.24L0.096,-0.237C0.1,-0.237 0.103,-0.239 0.103,-0.243L0.103,-0.246C0.098,-0.245 0.096,-0.243 0.096,-0.24ZM0.397,-0.222L0.397,-0.219L0.4,-0.219C0.408,-0.232 0.412,-0.241 0.412,-0.243C0.407,-0.242 0.402,-0.235 0.397,-0.222ZM0.066,-0.24C0.06,-0.222 0.055,-0.202 0.051,-0.179C0.055,-0.138 0.061,-0.108 0.069,-0.088L0.072,-0.088L0.072,-0.094C0.066,-0.121 0.063,-0.139 0.063,-0.146L0.066,-0.152C0.064,-0.155 0.063,-0.16 0.063,-0.164C0.063,-0.169 0.064,-0.173 0.066,-0.176C0.064,-0.18 0.063,-0.183 0.063,-0.186C0.067,-0.218 0.069,-0.235 0.069,-0.237L0.069,-0.24L0.066,-0.24ZM0.294,-0.125L0.294,-0.122C0.3,-0.122 0.322,-0.143 0.357,-0.186L0.357,-0.188L0.354,-0.188C0.314,-0.151 0.294,-0.129 0.294,-0.125ZM0.379,-0.149C0.379,-0.146 0.36,-0.126 0.321,-0.088L0.324,-0.088C0.351,-0.112 0.37,-0.132 0.382,-0.146L0.385,-0.146L0.385,-0.149L0.379,-0.149ZM0.09,-0.104L0.09,-0.101C0.094,-0.08 0.104,-0.07 0.121,-0.07L0.13,-0.07L0.13,-0.073L0.09,-0.104ZM0.151,-0.055L0.188,-0.055L0.203,-0.058L0.203,-0.061L0.157,-0.061C0.153,-0.06 0.151,-0.058 0.151,-0.055ZM0.096,-0.058L0.096,-0.055C0.104,-0.045 0.113,-0.04 0.124,-0.04L0.13,-0.04L0.13,-0.042C0.127,-0.042 0.116,-0.048 0.096,-0.058Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 229 B |
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M20,11L20,13L8,13L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11L20,11Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 584 B |
|
Before Width: | Height: | Size: 295 B |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
|
||||
<g id="Artboard1" transform="matrix(0.0666667,0,0,0.097561,-31,-18.6341)">
|
||||
<rect x="465" y="191" width="360" height="246" style="fill:none;"/>
|
||||
<g transform="matrix(134.328,0,0,102.5,-74228.5,-15214.7)">
|
||||
<g transform="matrix(1.11667,0,0,1,-57.3333,0)">
|
||||
<path d="M551,152L550,151" style="fill:none;stroke:white;stroke-width:0.3px;"/>
|
||||
</g>
|
||||
<g transform="matrix(1.11667,0,0,1,-57.3333,0)">
|
||||
<path d="M550,152L551,151" style="fill:none;stroke:white;stroke-width:0.3px;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 273 B |
|
Before Width: | Height: | Size: 933 B |
|
Before Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 228 B |
|
Before Width: | Height: | Size: 315 B |
|
Before Width: | Height: | Size: 105 B |
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 9 7" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,0,-10)">
|
||||
<g id="Bottom" transform="matrix(0.243243,0,0,0.179487,-59.1081,-30.2051)">
|
||||
<rect x="243" y="224" width="37" height="39" style="fill:none;"/>
|
||||
<clipPath id="_clip1">
|
||||
<rect x="243" y="224" width="37" height="39"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#_clip1)">
|
||||
<g transform="matrix(-4.11111,6.82303e-16,-3.59619e-16,-3.97959,280,259.816)">
|
||||
<path d="M7,5.5C7,3.568 5.88,2 4.5,2C3.12,2 2,3.568 2,5.5L2,9L7,9L7,5.5Z" style="fill:white;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 75 B |
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 9 1" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,0,-8)">
|
||||
<g id="Middle" transform="matrix(1,0,0,0.111111,0,7.11111)">
|
||||
<rect x="0" y="8" width="9" height="9" style="fill:none;"/>
|
||||
<g transform="matrix(1,0,0,9,0,-64)">
|
||||
<rect x="2" y="8" width="5" height="1" style="fill:white;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 796 B |
|
Before Width: | Height: | Size: 132 B |
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 9 7" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Top" transform="matrix(0.243243,0,0,0.179487,-59.1081,-40.2051)">
|
||||
<rect x="243" y="224" width="37" height="39" style="fill:none;"/>
|
||||
<g transform="matrix(4.11111,0,0,3.97959,243,227.184)">
|
||||
<path d="M7,5.5C7,3.568 5.88,2 4.5,2C3.12,2 2,3.568 2,5.5L2,9L7,9L7,5.5Z" style="fill:white;"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 684 B |
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Artboard1" transform="matrix(0.5,0,0,0.5,0,0)">
|
||||
<rect x="0" y="0" width="48" height="48" style="fill:none;"/>
|
||||
<path d="M24,0C37.246,0 48,10.754 48,24C48,37.246 37.246,48 24,48C10.754,48 0,37.246 0,24C0,10.754 10.754,0 24,0ZM24,8.4C32.61,8.4 39.6,15.39 39.6,24C39.6,32.61 32.61,39.6 24,39.6C15.39,39.6 8.4,32.61 8.4,24C8.4,15.39 15.39,8.4 24,8.4Z" style="fill:white;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 855 B |
|
Before Width: | Height: | Size: 340 B |
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g id="Artboard1" transform="matrix(0.5,0,0,0.5,0,0)">
|
||||
<rect x="0" y="0" width="48" height="48" style="fill:none;"/>
|
||||
<path d="M48,24C48,10.745 37.255,0 24,0L24,8.4C32.616,8.4 39.6,15.384 39.6,24L48,24Z" style="fill:white;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 574 B |
|
Before Width: | Height: | Size: 607 B |
185
assets/index.css
@@ -1,185 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
box-sizing: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
font-family: sans-serif;
|
||||
font-size: 18px;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #e7e7e7
|
||||
}
|
||||
|
||||
img {
|
||||
max-width:100%;
|
||||
max-height:100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.path-list > li {
|
||||
margin-left: 1.2em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0.5rem auto;
|
||||
width: 100%;
|
||||
max-width: 50rem;
|
||||
background-color: #efefef;
|
||||
border: 1px solid #666;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.header {
|
||||
flex: 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #666;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.main-logo {
|
||||
flex: 0 0 10rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.stats {
|
||||
flex: 0 0 20rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-name {
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.main-section:not(:last-of-type) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.button-list {
|
||||
flex: 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
margin: -1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
border: 1px solid #666;
|
||||
padding: 0.3em 1em;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.instance {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.instance-title {
|
||||
font-size: 1.2rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.expandable-section {
|
||||
margin: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.expandable-items {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.expandable-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expandable-label > label {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.expandable-input ~ .expandable-label .expandable-visualizer {
|
||||
font-family: monospace;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
font-size: 2rem;
|
||||
margin: 0 0.5rem;
|
||||
transition: transform 100ms ease-in-out;
|
||||
transform-origin: 60% 60%;
|
||||
}
|
||||
|
||||
.expandable-visualizer::before {
|
||||
content: "›";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.expandable-input:checked ~ .expandable-label {
|
||||
border-bottom: 1px solid #bbb;
|
||||
}
|
||||
|
||||
.expandable-input:checked ~ .expandable-label .expandable-visualizer {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.expandable-input:not(:checked) ~ .expandable-items {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vfs-entry {
|
||||
}
|
||||
|
||||
.vfs-entry-name {
|
||||
position: relative;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.vfs-entry-children .vfs-entry-name::before {
|
||||
content: "";
|
||||
width: 0.6em;
|
||||
height: 1px;
|
||||
background-color: #999;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -0.8em;
|
||||
}
|
||||
|
||||
.vfs-entry-note {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.vfs-entry-children {
|
||||
padding-left: 0.8em;
|
||||
margin-left: 0.2em;
|
||||
border-left: 1px solid #999;
|
||||
}
|
||||
BIN
assets/kenney-ui-gray-sheet.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
41
assets/kenney-ui-gray-sheet.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<TextureAtlas imagePath="sheet.png">
|
||||
<SubTexture name="grey_arrowDownGrey.png" x="78" y="498" width="15" height="10"/>
|
||||
<SubTexture name="grey_arrowDownWhite.png" x="123" y="496" width="15" height="10"/>
|
||||
<SubTexture name="grey_arrowUpGrey.png" x="108" y="498" width="15" height="10"/>
|
||||
<SubTexture name="grey_arrowUpWhite.png" x="93" y="498" width="15" height="10"/>
|
||||
<SubTexture name="grey_box.png" x="147" y="433" width="38" height="36"/>
|
||||
<SubTexture name="grey_boxCheckmark.png" x="147" y="469" width="38" height="36"/>
|
||||
<SubTexture name="grey_boxCross.png" x="185" y="433" width="38" height="36"/>
|
||||
<SubTexture name="grey_boxTick.png" x="190" y="198" width="36" height="36"/>
|
||||
<SubTexture name="grey_button00.png" x="0" y="143" width="190" height="45"/>
|
||||
<SubTexture name="grey_button01.png" x="0" y="188" width="190" height="49"/>
|
||||
<SubTexture name="grey_button02.png" x="0" y="98" width="190" height="45"/>
|
||||
<SubTexture name="grey_button03.png" x="0" y="331" width="190" height="49"/>
|
||||
<SubTexture name="grey_button04.png" x="0" y="286" width="190" height="45"/>
|
||||
<SubTexture name="grey_button05.png" x="0" y="0" width="195" height="49"/>
|
||||
<SubTexture name="grey_button06.png" x="0" y="49" width="191" height="49"/>
|
||||
<SubTexture name="grey_button07.png" x="195" y="0" width="49" height="49"/>
|
||||
<SubTexture name="grey_button08.png" x="240" y="49" width="49" height="49"/>
|
||||
<SubTexture name="grey_button09.png" x="98" y="433" width="49" height="45"/>
|
||||
<SubTexture name="grey_button10.png" x="191" y="49" width="49" height="49"/>
|
||||
<SubTexture name="grey_button11.png" x="0" y="433" width="49" height="45"/>
|
||||
<SubTexture name="grey_button12.png" x="244" y="0" width="49" height="49"/>
|
||||
<SubTexture name="grey_button13.png" x="49" y="433" width="49" height="45"/>
|
||||
<SubTexture name="grey_button14.png" x="0" y="384" width="190" height="49"/>
|
||||
<SubTexture name="grey_button15.png" x="0" y="237" width="190" height="49"/>
|
||||
<SubTexture name="grey_checkmarkGrey.png" x="99" y="478" width="21" height="20"/>
|
||||
<SubTexture name="grey_checkmarkWhite.png" x="78" y="478" width="21" height="20"/>
|
||||
<SubTexture name="grey_circle.png" x="185" y="469" width="36" height="36"/>
|
||||
<SubTexture name="grey_crossGrey.png" x="120" y="478" width="18" height="18"/>
|
||||
<SubTexture name="grey_crossWhite.png" x="190" y="318" width="18" height="18"/>
|
||||
<SubTexture name="grey_panel.png" x="190" y="98" width="100" height="100"/>
|
||||
<SubTexture name="grey_sliderDown.png" x="190" y="234" width="28" height="42"/>
|
||||
<SubTexture name="grey_sliderEnd.png" x="138" y="478" width="8" height="10"/>
|
||||
<SubTexture name="grey_sliderHorizontal.png" x="0" y="380" width="190" height="4"/>
|
||||
<SubTexture name="grey_sliderLeft.png" x="0" y="478" width="39" height="31"/>
|
||||
<SubTexture name="grey_sliderRight.png" x="39" y="478" width="39" height="31"/>
|
||||
<SubTexture name="grey_sliderUp.png" x="190" y="276" width="28" height="42"/>
|
||||
<SubTexture name="grey_sliderVertical.png" x="208" y="318" width="4" height="100"/>
|
||||
<SubTexture name="grey_tickGrey.png" x="190" y="336" width="17" height="17"/>
|
||||
<SubTexture name="grey_tickWhite.png" x="190" y="353" width="17" height="17"/>
|
||||
</TextureAtlas>
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
@@ -1,11 +0,0 @@
|
||||
# {project_name}
|
||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||
|
||||
## Getting Started
|
||||
To build this library, use:
|
||||
|
||||
```bash
|
||||
rojo build -o "{project_name}.rbxmx"
|
||||
```
|
||||
|
||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "{project_name}",
|
||||
"tree": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# Roblox Studio lock files
|
||||
/*.rbxlx.lock
|
||||
/*.rbxl.lock
|
||||
|
||||
sourcemap.json
|
||||
@@ -1,5 +0,0 @@
|
||||
return {
|
||||
hello = function()
|
||||
print("Hello world, from {project_name}!")
|
||||
end,
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# {project_name}
|
||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||
|
||||
## Getting Started
|
||||
To build the place from scratch, use:
|
||||
|
||||
```bash
|
||||
rojo build -o "{project_name}.rbxlx"
|
||||
```
|
||||
|
||||
Next, open `{project_name}.rbxlx` in Roblox Studio and start the Rojo server:
|
||||
|
||||
```bash
|
||||
rojo serve
|
||||
```
|
||||
|
||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
||||
@@ -1,8 +0,0 @@
|
||||
# Project place file
|
||||
/{project_name}.rbxlx
|
||||
|
||||
# Roblox Studio lock files
|
||||
/*.rbxlx.lock
|
||||
/*.rbxl.lock
|
||||
|
||||
sourcemap.json
|
||||
@@ -1 +0,0 @@
|
||||
print("Hello world, from client!")
|
||||
@@ -1 +0,0 @@
|
||||
print("Hello world, from server!")
|
||||
@@ -1,3 +0,0 @@
|
||||
return function()
|
||||
print("Hello, world!")
|
||||
end
|
||||
@@ -1,17 +0,0 @@
|
||||
# {project_name}
|
||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||
|
||||
## Getting Started
|
||||
To build this plugin to your local plugins folder, use:
|
||||
|
||||
```bash
|
||||
rojo build -p "{project_name}.rbxm"
|
||||
```
|
||||
|
||||
You can include the `watch` flag to re-build it on save:
|
||||
|
||||
```bash
|
||||
rojo build -p "{project_name}.rbxm" --watch
|
||||
```
|
||||
|
||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "{project_name}",
|
||||
"tree": {
|
||||
"$path": "src"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# Plugin model files
|
||||
/{project_name}.rbxmx
|
||||
/{project_name}.rbxm
|
||||
|
||||
sourcemap.json
|
||||
@@ -1 +0,0 @@
|
||||
print("Hello world, from plugin!")
|
||||
|
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 175 B |
@@ -1,44 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use librojo::cli::BuildCommand;
|
||||
|
||||
pub fn benchmark_small_place(c: &mut Criterion) {
|
||||
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
||||
}
|
||||
|
||||
criterion_group!(benches, benchmark_small_place);
|
||||
criterion_main!(benches);
|
||||
|
||||
fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
||||
let mut group = c.benchmark_group(name);
|
||||
|
||||
// 'rojo build' generally takes a fair bit of time to execute.
|
||||
group.sample_size(10);
|
||||
group.bench_function("build", |b| {
|
||||
b.iter_batched(
|
||||
|| place_setup(path),
|
||||
|(_dir, options)| options.run().unwrap(),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
||||
let dir = tempdir().unwrap();
|
||||
let input = input_path.as_ref().to_path_buf();
|
||||
let output = Some(dir.path().join("output.rbxlx"));
|
||||
|
||||
let options = BuildCommand {
|
||||
project: input,
|
||||
watch: false,
|
||||
plugin: None,
|
||||
output,
|
||||
};
|
||||
|
||||
(dir, options)
|
||||
}
|
||||
21
bin/test-scratch-project
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copies a project from 'test-projects' into a folder that can be messed with
|
||||
# without accidentally checking the results into version control.
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -d "test-projects/$1" ]
|
||||
then
|
||||
echo "Pick a project that exists!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "scratch-project/$1" ]
|
||||
then
|
||||
rm -rf "scratch-project/$1"
|
||||
fi
|
||||
|
||||
mkdir -p scratch-project
|
||||
cp -r "test-projects/$1" scratch-project
|
||||
cargo run -- serve "scratch-project/$1"
|
||||
93
build.rs
@@ -1,93 +0,0 @@
|
||||
use std::{
|
||||
env, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use fs_err as fs;
|
||||
use fs_err::File;
|
||||
use maplit::hashmap;
|
||||
use memofs::VfsSnapshot;
|
||||
use semver::Version;
|
||||
|
||||
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
|
||||
if path.is_dir() {
|
||||
let mut children = Vec::new();
|
||||
|
||||
for entry in fs::read_dir(path)? {
|
||||
let entry = entry?;
|
||||
|
||||
let file_name = entry.file_name().to_str().unwrap().to_owned();
|
||||
|
||||
if file_name.starts_with(".git") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We can skip any TestEZ test files since they aren't necessary for
|
||||
// the plugin to run.
|
||||
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore images in msgpack-luau because they aren't UTF-8 encoded.
|
||||
if file_name.ends_with(".png") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let child_snapshot = snapshot_from_fs_path(&entry.path())?;
|
||||
children.push((file_name, child_snapshot));
|
||||
}
|
||||
|
||||
Ok(VfsSnapshot::dir(children))
|
||||
} else {
|
||||
let content = fs::read_to_string(path)?;
|
||||
|
||||
Ok(VfsSnapshot::file(content))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
let root_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||
let plugin_dir = root_dir.join("plugin");
|
||||
let templates_dir = root_dir.join("assets").join("project-templates");
|
||||
|
||||
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
|
||||
let plugin_version =
|
||||
Version::parse(fs::read_to_string(plugin_dir.join("Version.txt"))?.trim())?;
|
||||
|
||||
assert_eq!(
|
||||
our_version, plugin_version,
|
||||
"plugin version does not match Cargo version"
|
||||
);
|
||||
|
||||
let template_snapshot = snapshot_from_fs_path(&templates_dir)?;
|
||||
|
||||
let plugin_snapshot = VfsSnapshot::dir(hashmap! {
|
||||
"default.project.json" => snapshot_from_fs_path(&root_dir.join("plugin.project.json"))?,
|
||||
"plugin" => VfsSnapshot::dir(hashmap! {
|
||||
"fmt" => snapshot_from_fs_path(&plugin_dir.join("fmt"))?,
|
||||
"http" => snapshot_from_fs_path(&plugin_dir.join("http"))?,
|
||||
"log" => snapshot_from_fs_path(&plugin_dir.join("log"))?,
|
||||
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_dir.join("rbx_dom_lua"))?,
|
||||
"src" => snapshot_from_fs_path(&plugin_dir.join("src"))?,
|
||||
"Packages" => snapshot_from_fs_path(&plugin_dir.join("Packages"))?,
|
||||
"Version.txt" => snapshot_from_fs_path(&plugin_dir.join("Version.txt"))?,
|
||||
"UploadDetails.json" => snapshot_from_fs_path(&plugin_dir.join("UploadDetails.json"))?,
|
||||
}),
|
||||
});
|
||||
|
||||
let template_file = File::create(Path::new(&out_dir).join("templates.bincode"))?;
|
||||
let plugin_file = File::create(Path::new(&out_dir).join("plugin.bincode"))?;
|
||||
|
||||
bincode::serialize_into(plugin_file, &plugin_snapshot)?;
|
||||
bincode::serialize_into(template_file, &template_snapshot)?;
|
||||
|
||||
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
|
||||
println!("cargo:rerun-if-changed=build/windows/rojo.manifest");
|
||||
embed_resource::compile("build/windows/rojo-manifest.rc");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#define RT_MANIFEST 24
|
||||
1 RT_MANIFEST "rojo.manifest"
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||
<ws2:longPathAware>true</ws2:longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -1,33 +0,0 @@
|
||||
# memofs Changelog
|
||||
|
||||
## Unreleased Changes
|
||||
* Added `Vfs::canonicalize`. [#1201]
|
||||
|
||||
## 0.3.1 (2025-11-27)
|
||||
* Added `Vfs::exists`. [#1169]
|
||||
* Added `create_dir` and `create_dir_all` to allow creating directories. [#937]
|
||||
|
||||
[#1169]: https://github.com/rojo-rbx/rojo/pull/1169
|
||||
[#937]: https://github.com/rojo-rbx/rojo/pull/937
|
||||
|
||||
## 0.3.0 (2024-03-15)
|
||||
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
||||
* Added `Vfs::read_to_string` and `Vfs::read_to_string_lf_normalized` [#854]
|
||||
|
||||
[#830]: https://github.com/rojo-rbx/rojo/pull/830
|
||||
[#854]: https://github.com/rojo-rbx/rojo/pull/854
|
||||
|
||||
## 0.2.0 (2021-08-23)
|
||||
* Updated to `crossbeam-channel` 0.5.1.
|
||||
|
||||
## 0.1.3 (2020-11-19)
|
||||
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
||||
|
||||
## 0.1.2 (2020-03-29)
|
||||
* `VfsSnapshot` now implements Serde's `Serialize` and `Deserialize` traits.
|
||||
|
||||
## 0.1.1 (2020-03-18)
|
||||
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
||||
|
||||
## 0.1.0 (2020-03-10)
|
||||
* Initial release
|
||||
@@ -1,24 +0,0 @@
|
||||
[package]
|
||||
name = "memofs"
|
||||
description = "Virtual filesystem with configurable backends."
|
||||
version = "0.3.1"
|
||||
authors = [
|
||||
"Lucien Greathouse <me@lpghatguy.com>",
|
||||
"Micah Reid <git@dekkonot.com>",
|
||||
"Ken Loeffler <kenloef@gmail.com>",
|
||||
]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.5.12"
|
||||
fs-err = "2.11.0"
|
||||
notify = "4.0.17"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.10.1"
|
||||
@@ -1,7 +0,0 @@
|
||||
Copyright 2020 The Rojo Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,22 +0,0 @@
|
||||
# memofs
|
||||
[](https://crates.io/crates/memofs)
|
||||
|
||||
Implementation of a virtual filesystem with a configurable backend and file
|
||||
watching.
|
||||
|
||||
memofs is currently an unstable minimum viable library. Its primary consumer is
|
||||
[Rojo](https://github.com/rojo-rbx/rojo), a build system for Roblox.
|
||||
|
||||
### Current Features
|
||||
* API similar to `std::fs`
|
||||
* Configurable backends
|
||||
* `StdBackend`, which uses `std::fs` and the `notify` crate
|
||||
* `NoopBackend`, which always throws errors
|
||||
* `InMemoryFs`, a simple in-memory filesystem useful for testing
|
||||
|
||||
### Future Features
|
||||
* Hash-based hierarchical memoization keys (hence the name)
|
||||
* Configurable caching (write-through, write-around, write-back)
|
||||
|
||||
## License
|
||||
memofs is available under the terms of the MIT license. See [LICENSE.txt](LICENSE.txt) or <https://opensource.org/licenses/MIT> for more details.
|
||||
@@ -1,7 +0,0 @@
|
||||
# {{crate}}
|
||||
[](https://crates.io/crates/memofs)
|
||||
|
||||
{{readme}}
|
||||
|
||||
## License
|
||||
memofs is available under the terms of the MIT license. See [LICENSE.txt](LICENSE.txt) or <https://opensource.org/licenses/MIT> for more details.
|
||||
@@ -1,296 +0,0 @@
|
||||
use std::collections::{BTreeSet, HashMap, VecDeque};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
|
||||
use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent, VfsSnapshot};
|
||||
|
||||
/// In-memory filesystem that can be used as a VFS backend.
|
||||
///
|
||||
/// Internally reference counted to enable giving a copy to
|
||||
/// [`Vfs`](struct.Vfs.html) and keeping the original to mutate the filesystem's
|
||||
/// state with.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InMemoryFs {
|
||||
inner: Arc<Mutex<InMemoryFsInner>>,
|
||||
}
|
||||
|
||||
impl InMemoryFs {
|
||||
/// Create a new empty `InMemoryFs`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(InMemoryFsInner::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a [`VfsSnapshot`](enum.VfsSnapshot.html) into a subtree of the
|
||||
/// in-memory filesystem.
|
||||
///
|
||||
/// This function will return an error if the operations required to apply
|
||||
/// the snapshot result in errors, like trying to create a file inside a
|
||||
/// file.
|
||||
pub fn load_snapshot<P: Into<PathBuf>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
snapshot: VfsSnapshot,
|
||||
) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.load_snapshot(path.into(), snapshot)
|
||||
}
|
||||
|
||||
/// Raises a filesystem change event.
|
||||
///
|
||||
/// If this `InMemoryFs` is being used as the backend of a
|
||||
/// [`Vfs`](struct.Vfs.html), then any listeners be notified of this event.
|
||||
pub fn raise_event(&mut self, event: VfsEvent) {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
inner.event_sender.send(event).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InMemoryFs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InMemoryFsInner {
|
||||
entries: HashMap<PathBuf, Entry>,
|
||||
orphans: BTreeSet<PathBuf>,
|
||||
|
||||
event_receiver: Receiver<VfsEvent>,
|
||||
event_sender: Sender<VfsEvent>,
|
||||
}
|
||||
|
||||
impl InMemoryFsInner {
|
||||
fn new() -> Self {
|
||||
let (event_sender, event_receiver) = crossbeam_channel::unbounded();
|
||||
|
||||
Self {
|
||||
entries: HashMap::new(),
|
||||
orphans: BTreeSet::new(),
|
||||
event_receiver,
|
||||
event_sender,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_snapshot(&mut self, path: PathBuf, snapshot: VfsSnapshot) -> io::Result<()> {
|
||||
if let Some(parent_path) = path.parent() {
|
||||
if let Some(parent_entry) = self.entries.get_mut(parent_path) {
|
||||
if let Entry::Dir { children } = parent_entry {
|
||||
children.insert(path.clone());
|
||||
} else {
|
||||
return must_be_dir(parent_path);
|
||||
}
|
||||
} else {
|
||||
self.orphans.insert(path.clone());
|
||||
}
|
||||
} else {
|
||||
self.orphans.insert(path.clone());
|
||||
}
|
||||
|
||||
match snapshot {
|
||||
VfsSnapshot::File { contents } => {
|
||||
self.entries.insert(path, Entry::File { contents });
|
||||
}
|
||||
VfsSnapshot::Dir { children } => {
|
||||
self.entries.insert(
|
||||
path.clone(),
|
||||
Entry::Dir {
|
||||
children: BTreeSet::new(),
|
||||
},
|
||||
);
|
||||
|
||||
for (child_name, child) in children {
|
||||
let full_path = path.join(child_name);
|
||||
self.load_snapshot(full_path, child)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&mut self, root_path: PathBuf) {
|
||||
self.orphans.remove(&root_path);
|
||||
|
||||
let mut to_remove = VecDeque::new();
|
||||
to_remove.push_back(root_path);
|
||||
|
||||
while let Some(path) = to_remove.pop_front() {
|
||||
if let Some(Entry::Dir { children }) = self.entries.remove(&path) {
|
||||
to_remove.extend(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Entry {
|
||||
File { contents: Vec<u8> },
|
||||
|
||||
Dir { children: BTreeSet<PathBuf> },
|
||||
}
|
||||
|
||||
impl VfsBackend for InMemoryFs {
|
||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::File { contents }) => Ok(contents.clone()),
|
||||
Some(Entry::Dir { .. }) => must_be_file(path),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
inner.load_snapshot(
|
||||
path.to_path_buf(),
|
||||
VfsSnapshot::File {
|
||||
contents: data.to_owned(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn exists(&mut self, path: &Path) -> io::Result<bool> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
Ok(inner.entries.contains_key(path))
|
||||
}
|
||||
|
||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::Dir { children }) => {
|
||||
let iter = children
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|path| Ok(DirEntry { path }));
|
||||
|
||||
Ok(ReadDir {
|
||||
inner: Box::new(iter),
|
||||
})
|
||||
}
|
||||
Some(Entry::File { .. }) => must_be_dir(path),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_dir(&mut self, path: &Path) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.load_snapshot(path.to_path_buf(), VfsSnapshot::empty_dir())
|
||||
}
|
||||
|
||||
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
let mut path_buf = path.to_path_buf();
|
||||
while let Some(parent) = path_buf.parent() {
|
||||
inner.load_snapshot(parent.to_path_buf(), VfsSnapshot::empty_dir())?;
|
||||
path_buf.pop();
|
||||
}
|
||||
inner.load_snapshot(path.to_path_buf(), VfsSnapshot::empty_dir())
|
||||
}
|
||||
|
||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::File { .. }) => {
|
||||
inner.remove(path.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
Some(Entry::Dir { .. }) => must_be_file(path),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::Dir { .. }) => {
|
||||
inner.remove(path.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
Some(Entry::File { .. }) => must_be_dir(path),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
|
||||
match inner.entries.get(path) {
|
||||
Some(Entry::File { .. }) => Ok(Metadata { is_file: true }),
|
||||
Some(Entry::Dir { .. }) => Ok(Metadata { is_file: false }),
|
||||
None => not_found(path),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We rely on Rojo to prepend cwd to any relative path before storing paths
|
||||
// in MemoFS. The current implementation will error if no prepended absolute path
|
||||
// is found. It really only normalizes paths within the provided path's context.
|
||||
// Example: "/Users/username/project/../other/file.txt" ->
|
||||
// "/Users/username/other/file.txt"
|
||||
// Erroneous example: "/Users/../../other/file.txt" -> "/other/file.txt"
|
||||
// This is not very robust. We should implement proper path normalization here or otherwise
|
||||
// warn if we are missing context and can not fully canonicalize the path correctly.
|
||||
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
|
||||
let mut normalized = PathBuf::new();
|
||||
for component in path.components() {
|
||||
match component {
|
||||
std::path::Component::ParentDir => {
|
||||
normalized.pop();
|
||||
}
|
||||
std::path::Component::CurDir => {}
|
||||
_ => normalized.push(component),
|
||||
}
|
||||
}
|
||||
|
||||
let inner = self.inner.lock().unwrap();
|
||||
match inner.entries.get(&normalized) {
|
||||
Some(_) => Ok(normalized),
|
||||
None => not_found(&normalized),
|
||||
}
|
||||
}
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
|
||||
inner.event_receiver.clone()
|
||||
}
|
||||
|
||||
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn must_be_file<T>(path: &Path) -> io::Result<T> {
|
||||
Err(io::Error::other(format!(
|
||||
"path {} was a directory, but must be a file",
|
||||
path.display()
|
||||
)))
|
||||
}
|
||||
|
||||
fn must_be_dir<T>(path: &Path) -> io::Result<T> {
|
||||
Err(io::Error::other(format!(
|
||||
"path {} was a file, but must be a directory",
|
||||
path.display()
|
||||
)))
|
||||
}
|
||||
|
||||
fn not_found<T>(path: &Path) -> io::Result<T> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("path {} not found", path.display()),
|
||||
))
|
||||
}
|
||||
@@ -1,660 +0,0 @@
|
||||
/*!
|
||||
Implementation of a virtual filesystem with a configurable backend and file
|
||||
watching.
|
||||
|
||||
memofs is currently an unstable minimum viable library. Its primary consumer is
|
||||
[Rojo](https://github.com/rojo-rbx/rojo), a build system for Roblox.
|
||||
|
||||
## Current Features
|
||||
* API similar to `std::fs`
|
||||
* Configurable backends
|
||||
* `StdBackend`, which uses `std::fs` and the `notify` crate
|
||||
* `NoopBackend`, which always throws errors
|
||||
* `InMemoryFs`, a simple in-memory filesystem useful for testing
|
||||
|
||||
## Future Features
|
||||
* Hash-based hierarchical memoization keys (hence the name)
|
||||
* Configurable caching (write-through, write-around, write-back)
|
||||
*/
|
||||
|
||||
mod in_memory_fs;
|
||||
mod noop_backend;
|
||||
mod snapshot;
|
||||
mod std_backend;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::{io, str};
|
||||
|
||||
pub use in_memory_fs::InMemoryFs;
|
||||
pub use noop_backend::NoopBackend;
|
||||
pub use snapshot::VfsSnapshot;
|
||||
pub use std_backend::StdBackend;
|
||||
|
||||
mod sealed {
|
||||
use super::*;
|
||||
|
||||
/// Sealing trait for VfsBackend.
|
||||
pub trait Sealed {}
|
||||
|
||||
impl Sealed for NoopBackend {}
|
||||
impl Sealed for StdBackend {}
|
||||
impl Sealed for InMemoryFs {}
|
||||
}
|
||||
|
||||
/// Trait that transforms `io::Result<T>` into `io::Result<Option<T>>`.
|
||||
///
|
||||
/// `Ok(None)` takes the place of IO errors whose `io::ErrorKind` is `NotFound`.
|
||||
pub trait IoResultExt<T> {
|
||||
fn with_not_found(self) -> io::Result<Option<T>>;
|
||||
}
|
||||
|
||||
impl<T> IoResultExt<T> for io::Result<T> {
|
||||
fn with_not_found(self) -> io::Result<Option<T>> {
|
||||
match self {
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(err) => {
|
||||
if err.kind() == io::ErrorKind::NotFound {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Backend that can be used to create a `Vfs`.
|
||||
///
|
||||
/// This trait is sealed and cannot not be implemented outside this crate.
|
||||
pub trait VfsBackend: sealed::Sealed + Send + 'static {
|
||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>>;
|
||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()>;
|
||||
fn exists(&mut self, path: &Path) -> io::Result<bool>;
|
||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir>;
|
||||
fn create_dir(&mut self, path: &Path) -> io::Result<()>;
|
||||
fn create_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata>;
|
||||
fn remove_file(&mut self, path: &Path) -> io::Result<()>;
|
||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>;
|
||||
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf>;
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent>;
|
||||
fn watch(&mut self, path: &Path) -> io::Result<()>;
|
||||
fn unwatch(&mut self, path: &Path) -> io::Result<()>;
|
||||
}
|
||||
|
||||
/// Vfs equivalent to [`std::fs::DirEntry`][std::fs::DirEntry].
|
||||
///
|
||||
/// [std::fs::DirEntry]: https://doc.rust-lang.org/stable/std/fs/struct.DirEntry.html
|
||||
pub struct DirEntry {
|
||||
pub(crate) path: PathBuf,
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
/// Vfs equivalent to [`std::fs::ReadDir`][std::fs::ReadDir].
|
||||
///
|
||||
/// [std::fs::ReadDir]: https://doc.rust-lang.org/stable/std/fs/struct.ReadDir.html
|
||||
pub struct ReadDir {
|
||||
pub(crate) inner: Box<dyn Iterator<Item = io::Result<DirEntry>>>,
|
||||
}
|
||||
|
||||
impl Iterator for ReadDir {
|
||||
type Item = io::Result<DirEntry>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
/// Vfs equivalent to [`std::fs::Metadata`][std::fs::Metadata].
|
||||
///
|
||||
/// [std::fs::Metadata]: https://doc.rust-lang.org/stable/std/fs/struct.Metadata.html
|
||||
#[derive(Debug)]
|
||||
pub struct Metadata {
|
||||
pub(crate) is_file: bool,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub fn is_file(&self) -> bool {
|
||||
self.is_file
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
!self.is_file
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an event that a filesystem can raise that might need to be
|
||||
/// handled.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum VfsEvent {
|
||||
Create(PathBuf),
|
||||
Write(PathBuf),
|
||||
Remove(PathBuf),
|
||||
}
|
||||
|
||||
/// Contains implementation details of the Vfs, wrapped by `Vfs` and `VfsLock`,
|
||||
/// the public interfaces to this type.
|
||||
struct VfsInner {
|
||||
backend: Box<dyn VfsBackend>,
|
||||
watch_enabled: bool,
|
||||
}
|
||||
|
||||
impl VfsInner {
|
||||
fn read<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<Vec<u8>>> {
|
||||
let path = path.as_ref();
|
||||
let contents = self.backend.read(path)?;
|
||||
|
||||
if self.watch_enabled {
|
||||
self.backend.watch(path)?;
|
||||
}
|
||||
|
||||
Ok(Arc::new(contents))
|
||||
}
|
||||
|
||||
fn read_to_string<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<String>> {
|
||||
let path = path.as_ref();
|
||||
let contents = self.backend.read(path)?;
|
||||
|
||||
if self.watch_enabled {
|
||||
self.backend.watch(path)?;
|
||||
}
|
||||
|
||||
let contents_str = str::from_utf8(&contents).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("File was not valid UTF-8: {}", path.display()),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Arc::new(contents_str.into()))
|
||||
}
|
||||
|
||||
fn exists<P: AsRef<Path>>(&mut self, path: P) -> io::Result<bool> {
|
||||
let path = path.as_ref();
|
||||
self.backend.exists(path)
|
||||
}
|
||||
|
||||
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
self.backend.write(path, contents)
|
||||
}
|
||||
|
||||
fn read_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref();
|
||||
let dir = self.backend.read_dir(path)?;
|
||||
|
||||
if self.watch_enabled {
|
||||
self.backend.watch(path)?;
|
||||
}
|
||||
|
||||
Ok(dir)
|
||||
}
|
||||
|
||||
fn create_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.backend.create_dir(path)
|
||||
}
|
||||
|
||||
fn create_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.backend.create_dir_all(path)
|
||||
}
|
||||
|
||||
fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let _ = self.backend.unwatch(path);
|
||||
self.backend.remove_file(path)
|
||||
}
|
||||
|
||||
fn remove_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let _ = self.backend.unwatch(path);
|
||||
self.backend.remove_dir_all(path)
|
||||
}
|
||||
|
||||
fn metadata<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Metadata> {
|
||||
let path = path.as_ref();
|
||||
self.backend.metadata(path)
|
||||
}
|
||||
|
||||
fn canonicalize<P: AsRef<Path>>(&mut self, path: P) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
self.backend.canonicalize(path)
|
||||
}
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
self.backend.event_receiver()
|
||||
}
|
||||
|
||||
fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
||||
if let VfsEvent::Remove(path) = event {
|
||||
let _ = self.backend.unwatch(path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A virtual filesystem with a configurable backend.
|
||||
///
|
||||
/// All operations on the Vfs take a lock on an internal backend. For performing
|
||||
/// large batches of operations, it might be more performant to call `lock()`
|
||||
/// and use [`VfsLock`](struct.VfsLock.html) instead.
|
||||
pub struct Vfs {
|
||||
inner: Mutex<VfsInner>,
|
||||
}
|
||||
|
||||
impl Vfs {
|
||||
/// Creates a new `Vfs` with the default backend, `StdBackend`.
|
||||
pub fn new_default() -> Self {
|
||||
Self::new(StdBackend::new())
|
||||
}
|
||||
|
||||
/// Creates a new `Vfs` with the given backend.
|
||||
pub fn new<B: VfsBackend>(backend: B) -> Self {
|
||||
let lock = VfsInner {
|
||||
backend: Box::new(backend),
|
||||
watch_enabled: true,
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: Mutex::new(lock),
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually lock the Vfs, useful for large batches of operations.
|
||||
pub fn lock(&self) -> VfsLock<'_> {
|
||||
VfsLock {
|
||||
inner: self.inner.lock().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns automatic file watching on or off. Enabled by default.
|
||||
///
|
||||
/// Turning off file watching may be useful for single-use cases, especially
|
||||
/// on platforms like macOS where registering file watches has significant
|
||||
/// performance cost.
|
||||
pub fn set_watch_enabled(&self, enabled: bool) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.watch_enabled = enabled;
|
||||
}
|
||||
|
||||
/// Read a file from the VFS, or the underlying backend if it isn't
|
||||
/// resident.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read`][std::fs::read].
|
||||
///
|
||||
/// [std::fs::read]: https://doc.rust-lang.org/stable/std/fs/fn.read.html
|
||||
#[inline]
|
||||
pub fn read<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<Vec<u8>>> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().read(path)
|
||||
}
|
||||
|
||||
/// Read a file from the VFS (or from the underlying backend if it isn't
|
||||
/// resident) into a string.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string].
|
||||
///
|
||||
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
||||
#[inline]
|
||||
pub fn read_to_string<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().read_to_string(path)
|
||||
}
|
||||
|
||||
/// Read a file from the VFS (or the underlying backend if it isn't
|
||||
/// resident) into a string, and normalize its line endings to LF.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string], but also performs
|
||||
/// line ending normalization.
|
||||
///
|
||||
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
||||
#[inline]
|
||||
pub fn read_to_string_lf_normalized<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
||||
let path = path.as_ref();
|
||||
let contents = self.inner.lock().unwrap().read_to_string(path)?;
|
||||
|
||||
Ok(contents.replace("\r\n", "\n").into())
|
||||
}
|
||||
|
||||
/// Write a file to the VFS and the underlying backend.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
||||
///
|
||||
/// [std::fs::write]: https://doc.rust-lang.org/stable/std/fs/fn.write.html
|
||||
#[inline]
|
||||
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&self, path: P, contents: C) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
self.inner.lock().unwrap().write(path, contents)
|
||||
}
|
||||
|
||||
/// Read all of the children of a directory.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read_dir`][std::fs::read_dir].
|
||||
///
|
||||
/// [std::fs::read_dir]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html
|
||||
#[inline]
|
||||
pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().read_dir(path)
|
||||
}
|
||||
|
||||
/// Return whether the given path exists.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::exists`][std::fs::exists].
|
||||
///
|
||||
/// [std::fs::exists]: https://doc.rust-lang.org/stable/std/fs/fn.exists.html
|
||||
#[inline]
|
||||
pub fn exists<P: AsRef<Path>>(&self, path: P) -> io::Result<bool> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().exists(path)
|
||||
}
|
||||
|
||||
/// Creates a directory at the provided location.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::create_dir`][std::fs::create_dir].
|
||||
/// Similiar to that function, this function will fail if the parent of the
|
||||
/// path does not exist.
|
||||
///
|
||||
/// [std::fs::create_dir]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html
|
||||
#[inline]
|
||||
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().create_dir(path)
|
||||
}
|
||||
|
||||
/// Creates a directory at the provided location, recursively creating
|
||||
/// all parent components if they are missing.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::create_dir_all`][std::fs::create_dir_all].
|
||||
///
|
||||
/// [std::fs::create_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html
|
||||
#[inline]
|
||||
pub fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().create_dir_all(path)
|
||||
}
|
||||
|
||||
/// Remove a file.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
||||
///
|
||||
/// [std::fs::remove_file]: https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html
|
||||
#[inline]
|
||||
pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().remove_file(path)
|
||||
}
|
||||
|
||||
/// Remove a directory and all of its descendants.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::remove_dir_all`][std::fs::remove_dir_all].
|
||||
///
|
||||
/// [std::fs::remove_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html
|
||||
#[inline]
|
||||
pub fn remove_dir_all<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().remove_dir_all(path)
|
||||
}
|
||||
|
||||
/// Query metadata about the given path.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::metadata`][std::fs::metadata].
|
||||
///
|
||||
/// [std::fs::metadata]: https://doc.rust-lang.org/stable/std/fs/fn.metadata.html
|
||||
#[inline]
|
||||
pub fn metadata<P: AsRef<Path>>(&self, path: P) -> io::Result<Metadata> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().metadata(path)
|
||||
}
|
||||
|
||||
/// Normalize a path via the underlying backend.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::canonicalize`][std::fs::canonicalize]. Relative paths are
|
||||
/// resolved against the backend's current working directory (if applicable) and errors are
|
||||
/// surfaced directly from the backend.
|
||||
///
|
||||
/// [std::fs::canonicalize]: https://doc.rust-lang.org/stable/std/fs/fn.canonicalize.html
|
||||
#[inline]
|
||||
pub fn canonicalize<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
self.inner.lock().unwrap().canonicalize(path)
|
||||
}
|
||||
|
||||
/// Retrieve a handle to the event receiver for this `Vfs`.
|
||||
#[inline]
|
||||
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
self.inner.lock().unwrap().event_receiver()
|
||||
}
|
||||
|
||||
/// Commit an event to this `Vfs`.
|
||||
#[inline]
|
||||
pub fn commit_event(&self, event: &VfsEvent) -> io::Result<()> {
|
||||
self.inner.lock().unwrap().commit_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
/// A locked handle to a [`Vfs`](struct.Vfs.html), created by `Vfs::lock`.
|
||||
///
|
||||
/// Implements roughly the same API as [`Vfs`](struct.Vfs.html).
|
||||
pub struct VfsLock<'a> {
|
||||
inner: MutexGuard<'a, VfsInner>,
|
||||
}
|
||||
|
||||
impl VfsLock<'_> {
|
||||
/// Turns automatic file watching on or off. Enabled by default.
|
||||
///
|
||||
/// Turning off file watching may be useful for single-use cases, especially
|
||||
/// on platforms like macOS where registering file watches has significant
|
||||
/// performance cost.
|
||||
pub fn set_watch_enabled(&mut self, enabled: bool) {
|
||||
self.inner.watch_enabled = enabled;
|
||||
}
|
||||
|
||||
/// Read a file from the VFS, or the underlying backend if it isn't
|
||||
/// resident.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read`][std::fs::read].
|
||||
///
|
||||
/// [std::fs::read]: https://doc.rust-lang.org/stable/std/fs/fn.read.html
|
||||
#[inline]
|
||||
pub fn read<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<Vec<u8>>> {
|
||||
let path = path.as_ref();
|
||||
self.inner.read(path)
|
||||
}
|
||||
|
||||
/// Write a file to the VFS and the underlying backend.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
||||
///
|
||||
/// [std::fs::write]: https://doc.rust-lang.org/stable/std/fs/fn.write.html
|
||||
#[inline]
|
||||
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
contents: C,
|
||||
) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
let contents = contents.as_ref();
|
||||
self.inner.write(path, contents)
|
||||
}
|
||||
|
||||
/// Read all of the children of a directory.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::read_dir`][std::fs::read_dir].
|
||||
///
|
||||
/// [std::fs::read_dir]: https://doc.rust-lang.org/stable/std/fs/fn.read_dir.html
|
||||
#[inline]
|
||||
pub fn read_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref();
|
||||
self.inner.read_dir(path)
|
||||
}
|
||||
|
||||
/// Creates a directory at the provided location.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::create_dir`][std::fs::create_dir].
|
||||
/// Similiar to that function, this function will fail if the parent of the
|
||||
/// path does not exist.
|
||||
///
|
||||
/// [std::fs::create_dir]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir.html
|
||||
#[inline]
|
||||
pub fn create_dir<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.create_dir(path)
|
||||
}
|
||||
|
||||
/// Creates a directory at the provided location, recursively creating
|
||||
/// all parent components if they are missing.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::create_dir_all`][std::fs::create_dir_all].
|
||||
///
|
||||
/// [std::fs::create_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.create_dir_all.html
|
||||
#[inline]
|
||||
pub fn create_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.create_dir_all(path)
|
||||
}
|
||||
|
||||
/// Remove a file.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::remove_file`][std::fs::remove_file].
|
||||
///
|
||||
/// [std::fs::remove_file]: https://doc.rust-lang.org/stable/std/fs/fn.remove_file.html
|
||||
#[inline]
|
||||
pub fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.remove_file(path)
|
||||
}
|
||||
|
||||
/// Remove a directory and all of its descendants.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::remove_dir_all`][std::fs::remove_dir_all].
|
||||
///
|
||||
/// [std::fs::remove_dir_all]: https://doc.rust-lang.org/stable/std/fs/fn.remove_dir_all.html
|
||||
#[inline]
|
||||
pub fn remove_dir_all<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
self.inner.remove_dir_all(path)
|
||||
}
|
||||
|
||||
/// Query metadata about the given path.
|
||||
///
|
||||
/// Roughly equivalent to [`std::fs::metadata`][std::fs::metadata].
|
||||
///
|
||||
/// [std::fs::metadata]: https://doc.rust-lang.org/stable/std/fs/fn.metadata.html
|
||||
#[inline]
|
||||
pub fn metadata<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Metadata> {
|
||||
let path = path.as_ref();
|
||||
self.inner.metadata(path)
|
||||
}
|
||||
|
||||
/// Normalize a path via the underlying backend.
|
||||
#[inline]
|
||||
pub fn normalize<P: AsRef<Path>>(&mut self, path: P) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
self.inner.canonicalize(path)
|
||||
}
|
||||
|
||||
/// Retrieve a handle to the event receiver for this `Vfs`.
|
||||
#[inline]
|
||||
pub fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
self.inner.event_receiver()
|
||||
}
|
||||
|
||||
/// Commit an event to this `Vfs`.
|
||||
#[inline]
|
||||
pub fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
||||
self.inner.commit_event(event)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{InMemoryFs, StdBackend, Vfs, VfsSnapshot};
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// https://github.com/rojo-rbx/rojo/issues/899
|
||||
#[test]
|
||||
fn read_to_string_lf_normalized_keeps_trailing_newline() {
|
||||
let mut imfs = InMemoryFs::new();
|
||||
imfs.load_snapshot("test", VfsSnapshot::file("bar\r\nfoo\r\n\r\n"))
|
||||
.unwrap();
|
||||
|
||||
let vfs = Vfs::new(imfs);
|
||||
|
||||
assert_eq!(
|
||||
vfs.read_to_string_lf_normalized("test").unwrap().as_str(),
|
||||
"bar\nfoo\n\n"
|
||||
);
|
||||
}
|
||||
|
||||
/// https://github.com/rojo-rbx/rojo/issues/1200
|
||||
#[test]
|
||||
fn canonicalize_in_memory_success() {
|
||||
let mut imfs = InMemoryFs::new();
|
||||
let contents = "Lorem ipsum dolor sit amet.".to_string();
|
||||
|
||||
imfs.load_snapshot("/test/file.txt", VfsSnapshot::file(contents.to_string()))
|
||||
.unwrap();
|
||||
|
||||
let vfs = Vfs::new(imfs);
|
||||
|
||||
assert_eq!(
|
||||
vfs.canonicalize("/test/nested/../file.txt").unwrap(),
|
||||
PathBuf::from("/test/file.txt")
|
||||
);
|
||||
assert_eq!(
|
||||
vfs.read_to_string(vfs.canonicalize("/test/nested/../file.txt").unwrap())
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
contents.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_in_memory_missing_errors() {
|
||||
let imfs = InMemoryFs::new();
|
||||
let vfs = Vfs::new(imfs);
|
||||
|
||||
let err = vfs.canonicalize("test").unwrap_err();
|
||||
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_std_backend_success() {
|
||||
let contents = "Lorem ipsum dolor sit amet.".to_string();
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let file_path = dir.path().join("file.txt");
|
||||
fs_err::write(&file_path, contents.to_string()).unwrap();
|
||||
|
||||
let vfs = Vfs::new(StdBackend::new());
|
||||
let canonicalized = vfs.canonicalize(&file_path).unwrap();
|
||||
assert_eq!(canonicalized, file_path.canonicalize().unwrap());
|
||||
assert_eq!(
|
||||
vfs.read_to_string(&canonicalized).unwrap().to_string(),
|
||||
contents.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonicalize_std_backend_missing_errors() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let file_path = dir.path().join("test");
|
||||
|
||||
let vfs = Vfs::new(StdBackend::new());
|
||||
let err = vfs.canonicalize(&file_path).unwrap_err();
|
||||
assert_eq!(err.kind(), io::ErrorKind::NotFound);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::{Metadata, ReadDir, VfsBackend, VfsEvent};
|
||||
|
||||
/// `VfsBackend` that returns an error on every operation.
|
||||
#[non_exhaustive]
|
||||
pub struct NoopBackend;
|
||||
|
||||
impl NoopBackend {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl VfsBackend for NoopBackend {
|
||||
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn exists(&mut self, _path: &Path) -> io::Result<bool> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn create_dir(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn create_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn canonicalize(&mut self, _path: &Path) -> io::Result<PathBuf> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
crossbeam_channel::never()
|
||||
}
|
||||
|
||||
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
||||
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NoopBackend {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// A slice of a tree of files. Can be loaded into an
|
||||
/// [`InMemoryFs`](struct.InMemoryFs.html).
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum VfsSnapshot {
|
||||
File {
|
||||
contents: Vec<u8>,
|
||||
},
|
||||
|
||||
Dir {
|
||||
children: BTreeMap<String, VfsSnapshot>,
|
||||
},
|
||||
}
|
||||
|
||||
impl VfsSnapshot {
|
||||
pub fn file<C: Into<Vec<u8>>>(contents: C) -> Self {
|
||||
Self::File {
|
||||
contents: contents.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dir<K: Into<String>, I: IntoIterator<Item = (K, VfsSnapshot)>>(children: I) -> Self {
|
||||
Self::Dir {
|
||||
children: children
|
||||
.into_iter()
|
||||
.map(|(key, value)| (key.into(), value))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_file() -> Self {
|
||||
Self::File {
|
||||
contents: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_dir() -> Self {
|
||||
Self::Dir {
|
||||
children: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashSet, io};
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent};
|
||||
|
||||
/// `VfsBackend` that uses `std::fs` and the `notify` crate.
|
||||
pub struct StdBackend {
|
||||
watcher: RecommendedWatcher,
|
||||
watcher_receiver: Receiver<VfsEvent>,
|
||||
watches: HashSet<PathBuf>,
|
||||
}
|
||||
|
||||
impl StdBackend {
|
||||
pub fn new() -> StdBackend {
|
||||
let (notify_tx, notify_rx) = mpsc::channel();
|
||||
let watcher = watcher(notify_tx, Duration::from_millis(50)).unwrap();
|
||||
|
||||
let (tx, rx) = crossbeam_channel::unbounded();
|
||||
|
||||
thread::spawn(move || {
|
||||
for event in notify_rx {
|
||||
match event {
|
||||
DebouncedEvent::Create(path) => {
|
||||
tx.send(VfsEvent::Create(path))?;
|
||||
}
|
||||
DebouncedEvent::Write(path) => {
|
||||
tx.send(VfsEvent::Write(path))?;
|
||||
}
|
||||
DebouncedEvent::Remove(path) => {
|
||||
tx.send(VfsEvent::Remove(path))?;
|
||||
}
|
||||
DebouncedEvent::Rename(from, to) => {
|
||||
tx.send(VfsEvent::Remove(from))?;
|
||||
tx.send(VfsEvent::Create(to))?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Result::<(), crossbeam_channel::SendError<VfsEvent>>::Ok(())
|
||||
});
|
||||
|
||||
Self {
|
||||
watcher,
|
||||
watcher_receiver: rx,
|
||||
watches: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VfsBackend for StdBackend {
|
||||
fn read(&mut self, path: &Path) -> io::Result<Vec<u8>> {
|
||||
fs_err::read(path)
|
||||
}
|
||||
|
||||
fn write(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
fs_err::write(path, data)
|
||||
}
|
||||
|
||||
fn exists(&mut self, path: &Path) -> io::Result<bool> {
|
||||
std::fs::exists(path)
|
||||
}
|
||||
|
||||
fn read_dir(&mut self, path: &Path) -> io::Result<ReadDir> {
|
||||
let entries: Result<Vec<_>, _> = fs_err::read_dir(path)?.collect();
|
||||
let mut entries = entries?;
|
||||
|
||||
entries.sort_by_cached_key(|entry| entry.file_name());
|
||||
|
||||
let inner = entries
|
||||
.into_iter()
|
||||
.map(|entry| Ok(DirEntry { path: entry.path() }));
|
||||
|
||||
Ok(ReadDir {
|
||||
inner: Box::new(inner),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_dir(&mut self, path: &Path) -> io::Result<()> {
|
||||
fs_err::create_dir(path)
|
||||
}
|
||||
|
||||
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||
fs_err::create_dir_all(path)
|
||||
}
|
||||
|
||||
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
|
||||
fs_err::remove_file(path)
|
||||
}
|
||||
|
||||
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
|
||||
fs_err::remove_dir_all(path)
|
||||
}
|
||||
|
||||
fn metadata(&mut self, path: &Path) -> io::Result<Metadata> {
|
||||
let inner = fs_err::metadata(path)?;
|
||||
|
||||
Ok(Metadata {
|
||||
is_file: inner.is_file(),
|
||||
})
|
||||
}
|
||||
|
||||
fn canonicalize(&mut self, path: &Path) -> io::Result<PathBuf> {
|
||||
fs_err::canonicalize(path)
|
||||
}
|
||||
|
||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||
self.watcher_receiver.clone()
|
||||
}
|
||||
|
||||
fn watch(&mut self, path: &Path) -> io::Result<()> {
|
||||
if self.watches.contains(path)
|
||||
|| path
|
||||
.ancestors()
|
||||
.any(|ancestor| self.watches.contains(ancestor))
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
self.watches.insert(path.to_path_buf());
|
||||
self.watcher
|
||||
.watch(path, RecursiveMode::Recursive)
|
||||
.map_err(io::Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
||||
self.watches.remove(path);
|
||||
self.watcher.unwatch(path).map_err(io::Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StdBackend {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "rojo-insta-ext"
|
||||
version = "0.1.0"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0.197"
|
||||
serde_yaml = "0.8.26"
|
||||
@@ -1,2 +0,0 @@
|
||||
# Rojo Insta Extensions
|
||||
This crate has add-ons intended for use with Insta that are useful for snapshot tests.
|
||||
@@ -1,3 +0,0 @@
|
||||
mod redaction_map;
|
||||
|
||||
pub use redaction_map::*;
|
||||
@@ -1,85 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
/// Enables redacting any value that serializes as a string.
|
||||
///
|
||||
/// Used for transforming Rojo instance IDs into something deterministic.
|
||||
#[derive(Default)]
|
||||
pub struct RedactionMap {
|
||||
ids: HashMap<String, usize>,
|
||||
last_id: usize,
|
||||
}
|
||||
|
||||
impl RedactionMap {
|
||||
pub fn get_redacted_value(&self, id: impl ToString) -> Option<String> {
|
||||
let id = id.to_string();
|
||||
|
||||
if self.ids.contains_key(&id) {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the numeric ID that was assigned to the provided value,
|
||||
/// if one exists.
|
||||
pub fn get_id_for_value(&self, value: impl ToString) -> Option<usize> {
|
||||
self.ids.get(&value.to_string()).cloned()
|
||||
}
|
||||
|
||||
pub fn intern(&mut self, id: impl ToString) {
|
||||
let last_id = &mut self.last_id;
|
||||
|
||||
self.ids.entry(id.to_string()).or_insert_with(|| {
|
||||
*last_id += 1;
|
||||
*last_id
|
||||
});
|
||||
}
|
||||
|
||||
pub fn intern_iter<S: ToString>(&mut self, ids: impl Iterator<Item = S>) {
|
||||
for id in ids {
|
||||
self.intern(id.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redacted_yaml(&self, value: impl Serialize) -> serde_yaml::Value {
|
||||
let mut encoded = serde_yaml::to_value(value).expect("Couldn't encode value as YAML");
|
||||
|
||||
self.redact(&mut encoded);
|
||||
encoded
|
||||
}
|
||||
|
||||
pub fn redact(&self, yaml_value: &mut serde_yaml::Value) {
|
||||
use serde_yaml::{Mapping, Value};
|
||||
|
||||
match yaml_value {
|
||||
Value::String(value) => {
|
||||
if let Some(redacted) = self.ids.get(value) {
|
||||
*yaml_value = Value::String(format!("id-{}", *redacted));
|
||||
}
|
||||
}
|
||||
Value::Sequence(sequence) => {
|
||||
for value in sequence {
|
||||
self.redact(value);
|
||||
}
|
||||
}
|
||||
Value::Mapping(mapping) => {
|
||||
// We can't mutate the keys of a map in-place, so we take
|
||||
// ownership of the map and rebuild it.
|
||||
|
||||
let owned_map = std::mem::replace(mapping, Mapping::new());
|
||||
let mut new_map = Mapping::with_capacity(owned_map.len());
|
||||
|
||||
for (mut key, mut value) in owned_map {
|
||||
self.redact(&mut key);
|
||||
self.redact(&mut value);
|
||||
new_map.insert(key, value);
|
||||
}
|
||||
|
||||
*mapping = new_map;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
docs/extra.css
Normal file
@@ -0,0 +1,13 @@
|
||||
.md-typeset__table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.feature-image img {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.codehilite {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
11
docs/guide/existing-game.md
Normal file
@@ -0,0 +1,11 @@
|
||||
**This page is under construction!**
|
||||
|
||||
## Summary
|
||||
* Tools to port existing games are in progress!
|
||||
* [rbxlx-to-rojo](https://github.com/rojo-rbx/rbxlx-to-rojo)
|
||||
* `rojo export` ([issue #208](https://github.com/rojo-rbx/rojo/issues/208))
|
||||
* Can port as much or as little of your game as you like
|
||||
* Rojo can manage just a slice of your game!
|
||||
* Some Roblox idioms aren't very well supported
|
||||
* Redundant copies of scripts don't work well with files
|
||||
* Having only a couple places with scripts simplifies your project dramatically!
|
||||
48
docs/guide/installation.md
Normal file
@@ -0,0 +1,48 @@
|
||||
This is this installation guide for Rojo **0.5.x**.
|
||||
|
||||
[TOC]
|
||||
|
||||
## Overview
|
||||
Rojo has two components:
|
||||
|
||||
* The command line interface (CLI)
|
||||
* The Roblox Studio plugin
|
||||
|
||||
!!! info
|
||||
It's important that your installed version of the plugin and CLI are compatible.
|
||||
|
||||
The plugin will show errors in the Roblox Studio output window if there is a version mismatch.
|
||||
|
||||
## Visual Studio Code Extension
|
||||
If you use Visual Studio Code, you can install [the Rojo VS Code extension](https://marketplace.visualstudio.com/items?itemName=evaera.vscode-rojo), which will install both halves of Rojo for you. It even has a nifty UI to sync files and start/stop the Rojo server!
|
||||
|
||||
## Installing the CLI
|
||||
|
||||
### Installing from GitHub
|
||||
If you're on Windows, there are pre-built binaries available from Rojo's [GitHub Releases page](https://github.com/LPGhatguy/rojo/releases).
|
||||
|
||||
The Rojo CLI must be run from the command line, like Terminal.app on MacOS or `cmd.exe` on Windows. It's recommended that you put the Rojo CLI executable on your `PATH` to make this easier.
|
||||
|
||||
### Installing from Cargo
|
||||
If you have Rust installed, the easiest way to get Rojo is with Cargo!
|
||||
|
||||
To install the latest 0.5.x release, use:
|
||||
|
||||
```sh
|
||||
cargo install rojo
|
||||
```
|
||||
|
||||
If you're upgrading from a previous version of Rojo, you may need to pass `--force` to tell Cargo to overwrite your existing version.
|
||||
|
||||
## Installing the Plugin
|
||||
|
||||
### Installing from GitHub
|
||||
The Rojo Roblox Studio plugin is available from Rojo's [GitHub Releases page](https://github.com/LPGhatguy/rojo/releases).
|
||||
|
||||
Download the attached `rbxm` file and put it into your Roblox Studio plugins folder. You can find that folder by pressing **Plugins Folder** from your Plugins toolbar in Roblox Studio:
|
||||
|
||||

|
||||
{: align="center" }
|
||||
|
||||
### Installing from Roblox.com
|
||||
Visit [Rojo's Roblox.com Plugin page](https://www.roblox.com/library/1997686364) in Roblox Studio and press **Install**.
|
||||