actual initial commit smh

This commit is contained in:
2026-06-20 21:21:19 +03:00
parent fff0e5ada5
commit 85978ee33d
219 changed files with 12350 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
[*.lua]
indent_style = space
indent_size = 2
+5
View File
@@ -0,0 +1,5 @@
.stfolder
.idea
home/*
halyde/logs/*
*.kate-swp
+101
View File
@@ -0,0 +1,101 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/lua-language-server/master/locale/en-us/setting.lua",
"runtime": {
"version": "Lua 5.3",
"special": {},
"unicodeName": false,
"nonstandardSymbol": []
},
"workspace": {
"checkThirdParty": false,
"maxPreload": 5000,
"preloadFileSize": 500
},
"diagnostics": {
"disable": [
"undefined-global",
"lowercase-global",
"missing-fields"
],
"globals": [
"_G",
"_VERSION",
"assert",
"error",
"getmetatable",
"ipairs",
"load",
"next",
"pairs",
"pcall",
"rawequal",
"rawget",
"rawlen",
"rawset",
"select",
"setmetatable",
"tonumber",
"tostring",
"type",
"xpcall",
"checkArg",
"bit32",
"coroutine",
"debug",
"math",
"os",
"string",
"table",
"component",
"computer",
"unicode",
"utf8"
]
},
"completion": {
"enable": true,
"callSnippet": "Both",
"keywordSnippet": "Both",
"displayContext": 6
},
"hover": {
"enable": true,
"viewString": true,
"viewStringMax": 1000,
"viewNumber": true,
"fieldInfer": 3000,
"previewFields": 50,
"enumsLimit": 5
},
"semantic": {
"enable": true,
"variable": true,
"annotation": true,
"keyword": false
},
"format": {
"enable": true,
"defaultConfig": {
"indent_style": "space",
"indent_size": "2",
"tab_width": "2"
}
},
"spell": {
"dict": []
},
"telemetry": {
"enable": false
},
"IntelliSense": {
"traceLocalSet": false,
"traceReturn": false,
"traceBeSetted": false,
"traceFieldInject": false
},
"type": {
"castNumberToInteger": false,
"weakUnionCheck": false,
"weakNilCheck": false
}
}
+674
View 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>.
+25
View File
@@ -0,0 +1,25 @@
# Halyde
### If you are viewing on GitHub: Halyde has moved to [Gitea](https://git.sting.lt/Cerulean-Blue/Halyde). This repository is now a read-only mirror. Contributions, issues and pull requests will not be processed here. Please go to [the Gitea instance](https://git.sting.lt/Cerulean-Blue/Halyde) for that.
A universal, customizable and feature-packed operating system for OpenComputers.
<p align="center">
<a href="https://lua.org/">
<img src="https://img.shields.io/badge/Written_in-Lua-blue?style=plastic&logo=lua" /></a>
<a href="https://ocdoc.cil.li/">
<img src="https://img.shields.io/badge/Made_for-OpenComputers-yellow?style=plastic" /></a>
<a href="https://wiki.sting.lt/">
<img src="https://img.shields.io/badge/Documented_on-DokuWiki-green?style=plastic" /></a>
</p>
## Installation
To install Halyde from OpenOS, simply run:
`pastebin run MB21BTMv`
If for some reason that doesn't work, try:
`wget -f https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/webinstall.lua /tmp/webinstall.lua && /tmp/webinstall.lua`
## Docs
Halyde is [documented on DokuWiki](https://wiki.sting.lt/), however there is an alternative documentation on [GitBook](https://cerulean-blue.gitbook.io/halyde-docs/).
+9
View File
@@ -0,0 +1,9 @@
{
"halyde": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"argentum": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"edit": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"package1": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"package2": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"vpkg": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"grouper": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/"
}
+117
View File
@@ -0,0 +1,117 @@
local agcfg = {
["halyde"] = {
["maindir"] = "",
["version"] = "2.8.1",
["description"] = "A universal, customizable and feature-packed operating system for OpenComputers.",
["directories"] = {
"halyde/apps",
"halyde/apps/helpdb",
"halyde/config/generate",
"halyde/core",
"halyde/drivers",
"halyde/lib",
"halyde",
"home",
"mnt",
"tmp",
"special/eeprom",
"special/drive",
"special"
},
["files"] = {
"init.lua",
"halyde/apps/helpdb/cat.txt",
"halyde/apps/helpdb/cd.txt",
"halyde/apps/helpdb/clear.txt",
"halyde/apps/helpdb/cp.txt",
"halyde/apps/helpdb/default.txt",
"halyde/apps/helpdb/download.txt",
"halyde/apps/helpdb/echo.txt",
"halyde/apps/helpdb/fetch.txt",
"halyde/apps/helpdb/help.txt",
"halyde/apps/helpdb/ls.txt",
"halyde/apps/helpdb/lscor.txt",
"halyde/apps/helpdb/lua.txt",
"halyde/apps/helpdb/mv.txt",
"halyde/apps/helpdb/reboot.txt",
"halyde/apps/helpdb/rm.txt",
"halyde/apps/helpdb/shutdown.txt",
"halyde/apps/boot.lua",
"halyde/apps/cat.lua",
"halyde/apps/cd.lua",
"halyde/apps/clear.lua",
"halyde/apps/cp.lua",
"halyde/apps/download.lua",
"halyde/apps/echo.lua",
"halyde/apps/fetch.lua",
"halyde/apps/help.lua",
"halyde/apps/label.lua",
"halyde/apps/ls.lua",
"halyde/apps/lscor.lua",
"halyde/apps/lsdrv.lua",
"halyde/apps/lua.lua",
"halyde/apps/maindrv.lua",
"halyde/apps/mkdir.lua",
"halyde/apps/mv.lua",
"halyde/apps/reboot.lua",
"halyde/apps/rm.lua",
"halyde/apps/shutdown.lua",
"halyde/config/oslogo.ans",
"halyde/config/generate/shell.json",
"halyde/config/generate/startupapps.json",
"halyde/core/boot.lua",
"halyde/core/cormgr.lua",
"halyde/core/datatools.lua",
"halyde/core/drvload.lua",
"halyde/core/evmgr.lua",
"halyde/core/fullkb.lua",
"halyde/core/shell.lua",
"halyde/core/termlib.lua",
"halyde/lib/component.lua",
"halyde/lib/computer.lua",
"halyde/lib/event.lua",
"halyde/lib/filesystem.lua",
"halyde/lib/json.lua",
"halyde/lib/raster.lua",
"halyde/lib/unicode.lua",
"halyde/lib/serialize.lua"
}
},
["argentum"] = {
["maindir"] = "",
["version"] = "1.3.1",
["description"] = "The default package manager for Halyde.",
["directories"] = {
"argentum",
"argentum/store"
},
["files"] = {
"argentum/registry.cfg",
"halyde/apps/argentum.lua",
"halyde/apps/helpdb/argentum.txt"
}
},
["edit"] = {
["maindir"] = "",
["version"] = "1.2.2",
["description"] = "The default text editor for Halyde.",
["files"] = {
"halyde/apps/edit.lua",
"halyde/apps/helpdb/edit.txt"
}
},
["webinstall-extras"] = { -- not actually a package
["directories"] = {
"argentum/store/halyde/files",
"argentum/store/edit/files",
"argentum/store/argentum/files"
},
["files"] = {
"argentum/store/halyde/package.cfg",
"argentum/store/edit/package.cfg",
"argentum/store/argentum/package.cfg",
}
}
}
return agcfg
+18
View File
@@ -0,0 +1,18 @@
local agregistry = {
["halyde"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/",
["edit"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/",
["argentum"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/",
["donut"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["libocif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["ocif-tools"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["hextra"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["utape"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["libctif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["ctif-viewer"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["libsha256"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/",
["sha256sum"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/",
["base64"] = "https://raw.githubusercontent.com/mcplayer3/AgPackages/refs/heads/main/"
}
return agregistry
+6
View File
@@ -0,0 +1,6 @@
Aargentum/store/
Aargentum/
V1.2.0
Aargentum/registry.cfg
Ahalyde/apps/argentum.lua
Ahalyde/apps/helpdb/argentum.txt
+3
View File
@@ -0,0 +1,3 @@
Ahalyde/apps/edit.lua
Ahalyde/apps/helpdb/edit.txt
V1.1.0
+53
View File
@@ -0,0 +1,53 @@
Amnt/
Ahome/
Ahalyde/lib/
Ahalyde/drivers/
Ahalyde/core/
Ahalyde/config/generate/
Ahalyde/apps/helpdb/
Ahalyde/apps/
V1.11.1
Ainit.lua
Ahalyde/apps/helpdb/cat.txt
Ahalyde/apps/helpdb/cd.txt
Ahalyde/apps/helpdb/clear.txt
Ahalyde/apps/helpdb/cp.txt
Ahalyde/apps/helpdb/default.txt
Ahalyde/apps/helpdb/echo.txt
Ahalyde/apps/helpdb/fetch.txt
Ahalyde/apps/helpdb/help.txt
Ahalyde/apps/helpdb/ls.txt
Ahalyde/apps/helpdb/lscor.txt
Ahalyde/apps/helpdb/lua.txt
Ahalyde/apps/helpdb/mv.txt
Ahalyde/apps/helpdb/rm.txt
Ahalyde/apps/cat.lua
Ahalyde/apps/cd.lua
Ahalyde/apps/clear.lua
Ahalyde/apps/cp.lua
Ahalyde/apps/echo.lua
Ahalyde/apps/fetch.lua
Ahalyde/apps/help.lua
Ahalyde/apps/ls.lua
Ahalyde/apps/lscor.lua
Ahalyde/apps/lua.lua
Ahalyde/apps/mkdir.lua
Ahalyde/apps/mv.lua
Ahalyde/apps/rm.lua
Ahalyde/config/oslogo.ans
Ahalyde/config/generate/shell.json
Ahalyde/config/generate/startupapps.json
Ahalyde/core/boot.lua
Ahalyde/core/cormgr.lua
Ahalyde/core/datatools.lua
Ahalyde/core/drvload.lua
Ahalyde/core/evmgr.lua
Ahalyde/core/fullkb.lua
Ahalyde/core/shell.lua
Ahalyde/core/termlib.lua
Ahalyde/lib/component.lua
Ahalyde/lib/computer.lua
Ahalyde/lib/event.lua
Ahalyde/lib/filesystem.lua
Ahalyde/lib/json.lua
Ahalyde/lib/raster.lua
+520
View File
@@ -0,0 +1,520 @@
local cliparse = require("cliparse")
local fs = require("filesystem")
local component = require("component")
local json = require("json")
local function getFile(path)
checkArg(1, path, "string")
if path:sub(1, 7) == "http://" or path:sub(1, 8) == "https://" then
if not component.list("internet")() then
return false, "Internet card required but not found."
end
local handle, data, tmpdata = component.internet.request(path), "", nil
local success, errorMessage = pcall(function()
handle:finishConnect()
end)
if not success then
return false, errorMessage
end
local code, message = handle:response()
if code and code ~= 200 then
return false, ("%d %s"):format(code, message)
end
repeat
tmpdata = handle.read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
return true, data
elseif path:sub(1, 1) == "/" then
if not fs.exists(path) then
return false, "No such file or directory: " .. path
end
if fs.isDirectory(path) then
return false, "Expected file, found directory: " .. path
end
local handle, data, tmpdata = fs.open(path, "r", false), "", nil
if not handle then
return false, data
end
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
return true, data
else
return false, "Unsupported path: " .. path
end
end
cliparse.config({
["x"] = 0,
["exclude-deps"] = 0,
["u"] = 0,
["update-registry"] = 0,
["f"] = 0,
["force"] = 0,
["c"] = 0,
["clean"] = 0,
["s"] = 1,
["source"] = 1,
["C"] = 0,
["cascade"] = 0,
})
local parsed, errorMessage = cliparse.parse(...)
if not parsed then
print(("\27[91m%s\n\27[0mExiting."):format(errorMessage))
return
end
local command = parsed.args[1]
if not command then
print("\27[91mNo command specified.\n\27[0mExiting.")
return
end
if
not (
command == "install"
or command == "remove"
or command == "update"
or command == "list"
or command == "repo-list"
or command == "repo-add"
or command == "repo-remove"
or command == "info"
)
then
print(("\27[91mInvalid command: %s\n\27[0mExiting."):format(command))
return
end
local packages = parsed.args
table.remove(packages, 1)
-- Remove the command from the actual package list
-- local result, data, failure
do
local function check(condition, message)
if not condition then
print(message)
failure = true
end
end
if parsed.flags.u or parsed.flags["update-registry"] then
terminal.write("Updating registry...")
result, data = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/ag2/registry.json")
check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m")
local handle, errorMessage = fs.open("/ag2/registry.json", "w")
check(handle, "\27[91mFailed to open write handle to registry: " .. errorMessage .. "\27[0m")
local success, errorMessage = handle:write(data)
check(success, "\27[91mFailed to write to registry: " .. errorMessage .. "\27[0m")
handle:close()
else
result, data = getFile("/ag2/registry.json")
check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m")
end
end
local success, registry = pcall(function()
return json.decode(data)
end)
if not success then
print(("\27[91mFailed to parse registry: %s\n\27[0mExiting."):format(registry))
return
end
local function getServersidePackageConfig(source, package)
local success, data = getFile(fs.concat(source, "/ag2.json"))
if not success then
return false, ("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data .. "\27[0m"):format(package)
end
local success, packageConfig = pcall(function()
return json.decode(data)
end)
if not success then
return false, ("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig .. "\27[0m"):format(package)
end
if not packageConfig[package] then
return false, ("\27[91mRepository package config (ag2.json) does not contain package '%s'.\27[0m"):format(package)
end
return packageConfig[package]
end
-- Check if everything is valid
failure = false
local dependencyCounter = 0
local previousI = 1 -- See 190
::RESETLOOP::
if command == "install" then
for i = previousI, #packages do
if not packages[i] then
-- When packages are removed, the last packages can end up reading as nil
goto SKIP
end
local otherPackages = table.copy(packages)
table.remove(otherPackages, i)
-- This is to check if the package can be found in the others, or in other words, checking for duplicates
if table.find(otherPackages, packages[i]) then
print(("\27[93mDuplicate package specified (%s), skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
goto SKIP
end
local source
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[packages[i]]
end
if not source then
print("\27[91mCould not find package in registry and no source provided: " .. packages[i] .. "\27[0m")
failure = true
goto SKIP
end
local packageConfig, errorMessage = getServersidePackageConfig(source, packages[i])
if not packageConfig then
failure = true
print(errorMessage)
goto SKIP
end
if packageConfig.type == "group" then
table.remove(packages, i)
for _, package in ipairs(packageConfig.packages) do
table.insert(packages, package)
end
previousI = i
goto RESETLOOP
-- Apparently "for i = 1, n" loops don't change when n changes. So when the table gets extended, packages near the end won't get picked up.
-- This is why it is needed to restart the loop.
elseif packageConfig.type == "virtual-package" then
print(("Installing virtual package %s"):format(packages[i]))
local pkgAskText = ("Select a package by typing in its number: 1) %s"):format(packageConfig.packages[1])
-- This is all a silly workaround to place commas correctly
for i = 2, #packageConfig.packages do
pkgAskText = pkgAskText .. (", %d) %s"):format(i, packageConfig.packages[i])
end
print(pkgAskText)
::RETRY::
local packageSel = terminal.read()
if not tonumber(packageSel) or tonumber(packageSel) % 1 ~= 0 then
-- Is there really no better way to check for an int..?
print("\27[93mNon-integer received - try again\27[0m")
goto RETRY
end
if tonumber(packageSel) < 1 or tonumber(packageSel) > #packageConfig.packages then
print("\27[93mInteger out of range - try again\27[0m")
end
packages[i] = packageConfig.packages[tonumber(packageSel)]
i = i - 1
goto SKIP
end
if fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then
print(("\27[93mPackage %s is already installed, skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
goto SKIP
end
if packageConfig.dependencies then
for _, dependency in ipairs(packageConfig.dependencies) do
table.insert(packages, i + 1, dependency)
dependencyCounter = dependencyCounter + 1
end
end
-- TODO: Add checks for conflicting packages
::SKIP::
end
elseif command == "remove" then
::JUMPBACK::
local doJumpBack = false
for i = 1, #packages do
if not fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[package]
end
if source then
local packageConfig = getServersidePackageConfig(source, packages[i])
if packageConfig then
if packageConfig.type == "virtual-package" or packageConfig.type == "group" then
table.remove(packages, i)
for _, groupPackage in ipairs(packageConfig.packages) do
table.insert(packages, groupPackage)
goto GOAHEAD
end
end
end
end
print(("\27[93mPackage %s is not installed, skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
::GOAHEAD::
end
end
-- I was originally gonna add this in the dependency cascade section, but realized it could shorten the normal dependency check code a bit
local dependencyList = {}
for _, packageConfig in ipairs(fs.list("/ag2/pkg/")) do
local package = packageConfig:sub(1, -6)
-- I'm not adding error handling here because if this fails then fuck you for touching the files by hand and good luck figuring this shit out
local _, data = getFile(("/ag2/pkg/%s.json"):format(package))
data = json.decode(data)
dependencyList[package] = data.dependencies
end
for _, package in ipairs(packages) do
if dependencyList[package] then
-- Check if all the deps are no longer needed and if they're auto-installed and stuff
for _, dependency in pairs(dependencyList[package]) do
if fs.exists(("/ag2/pkg/%s.json"):format(dependency)) then
local _, data = getFile(("/ag2/pkg/%s.json"):format(dependency))
data = json.decode(data)
if data.autoInstalled
and not table.find(packages, dependency) -- Just to prevent dependency loops and issues when re-checking the packages after jumpback
then
dependencyCounter = dependencyCounter + 1
table.insert(packages, dependency)
end
else
-- It could still be a group or a vpackage, and checking that is the job of the loop 2 loops back
table.insert(packages, dependency)
doJumpBack = true
end
end
end
end
-- Check for cascading dependencies
for packageName, dependencies in pairs(dependencyList) do
if not table.find(packages, packageName) then
for _, dependency in ipairs(dependencies) do
if table.find(packages, dependency) then
if parsed.flags.cascade or parsed.flags.C then
table.insert(packages, packageName)
dependencyCounter = dependencyCounter + 1
doJumpBack = true
-- Listen, I'm so sorry for this abhorrent bullshit code, but the newly added packages have to get checked one way or another and hopefully this is readable enough.
else
-- The Pyramids of Giza were built entirely out of silver. No they weren't...
print(("\27[93mPackage %s is depended on by %s, cannot uninstall without --cascade\27[0m"):format(dependency, packageName))
failure = true
end
end
end
end
end
if doJumpBack then
goto JUMPBACK
-- IT'S NOT SPAGHETTI SHUT UP SHUT UP SHU
end
end
-- TODO: Add checks for the other commands
if #packages == 0 then
print("\27[93mNo packages selected.\n\27[0mExiting.")
return
end
if failure then
print("Exiting.")
return
end
if command == "install" then
if dependencyCounter == 1 then
print("\27[93m1 dependency pulled in.\27[0m")
elseif dependencyCounter >= 2 then
print(("\27[93m%d dependencies pulled in.\27[0m"):format(dependencyCounter))
end
print("Packages that will be installed:")
print(table.concat(packages, ", "))
local answer = terminal.read({prefix = "\nContinue? [Y/n] "})
if answer:lower() == "n" then
print("Exiting.")
return
end
for _, package in ipairs(packages) do
local source
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[package]
end
print(("Installing %s..."):format(package))
local _, data = getFile(fs.concat(source, "/ag2.json"))
local packageConfig = json.decode(data)[package]
if packageConfig.directories then
for _, directory in ipairs(packageConfig.directories) do
print((" Creating directory %s..."):format(directory))
fs.makeDirectory(directory)
end
end
if packageConfig.files then
for _, file in ipairs(packageConfig.files) do
::RETRY::
print((" Downloading file %s..."):format(file))
local success, data = getFile(fs.concat(source, file))
if not success then
print(("\27[91mFailed to get file '%s' of package '%s': " .. data .. "\27[0m"):format(file, package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
if fs.exists(file) then
print(("\27[93mFile '%s' already exists.\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Overwrite, Skip? [a/O/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
end
end
local handle, errorMessage = fs.open(file, "w")
if not handle then
print(("\27[91mFailed to open write handle to file '%s': " .. errorMessage .. "\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
local success, errorMessage = handle:write(data)
if not success then
handle:close()
print(("\27[91mFailed to write to file '%s': " .. errorMessage .. "\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
handle:close()
::SKIP::
end
end
print(" Writing tracking file...")
if not fs.exists("/ag2/pkg/") then
fs.makeDirectory("/ag2/pkg/")
-- Technically this would break if /ag2/pkg/ was a file, but... why would it be a file?
end
-- TODO: Make functions for reading from and writing to a file with error handling since this is really repetitive
::RETRY::
local handle, errorMessage = fs.open(("/ag2/pkg/%s.json"):format(package), "w")
if not handle then
print(("\27[91mFailed to open write handle to file '/ag2/pkg/%s.json': " .. errorMessage .. "\27[0m"):format(package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package))
goto SKIP
else
goto RETRY
end
end
local packageData = {
name = package,
version = packageConfig.version,
autoInstalled = false,
-- TODO: Make the above actually work
dependencies = packageConfig.dependencies,
conflicts = packageConfig.conflicts,
files = packageConfig.files,
directories = packageConfig.directories,
config = packageConfig.config
}
local trackingFile = json.encode(packageData)
local success, errorMessage = handle:write(trackingFile)
if not success then
handle:close()
print(("\27[91mFailed to write to file '/ag2/pkg/%s.json': " .. errorMessage .. "\27[0m"):format(package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package))
goto SKIP
else
goto RETRY
end
else
handle:close()
end
::SKIP::
end
elseif command == "remove" then
if dependencyCounter == 1 then
print("\27[93m1 orphaned dependency will be removed.\27[0m")
elseif dependencyCounter >= 2 then
print(("\27[93m%d orphaned dependencies will be removed.\27[0m"):format(dependencyCounter))
end
print("Packages that will be removed:")
print(table.concat(packages, ", "))
local answer = terminal.read({prefix = "\nContinue? [Y/n] "})
if answer:lower() == "n" then
print("Exiting.")
return
end
for _, package in ipairs(packages) do
-- See line 263
local _, data = getFile(("/ag2/pkg/%s.json"):format(package))
data = json.decode(data)
if data.files then
for _, file in ipairs(data.files) do
print((" Removing file %s..."):format(file))
fs.remove(file) -- I cannot think of how this could fail. If you can, please add error handling here
end
end
if data.directories then
for _, directory in ipairs(data.directories) do
if fs.isDirectory(directory) then
if next(fs.list(directory)) == nil then
-- Apparently THAT's the best way to check if a table is empty in Lua. Alright I guess.
print((" Removing directory %s..."):format(directory))
fs.remove(directory)
else
print((" Directory %s specified by package %s still has something in it, skipping"):format(directory))
end
end
end
end
print(" Removing tracking file...")
fs.remove(("/ag2/pkg/%s.json"):format(package)) -- See line 500
end
end
print("Operation completed successfully.")
+688
View File
@@ -0,0 +1,688 @@
local packages = {...}
local command = table.remove(packages,1)
local shell = require("shell")
local fs = require("filesystem")
local component = require("component")
local agReg = require("/argentum/registry.cfg")
if not command then
shell.run("help argentum")
return
end
if not component.list("internet")() then
print("\27[91mThis program requires an internet card to run.\27[0m")
return
end
local internet = component.internet
local source
if table.find(packages, "-s") then
source = table.remove(packages, table.find(packages, "-s") + 1)
table.remove(packages, table.find(packages, "-s"))
print("Using " .. source .. " as package source")
elseif table.find(packages, "--source") then
source = table.remove(packages, table.find(packages, "--source") + 1)
table.remove(packages, table.find(packages, "--source"))
print("Using " .. source .. " as package source")
else
print("Using main registry as package source")
end
if source and source:sub(1, 1) == "/" and source:sub(-1, -1) ~= "/" then
source = source .. "/"
end
local packageList = table.copy(packages)
local function getFile(path)
if path:sub(1,1) == "/" then
if not fs.exists(path) then
return false, "file does not exist"
end
local handle, data, tmpdata = fs.open(path, "r"), "", nil
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
return data
else
local request, data, tmpdata = nil, "", nil
local status, errorMessage = pcall(function()
request = internet.request(path)
request:finishConnect()
end)
if not status then
return false, errorMessage
end
local responseCode = request:response()
if responseCode and responseCode ~= 200 then
return false, responseCode
end
repeat
tmpdata = request.read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
return data
end
end
local i = 1
local function getAgConfig(package, source)
source = source or agReg[package]
local data, errorMessage = getFile(source .. "argentum.cfg")
if not data or data == "" then
print("\27[91mCould not fetch Ag config: " .. (errorMessage or "returned nil data") .. "\27[0m")
return false
end
local func, errorMessage = load(data, "=argentum.cfg", "bt", {})
if not func then
print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.\27[0m")
return false
end
local agcfg
local status, errorMessage = pcall(function()
agcfg = func()
end)
if not status then
print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.\27[0m")
return false
end
if not agcfg[package] or not agcfg[package].maindir or not agcfg[package].directories or not agcfg[package].files or not agcfg[package].version then
local response = "\27[91mAg config of " .. package .. " is improperly configured.\nPlease contact the package owner.\27[0m"
end
return agcfg
end
local function doChecks(package)
if not agReg[package] and not source then
print("\27[91mPackage " .. package .. " does not exist.\27[0m")
return false
end
if fs.exists("/argentum/store/" .. package) then
print("\27[91mPackage " .. package .. " is already installed.\27[0m")
return false
end
agcfg = getAgConfig(package, source)
if not agcfg then
return false
end
if agcfg[package].dependencies then
for _, dependency in ipairs(agcfg[package].dependencies) do
if not agReg[dependency] and not agcfg[dependency] then
local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]\27[0m"})
if response:lower() ~= "s" then
fs.remove("/argentum/store/" .. package)
return false
end
end
end
for _, dependency in pairs(agcfg[package].dependencies) do
print(package .. " depends on " .. dependency)
if not table.find(packages, dependency) and doChecks(dependency) then
table.insert(packages, table.find(packages, package), dependency)
table.insert(packageList, dependency)
i = i + 1
end
end
end
return true
end
local function lpad(str, len, char)
str=tostring(str)
if char == nil then char = ' ' end
return string.rep(char, len - #str) .. str
end
local width, height = terminal.getResolution()
local function progress(package, progress)
terminal.write("\x1b[s")
local info = string.format("%s %s%%", package, lpad(math.floor(progress * 100), 2))
info = info .. string.rep(" ", width - #info)
local progX = math.floor(progress * width)
terminal.write("\x1b[42m\x1b[30m" .. info:sub(1, progX) .. "\x1b[40m\x1b[37m" .. info:sub(progX + 1) .. "\x1b[0m")
terminal.write("\x1b[u")
end
local function clearProgress()
terminal.write("\x1b[s")
terminal.write("\r\x1b[40m" .. string.rep(" ", width) .. "\x1b[0m\r")
terminal.write("\x1b[u")
end
local function installPackage(package, overwriteFlag)
if not overwriteFlag then
overwriteFlag = false
end
if not overwriteFlag then print("Installing " .. package .. "...") end
local agcfg = getAgConfig(package, source)
if not agcfg then
return false
end
local source = source or agReg[package]
local packageStore = "V" .. agcfg[package].version
if agcfg[package].dependencies then
for _, dependency in ipairs(agcfg[package].dependencies) do
if not agReg[dependency] and not agcfg[dependency] then
local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]\27[0m"})
if response:lower() ~= "s" then
fs.remove("/argentum/store/" .. package)
return false
end
end
end
for _, dependency in pairs(agcfg[package].dependencies) do
if agReg[dependency] or agcfg[dependency] then
--installPackage(dependency)
packageStore = packageStore .. "\nD" .. dependency
end
end
end
if agcfg[package].directories then
for _, directory in pairs(agcfg[package].directories) do
if directory:sub(-1, -1) ~= "/" then
directory = directory .. "/"
end
packageStore = "A" .. directory .. "\n" .. packageStore
if not fs.exists(directory) then
fs.makeDirectory(directory)
end
end
end
for idx, file in ipairs(agcfg[package].files) do
clearProgress()
::retry::
print(" Downloading " .. file .. "...")
progress(package,idx/#agcfg[package].files)
local data, errorMessage = getFile(source .. agcfg[package].maindir .. file)
if not data then
clearProgress()
local response = terminal.read({prefix = "\27[91mCould not fetch " .. file .. ": " .. errorMessage .. "\n\27[0m[a - Abort/R - Retry/s - Skip]"})
if response:lower() == "a" then
fs.remove("/argentum/store/" .. package)
return false
elseif response:lower() == "s" then
goto skip
else
goto retry
end
end
if fs.exists(file) and not overwriteFlag then
if not fs.exists("/argentum/store/" .. package .. "/files/" .. file:match("(.*/)")) then
fs.makeDirectory("/argentum/store/" .. package .. "/files/" .. file:match("(.*/)"))
end
fs.copy(file, "/argentum/store/" .. package .. "/files/" .. file)
packageStore = packageStore .. "\nM" .. file
else
packageStore = packageStore .. "\nA" .. file
end
local handle, err = fs.open(file, "w")
handle:write(data)
handle:close()
::skip::
end
clearProgress()
fs.makeDirectory("/argentum/store/" .. package)
local handle = fs.open("/argentum/store/" .. package .. "/package.cfg", "w")
handle:write(packageStore)
handle:close()
return true
end
local function removePackage(package)
print("Removing " .. package .. "...")
if not fs.exists("/argentum/store/" .. package .. "/package.cfg") then
print("\27[91mLocal Ag config of " .. package .. " does not exist.\27[0m")
return false
end
local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
data = data .. "\n"
local idx = 0
for line in data:gmatch("(.-)\n") do
idx=idx+1
if line:sub(1, 1) == "A" then
clearProgress()
::retry::
print(" Removing " .. line:sub(2) .. "...")
if line:sub(-1, -1) == "/" and fs.list(line:sub(2))[1] then
print(" There are still files in " .. line:sub(2) .. ". Skipping.")
else
progress(package,idx/select(2,data:gsub("\n","\n")))
local result, errorMessage = fs.remove(line:sub(2))
if not result then
clearProgress()
local response = terminal.read({prefix = "\27[91mFailed to remove " .. line:sub(2) .. ": " .. errorMessage .. "\n\27[0m[a - Abort/r - Retry/S - Skip]"})
if response:lower() == "a" then
return false
elseif response:lower() == "r" then
goto retry
end
end
end
elseif line:sub(1, 1) == "M" then
clearProgress()
::retry::
print(" Reverting " .. line:sub(2) .. "...")
progress(package,idx/select(2,data:gsub("\n","\n")))
local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/files/" .. line:sub(2), "r"), "", nil
if not handle then
clearProgress()
local response = terminal.read({prefix = "\27[91mFailed to revert " .. line:sub(2) .. ": " .. data .. "\n\27[0m[a - Abort/R - Retry/s - Skip]"}) -- this is pretty stupid but i think the error message would get pushed to data
if response:lower() == "a" then
return false
elseif response:lower() == "s" then
goto skip
else
goto retry
end
end
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
local handle = fs.open(line:sub(2), "w")
handle:write(data)
handle:close()
::skip::
end
end
clearProgress()
fs.remove("/argentum/store/" .. package .. "/")
return true
end
local function updatePackage(package)
print("Updating " .. package .. "...")
local agcfg = getAgConfig(package, source)
if not agcfg then
return false
end
local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
local oldFiles = {}
for line in (data .. "\n"):gmatch("(.-)\n") do
if line:sub(1, 1) == "A" or line:sub(1, 1) == "M" then
if agcfg[package].directories then
if not table.find(agcfg[package].files, line:sub(2)) and not table.find(agcfg[package].directories, line:sub(2, -2)) then
table.insert(oldFiles, line:sub(2))
end
else
if not table.find(agcfg[package].files, line:sub(2)) then
table.insert(oldFiles, line:sub(2))
end
end
end
end
for _, oldFile in pairs(oldFiles) do
print(" Removing " .. oldFile .. "...")
end
return installPackage(package, true)
end
local fails = {}
if command == "install" then
if not packages or not packages[1] then
print("Please specify packages to install.")
return
end
print("Fetching Ag registry...")
local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg")
if newRegistry then
local handle = fs.open("/argentum/registry.cfg", "w")
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end
agReg = require("/argentum/registry.cfg")
while true do
if not doChecks(packages[i]) then
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
end
i = i + 1
if i > #packages then
break
end
end
local answer
if #fails == 0 then
print("Packages that will be installed: " .. table.concat(packageList, ", "))
if terminal.read({prefix = "Would you like to proceed? [Y/n] "}):lower() == "n" then
return
end
elseif #packageList == 0 then
print("None of the packages can be installed.")
return
else
print("Some packages cannot be installed.")
print("Packages that will be installed: " .. table.concat(packageList, ", "))
print("Packages that cannot be installed: " .. table.concat(fails, ", "))
if terminal.read({prefix = "Would you like to proceed? [y/N] "}):lower() ~= "y" then
return
end
end
for _, failedPackage in pairs(fails) do
table.remove(packages, table.find(packages, failedPackage))
end
fails = {}
for _, package in ipairs(packages) do
if not installPackage(package) then
table.insert(fails, package)
table.remove(packageList, table.find(packageList, package))
end
end
if #fails == 0 then
print("Installation completed successfully.")
print("Packages installed: " .. table.concat(packageList, ", "))
elseif #packageList == 0 then
print("All packages failed to install.")
print("Packages that could not be installed: " .. table.concat(fails, ", "))
else
print("Some packages failed to install.")
print("Packages installed: " .. table.concat(packageList, ", "))
print("Packages that could not be installed: " .. table.concat(fails, ", "))
end
elseif command == "remove" then
if not packages or not packages[1] then
print("Please specify packages to remove.")
return
end
while true do
if not fs.exists("/argentum/store/" .. packages[i]) then
print("\27[91mPackage " .. packages[i] .. " is not installed.\27[0m")
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
i = i - 1
elseif packages[i] == "halyde" then -- yes, this stuff is hard-coded.
print("\27[91mFor obvious reasons, you can't uninstall Halyde.\27[0m")
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
i = i - 1
elseif packages[i] == "argentum" then
print("\27[91mFor obvious reasons, you can't uninstall Argentum.\27[0m")
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
i = i - 1
end
i = i + 1
if i > #packages then
break
end
end
-- do dependency checks
local packagesInstalled = fs.list("/argentum/store")
for _, currentPackage in pairs(packagesInstalled) do
if currentPackage:sub(-1, -1) == "/" and fs.exists("/argentum/store/" .. currentPackage .. "package.cfg") then
local handle, data, tmpdata = fs.open("/argentum/store/" .. currentPackage .. "package.cfg", "r"), "", nil
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
for line in (data.."\n"):gmatch("(.-)\n") do
for i = 1, #packages do
if line == "D" .. packages[i] then
print(packages[i] .. " depends on " .. currentPackage:sub(1, -2))
if not table.find(packages, currentPackage:sub(1, -2)) then
table.insert(packages, table.find(packages, packages[i]), currentPackage:sub(1, -2))
table.insert(packageList, currentPackage:sub(1, -2))
i = i + 1
end
end
end
end
end
end
local answer
if #fails == 0 then
print("Packages that will be removed: " .. table.concat(packageList, ", "))
if terminal.read({prefix = "Would you like to proceed? [Y/n] "}):lower() == "n" then
return
end
elseif #packageList == 0 then
print("None of the packages can be removed.")
return
else
print("Some packages cannot be removed.")
print("Packages that will be removed: " .. table.concat(packageList, ", "))
print("Packages that cannot be removed: " .. table.concat(fails, ", "))
if terminal.read({prefix = "Would you like to proceed? [y/N] "}):lower() ~= "y" then
return
end
end
for _, failedPackage in pairs(fails) do
table.remove(packages, table.find(packages, failedPackage))
end
fails = {}
for _, package in ipairs(packages) do
if not removePackage(package) then
table.insert(fails, package)
table.remove(packageList, table.find(packageList, package))
end
end
if #fails == 0 then
print("Removal completed successfully.")
print("Packages removed: " .. table.concat(packageList, ", "))
elseif #packageList == 0 then
print("All packages failed to be removed.")
print("Packages that could not be removed: " .. table.concat(fails, ", "))
else
print("Some packages failed to be removed.")
print("Packages removed: " .. table.concat(packageList, ", "))
print("Packages that could not be removed: " .. table.concat(fails, ", "))
end
elseif command == "update" then
print("Fetching Ag registry...")
local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg")
if newRegistry then
local handle = fs.open("/argentum/registry.cfg", "w")
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end
agReg = require("/argentum/registry.cfg")
if not packages[1] then
local packagesInstalled = fs.list("/argentum/store/")
for _, currentPackage in pairs(packagesInstalled) do
if currentPackage:sub(-1, -1) == "/" and fs.exists("/argentum/store/" .. currentPackage .. "package.cfg") then
table.insert(packages, currentPackage:sub(1, -2))
table.insert(packageList, currentPackage:sub(1, -2))
end
end
end
while true do
local nonexistent = false -- I couldn't figure out a better way to do this, so I have to use a flag if the package doesn't exist
if not fs.exists("/argentum/store/" .. packages[i]) then
print("\27[91mPackage " .. packages[i] .. " is not installed.")
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
i = i - 1
nonexistent = true
end
if not nonexistent then
-- Check if up to date
local agcfg = getAgConfig(packages[i], source)
if not agcfg then
return false
end
local handle, data, tmpdata = fs.open("/argentum/store/" .. packages[i] .. "/package.cfg", "r"), "", nil
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
local version = "0.0.0"
for line in (data.."\n"):gmatch("(.-)\n") do
if line:sub(1, 1) == "V" then
version = line:sub(2)
break
end
end
if agcfg[packages[i]].version == version then
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
i = i - 1
else
print(packages[i].." is out of date [\x1b[93m"..version.."\x1b[0m < \x1b[92m"..agcfg[packages[i]].version.."\x1b[0m")
end
end
i = i + 1
if i > #packages then
break
end
end
local answer
if #packageList == 0 then
if #fails == 0 then
print("All packages are up to date.")
return
else
print("None of the packages can be updated.")
return
end
elseif #fails == 0 then
print("Packages that will be updated: " .. table.concat(packageList, ", "))
if terminal.read({prefix = "Would you like to proceed? [Y/n] "}):lower() == "n" then
return
end
else
print("Some packages cannot be updated.")
print("Packages that will be updated: " .. table.concat(packageList, ", "))
print("Packages that cannot be updated: " .. table.concat(fails, ", "))
if terminal.read({prefix = "Would you like to proceed? [y/N] "}):lower() ~= "y" then
return
end
end
for _, failedPackage in pairs(fails) do
table.remove(packages, table.find(packages, failedPackage))
end
fails = {}
for _, package in pairs(packages) do
-- Previous up-to-date check
--local agcfg = getAgConfig(package, source)
--if not agcfg then
-- return false
--end
--local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil
--repeat
-- tmpdata = handle:read(math.huge)
-- data = data .. (tmpdata or "")
--until not tmpdata
--handle:close()
--local version = "0.0.0"
--for line in (data.."\n"):gmatch("(.-)\n") do
-- if line:sub(1, 1) == "V" then
-- version = line:sub(2)
-- break
-- end
--end
--if agcfg[package].version == version then
-- print(package .. " is up to date")
-- goto skip
--end
if not updatePackage(package) then
table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i]))
goto skip
end
::skip::
end
if #fails == 0 then
print("Update completed successfully.")
print("Packages updated: " .. table.concat(packageList, ", "))
elseif #packageList == 0 then
print("All packages failed to update.")
print("Packages that could not update: " .. table.concat(fails, ", "))
else
print("Some packages failed to update.")
print("Packages updated: " .. table.concat(packageList, ", "))
print("Packages that could not update: " .. table.concat(fails, ", "))
end
elseif command == "info" then
if not packages[1] then
print("Please specify a package to show information about.")
return
end
print("Fetching Ag registry...")
local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg")
if newRegistry then
local handle = fs.open("/argentum/registry.cfg", "w")
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end
agReg = require("/argentum/registry.cfg")
if not agReg[packages[1]] and not source then
print("\27[91mPackage " .. packages[1] .. " does not exist.")
return
end
local agcfg = getAgConfig(packages[1], source)
if not agcfg then
return false
end
print("\27[93m" .. packages[1] .. "\27[0m v" .. agcfg[packages[1]].version .. "\n " .. (agcfg[packages[1]].description or "No description."):gsub("\n", " \n"))
elseif command == "search" then
if not packages[1] then
print("Please specify a search term.")
return
end
print("Fetching Ag registry...")
local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg")
if newRegistry then
local handle = fs.open("/argentum/registry.cfg", "w")
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil"))
end
agReg = require("/argentum/registry.cfg")
local searchResults = {}
for packageName, _ in pairs(agReg) do
if packageName:find(packages[1], 1, true) then
table.insert(searchResults, packageName)
end
end
if not searchResults[1] then
print("No search results found for " .. packages[1] .. ".")
return
end
table.sort(searchResults)
print("Search results: \n " .. table.concat(searchResults, "\n "))
elseif command == "list" then
print("Fetching Ag registry...")
local newRegistry, errorMessage = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/argentum/registry.cfg")
if newRegistry then
local handle = fs.open("/argentum/registry.cfg", "w")
handle:write(newRegistry)
handle:close()
else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end
agReg = require("/argentum/registry.cfg")
local sortedPackages = {}
for packageName, _ in pairs(agReg) do
table.insert(sortedPackages, packageName)
end
table.sort(sortedPackages)
print("List of available Ag packages: \n " .. table.concat(sortedPackages, "\n "))
else
shell.run("help ag")
end
+334
View File
@@ -0,0 +1,334 @@
local fs = require("filesystem")
local shell = require("shell")
local gpu = require("component").gpu
local event = require("event")
local computer = require("computer")
local ocelot = require("component").ocelot
local resX, resY = gpu.getResolution()
local textBuffer = gpu.allocateBuffer(resX, resY - 1)
local args = {...}
local file = args[1]
if not file then
print("\x1b[91mEnter a file name.")
return
end
if fs.isDirectory(file) then
print("\x1b[91mThe specified file is a directory.")
return
end
if file:sub(1, 1) ~= "/" then
file = fs.concat(shell.getWorkingDirectory(), file)
end
local data = ""
if fs.exists(file) then
local handle = fs.open(file)
local tmpdata
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
end
local lines = {}
for line in data:gmatch("[^\r\n]+") do
table.insert(lines, line)
end
local function renderText(xOffset, yOffset)
gpu.setActiveBuffer(textBuffer)
gpu.setBackground(0x000000)
gpu.setForeground(0xFFFFFF)
gpu.fill(1, 1, resX, resY - 1, " ")
for i = yOffset + 1, #lines do
gpu.set(1, i - yOffset, lines[i]:sub(xOffset + 1))
end
gpu.setActiveBuffer(0)
gpu.bitblt(0, 1, 1, resX, resY - 1, textBuffer, 1, 1)
end
local function addLine(pos)
if pos <= #lines then
for i = #lines, pos, -1 do
lines[i + 1] = lines[i]
end
lines[pos] = ""
else
table.insert(lines, "")
end
end
local function removeLine(pos)
length = #lines
for i = pos + 1, length do
lines[i - 1] = lines[i]
end
lines[length] = nil
end
local function save()
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)
gpu.set(1, resY, "Enter a location to save:")
local savepath = file or ""
local saveCursorX = 27 + #savepath
local ready = false
local eventArgs = {}
gpu.fill(27, resY, resX - 27, 1, " ")
gpu.set(27, resY, savepath)
gpu.setForeground(0x000000)
gpu.setBackground(0xFFFFFF)
gpu.set(saveCursorX, resY, " ")
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)
repeat
local shouldRender = false
coroutine.yield()
eventArgs = {event.pull("key_down", 0)}
if keyboard.keys[eventArgs[4]] == "enter" then
ready = true
end
if keyboard.keys[eventArgs[4]] == "back" then
savepath = string.sub(savepath, 1, #savepath - 1)
if saveCursorX > 27 then
saveCursorX = saveCursorX - 1
end
shouldRender = true
end
if not keyboard.keys.special[eventArgs[4]] and keyboard.keys[eventArgs[4]] then
savepath = savepath .. (string.char(eventArgs[3]) or "")
saveCursorX = saveCursorX + 1
shouldRender = true
end
if shouldRender then
gpu.fill(27, resY, resX - 27, 1, " ")
gpu.set(27, resY, savepath)
gpu.setForeground(0x000000)
gpu.setBackground(0xFFFFFF)
gpu.set(saveCursorX, resY, " ")
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)
end
until ready
gpu.fill(1, resY, resX, 1, " ")
gpu.setForeground(0x000000)
gpu.setBackground(0xFFFFFF)
gpu.set(1, resY, "^X")
gpu.set(10, resY, "^S")
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)
gpu.set(4, resY, "Exit")
gpu.set(13, resY, "Save")
local text = table.concat(lines, "\n")
local handle = fs.open(savepath, "w")
handle:write(text)
handle:close()
file = savepath
end
-- Initialize screen
renderText(0, 0)
gpu.setForeground(0x000000)
gpu.setBackground(0xFFFFFF)
gpu.set(1, resY, "^X")
gpu.set(10, resY, "^S")
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)
gpu.set(4, resY, "Exit")
gpu.set(13, resY, "Save")
local scrollX = 0
local scrollY = 0
local cursorX = 1 -- Absolute position, not accounting for scrolling
local cursorY = 1
local cursorWhite = true
local oldTime = computer.uptime()
while true do
local renderBufferFlag = false -- Flag to render the whole text buffer
-- Handle events
local previousCursorX -- Used for blackening the previous cursor location when the cursor is moved
local previousCursorY
coroutine.yield()
local eventArgs = {}
repeat
eventArgs = {event.pull("key_down", 0)}
-- The logical solution here for flashing the cursor would be to set the timeout to 0.5, and, if the timeout is reached, change the color.
-- However, that makes scrolling freeze the screen up completely.
-- Thus, for flashing the cursor, a timer is needed.
if computer.uptime() >= oldTime + 0.5 then
oldTime = computer.uptime()
cursorWhite = not cursorWhite
end
if next(eventArgs) ~= nil then
cursorWhite = true
oldTime = computer.uptime()
end
if eventArgs[1] == "key_down" then
-- Mouse events might be added later, that's why this if statement is here
if keyboard.getCtrlDown() then
-- Special commands
if keyboard.keys[eventArgs[4]] == "x" then
goto exit
end
if keyboard.keys[eventArgs[4]] == "s" then
save()
end
else
if keyboard.keys[eventArgs[4]] == "up" and cursorY > 1 then
if cursorY - scrollY <= 1 then
renderBufferFlag = true
scrollY = scrollY - 1
else
if not previousCursorX and not previousCursorY then
previousCursorX = cursorX
previousCursorY = cursorY
end
end
cursorY = cursorY - 1
-- The cursor absolute position still has to be moved, even if on-screen it won't move
if cursorX > string.len(lines[cursorY]) + 1 then -- cursor snapping, +1 to be 1 char in front of end of line
cursorX = string.len(lines[cursorY]) + 1
end
end
if keyboard.keys[eventArgs[4]] == "down" then
if cursorY - scrollY >= resY - 1 then
renderBufferFlag = true
scrollY = scrollY + 1
else
if not previousCursorX and not previousCursorY then
previousCursorX = cursorX
previousCursorY = cursorY
end
end
cursorY = cursorY + 1
if cursorX > string.len(lines[cursorY]) + 1 then
cursorX = string.len(lines[cursorY]) + 1
end
end
if keyboard.keys[eventArgs[4]] == "left" and cursorX > 1 then
if cursorX - scrollX <= 1 then
renderBufferFlag = true
scrollX = scrollX - 1
else
if not previousCursorX and not previousCursorY then
previousCursorX = cursorX
previousCursorY = cursorY
end
end
cursorX = cursorX - 1
end
if keyboard.keys[eventArgs[4]] == "right" and cursorX < string.len(lines[cursorY]) + 1 then
if cursorX - scrollX >= resX then
renderBufferFlag = true
scrollX = scrollX + 1
else
if not previousCursorX and not previousCursorY then
previousCursorX = cursorX
previousCursorY = cursorY
end
end
cursorX = cursorX + 1
end
if not keyboard.keys.special[eventArgs[4]] then
local line = lines[cursorY] or ""
line = string.sub(line, 1, cursorX - 1) .. string.char(eventArgs[3]) .. string.sub(line, cursorX, -1)
lines[cursorY] = line
cursorX = cursorX + 1
renderBufferFlag = true
end
if keyboard.keys[eventArgs[4]] == "back" then
local line = lines[cursorY]
local prevline = lines[cursorY - 1]
if cursorX > 1 then
line = string.sub(line, 1, cursorX - 2) .. string.sub(line, cursorX, -1) -- remove 1 char
elseif prevline then
prevline = prevline .. line -- copy line up to the next before removing it
ocelot.log(prevline)
line = nil
end
if line then
lines[cursorY] = line
else
removeLine(cursorY)
end
lines[cursorY - 1] = prevline
if not line then
cursorY = cursorY - 1 -- this can only trigger if the line is not the first
end
if cursorX > 1 then
cursorX = cursorX - 1
end
renderBufferFlag = true
end
if keyboard.keys[eventArgs[4]] == "enter" then
addLine(cursorY + 1)
cursorY = cursorY + 1
cursorX = 1
renderBufferFlag = true
end
end
end
until next(eventArgs) == nil
local displayedCursorX = cursorX - scrollX
local displayedCursorY = cursorY - scrollY
local previousDisplayedCursorX -- What a mouthful
local previousDisplayedCursorY
if previousCursorX and previousCursorY then
previousDisplayedCursorX = previousCursorX - scrollX
previousDisplayedCursorY = previousCursorY - scrollY
end
if renderBufferFlag then
renderText(scrollX, scrollY)
if cursorWhite then
-- If the cursor is black, then there's no need to do anything because there is no cursor after calling renderText().
gpu.setForeground(0x000000)
gpu.setBackground(0xFFFFFF)
local letter = gpu.get(displayedCursorX, displayedCursorY)
gpu.set(displayedCursorX, displayedCursorY, letter)
-- TODO: Account for scrolling
end
else
if cursorWhite then
if previousCursorX or previousCursorY then
-- Remove old cursor
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)
local letter = gpu.get(previousDisplayedCursorX, previousDisplayedCursorY)
gpu.set(previousDisplayedCursorX, previousDisplayedCursorY, letter)
end
gpu.setForeground(0x000000)
gpu.setBackground(0xFFFFFF)
local letter = gpu.get(displayedCursorX, displayedCursorY)
gpu.set(displayedCursorX, displayedCursorY, letter)
else
-- If renderText() hasn't been called, the cursor may still be white and need to be turned black.
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0x000000)
local letter = gpu.get(displayedCursorX, displayedCursorY)
gpu.set(displayedCursorX, displayedCursorY, letter)
end
end
end
-- Cleanup
::exit::
gpu.freeBuffer(textBuffer)
gpu.setActiveBuffer(0)
terminal.clear()
+19
View File
@@ -0,0 +1,19 @@
local computer = require("computer")
local cliparse = require("cliparse")
cliparse.config({
["f"] = 1,
["frequency"] = 1,
["t"] = 1,
["time"] = 1,
})
local parsed, err = cliparse.parse(...)
if not parsed then
return print("\x1b[91m" .. err .. "\x1b[0m")
end
local freq =
tonumber(parsed.flags.f and parsed.flags.f[1] or parsed.flags.frequency and parsed.flags.frequency[1] or "440")
local time = tonumber(parsed.flags.t and parsed.flags.t[1] or parsed.flags.time and parsed.flags.time[1] or "0.1")
computer.beep(freq, time)
+585
View File
@@ -0,0 +1,585 @@
local raster = require("raster")
local event = require("event")
local color_table = {
0xAAAAAA, 0x666666, 0xFF0000, 0xFF0000, 0xFFDDDD, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF,
0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000,
0xAA00AA, 0x660066, 0xFF0000, 0xFF0000, 0xFFDDDD, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF,
0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000
}
local ball_bitplanes = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0007, 0xff80, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x001f, 0xffff, 0xfc00, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x00f8, 0x007f, 0xffff, 0xff80, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0fe6, 0x03ff, 0xffff, 0xfff0, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0xfd1b, 0x980f, 0xffff, 0xfffc, 0x0000, 0x0000,
0x0000, 0x0000, 0x000f, 0xc14e, 0xb410, 0xffff, 0xffff, 0x8000, 0x0000,
0x0000, 0x0000, 0x00fc, 0x927a, 0xe92a, 0x7fff, 0xffff, 0xf000, 0x0000,
0x0000, 0x0000, 0x00c5, 0x09db, 0xa26f, 0x6fff, 0xffff, 0xfe00, 0x0000,
0x0000, 0x0000, 0x01d0, 0x46ee, 0xc4d2, 0x0fff, 0xffff, 0xff80, 0x0000,
0x0000, 0x0000, 0x0bba, 0x3bbd, 0x99b2, 0xd5bf, 0xffff, 0xffc0, 0x0000,
0x0000, 0x0000, 0x0d76, 0x5ef6, 0x3324, 0xd57f, 0xffff, 0xffe0, 0x0000,
0x0000, 0x0000, 0x5dec, 0x8fdc, 0x4665, 0xd55f, 0xffff, 0xfff0, 0x0000,
0x0000, 0x0000, 0xab99, 0x18f8, 0x8ccd, 0x9455, 0xffff, 0xfff8, 0x0000,
0x0000, 0x0002, 0xd772, 0x3103, 0x19c9, 0x96aa, 0xffff, 0xfffc, 0x0000,
0x0000, 0x000d, 0xaecc, 0x6238, 0x3199, 0x96aa, 0x6fff, 0xfffe, 0x0000,
0x0000, 0x001a, 0xbd98, 0xc673, 0x8399, 0xb695, 0x97ff, 0xffff, 0x8000,
0x0000, 0x006d, 0x7331, 0x8c73, 0x1b39, 0xb255, 0xbbff, 0xffff, 0xc000,
0x0000, 0x00d2, 0xe663, 0x18e7, 0x30f3, 0x324a, 0x9dff, 0xffff, 0xe000,
0x0000, 0x0fa5, 0xccc3, 0x31ce, 0x31c3, 0x324b, 0xebff, 0xffff, 0xf000,
0x0000, 0x1917, 0xb986, 0x23ce, 0x618c, 0x336d, 0x71ff, 0xffff, 0xf800,
0x0000, 0x1ade, 0x670c, 0x639c, 0x639c, 0xc325, 0xa8ff, 0xffff, 0xfc00,
0x0000, 0x32a3, 0xce18, 0xc73c, 0xc319, 0xcf26, 0xf37f, 0xffff, 0xfe00,
0x0000, 0x35a6, 0x7c31, 0x8e38, 0xc719, 0xccf2, 0xd5df, 0xffff, 0xff00,
0x0000, 0x6946, 0xc463, 0x1e71, 0x8739, 0x8ccf, 0x7a6f, 0xffff, 0xff00,
0x0000, 0xcacc, 0xc707, 0x1c71, 0x8e31, 0x9ccc, 0x6d37, 0xffff, 0xff80,
0x0000, 0xd69d, 0x8e7e, 0x38e3, 0x0e33, 0x98cc, 0xcd4b, 0xffff, 0xff80,
0x0001, 0x9599, 0x8e73, 0xb9e3, 0x1c73, 0x98cc, 0xc9b7, 0xffff, 0xffc0,
0x0001, 0xad33, 0x1ce3, 0x81c6, 0x1c63, 0x98cc, 0xc92b, 0xffff, 0xffc0,
0x0003, 0x4b33, 0x1ce7, 0x0dc6, 0x3c63, 0x18cc, 0xc92b, 0xffff, 0xffe0,
0x0003, 0x5666, 0x39c7, 0x1c4c, 0x38e7, 0x18cc, 0xccaa, 0x3fff, 0xffe0,
0x0006, 0x96e6, 0x39ce, 0x1872, 0x78c7, 0x19cc, 0xc495, 0x3fff, 0xfff0,
0x000d, 0x2ccc, 0x738e, 0x38f3, 0xb0c7, 0x19cc, 0xc495, 0x1fff, 0xfff0,
0x000d, 0x5dcc, 0x739c, 0x30e7, 0x89c7, 0x39ce, 0xc495, 0x1fff, 0xfff0,
0x001a, 0x5b98, 0xe71c, 0x71e7, 0x0f8e, 0x31ce, 0x64d4, 0xffff, 0xfff8,
0x001a, 0xbb18, 0xe738, 0x71cf, 0x1e4e, 0x31ce, 0x64ca, 0x8fff, 0xfff8,
0x0008, 0xb731, 0xce38, 0xe1ce, 0x1c71, 0x31ce, 0x664a, 0x8fff, 0xfffc,
0x000a, 0xf631, 0xce78, 0xe38e, 0x3cf1, 0xf1ce, 0x664a, 0x77ff, 0xfffc,
0x000a, 0x9e63, 0x9c71, 0xc39e, 0x3ce3, 0xc9ce, 0x6649, 0x47ff, 0xfffe,
0x000a, 0x9063, 0x9cf1, 0xc71c, 0x38e3, 0x8ece, 0x6261, 0x5fff, 0xfffe,
0x0004, 0x9327, 0x38e3, 0x873c, 0x78e3, 0x9e36, 0x6265, 0x1bff, 0xffff,
0x0015, 0x933f, 0x39e3, 0x8e38, 0x79e3, 0x9c30, 0x6264, 0x23ff, 0xffff,
0x0015, 0x3339, 0xb1c7, 0x8e38, 0xf1c7, 0x9c71, 0x9324, 0xafff, 0xffff,
0x0015, 0x2639, 0x83c7, 0x0c78, 0xf1c7, 0x1c61, 0x9f30, 0x8dff, 0xffff,
0x0015, 0x2671, 0x8d8f, 0x1c70, 0xe3c7, 0x3c63, 0x9cf2, 0x91ff, 0xffff,
0x0015, 0x2673, 0x8c76, 0x1871, 0xe38f, 0x3863, 0x1cce, 0x16ff, 0xffff,
0x0009, 0x2673, 0x1c71, 0x38f1, 0xe38e, 0x38e3, 0x38cd, 0xc6ff, 0xffff,
0x000b, 0x2673, 0x1861, 0xf0e1, 0xc78e, 0x78c3, 0x39cd, 0xbbff, 0xffff,
0x000b, 0x6663, 0x18e3, 0xc9e3, 0xc70e, 0x70c7, 0x399d, 0xb47f, 0xffff,
0x000b, 0x6c67, 0x18e3, 0x8ec3, 0xc71e, 0x71c6, 0x3999, 0xb4ff, 0xffff,
0x004b, 0x6ce7, 0x38e3, 0x9e27, 0x871c, 0x71c6, 0x3199, 0x34ff, 0xffff,
0x002b, 0x6ce7, 0x30c3, 0x9c38, 0x4f1c, 0xf1c6, 0x339b, 0x2cff, 0xfffe,
0x0012, 0x6ce6, 0x31c7, 0x9c78, 0x761c, 0xe186, 0x739b, 0x6aff, 0xfffe,
0x0016, 0x4cc6, 0x31c7, 0x1c70, 0xf13c, 0xe38e, 0x739b, 0x69ff, 0xfffe,
0x0096, 0xccce, 0x3187, 0x3c70, 0xe1d8, 0xe38c, 0x733b, 0x69ff, 0xfffe,
0x00e6, 0xd8ce, 0x718f, 0x3871, 0xe3c1, 0xe38c, 0x6333, 0x69ff, 0xfffe,
0x0068, 0xd9ce, 0x618e, 0x38f1, 0xc386, 0xc30c, 0x6732, 0x69ff, 0xfffe,
0x0071, 0x198e, 0x638e, 0x78e1, 0xc78e, 0x271c, 0x6732, 0x59ff, 0xfffe,
0x0074, 0x219c, 0x630e, 0x70e3, 0xc70c, 0x3898, 0xe732, 0x55ff, 0xfffe,
0x0034, 0xa61c, 0xe31e, 0x71e3, 0x871c, 0x78f8, 0xc732, 0x55ff, 0xfffe,
0x0038, 0x866c, 0xc31c, 0x71c7, 0x8f18, 0x71e4, 0xce72, 0x55ff, 0xfffe,
0x003a, 0x9263, 0xc71c, 0xf1c7, 0x0e38, 0xf1c7, 0x0e66, 0xd5ff, 0xfffe,
0x001a, 0x1323, 0x261c, 0xe3c7, 0x1e38, 0xe3ce, 0x7664, 0xd5ff, 0xfffe,
0x001c, 0x5323, 0x393c, 0xe38f, 0x1c70, 0xe38e, 0x71e4, 0xb3ff, 0xfffc,
0x0015, 0x4323, 0x39d8, 0xe38e, 0x1c71, 0xc79c, 0xe304, 0xabff, 0xfffc,
0x0015, 0x0933, 0x39c1, 0xe38e, 0x3ce1, 0xc71c, 0xe33b, 0xabff, 0xfff8,
0x000f, 0x2933, 0x39c6, 0xc79e, 0x38e3, 0x8f39, 0xc636, 0x6bff, 0xfff8,
0x000a, 0xa933, 0x39c6, 0x3b1c, 0x39c3, 0x8e39, 0xc676, 0x97ff, 0xfff0,
0x000a, 0xa993, 0x39c6, 0x383c, 0x79c7, 0x0e73, 0x8c6c, 0xafff, 0xfff0,
0x0007, 0x9591, 0x39ce, 0x30d8, 0x73c7, 0x1c73, 0x8ccd, 0x2fff, 0xffe0,
0x0005, 0x5491, 0xb9cc, 0x71c4, 0xf386, 0x1ce7, 0x19db, 0x5fff, 0xffe0,
0x0005, 0x5491, 0x99cc, 0x7187, 0x678e, 0x38e7, 0x1992, 0x9fff, 0xffe0,
0x0002, 0xd491, 0x998c, 0x718e, 0x1b0c, 0x39ce, 0x33b4, 0xbfff, 0xffc0,
0x0001, 0x2a99, 0x998c, 0x638e, 0x389c, 0x71ce, 0x3325, 0x7fff, 0xffc0,
0x0000, 0x9249, 0x998c, 0x631c, 0x31f8, 0x739c, 0x6669, 0x7fff, 0xff80,
0x0000, 0x4849, 0x998c, 0xe31c, 0x71c4, 0xe39c, 0x665a, 0xffff, 0xff80,
0x0000, 0x26b9, 0x998c, 0xe718, 0x63cf, 0x0738, 0xccd4, 0xffff, 0xff00,
0x0000, 0x095f, 0x999c, 0xc638, 0xe38e, 0x3f38, 0xd8b5, 0xffff, 0xff00,
0x0000, 0x065b, 0x1998, 0xc630, 0xc71c, 0x71b1, 0x9969, 0xffff, 0xfe00,
0x0000, 0x032f, 0x6198, 0xce71, 0xc73c, 0xe301, 0xb153, 0xffff, 0xfe00,
0x0000, 0x01d5, 0xa619, 0xcc61, 0x8e38, 0xc61c, 0x32d7, 0xffff, 0xfc00,
0x0000, 0x02e7, 0xb279, 0x8c63, 0x9c71, 0xcc39, 0x82a7, 0xffff, 0xf800,
0x0000, 0x0112, 0xd367, 0x9cc3, 0x1ce3, 0x9873, 0x3baf, 0xffff, 0xe000,
0x0000, 0x0085, 0x5b26, 0x78c7, 0x39e7, 0x30e6, 0x748f, 0xffff, 0xc000,
0x0000, 0x004b, 0xe926, 0x6186, 0x39ce, 0x619d, 0xe96f, 0xffff, 0x8000,
0x0000, 0x001c, 0xad26, 0x664e, 0x739c, 0x6333, 0xa6df, 0xffff, 0x0000,
0x0000, 0x000e, 0xd5a6, 0xcefc, 0x6718, 0xc667, 0x4b7f, 0xfffe, 0x0000,
0x0000, 0x0004, 0xd4b6, 0xcce3, 0xe739, 0x8cce, 0xb6ff, 0xfff8, 0x0000,
0x0000, 0x000d, 0x2ab4, 0xcdc6, 0x3e73, 0x19bd, 0x6bff, 0xfff0, 0x0000,
0x0000, 0x0003, 0x6a94, 0xd9cc, 0x63e6, 0x3374, 0xb7ff, 0xffe0, 0x0000,
0x0000, 0x0000, 0xd954, 0xdb98, 0xc60c, 0x4eeb, 0x5fff, 0xffc0, 0x0000,
0x0000, 0x0000, 0x36d5, 0xd331, 0x1860, 0x99d6, 0x7fff, 0xff00, 0x0000,
0x0000, 0x0000, 0x0d85, 0x9662, 0x31ce, 0xf7ba, 0xffff, 0xfe00, 0x0000,
0x0000, 0x0000, 0x0313, 0xa6cc, 0x473b, 0x1eb3, 0xffff, 0xf800, 0x0000,
0x0000, 0x0000, 0x00c1, 0x4d99, 0x9cec, 0x42e7, 0xffff, 0xe000, 0x0000,
0x0000, 0x0000, 0x0030, 0x7722, 0x7362, 0x087f, 0xffff, 0x8000, 0x0000,
0x0000, 0x0000, 0x000c, 0xa185, 0xcb90, 0x81ff, 0xfffc, 0x0000, 0x0000,
0x0000, 0x0000, 0x0003, 0x0fef, 0x2e49, 0x1fff, 0xfff0, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0803, 0x7281, 0xffff, 0xff80, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0038, 0x481f, 0xffff, 0xfe00, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0xf100, 0x007f, 0xf800, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x00fe, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0f7b, 0xfc00, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0xf7ec, 0x3ff0, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x000f, 0x7e70, 0xc4ff, 0x8000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x00f7, 0xe383, 0x0fcd, 0xa000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x057e, 0x0e1c, 0x3f8f, 0x9800, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x1a60, 0x78f0, 0xff1c, 0xde00, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x2cc3, 0xc3c1, 0xfe3c, 0xeb80, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xd187, 0xdf07, 0xfc38, 0xe7e0, 0x0000, 0x0000, 0x0000,
0x0000, 0x0001, 0x660f, 0xf01f, 0xf879, 0xe798, 0x0000, 0x0000, 0x0000,
0x0000, 0x0006, 0xcc1f, 0xe0ff, 0xf0f1, 0xe798, 0x0000, 0x0000, 0x0000,
0x0000, 0x001b, 0x187f, 0xc1ff, 0xe1f1, 0xe7cd, 0x0000, 0x0000, 0x0000,
0x0000, 0x002e, 0x30ff, 0x83c1, 0xc1e1, 0xe7cc, 0xa000, 0x0000, 0x0000,
0x0000, 0x00dc, 0xc1ff, 0x0783, 0xe3e1, 0xc7e6, 0xd000, 0x0000, 0x0000,
0x0000, 0x0171, 0x83fe, 0x0f83, 0xe3c1, 0xc3e6, 0x5800, 0x0000, 0x0000,
0x0000, 0x06e3, 0x07fc, 0x1f07, 0xc0c3, 0xc3f3, 0x2c00, 0x0000, 0x0000,
0x0000, 0x0bc6, 0x0ffc, 0x3e0f, 0xc1f3, 0xc3f3, 0x3500, 0x0000, 0x0000,
0x0000, 0x1198, 0x3ff8, 0x3c0f, 0x81ff, 0xc3f1, 0x9a80, 0x0000, 0x0000,
0x0000, 0x13e0, 0x7ff0, 0x7c1f, 0x83ff, 0x03f9, 0xcd40, 0x0000, 0x0000,
0x0000, 0x23c3, 0xffe0, 0xf83f, 0x03fe, 0x0ff8, 0xc5a0, 0x0000, 0x0000,
0x0000, 0x27c7, 0x9fc1, 0xf03f, 0x07fe, 0x0f3c, 0xe6c0, 0x0000, 0x0000,
0x0000, 0x4f87, 0x0783, 0xe07e, 0x07fe, 0x0f0c, 0x6360, 0x0000, 0x0000,
0x0000, 0x8f0f, 0x07c7, 0xe07e, 0x0ffe, 0x1f0f, 0x71b0, 0x0000, 0x0000,
0x0000, 0x9f1e, 0x0f8f, 0xc0fc, 0x0ffc, 0x1f0f, 0x0198, 0x0000, 0x0000,
0x0001, 0x1e1e, 0x0f83, 0xc1fc, 0x1ffc, 0x1f0f, 0x0fda, 0x0000, 0x0000,
0x0001, 0x3e3c, 0x1f03, 0xf1f8, 0x1ffc, 0x1f0f, 0x0fcd, 0x0000, 0x0000,
0x0002, 0x7c3c, 0x1f07, 0xfff8, 0x3ffc, 0x1f0f, 0x0fcc, 0x8000, 0x0000,
0x0002, 0x7878, 0x3e07, 0xffb0, 0x3ff8, 0x1f0f, 0x0fcc, 0x8000, 0x0000,
0x0004, 0xf8f8, 0x3e0f, 0xff82, 0x7ff8, 0x1e0f, 0x07e6, 0x8000, 0x0000,
0x0009, 0xf0f0, 0x7c0f, 0xff03, 0xfff8, 0x1e0f, 0x07e6, 0x4000, 0x0000,
0x0009, 0xe1f0, 0x7c1f, 0xff07, 0xf7f8, 0x3e0f, 0x07e6, 0x4000, 0x0000,
0x0013, 0xe3e0, 0xf81f, 0xfe07, 0xf1f0, 0x3e0f, 0x87e7, 0x4000, 0x0000,
0x0013, 0xc3e0, 0xf83f, 0xfe0f, 0xe070, 0x3e0f, 0x87f3, 0x2000, 0x0000,
0x002f, 0xc7c1, 0xf03f, 0xfe0f, 0xe07e, 0x3e0f, 0x87f3, 0x2000, 0x0000,
0x002c, 0x87c1, 0xf07f, 0xfc0f, 0xc0fe, 0x3e0f, 0x87f3, 0xa800, 0x0000,
0x002c, 0xef83, 0xe07f, 0xfc1f, 0xc0fc, 0x0e0f, 0x87f1, 0x9000, 0x0000,
0x002c, 0xe383, 0xe0ff, 0xf81f, 0xc0fc, 0x0f0f, 0x83f9, 0x9000, 0x0000,
0x0028, 0xe3e7, 0xc0ff, 0xf83f, 0x80fc, 0x1fff, 0x83f9, 0xd400, 0x0000,
0x0059, 0xe3ff, 0xc1ff, 0xf03f, 0x81fc, 0x1fff, 0x83f8, 0xcc00, 0x0000,
0x0059, 0xc3fe, 0x01ff, 0xf03f, 0x01f8, 0x1ffe, 0x13f8, 0xc800, 0x0000,
0x0059, 0xc7fe, 0x03ff, 0xf07f, 0x01f8, 0x1ffe, 0x1ffc, 0xea00, 0x0000,
0x0059, 0xc7fe, 0x0fff, 0xe07f, 0x03f8, 0x3ffc, 0x1f3c, 0xe600, 0x0000,
0x0059, 0xc7fc, 0x0f87, 0xe07e, 0x03f0, 0x3ffc, 0x1f0c, 0x6500, 0x0000,
0x0051, 0xc7fc, 0x1f81, 0xc0fe, 0x03f0, 0x3ffc, 0x3f0e, 0x7500, 0x0000,
0x0053, 0xc7fc, 0x1f81, 0xc0fe, 0x07f0, 0x7ffc, 0x3e0e, 0x3200, 0x0000,
0x0053, 0x87fc, 0x1f03, 0xf1fc, 0x07f0, 0x7ff8, 0x3e1e, 0x3e80, 0x0000,
0x0053, 0x8ff8, 0x1f03, 0xf0fc, 0x07e0, 0x7ff8, 0x3e1e, 0x3e00, 0x0000,
0x0093, 0x8ff8, 0x3f03, 0xe038, 0x07e0, 0x7ff8, 0x3e1e, 0x3e00, 0x0000,
0x00b3, 0x8ff8, 0x3f03, 0xe03f, 0xcfe0, 0xfff8, 0x3c1c, 0x3e00, 0x0000,
0x00a3, 0x8ff8, 0x3e07, 0xe07f, 0xffe0, 0xfff8, 0x7c1c, 0x7c00, 0x0000,
0x00a7, 0x8ff8, 0x3e07, 0xe07f, 0xfec0, 0xfff0, 0x7c1c, 0x7d00, 0x0000,
0x00a7, 0x0ff0, 0x3e07, 0xc07f, 0xfe00, 0xfff0, 0x7c3c, 0x7c00, 0x0000,
0x00b7, 0x1ff0, 0x7e0f, 0xc07f, 0xfc01, 0xfff0, 0x7c3c, 0x7c00, 0x0000,
0x00ff, 0x1ff0, 0x7e0f, 0xc0ff, 0xfc07, 0xfff0, 0x783c, 0x7c00, 0x0000,
0x005e, 0x1ff0, 0x7c0f, 0x80ff, 0xf80f, 0xdfe0, 0x783c, 0x7c00, 0x0000,
0x005f, 0x3fe0, 0x7c0f, 0x80ff, 0xf80f, 0xc0e0, 0xf83c, 0x7800, 0x0000,
0x003f, 0x3860, 0xfc1f, 0x81ff, 0xf81f, 0x80e0, 0xf83c, 0x7800, 0x0000,
0x002f, 0x1870, 0xfc1f, 0x81ff, 0xf01f, 0x81f8, 0xf07c, 0x7800, 0x0000,
0x002f, 0x1c7c, 0xf81f, 0x01ff, 0xf03f, 0x01f8, 0x3078, 0xf800, 0x0000,
0x001f, 0x9c3c, 0x381f, 0x03ff, 0xe03f, 0x03f0, 0x7878, 0xf800, 0x0000,
0x0017, 0x9c3c, 0x3f3f, 0x03ff, 0xe07f, 0x03f0, 0x7ff8, 0xf800, 0x0000,
0x001f, 0x8c3c, 0x3fff, 0x03ff, 0xe07e, 0x07e0, 0xfff8, 0xf000, 0x0000,
0x001f, 0xce3c, 0x3ffe, 0x03ff, 0xc0fe, 0x07e0, 0xffc3, 0xf000, 0x0000,
0x000b, 0xce3c, 0x3ff8, 0x07ff, 0xc0fc, 0x0fc1, 0xffc7, 0xb000, 0x0000,
0x000f, 0xce3c, 0x3ff8, 0x3fff, 0xc1fc, 0x0fc1, 0xff87, 0x1800, 0x0000,
0x000f, 0xce1c, 0x3ff8, 0x3fff, 0x81f8, 0x0f83, 0xff8f, 0x3000, 0x0000,
0x0005, 0xe61e, 0x3ff0, 0x3f1f, 0x83f8, 0x1f83, 0xff0e, 0x3000, 0x0000,
0x0007, 0xe71e, 0x3ff0, 0x7e07, 0x03f8, 0x1f07, 0xfe1c, 0x6000, 0x0000,
0x0007, 0xe71e, 0x1ff0, 0x7e07, 0x87f0, 0x3f07, 0xfe1c, 0xe000, 0x0000,
0x0003, 0xe71e, 0x1ff0, 0x7e0f, 0xe3f0, 0x3e0f, 0xfc38, 0xc000, 0x0000,
0x0002, 0xf31e, 0x1ff0, 0x7c0f, 0xc0e0, 0x7e0f, 0xfc39, 0x8000, 0x0000,
0x0001, 0x5b8e, 0x1ff0, 0x7c1f, 0xc1e0, 0x7c1f, 0xf871, 0x8000, 0x0000,
0x0000, 0xad8e, 0x1ff0, 0xfc1f, 0x81fc, 0xfc1f, 0xf863, 0x0000, 0x0000,
0x0000, 0x54fe, 0x1ff0, 0xf81f, 0x83ff, 0xf83f, 0xf0e7, 0x0000, 0x0000,
0x0000, 0x2278, 0x1fe0, 0xf83f, 0x03ff, 0xc03f, 0xe0c6, 0x0000, 0x0000,
0x0000, 0x137c, 0x1fe0, 0xf83f, 0x07ff, 0x81ff, 0xe18e, 0x0000, 0x0000,
0x0000, 0x09bc, 0x7fe0, 0xf07e, 0x07ff, 0x03ff, 0xc19c, 0x0000, 0x0000,
0x0000, 0x049e, 0x3861, 0xf07e, 0x0fff, 0x07e0, 0xc318, 0x0000, 0x0000,
0x0000, 0x054e, 0x3c61, 0xf07c, 0x1ffe, 0x0fc1, 0xe338, 0x0000, 0x0000,
0x0000, 0x02a7, 0x1c79, 0xe0fc, 0x1ffc, 0x1f83, 0xc230, 0x0000, 0x0000,
0x0000, 0x0157, 0x9c38, 0x60f8, 0x3ff8, 0x3f07, 0x87f0, 0x0000, 0x0000,
0x0000, 0x00a3, 0x8e38, 0x79f8, 0x3ff0, 0x7e1e, 0x0f80, 0x0000, 0x0000,
0x0000, 0x0049, 0xce38, 0x7fb0, 0x7fe0, 0x7c3c, 0x3f00, 0x0000, 0x0000,
0x0000, 0x0024, 0xe638, 0xff00, 0x7fe0, 0xf878, 0x7c00, 0x0000, 0x0000,
0x0000, 0x0012, 0xe738, 0xff03, 0xffc1, 0xf0f0, 0xf800, 0x0000, 0x0000,
0x0000, 0x000c, 0x7338, 0xfe07, 0xcf83, 0xe1c1, 0xf000, 0x0000, 0x0000,
0x0000, 0x0003, 0x7318, 0xfe0f, 0x8307, 0xc387, 0xc000, 0x0000, 0x0000,
0x0000, 0x0000, 0xdd98, 0xfc1f, 0x07cf, 0x8f0f, 0x8000, 0x0000, 0x0000,
0x0000, 0x0000, 0x3799, 0xfc3e, 0x1f87, 0x1e1f, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0db9, 0xf87c, 0x3e0f, 0xf83c, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x033b, 0xf8f0, 0x783f, 0xe0f8, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x00cd, 0x91e1, 0xe0ff, 0x83f0, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0032, 0xbbc3, 0x83fc, 0x0c40, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x000c, 0xb1c6, 0x0fe0, 0xe100, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0003, 0x4ff0, 0x3f8e, 0x1000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0802, 0xfcf1, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0038, 0x6f90, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0xfd00, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x00ec, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0fbc, 0xf800, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0xf9f0, 0x3ff0, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x000f, 0x8f80, 0xfb1f, 0x8000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x00f8, 0xfc03, 0xf1ce, 0xa000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0687, 0xf01f, 0xc3f1, 0xd800, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x1c7f, 0x80ff, 0x07e0, 0xea00, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x30fc, 0x03fe, 0x1fc0, 0xf2c0, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xe1f8, 0x5ff8, 0x3fc0, 0xf9b0, 0x0000, 0x0000, 0x0000,
0x0000, 0x0001, 0x87f0, 0xffe0, 0x7f81, 0xf9ec, 0x0000, 0x0000, 0x0000,
0x0000, 0x0007, 0x0fe1, 0xff00, 0xff01, 0xf8e3, 0x0000, 0x0000, 0x0000,
0x0000, 0x001c, 0x1f83, 0xfe03, 0xfe01, 0xf8f1, 0xc000, 0x0000, 0x0000,
0x0000, 0x0030, 0x3f0f, 0xfc01, 0xfe01, 0xf8f0, 0xf000, 0x0000, 0x0000,
0x0000, 0x00e0, 0xfe1f, 0xf803, 0xfc01, 0xf8f8, 0xf800, 0x0000, 0x0000,
0x0000, 0x0181, 0xfc3f, 0xf003, 0xfc01, 0xfc78, 0x6c00, 0x0000, 0x0000,
0x0000, 0x0703, 0xf87f, 0xe007, 0xff03, 0xfc7c, 0x3600, 0x0000, 0x0000,
0x0000, 0x0c07, 0xf0ff, 0xc00f, 0xfe03, 0xfc7c, 0x3a00, 0x0000, 0x0000,
0x0000, 0x1e1f, 0xc1ff, 0xc00f, 0xfe0f, 0xfc7e, 0x1d00, 0x0000, 0x0000,
0x0000, 0x1cff, 0x87ff, 0x801f, 0xfc1f, 0xfc3e, 0x0e80, 0x0000, 0x0000,
0x0000, 0x3cfc, 0x0fff, 0x003f, 0xfc1f, 0xf03f, 0x0640, 0x0000, 0x0000,
0x0000, 0x39f8, 0x1ffe, 0x003f, 0xf81f, 0xf03f, 0x0760, 0x0000, 0x0000,
0x0000, 0x71f8, 0x07fc, 0x007f, 0xf83f, 0xf00f, 0x83b0, 0x0000, 0x0000,
0x0000, 0xf3f0, 0x07f8, 0x007f, 0xf03f, 0xe00f, 0x81d8, 0x0000, 0x0000,
0x0000, 0xe7e0, 0x0ff0, 0x00ff, 0xf03f, 0xe00f, 0xf1ec, 0x0000, 0x0000,
0x0001, 0xe7e0, 0x0ffc, 0x01ff, 0xe07f, 0xe00f, 0xf1e4, 0x0000, 0x0000,
0x0001, 0xcfc0, 0x1ffc, 0x01ff, 0xe07f, 0xe00f, 0xf1f2, 0x0000, 0x0000,
0x0003, 0x8fc0, 0x1ff8, 0x0fff, 0xc07f, 0xe00f, 0xf1f1, 0x0000, 0x0000,
0x0003, 0x9f80, 0x3ff8, 0x1fff, 0xc0ff, 0xe00f, 0xf0f0, 0xc000, 0x0000,
0x0007, 0x1f00, 0x3ff0, 0x1ffd, 0x80ff, 0xe00f, 0xf8f8, 0xc000, 0x0000,
0x000e, 0x3f00, 0x7ff0, 0x3ffc, 0x00ff, 0xe00f, 0xf8f8, 0x6000, 0x0000,
0x000e, 0x7e00, 0x7fe0, 0x3ff8, 0x01ff, 0xc00f, 0xf8f8, 0x6000, 0x0000,
0x001c, 0x7c00, 0xffe0, 0x7ff8, 0x01ff, 0xc00f, 0xf8f8, 0x6000, 0x0000,
0x001c, 0xfc00, 0xffc0, 0x7ff0, 0x007f, 0xc00f, 0xf8fc, 0x3000, 0x0000,
0x0030, 0xf801, 0xffc0, 0xfff0, 0x007f, 0xc00f, 0xf87c, 0x3000, 0x0000,
0x0030, 0xf801, 0xff80, 0xfff0, 0x00ff, 0xc00f, 0xf87c, 0x3000, 0x0000,
0x0030, 0xf003, 0xff81, 0xffe0, 0x00ff, 0xf00f, 0xf87e, 0x1800, 0x0000,
0x0030, 0xfc03, 0xff01, 0xffe0, 0x00ff, 0xf00f, 0xfc7e, 0x1800, 0x0000,
0x0030, 0xfc27, 0xff03, 0xffc0, 0x00ff, 0xe03f, 0xfc7e, 0x1800, 0x0000,
0x0061, 0xfc3f, 0xfe03, 0xffc0, 0x01ff, 0xe03f, 0xfc7f, 0x0800, 0x0000,
0x0061, 0xfc3f, 0xfe07, 0xffc0, 0x01ff, 0xe07f, 0xec3f, 0x0c00, 0x0000,
0x0061, 0xf83f, 0xfc07, 0xff80, 0x01ff, 0xe07f, 0xe03f, 0x0c00, 0x0000,
0x0061, 0xf87f, 0xf00f, 0xff80, 0x03ff, 0xc07f, 0xe03f, 0x0400, 0x0000,
0x0061, 0xf87f, 0xf007, 0xff80, 0x03ff, 0xc07f, 0xe00f, 0x8600, 0x0000,
0x0061, 0xf87f, 0xe001, 0xff00, 0x03ff, 0xc0ff, 0xc00f, 0x8600, 0x0000,
0x0063, 0xf87f, 0xe001, 0xff00, 0x07ff, 0x80ff, 0xc00f, 0xc300, 0x0000,
0x0063, 0xf87f, 0xe003, 0xfe00, 0x07ff, 0x80ff, 0xc01f, 0xc700, 0x0000,
0x0063, 0xf07f, 0xe003, 0xff00, 0x07ff, 0x81ff, 0xc01f, 0xc700, 0x0000,
0x00e3, 0xf0ff, 0xc003, 0xffc0, 0x07ff, 0x81ff, 0xc01f, 0xc700, 0x0000,
0x00c3, 0xf0ff, 0xc003, 0xffc0, 0x4fff, 0x01ff, 0xc01f, 0xcf00, 0x0000,
0x00c3, 0xf0ff, 0xc007, 0xff80, 0x7fff, 0x01ff, 0x801f, 0x8f00, 0x0000,
0x00c7, 0xf0ff, 0xc007, 0xff80, 0xffff, 0x03ff, 0x801f, 0x8e00, 0x0000,
0x00c7, 0xf0ff, 0xc007, 0xff80, 0xffff, 0x03ff, 0x803f, 0x8e00, 0x0000,
0x00c7, 0xe0ff, 0x800f, 0xff81, 0xfffe, 0x03ff, 0x803f, 0x8e00, 0x0000,
0x00cf, 0xe1ff, 0x800f, 0xff01, 0xfff8, 0x03ff, 0x803f, 0x8e00, 0x0000,
0x0067, 0xe1ff, 0x800f, 0xff01, 0xfff0, 0x07ff, 0x803f, 0x9e00, 0x0000,
0x0067, 0xc1ff, 0x800f, 0xff03, 0xfff0, 0x00ff, 0x003f, 0x9e00, 0x0000,
0x0027, 0xc07f, 0x001f, 0xfe03, 0xffe0, 0x00ff, 0x003f, 0x9e00, 0x0000,
0x0033, 0xe07f, 0x001f, 0xfe07, 0xffe0, 0x01ff, 0x007f, 0x9e00, 0x0000,
0x0033, 0xe07f, 0x001f, 0xfe07, 0xffc0, 0x01ff, 0xc07f, 0x1e00, 0x0000,
0x0013, 0xe03f, 0xc01f, 0xfc07, 0xffc0, 0x03ff, 0x807f, 0x1e00, 0x0000,
0x0019, 0xe03f, 0xc13f, 0xfc0f, 0xff80, 0x03ff, 0x81ff, 0x3c00, 0x0000,
0x0019, 0xf03f, 0xc1ff, 0xfc0f, 0xff80, 0x07ff, 0x03ff, 0x3c00, 0x0000,
0x0019, 0xf03f, 0xc1ff, 0xfc0f, 0xff00, 0x07ff, 0x03fc, 0x3c00, 0x0000,
0x000d, 0xf03f, 0xc1ff, 0xf81f, 0xff00, 0x0ffe, 0x07f8, 0x3c00, 0x0000,
0x000c, 0xf03f, 0xc1ff, 0xc01f, 0xfe00, 0x0ffe, 0x07f8, 0x1c00, 0x0000,
0x000c, 0xf01f, 0xc1ff, 0xc03f, 0xfe00, 0x0ffc, 0x0ff0, 0x3800, 0x0000,
0x0006, 0xf81f, 0xc1ff, 0xc01f, 0xfc00, 0x1ffc, 0x0ff0, 0x3800, 0x0000,
0x0006, 0x781f, 0xc1ff, 0x8007, 0xfc00, 0x1ff8, 0x1fe0, 0x7000, 0x0000,
0x0006, 0x781f, 0xe1ff, 0x8007, 0xf800, 0x3ff8, 0x1fe0, 0xf000, 0x0000,
0x0003, 0x781f, 0xe1ff, 0x800f, 0xfc00, 0x3ff0, 0x3fc0, 0xe000, 0x0000,
0x0003, 0x3c1f, 0xe1ff, 0x800f, 0xff00, 0x7ff0, 0x3fc1, 0xc000, 0x0000,
0x0001, 0x9c0f, 0xe1ff, 0x801f, 0xfe00, 0x7fe0, 0x7f81, 0xc000, 0x0000,
0x0000, 0xce0f, 0xe1ff, 0x001f, 0xfe04, 0xffe0, 0x7f83, 0x8000, 0x0000,
0x0000, 0x673f, 0xe1ff, 0x001f, 0xfc0f, 0xffc0, 0xff07, 0x8000, 0x0000,
0x0000, 0x339f, 0xe1ff, 0x003f, 0xfc0f, 0xffc0, 0xff07, 0x0000, 0x0000,
0x0000, 0x1b9f, 0xe1ff, 0x003f, 0xf81f, 0xfe01, 0xfe0f, 0x0000, 0x0000,
0x0000, 0x0dcf, 0x81ff, 0x007f, 0xf83f, 0xfc01, 0xfe1e, 0x0000, 0x0000,
0x0000, 0x06e7, 0xc07e, 0x007f, 0xf03f, 0xf800, 0xfc1c, 0x0000, 0x0000,
0x0000, 0x0677, 0xc07e, 0x007f, 0xe07f, 0xf001, 0xfc3c, 0x0000, 0x0000,
0x0000, 0x033b, 0xe07e, 0x00ff, 0xe0ff, 0xe003, 0xfc38, 0x0000, 0x0000,
0x0000, 0x0199, 0xe03f, 0x80ff, 0xc1ff, 0xc007, 0xf8f8, 0x0000, 0x0000,
0x0000, 0x00cd, 0xf03f, 0x81ff, 0xc1ff, 0x801f, 0xf1f0, 0x0000, 0x0000,
0x0000, 0x006e, 0xf03f, 0x87ff, 0x83ff, 0x803f, 0xc7e0, 0x0000, 0x0000,
0x0000, 0x0037, 0xf83f, 0x0fff, 0x87ff, 0x007f, 0x8f80, 0x0000, 0x0000,
0x0000, 0x001b, 0x783f, 0x0ffc, 0x07fe, 0x00ff, 0x3f00, 0x0000, 0x0000,
0x0000, 0x000f, 0xbc3f, 0x0ff8, 0x0ffc, 0x01fe, 0x7c00, 0x0000, 0x0000,
0x0000, 0x0003, 0xbc1f, 0x1ff0, 0x03f8, 0x03f8, 0xf800, 0x0000, 0x0000,
0x0000, 0x0000, 0xee1f, 0x1fe0, 0x07f0, 0x0ff3, 0xe000, 0x0000, 0x0000,
0x0000, 0x0000, 0x3b1e, 0x1fc0, 0x1ff8, 0x1fe7, 0x8000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0ede, 0x1f80, 0x3ff0, 0xffdf, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x03ac, 0x3f00, 0x7fc3, 0xff3c, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x00ee, 0x1e01, 0xff0f, 0xfcf8, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0039, 0x3c03, 0xfc7f, 0xf060, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x000e, 0x3e07, 0xf3ff, 0x0100, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0003, 0x77ff, 0xcff0, 0x1000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x09fc, 0x7f01, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0008, 0x7010, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0500, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x000f, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x003f, 0xfc00, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x01ff, 0xc7f0, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0fff, 0x030f, 0x8000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0xfffc, 0x01f0, 0xe000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0707, 0xffe0, 0x03f9, 0xe800, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x1f9f, 0xff00, 0x07ff, 0xf200, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x3f03, 0xfc00, 0x1fff, 0x0c80, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0xfe00, 0x6000, 0x3fff, 0x0020, 0x0000, 0x0000, 0x0000,
0x0000, 0x0001, 0xf800, 0xfc00, 0x7ffe, 0x01c8, 0x0000, 0x0000, 0x0000,
0x0000, 0x0007, 0xf001, 0xff80, 0xfffe, 0x00fe, 0x0000, 0x0000, 0x0000,
0x0000, 0x001f, 0xe003, 0xffe3, 0xfffe, 0x00fe, 0x0000, 0x0000, 0x0000,
0x0000, 0x003f, 0xc00f, 0xffff, 0xfffe, 0x00ff, 0x3000, 0x0000, 0x0000,
0x0000, 0x00ff, 0x001f, 0xfffc, 0x1ffe, 0x00ff, 0x1800, 0x0000, 0x0000,
0x0000, 0x01fe, 0x003f, 0xfffc, 0x03fe, 0x007f, 0x8c00, 0x0000, 0x0000,
0x0000, 0x07fc, 0x007f, 0xfff8, 0x003c, 0x007f, 0xc600, 0x0000, 0x0000,
0x0000, 0x03f8, 0x00ff, 0xfff0, 0x000c, 0x007f, 0xc300, 0x0000, 0x0000,
0x0000, 0x0060, 0x01ff, 0xfff0, 0x000f, 0x007f, 0xe180, 0x0000, 0x0000,
0x0000, 0x00f0, 0x07ff, 0xffe0, 0x001f, 0xf03f, 0xf0c0, 0x0000, 0x0000,
0x0000, 0x00ff, 0x0fff, 0xffc0, 0x001f, 0xfc3f, 0xf860, 0x0000, 0x0000,
0x0000, 0x01ff, 0xffff, 0xffc0, 0x001f, 0xffff, 0xf860, 0x0000, 0x0000,
0x0000, 0x01ff, 0xfbff, 0xff80, 0x003f, 0xfff3, 0xfc30, 0x0000, 0x0000,
0x0000, 0x03ff, 0xf83f, 0xff80, 0x003f, 0xfff0, 0xfe18, 0x0000, 0x0000,
0x0000, 0x07ff, 0xf00f, 0xff00, 0x003f, 0xfff0, 0x0e0c, 0x0000, 0x0000,
0x0000, 0x07ff, 0xf000, 0x3e00, 0x007f, 0xfff0, 0x0106, 0x0000, 0x0000,
0x0000, 0x0fff, 0xe000, 0x0e00, 0x007f, 0xfff0, 0x01e3, 0x0000, 0x0000,
0x0000, 0x0fff, 0xe000, 0x0e00, 0x007f, 0xfff0, 0x01ff, 0x8000, 0x0000,
0x0000, 0x1fff, 0xc000, 0x1fc0, 0x00ff, 0xfff0, 0x00ff, 0x0000, 0x0000,
0x0000, 0x1fff, 0xc000, 0x1ffe, 0x00ff, 0xfff0, 0x00ff, 0x0000, 0x0000,
0x0000, 0x3fff, 0x8000, 0x3fff, 0xc0ff, 0xfff0, 0x00ff, 0x8000, 0x0000,
0x0000, 0x7fff, 0x8000, 0x3fff, 0xf9ff, 0xfff0, 0x00ff, 0x8000, 0x0000,
0x0000, 0x7fff, 0x0000, 0x7fff, 0xffff, 0xfff0, 0x00ff, 0x8000, 0x0000,
0x0000, 0xffff, 0x0000, 0x7fff, 0xffbf, 0xfff0, 0x00ff, 0xc000, 0x0000,
0x003c, 0xfffe, 0x0000, 0xffff, 0xff80, 0xfff0, 0x007f, 0xc000, 0x0000,
0x003f, 0x7ffe, 0x0000, 0xffff, 0xff00, 0x3ff0, 0x007f, 0xc000, 0x0000,
0x003f, 0x0ffc, 0x0001, 0xffff, 0xff00, 0x07f0, 0x007f, 0xe000, 0x0000,
0x003f, 0x03fc, 0x0001, 0xffff, 0xff00, 0x00f0, 0x007f, 0xe000, 0x0000,
0x003f, 0x0038, 0x0003, 0xffff, 0xff00, 0x0038, 0x007f, 0xe000, 0x0000,
0x007e, 0x0038, 0x0003, 0xffff, 0xfe00, 0x003e, 0x007f, 0xf000, 0x0000,
0x007e, 0x003f, 0xc007, 0xffff, 0xfe00, 0x007f, 0xf03f, 0xf000, 0x0000,
0x007e, 0x003f, 0xf007, 0xffff, 0xfe00, 0x007f, 0xfc3f, 0xf000, 0x0000,
0x007e, 0x007f, 0xfe0f, 0xffff, 0xfc00, 0x007f, 0xffff, 0xf800, 0x0000,
0x007e, 0x007f, 0xffff, 0xffff, 0xfc00, 0x007f, 0xfff3, 0xf800, 0x0000,
0x007e, 0x007f, 0xfffe, 0xffff, 0xfc00, 0x00ff, 0xfff0, 0x7800, 0x0000,
0x007c, 0x007f, 0xfffe, 0x3fff, 0xf800, 0x00ff, 0xfff0, 0x0c00, 0x0000,
0x007c, 0x007f, 0xfffc, 0x07ff, 0xf800, 0x00ff, 0xffe0, 0x0700, 0x0000,
0x007c, 0x007f, 0xfffc, 0x00ff, 0xf800, 0x01ff, 0xffe0, 0x0780, 0x0000,
0x00fc, 0x00ff, 0xfffc, 0x001f, 0xf800, 0x01ff, 0xffe0, 0x0780, 0x0000,
0x00fc, 0x00ff, 0xfffc, 0x0000, 0x7000, 0x01ff, 0xffe0, 0x0f80, 0x0000,
0x00fc, 0x00ff, 0xfff8, 0x0000, 0x7800, 0x01ff, 0xffe0, 0x0f00, 0x0000,
0x00f8, 0x00ff, 0xfff8, 0x0000, 0xff00, 0x03ff, 0xffe0, 0x0f00, 0x0000,
0x0078, 0x00ff, 0xfff8, 0x0000, 0xffe0, 0x03ff, 0xffc0, 0x0f00, 0x0000,
0x0008, 0x00ff, 0xfff0, 0x0001, 0xfff8, 0x03ff, 0xffc0, 0x0f00, 0x0000,
0x000e, 0x01ff, 0xfff0, 0x0001, 0xffff, 0x03ff, 0xffc0, 0x0f00, 0x0000,
0x0007, 0xc1ff, 0xfff0, 0x0001, 0xffff, 0xe7ff, 0xffc0, 0x1f00, 0x0000,
0x0007, 0xf9ff, 0xfff0, 0x0003, 0xffff, 0xff7f, 0xffc0, 0x1e00, 0x0000,
0x0007, 0xffff, 0xffe0, 0x0003, 0xffff, 0xff1f, 0xffc0, 0x1e00, 0x0000,
0x0003, 0xff8f, 0xffe0, 0x0007, 0xffff, 0xfe03, 0xff80, 0x1e00, 0x0000,
0x0003, 0xff80, 0xffe0, 0x0007, 0xffff, 0xfe00, 0x3f80, 0x1e00, 0x0000,
0x0003, 0xffc0, 0x1fe0, 0x0007, 0xffff, 0xfc00, 0x0780, 0x1e00, 0x0000,
0x0001, 0xffc0, 0x01c0, 0x000f, 0xffff, 0xfc00, 0x0180, 0x3e00, 0x0000,
0x0001, 0xffc0, 0x01e0, 0x000f, 0xffff, 0xf800, 0x03e0, 0x3c00, 0x0000,
0x0001, 0xffc0, 0x01f8, 0x000f, 0xffff, 0xf800, 0x03ff, 0x3c00, 0x0000,
0x0001, 0xffc0, 0x01ff, 0x001f, 0xffff, 0xf000, 0x07ff, 0xfc00, 0x0000,
0x0000, 0xffc0, 0x01ff, 0xfc1f, 0xffff, 0xf000, 0x07ff, 0xe000, 0x0000,
0x0000, 0xffe0, 0x01ff, 0xff3f, 0xffff, 0xf000, 0x0fff, 0xc000, 0x0000,
0x0000, 0xffe0, 0x01ff, 0xffff, 0xffff, 0xe000, 0x0fff, 0xc000, 0x0000,
0x0000, 0x7fe0, 0x01ff, 0xfffb, 0xffff, 0xe000, 0x1fff, 0x8000, 0x0000,
0x0000, 0x7fe0, 0x01ff, 0xfff8, 0x7fff, 0xc000, 0x1fff, 0x0000, 0x0000,
0x0000, 0x7fe0, 0x01ff, 0xfff0, 0x03ff, 0xc000, 0x3fff, 0x0000, 0x0000,
0x0003, 0xbfe0, 0x01ff, 0xfff0, 0x007f, 0x8000, 0x3ffe, 0x0000, 0x0000,
0x0001, 0xe7f0, 0x01ff, 0xffe0, 0x001f, 0x8000, 0x7ffe, 0x0000, 0x0000,
0x0000, 0xf1f0, 0x01ff, 0xffe0, 0x0007, 0x0000, 0x7ffc, 0x0000, 0x0000,
0x0000, 0x7830, 0x01ff, 0xffe0, 0x000f, 0xe000, 0xfff8, 0x0000, 0x0000,
0x0000, 0x3c1e, 0x01ff, 0xffc0, 0x000f, 0xf800, 0xfff8, 0x0000, 0x0000,
0x0000, 0x1c1f, 0x81ff, 0xffc0, 0x001f, 0xffc1, 0xfff0, 0x0000, 0x0000,
0x0000, 0x0e0f, 0xf9ff, 0xff80, 0x003f, 0xfff1, 0xffe0, 0x0000, 0x0000,
0x0000, 0x0707, 0xffff, 0xff80, 0x003f, 0xffff, 0xffe0, 0x0000, 0x0000,
0x0000, 0x0787, 0xff9f, 0xff80, 0x007f, 0xfffe, 0x1fc0, 0x0000, 0x0000,
0x0000, 0x03c3, 0xff81, 0xff00, 0x00ff, 0xfffc, 0x01c0, 0x0000, 0x0000,
0x0000, 0x01e1, 0xffc0, 0x1f00, 0x01ff, 0xfff8, 0x00c0, 0x0000, 0x0000,
0x0000, 0x00f1, 0xffc0, 0x0600, 0x01ff, 0xffe0, 0x01f0, 0x0000, 0x0000,
0x0000, 0x0070, 0xffc0, 0x07c0, 0x03ff, 0xffc0, 0x07e0, 0x0000, 0x0000,
0x0000, 0x0038, 0xffc0, 0x0ff0, 0x07ff, 0xff80, 0x0f80, 0x0000, 0x0000,
0x0000, 0x001c, 0x7fc0, 0x0fff, 0x07ff, 0xff00, 0x3f00, 0x0000, 0x0000,
0x0000, 0x0000, 0x3fc0, 0x0fff, 0xffff, 0xfe00, 0x7c00, 0x0000, 0x0000,
0x0000, 0x0000, 0x7fe0, 0x1fff, 0xfcff, 0xfc00, 0xf800, 0x0000, 0x0000,
0x0000, 0x0000, 0x13e0, 0x1fff, 0xf83f, 0xf003, 0xe000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0460, 0x1fff, 0xe007, 0xe007, 0x8000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0110, 0x1fff, 0xc000, 0xc01f, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x004e, 0x3fff, 0x8003, 0xf03c, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0017, 0xfffe, 0x000f, 0xfff8, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0005, 0xc3fc, 0x007f, 0xff80, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0001, 0xc038, 0x03ff, 0xfe00, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x87f8, 0x0fff, 0xe000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x07ff, 0x7ffe, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0007, 0x8fe0, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0600, 0x0000, 0x0000, 0x0000, 0x0000
}
--local SCREEN_W = 336
--local SCREEN_H = 216
local SCREEN_W = 320
local SCREEN_H = 200
local function l(x1, y1, x2, y2, c)
raster.drawLine(x1-8, y1-8, x2-8, y2-8, c)
end
local function g(x, y)
return raster.get(x-8, y-8)
end
local function s(x, y, c)
raster.set(x-8, y-8, c)
end
local BALL_W = 144
local BALL_H = 100
local ONE_PLANE_LEN = BALL_W * BALL_H / 16
local function ball()
local color_cycle = 2
local pos = { x = 0.0, y = 0.0 }
local scroll = { x = -1, y = -1 }
local pixel_index = {}
local is_shadow = {}
local this = {}
function this.pos()
return -pos.x + 77, -pos.y
end
function this.step()
pos.x = pos.x + scroll.x
if pos.x <= -95 or pos.x >= 95 then scroll.x = -scroll.x end
local adj = scroll.y
if pos.y > -10.0 then adj = adj * 1.0
elseif pos.y > -30.0 then adj = adj * 2.0
elseif pos.y > -60.0 then adj = adj * 3.0
else adj = adj * 4.0 end
pos.y = pos.y + adj
if pos.y <= -100 or pos.y >= 0 then scroll.y = -scroll.y end
end
local function orderbits(value)
local result = 0
for i = 0, 15 do
if (value >> i) & 1 == 1 then
result = result | (1 << (15 - i))
end
end
return result
end
function this.decode()
for y = 0, BALL_H - 1 do
for x = 0, BALL_W - 1 do
local pos = y * BALL_W + x
local offset = pos // 16
local bitpos = pos % 16
local value = 0
for plane = 0, 3 do
local word = orderbits(ball_bitplanes[offset + plane * ONE_PLANE_LEN + 1])
if (word & (1 << bitpos)) ~= 0 then
value = value | (1 << plane)
end
end
if (value == 1) then
is_shadow[pos + 1] = true
pixel_index[pos + 1] = 0
else
is_shadow[pos + 1] = false
pixel_index[pos + 1] = value
end
end
end
end
function this.cycle_palette()
if scroll.x > 0 then color_cycle = color_cycle - 1
else color_cycle = color_cycle + 1 end
if color_cycle == -1 then color_cycle = 13
elseif color_cycle == 14 then color_cycle = 0 end
for i = 0, 6 do
if color_cycle + i < 14 then
color_table[color_cycle + i + 3] = 0xFFFFFF
color_table[color_cycle + i + 19] = 0xFFFFFF
else
color_table[color_cycle + i - 11] = 0xFFFFFF
color_table[color_cycle + i + 5] = 0xFFFFFF
end
end
for i = 7, 13 do
if color_cycle + i < 14 then
color_table[color_cycle + i + 3] = 0xFF0000
color_table[color_cycle + i + 19] = 0xFF0000
else
color_table[color_cycle + i - 11] = 0xFF0000
color_table[color_cycle + i + 5] = 0xFF0000
end
end
if scroll.x > -1 then
color_table[color_cycle + 3] = 0xFFDDDD
color_table[color_cycle + 19] = 0xFFDDDD
elseif color_cycle + 6 < 14 then
color_table[color_cycle + 9] = 0xFFDDDD
color_table[color_cycle + 25] = 0xFFDDDD
else
color_table[color_cycle - 5] = 0xFFDDDD
color_table[color_cycle + 11] = 0xFFDDDD
end
end
function this.draw(x, y, shadow)
for oy = 0, BALL_H - 1 do
for ox = 0, BALL_W - 1 do
local pos = oy * BALL_W + ox + 1
if shadow then
if is_shadow[pos] then
local c = g(ox+x, oy+y)
if c == 0xAAAAAA then s(ox+x, oy+y, 0x666666) end
end
else
local idx = pixel_index[pos]
if idx ~= 0 then
local c = color_table[idx + 1]
s(ox+x, oy+y, c)
end
end
end
end
end
return this
end
function draw_background()
raster.clear()
local lc = color_table[17]
local k = 20
for j = 48, 300, 16 do
l(j, 0, j, 192, lc)
l(j, 192, k, 215, lc)
k = k + 20
end
for j = 0, 200, 16 do
l(48, j, 288, j, lc)
end
l(45, 194, 291, 194, lc)
l(41, 197, 295, 197, lc)
l(37, 201, 300, 201, lc)
l(30, 207, 308, 207, lc)
l(20, 215, 319, 215, lc)
end
local b = ball()
b.decode()
raster.init(SCREEN_W/2, SCREEN_H/4, color_table[1])
local i = 0
while true do
i = i + 1
draw_background()
b.cycle_palette()
b.step()
local x, y = b.pos()
b.draw(x, y, true)
b.draw(x, y)
raster.update()
if event.pull("key_down", 0.01) then
break
end
end
raster.free()
terminal.clear()
+60
View File
@@ -0,0 +1,60 @@
local component = require("component")
local computer = require("computer")
local args = {...}
local force = false
local forceArgIdx = table.find(args,"-f") or table.find(args,"--force")
if forceArgIdx then
table.remove(args,forceArgIdx)
force = true
end
local function getComponentID(str)
local function fromSlot(slot)
for i,v in component.list() do
if component.slot(i)==slot then
return i
end
end
end
if str=="hdd1" or str=="#1" then return fromSlot(5) end
if str=="hdd2" or str=="#2" then return fromSlot(6) end
if str=="floppy" or str=="#3" then return fromSlot(7) end
if #str<3 then return nil,"Abbreviated ID must atleast have 3 characters" end
return component.get(str)
end
local function fileExists(compID,file)
return component.invoke(compID,"exists",file) and not component.invoke(compID,"isDirectory",file)
end
if type(args[1])=="string" then
local compID,err = getComponentID(args[1])
if not compID then
print("\x1b[91mCould not get component ID from '"..args[1].."'.")
if type(err)=="string" then print("\x1b[91m"..err.."\x1b[0m") end
return
end
if not force then
if component.virtual.check(compID) then
return print("\x1b[91mThis component is virtual and cannot be booted from directly.\nID: "..compID.."\x1b[0m")
end
local type = component.type(compID)
if type~="filesystem" and type~="drive" then
return print("\x1b[91mThis component is not a storage medium.\nID: "..compID.."\x1b[0m")
end
if type=="filesystem" and not fileExists(compID,"/init.lua") then
return print("\x1b[91mThis storage medium doesn't have an \"init.lua\" file.\nID: "..compID.."\x1b[0m")
end
end
computer.setBootAddress(compID)
if computer.getBootAddress()~=compID then
return print("\x1b[91mFailed to set the boot address.\x1b[0m")
end
computer.shutdown(true)
else
require("shell").run("help boot")
end
+30
View File
@@ -0,0 +1,30 @@
local fs = require("filesystem")
local shell = require("shell")
local args = {...}
if not args[1] then
return shell.run("help cat")
end
for _, file in pairs(args) do
file = shell.resolvePath(file)
local handle = fs.open(file, "r")
if handle == nil then
terminal.write("\27[91mCan't open " .. file .. "\27[0m\n")
goto continue
end
local enddata
while true do
local data = handle:read(math.huge or math.maxinteger)
if data == nil then break end
terminal.write(data)
enddata = data
end
handle:close()
if string.sub(enddata, -1, -1) ~= "\n" then
print("")
end
::continue::
end
+26
View File
@@ -0,0 +1,26 @@
local args = {...}
if args[2] then
terminal.write("\27[91mToo many arguments.\27[0m")
end
if not args[1] then
return
end
local fs = require("filesystem")
local shell = require("shell")
local directory = shell.resolvePath(args[1])
if not fs.exists(directory) then
terminal.write("\27[91mError: " .. directory .. ": No such file or directory\27[0m\n")
return
end
if not fs.isDirectory(directory) then
terminal.write("\27[91mError: " .. directory .. ": Not a directory\27[0m\n")
return
end
shell.setWorkingDirectory(fs.canonical(directory))
+3
View File
@@ -0,0 +1,3 @@
terminal.clear()
-- truly so much going on here
-- meow
+70
View File
@@ -0,0 +1,70 @@
local fs = require("filesystem")
local shell = require("shell")
local args = {...}
if not args[1] then
return shell.run("help cp")
end
if not args[2] then
terminal.write("\27[91mError: No destination\27[0m\n")
return
end
local dest = shell.resolvePath(args[#args])
if fs.isFile(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: Destination is not a directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
if fs.isDirectory(src) then
terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n")
return
end
fs.copy(src, dest)
elseif fs.isDirectory(dest) then
for i = 1, #args - 1 do
local src = shell.resolvePath(args[i])
if src == dest then
terminal.write("\27[91mError: Source and destination are the same\27[0m\n")
goto continue
end
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
goto continue
end
fs.copy(src, fs.concat(dest, fs.basename(src)))
::continue::
end
elseif not fs.exists(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: " .. dest .. ": No such file or directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
local destp = fs.parent(dest)
if not fs.exists(destp) then
terminal.write("\27[91mError: " .. destp .. ": No such file or directory\27[0m\n")
return
end
if not fs.isDirectory(destp) then
terminal.write("\27[91mError: " .. destp .. ": Not a directory\27[0m\n")
return
end
fs.copy(src, dest)
else
terminal.write("\27[91mUnknown error\27[0m\n")
end
+57
View File
@@ -0,0 +1,57 @@
local url = ...
local component = require("component")
local fs = require("filesystem")
if not component.list("internet")() then
print("\27[91mThis program requires an internet card to run.\27[0m")
return
end
if not url then
print("Please enter a URL to download from.")
require("shell").run("help download")
return
end
if url:sub(-1, -1) == "/" then
url = url:sub(1, -2)
end
local internet = component.internet
local request, data, tmpdata = nil, "", nil
local status, errorMessage = pcall(function()
request = internet.request(url)
request:finishConnect()
end)
if not status then
print("\27[91mDownload failed: " .. errorMessage .. "\27[0m")
end
local responseCode = request:response()
if responseCode and responseCode ~= 200 then
print("\27[91mDownload failed: " .. tostring(responseCode) .. "\27[0m")
end
repeat
tmpdata = request.read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
local saveLocation
local saveLocationOK = false
repeat
saveLocation = terminal.read(nil, "File save location: ", fs.concat(require("shell").getWorkingDirectory(), url:match("/([^/]+)$")))
if fs.isDirectory(saveLocation) then
print("\27[91mThe specified location is a directory.\27[0m")
elseif fs.exists(saveLocation) then
local answer = terminal.read({prefix = "\27[91mThere is already a file at the specified directory. Overwrite it? [Y/n]\27[0m"})
if answer:lower() ~= "n" then
saveLocationOK = true
end
else
saveLocationOK = true
end
until saveLocationOK
local handle = fs.open(saveLocation, "w")
handle:write(data)
handle:close()
print("File downloaded successfully.")
+7
View File
@@ -0,0 +1,7 @@
local args = {...}
local concatText = args[1]
table.remove(args, 1)
for _, item in pairs(args) do
concatText = concatText .. " " .. item
end
print(concatText)
+348
View File
@@ -0,0 +1,348 @@
local file = ...
local fs = require("filesystem")
local event = require("event")
local component = require("component")
local unicode = require("unicode")
local workingDirectory = require("shell").getWorkingDirectory()
local gpu = component.gpu
local width, height = gpu.getResolution()
local scrollPosX, scrollPosY = 1, 1
local cursorPosX, cursorPosY = 1, 1
local cursorWhite = true
local changesMade = false
local renderBuffer = gpu.allocateBuffer()
local scrollSpeed = 5
local tab = " "
--local ocelot = component.ocelot
local function rawset(x, y, text)
terminal.write("\x1b[".. tostring(y) .. ";" .. tostring(x) .. "H" .. text)
end
local filestring, filepath, handle, data, tmpdata
if file then
if file:sub(1, 1) == "/" then
filepath = file
else
filepath = workingDirectory .. file
end
handle, data, tmpdata = fs.open(filepath, "r"), "", nil
if fs.exists(filepath) then
filestring = filepath
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = {}
if data:gmatch("(.-)\n")() then
for line in data:gmatch("(.-)\n") do
local newLine = line:gsub("\r", "") -- this took me SO LONG TO FIGURE OUT AAAAAAAA I HATE CRLF I HATE CRLF I HATE CRLF
table.insert(tmpdata, newLine)
end
else
tmpdata = {data}
end
else
filepath = workingDirectory .. file
filestring = "[NEW FILE]"
tmpdata = {""}
end
else
filepath = ""
filestring = "[NEW FILE]"
tmpdata = {""}
end
local function render()
gpu.setActiveBuffer(renderBuffer)
terminal.clear()
--ocelot.log(tostring(scrollPosY))
local realCursorX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2)
if realCursorX < 1 then
scrollPosX = scrollPosX + realCursorX - 1
cursorPosX = 1
realCursorX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2)
end
for i = scrollPosY, height + scrollPosY - 3 do
gpu.set(1, i - scrollPosY + 1, (tmpdata[i] or ""):sub(scrollPosX))
end
rawset(1, height - 1, "\27[107m\27[30m" .. filestring .. string.rep(" ", width))
rawset(1, height, "\27[107m\27[30m^X\27[0m Exit \27[107m\27[30m^S\27[0m Save" .. string.rep(" ", width))
local char = gpu.get(realCursorX, cursorPosY)
if cursorWhite then
gpu.setForeground(0)
gpu.setBackground(0xFFFFFF)
end
gpu.set(realCursorX, cursorPosY, char)
gpu.bitblt()
gpu.setActiveBuffer(0)
end
local renderFlag, cursorRenderFlag = false, false
local function scrollUp()
cursorPosY = cursorPosY - 1
cursorRenderFlag = true
cursorWhite = true
if cursorPosY < 1 then
renderFlag = true
scrollPosY = scrollPosY - 1
cursorPosY = 1
end
if scrollPosY < 1 then
renderFlag = false
scrollPosY = 1
end
if math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) < 1 then
renderFlag = true
end
end
local function scrollDown()
cursorPosY = cursorPosY + 1
cursorRenderFlag = true
cursorWhite = true
if cursorPosY + scrollPosY - 1 > #tmpdata then
renderFlag = false
cursorPosY = #tmpdata - scrollPosY + 1
end
if cursorPosY > height - 2 then
renderFlag = true
scrollPosY = scrollPosY + 1
cursorPosY = height - 2
end
if math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) < 1 then
renderFlag = true
end
end
local function scrollLeft()
cursorRenderFlag = true
cursorWhite = true
if cursorPosX > 1 then
if cursorPosX <= unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2 then
cursorPosX = cursorPosX - 1
elseif unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 1 > 1 then
cursorPosX = unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 1
end
elseif scrollPosX > 1 then
scrollPosX = scrollPosX - 1
renderFlag = true
end
if math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2) < 1 then
renderFlag = true
end
end
local function scrollRight()
cursorRenderFlag = true
cursorWhite = true
if cursorPosX <= unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 1 then
cursorPosX = cursorPosX + 1
end
if cursorPosX > width then
cursorPosX = width
scrollPosX = scrollPosX + 1
renderFlag = true
end
end
local function processEvent(args)
renderFlag, cursorRenderFlag = false, false
if args[1] == "key_down" then
local keycode = args[4]
local key = keyboard.keys[keycode]
if keyboard.getCtrlDown() then
return false, false, key
end
if key == "down" and cursorPosY < #tmpdata then
scrollDown()
end
if key == "up" then
scrollUp()
end
if key == "left" then
scrollLeft()
end
if key == "right" then
scrollRight()
end
if key == "enter" then
changesMade = true
renderFlag = true
cursorWhite = true
table.insert(tmpdata, cursorPosY + 1, tmpdata[cursorPosY + scrollPosY - 1]:sub(cursorPosX))
tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1]:sub(1, cursorPosX - 1)
cursorPosX = 1
cursorPosY = cursorPosY + 1
scrollPosX = 1
if cursorPosY > height - 2 then
scrollPosY = scrollPosY + 1
cursorPosY = height - 2
end
end
if key == "back" then
changesMade = true
cursorRenderFlag = true
cursorWhite = true
if cursorPosX == 1 and cursorPosY + scrollPosY - 1 > 1 then
cursorPosY = cursorPosY - 1
if cursorPosY < 1 then
scrollPosY = scrollPosY - 1
cursorPosY = 1
end
if scrollPosY < 1 then
scrollPosY = 1
end
cursorPosX = unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2
if cursorPosX > width then
scrollPosX = cursorPosX - width + 1
cursorPosX = width
end
tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1] .. tmpdata[cursorPosY + 1]
table.remove(tmpdata, cursorPosY + 1)
renderFlag = true
else
tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1]:sub(1, cursorPosX + scrollPosX - 3) .. tmpdata[cursorPosY + scrollPosY - 1]:sub(cursorPosX + scrollPosX - 1)
cursorPosX = math.min(cursorPosX - 1, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) + 1)
if cursorPosX < 1 then
cursorPosX = 1
scrollPosX = scrollPosX - 1
renderFlag = true
else
gpu.set(1, cursorPosY, tmpdata[cursorPosY + scrollPosY - 1]:sub(scrollPosX) .. " ")
end
end
end
if key == "tab" then
changesMade = true
cursorRenderFlag = true
cursorWhite = true
tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1]:sub(1, cursorPosX + scrollPosX - 2) .. tab .. tmpdata[cursorPosY + scrollPosY - 1]:sub(cursorPosX + scrollPosX - 1)
cursorPosX = cursorPosX + unicode.wlen(tab)
if cursorPosX > width then
scrollPosX = scrollPosX + cursorPosX - width
cursorPosX = width
renderFlag = true
else
gpu.set(1, cursorPosY, tmpdata[cursorPosY + scrollPosY - 1]:sub(scrollPosX))
end
end
if args[3] >= 32 and args[3] <= 126 then
changesMade = true
cursorRenderFlag = true
cursorWhite = true
tmpdata[cursorPosY + scrollPosY - 1] = tmpdata[cursorPosY + scrollPosY - 1]:sub(1, cursorPosX + scrollPosX - 2) .. unicode.char(args[3]) .. tmpdata[cursorPosY + scrollPosY - 1]:sub(cursorPosX + scrollPosX - 1)
cursorPosX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1])) + 1
--ocelot.log(tostring(cursorPosX))
if cursorPosX > width then
cursorPosX = width
scrollPosX = scrollPosX + 1
renderFlag = true
else
gpu.set(1, cursorPosY, tmpdata[cursorPosY + scrollPosY - 1]:sub(scrollPosX))
end
end
elseif args[1] == "scroll" then
if args[5] == 1 then
for i = 1, scrollSpeed do
scrollUp()
end
elseif args[5] == -1 and cursorPosY < #tmpdata then
for i = 1, scrollSpeed do
scrollDown()
end
end
end
return renderFlag, cursorRenderFlag
end
local function save()
gpu.setBackground(0xFFFFFF)
gpu.setForeground(0)
gpu.set(1, height - 1, string.rep(" ", width))
rawset(1, height - 1)
local savepath = terminal.read({prefix = "\27[107m\27[30mSave location: ", defaultText = filepath})
gpu.setBackground(0xFFFFFF)
gpu.setForeground(0)
if fs.exists(savepath) then
gpu.set(1, height - 1, string.rep(" ", width))
local answer = terminal.read({prefix = "\27[107m\27[30mFile already exists. Overwrite it? [Y/n] "})
if answer:lower() == "n" then
gpu.setBackground(0xFFFFFF)
gpu.setForeground(0)
gpu.set(1, height - 1, filestring .. string.rep(" ", width))
return
end
end
local handle, errorMessage = fs.open(savepath, "w")
if handle then
if table.concat(tmpdata, "\n"):sub(-1, -1) == "\n" then
handle:write(table.concat(tmpdata, "\n"))
else
handle:write(table.concat(tmpdata, "\n") .. "\n") -- add a newline at the end to follow POSIX standards
end
handle:close()
gpu.set(1, height - 1, filestring .. string.rep(" ", width))
else
gpu.set(1, height - 1, "ERROR: " .. errorMessage:gsub("\n", "") .. string.rep(" ", width))
end
changesMade = false
end
render()
while true do
local args = {event.pull(0.5)}
local renderFlag, cursorRenderFlag, specialKey = false, false, nil
local previousCursorX, previousCursorY = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2), cursorPosY
if args and args[1] then
cursorWhite = true
renderFlag, cursorRenderFlag, specialKey = processEvent(args)
if specialKey == "x" then
if changesMade then
rawset(1, height - 1)
local response = terminal.read({prefix = "\27[107m\27[30mWould you like to save changes? [Y/n] "})
if response:lower() ~= "n" then
save()
end
end
gpu.freeAllBuffers()
terminal.clear()
return
end
if specialKey == "s" then
save()
end
repeat
args = {event.pull("key_down", 0)}
if args and args[1] then
processEvent(args)
end
until not args or not args[1]
else
cursorWhite = not cursorWhite
cursorRenderFlag = true
end
if cursorRenderFlag then
local char = gpu.get(previousCursorX, previousCursorY)
gpu.set(previousCursorX, previousCursorY, char)
local realCursorX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2)
if realCursorX < 1 then
scrollPosX = scrollPosX + realCursorX - 1
cursorPosX = 1
realCursorX = math.min(cursorPosX, unicode.wlen(tmpdata[cursorPosY + scrollPosY - 1]) - scrollPosX + 2)
end
local char = gpu.get(realCursorX, cursorPosY)
if cursorWhite then
gpu.setBackground(0xFFFFFF)
gpu.setForeground(0)
end
gpu.set(realCursorX, cursorPosY, char)
if cursorWhite then
gpu.setForeground(0xFFFFFF)
gpu.setBackground(0)
end
end
if renderFlag then
render()
end
end
+66
View File
@@ -0,0 +1,66 @@
local component = require("component")
local computer = require("computer")
local filesystem = require("filesystem")
local function convert(value, fromUnit, toUnit)
local units = {B = 1, KiB = 1024, MiB = 1024^2, GiB = 1024^3}
return value * units[fromUnit] / units[toUnit]
end
local function printstat(text)
terminal.write("\27[35G" .. text .. "\n")
end
local logo = ""
local handle, tmpdata = filesystem.open("/halyde/config/oslogo.ans", "r"), nil
repeat
tmpdata = handle:read(math.huge)
logo = logo .. (tmpdata or "")
until not tmpdata
handle:close()
terminal.write(logo)
terminal.write("\27[17A")
printstat("\27[92mOS\27[0m: " .. _OSVERSION)
printstat("\27[92mArchitecture\27[0m: " .. _VERSION)
local componentCounter = 0
for _ in component.list() do
componentCounter = componentCounter + 1
end
printstat("\27[92mComponents\27[0m: " .. tostring(componentCounter))
printstat("\27[92mCoroutines\27[0m: " .. tostring(#tsched.getTasks()))
printstat("\27[92mBattery\27[0m: " .. tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10) .. "%")
local totalMemory = computer.totalMemory()
local usedMemory = computer.totalMemory() - computer.freeMemory()
local function formatBytes(bytes)
if convert(bytes, "B", "GiB") >= 1 then
return tostring(math.floor(convert(bytes, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(bytes, "B", "MiB") >= 1 then
return tostring(math.floor(convert(bytes, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
elseif convert(bytes, "B", "KiB") >= 1 then
return tostring(math.floor(convert(bytes, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
return tostring(bytes) .. " B"
end
end
printstat("\27[92mMemory\27[0m: " .. formatBytes(usedMemory) .. " / " .. formatBytes(totalMemory))
local totalDisk = component.invoke(computer.getBootAddress(), "spaceTotal")
local usedDisk = component.invoke(computer.getBootAddress(), "spaceUsed")
printstat("\27[92mDisk\27[0m: " .. formatBytes(usedDisk) .. " / " .. formatBytes(totalDisk))
local gpuComponent = component.list("gpu")()
local width, height = component.invoke(gpuComponent, "getResolution")
printstat("\27[92mResolution\27[0m: " .. tostring(width) .. "x" .. tostring(height) .. "\n")
printstat("\27[40m \27[41m \27[42m \27[43m \27[44m \27[45m \27[46m \27[47m ")
printstat("\27[100m \27[101m \27[102m \27[103m \27[104m \27[105m \27[106m \27[107m ")
terminal.write("\27[5B\27[0m")
+293
View File
@@ -0,0 +1,293 @@
local fs = require("filesystem")
local shell = require("shell")
local arg = ... or "default"
local what = arg
local aliases = shell.getAliases()
if aliases[what] then
what = aliases[what]
end
local path = "/halyde/apps/helpdb/" .. what
if not fs.exists(path) then
print("Could not find help file for: " .. arg .. ".")
return
end
if path == "/halyde/apps/helpdb/default" then
local handle = fs.open(path, "r")
local shi = ""
while true do
local tmpdata = handle:read(math.huge or math.maxinteger)
if not tmpdata then break end
shi = shi .. tmpdata
end
shi = lz4(shi)
terminal.write(shi)
return
end
local handle = fs.open(path, "r")
local data = {
command = "",
usage = "",
description = "",
args = {},
examples = {}
}
local shi = ""
while true do
local tmpdata = handle:read(math.huge or math.maxinteger)
if not tmpdata then break end
shi = shi .. tmpdata
end
shi = lz4(shi)
local shit = 1
local function r()
if shit > #shi then return nil end
local v = shi:sub(shit, shit)
shit = shit + 1
return v
end
while true do
local line = ""
while true do
local char = r()
if not char then
if line == "" then
line = nil
break
end
break
end
if char == "\n" then
break
end
if char == "\r" then
local next_char = r()
if next_char and next_char == "\n" then
break
elseif next_char then
local pos = file:seek("cur")
if pos then
file:seek("set", pos - 1)
end
break
end
break
end
line = line .. char
end
if line == nil then
break
end
line = line:match("^%s*(.-)%s*$")
if line then
local key, value = line:match("^(%w+)%s+(.*)$")
if not key then goto continue end
if key:lower() == "command" then
data.command = value
end
if key:lower() == "usage" then
data.usage = value
end
if key:lower() == "description" then
data.description = value
end
if key:lower():match("^arg%d+$") then
local num = key:lower():match("^arg(%d+)$")
if not data.args[tonumber(num)] then data.args[tonumber(num)] = {} end
data.args[tonumber(num)].name = value
end
if key:lower():match("^arg%d+description$") then
local num = key:lower():match("^arg(%d+)description$")
if not data.args[tonumber(num)] then data.args[tonumber(num)] = {} end
data.args[tonumber(num)].description = value
end
if key:lower():match("^arg%d+sub%d+$") then
local main_num, sub_num = key:lower():match("^arg(%d+)sub(%d+)$")
if main_num and sub_num then
if not data.args[tonumber(main_num)] then data.args[tonumber(main_num)] = {} end
if not data.args[tonumber(main_num)].subflags then data.args[tonumber(main_num)].subflags = {} end
if not data.args[tonumber(main_num)].subflags[tonumber(sub_num)] then
data.args[tonumber(main_num)].subflags[tonumber(sub_num)] = {}
end
data.args[tonumber(main_num)].subflags[tonumber(sub_num)].name = value
end
end
if key:lower():match("^arg%d+sub%d+description$") then
local main_num, sub_num = key:lower():match("^arg(%d+)sub(%d+)description$")
if main_num and sub_num then
if not data.args[tonumber(main_num)] then data.args[tonumber(main_num)] = {} end
if not data.args[tonumber(main_num)].subflags then data.args[tonumber(main_num)].subflags = {} end
if not data.args[tonumber(main_num)].subflags[tonumber(sub_num)] then
data.args[tonumber(main_num)].subflags[tonumber(sub_num)] = {}
end
data.args[tonumber(main_num)].subflags[tonumber(sub_num)].description = value
end
end
if key:lower():match("^example%d+$") then
local num = key:lower():match("^example(%d+)$")
if not data.examples[tonumber(num)] then data.examples[tonumber(num)] = {} end
data.examples[tonumber(num)].name = value
end
if key:lower():match("^example%d+description$") then
local num = key:lower():match("^example(%d+)description$")
if not data.examples[tonumber(num)] then data.examples[tonumber(num)] = {} end
data.examples[tonumber(num)].description = value
end
::continue::
end
end
handle:close()
--print(require("serialize")(data, "\t"))
-- Halyde terminal doesn't support bold (CSI 1 m) but who cares
if data.command then
terminal.write("\27[1mUsage: \27[0m\n")
terminal.write(" \27[96m" .. data.command)
if data.usage then
terminal.write("\27[93m " .. data.usage)
end
terminal.write("\27[0m\n\n")
end
local width, height = terminal.getResolution()
local function wrap_text(text, indent)
if not text then return "" end
local words = {}
for word in text:gmatch("%S+") do
table.insert(words, word)
end
local lines = {}
local current_line = ""
for i, word in ipairs(words) do
if #current_line + #word + 1 <= width * 0.66 - indent then
if current_line == "" then
current_line = word
else
current_line = current_line .. " " .. word
end
else
table.insert(lines, current_line)
current_line = word
end
end
if current_line ~= "" then
table.insert(lines, current_line)
end
local result = {}
for i, line in ipairs(lines) do
if i == 1 then
table.insert(result, line)
else
table.insert(result, string.rep(" ", indent) .. line)
end
end
return table.concat(result, "\n")
end
if data.description then
terminal.write("\27[1mDescription:\27[0m\n")
terminal.write(" " .. wrap_text(data.description, 2))
terminal.write("\n\n")
end
if #data.args > 0 then
terminal.write("\27[1mArguments:\27[0m\n")
local max_len = 0
for _, flag in ipairs(data.args) do
if flag.name then
max_len = math.max(max_len, #flag.name)
end
for _, subf in ipairs(flag.subflags or {}) do
if subf.name then
max_len = math.max(max_len, #subf.name + 2)
end
end
end
for _, flag in ipairs(data.args) do
terminal.write(" \27[93m" .. (flag.name or "") .. "\27[0m" .. string.rep(" ", max_len - (flag.name and #flag.name or 0) + 2) .. wrap_text(flag.description, 4 + max_len) .. "\n")
for _, subf in ipairs(flag.subflags or {}) do
terminal.write(" \27[92m" .. (subf.name or "") .. "\27[0m" .. string.rep(" ", max_len - (subf.name and #subf.name or 0)) .. wrap_text(subf.description, 4 + max_len) .. "\n")
end
end
terminal.write("\n")
end
local function formatExampleName(name, utility)
if not name then return name end
local contains = false
if name:find(utility, 1, true) then
contains = true
else
for alias, cmd in pairs(aliases) do
if cmd == utility and name:find(alias, 1, true) then
contains = true
break
end
end
end
if not contains then
return "\27[92m" .. name
end
local formatted = name
formatted = formatted:gsub("(" .. utility .. ")", "\27[96m%1\27[92m")
for alias, cmd in pairs(aliases) do
if cmd == utility then
formatted = formatted:gsub("(" .. alias .. ")", "\27[96m%1\27[92m")
end
end
return formatted
end
if #data.examples > 0 then
terminal.write("\27[1mExamples:\27[0m\n")
local max_len = 0
for _, flag in ipairs(data.examples) do
max_len = math.max(max_len, #flag.name)
end
for _, flag in ipairs(data.examples) do
terminal.write(" " .. formatExampleName(flag.name, arg) .. "\27[0m" .. string.rep(" ", max_len - #flag.name + 2) .. wrap_text(flag.description, 4 + max_len) .. "\n")
end
terminal.write("\n")
end
local first = true
for k, v in pairs(aliases) do
if v == arg then
if first then
terminal.write("\27[1mAliases:\27[0m\n ")
end
terminal.write("\27[96m" .. k)
if not first then
terminal.write("\27[0m, ")
end
first = false
end
end
if not first then
terminal.writec(0xa)
end
+47
View File
@@ -0,0 +1,47 @@
COMMAND ag2
USAGE [COMMAND] [PACKAGES] [FLAGS]
DESCRIPTION Uses the Argentum 2 package manager.
ARG1 COMMAND
ARG1SUB1 install
ARG1SUB2 remove
ARG1SUB3 update
ARG1SUB4 list
ARG1SUB5 repo-list
ARG1SUB6 repo-add
ARG1SUB7 repo-remove
ARG1SUB8 info
ARG2 PACKAGES
ARG3 FLAGS
ARG3SUB1 -x, --exclude-deps
ARG3SUB2 -u, --update-repos
ARG3SUB3 -f, --force
ARG3SUB4 -c, --clean
ARG3SUB5 -s, --source [URL]
ARG3SUB6 -C, --cascade
ARG1DESCRIPTION Specifies the operation for Argentum 2 to do.
ARG1SUB1DESCRIPTION Installs packages.
ARG1SUB2DESCRIPTION Removes packages.
ARG1SUB3DESCRIPTION Updates packages.
ARG1SUB4DESCRIPTION Lists all available packages.
ARG1SUB5DESCRIPTION Lists all installed repositories.
ARG1SUB6DESCRIPTION Adds a custom repository.
ARG1SUB7DESCRIPTION Removes a repository.
ARG1SUB8DESCRIPTION Shows a packages version, description and other relevant information.
ARG2DESCRIPTION Packages to apply operations to.
ARG3DESCRIPTION These flags are available and can be inserted anywhere
ARG3SUB1DESCRIPTION Ignore dependencies. WARNING: Using this can and will leave you with broken packages. Use it at your own risk and only when truly necessary.
ARG3SUB2DESCRIPTION Update the list of repositories.
ARG3SUB3DESCRIPTION Force the operation, even if there are conflicts or unresolvable dependencies. WARNING: Using this can and will leave you with broken packages. Use it at your own risk and only when truly necessary.
ARG3SUB4DESCRIPTION Clean up now-unnecessary packages (previous dependencies).
ARG3SUB5DESCRIPTION Use a custom source for the operation.
ARG3SUB6DESCRIPTION When removing a package that other packages depend on, remove those packages too instead of aborting.
EXAMPLE1 ag2 install halyde
EXAMPLE2 ag2 list
EXAMPLE3 ag2 info halyde
EXAMPLE4 ag2 remove -x edit
EXAMPLE5 ag2 remove -c hal-draw
EXAMPLE1DESCRIPTION Installs the halyde package.
EXAMPLE2DESCRIPTION Lists all packages.
EXAMPLE3DESCRIPTION Shows information about the halyde package.
EXAMPLE4DESCRIPTION Removes edit, but does not remove any packages that depend on it.
EXAMPLE5DESCRIPTION Removes hal-draw and any dependencies that are no longer needed.
+29
View File
@@ -0,0 +1,29 @@
COMMAND argentum
USAGE [COMMAND] [PACKAGES]
DESCRIPTION Uses the Argentum package manager.
ARG1 COMMAND
ARG1SUB1 install
ARG1SUB2 remove
ARG1SUB3 update
ARG1SUB4 list
ARG1SUB5 search
ARG1SUB6 info
ARG2 PACKAGES
ARG1DESCRIPTION Specifies the operation for Ag to do.
ARG1SUB1DESCRIPTION Installs packages.
ARG1SUB2DESCRIPTION Removes packages.
ARG1SUB3DESCRIPTION Updates packages.
ARG1SUB4DESCRIPTION Lists all available packages.
ARG1SUB5DESCRIPTION Searches all available packages.
ARG1SUB6DESCRIPTION Shows information on a specific package.
ARG2DESCRIPTION Packages to apply operations to.
EXAMPLE1 ag install hal-draw
EXAMPLE2 ag list
EXAMPLE3 ag info hal-draw
EXAMPLE4 ag update hal-draw
EXAMPLE5 ag update hal-draw
EXAMPLE1DESCRIPTION Installs the hal-draw package.
EXAMPLE2DESCRIPTION Lists all packages.
EXAMPLE3DESCRIPTION Shows information about hal-draw.
EXAMPLE4DESCRIPTION Updates the hal-draw package if it's not at the newest version.
EXAMPLE5DESCRIPTION Updates all packages.
+8
View File
@@ -0,0 +1,8 @@
COMMAND beep
USAGE [FLAGS]
DESCRIPTION Make the computer beep.
ARG1 FLAGS
ARG1SUB1 -f, --frequency
ARG1SUB2 -t, --time
ARG1SUB1DESCRIPTION Specifies the frequency, in Hz. Defaults to 440Hz.
ARG1SUB2DESCRIPTION Specifies how long, in seconds, the computer should beep. Defaults to 0.1s.
+23
View File
@@ -0,0 +1,23 @@
COMMAND boot
USAGE [ADDRESS] [FLAGS]
DESCRIPTION Restarts and automatically boots into any storage medium. Meant to be used for systems using a Lua BIOS EEPROM.
ARG1 ADDRESS
ARG1SUB1 hdd1
ARG1SUB2 hdd2
ARG1SUB3 floppy
ARG1SUB4
ARG2 FLAGS
ARG2SUB1 -f, --force
ARG1DESCRIPTION The storage medium to boot to.
ARG1SUB1DESCRIPTION The first hard drive inserted in the computer.
ARG1SUB2DESCRIPTION The second hard drive inserted in the computer.
ARG1SUB3DESCRIPTION The floppy disk that is inserted in the computer.
ARG1SUB4DESCRIPTION The ID of the component, abbreviated. Must have three or more characters.
ARG2DESCRIPTION Specifies extra options when executing the command.
ARG2SUB1DESCRIPTION Forces booting into the storage medium.
EXAMPLE1 boot hdd1
EXAMPLE2 boot hdd2
EXAMPLE3 boot floppy
EXAMPLE1DESCRIPTION Boot into the first hard drive inserted in the computer.
EXAMPLE2DESCRIPTION Boot into the second hard drive inserted in the computer.
EXAMPLE3DESCRIPTION Boot into the floppy disk inserted in the comuter.
+9
View File
@@ -0,0 +1,9 @@
COMMAND cat
USAGE [FILES]...
DESCRIPTION Concatenates and prints a file.
ARG1 FILES
ARG1DESCRIPTION Specifies the paths to the files to print.
EXAMPLE1 cat /init.lua
EXAMPLE2 cat help.lua cat.lua
EXAMPLE1DESCRIPTION Concatenates and prints init.lua in the root directory.
EXAMPLE2DESCRIPTION Concatenates and prints help.lua and cat.lua in the current working directory.
+13
View File
@@ -0,0 +1,13 @@
COMMAND cd
USAGE [PATH]
DESCRIPTION Sets the shell working directory.
ARG1 PATH
ARG1DESCRIPTION Specifies the path to set the shell working directory to.
EXAMPLE1 cd /home/
EXAMPLE2 cd halyde
EXAMPLE3 cd ..
EXAMPLE4 ..
EXAMPLE1DESCRIPTION Sets the shell working directory to /home/.
EXAMPLE2DESCRIPTION Sets the shell working directory to a directory named "halyde" in the current working directory.
EXAMPLE3DESCRIPTION Sets the shell working directory back one directory.
EXAMPLE4DESCRIPTION Equivalent of "cd ..".
+4
View File
@@ -0,0 +1,4 @@
COMMAND clear
DESCRIPTION Clears the screen.
EXAMPLE1 clear
EXAMPLE1DESCRIPTION Clears the screen.
+11
View File
@@ -0,0 +1,11 @@
COMMAND cp
USAGE [SOURCES]... [DESTINATION]
DESCRIPTION Copy files and directories.
ARG1 SOURCES
ARG2 DESTINATION
ARG1DESCRIPTION Specifies the files and directories to be copied.
ARG2DESCRIPTION Specifies the path or a directory to copy to.
EXAMPLE1 cp /home/a.txt /b.txt
EXAMPLE2 cp c.lua /halyde/apps .
EXAMPLE1DESCRIPTION Copies the file at /home/a.txt to /b.txt.
EXAMPLE2DESCRIPTION Copies c.lua and /halyde/apps to the shell working directory.
+28
View File
@@ -0,0 +1,28 @@
All default Halyde shell commands:
argentum Uses the Argentum package manager.
boot Boots to another OS.
cat Concatenates and prints a file.
cd Changes directory.
clear Clears the screen.
cp Copies a file.
download Downloads a file from the internet.
echo Prints a message.
edit Opens the text editor.
fetch Displays system information.
help Shows this.
label Labels storage devices and EEPROMS.
ls Lists files.
lscor Lists coroutines.
lua Starts the Lua shell.
mkdir Makes a directory.
mv Moves/renames a file.
reboot Reboots the computer.
resolution Sets the resolution.
rm Deletes a file.
shutdown Shuts down the computer.
You can get additional information on any app or command by running:
help [COMMAND]
In the help documentation, an asterisk (*) next to an argument means it is optional.
This is excluding flags, which are all optional.
+7
View File
@@ -0,0 +1,7 @@
COMMAND download
USAGE [URL]
DESCRIPTION Downloads a file from the internet.
ARG1 URL
ARG1DESCRIPTION Specifies the URL from which to download the file from.
EXAMPLE1 download https://github.com/
EXAMPLE1DESCRIPTION Downloads github.com.
+9
View File
@@ -0,0 +1,9 @@
COMMAND echo
USAGE [TEXT]...
DESCRIPTION Concatenates and prints text to the standard output.
ARG1 TEXT
ARG1DESCRIPTION Text to print.
EXAMPLE1 echo test
EXAMPLE2 echo Hello World!
EXAMPLE1DESCRIPTION Prints "test" to the standard output.
EXAMPLE2DESCRIPTION Prints "Hello World!" to the standard output.
+9
View File
@@ -0,0 +1,9 @@
COMMAND edit
USAGE [PATH]
DESCRIPTION Opens a file with the text editor, or a new blank file if not specified.
ARG1 PATH
ARG1DESCRIPTION Specifies the file to be opened.
EXAMPLE1 edit
EXAMPLE2 edit /LICENSE
EXAMPLE1DESCRIPTION Opens a new blank file in the text editor.
EXAMPLE2DESCRIPTION Opens /LICENSE in the text editor.
+4
View File
@@ -0,0 +1,4 @@
COMMAND fetch
DESCRIPTION Displays system information including OS version, Lua version, memory, etc.
EXAMPLE1 fetch
EXAMPLE1DESCRIPTION Displays system information.
+9
View File
@@ -0,0 +1,9 @@
COMMAND help
USAGE [COMMAND]
DESCRIPTION Displays info on the command specified, or a list of commands if one is not specified.
ARG1 COMMAND
ARG1DESCRIPTION Command to display information on.
EXAMPLE1 help
EXAMPLE2 help cp
EXAMPLE1DESCRIPTION Displays a list of all default commands available.
EXAMPLE2DESCRIPTION Displays information about the cp command.
+24
View File
@@ -0,0 +1,24 @@
COMMAND label
USAGE [ADDRESS] [LABEL]
DESCRIPTION Get or set a label of a component that supports labelling.
ARG1 ADDRESS
ARG1SUB1 eeprom
ARG1SUB2 halyde
ARG1SUB3 slotN
ARG1SUB4 #N
ARG2 LABEL
ARG1DESCRIPTION The component to use for getting or setting the label.
ARG1SUB1DESCRIPTION The computer's EEPROM.
ARG1SUB2DESCRIPTION The drive where the Halyde installation resides in.
ARG1SUB3DESCRIPTION The slot number of the drive, in top-to-bottom order (range 7-9)
ARG1SUB4DESCRIPTION The slot number of the drive, in drive space (range 1-3)
ARG1SUB5DESCRIPTION The ID of the component, abbreviated. Must have three or more characters.
ARG2DESCRIPTION The label to set the component to. If not found, the current label will be printed out.
EXAMPLE1 label #3
EXAMPLE2 label eeprom
EXAMPLE3 label slot8 Storage
EXAMPLE4 label halyde Halyde
EXAMPLE1DESCRIPTION Get the label of the third drive in the computer.
EXAMPLE2DESCRIPTION Get the label of the EEPROM inserted in the computer.
EXAMPLE3DESCRIPTION Set the drive at slot 8 to have the label "Storage"
EXAMPLE4DESCRIPTION Set the label of the Halyde installation to "Halyde"
+19
View File
@@ -0,0 +1,19 @@
COMMAND log
USAGE [OPERATION] [ARGS]
DESCRIPTION Tool to manage system logs.
ARG1 OPERATION
ARG1SUB1 view [LOG]
ARG1SUB2 list
ARG1SUB3 clear [LOG]
ARG1SUB4 info/warn/error [LOG] [TEXT]
ARG2 ARGS
ARG1DESCRIPTION Operation to do with the system logs.
ARG1SUB1DESCRIPTION View a log file.
ARG1SUB2DESCRIPTION List all logs.
ARG1SUB3DESCRIPTION Clear a log file, or all if none specified.
ARG1SUB4DESCRIPTION Create a log entry for the specified log at the specified log level.
ARG2DESCRIPTION Arguments (specified under OPERATION)
EXAMPLE1 log view example
EXAMPLE2 log list
EXAMPLE3 log clear example
EXAMPLE4 log info example This is an example.
+11
View File
@@ -0,0 +1,11 @@
COMMAND ls
USAGE [PATH]...
DESCRIPTION Lists all files and directories in the specified path, or in the shell working directory if the path isn't specified. Directories are shown in yellow, executable files are shown in green, and other files are shown in white.
ARG1 PATH
ARG1DESCRIPTION Path to the directories to list files and subdirectories from.
EXAMPLE1 ls
EXAMPLE2 ls /halyde
EXAMPLE3 ls apps
EXAMPLE1DESCRIPTION Lists all files and directories from the current shell working directory.
EXAMPLE2DESCRIPTION Lists all files and directories from /halyde.
EXAMPLE3DESCRIPTION Lists all files and directories from the apps directory in the shell working directory.
+4
View File
@@ -0,0 +1,4 @@
COMMAND lscor
DESCRIPTION Lists every active coroutine by ID and name.
EXAMPLE1 lscor
EXAMPLE1DESCRIPTION Lists every active coroutine by ID and name.
+37
View File
@@ -0,0 +1,37 @@
COMMAND lsdrv
USAGE [FLAGS]
DESCRIPTION Shows all drives that are inserted into the computer.
ARG1 FLAGS
ARG1SUB1 -a, --all
ARG1SUB2 -o, --output [COLS]
ARG1SUB3 -s, --show [EXPR]
ARG1SUB4 -S, --sort [EXPR]
ARG1SUB5 EXPR
ARG2 PACKAGES
ARG1DESCRIPTION Specifies extra options when executing the command.
ARG1SUB1DESCRIPTION Shows every column and every component. Acts the same as '-o all -s all'. Possible columns are: "slot", "capacity", "managed", "readOnly", "id", "mount", "bootable", and "label". If the list of columns start with a "+", the default columns will appear first. Default columns are slots, capacity, the entire ID, the mount point, and the drive label.
ARG1SUB2DESCRIPTION Specifies the columns to output in the output table.
ARG1SUB3DESCRIPTION Only list drives when the expression returns 'true'.
ARG1SUB4DESCRIPTION Sort the output by an expression that returns a number. The higher the number, the lower the drive is displayed, and vice-versa.
ARG1SUB5DESCRIPTION An expression in Lua, for filtering or sorting output. If this expression contains spaces, make sure to put quotation marks on them!
ARG1SUB6DESCRIPTION Built-in variables are: "component", "computer", "type", "id", "readonly", "capacity", "managed", "eeprom", "halyde", "tmp", "proxy", "slot", and "all" (true).
EXAMPLE1 lsdrv
EXAMPLE2 lsdrv -a
EXAMPLE3 lsdrv -o +bootable
EXAMPLE4 lsdrv -o slot,label -s halyde
EXAMPLE5 lsdrv -o mount,capacity,label -s "not halyde"
EXAMPLE6 lsdrv -s type=='filesystem'
EXAMPLE7 lsdrv -s slot==1
EXAMPLE8 lsdrv -S capacity
EXAMPLE9 lsdrv -S -capacity
EXAMPLE10 lsdrv -o +managed -S managed
EXAMPLE1DESCRIPTION Show regular drives, with the default columns.
EXAMPLE2DESCRIPTION Show all storage components, with every column.
EXAMPLE3DESCRIPTION Show drives, with an added "bootable" category.
EXAMPLE4DESCRIPTION Show the slot and the label of the drive where Halyde is installed.
EXAMPLE5DESCRIPTION Show the mount points, capacities and labels of all drives other than Halyde.
EXAMPLE6DESCRIPTION Only show managed drives.
EXAMPLE7DESCRIPTION Show all drives that aren't physical (Virtual components, tmpfs)
EXAMPLE8DESCRIPTION Sort the drives by capacity, in ascending order.
EXAMPLE9DESCRIPTION Sort the drives by capacity, in descending order.
EXAMPLE10DESCRIPTION Show managed drives first, then unmanaged drives second, with an extra "managed" column.
+4
View File
@@ -0,0 +1,4 @@
COMMAND lua
DESCRIPTION Starts the Lua shell, where you can type commands to interpret them in real time.
EXAMPLE1 lua
EXAMPLE1DESCRIPTION Starts the Lua shell.
+2
View File
@@ -0,0 +1,2 @@
COMMAND maindrv
DESCRIPTION Shows the entire ID of the drive where Halyde is installed to.
+7
View File
@@ -0,0 +1,7 @@
COMMAND mkdir
USAGE [PATH]...
DESCRIPTION Makes a directory.
ARG1 PATH
ARG1DESCRIPTION Specifies the path to create the directory in.
EXAMPLE1 mkdir a
EXAMPLE1DESCRIPTION Creates a directory named a in the current shell working directory.
+11
View File
@@ -0,0 +1,11 @@
COMMAND mv
USAGE [SOURCE].. [DESTINATION]
DESCRIPTION Moves/renames a file.
ARG1 SOURCE
ARG2 DESTINATION
ARG1DESCRIPTION Specifies the files and directories to be moved.
ARG2DESCRIPTION Specifies the path or a directory to copy to.
EXAMPLE1 mv /home/a.txt /b.txt
EXAMPLE2 mv ../c.lua /halyde/apps .
EXAMPLE1DESCRIPTION Moves the file at /home/a.txt to /b.txt.
EXAMPLE2DESCRIPTION Moves c.lua from a subdirectory and /halyde/apps to the shell working directory.
+4
View File
@@ -0,0 +1,4 @@
COMMAND reboot
DESCRIPTION Reboots the computer.
EXAMPLE1 reboot
EXAMPLE1DESCRIPTION Reboots the computer.
+11
View File
@@ -0,0 +1,11 @@
COMMAND res
USAGE [FLAGS]
DESCRIPTION Gets or sets the current resolution.
ARG1 FLAGS
ARG1SUB1 [no flags]
ARG1SUB2 -x [number]
ARG1SUB3 -y [number]
ARG1DESCRIPTION Specifies extra options when executing the command.
ARG1SUB1DESCRIPTION Displays the current and maximum resolution.
ARG1SUB2DESCRIPTION Displays the current and maximum resolution on the x-axis. A new x-axis resolution can be specified as a number.
ARG1SUB3DESCRIPTION Displays the current and maximum resolution on the y-axis. A new y-axis resolution can be specified as a number.
+7
View File
@@ -0,0 +1,7 @@
COMMAND rm
USAGE [PATH]...
DESCRIPTION Removes files and directories.
ARG1 PATH
ARG1DESCRIPTION Specifies the files and directories to be moved/renamed.
EXAMPLE1 rm a.txt
EXAMPLE1DESCRIPTION Removes a.txt in the current shell working directory.
+3
View File
@@ -0,0 +1,3 @@
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDD
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFF
+4
View File
@@ -0,0 +1,4 @@
COMMAND shutdown
DESCRIPTION Shuts down the computer.
EXAMPLE1 shutdown
EXAMPLE1DESCRIPTION Shuts down the computer.
+5
View File
@@ -0,0 +1,5 @@
COMMAND touch
USAGE [FILE]...
DESCRIPTION Create an empty file.
ARG1 FILE
ARG1DESCRIPTION The path of the files to create.
+92
View File
@@ -0,0 +1,92 @@
local component = require("component")
local computer = require("computer")
local args = {...}
if not args then return print("\x1b[91mCannot get arguments.\x1b[0m") end
if not args[1] then
return require("shell").run("help label")
end
local inputID = args[1]
local comp
local function componentFromSlot(slotNum)
if slotNum>=5 and slotNum<=8 then slotNum=slotNum-1 end
for i,v in component.list() do
if component.slot(i)==slotNum then
comp=component.proxy(i)
end
end
end
if inputID=="eeprom" then
comp = component.eeprom
elseif inputID=="halyde" then
comp = component.proxy(computer.getBootAddress())
elseif inputID:sub(1,4)=="slot" and tonumber(inputID:sub(5)) then
local slotNum = tonumber(inputID:sub(5))-1
componentFromSlot(slotNum)
elseif inputID:sub(1,1)=="#" and tonumber(inputID:sub(2)) then
local slotNum = tonumber(inputID:sub(2))+5
componentFromSlot(slotNum)
elseif #inputID>=3 then
local fullID = component.get(inputID)
if not fullID then return print("\x1b[91mCould not find entire component ID from \""..inputID.."\".\x1b[0m") end
comp = component.proxy(fullID)
else
print("\x1b[91mAddress must have atleast 3 characters\x1b[0m")
return require("shell").run("help label")
end
if not comp then
return print("\x1b[91mCould not find component from \""..inputID.."\".\x1b[0m")
end
local compID = comp.address
local function formatID(id)
return id:sub(1,8).."\x1b[37m"..id:sub(9).."\x1b[0m"
end
local function unsupported(act)
print("This \x1b[92m"..(comp.type or "unknown").."\x1b[0m component doesn't support "..act.." labels.\nID: "..formatID(compID).."\x1b[0m")
end
local function compError(act,reason)
print("\x1b[91mAn error occured while "..act.." the label of this component.\nComponent: "..(compID or "unknown id").." ("..((comp or {}).type or "unknown type")..")\n\n"..reason.."\x1b[0m")
end
local function formatLabel(label)
local res = "No label defined"
if label then res="\""..label.."\"" end
return res
end
if type(args[2])~="string" then
if not comp.getLabel then
return unsupported("getting")
end
local label
local success,reason = pcall(function()
label = comp.getLabel()
end)
if success then
print("Label of "..formatID(compID)..((comp.type and comp.type~="filesystem") and " ("..comp.type..")" or "")..":\n \x1b[92m"..formatLabel(label).."\x1b[0m")
else
compError("getting",reason)
end
else
if not comp.setLabel then
return unsupported("setting")
end
local newLabel = ""
for i=2,#args do
newLabel=newLabel..tostring(args[i])
if i<#args then newLabel=newLabel.." " end
end
local label
local success,reason = pcall(function()
label = comp.setLabel(newLabel)
end)
if success then
print("Successfully set label of "..formatID(compID)..(comp.type and " ("..comp.type..")" or "").." to:\n \x1b[92m"..formatLabel(label).."\x1b[0m")
else
compError("setting",reason)
end
end
+110
View File
@@ -0,0 +1,110 @@
local log = require("log")
local shell = require("shell")
local fs = require("filesystem")
local args = {...}
if #args == 0 then
shell.run("help log")
return
end
local function viewlog(logname)
local logpath = "/halyde/logs/" .. logname .. ".log"
if not fs.exists(logpath) then
print("Log not found.")
return
end
local handle = fs.open(logpath)
local entry = ""
local byte
while true do
byte = handle:read(1)
if not byte then return end
if string.byte(byte) == 0x0a then --check for newline
if string.byte(string.sub(entry, -1, -1)) == 0x0d then --failsafe in case line endings are CRLF
entry = string.sub(entry, 1, -2)
else
entry = string.sub(entry, 1, -1)
end
if entry:sub(1, 4) == "WARN" then
print("\x1b[93m" .. entry .. "\x1b[0m")
elseif entry:sub(1, 5) == "ERROR" then
print("\x1b[91m" .. entry .. "\x1b[0m")
else
print(entry)
end
entry = ""
else
entry = entry .. byte
end
end
end
local function listlogs()
local files = fs.list("/halyde/logs")
local logs = {}
local j = 1
for i in ipairs(files) do
if not(string.sub(files[i], -1, -1) == "/") and string.sub(files[i], -4, -1) == ".log" then
logs[j] = string.sub(files[i], 1, -5)
j = j + 1
end
end
return logs
end
local function listlogs2()
local logs = listlogs()
print("Found \x1b[93m" .. #logs .. "\x1b[0m logs.")
for i in ipairs(logs) do
if i == #logs then
print("\x1b[93m└ \x1b[0m" .. logs[i] .. "\x1b[90m.log\x1b[0m")
else
print("\x1b[93m├ \x1b[0m" .. logs[i] .. "\x1b[90m.log\x1b[0m")
end
end
end
local function clearlog(logname)
if logname then
local logpath = "/halyde/logs/" .. logname .. ".log"
if not fs.exists(logpath) then
print("Log file not found.")
return
end
local success, err = fs.remove(logpath)
if not success then
print("Failed to remove log file: " .. err)
return
end
else
local logs = listlogs()
local j
for i in ipairs(logs) do
local success, err = fs.remove("/halyde/logs/" .. logs[i] .. ".log")
if not success then
print("Failed to remove log " .. logs[i] .. ": " .. err)
print("Removed" .. i - 1 .. "logs.")
return
end
j = i
end
print("Removed " .. j .. " log(s) successfully.")
end
end
if args[1] == "view" then
viewlog(args[2])
elseif args[1] == "list" then
listlogs2()
elseif args[1] == "clear" then
clearlog(args[2])
elseif args[1] == "info" or args[1] == "warn" or args[1] == "error" then
local loglevel = args[1]
local logname = args[2]
local logtext = args[3]
for i = 4, #args do
logtext = logtext .. " " .. args[i]
end
log[logname][loglevel](logtext)
end
+58
View File
@@ -0,0 +1,58 @@
local fs = require("filesystem")
local shell = require("shell")
local function formatSize(size, isDir)
if isDir then return "[DIR]" end
if size >= 1024^3 then return string.format("%.1fGiB", size / 1024^3) end
if size >= 1024^2 then return string.format("%.1fMiB", size / 1024^2) end
if size >= 1024 then return string.format("%.1fKiB", size / 1024) end
return size.."B"
end
local function getFileColor(name, isDir)
if isDir then return "\27[93m" end
if name:match("%.lua$") then return "\27[92m" end
return "\27[0m"
end
local args = {...}
if not args[1] then
args = {require("shell").getWorkingDirectory()}
end
for _, path in pairs(args) do
path = shell.resolvePath(path)
local files = fs.list(path)
if not files then
terminal.write("\27[91mError: " .. path .. ": No such file or directory\27[0m\n")
goto continue
end
local fileList = {}
for _, file in pairs(files) do
local isDir = file:sub(-1) == "/"
local name = isDir and file:sub(1, -2) or file
local size = isDir and 0 or fs.size(fs.concat(path, file))
table.insert(fileList, {name = name, isDir = isDir, size = size})
end
-- directories first
-- then files
table.sort(fileList, function(a, b)
if a.isDir ~= b.isDir then return a.isDir end
return a.name < b.name
end)
local maxSizeLen = 0
for _, item in ipairs(fileList) do
maxSizeLen = math.max(maxSizeLen, #formatSize(item.size, item.isDir))
end
terminal.write(path.."\n")
for _, item in ipairs(fileList) do
local sizeStr = formatSize(item.size, item.isDir)
sizeStr = string.rep(" ", maxSizeLen - #sizeStr) .. sizeStr
local color = getFileColor(item.name, item.isDir)
terminal.write(string.format("%s %s%s\27[0m\n", sizeStr, color, item.name))
end
::continue::
end
+333
View File
@@ -0,0 +1,333 @@
local serialize = require("serialize")
local component = require("component")
local computer = require("computer")
local unicode = require("unicode")
local width,height = component.gpu.getResolution()
local args = {...}
local showAll = not not (table.find(args,"-a") or table.find(args,"--all"))
local tablePos = {}
local tableOut = {}
local headers = {
["slot"]="SLOT",
["capacity"]="CAPACITY",
["managed"]="MANAGED",
["readOnly"]="READ-ONLY",
["id"]="FULL COMPONENT ID",
["mount"]="MOUNT",
["bootable"]="BOOTABLE",
["label"]="LABEL"
}
local headerAlign = {
["slot"]=false,
["capacity"]=true,
["managed"]=false,
["readOnly"]=false,
["id"]=false,
["mount"]=false,
["bootable"]=false,
["label"]=false
}
local function addHeader(id)
table.insert(tableOut,{headerAlign[id],headers[id]})
tablePos[id]=#tableOut
end
for i,v in pairs(tablePos) do print(i,v) end
local function everyHeader()
addHeader("slot")
addHeader("capacity")
addHeader("managed")
addHeader("readOnly")
addHeader("id")
addHeader("mount")
addHeader("bootable")
addHeader("label")
end
local function defaultHeaders()
if width>100 then addHeader("slot") end
addHeader("capacity")
if width>80 then addHeader("id") end
addHeader("mount")
addHeader("label")
end
local function invalidArgSyntax(err)
print(err)
return shell.run("help lsdrv")
end
local outArgIdx = table.find(args,"-o") or table.find(args,"--output")
if showAll then
everyHeader()
elseif outArgIdx then
table.remove(args,outArgIdx)
local arg = table.remove(args,outArgIdx)
if not arg then return invalidArgSyntax("Argument -o must have a value") end
if arg=="all" then
everyHeader()
else
if arg:sub(1,1)=="+" then
defaultHeaders()
end
for word in string.gmatch(arg:sub(2),"([^,]+)") do
if headers[word] then
addHeader(word)
else
print("\x1b[93mCategory \""..word.."\" doesn't exist\x1b[0m")
end
end
end
else
defaultHeaders()
end
local function headerUsed(id)
return not not tablePos[id]
end
local bibytes = not (table.find(args,"-H") or table.find(args,"--si"))
local function formatSize(mem)
local units = bibytes and {"B","KiB","MiB","GiB"} or {"B","KB","MB","GB"}
local unit = 1
local unitStep = bibytes and 1024 or 1000
while mem > unitStep and units[unit] do
unit = unit + 1
mem = mem/unitStep
end
local memStr = tostring(mem):sub(1,5)
if unicode.sub(memStr,-2)==".0" then
memStr=unicode.sub(memStr,1,-3)
end
return memStr.." "..units[unit]
end
local function fileExists(proxy,path)
return proxy.exists and proxy.isDirectory and proxy.exists(path) and not proxy.isDirectory(path)
end
local function getBootCode(proxy)
local sector1 = proxy.readSector(1)
for i = 1,#sector1 do
if sector1:sub(i,i)=="\0" then
sector1 = sector1:sub(1,i-1)
break
end
end
return sector1
end
local function isBootable(proxy,type)
if type=="drive" then
return not not load(getBootCode(proxy))
elseif type=="filesystem" then
return not not (fileExists(proxy,"/init.lua") or fileExists(proxy,"/OS.lua"))
end
end
local function formatBoolean(bool)
return ({[true]="Yes",[false]="No"})[bool]
end
local function handleComponent(id,type)
if not id then return end
local proxy = component.proxy(id)
if not proxy then return end
-- local out = {}
-- for i=1,#tableOut do table.insert(out,"unknown") end
local slot,capacity,managed,readOnly,mount,bootable,label
local virtual,vproc = component.virtual.check(id)
local cslot = component.slot(id)
if virtual then
if vproc and vproc.name then
slot="Virtual ("..vproc.name..")"
else
slot="Virtual (unknown)"
end
elseif cslot==-1 then
slot="External"
elseif cslot==9 then
slot="EEPROM"
elseif cslot==5 or cslot==6 then
slot="HDD #"..(cslot-4)
elseif cslot==7 then
slot="Floppy"
else
slot="#"..(cslot+2)
end
managed="No"
readOnly="No"
if type=="drive" then
capacity=formatSize(proxy.getCapacity())
mount="/special/drive/"..id:sub(1,3)
elseif type=="filesystem" then
managed="Yes"
if proxy.spaceTotal then
capacity=formatSize(proxy.spaceTotal())
end
if not proxy.isReadOnly or proxy.isReadOnly() then
readOnly="Yes"
end
mount="/mnt/"..id:sub(1,3)
if computer.getBootAddress()==id then mount="/" end
if computer.tmpAddress()==id then
mount="/tmp"
slot="Temporary"
end
elseif type=="eeprom" then
capacity=formatSize(proxy.getSize()+proxy.getDataSize())
mount="/special/eeprom/"
end
if headerUsed("bootable") then
bootable = formatBoolean(isBootable(proxy,type))
end
if proxy.getLabel then
local clabel = proxy.getLabel()
label=clabel and serialize(clabel) or "None"
else
label="Unsupported"
end
local function insertElement(i,v)
if not tablePos[i] then return end
table.insert(tableOut[tablePos[i]],v or "unknown")
end
insertElement("slot",slot)
insertElement("capacity",capacity)
insertElement("managed",managed)
insertElement("readOnly",readOnly)
insertElement("id",id)
insertElement("mount",mount)
insertElement("bootable",bootable)
insertElement("label",label)
end
local function filter(tbl,test)
local i=1
while i<=#tbl do
if not test(tbl[i]) then
table.remove(tbl,i)
i=i-1
end
i=i+1
end
end
local function luaExpr(arg)
local func,err = load("local component,computer,type,id,readonly,capacity=... local managed,eeprom,halyde,tmp,proxy,slot,all=type==\"drive\",type==\"eeprom\",id==\""..computer.getBootAddress().."\",id==\""..computer.tmpAddress().."\",component.proxy(id),component.slot(id)+2,true return "..arg)
if func then
return function(comp)
local readOnly,capacity=false,nil
if comp[2]=="filesystem" then
readOnly=component.invoke(comp[1],"isReadOnly")
capacity=component.invoke(comp[1],"spaceTotal")
elseif comp[2]=="drive" then
capacity=component.invoke(comp[1],"getCapacity")
elseif comp[2]=="eeprom" then
capacity=component.invoke(comp[1],"getSize")+component.invoke(comp[1],"getDataSize")
end
return func(component,computer,comp[2],comp[1],readOnly,capacity)
end
else
return nil,err
end
end
local function boolToNum(val)
if type(val)=="boolean" then
if val then
return 1
else
return 0
end
end
return val
end
local comps = {}
for i,v in component.list("filesystem") do table.insert(comps,{i,v}) end
for i,v in component.list("drive") do table.insert(comps,{i,v}) end
for i,v in component.list("eeprom") do table.insert(comps,{i,v}) end
if not showAll then
local showArgIdx = table.find(args,"-s") or table.find(args,"--show")
if showArgIdx then
table.remove(args,showArgIdx)
local arg = table.remove(args,showArgIdx)
if not arg then return invalidArgSyntax("Argument -s must have a value") end
local func,err = luaExpr(arg)
if func then
filter(comps,func)
else
return print("\x1b[91mInvalid component filter:\n\n"..tostring(err).."\x1b[0m")
end
else
filter(comps,function(comp)
return comp[2]~="eeprom" and comp[1]~=computer.tmpAddress()
end)
end
end
local sortArgIdx = table.find(args,"-S") or table.find(args,"--sort")
if sortArgIdx then
table.remove(args,sortArgIdx)
local arg = table.remove(args,sortArgIdx)
if not arg then return invalidArgSyntax("Argument -S must have a value") end
local func,err = luaExpr(arg)
if func then
table.sort(comps,function(a,b)
return (boolToNum(func(a)) or 0)<(boolToNum(func(b)) or 0)
end)
else
return print("\x1b[91mInvalid sort expression:\n\n"..tostring(err).."\x1b[0m")
end
else
table.sort(comps,function(a,b)
return a[1]<b[1]
end)
end
if #comps>0 then
for i,v in ipairs(comps) do handleComponent(v[1],v[2]) end
else
return print("Could not find storage components for this filter.")
end
local function renderTableOutput()
local lines = {}
for i=1,#tableOut[1]-1 do
table.insert(lines,"")
end
for i=1,#tableOut do
local length = 1
for j=1,#lines do
tableOut[i][j+1]=tableOut[i][j+1] or "[nil]"
length = math.max(length,unicode.wlen(tableOut[i][j+1]))
end
for j=1,#lines do
if tableOut[i][1] then
lines[j]=lines[j]..string.rep(" ",length-unicode.wlen(tableOut[i][j+1]))..tableOut[i][j+1]
elseif i<#tableOut then
lines[j]=lines[j]..tableOut[i][j+1]..string.rep(" ",length-unicode.wlen(tableOut[i][j+1]))
else
lines[j]=lines[j]..tableOut[i][j+1]
end
if i<#tableOut then lines[j]=lines[j].." " end
end
end
print(lines[1].."\n")
for i=2,#lines do
print(lines[i])
end
end
renderTableOutput()
+8
View File
@@ -0,0 +1,8 @@
local tasks = tsched.getTasks()
print("\27[93m"..tostring(#tasks).."\27[0m tasks active")
for i=1, #tasks do
local pipeChar = ""
if i==#tasks then pipeChar = "" end
local task = tasks[i]
print("\27[93m"..pipeChar..(task.id or i).."\27[0m - "..task.name.."\27[37m "..table.concat(task.args or {}," ").." \27[0m")
end
+83
View File
@@ -0,0 +1,83 @@
-- terminal.readHistory["lua"] = {""}
local fs = require("filesystem")
local computer = require("computer")
local log = require("log")
local bootTime = computer.uptime()
local libList = fs.list("/lib/")
local failed = false
for _, lib in pairs(libList) do
local status, err = xpcall(function()
if lib:match("(.+)%.lua") then
local name = lib:match("(.+)%.lua")
_G[name] = require(name)
end
end, function(errMsg)
return errMsg .. "\n\n" .. debug.traceback()
end)
if not status then
local firstLine = tostring(err):match("^[^\n]*")
print(
string.format(
"\x1b[91mLibrary %s has failed loading:\n │ %s\x1b[0m",
lib:match("(.+)%.lua") or lib,
firstLine or "unknown error"
)
)
log.lua.error(
string.format(
'The library located at "%s" has failed loading:\n%s',
lib,
type(err) ~= "nil" and tostring(err) or "unknown error"
)
)
failed = true
end
end
if failed then
print(
string.format(
'\x1b[93mOne or more libraries failed to load. For more information, check the log entries located at "%s".\x1b[0m',
tostring(log.lua.logpath or "[unknown]")
)
)
end
print(string.format("\27[37mLoaded %d libraries in %.2f seconds\27[0m", #libList, computer.uptime() - bootTime))
print(string.format("\27[44m%s\27[0m shell", _VERSION))
print('Type "exit" to exit.')
while true do
local command = terminal.read({readHistoryType = "lua", prefix = "\27[44mlua>\27[0m "})
if command == "exit" then
coroutine.yield()
return
elseif command ~= "" then
local function runCommand()
local func, err = load("return " .. command, "=stdin")
local returns = true
if not func then
func, err = load(command, "=stdin")
returns = false
end
if not func then
return print("\x1b[91msyntax error: " .. (err or "unknown error") .. "\x1b[0m")
end
local res = { func() }
if returns then
if res and type(res[1]) ~= "nil" then
print(table.unpack(res))
elseif res and type(res[2]) ~= "nil" then
print("nil", table.unpack(res))
end
end
end
local result, reason = xpcall(runCommand, function(errMsg)
return errMsg .. "\n\n" .. debug.traceback()
end)
if not result then
print("\27[91m" .. reason .. "\27[0m")
end
end
end
+10
View File
@@ -0,0 +1,10 @@
-- WTF: What the fuck is this for?? Why does it print the computer library???
-- TODO: Integrate this into lsdrv.
local computer = require("computer")
if type(computer)~="table" then
return print("\x1b[91mComputer library returned '"..type(computer).."' type\x1b[0m")
end
local address = computer.getBootAddress()
print(address)
+23
View File
@@ -0,0 +1,23 @@
local fs = require("filesystem")
local shell = require("shell")
local args = {...}
if not args[1] then
return shell.run("help mkdir")
end
for _, directory in pairs(args) do
directory = shell.resolvePath(directory)
if fs.exists(directory) then
terminal.write("\27[91mError: " .. directory ..": An object already exists\27[0m\n")
goto continue
end
local what, err = fs.makeDirectory(directory)
if err ~= nil then
terminal.write("\27[91mError: " .. err .. "\27[0m\n")
goto continue
end
::continue::
end
+70
View File
@@ -0,0 +1,70 @@
local fs = require("filesystem")
local shell = require("shell")
local args = {...}
if not args[1] then
return shell.run("help mv")
end
if not args[2] then
terminal.write("\27[91mError: No destination\27[0m\n")
return
end
local dest = shell.resolvePath(args[#args])
if fs.isFile(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: Destination is not a directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
if fs.isDirectory(src) then
terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n")
return
end
fs.rename(src, dest)
elseif fs.isDirectory(dest) then
for i = 1, #args - 1 do
local src = shell.resolvePath(args[i])
if src == dest then
terminal.write("\27[91mError: Source and destination are the same\27[0m\n")
goto continue
end
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
goto continue
end
fs.rename(src, fs.concat(dest, fs.basename(src)))
::continue::
end
elseif not fs.exists(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: " .. dest .. ": No such file or directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
local destp = fs.parent(dest)
if not fs.exists(destp) then
terminal.write("\27[91mError: " .. destp .. ": No such file or directory\27[0m\n")
return
end
if not fs.isDirectory(destp) then
terminal.write("\27[91mError: " .. destp .. ": Not a directory\27[0m\n")
return
end
fs.rename(src, dest)
else
terminal.write("\27[91mUnknown error\27[0m\n")
end
+222
View File
@@ -0,0 +1,222 @@
-- TODO: make it somehow not depend on ESC s and ESC u
local profiler = require("profiler")
local PAL = {
{ fg = "\27[31m", bg = "\27[41m" },
{ fg = "\27[32m", bg = "\27[42m" },
{ fg = "\27[33m", bg = "\27[43m" },
{ fg = "\27[34m", bg = "\27[44m" },
{ fg = "\27[35m", bg = "\27[45m" },
{ fg = "\27[36m", bg = "\27[46m" },
{ fg = "\27[37m", bg = "\27[47m" }
}
local function pal(i) return PAL[((i - 1) % #PAL) + 1] end
local results = profiler.results()
if not results then
print("No profiling data")
return
end
local radius
local W = terminal.getResolution()
if (W == 160) then radius = 20
elseif (W == 80) then radius = 6
else radius = 0; PAL = {{fg = "", bg = ""}} end
local MIN_PC = math.min(3, 100 / (radius * 2))
local total = 0
for _, r in ipairs(results) do total = total + r.time end
local main = {}
local other = {}
local ot = 0
local on_ = 0
for _, r in ipairs(results) do
if r.time / total * 100 >= MIN_PC then
table.insert(main, r)
else
table.insert(other, r)
ot = ot + r.time;
on_ = on_ + 1
end
end
if on_ then
table.insert(main, { label = "other (" .. on_ .. ")", time = ot })
end
local START = -math.pi / 2
local slices = {}
local cur = START
for i, m in ipairs(main) do
local sw = m.time / total * 2 * math.pi
slices[i] = {
label = m.label,
time = m.time,
pc = ("%.1f%%"):format(m.time / total * 100),
color = pal(i),
sa = cur,
sw = sw
}
cur = cur + sw
end
if on_ then slices[#slices].color={fg = "\27[30m", bg = "\27[40m"} end
local ASP = 2
local cx = radius * ASP
local cy = radius
local function slice_at(sx, sy)
local dy = sy - cy
local dx = (sx - cx) / ASP
if dx * dx + dy * dy > radius * radius then return nil end
local a = math.atan2(dy, dx)
if a < START then a = a + 2 * math.pi end
for i, sl in ipairs(slices) do
if a >= sl.sa and a < sl.sa + sl.sw then return i end
end
return #slices
end
local SUB = {
{ dx = -0.25, dy = -0.375 },
{ dx = -0.25, dy = -0.125 },
{ dx = -0.25, dy = 0.125 },
{ dx = 0.25, dy = -0.375 },
{ dx = 0.25, dy = -0.125 },
{ dx = 0.25, dy = 0.125 },
{ dx = -0.25, dy = 0.375 },
{ dx = 0.25, dy = 0.375 },
}
for y = 0, radius * 2 do
local lx, rx = math.ceil(cx - radius * ASP), math.floor(cx + radius * ASP)
terminal.write(("\27[%dC"):format(lx))
for x = lx, rx do
local c = {}
local n = 0
for k = 1, 8 do
local s = slice_at(x + SUB[k].dx, y + SUB[k].dy)
c[k] = s
if s then n = n + 1 end
end
if n == 0 then
terminal.write("\27[0m ")
else
local counts = {}
local order = {}
for k = 1, 8 do
if c[k] then
local key = c[k]
if not counts[key] then
counts[key] = 0
table.insert(order, key)
end
counts[key] = counts[key] + 1
end
end
table.sort(order, function(a, b) return counts[a] > counts[b] end)
local dom = order[1]
local sub = order[2]
if n == 8 then
terminal.write(slices[dom].color.bg)
if sub == nil then
terminal.write(" ")
else
local mask = 0
for k = 1, 8 do
if c[k] == sub then mask = mask + (1 << (k - 1)) end
end
terminal.write(slices[sub].color.fg)
terminal.write(utf8.char(0x2800 + mask))
end
else
local mask = 0
for k = 1, 8 do
if c[k] == dom then mask = mask + (1 << (k - 1)) end
end
terminal.write("\27[0m")
terminal.write(slices[dom].color.fg)
terminal.write(utf8.char(0x2800 + mask))
end
end
end
terminal.write('\n')
end
terminal.write("\27[" .. radius * 2 + 1 .. "A\27[s")
local function draw_text(col, row, s)
terminal.write("\27[u\27[" .. col .. "C\27[" .. row - 1 .. "B" .. s)
end
terminal.write("\27[0m")
if radius == 0 then goto tier1gpu end
for _, sl in ipairs(slices) do
local y = math.floor(cy + radius * 0.62 * math.sin(sl.sa + sl.sw / 2) + 0.5)
local function in_slice(x, row)
local a = math.atan2(row - cy, (x - cx) / ASP)
if a < START then
a = a + 2 * math.pi
end
return a >= sl.sa and a < sl.sa + sl.sw
end
local function centered_draw(row, text)
local dy = row - cy
local half_w = math.sqrt(radius * radius - dy * dy) * ASP
local lx, rx = math.ceil(cx - half_w), math.floor(cx + half_w)
while lx <= rx and not in_slice(lx, row) do
lx = lx + 1
end
while rx >= lx and not in_slice(rx, row) do
rx = rx - 1
end
local avail = rx - lx + 1
if avail < 3 then
return
end
local t = #text > avail and text:sub(1, avail - 3) .. "..." or text
local start_x = lx + math.floor((avail - #t) / 2)
draw_text(start_x, row, sl.color.bg .. t)
end
centered_draw(y, sl.label)
centered_draw(y + 1, sl.pc)
end
::tier1gpu::
local max_w = 0
for _, sl in pairs(slices) do
max_w = math.max(#sl.label + 1, max_w)
end
for _, sl in pairs(other) do
max_w = math.max(#sl.label + 3, max_w)
end
local x = radius * 2 * ASP + 2
if radius == 0 then x = 0 end
local cw = W - x - 1
local suffix_w = 18
local max_lbl = math.max(4, cw - suffix_w)
terminal.write("\27[u\27[" .. x .. "C" .. ("\27[0m%" .. max_w .. "s %9s %6s"):format("label", "time", "share"))
terminal.write("\n")
for _, sl in ipairs(slices) do
local lbl = sl.label
if #lbl > max_lbl then
lbl = sl.label:sub(1, max_lbl - 1) .. ""
end
terminal.write("\n\27[" .. x .. "C" .. ("%s%" .. max_w .. "s\27[0m %9.4fs %6s"):format(sl.color.bg, lbl, sl.time, sl.pc))
end
for _, sl in ipairs(other) do
local lbl = sl.label
if #lbl > max_lbl - 2 then
lbl = sl.label:sub(1, max_lbl - 3) .. ""
end
terminal.write("\n\27[" .. x .. "C" .. ("%s%" .. max_w .. "s\27[0m %9.4fs %6s"):format(slices[#slices].color.bg, lbl, sl.time, ("%.1f%%"):format(sl.time / total * 100)))
end
if radius * 2 + 2 > #slices + #other then
terminal.write("\27[" .. radius * 2 + 2 .. "B")
end
terminal.write("\n")
+1
View File
@@ -0,0 +1 @@
require("computer").shutdown(true)
+96
View File
@@ -0,0 +1,96 @@
local component = require("component")
local gpu = component.gpu
local shell = require("shell")
local args = {...}
local maxX, maxY = gpu.maxResolution()
local curX, curY = gpu.getResolution()
local function setRes()
if not(args[1] == "-x" or args[1] == "-y") then
print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return
end
local lastarg = ""
local x, y
for i = 1, 3, 2 do
if args[i] == "-x" then
if lastarg ~= "x" then
x = tonumber(args[i + 1])
lastarg = "x"
else
print("\x1b[91mValue \"x\" was set more than once. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return
end
elseif args[i] == "-y" then
if lastarg ~= "y" then
y = tonumber(args[i + 1])
lastarg = "y"
else
print("\x1b[91mValue \"y\" was set more than once. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return
end
end
end
if x then
if x > maxX then
print("\x1b[91mGPU does not support x higher than " .. maxX .. "\x1b[0m.")
return
end
end
if y then
if y > maxY then
print("\x1b[91mGPU does not support y higher than " .. maxY .. "\x1b[0m.")
return
end
end
if x and not(y) then
gpu.setResolution(x, curY)
print("Successfully set X resolution from \x1b[93m" .. curX .. "\x1b[0m to \x1b[92m" .. x .. "\x1b[0m.")
return
elseif not(x) and y then
gpu.setResolution(curX, y)
print("Successfully set Y resolution from \x1b[93m" .. curY .. "\x1b[0m to \x1b[92m" .. y .. "\x1b[0m.")
return
else
gpu.setResolution(x, y)
print("Successfully set resolution from \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m to \x1b[92m" .. x .. "x" .. y .. "\x1b[0m.")
return
end
end
local function getRes(val)
if val == "x" then
print("Current X resolution: \x1b[93m" .. curX .. "\x1b[0m")
print("Maximum supported X resolution: \x1b[92m" .. maxX .. "\x1b[0m")
elseif val == "y" then
print("Current Y resolution: \x1b[93m" .. curY .. "\x1b[0m")
print("Maximum supported Y resolution: \x1b[92m" .. maxY .. "\x1b[0m")
else
print("Current resolution: \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m")
print("Maximum supported resolution: \x1b[92m" .. maxX .. "x" .. maxY .. "\x1b[0m")
end
end
if #args == 0 then
getRes()
return
end
if not(#args == 1) then
setRes()
return
end
local axis = args[1]
if axis == "-x" then
getRes("x")
elseif axis == "-y" then
getRes("y")
else
print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
end
+17
View File
@@ -0,0 +1,17 @@
local fs = require("filesystem")
local shell = require("shell")
local args = {...}
if not args[1] then
return shell.run("help rm")
end
for _, file in pairs(args) do
file = shell.resolvePath(file)
local result = fs.remove(file)
if result == false then
terminal.write("\27[91mError: cannot delete " .. file .. "\27[0m\n")
end
end
+91
View File
@@ -0,0 +1,91 @@
local raster = require("raster")
raster.init()
--[[for i=4,20 do
raster.set(i,i)
raster.set(i,i+4,0xFF00FF)
end]]
--[[ for x=4,20 do
for y=4,20 do
if (x+y)%2==0 then
raster.set(x,y,0xFF00FF)
end
end
end ]]
local event = require("event")
local x=0
local y=0
local vx=1
local vy=1
local col = 0x808080
local i=0
while event.pull("key_down",0)==nil do
i = i + 1
raster.set(x,y,col)
x = x + vx
y = y + vy
if x>raster.displayWidth then
x=raster.displayWidth
vx = -math.abs(vx)
col = math.random(0,0xFFFFFF)
end
if x<1 then
x=1
vx = math.abs(vx)
col = math.random(0,0xFFFFFF)
end
if y>raster.displayHeight-6 then
y=raster.displayHeight-6
vy = -math.abs(vy)
col = math.random(0,0xFFFFFF)
end
if y<1 then
y=1
vy = math.abs(vy)
col = math.random(0,0xFFFFFF)
end
if i>10 and i%15>0 then
while true do
local tries=0
local dx,dy=math.random(1,raster.displayWidth),math.random(1,raster.displayHeight-6)
if raster.get(dx,dy)~=0 then
raster.set(dx,dy,0)
break
end
tries = tries + 1
if tries>20 then
break
end
end
end
if i%10==0 then
raster.update()
coroutine.yield()
end
end
--[[ for i=0,360,4 do
local angle = i/180*math.pi
if false then
local x1,y1,x2,y2=raster.displayWidth/2,raster.displayHeight/2,raster.displayWidth/2+math.sin(angle)*80,raster.displayHeight/2+math.cos(angle)*80
raster.fillEllipse(x1,y1,x2,y2,0xFF00FF)
raster.update()
raster.fillEllipse(x1,y1,x2,y2,0x000000)
else
local x,y,c=raster.displayWidth/2,raster.displayHeight/2,math.abs(math.sin(angle)*100)
raster.drawCircle(x,y,c,0xFF00FF)
raster.update()
raster.drawCircle(x,y,c,0x000000)
end
end ]]
raster.free()
terminal.clear()
+141
View File
@@ -0,0 +1,141 @@
local component = require("component")
local computer = require("computer")
local raster = require("raster")
local event = require("event")
-- Initialize the 3D renderer for a spinning cube
-- Using the raster library for drawing
-- Screen dimensions
local SCREEN_WIDTH, SCREEN_HEIGHT = component.invoke(component.list("gpu")(), "getResolution")
SCREEN_WIDTH, SCREEN_HEIGHT = SCREEN_WIDTH * 2, SCREEN_HEIGHT * 4
local CENTER_X = SCREEN_WIDTH / 2
local CENTER_Y = SCREEN_HEIGHT / 2
-- Cube properties
local CUBE_SIZE = 10
local increment = 0
local WHITE = 0xFFFFFF
local ROTATION_SPEED = 0.1
-- 3D cube vertices (centered at origin)
local vertices = {
{-CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE}, -- 0: left bottom back
{CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE}, -- 1: right bottom back
{CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}, -- 2: right top back
{-CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}, -- 3: left top back
{-CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE}, -- 4: left bottom front
{CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE}, -- 5: right bottom front
{CUBE_SIZE, CUBE_SIZE, CUBE_SIZE}, -- 6: right top front
{-CUBE_SIZE, CUBE_SIZE, CUBE_SIZE} -- 7: left top front
}
-- Cube edges defined by vertex indices
local edges = {
{0, 1}, {1, 2}, {2, 3}, {3, 0}, -- back face
{4, 5}, {5, 6}, {6, 7}, {7, 4}, -- front face
{0, 4}, {1, 5}, {2, 6}, {3, 7} -- connecting edges
}
-- Projection parameters
local FOV = 256 -- Field of view (distance from camera to screen)
local Z_OFFSET = 300 -- Distance from camera to cube center
-- Initialize rotation angles
local angleX, angleY, angleZ = 0, 0, 0
-- Matrix multiplication function (apply rotation to a 3D point)
local function rotatePoint(x, y, z)
-- Rotation around X axis
local cosX, sinX = math.cos(angleX), math.sin(angleX)
local y1 = y * cosX - z * sinX
local z1 = y * sinX + z * cosX
-- Rotation around Y axis
local cosY, sinY = math.cos(angleY), math.sin(angleY)
local x1 = x * cosY + z1 * sinY
local z2 = -x * sinY + z1 * cosY
-- Rotation around Z axis
local cosZ, sinZ = math.cos(angleZ), math.sin(angleZ)
local x2 = x1 * cosZ - y1 * sinZ
local y2 = x1 * sinZ + y1 * cosZ
return x2, y2, z2
end
-- Perspective projection function (3D to 2D)
local function projectPoint(x, y, z)
-- Apply perspective projection
local scale = FOV / (z + Z_OFFSET)
local x2d = x * scale + CENTER_X
local y2d = y * scale + CENTER_Y
return x2d, y2d
end
-- Render a single frame
local function renderFrame()
local time = computer.uptime()*20
increment = time*0.05 -- increment + 0.05
CUBE_SIZE = (math.sin(increment) + 1) * 25
vertices = {
{-CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE}, -- 0: left bottom back
{CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE}, -- 1: right bottom back
{CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}, -- 2: right top back
{-CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE}, -- 3: left top back
{-CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE}, -- 4: left bottom front
{CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE}, -- 5: right bottom front
{CUBE_SIZE, CUBE_SIZE, CUBE_SIZE}, -- 6: right top front
{-CUBE_SIZE, CUBE_SIZE, CUBE_SIZE} -- 7: left top front
}
-- Update rotation angles
raster.clear()
angleX = time * ROTATION_SPEED -- angleX
angleY = time * ROTATION_SPEED * 0.7 -- angleY
angleZ = time * ROTATION_SPEED * 0.5 -- angleZ
-- Project all vertices
local projectedPoints = {}
for i, vertex in ipairs(vertices) do
-- Rotate the point
local x, y, z = rotatePoint(vertex[1], vertex[2], vertex[3])
-- Project the point to 2D
local x2d, y2d = projectPoint(x, y, z)
projectedPoints[i] = {x2d, y2d}
end
-- Draw all edges
for _, edge in ipairs(edges) do
local p1 = projectedPoints[edge[1] + 1] -- +1 because Lua indices start at 1
local p2 = projectedPoints[edge[2] + 1]
-- Draw the line
raster.drawLine(p1[1], p1[2], p2[1], p2[2], WHITE)
end
-- Render the frame
raster.update()
end
-- Main program
function main()
-- Initialize raster engine
raster.init()
-- Main loop (assume this is called repeatedly by the host environment)
while true do
renderFrame()
coroutine.yield()
if event.pull("key_down", 0) then
raster.free()
break
end
end
-- Return a reference to renderFrame so it can be called for animation
return renderFrame
end
-- Start the program
return main()
+1
View File
@@ -0,0 +1 @@
require("computer").shutdown()
+23
View File
@@ -0,0 +1,23 @@
-- TODO: Rename this to something else (while making an alias from the original command).
-- Touch seems kind of a silly name for a command to make a file.
-- Maybe something like mkfile would be better?
local fs = require("filesystem")
local shell = require("shell")
local args = {...}
if not args[1] then
return shell.run("help touch")
end
for _, file in pairs(args) do
file = shell.resolvePath(file)
local handle, err = fs.open(file, "a")
if err ~= nil then
terminal.write("\27[91mError: " .. err .. "\27[0m\n")
goto continue
end
handle:close()
::continue::
end
+1
View File
@@ -0,0 +1 @@
{"prompt":"\u001b[92m%s > \u001b[0m","aliases":{"move":"mv","copy":"cp","ag":"argentum","rename":"mv","..":"cd ..","man":"help","del":"rm","delete":"rm","ren":"mv","remove":"rm","list":"ls","wget":"download","dir":"ls","ps":"lscor","poweroff":"shutdown","restart":"reboot","resolution":"res"},"splashMessages":["Made by John Haly- I mean Cerulean Blue.","Welcome! Type \"help\" to get started.","Also try KOCOS!","Welcome back, Commander. We have no idea what we're doing.","99.9% bug-free. The remaining 0.1% are features.","0 days since last error.","Everything is fine. The fire is decorative.","Please don't feed the background processes.","Also has fetch!","Anything red is no man's land. Trust me.","Machine...","Abort, Retry, Fail?","What's the deal with /argentum/store?","So cutting-edge you can't hold it in your hand.","Americans are the reason you see colors on-screen. I'm talking about ANSI escape codes, not politics.","Shoutout to Ponali!"],"path":["/halyde/apps/"],"startupMessage":"\n │\n │ %s\n │ %s\n │\n ","defaultWorkingDirectory":"/home/"}
@@ -0,0 +1 @@
["/halyde/core/fullkb.lua","/halyde/core/evmgr.lua","/halyde/core/drvload.lua","/halyde/core/shell.lua"]
+17
View File
@@ -0,0 +1,17 @@
           @@@@@@@@@@          
      /@@@@@@@@@@@@@@@@@@/     
    @@@@@@#**********#@@@@@@   
  /@@@@%*********/(#%(**%@@@@/ 
 @@@@@***********@@@@@****@@@@@
@@@@%*#@@@@#*****#@@@@#****%@@@@
@@@@***@@@@@******@@@@@*****@@@@
@@@#***#@@@@&@@@@@@@@@@#****#@@@
@@@#****@@@@@@@@@@@@@@@@****#@@@
@@@%****#@@@@@&%#(*(@@@@%***%@@@
@@@@/****@@@@@******&@@@@/*/@@@@
@@@@@/***(@@@@%*****(@@&%//@@@@@
  @@@@#***&@@@@/*********#@@@@ 
   @@@@@%*//***********%@@@@@  
     @@@@@@@%(****(%@@@@@@@    
        @@@@@@@@@@@@@@@@       

+1
View File
@@ -0,0 +1 @@
{"prompt":"\u001b[92m%s > \u001b[0m","aliases":{"move":"mv","copy":"cp","ag":"argentum","rename":"mv","..":"cd ..","man":"help","del":"rm","delete":"rm","ren":"mv","remove":"rm","list":"ls","wget":"download","dir":"ls","ps":"lstsk","poweroff":"shutdown","restart":"reboot","lsblk":"lsdrv","lscor":"lstsk"},"splashMessages":["Made by John Haly- I mean Cerulean Blue.","Welcome! Type \"help\" to get started.","Also try KOCOS!","Welcome back, Commander. We have no idea what we're doing.","99.9% bug-free. The remaining 0.1% are features.","0 days since last error.","Everything is fine. The fire is decorative.","Please don't feed the background processes.","Also has fetch!","Anything red is no man's land. Trust me.","Machine...","Abort, Retry, Fail?","What's the deal with /argentum/store?","So cutting-edge you can't hold it in your hand.","Americans are the reason you see colors on-screen. I'm talking about ANSI escape codes, not politics.","Shoutout to Ponali!","Now i% more secure!"],"path":["/halyde/apps/"],"startupMessage":"\n │\n │ %s\n │ %s\n │\n ","defaultWorkingDirectory":"/home/"}
+1
View File
@@ -0,0 +1 @@
["/halyde/scripts/login.lua"]
+92
View File
@@ -0,0 +1,92 @@
local loadfile, lz4 = ...
local filesystem = assert(loadfile("/lib/filesystem.lua")(loadfile))
_G._OSVERSION = "HALYDE VERSION" -- TODO: Put this in a separate config file
_G._PUBLIC = {}
_G._PUBLIC.unicode = assert(loadfile("/lib/unicode.lua")(loadfile))
local component = assert(loadfile("/lib/component.lua")(loadfile))
local gpu = component.gpu
local screenAddress = component.list("screen")()
_G.lz4 = lz4
_G._PUBLIC.lz4 = lz4
gpu.bind(screenAddress)
gpu.setResolution(gpu.maxResolution())
local log = assert(loadfile("/lib/log.lua")(loadfile))
_G.profiler = assert(loadfile("/lib/profiler.lua")())
log.kernel.info("Bound GPU to screen " .. tostring(screenAddress))
_G.package = { ["preloaded"] = {} }
function _G.reqgen(load)
return function(module, ...)
local args = table.pack(...)
if package.preloaded[module] then
return package.preloaded[module]
end
local modulepath
if filesystem.exists(module) and not filesystem.isDirectory(module) then
modulepath = module
elseif
filesystem.exists("/lib/" .. module .. ".lua") and not filesystem.isDirectory("/lib/" .. module .. ".lua")
then
modulepath = "/lib/" .. module .. ".lua"
elseif
shell
and shell.workingDirectory
and filesystem.exists(filesystem.concat(shell.workingDirectory, module .. ".lua"))
and not filesystem.isDirectory(filesystem.concat(shell.workingDirectory, module .. ".lua"))
then
modulepath = shell.workingDirectory .. module .. ".lua"
end
assert(modulepath, "Module not found\nPossible locations:\n/lib/" .. module .. ".lua") -- FIXME: When providing an absolute path, this spits out some weird stuff.
local handle, data, tmpdata = filesystem.open(modulepath), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
return (assert(load(lz4(data), "=" .. modulepath))(table.unpack(args)))
end
end
_G.require = reqgen(_G.load)
log.kernel.info("Generated userland require function")
function _G.package.preload(module)
local handle, data, tmpdata = assert(filesystem.open("/lib/" .. module .. ".lua", "r")), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
package.preloaded[module] = assert(load(lz4(data), "=" .. module))()
_G[module] = nil
log.kernel.info(string.format("Pre-loaded /lib/%s.lua", module))
end
require("/halyde/kernel/datatools.lua") -- If this is not imported BEFORE modload gets run, modload requires filesystem which requires computer which requires datatools. TODO: When VFS is implemented, make the pre-VFS loading of filesystem load a more basic version. And remove this.
log.kernel.info("Loading modules")
require("/halyde/kernel/modload.lua")
local toPreload = { "component", "computer", "log", "event" }
for _, p in pairs(toPreload) do
profiler.profile("pre-loading " .. p, package.preload, p)
end
local computer = require("computer")
function wait(seconds)
local oldTime = computer.uptime()
while computer.uptime() < oldTime + seconds do
coroutine.yield()
end
end
if not filesystem.exists("/halyde/config/startupapps.json") then
filesystem.copy("/halyde/config/generate/startupapps.json", "/halyde/config/startupapps.json")
end
log.kernel.info("Starting tsched")
require("/halyde/kernel/tsched.lua")
+46
View File
@@ -0,0 +1,46 @@
local conversionTables = {
["bytes"] = {
["B"] = 1,
["KB"] = 1000,
["MB"] = 1000000,
["GB"] = 1000000000
}, ["bibytes"] = {
["B"] = 1,
["KiB"] = 1024,
["MiB"] = 1048576,
["GiB"] = 1073741824
}
}
function table.find(tab, item)
checkArg(1,tab,"table")
for k, v in pairs(tab) do
if v == item then
return k
end
end
end
function table.copy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[table.copy(orig_key)] = table.copy(orig_value)
end
setmetatable(copy, table.copy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
function convert(amount, fromUnit, toUnit)
for _, convTable in pairs(conversionTables) do
if convTable[toUnit] then
return amount / convTable[toUnit] * convTable[fromUnit]
end
end
return false, "unit does not exist"
end
+192
View File
@@ -0,0 +1,192 @@
local log = require("log")
local fs = require("filesystem")
local modulePath = "/halyde/kernel/modules"
if not (fs.exists(modulePath) or fs.isDirectory(modulePath)) then
return log.kernel.warn(
string.format("Module directory (%s) does not exist and/or has been detected as a file - skipping", modulePath)
)
end
local moduleList, err = fs.list(modulePath)
if not moduleList then
return log.kernel.warn(
string.format("Could not get list of modules (from %s): %s", modulePath, tostring(err or "unknown error"))
)
end
local modules = {}
local moduleTypes = {}
local modulesLoaded = {}
local function loadModule(modName)
local stop = profiler.start("loadModule(" .. tostring(modName) .. ")")
if table.find(modulesLoaded, modName) then
log.kernel.warn(string.format("[modload: %s] Module was already loaded - skipping", modName))
stop()
return true
end
local moduleData = modules[modName]
if not moduleData then
log.kernel.warn(string.format("[modload: %s] Could not find module data.", modName))
table.remove(moduleList, table.find(moduleList, modName))
stop()
return true
end
local ready = false
local status, err = xpcall(function()
ready = moduleData.check()
end, debug.traceback)
if not status then
ready = false
log.kernel.error(
string.format("[modload: %s] Could not check if module was ready: %s", modName, tostring(err or "unknown error"))
)
end
if not ready then
log.kernel.info(string.format("[modload: %s] Module not ready - skipping", modName))
stop()
return false
end
if type(moduleData.dependencies) == "table" then
for _, dependency in pairs(moduleData.dependencies) do
if table.find(moduleList, dependency) then
loadModule(dependency)
elseif table.find(moduleList, dependency .. ".lua") then
loadModule(dependency .. ".lua")
else
for typeLookupDrvName, typeLookupDrvType in pairs(moduleTypes) do
if typeLookupDrvType == dependency then
loadModule(typeLookupDrvName)
-- Don't break, because there can be multiple modules of the correct type
end
end
end
end
end
--print(modName)
log.kernel.info(string.format("[modload: %s] Loading module", modName))
if moduleData.init then -- I have no idea why this would not exist, but it's a failsafe
local status, err = xpcall(function()
moduleData.init()
end, debug.traceback)
if not status then
log.kernel.error(
string.format(
"[modload: %s] An error has occured while initiating this module: %s",
modName,
tostring(err or "unknown error")
)
)
stop()
return false
else
table.insert(modulesLoaded, modName)
table.remove(moduleList, table.find(moduleList, modName))
end
end
stop()
return true
end
for _, modName in pairs(moduleList) do -- Get all the module types
local stop = profiler.start("getting module " .. modName .. ")")
log.kernel.info(string.format("[modload: %s] Getting data from module", modName))
local moduleData
local status, err = pcall(function()
moduleData = require(fs.concat(modulePath, modName))
end)
if not status then
log.kernel.error(
string.format(
"[modload: %s] Module returned error while getting data: %s",
modName,
tostring(err or "unknown error")
)
)
goto continue
end
if type(moduleData) ~= "table" then
log.kernel.error(
string.format("[modload: %s] Module returned invalid type (%s) - skipping", modName, type(moduleData))
)
goto continue
end
if type(moduleData.check) ~= "function" then
log.kernel.error(string.format('[modload: %s] Module doesn\'t contain a "check" function', modName))
goto continue
end
if type(moduleData.init) ~= "function" then
log.kernel.error(string.format('[modload: %s] Module doesn\'t contain an "init" function', modName))
goto continue
end
if type(moduleData.exit) ~= "function" then
log.kernel.error(string.format('[modload: %s] Module doesn\'t contain an "exit" function', modName))
goto continue
end
modules[modName] = moduleData
if moduleData.type then
--print(moduleData.type)
moduleTypes[modName] = moduleData.type -- Not the other way around because there can be multiple modules of the same type, but there can't be multiple entries with the same key
end
::continue::
stop()
end
local function loadAllModules() -- attempt at loading all modules, unless if they're not ready
local notReadyModules = {}
while moduleList[1] do
if moduleList[1]:sub(-1, -1) ~= "/" then -- Check if it's not a directory. If it is, it might be module config
local ready = loadModule(moduleList[1])
if not ready then
table.insert(notReadyModules, table.remove(moduleList, 1))
end
end
end
moduleList = notReadyModules
-- log.kernel.info("debug: modload finished attempting loading modules. remaining: " .. table.concat(moduleList, ","))
end
loadAllModules()
local function checkModules()
log.kernel.info("[modload] Updating module availability.")
loadAllModules() -- load modules that haven't returned true before
-- exit modules that haven't returned false before (check if this is right first)
for _, v in pairs(table.copy(modulesLoaded)) do
local ready = false
local status, err = xpcall(function()
ready = modules[v].check()
end, debug.traceback)
if not status then
ready = false
log.kernel.error(
string.format(
"[modload: %s] Could not check if module was ready: %s",
modName,
tostring(err or "unknown error")
)
)
end
if not ready then
log.kernel.info(string.format("[modload: %s] Module is no longer ready: exiting", v))
local status, err = xpcall(function()
modules[v].exit()
end, debug.traceback)
if not status then
log.kernel.error(string.format("[modload: %s] Could not exit module: %s", v, tostring(err or "unknown error")))
end
table.insert(moduleList, table.remove(modulesLoaded, table.find(modulesLoaded, v)))
end
end
end
if _PUBLIC.tsched then
_PUBLIC.tsched.addTask(function()
local event = require("event")
while true do
event.pull("component_added", "component_removed") -- wait until a component gets added or removed
checkModules()
end
end, "modload")
end
+53
View File
@@ -0,0 +1,53 @@
local module = {}
module.dependencies = { "terminal" }
function module.check()
return true -- This module should always be loaded
end
function module.init()
local publicTable = {
"print",
"_VERSION",
"_OSVERSION",
"assert",
"error",
"getmetatable",
"ipairs",
"load",
"next",
"pairs",
"pcall",
"rawequal",
"rawget",
"rawlen",
"rawset",
"select",
"setmetatable",
"tonumber",
"tostring",
"type",
"xpcall",
"bit32",
"coroutine",
"debug",
"math",
"os",
"string",
"table",
"checkArg",
"utf8",
"convert",
"wait"
}
for _, value in ipairs(publicTable) do
_G._PUBLIC[value] = table.copy(_G[value])
end
end
function module.exit()
_G._PUBLIC = nil
end
return module
+90
View File
@@ -0,0 +1,90 @@
local module = {}
module.dependencies = { "tsched", "keyboard" }
function module.check()
return true
end
local process
function module.init()
_G.evmgr = {}
_G.evmgr.eventQueue = { kernel = {} }
local maxEventQueueLength = 10 -- increase if events start getting dropped
local computer = require("computer")
local ctrlDown = false
local altDown = false
local shiftDown = false
function _G._PUBLIC.keyboard.getCtrlDown()
return ctrlDown
end
function _G._PUBLIC.keyboard.getAltDown()
return altDown
end
function _G._PUBLIC.keyboard.getShiftDown()
return shiftDown
end
_, process = _PUBLIC.tsched.addTask(function()
while true do
-- check for events
local args
repeat
args = { computer.uptime(), computer.pullSignal(0) }
if args and args[2] then
for pid in pairs(evmgr.eventQueue) do
table.insert(evmgr.eventQueue[pid], args)
end
if _PUBLIC.keyboard then
if args[2] == "key_down" then
local keycode = args[5]
local key = _PUBLIC.keyboard.keys[keycode]
if key == "lcontrol" then
ctrlDown = true
elseif key == "lmenu" then
altDown = true
elseif key == "lshift" then
shiftDown = true
elseif key == "c" and ctrlDown and altDown then
if print then
print("\n\27[91mCoroutine " .. tostring(#tsched.tasks) .. " killed.")
end
table.remove(tsched.tasks, #tsched.tasks)
end
elseif args[2] == "key_up" then
local keycode = args[5]
local key = _PUBLIC.keyboard.keys[keycode]
if key == "lcontrol" then
ctrlDown = false
elseif key == "lmenu" then
altDown = false
elseif key == "lshift" then
shiftDown = true
end
end
end
for pid in pairs(evmgr.eventQueue) do
while #evmgr.eventQueue[pid] > maxEventQueueLength do
--ocelot.log("Queue length breach, removing first signal")
table.remove(evmgr.eventQueue[pid], 1)
end
end
end
until not args or not args[1]
-- TODO: check for processes that have ended
-- run other tasks
coroutine.yield()
end
end, "evmgr")
end
function module.exit()
_G.evmgr = nil
_PUBLIC.tsched.removeTask(process.id)
end
return module
+219
View File
@@ -0,0 +1,219 @@
local module = {}
module.dependencies = { "tsched" }
function module.check()
return true -- IPC should always be loaded
end
local checkProcess
function module.init()
_G.ipc = {}
_G.ipc.shared = {}
_PUBLIC.ipc = {}
function _PUBLIC.ipc.shareWithAll()
local shareTable = {}
setmetatable(shareTable, {
["__newindex"] = function(_, key, value)
local currentPID = _PUBLIC.tsched.getCurrentTask().id
if not _G.ipc.shared[currentPID] then
_G.ipc.shared[currentPID] = {}
end
local globalTable
for _, tab in pairs(_G.ipc.shared[currentPID]) do
if tab.sharedWith == "all" then
globalTable = tab
end
end
if not globalTable then
globalTable = { ["sharedWith"] = "all" }
table.insert(_G.ipc.shared[currentPID], globalTable)
end
if not globalTable.vars then
globalTable.vars = {}
end
globalTable.vars[key] = value
end,
["__index"] = function(_, key)
local currentPID = _PUBLIC.tsched.getCurrentTask().id
if not _G.ipc.shared[currentPID] then
return nil
end
local globalTable
for _, tab in pairs(_G.ipc.shared[currentPID]) do
if tab.sharedWith == "all" then
globalTable = tab
end
end
if not globalTable then
return nil
end
if not globalTable.vars then
return nil
end
return globalTable.vars[key]
end,
["__pairs"] = function()
if not _G.ipc.shared[currentPID] then
return pairs({})
end
local globalTable
for _, tab in pairs(_G.ipc.shared[currentPID]) do
if tab.sharedWith == pid then
globalTable = tab
end
end
if not globalTable then
return pairs({})
end
if not globalTable.vars then
return pairs({})
end
return pairs(table.copy(globalTable.vars))
end,
})
return shareTable
end
function _PUBLIC.ipc.shareWith(pid)
checkArg(1, pid, "number")
local shareTable = {}
setmetatable(shareTable, {
["__newindex"] = function(_, key, value)
local currentPID = _PUBLIC.tsched.getCurrentTask().id
if not _G.ipc.shared[currentPID] then
_G.ipc.shared[currentPID] = {}
end
local globalTable
for _, tab in pairs(_G.ipc.shared[currentPID]) do
if tab.sharedWith == "all" then
globalTable = tab
end
end
if not globalTable then
globalTable = { ["sharedWith"] = pid }
table.insert(_G.ipc.shared[currentPID], globalTable)
end
if not globalTable.vars then
globalTable.vars = {}
end
globalTable.vars[key] = value
end,
["__index"] = function(_, key)
print(_G.ipc.shared)
local currentPID = _PUBLIC.tsched.getCurrentTask().id
if not _G.ipc.shared[currentPID] then
return nil
end
local globalTable
for _, tab in pairs(_G.ipc.shared[currentPID]) do
if tab.sharedWith == pid then
globalTable = tab
end
end
if not globalTable then
return nil
end
if not globalTable.vars then
return nil
end
return globalTable.vars[key]
end,
["__pairs"] = function()
if not _G.ipc.shared[currentPID] then
return pairs({})
end
local globalTable
for _, tab in pairs(_G.ipc.shared[currentPID]) do
if tab.sharedWith == pid then
globalTable = tab
end
end
if not globalTable then
return pairs({})
end
if not globalTable.vars then
return pairs({})
end
return pairs(table.copy(globalTable.vars))
end,
})
-- check if the reverse is also available
--[[ if not _G.ipc.shared[pid] then
_G.ipc.shared[pid]={}
end
for _, tab in pairs(_G.ipc.shared[pid]) do
if tab.sharedWith == currentPID then
return -- it's already added
end
end
local reverseTable = {}
reverseTable.vars = globalTable.vars
reverseTable.sharedWith = currentPID
table.insert(_G.ipc.shared[pid],reverseTable) ]]
return shareTable
end
_PUBLIC.ipc.shared = {}
setmetatable(_PUBLIC.ipc.shared, {
["__index"] = function(_, pid)
local currentPID = _PUBLIC.tsched.getCurrentTask().id
local returnTable = {}
for _, shareTable in pairs(ipc.shared[pid] or {}) do
if shareTable.sharedWith == currentPID then
for key, value in pairs(shareTable.vars) do
returnTable[key] = table.copy(value)
end
elseif shareTable.sharedWith == "all" then
for key, value in pairs(shareTable.vars) do
if not returnTable[key] then
returnTable[key] = table.copy(value)
end
end
end
end
return returnTable
end,
["__pairs"] = function()
local ftbl = {}
for i in pairs(_G.ipc.shared) do
ftbl[i] = _PUBLIC.ipc.shared[i]
end
return pairs(ftbl)
end,
})
_, checkProcess = _PUBLIC.tsched.addTask(function()
while true do
-- get all PIDs that exists
local tasks = _PUBLIC.tsched.getTasks()
local pids = {}
for _, v in pairs(tasks) do
table.insert(pids, v.id)
end
-- get all shares from unexistant processes and delete them
for i in pairs(_G.ipc.shared) do
if not table.find(pids, i) then
_G.ipc.shared[i] = nil
end
end
-- let the other processes run
coroutine.yield()
end
end, "ipc")
end
function module.exit()
_G.ipc = nil
_PUBLIC.ipc = nil
_PUBLIC.tsched.removeTask(checkProcess.id)
end
return module
+216
View File
@@ -0,0 +1,216 @@
local module = {}
function module.check()
return true -- This module should always be loaded
end
function module.init()
_G._PUBLIC.keyboard = {["keys"] = {}}
_PUBLIC.keyboard.keys["1"] = 0x02
_PUBLIC.keyboard.keys["2"] = 0x03
_PUBLIC.keyboard.keys["3"] = 0x04
_PUBLIC.keyboard.keys["4"] = 0x05
_PUBLIC.keyboard.keys["5"] = 0x06
_PUBLIC.keyboard.keys["6"] = 0x07
_PUBLIC.keyboard.keys["7"] = 0x08
_PUBLIC.keyboard.keys["8"] = 0x09
_PUBLIC.keyboard.keys["9"] = 0x0A
_PUBLIC.keyboard.keys["0"] = 0x0B
_PUBLIC.keyboard.keys.a = 0x1E
_PUBLIC.keyboard.keys.b = 0x30
_PUBLIC.keyboard.keys.c = 0x2E
_PUBLIC.keyboard.keys.d = 0x20
_PUBLIC.keyboard.keys.e = 0x12
_PUBLIC.keyboard.keys.f = 0x21
_PUBLIC.keyboard.keys.g = 0x22
_PUBLIC.keyboard.keys.h = 0x23
_PUBLIC.keyboard.keys.i = 0x17
_PUBLIC.keyboard.keys.j = 0x24
_PUBLIC.keyboard.keys.k = 0x25
_PUBLIC.keyboard.keys.l = 0x26
_PUBLIC.keyboard.keys.m = 0x32
_PUBLIC.keyboard.keys.n = 0x31
_PUBLIC.keyboard.keys.o = 0x18
_PUBLIC.keyboard.keys.p = 0x19
_PUBLIC.keyboard.keys.q = 0x10
_PUBLIC.keyboard.keys.r = 0x13
_PUBLIC.keyboard.keys.s = 0x1F
_PUBLIC.keyboard.keys.t = 0x14
_PUBLIC.keyboard.keys.u = 0x16
_PUBLIC.keyboard.keys.v = 0x2F
_PUBLIC.keyboard.keys.w = 0x11
_PUBLIC.keyboard.keys.x = 0x2D
_PUBLIC.keyboard.keys.y = 0x15
_PUBLIC.keyboard.keys.z = 0x2C
_PUBLIC.keyboard.keys.apostrophe = 0x28
_PUBLIC.keyboard.keys.at = 0x91
_PUBLIC.keyboard.keys.back = 0x0E -- backspace
_PUBLIC.keyboard.keys.backslash = 0x2B
_PUBLIC.keyboard.keys.capital = 0x3A -- capslock
_PUBLIC.keyboard.keys.colon = 0x92
_PUBLIC.keyboard.keys.comma = 0x33
_PUBLIC.keyboard.keys.enter = 0x1C
_PUBLIC.keyboard.keys.equals = 0x0D
_PUBLIC.keyboard.keys.grave = 0x29 -- accent grave
_PUBLIC.keyboard.keys.lbracket = 0x1A
_PUBLIC.keyboard.keys.lcontrol = 0x1D
_PUBLIC.keyboard.keys.lmenu = 0x38 -- left Alt
_PUBLIC.keyboard.keys.lshift = 0x2A
_PUBLIC.keyboard.keys.minus = 0x0C
_PUBLIC.keyboard.keys.numlock = 0x45
_PUBLIC.keyboard.keys.pause = 0xC5
_PUBLIC.keyboard.keys.period = 0x34
_PUBLIC.keyboard.keys.rbracket = 0x1B
_PUBLIC.keyboard.keys.rcontrol = 0x9D
_PUBLIC.keyboard.keys.rmenu = 0xB8 -- right Alt
_PUBLIC.keyboard.keys.rshift = 0x36
_PUBLIC.keyboard.keys.scroll = 0x46 -- Scroll Lock
_PUBLIC.keyboard.keys.semicolon = 0x27
_PUBLIC.keyboard.keys.slash = 0x35 -- / on main _PUBLIC.keyboard
_PUBLIC.keyboard.keys.space = 0x39
_PUBLIC.keyboard.keys.stop = 0x95
_PUBLIC.keyboard.keys.tab = 0x0F
_PUBLIC.keyboard.keys.underline = 0x93
-- Keypad (and numpad with numlock off)
_PUBLIC.keyboard.keys.up = 0xC8
_PUBLIC.keyboard.keys.down = 0xD0
_PUBLIC.keyboard.keys.left = 0xCB
_PUBLIC.keyboard.keys.right = 0xCD
_PUBLIC.keyboard.keys.home = 0xC7
_PUBLIC.keyboard.keys["end"] = 0xCF
_PUBLIC.keyboard.keys.pageUp = 0xC9
_PUBLIC.keyboard.keys.pageDown = 0xD1
_PUBLIC.keyboard.keys.insert = 0xD2
_PUBLIC.keyboard.keys.delete = 0xD3
-- Function keys
_PUBLIC.keyboard.keys.f1 = 0x3B
_PUBLIC.keyboard.keys.f2 = 0x3C
_PUBLIC.keyboard.keys.f3 = 0x3D
_PUBLIC.keyboard.keys.f4 = 0x3E
_PUBLIC.keyboard.keys.f5 = 0x3F
_PUBLIC.keyboard.keys.f6 = 0x40
_PUBLIC.keyboard.keys.f7 = 0x41
_PUBLIC.keyboard.keys.f8 = 0x42
_PUBLIC.keyboard.keys.f9 = 0x43
_PUBLIC.keyboard.keys.f10 = 0x44
_PUBLIC.keyboard.keys.f11 = 0x57
_PUBLIC.keyboard.keys.f12 = 0x58
_PUBLIC.keyboard.keys.f13 = 0x64
_PUBLIC.keyboard.keys.f14 = 0x65
_PUBLIC.keyboard.keys.f15 = 0x66
_PUBLIC.keyboard.keys.f16 = 0x67
_PUBLIC.keyboard.keys.f17 = 0x68
_PUBLIC.keyboard.keys.f18 = 0x69
_PUBLIC.keyboard.keys.f19 = 0x71
-- Japanese keyboards
_PUBLIC.keyboard.keys.kana = 0x70
_PUBLIC.keyboard.keys.kanji = 0x94
_PUBLIC.keyboard.keys.convert = 0x79
_PUBLIC.keyboard.keys.noconvert = 0x7B
_PUBLIC.keyboard.keys.yen = 0x7D
_PUBLIC.keyboard.keys.circumflex = 0x90
_PUBLIC.keyboard.keys.ax = 0x96
-- Numpad
_PUBLIC.keyboard.keys.numpad0 = 0x52
_PUBLIC.keyboard.keys.numpad1 = 0x4F
_PUBLIC.keyboard.keys.numpad2 = 0x50
_PUBLIC.keyboard.keys.numpad3 = 0x51
_PUBLIC.keyboard.keys.numpad4 = 0x4B
_PUBLIC.keyboard.keys.numpad5 = 0x4C
_PUBLIC.keyboard.keys.numpad6 = 0x4D
_PUBLIC.keyboard.keys.numpad7 = 0x47
_PUBLIC.keyboard.keys.numpad8 = 0x48
_PUBLIC.keyboard.keys.numpad9 = 0x49
_PUBLIC.keyboard.keys.numpadmul = 0x37
_PUBLIC.keyboard.keys.numpaddiv = 0xB5
_PUBLIC.keyboard.keys.numpadsub = 0x4A
_PUBLIC.keyboard.keys.numpadadd = 0x4E
_PUBLIC.keyboard.keys.numpaddecimal = 0x53
_PUBLIC.keyboard.keys.numpadcomma = 0xB3
_PUBLIC.keyboard.keys.numpadenter = 0x9C
_PUBLIC.keyboard.keys.numpadequals = 0x8D
-- Separate list for special keys
_PUBLIC.keyboard.keys.special = {}
_PUBLIC.keyboard.keys.special.back = 0x0E -- backspace
_PUBLIC.keyboard.keys.special.capital = 0x3A -- capslock
_PUBLIC.keyboard.keys.special.enter = 0x1C
_PUBLIC.keyboard.keys.special.lcontrol = 0x1D
_PUBLIC.keyboard.keys.special.lmenu = 0x38 -- left Alt
_PUBLIC.keyboard.keys.special.lshift = 0x2A
_PUBLIC.keyboard.keys.special.rcontrol = 0x9D
_PUBLIC.keyboard.keys.special.rmenu = 0xB8 -- right Alt
_PUBLIC.keyboard.keys.special.rshift = 0x36
_PUBLIC.keyboard.keys.special.scroll = 0x46 -- Scroll Lock
_PUBLIC.keyboard.keys.special.stop = 0x95
_PUBLIC.keyboard.keys.special.up = 0xC8
_PUBLIC.keyboard.keys.special.down = 0xD0
_PUBLIC.keyboard.keys.special.left = 0xCB
_PUBLIC.keyboard.keys.special.right = 0xCD
_PUBLIC.keyboard.keys.special.home = 0xC7
_PUBLIC.keyboard.keys.special["end"] = 0xCF
_PUBLIC.keyboard.keys.special.pageUp = 0xC9
_PUBLIC.keyboard.keys.special.pageDown = 0xD1
_PUBLIC.keyboard.keys.special.insert = 0xD2
_PUBLIC.keyboard.keys.special.delete = 0xD3
_PUBLIC.keyboard.keys.special.f1 = 0x3B
_PUBLIC.keyboard.keys.special.f2 = 0x3C
_PUBLIC.keyboard.keys.special.f3 = 0x3D
_PUBLIC.keyboard.keys.special.f4 = 0x3E
_PUBLIC.keyboard.keys.special.f5 = 0x3F
_PUBLIC.keyboard.keys.special.f6 = 0x40
_PUBLIC.keyboard.keys.special.f7 = 0x41
_PUBLIC.keyboard.keys.special.f8 = 0x42
_PUBLIC.keyboard.keys.special.f9 = 0x43
_PUBLIC.keyboard.keys.special.f10 = 0x44
_PUBLIC.keyboard.keys.special.f11 = 0x57
_PUBLIC.keyboard.keys.special.f12 = 0x58
_PUBLIC.keyboard.keys.special.f13 = 0x64
_PUBLIC.keyboard.keys.special.f14 = 0x65
_PUBLIC.keyboard.keys.special.f15 = 0x66
_PUBLIC.keyboard.keys.special.f16 = 0x67
_PUBLIC.keyboard.keys.special.f17 = 0x68
_PUBLIC.keyboard.keys.special.f18 = 0x69
_PUBLIC.keyboard.keys.special.f19 = 0x71
_PUBLIC.keyboard.keys.special.numpadenter = 0x9C
-- Create inverse mapping for name lookup.
setmetatable(_PUBLIC.keyboard.keys,
{
__index = function(tbl, k)
if type(k) ~= "number" then return end
for name,value in pairs(tbl) do
if value == k then
return name
end
end
end
})
setmetatable(_PUBLIC.keyboard.keys.special,
{
__index = function(tbl, k)
if type(k) ~= "number" then return end
for name,value in pairs(tbl) do
if value == k then
return name
end
end
end
})
end
function module.exit()
_G._PUBLIC.keyboard = nil
end
return module
+817
View File
@@ -0,0 +1,817 @@
--[[
TODO:
```bash
echo -e "\033[?25l" # hide
echo -e "\033[?25h" # show
```
]]
local module = {}
function module.check()
return true -- Usually always loaded, but maybe it would be worth it to check if the computer has a GPU or not? I'm not sure.
end
function module.init()
local serialize = require("serialize")
local unicode = require("unicode")
local event = require("event")
local component = require("component")
local gpu = component.gpu
_G._PUBLIC.terminal = {}
local readHistory = {}
function _PUBLIC.terminal.getHistory(id)
checkArg(1,id,"string")
return table.copy(readHistory[id])
end
function _PUBLIC.terminal.setHistory(id,hist)
checkArg(1,id,"string")
checkArg(2,hist,"table")
for i=1,#hist do
hist[i]=tostring(hist[i])
end
readHistory[id]=hist
end
function _PUBLIC.terminal.addToHistory(id,hist)
checkArg(1,id,"string")
checkArg(2,hist,"string")
table.insert(readHistory[id],hist)
end
local function getColorPalette(depth)
if depth == 1 then
return {
["dark"] = {
[0] = 0x000000,
[1] = 0xffffff,
[2] = 0xffffff,
[3] = 0xffffff,
[4] = 0xffffff,
[5] = 0xffffff,
[6] = 0xffffff,
[7] = 0xffffff,
},
["bright"] = {
[0] = 0x000000,
[1] = 0xffffff,
[2] = 0xffffff,
[3] = 0xffffff,
[4] = 0xffffff,
[5] = 0xffffff,
[6] = 0xffffff,
[7] = 0xffffff,
}
}
end
if depth == 4 then
return {
-- Closest colors to the 4 bit OC pallete
-- Better than outright failure
["dark"] = {
[0] = 0x000000, -- black
[1] = 0x663300, -- brown (dark red)
[2] = 0x336600, -- green (dark green)
[3] = 0x336600, -- green (dark yellow)
[4] = 0x333399, -- blue (dark blue)
[5] = 0x9933CC, -- purple (dark purple)
[6] = 0x333399, -- blue (dark cyan)
[7] = 0xCCCCCC -- silver (dark white)
},
["bright"] = {
[0] = 0x333333, -- gray (bright black)
[1] = 0xff3333, -- red
[2] = 0x33cc33, -- lime (green)
[3] = 0xffff33, -- yellow
[4] = 0x333399, -- blue
[5] = 0xcc66cc, -- magenta (purple)
[6] = 0x336699, -- cyan
[7] = 0xffffff -- white
}
}
end
if depth == 8 then
return {
["dark"] = {
[0] = 0x0f0f0f, -- black
[1] = 0xcc2424, -- dark red
[2] = 0x339280, -- dark green
[3] = 0x996d00, -- dark yellow
[4] = 0x004980, -- dark blue
[5] = 0x9949c0, -- dark purple
[6] = 0x33b6c0, -- dark cyan
[7] = 0xc3c3c3 -- dark white
},
["bright"] = {
[0] = 0x666d80, -- brighter black
[1] = 0xff6d40, -- red
[2] = 0x33db80, -- green
[3] = 0xffb600, -- yellow
[4] = 0x336dff, -- blue
[5] = 0xcc6dc0, -- purple
[6] = 0x33dbc0, -- cyan
[7] = 0xffffff -- white
}
}
end
--[[ Original color palette:
{
["dark"] = {
[0] = 0x171421,
[1] = 0xc01c28,
[2] = 0x26a269,
[3] = 0xa2734c,
[4] = 0x12488b,
[5] = 0xa347ba,
[6] = 0x2aa1b3,
[7] = 0xd0cfcc
},
["bright"] = {
[0] = 0x5e5c64,
[1] = 0xf66151,
[2] = 0x33d17a,
[3] = 0xe9ad0c,
[4] = 0x2a7bde,
[5] = 0xc061cb,
[6] = 0x33c7de,
[7] = 0xffffff
}
}
]]
-- Shouldn't reach here
error()
end
local ANSIColorPalette = getColorPalette(gpu.maxDepth())
local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u
local printState = 0 -- 0:none 1:in ESC 2:in CSI
local color = {
FG = ANSIColorPalette["bright"][7], BG = ANSIColorPalette["dark"][0],
fg = nil, bg = nil, reverse = false
}
color.fg = color.FG
color.bg = color.BG
local current_codepoint = 0
local bytes_remaining = 0
local seq = {}
local writeBuf = {}
local function update_gpu_colors()
if color.reverse then
gpu.setForeground(color.bg)
gpu.setBackground(color.fg)
else
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
end
end
local width, height = gpu.getResolution()
function _G._PUBLIC.terminal.getResolution()
return width, height
end
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
local function scroll()
if gpu.copy(1, 2, width, height - 1, 0, -1) then
gpu.setForeground(color.FG)
gpu.setBackground(color.BG)
gpu.fill(1, height, width, 1, " ")
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
cursor.y = height
end
end
local function check_wrap_and_scroll()
if cursor.x > width then
cursor.x = 1
cursor.y = cursor.y + 1
end
while cursor.y > height do
scroll()
end
end
local function exec_csi()
local params = {}
local op = 0
local current_num = 0
local have_num = false
for i = 1, #seq do
local byte = seq[i]
if 0x30 <= byte and byte <= 0x39 then
current_num = current_num * 10 + (byte - 0x30)
have_num = true
elseif byte == 0x3b then
table.insert(params, have_num and current_num or 0)
current_num = 0
have_num = false
else
if have_num then
table.insert(params, current_num)
end
if 0x40 <= byte and byte <= 0x7e then
op = byte
end
break
end
end
local function get_param(idx, default)
if idx <= #params and params[idx] ~= nil then
return params[idx]
end
return default
end
if op == 0x48 or op == 0x66 then
local row = get_param(1, 1)
local col = get_param(2, 1)
cursor.y = row
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.y < 1 then cursor.y = 1 end
if cursor.x > width then cursor.x = width end
if cursor.y > height then cursor.y = height end
return
end
if op == 0x41 then
local n = get_param(1, 1)
cursor.y = cursor.y - n
if cursor.y < 1 then cursor.y = 1 end
return
end
if op == 0x42 then
local n = get_param(1, 1)
cursor.y = cursor.y + n
if cursor.y > height then cursor.y = height end
return
end
if op == 0x43 then
local n = get_param(1, 1)
cursor.x = cursor.x + n
if cursor.x > width then cursor.x = width end
return
end
if op == 0x44 then
local n = get_param(1, 1)
cursor.x = cursor.x - n
if cursor.x < 1 then cursor.x = 1 end
return
end
if op == 0x47 then
local col = get_param(1, 1)
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end
if op == 0x4a then
local mode = get_param(1, 0)
if mode == 0 then
update_gpu_colors()
gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, height - cursor.y + 1, " ")
elseif mode == 1 then
update_gpu_colors()
gpu.fill(1, 1, cursor.x, cursor.y, " ")
elseif mode == 2 then
update_gpu_colors()
gpu.fill(1, 1, width, height, " ")
cursor.x = 1
cursor.y = 1
end
return
end
if op == 0x4b then
local mode = get_param(1, 0)
if mode == 0 then
update_gpu_colors()
gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, 1, " ")
elseif mode == 1 then
update_gpu_colors()
gpu.fill(1, cursor.y, cursor.x, 1, " ")
elseif mode == 2 then
update_gpu_colors()
gpu.fill(1, cursor.y, width, 1, " ")
end
return
end
if op == 0x60 then
local col = get_param(1, 1)
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end
if op == 0x64 then
local row = get_param(1, 1)
cursor.y = row
if cursor.y < 1 then cursor.y = 1 end
if cursor.y > height then cursor.y = height end
return
end
if op == 0x6d then
local j = 1
local function parse_extended_color()
local mode = get_param(j + 1, -1)
if mode == 5 then
local idx = get_param(j + 2, 0)
j = j + 2
if idx < 8 then
return ANSIColorPalette["dark"][idx]
elseif idx < 16 then
return ANSIColorPalette["bright"][idx - 8]
elseif idx < 232 then
local i = idx - 16
local b = (i % 6) * 51
local g = ((i // 6) % 6) * 51
local r = (i // 36) * 51
return (r << 16) | (g << 8) | b
else
local v = (idx - 232) * 10 + 8
return (v << 16) | (v << 8) | v
end
elseif mode == 2 then
local r = get_param(j + 2, 0)
local g = get_param(j + 3, 0)
local b = get_param(j + 4, 0)
j = j + 4
return (r << 16) | (g << 8) | b
end
return nil
end
if #params == 0 then
color.reverse = false
color.fg = color.FG
color.bg = color.BG
update_gpu_colors()
return
end
while j <= #params do
local p = params[j] or 0
if p == 0 then
color.reverse = false
color.fg = color.FG
color.bg = color.BG
elseif p == 1 then
elseif p == 2 then
elseif p == 3 then
elseif p == 4 then
elseif p == 5 or p == 6 then
elseif p == 7 then
color.reverse = true
elseif p == 8 then
color.fg = color.bg
elseif p == 9 then
elseif p == 21 then
elseif p == 22 then
elseif p == 23 then
elseif p == 24 then
elseif p == 25 then
elseif p == 27 then
color.reverse = false
elseif p == 28 then
color.fg = color.FG
elseif p == 29 then
elseif 30 <= p and p <= 37 then
color.fg = ANSIColorPalette["dark"][p - 30]
elseif p == 38 then
local c = parse_extended_color()
if c then color.fg = c end
elseif p == 39 then
color.fg = color.FG
elseif 40 <= p and p <= 47 then
color.bg = ANSIColorPalette["dark"][p - 40]
elseif p == 48 then
local c = parse_extended_color()
if c then color.bg = c end
elseif p == 49 then
color.bg = color.BG
elseif p == 58 then
parse_extended_color()
elseif p == 59 then
elseif 90 <= p and p <= 97 then
color.fg = ANSIColorPalette["bright"][p - 90]
elseif 100 <= p and p <= 107 then
color.bg = ANSIColorPalette["bright"][p - 100]
end
j = j + 1
end
update_gpu_colors()
return
end
if op == 0x73 then
cursor.X = cursor.x
cursor.Y = cursor.y
return
end
if op == 0x75 then
if cursor.X and cursor.Y then
cursor.x = cursor.X
cursor.y = cursor.Y
if cursor.x < 1 then cursor.x = 1 end
if cursor.y < 1 then cursor.y = 1 end
if cursor.x > width then cursor.x = width end
if cursor.y > height then cursor.y = height end
end
return
end
end
function _G._PUBLIC.terminal.writec(byte)
if byte == 0x1b then
_PUBLIC.terminal.flush()
printState = 1
seq = {}
return
end
if printState == 1 then
if byte == 0x5b then
printState = 2
else
printState = 0
end
return
end
if printState == 2 then
table.insert(seq, byte)
if 0x40 <= byte and byte <= 0x7e then
exec_csi()
printState = 0
seq = {}
end
return
end
if byte == 0xa then
_PUBLIC.terminal.flush()
cursor.y = cursor.y + 1
cursor.x = 1
check_wrap_and_scroll()
return
end
if byte == 0xd then
_PUBLIC.terminal.flush()
cursor.x = 1
return
end
if byte == 0x8 then
_PUBLIC.terminal.flush()
if cursor.x > 1 then
cursor.x = cursor.x - 1
end
return
end
if byte == 0x9 then
_PUBLIC.terminal.flush()
cursor.x = ((cursor.x - 1) // 8) * 8 + 9
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end
if byte >= 0x20 and byte <= 0x7F then
table.insert(writeBuf, string.char(byte))
cursor.x = cursor.x + 1
check_wrap_and_scroll()
if cursor.y ~= writeBufY or #writeBuf >= 32 then
_PUBLIC.terminal.flush()
end
elseif byte >= 0xC2 and byte <= 0xDF then
current_codepoint = (byte & 0x1F)
bytes_remaining = 1
elseif byte >= 0xE0 and byte <= 0xEF then
current_codepoint = (byte & 0x0F)
bytes_remaining = 2
elseif byte >= 0xF0 and byte <= 0xF7 then
current_codepoint = (byte & 0x07)
bytes_remaining = 3
elseif byte >= 0x80 and byte <= 0xBF and bytes_remaining > 0 then
current_codepoint = (current_codepoint << 6) | (byte & 0x3F)
bytes_remaining = bytes_remaining - 1
if bytes_remaining == 0 then
table.insert(writeBuf, utf8.char(current_codepoint))
cursor.x = cursor.x + 1
check_wrap_and_scroll()
if cursor.y ~= writeBufY or #writeBuf >= 32 then
_PUBLIC.terminal.flush()
end
current_codepoint = 0
end
else
current_codepoint = 0
bytes_remaining = 0
end
end
function _G._PUBLIC.terminal.write(text)
text = tostring(text)
for i = 1, #text do
_PUBLIC.terminal.writec(string.byte(text, i))
end
end
function _G._PUBLIC.terminal.clear()
update_gpu_colors()
gpu.fill(1, 1, width, height, " ")
writeBuf = {}
cursor.x = 1
cursor.y = 1
end
function _G._PUBLIC.terminal.flush()
if #writeBuf == 0 then return end
update_gpu_colors()
gpu.set(cursor.x - #writeBuf, cursor.y, table.concat(writeBuf))
writeBuf = {}
end
function _G.print(...)
local args = {...}
local stringArgs = {}
for _, arg in pairs(args) do
if type(arg) == "table" then
table.insert(stringArgs, serialize(arg))
elseif tostring(arg) then
table.insert(stringArgs, tostring(arg))
end
end
_PUBLIC.terminal.write(table.concat(stringArgs, "\t") .. "\n")
end
function _G._PUBLIC.terminal.read(options)
checkArg(1, options, "table", "nil")
local function checkOption(name, value, neededType)
assert(not value or type(value) == neededType, ("%s option must be %s, %s provided"):format(name, neededType, type(value)))
end
if not options then
options = {}
end
checkOption("readHistoryType", options.readHistoryType, "string")
checkOption("prefix", options.prefix, "string")
checkOption("maxChars", options.maxChars, "number")
checkOption("defaultText", options.defaultText, "string")
checkOption("censor", options.censor, "string")
options.maxChars = options.maxChars or math.huge
local text = options.defaultText or ""
_G._PUBLIC.terminal.flush()
local historyIdx
if options.readHistoryType then
if not readHistory[options.readHistoryType] then
readHistory[options.readHistoryType] = {text}
elseif readHistory[options.readHistoryType][#readHistory[options.readHistoryType] ] ~= text then
table.insert(readHistory[options.readHistoryType], text)
end
historyIdx = #readHistory[options.readHistoryType]
end
local function updateHistory()
if not options.readHistoryType then return end
if historyIdx ~= #readHistory[options.readHistoryType] then return end
readHistory[options.readHistoryType][historyIdx]=text
end
local cur = unicode.len(text)+1
if options.prefix then _PUBLIC.terminal.write(options.prefix) end
local startX, startY = cursor.x, cursor.y
local fg, bg = gpu.getForeground(), gpu.getBackground()
local cursorBlink = true
local function checkScroll(y)
for i=1,y-height do
scrollDown()
startY=startY-1
end
return math.min(y,height)
end
local function set(index, character, invertedColors) -- HACK: Currently, this will uncensor all spaces in the inputted text.
if character==nil or character=="" then return end
if options.censor then
character = character:gsub("[^ ]", options.censor)
end
if invertedColors then
gpu.setForeground(bg)
gpu.setBackground(fg)
else
gpu.setForeground(fg)
gpu.setBackground(bg)
end
index=startX+index-1
local setX, setY = (index-1)%width+1, startY+((index-1)//width+1)-1
setY = checkScroll(setY)
gpu.set(setX,setY,unicode.sub(character,1,width-setX+1))
for i=1,math.ceil((#character+setX-1)/width)+1 do
gpu.set(1,setY+i,unicode.sub(character,2-setX+i*width,width+i*width-setX))
setY = checkScroll(setY)
end
end
local function strDef(a,b)
if #a==0 then return b end
return a
end
local function curPos(cur)
return unicode.wlen(unicode.sub(text,1,cur-1))+1
end
local function add(chr)
if type(chr)~="string" or #chr==0 then return end
if unicode.len(text)>=options.maxChars then return end
if options.maxChars<math.huge then
chr=unicode.sub(chr,1,options.maxChars-unicode.len(text))
end
text=unicode.sub(text,1,cur-1)..chr..unicode.sub(text,cur)
set(curPos(cur),chr,false)
cur=math.min(cur+unicode.len(chr),options.maxChars+1)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
cursorBlink = true
set(curPos(cur+1),unicode.sub(text,cur+1),false)
end
local function moveCur(dir)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false)
cur=math.max(math.min(cur+dir,unicode.len(text)+1),1)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
cursorBlink = true
end
local function isLetter(chr)
return not string.find("\x09 :@-./_~?&=%+#",chr,1,true)
end
local function nextCur(dir,chr,icur)
if icur==nil then icur=cur end
local next = math.max(math.min(icur+dir,unicode.len(text)+1),1)
if chr then return unicode.sub(text,next,next) end
return next
end
local function curAfterWord(dir)
local ncur = cur
while nextCur(dir,false,ncur)~=ncur and isLetter(nextCur(dir,true,ncur))==(dir==1) do
ncur=nextCur(dir,false,ncur)
end
while nextCur(dir,false,ncur)~=ncur and isLetter(nextCur(dir,true,ncur))==(dir==-1) do
ncur=nextCur(dir,false,ncur)
end
return ncur
end
local function moveWord(dir)
if nextCur(dir)==cur then return end
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false)
cur=curAfterWord(dir)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
cursorBlink = true
end
local function deleteWord(dir)
local after = curAfterWord(dir)
local lenb = unicode.wlen(text)
if dir==1 then
text=unicode.sub(text,1,cur-1)..unicode.sub(text,after)
set(curPos(cur+1),unicode.sub(text,cur+1)..string.rep(" ",lenb-unicode.wlen(text)+1),false)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
else
text = unicode.sub(text,1,after-1)..unicode.sub(text,cur)
cur=after
set(curPos(cur+1),unicode.sub(text,cur+1)..string.rep(" ",lenb-unicode.wlen(text)+1),false)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
end
updateHistory()
cursorBlink = true
end
local function isLine(chr)
return chr=="\n" or chr=="\r"
end
--[[ gpu.set(startX,startY,unicode.sub(text,1,width-startX))
for i=1,(#text+startX)//width-1 do
gpu.set(startX,startY+i,unicode.sub(text,1+i*width,width-startX+i*width))
end ]]
set(1,text,false)
set(curPos(cur)," ",true)
local function reprint(new)
set(1,new..string.rep(" ",unicode.wlen(text)-unicode.wlen(new)+1),false)
cur=unicode.len(new)+1
text=new
set(curPos(cur)," ",true)
end
while true do
local args = {event.pull("key_down", "clipboard", 0.5)}
local ctrlDown = _PUBLIC.keyboard.getCtrlDown()
if args and args[1] == "key_down" and args[4] then
local key = _PUBLIC.keyboard.keys[args[4]]
if key=="up" and options.readHistoryType then
historyIdx=math.max(historyIdx-1,1)
reprint(readHistory[options.readHistoryType][historyIdx])
elseif key=="down" and options.readHistoryType then
historyIdx=math.min(historyIdx+1,#readHistory[options.readHistoryType])
reprint(readHistory[options.readHistoryType][historyIdx])
elseif key=="left" and ctrlDown then
moveWord(-1)
elseif key=="right" and ctrlDown then
moveWord(1)
elseif key=="left" then
moveCur(-1)
elseif key=="right" then
moveCur(1)
elseif key=="home" then
moveCur(-math.huge)
elseif key=="end" then
moveCur(math.huge)
elseif key=="back" and ctrlDown then
deleteWord(-1)
elseif key=="delete" and ctrlDown then
deleteWord(1)
elseif key=="back" and cur>1 then
text=unicode.sub(text,1,cur-2)..unicode.sub(text,cur)
cur=cur-1
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
cursorBlink = true
set(curPos(cur)+1,unicode.sub(text,cur+1).." ",false)
updateHistory()
elseif key=="delete" then
text = unicode.sub(text,1,cur-1)..unicode.sub(text,cur+1)
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),true)
cursorBlink = true
if cur<=unicode.len(text) then
set(curPos(cur+1),unicode.sub(text,cur+1).." ",false)
end
updateHistory()
elseif key=="enter" then
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),false)
break
elseif not (args[3]<32 or (args[3]>0x7F and args[3]<=0x9F)) then
add(unicode.char(args[3]) or " ")
updateHistory()
end
elseif args and args[1]=="clipboard" then
local clip = args[3]
if not args[3] then goto continue end
while isLine(unicode.sub(clip,1,1)) do clip=unicode.sub(clip,2) end
while isLine(unicode.sub(clip,-1)) do clip=unicode.sub(clip,1,-2) end
add(clip)
updateHistory()
else
cursorBlink=not cursorBlink
set(curPos(cur),strDef(unicode.sub(text,cur,cur)," "),cursorBlink)
end
::continue::
end
if options.readHistoryType then
if readHistory[options.readHistoryType][#readHistory[options.readHistoryType]]=="" then
table.remove(readHistory[options.readHistoryType],#readHistory[options.readHistoryType])
end
if historyIdx<#readHistory[options.readHistoryType] then
-- table.remove(readHistory[options.readHistoryType],historyIdx)
table.insert(readHistory[options.readHistoryType],text)
end
while #readHistory[options.readHistoryType] > 50 do
table.remove(readHistory[options.readHistoryType], 1)
end
end
cursor.x=1
cursor.y=cursor.y+math.ceil((unicode.wlen(text)+startX-1)/width)
if cursor.y>height then scroll() end
return text
end
end
function module.exit()
_G._PUBLIC.terminal = nil
end
return module
+126
View File
@@ -0,0 +1,126 @@
local module = {}
module.dependencies = {}
function module.check()
return true
end
function module.init()
_G._PUBLIC.tsched = {}
_G.tsched = {}
_G.tsched.tasks = {}
local component = require("component")
local filesystem = require("filesystem")
local gpu = component.gpu
local log = require("log")
tsched.idCounter = 1
function _G._PUBLIC.tsched.runAsTask(path, ...)
checkArg(1, path, "string")
local args = { ... }
local function taskFunction()
local result, errorMessage = xpcall(function(...)
local args = table.pack(...)
if not filesystem.exists(path) then
error("No such file: " .. path)
end
local handle, data, tmpdata = filesystem.open(path), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
-- Userland environment definition
local userland = table.copy(_PUBLIC)
userland._G = userland
userland.load = function(chunk, chunkname, mode, env)
if not env or env == _G then
env = userland
end -- if they SOMEHOW get the kernel environment they're not running jack shit
return load(chunk, chunkname, mode, env)
end
userland.require = reqgen(userland.load)
assert(load(lz4(data), "=" .. path, "t", userland))(table.unpack(args))
end, function(errorMessage)
return errorMessage .. "\n \n" .. debug.traceback()
end, --[[ path,]] table.unpack(args))
if not result then
if print then
gpu.freeAllBuffers()
print("\n\27[91m" .. errorMessage)
else
error(errorMessage)
end
end
--require(path, table.unpack(args))
end
local _, taskInfo = _PUBLIC.tsched.addTask(taskFunction, string.match(tostring(path), "([^/]+)%.lua$"))
taskInfo.path = path
taskInfo.args = table.copy(args)
end
function _G._PUBLIC.tsched.addTask(func, name)
checkArg(1, func, "function")
checkArg(2, name, "string")
local task = coroutine.create(func)
local taskInfo = { ["task"] = task, ["name"] = name, ["id"] = tsched.idCounter }
if type(tsched.currentTask) == "table" and type(tsched.currentTask.id) == "number" then
taskInfo.parent = tsched.currentTask.id
taskInfo.user = tsched.currentTask.user
end
table.insert(tsched.tasks, taskInfo)
tsched.idCounter = tsched.idCounter + 1
if taskInfo.parent then
log.kernel.info(
("[tsched] Created task %s (PID %d) by parent PID %d as UID %d"):format(
name,
tsched.idCounter - 1,
taskInfo.parent,
taskInfo.user
)
)
else
taskInfo.user = 1 -- It's probably being run from kernel level
log.kernel.info(
string.format("[tsched] Created task %s (PID %d) as UID 1 (no parent found)", name, tsched.idCounter - 1)
)
end
return task, taskInfo
end
function _G._PUBLIC.tsched.removeTask(id)
checkArg(1, id, "number")
-- TODO: Check for user permissions before running
for index, task in pairs(tsched.tasks) do
if task.id == id then
table.remove(tsched.tasks, index)
log.kernel.info(string.format("[tsched] Removed task with PID %d", id))
return true
end
end
log.kernel.warn(string.format("[tsched] Tried to remove task that doesn't exist - PID %d", id))
return false
end
function _G._PUBLIC.tsched.getCurrentTask()
return table.copy(tsched.currentTask)
end
function _G._PUBLIC.tsched.getTasks()
return table.copy(tsched.tasks)
end
end
function module.exit()
-- why would you even want to
_G._PUBLIC.tsched = nil
_G.tsched = nil
_G.tsched.tasks = nil
end
return module
+75
View File
@@ -0,0 +1,75 @@
local module = {}
function module.check()
return true -- The user system is kind of essential all the time...
end
function module.init()
local fs = require("filesystem")
local md5 = require("md5")
local json = require("json")
local log = require("log")
_PUBLIC.user = {}
function _PUBLIC.user.addTask(func, name, userId, userPassword)
checkArg(1, func, "function")
checkArg(2, name, "string")
checkArg(3, userId, "number")
checkArg(4, userPassword, "string")
local handle, data, tmpdata = fs.open("/halyde/kernel/userreg.json"), "", nil
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
local userRegistry = json.decode(data)
if not userRegistry[userId] then
return false, "No such UID."
end
local salt = md5.sumhexa(userRegistry[userId].name) -- A little bit of salt and pepper
local passwordHash = md5.sumhexa(userPassword .. salt)
if passwordHash ~= userRegistry[userId].hash then
wait(3) -- Something to hopefully shove away brute forcers
return false, "Password incorrect."
end
local task = coroutine.create(func)
local taskInfo = { ["task"] = task, ["name"] = name, ["id"] = tsched.idCounter, ["user"] = userId }
if type(tsched.currentTask) == "table" and type(tsched.currentTask.id) == "number" then
taskInfo.parent = tsched.currentTask.id
else
log.kernel.info("debug: tsched.currentTask is " .. require("serialize")(tsched.currentTask))
end
table.insert(tsched.tasks, taskInfo)
if taskInfo.parent then
log.kernel.info(
("[tsched (user)] Created task %s (PID %d) by parent PID %d as UID %d"):format(
name,
#_PUBLIC.tsched.getTasks(),
taskInfo.parent,
taskInfo.user
)
)
log.kernel.info(
string.format(
"[tsched (user)] Created task %s (PID %d) as UID %d (no parent found)",
name,
#_PUBLIC.tsched.getTasks(),
taskInfo.user
)
)
end
tsched.idCounter = tsched.idCounter + 1
return task, taskInfo
end
end
function module.exit() -- Ok bro
_PUBLIC.user = nil
end
return module
+72
View File
@@ -0,0 +1,72 @@
local lz4 = ...
local computer = require("computer")
local filesystem = require("filesystem")
local json = require("json")
local log = require("log")
function handleError(errormsg)
local traceback = debug.traceback()
if errormsg == nil then -- TODO: Replace with proper error handling
print("\27[91munknown error" .. "\n \n" .. traceback)
log.kernel.error(string.format("[tsched] Process ID %d has crashed!\n\n%s", tsched.currentTask.id, traceback))
else
print("\27[91m" .. tostring(errormsg) .. "\n \n" .. traceback)
log.kernel.error(
string.format(
"[tsched] Process ID %d has crashed: %s\n\n%s",
tsched.currentTask.id,
tostring(errormsg),
traceback
)
)
end
end
local function runTasks()
for i = 1, #_G.tsched.tasks do
if tsched.tasks[i] then
tsched.currentTask = tsched.tasks[i]
local result, errorMessage = coroutine.resume(tsched.tasks[i].task)
if not result then
handleError(errorMessage)
end
if not tsched.tasks[i] then
log.kernel.warn("[tsched] Attempted to update a non-existent task. This is likely because it was removed.")
elseif coroutine.status(tsched.tasks[i].task) == "dead" then
_PUBLIC.tsched.removeTask(tsched.tasks[i].id)
--ocelot.log("Removed coroutine")
i = i - 1
end
--computer.pullSignal(0)
--coroutine.yield()
end
end
end
log.kernel.info("[tsched] Starting startup apps...")
local handle, data, tmpdata = filesystem.open("/halyde/config/startupapps.json", "r"), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
for _, line in ipairs(json.decode(data)) do
if line ~= "" then
--[[ if _G.print then
print(line)
end ]]
_G._PUBLIC.tsched.runAsTask(line)
runTasks()
end
end
-- _G.cormgr.loadCoroutine("/halyde/core/shell.lua")
log.setPrintLogs(false)
while true do
runTasks()
if #_G.tsched.tasks == 0 then
log.kernel.warn("[tsched] No more tasks left! Shutting down...")
computer.shutdown()
end
end
+1
View File
@@ -0,0 +1 @@
[{"name":"admin","hash":"c0e024d9200b5705bc4804722636378a"},{"name":"user","hash":"c040e68c4d44c8f8c48746bca89c1b21"}]
+60
View File
@@ -0,0 +1,60 @@
local fs = require("filesystem")
local json = require("json")
terminal.clear()
::retry::
local username = terminal.read({
prefix = "Username: "
})
local handle, data, tmpdata = fs.open("/halyde/kernel/userreg.json"), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
local userRegistry = json.decode(data)
local foundUser, uid = false, nil
for i, user in pairs(userRegistry) do
if user.name == username then
foundUser = true
uid = i
break
end
end
if not foundUser then
print("User does not exist.")
goto retry
end
local password = terminal.read({
prefix = "Password: ",
censor = "*"
})
local shellPath = "/halyde/scripts/shell.lua" -- TODO: Add shell selection (perhaps in a config file or user prompt?)
local handle, data, tmpdata = fs.open(shellPath), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
-- Prepare userland environment
local temporaryGlobals = _G
_G = nil -- This is so copying doesn't cause an infinite loop
local userland = table.copy(temporaryGlobals)
_G = temporaryGlobals
userland._G = userland
local result, errorMessage = user.addTask(assert(load(lz4(data), "=" .. shellPath, "t", userland)), "shell", uid, password)
if not result then
print(errorMessage)
goto retry
end
+150
View File
@@ -0,0 +1,150 @@
local fs = require("filesystem")
local json = require("json")
if not fs.exists("/halyde/config/shell.json") then -- Auto-generate configs
fs.copy("/halyde/config/generate/shell.json", "/halyde/config/shell.json")
end
local handle, data, tmpdata = fs.open("/halyde/config/shell.json", "r"), "", nil
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until not tmpdata
handle:close()
local shellcfg = json.decode(data)
local component = require("component")
local gpu = component.gpu
local workingDirectory = shellcfg["defaultWorkingDirectory"]
local aliases = shellcfg["aliases"]
_G.shell = {}
function _G.shell.getWorkingDirectory()
return workingDirectory
end
function _G.shell.resolvePath(path)
if path:sub(1, 1) == "/" then return path end
return fs.concat(workingDirectory, path)
end
function _G.shell.setWorkingDirectory(dir)
checkArg(1, dir, "string")
workingDirectory = dir
end
function _G.shell.getAliases()
return table.copy(aliases)
end
function _G.shell.addAlias(executable, aliasName)
checkArg(1, executable, "string")
checkArg(2, aliasName, "string")
aliases[aliasName] = executable
end
function _G.shell.removeAlias(aliasName)
checkArg(1, aliasName, "string")
aliases[aliasName] = nil
end
local function runAsTask(path, ...)
--ocelot.log("running " .. path .. " as coroutine")
tsched.runAsTask(path, ...)
local pid = tsched.getTasks()[#tsched.getTasks()].id
while true do
local foundTask = false
for _, task in pairs(tsched.getTasks()) do
if task.id == pid then
foundTask = true
break
end
end
if foundTask then
coroutine.yield()
else
break
end
end
end
function _G.shell.run(command)
checkArg(1, command, "string")
if aliases[command:match("[^ ]+")] then
local _, cmdend = command:find("[^ ]+")
command = aliases[command:match("[^ ]+")] .. command:sub(cmdend + 1)
end
local gm, result, args, trimmedCommand = command:gmatch("[^ ]+"), nil, {}, command
while true do
result = gm()
if not result then
break
end
if result:find('"') then
local location = trimmedCommand:find('"')
local argBefore = result:sub(1, result:find('"') - 1) -- edge case where there is no space before the quote, get the argument there
if argBefore and argBefore ~= "" then
table.insert(args, argBefore)
end
trimmedCommand = trimmedCommand:sub(location + 1)
if trimmedCommand:find('"') then
table.insert(args, trimmedCommand:sub(1, trimmedCommand:find('"') - 1))
trimmedCommand = trimmedCommand:sub(trimmedCommand:find('"') + 1)
gm = trimmedCommand:gmatch("[^ ]+")
else
print("\27[91mmalformed shell command")
return
end
else
table.insert(args, result)
end
end
-- execute the program
local PATH = table.copy(shellcfg.path)
table.insert(PATH, workingDirectory)
if not args[1] then
return
end
if fs.exists(args[1]) and not fs.isDirectory(args[1]) then
local path = args[1]
table.remove(args, 1)
runAsTask(path, table.unpack(args))
return
end
for _, item in pairs(PATH) do
if fs.exists(item .. args[1]) and not fs.isDirectory(item .. args[1]) then
local path = fs.concat(item, args[1])
table.remove(args, 1)
runAsTask(path, table.unpack(args))
return
else -- try to look for it without the file extension
local files = fs.list(item) or {}
for _, file in pairs(files) do
-- previous pattern: (.+)%.[^%.]+$
if args[1] == file:match("(.+)%.[^%.]+$") and not fs.isDirectory(item .. file) then
table.remove(args, 1)
runAsTask(item .. file, table.unpack(args))
return
end
end
end
end
print("No such file or command: " .. args[1])
end
local shareTable = ipc.shareWithAll()
shareTable.shell = _G.shell
print(shellcfg["startupMessage"]:format(_OSVERSION, shellcfg.splashMessages[math.random(1, #shellcfg.splashMessages)]))
while true do
coroutine.yield()
-- print(shell.workingDirectory .. " >")
--print(shellcfg["prompt"]:format(shell.workingDirectory),false)
-- termlib.cursorPosX = #(shell.workingDirectory .. " > ")
-- termlib.cursorPosY = termlib.cursorPosY - 1
if workingDirectory:sub(-1, -1) ~= "/" then
workingDirectory = workingDirectory .. "/"
end
local shellCommand = terminal.read({readHistoryType = "shell", prefix = shellcfg.prompt:format(workingDirectory)})
shell.run(shellCommand)
gpu.freeAllBuffers()
end
+254
View File
@@ -0,0 +1,254 @@
local gpu = component.proxy(component.list("gpu")())
local resX, resY = gpu.getResolution()
local function lz4(data)
assert(type(data) == "string", "bad argument #1 to 'decompress' (string expected, got " .. type(data) .. ")")
local out, outNext = {}, 1
local dataLen = #data
local pos = 1 -- 1-indexed
while pos <= dataLen do
local token = string.byte(data, pos)
pos = pos + 1
local literalCount = token // 16
if literalCount == 15 then
repeat
local lenPart = string.byte(data, pos)
pos = pos + 1
literalCount = literalCount + lenPart
until lenPart < 0xFF
end
-- Copy literals (if any)
for i = 0, literalCount - 1 do
out[outNext + i] = string.char(string.byte(data, pos + i))
end
outNext = outNext + literalCount
pos = pos + literalCount
if pos > dataLen then
break -- This was the last sequence (which has no match part)
end
-- Match --
local matchLength = token & 15
local offsetA, offsetB = string.byte(data, pos, pos + 1)
local matchOffset = offsetA + offsetB * 256
pos = pos + 2
if matchLength == 15 then
repeat
local lenPart = string.byte(data, pos)
pos = pos + 1
matchLength = matchLength + lenPart
until lenPart < 0xFF
end
matchLength = matchLength + 4
for i = 0, matchLength - 1 do
out[outNext + i] = out[outNext - matchOffset + i]
end
outNext = outNext + matchLength
end
return table.concat(out)
end
local function loadfile(file)
checkArg(1, file, "string")
local handle = component.invoke(computer.getBootAddress(), "open", file, "r")
local data = ""
repeat
local tmpdata = component.invoke(computer.getBootAddress(), "read", handle, math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
component.invoke(computer.getBootAddress(), "close", handle)
return assert(load(lz4(data), "=" .. file))
end
local function handleError(errorMessage)
return (errorMessage .. "\n \n" .. debug.traceback())
end
function loadthething()
local foundArchitecture = false
for _, arch in pairs(computer.getArchitectures()) do
if arch == "Lua 5.3" then
foundArchitecture = true
break
end
end
if foundArchitecture then
local _, errorMesage = computer.setArchitecture("Lua 5.3")
if errorMessage then
error(errorMessage)
end
else
gpu.set(1, 1, "Required architecture (Lua 5.3) is not supported.")
gpu.set(1, 2, "Halting.")
while true do
computer.pullSignal()
end
end
loadfile("/halyde/kernel/boot.lua")(loadfile, lz4)
end
gpu.setBackground(0x000000)
gpu.fill(1, 1, resX, resY, " ")
-- Copying low-level functions in case of post-preload failure
local pullSignal = computer.pullSignal
local beep = computer.beep
local shutdown = computer.shutdown
local unicode = unicode
local result, reason = xpcall(loadthething, handleError)
local lines = {}
if not result then
resX, resY = gpu.getResolution() -- doing it again because boot.lua changes the resolution
local log
local logSuccess, logError = false, nil
if _G.require then
logSuccess, logError = pcall(function()
log = _G.require("log")
log.kernel.error("Halyde has crashed!\n" .. tostring(reason or "unknown error"))
end)
end
reason = "A fatal error has occurred.\nHalyde cannot continue.\n \n"
.. tostring(reason or "unknown error"):gsub("\t", " ")
if not log then
reason = "WARNING: This error has occured early enough in the boot process that no log entry could be made.\n\n"
.. reason
elseif not logSuccess then
if type(logError) == "nil" then
logError = ""
else
logError = "\n" .. tostring(logError)
end
reason = "WARNING: An error has occured when making a log entry for this crash." .. logError .. "\n\n" .. reason
end
local bgColor
if gpu.getDepth() == 1 then
bgColor = 0x000000
else
bgColor = 0x000080
end
gpu.setBackground(bgColor)
gpu.fill(1, 1, resX, resY, " ")
for line in string.gmatch(reason, "([^\n]*)\n?") do
table.insert(lines, line)
end
local function render()
gpu.setForeground(0xFFFFFF)
for i = 1, #lines do
gpu.set(2, i + 1, lines[i])
end
gpu.fill(1, resY - 1, resX, 1, "")
gpu.fill(1, resY, resX, 1, " ")
gpu.setForeground(bgColor)
gpu.setBackground(0xFFFFFF)
gpu.set(2, resY, "🠅 🠄 🠇 🠆 ⏎")
gpu.setForeground(0xFFFFFF)
gpu.setBackground(bgColor)
gpu.set(4, resY, " / ")
gpu.set(9, resY, " / ")
gpu.set(14, resY, " / ")
gpu.set(19, resY, " Scroll ")
gpu.set(29, resY, " Reboot")
end
local function cropset(x, y, txt)
gpu.set(math.max(x, 1), y, unicode.sub(txt, math.max(2 - x, 1)))
end
local scrollX = 0
local scrollY = 0
local function scrollDown()
if scrollY >= #lines - resY + 2 then
return
end
gpu.copy(1, 2, resX, resY - 3, 0, -1)
gpu.fill(1, resY - 2, resX, 1, " ")
local line = lines[scrollY + resY - 2]
if type(line) == "string" then
cropset(2 - scrollX, resY - 2, line)
end
scrollY = scrollY + 1
end
local function scrollUp()
if scrollY <= 0 then
return
end
gpu.copy(1, 1, resX, resY - 3, 0, 1)
gpu.fill(1, 1, resX, 1, " ")
local line = lines[scrollY - 1]
if type(line) == "string" then
cropset(2 - scrollX, 1, line)
end
scrollY = scrollY - 1
end
local width = 0
for i = 1, #lines do
width = math.max(width, unicode.len(lines[i]))
end
local function rerender()
for i = 1, #lines do
local y = i - scrollY + 1
if y > 0 and y <= resY - 2 then
gpu.fill(1, y, resX, 1, " ")
cropset(2 - scrollX, y, lines[i])
end
end
end
local function scrollRight()
if scrollX >= width - resX + 2 then
return
end
scrollX = scrollX + 1
rerender()
end
local function scrollLeft()
if scrollX <= 0 then
return
end
scrollX = scrollX - 1
rerender()
end
render()
beep(440, 0.2)
beep(465, 0.2)
beep(440, 0.2)
beep(370, 0.5)
while true do
local ev = { pullSignal() }
if ev[1] == "key_down" then
if ev[4] == 200 then
scrollUp()
end
if ev[4] == 208 then
scrollDown()
end
if ev[4] == 203 then
scrollLeft()
end
if ev[4] == 205 then
scrollRight()
end
if ev[4] == 28 then
-- Next time copy this function too doofus
-- computer.shutdown(true)
shutdown(true)
end
end
if ev[1] == "scroll" then
if ev[5] > 0 then
scrollUp()
else
scrollDown()
end
end
end
end
+120
View File
@@ -0,0 +1,120 @@
local cliParse = {}
local cliParseConfig
function cliParse.config(config)
-- Check if config is valid
checkArg(1, config, "table")
for flagName, argCount in pairs(config) do
if type(flagName) ~= "string" then
error("Flag name " .. tostring(flagName) .. " must be a string")
end
if type(argCount) == "table" then -- Min and max arg count specified
if type(argCount[1]) ~= "number" then
error("Min args for flag " .. flagName .. " must be a number")
end
if argCount[1] < 0 then
error("Min args for flag " .. flagName .. " must not be lower than 0")
end
if type(argCount[2]) ~= "number" then
error("Max args for flag " .. flagName .. " must be a number")
end
if argCount[2] < 0 then
error("Max args for flag " .. flagName .. " must not be lower than 0")
end
if argCount[2] < argCount[1] then
error("Max args for flag " .. flagName .. " must be more than or equal to min args")
end
elseif type(argCount) == "number" then -- Required arg count specified
if argCount < 0 then
error("Required args for flag " .. flagName .. " must not be lower than 0")
end
else
error("Arg count for flag " .. flagName .. " must be either a table of 2 numbers or a number")
end
-- Config is all good, set it
cliParseConfig = config
end
end
function cliParse.parse(...)
local args = { ... }
local returnTable = { ["flags"] = {}, ["args"] = {} } -- This will be filled out and returned in the end
for i = 1, #args do -- This is used instead of pairs() so that the code can skip ahead when it finds arguments instead of flags by just incrementing i
local flagList = {}
if args[i]:sub(1, 2) == "--" then -- Long flag
flagList = { args[i]:sub(3) }
elseif args[i]:sub(1, 1) == "-" then -- Short flag(s)
for i2 = 2, #args[i] do -- i is 2 to account for the - character at the start
table.insert(flagList, args[i]:sub(i2, i2))
end
end
for flagIndex, flag in ipairs(flagList) do -- Yes, this has to be in the argument loop for the skipahead to work.
local flagConfig = cliParseConfig[flag]
if flagConfig then -- This is a real flag
returnTable.flags[flag] = {}
if type(flagConfig) == "table" then
if flagIndex ~= #flagList then -- This flag is in a chain and it's not the last one
if flagConfig[1] ~= 0 then
return false, "Flag " .. flag .. " expects at least " .. flagConfig[1] .. " arguments, got 0"
end
else
for i2 = 1, flagConfig[1] do -- Iterate through the items AFTER the flags to find the minimum arguments
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
else
return false, "Flag " .. flag .. " expects at least " .. flagConfig[1] .. " arguments, got " .. i2
end
end
i = i + flagConfig[1]
local i2IncrementAmount -- See line 71 and 76
for i2 = 1, flagConfig[2] - flagConfig[1] do -- Now search for the max args
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
else
i2IncrementAmount = i2 - 1
-- Since the current argument is NOT a valid one, decrement by 1 to return to the last valid one (this is important when adding i2 to i)
break
end
end
i = i + (i2IncrementAmount or flagConfig[2])
end
else -- Flag required args are a single number
if flagIndex ~= #flagList then -- This flag is in a chain and it's not the last one
if flagConfig ~= 0 then
return false, "Flag " .. flag .. " expects " .. flagConfig .. " arguments, got 0"
end
else
for i2 = 1, flagConfig do -- Now search for the max args
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
else
return false, "Flag " .. flag .. " expects " .. flagConfig[1] .. " arguments, got " .. i2
end
end
i = i + flagConfig
end
end
else
return false, "Unexpected flag: " .. flag
end
end
end
for _, arg in pairs(args) do
local foundArg = false
for _, flag in pairs(returnTable.flags) do -- A loop in a loop... Peak efficiency
if table.find(flag, arg) or arg:sub(1, 1) == "-" then -- AAAND ANOTHER LOOP?!
foundArg = true
break
end
end
if not foundArg then
table.insert(returnTable.args, arg)
end
end
return returnTable
end
return cliParse
+140
View File
@@ -0,0 +1,140 @@
local computer
if require then
-- libcomponent can get loaded early enough in the boot process where require is not present
computer = require("computer")
else
computer = _G.computer
end
local compLib
local LLcomponent
if table.copy then
compLib = table.copy(component)
LLcomponent = table.copy(component)
else
compLib = {}
LLcomponent = component
end
-- local ocelot = LLcomponent.proxy(LLcomponent.list("ocelot")())
-- ocelot.log("loaded")
_G.componentlib = { ["additions"] = {}, ["removals"] = {} }
compLib.virtual = {}
function compLib.virtual.add(address, componentType, proxy)
checkArg(1, address, "string")
checkArg(2, componentType, "string")
checkArg(3, proxy, "table")
proxy["address"] = address
local proc
if _PUBLIC.tsched then
proc = _PUBLIC.tsched.getCurrentTask()
end
componentlib.additions[address] = { ["componentType"] = componentType, ["proxy"] = proxy, ["proc"] = proc }
if componentlib.removals[address] then
componentlib.removals[address] = nil
end
computer.pushSignal("component_added", address, componentType)
end
function compLib.virtual.remove(address)
checkArg(1, address, "string")
if componentlib.additions[address] then
computer.pushSignal("component_removed", address, componentlib.additions[address].componentType)
componentlib.additions[address] = nil
else
local componentTypeOutput = compLib.type(address)
computer.pushSignal("component_removed", address, componentTypeOutput or "unknown")
table.insert(componentlib.removals, address)
end
end
function compLib.virtual.check(address)
checkArg(1, address, "string")
if _G.componentlib.additions[address] then
return true, _G.componentlib.additions[address].proc
else
return false
end
end
function compLib.list(componentType)
checkArg(1, componentType, "string", "nil")
local componentList = LLcomponent.list(componentType)
for address, dataTable in pairs(componentlib.additions) do
if dataTable.componentType == componentType or not componentType then
componentList[address] = dataTable.componentType
end
end
for _, address in pairs(componentlib.removals) do
componentList[address] = nil
end
local i, value
setmetatable(componentList, {
__call = function(self)
i, value = next(self, i)
return i, value
end,
})
return componentList
end
function compLib.proxy(address)
if componentlib.additions[address] then
--ocelot.log("vcomponent")
return componentlib.additions[address].proxy
else
return LLcomponent.proxy(address)
end
end
function compLib.invoke(address, funcName, ...)
--ocelot.log("Invoking " .. funcName .. " from " .. address)
if componentlib.additions[address] then
--ocelot.log("vcomponent")
if not componentlib.additions[address].proxy[funcName] then
error("no such method")
end
return componentlib.additions[address].proxy[funcName](...)
else
return LLcomponent.invoke(address, funcName, ...)
end
end
function compLib.get(address)
checkArg(1, address, "string")
if #address < 3 then
return nil, "abbreviated address must be at least 3 characters long"
end
for currentAddress, name in compLib.list() do
if currentAddress:find("^" .. address) then
return currentAddress
end
end
return nil, "full address not found"
end
function compLib.isAvailable(componentType)
checkArg(1, componentType, "string")
if LLcomponent.list(componentType)() then
return true
else
return false
end
end
-- Add main component proxies to the library
setmetatable(compLib, {
["__index"] = function(_, item)
if LLcomponent.list(item)() then
return compLib.proxy(compLib.list(item)())
else
-- Why did I ever fucking write this??
-- return compLib[item]
return nil
end
end,
})
return compLib
+14
View File
@@ -0,0 +1,14 @@
local computerlib = table.copy(computer)
local LLcomputer = table.copy(computer)
function computerlib.pullSignal(timeout)
local startTime = LLcomputer.uptime()
local result
repeat
result = {LLcomputer.pullSignal(0)}
coroutine.yield()
until result or timeout and LLcomputer.uptime() >= startTime + timeout
return table.unpack(result)
end
return computerlib
+62
View File
@@ -0,0 +1,62 @@
local computer = require("computer")
local event = {}
local bufferTime = 0.1 -- A little bit of buffer time so events won't be skipped by accident.
--local ocelot = component.proxy(component.list("ocelot")())
function event.pull(...)
local pid = _PUBLIC.tsched and _PUBLIC.tsched.getCurrentTask() and _PUBLIC.tsched.getCurrentTask().id or "kernel"
if not evmgr.eventQueue[pid] then
evmgr.eventQueue[pid] = {}
end
local eventQueue = evmgr.eventQueue[pid]
local args = { ... }
local evtypes, timeout = {}, nil
for _, arg in pairs(args) do
if type(arg) == "number" and not timeout then -- It's a timeout
timeout = arg
else -- It's an event type
table.insert(evtypes, tostring(arg))
end
end
local startTime = computer.uptime()
while true do
-- Check event queue for matching event
for i = 1, #eventQueue do
local foundevent = false
if evtypes[1] then -- event type(s) specified
for _, evtype in pairs(evtypes) do
if eventQueue[i][2] == evtype and eventQueue[i][1] >= startTime - bufferTime then
foundevent = true
end
end
else
if eventQueue[i][1] >= startTime - bufferTime then
foundevent = true
end
end
if foundevent then
-- Found matching event (or any event if no type specified)
local result = table.copy(eventQueue[i])
table.remove(eventQueue, i)
table.remove(result, 1) -- remove the time of event argument
return table.unpack(result)
end
end
if timeout ~= nil and computer.uptime() >= startTime + timeout then
return nil
end
if timeout == nil then
coroutine.yield()
elseif timeout > 0 then
coroutine.yield()
end
end
end
return event
+630
View File
@@ -0,0 +1,630 @@
local loadfile = ... -- raw loadfile from boot.lua
local unicode, component, computer
local bufferSize = math.huge or math.maxinteger
if loadfile then
unicode = loadfile("/lib/unicode.lua")(loadfile)
component = loadfile("/lib/component.lua")(loadfile)
computer = _G.computer
elseif require then
unicode = require("unicode")
component = require("component")
computer = require("computer")
end
local filesystem = {}
function filesystem.canonical(path)
checkArg(1, path, "string")
local segList = {}
if path:sub(1, 1) ~= "/" then
path = "/" .. path
end
path = path:gsub("/+", "/")
for segment in path:gmatch("[^/]+") do
if segment == ".." and segList[1] then
table.remove(segList, #segList)
elseif segment ~= "." then
table.insert(segList, segment)
end
end
return "/" .. table.concat(segList, "/")
end
function filesystem.basename(path)
checkArg(1, path, "string")
return path:match("/([^/]+)/?$") or ""
end
function filesystem.concat(path1, path2)
checkArg(1, path1, "string")
checkArg(2, path2, "string")
if path1:sub(-1, -1) == "/" then
path1 = path1:sub(1, -2)
end
if path2:sub(1, 1) ~= "/" then
path2 = "/" .. path2
end
return path1 .. path2
end
function filesystem.absolutePath(path) -- returns the address and absolute path of an object
checkArg(1, path, "string")
path = filesystem.canonical(path)
local address = nil
if path:find("^/tmp") then
address = computer.tmpAddress()
path = path:sub(5)
elseif path:find("^/mnt/...") then
address = component.get(path:sub(6, 8))
if not address then
address = computer.getBootAddress()
else
path = path:sub(9)
end
else
address = computer.getBootAddress()
end
if not address then
return nil, "no such component"
end
return address, path
end
function filesystem.parent(path)
checkArg(1, path, "string")
local p = filesystem.canonical(path)
-- return "/" on "/"
return p == "/" and "/" or (p:match("^(.*)/[^/]+/?$") or "/")
end
function filesystem.exists(path) -- check if path exists
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if absPath:find("^/special/drive/...") then
return not not (computer.getBootAddress() and component.get(absPath:sub(16, 18)))
end
if absPath:find("^/special/eeprom/") then
return table.find({ "init.lua", "data.bin", "label.txt" }, absPath:sub(17))
end
return component.invoke(address, "exists", absPath)
end
local function readBytes(self, n)
n = n or 1
if n == 1 then
local byte = self:read(1)
if byte == nil then
return nil
end
return string.byte(byte)
end
local bytes, res = { string.byte(self:read(n), 1, n) }, 0
if self.littleEndian then
for i = #bytes, 1, -1 do
res = (res << 8) & 0xFFFFFFFF | bytes[i]
end
else
for i = 1, #bytes do
res = (res << 8) & 0xFFFFFFFF | bytes[i]
end
end
return res
end
local function readUnicodeChar(self)
return unicode.readChar(function()
return self:readBytes(1)
end)
end
local function iterateBytes(self)
return function()
local byte = readBytes(self, 1)
if byte == nil then
self:close()
end
return byte
end
end
local function iterateUnicodeChars(self)
return unicode.iterate(iterateBytes(self))
end
function filesystem.makeReadStream(content)
local properHandle = {}
local readCursor = 1
function properHandle.read(self, amount)
checkArg(2, amount, "number")
local limit = string.len(content) + 1
local out = nil
if readCursor < limit then
if amount == math.huge then
out = string.sub(content, math.min(readCursor, limit))
else
out = string.sub(content, math.min(readCursor, limit), math.min(readCursor + amount - 1, limit))
end
end
readCursor = readCursor + amount
if out == "" then
return nil
end
return out
end
properHandle.readBytes = readBytes
properHandle.readUnicodeChar = readUnicodeChar
properHandle.iterateBytes = iterateBytes
properHandle.iterateUnicodeChars = iterateUnicodeChars
function properHandle.write()
return nil
end
function properHandle.close()
content = nil
end
return properHandle
end
function filesystem.open(path, mode, buffered) -- opens a file and returns its handle
checkArg(1, path, "string")
checkArg(2, mode, "string", "nil")
checkArg(3, buffered, "boolean", "nil")
if not mode then
mode = "r"
end
if buffered == nil then
buffered = true
end
if not (mode == "r" or mode == "w" or mode == "rb" or mode == "wb" or mode == "a" or mode == "ab") then
return nil, "invalid handle type"
end
if path:find("^/special") and not filesystem.exists(path) then
return nil, "/special does not allow creating files"
end
local address, absPath = filesystem.absolutePath(path)
local unmanagedDrive = address == computer.getBootAddress() and absPath:find("^/special/drive")
local unmanagedProxy, sectorSize, sectorCount, handle
if unmanagedDrive then
unmanagedProxy = component.proxy(component.get(absPath:sub(16, 18)))
sectorSize = unmanagedProxy.getSectorSize()
sectorCount = math.ceil(unmanagedProxy.getCapacity() / sectorSize)
elseif not (address == computer.getBootAddress() and absPath:find("^/special/")) then
local handleArgs = { component.invoke(address, "open", absPath, mode) }
handle = handleArgs[1]
if not handle then
return table.unpack(handleArgs)
end
handleArgs = nil
end
local properHandle = {}
properHandle.handle = handle
properHandle.address = address
local content = nil
local bufferOffset = 0 -- Position in file where buffer starts
local readCursor = 1 -- Position within buffer (1-based)
if buffered and mode == "r" then
content = component.invoke(address, "read", handle, bufferSize) or ""
bufferOffset = 0
readCursor = 1
end
function properHandle.read(self, amount)
checkArg(2, amount, "number")
if unmanagedDrive then
-- TODO: Test if this still works
local sectorIdx = ((readCursor - 1) // sectorSize) + 1
if sectorIdx > sectorCount then
return nil
end
local sector = unmanagedProxy.readSector(sectorIdx)
local data = sector:sub(
((readCursor - 1) % sectorSize) + 1,
((readCursor + math.min(amount, sectorSize) - 2) % sectorSize) + 1
)
readCursor = readCursor + #data
if data == "" then
return nil
end
return data
else
if buffered then
if amount == math.huge or amount == math.maxinteger then
-- Read everything remaining
local result = ""
-- First, get what's left in current buffer
if content and readCursor <= #content then
result = content:sub(readCursor)
readCursor = #content + 1
end
-- Then read all remaining data from file
while true do
local newData = component.invoke(address, "read", handle, bufferSize)
if not newData or newData == "" then
break
end
result = result .. newData
end
-- Update buffer state
content = nil
bufferOffset = bufferOffset + #(content or "")
return result ~= "" and result or nil
else
local result = ""
local remaining = amount
while remaining > 0 do
-- If we need more data or buffer is empty
if not content or readCursor > #content then
content = component.invoke(address, "read", handle, bufferSize)
if not content or content == "" then
break
end
bufferOffset = bufferOffset + (readCursor - 1)
readCursor = 1
end
-- Extract data from current buffer
local available = #content - readCursor + 1
local toRead = math.min(remaining, available)
local chunk = content:sub(readCursor, readCursor + toRead - 1)
result = result .. chunk
readCursor = readCursor + toRead
remaining = remaining - toRead
end
return result ~= "" and result or nil
end
else
return component.invoke(self.address, "read", self.handle, amount)
end
end
end
properHandle.readBytes = readBytes
properHandle.readUnicodeChar = readUnicodeChar
properHandle.iterateBytes = iterateBytes
properHandle.iterateUnicodeChars = iterateUnicodeChars
function properHandle.write(self, data)
checkArg(2, data, "string")
if unmanagedDrive then
local startSector = ((readCursor - 1) // sectorSize) + 1
if startSector > sectorCount then
return nil, "not enough space"
end
local startSByte = ((readCursor - 1) % sectorSize) + 1
local sect = unmanagedProxy.readSector(startSector)
unmanagedProxy.writeSector(startSector, sect:sub(1, startSByte - 1) .. data:sub(1, sectorSize - startSByte + 1))
for i = 2, (#data + startSByte) // sectorSize do
if startSector + i - 1 > sectorCount then
return nil, "not enough space"
end
unmanagedProxy.writeSector(
startSector + i - 1,
data:sub(startSByte + sectorSize * (i - 1), startSByte + sectorSize * i - 1)
)
end
readCursor = readCursor + #data
return true
else
return component.invoke(self.address, "write", self.handle, data)
end
end
function properHandle.close(self)
if buffered then
content = nil
end
return component.invoke(self.address, "close", self.handle)
end
if address == computer.getBootAddress() then
local eeprom
pcall(function()
eeprom = component.eeprom
end)
if eeprom then
local getFunc, setFunc
if absPath == "/special/eeprom/init.lua" then
getFunc, setFunc = "get", "set"
elseif absPath == "/special/eeprom/data.bin" then
getFunc, setFunc = "getData", "setData"
elseif absPath == "/special/eeprom/label.txt" then
getFunc, setFunc = "getLabel", "setLabel"
end
if mode:sub(1, 1) == "r" and getFunc then
local stream = filesystem.makeReadStream(eeprom[getFunc]() or "")
properHandle.read = stream.read
properHandle.close = stream.close
elseif mode:sub(1, 1) == "w" and setFunc then
local content = ""
function properHandle.write(self, data)
checkArg(2, data, "string")
content = content .. data
end
function properHandle.close(self)
return eeprom[setFunc](content)
end
end
end
end
function properHandle.seek(self, whence, offset)
checkArg(2, whence, "string", "number", "nil")
checkArg(3, offset, "number", "nil")
if not offset then
offset = 0
end
if type(whence) == "number" then
offset = whence
end
if not whence or type(whence) == "number" then
whence = "cur"
end
if buffered then
local currentAbsolutePos = bufferOffset + readCursor - 1
-- Seek real handle position to buffer handle position
component.invoke(self.address, "seek", self.handle, "set", currentAbsolutePos)
local newPos = component.invoke(self.address, "seek", self.handle, whence, math.max(offset, -currentAbsolutePos))
content = nil
bufferOffset = newPos or 0
readCursor = 1
return newPos
else
return component.invoke(self.address, "seek", self.handle, whence, offset)
end
end
return properHandle
end
function filesystem.list(path)
checkArg(1, path, "string")
path = filesystem.canonical(path)
if path == "/mnt" then
-- list drives
local returnTable = {}
local tmpAddress = computer.tmpAddress()
for address, _ in component.list("filesystem") do
if address ~= tmpAddress then
table.insert(returnTable, address:sub(1, 3) .. "/")
end
end
return returnTable
elseif path == "/special/drive" then
local returnTable = {}
local tmpAddress = computer.tmpAddress()
for address, type in component.list("drive") do
if address ~= tmpAddress and type == "drive" then
table.insert(returnTable, address:sub(1, 3))
end
end
return returnTable
elseif path == "/special/eeprom" then
return { "init.lua", "data.bin", "label.txt" }
else
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "list", absPath)
end
end
function filesystem.size(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if address == computer.getBootAddress() then
if absPath:find("^/special/drive") then
local drive = component.get(absPath:sub(16, 18))
if not drive then
return false
end
return component.invoke(drive, "getCapacity")
elseif absPath:find("^/special/eeprom") then
local eeprom
pcall(function()
eeprom = component.eeprom
end)
if eeprom then
local getFunc
if absPath == "/special/eeprom/init.lua" then
getFunc = "get"
elseif absPath == "/special/eeprom/data.bin" then
getFunc = "getData"
elseif absPath == "/special/eeprom/label.txt" then
getFunc = "getLabel"
end
return #(eeprom[getFunc]())
end
end
end
return component.invoke(address, "size", absPath)
end
local function getRecursiveList(address, absPath)
local list = component.invoke(address, "list", absPath)
local dirList = {}
local listChanged = true
while listChanged do
listChanged = false
for i = 1, #list do
if component.invoke(address, "isDirectory", absPath .. "/" .. list[i]) then
listChanged = true
local dir = list[i]
if dir:sub(-1) == "/" then
dir = dir:sub(1, -2)
end
table.insert(dirList, dir)
table.remove(list, i)
local subDir = component.invoke(address, "list", absPath .. "/" .. dir)
for j = 1, #subDir do
table.insert(list, dir .. "/" .. subDir[j])
end
end
end
end
return list, dirList
end
local function copyContent(fromHandle, toHandle)
if not (fromHandle and toHandle) then
return
end
while true do
local tmpdata = fromHandle.read(2048)
if not tmpdata then
break
end
local status, reason = toHandle.write(tmpdata)
if status ~= true then
break
end
end
fromHandle:close()
toHandle:close()
end
local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
if fromAbsPath:sub(-1) == "/" then
fromAbsPath = fromAbsPath:sub(1, -2)
end
if toAbsPath:sub(-1) == "/" then
toAbsPath = toAbsPath:sub(1, -2)
end
component.invoke(toAddress, "makeDirectory", toAbsPath)
local fileList, dirList = getRecursiveList(fromAddress, fromAbsPath)
for i = 1, #dirList do
component.invoke(toAddress, "makeDirectory", toAbsPath .. "/" .. dirList[i])
end
for i = 1, #fileList do
local fromFile, toFile = fromAbsPath .. "/" .. fileList[i], toAbsPath .. "/" .. fileList[i]
local fromHandle = component.invoke(fromAddress, "open", fromFile, "r")
local toHandle = component.invoke(toAddress, "open", toFile, "w")
copyContent({
["read"] = function(...)
return component.invoke(fromAddress, "read", fromHandle, ...)
end,
["close"] = function(...)
return component.invoke(fromAddress, "close", fromHandle, ...)
end,
}, {
["write"] = function(...)
return component.invoke(toAddress, "write", toHandle, ...)
end,
["close"] = function(...)
return component.invoke(toAddress, "close", toHandle, ...)
end,
})
end
end
function filesystem.isDirectory(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "isDirectory", absPath)
end
function filesystem.isFile(path)
return not filesystem.isDirectory(path) and filesystem.exists(path)
end
function filesystem.rename(fromPath, toPath)
checkArg(1, fromPath, "string")
checkArg(2, toPath, "string")
local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath)
local toAddress, toAbsPath = filesystem.absolutePath(toPath)
if not fromAddress or not toAddress then
return false
end
if fromAddress == toAddress then
return component.invoke(fromAddress, "rename", fromAbsPath, toAbsPath)
elseif filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath) then
copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
filesystem.remove(fromPath) -- component.invoke(fromAddress,"remove", fromAbsPath)
else
local handle, data, tmpdata = filesystem.open(fromPath), "", nil -- component.invoke(fromAddress, "open", fromAbsPath, "r"), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger) -- component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = handle:close() -- component.invoke(fromAddress, "close", handle)
local handle = filesystem.open(toPath) -- component.invoke(toAddress, "open", toAbsPath, "w")
handle:write(data) -- component.invoke(toAddress, "write", handle, data)
handle:close() -- component.invoke(toAddress, "close", handle)
filesystem.remove(fromPath) -- component.invoke(fromAddress, "remove", fromAbsPath)
end
end
function filesystem.copy(fromPath, toPath)
checkArg(1, fromPath, "string")
checkArg(2, toPath, "string")
local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath)
local toAddress, toAbsPath = filesystem.absolutePath(toPath)
if not fromAddress or not toAddress then
return false
end
if filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath)
copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
else
--[[ local handle = filesystem.open(fromPath,"r")
local data, tmpdata = "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = handle:close()
local handle = filesystem.open(toPath,"w")
handle:write(data)
handle:close() ]]
copyContent(filesystem.open(fromPath, "r"), filesystem.open(toPath, "w"))
end
end
function filesystem.remove(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if absPath:find("^/special") then
return false
end
if absPath:find("^/tmp") then
return false
end
if absPath:find("^/mnt") then
return false
end
return component.invoke(address, "remove", absPath)
end
function filesystem.makeDirectory(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "makeDirectory", absPath)
end
return filesystem
+4
View File
@@ -0,0 +1,4 @@
-- json.lua by rxi
-- Minified with luamin
-- Original: https://github.com/rxi/json.lua
local a={_version="0.1.2"}local b;local c={["\\"]="\\",["\""]="\"",["\b"]="b",["\f"]="f",["\n"]="n",["\r"]="r",["\t"]="t"}local d={["/"]="/"}for e,f in pairs(c)do d[f]=e end;local function g(h)return"\\"..(c[h]or string.format("u%04x",h:byte()))end;local function i(j)return"null"end;local function k(j,l)local m={}l=l or{}if l[j]then error("circular reference")end;l[j]=true;if rawget(j,1)~=nil or next(j)==nil then local n=0;for e in pairs(j)do if type(e)~="number"then error("invalid table: mixed or invalid key types")end;n=n+1 end;if n~=#j then error("invalid table: sparse array")end;for o,f in ipairs(j)do table.insert(m,b(f,l))end;l[j]=nil;return"["..table.concat(m,",").."]"else for e,f in pairs(j)do if type(e)~="string"then error("invalid table: mixed or invalid key types")end;table.insert(m,b(e,l)..":"..b(f,l))end;l[j]=nil;return"{"..table.concat(m,",").."}"end end;local function p(j)return'"'..j:gsub('[%z\1-\31\\"]',g)..'"'end;local function q(j)if j~=j or j<=-math.huge or j>=math.huge then error("unexpected number value '"..tostring(j).."'")end;return string.format("%.14g",j)end;local r={["nil"]=i,["table"]=k,["string"]=p,["number"]=q,["boolean"]=tostring}b=function(j,l)local s=type(j)local t=r[s]if t then return t(j,l)end;error("unexpected type '"..s.."'")end;function a.encode(j)return b(j)end;local u;local function v(...)local m={}for o=1,select("#",...)do m[select(o,...)]=true end;return m end;local w=v(" ","\t","\r","\n")local x=v(" ","\t","\r","\n","]","}",",")local y=v("\\","/",'"',"b","f","n","r","t","u")local z=v("true","false","null")local A={["true"]=true,["false"]=false,["null"]=nil}local function B(C,D,E,F)for o=D,#C do if E[C:sub(o,o)]~=F then return o end end;return#C+1 end;local function G(C,D,H)local I=1;local J=1;for o=1,D-1 do J=J+1;if C:sub(o,o)=="\n"then I=I+1;J=1 end end;error(string.format("%s at line %d col %d",H,I,J))end;local function K(n)local t=math.floor;if n<=0x7f then return string.char(n)elseif n<=0x7ff then return string.char(t(n/64)+192,n%64+128)elseif n<=0xffff then return string.char(t(n/4096)+224,t(n%4096/64)+128,n%64+128)elseif n<=0x10ffff then return string.char(t(n/262144)+240,t(n%262144/4096)+128,t(n%4096/64)+128,n%64+128)end;error(string.format("invalid unicode codepoint '%x'",n))end;local function L(M)local N=tonumber(M:sub(1,4),16)local O=tonumber(M:sub(7,10),16)if O then return K((N-0xd800)*0x400+O-0xdc00+0x10000)else return K(N)end end;local function P(C,o)local m=""local Q=o+1;local e=Q;while Q<=#C do local R=C:byte(Q)if R<32 then G(C,Q,"control character in string")elseif R==92 then m=m..C:sub(e,Q-1)Q=Q+1;local h=C:sub(Q,Q)if h=="u"then local S=C:match("^[dD][89aAbB]%x%x\\u%x%x%x%x",Q+1)or C:match("^%x%x%x%x",Q+1)or G(C,Q-1,"invalid unicode escape in string")m=m..L(S)Q=Q+#S else if not y[h]then G(C,Q-1,"invalid escape char '"..h.."' in string")end;m=m..d[h]end;e=Q+1 elseif R==34 then m=m..C:sub(e,Q-1)return m,Q+1 end;Q=Q+1 end;G(C,o,"expected closing quote for string")end;local function T(C,o)local R=B(C,o,x)local M=C:sub(o,R-1)local n=tonumber(M)if not n then G(C,o,"invalid number '"..M.."'")end;return n,R end;local function U(C,o)local R=B(C,o,x)local V=C:sub(o,R-1)if not z[V]then G(C,o,"invalid literal '"..V.."'")end;return A[V],R end;local function W(C,o)local m={}local n=1;o=o+1;while 1 do local R;o=B(C,o,w,true)if C:sub(o,o)=="]"then o=o+1;break end;R,o=u(C,o)m[n]=R;n=n+1;o=B(C,o,w,true)local X=C:sub(o,o)o=o+1;if X=="]"then break end;if X~=","then G(C,o,"expected ']' or ','")end end;return m,o end;local function Y(C,o)local m={}o=o+1;while 1 do local Z,j;o=B(C,o,w,true)if C:sub(o,o)=="}"then o=o+1;break end;if C:sub(o,o)~='"'then G(C,o,"expected string for key")end;Z,o=u(C,o)o=B(C,o,w,true)if C:sub(o,o)~=":"then G(C,o,"expected ':' after key")end;o=B(C,o+1,w,true)j,o=u(C,o)m[Z]=j;o=B(C,o,w,true)local X=C:sub(o,o)o=o+1;if X=="}"then break end;if X~=","then G(C,o,"expected '}' or ','")end end;return m,o end;local _={['"']=P,["0"]=T,["1"]=T,["2"]=T,["3"]=T,["4"]=T,["5"]=T,["6"]=T,["7"]=T,["8"]=T,["9"]=T,["-"]=T,["t"]=U,["f"]=U,["n"]=U,["["]=W,["{"]=Y}u=function(C,D)local X=C:sub(D,D)local t=_[X]if t then return t(C,D)end;G(C,D,"unexpected character '"..X.."'")end;function a.decode(C)if type(C)~="string"then error("expected argument of type string, got "..type(C))end;local m,D=u(C,B(C,1,w,true))D=B(C,D,w,true)if D<=#C then G(C,D,"trailing garbage")end;return m end;return a
+143
View File
@@ -0,0 +1,143 @@
local fs, computer, gpu
local chunkSize = 1024
if require then
fs = require("filesystem")
computer = require("computer")
gpu = require("component").gpu
else
local loadfile = ...
fs = loadfile("/lib/filesystem.lua")(loadfile)
computer = _G.computer
gpu = loadfile("/lib/component.lua")(loadfile).gpu
end
local resX, resY = gpu.getResolution()
local log = {}
if not _G.logSettings then
_G.logSettings = { -- We have to preload the library just for this :P
["printLogs"] = true, -- FIXME: Or do we?
["printerY"] = 1,
}
end
function getlines(s)
if s:sub(-1) ~= "\n" then
s = s .. "\n"
end
return s:gmatch("(.-)\n")
end
local function writeToScreen(text)
-- Print onscreen
if text:sub(1, 4) == "INFO" then -- Set color
gpu.setForeground(0xFFFFFF)
elseif text:sub(1, 4) == "WARN" then
gpu.setForeground(0xFFFF00)
elseif text:sub(1, 5) == "ERROR" then
gpu.setForeground(0xFF0000)
end
local i = 0
for line in getlines(text) do
line = line:gsub("\t", " ")
repeat -- Line wrapping
if _G.logSettings.printerY > resY then
gpu.copy(1, 2, resX, resY - 1, 0, -1)
_G.logSettings.printerY = resY
end
gpu.set(1, _G.logSettings.printerY, line .. string.rep(" ", resX - #line))
line = line:sub(resX + 1)
_G.logSettings.printerY = _G.logSettings.printerY + 1
until line == ""
end
end
local logFileSizeLimit = 16384
local function writeToLog(path, text)
fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories
local handle = fs.open(path, "a")
if handle then
handle:write(text .. "\n")
handle:close()
end
-- Log trimming if it gets too long
if fs.size(path) > logFileSizeLimit then
local sizeCounter = 0
local readHandle = fs.open(path, "r", false) -- Making sure buffering is disabled, otherwise this whole thing is pointless
local currentChunk = ""
readHandle:seek("end", -chunkSize)
repeat
currentChunk = readHandle:read(chunkSize)
readHandle:seek(-chunkSize * 2)
sizeCounter = sizeCounter + chunkSize
until sizeCounter >= logFileSizeLimit * 0.75
while true do
local infoEntry = currentChunk:find("INFO [", 1, true)
local warnEntry = currentChunk:find("WARN [", 1, true)
local errorEntry = currentChunk:find("ERROR [", 1, true)
if not infoEntry and not warnEntry and not errorEntry then
readHandle:seek(-chunkSize)
else
readHandle:seek(
math.min(
infoEntry or math.huge or math.maxinteger,
warnEntry or math.huge or math.maxinteger,
errorEntry or math.huge or math.maxinteger
) - 1
)
break
end
if readHandle:seek("cur") == 0 then -- Failsafe to prevent infinite loops
break
end
end
local writeHandle = fs.open(path, "w")
while true do
local tmpdata = readHandle:read(math.huge or math.maxinteger)
if not tmpdata then
break
end
writeHandle:write(tmpdata)
end
readHandle:close()
writeHandle:close()
end
if _G.logSettings.printLogs then
writeToScreen(text)
end
end
setmetatable(log, {
["__index"] = function(_, index)
return {
["logpath"] = fs.concat("/halyde/logs/", index .. ".log"),
["info"] = function(text)
writeToLog(
fs.concat("/halyde/logs/", index .. ".log"),
"INFO [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
)
end,
["warn"] = function(text)
writeToLog(
fs.concat("/halyde/logs/", index .. ".log"),
"WARN [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
)
end,
["error"] = function(text)
writeToLog(
fs.concat("/halyde/logs/", index .. ".log"),
"ERROR [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
)
end,
}
end,
})
function log.setPrintLogs(setting) -- Yes, this works with the metatable.
checkArg(1, setting, "boolean")
_G.logSettings.printLogs = setting
end
return log
+27
View File
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More