├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets ├── cargo-toml │ ├── syntax.txt │ ├── test.txt │ ├── test2.txt │ ├── test3.txt │ └── test4.txt ├── dependencies │ ├── syntax.txt │ ├── test.txt │ ├── test2.txt │ ├── test3.txt │ └── test4.txt ├── extract │ ├── gfx.txt │ ├── piston.txt │ ├── rust_audio.txt │ ├── syntax.txt │ ├── test.txt │ ├── test2.txt │ ├── test3.txt │ └── window_apis.txt ├── json │ └── syntax.txt └── update │ ├── syntax.txt │ ├── test.txt │ ├── test2.txt │ └── test3.txt ├── examples ├── gfx.rs ├── piston.rs ├── rust_audio.rs ├── test.rs ├── todo_piston.rs └── window_apis.rs └── src ├── dependencies.rs ├── extract.rs ├── lib.rs ├── todo.rs └── update.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: bvssvni 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *# 4 | *.o 5 | *.so 6 | *.swp 7 | *.dylib 8 | *.dSYM 9 | *.dll 10 | *.rlib 11 | *.dummy 12 | *.exe 13 | *-test 14 | /bin/main 15 | /bin/test-internal 16 | /bin/test-external 17 | /doc/ 18 | /target/ 19 | /build/ 20 | /.rust/ 21 | rusti.sh 22 | 23 | 24 | examples/image 25 | 26 | examples/user_input 27 | 28 | examples/image_iter 29 | 30 | examples/audio 31 | 32 | examples/audio_iter 33 | 34 | examples/soundstream 35 | 36 | watch.sh 37 | 38 | examples/hgl_triangle 39 | 40 | Cargo.lock 41 | 42 | todo.txt 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eco" 3 | version = "0.23.0" 4 | authors = [ 5 | "bvssvni " 6 | ] 7 | keywords = ["piston", "ecosystem", "dependencies", "analysis"] 8 | description = "A tool for reasoning about breaking changes in Rust ecosystems" 9 | license = "MIT OR Apache-2.0" 10 | repository = "https://github.com/pistondevelopers/eco.git" 11 | homepage = "https://github.com/pistondevelopers/eco" 12 | edition = "2021" 13 | 14 | [lib] 15 | name = "eco" 16 | path = "src/lib.rs" 17 | 18 | [dependencies] 19 | piston_meta = "2.0.0" 20 | reqwest = { version = "0.11", features = ["blocking"] } 21 | semver = "0.2.3" 22 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2016 PistonDevelopers 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 PistonDevelopers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eco 2 | [![Build Status](https://travis-ci.org/PistonDevelopers/eco.svg?branch=master)](https://travis-ci.org/PistonDevelopers/eco) 3 | 4 | A tool for reasoning about breaking changes in Rust ecosystems 5 | 6 | Eco processes custom JSON formats about Rust ecosystems. 7 | 8 | Currently supported: 9 | 10 | - Extract info: A list of urls to extract dependency information from Cargo.toml. 11 | - Dependency info: Version info about packages and their dependencies. 12 | - Update info: Actions to improve the integration of the ecosystem. 13 | 14 | ### Help out with updating! 15 | 16 | Breaking changes happen all the time, and keeping libraries updated is important. 17 | Here we keep a list of various ecosystems important for Rust gamedev. 18 | 19 | - Piston: `cargo run --example piston > todo.txt` 20 | 21 | Eco generates a list of updates, which you follow as instructions and then make PR to the respective repos. 22 | 23 | ### Motivation 24 | 25 | Rust ecosystems often consist of many smaller crates following semver versioning. 26 | When the first non-zero number changes, it means a breaking change. 27 | Depending on the shape and size of your ecosystem, different breaking changes have different consequences. 28 | 29 | Keeping an ecosystem integrated is a huge task, and Piston is part of a large ecosystem, 30 | even extending beyond the PistonDevelopers organization. 31 | What matters most is that existing code continues working, and that updates happen soon after changes are made. 32 | Ideally, to avoid dependency conflicts and large binaries, the ecosystem should use the same versions of libraries. 33 | This is a hard mental task to do manually, and almost impossible to do without making mistakes. 34 | 35 | Eco is designed to complement [other tools](https://github.com/PistonDevelopers/eco/issues/20) for Rust ecosystems. 36 | It can extract dependency information directly from Cargo.toml, 37 | then run an analysis on the current state and output recommended actions. 38 | These actions can then be used by to assist maintainers in their work, or perhaps automate some tasks in the future. 39 | 40 | Eco uses [Piston-Meta](https://github.com/pistondevelopers/meta) for parsing text. 41 | Meta parsing is a techinque where data from arbitrary text can be queried using a "meta syntax", 42 | supporting a large number of formats for specific purposes using a single library. 43 | This allows quick fixes to custom formats, validation of structure, and gives good error messages. 44 | One sub-goal of this project is to test and improve Piston-Meta for use in infrastructure, 45 | where various parts are interfacing each other through text. 46 | 47 | Because Eco might be used for automation in the future, the algorithms are based on analysis and models. 48 | When something goes wrong, it should be known what error might have caused it. 49 | This is necessary to use it with other tools, so the overall behavior can be reasoned about. 50 | 51 | ### OpenSSL issues on macOS 52 | 53 | On macOS, if you use Homebrew or MacPorts to install OpenSSL, you may need to add the following lines 54 | to your `.bash_profile` to resolve compile-time errors: 55 | 56 | ``` 57 | export OPENSSL_INCLUDE_DIR=`brew --prefix openssl`/include 58 | export OPENSSL_LIB_DIR=`brew --prefix openssl`/lib 59 | export DEP_OPENSSL_INCLUDE=`brew --prefix openssl`/include 60 | ``` 61 | 62 | ## Making pull requests 63 | 64 | Prior to making a pull request on eco, make sure that you have `cargo fmt` against the 65 | codebase. Instructions to install `rustfmt` can be found [here](https://github.com/rust-lang-nursery/rustfmt). 66 | Ensure that you are installing the nightly version, since there is currently an effort 67 | to port `rustfmt` away from the `syntex` crate, and `rustfmt-nightly` is the latest version. 68 | 69 | Unfortunately, this also means that you will have to switch over to nightly prior to 70 | running `cargo fmt` against the codebase. 71 | 72 | ## License 73 | 74 | Licensed under either of 75 | 76 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 77 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 78 | 79 | at your option. 80 | 81 | ### Contribution 82 | 83 | Unless you explicitly state otherwise, any contribution intentionally submitted 84 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 85 | additional terms or conditions. 86 | -------------------------------------------------------------------------------- /assets/cargo-toml/syntax.txt: -------------------------------------------------------------------------------- 1 | 0 w = {[.w? "#" ..."\n"?] .w!} 2 | 1 package = ["[package]" ?w .l({ 3 | ["name" ?w "=" ?w .t!:"name" ?w] 4 | ["version" ?w "=" ?w .t!:"version" ?w] 5 | [!"#" .."="? ?w "=" ?w ..."\n"? ?w] 6 | })] 7 | 2 dependency = ["[dependencies." .."]"!:"name" "]" ?w .l({ 8 | ["version" ?w "=" ?w .t!:"version" ?w] 9 | [!"#" .."="? ?w "=" ?w ..."\n"? ?w] 10 | })] 11 | 3 dev_dependency = ["[dev-dependencies." .."]"!:"name" "]" ?w .l({ 12 | ["version" ?w "=" ?w .t!:"version" ?w] 13 | [!"#" .."="? ?w "=" ?w ..."\n"? ?w] 14 | })] 15 | 4 dependency_list_item = [!"#" .."="!:"name" ?w "=" ?w { 16 | ["{" ?w .s?.([.w? "," .w?] { 17 | ["version" ?w "=" ?w .t!:"version"] 18 | [...",}"!] 19 | }) ?w "}"] 20 | .t!:"version" 21 | } ?w] 22 | 5 dependency_list = ["[dependencies]" ?w .l({ 23 | dependency_list_item:"dependency" 24 | })] 25 | 6 dev_dependency_list = ["[dev-dependencies]" ?w .l({ 26 | dependency_list_item:"dev_dependency" 27 | })] 28 | 7 dependencies = [.l({ 29 | dependency:"dependency" 30 | dependency_list 31 | dev_dependency:"dev_dependency" 32 | dev_dependency_list 33 | ..."\n"? 34 | })] 35 | 9 package_node = [package ?w dependencies] 36 | 10 document = [ 37 | .r?({w [!"[package]" ..."\n"? .w!]}) 38 | package_node:"package" 39 | ...""? 40 | ] 41 | -------------------------------------------------------------------------------- /assets/cargo-toml/test.txt: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "pistoncore-window" 4 | version = "0.8.0" 5 | authors = [ 6 | "bvssvni ", 7 | "Coeuvre " 8 | ] 9 | keywords = ["window", "game", "piston"] 10 | description = "A library for window abstraction" 11 | license = "MIT" 12 | readme = "README.md" 13 | repository = "https://github.com/PistonDevelopers/piston.git" 14 | homepage = "https://github.com/PistonDevelopers/piston" 15 | 16 | [lib] 17 | name = "window" 18 | path = "src/lib.rs" 19 | 20 | [dependencies.pistoncore-input] 21 | path = "../input" 22 | version = "0.4.0" 23 | 24 | [dependencies] 25 | libc = "0.1" 26 | shader_version = "0.2.0" 27 | -------------------------------------------------------------------------------- /assets/cargo-toml/test2.txt: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elmesque" 3 | version = "0.6.0" 4 | authors = ["mitchmindtree "] 5 | description = "An attempt at porting Elm's incredibly useful, purely functional std graphics modules." 6 | readme = "README.md" 7 | keywords = ["elm", "graphics", "2d", "ui", "shape"] 8 | license = "MIT" 9 | repository = "https://github.com/mitchmindtree/elmesque.git" 10 | homepage = "https://github.com/mitchmindtree/elmesque" 11 | 12 | 13 | [dependencies] 14 | num = "*" 15 | piston2d-graphics = "0.7.1" 16 | rand = "*" 17 | rustc-serialize = "*" 18 | vecmath = "0.1.1" 19 | 20 | [dev-dependencies] 21 | find_folder = "0.2.0" 22 | piston = "0.10.0" 23 | piston_window = "0.18.0" 24 | -------------------------------------------------------------------------------- /assets/cargo-toml/test3.txt: -------------------------------------------------------------------------------- 1 | example = [] 2 | 3 | [package] 4 | name = "gl" 5 | version = "0.1.0" 6 | authors = ["Brendan Zabarauskas ", 7 | "Corey Richardson", 8 | "Arseny Kapoulkine" 9 | ] 10 | description = "OpenGL bindings" 11 | license = "Apache-2.0" 12 | build = "build.rs" 13 | repository = "https://github.com/bjz/gl-rs/" 14 | 15 | [build-dependencies.gl_generator] 16 | path = "../gl_generator" 17 | version = "0.1.0" 18 | 19 | [build-dependencies.khronos_api] 20 | path = "../khronos_api" 21 | version = "0.0.8" 22 | 23 | [dependencies.gl_common] 24 | path = "../gl_common" 25 | version = "0.1.0" 26 | 27 | [dependencies] 28 | libc = "0.1" 29 | 30 | #[dev-dependencies.glfw] 31 | #git = "https://github.com/bjz/glfw-rs" 32 | -------------------------------------------------------------------------------- /assets/cargo-toml/test4.txt: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "texture_packer" 3 | version = "0.9.0" 4 | authors = ["Coeuvre "] 5 | keywords = ["texture", "packer", "piston"] 6 | description = "A texture packing library using the skyline heuristic" 7 | license = "MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/PistonDevelopers/texture_packer.git" 10 | homepage = "https://github.com/PistonDevelopers/texture_packer" 11 | documentation = "http://docs.piston.rs/texture_packer" 12 | 13 | [features] 14 | gif_codec = ["image/gif_codec"] 15 | ico = ["image/ico"] 16 | jpeg = ["image/jpeg"] 17 | png_codec = ["image/png_codec"] 18 | ppm = ["image/ppm"] 19 | tga = ["image/tga"] 20 | tiff = ["image/tiff"] 21 | webp = ["image/webp"] 22 | bmp = ["image/bmp"] 23 | hdr = ["image/hdr"] 24 | 25 | [dependencies] 26 | image = { version = "0.10.0", default-features = false } 27 | -------------------------------------------------------------------------------- /assets/dependencies/syntax.txt: -------------------------------------------------------------------------------- 1 | 0 , = [.w? "," .w?] 2 | 1 dependency = [.t!:"name" .w? ":" .w? "{" .w? .s?(, { 3 | ["\"version\"" .w? ":" .w? .t!:"version"] 4 | ["\"ignore-version\"" .w? ":" .w? .t!:"ignore-version"] 5 | }) .w? "}"] 6 | 1 package = [.t!:"name" .w? ":" .w? "{" .w? .s?(, { 7 | ["\"version\"" .w? ":" .w? .t!:"version"] 8 | ["\"dependencies\"" .w? ":" .w? "{" .w? .s?(, dependency:"dependency") .w? "}"] 9 | ["\"dev-dependencies\"" .w? ":" .w? "{" .w? .s?(, dependency:"dev_dependency") .w? "}"] 10 | }) .w? "}"] 11 | 2 document = [.w? "{" .w? .s?(, [package:"package" .w?]) "}" .w?] 12 | -------------------------------------------------------------------------------- /assets/dependencies/test.txt: -------------------------------------------------------------------------------- 1 | { 2 | "pistoncore-input": { 3 | "version": "0.4.0", 4 | "dependencies": {} 5 | }, 6 | "pistoncore-window": { 7 | "version": "0.6.0", 8 | "dependencies": { 9 | "pistoncore-input": { 10 | "version": "0.4.0" 11 | } 12 | } 13 | }, 14 | "pistoncore-event_loop": { 15 | "version": "0.7.0", 16 | "dependencies": { 17 | "pistoncore-input": { 18 | "version": "0.4.0" 19 | }, 20 | "pistoncore-window": { 21 | "version": "0.6.0" 22 | } 23 | } 24 | }, 25 | "piston": { 26 | "version": "0.7.0", 27 | "dependencies": { 28 | "pistoncore-input": { 29 | "version": "0.4.0" 30 | }, 31 | "pistoncore-window": { 32 | "version": "0.6.0" 33 | }, 34 | "pistoncore-event_loop": { 35 | "version": "0.7.0" 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /assets/dependencies/test2.txt: -------------------------------------------------------------------------------- 1 | { 2 | "pistoncore-input": { 3 | "version": "0.5.0", 4 | "dependencies": { 5 | "rustc-serialize": { 6 | "version": "0.3.15" 7 | }, 8 | "bitflags": { 9 | "version": "0.3.2" 10 | }, 11 | "piston-viewport": { 12 | "version": "0.1.0" 13 | } 14 | } 15 | }, 16 | "pistoncore-window": { 17 | "version": "0.9.0", 18 | "dependencies": { 19 | "pistoncore-input": { 20 | "version": "0.5.0" 21 | }, 22 | "libc": { 23 | "version": "0.1" 24 | }, 25 | "shader_version": { 26 | "version": "0.2.0" 27 | } 28 | } 29 | }, 30 | "pistoncore-event_loop": { 31 | "version": "0.10.0", 32 | "dependencies": { 33 | "pistoncore-window": { 34 | "version": "0.9.0" 35 | }, 36 | "pistoncore-input": { 37 | "version": "0.5.0" 38 | }, 39 | "clock_ticks": { 40 | "version": "0.0.6" 41 | }, 42 | "piston-viewport": { 43 | "version": "0.1.0" 44 | } 45 | } 46 | }, 47 | "piston": { 48 | "version": "0.10.0", 49 | "dependencies": { 50 | "pistoncore-input": { 51 | "version": "0.5.0" 52 | }, 53 | "pistoncore-window": { 54 | "version": "0.9.0" 55 | }, 56 | "pistoncore-event_loop": { 57 | "version": "0.10.0" 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /assets/dependencies/test3.txt: -------------------------------------------------------------------------------- 1 | { 2 | "elmesque": { 3 | "version": "0.6.0", 4 | "dependencies": { 5 | "num": { 6 | "version": "*" 7 | }, 8 | "piston2d-graphics": { 9 | "version": "0.7.1" 10 | }, 11 | "rand": { 12 | "version": "*" 13 | }, 14 | "rustc-serialize": { 15 | "version": "*" 16 | }, 17 | "vecmath": { 18 | "version": "0.1.1" 19 | } 20 | }, 21 | "dev-dependencies": { 22 | "find_folder": { 23 | "version": "0.2.0" 24 | }, 25 | "piston": { 26 | "version": "0.10.0" 27 | }, 28 | "piston_window": { 29 | "version": "0.18.0" 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/dependencies/test4.txt: -------------------------------------------------------------------------------- 1 | { 2 | "pistoncore-input": { 3 | "version": "0.4.0", 4 | "dependencies": { 5 | "bitflags": { 6 | "version": "0.6.0", 7 | "ignore-version": "0.7.0" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/extract/gfx.txt: -------------------------------------------------------------------------------- 1 | { 2 | "gfx_app": { 3 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/Cargo.toml" 4 | }, 5 | "gfx_device_dx11": { 6 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/src/backend/dx11/Cargo.toml" 7 | }, 8 | "gfx_device_gl": { 9 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/src/backend/gl/Cargo.toml" 10 | }, 11 | "gfx_core": { 12 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/src/core/Cargo.toml" 13 | }, 14 | "gfx": { 15 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/src/render/Cargo.toml" 16 | }, 17 | "gfx_window_dxgi": { 18 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/src/window/dxgi/Cargo.toml" 19 | }, 20 | "gfx_window_glfw": { 21 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/src/window/glfw/Cargo.toml" 22 | }, 23 | "gfx_window_glutin": { 24 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/src/window/glutin/Cargo.toml" 25 | }, 26 | "gfx_window_sdl": { 27 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx/master/src/window/sdl/Cargo.toml" 28 | }, 29 | "draw_state": { 30 | "url": "https://raw.githubusercontent.com/gfx-rs/draw_state/master/Cargo.toml" 31 | }, 32 | "gfx_gl": { 33 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx_gl/master/Cargo.toml" 34 | }, 35 | "genmesh": { 36 | "url": "https://raw.githubusercontent.com/gfx-rs/genmesh/master/Cargo.toml" 37 | }, 38 | "gfx_scene_meta": { 39 | "url": "https://raw.githubusercontent.com/gfx-rs/gfx_scene/master/Cargo.toml" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /assets/extract/piston.txt: -------------------------------------------------------------------------------- 1 | { 2 | "piston-graphics_api_version": { 3 | "url": "https://raw.githubusercontent.com/PistonDevelopers/graphics_api_version/master/Cargo.toml" 4 | }, 5 | 6 | "pistoncore-input": { 7 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/input/Cargo.toml" 8 | }, 9 | 10 | "pistoncore-window": { 11 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/window/Cargo.toml" 12 | }, 13 | 14 | "pistoncore-event_loop": { 15 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/event_loop/Cargo.toml" 16 | }, 17 | 18 | "piston": { 19 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/Cargo.toml" 20 | }, 21 | 22 | "piston-fake_dpi": { 23 | "url": "https://raw.githubusercontent.com/PistonDevelopers/fake_dpi/master/Cargo.toml" 24 | }, 25 | 26 | "shader_version": { 27 | "url": "https://raw.githubusercontent.com/PistonDevelopers/shader_version/master/Cargo.toml" 28 | }, 29 | 30 | "piston-rect": { 31 | "url": "https://raw.githubusercontent.com/PistonDevelopers/rect/master/Cargo.toml" 32 | }, 33 | 34 | "piston-viewport": { 35 | "url": "https://raw.githubusercontent.com/PistonDevelopers/viewport/master/Cargo.toml" 36 | }, 37 | 38 | "pistoncore-glutin_window": { 39 | "url": "https://raw.githubusercontent.com/PistonDevelopers/glutin_window/master/Cargo.toml" 40 | }, 41 | 42 | "pistoncore-sdl2_window": { 43 | "url": "https://raw.githubusercontent.com/PistonDevelopers/sdl2_window/master/Cargo.toml" 44 | }, 45 | 46 | "pistoncore-glfw_window": { 47 | "url": "https://raw.githubusercontent.com/PistonDevelopers/glfw_window/master/Cargo.toml" 48 | }, 49 | 50 | "piston2d-graphics": { 51 | "url": "https://raw.githubusercontent.com/PistonDevelopers/graphics/master/Cargo.toml" 52 | }, 53 | 54 | "piston2d-graphics_tree": { 55 | "url": "https://raw.githubusercontent.com/PistonDevelopers/graphics_tree/master/Cargo.toml" 56 | }, 57 | 58 | "piston-texture": { 59 | "url": "https://raw.githubusercontent.com/PistonDevelopers/texture/master/Cargo.toml" 60 | }, 61 | 62 | "piston2d-gfx_graphics": { 63 | "url": "https://raw.githubusercontent.com/PistonDevelopers/gfx_graphics/master/Cargo.toml" 64 | }, 65 | 66 | "piston2d-opengl_graphics": { 67 | "url": "https://raw.githubusercontent.com/PistonDevelopers/opengl_graphics/master/Cargo.toml" 68 | }, 69 | 70 | "piston2d-glium_graphics": { 71 | "url": "https://raw.githubusercontent.com/PistonDevelopers/glium_graphics/master/Cargo.toml" 72 | }, 73 | 74 | "piston2d-wgpu_graphics": { 75 | "url": "https://raw.githubusercontent.com/PistonDevelopers/wgpu_graphics/master/Cargo.toml" 76 | }, 77 | 78 | "piston-ai_behavior": { 79 | "url": "https://raw.githubusercontent.com/PistonDevelopers/ai_behavior/master/Cargo.toml" 80 | }, 81 | 82 | "piston2d-sprite": { 83 | "url": "https://raw.githubusercontent.com/PistonDevelopers/sprite/master/Cargo.toml" 84 | }, 85 | 86 | "piston2d-deform_grid": { 87 | "url": "https://raw.githubusercontent.com/PistonDevelopers/deform_grid/master/Cargo.toml" 88 | }, 89 | 90 | "interpolation": { 91 | "url": "https://raw.githubusercontent.com/PistonDevelopers/interpolation/master/Cargo.toml" 92 | }, 93 | 94 | "piston-button_tracker": { 95 | "url": "https://raw.githubusercontent.com/PistonDevelopers/button_tracker/master/Cargo.toml" 96 | }, 97 | 98 | "piston-button_controller": { 99 | "url": "https://raw.githubusercontent.com/PistonDevelopers/button_controller/master/Cargo.toml" 100 | }, 101 | 102 | "piston-split_controller": { 103 | "url": "https://raw.githubusercontent.com/PistonDevelopers/split_controller/master/Cargo.toml" 104 | }, 105 | 106 | "piston2d-touch_visualizer": { 107 | "url": "https://raw.githubusercontent.com/PistonDevelopers/touch_visualizer/master/Cargo.toml" 108 | }, 109 | 110 | "piston2d-scroll_controller": { 111 | "url": "https://raw.githubusercontent.com/PistonDevelopers/scroll_controller/master/Cargo.toml" 112 | }, 113 | 114 | "piston-timer_controller": { 115 | "url": "https://raw.githubusercontent.com/PistonDevelopers/timer_controller/master/Cargo.toml" 116 | }, 117 | 118 | "vecmath": { 119 | "url": "https://raw.githubusercontent.com/PistonDevelopers/vecmath/master/Cargo.toml" 120 | }, 121 | 122 | "piston3d-cam": { 123 | "url": "https://raw.githubusercontent.com/PistonDevelopers/cam/master/Cargo.toml" 124 | }, 125 | 126 | "quaternion": { 127 | "url": "https://raw.githubusercontent.com/PistonDevelopers/quaternion/master/Cargo.toml" 128 | }, 129 | 130 | "dual_quaternion": { 131 | "url": "https://raw.githubusercontent.com/PistonDevelopers/dual_quaternion/master/Cargo.toml" 132 | }, 133 | 134 | "texture_packer": { 135 | "url": "https://raw.githubusercontent.com/PistonDevelopers/texture_packer/master/Cargo.toml" 136 | }, 137 | 138 | "piston2d-shapes": { 139 | "url": "https://raw.githubusercontent.com/PistonDevelopers/shapes/master/Cargo.toml" 140 | }, 141 | 142 | "select_color": { 143 | "url": "https://raw.githubusercontent.com/PistonDevelopers/select_color/master/Cargo.toml" 144 | }, 145 | 146 | "camera_controllers": { 147 | "url": "https://raw.githubusercontent.com/PistonDevelopers/camera_controllers/master/Cargo.toml" 148 | }, 149 | 150 | "piston2d-drag_controller": { 151 | "url": "https://raw.githubusercontent.com/PistonDevelopers/drag_controller/master/Cargo.toml" 152 | }, 153 | 154 | "range": { 155 | "url": "https://raw.githubusercontent.com/PistonDevelopers/range/master/Cargo.toml" 156 | }, 157 | 158 | "piston-shaders": { 159 | "url": "https://raw.githubusercontent.com/PistonDevelopers/shaders/master/Cargo.toml" 160 | }, 161 | 162 | "piston-shaders_graphics2d": { 163 | "url": "https://raw.githubusercontent.com/PistonDevelopers/shaders/master/graphics2d/Cargo.toml" 164 | }, 165 | 166 | "piston-float": { 167 | "url": "https://raw.githubusercontent.com/PistonDevelopers/float/master/Cargo.toml" 168 | }, 169 | 170 | "piston_meta": { 171 | "url": "https://raw.githubusercontent.com/PistonDevelopers/meta/master/Cargo.toml" 172 | }, 173 | 174 | "piston_meta_search": { 175 | "url": "https://raw.githubusercontent.com/PistonDevelopers/meta/master/src/search/Cargo.toml" 176 | }, 177 | 178 | "read_token": { 179 | "url": "https://raw.githubusercontent.com/PistonDevelopers/read_token/master/Cargo.toml" 180 | }, 181 | 182 | "read_color": { 183 | "url": "https://raw.githubusercontent.com/PistonDevelopers/read_color/master/Cargo.toml" 184 | }, 185 | 186 | "piston_window": { 187 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston_window/master/Cargo.toml", 188 | "ignore": { 189 | "gfx_device_gl": "0.16.0" 190 | } 191 | }, 192 | 193 | "conrod_core": { 194 | "url": "https://raw.githubusercontent.com/PistonDevelopers/conrod/master/conrod_core/Cargo.toml" 195 | }, 196 | 197 | "color_quant": { 198 | "url": "https://raw.githubusercontent.com/PistonDevelopers/color_quant/master/Cargo.toml" 199 | }, 200 | 201 | "dyon": { 202 | "url": "https://raw.githubusercontent.com/PistonDevelopers/dyon/master/Cargo.toml" 203 | }, 204 | 205 | "piston-dyon_interactive": { 206 | "url": "https://raw.githubusercontent.com/PistonDevelopers/dyon/master/interactive/Cargo.toml" 207 | }, 208 | 209 | "find_folder": { 210 | "url": "https://raw.githubusercontent.com/PistonDevelopers/find_folder/master/Cargo.toml" 211 | }, 212 | 213 | "freetype-rs": { 214 | "url": "https://raw.githubusercontent.com/PistonDevelopers/freetype-rs/master/Cargo.toml" 215 | }, 216 | 217 | "freetype-sys": { 218 | "url": "https://raw.githubusercontent.com/PistonDevelopers/freetype-sys/master/Cargo.toml" 219 | }, 220 | 221 | "glfw": { 222 | "url": "https://raw.githubusercontent.com/PistonDevelopers/glfw-rs/master/Cargo.toml" 223 | }, 224 | 225 | "piston-gfx_texture": { 226 | "url": "https://raw.githubusercontent.com/PistonDevelopers/gfx_texture/master/Cargo.toml" 227 | }, 228 | 229 | "piston3d-gfx_voxel": { 230 | "url": "https://raw.githubusercontent.com/PistonDevelopers/gfx_voxel/master/Cargo.toml" 231 | }, 232 | 233 | "hematite": { 234 | "url": "https://raw.githubusercontent.com/PistonDevelopers/hematite/master/Cargo.toml", 235 | "ignore": { 236 | "gfx_device_gl": "0.16.0" 237 | } 238 | }, 239 | 240 | "fps_counter": { 241 | "url": "https://raw.githubusercontent.com/PistonDevelopers/fps_counter/master/Cargo.toml" 242 | }, 243 | 244 | "dev_menu": { 245 | "url": "https://raw.githubusercontent.com/PistonDevelopers/dev_menu/master/Cargo.toml" 246 | }, 247 | 248 | "gfx_debug_draw": { 249 | "url": "https://raw.githubusercontent.com/PistonDevelopers/gfx_debug_draw/master/Cargo.toml" 250 | }, 251 | 252 | "skeletal_animation": { 253 | "url": "https://raw.githubusercontent.com/PistonDevelopers/skeletal_animation/master/Cargo.toml" 254 | }, 255 | 256 | "skeletal_animation_demo": { 257 | "url": "https://raw.githubusercontent.com/PistonDevelopers/skeletal_animation_demo/master/Cargo.toml", 258 | "ignore": { 259 | "gfx_device_gl": "0.16.0" 260 | } 261 | }, 262 | 263 | "gfx_text": { 264 | "url": "https://raw.githubusercontent.com/PistonDevelopers/gfx_text/master/Cargo.toml", 265 | }, 266 | 267 | "current": { 268 | "url": "https://raw.githubusercontent.com/PistonDevelopers/current/master/Cargo.toml" 269 | }, 270 | 271 | "piston-music": { 272 | "url": "https://raw.githubusercontent.com/PistonDevelopers/music/master/Cargo.toml", 273 | }, 274 | 275 | "collada": { 276 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston_collada/master/Cargo.toml" 277 | }, 278 | 279 | "wavefront_obj": { 280 | "url": "https://raw.githubusercontent.com/PistonDevelopers/wavefront_obj/master/Cargo.toml" 281 | }, 282 | 283 | "draw_state": { 284 | "url": "https://raw.githubusercontent.com/gfx-rs/draw_state/master/Cargo.toml", 285 | "ignore": { 286 | "bitflags": "1.0.0" 287 | } 288 | }, 289 | 290 | "piston-examples": { 291 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston-examples/master/Cargo.toml", 292 | "ignore": { 293 | "gfx_device_gl": "0.16.0" 294 | } 295 | }, 296 | 297 | "piston-example-user_input": { 298 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston-examples/master/examples/user_input/Cargo.toml" 299 | }, 300 | 301 | "piston-example-freetype": { 302 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston-examples/master/examples/freetype/Cargo.toml" 303 | }, 304 | 305 | "spinning-square": { 306 | "url": "https://raw.githubusercontent.com/PistonDevelopers/Piston-Tutorials/master/getting-started/Cargo.toml" 307 | }, 308 | 309 | "eco": { 310 | "url": "https://raw.githubusercontent.com/PistonDevelopers/eco/master/Cargo.toml" 311 | }, 312 | 313 | "turbine_scene3d": { 314 | "url": "https://raw.githubusercontent.com/PistonDevelopers/turbine/master/scene3d/Cargo.toml" 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /assets/extract/rust_audio.txt: -------------------------------------------------------------------------------- 1 | { 2 | "portaudio": { 3 | "url": "https://raw.githubusercontent.com/RustAudio/rust-portaudio/master/Cargo.toml" 4 | }, 5 | "synth": { 6 | "url": "https://raw.githubusercontent.com/RustAudio/synth/master/Cargo.toml" 7 | }, 8 | "sample": { 9 | "url": "https://raw.githubusercontent.com/RustAudio/sample/master/Cargo.toml" 10 | }, 11 | "coreaudio-rs": { 12 | "url": "https://raw.githubusercontent.com/RustAudio/coreaudio-rs/master/Cargo.toml" 13 | }, 14 | "sampler": { 15 | "url": "https://raw.githubusercontent.com/RustAudio/sampler/master/Cargo.toml" 16 | }, 17 | "volume": { 18 | "url": "https://raw.githubusercontent.com/RustAudio/volume/master/Cargo.toml" 19 | }, 20 | "dsp-chain": { 21 | "url": "https://raw.githubusercontent.com/RustAudio/dsp-chain/master/Cargo.toml" 22 | }, 23 | "rimd": { 24 | "url": "https://raw.githubusercontent.com/RustAudio/rimd/master/Cargo.toml" 25 | }, 26 | "sound_stream": { 27 | "url": "https://raw.githubusercontent.com/RustAudio/sound_stream/master/Cargo.toml" 28 | }, 29 | "pitch_calc": { 30 | "url": "https://raw.githubusercontent.com/RustAudio/pitch_calc/master/Cargo.toml" 31 | }, 32 | "rms": { 33 | "url": "https://raw.githubusercontent.com/RustAudio/rms/master/Cargo.toml" 34 | }, 35 | "time_calc": { 36 | "url": "https://raw.githubusercontent.com/RustAudio/time_calc/master/Cargo.toml" 37 | }, 38 | "coreaudio-sys": { 39 | "url": "https://raw.githubusercontent.com/RustAudio/coreaudio-sys/master/Cargo.toml" 40 | }, 41 | "envelope": { 42 | "url": "https://raw.githubusercontent.com/RustAudio/envelope/master/Cargo.toml" 43 | }, 44 | "jack": { 45 | "url": "https://raw.githubusercontent.com/RustAudio/rust-jack/master/Cargo.toml" 46 | }, 47 | "musical_keyboard": { 48 | "url": "https://raw.githubusercontent.com/RustAudio/musical_keyboard/master/Cargo.toml" 49 | }, 50 | "panning": { 51 | "url": "https://raw.githubusercontent.com/RustAudio/panning/master/Cargo.toml" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /assets/extract/syntax.txt: -------------------------------------------------------------------------------- 1 | 3 ignore_dependency = [.t!:"package" .w? ":" .w? .t!:"version"] 2 | 2 ignore = ["{" .w? .s?([.w? "," .w?] 3 | ignore_dependency:"dependency") .w? "}"] 4 | 1 library = [.t!:"package" .w? ":" .w? "{" .w? .s?([.w? "," .w?] { 5 | ["\"url\"" .w? ":" .w? .t!:"url"] 6 | ["\"ignore-version\"" .w? ":" .w? .t!:"ignore_version"] 7 | ["\"override-version\"" .w? ":" .w? .t!:"override_version"] 8 | ["\"ignore\"" .w? ":" .w? ignore:"ignore"] 9 | }) .w? "}"] 10 | 0 document = [.w? "{" .w? .s?([.w? "," .w?] [library:"library" .w?]) "}" .w?] 11 | -------------------------------------------------------------------------------- /assets/extract/test.txt: -------------------------------------------------------------------------------- 1 | { 2 | "pistoncore-input": { 3 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/input/Cargo.toml", 4 | "ignore-version": "0.7.0" 5 | }, 6 | 7 | "pistoncore-window": { 8 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/window/Cargo.toml" 9 | }, 10 | 11 | "pistoncore-event_loop": { 12 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/event_loop/Cargo.toml" 13 | }, 14 | 15 | "piston": { 16 | "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/Cargo.toml" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /assets/extract/test2.txt: -------------------------------------------------------------------------------- 1 | { 2 | "elmesque": { 3 | "url": "https://raw.githubusercontent.com/mitchmindtree/elmesque/master/Cargo.toml" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /assets/extract/test3.txt: -------------------------------------------------------------------------------- 1 | { 2 | "piston2d-opengl_graphics": { 3 | "url": "https://raw.githubusercontent.com/PistonDevelopers/opengl_graphics/master/Cargo.toml", 4 | "ignore": { 5 | "piston-shaders_graphics2d": "0.2.1" 6 | } 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /assets/extract/window_apis.txt: -------------------------------------------------------------------------------- 1 | { 2 | "glutin": { 3 | "url": "https://raw.githubusercontent.com/tomaka/glutin/master/Cargo.toml" 4 | }, 5 | "cocoa": { 6 | "url": "https://raw.githubusercontent.com/servo/cocoa-rs/master/Cargo.toml" 7 | }, 8 | "core-graphics": { 9 | "url": "https://raw.githubusercontent.com/servo/core-graphics-rs/master/Cargo.toml" 10 | }, 11 | "core-foundation": { 12 | "url": "https://raw.githubusercontent.com/servo/core-foundation-rs/master/core-foundation/Cargo.toml" 13 | }, 14 | "core-foundation-sys": { 15 | "url": "https://raw.githubusercontent.com/servo/core-foundation-rs/master/core-foundation-sys/Cargo.toml" 16 | }, 17 | "winit": { 18 | "url": "https://raw.githubusercontent.com/tomaka/winit/master/Cargo.toml" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /assets/json/syntax.txt: -------------------------------------------------------------------------------- 1 | 0 , = [.w? "," .w?] 2 | 1 object = ["{" .w? .s?(, [.t? .w? ":" .w? value .w?]) "}"] 3 | 2 array = ["[" .w? .s?(, value) .w? "]"] 4 | 3 value = [{ 5 | .t? 6 | .$ 7 | object 8 | array 9 | "true" 10 | "false" 11 | "null" 12 | }] 13 | 4 document = [.w? object .w?] 14 | -------------------------------------------------------------------------------- /assets/update/syntax.txt: -------------------------------------------------------------------------------- 1 | 0 , = [.w? "," .w?] 2 | 1 bump = ["\"bump\"" .w? ":" .w? "{" .w? .s?(, { 3 | ["\"old\"" .w? ":" .w? .t!:"old"] 4 | ["\"new\"" .w? ":" .w? .t!:"new"] 5 | }) .w? "}"] 6 | 2 dependency = [.t!:"name" .w? ":" .w? "{" .w? bump:"bump" .w? "}"] 7 | 3 dependencies = ["\"dependencies\"" .w? ":" .w? "{" .w? 8 | .s?(, dependency:"dependency") 9 | .w? "}"] 10 | 4 dev_dependencies = ["\"dev-dependencies\"" .w? ":" .w? "{" .w? 11 | .s?(, dependency:"dependency") 12 | .w? "}"] 13 | 5 package = [.t!:"name" .w? ":" .w? "{" .w? .s?(, { 14 | ["\"order\"" .w? ":" .w? .$:"order"] 15 | bump:"bump" 16 | dependencies:"dependencies" 17 | dev_dependencies:"dev_dependencies" 18 | }) .w? "}"] 19 | 6 document = [.w? "{" .w? .s?(, package:"package") .w? "}" .w?] 20 | -------------------------------------------------------------------------------- /assets/update/test.txt: -------------------------------------------------------------------------------- 1 | { 2 | "piston2d-gfx_graphics": { 3 | "order": 0, 4 | "bump": { 5 | "old": "0.4.0", 6 | "new": "0.5.0" 7 | }, 8 | "dependencies": { 9 | "piston2d-graphics": { 10 | "bump": { 11 | "old": "0.4.0", 12 | "new": "0.5.0" 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /assets/update/test2.txt: -------------------------------------------------------------------------------- 1 | { 2 | "read_token": { 3 | "order": 2, 4 | "bump": { 5 | "old": "0.2.2", 6 | "new": "0.3.0" 7 | }, 8 | "dependencies": { 9 | "range": { 10 | "bump": { 11 | "old": "0.1.1", 12 | "new": "0.2.0" 13 | } 14 | } 15 | } 16 | }, 17 | "piston2d-gfx_graphics": { 18 | "order": 3, 19 | "bump": { 20 | "old": "0.5.0", 21 | "new": "0.6.0" 22 | }, 23 | "dependencies": { 24 | "freetype-rs": { 25 | "bump": { 26 | "old": "0.1.0", 27 | "new": "0.2.1" 28 | } 29 | } 30 | } 31 | }, 32 | "piston_meta": { 33 | "order": 3, 34 | "bump": { 35 | "old": "0.12.1", 36 | "new": "0.13.0" 37 | }, 38 | "dependencies": { 39 | "read_token": { 40 | "bump": { 41 | "old": "0.2.2", 42 | "new": "0.3.0" 43 | } 44 | }, 45 | "range": { 46 | "bump": { 47 | "old": "0.1.1", 48 | "new": "0.2.0" 49 | } 50 | } 51 | } 52 | }, 53 | "piston_meta_search": { 54 | "order": 4, 55 | "bump": { 56 | "old": "0.2.0", 57 | "new": "0.3.0" 58 | }, 59 | "dependencies": { 60 | "piston_meta": { 61 | "bump": { 62 | "old": "0.12.0", 63 | "new": "0.13.0" 64 | } 65 | }, 66 | "range": { 67 | "bump": { 68 | "old": "0.1.1", 69 | "new": "0.2.0" 70 | } 71 | } 72 | } 73 | }, 74 | "piston_window": { 75 | "order": 6, 76 | "bump": { 77 | "old": "0.16.0", 78 | "new": "0.17.0" 79 | }, 80 | "dependencies": { 81 | "piston2d-gfx_graphics": { 82 | "bump": { 83 | "old": "0.5.0", 84 | "new": "0.6.0" 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /assets/update/test3.txt: -------------------------------------------------------------------------------- 1 | { 2 | "piston2d-gfx_graphics": { 3 | "order": 0, 4 | "bump": { 5 | "old": "0.4.0", 6 | "new": "0.5.0" 7 | }, 8 | "dependencies": { 9 | "piston2d-graphics": { 10 | "bump": { 11 | "old": "0.4.0", 12 | "new": "0.5.0" 13 | } 14 | } 15 | }, 16 | "dev-dependencies": { 17 | "piston_window": { 18 | "bump": { 19 | "old": "0.10.0", 20 | "new": "0.11.0" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/gfx.rs: -------------------------------------------------------------------------------- 1 | extern crate eco; 2 | 3 | fn main() { 4 | use std::io::Read; 5 | use std::fs::File; 6 | 7 | // Load extract info from file. 8 | let mut extract_info_file = File::open("assets/extract/gfx.txt").unwrap(); 9 | let mut extract_info = String::new(); 10 | extract_info_file.read_to_string(&mut extract_info).unwrap(); 11 | 12 | let dependency_info = eco::extract::extract_dependency_info_from(&extract_info).unwrap(); 13 | let update_info = eco::update::generate_update_info_from(&dependency_info).unwrap(); 14 | println!("{}", update_info); 15 | } 16 | -------------------------------------------------------------------------------- /examples/piston.rs: -------------------------------------------------------------------------------- 1 | extern crate eco; 2 | 3 | fn main() { 4 | use std::io::Read; 5 | use std::fs::File; 6 | 7 | // Load extract info from file. 8 | let mut extract_info_file = File::open("assets/extract/piston.txt").unwrap(); 9 | let mut extract_info = String::new(); 10 | extract_info_file.read_to_string(&mut extract_info).unwrap(); 11 | 12 | let dependency_info = eco::extract::extract_dependency_info_from(&extract_info).unwrap(); 13 | let update_info = eco::update::generate_update_info_from(&dependency_info).unwrap(); 14 | println!("{}", update_info); 15 | } 16 | -------------------------------------------------------------------------------- /examples/rust_audio.rs: -------------------------------------------------------------------------------- 1 | extern crate eco; 2 | 3 | fn main() { 4 | use std::io::Read; 5 | use std::fs::File; 6 | 7 | // Load extract info from file. 8 | let mut extract_info_file = File::open("assets/extract/rust_audio.txt").unwrap(); 9 | let mut extract_info = String::new(); 10 | extract_info_file.read_to_string(&mut extract_info).unwrap(); 11 | 12 | let dependency_info = eco::extract::extract_dependency_info_from(&extract_info).unwrap(); 13 | let update_info = eco::update::generate_update_info_from(&dependency_info).unwrap(); 14 | println!("{}", update_info); 15 | } 16 | -------------------------------------------------------------------------------- /examples/test.rs: -------------------------------------------------------------------------------- 1 | extern crate eco; 2 | 3 | fn main() { 4 | use std::io::Read; 5 | use std::fs::File; 6 | 7 | // Load extract info from file. 8 | let mut extract_info_file = File::open("assets/extract/test2.txt").unwrap(); 9 | let mut extract_info = String::new(); 10 | extract_info_file.read_to_string(&mut extract_info).unwrap(); 11 | 12 | let dependency_info = eco::extract::extract_dependency_info_from(&extract_info).unwrap(); 13 | println!("{}", dependency_info); 14 | } 15 | -------------------------------------------------------------------------------- /examples/todo_piston.rs: -------------------------------------------------------------------------------- 1 | extern crate eco; 2 | 3 | fn main() { 4 | use std::io::Read; 5 | use std::fs::File; 6 | 7 | // Load extract info from file. 8 | let mut extract_info_file = File::open("assets/extract/piston.txt").unwrap(); 9 | let mut extract_info = String::new(); 10 | extract_info_file.read_to_string(&mut extract_info).unwrap(); 11 | 12 | let todo = eco::todo::todo_from_extract_info(&extract_info).unwrap(); 13 | println!("{}", todo); 14 | } 15 | -------------------------------------------------------------------------------- /examples/window_apis.rs: -------------------------------------------------------------------------------- 1 | extern crate eco; 2 | 3 | fn main() { 4 | use std::io::Read; 5 | use std::fs::File; 6 | 7 | // Load extract info from file. 8 | let mut extract_info_file = File::open("assets/extract/window_apis.txt").unwrap(); 9 | let mut extract_info = String::new(); 10 | extract_info_file.read_to_string(&mut extract_info).unwrap(); 11 | 12 | let dependency_info = eco::extract::extract_dependency_info_from(&extract_info).unwrap(); 13 | let update_info = eco::update::generate_update_info_from(&dependency_info).unwrap(); 14 | println!("{}", update_info); 15 | } 16 | -------------------------------------------------------------------------------- /src/dependencies.rs: -------------------------------------------------------------------------------- 1 | //! Dependency info. 2 | 3 | use piston_meta::{json, MetaData, Convert, Range}; 4 | 5 | use std::sync::Arc; 6 | use std::io::{self, Write}; 7 | 8 | /// Writes the dependency info. 9 | pub fn write(package_data: &[Package], w: &mut W) -> Result<(), io::Error> { 10 | let write_dep = |w: &mut W, dependency: &Dependency, has_next: bool| -> Result<(), io::Error> { 11 | write!(w, " ")?; 12 | json::write_string(w, &dependency.name)?; 13 | writeln!(w, ": {{")?; 14 | // Version. 15 | write!(w, " \"version\": ")?; 16 | json::write_string(w, &dependency.version)?; 17 | if let Some(ref ignore_version) = dependency.ignore_version { 18 | writeln!(w, ",")?; 19 | write!(w, " \"ignore-version\": ")?; 20 | json::write_string(w, ignore_version)?; 21 | } 22 | writeln!(w, "")?; 23 | write!(w, " }}")?; 24 | if has_next { 25 | writeln!(w, ",")?; 26 | } else { 27 | writeln!(w, "")?; 28 | } 29 | Ok(()) 30 | }; 31 | 32 | writeln!(w, "{{")?; 33 | let n0 = package_data.len(); 34 | for (i0, package) in package_data.iter().enumerate() { 35 | // Package name. 36 | write!(w, " ")?; 37 | json::write_string(w, &package.name)?; 38 | writeln!(w, ": {{")?; 39 | 40 | // Version. 41 | write!(w, " \"version\": ")?; 42 | json::write_string(w, &package.version)?; 43 | writeln!(w, ",")?; 44 | 45 | // Dependencies. 46 | writeln!(w, " \"dependencies\": {{")?; 47 | let n1 = package.dependencies.len(); 48 | for (i1, dependency) in package.dependencies.iter().enumerate() { 49 | write_dep(w, dependency, i1 + 1 != n1)?; 50 | } 51 | writeln!(w, " }},")?; 52 | 53 | // Dev dependencies. 54 | writeln!(w, " \"dev-dependencies\": {{")?; 55 | let n1 = package.dev_dependencies.len(); 56 | for (i1, dependency) in package.dev_dependencies.iter().enumerate() { 57 | write_dep(w, dependency, i1 + 1 != n1)?; 58 | } 59 | writeln!(w, " }}")?; 60 | 61 | // End package. 62 | write!(w, " }}")?; 63 | if i0 + 1 != n0 { 64 | writeln!(w, ",")?; 65 | } else { 66 | writeln!(w, "")?; 67 | } 68 | } 69 | writeln!(w, "}}")?; 70 | Ok(()) 71 | } 72 | 73 | /// Converts from meta data to dependency information. 74 | pub fn convert(data: &[Range], ignored: &mut Vec) -> Result, ()> { 75 | let mut convert = Convert::new(data); 76 | let mut res = vec![]; 77 | loop { 78 | if let Ok((range, package)) = Package::from_meta_data(convert, ignored) { 79 | convert.update(range); 80 | res.push(package); 81 | } else if convert.remaining_data_len() > 0 { 82 | return Err(()); 83 | } else { 84 | break; 85 | } 86 | } 87 | Ok(res) 88 | } 89 | 90 | /// Stores package information. 91 | pub struct Package { 92 | /// The package name. 93 | pub name: Arc, 94 | /// The version. 95 | pub version: Arc, 96 | /// Dependencies. 97 | pub dependencies: Vec, 98 | /// Dev dependencies. 99 | pub dev_dependencies: Vec, 100 | } 101 | 102 | impl Package { 103 | /// Converts from meta data. 104 | pub fn from_meta_data( 105 | mut convert: Convert, 106 | ignored: &mut Vec, 107 | ) -> Result<(Range, Package), ()> { 108 | let start = convert.clone(); 109 | let node = "package"; 110 | let start_range = convert.start_node(node)?; 111 | convert.update(start_range); 112 | 113 | let mut name: Option> = None; 114 | let mut version: Option> = None; 115 | let mut dependencies = vec![]; 116 | let mut dev_dependencies = vec![]; 117 | loop { 118 | if let Ok(range) = convert.end_node(node) { 119 | convert.update(range); 120 | break; 121 | } else if let Ok((range, val)) = convert.meta_string("name") { 122 | convert.update(range); 123 | name = Some(val); 124 | } else if let Ok((range, val)) = convert.meta_string("version") { 125 | convert.update(range); 126 | version = Some(val); 127 | } else if let Ok((range, dependency)) = 128 | Dependency::from_meta_data("dependency", convert, ignored) 129 | { 130 | convert.update(range); 131 | dependencies.push(dependency); 132 | } else if let Ok((range, dev_dependency)) = 133 | Dependency::from_meta_data("dev_dependency", convert, ignored) 134 | { 135 | convert.update(range); 136 | dev_dependencies.push(dev_dependency); 137 | } else { 138 | let range = convert.ignore(); 139 | convert.update(range); 140 | ignored.push(range); 141 | } 142 | } 143 | 144 | let name = name.ok_or(())?; 145 | let version = version.ok_or(())?; 146 | Ok(( 147 | convert.subtract(start), 148 | Package { 149 | name: name, 150 | version: version, 151 | dependencies: dependencies, 152 | dev_dependencies: dev_dependencies, 153 | }, 154 | )) 155 | } 156 | } 157 | 158 | /// Stores dependency information. 159 | pub struct Dependency { 160 | /// The package name. 161 | pub name: Arc, 162 | /// The semver version of the library. 163 | pub version: Arc, 164 | /// A version to ignore. 165 | pub ignore_version: Option>, 166 | } 167 | 168 | impl Dependency { 169 | /// Converts from meta data. 170 | pub fn from_meta_data( 171 | node: &str, 172 | mut convert: Convert, 173 | ignored: &mut Vec, 174 | ) -> Result<(Range, Dependency), ()> { 175 | let start = convert.clone(); 176 | let start_range = convert.start_node(node)?; 177 | convert.update(start_range); 178 | 179 | let mut name: Option> = None; 180 | let mut version: Option> = None; 181 | let mut ignore_version: Option> = None; 182 | loop { 183 | if let Ok(range) = convert.end_node(node) { 184 | convert.update(range); 185 | break; 186 | } else if let Ok((range, val)) = convert.meta_string("name") { 187 | convert.update(range); 188 | name = Some(val); 189 | } else if let Ok((range, val)) = convert.meta_string("version") { 190 | convert.update(range); 191 | version = Some(val); 192 | } else if let Ok((range, val)) = convert.meta_string("ignore-version") { 193 | convert.update(range); 194 | ignore_version = Some(val); 195 | } else { 196 | let range = convert.ignore(); 197 | convert.update(range); 198 | ignored.push(range); 199 | } 200 | } 201 | 202 | let name = name.ok_or(())?; 203 | let version = version.ok_or(())?; 204 | Ok(( 205 | convert.subtract(start), 206 | Dependency { 207 | name: name, 208 | version: version, 209 | ignore_version: ignore_version, 210 | }, 211 | )) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/extract.rs: -------------------------------------------------------------------------------- 1 | //! Extract dependency information from extract info. 2 | //! 3 | //! ### Extract info 4 | //! 5 | //! This is a JSON format with meta syntax `./assets/extract/syntax.txt`. 6 | //! 7 | //! It is used for collecting dependency data from raw Cargo.toml files. 8 | //! 9 | //! Example: 10 | //! 11 | //! ```json 12 | //! { 13 | //! "pistoncore-input": { 14 | //! "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/input/Cargo.toml", 15 | //! "ignore-version": "0.7.0" 16 | //! }, 17 | //! 18 | //! "pistoncore-window": { 19 | //! "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/window/Cargo.toml" 20 | //! }, 21 | //! 22 | //! "pistoncore-event_loop": { 23 | //! "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/src/event_loop/Cargo.toml" 24 | //! }, 25 | //! 26 | //! "piston": { 27 | //! "url": "https://raw.githubusercontent.com/PistonDevelopers/piston/master/Cargo.toml" 28 | //! } 29 | //! } 30 | //! ``` 31 | //! 32 | //! Fields: 33 | //! 34 | //! - url (the url to the raw Cargo.toml data) 35 | //! - ignore-version (don't update projects using this version) 36 | //! - override-version (replace version extracted from Cargo.toml) 37 | //! - ignore (ignore update of specific dependency version) 38 | //! 39 | //! ### Ignore version 40 | //! 41 | //! The ignore-version field is used to delay updates, to reduce frequency 42 | //! of breaking changes, or put them on hold until some work is done. 43 | //! 44 | //! Example: 45 | //! 46 | //! ```text 47 | //! A (uses B 0.7.0) -> B (0.8.0) 48 | //! ``` 49 | //! 50 | //! A new version of library B is available, but some work might be needed in A 51 | //! before releasing a new version. By listing "0.8.0" in the ignore-version 52 | //! field, there will be no recommended update for A. This will also avoid 53 | //! further updates for libraries depending on A triggered by this version. 54 | //! 55 | //! This might cause unsoundness (see top level documentation) when done to 56 | //! a library that is not at the bottom of the dependency graph. 57 | //! 58 | //! The rule used for ignoring version is: If the package has this version, 59 | //! then filter it from dependency info. 60 | //! 61 | //! ### Override version 62 | //! 63 | //! The override-version field replaces the version extracted from 64 | //! Cargo.toml with another version. 65 | //! 66 | //! For example, a new library is published but the maintainer forgot 67 | //! to merge the changes into master. 68 | //! 69 | //! ### Ignore 70 | //! 71 | //! The ignore field specifies a collection of dependency versions to ignore. 72 | //! This allows precise control over which dependencies to update. 73 | //! 74 | //! Example: 75 | //! 76 | //! ```text 77 | //! { 78 | //! "piston2d-opengl_graphics": { 79 | //! "url": "https://raw.githubusercontent.com/PistonDevelopers/opengl_graphics/master/Cargo.toml", 80 | //! "ignore": { 81 | //! "piston-shaders_graphics2d": "0.2.1" 82 | //! } 83 | //! } 84 | //! } 85 | //! ``` 86 | //! 87 | //! This might cause unsoundess (see top level documentation) when some of 88 | //! the interface of the dependency is exposed in the interface of the library. 89 | //! 90 | //! The rule used for ignoring dependencies is: 91 | //! If there is a recommended update to this version, 92 | //! then filter it from update info. 93 | 94 | use piston_meta::{MetaData, Range, Convert}; 95 | use crate::dependencies::{self, Package}; 96 | use std::sync::Arc; 97 | 98 | /// Stores extract information. 99 | pub struct Extract { 100 | /// The package name. 101 | pub package: Arc, 102 | /// The url of the Cargo.toml. 103 | pub url: Arc, 104 | /// Ignore updates to projects using this version. 105 | pub ignore_version: Option>, 106 | /// Override package version. 107 | pub override_version: Option>, 108 | /// Ignore specific dependency versions. 109 | pub ignore: Vec<(Arc, Arc)>, 110 | } 111 | 112 | impl Extract { 113 | /// Converts from meta data. 114 | pub fn from_meta_data( 115 | mut convert: Convert, 116 | ignored: &mut Vec, 117 | ) -> Result<(Range, Extract), ()> { 118 | let start = convert.clone(); 119 | let node = "library"; 120 | let start_range = convert.start_node(node)?; 121 | convert.update(start_range); 122 | 123 | let mut package: Option> = None; 124 | let mut url: Option> = None; 125 | let mut ignore_version: Option> = None; 126 | let mut override_version: Option> = None; 127 | let mut ignore: Vec<(Arc, Arc)> = vec![]; 128 | loop { 129 | if let Ok(range) = convert.end_node(node) { 130 | convert.update(range); 131 | break; 132 | } else if let Ok((range, val)) = convert.meta_string("package") { 133 | convert.update(range); 134 | package = Some(val); 135 | } else if let Ok((range, val)) = convert.meta_string("url") { 136 | convert.update(range); 137 | url = Some(val); 138 | } else if let Ok((range, val)) = convert.meta_string("ignore_version") { 139 | convert.update(range); 140 | ignore_version = Some(val); 141 | } else if let Ok((range, val)) = convert.meta_string("override_version") { 142 | convert.update(range); 143 | override_version = Some(val); 144 | } else if let Ok((range, val)) = ignore_from_meta_data(convert, ignored) { 145 | convert.update(range); 146 | ignore = val; 147 | } else { 148 | let range = convert.ignore(); 149 | convert.update(range); 150 | ignored.push(range); 151 | } 152 | } 153 | 154 | let package = package.ok_or(())?; 155 | let url = url.ok_or(())?; 156 | Ok(( 157 | convert.subtract(start), 158 | Extract { 159 | package: package, 160 | url: url, 161 | ignore_version: ignore_version, 162 | override_version: override_version, 163 | ignore: ignore, 164 | }, 165 | )) 166 | } 167 | } 168 | 169 | fn ignore_from_meta_data( 170 | mut convert: Convert, 171 | ignored: &mut Vec, 172 | ) -> Result<(Range, Vec<(Arc, Arc)>), ()> { 173 | let start = convert.clone(); 174 | let node = "ignore"; 175 | let start_range = convert.start_node(node)?; 176 | convert.update(start_range); 177 | 178 | let mut res: Vec<(Arc, Arc)> = vec![]; 179 | loop { 180 | if let Ok(range) = convert.end_node(node) { 181 | convert.update(range); 182 | break; 183 | } else if let Ok((range, val)) = dependency_from_meta_data(convert, ignored) { 184 | convert.update(range); 185 | res.push(val); 186 | } else { 187 | let range = convert.ignore(); 188 | convert.update(range); 189 | ignored.push(range); 190 | } 191 | } 192 | 193 | Ok((convert.subtract(start), res)) 194 | } 195 | 196 | fn dependency_from_meta_data( 197 | mut convert: Convert, 198 | ignored: &mut Vec, 199 | ) -> Result<(Range, (Arc, Arc)), ()> { 200 | let start = convert.clone(); 201 | let node = "dependency"; 202 | let start_range = convert.start_node(node)?; 203 | convert.update(start_range); 204 | 205 | let mut package: Option> = None; 206 | let mut version: Option> = None; 207 | loop { 208 | if let Ok(range) = convert.end_node(node) { 209 | convert.update(range); 210 | break; 211 | } else if let Ok((range, val)) = convert.meta_string("package") { 212 | convert.update(range); 213 | package = Some(val); 214 | } else if let Ok((range, val)) = convert.meta_string("version") { 215 | convert.update(range); 216 | version = Some(val); 217 | } else { 218 | let range = convert.ignore(); 219 | convert.update(range); 220 | ignored.push(range); 221 | } 222 | } 223 | 224 | let package = package.ok_or(())?; 225 | let version = version.ok_or(())?; 226 | Ok((convert.subtract(start), (package, version))) 227 | } 228 | 229 | /// Loads a text file from url. 230 | pub fn load_text_file_from_url(url: &str) -> Result { 231 | use reqwest::StatusCode; 232 | use std::io::Read; 233 | 234 | let mut response = reqwest::blocking::get(url) 235 | .map_err(|e| format!("Error fetching file over http {}: {}", url, e.to_string()))?; 236 | if response.status() == StatusCode::OK { 237 | let mut data = String::new(); 238 | response 239 | .read_to_string(&mut data) 240 | .map_err(|e| format!("Error fetching file over http {}: {}", url, e.to_string()))?; 241 | Ok(data) 242 | } else { 243 | Err(format!( 244 | "Error fetching file over http {}: {}", 245 | url, response.status() 246 | )) 247 | } 248 | } 249 | 250 | /// Converts meta data into extract info. 251 | pub fn convert_extract_info( 252 | data: &[Range], 253 | ignored: &mut Vec, 254 | ) -> Result, ()> { 255 | use piston_meta::bootstrap::*; 256 | 257 | let mut list = vec![]; 258 | let mut convert = Convert::new(data); 259 | loop { 260 | if let Ok((range, extract)) = Extract::from_meta_data(convert, ignored) { 261 | convert.update(range); 262 | list.push(extract); 263 | } else if convert.remaining_data_len() > 0 { 264 | return Err(()); 265 | } else { 266 | break; 267 | } 268 | } 269 | Ok(list) 270 | } 271 | 272 | /// Converts meta data into Cargo.toml information. 273 | pub fn convert_cargo_toml( 274 | data: &[Range], 275 | ignored: &mut Vec, 276 | ) -> Result { 277 | let (_, package) = Package::from_meta_data(Convert::new(data), ignored)?; 278 | Ok(package) 279 | } 280 | 281 | /// Extracts dependency info. 282 | pub fn extract_dependency_info_from(extract_info: &str) -> Result { 283 | use std::sync::Mutex; 284 | use std::thread; 285 | use piston_meta::*; 286 | 287 | let extract_meta_syntax = include_str!("../assets/extract/syntax.txt"); 288 | let extract_meta_rules = stderr_unwrap(extract_meta_syntax, syntax(extract_meta_syntax)); 289 | let mut extract_data = vec![]; 290 | stderr_unwrap( 291 | extract_info, 292 | parse(&extract_meta_rules, extract_info, &mut extract_data), 293 | ); 294 | 295 | let mut ignored = vec![]; 296 | let list = convert_extract_info(&extract_data, &mut ignored) 297 | .map_err(|_| String::from("Could not convert extract data"))?; 298 | 299 | // Stores package and dependency information extracted from Cargo.toml. 300 | let package_data = Arc::new(Mutex::new(vec![])); 301 | 302 | // Extract information. 303 | let cargo_toml_syntax = include_str!("../assets/cargo-toml/syntax.txt"); 304 | let cargo_toml_rules = Arc::new(stderr_unwrap(cargo_toml_syntax, syntax(cargo_toml_syntax))); 305 | let mut handles = vec![]; 306 | for extract in list.into_iter() { 307 | let cargo_toml_rules = cargo_toml_rules.clone(); 308 | let package_data = package_data.clone(); 309 | handles.push(thread::spawn(move || { 310 | let config = load_text_file_from_url(&extract.url)?; 311 | let mut cargo_toml_data = vec![]; 312 | match parse(&cargo_toml_rules, &config, &mut cargo_toml_data) { 313 | Ok(val) => val, 314 | Err(range_err) => { 315 | let mut w: Vec = vec![]; 316 | ParseErrorHandler::new(&config) 317 | .write(&mut w, range_err) 318 | .unwrap(); 319 | return Err(format!( 320 | "{}: Syntax error in Cargo.toml for url `{}`\n{}", 321 | &extract.package, 322 | &extract.url, 323 | &String::from_utf8(w).unwrap() 324 | )); 325 | } 326 | }; 327 | 328 | let mut ignored = vec![]; 329 | let mut package = convert_cargo_toml(&cargo_toml_data, &mut ignored).map_err(|_| { 330 | format!( 331 | "Could not convert Cargo.toml data for url `{}`", 332 | &extract.url 333 | ) 334 | })?; 335 | if extract.package != package.name { 336 | return Err(format!( 337 | "Wrong Cargo.toml: `{}` does not match `{}`", 338 | extract.package, package.name 339 | )); 340 | } 341 | if let Some(ref ignore_version) = extract.ignore_version { 342 | if ignore_version == &package.version { 343 | return Ok(()); 344 | } 345 | } 346 | if let Some(ref override_version) = extract.override_version { 347 | package.version = override_version.clone(); 348 | } 349 | for &(ref name, ref version) in &extract.ignore { 350 | for dep in &mut package.dependencies { 351 | if &**dep.name == &**name { 352 | dep.ignore_version = Some(version.clone()); 353 | } 354 | } 355 | for dep in &mut package.dev_dependencies { 356 | if &**dep.name == &**name { 357 | dep.ignore_version = Some(version.clone()); 358 | } 359 | } 360 | } 361 | package_data.lock().unwrap().push(package); 362 | Ok(()) 363 | })) 364 | } 365 | for handle in handles.into_iter() { 366 | handle.join().unwrap().map_err(|e| e)?; 367 | } 368 | 369 | let mut res: Vec = vec![]; 370 | dependencies::write(&package_data.lock().unwrap(), &mut res).unwrap(); 371 | 372 | let res = String::from_utf8(res).map_err(|e| format!("UTF8 error: {}", e))?; 373 | 374 | Ok(res) 375 | } 376 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! A tool for reasoning about breaking changes in Rust ecosystems 4 | //! 5 | //! Eco helps Rust programmers to keep an ecosystem updated. 6 | //! An ecosystem is a collection of libraries that are relevant to a project. 7 | //! 8 | //! This library is organized into modules, where each module has its own 9 | //! custom text format. By using text, it is easy to customize the 10 | //! collecting and generating of data. 11 | //! 12 | //! ### Ecosystem 13 | //! 14 | //! The definition of an ecosystem used by Eco is: 15 | //! 16 | //! ```test 17 | //! A list of libraries, each with a set of dependencies, 18 | //! forming an directed acyclic graph for dependencies and directed cyclic 19 | //! graph for dev-dependencies, with no holes. 20 | //! ``` 21 | //! 22 | //! ### Example 23 | //! 24 | //! Extract info is a bird view of an ecosystem of libraries. 25 | //! This is used to build a dependency graph, which then is used to generate 26 | //! recommended update actions to keep the ecosystem healthy. 27 | //! 28 | //! ```ignore 29 | //! extern crate eco; 30 | //! 31 | //! fn main() { 32 | //! use std::io::Read; 33 | //! use std::fs::File; 34 | //! 35 | //! // Load extract info from file. 36 | //! let mut extract_info_file = File::open("assets/extract/piston.txt").unwrap(); 37 | //! let mut extract_info = String::new(); 38 | //! extract_info_file.read_to_string(&mut extract_info).unwrap(); 39 | //! 40 | //! let dependency_info = eco::extract::extract_dependency_info_from(&extract_info).unwrap(); 41 | //! let update_info = eco::update::generate_update_info_from(&dependency_info).unwrap(); 42 | //! println!("{}", update_info); 43 | //! } 44 | //! ``` 45 | //! 46 | //! ### Unsoundness of holes 47 | //! 48 | //! It is important to not leave any hole in the ecosystem. 49 | //! A hole is when a dependency is not listed that uses a listed library. 50 | //! 51 | //! Example: 52 | //! 53 | //! ```text 54 | //! A (listed) -> B (not listed) -> C (listed) 55 | //! ``` 56 | //! 57 | //! The dependencies of library B will not be analyzed because they are not 58 | //! listed. This will lead to a potential error since breaking changes in 59 | //! library C does not cause a breaking change in A. 60 | //! 61 | //! Notice it is OK to not list libraries that are lower level dependencies. 62 | //! 63 | //! As long as there are no holes, the update algorithm is sound. 64 | 65 | extern crate piston_meta; 66 | extern crate semver; 67 | 68 | pub mod extract; 69 | pub mod update; 70 | pub mod dependencies; 71 | pub mod todo; 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use piston_meta::*; 76 | 77 | #[test] 78 | fn extract_is_json() { 79 | let _ = load_syntax_data("assets/json/syntax.txt", "assets/extract/test.txt"); 80 | } 81 | 82 | #[test] 83 | fn extract() { 84 | let _data = load_syntax_data("assets/extract/syntax.txt", "assets/extract/test.txt"); 85 | let _data = load_syntax_data("assets/extract/syntax.txt", "assets/extract/test2.txt"); 86 | let _data = load_syntax_data("assets/extract/syntax.txt", "assets/extract/test3.txt"); 87 | } 88 | 89 | #[test] 90 | fn dependencies_is_json() { 91 | let _ = load_syntax_data("assets/json/syntax.txt", "assets/dependencies/test.txt"); 92 | let _ = load_syntax_data("assets/json/syntax.txt", "assets/dependencies/test2.txt"); 93 | let _ = load_syntax_data("assets/json/syntax.txt", "assets/dependencies/test3.txt"); 94 | } 95 | 96 | #[test] 97 | fn dependencies() { 98 | let _data = load_syntax_data( 99 | "assets/dependencies/syntax.txt", 100 | "assets/dependencies/test.txt", 101 | ); 102 | let _data = load_syntax_data( 103 | "assets/dependencies/syntax.txt", 104 | "assets/dependencies/test2.txt", 105 | ); 106 | let _data = load_syntax_data( 107 | "assets/dependencies/syntax.txt", 108 | "assets/dependencies/test3.txt", 109 | ); 110 | let _data = load_syntax_data( 111 | "assets/dependencies/syntax.txt", 112 | "assets/dependencies/test4.txt", 113 | ); 114 | } 115 | 116 | #[test] 117 | fn cargo_toml() { 118 | let _data = load_syntax_data("assets/cargo-toml/syntax.txt", "assets/cargo-toml/test.txt"); 119 | let _data = load_syntax_data( 120 | "assets/cargo-toml/syntax.txt", 121 | "assets/cargo-toml/test2.txt", 122 | ); 123 | let _data = load_syntax_data( 124 | "assets/cargo-toml/syntax.txt", 125 | "assets/cargo-toml/test3.txt", 126 | ); 127 | let _data = load_syntax_data( 128 | "assets/cargo-toml/syntax.txt", 129 | "assets/cargo-toml/test4.txt", 130 | ); 131 | } 132 | 133 | #[test] 134 | fn update_is_json() { 135 | let _ = load_syntax_data("assets/json/syntax.txt", "assets/update/test.txt"); 136 | let _ = load_syntax_data("assets/json/syntax.txt", "assets/update/test2.txt"); 137 | let _ = load_syntax_data("assets/json/syntax.txt", "assets/update/test3.txt"); 138 | } 139 | 140 | #[test] 141 | fn update() { 142 | let _data = load_syntax_data("assets/update/syntax.txt", "assets/update/test.txt"); 143 | let _data = load_syntax_data("assets/update/syntax.txt", "assets/update/test2.txt"); 144 | let _data = load_syntax_data("assets/update/syntax.txt", "assets/update/test3.txt"); 145 | } 146 | 147 | #[test] 148 | fn parse_version() { 149 | let res = ::update::parse_version(">= 1.0, <= 1.3"); 150 | assert!(res.is_ok()); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/todo.rs: -------------------------------------------------------------------------------- 1 | //! Generate todo list for each package. 2 | 3 | /// Generates a todo list from extract info. 4 | /// This can be used in Github markdown. 5 | /// 6 | /// ```ignore 7 | /// - [ ] foo 8 | /// - [ ] bar 9 | /// - [ ] baz 10 | /// ``` 11 | pub fn todo_from_extract_info(extract_info: &str) -> Result { 12 | use std::io::Write; 13 | use piston_meta::*; 14 | use crate::extract::*; 15 | 16 | let extract_meta_syntax = include_str!("../assets/extract/syntax.txt"); 17 | let extract_meta_rules = stderr_unwrap(extract_meta_syntax, syntax(extract_meta_syntax)); 18 | let mut extract_data = vec![]; 19 | stderr_unwrap( 20 | extract_info, 21 | parse(&extract_meta_rules, extract_info, &mut extract_data), 22 | ); 23 | 24 | let mut ignored = vec![]; 25 | let list = convert_extract_info(&extract_data, &mut ignored) 26 | .map_err(|_| String::from("Could not convert extract data"))?; 27 | 28 | let mut res: Vec = vec![]; 29 | for package in &list { 30 | writeln!(&mut res, "- [ ] {}", &package.package).unwrap(); 31 | } 32 | Ok(String::from_utf8(res).unwrap()) 33 | } 34 | -------------------------------------------------------------------------------- /src/update.rs: -------------------------------------------------------------------------------- 1 | //! Generate update information from dependency information. 2 | 3 | use std::sync::Arc; 4 | use std::collections::HashMap; 5 | use std::io::{self, Write}; 6 | 7 | use semver::{self, Version}; 8 | 9 | use crate::dependencies; 10 | 11 | /// Writes update info. 12 | pub fn write(update_packages: &[Package], w: &mut W) -> Result<(), io::Error> { 13 | use piston_meta::json; 14 | 15 | writeln!(w, "{{")?; 16 | let n0 = update_packages.len(); 17 | for (i0, update_package) in update_packages.iter().enumerate() { 18 | write!(w, " ")?; 19 | json::write_string(w, &update_package.name)?; 20 | writeln!(w, ": {{")?; 21 | writeln!(w, " \"order\": {},", update_package.order)?; 22 | 23 | // Bump package. 24 | writeln!(w, " \"bump\": {{")?; 25 | write!(w, " \"old\": ")?; 26 | json::write_string(w, &format!("{}", update_package.bump.old))?; 27 | writeln!(w, ",")?; 28 | write!(w, " \"new\": ")?; 29 | json::write_string(w, &format!("{}", update_package.bump.new))?; 30 | writeln!(w, "")?; 31 | writeln!(w, " }},")?; 32 | 33 | // Dependencies. 34 | writeln!(w, " \"dependencies\": {{")?; 35 | let n1 = update_package.dependencies.len(); 36 | for (i1, dep) in update_package.dependencies.iter().enumerate() { 37 | write!(w, " ")?; 38 | json::write_string(w, &dep.name)?; 39 | writeln!(w, ": {{")?; 40 | writeln!(w, " \"bump\": {{")?; 41 | write!(w, " \"old\": ")?; 42 | json::write_string(w, &format!("{}", dep.bump.old))?; 43 | writeln!(w, ",")?; 44 | write!(w, " \"new\": ")?; 45 | json::write_string(w, &format!("{}", dep.bump.new))?; 46 | writeln!(w, "")?; 47 | writeln!(w, " }}")?; 48 | write!(w, " }}")?; 49 | if i1 + 1 < n1 { 50 | writeln!(w, ",")?; 51 | } else { 52 | writeln!(w, "")?; 53 | } 54 | } 55 | writeln!(w, " }},")?; 56 | 57 | // Dev dependencies. 58 | writeln!(w, " \"dev-dependencies\": {{")?; 59 | let n1 = update_package.dev_dependencies.len(); 60 | for (i1, dep) in update_package.dev_dependencies.iter().enumerate() { 61 | write!(w, " ")?; 62 | json::write_string(w, &dep.name)?; 63 | writeln!(w, ": {{")?; 64 | writeln!(w, " \"bump\": {{")?; 65 | write!(w, " \"old\": ")?; 66 | json::write_string(w, &format!("{}", dep.bump.old))?; 67 | writeln!(w, ",")?; 68 | write!(w, " \"new\": ")?; 69 | json::write_string(w, &format!("{}", dep.bump.new))?; 70 | writeln!(w, "")?; 71 | writeln!(w, " }}")?; 72 | write!(w, " }}")?; 73 | if i1 + 1 < n1 { 74 | writeln!(w, ",")?; 75 | } else { 76 | writeln!(w, "")?; 77 | } 78 | } 79 | writeln!(w, " }}")?; 80 | 81 | write!(w, " }}")?; 82 | if i0 + 1 < n0 { 83 | writeln!(w, ",")?; 84 | } else { 85 | writeln!(w, "")?; 86 | } 87 | } 88 | writeln!(w, "}}")?; 89 | Ok(()) 90 | } 91 | 92 | /// Stores old and new version. 93 | pub struct Bump { 94 | /// The old version. 95 | pub old: Version, 96 | /// The new version. 97 | pub new: Version, 98 | } 99 | 100 | /// Stores update info for dependency. 101 | pub struct Dependency { 102 | /// The library name. 103 | pub name: Arc, 104 | /// Bump info. 105 | pub bump: Bump, 106 | } 107 | 108 | /// Stores information about a package. 109 | pub struct Package { 110 | /// The package name. 111 | pub name: Arc, 112 | /// The order to update library. 113 | pub order: u32, 114 | /// Update information. 115 | pub bump: Bump, 116 | /// The dependencies. 117 | pub dependencies: Vec, 118 | /// The dev dependencies. 119 | pub dev_dependencies: Vec, 120 | } 121 | 122 | /// Tries appending zero to version to make it parse. 123 | /// Ignores extra information after `,`. 124 | pub fn parse_version(text: &str) -> Result { 125 | let text = if text == "1" { "1.0" } else { text }; 126 | let text = if text == "^1" { "1.0" } else { text }; 127 | 128 | // Ignore `>=`. 129 | let mut text = if text.starts_with(">=") { 130 | &text[2..] 131 | } else if text.starts_with("^") { 132 | &text[1..] 133 | } else if text.starts_with("=") { 134 | &text[1..] 135 | } else { 136 | text 137 | }; 138 | 139 | // Ignore extra information after `,`. 140 | for ch in text.char_indices() { 141 | if ch.1 == ',' { 142 | text = &text[..ch.0]; 143 | } 144 | } 145 | 146 | let text = { 147 | let n = text.len(); 148 | if text.ends_with(".*") { 149 | &text[..n - 2] 150 | } else { 151 | text 152 | } 153 | }; 154 | match Version::parse(text) { 155 | Err(_) => { 156 | let append_zero = format!("{}.0", text); 157 | Version::parse(&append_zero) 158 | } 159 | x => x, 160 | } 161 | } 162 | 163 | /// Generates update info. 164 | pub fn generate_update_info_from(dependency_info: &str) -> Result { 165 | use piston_meta::*; 166 | 167 | type PackageIndex = usize; 168 | type Depth = u32; 169 | 170 | fn depth_of( 171 | package_index: PackageIndex, 172 | package_indices: &HashMap, PackageIndex>, 173 | depths: &mut HashMap, 174 | dependencies_data: &[dependencies::Package], 175 | ) -> Depth { 176 | let package = &dependencies_data[package_index]; 177 | let d = depths.get(&package_index).map(|d| *d); 178 | match d { 179 | None => { 180 | // The depth of a package equals maximum depth of the dependencies + 1. 181 | let new_depth: Depth = package 182 | .dependencies 183 | .iter() 184 | .map(|dep| { 185 | package_indices 186 | .get(&dep.name) 187 | .map(|&p| depth_of(p, package_indices, depths, dependencies_data)) 188 | .unwrap_or(0) 189 | }) 190 | .max() 191 | .unwrap_or(0) + 1; 192 | depths.insert(package_index, new_depth); 193 | new_depth 194 | } 195 | Some(x) => x, 196 | } 197 | } 198 | 199 | // Whether the version should be ignored. 200 | fn ignore_version(text: &str) -> bool { 201 | text == "*" 202 | } 203 | 204 | // Returns true if two different versions means a breaking change. 205 | fn breaks(a: &Version, b: &Version) -> bool { 206 | if a.major != b.major { 207 | true 208 | } else if a.major != 0 { 209 | false 210 | } else if a.minor != b.minor { 211 | true 212 | } else if a.minor != 0 { 213 | false 214 | } else if a.patch != b.patch { 215 | true 216 | } else { 217 | false 218 | } 219 | } 220 | 221 | // Increment first non-zero number. 222 | fn increment_version(version: &mut Version) { 223 | if version.major != 0 { 224 | version.increment_major(); 225 | } else if version.minor != 0 { 226 | version.increment_minor(); 227 | } else { 228 | version.increment_patch(); 229 | } 230 | } 231 | 232 | // Parse and convert to dependencies data. 233 | let dependencies_meta_syntax = include_str!("../assets/dependencies/syntax.txt"); 234 | let dependencies_meta_rules = 235 | stderr_unwrap(dependencies_meta_syntax, syntax(dependencies_meta_syntax)); 236 | let mut dependency_info_meta_data = vec![]; 237 | stderr_unwrap( 238 | dependency_info, 239 | parse( 240 | &dependencies_meta_rules, 241 | dependency_info, 242 | &mut dependency_info_meta_data, 243 | ), 244 | ); 245 | let mut ignored = vec![]; 246 | let dependencies_data = dependencies::convert(&dependency_info_meta_data, &mut ignored) 247 | .map_err(|_| String::from("Could not convert dependency info"))?; 248 | 249 | // Stores the package indices using package name as key. 250 | let package_indices: HashMap, PackageIndex> = HashMap::from_iter( 251 | dependencies_data 252 | .iter() 253 | .enumerate() 254 | .map(|(i, p)| (p.name.clone(), i)), 255 | ); 256 | 257 | // Store the depths of libraries. 258 | let mut depths: HashMap = HashMap::new(); 259 | for i in 0..dependencies_data.len() { 260 | let _depth = depth_of(i, &package_indices, &mut depths, &dependencies_data); 261 | } 262 | 263 | let mut new_versions: HashMap, Version> = HashMap::new(); 264 | for package in &dependencies_data { 265 | // Get latest version used by any dependency. 266 | for dep in &package.dependencies { 267 | if ignore_version(&dep.version) { 268 | continue; 269 | } 270 | 271 | let version = parse_version(&dep.version).map_err(|_| { 272 | format!( 273 | "Could not parse version `{}` for dependency `{}` in `{}`", 274 | &dep.version, &dep.name, &package.name 275 | ) 276 | })?; 277 | let v = new_versions.get(&dep.name).map(|v| v.clone()); 278 | match v { 279 | None => { 280 | new_versions.insert(dep.name.clone(), version); 281 | } 282 | Some(v) => { 283 | if v < version { 284 | new_versions.insert(dep.name.clone(), version); 285 | } 286 | } 287 | } 288 | } 289 | 290 | // Get latest version used by any dev dependency. 291 | for dep in &package.dev_dependencies { 292 | if ignore_version(&dep.version) { 293 | continue; 294 | } 295 | 296 | let version = parse_version(&dep.version).map_err(|_| { 297 | format!( 298 | "Could not parse version `{}` for dev dependency `{}` in `{}`", 299 | &dep.version, &dep.name, &package.name 300 | ) 301 | })?; 302 | let v = new_versions.get(&dep.name).map(|v| v.clone()); 303 | match v { 304 | None => { 305 | new_versions.insert(dep.name.clone(), version); 306 | } 307 | Some(v) => { 308 | if v < version { 309 | new_versions.insert(dep.name.clone(), version); 310 | } 311 | } 312 | } 313 | } 314 | } 315 | 316 | // Overwrite the versions used by packages. 317 | for package in &dependencies_data { 318 | let version = Version::parse(&package.version).map_err(|_| { 319 | format!( 320 | "Could not parse version `{}` for `{}`", 321 | &package.version, &package.name 322 | ) 323 | })?; 324 | new_versions.insert(package.name.clone(), version); 325 | } 326 | 327 | // Create list of sorted package indices by depth. 328 | let mut sorted_depths: Vec<_> = depths.iter().collect(); 329 | sorted_depths.sort_by(|&(_, da), &(_, db)| da.cmp(db)); 330 | 331 | // Stores the update info. 332 | let mut update_packages: Vec = vec![]; 333 | 334 | for (&package_index, &order) in sorted_depths { 335 | let package = &dependencies_data[package_index]; 336 | let mut update_dependencies = vec![]; 337 | 338 | // Find dependencies that needs update. 339 | for dep in &package.dependencies { 340 | if ignore_version(&dep.version) { 341 | continue; 342 | } 343 | // Do not generate update info for dependencies starting with `>=`. 344 | if dep.version.starts_with(">=") { 345 | continue; 346 | } 347 | 348 | let old_version = parse_version(&dep.version).map_err(|_| { 349 | format!( 350 | "Could not parse version `{}` for dependency `{}` in `{}`", 351 | &dep.version, &dep.name, &package.name 352 | ) 353 | })?; 354 | let new_version = new_versions.get(&dep.name).unwrap(); 355 | if breaks(new_version, &old_version) { 356 | if let Some(ref ignore_version) = dep.ignore_version { 357 | // Ignore update to a specific version. 358 | if &new_version.to_string() == &**ignore_version { 359 | continue; 360 | } 361 | } 362 | update_dependencies.push(Dependency { 363 | name: dep.name.clone(), 364 | bump: Bump { 365 | old: old_version, 366 | new: new_version.clone(), 367 | }, 368 | }); 369 | } 370 | } 371 | 372 | // If any dependency needs update, then the package needs update. 373 | if update_dependencies.len() > 0 { 374 | let old_version = Version::parse(&package.version).map_err(|_| { 375 | format!( 376 | "Could not parse version `{}` for `{}`", 377 | &package.version, &package.name 378 | ) 379 | })?; 380 | let new_version = new_versions.get_mut(&package.name).unwrap(); 381 | if *new_version == old_version { 382 | increment_version(new_version); 383 | } 384 | update_packages.push(Package { 385 | name: package.name.clone(), 386 | order: order, 387 | bump: Bump { 388 | old: old_version, 389 | new: new_version.clone(), 390 | }, 391 | dependencies: update_dependencies, 392 | dev_dependencies: vec![], 393 | }); 394 | } 395 | } 396 | 397 | // Find dev dependencies that needs update. 398 | // This requires a second step since a library can have dev-dependencies to 399 | // a library with higher depth. 400 | for update_package in &mut update_packages { 401 | let package_index = *package_indices.get(&update_package.name).unwrap(); 402 | let package = &dependencies_data[package_index]; 403 | 404 | for dep in &package.dev_dependencies { 405 | if ignore_version(&dep.version) { 406 | continue; 407 | } 408 | // Do not generate update info for dependencies starting with `>=`. 409 | if dep.version.starts_with(">=") { 410 | continue; 411 | } 412 | 413 | let old_version = parse_version(&dep.version).map_err(|_| { 414 | format!( 415 | "Could not parse version `{}` for dev dependency `{}` in `{}`", 416 | &dep.version, &dep.name, &package.name 417 | ) 418 | })?; 419 | let new_version = new_versions.get(&dep.name).unwrap(); 420 | if breaks(new_version, &old_version) { 421 | if let Some(ref ignore_version) = dep.ignore_version { 422 | // Ignore update to a specific version. 423 | if &new_version.to_string() == &**ignore_version { 424 | continue; 425 | } 426 | } 427 | update_package.dev_dependencies.push(Dependency { 428 | name: dep.name.clone(), 429 | bump: Bump { 430 | old: old_version, 431 | new: new_version.clone(), 432 | }, 433 | }); 434 | } 435 | } 436 | } 437 | 438 | let mut w: Vec = vec![]; 439 | write(&update_packages, &mut w).unwrap(); 440 | 441 | Ok(String::from_utf8(w).unwrap()) 442 | } 443 | --------------------------------------------------------------------------------