major changes
- Added documentation via mdbook - Created basic VS code extension - Implemented if else blocks and changed some syntax - fixed some issues
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
target
|
target
|
||||||
|
node_modules
|
||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -271,6 +271,7 @@ name = "rush"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
|
"libc",
|
||||||
"predicates 1.0.8",
|
"predicates 1.0.8",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ tokio = { version = "1", features = ["full"] }
|
|||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
|
|||||||
674
LICENSE.txt
Normal file
674
LICENSE.txt
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://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 <https://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
|
||||||
|
<https://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
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
93
README.md
93
README.md
@@ -1,6 +1,91 @@
|
|||||||
# rush - A Modern Shell Scripting Language Interpreter
|
# Rush
|
||||||
|
|
||||||
## Overview
|
> ⚠️ **AI-Generated Content Notice**: Much of this project's documentation has been generated with AI and may contain errors or incomplete information. This will get replaced as development continues. **This is experimental software and NOT recommended for production use.**
|
||||||
Rush is a modern shell scripting language interpreter designed to execute scripts written in the `.rsh` format. It supports features such as variable assignment, control flow, and parallel execution, making it a powerful tool for automating tasks and scripting.
|
|
||||||
|
|
||||||
More info coming soon
|
Rush is an experimental shell scripting language interpreter that combines simple syntax with strict validation and built-in concurrency.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Variable interpolation** with validation
|
||||||
|
- **Built-in variables** (`$USER`, `$HOME`, `$IS_ROOT`)
|
||||||
|
- **Control flow** with `if`/`else` and `not`
|
||||||
|
- **For loops** for iteration
|
||||||
|
- **Parallel execution** blocks for concurrent tasks
|
||||||
|
- **Strict parsing** that catches errors before execution
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
**Build:**
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
**Your first script** (`hello.rsh`):
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
NAME = "World"
|
||||||
|
echo "Hello, $NAME!"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run:**
|
||||||
|
```bash
|
||||||
|
./target/debug/rush hello.rsh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
📚 **[Read the full documentation](docs/)** (built with mdBook)
|
||||||
|
|
||||||
|
**Build and view docs locally:**
|
||||||
|
```bash
|
||||||
|
mdbook serve docs
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://localhost:3000 in your browser.
|
||||||
|
|
||||||
|
**Or build static HTML:**
|
||||||
|
```bash
|
||||||
|
mdbook build docs
|
||||||
|
# Open docs/book/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Scripts
|
||||||
|
|
||||||
|
The documentation includes comprehensive examples:
|
||||||
|
|
||||||
|
- **Web Deployment** - Multi-server deployment with parallel execution
|
||||||
|
- **System Maintenance** - Permission-aware system tasks
|
||||||
|
- **Data Pipeline** - Parallel batch processing
|
||||||
|
- **CI/CD Pipeline** - Build, test, and deploy automation
|
||||||
|
- **Database Backup** - Backup with integrity verification
|
||||||
|
|
||||||
|
See the [Examples](docs/src/examples/) section in the documentation.
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
**⚠️ Experimental - Not for Production**
|
||||||
|
|
||||||
|
Rush is under active development. The language design and implementation are subject to change. Current capabilities:
|
||||||
|
|
||||||
|
- ✅ Basic scripting features
|
||||||
|
- ✅ Parallel execution
|
||||||
|
- ✅ Variable interpolation
|
||||||
|
- ⚠️ Limited command support
|
||||||
|
- ❌ No package management
|
||||||
|
- ❌ No module system
|
||||||
|
- ❌ Limited error handling
|
||||||
|
|
||||||
|
**Do not use Rush for:**
|
||||||
|
- Production systems
|
||||||
|
- Critical automation
|
||||||
|
- Enterprise deployments
|
||||||
|
- Anything requiring stability
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Found an issue? Have a suggestion? Contributions are welcome! This project is experimental and could benefit from community input.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See LICENSE file for details.
|
||||||
|
|||||||
2
docs/.gitignore
vendored
Normal file
2
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
book
|
||||||
|
docs/book/
|
||||||
14
docs/book.toml
Normal file
14
docs/book.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[book]
|
||||||
|
authors = ["tototomate123"]
|
||||||
|
language = "en"
|
||||||
|
src = "src"
|
||||||
|
title = "Rush Documentation"
|
||||||
|
description = "Documentation for the Rush experimental scripting language"
|
||||||
|
|
||||||
|
[output.html]
|
||||||
|
git-repository-url = "https://github.com/yourusername/rush"
|
||||||
|
edit-url-template = "https://github.com/yourusername/rush/edit/main/docs/{path}"
|
||||||
|
|
||||||
|
[output.html.playground]
|
||||||
|
editable = false
|
||||||
|
copyable = true
|
||||||
24
docs/src/SUMMARY.md
Normal file
24
docs/src/SUMMARY.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Summary
|
||||||
|
|
||||||
|
[Introduction](./introduction.md)
|
||||||
|
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
- [Installation](./getting-started/installation.md)
|
||||||
|
- [Quick Start](./getting-started/quick-start.md)
|
||||||
|
|
||||||
|
# Language Guide
|
||||||
|
|
||||||
|
- [Variables](./language/variables.md)
|
||||||
|
- [Control Flow](./language/control-flow.md)
|
||||||
|
- [Loops](./language/loops.md)
|
||||||
|
- [Parallel Execution](./language/parallel.md)
|
||||||
|
- [Built-in Variables](./language/builtins.md)
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
- [Web Deployment](./examples/web-deploy.md)
|
||||||
|
- [System Maintenance](./examples/system-maintenance.md)
|
||||||
|
- [Data Pipeline](./examples/data-pipeline.md)
|
||||||
|
- [CI/CD Pipeline](./examples/ci-pipeline.md)
|
||||||
|
- [Database Backup](./examples/db-backup.md)
|
||||||
65
docs/src/examples/ci-pipeline.md
Normal file
65
docs/src/examples/ci-pipeline.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# CI/CD Pipeline
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Continuous Integration/Deployment pipeline with parallel testing.
|
||||||
|
|
||||||
|
## Script
|
||||||
|
|
||||||
|
See `/examples/ci_pipeline.rsh` in the repository.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
PROJECT = "my-rust-app"
|
||||||
|
BRANCH = "main"
|
||||||
|
BUILD_ID = "build-12345"
|
||||||
|
|
||||||
|
echo "=== CI/CD Pipeline ==="
|
||||||
|
echo "Project: $PROJECT"
|
||||||
|
|
||||||
|
# Environment validation
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "ERROR: Do not run CI/CD pipeline as root"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build stages (sequential)
|
||||||
|
for stage in checkout lint test build package {
|
||||||
|
echo "[$BUILD_ID] Stage: $stage"
|
||||||
|
if $stage {
|
||||||
|
echo "[$BUILD_ID] ✓ $stage completed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run tests in parallel
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[unit-tests] Running unit tests..."
|
||||||
|
echo "[unit-tests] 127 tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[integration-tests] Running integration tests..."
|
||||||
|
echo "[integration-tests] 45 tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[lint-check] Running cargo clippy..."
|
||||||
|
echo "[lint-check] No warnings found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Multi-environment deployment
|
||||||
|
for env in dev staging prod {
|
||||||
|
DEPLOY_URL = "https://$PROJECT.$env.example.com"
|
||||||
|
echo "[$env] Deployment target: $DEPLOY_URL"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Security check**: Refuse to run as root
|
||||||
|
- **Sequential build**: Steps that depend on each other
|
||||||
|
- **Parallel tests**: Independent test suites run simultaneously
|
||||||
|
- **Multi-environment deployment**: Same code deployed to different environments
|
||||||
52
docs/src/examples/data-pipeline.md
Normal file
52
docs/src/examples/data-pipeline.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Data Pipeline
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Data processing pipeline showing how to process multiple batches in parallel.
|
||||||
|
|
||||||
|
## Script
|
||||||
|
|
||||||
|
See `/examples/data_pipeline.rsh` in the repository.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
DATASET = "user_analytics"
|
||||||
|
INPUT_DIR = "$HOME/data/raw"
|
||||||
|
OUTPUT_DIR = "$HOME/data/processed"
|
||||||
|
BATCH_SIZE = "1000"
|
||||||
|
|
||||||
|
echo "Data Processing Pipeline: $DATASET"
|
||||||
|
|
||||||
|
# Pre-processing stages
|
||||||
|
for stage in validate clean normalize {
|
||||||
|
STAGE_UPPER = "$stage"
|
||||||
|
echo " Stage: $STAGE_UPPER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process batches in parallel
|
||||||
|
BATCH_1_IN = "$INPUT_DIR/batch_001.csv"
|
||||||
|
BATCH_1_OUT = "$OUTPUT_DIR/batch_001.json"
|
||||||
|
# ... (define other batches)
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[batch_001] Processing $BATCH_1_IN -> $BATCH_1_OUT"
|
||||||
|
echo "[batch_001] Transformed 1000 records"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[batch_002] Processing $BATCH_2_IN -> $BATCH_2_OUT"
|
||||||
|
echo "[batch_002] Transformed 1000 records"
|
||||||
|
}
|
||||||
|
# ... (more batches)
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All batches processed successfully"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Parallel data processing**: Process multiple batches simultaneously
|
||||||
|
- **Path construction**: Building input/output file paths
|
||||||
|
- **Pipeline stages**: Sequential setup, parallel processing, sequential summary
|
||||||
72
docs/src/examples/db-backup.md
Normal file
72
docs/src/examples/db-backup.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Database Backup
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Database backup automation with validation and integrity checks.
|
||||||
|
|
||||||
|
## Script
|
||||||
|
|
||||||
|
See `/examples/db_backup.rsh` in the repository.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
DB_NAME = "production_db"
|
||||||
|
DB_HOST = "db.example.com"
|
||||||
|
BACKUP_ROOT = "/backups/databases"
|
||||||
|
BACKUP_PATH = "$BACKUP_ROOT/$DB_NAME"
|
||||||
|
TIMESTAMP = "2024-01-15-143022"
|
||||||
|
BACKUP_FILE = "$BACKUP_PATH/backup-$TIMESTAMP.sql.gz"
|
||||||
|
|
||||||
|
echo "Database Backup Script"
|
||||||
|
|
||||||
|
# Permission check
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Running as $USER (not root)"
|
||||||
|
echo "Ensure $USER has database access"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pre-backup validation
|
||||||
|
for check in connectivity disk_space permissions {
|
||||||
|
echo " Checking $check..."
|
||||||
|
if $check {
|
||||||
|
echo " ✓ $check OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backup stages
|
||||||
|
for stage in dump compress encrypt verify {
|
||||||
|
STAGE_FILE = "$BACKUP_PATH/$stage.tmp"
|
||||||
|
echo "[$stage] Processing..."
|
||||||
|
if $stage {
|
||||||
|
echo "[$stage] ✓ Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parallel integrity verification
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[checksum] Computing SHA256..."
|
||||||
|
echo "[checksum] a1b2c3d4e5f6..."
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[compression] Verifying gzip integrity..."
|
||||||
|
echo "[compression] OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[restore-test] Testing restore on sample..."
|
||||||
|
echo "[restore-test] Restore successful"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Backup completed successfully!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Pre-flight validation**: Check prerequisites before starting
|
||||||
|
- **Multi-stage processing**: Sequential backup pipeline
|
||||||
|
- **Parallel verification**: Simultaneously verify different aspects
|
||||||
|
- **Comprehensive reporting**: Clear status messages throughout
|
||||||
51
docs/src/examples/system-maintenance.md
Normal file
51
docs/src/examples/system-maintenance.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# System Maintenance
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Example system maintenance script demonstrating permission checks, environment variables, and log rotation.
|
||||||
|
|
||||||
|
## Script
|
||||||
|
|
||||||
|
See the full script in `/examples/system_maintenance.rsh` in the repository.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
HOSTNAME = "prod-server-01"
|
||||||
|
MAX_DISK_USAGE = "85"
|
||||||
|
LOG_DIR = "/var/log"
|
||||||
|
BACKUP_DIR = "$HOME/backups"
|
||||||
|
|
||||||
|
echo "System Maintenance Script"
|
||||||
|
echo "Running on: $HOSTNAME"
|
||||||
|
echo "User: $USER"
|
||||||
|
|
||||||
|
# Permission-aware execution
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Running with root privileges"
|
||||||
|
|
||||||
|
for check in disk memory services {
|
||||||
|
echo "Checking $check status..."
|
||||||
|
if $check {
|
||||||
|
echo " ✓ $check is healthy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "Running as $USER (limited permissions)"
|
||||||
|
echo "Some checks may be skipped"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log rotation planning
|
||||||
|
echo "Preparing log rotation in $LOG_DIR"
|
||||||
|
|
||||||
|
for days in 1 7 30 {
|
||||||
|
LOG_ARCHIVE = "$BACKUP_DIR/logs-$days-days-old.tar.gz"
|
||||||
|
echo "Would archive logs older than $days days to $LOG_ARCHIVE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
- **Permission-aware execution**: Different behavior for root vs normal user
|
||||||
|
- **String interpolation**: Building paths with `$HOME`, `$USER`
|
||||||
|
- **Loops for maintenance tasks**: Iterating over checks and retention periods
|
||||||
184
docs/src/examples/web-deploy.md
Normal file
184
docs/src/examples/web-deploy.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# Web Deployment
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
This example demonstrates a web application deployment pipeline using Rush's parallel execution capabilities.
|
||||||
|
|
||||||
|
## Full Script
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Web deployment pipeline example
|
||||||
|
# Shows conditional logic, loops, and parallel execution
|
||||||
|
|
||||||
|
APP_NAME = "my-app"
|
||||||
|
VERSION = "1.2.3"
|
||||||
|
ENV = "staging"
|
||||||
|
DEPLOY_DIR = "/var/www/$APP_NAME"
|
||||||
|
BUILD_DIR = "./dist"
|
||||||
|
|
||||||
|
echo "Starting deployment for $APP_NAME v$VERSION to $ENV"
|
||||||
|
|
||||||
|
# Check prerequisites
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Not running as root. Some operations may fail."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build steps
|
||||||
|
STEPS = "lint test build"
|
||||||
|
for step in lint test build {
|
||||||
|
echo "[$step] Running..."
|
||||||
|
|
||||||
|
if $step {
|
||||||
|
# Simulate the build step
|
||||||
|
echo "[$step] Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy in parallel
|
||||||
|
echo "Deploying to multiple servers..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Server 1: Syncing files to web-1.$ENV.example.com"
|
||||||
|
echo "Server 1: Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Server 2: Syncing files to web-2.$ENV.example.com"
|
||||||
|
echo "Server 2: Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Server 3: Syncing files to web-3.$ENV.example.com"
|
||||||
|
echo "Server 3: Deployment complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All servers updated with $APP_NAME v$VERSION"
|
||||||
|
|
||||||
|
# Post-deployment checks
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
FULL_HOST = "$server.$ENV.example.com"
|
||||||
|
echo "Health check: $FULL_HOST - OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Deployment complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features Demonstrated
|
||||||
|
|
||||||
|
### 1. Configuration Variables
|
||||||
|
|
||||||
|
The script starts by defining configuration:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
APP_NAME = "my-app"
|
||||||
|
VERSION = "1.2.3"
|
||||||
|
ENV = "staging"
|
||||||
|
DEPLOY_DIR = "/var/www/$APP_NAME"
|
||||||
|
```
|
||||||
|
|
||||||
|
These variables are used throughout the script, making it easy to adapt for different applications.
|
||||||
|
|
||||||
|
### 2. Permission Check
|
||||||
|
|
||||||
|
```rush
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Not running as root. Some operations may fail."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The script warns if not running as root, but continues anyway (since deployment might work without root depending on file permissions).
|
||||||
|
|
||||||
|
### 3. Sequential Build Steps
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for step in lint test build {
|
||||||
|
echo "[$step] Running..."
|
||||||
|
|
||||||
|
if $step {
|
||||||
|
echo "[$step] Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Build steps run sequentially since each step depends on the previous one completing successfully.
|
||||||
|
|
||||||
|
### 4. Parallel Server Deployment
|
||||||
|
|
||||||
|
```rush
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Server 1: Syncing files to web-1.$ENV.example.com"
|
||||||
|
echo "Server 1: Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Server 2: Syncing files to web-2.$ENV.example.com"
|
||||||
|
echo "Server 2: Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Server 3: Syncing files to web-3.$ENV.example.com"
|
||||||
|
echo "Server 3: Deployment complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Servers are updated in parallel since they're independent. This saves significant time vs deploying sequentially.
|
||||||
|
|
||||||
|
### 5. Post-Deployment Verification
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
FULL_HOST = "$server.$ENV.example.com"
|
||||||
|
echo "Health check: $FULL_HOST - OK"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After deployment, verify each server is healthy.
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Starting deployment for my-app v1.2.3 to staging
|
||||||
|
Warning: Not running as root. Some operations may fail.
|
||||||
|
[lint] Running...
|
||||||
|
[lint] Complete
|
||||||
|
[test] Running...
|
||||||
|
[test] Complete
|
||||||
|
[build] Running...
|
||||||
|
[build] Complete
|
||||||
|
Deploying to multiple servers...
|
||||||
|
Server 1: Syncing files to web-1.staging.example.com
|
||||||
|
Server 2: Syncing files to web-2.staging.example.com
|
||||||
|
Server 3: Syncing files to web-3.staging.example.com
|
||||||
|
Server 1: Deployment complete
|
||||||
|
Server 2: Deployment complete
|
||||||
|
Server 3: Deployment complete
|
||||||
|
All servers updated with my-app v1.2.3
|
||||||
|
Health check: web-1.staging.example.com - OK
|
||||||
|
Health check: web-2.staging.example.com - OK
|
||||||
|
Health check: web-3.staging.example.com - OK
|
||||||
|
Deployment complete!
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Output from parallel tasks may be interleaved.
|
||||||
|
|
||||||
|
## Adapting for Your Use
|
||||||
|
|
||||||
|
To use this for real deployments:
|
||||||
|
|
||||||
|
1. **Change configuration variables** to match your app
|
||||||
|
2. **Replace echo with actual commands** (rsync, scp, etc.)
|
||||||
|
3. **Add error handling** (check command exit codes)
|
||||||
|
4. **Customize server list** for your infrastructure
|
||||||
|
5. **Add real health checks** (curl, wget, etc.)
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [Parallel Execution](../language/parallel.md) - Learn more about parallel blocks
|
||||||
|
- [Loops](../language/loops.md) - Sequential iteration patterns
|
||||||
|
- [Variables](../language/variables.md) - Working with configuration
|
||||||
72
docs/src/getting-started/installation.md
Normal file
72
docs/src/getting-started/installation.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Rush is written in Rust, so you'll need:
|
||||||
|
- Rust toolchain (rustc, cargo)
|
||||||
|
- Git (to clone the repository)
|
||||||
|
|
||||||
|
## Installing Rust
|
||||||
|
|
||||||
|
If you don't have Rust installed, get it from [rustup.rs](https://rustup.rs/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building Rush
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/rush.git
|
||||||
|
cd rush
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build the project:
|
||||||
|
```bash
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The binary will be available at `./target/release/rush`
|
||||||
|
|
||||||
|
## Optional: Install System-Wide
|
||||||
|
|
||||||
|
To make Rush available system-wide:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install --path .
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs Rush to `~/.cargo/bin/rush` (make sure `~/.cargo/bin` is in your PATH).
|
||||||
|
|
||||||
|
## Verifying Installation
|
||||||
|
|
||||||
|
Test your installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./target/release/rush --version
|
||||||
|
# or if installed system-wide:
|
||||||
|
rush --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a test script `hello.rsh`:
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
NAME = "World"
|
||||||
|
echo "Hello, $NAME!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Make it executable and run it:
|
||||||
|
```bash
|
||||||
|
chmod +x hello.rsh
|
||||||
|
./target/release/rush hello.rsh
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see: `Hello, World!`
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Now that Rush is installed, proceed to the [Quick Start](./quick-start.md) guide.
|
||||||
172
docs/src/getting-started/quick-start.md
Normal file
172
docs/src/getting-started/quick-start.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
# Quick Start
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
This guide will walk you through creating your first Rush script.
|
||||||
|
|
||||||
|
## Your First Script
|
||||||
|
|
||||||
|
Create a file called `hello.rsh`:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Variables are assigned with =
|
||||||
|
NAME = "Rush"
|
||||||
|
echo "Hello from $NAME!"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it:
|
||||||
|
```bash
|
||||||
|
rush hello.rsh
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Hello from Rush!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variables and Interpolation
|
||||||
|
|
||||||
|
Variables are automatically interpolated in strings:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
PROJECT = "my-app"
|
||||||
|
VERSION = "1.0.0"
|
||||||
|
FULL_NAME = "$PROJECT-v$VERSION"
|
||||||
|
|
||||||
|
echo "Building $FULL_NAME"
|
||||||
|
# Output: Building my-app-v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Built-in Variables
|
||||||
|
|
||||||
|
Rush provides several built-in variables:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Current user: $USER"
|
||||||
|
echo "Home directory: $HOME"
|
||||||
|
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Running as root"
|
||||||
|
} else {
|
||||||
|
echo "Running as normal user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional Logic
|
||||||
|
|
||||||
|
Use `if`/`else` for branching:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
ENV = "production"
|
||||||
|
|
||||||
|
if $ENV {
|
||||||
|
echo "Environment: $ENV"
|
||||||
|
}
|
||||||
|
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Not running as root"
|
||||||
|
} else {
|
||||||
|
echo "Running with elevated privileges"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loops
|
||||||
|
|
||||||
|
Iterate over items with `for`:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Loop over space-separated items
|
||||||
|
for name in Alice Bob Charlie {
|
||||||
|
echo "Hello, $name!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use variables in loops
|
||||||
|
SERVERS = "web-1 web-2 web-3"
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
echo "Deploying to $server"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parallel Execution
|
||||||
|
|
||||||
|
Run tasks concurrently:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Task 1: Starting..."
|
||||||
|
echo "Task 1: Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Task 2: Starting..."
|
||||||
|
echo "Task 2: Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Task 3: Starting..."
|
||||||
|
echo "Task 3: Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All tasks finished"
|
||||||
|
```
|
||||||
|
|
||||||
|
The output from parallel blocks may be interleaved since they run concurrently.
|
||||||
|
|
||||||
|
## A Complete Example
|
||||||
|
|
||||||
|
Here's a more complete script showing multiple features:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
PROJECT = "web-app"
|
||||||
|
ENV = "staging"
|
||||||
|
DEPLOY_DIR = "/var/www/$PROJECT"
|
||||||
|
|
||||||
|
echo "Deployment Script for $PROJECT"
|
||||||
|
echo "Environment: $ENV"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Pre-flight checks
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: Not root. Some operations may fail."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build steps
|
||||||
|
echo "Running build steps:"
|
||||||
|
for step in lint test build {
|
||||||
|
echo " - $step"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy to multiple servers in parallel
|
||||||
|
echo ""
|
||||||
|
echo "Deploying to $ENV servers..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[server-1] Deploying $PROJECT"
|
||||||
|
echo "[server-1] Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[server-2] Deploying $PROJECT"
|
||||||
|
echo "[server-2] Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Deployment complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Learn more about Rush's features:
|
||||||
|
- [Variables](../language/variables.md) - Deep dive into variable handling
|
||||||
|
- [Control Flow](../language/control-flow.md) - Conditionals and branching
|
||||||
|
- [Loops](../language/loops.md) - Iteration patterns
|
||||||
|
- [Parallel Execution](../language/parallel.md) - Concurrency in Rush
|
||||||
85
docs/src/introduction.md
Normal file
85
docs/src/introduction.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Rush Documentation
|
||||||
|
|
||||||
|
Welcome to the Rush programming language documentation!
|
||||||
|
|
||||||
|
> ⚠️ **AI-Generated Documentation Notice**
|
||||||
|
>
|
||||||
|
> This documentation has been primarily generated by AI and may contain errors, inconsistencies, or incomplete information. Rush is an experimental project and is **not recommended for production use** at this time.
|
||||||
|
>
|
||||||
|
> If you find issues or have suggestions, please contribute to improving this documentation.
|
||||||
|
|
||||||
|
## What is Rush?
|
||||||
|
|
||||||
|
Rush is an experimental shell scripting language that combines:
|
||||||
|
|
||||||
|
- **Simple syntax** inspired by modern scripting languages
|
||||||
|
- **Strict validation** to catch errors at parse time
|
||||||
|
- **Built-in concurrency** with parallel execution blocks
|
||||||
|
- **Type-aware variables** with automatic interpolation
|
||||||
|
- **Control flow** familiar to programmers (if/else, for loops)
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Variable Interpolation
|
||||||
|
Variables are automatically expanded in strings:
|
||||||
|
```rush
|
||||||
|
PROJECT = "my-app"
|
||||||
|
DIR = "$HOME/projects/$PROJECT"
|
||||||
|
echo $DIR # Outputs: /home/user/projects/my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parallel Execution
|
||||||
|
Run tasks concurrently with ease:
|
||||||
|
```rush
|
||||||
|
parallel {
|
||||||
|
run { echo "Task 1" }
|
||||||
|
run { echo "Task 2" }
|
||||||
|
run { echo "Task 3" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Strict Validation
|
||||||
|
Rush validates your scripts before execution:
|
||||||
|
- Variables must be defined before use
|
||||||
|
- Syntax errors are caught immediately
|
||||||
|
- Clear error messages guide you to fixes
|
||||||
|
|
||||||
|
### Built-in Variables
|
||||||
|
Access system information easily:
|
||||||
|
- `$USER` - Current username
|
||||||
|
- `$HOME` - Home directory
|
||||||
|
- `$IS_ROOT` - Whether running as root
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
Rush is in **early experimental development**. The language design and implementation are subject to change. Current capabilities include:
|
||||||
|
|
||||||
|
- ✅ Variable assignment and interpolation
|
||||||
|
- ✅ Conditional execution (if/else)
|
||||||
|
- ✅ For loops
|
||||||
|
- ✅ Parallel execution blocks
|
||||||
|
- ✅ Basic built-in commands (echo, exit, require_root)
|
||||||
|
- ⚠️ Limited external command support
|
||||||
|
- ❌ No package management
|
||||||
|
- ❌ No module system
|
||||||
|
- ❌ Limited error handling
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
Rush is designed for:
|
||||||
|
- **Learning** parallel programming concepts
|
||||||
|
- **Experimenting** with shell script alternatives
|
||||||
|
- **Prototyping** automation workflows
|
||||||
|
- **Exploring** strict validation in scripting languages
|
||||||
|
|
||||||
|
**Not recommended for:**
|
||||||
|
- Production systems
|
||||||
|
- Critical automation
|
||||||
|
- Enterprise deployments
|
||||||
|
- Anything requiring stability
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Installation](./getting-started/installation.md) - Get Rush set up
|
||||||
|
- [Quick Start](./getting-started/quick-start.md) - Your first Rush script
|
||||||
|
- [Language Guide](./language/variables.md) - Learn Rush syntax
|
||||||
282
docs/src/language/builtins.md
Normal file
282
docs/src/language/builtins.md
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# Built-in Variables
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Rush provides several built-in variables that give you access to system information.
|
||||||
|
|
||||||
|
## Available Built-ins
|
||||||
|
|
||||||
|
### `$USER`
|
||||||
|
|
||||||
|
The current username.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Running as: $USER"
|
||||||
|
# Output: Running as: alice
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- Logging who ran a script
|
||||||
|
- User-specific configuration
|
||||||
|
- Checking if running as a specific user
|
||||||
|
|
||||||
|
```rush
|
||||||
|
EXPECTED_USER = "deploy"
|
||||||
|
|
||||||
|
if not $USER {
|
||||||
|
echo "ERROR: USER not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Script executed by: $USER"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `$HOME`
|
||||||
|
|
||||||
|
The current user's home directory.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Home directory: $HOME"
|
||||||
|
# Output: Home directory: /home/alice
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- Building paths to user files
|
||||||
|
- Configuration file locations
|
||||||
|
- User-specific data directories
|
||||||
|
|
||||||
|
```rush
|
||||||
|
CONFIG_DIR = "$HOME/.config/myapp"
|
||||||
|
DATA_DIR = "$HOME/.local/share/myapp"
|
||||||
|
CACHE_DIR = "$HOME/.cache/myapp"
|
||||||
|
|
||||||
|
echo "Configuration: $CONFIG_DIR"
|
||||||
|
echo "Data: $DATA_DIR"
|
||||||
|
echo "Cache: $CACHE_DIR"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `$IS_ROOT`
|
||||||
|
|
||||||
|
Boolean indicating if the script is running as root (UID 0).
|
||||||
|
|
||||||
|
```rush
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Running with root privileges"
|
||||||
|
} else {
|
||||||
|
echo "Running as normal user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use cases:**
|
||||||
|
- Permission validation
|
||||||
|
- Security checks
|
||||||
|
- Conditional behavior based on privileges
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Require root for system operations
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "ERROR: This script must be run as root"
|
||||||
|
echo "Please run with: sudo rush script.rsh"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Proceeding with system modifications..."
|
||||||
|
```
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Warn but don't fail
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "WARNING: Not running as root"
|
||||||
|
echo "Some operations may fail"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Continuing anyway..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### User-Specific Paths
|
||||||
|
|
||||||
|
```rush
|
||||||
|
PROJECT = "myapp"
|
||||||
|
CONFIG_FILE = "$HOME/.config/$PROJECT/settings.conf"
|
||||||
|
LOG_FILE = "$HOME/.local/share/$PROJECT/app.log"
|
||||||
|
|
||||||
|
echo "Config: $CONFIG_FILE"
|
||||||
|
echo "Logs: $LOG_FILE"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Validation
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Validate we have the right permissions
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "This script requires root privileges"
|
||||||
|
echo "Current user: $USER"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Running as root - proceeding with installation"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Information
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "=== Environment Information ==="
|
||||||
|
echo "User: $USER"
|
||||||
|
echo "Home: $HOME"
|
||||||
|
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Privileges: root"
|
||||||
|
} else {
|
||||||
|
echo "Privileges: normal user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conditional Behavior
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Different behavior for root vs normal user
|
||||||
|
if $IS_ROOT {
|
||||||
|
INSTALL_DIR = "/opt/myapp"
|
||||||
|
CONFIG_DIR = "/etc/myapp"
|
||||||
|
} else {
|
||||||
|
INSTALL_DIR = "$HOME/.local/share/myapp"
|
||||||
|
CONFIG_DIR = "$HOME/.config/myapp"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Installing to: $INSTALL_DIR"
|
||||||
|
echo "Config at: $CONFIG_DIR"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Built-in Commands
|
||||||
|
|
||||||
|
In addition to variables, Rush provides built-in commands:
|
||||||
|
|
||||||
|
### `echo`
|
||||||
|
|
||||||
|
Print output to stdout.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Hello, World!"
|
||||||
|
echo "Multiple arguments" "are joined" "with spaces"
|
||||||
|
|
||||||
|
NAME = "Alice"
|
||||||
|
echo "Hello, $NAME!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `exit`
|
||||||
|
|
||||||
|
Exit the script with a status code.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Exit with success
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
# Exit with error
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# Exit with custom code
|
||||||
|
CODE = "42"
|
||||||
|
exit $CODE
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common pattern:**
|
||||||
|
```rush
|
||||||
|
if not $REQUIRED_VAR {
|
||||||
|
echo "ERROR: REQUIRED_VAR must be set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Continuing..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### `require_root`
|
||||||
|
|
||||||
|
Built-in helper that ensures the script is running as root, exiting if not.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# This will exit if not root
|
||||||
|
require_root
|
||||||
|
|
||||||
|
echo "This only runs as root"
|
||||||
|
```
|
||||||
|
|
||||||
|
Equivalent to:
|
||||||
|
```rush
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "This script must be run as root"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Currently, Rush built-in variables are limited to `$USER`, `$HOME`, and `$IS_ROOT`.
|
||||||
|
|
||||||
|
**Not currently supported:**
|
||||||
|
- ❌ `$PATH` or other environment variables
|
||||||
|
- ❌ `$PWD` (current working directory)
|
||||||
|
- ❌ `$SHELL` (user's shell)
|
||||||
|
- ❌ Custom environment variable access (like `$MY_VAR` from the environment)
|
||||||
|
|
||||||
|
### Workaround
|
||||||
|
|
||||||
|
For now, if you need other environment variables, you would need to run external commands to get them (if supported).
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Always check built-ins exist** - Validate `$USER` and `$HOME` are set
|
||||||
|
2. **Validate permissions early** - Check `$IS_ROOT` at script start
|
||||||
|
3. **Provide clear error messages** - Tell users what went wrong
|
||||||
|
4. **Document requirements** - Make permission requirements clear in comments
|
||||||
|
5. **Use meaningful exit codes** - Exit with non-zero on errors
|
||||||
|
|
||||||
|
### Example Script Template
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Script: system-setup.rsh
|
||||||
|
# Description: Configure system settings
|
||||||
|
# Requirements: Must run as root
|
||||||
|
|
||||||
|
# Validate environment
|
||||||
|
if not $USER {
|
||||||
|
echo "ERROR: USER not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if not $HOME {
|
||||||
|
echo "ERROR: HOME not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check permissions
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "ERROR: This script must be run as root"
|
||||||
|
echo "Current user: $USER"
|
||||||
|
echo "Please run: sudo rush system-setup.rsh"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main script logic
|
||||||
|
echo "Configuring system as $USER (root)"
|
||||||
|
# ... rest of script ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential additions:
|
||||||
|
|
||||||
|
- Access to all environment variables
|
||||||
|
- `$PWD` for current directory
|
||||||
|
- `$HOSTNAME` for system name
|
||||||
|
- `$SHELL` for user's shell
|
||||||
|
- Function to read environment: `env("VAR_NAME")`
|
||||||
|
- Process ID: `$PID`
|
||||||
|
- Parent process ID: `$PPID`
|
||||||
|
|
||||||
|
These are not currently implemented.
|
||||||
214
docs/src/language/control-flow.md
Normal file
214
docs/src/language/control-flow.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# Control Flow
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Rush provides conditional execution through `if`/`else` statements.
|
||||||
|
|
||||||
|
## Basic If Statements
|
||||||
|
|
||||||
|
The simplest form checks if a variable is defined and non-empty:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
USER_NAME = "Alice"
|
||||||
|
|
||||||
|
if $USER_NAME {
|
||||||
|
echo "Hello, $USER_NAME!"
|
||||||
|
}
|
||||||
|
# Output: Hello, Alice!
|
||||||
|
```
|
||||||
|
|
||||||
|
## If-Else
|
||||||
|
|
||||||
|
Add an `else` branch for alternative execution:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
ENV = "production"
|
||||||
|
|
||||||
|
if $ENV {
|
||||||
|
echo "Environment: $ENV"
|
||||||
|
} else {
|
||||||
|
echo "No environment set"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Negation with `not`
|
||||||
|
|
||||||
|
Use `not` to check if a variable is empty or undefined:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Not running as root"
|
||||||
|
} else {
|
||||||
|
echo "Running as root"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Condition Evaluation
|
||||||
|
|
||||||
|
In Rush, conditions are evaluated as follows:
|
||||||
|
|
||||||
|
- **Truthy:** Variable exists and has a non-empty value
|
||||||
|
- **Falsy:** Variable is empty or undefined
|
||||||
|
|
||||||
|
```rush
|
||||||
|
EMPTY = ""
|
||||||
|
FILLED = "data"
|
||||||
|
|
||||||
|
if $FILLED {
|
||||||
|
echo "This runs" # ✅ Runs
|
||||||
|
}
|
||||||
|
|
||||||
|
if $EMPTY {
|
||||||
|
echo "This doesn't run" # ❌ Skipped
|
||||||
|
}
|
||||||
|
|
||||||
|
if not $EMPTY {
|
||||||
|
echo "Empty variable detected" # ✅ Runs
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Permission Checks
|
||||||
|
|
||||||
|
```rush
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Warning: This script should be run as root"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Proceeding with root privileges..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Validation
|
||||||
|
|
||||||
|
```rush
|
||||||
|
REQUIRED_VAR = "" # Simulating an undefined/empty variable
|
||||||
|
|
||||||
|
if not $REQUIRED_VAR {
|
||||||
|
echo "ERROR: REQUIRED_VAR must be set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Configuration valid"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature Flags
|
||||||
|
|
||||||
|
```rush
|
||||||
|
DEBUG_MODE = "true"
|
||||||
|
|
||||||
|
if $DEBUG_MODE {
|
||||||
|
echo "Debug mode enabled"
|
||||||
|
echo "Verbose logging active"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Step Validation
|
||||||
|
|
||||||
|
```rush
|
||||||
|
CONFIG_FILE = "/etc/myapp/config.conf"
|
||||||
|
DATA_DIR = "/var/lib/myapp"
|
||||||
|
|
||||||
|
if $CONFIG_FILE {
|
||||||
|
echo "Config: $CONFIG_FILE"
|
||||||
|
|
||||||
|
if $DATA_DIR {
|
||||||
|
echo "Data directory: $DATA_DIR"
|
||||||
|
echo "All paths configured"
|
||||||
|
} else {
|
||||||
|
echo "ERROR: Data directory not set"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "ERROR: Config file not set"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nested Conditions
|
||||||
|
|
||||||
|
You can nest `if` statements:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
ENV = "production"
|
||||||
|
BACKUP_ENABLED = "yes"
|
||||||
|
|
||||||
|
if $ENV {
|
||||||
|
echo "Environment: $ENV"
|
||||||
|
|
||||||
|
if $BACKUP_ENABLED {
|
||||||
|
echo "Backups are enabled"
|
||||||
|
} else {
|
||||||
|
echo "Warning: Backups disabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of Rush conditionals:
|
||||||
|
|
||||||
|
- ❌ No `elif` or `else if` (use nested `if` instead)
|
||||||
|
- ❌ No comparison operators (`==`, `!=`, `<`, `>`)
|
||||||
|
- ❌ No logical operators (`&&`, `||`) beyond `not`
|
||||||
|
- ❌ No string matching or regex
|
||||||
|
- ❌ No numeric comparisons
|
||||||
|
- ❌ Can only test variable truthiness
|
||||||
|
|
||||||
|
### Workaround Example
|
||||||
|
|
||||||
|
Since there's no `elif`, use nested conditions:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
MODE = "production"
|
||||||
|
|
||||||
|
if $MODE {
|
||||||
|
# Check what MODE actually is would require comparison operators
|
||||||
|
# For now, any non-empty value is truthy
|
||||||
|
echo "Mode is set to: $MODE"
|
||||||
|
} else {
|
||||||
|
echo "Mode not set, using default"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Keep conditions simple** - Rush conditionals are basic, so keep logic straightforward
|
||||||
|
2. **Use descriptive variable names** - Make intent clear since you can't use complex expressions
|
||||||
|
3. **Validate early** - Check required conditions at the start of your script
|
||||||
|
4. **Provide clear messages** - Echo helpful information in each branch
|
||||||
|
5. **Use exit codes** - Exit with non-zero status on errors
|
||||||
|
|
||||||
|
### Example: Script Validation
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Validate environment
|
||||||
|
if not $USER {
|
||||||
|
echo "ERROR: USER not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if not $HOME {
|
||||||
|
echo "ERROR: HOME not set"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proceed with script
|
||||||
|
echo "Environment validated for $USER"
|
||||||
|
echo "Home directory: $HOME"
|
||||||
|
|
||||||
|
# Execute main logic here...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
Potential future enhancements to control flow:
|
||||||
|
|
||||||
|
- `elif` for multi-way branching
|
||||||
|
- Comparison operators for string/numeric comparison
|
||||||
|
- Logical operators for combining conditions
|
||||||
|
- Pattern matching
|
||||||
|
- Case/switch statements
|
||||||
|
|
||||||
|
These are not currently implemented.
|
||||||
264
docs/src/language/loops.md
Normal file
264
docs/src/language/loops.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
# Loops
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Rush supports iteration through `for` loops.
|
||||||
|
|
||||||
|
## Basic For Loop
|
||||||
|
|
||||||
|
Loop over a space-separated list of items:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for name in Alice Bob Charlie {
|
||||||
|
echo "Hello, $name!"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Hello, Alice!
|
||||||
|
Hello, Bob!
|
||||||
|
Hello, Charlie!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loop Variable
|
||||||
|
|
||||||
|
The loop variable takes on each value in sequence:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for number in 1 2 3 4 5 {
|
||||||
|
echo "Number: $number"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Number: 1
|
||||||
|
Number: 2
|
||||||
|
Number: 3
|
||||||
|
Number: 4
|
||||||
|
Number: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## Iterating Over Configuration
|
||||||
|
|
||||||
|
Common pattern for processing multiple items:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
SERVERS = "web-1 web-2 web-3"
|
||||||
|
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
echo "Deploying to $server"
|
||||||
|
# Deployment commands here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Variables in Loops
|
||||||
|
|
||||||
|
You can use variables within the loop body:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
ENV = "production"
|
||||||
|
BASE_URL = "https://api.example.com"
|
||||||
|
|
||||||
|
for endpoint in users posts comments {
|
||||||
|
URL = "$BASE_URL/$endpoint"
|
||||||
|
echo "Checking $URL in $ENV"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nested Loops
|
||||||
|
|
||||||
|
Loops can be nested for multi-dimensional iteration:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for env in dev staging prod {
|
||||||
|
echo "Environment: $env"
|
||||||
|
|
||||||
|
for server in web-1 web-2 {
|
||||||
|
HOSTNAME = "$server.$env.example.com"
|
||||||
|
echo " - $HOSTNAME"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
Environment: dev
|
||||||
|
- web-1.dev.example.com
|
||||||
|
- web-2.dev.example.com
|
||||||
|
Environment: staging
|
||||||
|
- web-1.staging.example.com
|
||||||
|
- web-2.staging.example.com
|
||||||
|
Environment: prod
|
||||||
|
- web-1.prod.example.com
|
||||||
|
- web-2.prod.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditionals in Loops
|
||||||
|
|
||||||
|
Combine loops with `if` statements:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for item in file1 file2 file3 {
|
||||||
|
if $item {
|
||||||
|
echo "Processing $item"
|
||||||
|
# Process the file
|
||||||
|
} else {
|
||||||
|
echo "Skipping empty item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Processing Files
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for file in config.yml database.yml secrets.yml {
|
||||||
|
FILE_PATH = "/etc/app/$file"
|
||||||
|
echo "Loading $FILE_PATH"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Stage Pipeline
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Build Pipeline"
|
||||||
|
|
||||||
|
for stage in fetch compile test package deploy {
|
||||||
|
echo "[$stage] Starting..."
|
||||||
|
# Stage-specific logic here
|
||||||
|
echo "[$stage] Complete"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Configuration
|
||||||
|
|
||||||
|
```rush
|
||||||
|
APP = "myapp"
|
||||||
|
|
||||||
|
for region in us-east us-west eu-west {
|
||||||
|
SERVER = "$APP-$region"
|
||||||
|
echo "Configuring $SERVER"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cleanup Tasks
|
||||||
|
|
||||||
|
```rush
|
||||||
|
TEMP_DIR = "/tmp/build"
|
||||||
|
|
||||||
|
for artifact in logs cache temp build {
|
||||||
|
ARTIFACT_PATH = "$TEMP_DIR/$artifact"
|
||||||
|
echo "Removing $ARTIFACT_PATH"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loop Variable Scope
|
||||||
|
|
||||||
|
Loop variables persist after the loop ends with their last value:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for item in a b c {
|
||||||
|
echo "Current: $item"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Last item was: $item"
|
||||||
|
# Output: Last item was: c
|
||||||
|
```
|
||||||
|
|
||||||
|
This may change in future versions to limit scope to the loop body.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of Rush loops:
|
||||||
|
|
||||||
|
- ❌ Only `for` loops (no `while` or `until`)
|
||||||
|
- ❌ No `break` or `continue` statements
|
||||||
|
- ❌ No range syntax (like `1..10`)
|
||||||
|
- ❌ Cannot iterate over arrays (Rush has no arrays)
|
||||||
|
- ❌ Cannot iterate over command output (like `for i in $(ls)`)
|
||||||
|
- ❌ No C-style `for(i=0; i<10; i++)` syntax
|
||||||
|
- ❌ Loop items must be explicitly listed
|
||||||
|
|
||||||
|
### Workarounds
|
||||||
|
|
||||||
|
Since you can't iterate over command output, you need to explicitly list items:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ❌ Not supported (yet):
|
||||||
|
# for file in $(ls *.txt) { ... }
|
||||||
|
|
||||||
|
# ✅ Instead, list explicitly:
|
||||||
|
for file in file1.txt file2.txt file3.txt {
|
||||||
|
echo "Processing $file"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use descriptive loop variables** - `for server in ...` is clearer than `for i in ...`
|
||||||
|
2. **Keep loop bodies simple** - Complex logic should be broken down
|
||||||
|
3. **Document what you're iterating** - Add comments for clarity
|
||||||
|
4. **Consider parallel execution** - If iterations are independent, use `parallel` blocks
|
||||||
|
|
||||||
|
### Good Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Deploy to multiple environments
|
||||||
|
echo "Starting deployment"
|
||||||
|
|
||||||
|
for env in dev staging production {
|
||||||
|
echo ""
|
||||||
|
echo "=== Deploying to $env ==="
|
||||||
|
|
||||||
|
DEPLOY_URL = "https://deploy.$env.example.com"
|
||||||
|
echo "Target: $DEPLOY_URL"
|
||||||
|
|
||||||
|
# Deployment steps
|
||||||
|
for step in backup deploy verify {
|
||||||
|
echo " [$step] Running..."
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== $env deployment complete ==="
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "All deployments finished"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Combining with Parallel Execution
|
||||||
|
|
||||||
|
For independent iterations, consider using parallel blocks instead:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# Sequential (slow)
|
||||||
|
for server in web-1 web-2 web-3 {
|
||||||
|
echo "Deploying to $server"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parallel (fast)
|
||||||
|
parallel {
|
||||||
|
run { echo "Deploying to web-1" }
|
||||||
|
run { echo "Deploying to web-2" }
|
||||||
|
run { echo "Deploying to web-3" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Parallel Execution](./parallel.md) for more details.
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
Potential enhancements:
|
||||||
|
|
||||||
|
- `while` and `until` loops
|
||||||
|
- `break` and `continue` statements
|
||||||
|
- Range syntax: `for i in 1..10`
|
||||||
|
- Iterating over command output
|
||||||
|
- Array iteration
|
||||||
|
- Step values: `for i in 1..10 step 2`
|
||||||
|
|
||||||
|
These are not currently implemented.
|
||||||
331
docs/src/language/parallel.md
Normal file
331
docs/src/language/parallel.md
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
# Parallel Execution
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
One of Rush's key features is built-in support for parallel execution through `parallel` blocks.
|
||||||
|
|
||||||
|
## Basic Parallel Block
|
||||||
|
|
||||||
|
Run multiple tasks concurrently:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Task 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Task 2"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Task 3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The tasks run simultaneously, so output may be interleaved:
|
||||||
|
```
|
||||||
|
Task 1
|
||||||
|
Task 3
|
||||||
|
Task 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use Parallel Execution
|
||||||
|
|
||||||
|
Use parallel blocks when:
|
||||||
|
|
||||||
|
- ✅ Tasks are **independent** (don't depend on each other)
|
||||||
|
- ✅ Tasks are **I/O bound** (network, disk operations)
|
||||||
|
- ✅ You want to **save time** by running concurrently
|
||||||
|
- ✅ Order of completion doesn't matter
|
||||||
|
|
||||||
|
Don't use parallel blocks when:
|
||||||
|
|
||||||
|
- ❌ Tasks must run in **specific order**
|
||||||
|
- ❌ Tasks **share state** or modify the same resources
|
||||||
|
- ❌ Tasks are **CPU intensive** and would compete for resources
|
||||||
|
- ❌ You need **deterministic output** order
|
||||||
|
|
||||||
|
## Practical Examples
|
||||||
|
|
||||||
|
### Deploying to Multiple Servers
|
||||||
|
|
||||||
|
```rush
|
||||||
|
APP = "my-service"
|
||||||
|
VERSION = "1.2.3"
|
||||||
|
|
||||||
|
echo "Deploying $APP v$VERSION to all servers..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[web-1] Starting deployment"
|
||||||
|
echo "[web-1] Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[web-2] Starting deployment"
|
||||||
|
echo "[web-2] Deployment complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[web-3] Starting deployment"
|
||||||
|
echo "[web-3] Deployment complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All servers updated!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Multiple Tests
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Running test suite in parallel..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[unit-tests] Running..."
|
||||||
|
echo "[unit-tests] 127 tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[integration-tests] Running..."
|
||||||
|
echo "[integration-tests] 45 tests passed"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[lint] Running..."
|
||||||
|
echo "[lint] No issues found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All tests complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Processing Batches
|
||||||
|
|
||||||
|
```rush
|
||||||
|
DATA_DIR = "$HOME/data"
|
||||||
|
|
||||||
|
echo "Processing data batches..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
INPUT = "$DATA_DIR/batch_001.csv"
|
||||||
|
echo "[batch-001] Processing $INPUT"
|
||||||
|
echo "[batch-001] Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
INPUT = "$DATA_DIR/batch_002.csv"
|
||||||
|
echo "[batch-002] Processing $INPUT"
|
||||||
|
echo "[batch-002] Complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
INPUT = "$DATA_DIR/batch_003.csv"
|
||||||
|
echo "[batch-003] Processing $INPUT"
|
||||||
|
echo "[batch-003] Complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All batches processed!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "Checking service health..."
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[database] Checking connection..."
|
||||||
|
echo "[database] OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[cache] Checking Redis..."
|
||||||
|
echo "[cache] OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[api] Checking endpoint..."
|
||||||
|
echo "[api] OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "All services healthy"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variable Access
|
||||||
|
|
||||||
|
Variables defined before the `parallel` block are accessible inside:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
PROJECT = "my-app"
|
||||||
|
ENV = "production"
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "Deploying $PROJECT to server-1 ($ENV)"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "Deploying $PROJECT to server-2 ($ENV)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of parallel execution in Rush:
|
||||||
|
|
||||||
|
- ❌ **Cannot define variables** inside `run` blocks
|
||||||
|
- ❌ **No synchronization** primitives (no locks, semaphores)
|
||||||
|
- ❌ **No return values** from parallel blocks
|
||||||
|
- ❌ **No error propagation** (if one task fails, others continue)
|
||||||
|
- ❌ **Fixed worker thread count** (currently hardcoded to 4)
|
||||||
|
- ❌ **No task dependencies** (can't say "run A, then B and C in parallel")
|
||||||
|
- ❌ **Output interleaving** (output from tasks mixed randomly)
|
||||||
|
|
||||||
|
### Variable Limitation Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ❌ This doesn't work:
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
MY_VAR = "value" # ERROR: Can't define variables in run blocks
|
||||||
|
echo $MY_VAR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ✅ Define variables before the parallel block:
|
||||||
|
MY_VAR = "value"
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo $MY_VAR # OK: Can read existing variables
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Thread Pool
|
||||||
|
|
||||||
|
Rush currently uses a fixed pool of 4 worker threads. This means:
|
||||||
|
|
||||||
|
- If you have 4 tasks, they all run simultaneously
|
||||||
|
- If you have 10 tasks, 4 run at a time, then the next 4, then the final 2
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# All run simultaneously (4 tasks, 4 threads)
|
||||||
|
parallel {
|
||||||
|
run { echo "A" }
|
||||||
|
run { echo "B" }
|
||||||
|
run { echo "C" }
|
||||||
|
run { echo "D" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runs in batches (8 tasks, 4 threads)
|
||||||
|
parallel {
|
||||||
|
run { echo "1" } # First batch
|
||||||
|
run { echo "2" } # First batch
|
||||||
|
run { echo "3" } # First batch
|
||||||
|
run { echo "4" } # First batch
|
||||||
|
run { echo "5" } # Second batch (waits)
|
||||||
|
run { echo "6" } # Second batch (waits)
|
||||||
|
run { echo "7" } # Second batch (waits)
|
||||||
|
run { echo "8" } # Second batch (waits)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### I/O vs CPU Bound
|
||||||
|
|
||||||
|
Parallel execution works best for I/O-bound tasks:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ✅ Good use case (I/O bound)
|
||||||
|
parallel {
|
||||||
|
run { echo "Downloading file 1..." }
|
||||||
|
run { echo "Downloading file 2..." }
|
||||||
|
run { echo "Downloading file 3..." }
|
||||||
|
}
|
||||||
|
|
||||||
|
# ⚠️ May not help much (CPU bound)
|
||||||
|
parallel {
|
||||||
|
run { echo "Computing hash 1..." }
|
||||||
|
run { echo "Computing hash 2..." }
|
||||||
|
run { echo "Computing hash 3..." }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Combining Parallel with Loops
|
||||||
|
|
||||||
|
You can't directly parallelize a loop, but you can manually write parallel tasks:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ❌ Can't do this (loop over parallel tasks):
|
||||||
|
# for server in web-1 web-2 web-3 {
|
||||||
|
# run { echo "Deploy to $server" }
|
||||||
|
# }
|
||||||
|
|
||||||
|
# ✅ Instead, write out each task:
|
||||||
|
parallel {
|
||||||
|
run { echo "Deploy to web-1" }
|
||||||
|
run { echo "Deploy to web-2" }
|
||||||
|
run { echo "Deploy to web-3" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Keep run blocks simple** - Complex logic is harder to debug when parallel
|
||||||
|
2. **Prefix output** - Use `[task-name]` prefixes to identify which task is logging
|
||||||
|
3. **Make tasks independent** - Don't rely on order of execution
|
||||||
|
4. **Use for I/O operations** - Network requests, file operations, database queries
|
||||||
|
5. **Avoid shared state** - Don't modify the same resources from multiple tasks
|
||||||
|
|
||||||
|
### Good Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
BUILD_ID = "12345"
|
||||||
|
REGISTRY = "registry.example.com"
|
||||||
|
|
||||||
|
echo "Starting parallel build verification"
|
||||||
|
|
||||||
|
parallel {
|
||||||
|
run {
|
||||||
|
echo "[checksums] Computing artifact checksums..."
|
||||||
|
echo "[checksums] SHA256: abc123def456"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[docker] Verifying container image..."
|
||||||
|
echo "[docker] Image $REGISTRY/app:$BUILD_ID verified"
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
echo "[tests] Running smoke tests..."
|
||||||
|
echo "[tests] All smoke tests passed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Verification complete for build $BUILD_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
Potential enhancements:
|
||||||
|
|
||||||
|
- Configurable worker thread count
|
||||||
|
- Error handling and propagation
|
||||||
|
- Waiting for specific tasks
|
||||||
|
- Task dependencies (DAG execution)
|
||||||
|
- Return values from parallel tasks
|
||||||
|
- Synchronized output (prevent interleaving)
|
||||||
|
- Progress indicators
|
||||||
|
|
||||||
|
These are not currently implemented.
|
||||||
188
docs/src/language/variables.md
Normal file
188
docs/src/language/variables.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
# Variables
|
||||||
|
|
||||||
|
> ⚠️ This documentation is AI-generated and may contain errors.
|
||||||
|
|
||||||
|
Variables in Rush are simple to use and automatically interpolated in strings.
|
||||||
|
|
||||||
|
## Declaration and Assignment
|
||||||
|
|
||||||
|
Variables are declared and assigned using the `=` operator:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
NAME = "Alice"
|
||||||
|
AGE = "30"
|
||||||
|
CITY = "San Francisco"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Currently, all values are treated as strings in Rush.
|
||||||
|
|
||||||
|
## Variable Naming Rules
|
||||||
|
|
||||||
|
Variable names:
|
||||||
|
- Must start with a letter or underscore
|
||||||
|
- Can contain letters, numbers, and underscores
|
||||||
|
- Are case-sensitive
|
||||||
|
- Should be in UPPER_CASE by convention (though not required)
|
||||||
|
|
||||||
|
```rush
|
||||||
|
valid_name = "yes"
|
||||||
|
ALSO_VALID = "yes"
|
||||||
|
name123 = "yes"
|
||||||
|
_private = "yes"
|
||||||
|
|
||||||
|
# Invalid (will cause parse errors):
|
||||||
|
# 123invalid = "no" # Can't start with number
|
||||||
|
# my-var = "no" # Hyphens not allowed
|
||||||
|
```
|
||||||
|
|
||||||
|
## String Interpolation
|
||||||
|
|
||||||
|
Variables are automatically interpolated when prefixed with `$`:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
USER_NAME = "Alice"
|
||||||
|
GREETING = "Hello, $USER_NAME!"
|
||||||
|
echo $GREETING
|
||||||
|
# Output: Hello, Alice!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested Interpolation
|
||||||
|
|
||||||
|
Variables can reference other variables:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
PROJECT = "my-app"
|
||||||
|
VERSION = "2.1.0"
|
||||||
|
ARTIFACT = "$PROJECT-$VERSION.tar.gz"
|
||||||
|
|
||||||
|
echo $ARTIFACT
|
||||||
|
# Output: my-app-2.1.0.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path Construction
|
||||||
|
|
||||||
|
Common pattern for building paths:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
BASE_DIR = "$HOME/projects"
|
||||||
|
PROJECT_NAME = "rush"
|
||||||
|
PROJECT_DIR = "$BASE_DIR/$PROJECT_NAME"
|
||||||
|
|
||||||
|
echo $PROJECT_DIR
|
||||||
|
# Output: /home/user/projects/rush
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation and Errors
|
||||||
|
|
||||||
|
Rush validates variable usage at parse time:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# ❌ ERROR: Variable used before definition
|
||||||
|
echo $UNDEFINED_VAR
|
||||||
|
MESSAGE = "Hello"
|
||||||
|
|
||||||
|
# ✅ CORRECT: Define before use
|
||||||
|
MESSAGE = "Hello"
|
||||||
|
echo $MESSAGE
|
||||||
|
```
|
||||||
|
|
||||||
|
This strict validation helps catch typos and undefined variable references early.
|
||||||
|
|
||||||
|
## Variable Scope
|
||||||
|
|
||||||
|
### Global Scope
|
||||||
|
|
||||||
|
Variables defined at the top level are available throughout the script:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
GLOBAL_VAR = "accessible everywhere"
|
||||||
|
|
||||||
|
if $GLOBAL_VAR {
|
||||||
|
echo $GLOBAL_VAR # ✅ Works
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in a b c {
|
||||||
|
echo $GLOBAL_VAR # ✅ Works
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Loop Variables
|
||||||
|
|
||||||
|
Loop variables are scoped to the loop body:
|
||||||
|
|
||||||
|
```rush
|
||||||
|
for item in x y z {
|
||||||
|
# 'item' is available here
|
||||||
|
echo "Processing $item"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 'item' retains its last value after the loop
|
||||||
|
echo "Last item was: $item"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Currently, Rush has simple scoping rules. Variables defined in loops persist after the loop exits with their last assigned value.
|
||||||
|
|
||||||
|
## Built-in Variables
|
||||||
|
|
||||||
|
Rush provides several built-in variables (see [Built-in Variables](./builtins.md) for details):
|
||||||
|
|
||||||
|
```rush
|
||||||
|
echo "User: $USER"
|
||||||
|
echo "Home: $HOME"
|
||||||
|
|
||||||
|
if $IS_ROOT {
|
||||||
|
echo "Running as root"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Escaping Variables
|
||||||
|
|
||||||
|
Currently, Rush does not support escaping `$` to print it literally. If you need a literal `$`, you may need to work around this limitation.
|
||||||
|
|
||||||
|
```rush
|
||||||
|
# This will attempt variable interpolation:
|
||||||
|
echo "Price: $100" # Looks for variable named "100"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Define variables at the top** of your script for clarity
|
||||||
|
2. **Use descriptive names** that indicate purpose
|
||||||
|
3. **Group related variables** together
|
||||||
|
4. **Use UPPER_CASE** for constants/configuration
|
||||||
|
5. **Validate inputs** early in your script
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/usr/bin/env rush
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
APP_NAME = "my-service"
|
||||||
|
APP_VERSION = "1.0.0"
|
||||||
|
DEPLOY_ENV = "production"
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
BASE_DIR = "/opt/apps"
|
||||||
|
APP_DIR = "$BASE_DIR/$APP_NAME"
|
||||||
|
LOG_DIR = "/var/log/$APP_NAME"
|
||||||
|
|
||||||
|
# Deployment
|
||||||
|
ARTIFACT = "$APP_NAME-$APP_VERSION.tar.gz"
|
||||||
|
ARTIFACT_URL = "https://releases.example.com/$ARTIFACT"
|
||||||
|
|
||||||
|
echo "Deploying $ARTIFACT to $DEPLOY_ENV"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Current limitations of Rush variables:
|
||||||
|
|
||||||
|
- ❌ No arrays or lists
|
||||||
|
- ❌ No numeric operations
|
||||||
|
- ❌ No string manipulation functions
|
||||||
|
- ❌ No variable expansion modifiers (like `${var:-default}`)
|
||||||
|
- ❌ All values are strings
|
||||||
|
- ❌ No explicit quoting mechanism
|
||||||
|
|
||||||
|
These limitations may be addressed in future versions of Rush.
|
||||||
@@ -16,28 +16,22 @@ impl<'a> Executor<'a> {
|
|||||||
match node {
|
match node {
|
||||||
AstNode::VariableAssignment { name, value } => {
|
AstNode::VariableAssignment { name, value } => {
|
||||||
if let AstNode::Literal(val) = *value {
|
if let AstNode::Literal(val) = *value {
|
||||||
self.env.set_variable(name, val);
|
let substituted = self.substitute_variables(&val);
|
||||||
|
self.env.set_variable(name, substituted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::Command { name, args } => {
|
AstNode::Command { name, args } => {
|
||||||
// convert args to strings
|
|
||||||
let arg_strings: Vec<String> = args
|
let arg_strings: Vec<String> = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|arg| {
|
.filter_map(|arg| {
|
||||||
if let AstNode::Literal(s) = arg {
|
if let AstNode::Literal(s) = arg {
|
||||||
// Check if its a variable reference
|
Some(self.substitute_variables(&s))
|
||||||
if s.starts_with('$') {
|
|
||||||
self.env.get_variable(&s[1..]).map(|v| v.clone())
|
|
||||||
} else {
|
|
||||||
Some(s)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// built in commands :D
|
|
||||||
//todo: move and add more builtins
|
//todo: move and add more builtins
|
||||||
if name == "echo" {
|
if name == "echo" {
|
||||||
builtins::echo(arg_strings);
|
builtins::echo(arg_strings);
|
||||||
@@ -50,7 +44,6 @@ impl<'a> Executor<'a> {
|
|||||||
} else if name == "require_root" {
|
} else if name == "require_root" {
|
||||||
builtins::require_root();
|
builtins::require_root();
|
||||||
} else {
|
} else {
|
||||||
// execute external cmd
|
|
||||||
let cmd = Command {
|
let cmd = Command {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
args: arg_strings,
|
args: arg_strings,
|
||||||
@@ -59,34 +52,32 @@ impl<'a> Executor<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::ControlFlow {
|
AstNode::ControlFlow {
|
||||||
condition: _,
|
condition,
|
||||||
then_branch,
|
then_branch,
|
||||||
else_branch,
|
else_branch,
|
||||||
} => {
|
} => {
|
||||||
println!("TODO: Execute if statement");
|
let condition_result = self.evaluate_condition(&condition);
|
||||||
for node in then_branch {
|
|
||||||
self.execute_node(node);
|
if condition_result {
|
||||||
}
|
for node in then_branch {
|
||||||
if let Some(else_nodes) = else_branch {
|
self.execute_node(node);
|
||||||
|
}
|
||||||
|
} else if let Some(else_nodes) = else_branch {
|
||||||
for node in else_nodes {
|
for node in else_nodes {
|
||||||
self.execute_node(node);
|
self.execute_node(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::For { var, items, body } => {
|
AstNode::For { var, items, body } => {
|
||||||
// execute for loop by iterating over items
|
|
||||||
for item in items {
|
for item in items {
|
||||||
// set loop variable
|
|
||||||
self.env.set_variable(var.clone(), item);
|
self.env.set_variable(var.clone(), item);
|
||||||
|
|
||||||
// Execute body
|
|
||||||
for node in &body {
|
for node in &body {
|
||||||
self.execute_node(node.clone());
|
self.execute_node(node.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::Parallel { blocks } => {
|
AstNode::Parallel { blocks } => {
|
||||||
// create multithreaded runtime
|
|
||||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(4) //todo: make configurable or dynamic
|
.worker_threads(4) //todo: make configurable or dynamic
|
||||||
.enable_all()
|
.enable_all()
|
||||||
@@ -96,24 +87,16 @@ impl<'a> Executor<'a> {
|
|||||||
runtime.block_on(async {
|
runtime.block_on(async {
|
||||||
let mut handles = vec![];
|
let mut handles = vec![];
|
||||||
for block in blocks {
|
for block in blocks {
|
||||||
// clone the environment for this block to access variables
|
|
||||||
let env = self.env.clone();
|
let env = self.env.clone();
|
||||||
|
|
||||||
// Spawn each block as a separate task (can run on different threads)
|
|
||||||
let handle = tokio::task::spawn_blocking(move || {
|
let handle = tokio::task::spawn_blocking(move || {
|
||||||
for node in block {
|
for node in block {
|
||||||
// Execute node
|
|
||||||
if let AstNode::Command { name, args } = node {
|
if let AstNode::Command { name, args } = node {
|
||||||
let arg_strings: Vec<String> = args
|
let arg_strings: Vec<String> = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|arg| {
|
.filter_map(|arg| {
|
||||||
if let AstNode::Literal(s) = arg {
|
if let AstNode::Literal(s) = arg {
|
||||||
// Check if its a variable reference
|
Some(env.substitute_variables(&s))
|
||||||
if s.starts_with('$') {
|
|
||||||
env.get_variable(&s[1..]).map(|v| v.clone())
|
|
||||||
} else {
|
|
||||||
Some(s)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -123,7 +106,6 @@ impl<'a> Executor<'a> {
|
|||||||
if name == "echo" {
|
if name == "echo" {
|
||||||
println!("{}", arg_strings.join(" "));
|
println!("{}", arg_strings.join(" "));
|
||||||
} else {
|
} else {
|
||||||
// execute external cmd
|
|
||||||
match std::process::Command::new(&name)
|
match std::process::Command::new(&name)
|
||||||
.args(&arg_strings)
|
.args(&arg_strings)
|
||||||
.output()
|
.output()
|
||||||
@@ -153,16 +135,14 @@ impl<'a> Executor<'a> {
|
|||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for all
|
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
let _ = handle.await;
|
let _ = handle.await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AstNode::Workers { count, body } => {
|
AstNode::Workers { count, body } => {
|
||||||
// execute with limited concurrency using a semaphore and multi-threaded runtime
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.worker_threads(count.max(2)) // use at least 2 threads, or the worker count
|
.worker_threads(count.max(2))
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -171,7 +151,6 @@ impl<'a> Executor<'a> {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
// clone environment for workers
|
|
||||||
let env = self.env.clone();
|
let env = self.env.clone();
|
||||||
|
|
||||||
let semaphore = Arc::new(Semaphore::new(count));
|
let semaphore = Arc::new(Semaphore::new(count));
|
||||||
@@ -181,31 +160,19 @@ impl<'a> Executor<'a> {
|
|||||||
let sem = semaphore.clone();
|
let sem = semaphore.clone();
|
||||||
let env_clone = env.clone();
|
let env_clone = env.clone();
|
||||||
|
|
||||||
// clone the node for the async task
|
|
||||||
let node_clone = node.clone();
|
let node_clone = node.clone();
|
||||||
|
|
||||||
// Use spawn_blocking for CPU bound work to run on thread pool
|
|
||||||
let handle = tokio::task::spawn_blocking(move || {
|
let handle = tokio::task::spawn_blocking(move || {
|
||||||
// Acquire semaphore permit (limits concurrency)
|
|
||||||
// Note: We need to do this in a blocking context
|
|
||||||
let rt_inner = tokio::runtime::Handle::current();
|
let rt_inner = tokio::runtime::Handle::current();
|
||||||
let _permit = rt_inner.block_on(async { sem.acquire().await.unwrap() });
|
let _permit = rt_inner.block_on(async { sem.acquire().await.unwrap() });
|
||||||
|
|
||||||
// execute the node based on its type
|
|
||||||
match node_clone {
|
match node_clone {
|
||||||
AstNode::Command { name, args } => {
|
AstNode::Command { name, args } => {
|
||||||
let arg_strings: Vec<String> = args
|
let arg_strings: Vec<String> = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|arg| {
|
.filter_map(|arg| {
|
||||||
if let AstNode::Literal(s) = arg {
|
if let AstNode::Literal(s) = arg {
|
||||||
// Check if it's a variable reference
|
Some(env_clone.substitute_variables(&s))
|
||||||
if s.starts_with('$') {
|
|
||||||
env_clone
|
|
||||||
.get_variable(&s[1..])
|
|
||||||
.map(|v| v.clone())
|
|
||||||
} else {
|
|
||||||
Some(s)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -244,30 +211,94 @@ impl<'a> Executor<'a> {
|
|||||||
items,
|
items,
|
||||||
body: for_body,
|
body: for_body,
|
||||||
} => {
|
} => {
|
||||||
// execute for loop
|
|
||||||
for item in items {
|
for item in items {
|
||||||
for for_node in &for_body {
|
for for_node in &for_body {
|
||||||
// simple execution for commands in for loops
|
match for_node {
|
||||||
if let AstNode::Command { name, args } = for_node {
|
AstNode::Command { name, args } => {
|
||||||
|
let arg_strings: Vec<String> = args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| {
|
||||||
|
if let AstNode::Literal(s) = arg {
|
||||||
|
let mut temp_env = env_clone.clone();
|
||||||
|
temp_env.set_variable(
|
||||||
|
var.clone(),
|
||||||
|
item.clone(),
|
||||||
|
);
|
||||||
|
Some(temp_env.substitute_variables(s))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if name == "echo" {
|
||||||
|
println!("{}", arg_strings.join(" "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AstNode::Parallel { blocks } => {
|
||||||
|
for block in blocks {
|
||||||
|
for cmd_node in block {
|
||||||
|
if let AstNode::Command { name, args } = cmd_node {
|
||||||
|
let arg_strings: Vec<String> = args
|
||||||
|
.iter()
|
||||||
|
.filter_map(|arg| {
|
||||||
|
if let AstNode::Literal(s) = arg {
|
||||||
|
let mut temp_env = env_clone.clone();
|
||||||
|
temp_env.set_variable(
|
||||||
|
var.clone(),
|
||||||
|
item.clone(),
|
||||||
|
);
|
||||||
|
Some(temp_env.substitute_variables(s))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if name == "echo" {
|
||||||
|
println!("{}", arg_strings.join(" "));
|
||||||
|
} else {
|
||||||
|
match std::process::Command::new(name)
|
||||||
|
.args(&arg_strings)
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) => {
|
||||||
|
if !output.stdout.is_empty() {
|
||||||
|
print!(
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
eprint!(
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to execute '{}': {}", name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AstNode::Parallel { blocks } => {
|
||||||
|
for block in blocks {
|
||||||
|
for cmd_node in block {
|
||||||
|
if let AstNode::Command { name, args } = cmd_node {
|
||||||
let arg_strings: Vec<String> = args
|
let arg_strings: Vec<String> = args
|
||||||
.iter()
|
.into_iter()
|
||||||
.filter_map(|arg| {
|
.filter_map(|arg| {
|
||||||
if let AstNode::Literal(s) = arg {
|
if let AstNode::Literal(s) = arg {
|
||||||
|
Some(env_clone.substitute_variables(&s))
|
||||||
if s.starts_with('$') {
|
|
||||||
let var_name = &s[1..];
|
|
||||||
if var_name == var {
|
|
||||||
// loop var
|
|
||||||
Some(item.clone())
|
|
||||||
} else {
|
|
||||||
// Environment var
|
|
||||||
env_clone
|
|
||||||
.get_variable(var_name)
|
|
||||||
.map(|v| v.clone())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Some(s.clone())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -276,6 +307,29 @@ impl<'a> Executor<'a> {
|
|||||||
|
|
||||||
if name == "echo" {
|
if name == "echo" {
|
||||||
println!("{}", arg_strings.join(" "));
|
println!("{}", arg_strings.join(" "));
|
||||||
|
} else {
|
||||||
|
match std::process::Command::new(&name)
|
||||||
|
.args(&arg_strings)
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) => {
|
||||||
|
if !output.stdout.is_empty() {
|
||||||
|
print!(
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stdout)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
eprint!(
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to execute '{}': {}", name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,22 +337,18 @@ impl<'a> Executor<'a> {
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
//todo: other node types not yet supported in workers
|
//todo: other node types not yet supported in workers
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for all tasks to complete
|
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
let _ = handle.await;
|
let _ = handle.await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AstNode::Literal(_) => {
|
AstNode::Literal(_) => {}
|
||||||
// literals dont execute on their own
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,9 +363,7 @@ impl<'a> Executor<'a> {
|
|||||||
var,
|
var,
|
||||||
items.len()
|
items.len()
|
||||||
);
|
);
|
||||||
for _stmt in body {
|
for _stmt in body {}
|
||||||
// execute each statement in the loop body
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Statement::If {
|
Statement::If {
|
||||||
condition,
|
condition,
|
||||||
@@ -324,13 +372,9 @@ impl<'a> Executor<'a> {
|
|||||||
} => {
|
} => {
|
||||||
// todo: Implement if statement execution
|
// todo: Implement if statement execution
|
||||||
println!("todo: Execute if with condition '{}'", condition);
|
println!("todo: Execute if with condition '{}'", condition);
|
||||||
for _stmt in then_branch {
|
for _stmt in then_branch {}
|
||||||
// Execute then branch
|
|
||||||
}
|
|
||||||
if let Some(else_stmts) = else_branch {
|
if let Some(else_stmts) = else_branch {
|
||||||
for _stmt in else_stmts {
|
for _stmt in else_stmts {}
|
||||||
// Execute else branch
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Statement::Parallel { blocks } => {
|
Statement::Parallel { blocks } => {
|
||||||
@@ -364,4 +408,49 @@ impl<'a> Executor<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn evaluate_condition(&self, condition: &AstNode) -> bool {
|
||||||
|
match condition {
|
||||||
|
AstNode::Literal(s) => {
|
||||||
|
let s = s.trim();
|
||||||
|
|
||||||
|
let (negated, condition_str) = if s.starts_with("not ") {
|
||||||
|
(true, s[4..].trim())
|
||||||
|
} else {
|
||||||
|
(false, s)
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = if condition_str.starts_with('$') {
|
||||||
|
let var_name = &condition_str[1..];
|
||||||
|
if let Some(value) = self.env.get_variable(var_name) {
|
||||||
|
self.is_truthy(value)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.is_truthy(condition_str)
|
||||||
|
};
|
||||||
|
|
||||||
|
if negated {
|
||||||
|
!result
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_truthy(&self, value: &str) -> bool {
|
||||||
|
// Empty string, "0", "false", "no" are falsy
|
||||||
|
// Everything else is truthy
|
||||||
|
!value.is_empty()
|
||||||
|
&& value != "0"
|
||||||
|
&& value.to_lowercase() != "false"
|
||||||
|
&& value.to_lowercase() != "no"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn substitute_variables(&self, s: &str) -> String {
|
||||||
|
self.env.substitute_variables(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,9 @@ use crate::parser::Parser;
|
|||||||
use crate::runtime::environment::Environment;
|
use crate::runtime::environment::Environment;
|
||||||
use executor::Executor;
|
use executor::Executor;
|
||||||
|
|
||||||
/// Main entry point for executing rush scripts
|
|
||||||
pub fn execute(content: &str) {
|
pub fn execute(content: &str) {
|
||||||
// create a new parser
|
|
||||||
let parser = Parser::new();
|
let parser = Parser::new();
|
||||||
|
|
||||||
// parse the script content
|
|
||||||
let program = match parser.parse(content) {
|
let program = match parser.parse(content) {
|
||||||
Ok(prog) => prog,
|
Ok(prog) => prog,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -19,13 +16,10 @@ pub fn execute(content: &str) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// create a new environment
|
|
||||||
let mut env = Environment::new();
|
let mut env = Environment::new();
|
||||||
|
|
||||||
// create an executor
|
|
||||||
let mut executor = Executor::new(&mut env);
|
let mut executor = Executor::new(&mut env);
|
||||||
|
|
||||||
// execute each statement in the program
|
|
||||||
for statement in program.statements {
|
for statement in program.statements {
|
||||||
executor.execute_node(statement);
|
executor.execute_node(statement);
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@@ -8,7 +8,16 @@ fn main() {
|
|||||||
|
|
||||||
let script_path = &args[1];
|
let script_path = &args[1];
|
||||||
|
|
||||||
let content = std::fs::read_to_string(script_path).expect("Failed to read the script file");
|
let content = std::fs::read_to_string(script_path);
|
||||||
|
if content.is_err() {
|
||||||
|
eprintln!(
|
||||||
|
"Error reading file {}: {}",
|
||||||
|
script_path,
|
||||||
|
content.err().unwrap()
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let content = content.unwrap();
|
||||||
|
|
||||||
let stripped_content = if content.starts_with("#!") {
|
let stripped_content = if content.starts_with("#!") {
|
||||||
content.lines().skip(1).collect::<Vec<&str>>().join("\n")
|
content.lines().skip(1).collect::<Vec<&str>>().join("\n")
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ impl Parser {
|
|||||||
|
|
||||||
let mut defined_vars: HashSet<String> = HashSet::new();
|
let mut defined_vars: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
|
// built in variables that are always available
|
||||||
|
defined_vars.insert("IS_ROOT".to_string());
|
||||||
|
defined_vars.insert("USER".to_string());
|
||||||
|
defined_vars.insert("HOME".to_string());
|
||||||
|
defined_vars.insert("SHELL".to_string());
|
||||||
|
defined_vars.insert("PWD".to_string());
|
||||||
|
defined_vars.insert("OS".to_string());
|
||||||
|
defined_vars.insert("ARCH".to_string());
|
||||||
|
|
||||||
for statement in &program.statements {
|
for statement in &program.statements {
|
||||||
self.validate_node(statement, &mut defined_vars)?;
|
self.validate_node(statement, &mut defined_vars)?;
|
||||||
}
|
}
|
||||||
@@ -55,13 +64,10 @@ impl Parser {
|
|||||||
) -> Result<(), RushError> {
|
) -> Result<(), RushError> {
|
||||||
match node {
|
match node {
|
||||||
AstNode::VariableAssignment { name, value } => {
|
AstNode::VariableAssignment { name, value } => {
|
||||||
// first check if the value uses any undefined variables
|
|
||||||
self.check_node_for_undefined_vars(value, defined_vars)?;
|
self.check_node_for_undefined_vars(value, defined_vars)?;
|
||||||
// then add this variable to the defined set
|
|
||||||
defined_vars.insert(name.clone());
|
defined_vars.insert(name.clone());
|
||||||
}
|
}
|
||||||
AstNode::Command { name: _, args } => {
|
AstNode::Command { name: _, args } => {
|
||||||
// check all arguments for variable references
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
self.check_node_for_undefined_vars(arg, defined_vars)?;
|
self.check_node_for_undefined_vars(arg, defined_vars)?;
|
||||||
}
|
}
|
||||||
@@ -71,17 +77,14 @@ impl Parser {
|
|||||||
items: _,
|
items: _,
|
||||||
body,
|
body,
|
||||||
} => {
|
} => {
|
||||||
// create a new scope for the for loop variable
|
|
||||||
let mut loop_scope = defined_vars.clone();
|
let mut loop_scope = defined_vars.clone();
|
||||||
loop_scope.insert(var.clone());
|
loop_scope.insert(var.clone());
|
||||||
|
|
||||||
// validate the body with the loop variable in scope
|
|
||||||
for body_node in body {
|
for body_node in body {
|
||||||
self.validate_node(body_node, &mut loop_scope)?;
|
self.validate_node(body_node, &mut loop_scope)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::Parallel { blocks } => {
|
AstNode::Parallel { blocks } => {
|
||||||
// each parallel block sees the current scope but can't define new vars
|
|
||||||
for block in blocks {
|
for block in blocks {
|
||||||
for block_node in block {
|
for block_node in block {
|
||||||
let mut parallel_scope = defined_vars.clone();
|
let mut parallel_scope = defined_vars.clone();
|
||||||
@@ -90,7 +93,6 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AstNode::Workers { count: _, body } => {
|
AstNode::Workers { count: _, body } => {
|
||||||
// workers blocks see the current scope
|
|
||||||
for worker_node in body {
|
for worker_node in body {
|
||||||
let mut worker_scope = defined_vars.clone();
|
let mut worker_scope = defined_vars.clone();
|
||||||
self.validate_node(worker_node, &mut worker_scope)?;
|
self.validate_node(worker_node, &mut worker_scope)?;
|
||||||
@@ -125,10 +127,8 @@ impl Parser {
|
|||||||
) -> Result<(), RushError> {
|
) -> Result<(), RushError> {
|
||||||
match node {
|
match node {
|
||||||
AstNode::Literal(s) => {
|
AstNode::Literal(s) => {
|
||||||
// check if this is a variable reference
|
for var_name in self.extract_variables(s) {
|
||||||
if s.starts_with('$') {
|
if !defined_vars.contains(&var_name) {
|
||||||
let var_name = &s[1..];
|
|
||||||
if !defined_vars.contains(var_name) {
|
|
||||||
return Err(RushError::VariableError(format!(
|
return Err(RushError::VariableError(format!(
|
||||||
"Variable '{}' is used before being defined",
|
"Variable '{}' is used before being defined",
|
||||||
var_name
|
var_name
|
||||||
@@ -151,20 +151,123 @@ impl Parser {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_variables(&self, s: &str) -> Vec<String> {
|
||||||
|
let mut vars = Vec::new();
|
||||||
|
let mut chars = s.chars().peekable();
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
if ch == '$' {
|
||||||
|
let mut var_name = String::new();
|
||||||
|
|
||||||
|
while let Some(&next_ch) = chars.peek() {
|
||||||
|
if next_ch.is_alphanumeric() || next_ch == '_' {
|
||||||
|
var_name.push(next_ch);
|
||||||
|
chars.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !var_name.is_empty() {
|
||||||
|
vars.push(var_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vars
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_statement(
|
fn parse_statement(
|
||||||
&self,
|
&self,
|
||||||
lines: &[&str],
|
lines: &[&str],
|
||||||
mut i: usize,
|
mut i: usize,
|
||||||
|
) -> Result<(Option<AstNode>, usize), RushError> {
|
||||||
|
self.parse_statement_with_context(lines, i, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_statement_with_context(
|
||||||
|
&self,
|
||||||
|
lines: &[&str],
|
||||||
|
mut i: usize,
|
||||||
|
in_parallel_context: bool,
|
||||||
) -> Result<(Option<AstNode>, usize), RushError> {
|
) -> Result<(Option<AstNode>, usize), RushError> {
|
||||||
let line = lines[i].trim();
|
let line = lines[i].trim();
|
||||||
|
|
||||||
// skip empty lines and comments
|
if line.is_empty() || line.starts_with('#') || line == "}" {
|
||||||
if line.is_empty() || line.starts_with('#') {
|
|
||||||
return Ok((None, i + 1));
|
return Ok((None, i + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse variable assignment: name = "value"
|
if line.starts_with("run ") && !in_parallel_context {
|
||||||
// make sure it's actually an assignment (not a command with = in args)
|
return Err(RushError::SyntaxError(
|
||||||
|
"The 'run' keyword can only be used inside 'parallel' or 'workers' blocks".to_string()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_parallel_context && line.starts_with("run") {
|
||||||
|
if !line.contains('{') {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Expected '{{' after 'run' keyword on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut run_body = Vec::new();
|
||||||
|
|
||||||
|
if line.contains('}') {
|
||||||
|
let start = line.find('{').unwrap() + 1;
|
||||||
|
let end = line.rfind('}').unwrap();
|
||||||
|
let content = line[start..end].trim();
|
||||||
|
|
||||||
|
if content.is_empty() {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Empty 'run' block on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for cmd_part in content.split(';') {
|
||||||
|
let cmd_trimmed = cmd_part.trim();
|
||||||
|
if !cmd_trimmed.is_empty() {
|
||||||
|
let parts: Vec<&str> = cmd_trimmed.split_whitespace().collect();
|
||||||
|
if !parts.is_empty() {
|
||||||
|
let cmd_name = parts[0].to_string();
|
||||||
|
let args: Vec<AstNode> = parts[1..]
|
||||||
|
.iter()
|
||||||
|
.map(|arg| AstNode::Literal(arg.trim_matches('"').to_string()))
|
||||||
|
.collect();
|
||||||
|
run_body.push(AstNode::Command {
|
||||||
|
name: cmd_name,
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok((Some(AstNode::Parallel { blocks: vec![run_body] }), i + 1));
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
let block_start_line = i;
|
||||||
|
while i < lines.len() {
|
||||||
|
let run_line = lines[i].trim();
|
||||||
|
if run_line.starts_with('}') {
|
||||||
|
i += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (node, next_i) = self.parse_statement_with_context(lines, i, false)?;
|
||||||
|
if let Some(n) = node {
|
||||||
|
run_body.push(n);
|
||||||
|
}
|
||||||
|
i = next_i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= lines.len() && !lines[i-1].trim().starts_with('}') {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing closing '}}' for 'run' block starting at line {}", block_start_line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok((Some(AstNode::Parallel { blocks: vec![run_body] }), i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if line.contains('=')
|
if line.contains('=')
|
||||||
&& !line.starts_with("if")
|
&& !line.starts_with("if")
|
||||||
&& !line.starts_with("for")
|
&& !line.starts_with("for")
|
||||||
@@ -173,7 +276,6 @@ impl Parser {
|
|||||||
let parts: Vec<&str> = line.splitn(2, '=').collect();
|
let parts: Vec<&str> = line.splitn(2, '=').collect();
|
||||||
if parts.len() == 2 {
|
if parts.len() == 2 {
|
||||||
let var_name = parts[0].trim();
|
let var_name = parts[0].trim();
|
||||||
// only treat as assignment if var_name is a valid identifier (no spaces, no special chars)
|
|
||||||
if !var_name.is_empty() && var_name.chars().all(|c| c.is_alphanumeric() || c == '_')
|
if !var_name.is_empty() && var_name.chars().all(|c| c.is_alphanumeric() || c == '_')
|
||||||
{
|
{
|
||||||
let value = parts[1].trim().trim_matches('"').to_string();
|
let value = parts[1].trim().trim_matches('"').to_string();
|
||||||
@@ -188,71 +290,187 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse for loop: for var in items { ... }
|
|
||||||
if line.starts_with("for ") {
|
if line.starts_with("for ") {
|
||||||
// parse: for i in 1 2 3 {
|
// parse: for i in 1 2 3 {
|
||||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
if parts.len() >= 4 && parts[0] == "for" && parts[2] == "in" {
|
|
||||||
let var_name = parts[1].to_string();
|
if parts.len() < 4 {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
// collect items until we hit '{'
|
format!("Invalid 'for' loop syntax on line {}: Expected 'for VAR in ITEMS {{ ... }}'", i + 1)
|
||||||
let mut items = Vec::new();
|
|
||||||
for part in &parts[3..] {
|
|
||||||
if *part == "{" {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
items.push(part.trim_matches('"').to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse body
|
|
||||||
i += 1;
|
|
||||||
let mut body = Vec::new();
|
|
||||||
while i < lines.len() && !lines[i].trim().starts_with('}') {
|
|
||||||
let (node, next_i) = self.parse_statement(lines, i)?;
|
|
||||||
if let Some(n) = node {
|
|
||||||
body.push(n);
|
|
||||||
}
|
|
||||||
i = next_i;
|
|
||||||
}
|
|
||||||
i += 1; // skip closing brace
|
|
||||||
|
|
||||||
return Ok((
|
|
||||||
Some(AstNode::For {
|
|
||||||
var: var_name,
|
|
||||||
items,
|
|
||||||
body,
|
|
||||||
}),
|
|
||||||
i,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parts[0] != "for" {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Expected 'for' keyword on line {}", i + 1)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts[2] != "in" {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Expected 'in' keyword in for loop on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !line.contains('{') {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing '{{' in 'for' loop on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let var_name = parts[1].to_string();
|
||||||
|
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for part in &parts[3..] {
|
||||||
|
if *part == "{" {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
items.push(part.trim_matches('"').to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.is_empty() {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("'for' loop has no items to iterate over on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
let loop_start_line = i;
|
||||||
|
let mut body = Vec::new();
|
||||||
|
while i < lines.len() && !lines[i].trim().starts_with('}') {
|
||||||
|
let (node, next_i) = self.parse_statement_with_context(lines, i, in_parallel_context)?;
|
||||||
|
if let Some(n) = node {
|
||||||
|
body.push(n);
|
||||||
|
}
|
||||||
|
i = next_i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= lines.len() {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing closing '}}' for 'for' loop starting at line {}", loop_start_line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1; // skip closing brace
|
||||||
|
|
||||||
|
return Ok((
|
||||||
|
Some(AstNode::For {
|
||||||
|
var: var_name,
|
||||||
|
items,
|
||||||
|
body,
|
||||||
|
}),
|
||||||
|
i,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse parallel block: parallel { ... }
|
if line.starts_with("if ") {
|
||||||
if line.starts_with("parallel") && line.contains('{') {
|
if !line.contains('{') {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing '{{' in 'if' statement on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let cond_start = 3; // after "if "
|
||||||
|
let cond_end = line.find('{').unwrap();
|
||||||
|
let condition_str = line[cond_start..cond_end].trim();
|
||||||
|
|
||||||
|
if condition_str.is_empty() {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Empty condition in 'if' statement on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let condition = Box::new(AstNode::Literal(condition_str.to_string()));
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
|
let if_start_line = i;
|
||||||
|
let mut then_branch = Vec::new();
|
||||||
|
while i < lines.len() {
|
||||||
|
let current = lines[i].trim();
|
||||||
|
if current.starts_with('}') {
|
||||||
|
if !current.contains("else") {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let (node, next_i) = self.parse_statement(lines, i)?;
|
||||||
|
if let Some(n) = node {
|
||||||
|
then_branch.push(n);
|
||||||
|
}
|
||||||
|
i = next_i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= lines.len() {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing closing '}}' for 'if' block starting at line {}", if_start_line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut else_branch = None;
|
||||||
|
if i < lines.len() {
|
||||||
|
let current = lines[i].trim();
|
||||||
|
if current.starts_with("} else") || current.starts_with("else") {
|
||||||
|
if !current.contains('{') && (i + 1 >= lines.len() || !lines[i + 1].contains('{')) {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing '{{' after 'else' keyword on line {}: {}", i + 1, current)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1; // skip "} else {" or "else {"
|
||||||
|
let mut else_nodes = Vec::new();
|
||||||
|
while i < lines.len() {
|
||||||
|
let current = lines[i].trim();
|
||||||
|
if current.starts_with('}') {
|
||||||
|
i += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let (node, next_i) = self.parse_statement(lines, i)?;
|
||||||
|
if let Some(n) = node {
|
||||||
|
else_nodes.push(n);
|
||||||
|
}
|
||||||
|
i = next_i;
|
||||||
|
}
|
||||||
|
else_branch = Some(else_nodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok((
|
||||||
|
Some(AstNode::ControlFlow {
|
||||||
|
condition,
|
||||||
|
then_branch,
|
||||||
|
else_branch,
|
||||||
|
}),
|
||||||
|
i,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.starts_with("parallel") {
|
||||||
|
if !line.contains('{') {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing '{{' after 'parallel' keyword on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
let parallel_start_line = i;
|
||||||
let mut blocks = Vec::new();
|
let mut blocks = Vec::new();
|
||||||
|
|
||||||
while i < lines.len() {
|
while i < lines.len() {
|
||||||
let inner_line = lines[i].trim();
|
let inner_line = lines[i].trim();
|
||||||
|
|
||||||
// end of parallel block
|
|
||||||
if inner_line.starts_with('}') {
|
if inner_line.starts_with('}') {
|
||||||
i += 1;
|
i += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle 'run {' blocks inside parallel
|
|
||||||
if inner_line.starts_with("run") && inner_line.contains('{') {
|
if inner_line.starts_with("run") && inner_line.contains('{') {
|
||||||
let mut current_block = Vec::new();
|
let mut current_block = Vec::new();
|
||||||
|
|
||||||
// check if single line
|
|
||||||
if inner_line.contains('}') {
|
if inner_line.contains('}') {
|
||||||
|
|
||||||
let start = inner_line.find('{').unwrap() + 1;
|
let start = inner_line.find('{').unwrap() + 1;
|
||||||
let end = inner_line.rfind('}').unwrap();
|
let end = inner_line.rfind('}').unwrap();
|
||||||
let content = &inner_line[start..end].trim();
|
let content = &inner_line[start..end].trim();
|
||||||
|
|
||||||
// parse commands separated by semicolons
|
|
||||||
for cmd_part in content.split(';') {
|
for cmd_part in content.split(';') {
|
||||||
let cmd_trimmed = cmd_part.trim();
|
let cmd_trimmed = cmd_part.trim();
|
||||||
if !cmd_trimmed.is_empty() {
|
if !cmd_trimmed.is_empty() {
|
||||||
@@ -275,20 +493,17 @@ impl Parser {
|
|||||||
blocks.push(current_block);
|
blocks.push(current_block);
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
// multi line run block
|
|
||||||
i += 1;
|
i += 1;
|
||||||
|
|
||||||
while i < lines.len() {
|
while i < lines.len() {
|
||||||
let run_line = lines[i].trim();
|
let run_line = lines[i].trim();
|
||||||
|
|
||||||
// end of this run block
|
|
||||||
if run_line.starts_with('}') {
|
if run_line.starts_with('}') {
|
||||||
blocks.push(current_block);
|
blocks.push(current_block);
|
||||||
i += 1;
|
i += 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle multiple commands on one line separated by semicolons
|
|
||||||
if run_line.contains(';') {
|
if run_line.contains(';') {
|
||||||
for cmd_part in run_line.split(';') {
|
for cmd_part in run_line.split(';') {
|
||||||
let cmd_trimmed = cmd_part.trim();
|
let cmd_trimmed = cmd_part.trim();
|
||||||
@@ -314,7 +529,7 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
let (node, next_i) = self.parse_statement(lines, i)?;
|
let (node, next_i) = self.parse_statement_with_context(lines, i, true)?;
|
||||||
if let Some(n) = node {
|
if let Some(n) = node {
|
||||||
current_block.push(n);
|
current_block.push(n);
|
||||||
}
|
}
|
||||||
@@ -323,46 +538,80 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// direct statement in parallel block (not inside a run {})
|
let (node, next_i) = self.parse_statement_with_context(lines, i, true)?;
|
||||||
let (node, next_i) = self.parse_statement(lines, i)?;
|
|
||||||
if let Some(n) = node {
|
if let Some(n) = node {
|
||||||
blocks.push(vec![n]);
|
blocks.push(vec![n]);
|
||||||
}
|
}
|
||||||
i = next_i;
|
i = next_i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if i > parallel_start_line && (i >= lines.len() || !lines[i-1].trim().starts_with('}')) {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing closing '}}' for 'parallel' block starting at line {}", parallel_start_line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return Ok((Some(AstNode::Parallel { blocks }), i));
|
return Ok((Some(AstNode::Parallel { blocks }), i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse workers N {} block
|
|
||||||
if line.starts_with("workers ") {
|
if line.starts_with("workers ") {
|
||||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
if parts.len() >= 2 {
|
|
||||||
let worker_count = parts[1].parse::<usize>().unwrap_or(1);
|
if parts.len() < 2 {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
i += 1;
|
format!("Missing worker count in 'workers' statement on line {}: {}", i + 1, line)
|
||||||
let mut body = Vec::new();
|
|
||||||
while i < lines.len() && !lines[i].trim().starts_with('}') {
|
|
||||||
let (node, next_i) = self.parse_statement(lines, i)?;
|
|
||||||
if let Some(n) = node {
|
|
||||||
body.push(n);
|
|
||||||
}
|
|
||||||
i = next_i;
|
|
||||||
}
|
|
||||||
i += 1; // skip closing brace
|
|
||||||
|
|
||||||
return Ok((
|
|
||||||
Some(AstNode::Workers {
|
|
||||||
count: worker_count,
|
|
||||||
body,
|
|
||||||
}),
|
|
||||||
i,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let worker_count = match parts[1].parse::<usize>() {
|
||||||
|
Ok(count) if count > 0 => count,
|
||||||
|
Ok(_) => {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Worker count must be greater than 0 on line {}: {}", i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Invalid worker count '{}' on line {}: Expected a positive number", parts[1], i + 1)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !line.contains('{') {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing '{{' after 'workers {}' on line {}: {}", worker_count, i + 1, line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
let workers_start_line = i;
|
||||||
|
let mut body = Vec::new();
|
||||||
|
while i < lines.len() && !lines[i].trim().starts_with('}') {
|
||||||
|
let (node, next_i) = self.parse_statement_with_context(lines, i, true)?;
|
||||||
|
if let Some(n) = node {
|
||||||
|
body.push(n);
|
||||||
|
}
|
||||||
|
i = next_i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= lines.len() {
|
||||||
|
return Err(RushError::SyntaxError(
|
||||||
|
format!("Missing closing '}}' for 'workers' block starting at line {}", workers_start_line)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1; // skip closing brace
|
||||||
|
|
||||||
|
return Ok((
|
||||||
|
Some(AstNode::Workers {
|
||||||
|
count: worker_count,
|
||||||
|
body,
|
||||||
|
}),
|
||||||
|
i,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse command: echo "text" or any other command
|
|
||||||
if !line.is_empty() {
|
if !line.is_empty() {
|
||||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
if !parts.is_empty() {
|
if !parts.is_empty() {
|
||||||
|
|||||||
@@ -1,18 +1,71 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::env;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
variables: HashMap<String, String>,
|
variables: HashMap<String, String>,
|
||||||
|
builtin_vars: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Environment {
|
impl Environment {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Environment {
|
let mut env = Environment {
|
||||||
variables: HashMap::new(),
|
variables: HashMap::new(),
|
||||||
|
builtin_vars: HashSet::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
env.init_builtins();
|
||||||
|
env
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_builtins(&mut self) {
|
||||||
|
let is_root = if unsafe { libc::geteuid() } == 0 {
|
||||||
|
"1"
|
||||||
|
} else {
|
||||||
|
"0"
|
||||||
|
};
|
||||||
|
self.set_builtin("IS_ROOT", is_root);
|
||||||
|
|
||||||
|
if let Ok(user) = env::var("USER") {
|
||||||
|
self.set_builtin("USER", &user);
|
||||||
|
} else {
|
||||||
|
self.set_builtin("USER", "unknown");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(home) = env::var("HOME") {
|
||||||
|
self.set_builtin("HOME", &home);
|
||||||
|
} else {
|
||||||
|
self.set_builtin("HOME", "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(shell) = env::var("SHELL") {
|
||||||
|
self.set_builtin("SHELL", &shell);
|
||||||
|
} else {
|
||||||
|
self.set_builtin("SHELL", "/bin/sh");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(pwd) = env::var("PWD") {
|
||||||
|
self.set_builtin("PWD", &pwd);
|
||||||
|
} else if let Ok(pwd) = env::current_dir() {
|
||||||
|
self.set_builtin("PWD", pwd.to_str().unwrap_or("/"));
|
||||||
|
} else {
|
||||||
|
self.set_builtin("PWD", "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_builtin("OS", std::env::consts::OS);
|
||||||
|
|
||||||
|
self.set_builtin("ARCH", std::env::consts::ARCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_builtin(&mut self, name: &str, value: &str) {
|
||||||
|
self.variables.insert(name.to_string(), value.to_string());
|
||||||
|
self.builtin_vars.insert(name.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_variable(&mut self, name: String, value: String) {
|
pub fn set_variable(&mut self, name: String, value: String) {
|
||||||
|
if self.builtin_vars.contains(&name) {
|
||||||
|
eprintln!("Warning: Overriding built-in variable '{}'", name);
|
||||||
|
}
|
||||||
self.variables.insert(name, value);
|
self.variables.insert(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,4 +76,43 @@ impl Environment {
|
|||||||
pub fn remove_variable(&mut self, name: &str) {
|
pub fn remove_variable(&mut self, name: &str) {
|
||||||
self.variables.remove(name);
|
self.variables.remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_builtin(&self, name: &str) -> bool {
|
||||||
|
self.builtin_vars.contains(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn substitute_variables(&self, s: &str) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut chars = s.chars().peekable();
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
if ch == '$' {
|
||||||
|
let mut var_name = String::new();
|
||||||
|
|
||||||
|
while let Some(&next_ch) = chars.peek() {
|
||||||
|
if next_ch.is_alphanumeric() || next_ch == '_' {
|
||||||
|
var_name.push(next_ch);
|
||||||
|
chars.next();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !var_name.is_empty() {
|
||||||
|
if let Some(value) = self.get_variable(&var_name) {
|
||||||
|
result.push_str(value);
|
||||||
|
} else {
|
||||||
|
result.push('$');
|
||||||
|
result.push_str(&var_name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push('$');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
vsc-extension/package.json
Normal file
6
vsc-extension/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"yo": "^5.1.0",
|
||||||
|
"generator-code": "^1.11.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
5715
vsc-extension/pnpm-lock.yaml
generated
Normal file
5715
vsc-extension/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
vsc-extension/rush/.vscode/launch.json
vendored
Normal file
17
vsc-extension/rush/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// A launch configuration that launches the extension inside a new window
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Extension",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"args": [
|
||||||
|
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4
vsc-extension/rush/.vscodeignore
Normal file
4
vsc-extension/rush/.vscodeignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.vscode/**
|
||||||
|
.vscode-test/**
|
||||||
|
.gitignore
|
||||||
|
vsc-extension-quickstart.md
|
||||||
9
vsc-extension/rush/CHANGELOG.md
Normal file
9
vsc-extension/rush/CHANGELOG.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to the "rush" extension will be documented in this file.
|
||||||
|
|
||||||
|
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
- Initial release
|
||||||
47
vsc-extension/rush/INSTALL.md
Normal file
47
vsc-extension/rush/INSTALL.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Installing the Rush VS Code Extension
|
||||||
|
|
||||||
|
## Method 1: Install from VSIX file (Recommended for sharing)
|
||||||
|
|
||||||
|
1. **Get the VSIX file**: `rush-0.0.1.vsix` (located in this directory)
|
||||||
|
|
||||||
|
2. **Install in VS Code**:
|
||||||
|
- Open VS Code
|
||||||
|
- Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac)
|
||||||
|
- Type: `Extensions: Install from VSIX...`
|
||||||
|
- Select the `rush-0.0.1.vsix` file
|
||||||
|
- Restart VS Code if prompted
|
||||||
|
|
||||||
|
3. **Verify installation**:
|
||||||
|
- Open any `.rush` or `.rsh` file
|
||||||
|
- You should see syntax highlighting automatically
|
||||||
|
- Try typing `for` and pressing Tab to test snippets
|
||||||
|
|
||||||
|
## Method 2: Install via command line
|
||||||
|
|
||||||
|
```bash
|
||||||
|
code --install-extension rush-0.0.1.vsix
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sharing with friends
|
||||||
|
|
||||||
|
Just send them the `rush-0.0.1.vsix` file! They can install it using Method 1 or 2 above.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ Syntax highlighting for all Rush keywords
|
||||||
|
- ✅ Built-in variable highlighting ($IS_ROOT, $USER, etc.)
|
||||||
|
- ✅ Shebang highlighting (#!/path/to/rush)
|
||||||
|
- ✅ Error detection for common syntax mistakes
|
||||||
|
- ✅ Code snippets (type `for`, `if`, `parallel`, etc.)
|
||||||
|
- ✅ Auto-closing braces and quotes
|
||||||
|
|
||||||
|
## Uninstalling
|
||||||
|
|
||||||
|
1. Open VS Code
|
||||||
|
2. Go to Extensions (Ctrl+Shift+X)
|
||||||
|
3. Find "Rush Shell"
|
||||||
|
4. Click the gear icon → Uninstall
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**File location**: `/home/louis/dev/rush/vsc-extension/rush/rush-0.0.1.vsix`
|
||||||
153
vsc-extension/rush/README-old.md
Normal file
153
vsc-extension/rush/README-old.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# rush README
|
||||||
|
|
||||||
|
This is the README for your extension "rush". After writing up a brief description, we recommend including the following sections.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file.
|
||||||
|
|
||||||
|
# Rush Shell Extension for VS Code
|
||||||
|
|
||||||
|
Syntax highlighting and basic validation support for Rush shell scripts (`.rush`, `.rsh`).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Syntax Highlighting
|
||||||
|
|
||||||
|
- **Keywords**: `if`, `else`, `for`, `in`, `not`, `parallel`, `workers`, `run`
|
||||||
|
- **Built-in Variables**: `$IS_ROOT`, `$USER`, `$HOME`, `$SHELL`, `$PWD`, `$OS`, `$ARCH`
|
||||||
|
- **Variables**: `$VARNAME` with proper substitution highlighting
|
||||||
|
- **Comments**: `# comments` and shebangs `#!/path/to/rush`
|
||||||
|
- **Strings**: Double-quoted strings with embedded variable highlighting
|
||||||
|
- **Commands**: Common shell commands like `echo`, `ls`, `cd`, etc.
|
||||||
|
|
||||||
|
### Error Highlighting
|
||||||
|
|
||||||
|
The extension highlights common syntax errors:
|
||||||
|
|
||||||
|
- Missing `{` after `for`, `if`, `parallel`, `workers`
|
||||||
|
- `run` keyword used without braces
|
||||||
|
- Missing `in` keyword in `for` loops
|
||||||
|
|
||||||
|
### Code Snippets
|
||||||
|
|
||||||
|
Type these prefixes and press Tab:
|
||||||
|
|
||||||
|
- `for` - Create a for loop
|
||||||
|
- `if` - Create an if statement
|
||||||
|
- `ifelse` - Create an if-else statement
|
||||||
|
- `parallel` - Create a parallel execution block
|
||||||
|
- `workers` - Create a workers block
|
||||||
|
- `run` - Create a run block
|
||||||
|
- `echo` - Echo command
|
||||||
|
- `var` - Variable assignment
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Open any `.rush` or `.rsh` file
|
||||||
|
2. Syntax highlighting will be applied automatically
|
||||||
|
3. Type snippet prefixes for quick code generation
|
||||||
|
4. Syntax errors will be highlighted in red
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```rush
|
||||||
|
#!/home/user/rush/target/debug/rush
|
||||||
|
|
||||||
|
# Built-in variables
|
||||||
|
echo "User: $USER"
|
||||||
|
echo "Home: $HOME"
|
||||||
|
|
||||||
|
# For loops
|
||||||
|
for i in 1 2 3 {
|
||||||
|
echo "Count: $i"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parallel execution
|
||||||
|
parallel {
|
||||||
|
run { echo "Task 1" }
|
||||||
|
run { echo "Task 2" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Workers with concurrency
|
||||||
|
workers 2 {
|
||||||
|
for task in alpha beta gamma {
|
||||||
|
run { echo "Processing: $task" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Conditionals
|
||||||
|
if not $IS_ROOT {
|
||||||
|
echo "Running as regular user"
|
||||||
|
} else {
|
||||||
|
echo "Running as root"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- VS Code 1.105.0 or higher
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
### 0.0.1
|
||||||
|
|
||||||
|
Initial release:
|
||||||
|
- Syntax highlighting for Rush shell scripts
|
||||||
|
- Basic error detection
|
||||||
|
- Code snippets for common patterns
|
||||||
|
|
||||||
|
|
||||||
|
> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
If you have any requirements or dependencies, add a section describing those and how to install and configure them.
|
||||||
|
|
||||||
|
## Extension Settings
|
||||||
|
|
||||||
|
Include if your extension adds any VS Code settings through the `contributes.configuration` extension point.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
This extension contributes the following settings:
|
||||||
|
|
||||||
|
* `myExtension.enable`: Enable/disable this extension.
|
||||||
|
* `myExtension.thing`: Set to `blah` to do something.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
Calling out known issues can help limit users opening duplicate issues against your extension.
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
Users appreciate release notes as you update your extension.
|
||||||
|
|
||||||
|
### 1.0.0
|
||||||
|
|
||||||
|
Initial release of ...
|
||||||
|
|
||||||
|
### 1.0.1
|
||||||
|
|
||||||
|
Fixed issue #.
|
||||||
|
|
||||||
|
### 1.1.0
|
||||||
|
|
||||||
|
Added features X, Y, and Z.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Working with Markdown
|
||||||
|
|
||||||
|
You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts:
|
||||||
|
|
||||||
|
* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux).
|
||||||
|
* Toggle preview (`Shift+Cmd+V` on macOS or `Shift+Ctrl+V` on Windows and Linux).
|
||||||
|
* Press `Ctrl+Space` (Windows, Linux, macOS) to see a list of Markdown snippets.
|
||||||
|
|
||||||
|
## For more information
|
||||||
|
|
||||||
|
* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown)
|
||||||
|
* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/)
|
||||||
|
|
||||||
|
**Enjoy!**
|
||||||
39
vsc-extension/rush/README.md
Normal file
39
vsc-extension/rush/README.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Rush Shell Extension for VS Code
|
||||||
|
|
||||||
|
Syntax highlighting and basic validation support for Rush shell scripts (`.rush`, `.rsh`).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Syntax Highlighting & Code Snippets
|
||||||
|
|
||||||
|
Type these prefixes and press Tab:
|
||||||
|
|
||||||
|
- `for` - Create a for loop
|
||||||
|
- `if` - Create an if statement
|
||||||
|
- `ifelse` - Create an if-else statement
|
||||||
|
- `parallel` - Create a parallel execution block
|
||||||
|
- `workers` - Create a workers block
|
||||||
|
- `run` - Create a run block
|
||||||
|
- `echo` - Echo command
|
||||||
|
- `var` - Variable assignment
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Open any `.rush` or `.rsh` file
|
||||||
|
2. Syntax highlighting will be applied automatically
|
||||||
|
3. Type snippet prefixes for quick code generation
|
||||||
|
4. Syntax errors will be highlighted in red
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- VS Code 1.105.0 or higher
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
### 0.0.1
|
||||||
|
|
||||||
|
Initial release:
|
||||||
|
- Syntax highlighting for Rush shell scripts
|
||||||
|
- Basic error detection
|
||||||
|
- Code snippets for common patterns
|
||||||
|
|
||||||
24
vsc-extension/rush/language-configuration.json
Normal file
24
vsc-extension/rush/language-configuration.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"brackets": [
|
||||||
|
["{", "}"],
|
||||||
|
["[", "]"],
|
||||||
|
["(", ")"]
|
||||||
|
],
|
||||||
|
"autoClosingPairs": [
|
||||||
|
["{", "}"],
|
||||||
|
["[", "]"],
|
||||||
|
["(", ")"],
|
||||||
|
["\"", "\""]
|
||||||
|
],
|
||||||
|
"surroundingPairs": [
|
||||||
|
["{", "}"],
|
||||||
|
["[", "]"],
|
||||||
|
["(", ")"],
|
||||||
|
["\"", "\""]
|
||||||
|
],
|
||||||
|
"wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)",
|
||||||
|
"indentationRules": {
|
||||||
|
"increaseIndentPattern": "^.*\\{[^}]*$",
|
||||||
|
"decreaseIndentPattern": "^\\s*\\}.*$"
|
||||||
|
}
|
||||||
|
}
|
||||||
4224
vsc-extension/rush/package-lock.json
generated
Normal file
4224
vsc-extension/rush/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
vsc-extension/rush/package.json
Normal file
57
vsc-extension/rush/package.json
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "rush",
|
||||||
|
"displayName": "Rush Shell",
|
||||||
|
"description": "Syntax highlighting and validation for Rush shell scripts",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"publisher": "rush-lang",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.louiscreates.com/tototomate123/rush"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.105.0"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"Programming Languages",
|
||||||
|
"Snippets"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"rush",
|
||||||
|
"shell",
|
||||||
|
"scripting",
|
||||||
|
"parallel",
|
||||||
|
"concurrency"
|
||||||
|
],
|
||||||
|
"contributes": {
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"id": "rush",
|
||||||
|
"aliases": [
|
||||||
|
"Rush",
|
||||||
|
"rush"
|
||||||
|
],
|
||||||
|
"extensions": [
|
||||||
|
".rush",
|
||||||
|
".rsh"
|
||||||
|
],
|
||||||
|
"configuration": "./language-configuration.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grammars": [
|
||||||
|
{
|
||||||
|
"language": "rush",
|
||||||
|
"scopeName": "source.rush",
|
||||||
|
"path": "./syntaxes/rush.tmLanguage.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"snippets": [
|
||||||
|
{
|
||||||
|
"language": "rush",
|
||||||
|
"path": "./snippets/rush.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vscode/vsce": "^3.6.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
74
vsc-extension/rush/snippets/rush.json
Normal file
74
vsc-extension/rush/snippets/rush.json
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"For Loop": {
|
||||||
|
"prefix": "for",
|
||||||
|
"body": [
|
||||||
|
"for ${1:i} in ${2:items} {",
|
||||||
|
"\t$0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "For loop with items"
|
||||||
|
},
|
||||||
|
"If Statement": {
|
||||||
|
"prefix": "if",
|
||||||
|
"body": [
|
||||||
|
"if ${1:condition} {",
|
||||||
|
"\t$0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "If statement"
|
||||||
|
},
|
||||||
|
"If-Else Statement": {
|
||||||
|
"prefix": "ifelse",
|
||||||
|
"body": [
|
||||||
|
"if ${1:condition} {",
|
||||||
|
"\t$2",
|
||||||
|
"} else {",
|
||||||
|
"\t$0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "If-else statement"
|
||||||
|
},
|
||||||
|
"Parallel Block": {
|
||||||
|
"prefix": "parallel",
|
||||||
|
"body": [
|
||||||
|
"parallel {",
|
||||||
|
"\trun { ${1:command} }",
|
||||||
|
"\trun { ${2:command} }",
|
||||||
|
"\t$0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Parallel execution block"
|
||||||
|
},
|
||||||
|
"Workers Block": {
|
||||||
|
"prefix": "workers",
|
||||||
|
"body": [
|
||||||
|
"workers ${1:2} {",
|
||||||
|
"\t$0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Workers block with concurrency limit"
|
||||||
|
},
|
||||||
|
"Run Block": {
|
||||||
|
"prefix": "run",
|
||||||
|
"body": [
|
||||||
|
"run {",
|
||||||
|
"\t$0",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Run block (use inside parallel/workers)"
|
||||||
|
},
|
||||||
|
"Echo": {
|
||||||
|
"prefix": "echo",
|
||||||
|
"body": [
|
||||||
|
"echo \"$0\""
|
||||||
|
],
|
||||||
|
"description": "Echo command"
|
||||||
|
},
|
||||||
|
"Variable Assignment": {
|
||||||
|
"prefix": "var",
|
||||||
|
"body": [
|
||||||
|
"${1:VARNAME} = \"${2:value}\"$0"
|
||||||
|
],
|
||||||
|
"description": "Variable assignment"
|
||||||
|
}
|
||||||
|
}
|
||||||
172
vsc-extension/rush/syntaxes/rush.tmLanguage.json
Normal file
172
vsc-extension/rush/syntaxes/rush.tmLanguage.json
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||||
|
"name": "Rush",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#shebang"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#invalid-syntax"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#comments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#keywords"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#control-flow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#builtin-variables"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#variables"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#strings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#numbers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#operators"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "#commands"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"shebang": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "meta.shebang.rush",
|
||||||
|
"match": "\\A#!.*$",
|
||||||
|
"captures": {
|
||||||
|
"0": {
|
||||||
|
"name": "comment.line.shebang.rush"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"invalid-syntax": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "invalid.illegal.missing-brace.rush",
|
||||||
|
"comment": "for/if/parallel/workers without opening brace on same line",
|
||||||
|
"match": "^\\s*(for\\s+\\w+\\s+in\\s+[^{]+$|if\\s+[^{]+$|parallel\\s*$|workers\\s+\\d+\\s*$)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "invalid.illegal.run-without-brace.rush",
|
||||||
|
"comment": "run keyword not followed by opening brace",
|
||||||
|
"match": "\\brun\\s+(?!\\{)\\w"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "invalid.illegal.missing-in.rush",
|
||||||
|
"comment": "for loop without 'in' keyword",
|
||||||
|
"match": "\\bfor\\s+\\w+\\s+(?!in\\b)\\w"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"comments": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "comment.line.number-sign.rush",
|
||||||
|
"match": "#(?!!).*$"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.control.rush",
|
||||||
|
"match": "\\b(if|else|for|in|while|return|not)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.other.rush",
|
||||||
|
"match": "\\b(parallel|workers|run)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"control-flow": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.control.conditional.rush",
|
||||||
|
"match": "\\b(if|else)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.control.loop.rush",
|
||||||
|
"match": "\\b(for|while|in)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"builtin-variables": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "variable.language.rush",
|
||||||
|
"match": "\\$\\b(IS_ROOT|USER|HOME|SHELL|PWD|OS|ARCH)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"variables": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "variable.other.rush",
|
||||||
|
"match": "\\$[a-zA-Z_][a-zA-Z0-9_]*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "variable.other.assignment.rush",
|
||||||
|
"match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?==)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"strings": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "string.quoted.double.rush",
|
||||||
|
"begin": "\"",
|
||||||
|
"end": "\"",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "#variables"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.character.escape.rush",
|
||||||
|
"match": "\\\\."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"numbers": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.rush",
|
||||||
|
"match": "\\b[0-9]+\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"operators": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.assignment.rush",
|
||||||
|
"match": "="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "punctuation.definition.block.rush",
|
||||||
|
"match": "[{}]"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"commands": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "support.function.builtin.rush",
|
||||||
|
"match": "\\b(echo|exit|cd|ls|mkdir|rm|cp|mv|cat|grep|find|chmod|chown)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scopeName": "source.rush"
|
||||||
|
}
|
||||||
29
vsc-extension/rush/vsc-extension-quickstart.md
Normal file
29
vsc-extension/rush/vsc-extension-quickstart.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Welcome to your VS Code Extension
|
||||||
|
|
||||||
|
## What's in the folder
|
||||||
|
|
||||||
|
* This folder contains all of the files necessary for your extension.
|
||||||
|
* `package.json` - this is the manifest file in which you declare your language support and define the location of the grammar file that has been copied into your extension.
|
||||||
|
* `syntaxes/rush.tmLanguage.json` - this is the Text mate grammar file that is used for tokenization.
|
||||||
|
* `language-configuration.json` - this is the language configuration, defining the tokens that are used for comments and brackets.
|
||||||
|
|
||||||
|
## Get up and running straight away
|
||||||
|
|
||||||
|
* Make sure the language configuration settings in `language-configuration.json` are accurate.
|
||||||
|
* Press `F5` to open a new window with your extension loaded.
|
||||||
|
* Create a new file with a file name suffix matching your language.
|
||||||
|
* Verify that syntax highlighting works and that the language configuration settings are working.
|
||||||
|
|
||||||
|
## Make changes
|
||||||
|
|
||||||
|
* You can relaunch the extension from the debug toolbar after making changes to the files listed above.
|
||||||
|
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
|
||||||
|
|
||||||
|
## Add more language features
|
||||||
|
|
||||||
|
* To add features such as IntelliSense, hovers and validators check out the VS Code extenders documentation at https://code.visualstudio.com/api/language-extensions/overview
|
||||||
|
|
||||||
|
## Install your extension
|
||||||
|
|
||||||
|
* To start using your extension with Visual Studio Code copy it into the `<user home>/.vscode/extensions` folder and restart Code.
|
||||||
|
* To share your extension with the world, read on https://code.visualstudio.com/api/working-with-extensions/publishing-extension about publishing an extension.
|
||||||
Reference in New Issue
Block a user