git-subtree-dir: development/tools/cargo-workspace2 git-subtree-mainline: ddeaea72333d8bb6911ac45fcfe40ee1caa00b04 git-subtree-split: c3e1e0679bae7aa6bd668125cb997812a590f875wip/yesman
parent
f56286e4c4
commit
90a45a314f
@ -0,0 +1 @@ |
|||||||
|
eval "$(lorri direnv)" |
@ -0,0 +1,2 @@ |
|||||||
|
/target |
||||||
|
**/*.rs.bk |
@ -0,0 +1,8 @@ |
|||||||
|
stages: |
||||||
|
- test |
||||||
|
|
||||||
|
test: |
||||||
|
image: rust:buster |
||||||
|
script: |
||||||
|
- cargo test |
||||||
|
|
@ -0,0 +1,17 @@ |
|||||||
|
language: rust |
||||||
|
|
||||||
|
cache: cargo |
||||||
|
|
||||||
|
rust: |
||||||
|
- stable |
||||||
|
- beta |
||||||
|
- nightly |
||||||
|
|
||||||
|
matrix: |
||||||
|
allow_failures: |
||||||
|
- rust: nightly |
||||||
|
fast_finish: true |
||||||
|
|
||||||
|
script: |
||||||
|
- cargo build --verbose --all |
||||||
|
- cargo test --verbose --all |
@ -0,0 +1,192 @@ |
|||||||
|
# This file is automatically @generated by Cargo. |
||||||
|
# It is not intended for manual editing. |
||||||
|
[[package]] |
||||||
|
name = "ascii" |
||||||
|
version = "0.9.3" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "autocfg" |
||||||
|
version = "1.0.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "byteorder" |
||||||
|
version = "1.3.4" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "cargo-workspace2" |
||||||
|
version = "0.2.1" |
||||||
|
dependencies = [ |
||||||
|
"semver", |
||||||
|
"textwrap", |
||||||
|
"toml_edit", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "chrono" |
||||||
|
version = "0.4.19" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" |
||||||
|
dependencies = [ |
||||||
|
"libc", |
||||||
|
"num-integer", |
||||||
|
"num-traits", |
||||||
|
"time", |
||||||
|
"winapi", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "combine" |
||||||
|
version = "3.8.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" |
||||||
|
dependencies = [ |
||||||
|
"ascii", |
||||||
|
"byteorder", |
||||||
|
"either", |
||||||
|
"memchr", |
||||||
|
"unreachable", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "either" |
||||||
|
version = "1.6.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "libc" |
||||||
|
version = "0.2.79" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "linked-hash-map" |
||||||
|
version = "0.5.3" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "memchr" |
||||||
|
version = "2.3.3" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "num-integer" |
||||||
|
version = "0.1.43" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" |
||||||
|
dependencies = [ |
||||||
|
"autocfg", |
||||||
|
"num-traits", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "num-traits" |
||||||
|
version = "0.2.12" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" |
||||||
|
dependencies = [ |
||||||
|
"autocfg", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "semver" |
||||||
|
version = "0.9.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" |
||||||
|
dependencies = [ |
||||||
|
"semver-parser", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "semver-parser" |
||||||
|
version = "0.7.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "textwrap" |
||||||
|
version = "0.12.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" |
||||||
|
dependencies = [ |
||||||
|
"unicode-width", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "time" |
||||||
|
version = "0.1.44" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" |
||||||
|
dependencies = [ |
||||||
|
"libc", |
||||||
|
"wasi", |
||||||
|
"winapi", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "toml_edit" |
||||||
|
version = "0.1.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "87f53b1aca7d5fe2e17498a38cac0e1f5a33234d5b980fb36b9402bb93b98ae4" |
||||||
|
dependencies = [ |
||||||
|
"chrono", |
||||||
|
"combine", |
||||||
|
"linked-hash-map", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "unicode-width" |
||||||
|
version = "0.1.8" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "unreachable" |
||||||
|
version = "1.0.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" |
||||||
|
dependencies = [ |
||||||
|
"void", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "void" |
||||||
|
version = "1.0.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "wasi" |
||||||
|
version = "0.10.0+wasi-snapshot-preview1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "winapi" |
||||||
|
version = "0.3.9" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" |
||||||
|
dependencies = [ |
||||||
|
"winapi-i686-pc-windows-gnu", |
||||||
|
"winapi-x86_64-pc-windows-gnu", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "winapi-i686-pc-windows-gnu" |
||||||
|
version = "0.4.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "winapi-x86_64-pc-windows-gnu" |
||||||
|
version = "0.4.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" |
@ -0,0 +1,14 @@ |
|||||||
|
[package] |
||||||
|
name = "cargo-workspace2" |
||||||
|
version = "0.2.1" |
||||||
|
description = "A tool to query and manage complex cargo workspaces" |
||||||
|
authors = ["Mx Kookie <kookie@spacekookie.de>"] |
||||||
|
documentation = "https://docs.rs/cargo-workspace2" |
||||||
|
repository = "https://git.open-communication.net/spacekookie/cargo-workspace2" |
||||||
|
license = "GPL-3.0-or-later" |
||||||
|
edition = "2018" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
toml_edit = "0.1.3" |
||||||
|
semver = "0.9.0" |
||||||
|
textwrap = "0.12" |
@ -0,0 +1,674 @@ |
|||||||
|
GNU GENERAL PUBLIC LICENSE |
||||||
|
Version 3, 29 June 2007 |
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
||||||
|
Everyone is permitted to copy and distribute verbatim copies |
||||||
|
of this license document, but changing it is not allowed. |
||||||
|
|
||||||
|
Preamble |
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for |
||||||
|
software and other kinds of works. |
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed |
||||||
|
to take away your freedom to share and change the works. By contrast, |
||||||
|
the GNU General Public License is intended to guarantee your freedom to |
||||||
|
share and change all versions of a program--to make sure it remains free |
||||||
|
software for all its users. We, the Free Software Foundation, use the |
||||||
|
GNU General Public License for most of our software; it applies also to |
||||||
|
any other work released this way by its authors. You can apply it to |
||||||
|
your programs, too. |
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not |
||||||
|
price. Our General Public Licenses are designed to make sure that you |
||||||
|
have the freedom to distribute copies of free software (and charge for |
||||||
|
them if you wish), that you receive source code or can get it if you |
||||||
|
want it, that you can change the software or use pieces of it in new |
||||||
|
free programs, and that you know you can do these things. |
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you |
||||||
|
these rights or asking you to surrender the rights. Therefore, you have |
||||||
|
certain responsibilities if you distribute copies of the software, or if |
||||||
|
you modify it: responsibilities to respect the freedom of others. |
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether |
||||||
|
gratis or for a fee, you must pass on to the recipients the same |
||||||
|
freedoms that you received. You must make sure that they, too, receive |
||||||
|
or can get the source code. And you must show them these terms so they |
||||||
|
know their rights. |
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps: |
||||||
|
(1) assert copyright on the software, and (2) offer you this License |
||||||
|
giving you legal permission to copy, distribute and/or modify it. |
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains |
||||||
|
that there is no warranty for this free software. For both users' and |
||||||
|
authors' sake, the GPL requires that modified versions be marked as |
||||||
|
changed, so that their problems will not be attributed erroneously to |
||||||
|
authors of previous versions. |
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run |
||||||
|
modified versions of the software inside them, although the manufacturer |
||||||
|
can do so. This is fundamentally incompatible with the aim of |
||||||
|
protecting users' freedom to change the software. The systematic |
||||||
|
pattern of such abuse occurs in the area of products for individuals to |
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we |
||||||
|
have designed this version of the GPL to prohibit the practice for those |
||||||
|
products. If such problems arise substantially in other domains, we |
||||||
|
stand ready to extend this provision to those domains in future versions |
||||||
|
of the GPL, as needed to protect the freedom of users. |
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents. |
||||||
|
States should not allow patents to restrict development and use of |
||||||
|
software on general-purpose computers, but in those that do, we wish to |
||||||
|
avoid the special danger that patents applied to a free program could |
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that |
||||||
|
patents cannot be used to render the program non-free. |
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and |
||||||
|
modification follow. |
||||||
|
|
||||||
|
TERMS AND CONDITIONS |
||||||
|
|
||||||
|
0. Definitions. |
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License. |
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of |
||||||
|
works, such as semiconductor masks. |
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this |
||||||
|
License. Each licensee is addressed as "you". "Licensees" and |
||||||
|
"recipients" may be individuals or organizations. |
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work |
||||||
|
in a fashion requiring copyright permission, other than the making of an |
||||||
|
exact copy. The resulting work is called a "modified version" of the |
||||||
|
earlier work or a work "based on" the earlier work. |
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based |
||||||
|
on the Program. |
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without |
||||||
|
permission, would make you directly or secondarily liable for |
||||||
|
infringement under applicable copyright law, except executing it on a |
||||||
|
computer or modifying a private copy. Propagation includes copying, |
||||||
|
distribution (with or without modification), making available to the |
||||||
|
public, and in some countries other activities as well. |
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other |
||||||
|
parties to make or receive copies. Mere interaction with a user through |
||||||
|
a computer network, with no transfer of a copy, is not conveying. |
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices" |
||||||
|
to the extent that it includes a convenient and prominently visible |
||||||
|
feature that (1) displays an appropriate copyright notice, and (2) |
||||||
|
tells the user that there is no warranty for the work (except to the |
||||||
|
extent that warranties are provided), that licensees may convey the |
||||||
|
work under this License, and how to view a copy of this License. If |
||||||
|
the interface presents a list of user commands or options, such as a |
||||||
|
menu, a prominent item in the list meets this criterion. |
||||||
|
|
||||||
|
1. Source Code. |
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work |
||||||
|
for making modifications to it. "Object code" means any non-source |
||||||
|
form of a work. |
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official |
||||||
|
standard defined by a recognized standards body, or, in the case of |
||||||
|
interfaces specified for a particular programming language, one that |
||||||
|
is widely used among developers working in that language. |
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other |
||||||
|
than the work as a whole, that (a) is included in the normal form of |
||||||
|
packaging a Major Component, but which is not part of that Major |
||||||
|
Component, and (b) serves only to enable use of the work with that |
||||||
|
Major Component, or to implement a Standard Interface for which an |
||||||
|
implementation is available to the public in source code form. A |
||||||
|
"Major Component", in this context, means a major essential component |
||||||
|
(kernel, window system, and so on) of the specific operating system |
||||||
|
(if any) on which the executable work runs, or a compiler used to |
||||||
|
produce the work, or an object code interpreter used to run it. |
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all |
||||||
|
the source code needed to generate, install, and (for an executable |
||||||
|
work) run the object code and to modify the work, including scripts to |
||||||
|
control those activities. However, it does not include the work's |
||||||
|
System Libraries, or general-purpose tools or generally available free |
||||||
|
programs which are used unmodified in performing those activities but |
||||||
|
which are not part of the work. For example, Corresponding Source |
||||||
|
includes interface definition files associated with source files for |
||||||
|
the work, and the source code for shared libraries and dynamically |
||||||
|
linked subprograms that the work is specifically designed to require, |
||||||
|
such as by intimate data communication or control flow between those |
||||||
|
subprograms and other parts of the work. |
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users |
||||||
|
can regenerate automatically from other parts of the Corresponding |
||||||
|
Source. |
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that |
||||||
|
same work. |
||||||
|
|
||||||
|
2. Basic Permissions. |
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of |
||||||
|
copyright on the Program, and are irrevocable provided the stated |
||||||
|
conditions are met. This License explicitly affirms your unlimited |
||||||
|
permission to run the unmodified Program. The output from running a |
||||||
|
covered work is covered by this License only if the output, given its |
||||||
|
content, constitutes a covered work. This License acknowledges your |
||||||
|
rights of fair use or other equivalent, as provided by copyright law. |
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not |
||||||
|
convey, without conditions so long as your license otherwise remains |
||||||
|
in force. You may convey covered works to others for the sole purpose |
||||||
|
of having them make modifications exclusively for you, or provide you |
||||||
|
with facilities for running those works, provided that you comply with |
||||||
|
the terms of this License in conveying all material for which you do |
||||||
|
not control copyright. Those thus making or running the covered works |
||||||
|
for you must do so exclusively on your behalf, under your direction |
||||||
|
and control, on terms that prohibit them from making any copies of |
||||||
|
your copyrighted material outside their relationship with you. |
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under |
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10 |
||||||
|
makes it unnecessary. |
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological |
||||||
|
measure under any applicable law fulfilling obligations under article |
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
||||||
|
similar laws prohibiting or restricting circumvention of such |
||||||
|
measures. |
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid |
||||||
|
circumvention of technological measures to the extent such circumvention |
||||||
|
is effected by exercising rights under this License with respect to |
||||||
|
the covered work, and you disclaim any intention to limit operation or |
||||||
|
modification of the work as a means of enforcing, against the work's |
||||||
|
users, your or third parties' legal rights to forbid circumvention of |
||||||
|
technological measures. |
||||||
|
|
||||||
|
4. Conveying Verbatim Copies. |
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you |
||||||
|
receive it, in any medium, provided that you conspicuously and |
||||||
|
appropriately publish on each copy an appropriate copyright notice; |
||||||
|
keep intact all notices stating that this License and any |
||||||
|
non-permissive terms added in accord with section 7 apply to the code; |
||||||
|
keep intact all notices of the absence of any warranty; and give all |
||||||
|
recipients a copy of this License along with the Program. |
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey, |
||||||
|
and you may offer support or warranty protection for a fee. |
||||||
|
|
||||||
|
5. Conveying Modified Source Versions. |
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to |
||||||
|
produce it from the Program, in the form of source code under the |
||||||
|
terms of section 4, provided that you also meet all of these conditions: |
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified |
||||||
|
it, and giving a relevant date. |
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is |
||||||
|
released under this License and any conditions added under section |
||||||
|
7. This requirement modifies the requirement in section 4 to |
||||||
|
"keep intact all notices". |
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this |
||||||
|
License to anyone who comes into possession of a copy. This |
||||||
|
License will therefore apply, along with any applicable section 7 |
||||||
|
additional terms, to the whole of the work, and all its parts, |
||||||
|
regardless of how they are packaged. This License gives no |
||||||
|
permission to license the work in any other way, but it does not |
||||||
|
invalidate such permission if you have separately received it. |
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display |
||||||
|
Appropriate Legal Notices; however, if the Program has interactive |
||||||
|
interfaces that do not display Appropriate Legal Notices, your |
||||||
|
work need not make them do so. |
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent |
||||||
|
works, which are not by their nature extensions of the covered work, |
||||||
|
and which are not combined with it such as to form a larger program, |
||||||
|
in or on a volume of a storage or distribution medium, is called an |
||||||
|
"aggregate" if the compilation and its resulting copyright are not |
||||||
|
used to limit the access or legal rights of the compilation's users |
||||||
|
beyond what the individual works permit. Inclusion of a covered work |
||||||
|
in an aggregate does not cause this License to apply to the other |
||||||
|
parts of the aggregate. |
||||||
|
|
||||||
|
6. Conveying Non-Source Forms. |
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms |
||||||
|
of sections 4 and 5, provided that you also convey the |
||||||
|
machine-readable Corresponding Source under the terms of this License, |
||||||
|
in one of these ways: |
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product |
||||||
|
(including a physical distribution medium), accompanied by the |
||||||
|
Corresponding Source fixed on a durable physical medium |
||||||
|
customarily used for software interchange. |
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product |
||||||
|
(including a physical distribution medium), accompanied by a |
||||||
|
written offer, valid for at least three years and valid for as |
||||||
|
long as you offer spare parts or customer support for that product |
||||||
|
model, to give anyone who possesses the object code either (1) a |
||||||
|
copy of the Corresponding Source for all the software in the |
||||||
|
product that is covered by this License, on a durable physical |
||||||
|
medium customarily used for software interchange, for a price no |
||||||
|
more than your reasonable cost of physically performing this |
||||||
|
conveying of source, or (2) access to copy the |
||||||
|
Corresponding Source from a network server at no charge. |
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the |
||||||
|
written offer to provide the Corresponding Source. This |
||||||
|
alternative is allowed only occasionally and noncommercially, and |
||||||
|
only if you received the object code with such an offer, in accord |
||||||
|
with subsection 6b. |
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated |
||||||
|
place (gratis or for a charge), and offer equivalent access to the |
||||||
|
Corresponding Source in the same way through the same place at no |
||||||
|
further charge. You need not require recipients to copy the |
||||||
|
Corresponding Source along with the object code. If the place to |
||||||
|
copy the object code is a network server, the Corresponding Source |
||||||
|
may be on a different server (operated by you or a third party) |
||||||
|
that supports equivalent copying facilities, provided you maintain |
||||||
|
clear directions next to the object code saying where to find the |
||||||
|
Corresponding Source. Regardless of what server hosts the |
||||||
|
Corresponding Source, you remain obligated to ensure that it is |
||||||
|
available for as long as needed to satisfy these requirements. |
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided |
||||||
|
you inform other peers where the object code and Corresponding |
||||||
|
Source of the work are being offered to the general public at no |
||||||
|
charge under subsection 6d. |
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded |
||||||
|
from the Corresponding Source as a System Library, need not be |
||||||
|
included in conveying the object code work. |
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any |
||||||
|
tangible personal property which is normally used for personal, family, |
||||||
|
or household purposes, or (2) anything designed or sold for incorporation |
||||||
|
into a dwelling. In determining whether a product is a consumer product, |
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular |
||||||
|
product received by a particular user, "normally used" refers to a |
||||||
|
typical or common use of that class of product, regardless of the status |
||||||
|
of the particular user or of the way in which the particular user |
||||||
|
actually uses, or expects or is expected to use, the product. A product |
||||||
|
is a consumer product regardless of whether the product has substantial |
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent |
||||||
|
the only significant mode of use of the product. |
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods, |
||||||
|
procedures, authorization keys, or other information required to install |
||||||
|
and execute modified versions of a covered work in that User Product from |
||||||
|
a modified version of its Corresponding Source. The information must |
||||||
|
suffice to ensure that the continued functioning of the modified object |
||||||
|
code is in no case prevented or interfered with solely because |
||||||
|
modification has been made. |
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or |
||||||
|
specifically for use in, a User Product, and the conveying occurs as |
||||||
|
part of a transaction in which the right of possession and use of the |
||||||
|
User Product is transferred to the recipient in perpetuity or for a |
||||||
|
fixed term (regardless of how the transaction is characterized), the |
||||||
|
Corresponding Source conveyed under this section must be accompanied |
||||||
|
by the Installation Information. But this requirement does not apply |
||||||
|
if neither you nor any third party retains the ability to install |
||||||
|
modified object code on the User Product (for example, the work has |
||||||
|
been installed in ROM). |
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a |
||||||
|
requirement to continue to provide support service, warranty, or updates |
||||||
|
for a work that has been modified or installed by the recipient, or for |
||||||
|
the User Product in which it has been modified or installed. Access to a |
||||||
|
network may be denied when the modification itself materially and |
||||||
|
adversely affects the operation of the network or violates the rules and |
||||||
|
protocols for communication across the network. |
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided, |
||||||
|
in accord with this section must be in a format that is publicly |
||||||
|
documented (and with an implementation available to the public in |
||||||
|
source code form), and must require no special password or key for |
||||||
|
unpacking, reading or copying. |
||||||
|
|
||||||
|
7. Additional Terms. |
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this |
||||||
|
License by making exceptions from one or more of its conditions. |
||||||
|
Additional permissions that are applicable to the entire Program shall |
||||||
|
be treated as though they were included in this License, to the extent |
||||||
|
that they are valid under applicable law. If additional permissions |
||||||
|
apply only to part of the Program, that part may be used separately |
||||||
|
under those permissions, but the entire Program remains governed by |
||||||
|
this License without regard to the additional permissions. |
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option |
||||||
|
remove any additional permissions from that copy, or from any part of |
||||||
|
it. (Additional permissions may be written to require their own |
||||||
|
removal in certain cases when you modify the work.) You may place |
||||||
|
additional permissions on material, added by you to a covered work, |
||||||
|
for which you have or can give appropriate copyright permission. |
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you |
||||||
|
add to a covered work, you may (if authorized by the copyright holders of |
||||||
|
that material) supplement the terms of this License with terms: |
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the |
||||||
|
terms of sections 15 and 16 of this License; or |
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or |
||||||
|
author attributions in that material or in the Appropriate Legal |
||||||
|
Notices displayed by works containing it; or |
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or |
||||||
|
requiring that modified versions of such material be marked in |
||||||
|
reasonable ways as different from the original version; or |
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or |
||||||
|
authors of the material; or |
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some |
||||||
|
trade names, trademarks, or service marks; or |
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that |
||||||
|
material by anyone who conveys the material (or modified versions of |
||||||
|
it) with contractual assumptions of liability to the recipient, for |
||||||
|
any liability that these contractual assumptions directly impose on |
||||||
|
those licensors and authors. |
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further |
||||||
|
restrictions" within the meaning of section 10. If the Program as you |
||||||
|
received it, or any part of it, contains a notice stating that it is |
||||||
|
governed by this License along with a term that is a further |
||||||
|
restriction, you may remove that term. If a license document contains |
||||||
|
a further restriction but permits relicensing or conveying under this |
||||||
|
License, you may add to a covered work material governed by the terms |
||||||
|
of that license document, provided that the further restriction does |
||||||
|
not survive such relicensing or conveying. |
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you |
||||||
|
must place, in the relevant source files, a statement of the |
||||||
|
additional terms that apply to those files, or a notice indicating |
||||||
|
where to find the applicable terms. |
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the |
||||||
|
form of a separately written license, or stated as exceptions; |
||||||
|
the above requirements apply either way. |
||||||
|
|
||||||
|
8. Termination. |
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly |
||||||
|
provided under this License. Any attempt otherwise to propagate or |
||||||
|
modify it is void, and will automatically terminate your rights under |
||||||
|
this License (including any patent licenses granted under the third |
||||||
|
paragraph of section 11). |
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your |
||||||
|
license from a particular copyright holder is reinstated (a) |
||||||
|
provisionally, unless and until the copyright holder explicitly and |
||||||
|
finally terminates your license, and (b) permanently, if the copyright |
||||||
|
holder fails to notify you of the violation by some reasonable means |
||||||
|
prior to 60 days after the cessation. |
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is |
||||||
|
reinstated permanently if the copyright holder notifies you of the |
||||||
|
violation by some reasonable means, this is the first time you have |
||||||
|
received notice of violation of this License (for any work) from that |
||||||
|
copyright holder, and you cure the violation prior to 30 days after |
||||||
|
your receipt of the notice. |
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the |
||||||
|
licenses of parties who have received copies or rights from you under |
||||||
|
this License. If your rights have been terminated and not permanently |
||||||
|
reinstated, you do not qualify to receive new licenses for the same |
||||||
|
material under section 10. |
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies. |
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or |
||||||
|
run a copy of the Program. Ancillary propagation of a covered work |
||||||
|
occurring solely as a consequence of using peer-to-peer transmission |
||||||
|
to receive a copy likewise does not require acceptance. However, |
||||||
|
nothing other than this License grants you permission to propagate or |
||||||
|
modify any covered work. These actions infringe copyright if you do |
||||||
|
not accept this License. Therefore, by modifying or propagating a |
||||||
|
covered work, you indicate your acceptance of this License to do so. |
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients. |
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically |
||||||
|
receives a license from the original licensors, to run, modify and |
||||||
|
propagate that work, subject to this License. You are not responsible |
||||||
|
for enforcing compliance by third parties with this License. |
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an |
||||||
|
organization, or substantially all assets of one, or subdividing an |
||||||
|
organization, or merging organizations. If propagation of a covered |
||||||
|
work results from an entity transaction, each party to that |
||||||
|
transaction who receives a copy of the work also receives whatever |
||||||
|
licenses to the work the party's predecessor in interest had or could |
||||||
|
give under the previous paragraph, plus a right to possession of the |
||||||
|
Corresponding Source of the work from the predecessor in interest, if |
||||||
|
the predecessor has it or can get it with reasonable efforts. |
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the |
||||||
|
rights granted or affirmed under this License. For example, you may |
||||||
|
not impose a license fee, royalty, or other charge for exercise of |
||||||
|
rights granted under this License, and you may not initiate litigation |
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that |
||||||
|
any patent claim is infringed by making, using, selling, offering for |
||||||
|
sale, or importing the Program or any portion of it. |
||||||
|
|
||||||
|
11. Patents. |
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this |
||||||
|
License of the Program or a work on which the Program is based. The |
||||||
|
work thus licensed is called the contributor's "contributor version". |
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims |
||||||
|
owned or controlled by the contributor, whether already acquired or |
||||||
|
hereafter acquired, that would be infringed by some manner, permitted |
||||||
|
by this License, of making, using, or selling its contributor version, |
||||||
|
but do not include claims that would be infringed only as a |
||||||
|
consequence of further modification of the contributor version. For |
||||||
|
purposes of this definition, "control" includes the right to grant |
||||||
|
patent sublicenses in a manner consistent with the requirements of |
||||||
|
this License. |
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free |
||||||
|
patent license under the contributor's essential patent claims, to |
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and |
||||||
|
propagate the contents of its contributor version. |
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express |
||||||
|
agreement or commitment, however denominated, not to enforce a patent |
||||||
|
(such as an express permission to practice a patent or covenant not to |
||||||
|
sue for patent infringement). To "grant" such a patent license to a |
||||||
|
party means to make such an agreement or commitment not to enforce a |
||||||
|
patent against the party. |
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license, |
||||||
|
and the Corresponding Source of the work is not available for anyone |
||||||
|
to copy, free of charge and under the terms of this License, through a |
||||||
|
publicly available network server or other readily accessible means, |
||||||
|
then you must either (1) cause the Corresponding Source to be so |
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the |
||||||
|
patent license for this particular work, or (3) arrange, in a manner |
||||||
|
consistent with the requirements of this License, to extend the patent |
||||||
|
license to downstream recipients. "Knowingly relying" means you have |
||||||
|
actual knowledge that, but for the patent license, your conveying the |
||||||
|
covered work in a country, or your recipient's use of the covered work |
||||||
|
in a country, would infringe one or more identifiable patents in that |
||||||
|
country that you have reason to believe are valid. |
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or |
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a |
||||||
|
covered work, and grant a patent license to some of the parties |
||||||
|
receiving the covered work authorizing them to use, propagate, modify |
||||||
|
or convey a specific copy of the covered work, then the patent license |
||||||
|
you grant is automatically extended to all recipients of the covered |
||||||
|
work and works based on it. |
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within |
||||||
|
the scope of its coverage, prohibits the exercise of, or is |
||||||
|
conditioned on the non-exercise of one or more of the rights that are |
||||||
|
specifically granted under this License. You may not convey a covered |
||||||
|
work if you are a party to an arrangement with a third party that is |
||||||
|
in the business of distributing software, under which you make payment |
||||||
|
to the third party based on the extent of your activity of conveying |
||||||
|
the work, and under which the third party grants, to any of the |
||||||
|
parties who would receive the covered work from you, a discriminatory |
||||||
|
patent license (a) in connection with copies of the covered work |
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily |
||||||
|
for and in connection with specific products or compilations that |
||||||
|
contain the covered work, unless you entered into that arrangement, |
||||||
|
or that patent license was granted, prior to 28 March 2007. |
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting |
||||||
|
any implied license or other defenses to infringement that may |
||||||
|
otherwise be available to you under applicable patent law. |
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom. |
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or |
||||||
|
otherwise) that contradict the conditions of this License, they do not |
||||||
|
excuse you from the conditions of this License. If you cannot convey a |
||||||
|
covered work so as to satisfy simultaneously your obligations under this |
||||||
|
License and any other pertinent obligations, then as a consequence you may |
||||||
|
not convey it at all. For example, if you agree to terms that obligate you |
||||||
|
to collect a royalty for further conveying from those to whom you convey |
||||||
|
the Program, the only way you could satisfy both those terms and this |
||||||
|
License would be to refrain entirely from conveying the Program. |
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License. |
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have |
||||||
|
permission to link or combine any covered work with a work licensed |
||||||
|
under version 3 of the GNU Affero General Public License into a single |
||||||
|
combined work, and to convey the resulting work. The terms of this |
||||||
|
License will continue to apply to the part which is the covered work, |
||||||
|
but the special requirements of the GNU Affero General Public License, |
||||||
|
section 13, concerning interaction through a network will apply to the |
||||||
|
combination as such. |
||||||
|
|
||||||
|
14. Revised Versions of this License. |
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of |
||||||
|
the GNU General Public License from time to time. Such new versions will |
||||||
|
be similar in spirit to the present version, but may differ in detail to |
||||||
|
address new problems or concerns. |
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the |
||||||
|
Program specifies that a certain numbered version of the GNU General |
||||||
|
Public License "or any later version" applies to it, you have the |
||||||
|
option of following the terms and conditions either of that numbered |
||||||
|
version or of any later version published by the Free Software |
||||||
|
Foundation. If the Program does not specify a version number of the |
||||||
|
GNU General Public License, you may choose any version ever published |
||||||
|
by the Free Software Foundation. |
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future |
||||||
|
versions of the GNU General Public License can be used, that proxy's |
||||||
|
public statement of acceptance of a version permanently authorizes you |
||||||
|
to choose that version for the Program. |
||||||
|
|
||||||
|
Later license versions may give you additional or different |
||||||
|
permissions. However, no additional obligations are imposed on any |
||||||
|
author or copyright holder as a result of your choosing to follow a |
||||||
|
later version. |
||||||
|
|
||||||
|
15. Disclaimer of Warranty. |
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
||||||
|
|
||||||
|
16. Limitation of Liability. |
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
||||||
|
SUCH DAMAGES. |
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16. |
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided |
||||||
|
above cannot be given local legal effect according to their terms, |
||||||
|
reviewing courts shall apply local law that most closely approximates |
||||||
|
an absolute waiver of all civil liability in connection with the |
||||||
|
Program, unless a warranty or assumption of liability accompanies a |
||||||
|
copy of the Program in return for a fee. |
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS |
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs |
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest |
||||||
|
possible use to the public, the best way to achieve this is to make it |
||||||
|
free software which everyone can redistribute and change under these terms. |
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest |
||||||
|
to attach them to the start of each source file to most effectively |
||||||
|
state the exclusion of warranty; and each file should have at least |
||||||
|
the "copyright" line and a pointer to where the full notice is found. |
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.> |
||||||
|
Copyright (C) <year> <name of author> |
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, |
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
GNU General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail. |
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short |
||||||
|
notice like this when it starts in an interactive mode: |
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author> |
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
||||||
|
This is free software, and you are welcome to redistribute it |
||||||
|
under certain conditions; type `show c' for details. |
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate |
||||||
|
parts of the General Public License. Of course, your program's commands |
||||||
|
might be different; for a GUI interface, you would use an "about box". |
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school, |
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary. |
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see |
||||||
|
<http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program |
||||||
|
into proprietary programs. If your program is a subroutine library, you |
||||||
|
may consider it more useful to permit linking proprietary applications with |
||||||
|
the library. If this is what you want to do, use the GNU Lesser General |
||||||
|
Public License instead of this License. But first, please read |
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>. |
@ -0,0 +1,90 @@ |
|||||||
|
# cargo-workspace2 🔹 [![pipeline status](https://git.open-communication.net/spacekookie/cargo-workspace2/badges/develop/pipeline.svg)](https://git.open-communication.net/spacekookie/cargo-workspace2/-/commits/develop) |
||||||
|
|
||||||
|
A better tool to manage complex cargo workspaces, by dynamically |
||||||
|
building a dependency graph of your project to query, and run commands |
||||||
|
on. This project is both a CLI and a library. A repl is on the |
||||||
|
roadmap. |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
|
When using `cargo-ws2` you first provide it some query to find crates |
||||||
|
in your workspace, and then a command with it's specific parameters to |
||||||
|
execute for each crate selected in the query. |
||||||
|
|
||||||
|
`cargo ws2 <QUERY LANG> <COMMAND> [COMMAND OPTIONS]` |
||||||
|
|
||||||
|
|
||||||
|
Currently supported commands. |
||||||
|
|
||||||
|
* `print` - echo the selected crate set |
||||||
|
* `publish` - publish a set of crates |
||||||
|
|
||||||
|
Additionally, there are special "bang commands", that take precedence |
||||||
|
over other argument line input, and don't have to be put in a |
||||||
|
particular position. |
||||||
|
|
||||||
|
* `!help [COMMAND]` - show a help screen for the program, or a specific command |
||||||
|
* `!version` - print the program, rustc version, etc |
||||||
|
* `!debug` - enable debug printing, and parse the rest of the line as normal |
||||||
|
|
||||||
|
`cargo-ws2` provides a query system based on the `ws2ql` query |
||||||
|
expression language. Following are some examples. |
||||||
|
|
||||||
|
* List crates between `[ ]`: `[ foo bar baz ]` |
||||||
|
* Or query the dependency graph with a `{}` block: `{ foo < }` (all |
||||||
|
crates that depend on `foo`) |
||||||
|
* Include crates by path tree: `[ ./foo/* ]` (not implemented yet!) |
||||||
|
* Even search via regex: `[/crate-suffix\$/]` (not implemented yet!) |
||||||
|
|
||||||
|
See the full description of `ws2ql` in the [docs](./docs/ws2ql.md)! |
||||||
|
|
||||||
|
|
||||||
|
### Publishing crates |
||||||
|
|
||||||
|
This tool was largely written to make publishing crates in a workspace |
||||||
|
easier. Let's look at an example. |
||||||
|
|
||||||
|
```toml |
||||||
|
[package] |
||||||
|
name = "foo" |
||||||
|
version = "0.1.0" |
||||||
|
# ... |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
bar = { version = "0.5.0", path = "../bar" } |
||||||
|
``` |
||||||
|
|
||||||
|
```toml |
||||||
|
[package] |
||||||
|
name = "bar" |
||||||
|
version = "0.5.0" |
||||||
|
``` |
||||||
|
|
||||||
|
This is a common setup: pointing cargo at the `path` of `bar` means |
||||||
|
that changes to the sources on disk become available, before having to |
||||||
|
publish to [crates.io](https://crates.io). But having a version |
||||||
|
dependency is required when trying to publish, thus we add this too. |
||||||
|
|
||||||
|
Unfortunately now, when we update `bar` to version `0.6.0` our |
||||||
|
workspace will stop building, because `foo` depends on a version of |
||||||
|
`bar` that's different from the one we're pointing it to. |
||||||
|
|
||||||
|
What `cargo-ws2` does when you run `cargo ws2 [bar] publish minor` is |
||||||
|
bump bar to `0.6.0`, and also update the dependency line in `foo` to |
||||||
|
be `{ version = "0.6.0", path = "../bar" }`, without touching the |
||||||
|
version of `foo`. |
||||||
|
|
||||||
|
If you want all dependent crates to be bumped to the same version, |
||||||
|
prefix your publish level (`major`, `minor`, `patch`, or a semver |
||||||
|
string) with `=`. |
||||||
|
|
||||||
|
`cargo ws2 [bar] publish =minor` will bump both foo, and bar to |
||||||
|
`0.6.0` (picking the highest version of the set if their starting |
||||||
|
versions are not yet the same. |
||||||
|
|
||||||
|
|
||||||
|
## License |
||||||
|
|
||||||
|
`cargo-workspace2` is free software, licensed under the GNU General |
||||||
|
Public License 3.0 (or later). See the LICENSE file for a full copy |
||||||
|
of the license. |
@ -0,0 +1,26 @@ |
|||||||
|
use std::{env, fs::File, io::Write, path::PathBuf, process::Command}; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let rustc_path = env::var("CARGO_WS2_RUSTC").unwrap_or_else(|_| "rustc".into()); |
||||||
|
let version = Command::new(rustc_path) |
||||||
|
.arg("-V") |
||||||
|
.output() |
||||||
|
.expect("failed to get rustc version. Is rustc in $PATH?") |
||||||
|
.stdout; |
||||||
|
|
||||||
|
let out_path = PathBuf::new(); |
||||||
|
let mut f = File::create( |
||||||
|
out_path |
||||||
|
.join(env::var("OUT_DIR").unwrap()) |
||||||
|
.join("rustc.version"), |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
f.write_all( |
||||||
|
String::from_utf8(version) |
||||||
|
.expect("Invalid rustc command return") |
||||||
|
.replace("\"", "") |
||||||
|
.trim() |
||||||
|
.as_bytes(), |
||||||
|
) |
||||||
|
.unwrap(); |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
# ws2 query language |
||||||
|
|
||||||
|
The `cargo-ws2` query language (`ws2ql`) allows users to specify a |
||||||
|
set of inputs, and an operation to execute on it. |
||||||
|
|
||||||
|
## Basic rules |
||||||
|
|
||||||
|
* Inside `[]` are sets (meaning items de-dup), space separated |
||||||
|
* IF `[]` contains a `/` anywhere _but_ the beginning AND end, |
||||||
|
query becomes a path glob |
||||||
|
* IF `[]` contains `/` at start AND end, query becomes a regex |
||||||
|
* An operation is parsed in the order of the fields in it's struct |
||||||
|
(so publish order is `type mod devel`, etc) |
||||||
|
* Inside `{}` you can create dependency maps |
||||||
|
* `{ foo < }` represents all crates that depend on `foo` |
||||||
|
* `{ foo < bar &< }` represents all crates that depend on `foo` AND `bar` |
||||||
|
* `{ foo < bar |< }` represents all crates that depend on `foo` OR `bar` |
||||||
|
* `{ foo < bar &!< }` represents all crates that depend on `foo` AND NOT `bar` |
||||||
|
|
@ -0,0 +1,9 @@ |
|||||||
|
with import <nixpkgs> { |
||||||
|
config.android_sdk.accept_license = true; |
||||||
|
config.allowUnfree = true; |
||||||
|
}; |
||||||
|
|
||||||
|
stdenv.mkDerivation { |
||||||
|
name = "cargo-workspace2"; |
||||||
|
buildInputs = with pkgs; [ clangStdenv ]; |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
// extern crate cargo_ws_release;
|
||||||
|
// #[macro_use]
|
||||||
|
// extern crate clap;
|
||||||
|
// extern crate toml;
|
||||||
|
// extern crate toml_edit;
|
||||||
|
|
||||||
|
// use cargo_ws_release::data_models::level::*;
|
||||||
|
// use cargo_ws_release::do_batch_release;
|
||||||
|
// use clap::{App, Arg};
|
||||||
|
// use std::fs::File;
|
||||||
|
// use std::process;
|
||||||
|
|
||||||
|
use cargo_workspace2 as ws2; |
||||||
|
|
||||||
|
use std::env::{args, current_dir}; |
||||||
|
use ws2::cli::{self, CmdSet}; |
||||||
|
use ws2::models::{CargoWorkspace, CrateId, Workspace}; |
||||||
|
use ws2::{ops::Op, query::Query}; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let CmdSet { debug, line } = cli::parse_env_args(); |
||||||
|
let (q, rest) = Query::parse(line.into_iter()); |
||||||
|
|
||||||
|
let path = current_dir().unwrap(); |
||||||
|
let ws = Workspace::process(match CargoWorkspace::open(path) { |
||||||
|
Ok(c) => c, |
||||||
|
Err(e) => { |
||||||
|
eprintln!("An error occured: {}", e); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
let set = ws.query(q); |
||||||
|
let op = Op::parse(rest); |
||||||
|
|
||||||
|
mutate(ws, op, set); |
||||||
|
} |
||||||
|
|
||||||
|
/// Consume a workspace to mutate it
|
||||||
|
fn mutate(mut ws: Workspace, op: Op, set: Vec<CrateId>) { |
||||||
|
ws.execute(op, set); |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
use super::v_to_s; |
||||||
|
use toml_edit::InlineTable; |
||||||
|
|
||||||
|
/// An intra-workspace dependency
|
||||||
|
///
|
||||||
|
/// In a Cargo.toml file these are expressed as paths, and sometimes
|
||||||
|
/// also as versions.
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// [dependencies]
|
||||||
|
/// my-other-crate = { version = "0.1.0", path = "../other-crate" }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub struct Dependency { |
||||||
|
pub name: String, |
||||||
|
pub alias: Option<String>, |
||||||
|
pub version: Option<String>, |
||||||
|
pub path: Option<String>, |
||||||
|
} |
||||||
|
|
||||||
|
impl Dependency { |
||||||
|
pub(crate) fn parse(n: String, t: &InlineTable) -> Option<Self> { |
||||||
|
let v = t.get("version").map(|s| v_to_s(s)); |
||||||
|
let p = t.get("path").map(|s| v_to_s(s)); |
||||||
|
|
||||||
|
// If a `package` key is present, set it as the name, and set
|
||||||
|
// the `n` as the alias. When we look for keys later, the
|
||||||
|
// alias has precedence over the actual name, but this way
|
||||||
|
// `name` is always the actual crate name which is important
|
||||||
|
// for dependency resolution.
|
||||||
|
let (alias, name) = match t |
||||||
|
.get("package") |
||||||
|
.map(|s| v_to_s(s).replace("\"", "").trim().to_string()) |
||||||
|
{ |
||||||
|
Some(alias) => (Some(n), alias), |
||||||
|
None => (None, n), |
||||||
|
}; |
||||||
|
|
||||||
|
match (v, p) { |
||||||
|
(version @ Some(_), path @ Some(_)) => Some(Self { |
||||||
|
name, |
||||||
|
alias, |
||||||
|
version, |
||||||
|
path, |
||||||
|
}), |
||||||
|
(version @ Some(_), None) => Some(Self { |
||||||
|
name, |
||||||
|
alias, |
||||||
|
version, |
||||||
|
path: None, |
||||||
|
}), |
||||||
|
(None, path @ Some(_)) => Some(Self { |
||||||
|
name, |
||||||
|
alias, |
||||||
|
version: None, |
||||||
|
path, |
||||||
|
}), |
||||||
|
(None, None) => None, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if the dependency has a provided version
|
||||||
|
pub fn has_version(&self) -> bool { |
||||||
|
self.version.is_some() |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if the dependency has a provided path
|
||||||
|
pub fn has_path(&self) -> bool { |
||||||
|
self.path.is_some() |
||||||
|
} |
||||||
|
|
||||||
|
pub fn alias(&self) -> Option<String> { |
||||||
|
self.alias.clone() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
use std::{fmt, io}; |
||||||
|
use toml_edit::TomlError; |
||||||
|
|
||||||
|
/// Errors occured while interacting with Cargo.toml files
|
||||||
|
#[derive(Debug)] |
||||||
|
pub enum CargoError { |
||||||
|
/// Failed to read or write a file
|
||||||
|
Io, |
||||||
|
/// Error parsing Cargo.toml file
|
||||||
|
Parsing, |
||||||
|
/// Provided Cargo.toml was no workspace
|
||||||
|
NoWorkspace, |
||||||
|
/// Provided Cargo.toml had no dependencies
|
||||||
|
NoDependencies, |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Display for CargoError { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
write!( |
||||||
|
f, |
||||||
|
"{}", |
||||||
|
match self { |
||||||
|
Self::Io => "General I/O error", |
||||||
|
Self::Parsing => "Parsing error", |
||||||
|
Self::NoWorkspace => "Selected crate root is not a workspace!", |
||||||
|
Self::NoDependencies => "No dependencies found!", |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<io::Error> for CargoError { |
||||||
|
fn from(_: io::Error) -> Self { |
||||||
|
Self::Io |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<TomlError> for CargoError { |
||||||
|
fn from(_: TomlError) -> Self { |
||||||
|
Self::Parsing |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
//! Generate toml data
|
||||||
|
|
||||||
|
use super::{CargoError, Result}; |
||||||
|
use std::{fs::File, io::Write, path::PathBuf}; |
||||||
|
use toml_edit::{value, Document, Item, Value}; |
||||||
|
|
||||||
|
/// Sync a document back into it's Cargo.toml
|
||||||
|
pub(crate) fn sync(doc: &mut Document, path: PathBuf) -> Result<()> { |
||||||
|
if !path.exists() { |
||||||
|
return Err(CargoError::Io); |
||||||
|
} |
||||||
|
|
||||||
|
let mut f = File::create(path)?; |
||||||
|
f.write_all(doc.to_string().as_bytes())?; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Takes a mutable document, dependency alias or name, and version
|
||||||
|
pub(crate) fn update_dependency(doc: &mut Document, dep: &String, ver: &String) { |
||||||
|
match doc.as_table_mut().entry("dependencies") { |
||||||
|
Item::Table(ref mut t) => match t.entry(dep.as_str()) { |
||||||
|
Item::Value(Value::InlineTable(ref mut t)) => { |
||||||
|
if let Some(v) = t.get_mut("version") { |
||||||
|
*v = Value::from(ver.clone()); |
||||||
|
} |
||||||
|
return; |
||||||
|
} |
||||||
|
_ => {} |
||||||
|
}, |
||||||
|
_ => {} |
||||||
|
} |
||||||
|
|
||||||
|
// eprintln!("Invalid dependency format!");
|
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
//! A set of models directly related to `Cargo.toml` files
|
||||||
|
|
||||||
|
mod deps; |
||||||
|
pub use deps::Dependency; |
||||||
|
|
||||||
|
mod error; |
||||||
|
pub use error::CargoError; |
||||||
|
|
||||||
|
mod parser; |
||||||
|
pub(crate) use parser::{get_members, parse_dependencies, parse_root_toml, parse_toml}; |
||||||
|
|
||||||
|
mod gen; |
||||||
|
pub(crate) use gen::{sync, update_dependency}; |
||||||
|
|
||||||
|
pub(crate) type Result<T> = std::result::Result<T, CargoError>; |
||||||
|
|
||||||
|
use toml_edit::Value; |
||||||
|
|
||||||
|
/// Turn a toml Value into a String, panic if it fails
|
||||||
|
pub(self) fn v_to_s(v: &Value) -> String { |
||||||
|
match v { |
||||||
|
Value::String(s) => s.to_string(), |
||||||
|
_ => unreachable!(), |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
use crate::cargo::{CargoError, Dependency, Result}; |
||||||
|
use std::{fs::File, io::Read, path::PathBuf}; |
||||||
|
use toml_edit::{Document, Item, Value}; |
||||||
|
|
||||||
|
/// Parse the root entry of a cargo workspace into a document
|
||||||
|
pub(crate) fn parse_root_toml(p: PathBuf) -> Result<Document> { |
||||||
|
let mut buf = String::new(); |
||||||
|
File::open(p)?.read_to_string(&mut buf)?; |
||||||
|
Ok(buf.parse::<Document>()?) |
||||||
|
} |
||||||
|
|
||||||
|
/// For a root-config, get the paths of member crates
|
||||||
|
pub(crate) fn get_members(doc: &Document) -> Result<Vec<String>> { |
||||||
|
match &doc["workspace"]["members"] { |
||||||
|
Item::Value(Value::Array(arr)) => Ok(arr |
||||||
|
.iter() |
||||||
|
.filter_map(|val| match val { |
||||||
|
Value::String(name) => Some(format!("{}", name)), |
||||||
|
val => { |
||||||
|
eprintln!("Ignoring value `{:?}` in List of strings", val); |
||||||
|
None |
||||||
|
} |
||||||
|
}) |
||||||
|
.map(|s| s.replace("\"", "").trim().into()) |
||||||
|
.map(|s: String| { |
||||||
|
if s.trim().starts_with("#") { |
||||||
|
s.split("\n").map(ToString::to_string).collect() |
||||||
|
} else { |
||||||
|
vec![s] |
||||||
|
} |
||||||
|
}) |
||||||
|
// Holy shit this crate is useless! Why would it not just
|
||||||
|
// strip the commented lines, instead of doing some
|
||||||
|
// bullshit like this...
|
||||||
|
.fold(vec![], |mut vec, ovec| { |
||||||
|
ovec.into_iter().for_each(|i| { |
||||||
|
if !i.trim().starts_with("#") && i != "" { |
||||||
|
vec.push(i.trim().to_string()); |
||||||
|
} |
||||||
|
}); |
||||||
|
vec |
||||||
|
})), |
||||||
|
_ => Err(CargoError::NoWorkspace), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parse a member crate Cargo.toml file
|
||||||
|
pub(crate) fn parse_toml(p: PathBuf) -> Result<Document> { |
||||||
|
let mut buf = String::new(); |
||||||
|
File::open(p)?.read_to_string(&mut buf)?; |
||||||
|
Ok(buf.parse::<Document>()?) |
||||||
|
} |
||||||
|
|
||||||
|
/// Parse a member crate set of dependencies
|
||||||
|
///
|
||||||
|
/// When a crate is not a table, it can't be a workspace member
|
||||||
|
/// dependency, so is skipped.
|
||||||
|
pub(crate) fn parse_dependencies(doc: &Document) -> Vec<Dependency> { |
||||||
|
match &doc["dependencies"] { |
||||||
|
Item::Table(ref t) => t |
||||||
|
.iter() |
||||||
|
.filter_map(|(n, v)| match v { |
||||||
|
Item::Value(Value::InlineTable(ref t)) => Dependency::parse(n.to_string(), t), |
||||||
|
_ => None, |
||||||
|
}) |
||||||
|
.collect(), |
||||||
|
_ => vec![], |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
//! Helpers and utilities to parse the CLI input
|
||||||
|
|
||||||
|
use std::env; |
||||||
|
|
||||||
|
pub struct CmdSet { |
||||||
|
pub debug: bool, |
||||||
|
pub line: Vec<String>, |
||||||
|
} |
||||||
|
|
||||||
|
fn get_nth(idx: usize) -> Option<String> { |
||||||
|
env::args().nth(idx).as_ref().map(|s| s.to_owned()).clone() |
||||||
|
} |
||||||
|
|
||||||
|
/// Call this instead of env::args() - it handles !commands too
|
||||||
|
pub fn parse_env_args() -> CmdSet { |
||||||
|
let mut line: Vec<_> = env::args().collect(); |
||||||
|
let mut debug = false; |
||||||
|
|
||||||
|
if line.len() == 1 { |
||||||
|
render_help(2); |
||||||
|
} |
||||||
|
|
||||||
|
find_bang_commands().into_iter().for_each(|(idx, bang)| { |
||||||
|
let maybe_next = line.iter().nth(idx + 1).as_ref().map(|s| s.to_owned()); |
||||||
|
|
||||||
|
match bang.trim() { |
||||||
|
"!help" => match maybe_next { |
||||||
|
None => render_help(0), |
||||||
|
Some(cmd) => crate::ops::render_help(cmd.to_string()), |
||||||
|
}, |
||||||
|
"!version" => render_version(), |
||||||
|
"!debug" => { |
||||||
|
debug = true; |
||||||
|
line.remove(idx); |
||||||
|
} |
||||||
|
bang => { |
||||||
|
if debug { |
||||||
|
eprintln!("Unrecognised bang command: {}", bang); |
||||||
|
} |
||||||
|
|
||||||
|
line.remove(idx); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
CmdSet { line, debug } |
||||||
|
} |
||||||
|
|
||||||
|
/// Get env::args() and look for any string with a `!` in front of it.
|
||||||
|
/// If it's not in the set of known bang commands, ignore it.
|
||||||
|
fn find_bang_commands() -> Vec<(usize, String)> { |
||||||
|
env::args() |
||||||
|
.enumerate() |
||||||
|
.filter_map(|(idx, s)| { |
||||||
|
if s.starts_with("!") { |
||||||
|
Some((idx, s)) |
||||||
|
} else { |
||||||
|
None |
||||||
|
} |
||||||
|
}) |
||||||
|
.collect() |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn render_help(code: i32) -> ! { |
||||||
|
eprintln!("cargo-ws v{}", env!("CARGO_PKG_VERSION")); |
||||||
|
eprintln!("An expression language and command executor for cargo workspaces."); |
||||||
|
eprintln!("Usage: cargo ws2 <QUERY LANG> <COMMAND> [COMMAND ARGS] [!debug]"); |
||||||
|
eprintln!(" cargo ws2 [!version | !debug]"); |
||||||
|
eprintln!(" cargo ws2 !help [COMMAND]"); |
||||||
|
eprintln!(""); |
||||||
|
|
||||||
|
crate::ops::list_commands(); |
||||||
|
eprintln!(""); |
||||||
|
|
||||||
|
eprintln!("Query language examples:\n"); |
||||||
|
eprintln!(" - [ foo bar ]: select crates foo and bar"); |
||||||
|
eprintln!(" - {{ foo < }}: select crates that depend on foo"); |
||||||
|
eprintln!(" - {{ foo < bar &< }}: select crates that depend on foo AND bar"); |
||||||
|
eprintln!(" - {{ foo < bar |< }}: select crates that depend on foo OR bar"); |
||||||
|
eprintln!("\nIf you have any questions, or find bugs, please e-mail me: kookie@spacekookie.de"); |
||||||
|
std::process::exit(code) |
||||||
|
} |
||||||
|
|
||||||
|
fn render_version() -> ! { |
||||||
|
eprintln!("cargo-ws v{}", env!("CARGO_PKG_VERSION")); |
||||||
|
eprintln!( |
||||||
|
"Build with: {}", |
||||||
|
include_str!(concat!(env!("OUT_DIR"), "/rustc.version")) |
||||||
|
); |
||||||
|
std::process::exit(0) |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
//! cargo-workspace2 is a library to help manage cargo workspaces
|
||||||
|
//!
|
||||||
|
//! Out of the box the `cargo` workspace experience leaves a lot to be
|
||||||
|
//! desired. Managing a repo with many crates in it can get out of
|
||||||
|
//! hand quickly. Moreover, other tools that try to solve these
|
||||||
|
//! issues often pick _one_ particular usecase of cargo workspaces,
|
||||||
|
//! and enforce very strict rules on how to use them.
|
||||||
|
//!
|
||||||
|
//! This library aims to solve some of the issues of dealing with
|
||||||
|
//! workspaces in a way that doesn't enforce a usage mode for the
|
||||||
|
//! user.
|
||||||
|
//!
|
||||||
|
//! This package also publishes a binary (cargo ws2), which is
|
||||||
|
//! recommended for most users. In case the binary handles a use-case
|
||||||
|
//! you have in a way that you don't like, this library aims to
|
||||||
|
//! provide a fallback so that you don't have to re-implement
|
||||||
|
//! everything from scratch.
|
||||||
|
//!
|
||||||
|
//! ## Using this library
|
||||||
|
//!
|
||||||
|
//! Parsing happens in stages. First you need to use the
|
||||||
|
//! [`cargo`](./cargo/index.html) module to parse the actual
|
||||||
|
//! `Cargo.toml` files. After that you can use the cargo models in
|
||||||
|
//! [`models`](models/index.html) to further process dependencies, and
|
||||||
|
//! create a [`DepGraph`](models/struct.DepGraph.html) to resolve queries and make changes.
|
||||||
|
|
||||||
|
pub mod cargo; |
||||||
|
pub mod models; |
||||||
|
pub mod ops; |
||||||
|
pub mod query; |
||||||
|
|
||||||
|
#[doc(hidden)] |
||||||
|
pub mod cli; |
||||||
|
|
||||||
|
// extern crate toml;
|
||||||
|
// extern crate toml_edit;
|
||||||
|
|
||||||
|
// pub use data_models::graph;
|
||||||
|
// use data_models::level::Level;
|
||||||
|
// use graph::DepGraph;
|
||||||
|
// use std::fs::File;
|
||||||
|
// pub use utilities::cargo_utils;
|
||||||
|
// pub use utilities::utils;
|
||||||
|
|
||||||
|
// pub mod data_models;
|
||||||
|
// pub mod utilities;
|
||||||
|
|
||||||
|
// pub fn do_batch_release(f: File, lvl: &Level) -> DepGraph {
|
||||||
|
// let members = cargo_utils::get_members(f);
|
||||||
|
// let configs = cargo_utils::batch_load_configs(&members);
|
||||||
|
|
||||||
|
// let v = configs
|
||||||
|
// .iter()
|
||||||
|
// .map(|c| cargo_utils::parse_config(c, &members))
|
||||||
|
// .fold(DepGraph::new(), |mut graph, (name, deps)| {
|
||||||
|
// graph.add_node(name.clone());
|
||||||
|
|
||||||
|
// deps.iter()
|
||||||
|
// .fold(graph, |graph, dep| graph.add_dependency(&name, dep))
|
||||||
|
// });
|
||||||
|
|
||||||
|
// println!("{:#?}", v);
|
||||||
|
// v
|
||||||
|
// }
|
@ -0,0 +1,112 @@ |
|||||||
|
use crate::models::{CargoCrate, CrateId, DepGraph}; |
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering}; |
||||||
|
use std::{ |
||||||
|
cmp::{self, Eq, Ord, PartialEq, PartialOrd}, |
||||||
|
collections::BTreeSet, |
||||||
|
path::PathBuf, |
||||||
|
}; |
||||||
|
|
||||||
|
static ID_CTR: AtomicUsize = AtomicUsize::new(0); |
||||||
|
|
||||||
|
/// A crate in a cargo workspace
|
||||||
|
///
|
||||||
|
/// Has a name, path (stored as the offset of the root), and set of
|
||||||
|
/// dependencies inside the workspace. To get the dependents of this
|
||||||
|
/// crate, query the dependency graph with the set of other crate IDs.
|
||||||
|
#[derive(Clone, Debug)] |
||||||
|
pub struct Crate { |
||||||
|
/// Numeric Id of this crate
|
||||||
|
pub id: CrateId, |
||||||
|
/// Package name, not the folder name
|
||||||
|
pub name: String, |
||||||
|
/// Path offset of the workspace root
|
||||||
|
pub cc: CargoCrate, |
||||||
|
/// List of dependencies this crate has inside this workspace
|
||||||
|
pub dependencies: BTreeSet<CrateId>, |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialEq for Crate { |
||||||
|
fn eq(&self, other: &Self) -> bool { |
||||||
|
self.id == other.id |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Eq for Crate {} |
||||||
|
|
||||||
|
impl Ord for Crate { |
||||||
|
fn cmp(&self, other: &Self) -> cmp::Ordering { |
||||||
|
self.id.cmp(&other.id) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl PartialOrd for Crate { |
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { |
||||||
|
Some(self.cmp(other)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Increment the monotonicly increasing Id
|
||||||
|
fn incr_id() -> usize { |
||||||
|
ID_CTR.fetch_add(1, Ordering::Relaxed) |
||||||
|
} |
||||||
|
|
||||||
|
impl Crate { |
||||||
|
pub fn new(cc: CargoCrate) -> Self { |
||||||
|
Self { |
||||||
|
id: incr_id(), |
||||||
|
name: cc.name(), |
||||||
|
cc, |
||||||
|
dependencies: BTreeSet::default(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Call this function once all crates have been loaded into scope
|
||||||
|
pub fn process(&mut self, g: &DepGraph) { |
||||||
|
let deps: Vec<_> = self |
||||||
|
.cc |
||||||
|
.dependencies |
||||||
|
.iter() |
||||||
|
.filter_map(|d| g.find_crate(&d.name)) |
||||||
|
.collect(); |
||||||
|
|
||||||
|
deps.into_iter().for_each(|cid| self.add_dependency(cid)); |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the crate name
|
||||||
|
pub fn name(&self) -> &String { |
||||||
|
&self.name |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the crate path
|
||||||
|
pub fn path(&self) -> &PathBuf { |
||||||
|
&self.cc.path |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the current version
|
||||||
|
pub fn version(&self) -> String { |
||||||
|
self.cc.version() |
||||||
|
} |
||||||
|
|
||||||
|
/// Add a dependency of this crate
|
||||||
|
pub fn add_dependency(&mut self, id: CrateId) { |
||||||
|
self.dependencies.insert(id); |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if this crate has a particular dependency
|
||||||
|
pub fn has_dependency(&self, id: CrateId) -> bool { |
||||||
|
self.dependencies.contains(&id) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn change_dependency(&mut self, dep: &String, new_ver: &String) { |
||||||
|
self.cc.change_dep(dep, new_ver); |
||||||
|
} |
||||||
|
|
||||||
|
/// Publish a new version of this crate
|
||||||
|
pub fn publish(&mut self, new_version: String) { |
||||||
|
self.cc.set_version(new_version); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn sync(&mut self) { |
||||||
|
self.cc.sync(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
use crate::cargo::{self, Dependency, Result}; |
||||||
|
use std::{fmt, path::PathBuf}; |
||||||
|
use toml_edit::{value, Document, Item, Value}; |
||||||
|
|
||||||
|
/// Initial representation of a crate, before being parsed
|
||||||
|
#[derive(Clone)] |
||||||
|
pub struct CargoCrate { |
||||||
|
pub doc: Document, |
||||||
|
pub path: PathBuf, |
||||||
|
pub dependencies: Vec<Dependency>, |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Debug for CargoCrate { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
write!(f, "{}", self.path.as_path().display()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl CargoCrate { |
||||||
|
/// Get the crate name from the inner document
|
||||||
|
pub fn name(&self) -> String { |
||||||
|
match &self.doc["package"]["name"] { |
||||||
|
Item::Value(Value::String(ref name)) => { |
||||||
|
name.to_string().replace("\"", "").as_str().trim().into() |
||||||
|
} |
||||||
|
_ => panic!(format!("Invalid Cargo.toml: {:?}", self.path)), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the current version
|
||||||
|
pub fn version(&self) -> String { |
||||||
|
match &self.doc["package"]["version"] { |
||||||
|
Item::Value(Value::String(ref name)) => { |
||||||
|
name.to_string().replace("\"", "").as_str().trim().into() |
||||||
|
} |
||||||
|
_ => panic!(format!("Invalid Cargo.toml: {:?}", self.path)), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Find a cargo dependency by name
|
||||||
|
pub fn dep_by_name(&self, name: &String) -> &Dependency { |
||||||
|
self.dependencies |
||||||
|
.iter() |
||||||
|
.find(|c| &c.name == name) |
||||||
|
.as_ref() |
||||||
|
.unwrap() |
||||||
|
} |
||||||
|
|
||||||
|
pub fn change_dep(&mut self, dep: &String, ver: &String) { |
||||||
|
let dep = self |
||||||
|
.dep_by_name(dep) |
||||||
|
.alias() |
||||||
|
.unwrap_or(dep.to_string()) |
||||||
|
.clone(); |
||||||
|
cargo::update_dependency(&mut self.doc, &dep, ver); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn all_deps_mut(&mut self) -> Vec<&mut Dependency> { |
||||||
|
self.dependencies.iter_mut().collect() |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if this crate depends on a specific version of another
|
||||||
|
pub fn has_version(&self, name: &String) -> bool { |
||||||
|
self.dep_by_name(name).has_version() |
||||||
|
} |
||||||
|
|
||||||
|
/// Check if this crate depends on a specific path of another
|
||||||
|
pub fn has_path(&self, name: &String) -> bool { |
||||||
|
self.dep_by_name(name).has_version() |
||||||
|
} |
||||||
|
|
||||||
|
/// Set a new version for this crate
|
||||||
|
pub fn set_version(&mut self, version: String) { |
||||||
|
self.doc["package"]["version"] = value(version); |
||||||
|
} |
||||||
|
|
||||||
|
/// Sync any changes made to the document to disk
|
||||||
|
pub fn sync(&mut self) { |
||||||
|
cargo::sync(&mut self.doc, self.path.join("Cargo.toml")).unwrap(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Initial representation of the workspate, before getting parsed
|
||||||
|
pub struct CargoWorkspace { |
||||||
|
pub root: PathBuf, |
||||||
|
pub crates: Vec<CargoCrate>, |
||||||
|
} |
||||||
|
|
||||||
|
impl CargoWorkspace { |
||||||
|
/// Open a workspace and parse dependency graph
|
||||||
|
///
|
||||||
|
/// Point this to the root of the workspace, do the root
|
||||||
|
/// `Cargo.toml` file.
|
||||||
|
pub fn open(p: impl Into<PathBuf>) -> Result<Self> { |
||||||
|
let path = p.into(); |
||||||
|
|
||||||
|
let root_cfg = cargo::parse_root_toml(path.join("Cargo.toml"))?; |
||||||
|
let members = cargo::get_members(&root_cfg)?; |
||||||
|
|
||||||
|
let m_cfg: Vec<_> = members |
||||||
|
.into_iter() |
||||||
|
.filter_map( |
||||||
|
|name| match cargo::parse_toml(path.join(&name).join("Cargo.toml")) { |
||||||
|
Ok(doc) => Some(( |
||||||
|
PathBuf::new().join(name), |
||||||
|
cargo::parse_dependencies(&doc), |
||||||
|
doc, |
||||||
|
)), |
||||||
|
Err(e) => { |
||||||
|
eprintln!( |
||||||
|
"Error occured while parsing member `{}`/`Cargo.toml`: {:?}", |
||||||
|
name, e |
||||||
|
); |
||||||
|
None |
||||||
|
} |
||||||
|
}, |
||||||
|
) |
||||||
|
.collect(); |
||||||
|
|
||||||
|
Ok(Self { |
||||||
|
root: path, |
||||||
|
crates: m_cfg |
||||||
|
.into_iter() |
||||||
|
.map(|(path, dependencies, doc)| CargoCrate { |
||||||
|
path, |
||||||
|
dependencies, |
||||||
|
doc, |
||||||
|
}) |
||||||
|
.collect(), |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
use crate::models::{CargoCrate, Crate, CrateId}; |
||||||
|
use std::collections::{BTreeMap, BTreeSet}; |
||||||
|
use std::path::PathBuf; |
||||||
|
|
||||||
|
/// Dependency graph in a workspace
|
||||||
|
pub struct DepGraph { |
||||||
|
/// Mapping of crates in the workspace
|
||||||
|
members: BTreeMap<CrateId, Crate>, |
||||||
|
/// Map of crates and the members that depend on them
|
||||||
|
dependents: BTreeMap<CrateId, BTreeSet<CrateId>>, |
||||||
|
} |
||||||
|
|
||||||
|
impl DepGraph { |
||||||
|
/// Create a new, empty dependency graph
|
||||||
|
pub fn new() -> Self { |
||||||
|
Self { |
||||||
|
members: Default::default(), |
||||||
|
dependents: Default::default(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn add_crate(&mut self, cc: CargoCrate) { |
||||||
|
let cc = Crate::new(cc); |
||||||
|
self.members.insert(cc.id, cc); |
||||||
|
} |
||||||
|
|
||||||
|
/// Cache the dependents graph for all crates
|
||||||
|
pub fn finalise(&mut self) { |
||||||
|
let mut members = self.members.clone(); |
||||||
|
members.iter_mut().for_each(|(_, c)| c.process(&self)); |
||||||
|
|
||||||
|
members.iter().for_each(|(id, _crate)| { |
||||||
|
_crate.dependencies.iter().for_each(|dep_id| { |
||||||
|
self.dependents.entry(*dep_id).or_default().insert(*id); |
||||||
|
}); |
||||||
|
}); |
||||||
|
let _ = std::mem::replace(&mut self.members, members); |
||||||
|
} |
||||||
|
|
||||||
|
/// Get a crate by ID
|
||||||
|
pub fn get_crate(&self, id: CrateId) -> &Crate { |
||||||
|
self.members.get(&id).as_ref().unwrap() |
||||||
|
} |
||||||
|
|
||||||
|
/// Get mutable access to a crate by ID
|
||||||
|
pub fn mut_crate(&mut self, id: CrateId) -> &mut Crate { |
||||||
|
self.members.get_mut(&id).unwrap() |
||||||
|
} |
||||||
|
|
||||||
|
/// Find a crate via it's name
|
||||||
|
pub fn find_crate(&self, name: &String) -> Option<CrateId> { |
||||||
|
self.members |
||||||
|
.iter() |
||||||
|
.find(|(_, c)| c.name() == name) |
||||||
|
.map(|(id, _)| *id) |
||||||
|
} |
||||||
|
|
||||||
|
/// Find a crate by it's path-offset in the workspace
|
||||||
|
pub fn find_crate_by_path(&self, name: &String) -> Option<CrateId> { |
||||||
|
self.members |
||||||
|
.iter() |
||||||
|
.find(|(_, c)| c.path() == &PathBuf::new().join(name)) |
||||||
|
.map(|(id, _)| *id) |
||||||
|
} |
||||||
|
|
||||||
|
/// Get a crate's dependents
|
||||||
|
pub fn get_dependents(&self, id: CrateId) -> Vec<CrateId> { |
||||||
|
self.dependents |
||||||
|
.get(&id) |
||||||
|
.as_ref() |
||||||
|
.map(|set| set.iter().cloned().collect()) |
||||||
|
.unwrap_or(vec![]) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn get_all(&self) -> Vec<&Crate> { |
||||||
|
self.members.iter().map(|(_, c)| c).collect() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
//! Collection of cargo workspace data models.
|
||||||
|
//!
|
||||||
|
//! To start parsing types, construct a `CargoWorkspace`, which you
|
||||||
|
//! can then modify with commands found in [`ops`](../ops/index.html).
|
||||||
|
|
||||||
|
mod cargo; |
||||||
|
pub use cargo::{CargoCrate, CargoWorkspace}; |
||||||
|
|
||||||
|
mod _crate; |
||||||
|
pub use _crate::Crate; |
||||||
|
|
||||||
|
mod publish; |
||||||
|
pub use publish::{MutationSet, PubMutation}; |
||||||
|
|
||||||
|
mod graph; |
||||||
|
pub use graph::DepGraph; |
||||||
|
|
||||||
|
pub type CrateId = usize; |
||||||
|
|
||||||
|
use crate::{ops::Op, query::Query}; |
||||||
|
use std::path::PathBuf; |
||||||
|
|
||||||
|
/// A fully parsed workspace
|
||||||
|
pub struct Workspace { |
||||||
|
pub root: PathBuf, |
||||||
|
dgraph: DepGraph, |
||||||
|
} |
||||||
|
|
||||||
|
impl Workspace { |
||||||
|
/// Create a parsed workspace by passing in the stage1 parse data
|
||||||
|
pub fn process(cws: CargoWorkspace) -> Self { |
||||||
|
let CargoWorkspace { root, crates } = cws; |
||||||
|
|
||||||
|
let mut dgraph = DepGraph::new(); |
||||||
|
crates.into_iter().for_each(|cc| dgraph.add_crate(cc)); |
||||||
|
dgraph.finalise(); |
||||||
|
|
||||||
|
Self { root, dgraph } |
||||||
|
} |
||||||
|
|
||||||
|
/// Execute a query on this workspace to find crate IDs
|
||||||
|
pub fn query(&self, q: Query) -> Vec<CrateId> { |
||||||
|
q.execute(&self.dgraph) |
||||||
|
} |
||||||
|
|
||||||
|
/// Execute an operation on a set of crates this in workspace
|
||||||
|
pub fn execute(&mut self, op: Op, target: Vec<CrateId>) { |
||||||
|
op.execute(target, self.root.clone(), &mut self.dgraph) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
use crate::models::{Crate, CrateId}; |
||||||
|
|
||||||
|
/// A publishing mutation executed on the graph
|
||||||
|
pub struct PubMutation { |
||||||
|
_crate: CrateId, |
||||||
|
new_version: String, |
||||||
|
} |
||||||
|
|
||||||
|
impl PubMutation { |
||||||
|
/// Createa new motation from a crate a version string
|
||||||
|
pub fn new(c: &Crate, new_version: String) -> Self { |
||||||
|
Self { |
||||||
|
_crate: c.id, |
||||||
|
new_version, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A collection of mutations performed in a batch
|
||||||
|
pub struct MutationSet {} |
@ -0,0 +1,28 @@ |
|||||||
|
use std::fmt::{self, Display, Formatter}; |
||||||
|
|
||||||
|
/// Special result type that wraps an OpError
|
||||||
|
pub type Result<T> = std::result::Result<T, OpError>; |
||||||
|
|
||||||
|
/// An error that occured while running an operation
|
||||||
|
#[derive(Debug)] |
||||||
|
pub enum OpError { |
||||||
|
NoSuchCrate(String), |
||||||
|
CircularDependency(String, String), |
||||||
|
} |
||||||
|
|
||||||
|
impl Display for OpError { |
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result { |
||||||
|
write!( |
||||||
|
f, |
||||||
|
"{}", |
||||||
|
match self { |
||||||
|
Self::NoSuchCrate(n) => format!("No crate `{}` was not found in the workspace", n), |
||||||
|
Self::CircularDependency(a, b) => format!( |
||||||
|
"Crates `{}` and `{}` share a hard circular dependency.\ |
||||||
|
Operation not possible!", |
||||||
|
a, b |
||||||
|
), |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
//! An op executor
|
||||||
|
|
||||||
|
use super::{versions, Print, Publish, PublishMod as Mod, PublishType}; |
||||||
|
use crate::models::{CrateId, DepGraph}; |
||||||
|
use semver::Version; |
||||||
|
use std::path::PathBuf; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn abs_path<'a>(root: &PathBuf, c_path: PathBuf, abs: bool) -> String { |
||||||
|
if abs { |
||||||
|
root.join(c_path).display().to_string() |
||||||
|
} else { |
||||||
|
c_path.display().to_string() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A simple print executor
|
||||||
|
pub(super) fn print(p: Print, set: Vec<CrateId>, root: PathBuf, g: &mut DepGraph) { |
||||||
|
set.into_iter() |
||||||
|
.map(|id| g.get_crate(id)) |
||||||
|
.map(|_crate| { |
||||||
|
let c_path = _crate.path().clone(); |
||||||
|
|
||||||
|
match p { |
||||||
|
Print::Name => format!("{}", _crate.name()), |
||||||
|
Print::Path { abs } => format!("{}", abs_path(&root, c_path, abs)), |
||||||
|
Print::Both { abs } => { |
||||||
|
format!("{}: {}", _crate.name(), abs_path(&root, c_path, abs)) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
.for_each(|s| println!("{}", s)); |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
//! Atomic operations on a cargo workspace
|
||||||
|
//!
|
||||||
|
//! This module contains operations that can be executed on a
|
||||||
|
//! workspace. They take some set of inputs, modelled as fields, and
|
||||||
|
//! produce a shared output which is represented by `Result`
|
||||||
|
|
||||||
|
mod publish; |
||||||
|
pub(self) use publish::{versions, Publish, PublishMod, PublishType}; |
||||||
|
|
||||||
|
mod error; |
||||||
|
mod executor; |
||||||
|
mod parser; |
||||||
|
|
||||||
|
use crate::models::{CrateId, DepGraph}; |
||||||
|
pub use error::{OpError, Result}; |
||||||
|
use std::path::PathBuf; |
||||||
|
|
||||||
|
trait RenderHelp { |
||||||
|
fn render_help(c: i32) -> !; |
||||||
|
} |
||||||
|
|
||||||
|
/// Render the help-page for a particular command
|
||||||
|
pub(crate) fn render_help(cmd: String) -> ! { |
||||||
|
match cmd.as_str() { |
||||||
|
"print" => Print::render_help(0), |
||||||
|
"publish" => Publish::render_help(0), |
||||||
|
c => { |
||||||
|
eprintln!("Unknown command `{}`", c); |
||||||
|
std::process::exit(2); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn list_commands() { |
||||||
|
eprintln!("Available commands:\n"); |
||||||
|
eprintln!(" - print: echo the selected crate set"); |
||||||
|
eprintln!(" - publish: release new crates on crates.io"); |
||||||
|
} |
||||||
|
|
||||||
|
/// Differentiating operation enum
|
||||||
|
pub enum Op { |
||||||
|
/// Publish a new version, according to some rules
|
||||||
|
Publish(Publish), |
||||||
|
/// Print the query selection
|
||||||
|
Print(Print), |
||||||
|
} |
||||||
|
|
||||||
|
impl Op { |
||||||
|
/// Parse an arg line into an operation to execute
|
||||||
|
pub fn parse(line: Vec<String>) -> Self { |
||||||
|
match parser::run(line) { |
||||||
|
Some(op) => op, |
||||||
|
None => std::process::exit(2), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn execute(self, set: Vec<CrateId>, root: PathBuf, g: &mut DepGraph) { |
||||||
|
match self { |
||||||
|
Self::Publish(p) => publish::run(p, set, g), |
||||||
|
Self::Print(p) => executor::print(p, set, root, g), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Ask the user to be sure
|
||||||
|
pub(self) fn verify_user() { |
||||||
|
eprintln!("------"); |
||||||
|
use std::io::{stdin, stdout, Write}; |
||||||
|
let mut s = String::new(); |
||||||
|
print!("Execute operations? [N|y]: "); |
||||||
|
let _ = stdout().flush(); |
||||||
|
stdin().read_line(&mut s).expect("Failed to read term!"); |
||||||
|
|
||||||
|
match s.trim() { |
||||||
|
"Y" | "y" => {} |
||||||
|
_ => std::process::exit(0), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Selection of which type to print
|
||||||
|
pub enum Print { |
||||||
|
/// Default: just the package name
|
||||||
|
Name, |
||||||
|
/// The path inside the repo
|
||||||
|
Path { abs: bool }, |
||||||
|
/// Both the name and path
|
||||||
|
Both { abs: bool }, |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for Print { |
||||||
|
fn default() -> Self { |
||||||
|
Self::Name |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl RenderHelp for Print { |
||||||
|
fn render_help(code: i32) -> ! { |
||||||
|
eprintln!("Print the selected set of crates"); |
||||||
|
eprintln!("Usage: cargo ws2 <...> print [OPTIONS]\n"); |
||||||
|
eprintln!("Available options:\n"); |
||||||
|
eprintln!(" - path: print the path of the crate, instead of the name"); |
||||||
|
eprintln!(" - both: print the both the path and the name"); |
||||||
|
eprintln!(" - abs: (If `path` or `both`) Use an absolute path, instead of relative"); |
||||||
|
std::process::exit(code) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,112 @@ |
|||||||
|
use super::{Op, Print, Publish, PublishMod as Mod, PublishType::*}; |
||||||
|
use semver::Version; |
||||||
|
|
||||||
|
/// Parse a argument line into an operation
|
||||||
|
pub(super) fn run(mut line: Vec<String>) -> Option<Op> { |
||||||
|
let op = line.remove(0); |
||||||
|
|
||||||
|
match op.as_str() { |
||||||
|
"print" => parse_print(line), |
||||||
|
"publish" => parse_publish(line), |
||||||
|
op => { |
||||||
|
eprintln!("[ERROR]: Unrecognised operation `{}`", op); |
||||||
|
None |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_print(line: Vec<String>) -> Option<Op> { |
||||||
|
let abs = line |
||||||
|
.get(1) |
||||||
|
.map(|abs| match abs.as_str() { |
||||||
|
"abs" | "absolute" => true, |
||||||
|
_ => false, |
||||||
|
}) |
||||||
|
.unwrap_or(false); |
||||||
|
|
||||||
|
match line.get(0).map(|s| s.as_str()) { |
||||||
|
Some("path") => Some(Op::Print(Print::Path { abs })), |
||||||
|
Some("both") => Some(Op::Print(Print::Both { abs })), |
||||||
|
Some("name") | None => Some(Op::Print(Print::Name)), |
||||||
|
Some(val) => { |
||||||
|
eprintln!("[ERROR]: Unrecognised param: `{}`", val); |
||||||
|
None |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn publish_error() -> Option<Op> { |
||||||
|
eprintln!( |
||||||
|
"[ERROR]: Missing or invalid operands! |
||||||
|
Usage: cargo ws2 <QUERY LANG> publish <level> [modifier] [devel]\n |
||||||
|
Valid <level>: major, minor, patch, or '=<VERSION>' to set an override version.\n |
||||||
|
Valid [modifier]: alpha, beta, rc\n |
||||||
|
[devel]: after publishing, set crate version to <VERSION>-devel" |
||||||
|
); |
||||||
|
None |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_publish(mut line: Vec<String>) -> Option<Op> { |
||||||
|
line.reverse(); |
||||||
|
let tt = match line.pop() { |
||||||
|
Some(tt) => tt, |
||||||
|
None => { |
||||||
|
return publish_error(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let _mod = line.pop(); |
||||||
|
let tt = match (tt.as_str(), _mod.as_ref().map(|s| s.as_str())) { |
||||||
|
("major", Some("alpha")) => Major(Mod::Alpha), |
||||||
|
("major", Some("beta")) => Major(Mod::Beta), |
||||||
|
("major", Some("rc")) => Major(Mod::Rc), |
||||||
|
("minor", Some("alpha")) => Minor(Mod::Alpha), |
||||||
|
("minor", Some("beta")) => Minor(Mod::Beta), |
||||||
|
("minor", Some("rc")) => Minor(Mod::Rc), |
||||||
|
("patch", Some("alpha")) => Patch(Mod::Alpha), |
||||||
|
("patch", Some("beta")) => Patch(Mod::Beta), |
||||||
|
("patch", Some("rc")) => Patch(Mod::Rc), |
||||||
|
("major", Some(_)) | ("major", None) => Major(Mod::None), |
||||||
|
("minor", Some(_)) | ("minor", None) => Minor(Mod::None), |
||||||
|
("patch", Some(_)) | ("patch", None) => Patch(Mod::None), |
||||||
|
(v, _) if v.trim().starts_with("=") => { |
||||||
|
let vers = v.replace("=", "").to_string(); |
||||||
|
if vers.as_str() == "" { |
||||||
|
return publish_error(); |
||||||
|
} else { |
||||||
|
Fixed(vers) |
||||||
|
} |
||||||
|
} |
||||||
|
(_, _) => { |
||||||
|
return publish_error(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let devel = match tt { |
||||||
|
// Means _mod was not a mod
|
||||||
|
Major(Mod::None) | Minor(Mod::None) | Patch(Mod::None) => { |
||||||
|
if let Some(devel) = _mod { |
||||||
|
if devel == "devel" { |
||||||
|
true |
||||||
|
} else { |
||||||
|
return publish_error(); |
||||||
|
} |
||||||
|
} else { |
||||||
|
false |
||||||
|
} |
||||||
|
} |
||||||
|
_ => { |
||||||
|
if let Some(devel) = line.pop() { |
||||||
|
if devel == "devel" { |
||||||
|
true |
||||||
|
} else { |
||||||
|
false |
||||||
|
} |
||||||
|
} else { |
||||||
|
false |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Some(Op::Publish(Publish { tt, devel })) |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
//! Publishing executor
|
||||||
|
|
||||||
|
use super::{Publish, PublishMod as Mod, PublishType}; |
||||||
|
use crate::{ |
||||||
|
models::{CrateId, DepGraph}, |
||||||
|
ops::verify_user, |
||||||
|
}; |
||||||
|
use semver::Version; |
||||||
|
use textwrap; |
||||||
|
|
||||||
|
pub(in crate::ops) fn run(p: Publish, set: Vec<CrateId>, g: &mut DepGraph) { |
||||||
|
let Publish { ref tt, devel } = p; |
||||||
|
|
||||||
|
let vec = set.into_iter().fold(vec![], |mut vec, id| { |
||||||
|
let c = g.mut_crate(id); |
||||||
|
let c_name = c.name().clone(); |
||||||
|
let curr_ver = c.version(); |
||||||
|
let new_ver = bump(&curr_ver, tt); |
||||||
|
c.publish(new_ver.clone()); |
||||||
|
eprintln!("Bumping `{}`: `{}` ==> `{}`", c.name(), &curr_ver, new_ver); |
||||||
|
drop(c); |
||||||
|
|
||||||
|
g.get_dependents(id).into_iter().for_each(|id| { |
||||||
|
let dep = g.mut_crate(id); |
||||||
|
dep.change_dependency(&c_name, &new_ver); |
||||||
|
eprintln!("Changing dependency `{}`: {} = {{ version = `{}`, ... }} ==> {{ version = `{}`, ... }}", |
||||||
|
dep.name(), |
||||||
|
c_name, |
||||||
|
&curr_ver, |
||||||
|
new_ver); |
||||||
|
vec.push(id); |
||||||
|
}); |
||||||
|
|
||||||
|
vec.push(id); |
||||||
|
vec |
||||||
|
}); |
||||||
|
|
||||||
|
// If we make it past this point the user is sure
|
||||||
|
verify_user(); |
||||||
|
|
||||||
|
vec.into_iter().for_each(|(id)| { |
||||||
|
let c = g.mut_crate(id); |
||||||
|
c.sync(); |
||||||
|
}); |
||||||
|
|
||||||
|
eprintln!("{}", textwrap::fill("Publish complete. Check that everything is in order, \ |
||||||
|
then run `cargo publish` to upload to crates.io!", 80)); |
||||||
|
} |
||||||
|
|
||||||
|
/// Bump a version number according to some rules
|
||||||
|
fn bump(v: &String, tt: &PublishType) -> String { |
||||||
|
let mut ver = Version::parse(&v).unwrap(); |
||||||
|
let ver = match tt { |
||||||
|
PublishType::Fixed(ref ver) => Version::parse(ver).unwrap(), |
||||||
|
PublishType::Major(_) => { |
||||||
|
ver.increment_major(); |
||||||
|
ver |
||||||
|
} |
||||||
|
PublishType::Minor(_) => { |
||||||
|
ver.increment_minor(); |
||||||
|
ver |
||||||
|
} |
||||||
|
PublishType::Patch(_) => { |
||||||
|
ver.increment_patch(); |
||||||
|
ver |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// If a mod applies, handle it...
|
||||||
|
if let Some(_mod) = tt._mod() { |
||||||
|
if ver.is_prerelease() { |
||||||
|
let v = ver.clone().to_string(); |
||||||
|
let num = v.split(".").last().unwrap(); |
||||||
|
let num: usize = num.parse().unwrap(); |
||||||
|
ver.clone() |
||||||
|
.to_string() |
||||||
|
.replace(&format!("{}", num), &format!("{}", num + 1)) |
||||||
|
.to_string() |
||||||
|
} else { |
||||||
|
format!( |
||||||
|
"{}-{}", |
||||||
|
ver.to_string(), |
||||||
|
match _mod { |
||||||
|
Mod::Alpha => "alpha.0", |
||||||
|
Mod::Beta => "beta.0", |
||||||
|
Mod::Rc => "rc.0", |
||||||
|
_ => unreachable!(), |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
} else { |
||||||
|
ver.to_string() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
macro_rules! assert_bump { |
||||||
|
($input:expr, $level_mod:expr, $expect:expr) => { |
||||||
|
assert_eq!(bump(&$input.to_owned(), $level_mod), $expect.to_string()); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
use super::{PublishMod::*, PublishType::*}; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn bump_major() { |
||||||
|
assert_bump!("1.0.0", &Major(None), "2.0.0"); |
||||||
|
assert_bump!("1.5.4", &Major(None), "2.0.0"); |
||||||
|
assert_bump!("1.3.0", &Major(Alpha), "2.0.0-alpha.0"); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn bump_minor() { |
||||||
|
assert_bump!("1.1.0", &Minor(None), "1.2.0"); |
||||||
|
assert_bump!("1.5.4", &Minor(Beta), "1.6.0-beta.0"); |
||||||
|
assert_bump!("1.5.4-beta.0", &Minor(Beta), "1.6.0-beta.0"); |
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
//! Publishing operation handling
|
||||||
|
|
||||||
|
mod exec; |
||||||
|
pub(super) use exec::run; |
||||||
|
|
||||||
|
use super::RenderHelp; |
||||||
|
|
||||||
|
/// Publish a single crate to crates.io
|
||||||
|
///
|
||||||
|
/// This command does the following things
|
||||||
|
///
|
||||||
|
/// 0. Determine if the git tree has modifications, `cargo publish`
|
||||||
|
/// will refuse to work otherwise.
|
||||||
|
/// 1. Find the crate in question
|
||||||
|
/// 2. Bump the version number according to the user input
|
||||||
|
/// 3. Find all dependent crates in the workspace with a version bound
|
||||||
|
/// 4. Update the version bound to the new version
|
||||||
|
pub struct Publish { |
||||||
|
/// The version type to publish
|
||||||
|
pub tt: PublishType, |
||||||
|
/// Whether to set a new devel version after publish
|
||||||
|
pub devel: bool, |
||||||
|
} |
||||||
|
|
||||||
|
impl RenderHelp for Publish { |
||||||
|
fn render_help(code: i32) -> ! { |
||||||
|
eprintln!("Publish the selected set of crates"); |
||||||
|
eprintln!("Usage: cargo ws2 <...> publish [=]<level> [OPTIONS]"); |
||||||
|
eprintln!(""); |
||||||
|
eprintln!("When prepending `=` to the level, bump crates in sync\n"); |
||||||
|
eprintln!("Available levels:\n"); |
||||||
|
eprintln!(" - major: Bump major version (1.0.0 -> 2.0.0)"); |
||||||
|
eprintln!(" - minor: Bump minor version (0.5.0 -> 0.6.0)"); |
||||||
|
eprintln!(" - patch: Bump patch version (0.5.0 -> 0.5.1)"); |
||||||
|
eprintln!(""); |
||||||
|
eprintln!("Available options:\n"); |
||||||
|
eprintln!(" - alpha: Create a new alpha (append `-alpha.X`)"); |
||||||
|
eprintln!(" - beta: Create a new beta (append `-beta.X`)"); |
||||||
|
eprintln!(" - rc: Create a new rc (append `-rc.X`)"); |
||||||
|
eprintln!(" - devel: Tag next version as -devel"); |
||||||
|
std::process::exit(code) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// The level to which to update
|
||||||
|
///
|
||||||
|
/// New versions are based on the previous version, and are always
|
||||||
|
/// computed on the fly.
|
||||||
|
///
|
||||||
|
/// It's recommended you use the [`versions`](./versions/index.html)
|
||||||
|
/// builder functions.
|
||||||
|
pub enum PublishType { |
||||||
|
/// A fixed version to set the set to
|
||||||
|
Fixed(String), |
||||||
|
/// A major bump (e.g. 1.0 -> 2.0)
|
||||||
|
Major(PublishMod), |
||||||
|
/// A minor bump (e.g. 0.1 -> 0.2)
|
||||||
|
Minor(PublishMod), |
||||||
|
/// A patch bump (e.g. 1.5.0 -> 1.5.1)
|
||||||
|
Patch(PublishMod), |
||||||
|
} |
||||||
|
|
||||||
|
impl PublishType { |
||||||
|
pub(crate) fn _mod(&self) -> Option<&PublishMod> { |
||||||
|
match self { |
||||||
|
Self::Major(ref m) => Some(m), |
||||||
|
Self::Minor(ref m) => Some(m), |
||||||
|
Self::Patch(ref m) => Some(m), |
||||||
|
Self::Fixed(_) => None, |
||||||
|
} |
||||||
|
.and_then(|_mod| match _mod { |
||||||
|
PublishMod::None => None, |
||||||
|
other => Some(other), |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Version string modifier
|
||||||
|
///
|
||||||
|
/// It's recommended you use the [`versions`](./versions/index.html)
|
||||||
|
/// builder functions.
|
||||||
|
pub enum PublishMod { |
||||||
|
/// No version modification
|
||||||
|
None, |
||||||
|
/// Append `-alpha$X` where `$X` is a continuously increasing number
|
||||||
|
Alpha, |
||||||
|
/// Append `-beta$X` where `$X` is a continuously increasing number
|
||||||
|
Beta, |
||||||
|
/// Append `-rc$X` where `$X` is a continuously increasing number
|
||||||
|
Rc, |
||||||
|
} |
||||||
|
|
||||||
|
/// Version bump management
|
||||||
|
pub mod versions { |
||||||
|
use super::{PublishMod, PublishType}; |
||||||
|
|
||||||
|
/// Create a major publish
|
||||||
|
pub fn major(_mod: PublishMod) -> PublishType { |
||||||
|
PublishType::Major(_mod) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a minor publish
|
||||||
|
pub fn minor(_mod: PublishMod) -> PublishType { |
||||||
|
PublishType::Minor(_mod) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a patch publish
|
||||||
|
pub fn patch(_mod: PublishMod) -> PublishType { |
||||||
|
PublishType::Patch(_mod) |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a none modifier
|
||||||
|
pub fn mod_none() -> PublishMod { |
||||||
|
PublishMod::None |
||||||
|
} |
||||||
|
|
||||||
|
/// Create an alpha modifier
|
||||||
|
pub fn mod_alpha() -> PublishMod { |
||||||
|
PublishMod::Alpha |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a beta modifier
|
||||||
|
pub fn mod_beta() -> PublishMod { |
||||||
|
PublishMod::Beta |
||||||
|
} |
||||||
|
|
||||||
|
/// Create an rc modifier
|
||||||
|
pub fn mod_rc() -> PublishMod { |
||||||
|
PublishMod::Rc |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,83 @@ |
|||||||
|
use super::{Constraint, DepConstraint}; |
||||||
|
use crate::models::{CrateId, DepGraph}; |
||||||
|
use std::collections::BTreeSet; |
||||||
|
|
||||||
|
/// Execute a simple set query
|
||||||
|
pub(crate) fn set(crates: &Vec<String>, g: &DepGraph) -> Vec<CrateId> { |
||||||
|
crates |
||||||
|
.iter() |
||||||
|
.filter_map(|name| match g.find_crate(name) { |
||||||
|
None => { |
||||||
|
eprintln!("[ERROR]: Unable to find crate: `{}`", name); |
||||||
|
None |
||||||
|
} |
||||||
|
some => some, |
||||||
|
}) |
||||||
|
.collect() |
||||||
|
} |
||||||
|
|
||||||
|
/// Execute a search query on the dependency graph
|
||||||
|
pub(crate) fn deps(mut deps: Vec<DepConstraint>, g: &DepGraph) -> Vec<CrateId> { |
||||||
|
// Parse the anchor point (first crate)
|
||||||
|
let DepConstraint { |
||||||
|
ref _crate, |
||||||
|
ref constraint, |
||||||
|
} = deps.remove(0); |
||||||
|
let init_id = get_crate_error(_crate, g); |
||||||
|
|
||||||
|
// Get it's dependents
|
||||||
|
let mut dependents: BTreeSet<_> = match constraint { |
||||||
|
Constraint::Initial(true) => g.get_dependents(init_id).into_iter().collect(), |
||||||
|
Constraint::Initial(false) => g |
||||||
|
.get_all() |
||||||
|
.iter() |
||||||
|
.filter(|c| c.has_dependency(init_id)) |
||||||
|
.map(|c| c.id) |
||||||
|
.collect(), |
||||||
|
_ => { |
||||||
|
eprintln!("Invalid initial constraint! Only `<` and `!<` are allowed!"); |
||||||
|
std::process::exit(2); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// Then loop over all other constraints and subtract crates from
|
||||||
|
// the dependents set until all constraints are met.
|
||||||
|
deps.reverse(); |
||||||
|
while let Some(dc) = deps.pop() { |
||||||
|
let DepConstraint { |
||||||
|
ref _crate, |
||||||
|
ref constraint, |
||||||
|
} = dc; |
||||||
|
|
||||||
|
let id = get_crate_error(_crate, g); |
||||||
|
let ldeps = g.get_dependents(id); |
||||||
|
dependents = apply_constraint(dependents, ldeps, constraint); |
||||||
|
} |
||||||
|
|
||||||
|
dependents.into_iter().collect() |
||||||
|
} |
||||||
|
|
||||||
|
fn get_crate_error(_crate: &String, g: &DepGraph) -> CrateId { |
||||||
|
match g.find_crate(&_crate.trim().to_string()) { |
||||||
|
Some(id) => id, |
||||||
|
None => { |
||||||
|
eprintln!("[ERROR]: Crate `{}` not found in workspace!", _crate); |
||||||
|
std::process::exit(2); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn apply_constraint( |
||||||
|
init: BTreeSet<CrateId>, |
||||||
|
cmp: Vec<CrateId>, |
||||||
|
cnd: &Constraint, |
||||||
|
) -> BTreeSet<CrateId> { |
||||||
|
let cmp: BTreeSet<CrateId> = cmp.into_iter().collect(); |
||||||
|
let init = init.into_iter(); |
||||||
|
match cnd { |
||||||
|
Constraint::And(true) => init.filter(|id| cmp.contains(id)).collect(), |
||||||
|
Constraint::And(false) => init.filter(|id| !cmp.contains(id)).collect(), |
||||||
|
Constraint::Or => init.chain(cmp.into_iter()).collect(), |
||||||
|
_ => todo!(), |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,280 @@ |
|||||||
|
//! Parse the query language for the CLI and other tools
|
||||||
|
//!
|
||||||
|
//! The `cargo-ws2` query language (`ws2ql`) allows users to specify a
|
||||||
|
//! set of inputs, and an operation to execute on it.
|
||||||
|
//!
|
||||||
|
//! ## Basic rules
|
||||||
|
//!
|
||||||
|
//! * Inside `[]` are sets (meaning items de-dup) that don't require
|
||||||
|
//! reparations
|
||||||
|
//! * IF `[]` contains a `/` anywhere _but_ the beginning AND end,
|
||||||
|
//! query becomes a path glob
|
||||||
|
//! * IF `[]` contains `/` at start and end, query becomes a regex
|
||||||
|
//! * An operation is parsed in the order of the fields in it's struct
|
||||||
|
//! (so publish order is `type mod devel`, etc)
|
||||||
|
//! * Inside `{}` you can create dependency maps with arrows.
|
||||||
|
//! * `{ foo < }` represents all crates that depend on `foo`
|
||||||
|
//! * `{ foo < bar < }` represents all crates that depend on `foo` AND `bar`
|
||||||
|
//! * `{ foo < bar |< }` represents all crates that depend on `foo` but NOT on `bar`
|
||||||
|
//!
|
||||||
|
//! ... etc.
|
||||||
|
|
||||||
|
use crate::models::{CrateId, DepGraph}; |
||||||
|
|
||||||
|
mod executor; |
||||||
|
|
||||||
|
/// A query on the dependency graph
|
||||||
|
#[derive(Debug)] |
||||||
|
pub enum Query { |
||||||
|
/// Simple set of names `[ a b c ]`
|
||||||
|
Set(Vec<String>), |
||||||
|
/// Simple path glob query `./foo/*`
|
||||||
|
Path(String), |
||||||
|
/// Regular expression done on paths in tree `/foo\$/`
|
||||||
|
Regex(String), |
||||||
|
/// A dependency graph query (see documentation for rules)
|
||||||
|
DepGraph(Vec<DepConstraint>), |
||||||
|
} |
||||||
|
|
||||||
|
impl Query { |
||||||
|
/// Parse an argument iterator (provided by `std::env::args`)
|
||||||
|
pub fn parse<'a>(line: impl Iterator<Item = String>) -> (Self, Vec<String>) { |
||||||
|
let parser = QueryParser::new(line.skip(1).collect()); |
||||||
|
match parser.run() { |
||||||
|
Some((q, rest)) => (q, rest), |
||||||
|
None => { |
||||||
|
eprintln!("Failed to parse query!"); |
||||||
|
std::process::exit(2); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Execute a query with a dependency graph
|
||||||
|
pub fn execute(self, g: &DepGraph) -> Vec<CrateId> { |
||||||
|
match self { |
||||||
|
Self::Set(ref crates) => executor::set(crates, g), |
||||||
|
Self::DepGraph(deps) => executor::deps(deps, g), |
||||||
|
_ => todo!(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Express a dependency constraint
|
||||||
|
#[derive(Debug)] |
||||||
|
pub struct DepConstraint { |
||||||
|
pub _crate: String, |
||||||
|
pub constraint: Constraint, |
||||||
|
} |
||||||
|
|
||||||
|
/// All constraints can be negated
|
||||||
|
#[derive(Debug)] |
||||||
|
pub enum Constraint { |
||||||
|
Initial(bool), |
||||||
|
And(bool), |
||||||
|
Or, |
||||||
|
} |
||||||
|
|
||||||
|
struct QueryParser { |
||||||
|
line: Vec<String>, |
||||||
|
} |
||||||
|
|
||||||
|
impl QueryParser { |
||||||
|
fn new(line: Vec<String>) -> Self { |
||||||
|
Self { line } |
||||||
|
} |
||||||
|
|
||||||
|
/// Run the parser until it yields an error or finished query
|
||||||
|
fn run(mut self) -> Option<(Query, Vec<String>)> { |
||||||
|
let line: Vec<String> = |
||||||
|
std::mem::replace(&mut self.line, vec![]) |
||||||
|
.into_iter() |
||||||
|
.fold(vec![], |mut vec, line| { |
||||||
|
line.split(" ").for_each(|seg| { |
||||||
|
vec.push(seg.into()); |
||||||
|
}); |
||||||
|
vec |
||||||
|
}); |
||||||
|
|
||||||
|
// This is here to get around infinately sized enums
|
||||||
|
#[derive(Debug)] |
||||||
|
enum Brace { |
||||||
|
Block, |
||||||
|
Curly, |
||||||
|
} |
||||||
|
|
||||||
|
// Track the state of the query braces
|
||||||
|
#[derive(Debug)] |
||||||
|
enum BraceState { |
||||||
|
Missing, |
||||||
|
BlockOpen, |
||||||
|
CurlyOpen, |
||||||
|
Done(Brace), |
||||||
|
} |
||||||
|
use {Brace::*, BraceState::*}; |
||||||
|
let mut bs = Missing; |
||||||
|
let mut buf = vec![]; |
||||||
|
let mut cbuf = String::new(); // Store a single crate name as a buffer
|
||||||
|
let mut skip = 1; |
||||||
|
|
||||||
|
// Parse the crate set
|
||||||
|
for elem in &line { |
||||||
|
match (&bs, elem.as_str()) { |
||||||
|
// The segment starts with a [
|
||||||
|
(Missing, e) if e.starts_with("[") => { |
||||||
|
bs = BlockOpen; |
||||||
|
// Handle the case where we need to grab the crate from this segment
|
||||||
|
if let Some(_crate) = e.strip_prefix("[") { |
||||||
|
if _crate != "" { |
||||||
|
buf.push(_crate.to_string()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// The segment starts with a {
|
||||||
|
(Missing, e) if e.starts_with("{") => { |
||||||
|
bs = CurlyOpen; |
||||||
|
|
||||||
|
if let Some(_crate) = e.strip_prefix("{") { |
||||||
|
if _crate != "" { |
||||||
|
cbuf = _crate.into(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
(BlockOpen, e) if e.ends_with("]") => { |
||||||
|
if let Some(_crate) = e.strip_suffix("]") { |
||||||
|
if _crate != "" { |
||||||
|
buf.push(_crate.to_string()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bs = Done(Block); |
||||||
|
break; |
||||||
|
} |
||||||
|
(BlockOpen, _crate) => buf.push(_crate.to_string()), |
||||||
|
(CurlyOpen, e) if e.ends_with("}") && cbuf == "" => { |
||||||
|
bs = Done(Curly); |
||||||
|
break; |
||||||
|
} |
||||||
|
(CurlyOpen, e) if e.ends_with("}") && cbuf != "" => { |
||||||
|
eprintln!("[ERROR]: Out of place `}}`, expected operand!"); |
||||||
|
std::process::exit(2); |
||||||
|
} |
||||||
|
(CurlyOpen, op) if cbuf != "" => { |
||||||
|
buf.push(format!("{} $ {}", cbuf, op)); |
||||||
|
cbuf = "".into(); |
||||||
|
} |
||||||
|
(CurlyOpen, _crate) => { |
||||||
|
cbuf = _crate.into(); |
||||||
|
} |
||||||
|
(_, _) => {} |
||||||
|
} |
||||||
|
skip += 1; |
||||||
|
} |
||||||
|
|
||||||
|
let rest = line.into_iter().skip(skip).collect(); |
||||||
|
match bs { |
||||||
|
Done(Block) => Some((Query::Set(buf), rest)), |
||||||
|
Done(Curly) => { |
||||||
|
let mut init = true; |
||||||
|
|
||||||
|
let c: Vec<_> = buf |
||||||
|
.into_iter() |
||||||
|
.map(|val| { |
||||||
|
let mut s: Vec<_> = val.split("$").collect(); |
||||||
|
let _crate = s.remove(0).trim().to_string(); |
||||||
|
let c = s.remove(0).trim().to_string(); |
||||||
|
|
||||||
|
DepConstraint { |
||||||
|
_crate, |
||||||
|
constraint: match c.as_str() { |
||||||
|
"<" if init => { |
||||||
|
init = false; |
||||||
|
Constraint::Initial(true) |
||||||
|
} |
||||||
|
"!<" if init => { |
||||||
|
init = false; |
||||||
|
Constraint::Initial(false) |
||||||
|
} |
||||||
|
"&<" => Constraint::And(true), |
||||||
|
"!&<" => Constraint::And(false), |
||||||
|
"|<" => Constraint::Or, |
||||||
|
c => { |
||||||
|
eprintln!("[ERROR]: Invalid constraint: `{}`", c); |
||||||
|
std::process::exit(2); |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
}) |
||||||
|
.collect(); |
||||||
|
|
||||||
|
if c.len() < 1 { |
||||||
|
eprintln!("[ERROR]: Provided an empty graph set: {{ }}. At least one dependency required!"); |
||||||
|
std::process::exit(2); |
||||||
|
} |
||||||
|
|
||||||
|
Some((Query::DepGraph(c), rest)) |
||||||
|
} |
||||||
|
_ if rest.len() < 1 => crate::cli::render_help(2), |
||||||
|
_line => { |
||||||
|
eprintln!("[ERROR]: You reached some unimplemented code in cargo-ws2! \ |
||||||
|
This might be a bug, or it might be a missing feature. Contact me with your query, \ |
||||||
|
and we can see which one it is :)"); |
||||||
|
std::process::exit(2); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn block_parser_spaced() { |
||||||
|
let _ = QueryParser::new( |
||||||
|
vec!["", "[", "foo", "bar", "baz", "]", "publish", "minor"] |
||||||
|
.into_iter() |
||||||
|
.map(Into::into) |
||||||
|
.collect(), |
||||||
|
) |
||||||
|
.run(); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn block_parser_offset_front() { |
||||||
|
let _ = QueryParser::new( |
||||||
|
vec!["my-program", "[foo", "bar", "baz", "]", "publish", "minor"] |
||||||
|
.into_iter() |
||||||
|
.map(Into::into) |
||||||
|
.collect(), |
||||||
|
) |
||||||
|
.run(); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn block_parser_offset_back() { |
||||||
|
let _ = QueryParser::new( |
||||||
|
vec!["my-program", "[", "foo", "bar", "baz]", "publish", "minor"] |
||||||
|
.into_iter() |
||||||
|
.map(Into::into) |
||||||
|
.collect(), |
||||||
|
) |
||||||
|
.run(); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn block_parser_offset_both() { |
||||||
|
let _ = QueryParser::new( |
||||||
|
vec!["my-program", "[foo", "bar", "baz]", "publish", "minor"] |
||||||
|
.into_iter() |
||||||
|
.map(Into::into) |
||||||
|
.collect(), |
||||||
|
) |
||||||
|
.run(); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn curly_parser_simple() { |
||||||
|
let _ = QueryParser::new( |
||||||
|
vec!["my-program", "{ foo < bar &< }", "print"] |
||||||
|
.into_iter() |
||||||
|
.map(Into::into) |
||||||
|
.collect(), |
||||||
|
) |
||||||
|
.run(); |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
[workspace] |
||||||
|
members = [ |
||||||
|
"lockchain-core", |
||||||
|
"lockchain-files", |
||||||
|
"lockchain-crypto", |
||||||
|
|
||||||
|
"lockchain-server", |
||||||
|
"lockchain-client", |
||||||
|
|
||||||
|
"lockchain-http", |
||||||
|
] |
@ -0,0 +1,13 @@ |
|||||||
|
[package] |
||||||
|
name = "lockchain-client" |
||||||
|
description = "Client side component of the lockchain stack. Decrypts records and provides a Rust API to use them." |
||||||
|
version = "0.0.0" |
||||||
|
authors = ["Katharina Fey <kookie@spacekookie.de>"] |
||||||
|
|
||||||
|
documentation = "https://docs.rs/lockchain-client" |
||||||
|
homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-client" |
||||||
|
readme = "README.md" |
||||||
|
license = "MIT/X11 OR Apache-2.0" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
lockchain-core = { version = "0.9.0" } #, path = "../lockchain-core" } |
@ -0,0 +1,25 @@ |
|||||||
|
[package] |
||||||
|
name = "lockchain-core" |
||||||
|
description = "Provides common abstractions for the lockchain crate ecoystem" |
||||||
|
documentation = "https://docs.rs/lockchain-core" |
||||||
|
homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-core" |
||||||
|
readme = "README.md" |
||||||
|
license = "MIT/X11 OR Apache-2.0" |
||||||
|
version = "0.9.1-alpha.0" |
||||||
|
authors = ["Katharina Fey <kookie@spacekookie.de>"] |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
chrono = { version = "0.4", features = ["serde"] } |
||||||
|
serde_derive = "1.0" |
||||||
|
serde_json = "1.0" |
||||||
|
serde = "1.0" |
||||||
|
|
||||||
|
nix = "0.11" |
||||||
|
pam-auth = "0.5" |
||||||
|
|
||||||
|
base64 = "0.8" |
||||||
|
bcrypt = "0.2" |
||||||
|
rand = "0.4" |
||||||
|
blake2 = "0.7" |
||||||
|
|
||||||
|
keybob = "0.3" |
@ -0,0 +1,16 @@ |
|||||||
|
[package] |
||||||
|
name = "lockchain-crypto" |
||||||
|
description = "Small shim layer crate for cryptographic operations on lockchain data records" |
||||||
|
documentation = "https://docs.rs/lockchain-crypto" |
||||||
|
homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-crypto" |
||||||
|
readme = "README.md" |
||||||
|
license = "MIT/X11 OR Apache-2.0" |
||||||
|
version = "0.8.1-alpha.0" |
||||||
|
authors = ["Katharina Fey <kookie@spacekookie.de>"] |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
lockchain-core = { version = "0.9.1-alpha.0", path = "../lockchain-core" } |
||||||
|
serde_derive = "1.0" |
||||||
|
serde = "1.0" |
||||||
|
|
||||||
|
miscreant = "0.4.0-beta1" |
@ -0,0 +1,13 @@ |
|||||||
|
[package] |
||||||
|
name = "lockchain-files" |
||||||
|
description = "Filesystem storage backend for lockchain vaults" |
||||||
|
version = "0.9.1-alpha.0" |
||||||
|
authors = ["Katharina Fey <kookie@spacekookie.de>"] |
||||||
|
|
||||||
|
documentation = "https://docs.rs/lockchain-files" |
||||||
|
homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-files" |
||||||
|
readme = "README.md" |
||||||
|
license = "MIT/X11 OR Apache-2.0" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
lockchain-core = { version = "0.9.1-alpha.0", path = "../lockchain-core" } |
@ -0,0 +1,23 @@ |
|||||||
|
[package] |
||||||
|
name = "lockchain-http" |
||||||
|
description = "Generic HTTP interface crate for the lockchain ecosystem. Can serve both encrypted and cleartext records" |
||||||
|
version = "0.4.1-alpha.0" |
||||||
|
authors = ["Katharina Fey <kookie@spacekookie.de>"] |
||||||
|
|
||||||
|
documentation = "https://docs.rs/lockchain-http" |
||||||
|
homepage = "https://github.com/spacekookie/lockchain/tree/master/lockchain-http" |
||||||
|
readme = "README.md" |
||||||
|
license = "MIT/X11 OR Apache-2.0" |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
lockchain-core = { version = "0.9.1-alpha.0", path = "../lockchain-core" } |
||||||
|
env_logger = "0.5" |
||||||
|
|
||||||
|
# Serialisation stack |
||||||
|
serde = "1.0" |
||||||
|
serde_derive = "1.0" |
||||||
|
|
||||||
|
# Web Stack |
||||||
|
futures = "0.1" |
||||||
|
actix = "0.5" |
||||||
|
actix-web = "0.6" |
@ -0,0 +1,12 @@ |
|||||||
|
[package] |
||||||
|
name = "lockchain-server" |
||||||
|
version = "0.1.0" |
||||||
|
authors = ["Katharina Fey <kookie@spacekookie.de>"] |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
lockchain-core = { path = "../lockchain-core" } |
||||||
|
lockchain-files = { path = "../lockchain-files" } |
||||||
|
lockchain-http = { path = "../lockchain-http" } |
||||||
|
|
||||||
|
clap = "2.0" |
||||||
|
insult = "2.0.3" |
@ -0,0 +1,89 @@ |
|||||||
|
// extern crate cargo_ws_release;
|
||||||
|
|
||||||
|
// use cargo_ws_release::data_models::level::Level;
|
||||||
|
// use cargo_ws_release::do_batch_release;
|
||||||
|
// use cargo_ws_release::utilities::cargo_utils::bump_version;
|
||||||
|
// use std::env::*;
|
||||||
|
// use std::fs::File;
|
||||||
|
// use std::path::Path;
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn do_batch_release_returns_dependency_graph() {
|
||||||
|
// let project_dir = current_dir().unwrap().to_str().unwrap().to_owned();
|
||||||
|
// let test_resource_dir = format!("{}/tests/resources", project_dir);
|
||||||
|
// assert!(set_current_dir(Path::new(&test_resource_dir)).is_ok());
|
||||||
|
// let test_cargo_file_path = format!("{}/Cargo.toml", test_resource_dir);
|
||||||
|
// let cargo_file = File::open(test_cargo_file_path).expect("Failed to open test Cargo.toml");
|
||||||
|
|
||||||
|
// let dep_graph = do_batch_release(cargo_file, &Level::Major);
|
||||||
|
|
||||||
|
// assert!(dep_graph.nodes.contains_key("lockchain-server"));
|
||||||
|
// assert!(
|
||||||
|
// dep_graph
|
||||||
|
// .nodes
|
||||||
|
// .get("lockchain-http")
|
||||||
|
// .unwrap()
|
||||||
|
// .deps
|
||||||
|
// .first()
|
||||||
|
// .unwrap()
|
||||||
|
// .name
|
||||||
|
// == "lockchain-core"
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn parse_level_from_str() {
|
||||||
|
// let levels = vec![
|
||||||
|
// ("major", Some(Level::Major)),
|
||||||
|
// ("minor", Some(Level::Minor)),
|
||||||
|
// ("patch", Some(Level::Patch)),
|
||||||
|
// ("rc", Some(Level::RC)),
|
||||||
|
// ("beta", Some(Level::Beta)),
|
||||||
|
// ("alpha", Some(Level::Alpha)),
|
||||||
|
// ("invalid_level", None),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// levels
|
||||||
|
// .iter()
|
||||||
|
// .for_each(|(level_str, level)| assert_eq!(Level::from_str(level_str), *level));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn bump_version_with_level() {
|
||||||
|
// let versions = vec![
|
||||||
|
// ("0.9.9", Level::Major, "1.0.0"),
|
||||||
|
// ("0.9.9", Level::Minor, "0.10.0"),
|
||||||
|
// ("0.9.9", Level::Patch, "0.9.10"),
|
||||||
|
// ("1.9.9", Level::Alpha, "1.9.10-alpha.1"),
|
||||||
|
// ("1.9.9", Level::Beta, "1.9.10-beta.1"),
|
||||||
|
// ("1.9.9", Level::RC, "1.9.10-rc.1"),
|
||||||
|
// ("1.9.9-dev", Level::Alpha, "1.9.9-alpha.1"),
|
||||||
|
// ("1.9.9-test", Level::Beta, "1.9.9-beta.1"),
|
||||||
|
// ("1.9.9-stg", Level::RC, "1.9.9-rc.1"),
|
||||||
|
// ("1.9.9-dev.1", Level::Alpha, "1.9.9-alpha.1"),
|
||||||
|
// ("1.9.9-test.4", Level::Beta, "1.9.9-beta.1"),
|
||||||
|
// ("1.9.9-stg.2", Level::RC, "1.9.9-rc.1"),
|
||||||
|
// ("2.3.9-alpha.8", Level::Alpha, "2.3.9-alpha.9"),
|
||||||
|
// ("2.3.9-beta.7", Level::Beta, "2.3.9-beta.8"),
|
||||||
|
// ("2.3.9-rc.6", Level::RC, "2.3.9-rc.7"),
|
||||||
|
// ("2.3.9-alpha.8.5.6.7.4", Level::Alpha, "2.3.9-alpha.9"),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// versions
|
||||||
|
// .iter()
|
||||||
|
// .for_each(|(given_version, given_level, expected_version)| {
|
||||||
|
// assert_eq!(
|
||||||
|
// bump_version(given_version, given_level).unwrap(),
|
||||||
|
// expected_version.to_string()
|
||||||
|
// )
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn bump_version_invalid_version_error() {
|
||||||
|
// let invalid_versions = vec!["a.b.c", "1.x.0", "1.", ".0.1", "1.1", "", " 1. 2. 3"];
|
||||||
|
|
||||||
|
// invalid_versions
|
||||||
|
// .iter()
|
||||||
|
// .for_each(|v| assert!(bump_version(v, &Level::Major).is_err()));
|
||||||
|
// }
|
Loading…
Reference in new issue