Schneider
5 years ago
commit
e7e0c9d0df
14 changed files with 1918 additions and 0 deletions
-
23.editorconfig
-
3.gitignore
-
10.travis.yml
-
37CONTRIBUTING
-
25Cargo.toml
-
674LICENSE
-
2clippy.toml
-
313justfile
-
36rustfmt.toml
-
87src/app.rs
-
7src/errors.rs
-
56src/helpers.rs
-
82src/main.rs
-
563src/validators.rs
@ -0,0 +1,23 @@ |
|||||
|
root = true |
||||
|
|
||||
|
[*] |
||||
|
charset = utf-8 |
||||
|
end_of_line = lf |
||||
|
indent_style = space |
||||
|
indent_size = 4 |
||||
|
insert_final_newline = false |
||||
|
trim_trailing_whitespace = true |
||||
|
|
||||
|
# justfile uses Makefile-like significant indentation |
||||
|
# but doesn't have a problem with blank lines staying un-indented |
||||
|
# within an indented block |
||||
|
[justfile] |
||||
|
indent_style = tab |
||||
|
trim_trailing_whitespace = true |
||||
|
|
||||
|
# If I have a TODO file, I want the outline lists to line up nicely and |
||||
|
# .travis.yml just uses 2-space indentation by convention |
||||
|
[{.travis.yml, TODO}] |
||||
|
indent_style = space |
||||
|
indent_size = 2 |
||||
|
|
@ -0,0 +1,3 @@ |
|||||
|
/callgrind.out.justfile |
||||
|
/dist |
||||
|
/target |
@ -0,0 +1,10 @@ |
|||||
|
language: rust |
||||
|
rust: |
||||
|
- stable |
||||
|
- beta |
||||
|
- nightly |
||||
|
matrix: |
||||
|
allow_failures: |
||||
|
- rust: nightly |
||||
|
notifications: |
||||
|
email: false |
@ -0,0 +1,37 @@ |
|||||
|
Developer Certificate of Origin |
||||
|
Version 1.1 |
||||
|
|
||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors. |
||||
|
1 Letterman Drive |
||||
|
Suite D4700 |
||||
|
San Francisco, CA, 94129 |
||||
|
|
||||
|
Everyone is permitted to copy and distribute verbatim copies of this |
||||
|
license document, but changing it is not allowed. |
||||
|
|
||||
|
|
||||
|
Developer's Certificate of Origin 1.1 |
||||
|
|
||||
|
By making a contribution to this project, I certify that: |
||||
|
|
||||
|
(a) The contribution was created in whole or in part by me and I |
||||
|
have the right to submit it under the open source license |
||||
|
indicated in the file; or |
||||
|
|
||||
|
(b) The contribution is based upon previous work that, to the best |
||||
|
of my knowledge, is covered under an appropriate open source |
||||
|
license and I have the right under that license to submit that |
||||
|
work with modifications, whether created in whole or in part |
||||
|
by me, under the same open source license (unless I am |
||||
|
permitted to submit under a different license), as indicated |
||||
|
in the file; or |
||||
|
|
||||
|
(c) The contribution was provided directly to me by some other |
||||
|
person who certified (a), (b) or (c) and I have not modified |
||||
|
it. |
||||
|
|
||||
|
(d) I understand and agree that this project and the contribution |
||||
|
are public and that a record of the contribution (including all |
||||
|
personal information I submit with it, including my sign-off) is |
||||
|
maintained indefinitely and may be redistributed consistent with |
||||
|
this project or the open source license(s) involved. |
@ -0,0 +1,25 @@ |
|||||
|
[package] |
||||
|
name = "gitig" |
||||
|
version = "0.1.0" |
||||
|
authors = ["Marcel Schneider <marcel@webschneider.org>"] |
||||
|
edition = "2018" |
||||
|
|
||||
|
[dependencies] |
||||
|
log = "0.4" |
||||
|
stderrlog = "0.4" |
||||
|
structopt = "0.3" |
||||
|
|
||||
|
[target.'cfg(unix)'.dependencies] |
||||
|
libc = "0.2" |
||||
|
|
||||
|
[dependencies.error-chain] |
||||
|
version = "0.12" |
||||
|
default-features = false # disable pulling in backtrace |
||||
|
|
||||
|
[profile.release] |
||||
|
lto = true |
||||
|
codegen-units = 1 |
||||
|
opt-level = "z" |
||||
|
|
||||
|
# Uncomment to sacrifice Drop-on-panic cleanup for 20K space saving |
||||
|
#panic = 'abort' |
@ -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,2 @@ |
|||||
|
# Version 0.1 |
||||
|
doc-valid-idents = ["MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "GPLv2", "GPLv3", "GitHub", "IPv4", "IPv6", "JavaScript", "NaN", "OAuth", "OpenGL", "TrueType", "OSes", "node_modules", "exFAT", "eCryptFS"] |
@ -0,0 +1,313 @@ |
|||||
|
# Version 0.2 |
||||
|
# Copyright 2017-2019, Stephan Sokolow |
||||
|
|
||||
|
# --== Variables to be customized/overridden by the user ==-- |
||||
|
|
||||
|
# The target for `cargo` commands to use and `install-rustup-deps` to install |
||||
|
export CARGO_BUILD_TARGET = "i686-unknown-linux-musl" |
||||
|
|
||||
|
# An easy way to override the `cargo` channel for just this project |
||||
|
channel = "stable" |
||||
|
|
||||
|
# Extra cargo features to enable |
||||
|
features = "" |
||||
|
|
||||
|
# An easy place to modify the build flags used |
||||
|
build_flags = "" |
||||
|
|
||||
|
# Example for OpenPandora cross-compilation |
||||
|
# export CARGO_BUILD_TARGET = "arm-unknown-linux-gnueabi" |
||||
|
|
||||
|
# -- `build-dist` -- |
||||
|
|
||||
|
# Set this to the cross-compiler's `strip` when cross-compiling |
||||
|
strip_bin = "strip" |
||||
|
|
||||
|
# Flags passed to `strip_bin` |
||||
|
strip_flags = "--strip-unneeded" |
||||
|
|
||||
|
# Set this if you need to override it for a cross-compiling `sstrip` |
||||
|
sstrip_bin = "sstrip" |
||||
|
|
||||
|
# Flags passed to [UPX](https://upx.github.io/) |
||||
|
upx_flags = "--ultra-brute" |
||||
|
|
||||
|
# Example for OpenPandora cross-compilation |
||||
|
# strip_bin = `echo $HOME/opt/pandora-dev/arm-2011.09/bin/pandora-strip` |
||||
|
|
||||
|
# -- `kcachegrind` -- |
||||
|
|
||||
|
# Extra arguments to pass to [callgrind](http://valgrind.org/docs/manual/cl-manual.html). |
||||
|
callgrind_args = "" |
||||
|
|
||||
|
# Temporary file used by `just kcachegrind` |
||||
|
callgrind_out_file = "callgrind.out.justfile" |
||||
|
|
||||
|
# Set this to override how `kcachegrind` is called |
||||
|
kcachegrind = "kcachegrind" |
||||
|
|
||||
|
# -- `install` and `uninstall` -- |
||||
|
|
||||
|
# Where to `install` bash completions. |
||||
|
# **You'll need to manually add some lines to source these files in `.bashrc.`** |
||||
|
bash_completion_dir = "~/.bash_completion.d" |
||||
|
|
||||
|
# Where to `install` fish completions. You'll probably never need to change this. |
||||
|
fish_completion_dir = "~/.config/fish/completions" |
||||
|
|
||||
|
# Where to `install` zsh completions. |
||||
|
# **You'll need to add this to your `fpath` manually** |
||||
|
zsh_completion_dir = "~/.zsh/functions" |
||||
|
|
||||
|
# Where to `install` manpages. As long as `~/.cargo/bin` is in your `PATH`, `man` should |
||||
|
# automatically pick up this location. |
||||
|
manpage_dir = "~/.cargo/share/man/man1" |
||||
|
|
||||
|
# --== Code Begins ==-- |
||||
|
|
||||
|
# Internal variables |
||||
|
# TODO: Look up that GitHub issues post on whitespace handling |
||||
|
_cargo_cmd = "cargo" # Used for --dry-run simulation |
||||
|
_cargo = _cargo_cmd + " \"+" + channel + "\"" |
||||
|
_build_flags = "--features=\"" + features + "\" " + build_flags |
||||
|
_doc_flags = "--document-private-items --features=\"" + features + "\"" |
||||
|
|
||||
|
# Parse the value of the "name" key in the [package] section of Cargo.toml |
||||
|
# using only the commands any POSIX-compliant platform should have |
||||
|
# Source: http://stackoverflow.com/a/40778047/435253 |
||||
|
export _pkgname=`sed -nr "/^\[package\]/ { :l /^name[ ]*=/ { s/.*=[ ]*//; p; q;}; n; b l;}" Cargo.toml | sed 's@^"\(.*\)"$@\1@'` |
||||
|
export _rls_bin_path="target/" + CARGO_BUILD_TARGET + "/release/" + _pkgname |
||||
|
export _dbg_bin_path="target/" + CARGO_BUILD_TARGET + "/debug/" + _pkgname |
||||
|
|
||||
|
# Shorthand for `just test` |
||||
|
DEFAULT: test |
||||
|
|
||||
|
# -- Development -- |
||||
|
|
||||
|
# Alias for cargo-edit's `cargo add` which regenerates local API docs afterwards |
||||
|
add +args="": |
||||
|
{{_cargo}} add {{args}} |
||||
|
just doc |
||||
|
|
||||
|
# Alias for `cargo bloat` |
||||
|
bloat +args="": |
||||
|
{{_cargo}} bloat {{_build_flags}} {{args}} |
||||
|
|
||||
|
# Alias for `cargo check` |
||||
|
check +args="": |
||||
|
{{_cargo}} check {{_build_flags}} {{args}} |
||||
|
|
||||
|
# Superset of `cargo clean -v` which deletes other stuff this justfile builds |
||||
|
clean +args="": |
||||
|
{{_cargo}} clean -v {{args}} |
||||
|
export CARGO_TARGET_DIR="target/kcov" && {{_cargo}} clean -v |
||||
|
rm -rf dist |
||||
|
|
||||
|
# Run rustdoc with `--document-private-items` and then run cargo-deadlinks |
||||
|
doc +args="": |
||||
|
{{_cargo}} doc {{_doc_flags}} {{args}} && \ |
||||
|
{{_cargo}} deadlinks --dir target/$CARGO_BUILD_TARGET/doc/{{_pkgname}} |
||||
|
|
||||
|
# Alias for `cargo +nightly fmt -- {{args}}` |
||||
|
fmt +args="": |
||||
|
{{_cargo_cmd}} +nightly fmt -- {{args}} |
||||
|
|
||||
|
# Alias for `cargo +nightly fmt -- --check {{args}}` which un-bloats TODO/FIXME warnings |
||||
|
fmt-check +args="": |
||||
|
cargo +nightly fmt -- --check --color always {{args}} 2>&1 | egrep -v '[0-9]*[ ]*\|' |
||||
|
|
||||
|
# Run a debug build under [callgrind](http://valgrind.org/docs/manual/cl-manual.html), then open the |
||||
|
# profile in [KCachegrind](https://kcachegrind.github.io/) |
||||
|
kcachegrind +args="": |
||||
|
{{_cargo}} build |
||||
|
rm -rf '{{ callgrind_out_file }}' |
||||
|
valgrind --tool=callgrind --callgrind-out-file='{{ callgrind_out_file }}' \ |
||||
|
{{ callgrind_args }} 'target/{{ CARGO_BUILD_TARGET }}/debug/{{ _pkgname }}' \ |
||||
|
'{{ args }}' || true |
||||
|
test -e '{{ callgrind_out_file }}' |
||||
|
{{kcachegrind}} '{{ callgrind_out_file }}' |
||||
|
|
||||
|
# Generate a statement coverage report in `target/cov/` |
||||
|
kcov: |
||||
|
#!/bin/sh |
||||
|
# Adapted from: |
||||
|
# - http://sunjay.ca/2016/07/25/rust-code-coverage |
||||
|
# - https://users.rust-lang.org/t/tutorial-how-to-collect-test-coverages-for-rust-project/650 |
||||
|
# |
||||
|
# As of July 2, 2016, there is no option to make rustdoc generate a runnable |
||||
|
# test executable. That means that documentation tests will not show in your |
||||
|
# coverage data. If you discover a way to run the doctest executable with kcov, |
||||
|
# please open an Issue and we will add that to these instructions. |
||||
|
# -- https://github.com/codecov/example-rust |
||||
|
|
||||
|
# Ensure that kcov can see totally unused functions without clobbering regular builds |
||||
|
# Adapted from: |
||||
|
# - http://stackoverflow.com/a/38371687/435253 |
||||
|
# - https://gist.github.com/dikaiosune/07177baf5cea76c27783efa55e99da89 |
||||
|
export CARGO_TARGET_DIR="target/kcov" |
||||
|
export RUSTFLAGS='-C link-dead-code' |
||||
|
kcov_path="$CARGO_TARGET_DIR/html" |
||||
|
|
||||
|
if [ "$#" -gt 0 ]; then shift; fi # workaround for "can't shift that many" being fatal in dash |
||||
|
cargo test --no-run || exit $? |
||||
|
rm -rf "$kcov_path" |
||||
|
|
||||
|
for file in "$CARGO_TARGET_DIR"/"$CARGO_BUILD_TARGET"/debug/$_pkgname-*; do |
||||
|
if [ -x "$file" ]; then |
||||
|
outpath="$kcov_path/$(basename "$file")" |
||||
|
mkdir -p "$outpath" |
||||
|
kcov --exclude-pattern=/.cargo,/usr/lib --verify "$outpath" "$file" "$@" |
||||
|
elif echo "$file" | grep -F -e '-*'; then |
||||
|
echo "No build files found for coverage!" |
||||
|
exit 1 |
||||
|
fi |
||||
|
done |
||||
|
|
||||
|
# Alias for cargo-edit's `cargo rm` which regenerates local API docs afterwards |
||||
|
rm +args="": |
||||
|
{{_cargo}} rm {{args}} |
||||
|
just doc |
||||
|
|
||||
|
# Convenience alias for opening a crate search on lib.rs in the browser |
||||
|
search +args="": |
||||
|
xdg-open "https://lib.rs/search?q={{args}}" |
||||
|
|
||||
|
# Run all installed static analysis, plus `cargo test` |
||||
|
test: |
||||
|
@echo "============================= Outdated Packages =============================" |
||||
|
@{{_cargo}} outdated |
||||
|
@echo "\n============================= Insecure Packages =============================" |
||||
|
@{{_cargo}} audit -q |
||||
|
@echo "\n=============================== Clippy Lints ================================" |
||||
|
@{{_cargo}} clippy -q {{_build_flags}} |
||||
|
@echo "\n===================== Dead Internal Documentation Links =====================" |
||||
|
@{{_cargo}} doc -q --document-private-items {{_build_flags}} && \ |
||||
|
{{_cargo}} deadlinks --dir target/$CARGO_BUILD_TARGET/doc/{{_pkgname}} |
||||
|
@echo "\n================================ Test Suite =================================" |
||||
|
@{{_cargo}} test -q {{_build_flags}} |
||||
|
@echo "=============================================================================" |
||||
|
|
||||
|
# Alias for cargo-edit's `cargo update` which regenerates local API docs afterwards |
||||
|
update +args="": |
||||
|
{{_cargo}} update {{args}} |
||||
|
just doc |
||||
|
|
||||
|
# TODO: https://users.rust-lang.org/t/howto-sanitize-your-rust-code/9378 |
||||
|
|
||||
|
# -- Local Builds -- |
||||
|
|
||||
|
# Alias for `cargo build` |
||||
|
build: |
||||
|
@echo "\n--== Building with {{channel}} for {{CARGO_BUILD_TARGET}} (features: {{features}}) ==--\n" |
||||
|
{{_cargo}} build {{_build_flags}} |
||||
|
|
||||
|
# Install the un-packed binary, shell completions, and a manpage |
||||
|
install: dist-supplemental |
||||
|
@# Install completions |
||||
|
@# NOTE: bash and zsh completion requires additional setup to source a non-root dir |
||||
|
mkdir -p {{bash_completion_dir}} {{zsh_completion_dir}} {{ fish_completion_dir }} {{ manpage_dir }} |
||||
|
cp dist/{{ _pkgname }}.bash {{ bash_completion_dir }}/{{ _pkgname }} |
||||
|
cp dist/{{ _pkgname }}.zsh {{ zsh_completion_dir }}/_{{ _pkgname }} |
||||
|
cp dist/{{ _pkgname }}.fish {{ fish_completion_dir }}/{{ _pkgname }}.fish |
||||
|
@# Install the manpage |
||||
|
cp dist/{{ _pkgname }}.1.gz {{ manpage_dir }}/{{ _pkgname }}.1.gz || true |
||||
|
@# Install the command to ~/.cargo/bin |
||||
|
{{_cargo}} install --path . --force --features="{{features}}" |
||||
|
|
||||
|
# Alias for `cargo run -- {{args}}` |
||||
|
run +args="": |
||||
|
{{_cargo}} run {{_build_flags}} -- {{args}} |
||||
|
|
||||
|
# Remove any files installed by the `install` task (but leave any parent directories created) |
||||
|
uninstall: |
||||
|
@# TODO: Implement the proper fallback chain from `cargo install` |
||||
|
rm ~/.cargo/bin/{{ _pkgname }} || true |
||||
|
rm {{ manpage_dir }}/{{ _pkgname }}.1.gz || true |
||||
|
rm {{ bash_completion_dir }}/{{ _pkgname }} || true |
||||
|
rm {{ fish_completion_dir }}/{{ _pkgname }}.fish || true |
||||
|
rm {{ zsh_completion_dir }}/_{{ _pkgname }} || true |
||||
|
|
||||
|
# -- Release Builds -- |
||||
|
|
||||
|
# Make a release build and then strip and compress the resulting binary |
||||
|
build-dist: |
||||
|
@echo "\n--== Building with {{channel}} for {{CARGO_BUILD_TARGET}} (features: {{features}}) ==--\n" |
||||
|
{{_cargo}} build --release {{_build_flags}} |
||||
|
|
||||
|
@# Don't modify the original "cargo" output. That confuses cargo somehow. |
||||
|
cp "{{_rls_bin_path}}" "{{_rls_bin_path}}.stripped" |
||||
|
@printf "\n--== Stripping, SStripping, and Compressing With UPX ==--\n" |
||||
|
{{strip_bin}} {{strip_flags}} "{{_rls_bin_path}}.stripped" |
||||
|
@# Allow sstrip to fail because it can't be installed via "just install-deps" |
||||
|
{{sstrip_bin}} "{{_rls_bin_path}}.stripped" || true |
||||
|
@# Allow upx to fail in case the user wants to force no UPXing by leaving it uninstalled |
||||
|
cp "{{_rls_bin_path}}.stripped" "{{_rls_bin_path}}.packed" |
||||
|
upx {{upx_flags}} "{{_rls_bin_path}}.packed" || true |
||||
|
@# Display the resulting file sizes so we can keep an eye on them |
||||
|
@# (Separate `ls` invocations are used to force the display ordering) |
||||
|
@printf "\n--== Final Result ==--\n" |
||||
|
@ls -1sh "{{_rls_bin_path}}" |
||||
|
@ls -1sh "{{_rls_bin_path}}.stripped" |
||||
|
@ls -1sh "{{_rls_bin_path}}.packed" |
||||
|
@printf "\n" |
||||
|
|
||||
|
|
||||
|
# Build the shell completions and a manpage, and put them in `dist/` |
||||
|
dist-supplemental: |
||||
|
mkdir -p dist |
||||
|
@# Generate completions and store them in dist/ |
||||
|
{{_cargo}} run --release {{_build_flags}} -- --dump-completions bash > dist/{{ _pkgname }}.bash |
||||
|
{{_cargo}} run --release {{_build_flags}} -- --dump-completions zsh > dist/{{ _pkgname }}.zsh |
||||
|
{{_cargo}} run --release {{_build_flags}} -- --dump-completions fish > dist/{{ _pkgname }}.fish |
||||
|
{{_cargo}} run --release {{_build_flags}} -- --dump-completions elvish > dist/{{ _pkgname }}.elvish |
||||
|
{{_cargo}} run --release {{_build_flags}} -- --dump-completions powershell > dist/{{ _pkgname }}.powershell |
||||
|
@# Generate manpage and store it gzipped in dist/ |
||||
|
@# (This comes last so the earlier calls to `cargo run` will get the compiler warnings out) |
||||
|
help2man -N '{{_cargo}} run {{_build_flags}} --' \ |
||||
|
| gzip -9 > dist/{{ _pkgname }}.1.gz || true |
||||
|
|
||||
|
# Call `dist-supplemental` and `build-dist` and copy the packed binary to `dist/` |
||||
|
dist: build-dist dist-supplemental |
||||
|
@# Copy the packed command to dist/ |
||||
|
cp "{{ _rls_bin_path }}.packed" dist/{{ _pkgname }} || \ |
||||
|
cp "{{ _rls_bin_path }}.stripped" dist/{{ _pkgname }} |
||||
|
|
||||
|
# -- Dependencies -- |
||||
|
|
||||
|
# Use `apt-get` to install dependencies `cargo` can't (except `kcov` and `sstrip`) |
||||
|
install-apt-deps: |
||||
|
sudo apt-get install binutils help2man kcachegrind upx valgrind |
||||
|
|
||||
|
# `install-rustup-deps` and then `cargo install` tools |
||||
|
install-cargo-deps: install-rustup-deps |
||||
|
@# Prevent "already installed" from causing a failure |
||||
|
{{_cargo}} install cargo-audit || true |
||||
|
{{_cargo}} install cargo-bloat || true |
||||
|
{{_cargo}} install cargo-deadlinks || true |
||||
|
{{_cargo}} install cargo-edit || true |
||||
|
{{_cargo}} install cargo-outdated || true |
||||
|
cargo +nightly install cargo-cov || true |
||||
|
|
||||
|
# Install (don't update) nightly and `channel` toolchains, plus `CARGO_BUILD_TARGET`, clippy, and rustfmt |
||||
|
install-rustup-deps: |
||||
|
@# Prevent this from gleefully doing an unwanted "rustup update" |
||||
|
rustup toolchain list | grep -q '{{channel}}' || rustup toolchain install '{{channel}}' |
||||
|
rustup toolchain list | grep -q nightly || rustup toolchain install nightly |
||||
|
rustup target list | grep -q '{{CARGO_BUILD_TARGET}} (' || rustup target add '{{CARGO_BUILD_TARGET}}' |
||||
|
rustup component list | grep -q 'clippy-\S* (' || rustup component add clippy |
||||
|
rustup component list --toolchain nightly | grep 'rustfmt-\S* (' || rustup component add rustfmt --toolchain nightly |
||||
|
|
||||
|
# Run `install-apt-deps` and `install-cargo-deps`. List what remains. |
||||
|
@install-deps: install-apt-deps install-cargo-deps |
||||
|
echo |
||||
|
echo "-----------------------------------------------------------" |
||||
|
echo "IMPORTANT: You will need to install the following manually:" |
||||
|
echo "-----------------------------------------------------------" |
||||
|
echo " * Rust-compatible kcov (http://sunjay.ca/2016/07/25/rust-code-coverage)" |
||||
|
echo " * sstrip (http://www.muppetlabs.com/%7Ebreadbox/software/elfkickers.html)" |
||||
|
|
||||
|
# Local Variables: |
||||
|
# mode: makefile |
||||
|
# End: |
||||
|
|
||||
|
# vim: set ft=make textwidth=100 colorcolumn=101 noexpandtab sw=8 sts=8 ts=8 : |
@ -0,0 +1,36 @@ |
|||||
|
# Version 0.1 |
||||
|
|
||||
|
# Always nice to have a way to gather TODOs and FIXMEs quickly |
||||
|
report_todo = "Always" |
||||
|
report_fixme = "Always" |
||||
|
|
||||
|
# I was one of the proponents in the RFC discussion |
||||
|
use_try_shorthand = true |
||||
|
|
||||
|
# Try to wrestle rustfmt as close as possible to the style I habitually use |
||||
|
# and rigorously enforce by using `git gui` to only commit rustfmt changes |
||||
|
# I approve of. |
||||
|
brace_style = "PreferSameLine" |
||||
|
comment_width = 99 |
||||
|
enum_discrim_align_threshold = 10 |
||||
|
format_strings = true |
||||
|
fn_args_density = "Compressed" |
||||
|
fn_single_line = true |
||||
|
imports_indent = "Visual" |
||||
|
match_block_trailing_comma = true |
||||
|
normalize_doc_attributes = true |
||||
|
overflow_delimited_expr = true |
||||
|
reorder_impl_items = true |
||||
|
struct_field_align_threshold = 10 |
||||
|
use_field_init_shorthand = true |
||||
|
use_small_heuristics = "Max" |
||||
|
where_single_line = true |
||||
|
wrap_comments = true |
||||
|
|
||||
|
# I happen to like /* this */ for multi-line comments, thank you very much |
||||
|
normalize_comments = false |
||||
|
|
||||
|
# ----------------------------------------------------------------------------- |
||||
|
|
||||
|
# Used for debugging rustfmt configurations |
||||
|
#write_mode = "Diff" |
@ -0,0 +1,87 @@ |
|||||
|
/*! Application-specific logic lives here */
|
||||
|
// Parts Copyright 2017-2019, Stephan Sokolow
|
||||
|
|
||||
|
// Standard library imports
|
||||
|
use std::path::PathBuf;
|
||||
|
|
||||
|
// 3rd-party crate imports
|
||||
|
use structopt::StructOpt;
|
||||
|
|
||||
|
use log::{debug, error, info, trace, warn};
|
||||
|
|
||||
|
// Local Imports
|
||||
|
use crate::errors::*;
|
||||
|
use crate::helpers::{BoilerplateOpts, HELP_TEMPLATE};
|
||||
|
use crate::validators::path_readable_file;
|
||||
|
|
||||
|
/// The verbosity level when no `-q` or `-v` arguments are given, with `0` being `-q`
|
||||
|
pub const DEFAULT_VERBOSITY: u64 = 1;
|
||||
|
|
||||
|
/// Command-line argument schema
|
||||
|
///
|
||||
|
/// ## Relevant Conventions:
|
||||
|
///
|
||||
|
/// * Make sure that there is a blank space between the `<name>` `<version>` line and the
|
||||
|
/// description text or the `--help` output won't comply with the platform conventions that
|
||||
|
/// `help2man` depends on to generate your manpage.
|
||||
|
/// (Specifically, it will mistake the `<name> <version>` line for part of the description.)
|
||||
|
/// * `StructOpt`'s default behaviour of including the author name in the `--help` output is an
|
||||
|
/// oddity among Linux commands and, if you don't disable it, you run the risk of people
|
||||
|
/// unfamiliar with `StructOpt` assuming that you are an egotistical person who made a conscious
|
||||
|
/// choice to add it.
|
||||
|
///
|
||||
|
/// The proper standardized location for author information is the `AUTHOR` section which you
|
||||
|
/// can read about by typing `man help2man`.
|
||||
|
///
|
||||
|
/// ## Cautions:
|
||||
|
/// * Subcommands do not inherit `template` and it must be re-specified for each one.
|
||||
|
/// ([clap-rs/clap#1184](https://github.com/clap-rs/clap/issues/1184))
|
||||
|
/// * Double-check that your choice of `about` or `long_about` is actually overriding this
|
||||
|
/// doc comment. The precedence is affected by things you wouldn't expect, such as the presence
|
||||
|
/// or absence of `template` and it's easy to wind up with this doc-comment as your `--help`
|
||||
|
/// ([TeXitoi/structopt#173](https://github.com/TeXitoi/structopt/issues/173))
|
||||
|
/// * Do not begin the description text for subcommands with `\n`. It will break the formatting
|
||||
|
/// in the top-level help output's list of subcommands.
|
||||
|
#[derive(StructOpt, Debug)]
|
||||
|
#[structopt(template = HELP_TEMPLATE,
|
||||
|
about = "TODO: Replace me with the description text for the command",
|
||||
|
global_setting = structopt::clap::AppSettings::ColoredHelp)]
|
||||
|
pub struct CliOpts {
|
||||
|
#[allow(clippy::missing_docs_in_private_items)] // StructOpt won't let us document this
|
||||
|
#[structopt(flatten)]
|
||||
|
pub boilerplate: BoilerplateOpts,
|
||||
|
|
||||
|
// -- Arguments used by application-specific logic --
|
||||
|
|
||||
|
/// File(s) to use as input
|
||||
|
///
|
||||
|
/// **TODO:** Figure out if there's a way to only enforce constraints on this when not asking
|
||||
|
/// to dump completions.
|
||||
|
#[structopt(parse(from_os_str),
|
||||
|
validator_os = path_readable_file)]
|
||||
|
inpath: Vec<PathBuf>,
|
||||
|
}
|
||||
|
|
||||
|
/// The actual `main()`
|
||||
|
pub fn main(opts: CliOpts) -> Result<()> {
|
||||
|
for inpath in opts.inpath {
|
||||
|
unimplemented!()
|
||||
|
}
|
||||
|
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
|
||||
|
// Tests go below the code where they'll be out of the way when not the target of attention
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use super::CliOpts;
|
||||
|
|
||||
|
// TODO: Unit test to verify that the doc comment on `CliOpts` isn't overriding the intended
|
||||
|
// about string.
|
||||
|
|
||||
|
#[test]
|
||||
|
/// Test something
|
||||
|
fn test_something() {
|
||||
|
// TODO: Test something
|
||||
|
}
|
||||
|
}
|
@ -0,0 +1,7 @@ |
|||||
|
/*! `error-chain` boilerplate and custom `Error` types */
|
||||
|
// Copyright 2020, Marcel Schneider <marcel@webschneider.org>
|
||||
|
|
||||
|
use error_chain::*;
|
||||
|
|
||||
|
// Create the Error, ErrorKind, ResultExt, and Result types
|
||||
|
error_chain! {}
|
@ -0,0 +1,56 @@ |
|||||
|
/*! Functions and templates which can be imported by app.rs to save effort */
|
||||
|
// Copyright 2017-2019, Stephan Sokolow
|
||||
|
|
||||
|
// FIXME: Report that StructOpt is tripping Clippy's `result_unwrap_used` lint (which I use to push
|
||||
|
// for .expect() instead) in my two Option<T> fields and the `allow` gets ignored unless I
|
||||
|
// `#![...]` it onto the entire module.
|
||||
|
#![allow(clippy::result_unwrap_used)]
|
||||
|
|
||||
|
use structopt::{clap, StructOpt};
|
||||
|
|
||||
|
/// Modified version of Clap's default template for proper help2man compatibility
|
||||
|
///
|
||||
|
/// Used as a workaround for:
|
||||
|
/// 1. Clap's default template interfering with `help2man`'s proper function
|
||||
|
/// ([clap-rs/clap/#1432](https://github.com/clap-rs/clap/issues/1432))
|
||||
|
/// 2. Workarounds involving injecting `\n` into the description breaking help output if used
|
||||
|
/// on subcommand descriptions.
|
||||
|
pub const HELP_TEMPLATE: &str = "{bin} {version}
|
||||
|
|
||||
|
{about}
|
||||
|
|
||||
|
USAGE: |
||||
|
{usage}
|
||||
|
|
||||
|
{all-args}
|
||||
|
";
|
||||
|
|
||||
|
/// Options used by boilerplate code
|
||||
|
// TODO: Move these into a struct of their own in something like helpers.rs
|
||||
|
#[derive(StructOpt, Debug)]
|
||||
|
#[structopt(rename_all = "kebab-case")]
|
||||
|
pub struct BoilerplateOpts {
|
||||
|
|
||||
|
// -- Arguments used by main.rs --
|
||||
|
// TODO: Move these into a struct of their own in something like helpers.rs
|
||||
|
|
||||
|
// FIXME: Report that StructOpt trips Clippy's `cast_possible_truncation` lint unless I use
|
||||
|
// `u64` for my `from_occurrences` inputs, which is a ridiculous state of things.
|
||||
|
|
||||
|
/// Decrease verbosity (-q, -qq, -qqq, etc.)
|
||||
|
#[structopt(short, long, parse(from_occurrences))]
|
||||
|
pub quiet: u64,
|
||||
|
|
||||
|
/// Increase verbosity (-v, -vv, -vvv, etc.)
|
||||
|
#[structopt(short, long, parse(from_occurrences))]
|
||||
|
pub verbose: u64,
|
||||
|
|
||||
|
/// Display timestamps on log messages (sec, ms, ns, none)
|
||||
|
#[structopt(short, long, value_name = "resolution")]
|
||||
|
pub timestamp: Option<stderrlog::Timestamp>,
|
||||
|
|
||||
|
/// Write a completion definition for the specified shell to stdout (bash, zsh, etc.)
|
||||
|
#[structopt(long, value_name = "shell")]
|
||||
|
pub dump_completions: Option<clap::Shell>,
|
||||
|
}
|
||||
|
|
@ -0,0 +1,82 @@ |
|||||
|
/*! TODO: Application description here
|
||||
|
|
||||
|
This file provided by [rust-cli-boilerplate](https://github.com/ssokolow/rust-cli-boilerplate)
|
||||
|
*/
|
||||
|
// Copyright 2017-2019, Stephan Sokolow
|
||||
|
|
||||
|
// `error_chain` recursion adjustment
|
||||
|
#![recursion_limit = "1024"]
|
||||
|
|
||||
|
// Make rustc's built-in lints more strict and set clippy into a whitelist-based configuration so
|
||||
|
// we see new lints as they get written (We'll opt back out selectively)
|
||||
|
#![warn(warnings, rust_2018_idioms, clippy::all, clippy::complexity, clippy::correctness,
|
||||
|
clippy::pedantic, clippy::perf, clippy::style, clippy::restriction)]
|
||||
|
|
||||
|
// Opt out of the lints I've seen and don't want
|
||||
|
#![allow(clippy::float_arithmetic, clippy::implicit_return)]
|
||||
|
|
||||
|
// stdlib imports
|
||||
|
use std::io;
|
||||
|
use std::convert::TryInto;
|
||||
|
|
||||
|
// 3rd-party imports
|
||||
|
mod errors;
|
||||
|
use structopt::{clap, StructOpt};
|
||||
|
use log::error;
|
||||
|
|
||||
|
// Local imports
|
||||
|
mod app;
|
||||
|
mod helpers;
|
||||
|
mod validators;
|
||||
|
|
||||
|
/// Boilerplate to parse command-line arguments, set up logging, and handle bubbled-up `Error`s.
|
||||
|
///
|
||||
|
/// Based on the `StructOpt` example from stderrlog and the suggested error-chain harness from
|
||||
|
/// [quickstart.rs](https://github.com/brson/error-chain/blob/master/examples/quickstart.rs).
|
||||
|
///
|
||||
|
/// See `app::main` for the application-specific logic.
|
||||
|
///
|
||||
|
/// **TODO:** Consider switching to Failure and look into `impl Termination` as a way to avoid
|
||||
|
/// having to put the error message pretty-printing inside main()
|
||||
|
fn main() {
|
||||
|
// Parse command-line arguments (exiting on parse error, --version, or --help)
|
||||
|
let opts = app::CliOpts::from_args();
|
||||
|
|
||||
|
// Configure logging output so that -q is "decrease verbosity" rather than instant silence
|
||||
|
let verbosity = opts.boilerplate.verbose
|
||||
|
.saturating_add(app::DEFAULT_VERBOSITY)
|
||||
|
.saturating_sub(opts.boilerplate.quiet);
|
||||
|
stderrlog::new()
|
||||
|
.module(module_path!())
|
||||
|
.quiet(verbosity == 0)
|
||||
|
.verbosity(verbosity.saturating_sub(1).try_into().expect("should never even come close"))
|
||||
|
.timestamp(opts.boilerplate.timestamp.unwrap_or(stderrlog::Timestamp::Off))
|
||||
|
.init()
|
||||
|
.expect("initializing logging output");
|
||||
|
|
||||
|
// If requested, generate shell completions and then exit with status of "success"
|
||||
|
if let Some(shell) = opts.boilerplate.dump_completions {
|
||||
|
app::CliOpts::clap().gen_completions_to(
|
||||
|
app::CliOpts::clap().get_bin_name().unwrap_or_else(|| clap::crate_name!()),
|
||||
|
shell,
|
||||
|
&mut io::stdout());
|
||||
|
std::process::exit(0);
|
||||
|
};
|
||||
|
|
||||
|
if let Err(ref e) = app::main(opts) {
|
||||
|
// Write the top-level error message, then chained errors, then backtrace if available
|
||||
|
error!("error: {}", e);
|
||||
|
for e in e.iter().skip(1) {
|
||||
|
error!("caused by: {}", e);
|
||||
|
}
|
||||
|
if let Some(backtrace) = e.backtrace() {
|
||||
|
error!("backtrace: {:?}", backtrace);
|
||||
|
}
|
||||
|
|
||||
|
// Exit with a nonzero exit code
|
||||
|
// TODO: Decide how to allow code to set this to something other than 1
|
||||
|
std::process::exit(1);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
// vim: set sw=4 sts=4 expandtab :
|
@ -0,0 +1,563 @@ |
|||||
|
/*! Validator functions suitable for use with `Clap` and `StructOpt` */
|
||||
|
// Copyright 2017-2019, Stephan Sokolow
|
||||
|
|
||||
|
use std::ffi::OsString;
|
||||
|
use std::fs::File;
|
||||
|
use std::path::{Component, Path};
|
||||
|
|
||||
|
/// Special filenames which cannot be used for real files under Win32
|
||||
|
///
|
||||
|
/// (Unless your app uses the `\\?\` path prefix to bypass legacy Win32 API compatibility
|
||||
|
/// limitations)
|
||||
|
///
|
||||
|
/// **NOTE:** These are still reserved if you append an extension to them.
|
||||
|
///
|
||||
|
/// Source: [Boost Path Name Portability Guide
|
||||
|
/// ](https://www.boost.org/doc/libs/1_36_0/libs/filesystem/doc/portability_guide.htm)
|
||||
|
pub const RESERVED_DOS_FILENAMES: &[&str] = &["AUX", "CON", "NUL", "PRN", // Comments for rustfmt
|
||||
|
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", // Serial Ports
|
||||
|
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", // Parallel Ports
|
||||
|
"CLOCK$" ]; // https://www.boost.org/doc/libs/1_36_0/libs/filesystem/doc/portability_guide.htm
|
||||
|
// TODO: Add the rest of the disallowed names from
|
||||
|
// https://en.wikipedia.org/wiki/Filename#Comparison_of_filename_limitations
|
||||
|
|
||||
|
/// Module to contain the unsafety of an `unsafe` call to `access()`
|
||||
|
#[cfg(unix)]
|
||||
|
mod access {
|
||||
|
/// TODO: Make this wrapper portable
|
||||
|
/// <https://doc.rust-lang.org/book/conditional-compilation.html>
|
||||
|
/// TODO: Consider making `wrapped_access` typesafe using the `bitflags`
|
||||
|
/// crate `clap` pulled in
|
||||
|
use libc::{access, c_int, W_OK};
|
||||
|
use std::ffi::CString;
|
||||
|
use std::os::unix::ffi::OsStrExt;
|
||||
|
use std::path::Path;
|
||||
|
|
||||
|
/// Lower-level safety wrapper shared by all probably_* functions I define
|
||||
|
/// TODO: Unit test **HEAVILY** (Has unsafe block. Here be dragons!)
|
||||
|
fn wrapped_access(abs_path: &Path, mode: c_int) -> bool {
|
||||
|
// Debug-time check that we're using the API properly
|
||||
|
// (Debug-only because relying on it in a release build grants a false
|
||||
|
// sense of security and, besides, access() is only really safe to use
|
||||
|
// as a way to abort early for convenience on errors that would still
|
||||
|
// be safe anyway.)
|
||||
|
debug_assert!(abs_path.is_absolute());
|
||||
|
|
||||
|
// Make a null-terminated copy of the path for libc
|
||||
|
match CString::new(abs_path.as_os_str().as_bytes()) {
|
||||
|
// If we succeed, call access(2), convert the result into bool, and return it
|
||||
|
Ok(cstr) => unsafe { access(cstr.as_ptr(), mode) == 0 },
|
||||
|
// If we fail, return false because it can't be an access()ible path
|
||||
|
Err(_) => false,
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// API suitable for a lightweight "fail early" check for whether a target
|
||||
|
/// directory is writable without worry that a fancy filesystem may be
|
||||
|
/// configured to allow write but deny deletion for the resulting test file.
|
||||
|
/// (It's been seen in the wild)
|
||||
|
///
|
||||
|
/// Uses a name which helps to drive home the security hazard in access()
|
||||
|
/// abuse and hide the mode flag behind an abstraction so the user can't
|
||||
|
/// mess up unsafe{} (eg. On my system, "/" erroneously returns success)
|
||||
|
pub fn probably_writable<P: AsRef<Path> + ?Sized>(path: &P) -> bool {
|
||||
|
wrapped_access(path.as_ref(), W_OK)
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use std::ffi::OsStr;
|
||||
|
use std::os::unix::ffi::OsStrExt; // TODO: Find a better way to produce invalid UTF-8
|
||||
|
use super::probably_writable;
|
||||
|
|
||||
|
#[test]
|
||||
|
fn probably_writable_basic_functionality() {
|
||||
|
assert!(probably_writable(OsStr::new("/tmp"))); // OK Folder
|
||||
|
assert!(probably_writable(OsStr::new("/dev/null"))); // OK File
|
||||
|
assert!(!probably_writable(OsStr::new("/etc/shadow"))); // Denied File
|
||||
|
assert!(!probably_writable(OsStr::new("/etc/ssl/private"))); // Denied Folder
|
||||
|
assert!(!probably_writable(OsStr::new("/nonexistant_test_path"))); // Missing Path
|
||||
|
assert!(!probably_writable(OsStr::new("/tmp\0with\0null"))); // Bad CString
|
||||
|
assert!(!probably_writable(OsStr::from_bytes(b"/not\xffutf8"))); // Bad UTF-8
|
||||
|
assert!(!probably_writable(OsStr::new("/"))); // Root
|
||||
|
// TODO: Relative path
|
||||
|
// TODO: Non-UTF8 path that actually does exist and is writable
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// Test that the given path **should** be writable
|
||||
|
///
|
||||
|
/// **TODO:** Implement a Windows version of this.
|
||||
|
///
|
||||
|
/// Given that every relevant Windows API I can find seems to be a complex mess compared to
|
||||
|
/// `access(2)`, I'll probably just want to settle for the compromise I rejected and just try
|
||||
|
/// writing and then deleting a test file.
|
||||
|
#[cfg(unix)]
|
||||
|
pub fn path_output_dir<P: AsRef<Path> + ?Sized>(value: &P) -> Result<(), OsString> {
|
||||
|
let path = value.as_ref();
|
||||
|
|
||||
|
// Test that the path is a directory
|
||||
|
// (Check before, not after, as an extra safety guard on the unsafe block)
|
||||
|
if !path.is_dir() {
|
||||
|
return Err(format!("Not a directory: {}", path.display()).into());
|
||||
|
}
|
||||
|
|
||||
|
// TODO: Think about how to code this more elegantly (try! perhaps?)
|
||||
|
if let Ok(abs_pathbuf) = path.canonicalize() {
|
||||
|
if let Some(abs_path) = abs_pathbuf.to_str() {
|
||||
|
if self::access::probably_writable(abs_path) {
|
||||
|
return Ok(());
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
Err(format!("Would be unable to write to destination directory: {}", path.display()).into())
|
||||
|
}
|
||||
|
|
||||
|
/// The given path is a file that can be opened for reading
|
||||
|
///
|
||||
|
/// ## Use For:
|
||||
|
/// * Input file paths
|
||||
|
///
|
||||
|
/// ## Relevant Conventions:
|
||||
|
/// * Commands which read from `stdin` by default should use `-f` to specify the input path.
|
||||
|
/// [[1]](http://www.catb.org/esr/writings/taoup/html/ch10s05.html)
|
||||
|
/// * Commands which read from files by default should use positional arguments to specify input
|
||||
|
/// paths.
|
||||
|
/// * Allow an arbitrary number of input paths if feasible.
|
||||
|
/// * Interpret a value of `-` to mean "read from `stdin`" if feasible.
|
||||
|
/// [[2]](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html)
|
||||
|
///
|
||||
|
/// **Note:** The following command-lines, which interleave files and `stdin`, are a good test of
|
||||
|
/// how the above conventions should interact:
|
||||
|
///
|
||||
|
/// data_source | my_utility_a header.dat - footer.dat > output.dat
|
||||
|
/// data_source | my_utility_b -f header.dat -f - -f footer.dat > output.dat
|
||||
|
///
|
||||
|
/// ## Cautions:
|
||||
|
/// * This will momentarily open the given path for reading to verify that it is readable.
|
||||
|
/// However, relying on this to remain true will introduce a race condition. This validator is
|
||||
|
/// intended only to allow your program to exit as quickly as possible in the case of obviously
|
||||
|
/// bad input.
|
||||
|
/// * As a more reliable validity check, you are advised to open a handle to the file in question
|
||||
|
/// as early in your program's operation as possible, use it for all your interactions with the
|
||||
|
/// file, and keep it open until you are finished. This will both verify its validity and
|
||||
|
/// minimize the window in which another process could render the path invalid.
|
||||
|
pub fn path_readable_file<P: AsRef<Path> + ?Sized>(value: &P)
|
||||
|
-> std::result::Result<(), OsString> {
|
||||
|
let path = value.as_ref();
|
||||
|
|
||||
|
if path.is_dir() {
|
||||
|
return Err(format!("{}: Input path must be a file, not a directory",
|
||||
|
path.display()).into());
|
||||
|
}
|
||||
|
|
||||
|
// TODO: Why does this not fail on Linux? I forget what reading a directory actually does.
|
||||
|
File::open(path).map(|_| ()).map_err(|e| format!("{}: {}", path.display(), e).into())
|
||||
|
}
|
||||
|
|
||||
|
// TODO: Implement path_readable_dir and path_readable for --recurse use-cases
|
||||
|
|
||||
|
/// The given path is valid on all major filesystems and OSes
|
||||
|
///
|
||||
|
/// ## Use For:
|
||||
|
/// * Output file or directory paths
|
||||
|
///
|
||||
|
/// ## Relevant Conventions:
|
||||
|
/// * Use `-o` to specify the output path.
|
||||
|
/// [[1]](http://www.catb.org/esr/writings/taoup/html/ch10s05.html)
|
||||
|
/// [[2]](http://tldp.org/LDP/abs/html/standard-options.html)
|
||||
|
/// * Interpret a value of `-` to mean "Write output to stdout".
|
||||
|
/// [[3]](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html)
|
||||
|
/// * Because `-o` does not inherently indicate whether it expects a file or a directory, consider
|
||||
|
/// also providing a GNU-style long version with a name like `--outfile` to allow scripts which
|
||||
|
/// depend on your tool to be more self-documenting.
|
||||
|
///
|
||||
|
/// ## Cautions:
|
||||
|
/// * To ensure files can be copied/moved without issue, this validator may impose stricter
|
||||
|
/// restrictions on filenames than your filesystem. Do *not* use it for input paths.
|
||||
|
/// * Other considerations, such as paths containing symbolic links with longer target names, may
|
||||
|
/// still cause your system to reject paths which pass this check.
|
||||
|
/// * As a more reliable validity check, you are advised to open a handle to the file in question
|
||||
|
/// as early in your program's operation as possible and keep it open until you are finished.
|
||||
|
/// This will both verify its validity and minimize the window in which another process could
|
||||
|
/// render the path invalid.
|
||||
|
///
|
||||
|
/// ## Design Considerations: [[4]](https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits)
|
||||
|
/// * Many popular Linux filesystems impose no total length limit.
|
||||
|
/// * This function imposes a 32,760-character limit for compatibility with flash drives formatted
|
||||
|
/// FAT32 or exFAT.
|
||||
|
/// * Some POSIX API functions, such as `getcwd()` and `realpath()` rely on the `PATH_MAX`
|
||||
|
/// constant, which typically specifies a length of 4096 bytes including terminal `NUL`, but
|
||||
|
/// this is not enforced by the filesystem itself.
|
||||
|
/// [[4]](https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html)
|
||||
|
///
|
||||
|
/// Programs which rely on libc for this functionality but do not attempt to canonicalize paths
|
||||
|
/// will usually work if you change the working directory and use relative paths.
|
||||
|
/// * The following lengths were considered too limiting to be enforced by this function:
|
||||
|
/// * The UDF filesystem used on DVDs imposes a 1023-byte length limit on paths.
|
||||
|
/// * When not using the `\\?\` prefix to disable legacy compatibility, Windows paths are
|
||||
|
/// limited to 260 characters, which was arrived at as `A:\MAX_FILENAME_LENGTH<NULL>`.
|
||||
|
/// [[5]](https://stackoverflow.com/a/1880453/435253)
|
||||
|
/// * ISO 9660 without Joliet or Rock Ridge extensions does not permit periods in directory
|
||||
|
/// names, directory trees more than 8 levels deep, or filenames longer than 32 characters.
|
||||
|
/// [[6]](https://www.boost.org/doc/libs/1_36_0/libs/filesystem/doc/portability_guide.htm)
|
||||
|
///
|
||||
|
/// **TODO:**
|
||||
|
/// * Write another function for enforcing the limits imposed by targeting optical media.
|
||||
|
pub fn path_valid_portable<P: AsRef<Path> + ?Sized>(value: &P) -> Result<(), OsString> {
|
||||
|
#![allow(clippy::match_same_arms, clippy::decimal_literal_representation)]
|
||||
|
let path = value.as_ref();
|
||||
|
|
||||
|
if path.as_os_str().is_empty() {
|
||||
|
Err("Path is empty".into())
|
||||
|
} else if path.as_os_str().len() > 32760 {
|
||||
|
// Limit length to fit on VFAT/exFAT when using the `\\?\` prefix to disable legacy limits
|
||||
|
// Source: https://en.wikipedia.org/wiki/Comparison_of_file_systems
|
||||
|
Err(format!("Path is too long ({} chars): {:?}", path.as_os_str().len(), path).into())
|
||||
|
} else {
|
||||
|
for component in path.components() {
|
||||
|
if let Component::Normal(string) = component {
|
||||
|
filename_valid_portable(string)?
|
||||
|
}
|
||||
|
}
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
/// The string is a valid file/folder name on all major filesystems and OSes
|
||||
|
///
|
||||
|
/// ## Use For:
|
||||
|
/// * Output file or directory names within a parent directory specified through other means.
|
||||
|
///
|
||||
|
/// ## Relevant Conventions:
|
||||
|
/// * Most of the time, you want to let users specify a full path via [`path_valid_portable`
|
||||
|
/// ](fn.path_valid_portable.html)instead.
|
||||
|
///
|
||||
|
/// ## Cautions:
|
||||
|
/// * To ensure files can be copied/moved without issue, this validator may impose stricter
|
||||
|
/// restrictions on filenames than your filesystem. Do *not* use it for input filenames.
|
||||
|
/// * This validator cannot guarantee that a given filename will be valid once other
|
||||
|
/// considerations such as overall path length limits are taken into account.
|
||||
|
/// * As a more reliable validity check, you are advised to open a handle to the file in question
|
||||
|
/// as early in your program's operation as possible, use it for all your interactions with the
|
||||
|
/// file, and keep it open until you are finished. This will both verify its validity and
|
||||
|
/// minimize the window in which another process could render the path invalid.
|
||||
|
///
|
||||
|
/// ## Design Considerations: [[3]](https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits)
|
||||
|
/// * In the interest of not inconveniencing users in the most common case, this validator imposes
|
||||
|
/// a 255-character length limit.
|
||||
|
/// * The eCryptFS home directory encryption offered by Ubuntu Linux imposes a 143-character
|
||||
|
/// length limit when filename encryption is enabled.
|
||||
|
/// [[4]](https://bugs.launchpad.net/ecryptfs/+bug/344878)
|
||||
|
/// * the Joliet extensions for ISO 9660 are specified to support only 64-character filenames and
|
||||
|
/// tested to support either 103 or 110 characters depending whether you ask the mkisofs
|
||||
|
/// developers or Microsoft. [[5]](https://en.wikipedia.org/wiki/Joliet_(file_system))
|
||||
|
/// * The [POSIX Portable Filename Character Set
|
||||
|
/// ](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282)
|
||||
|
/// is too restrictive to be baked into a general-purpose validator.
|
||||
|
///
|
||||
|
/// **TODO:** Consider converting this to a private function that just exists as a helper for the
|
||||
|
/// path validator in favour of more specialized validators for filename patterns, prefixes, and/or
|
||||
|
/// suffixes, to properly account for how "you can specify a name bu not a path" generally
|
||||
|
/// comes about.
|
||||
|
pub fn filename_valid_portable<P: AsRef<Path> + ?Sized>(value: &P) -> Result<(), OsString> {
|
||||
|
#![allow(clippy::match_same_arms, clippy::else_if_without_else)]
|
||||
|
let path = value.as_ref();
|
||||
|
|
||||
|
// TODO: Should I refuse incorrect Unicode normalization since Finder doesn't like it or just
|
||||
|
// advise users to run a normalization pass?
|
||||
|
// Source: https://news.ycombinator.com/item?id=16993687
|
||||
|
|
||||
|
// Check that the length is within range
|
||||
|
let os_str = path.as_os_str();
|
||||
|
if os_str.len() > 255 {
|
||||
|
return Err(format!("File/folder name is too long ({} chars): {:?}",
|
||||
|
path.as_os_str().len(), path).into());
|
||||
|
} else if os_str.is_empty() {
|
||||
|
return Err("Path component is empty".into());
|
||||
|
}
|
||||
|
|
||||
|
// Check for invalid characters
|
||||
|
let lossy_str = os_str.to_string_lossy();
|
||||
|
let last_char = lossy_str.chars().last().expect("getting last character");
|
||||
|
if [' ', '.'].iter().any(|&x| x == last_char) {
|
||||
|
// The Windows shell and UI don't support component names ending in periods or spaces
|
||||
|
// Source: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file
|
||||
|
return Err("Windows forbids path components ending with spaces/periods".into());
|
||||
|
} else if lossy_str.as_bytes().iter().any(|c| match c {
|
||||
|
// invalid on all APIs which don't use counted strings like inside the NT kernel
|
||||
|
b'\0' => true,
|
||||
|
// invalid under FAT*, VFAT, exFAT, and NTFS
|
||||
|
0x0..=0x1f | 0x7f | b'"' | b'*' | b'<' | b'>' | b'?' | b'|' => true,
|
||||
|
// POSIX path separator (invalid on Unixy platforms like Linux and BSD)
|
||||
|
b'/' => true,
|
||||
|
// HFS/Carbon path separator (invalid in filenames on MacOS and Mac filesystems)
|
||||
|
// DOS/Win32 drive separator (invalid in filenames on Windows and Windows filesystems)
|
||||
|
b':' => true,
|
||||
|
// DOS/Windows path separator (invalid in filenames on Windows and Windows filesystems)
|
||||
|
b'\\' => true,
|
||||
|
// let everything else through
|
||||
|
_ => false,
|
||||
|
}) {
|
||||
|
#[allow(clippy::use_debug)]
|
||||
|
return Err(format!("Path component contains invalid characters: {:?}", path).into());
|
||||
|
}
|
||||
|
|
||||
|
// Reserved DOS filenames that still can't be used on modern Windows for compatibility
|
||||
|
if let Some(file_stem) = path.file_stem() {
|
||||
|
let stem = file_stem.to_string_lossy().to_uppercase();
|
||||
|
if RESERVED_DOS_FILENAMES.iter().any(|&x| x == stem) {
|
||||
|
Err(format!("Filename is reserved on Windows: {:?}", file_stem).into())
|
||||
|
} else {
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
} else {
|
||||
|
Ok(())
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
|
||||
|
#[cfg(test)]
|
||||
|
mod tests {
|
||||
|
use super::*;
|
||||
|
use std::ffi::OsStr;
|
||||
|
|
||||
|
#[cfg(unix)]
|
||||
|
use std::os::unix::ffi::OsStrExt;
|
||||
|
#[cfg(windows)]
|
||||
|
use std::os::windows::ffi::OsStringExt;
|
||||
|
|
||||
|
#[test]
|
||||
|
#[cfg(unix)]
|
||||
|
fn path_output_dir_basic_functionality() {
|
||||
|
assert!(path_output_dir(OsStr::new("/")).is_err()); // Root
|
||||
|
assert!(path_output_dir(OsStr::new("/tmp")).is_ok()); // OK Folder
|
||||
|
assert!(path_output_dir(OsStr::new("/dev/null")).is_err()); // OK File
|
||||
|
assert!(path_output_dir(OsStr::new("/etc/shadow")).is_err()); // Denied File
|
||||
|
assert!(path_output_dir(OsStr::new("/etc/ssl/private")).is_err()); // Denied Folder
|
||||
|
assert!(path_output_dir(OsStr::new("/nonexistant_test_path")).is_err()); // Missing Path
|
||||
|
assert!(path_output_dir(OsStr::new("/tmp\0with\0null")).is_err()); // Invalid CString
|
||||
|
// TODO: is_dir but fails to canonicalize()
|
||||
|
// TODO: Not-already-canonicalized paths
|
||||
|
|
||||
|
assert!(path_output_dir(OsStr::from_bytes(b"/not\xffutf8")).is_err()); // Invalid UTF-8
|
||||
|
// TODO: Non-UTF8 path that actually does exist and is writable
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
#[cfg(windows)]
|
||||
|
fn path_output_dir_basic_functionality() {
|
||||
|
unimplemented!("TODO: Implement Windows version of path_output_dir");
|
||||
|
}
|
||||
|
|
||||
|
// ---- path_readable_file ----
|
||||
|
|
||||
|
#[cfg(unix)]
|
||||
|
#[test]
|
||||
|
fn path_readable_file_basic_functionality() {
|
||||
|
// Existing paths
|
||||
|
assert!(path_readable_file(OsStr::new("/bin/sh")).is_ok()); // OK File
|
||||
|
assert!(path_readable_file(OsStr::new("/bin/../etc/.././bin/sh")).is_ok()); // Non-canonic.
|
||||
|
assert!(path_readable_file(OsStr::new("/../../../../bin/sh")).is_ok()); // Above root
|
||||
|
|
||||
|
// Inaccessible, nonexistent, or invalid paths
|
||||
|
assert!(path_readable_file(OsStr::new("")).is_err()); // Empty String
|
||||
|
assert!(path_readable_file(OsStr::new("/")).is_err()); // OK Folder
|
||||
|
assert!(path_readable_file(OsStr::new("/etc/shadow")).is_err()); // Denied File
|
||||
|
assert!(path_readable_file(OsStr::new("/etc/ssl/private")).is_err()); // Denied Foldr
|
||||
|
assert!(path_readable_file(OsStr::new("/nonexistant_test_path")).is_err()); // Missing Path
|
||||
|
assert!(path_readable_file(OsStr::new("/null\0containing")).is_err()); // Invalid CStr
|
||||
|
|
||||
|
}
|
||||
|
#[cfg(windows)]
|
||||
|
#[test]
|
||||
|
fn path_readable_file_basic_functionality() {
|
||||
|
unimplemented!("TODO: Pick some appropriate equivalent test paths for Windows");
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(unix)]
|
||||
|
#[test]
|
||||
|
fn path_readable_file_invalid_utf8() {
|
||||
|
assert!(path_readable_file(OsStr::from_bytes(b"/not\xffutf8")).is_err()); // Invalid UTF-8
|
||||
|
// TODO: Non-UTF8 path that actually IS valid
|
||||
|
}
|
||||
|
#[cfg(windows)]
|
||||
|
#[test]
|
||||
|
fn path_readable_file_unpaired_surrogates() {
|
||||
|
assert!(path_readable_file(&OsString::from_wide(
|
||||
|
&['C' as u16, ':' as u16, '\\' as u16, 0xd800])).is_err());
|
||||
|
// TODO: Unpaired surrogate path that actually IS valid
|
||||
|
}
|
||||
|
|
||||
|
// ---- filename_valid_portable ----
|
||||
|
|
||||
|
const VALID_FILENAMES: &[&str] = &[
|
||||
|
// regular, space, and leading period
|
||||
|
"test1", "te st", ".test",
|
||||
|
// Stuff which would break if the DOS reserved names check is doing dumb pattern matching
|
||||
|
"lpt", "lpt0", "lpt10",
|
||||
|
];
|
||||
|
|
||||
|
// Paths which should pass because std::path::Path will recognize the separators
|
||||
|
// TODO: Actually run the tests on Windows to make sure they work
|
||||
|
#[cfg(windows)]
|
||||
|
const PATHS_WITH_NATIVE_SEPARATORS: &[&str] = &[
|
||||
|
"re/lative", "/ab/solute", "re\\lative", "\\ab\\solute"];
|
||||
|
#[cfg(unix)]
|
||||
|
const PATHS_WITH_NATIVE_SEPARATORS: &[&str] = &["re/lative", "/ab/solute"];
|
||||
|
|
||||
|
// Paths which should fail because std::path::Path won't recognize the separators and we don't
|
||||
|
// want them showing up in the components.
|
||||
|
#[cfg(windows)]
|
||||
|
const PATHS_WITH_FOREIGN_SEPARATORS: &[&str] = &["Classic Mac HD:Folder Name:File"];
|
||||
|
#[cfg(unix)]
|
||||
|
const PATHS_WITH_FOREIGN_SEPARATORS: &[&str] = &[
|
||||
|
"relative\\win32",
|
||||
|
"C:\\absolute\\win32",
|
||||
|
"\\drive\\relative\\win32",
|
||||
|
"\\\\unc\\path\\for\\win32",
|
||||
|
"Classic Mac HD:Folder Name:File",
|
||||
|
];
|
||||
|
|
||||
|
// Source: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file
|
||||
|
const INVALID_PORTABLE_FILENAMES: &[&str] = &[
|
||||
|
"test\x03", "test\x07", "test\x08", "test\x0B", "test\x7f", // Control characters (VFAT)
|
||||
|
"\"test\"", "<testsss", "testsss>", "testsss|", "testsss*", "testsss?", "?estsss", // VFAT
|
||||
|
"ends with space ", "ends_with_period.", // DOS/Win32
|
||||
|
"CON", "Con", "coN", "cOn", "CoN", "con", "lpt1", "com9", // Reserved names (DOS/Win32)
|
||||
|
"con.txt", "lpt1.dat", // DOS/Win32 API (Reserved names are extension agnostic)
|
||||
|
"", "\0"]; // POSIX
|
||||
|
|
||||
|
#[test]
|
||||
|
fn filename_valid_portable_accepts_valid_names() {
|
||||
|
for path in VALID_FILENAMES {
|
||||
|
assert!(filename_valid_portable(OsStr::new(path)).is_ok(), "{:?}", path);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn filename_valid_portable_refuses_path_separators() {
|
||||
|
for path in PATHS_WITH_NATIVE_SEPARATORS {
|
||||
|
assert!(filename_valid_portable(OsStr::new(path)).is_err(), "{:?}", path);
|
||||
|
}
|
||||
|
for path in PATHS_WITH_FOREIGN_SEPARATORS {
|
||||
|
assert!(filename_valid_portable(OsStr::new(path)).is_err(), "{:?}", path);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn filename_valid_portable_refuses_invalid_characters() {
|
||||
|
for fname in INVALID_PORTABLE_FILENAMES {
|
||||
|
assert!(filename_valid_portable(OsStr::new(fname)).is_err(), "{:?}", fname);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn filename_valid_portable_refuses_empty_strings() {
|
||||
|
assert!(filename_valid_portable(OsStr::new("")).is_err());
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn filename_valid_portable_enforces_length_limits() {
|
||||
|
// 256 characters
|
||||
|
let mut test_str = std::str::from_utf8(&[b'X'; 256]).expect("parsing constant");
|
||||
|
assert!(filename_valid_portable(OsStr::new(test_str)).is_err());
|
||||
|
|
||||
|
// 255 characters (maximum for NTFS, ext2/3/4, and a lot of others)
|
||||
|
test_str = std::str::from_utf8(&[b'X'; 255]).expect("parsing constant");
|
||||
|
assert!(filename_valid_portable(OsStr::new(test_str)).is_ok());
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(unix)]
|
||||
|
#[test]
|
||||
|
fn filename_valid_portable_accepts_non_utf8_bytes() {
|
||||
|
// Ensure that we don't refuse invalid UTF-8 that "bag of bytes" POSIX allows
|
||||
|
assert!(filename_valid_portable(OsStr::from_bytes(b"\xff")).is_ok());
|
||||
|
}
|
||||
|
#[cfg(windows)]
|
||||
|
#[test]
|
||||
|
fn filename_valid_portable_accepts_unpaired_surrogates() {
|
||||
|
assert!(path_valid_portable(&OsString::from_wide(&[0xd800])).is_ok());
|
||||
|
}
|
||||
|
|
||||
|
// ---- path_valid_portable ----
|
||||
|
|
||||
|
#[test]
|
||||
|
fn path_valid_portable_accepts_valid_names() {
|
||||
|
for path in VALID_FILENAMES {
|
||||
|
assert!(path_valid_portable(OsStr::new(path)).is_ok(), "{:?}", path);
|
||||
|
}
|
||||
|
|
||||
|
// No filename (.file_stem() returns None)
|
||||
|
assert!(path_valid_portable(OsStr::new("foo/..")).is_ok());
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn path_valid_portable_accepts_native_path_separators() {
|
||||
|
for path in PATHS_WITH_NATIVE_SEPARATORS {
|
||||
|
assert!(path_valid_portable(OsStr::new(path)).is_ok(), "{:?}", path);
|
||||
|
}
|
||||
|
|
||||
|
// Verify that repeated separators are getting collapsed before filename_valid_portable
|
||||
|
// sees them.
|
||||
|
// TODO: Make this conditional on platform and also test repeated backslashes on Windows
|
||||
|
assert!(path_valid_portable(OsStr::new("/path//with/repeated//separators")).is_ok());
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn path_valid_portable_refuses_foreign_path_separators() {
|
||||
|
for path in PATHS_WITH_FOREIGN_SEPARATORS {
|
||||
|
assert!(path_valid_portable(OsStr::new(path)).is_err(), "{:?}", path);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn path_valid_portable_refuses_invalid_characters() {
|
||||
|
for fname in INVALID_PORTABLE_FILENAMES {
|
||||
|
assert!(path_valid_portable(OsStr::new(fname)).is_err(), "{:?}", fname);
|
||||
|
}
|
||||
|
}
|
||||
|
|
||||
|
#[test]
|
||||
|
fn path_valid_portable_enforces_length_limits() {
|
||||
|
let mut test_string = String::with_capacity(255 * 130);
|
||||
|
#[allow(clippy::decimal_literal_representation)]
|
||||
|
while test_string.len() < 32761 {
|
||||
|
test_string.push_str(std::str::from_utf8(&[b'X'; 255]).expect("utf8 from literal"));
|
||||
|
test_string.push('/');
|
||||
|
}
|
||||
|
|
||||
|
// >32760 characters
|
||||
|
assert!(path_valid_portable(OsStr::new(&test_string)).is_err());
|
||||
|
|
||||
|
// 32760 characters (maximum for FAT32/VFAT/exFAT)
|
||||
|
#[allow(clippy::decimal_literal_representation)]
|
||||
|
test_string.truncate(32760);
|
||||
|
assert!(path_valid_portable(OsStr::new(&test_string)).is_ok());
|
||||
|
|
||||
|
// 256 characters with no path separators
|
||||
|
test_string.truncate(255);
|
||||
|
test_string.push('X');
|
||||
|
assert!(path_valid_portable(OsStr::new(&test_string)).is_err());
|
||||
|
|
||||
|
// 255 characters with no path separators
|
||||
|
test_string.truncate(255);
|
||||
|
assert!(path_valid_portable(OsStr::new(&test_string)).is_ok());
|
||||
|
}
|
||||
|
|
||||
|
#[cfg(unix)]
|
||||
|
#[test]
|
||||
|
fn path_valid_portable_accepts_non_utf8_bytes() {
|
||||
|
// Ensure that we don't refuse invalid UTF-8 that "bag of bytes" POSIX allows
|
||||
|
assert!(path_valid_portable(OsStr::from_bytes(b"/\xff/foo")).is_ok());
|
||||
|
}
|
||||
|
#[cfg(windows)]
|
||||
|
#[test]
|
||||
|
fn path_valid_portable_accepts_unpaired_surrogates() {
|
||||
|
assert!(path_valid_portable(&OsString::from_wide(
|
||||
|
&['C' as u16, ':' as u16, '\\' as u16, 0xd800])).is_ok());
|
||||
|
}
|
||||
|
|
||||
|
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue