mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-09-30 00:39:19 +08:00
Compare commits
435 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
b10d31c8b7 | ||
|
133c779701 | ||
|
d830a36bc8 | ||
|
1af23fd131 | ||
|
d21b3362b2 | ||
|
6cd454e88b | ||
|
0b5f6b9762 | ||
|
3824e9cfc2 | ||
|
fdce0116da | ||
|
b069f7ed27 | ||
|
c4181d45d2 | ||
|
680f838c63 | ||
|
e6d81ca7b7 | ||
|
fd909eeca0 | ||
|
deab146fab | ||
|
f9aeabc245 | ||
|
558d8ffc6c | ||
|
e96c4c3ffa | ||
|
c566efd442 | ||
|
47c220d59c | ||
|
4bd902c5c4 | ||
|
033ce6d941 | ||
|
6ad3f557fe | ||
|
d0bad6c057 | ||
|
58c362f97c | ||
|
5f568dd207 | ||
|
2c1e414ac5 | ||
|
6699109ab8 | ||
|
423ca82296 | ||
|
0a263579e0 | ||
|
d63406abe4 | ||
|
81956d324d | ||
|
f549aad809 | ||
|
f9f965693d | ||
|
727d940e10 | ||
|
c26c2b8536 | ||
|
d8878ed852 | ||
|
c3cc5f649f | ||
|
8bd4365cdb | ||
|
a00a6fb53b | ||
|
3332eba9a0 | ||
|
ad08bb64aa | ||
|
f01e6e1a99 | ||
|
de0145fb29 | ||
|
0d4ae95e1d | ||
|
50db49e2fb | ||
|
60aafae21d | ||
|
902cb9c303 | ||
|
4f1fd43360 | ||
|
ca5af5c55a | ||
|
503cac3bb3 | ||
|
2a9c9ae0ad | ||
|
def4551991 | ||
|
67f4a0d6eb | ||
|
f92f934688 | ||
|
d4059c30b7 | ||
|
b68de7f42b | ||
|
7dacb62d36 | ||
|
3423cde931 | ||
|
b2d1aa01f7 | ||
|
19e1bdf11f | ||
|
75d51087c8 | ||
|
ed524fb77f | ||
|
97045c4af1 | ||
|
1b806f6c9f | ||
|
632c1614a1 | ||
|
00d9ef12e7 | ||
|
d07294810b | ||
|
948b750754 | ||
|
41ae95116d | ||
|
bf3c942294 | ||
|
055b40462d | ||
|
b087b7efb1 | ||
|
5d8d57cf07 | ||
|
661091a9da | ||
|
2ec60c2627 | ||
|
8b4909b238 | ||
|
95a3be6546 | ||
|
968e34e002 | ||
|
197b3e3f20 | ||
|
0e464ea476 | ||
|
811df6f0da | ||
|
4e5d32b05a | ||
|
c5141b77bf | ||
|
9dfeb6b42a | ||
|
77916d2645 | ||
|
4968e7d9ff | ||
|
075d58ee90 | ||
|
6a4a5a815e | ||
|
76a39f254b | ||
|
8ec9fa43fd | ||
|
e8634a3c27 | ||
|
9ae776530b | ||
|
0ec62390d5 | ||
|
82328cd86e | ||
|
5b58da7249 | ||
|
8676517270 | ||
|
4262c4b1bf | ||
|
7ad0110443 | ||
|
e9bba2f75a | ||
|
74ea5eaeec | ||
|
b7ee5f4410 | ||
|
e294db171e | ||
|
8c3d8d7cfa | ||
|
380d6c3317 | ||
|
16bd52333a | ||
|
cfb44b3fe2 | ||
|
43ed5e748d | ||
|
4dca88aade | ||
|
1d2c7a8551 | ||
|
ba080e7e34 | ||
|
fc716738eb | ||
|
659709d529 | ||
|
5b4729d940 | ||
|
b936f28763 | ||
|
78d9a7ad97 | ||
|
d540a98d33 | ||
|
8c00850134 | ||
|
d1990e3396 | ||
|
91fc4a046c | ||
|
95ebe1cd07 | ||
|
27822a1f56 | ||
|
eb06b06475 | ||
|
5d72432046 | ||
|
da51b14789 | ||
|
7be8485b8b | ||
|
a4d36ba0d2 | ||
|
d4bc0f6e10 | ||
|
1011ae7b3c | ||
|
d603ee1e89 | ||
|
4fc518c877 | ||
|
7fda86d6e2 | ||
|
6905373b6c | ||
|
1d8401d583 | ||
|
a89aee1a34 | ||
|
4853dce3fe | ||
|
a793e09bab | ||
|
fbd85e93ee | ||
|
77f754fa32 | ||
|
01d557abe6 | ||
|
68cc00b6e8 | ||
|
8b7c0be06f | ||
|
473bb666d8 | ||
|
376d407ea1 | ||
|
2e13cedc4b | ||
|
17515ad706 | ||
|
d8b5d6393a | ||
|
d404bc703d | ||
|
e5e08df1d9 | ||
|
1988cba147 | ||
|
4cee7fd27f | ||
|
b75fe02aac | ||
|
83c3dd3418 | ||
|
020850dbbb | ||
|
8d265aa25e | ||
|
c343217fd2 | ||
|
71bc26aefa | ||
|
8a3d259ae6 | ||
|
3a9ae0ebf1 | ||
|
d6b903e6cc | ||
|
b9f7f82e29 | ||
|
6d0bfcf37a | ||
|
e0bbb89d00 | ||
|
a0a58d432a | ||
|
206900fb64 | ||
|
794a5523d1 | ||
|
389c7b670c | ||
|
b1af7bb8f2 | ||
|
157fea73da | ||
|
b439f02b8e | ||
|
710a28c572 | ||
|
702d57b655 | ||
|
34e69556b1 | ||
|
7c411b39ac | ||
|
5a959bc340 | ||
|
fb5f72951d | ||
|
7630136d6c | ||
|
dacb8c597f | ||
|
d99aaaf8dc | ||
|
876831b419 | ||
|
24580609b8 | ||
|
5828abe324 | ||
|
c229d3929a | ||
|
31907ca51d | ||
|
58b8e0ab70 | ||
|
9586a46c9c | ||
|
bb49cf8e65 | ||
|
de1fa61560 | ||
|
07b1fd6f44 | ||
|
d0caa1e1df | ||
|
4f7926cf26 | ||
|
62566ee016 | ||
|
c1731bd72c | ||
|
4d9f8ebb39 | ||
|
6aab109afb | ||
|
8c5f0a062e | ||
|
5ba382d79b | ||
|
d28f1fff56 | ||
|
1784972af7 | ||
|
6974497f45 | ||
|
b147419717 | ||
|
d6dab3bd05 | ||
|
cd1368b434 | ||
|
f348661e7e | ||
|
9393e4405b | ||
|
e84d5abc3e | ||
|
0a2314cdcd | ||
|
3e39411b38 | ||
|
2214889a36 | ||
|
5a3493740e | ||
|
e6f2ee1f88 | ||
|
3832ca9d5c | ||
|
636c6a9336 | ||
|
6b9cad55a5 | ||
|
4780da31c2 | ||
|
d04262c70f | ||
|
8055b6f9c5 | ||
|
c3211e559d | ||
|
3d47609e78 | ||
|
52f2f71b40 | ||
|
599beff5b1 | ||
|
a08e60cd07 | ||
|
2500b2cce6 | ||
|
56e0119db1 | ||
|
76c5af2973 | ||
|
fc4a6043d7 | ||
|
a20a3499ed | ||
|
73c6202842 | ||
|
10b5e44ad0 | ||
|
66cebe7c7b | ||
|
af4d24c6f6 | ||
|
fc3045232f | ||
|
dbd4ff109c | ||
|
d2c5802a9d | ||
|
ab20747ef2 | ||
|
9e84ff66f7 | ||
|
b060370b92 | ||
|
f557ac3324 | ||
|
5d46c8a53f | ||
|
99be2736a1 | ||
|
6aafc86a67 | ||
|
9cfa25cb56 | ||
|
499f7c8733 | ||
|
651bab73de | ||
|
652f8a24fa | ||
|
f820298b6e | ||
|
25ee7e20f4 | ||
|
438c4ec572 | ||
|
5794f3d390 | ||
|
092073d0b3 | ||
|
51cd951baa | ||
|
3b246f94a3 | ||
|
f3c8ce3e3d | ||
|
564e3c5413 | ||
|
103b037921 | ||
|
1b8b3b84d0 | ||
|
1dbbc51f86 | ||
|
6b89f33d0c | ||
|
b279411d70 | ||
|
fc1af1b918 | ||
|
17cf796486 | ||
|
cf67bf2294 | ||
|
e8a0fe09bf | ||
|
7ae5351de3 | ||
|
034cfee66e | ||
|
13d4ea6540 | ||
|
92d0ae8b6b | ||
|
10d4abf235 | ||
|
ce0b313b93 | ||
|
2f21ced552 | ||
|
82b16b4076 | ||
|
6abb5fe72b | ||
|
9f244edae3 | ||
|
d2e2d06978 | ||
|
585529a636 | ||
|
05cb806642 | ||
|
795af72cf7 | ||
|
899d9eb445 | ||
|
84f87002b7 | ||
|
e64698dc78 | ||
|
dd115a6d35 | ||
|
f6f05234bf | ||
|
0d3dded238 | ||
|
02efc2e945 | ||
|
0c66cfb936 | ||
|
844a07afa0 | ||
|
389d5588d8 | ||
|
7c18ecee4f | ||
|
b517ad9e19 | ||
|
059ef63b44 | ||
|
1d7c6f68b4 | ||
|
bb6c155341 | ||
|
6d2e739e09 | ||
|
6e263e6b76 | ||
|
b765ed1a44 | ||
|
1fb3380e68 | ||
|
6402f7f4a3 | ||
|
d3a4c9852f | ||
|
35b8d58c3e | ||
|
55a4c3c44f | ||
|
1a4301ea98 | ||
|
3c2d9557e0 | ||
|
8c1ab0c9b6 | ||
|
b144700ae0 | ||
|
ff85c67c29 | ||
|
a73d898bd8 | ||
|
34259f16db | ||
|
24f91ae711 | ||
|
ea4176691d | ||
|
f7be39cb5f | ||
|
c2b9c1ff2a | ||
|
1e3b429abe | ||
|
b718e5f108 | ||
|
1bc6086aec | ||
|
3308ac9173 | ||
|
089537afed | ||
|
beafb9284a | ||
|
8cf899300d | ||
|
5d408875f1 | ||
|
7d7624252b | ||
|
3b1ec7f84e | ||
|
01d3e5e858 | ||
|
b2c1c103c1 | ||
|
55ea991da7 | ||
|
8db22b02e1 | ||
|
396541f3c2 | ||
|
0cbbee7b89 | ||
|
77a3e3b331 | ||
|
2b2ee0a897 | ||
|
e8a2ac09c7 | ||
|
96c8a01017 | ||
|
e2a6ffbea4 | ||
|
807e56355d | ||
|
bb7e844125 | ||
|
100fff4835 | ||
|
1aeff4f955 | ||
|
adfdc0a627 | ||
|
a8715d2d5f | ||
|
0ca6d0f6cc | ||
|
131b9f0517 | ||
|
ba5bb488d1 | ||
|
2052adffef | ||
|
8cf02e60af | ||
|
2ea4711ff4 | ||
|
ef332217a1 | ||
|
34690ad3db | ||
|
7025ebd633 | ||
|
211c923f8b | ||
|
4a803d2e48 | ||
|
f4afb9a88f | ||
|
648090af31 | ||
|
21262399cc | ||
|
d58bd400ea | ||
|
aaf5ac6f8f | ||
|
aae87fc030 | ||
|
0d34f2dedd | ||
|
807ecbd038 | ||
|
5100bc0989 | ||
|
3f3ca2789b | ||
|
0e4f8a763f | ||
|
6977963124 | ||
|
7e3712f853 | ||
|
2fb011aa9b | ||
|
090b94161d | ||
|
ecccc7a6b7 | ||
|
0141bd812b | ||
|
9eac0bfab9 | ||
|
45d5896cf8 | ||
|
89b0168254 | ||
|
19a7698785 | ||
|
07b29dceb4 | ||
|
258a13721e | ||
|
a7a19fa366 | ||
|
851de930c0 | ||
|
a172c8a8b9 | ||
|
900c6d01d4 | ||
|
2581be14e4 | ||
|
5faf8e7141 | ||
|
686c895858 | ||
|
a2cc44a04d | ||
|
ad9db04856 | ||
|
5d26f627cf | ||
|
61baf730e0 | ||
|
38c5c6f847 | ||
|
7dbae12c7e | ||
|
33913366b1 | ||
|
f9f2982c9f | ||
|
947ae519a2 | ||
|
97e886e6dd | ||
|
977cf427ca | ||
|
99e765ff34 | ||
|
de31835676 | ||
|
7e3a20c14a | ||
|
9ca7d57780 | ||
|
e264f64266 | ||
|
45b98f408c | ||
|
a25cc75afa | ||
|
0c0b386cf3 | ||
|
ce46defec8 | ||
|
90c1b63790 | ||
|
e251e4a04f | ||
|
c3f62aaad6 | ||
|
da8ab3322c | ||
|
d2b258434d | ||
|
0fda08b36e | ||
|
a14d0a8790 | ||
|
5fef47a8d4 | ||
|
fb8e843717 | ||
|
1bf382e370 | ||
|
ae175bbdf4 | ||
|
9140544176 | ||
|
5de7a39f3e | ||
|
af1517146e | ||
|
7bc732b2a2 | ||
|
98f5c48d47 | ||
|
3f630d3faa | ||
|
fdd2110437 | ||
|
69183f6609 | ||
|
67d27ea42d | ||
|
279e972b61 | ||
|
c6a05179e0 | ||
|
4557f4acd3 | ||
|
e6edffa8d1 | ||
|
a92598c372 | ||
|
22ae83e372 | ||
|
d5587dd104 | ||
|
a7afa32075 | ||
|
cde1e2966f | ||
|
2f5a7be421 | ||
|
17633aa2a8 | ||
|
71a571b083 | ||
|
9bc0d57b14 | ||
|
bc810e9eab | ||
|
54de7e7e1c | ||
|
041581b05f |
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
*.hi
|
||||||
|
*.o
|
||||||
|
.tests
|
||||||
|
jsoncheck
|
||||||
|
shellcheck
|
||||||
|
shellcheck.1
|
||||||
|
dist
|
661
LICENSE
Normal file
661
LICENSE
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
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 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 work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
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 AGPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
30
Makefile
Normal file
30
Makefile
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# TODO: Phase out Makefile in favor of Cabal
|
||||||
|
|
||||||
|
GHCFLAGS=-O9
|
||||||
|
GHCFLAGS_STATIC=$(GHCFLAGS) -optl-static -optl-pthread
|
||||||
|
|
||||||
|
all: shellcheck .tests shellcheck.1
|
||||||
|
: Done
|
||||||
|
|
||||||
|
shellcheck: regardless
|
||||||
|
: Conditionally compiling shellcheck
|
||||||
|
ghc $(GHCFLAGS) --make shellcheck
|
||||||
|
|
||||||
|
.tests: *.hs */*.hs
|
||||||
|
: Running unit tests
|
||||||
|
./test/runQuack && touch .tests
|
||||||
|
|
||||||
|
shellcheck.1: shellcheck.1.md
|
||||||
|
: Formatting man page
|
||||||
|
pandoc -s -t man $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f .tests shellcheck shellcheck.1
|
||||||
|
rm -f *.hi *.o ShellCheck/*.hi ShellCheck/*.o
|
||||||
|
rm -rf dist
|
||||||
|
|
||||||
|
shellcheck-static: regardless
|
||||||
|
: Conditionally compiling a statically linked shellcheck-static
|
||||||
|
ghc $(GHCFLAGS_STATIC) --make shellcheck -o shellcheck-static
|
||||||
|
|
||||||
|
regardless:
|
83
README.md
Normal file
83
README.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# ShellCheck - A shell script static analysis tool
|
||||||
|
|
||||||
|
http://www.shellcheck.net
|
||||||
|
|
||||||
|
Copyright 2012-2014, Vidar 'koala_man' Holen
|
||||||
|
Licensed under the GNU Affero General Public License, v3
|
||||||
|
|
||||||
|
The goals of ShellCheck are:
|
||||||
|
|
||||||
|
- To point out and clarify typical beginner's syntax issues,
|
||||||
|
that causes a shell to give cryptic error messages.
|
||||||
|
|
||||||
|
- To point out and clarify typical intermediate level semantic problems,
|
||||||
|
that causes a shell to behave strangely and counter-intuitively.
|
||||||
|
|
||||||
|
- To point out subtle caveats, corner cases and pitfalls, that may cause an
|
||||||
|
advanced user's otherwise working script to fail under future circumstances.
|
||||||
|
|
||||||
|
ShellCheck requires at least 1 GB of RAM to compile. Executables can be built with cabal. Tests currently still rely on a Makefile.
|
||||||
|
|
||||||
|
|
||||||
|
## Building with Cabal
|
||||||
|
|
||||||
|
Make sure cabal is installed. On Debian based distros:
|
||||||
|
|
||||||
|
apt-get install cabal-install
|
||||||
|
|
||||||
|
On Fedora:
|
||||||
|
|
||||||
|
yum install cabal-install
|
||||||
|
|
||||||
|
On Mac OS X with homebrew (http://brew.sh/):
|
||||||
|
|
||||||
|
brew install cabal-install
|
||||||
|
|
||||||
|
On Mac OS X with MacPorts (http://www.macports.org/):
|
||||||
|
|
||||||
|
port install hs-cabal-install
|
||||||
|
|
||||||
|
Let cabal update itself, in case your distro version is outdated:
|
||||||
|
|
||||||
|
$ cabal update
|
||||||
|
$ cabal install cabal-install
|
||||||
|
|
||||||
|
With cabal installed, cd to the ShellCheck source directory and:
|
||||||
|
|
||||||
|
$ cabal install
|
||||||
|
|
||||||
|
This will install ShellCheck to your ~/.cabal/bin directory.
|
||||||
|
|
||||||
|
Add the directory to your PATH (for bash, add this to your ~/.bashrc file):
|
||||||
|
|
||||||
|
export PATH=$HOME/.cabal/bin:$PATH
|
||||||
|
|
||||||
|
Verify that your PATH is set up correctly:
|
||||||
|
|
||||||
|
$ which shellcheck
|
||||||
|
~/.cabal/bin/shellcheck
|
||||||
|
|
||||||
|
## Building with Make
|
||||||
|
|
||||||
|
ShellCheck is written in Haskell, and requires GHC, Parsec3, JSON and
|
||||||
|
Text.Regex. To run the unit tests, it also requires QuickCheck2.
|
||||||
|
|
||||||
|
On Fedora, these can be installed with:
|
||||||
|
|
||||||
|
yum install ghc ghc-parsec-devel ghc-QuickCheck-devel \
|
||||||
|
ghc-json-devel ghc-regex-compat-devel pandoc
|
||||||
|
|
||||||
|
On Ubuntu and similar, use:
|
||||||
|
|
||||||
|
apt-get install ghc libghc-parsec3-dev libghc-json-dev \
|
||||||
|
libghc-regex-compat-dev libghc-quickcheck2-dev pandoc
|
||||||
|
|
||||||
|
To build and run the tests, cd to the shellcheck source directory and:
|
||||||
|
|
||||||
|
$ make
|
||||||
|
|
||||||
|
If you want to distribute the binary and/or run it on other distros, you
|
||||||
|
can `make shellcheck-static` to build a statically linked executable without
|
||||||
|
library dependencies.
|
||||||
|
|
||||||
|
Happy ShellChecking!
|
56
ShellCheck.cabal
Normal file
56
ShellCheck.cabal
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
Name: ShellCheck
|
||||||
|
-- Must also be updated in ShellCheck/Data.hs :
|
||||||
|
Version: 0.3.3
|
||||||
|
Synopsis: Shell script analysis tool
|
||||||
|
License: OtherLicense
|
||||||
|
License-file: LICENSE
|
||||||
|
Category: Static Analysis
|
||||||
|
Author: Vidar Holen
|
||||||
|
Maintainer: vidar@vidarholen.net
|
||||||
|
Homepage: http://www.shellcheck.net/
|
||||||
|
Build-Type: Simple
|
||||||
|
Cabal-Version: >= 1.8
|
||||||
|
Bug-reports: https://github.com/koalaman/shellcheck/issues
|
||||||
|
Description:
|
||||||
|
The goals of ShellCheck are:
|
||||||
|
.
|
||||||
|
* To point out and clarify typical beginner's syntax issues,
|
||||||
|
that causes a shell to give cryptic error messages.
|
||||||
|
.
|
||||||
|
* To point out and clarify typical intermediate level semantic problems,
|
||||||
|
that causes a shell to behave strangely and counter-intuitively.
|
||||||
|
.
|
||||||
|
* To point out subtle caveats, corner cases and pitfalls, that may cause an
|
||||||
|
advanced user's otherwise working script to fail under future circumstances.
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: git://github.com/koalaman/shellcheck.git
|
||||||
|
|
||||||
|
library
|
||||||
|
build-depends:
|
||||||
|
base >= 4 && < 5,
|
||||||
|
containers,
|
||||||
|
directory,
|
||||||
|
json,
|
||||||
|
mtl,
|
||||||
|
parsec,
|
||||||
|
regex-compat
|
||||||
|
exposed-modules:
|
||||||
|
ShellCheck.Analytics
|
||||||
|
ShellCheck.AST
|
||||||
|
ShellCheck.Data
|
||||||
|
ShellCheck.Parser
|
||||||
|
ShellCheck.Simple
|
||||||
|
|
||||||
|
executable shellcheck
|
||||||
|
build-depends:
|
||||||
|
ShellCheck,
|
||||||
|
base >= 4 && < 5,
|
||||||
|
containers,
|
||||||
|
directory,
|
||||||
|
json,
|
||||||
|
mtl,
|
||||||
|
parsec,
|
||||||
|
regex-compat
|
||||||
|
main-is: shellcheck.hs
|
356
ShellCheck/AST.hs
Normal file
356
ShellCheck/AST.hs
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
{-
|
||||||
|
This file is part of ShellCheck.
|
||||||
|
http://www.vidarholen.net/contents/shellcheck
|
||||||
|
|
||||||
|
ShellCheck is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
ShellCheck 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-}
|
||||||
|
module ShellCheck.AST where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.Identity
|
||||||
|
import qualified Text.Regex as Re
|
||||||
|
|
||||||
|
data Id = Id Int deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
|
data Quoted = Quoted | Unquoted deriving (Show, Eq)
|
||||||
|
data Dashed = Dashed | Undashed deriving (Show, Eq)
|
||||||
|
data AssignmentMode = Assign | Append deriving (Show, Eq)
|
||||||
|
data FunctionKeyword = FunctionKeyword Bool deriving (Show, Eq)
|
||||||
|
data FunctionParentheses = FunctionParentheses Bool deriving (Show, Eq)
|
||||||
|
data ForInType = NormalForIn | ShortForIn deriving (Show, Eq)
|
||||||
|
|
||||||
|
data Token =
|
||||||
|
TA_Base Id String Token
|
||||||
|
| TA_Binary Id String Token Token
|
||||||
|
| TA_Expansion Id Token
|
||||||
|
| TA_Literal Id String
|
||||||
|
| TA_Sequence Id [Token]
|
||||||
|
| TA_Trinary Id Token Token Token
|
||||||
|
| TA_Unary Id String Token
|
||||||
|
| TA_Variable Id String
|
||||||
|
| TC_And Id ConditionType String Token Token
|
||||||
|
| TC_Binary Id ConditionType String Token Token
|
||||||
|
| TC_Group Id ConditionType Token
|
||||||
|
| TC_Noary Id ConditionType Token
|
||||||
|
| TC_Or Id ConditionType String Token Token
|
||||||
|
| TC_Unary Id ConditionType String Token
|
||||||
|
| T_AND_IF Id
|
||||||
|
| T_AndIf Id (Token) (Token)
|
||||||
|
| T_Arithmetic Id Token
|
||||||
|
| T_Array Id [Token]
|
||||||
|
| T_Assignment Id AssignmentMode String (Maybe Token) Token
|
||||||
|
| T_Backgrounded Id Token
|
||||||
|
| T_Backticked Id [Token]
|
||||||
|
| T_Bang Id
|
||||||
|
| T_Banged Id Token
|
||||||
|
| T_BraceExpansion Id String
|
||||||
|
| T_BraceGroup Id [Token]
|
||||||
|
| T_CLOBBER Id
|
||||||
|
| T_Case Id
|
||||||
|
| T_CaseExpression Id Token [([Token],[Token])]
|
||||||
|
| T_Condition Id ConditionType Token
|
||||||
|
| T_DGREAT Id
|
||||||
|
| T_DLESS Id
|
||||||
|
| T_DLESSDASH Id
|
||||||
|
| T_DSEMI Id
|
||||||
|
| T_Do Id
|
||||||
|
| T_DollarArithmetic Id Token
|
||||||
|
| T_DollarBraced Id Token
|
||||||
|
| T_DollarBracket Id Token
|
||||||
|
| T_DollarDoubleQuoted Id [Token]
|
||||||
|
| T_DollarExpansion Id [Token]
|
||||||
|
| T_DollarSingleQuoted Id String
|
||||||
|
| T_Done Id
|
||||||
|
| T_DoubleQuoted Id [Token]
|
||||||
|
| T_EOF Id
|
||||||
|
| T_Elif Id
|
||||||
|
| T_Else Id
|
||||||
|
| T_Esac Id
|
||||||
|
| T_Extglob Id String [Token]
|
||||||
|
| T_FdRedirect Id String Token
|
||||||
|
| T_Fi Id
|
||||||
|
| T_For Id
|
||||||
|
| T_ForArithmetic Id Token Token Token [Token]
|
||||||
|
| T_ForIn Id ForInType [String] [Token] [Token]
|
||||||
|
| T_Function Id FunctionKeyword FunctionParentheses String Token
|
||||||
|
| T_GREATAND Id
|
||||||
|
| T_Glob Id String
|
||||||
|
| T_Greater Id
|
||||||
|
| T_HereDoc Id Dashed Quoted String [Token]
|
||||||
|
| T_HereString Id Token
|
||||||
|
| T_If Id
|
||||||
|
| T_IfExpression Id [([Token],[Token])] [Token]
|
||||||
|
| T_In Id
|
||||||
|
| T_IoFile Id Token Token
|
||||||
|
| T_LESSAND Id
|
||||||
|
| T_LESSGREAT Id
|
||||||
|
| T_Lbrace Id
|
||||||
|
| T_Less Id
|
||||||
|
| T_Literal Id String
|
||||||
|
| T_Lparen Id
|
||||||
|
| T_NEWLINE Id
|
||||||
|
| T_NormalWord Id [Token]
|
||||||
|
| T_OR_IF Id
|
||||||
|
| T_OrIf Id (Token) (Token)
|
||||||
|
| T_Pipeline Id [Token] [Token] -- [Pipe separators] [Commands]
|
||||||
|
| T_ProcSub Id String [Token]
|
||||||
|
| T_Rbrace Id
|
||||||
|
| T_Redirecting Id [Token] Token
|
||||||
|
| T_Rparen Id
|
||||||
|
| T_Script Id String [Token]
|
||||||
|
| T_Select Id
|
||||||
|
| T_SelectIn Id String [Token] [Token]
|
||||||
|
| T_Semi Id
|
||||||
|
| T_SimpleCommand Id [Token] [Token]
|
||||||
|
| T_SingleQuoted Id String
|
||||||
|
| T_Subshell Id [Token]
|
||||||
|
| T_Then Id
|
||||||
|
| T_Until Id
|
||||||
|
| T_UntilExpression Id [Token] [Token]
|
||||||
|
| T_While Id
|
||||||
|
| T_WhileExpression Id [Token] [Token]
|
||||||
|
| T_Annotation Id [Annotation] Token
|
||||||
|
| T_Pipe Id String
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data Annotation = DisableComment Integer deriving (Show, Eq)
|
||||||
|
data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- I apologize for nothing!
|
||||||
|
lolHax s = Re.subRegex (Re.mkRegex "(Id [0-9]+)") (show s) "(Id 0)"
|
||||||
|
instance Eq Token where
|
||||||
|
(==) a b = lolHax a == lolHax b
|
||||||
|
|
||||||
|
|
||||||
|
analyze :: Monad m => (Token -> m ()) -> (Token -> m ()) -> (Token -> Token) -> Token -> m Token
|
||||||
|
analyze f g i =
|
||||||
|
round
|
||||||
|
where
|
||||||
|
round t = do
|
||||||
|
f t
|
||||||
|
newT <- delve t
|
||||||
|
g t
|
||||||
|
return . i $ newT
|
||||||
|
roundAll = mapM round
|
||||||
|
|
||||||
|
roundMaybe Nothing = return Nothing
|
||||||
|
roundMaybe (Just v) = do
|
||||||
|
s <- round v
|
||||||
|
return (Just s)
|
||||||
|
|
||||||
|
dl l v = do
|
||||||
|
x <- roundAll l
|
||||||
|
return $ v x
|
||||||
|
dll l m v = do
|
||||||
|
x <- roundAll l
|
||||||
|
y <- roundAll m
|
||||||
|
return $ v x m
|
||||||
|
d1 t v = do
|
||||||
|
x <- round t
|
||||||
|
return $ v x
|
||||||
|
d2 t1 t2 v = do
|
||||||
|
x <- round t1
|
||||||
|
y <- round t2
|
||||||
|
return $ v x y
|
||||||
|
|
||||||
|
delve (T_NormalWord id list) = dl list $ T_NormalWord id
|
||||||
|
delve (T_DoubleQuoted id list) = dl list $ T_DoubleQuoted id
|
||||||
|
delve (T_DollarDoubleQuoted id list) = dl list $ T_DollarDoubleQuoted id
|
||||||
|
delve (T_DollarExpansion id list) = dl list $ T_DollarExpansion id
|
||||||
|
delve (T_Backticked id list) = dl list $ T_Backticked id
|
||||||
|
delve (T_DollarArithmetic id c) = d1 c $ T_DollarArithmetic id
|
||||||
|
delve (T_DollarBracket id c) = d1 c $ T_DollarBracket id
|
||||||
|
delve (T_IoFile id op file) = d2 op file $ T_IoFile id
|
||||||
|
delve (T_HereString id word) = d1 word $ T_HereString id
|
||||||
|
delve (T_FdRedirect id v t) = d1 t $ T_FdRedirect id v
|
||||||
|
delve (T_Assignment id mode var index value) = do
|
||||||
|
a <- roundMaybe index
|
||||||
|
b <- round value
|
||||||
|
return $ T_Assignment id mode var a b
|
||||||
|
delve (T_Array id t) = dl t $ T_Array id
|
||||||
|
delve (T_Redirecting id redirs cmd) = do
|
||||||
|
a <- roundAll redirs
|
||||||
|
b <- round cmd
|
||||||
|
return $ T_Redirecting id a b
|
||||||
|
delve (T_SimpleCommand id vars cmds) = dll vars cmds $ T_SimpleCommand id
|
||||||
|
delve (T_Pipeline id l1 l2) = dll l1 l2 $ T_Pipeline id
|
||||||
|
delve (T_Banged id l) = d1 l $ T_Banged id
|
||||||
|
delve (T_AndIf id t u) = d2 t u $ T_AndIf id
|
||||||
|
delve (T_OrIf id t u) = d2 t u $ T_OrIf id
|
||||||
|
delve (T_Backgrounded id l) = d1 l $ T_Backgrounded id
|
||||||
|
delve (T_Subshell id l) = dl l $ T_Subshell id
|
||||||
|
delve (T_ProcSub id typ l) = dl l $ T_ProcSub id typ
|
||||||
|
delve (T_Arithmetic id c) = d1 c $ T_Arithmetic id
|
||||||
|
delve (T_IfExpression id conditions elses) = do
|
||||||
|
newConds <- mapM (\(c, t) -> do
|
||||||
|
x <- mapM round c
|
||||||
|
y <- mapM round t
|
||||||
|
return (x,y)
|
||||||
|
) conditions
|
||||||
|
newElses <- roundAll elses
|
||||||
|
return $ T_IfExpression id newConds newElses
|
||||||
|
delve (T_BraceGroup id l) = dl l $ T_BraceGroup id
|
||||||
|
delve (T_WhileExpression id c l) = dll c l $ T_WhileExpression id
|
||||||
|
delve (T_UntilExpression id c l) = dll c l $ T_UntilExpression id
|
||||||
|
delve (T_ForIn id t v w l) = dll w l $ T_ForIn id t v
|
||||||
|
delve (T_SelectIn id v w l) = dll w l $ T_SelectIn id v
|
||||||
|
delve (T_CaseExpression id word cases) = do
|
||||||
|
newWord <- round word
|
||||||
|
newCases <- mapM (\(c, t) -> do
|
||||||
|
x <- mapM round c
|
||||||
|
y <- mapM round t
|
||||||
|
return (x,y)
|
||||||
|
) cases
|
||||||
|
return $ T_CaseExpression id newWord newCases
|
||||||
|
|
||||||
|
delve (T_ForArithmetic id a b c group) = do
|
||||||
|
x <- round a
|
||||||
|
y <- round b
|
||||||
|
z <- round c
|
||||||
|
list <- mapM round group
|
||||||
|
return $ T_ForArithmetic id x y z list
|
||||||
|
|
||||||
|
delve (T_Script id s l) = dl l $ T_Script id s
|
||||||
|
delve (T_Function id a b name body) = d1 body $ T_Function id a b name
|
||||||
|
delve (T_Condition id typ token) = d1 token $ T_Condition id typ
|
||||||
|
delve (T_Extglob id str l) = dl l $ T_Extglob id str
|
||||||
|
delve (T_DollarBraced id op) = d1 op $ T_DollarBraced id
|
||||||
|
delve (T_HereDoc id d q str l) = dl l $ T_HereDoc id d q str
|
||||||
|
|
||||||
|
delve (TC_And id typ str t1 t2) = d2 t1 t2 $ TC_And id typ str
|
||||||
|
delve (TC_Or id typ str t1 t2) = d2 t1 t2 $ TC_Or id typ str
|
||||||
|
delve (TC_Group id typ token) = d1 token $ TC_Group id typ
|
||||||
|
delve (TC_Binary id typ op lhs rhs) = d2 lhs rhs $ TC_Binary id typ op
|
||||||
|
delve (TC_Unary id typ op token) = d1 token $ TC_Unary id typ op
|
||||||
|
delve (TC_Noary id typ token) = d1 token $ TC_Noary id typ
|
||||||
|
|
||||||
|
delve (TA_Binary id op t1 t2) = d2 t1 t2 $ TA_Binary id op
|
||||||
|
delve (TA_Unary id op t1) = d1 t1 $ TA_Unary id op
|
||||||
|
delve (TA_Sequence id l) = dl l $ TA_Sequence id
|
||||||
|
delve (TA_Trinary id t1 t2 t3) = do
|
||||||
|
a <- round t1
|
||||||
|
b <- round t2
|
||||||
|
c <- round t3
|
||||||
|
return $ TA_Trinary id a b c
|
||||||
|
delve (TA_Expansion id t) = d1 t $ TA_Expansion id
|
||||||
|
delve (TA_Base id b t) = d1 t $ TA_Base id b
|
||||||
|
delve (T_Annotation id anns t) = d1 t $ T_Annotation id anns
|
||||||
|
delve t = return t
|
||||||
|
|
||||||
|
getId t = case t of
|
||||||
|
T_AND_IF id -> id
|
||||||
|
T_OR_IF id -> id
|
||||||
|
T_DSEMI id -> id
|
||||||
|
T_Semi id -> id
|
||||||
|
T_DLESS id -> id
|
||||||
|
T_DGREAT id -> id
|
||||||
|
T_LESSAND id -> id
|
||||||
|
T_GREATAND id -> id
|
||||||
|
T_LESSGREAT id -> id
|
||||||
|
T_DLESSDASH id -> id
|
||||||
|
T_CLOBBER id -> id
|
||||||
|
T_If id -> id
|
||||||
|
T_Then id -> id
|
||||||
|
T_Else id -> id
|
||||||
|
T_Elif id -> id
|
||||||
|
T_Fi id -> id
|
||||||
|
T_Do id -> id
|
||||||
|
T_Done id -> id
|
||||||
|
T_Case id -> id
|
||||||
|
T_Esac id -> id
|
||||||
|
T_While id -> id
|
||||||
|
T_Until id -> id
|
||||||
|
T_For id -> id
|
||||||
|
T_Select id -> id
|
||||||
|
T_Lbrace id -> id
|
||||||
|
T_Rbrace id -> id
|
||||||
|
T_Lparen id -> id
|
||||||
|
T_Rparen id -> id
|
||||||
|
T_Bang id -> id
|
||||||
|
T_In id -> id
|
||||||
|
T_NEWLINE id -> id
|
||||||
|
T_EOF id -> id
|
||||||
|
T_Less id -> id
|
||||||
|
T_Greater id -> id
|
||||||
|
T_SingleQuoted id _ -> id
|
||||||
|
T_Literal id _ -> id
|
||||||
|
T_NormalWord id _ -> id
|
||||||
|
T_DoubleQuoted id _ -> id
|
||||||
|
T_DollarExpansion id _ -> id
|
||||||
|
T_DollarBraced id _ -> id
|
||||||
|
T_DollarArithmetic id _ -> id
|
||||||
|
T_BraceExpansion id _ -> id
|
||||||
|
T_IoFile id _ _ -> id
|
||||||
|
T_HereDoc id _ _ _ _ -> id
|
||||||
|
T_HereString id _ -> id
|
||||||
|
T_FdRedirect id _ _ -> id
|
||||||
|
T_Assignment id _ _ _ _ -> id
|
||||||
|
T_Array id _ -> id
|
||||||
|
T_Redirecting id _ _ -> id
|
||||||
|
T_SimpleCommand id _ _ -> id
|
||||||
|
T_Pipeline id _ _ -> id
|
||||||
|
T_Banged id _ -> id
|
||||||
|
T_AndIf id _ _ -> id
|
||||||
|
T_OrIf id _ _ -> id
|
||||||
|
T_Backgrounded id _ -> id
|
||||||
|
T_IfExpression id _ _ -> id
|
||||||
|
T_Subshell id _ -> id
|
||||||
|
T_BraceGroup id _ -> id
|
||||||
|
T_WhileExpression id _ _ -> id
|
||||||
|
T_UntilExpression id _ _ -> id
|
||||||
|
T_ForIn id _ _ _ _ -> id
|
||||||
|
T_SelectIn id _ _ _ -> id
|
||||||
|
T_CaseExpression id _ _ -> id
|
||||||
|
T_Function id _ _ _ _ -> id
|
||||||
|
T_Arithmetic id _ -> id
|
||||||
|
T_Script id _ _ -> id
|
||||||
|
T_Condition id _ _ -> id
|
||||||
|
T_Extglob id _ _ -> id
|
||||||
|
T_Backticked id _ -> id
|
||||||
|
TC_And id _ _ _ _ -> id
|
||||||
|
TC_Or id _ _ _ _ -> id
|
||||||
|
TC_Group id _ _ -> id
|
||||||
|
TC_Binary id _ _ _ _ -> id
|
||||||
|
TC_Unary id _ _ _ -> id
|
||||||
|
TC_Noary id _ _ -> id
|
||||||
|
TA_Binary id _ _ _ -> id
|
||||||
|
TA_Unary id _ _ -> id
|
||||||
|
TA_Sequence id _ -> id
|
||||||
|
TA_Variable id _ -> id
|
||||||
|
TA_Trinary id _ _ _ -> id
|
||||||
|
TA_Expansion id _ -> id
|
||||||
|
TA_Literal id _ -> id
|
||||||
|
TA_Base id _ _ -> id
|
||||||
|
T_ProcSub id _ _ -> id
|
||||||
|
T_Glob id _ -> id
|
||||||
|
T_ForArithmetic id _ _ _ _ -> id
|
||||||
|
T_DollarSingleQuoted id _ -> id
|
||||||
|
T_DollarDoubleQuoted id _ -> id
|
||||||
|
T_DollarBracket id _ -> id
|
||||||
|
T_Annotation id _ _ -> id
|
||||||
|
T_Pipe id _ -> id
|
||||||
|
|
||||||
|
blank :: Monad m => Token -> m ()
|
||||||
|
blank = const $ return ()
|
||||||
|
doAnalysis f = analyze f blank id
|
||||||
|
doStackAnalysis startToken endToken = analyze startToken endToken id
|
||||||
|
doTransform i = runIdentity . analyze blank blank i
|
||||||
|
|
||||||
|
isLoop t = case t of
|
||||||
|
T_WhileExpression {} -> True
|
||||||
|
T_UntilExpression {} -> True
|
||||||
|
T_ForIn {} -> True
|
||||||
|
T_ForArithmetic {} -> True
|
||||||
|
T_SelectIn {} -> True
|
||||||
|
_ -> False
|
2817
ShellCheck/Analytics.hs
Normal file
2817
ShellCheck/Analytics.hs
Normal file
File diff suppressed because it is too large
Load Diff
84
ShellCheck/Data.hs
Normal file
84
ShellCheck/Data.hs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
module ShellCheck.Data where
|
||||||
|
|
||||||
|
shellcheckVersion = "0.3.3" -- Must also be updated in ShellCheck.cabal
|
||||||
|
|
||||||
|
internalVariables = [
|
||||||
|
-- Generic
|
||||||
|
"", "_", "rest", "REST",
|
||||||
|
|
||||||
|
-- Bash
|
||||||
|
"BASH", "BASHOPTS", "BASHPID", "BASH_ALIASES", "BASH_ARGC",
|
||||||
|
"BASH_ARGV", "BASH_CMDS", "BASH_COMMAND", "BASH_EXECUTION_STRING",
|
||||||
|
"BASH_LINENO", "BASH_REMATCH", "BASH_SOURCE", "BASH_SUBSHELL",
|
||||||
|
"BASH_VERSINFO", "BASH_VERSION", "COMP_CWORD", "COMP_KEY",
|
||||||
|
"COMP_LINE", "COMP_POINT", "COMP_TYPE", "COMP_WORDBREAKS",
|
||||||
|
"COMP_WORDS", "COPROC", "DIRSTACK", "EUID", "FUNCNAME", "GROUPS",
|
||||||
|
"HISTCMD", "HOSTNAME", "HOSTTYPE", "LINENO", "MACHTYPE", "MAPFILE",
|
||||||
|
"OLDPWD", "OPTARG", "OPTIND", "OSTYPE", "PIPESTATUS", "PPID", "PWD",
|
||||||
|
"RANDOM", "READLINE_LINE", "READLINE_POINT", "REPLY", "SECONDS",
|
||||||
|
"SHELLOPTS", "SHLVL", "UID", "BASH_ENV", "BASH_XTRACEFD", "CDPATH",
|
||||||
|
"COLUMNS", "COMPREPLY", "EMACS", "ENV", "FCEDIT", "FIGNORE",
|
||||||
|
"FUNCNEST", "GLOBIGNORE", "HISTCONTROL", "HISTFILE", "HISTFILESIZE",
|
||||||
|
"HISTIGNORE", "HISTSIZE", "HISTTIMEFORMAT", "HOME", "HOSTFILE", "IFS",
|
||||||
|
"IGNOREEOF", "INPUTRC", "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
|
||||||
|
"LC_MESSAGES", "LC_NUMERIC", "LINES", "MAIL", "MAILCHECK", "MAILPATH",
|
||||||
|
"OPTERR", "PATH", "POSIXLY_CORRECT", "PROMPT_COMMAND",
|
||||||
|
"PROMPT_DIRTRIM", "PS1", "PS2", "PS3", "PS4", "SHELL", "TIMEFORMAT",
|
||||||
|
"TMOUT", "TMPDIR", "auto_resume", "histchars",
|
||||||
|
|
||||||
|
-- Zsh
|
||||||
|
"ARGV0", "BAUD", "cdpath", "COLUMNS", "CORRECT_IGNORE",
|
||||||
|
"DIRSTACKSIZE", "ENV", "FCEDIT", "fignore", "fpath", "histchars",
|
||||||
|
"HISTCHARS", "HISTFILE", "HISTSIZE", "HOME", "IFS", "KEYBOARD_HACK",
|
||||||
|
"KEYTIMEOUT", "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
|
||||||
|
"LC_MESSAGES", "LC_NUMERIC", "LC_TIME", "LINES", "LISTMAX",
|
||||||
|
"LOGCHECK", "MAIL", "MAILCHECK", "mailpath", "manpath", "module_path",
|
||||||
|
"NULLCMD", "path", "POSTEDIT", "PROMPT", "PROMPT2", "PROMPT3",
|
||||||
|
"PROMPT4", "prompt", "PROMPT_EOL_MARK", "PS1", "PS2", "PS3", "PS4",
|
||||||
|
"psvar", "READNULLCMD", "REPORTTIME", "REPLY", "reply", "RPROMPT",
|
||||||
|
"RPS1", "RPROMPT2", "RPS2", "SAVEHIST", "SPROMPT", "STTY", "TERM",
|
||||||
|
"TERMINFO", "TIMEFMT", "TMOUT", "TMPPREFIX", "watch", "WATCHFMT",
|
||||||
|
"WORDCHARS", "ZBEEP", "ZDOTDIR", "ZLE_LINE_ABORTED",
|
||||||
|
"ZLE_REMOVE_SUFFIX_CHARS", "ZLE_SPACE_SUFFIX_CHARS"
|
||||||
|
]
|
||||||
|
|
||||||
|
variablesWithoutSpaces = [
|
||||||
|
"$", "-", "?", "!",
|
||||||
|
"BASHPID", "BASH_ARGC", "BASH_LINENO", "BASH_SUBSHELL", "EUID", "LINENO",
|
||||||
|
"OPTIND", "PPID", "RANDOM", "SECONDS", "SHELLOPTS", "SHLVL", "UID",
|
||||||
|
"COLUMNS", "HISTFILESIZE", "HISTSIZE", "LINES"
|
||||||
|
]
|
||||||
|
|
||||||
|
commonCommands = [
|
||||||
|
"admin", "alias", "ar", "asa", "at", "awk", "basename", "batch",
|
||||||
|
"bc", "bg", "break", "c99", "cal", "cat", "cd", "cflow", "chgrp",
|
||||||
|
"chmod", "chown", "cksum", "cmp", "colon", "comm", "command",
|
||||||
|
"compress", "continue", "cp", "crontab", "csplit", "ctags", "cut",
|
||||||
|
"cxref", "date", "dd", "delta", "df", "diff", "dirname", "dot",
|
||||||
|
"du", "echo", "ed", "env", "eval", "ex", "exec", "exit", "expand",
|
||||||
|
"export", "expr", "fc", "fg", "file", "find", "fold", "fort77",
|
||||||
|
"fuser", "gencat", "get", "getconf", "getopts", "grep", "hash",
|
||||||
|
"head", "iconv", "ipcrm", "ipcs", "jobs", "join", "kill", "lex",
|
||||||
|
"link", "ln", "locale", "localedef", "logger", "logname", "lp",
|
||||||
|
"ls", "m4", "mailx", "make", "man", "mesg", "mkdir", "mkfifo",
|
||||||
|
"more", "mv", "newgrp", "nice", "nl", "nm", "nohup", "od", "paste",
|
||||||
|
"patch", "pathchk", "pax", "pr", "printf", "prs", "ps", "pwd",
|
||||||
|
"qalter", "qdel", "qhold", "qmove", "qmsg", "qrerun", "qrls",
|
||||||
|
"qselect", "qsig", "qstat", "qsub", "read", "readonly", "renice",
|
||||||
|
"return", "rm", "rmdel", "rmdir", "sact", "sccs", "sed", "set",
|
||||||
|
"sh", "shift", "sleep", "sort", "split", "strings", "strip", "stty",
|
||||||
|
"tabs", "tail", "talk", "tee", "test", "time", "times", "touch",
|
||||||
|
"tput", "tr", "trap", "tsort", "tty", "type", "ulimit", "umask",
|
||||||
|
"unalias", "uname", "uncompress", "unexpand", "unget", "uniq",
|
||||||
|
"unlink", "unset", "uucp", "uudecode", "uuencode", "uustat", "uux",
|
||||||
|
"val", "vi", "wait", "wc", "what", "who", "write", "xargs", "yacc",
|
||||||
|
"zcat"
|
||||||
|
]
|
||||||
|
|
||||||
|
sampleWords = [
|
||||||
|
"alpha", "bravo", "charlie", "delta", "echo", "foxtrot",
|
||||||
|
"golf", "hotel", "india", "juliett", "kilo", "lima", "mike",
|
||||||
|
"november", "oscar", "papa", "quebec", "romeo", "sierra",
|
||||||
|
"tango", "uniform", "victor", "whiskey", "xray", "yankee",
|
||||||
|
"zulu"
|
||||||
|
]
|
2074
ShellCheck/Parser.hs
Normal file
2074
ShellCheck/Parser.hs
Normal file
File diff suppressed because it is too large
Load Diff
67
ShellCheck/Simple.hs
Normal file
67
ShellCheck/Simple.hs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{-
|
||||||
|
This file is part of ShellCheck.
|
||||||
|
http://www.vidarholen.net/contents/shellcheck
|
||||||
|
|
||||||
|
ShellCheck is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
ShellCheck 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-}
|
||||||
|
module ShellCheck.Simple (shellCheck, ShellCheckComment, scLine, scColumn, scSeverity, scCode, scMessage) where
|
||||||
|
|
||||||
|
import ShellCheck.Parser
|
||||||
|
import ShellCheck.Analytics
|
||||||
|
import Data.Maybe
|
||||||
|
import Text.Parsec.Pos
|
||||||
|
import Data.List
|
||||||
|
|
||||||
|
|
||||||
|
prop_findsParseIssue =
|
||||||
|
let comments = shellCheck "echo \"$12\"" [] in
|
||||||
|
(length comments) == 1 && (scCode $ head comments) == 1037
|
||||||
|
prop_commentDisablesParseIssue1 =
|
||||||
|
null $ shellCheck "#shellcheck disable=SC1037\necho \"$12\"" []
|
||||||
|
prop_commentDisablesParseIssue2 =
|
||||||
|
null $ shellCheck "#shellcheck disable=SC1037\n#lol\necho \"$12\"" []
|
||||||
|
|
||||||
|
prop_findsAnalysisIssue =
|
||||||
|
let comments = shellCheck "echo $1" [] in
|
||||||
|
(length comments) == 1 && (scCode $ head comments) == 2086
|
||||||
|
prop_commentDisablesAnalysisIssue1 =
|
||||||
|
null $ shellCheck "#shellcheck disable=SC2086\necho $1" []
|
||||||
|
prop_commentDisablesAnalysisIssue2 =
|
||||||
|
null $ shellCheck "#shellcheck disable=SC2086\n#lol\necho $1" []
|
||||||
|
|
||||||
|
shellCheck :: String -> [AnalysisOption] -> [ShellCheckComment]
|
||||||
|
shellCheck script options =
|
||||||
|
let (ParseResult result notes) = parseShell "-" script in
|
||||||
|
let allNotes = notes ++ (concat $ maybeToList $ do
|
||||||
|
(tree, posMap) <- result
|
||||||
|
let list = runAnalytics options tree
|
||||||
|
return $ map (noteToParseNote posMap) $ filterByAnnotation tree list
|
||||||
|
)
|
||||||
|
in
|
||||||
|
map formatNote $ nub $ sortNotes allNotes
|
||||||
|
|
||||||
|
data ShellCheckComment = ShellCheckComment { scLine :: Int, scColumn :: Int, scSeverity :: String, scCode :: Int, scMessage :: String }
|
||||||
|
|
||||||
|
instance Show ShellCheckComment where
|
||||||
|
show c = concat ["(", show $ scLine c, ",", show $ scColumn c, ") ", scSeverity c, ": ", show (scCode c), " ", scMessage c]
|
||||||
|
|
||||||
|
severityToString s =
|
||||||
|
case s of
|
||||||
|
ErrorC -> "error"
|
||||||
|
WarningC -> "warning"
|
||||||
|
InfoC -> "info"
|
||||||
|
StyleC -> "style"
|
||||||
|
|
||||||
|
formatNote (ParseNote pos severity code text) =
|
||||||
|
ShellCheckComment (sourceLine pos) (sourceColumn pos) (severityToString severity) (fromIntegral code) text
|
121
shellcheck.1.md
Normal file
121
shellcheck.1.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
% SHELLCHECK(1) Shell script analysis tool
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
|
||||||
|
shellcheck - Shell script analysis tool
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
|
||||||
|
**shellcheck** [*OPTIONS*...] *FILES*...
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
ShellCheck is a static analysis and linting tool for sh/bash scripts. It's
|
||||||
|
mainly focused on handling typical beginner and intermediate level syntax
|
||||||
|
errors and pitfalls where the shell just gives a cryptic error message or
|
||||||
|
strange behavior, but it also reports on a few more advanced issues where
|
||||||
|
corner cases can cause delayed failures.
|
||||||
|
|
||||||
|
# OPTIONS
|
||||||
|
|
||||||
|
**-f** *FORMAT*, **--format=***FORMAT*
|
||||||
|
|
||||||
|
: Specify the output format of shellcheck, which prints its results in the
|
||||||
|
standard output. Subsequent **-f** options are ignored, see **FORMATS**
|
||||||
|
below for more information.
|
||||||
|
|
||||||
|
**-e**\ *CODE1*[,*CODE2*...],\ **--exclude=***CODE1*[,*CODE2*...]
|
||||||
|
|
||||||
|
: Explicitly exclude the specified codes from the report. Subsequent **-e**
|
||||||
|
options are cumulative, but all the codes can be specified at once,
|
||||||
|
comma-separated as a single argument.
|
||||||
|
|
||||||
|
**-s**\ *shell*,\ **--shell=***shell*
|
||||||
|
|
||||||
|
: Specify Bourne shell dialect. Valid values are *sh*, *bash*, *ksh* and
|
||||||
|
*zsh*. The default is to use the file's shebang, or *bash* if the target
|
||||||
|
shell can't be determined.
|
||||||
|
|
||||||
|
# FORMATS
|
||||||
|
|
||||||
|
**tty**
|
||||||
|
|
||||||
|
: Plain text, human readable output. This is the default.
|
||||||
|
|
||||||
|
**gcc**
|
||||||
|
|
||||||
|
: GCC compatible output. Useful for editors that support compiling and
|
||||||
|
showing syntax errors.
|
||||||
|
|
||||||
|
For example, in Vim, `:set makeprg=shellcheck\ -f\ gcc\ %` will allow
|
||||||
|
using `:make` to check the script, and `:cnext` to jump to the next error.
|
||||||
|
|
||||||
|
<file>:<line>:<column>: <type>: <message>
|
||||||
|
|
||||||
|
**checkstyle**
|
||||||
|
|
||||||
|
: Checkstyle compatible XML output. Supported directly or through plugins
|
||||||
|
by many IDEs and build monitoring systems.
|
||||||
|
|
||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<checkstyle version='4.3'>
|
||||||
|
<file name='file'>
|
||||||
|
<error
|
||||||
|
line='line'
|
||||||
|
column='column'
|
||||||
|
severity='severity'
|
||||||
|
message='message'
|
||||||
|
source='ShellCheck.SC####' />
|
||||||
|
...
|
||||||
|
</file>
|
||||||
|
...
|
||||||
|
</checkstyle>
|
||||||
|
|
||||||
|
**json**
|
||||||
|
|
||||||
|
: Json is a popular serialization format that is more suitable for web
|
||||||
|
applications. ShellCheck's json is compact and contains only the bare
|
||||||
|
minimum.
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"line": line,
|
||||||
|
"column": column,
|
||||||
|
"level": level,
|
||||||
|
"code": ####,
|
||||||
|
"message": message
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
|
||||||
|
# DIRECTIVES
|
||||||
|
ShellCheck directives can be specified as comments in the shell script
|
||||||
|
before a command or block:
|
||||||
|
|
||||||
|
# shellcheck key=value key=value
|
||||||
|
command-or-structure
|
||||||
|
|
||||||
|
For example, to suppress SC2035 about using `./*.jpg`:
|
||||||
|
|
||||||
|
# shellcheck disable=SC2035
|
||||||
|
echo "Files: " *.jpg
|
||||||
|
|
||||||
|
Valid keys are:
|
||||||
|
|
||||||
|
**disable**
|
||||||
|
: Disables a comma separated list of error codes for the following command.
|
||||||
|
The command can be a simple command like `echo foo`, or a compound command
|
||||||
|
like a function definition, subshell block or loop.
|
||||||
|
|
||||||
|
|
||||||
|
# AUTHOR
|
||||||
|
ShellCheck is written and maintained by Vidar Holen.
|
||||||
|
|
||||||
|
# REPORTING BUGS
|
||||||
|
Bugs and issues can be reported on GitHub:
|
||||||
|
|
||||||
|
https://github.com/koalaman/shellcheck/issues
|
||||||
|
|
||||||
|
# SEE ALSO
|
||||||
|
|
||||||
|
sh(1) bash(1)
|
301
shellcheck.hs
Normal file
301
shellcheck.hs
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
{-
|
||||||
|
This file is part of ShellCheck.
|
||||||
|
http://www.vidarholen.net/contents/shellcheck
|
||||||
|
|
||||||
|
ShellCheck is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
ShellCheck 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-}
|
||||||
|
import Control.Exception
|
||||||
|
import Control.Monad
|
||||||
|
import Data.Char
|
||||||
|
import Data.Maybe
|
||||||
|
import GHC.Exts
|
||||||
|
import GHC.IO.Device
|
||||||
|
import Prelude hiding (catch)
|
||||||
|
import ShellCheck.Data
|
||||||
|
import ShellCheck.Simple
|
||||||
|
import ShellCheck.Analytics
|
||||||
|
import System.Console.GetOpt
|
||||||
|
import System.Directory
|
||||||
|
import System.Environment
|
||||||
|
import System.Exit
|
||||||
|
import System.IO
|
||||||
|
import Text.JSON
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
|
data Flag = Flag String String
|
||||||
|
|
||||||
|
header = "Usage: shellcheck [OPTIONS...] FILES..."
|
||||||
|
options = [
|
||||||
|
Option ['f'] ["format"]
|
||||||
|
(ReqArg (Flag "format") "FORMAT") "output format",
|
||||||
|
Option ['e'] ["exclude"]
|
||||||
|
(ReqArg (Flag "exclude") "CODE1,CODE2..") "exclude types of warnings",
|
||||||
|
Option ['s'] ["shell"]
|
||||||
|
(ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh,zsh)",
|
||||||
|
Option ['V'] ["version"]
|
||||||
|
(NoArg $ Flag "version" "true") "Print version information"
|
||||||
|
]
|
||||||
|
|
||||||
|
printErr = hPutStrLn stderr
|
||||||
|
|
||||||
|
syntaxFailure = ExitFailure 3
|
||||||
|
supportFailure = ExitFailure 4
|
||||||
|
|
||||||
|
instance JSON ShellCheckComment where
|
||||||
|
showJSON c = makeObj [
|
||||||
|
("line", showJSON $ scLine c),
|
||||||
|
("column", showJSON $ scColumn c),
|
||||||
|
("level", showJSON $ scSeverity c),
|
||||||
|
("code", showJSON $ scCode c),
|
||||||
|
("message", showJSON $ scMessage c)
|
||||||
|
]
|
||||||
|
readJSON = undefined
|
||||||
|
|
||||||
|
parseArguments argv =
|
||||||
|
case getOpt Permute options argv of
|
||||||
|
(opts, files, []) -> do
|
||||||
|
verifyOptions opts files
|
||||||
|
return $ Just (opts, files)
|
||||||
|
|
||||||
|
(_, _, errors) -> do
|
||||||
|
printErr $ concat errors ++ "\n" ++ usageInfo header options
|
||||||
|
exitWith syntaxFailure
|
||||||
|
|
||||||
|
formats = Map.fromList [
|
||||||
|
("json", forJson),
|
||||||
|
("gcc", forGcc),
|
||||||
|
("checkstyle", forCheckstyle),
|
||||||
|
("tty", forTty)
|
||||||
|
]
|
||||||
|
|
||||||
|
forTty options files = do
|
||||||
|
output <- mapM doFile files
|
||||||
|
return $ and output
|
||||||
|
where
|
||||||
|
clear = ansi 0
|
||||||
|
ansi n = "\x1B[" ++ show n ++ "m"
|
||||||
|
|
||||||
|
colorForLevel "error" = 31 -- red
|
||||||
|
colorForLevel "warning" = 33 -- yellow
|
||||||
|
colorForLevel "info" = 32 -- green
|
||||||
|
colorForLevel "style" = 32 -- green
|
||||||
|
colorForLevel "message" = 1 -- bold
|
||||||
|
colorForLevel "source" = 0 -- none
|
||||||
|
colorForLevel _ = 0 -- none
|
||||||
|
|
||||||
|
colorComment level comment =
|
||||||
|
ansi (colorForLevel level) ++ comment ++ clear
|
||||||
|
|
||||||
|
doFile path = do
|
||||||
|
contents <- readContents path
|
||||||
|
doInput path contents
|
||||||
|
|
||||||
|
doInput filename contents = do
|
||||||
|
let fileLines = lines contents
|
||||||
|
let lineCount = length fileLines
|
||||||
|
let comments = getComments options contents
|
||||||
|
let groups = groupWith scLine comments
|
||||||
|
colorFunc <- getColorFunc
|
||||||
|
mapM_ (\x -> do
|
||||||
|
let lineNum = scLine (head x)
|
||||||
|
let line = if lineNum < 1 || lineNum > lineCount
|
||||||
|
then ""
|
||||||
|
else fileLines !! (lineNum - 1)
|
||||||
|
putStrLn ""
|
||||||
|
putStrLn $ colorFunc "message"
|
||||||
|
("In " ++ filename ++" line " ++ show lineNum ++ ":")
|
||||||
|
putStrLn (colorFunc "source" line)
|
||||||
|
mapM_ (\c -> putStrLn (colorFunc (scSeverity c) $ cuteIndent c)) x
|
||||||
|
putStrLn ""
|
||||||
|
) groups
|
||||||
|
return $ null comments
|
||||||
|
|
||||||
|
cuteIndent comment =
|
||||||
|
replicate (scColumn comment - 1) ' ' ++
|
||||||
|
"^-- " ++ code (scCode comment) ++ ": " ++ scMessage comment
|
||||||
|
|
||||||
|
code code = "SC" ++ (show code)
|
||||||
|
|
||||||
|
getColorFunc = do
|
||||||
|
term <- hIsTerminalDevice stdout
|
||||||
|
return $ if term then colorComment else const id
|
||||||
|
|
||||||
|
-- This totally ignores the filenames. Fixme?
|
||||||
|
forJson options files = do
|
||||||
|
comments <- liftM concat $ mapM (commentsFor options) files
|
||||||
|
putStrLn $ encodeStrict comments
|
||||||
|
return . null $ comments
|
||||||
|
|
||||||
|
-- Mimic GCC "file:line:col: (error|warning|note): message" format
|
||||||
|
forGcc options files = do
|
||||||
|
files <- mapM process files
|
||||||
|
return $ and files
|
||||||
|
where
|
||||||
|
process file = do
|
||||||
|
contents <- readContents file
|
||||||
|
let comments = makeNonVirtual (getComments options contents) contents
|
||||||
|
mapM_ (putStrLn . format file) comments
|
||||||
|
return $ null comments
|
||||||
|
|
||||||
|
format filename c = concat [
|
||||||
|
filename, ":",
|
||||||
|
show $ scLine c, ":",
|
||||||
|
show $ scColumn c, ": ",
|
||||||
|
case scSeverity c of
|
||||||
|
"error" -> "error"
|
||||||
|
"warning" -> "warning"
|
||||||
|
_ -> "note",
|
||||||
|
": ",
|
||||||
|
concat . lines $ scMessage c,
|
||||||
|
" [SC", show $ scCode c, "]"
|
||||||
|
]
|
||||||
|
|
||||||
|
-- Checkstyle compatible output. A bit of a hack to avoid XML dependencies
|
||||||
|
forCheckstyle options files = do
|
||||||
|
putStrLn "<?xml version='1.0' encoding='UTF-8'?>"
|
||||||
|
putStrLn "<checkstyle version='4.3'>"
|
||||||
|
statuses <- mapM (\x -> process x `catch` report) files
|
||||||
|
putStrLn "</checkstyle>"
|
||||||
|
return $ and statuses
|
||||||
|
where
|
||||||
|
process file = do
|
||||||
|
comments <- commentsFor options file
|
||||||
|
putStrLn (formatFile file comments)
|
||||||
|
return $ null comments
|
||||||
|
report error = do
|
||||||
|
printErr $ show (error :: SomeException)
|
||||||
|
return False
|
||||||
|
|
||||||
|
severity "error" = "error"
|
||||||
|
severity "warning" = "warning"
|
||||||
|
severity _ = "info"
|
||||||
|
attr s v = concat [ s, "='", escape v, "' " ]
|
||||||
|
escape = concatMap escape'
|
||||||
|
escape' c = if isOk c then [c] else "&#" ++ show (ord c) ++ ";"
|
||||||
|
isOk x = any ($x) [isAsciiUpper, isAsciiLower, isDigit, (`elem` " ./")]
|
||||||
|
|
||||||
|
formatFile name comments = concat [
|
||||||
|
"<file ", attr "name" name, ">\n",
|
||||||
|
concatMap format comments,
|
||||||
|
"</file>"
|
||||||
|
]
|
||||||
|
|
||||||
|
format c = concat [
|
||||||
|
"<error ",
|
||||||
|
attr "line" $ show . scLine $ c,
|
||||||
|
attr "column" $ show . scColumn $ c,
|
||||||
|
attr "severity" $ severity . scSeverity $ c,
|
||||||
|
attr "message" $ scMessage c,
|
||||||
|
attr "source" $ "ShellCheck.SC" ++ (show $ scCode c),
|
||||||
|
"/>\n"
|
||||||
|
]
|
||||||
|
|
||||||
|
commentsFor options file =
|
||||||
|
liftM (getComments options) $ readContents file
|
||||||
|
|
||||||
|
getComments options contents =
|
||||||
|
excludeCodes (getExclusions options) $ shellCheck contents analysisOptions
|
||||||
|
where
|
||||||
|
analysisOptions = catMaybes [ shellOption ]
|
||||||
|
shellOption = do
|
||||||
|
option <- getOption options "shell"
|
||||||
|
sh <- shellForExecutable option
|
||||||
|
return $ ForceShell sh
|
||||||
|
|
||||||
|
|
||||||
|
readContents file = if file == "-" then getContents else readFile file
|
||||||
|
|
||||||
|
-- Realign comments from a tabstop of 8 to 1
|
||||||
|
makeNonVirtual comments contents =
|
||||||
|
map fix comments
|
||||||
|
where
|
||||||
|
ls = lines contents
|
||||||
|
fix c = c { scColumn = real (ls !! (scLine c - 1)) 0 0 (scColumn c) }
|
||||||
|
real _ r v target | target <= v = r
|
||||||
|
real [] r v _ = r -- should never happen
|
||||||
|
real ('\t':rest) r v target =
|
||||||
|
real rest (r+1) (v + 8 - (v `mod` 8)) target
|
||||||
|
real (_:rest) r v target = real rest (r+1) (v+1) target
|
||||||
|
|
||||||
|
getOption [] _ = Nothing
|
||||||
|
getOption (Flag var val:_) name | name == var = return val
|
||||||
|
getOption (_:rest) flag = getOption rest flag
|
||||||
|
|
||||||
|
getOptions options name =
|
||||||
|
map (\(Flag _ val) -> val) . filter (\(Flag var _) -> var == name) $ options
|
||||||
|
|
||||||
|
split char str =
|
||||||
|
split' str []
|
||||||
|
where
|
||||||
|
split' (a:rest) element =
|
||||||
|
if a == char
|
||||||
|
then (reverse element) : split' rest []
|
||||||
|
else split' rest (a:element)
|
||||||
|
split' [] element = [reverse element]
|
||||||
|
|
||||||
|
getExclusions options =
|
||||||
|
let elements = concatMap (split ',') $ getOptions options "exclude"
|
||||||
|
clean = dropWhile (not . isDigit)
|
||||||
|
in
|
||||||
|
map (Prelude.read . clean) elements :: [Int]
|
||||||
|
|
||||||
|
excludeCodes codes =
|
||||||
|
filter (not . hasCode)
|
||||||
|
where
|
||||||
|
hasCode c = scCode c `elem` codes
|
||||||
|
|
||||||
|
main = do
|
||||||
|
args <- getArgs
|
||||||
|
parsedArgs <- parseArguments args
|
||||||
|
code <- do
|
||||||
|
status <- process parsedArgs
|
||||||
|
return $ if status then ExitSuccess else ExitFailure 1
|
||||||
|
`catch` return
|
||||||
|
`catch` \err -> do
|
||||||
|
printErr $ show (err :: SomeException)
|
||||||
|
return $ ExitFailure 2
|
||||||
|
exitWith code
|
||||||
|
|
||||||
|
process Nothing = return False
|
||||||
|
process (Just (options, files)) =
|
||||||
|
let format = fromMaybe "tty" $ getOption options "format" in
|
||||||
|
case Map.lookup format formats of
|
||||||
|
Nothing -> do
|
||||||
|
printErr $ "Unknown format " ++ format
|
||||||
|
printErr $ "Supported formats:"
|
||||||
|
mapM_ (printErr . write) $ Map.keys formats
|
||||||
|
exitWith supportFailure
|
||||||
|
where write s = " " ++ s
|
||||||
|
Just f -> do
|
||||||
|
f options files
|
||||||
|
|
||||||
|
verifyOptions opts files = do
|
||||||
|
when (isJust $ getOption opts "version") printVersionAndExit
|
||||||
|
|
||||||
|
let shell = getOption opts "shell" in
|
||||||
|
when (isJust shell && isNothing (shell >>= shellForExecutable)) $ do
|
||||||
|
printErr $ "Unknown shell: " ++ (fromJust shell)
|
||||||
|
exitWith supportFailure
|
||||||
|
|
||||||
|
when (null files) $ do
|
||||||
|
printErr "No files specified.\n"
|
||||||
|
printErr $ usageInfo header options
|
||||||
|
exitWith syntaxFailure
|
||||||
|
|
||||||
|
printVersionAndExit = do
|
||||||
|
putStrLn $ "ShellCheck - shell script analysis tool"
|
||||||
|
putStrLn $ "version: " ++ shellcheckVersion
|
||||||
|
putStrLn $ "license: GNU Affero General Public License, version 3"
|
||||||
|
putStrLn $ "website: http://www.shellcheck.net"
|
||||||
|
exitWith ExitSuccess
|
934
shpell.hs
934
shpell.hs
@@ -1,934 +0,0 @@
|
|||||||
{-# LANGUAGE NoMonomorphismRestriction #-}
|
|
||||||
|
|
||||||
-- Shpell Check, by Vidar 'koala_man' Holen
|
|
||||||
-- Sorry about the code. It was a week's worth of hacking.
|
|
||||||
|
|
||||||
import Text.Parsec
|
|
||||||
import Text.Parsec.Pos (initialPos)
|
|
||||||
import Debug.Trace
|
|
||||||
import Control.Monad
|
|
||||||
import Data.Char
|
|
||||||
import Data.List (isInfixOf, partition, sortBy, intercalate)
|
|
||||||
import qualified Control.Monad.State as Ms
|
|
||||||
import Data.Maybe
|
|
||||||
import Prelude hiding (readList)
|
|
||||||
import System.IO
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
backslash = char '\\'
|
|
||||||
linefeed = char '\n'
|
|
||||||
singleQuote = char '\''
|
|
||||||
doubleQuote = char '"'
|
|
||||||
variableStart = upper <|> lower <|> oneOf "_"
|
|
||||||
variableChars = upper <|> lower <|> digit <|> oneOf "_"
|
|
||||||
specialVariable = oneOf "@*#?-$!"
|
|
||||||
tokenDelimiter = oneOf "&|;<> \t\n"
|
|
||||||
quotable = oneOf "#|&;<>()$`\\ \"'\t\n"
|
|
||||||
doubleQuotable = oneOf "\"$`"
|
|
||||||
whitespace = oneOf " \t\n"
|
|
||||||
linewhitespace = oneOf " \t"
|
|
||||||
|
|
||||||
spacing = do
|
|
||||||
x <- many (many1 linewhitespace <|> (try $ string "\\\n"))
|
|
||||||
optional readComment
|
|
||||||
return $ concat x
|
|
||||||
|
|
||||||
allspacing = do
|
|
||||||
spacing
|
|
||||||
x <- option False ((linefeed <|> carriageReturn) >> return True)
|
|
||||||
when x allspacing
|
|
||||||
|
|
||||||
carriageReturn = do
|
|
||||||
parseNote ErrorC "Literal carriage return. Run script through tr -d '\\r' "
|
|
||||||
char '\r'
|
|
||||||
|
|
||||||
|
|
||||||
--------- Message/position annotation on top of user state
|
|
||||||
data Annotated a = Annotated SourcePos [Note] a deriving (Show, Eq)
|
|
||||||
data Note = ParseNote SourcePos Severity String | Note Severity String deriving (Show, Eq)
|
|
||||||
data MessageStack = StackNode Note MessageStack | StackMark String SourcePos MessageStack | StackEmpty
|
|
||||||
data ParseProblem = ParseProblem SourcePos Severity String deriving (Show, Eq)
|
|
||||||
data OutputNote = OutputNote SourcePos Severity String deriving (Show, Eq)
|
|
||||||
data Severity = ErrorC | WarningC | InfoC | StyleC deriving (Show, Eq, Ord)
|
|
||||||
|
|
||||||
instance Functor Annotated where
|
|
||||||
fmap f (Annotated p n a) = Annotated p n (f a)
|
|
||||||
|
|
||||||
markStack msg = do
|
|
||||||
pos <- getPosition
|
|
||||||
modifyState (StackMark msg pos)
|
|
||||||
|
|
||||||
getMessages r (StackMark _ _ s) = (r, s)
|
|
||||||
getMessages r (StackNode n s) = getMessages (n:r) s
|
|
||||||
popStack = do
|
|
||||||
f <- getState
|
|
||||||
let (notes, stack) = getMessages [] f
|
|
||||||
putState stack
|
|
||||||
return notes
|
|
||||||
|
|
||||||
-- Store potential parse problems outside of parsec
|
|
||||||
parseProblem level msg = do
|
|
||||||
pos <- getPosition
|
|
||||||
parseProblemAt pos level msg
|
|
||||||
|
|
||||||
parseProblemAt pos level msg = do
|
|
||||||
Ms.modify ((ParseProblem pos level msg):)
|
|
||||||
|
|
||||||
pushNote n = modifyState (StackNode n)
|
|
||||||
|
|
||||||
parseNote l a = do
|
|
||||||
pos <- getPosition
|
|
||||||
parseNoteAt pos l a
|
|
||||||
|
|
||||||
parseNoteAt pos l a = pushNote $ ParseNote pos l a
|
|
||||||
|
|
||||||
|
|
||||||
annotated msg parser = do
|
|
||||||
pos <- getPosition
|
|
||||||
markStack msg
|
|
||||||
result <- parser
|
|
||||||
messages <- popStack
|
|
||||||
return $ Annotated pos messages result
|
|
||||||
|
|
||||||
dropAnnotation (Annotated _ _ s) = s
|
|
||||||
blankAnnotation pos t = Annotated pos [] t
|
|
||||||
|
|
||||||
merge (Annotated pos messages result) = do
|
|
||||||
mapM pushNote messages
|
|
||||||
return result
|
|
||||||
|
|
||||||
merging p = p >>= merge
|
|
||||||
|
|
||||||
getOutputNotes (Annotated p notes _) = map (makeOutputNote p) notes
|
|
||||||
|
|
||||||
makeOutputNote _ (ParseNote p l s) = OutputNote p l s
|
|
||||||
makeOutputNote p (Note l s) = OutputNote p l s
|
|
||||||
|
|
||||||
--------- Convenient combinators
|
|
||||||
|
|
||||||
thenSkip main follow = do
|
|
||||||
r <- main
|
|
||||||
optional follow
|
|
||||||
return r
|
|
||||||
|
|
||||||
disregard x = x >> return ()
|
|
||||||
|
|
||||||
reluctantlyTill p end = do -- parse p until end <|> eof matches ahead
|
|
||||||
(lookAhead ((disregard $ try end) <|> eof) >> return []) <|> do
|
|
||||||
x <- p
|
|
||||||
more <- reluctantlyTill p end
|
|
||||||
return $ x:more
|
|
||||||
<|> return []
|
|
||||||
|
|
||||||
reluctantlyTill1 p end = do
|
|
||||||
notFollowedBy end
|
|
||||||
x <- p
|
|
||||||
more <- reluctantlyTill p end
|
|
||||||
return $ x:more
|
|
||||||
|
|
||||||
attempting rest branch = do
|
|
||||||
((try branch) >> rest) <|> rest
|
|
||||||
|
|
||||||
wasIncluded p = option False (p >> return True)
|
|
||||||
|
|
||||||
-- Horrifying AST
|
|
||||||
data Token = T_AND_IF | T_OR_IF | T_DSEMI | T_Semi | T_DLESS | T_DGREAT | T_LESSAND | T_GREATAND | T_LESSGREAT | T_DLESSDASH | T_CLOBBER | T_If | T_Then | T_Else | T_Elif | T_Fi | T_Do | T_Done | T_Case | T_Esac | T_While | T_Until | T_For | T_Lbrace | T_Rbrace | T_Lparen | T_Rparen | T_Bang | T_In | T_NEWLINE | T_EOF | T_Less | T_Greater | T_SingleQuoted String | T_Literal String | T_NormalWord [Annotated Token] | T_DoubleQuoted [Annotated Token] | T_DollarExpansion [Token] | T_DollarBraced String | T_DollarVariable String | T_DollarArithmetic String | T_BraceExpansion String | T_IoFile Token Token | T_HereDoc Bool Bool String | T_HereString Token | T_FdRedirect String Token | T_Assignment String Token | T_Redirecting [Annotated Token] Token | T_SimpleCommand [Annotated Token] [Annotated Token] | T_Pipeline [Annotated Token] | T_Banged Token | T_AndIf (Annotated Token) (Annotated Token) | T_OrIf (Annotated Token) (Annotated Token) | T_Backgrounded Token | T_IfExpression [([Token],[Token])] [Token] | T_Subshell [Token] | T_BraceGroup [Token] | T_WhileExpression [Token] [Token] | T_UntilExpression [Token] [Token] | T_ForIn String [Token] [Token] | T_CaseExpression Token [([Token],[Token])] |T_Function String Token | T_Command (Annotated Token) | T_Script [Token]
|
|
||||||
deriving (Show)
|
|
||||||
|
|
||||||
extractNotes' list = modifyFlag ((++) $ concatMap getOutputNotes list) >> return ()
|
|
||||||
extractNotes (T_NormalWord list) = extractNotes' list
|
|
||||||
extractNotes (T_DoubleQuoted list) = extractNotes' list
|
|
||||||
extractNotes (T_Redirecting list f) = extractNotes' list
|
|
||||||
extractNotes (T_Pipeline list) = extractNotes' list
|
|
||||||
extractNotes (T_Command list) = extractNotes' [list]
|
|
||||||
extractNotes (T_SimpleCommand list1 list2) = do
|
|
||||||
extractNotes' list1
|
|
||||||
extractNotes' list2
|
|
||||||
extractNotes t = return ()
|
|
||||||
|
|
||||||
|
|
||||||
postMessage level s = Ms.modify $ \(x, l) -> (x, Note level s : l)
|
|
||||||
warn s = postMessage WarningC s
|
|
||||||
inform s = postMessage InfoC s
|
|
||||||
style s = postMessage StyleC s
|
|
||||||
|
|
||||||
|
|
||||||
putFlag v = modifyFlag (const v) >> return ()
|
|
||||||
getFlag = modifyFlag id
|
|
||||||
modifyFlag f = do
|
|
||||||
Ms.modify $ \(x, l) -> (f x, l)
|
|
||||||
v <- Ms.get
|
|
||||||
return $ fst v
|
|
||||||
|
|
||||||
|
|
||||||
analyzeScopes f i = mapM (analyzeScope f i)
|
|
||||||
analyzeScope f i (Annotated pos notes t) = do
|
|
||||||
v <- getFlag
|
|
||||||
let (ret, (flag, list)) = Ms.runState (analyze f i t) (v, [])
|
|
||||||
putFlag flag
|
|
||||||
return $ Annotated pos (notes++list) ret
|
|
||||||
|
|
||||||
analyze f i s@(T_NormalWord list) = do
|
|
||||||
f s
|
|
||||||
a <- analyzeScopes f i list
|
|
||||||
return . i $ T_NormalWord a
|
|
||||||
|
|
||||||
analyze f i s@(T_DoubleQuoted list) = do
|
|
||||||
f s
|
|
||||||
a <- analyzeScopes f i list
|
|
||||||
return . i $ T_DoubleQuoted a
|
|
||||||
|
|
||||||
analyze f i s@(T_DollarExpansion l) = do
|
|
||||||
f s
|
|
||||||
nl <- mapM (analyze f i) l
|
|
||||||
return . i $ T_DollarExpansion nl
|
|
||||||
|
|
||||||
analyze f i s@(T_IoFile op file) = do
|
|
||||||
f s
|
|
||||||
a <- analyze f i op
|
|
||||||
b <- analyze f i file
|
|
||||||
return . i $ T_IoFile a b
|
|
||||||
|
|
||||||
analyze f i s@(T_HereString word) = do
|
|
||||||
f s
|
|
||||||
a <- analyze f i word
|
|
||||||
return . i $ T_HereString a
|
|
||||||
|
|
||||||
analyze f i s@(T_FdRedirect v t) = do
|
|
||||||
f s
|
|
||||||
a <- analyze f i t
|
|
||||||
return . i $ T_FdRedirect v a
|
|
||||||
|
|
||||||
analyze f i s@(T_Assignment v t) = do
|
|
||||||
f s
|
|
||||||
a <- analyze f i t
|
|
||||||
return . i $ T_Assignment v a
|
|
||||||
|
|
||||||
analyze f i s@(T_Redirecting redirs cmd) = do
|
|
||||||
f s
|
|
||||||
newRedirs <- analyzeScopes f i redirs
|
|
||||||
newCmd <- analyze f i $ cmd
|
|
||||||
return . i $ (T_Redirecting newRedirs newCmd)
|
|
||||||
|
|
||||||
analyze f i s@(T_SimpleCommand vars cmds) = do
|
|
||||||
f s
|
|
||||||
a <- analyzeScopes f i vars
|
|
||||||
b <- analyzeScopes f i cmds
|
|
||||||
return . i $ T_SimpleCommand a b
|
|
||||||
|
|
||||||
analyze f i s@(T_Pipeline l) = do
|
|
||||||
f s
|
|
||||||
a <- analyzeScopes f i l
|
|
||||||
return . i $ T_Pipeline a
|
|
||||||
|
|
||||||
analyze f i s@(T_Banged l) = do
|
|
||||||
f s
|
|
||||||
a <- analyze f i l
|
|
||||||
return . i $ T_Banged a
|
|
||||||
|
|
||||||
analyze f i s@(T_AndIf t u) = do
|
|
||||||
f s
|
|
||||||
a <- analyzeScope f i t
|
|
||||||
b <- analyzeScope f i u
|
|
||||||
return . i $ T_AndIf a b
|
|
||||||
|
|
||||||
analyze f i s@(T_OrIf t u) = do
|
|
||||||
f s
|
|
||||||
a <- analyzeScope f i t
|
|
||||||
b <- analyzeScope f i u
|
|
||||||
return . i $ T_OrIf a b
|
|
||||||
|
|
||||||
analyze f i s@(T_Backgrounded l) = do
|
|
||||||
f s
|
|
||||||
a <- analyze f i l
|
|
||||||
return . i $ T_Backgrounded a
|
|
||||||
|
|
||||||
analyze f i s@(T_IfExpression conditions elses) = do
|
|
||||||
f s
|
|
||||||
newConds <- mapM (\(c, t) -> do
|
|
||||||
x <- mapM (analyze f i) c
|
|
||||||
y <- mapM (analyze f i) t
|
|
||||||
return (x, y)
|
|
||||||
) conditions
|
|
||||||
newElses <- mapM (analyze f i) elses
|
|
||||||
return . i $ T_IfExpression newConds newElses
|
|
||||||
|
|
||||||
analyze f i s@(T_Subshell l) = do
|
|
||||||
f s
|
|
||||||
a <- mapM (analyze f i) l
|
|
||||||
return . i $ T_Subshell a
|
|
||||||
|
|
||||||
analyze f i s@(T_BraceGroup l) = do
|
|
||||||
f s
|
|
||||||
a <- mapM (analyze f i) l
|
|
||||||
return . i $ T_BraceGroup a
|
|
||||||
|
|
||||||
analyze f i s@(T_WhileExpression c l) = do
|
|
||||||
f s
|
|
||||||
a <- mapM (analyze f i) c
|
|
||||||
b <- mapM (analyze f i) l
|
|
||||||
return . i $ T_WhileExpression a b
|
|
||||||
|
|
||||||
analyze f i s@(T_UntilExpression c l) = do
|
|
||||||
f s
|
|
||||||
a <- mapM (analyze f i) c
|
|
||||||
b <- mapM (analyze f i) l
|
|
||||||
return . i $ T_UntilExpression a b
|
|
||||||
|
|
||||||
analyze f i s@(T_ForIn v w l) = do
|
|
||||||
f s
|
|
||||||
a <- mapM (analyze f i) w
|
|
||||||
b <- mapM (analyze f i) l
|
|
||||||
return . i $ T_ForIn v a b
|
|
||||||
|
|
||||||
analyze f i s@(T_CaseExpression word cases) = do
|
|
||||||
f s
|
|
||||||
newWord <- analyze f i word
|
|
||||||
newCases <- mapM (\(c, t) -> do
|
|
||||||
x <- mapM (analyze f i) c
|
|
||||||
y <- mapM (analyze f i) t
|
|
||||||
return (x, y)
|
|
||||||
) cases
|
|
||||||
return . i $ T_CaseExpression newWord newCases
|
|
||||||
|
|
||||||
analyze f i s@(T_Script l) = do
|
|
||||||
f s
|
|
||||||
a <- mapM (analyze f i) l
|
|
||||||
return . i $ T_Script a
|
|
||||||
|
|
||||||
analyze f i s@(T_Function name body) = do
|
|
||||||
f s
|
|
||||||
a <- analyze f i body
|
|
||||||
return . i $ T_Function name a
|
|
||||||
|
|
||||||
analyze f i s@(T_Command c) = do
|
|
||||||
f s
|
|
||||||
a <- analyzeScope f i c
|
|
||||||
return . i $ T_Command a
|
|
||||||
|
|
||||||
analyze f i t = do
|
|
||||||
f t
|
|
||||||
return . i $ t
|
|
||||||
|
|
||||||
doAnalysis f t = fst $ Ms.runState (analyze f id t) ((), [])
|
|
||||||
explore f d t = fst . snd $ Ms.runState (analyze f id t) (d, [])
|
|
||||||
transform i t = fst $ Ms.runState (analyze (const $ return ()) i t) ((), [])
|
|
||||||
|
|
||||||
findNotes t = explore extractNotes [] t
|
|
||||||
sortNotes l = sortBy compareNotes l
|
|
||||||
compareNotes (OutputNote pos1 level1 _) (OutputNote pos2 level2 _) = compare (pos1, level1) (pos2, level2)
|
|
||||||
findParseNotes l = map (\(ParseProblem p level s) -> OutputNote p level s) l
|
|
||||||
-- T_UntilExpression [Token] [Token] | T_ForIn String [Token] [Token]
|
|
||||||
|
|
||||||
getNotes s =
|
|
||||||
case rp readScript s of
|
|
||||||
(Right x, p) -> sortNotes $ (findNotes $ doAllAnalysis x) ++ (findParseNotes p)
|
|
||||||
(Left _, p) -> sortNotes $ (OutputNote (initialPos "-") ErrorC "Parsing failed"):(findParseNotes p)
|
|
||||||
|
|
||||||
readComment = do
|
|
||||||
char '#'
|
|
||||||
anyChar `reluctantlyTill` linefeed
|
|
||||||
|
|
||||||
readNormalWord = do
|
|
||||||
x <- many1 readNormalWordPart
|
|
||||||
return $ T_NormalWord x
|
|
||||||
|
|
||||||
readNormalWordPart = readSingleQuoted <|> readDoubleQuoted <|> readDollar <|> readBraced <|> readBackTicked <|> (annotated "normal literal" $ readNormalLiteral)
|
|
||||||
|
|
||||||
readSingleQuoted = annotated "single quoted string" $ do
|
|
||||||
singleQuote
|
|
||||||
s <- readSingleQuotedPart `reluctantlyTill` singleQuote
|
|
||||||
singleQuote <?> "End single quoted string"
|
|
||||||
|
|
||||||
let string = concat s
|
|
||||||
return (T_SingleQuoted string) `attempting` do
|
|
||||||
x <- lookAhead anyChar
|
|
||||||
when (isAlpha x && isAlpha (last string)) $ parseProblem WarningC "This apostrophe terminated the single quoted string."
|
|
||||||
|
|
||||||
readSingleQuotedLiteral = do
|
|
||||||
singleQuote
|
|
||||||
strs <- many1 readSingleQuotedPart
|
|
||||||
singleQuote
|
|
||||||
return $ concat strs
|
|
||||||
|
|
||||||
readSingleQuotedPart =
|
|
||||||
readSingleEscaped
|
|
||||||
<|> anyChar `reluctantlyTill1` (singleQuote <|> backslash)
|
|
||||||
|
|
||||||
readBackTicked = annotated "backtick expansion" $ do
|
|
||||||
parseNote StyleC "`..` style expansion is deprecated, use $(..) instead if you want my help"
|
|
||||||
pos <- getPosition
|
|
||||||
char '`'
|
|
||||||
f <- readGenericLiteral (char '`')
|
|
||||||
char '`' `attempting` (eof >> parseProblemAt pos ErrorC "Can't find terminating backtick for this one")
|
|
||||||
return $ T_Literal f
|
|
||||||
|
|
||||||
|
|
||||||
readDoubleQuoted = annotated "double quoted string" $ do
|
|
||||||
doubleQuote
|
|
||||||
x <- many doubleQuotedPart
|
|
||||||
doubleQuote <?> "End double quoted"
|
|
||||||
return $ T_DoubleQuoted x
|
|
||||||
|
|
||||||
doubleQuotedPart = readDoubleLiteral <|> readDollar <|> readBackTicked
|
|
||||||
|
|
||||||
readDoubleQuotedLiteral = do
|
|
||||||
doubleQuote
|
|
||||||
x <- readDoubleLiteral
|
|
||||||
doubleQuote
|
|
||||||
return $ dropAnnotation x
|
|
||||||
|
|
||||||
readDoubleLiteral = annotated "double literal" $ do
|
|
||||||
s <- many1 readDoubleLiteralPart
|
|
||||||
return $ T_Literal (concat s)
|
|
||||||
|
|
||||||
readDoubleLiteralPart = do
|
|
||||||
x <- (readDoubleEscaped <|> (anyChar >>= \x -> return [x])) `reluctantlyTill1` doubleQuotable
|
|
||||||
return $ concat x
|
|
||||||
|
|
||||||
readNormalLiteral = do
|
|
||||||
s <- many1 readNormalLiteralPart
|
|
||||||
return $ T_Literal (concat s)
|
|
||||||
|
|
||||||
readNormalLiteralPart = do
|
|
||||||
readNormalEscaped <|> (anyChar `reluctantlyTill1` quotable)
|
|
||||||
|
|
||||||
readNormalEscaped = do
|
|
||||||
backslash
|
|
||||||
pos <- getPosition
|
|
||||||
do
|
|
||||||
next <- (quotable <|> oneOf "?*[]")
|
|
||||||
return $ if next == '\n' then "" else [next]
|
|
||||||
<|>
|
|
||||||
do
|
|
||||||
next <- anyChar <?> "No character after \\"
|
|
||||||
parseNoteAt pos WarningC $ "This character doesn't need escaping here, the \\ is ignored"
|
|
||||||
return [next]
|
|
||||||
|
|
||||||
readSingleEscaped = do
|
|
||||||
s <- backslash
|
|
||||||
let attempt level p msg = do { try $ parseNote level msg; x <- p; return [s,x]; }
|
|
||||||
|
|
||||||
do {
|
|
||||||
x <- singleQuote;
|
|
||||||
parseProblem InfoC "Are you trying to escape a single quote? echo 'You'\\''re doing it wrong'.";
|
|
||||||
return [s,x];
|
|
||||||
}
|
|
||||||
<|> attempt InfoC linefeed "You don't break lines with \\ in single quotes, it results in literal backslash-linefeed."
|
|
||||||
<|> do
|
|
||||||
x <- anyChar
|
|
||||||
return [s,x]
|
|
||||||
|
|
||||||
|
|
||||||
readDoubleEscaped = do
|
|
||||||
bs <- backslash
|
|
||||||
(linefeed >> return "")
|
|
||||||
<|> (doubleQuotable >>= return . return)
|
|
||||||
<|> (anyChar >>= (return . \x -> [bs, x]))
|
|
||||||
|
|
||||||
|
|
||||||
readGenericLiteral endExp = do
|
|
||||||
strings <- many (readGenericEscaped <|> anyChar `reluctantlyTill1` endExp)
|
|
||||||
return $ concat strings
|
|
||||||
|
|
||||||
readGenericLiteral1 endExp = do
|
|
||||||
strings <- many1 (readGenericEscaped <|> anyChar `reluctantlyTill1` endExp)
|
|
||||||
return $ concat strings
|
|
||||||
|
|
||||||
readGenericEscaped = do
|
|
||||||
backslash
|
|
||||||
x <- anyChar
|
|
||||||
return $ if x == '\n' then [] else [x]
|
|
||||||
|
|
||||||
readBraced = annotated "{1,2..3} expression" $ try $ do
|
|
||||||
let strip (T_Literal s) = return ("\"" ++ s ++ "\"")
|
|
||||||
char '{'
|
|
||||||
str <- many1 ((readDoubleQuotedLiteral >>= (strip )) <|> readGenericLiteral1 (oneOf "}" <|> whitespace))
|
|
||||||
char '}'
|
|
||||||
return $ T_BraceExpansion $ concat str
|
|
||||||
|
|
||||||
readDollar = readDollarArithmetic <|> readDollarBraced <|> readDollarExpansion <|> readDollarVariable <|> readDollarLonely
|
|
||||||
|
|
||||||
|
|
||||||
readParenLiteralHack = do
|
|
||||||
strs <- ((anyChar >>= \x -> return [x]) <|> readParenHack) `reluctantlyTill1` (string "))")
|
|
||||||
return $ concat strs
|
|
||||||
|
|
||||||
readParenHack = do
|
|
||||||
char '('
|
|
||||||
x <- many anyChar
|
|
||||||
char ')'
|
|
||||||
return $ "(" ++ x ++ ")"
|
|
||||||
|
|
||||||
readDollarArithmetic = annotated "$(( )) expression" $ do
|
|
||||||
try (string "$((")
|
|
||||||
-- TODO
|
|
||||||
str <- readParenLiteralHack
|
|
||||||
string "))"
|
|
||||||
return (T_DollarArithmetic str)
|
|
||||||
|
|
||||||
readDollarBraced = annotated "${ } expression" $ do
|
|
||||||
try (string "${")
|
|
||||||
-- TODO
|
|
||||||
str <- readGenericLiteral (char '}')
|
|
||||||
char '}' <?> "matching }"
|
|
||||||
return $ (T_DollarBraced str)
|
|
||||||
|
|
||||||
readDollarExpansion = annotated "$( )" $ do
|
|
||||||
try (string "$(")
|
|
||||||
cmds <- readCompoundList
|
|
||||||
char ')'
|
|
||||||
return $ (T_DollarExpansion cmds)
|
|
||||||
|
|
||||||
readDollarVariable = annotated "$variable" $ do
|
|
||||||
let singleCharred p = do
|
|
||||||
n <- p
|
|
||||||
return (T_DollarVariable [n]) `attempting` do
|
|
||||||
pos <- getPosition
|
|
||||||
num <- lookAhead $ many1 p
|
|
||||||
parseNoteAt pos ErrorC $ "$" ++ (n:num) ++ " is equivalent to ${" ++ [n] ++ "}"++ num
|
|
||||||
|
|
||||||
let positional = singleCharred digit
|
|
||||||
let special = singleCharred specialVariable
|
|
||||||
|
|
||||||
let regular = do
|
|
||||||
name <- readVariableName
|
|
||||||
return $ T_DollarVariable (name)
|
|
||||||
|
|
||||||
char '$'
|
|
||||||
positional <|> special <|> regular
|
|
||||||
|
|
||||||
readVariableName = do
|
|
||||||
f <- variableStart
|
|
||||||
rest <- many variableChars
|
|
||||||
return (f:rest)
|
|
||||||
|
|
||||||
readDollarLonely = annotated "lonely $" $ do
|
|
||||||
parseNote ErrorC "$ is not used specially and should therefore be escaped"
|
|
||||||
char '$'
|
|
||||||
return $ T_Literal "$"
|
|
||||||
|
|
||||||
readHereDoc = annotated "here document" $ do
|
|
||||||
let stripLiteral (T_Literal x) = x
|
|
||||||
stripLiteral (T_SingleQuoted x) = x
|
|
||||||
try $ string "<<"
|
|
||||||
dashed <- (char '-' >> return True) <|> return False
|
|
||||||
tokenPosition <- getPosition
|
|
||||||
spacing
|
|
||||||
(quoted, endToken) <- (readNormalLiteral >>= (\x -> return (False, stripLiteral x)) )
|
|
||||||
<|> (readDoubleQuotedLiteral >>= return . (\x -> (True, stripLiteral x)))
|
|
||||||
<|> (readSingleQuotedLiteral >>= return . (\x -> (True, x)))
|
|
||||||
spacing
|
|
||||||
|
|
||||||
hereInfo <- anyChar `reluctantlyTill` (linefeed >> spacing >> (string endToken) >> (disregard whitespace <|> eof))
|
|
||||||
|
|
||||||
do
|
|
||||||
linefeed
|
|
||||||
spaces <- spacing
|
|
||||||
verifyHereDoc dashed quoted spaces hereInfo
|
|
||||||
token <- string endToken
|
|
||||||
return $ T_FdRedirect "" $ T_HereDoc dashed quoted hereInfo
|
|
||||||
`attempting` (eof >> debugHereDoc tokenPosition endToken hereInfo)
|
|
||||||
|
|
||||||
verifyHereDoc dashed quoted spacing hereInfo = do
|
|
||||||
when (not dashed && spacing /= "") $ parseNote ErrorC "When using << instead of <<-, the end tokens can't be indented"
|
|
||||||
when (dashed && filter (/= '\t') spacing /= "" ) $ parseNote ErrorC "When using <<-, you can only indent with tabs"
|
|
||||||
return ()
|
|
||||||
|
|
||||||
debugHereDoc pos endToken doc =
|
|
||||||
if endToken `isInfixOf` doc
|
|
||||||
then parseProblemAt pos ErrorC (endToken ++ " was part of the here document, but not by itself at the start of the line")
|
|
||||||
else if (map toLower endToken) `isInfixOf` (map toLower doc)
|
|
||||||
then parseProblemAt pos ErrorC (endToken ++ " appears in the here document, but with different case")
|
|
||||||
else parseProblemAt pos ErrorC ("Couldn't find end token `" ++ endToken ++ "' in the here document ")
|
|
||||||
|
|
||||||
|
|
||||||
readFilename = readNormalWord
|
|
||||||
readIoFileOp = choice [g_LESSAND, g_GREATAND, g_DGREAT, g_LESSGREAT, g_CLOBBER, string "<" >> return T_Less, string ">" >> return T_Greater ]
|
|
||||||
readIoFile = do
|
|
||||||
op <- readIoFileOp
|
|
||||||
spacing
|
|
||||||
file <- readFilename
|
|
||||||
return $ T_FdRedirect "" $ T_IoFile op file
|
|
||||||
readIoNumber = try $ do
|
|
||||||
x <- many1 digit
|
|
||||||
lookAhead readIoFileOp
|
|
||||||
return x
|
|
||||||
readIoNumberRedirect = annotated "fd io redirect" $ do
|
|
||||||
n <- readIoNumber
|
|
||||||
op <- merging readHereString <|> merging readHereDoc <|> readIoFile
|
|
||||||
let actualOp = case op of T_FdRedirect "" x -> x
|
|
||||||
spacing
|
|
||||||
return $ T_FdRedirect n actualOp
|
|
||||||
|
|
||||||
readIoRedirect = annotated "io redirect" $ choice [ merging readIoNumberRedirect, merging readHereString, merging readHereDoc, readIoFile ] `thenSkip` spacing
|
|
||||||
|
|
||||||
readRedirectList = many1 readIoRedirect
|
|
||||||
|
|
||||||
readHereString = annotated "here string" $ do
|
|
||||||
try $ string "<<<"
|
|
||||||
spacing
|
|
||||||
word <- readNormalWord
|
|
||||||
return $ T_FdRedirect "" $ T_HereString word
|
|
||||||
|
|
||||||
readNewlineList = many1 ((newline <|> carriageReturn) `thenSkip` spacing)
|
|
||||||
readLineBreak = optional readNewlineList
|
|
||||||
|
|
||||||
readSeparatorOp = do
|
|
||||||
notFollowedBy (g_AND_IF <|> g_DSEMI)
|
|
||||||
f <- char ';' <|> char '&'
|
|
||||||
spacing
|
|
||||||
return f
|
|
||||||
|
|
||||||
readSequentialSep = (disregard $ g_Semi >> readLineBreak) <|> (disregard readNewlineList)
|
|
||||||
readSeparator =
|
|
||||||
do
|
|
||||||
separator <- readSeparatorOp
|
|
||||||
readLineBreak
|
|
||||||
return separator
|
|
||||||
<|>
|
|
||||||
do
|
|
||||||
readNewlineList
|
|
||||||
return '\n'
|
|
||||||
|
|
||||||
makeSimpleCommand tokens =
|
|
||||||
let (assignment, rest) = partition (\x -> case dropAnnotation x of T_Assignment _ _ -> True; _ -> False) tokens
|
|
||||||
in let (redirections, rest2) = partition (\x -> case dropAnnotation x of T_FdRedirect _ _ -> True; _ -> False) rest
|
|
||||||
in T_Redirecting redirections $ T_SimpleCommand assignment rest2
|
|
||||||
|
|
||||||
readSimpleCommand = annotated "simple command" $ do
|
|
||||||
prefix <- option [] readCmdPrefix
|
|
||||||
cmd <- option [] $ do { f <- annotated "command name" readCmdName; return [f]; }
|
|
||||||
when (null prefix && null cmd) $ fail "No command"
|
|
||||||
if null cmd
|
|
||||||
then return $ makeSimpleCommand prefix
|
|
||||||
else do
|
|
||||||
suffix <- option [] readCmdSuffix
|
|
||||||
return $ makeSimpleCommand (prefix ++ cmd ++ suffix)
|
|
||||||
|
|
||||||
readPipeline = annotated "Pipeline" $ do
|
|
||||||
notFollowedBy $ try readKeyword
|
|
||||||
do
|
|
||||||
g_Bang `thenSkip` spacing
|
|
||||||
pipe <- readPipeSequence
|
|
||||||
return $ T_Banged pipe
|
|
||||||
<|> do
|
|
||||||
readPipeSequence
|
|
||||||
|
|
||||||
readAndOr = (flip (>>=)) (return . T_Command) $ chainr1 readPipeline $ do
|
|
||||||
pos <- getPosition
|
|
||||||
op <- g_AND_IF <|> g_OR_IF
|
|
||||||
readLineBreak
|
|
||||||
return $ \a b ->
|
|
||||||
blankAnnotation pos $
|
|
||||||
case op of T_AND_IF -> T_AndIf a b
|
|
||||||
T_OR_IF -> T_OrIf a b
|
|
||||||
|
|
||||||
readTerm = do
|
|
||||||
m <- readAndOr
|
|
||||||
readTerm' m
|
|
||||||
|
|
||||||
readTerm' current =
|
|
||||||
do
|
|
||||||
sep <- readSeparator
|
|
||||||
more <- (option T_EOF $ readAndOr)
|
|
||||||
case more of T_EOF -> return [transformWithSeparator sep current]
|
|
||||||
_ -> do
|
|
||||||
list <- readTerm' more
|
|
||||||
return $ (transformWithSeparator sep current : list)
|
|
||||||
<|>
|
|
||||||
return [current]
|
|
||||||
|
|
||||||
transformWithSeparator '&' = T_Backgrounded
|
|
||||||
transformWithSeparator _ = id
|
|
||||||
|
|
||||||
|
|
||||||
readPipeSequence = do
|
|
||||||
list <- readCommand `sepBy1` (readPipe `thenSkip` (spacing >> readLineBreak))
|
|
||||||
spacing
|
|
||||||
return $ T_Pipeline list
|
|
||||||
|
|
||||||
readPipe = do
|
|
||||||
notFollowedBy g_OR_IF
|
|
||||||
char '|' `thenSkip` spacing
|
|
||||||
|
|
||||||
readCommand = (readCompoundCommand <|> readSimpleCommand)
|
|
||||||
|
|
||||||
readCmdName = do
|
|
||||||
f <- readNormalWord
|
|
||||||
spacing
|
|
||||||
return f
|
|
||||||
|
|
||||||
readCmdWord = do
|
|
||||||
f <- readNormalWord
|
|
||||||
spacing
|
|
||||||
return f
|
|
||||||
|
|
||||||
readIfClause = annotated "if statement" $ do
|
|
||||||
(condition, action) <- readIfPart
|
|
||||||
elifs <- many readElifPart
|
|
||||||
elses <- option [] readElsePart
|
|
||||||
g_Fi
|
|
||||||
return $ T_IfExpression ((condition, action):elifs) elses
|
|
||||||
|
|
||||||
readIfPart = do
|
|
||||||
g_If
|
|
||||||
allspacing
|
|
||||||
condition <- readTerm
|
|
||||||
g_Then
|
|
||||||
allspacing
|
|
||||||
action <- readTerm
|
|
||||||
return (condition, action)
|
|
||||||
|
|
||||||
readElifPart = do
|
|
||||||
g_Elif
|
|
||||||
allspacing
|
|
||||||
condition <- readTerm
|
|
||||||
g_Then
|
|
||||||
allspacing
|
|
||||||
action <- readTerm
|
|
||||||
return (condition, action)
|
|
||||||
|
|
||||||
readElsePart = do
|
|
||||||
g_Else
|
|
||||||
allspacing
|
|
||||||
readTerm
|
|
||||||
|
|
||||||
readSubshell = annotated "subshell group" $ do
|
|
||||||
char '('
|
|
||||||
allspacing
|
|
||||||
list <- readCompoundList
|
|
||||||
allspacing
|
|
||||||
char ')'
|
|
||||||
return $ T_Subshell list
|
|
||||||
|
|
||||||
readBraceGroup = annotated "brace group" $ do
|
|
||||||
char '{'
|
|
||||||
allspacing
|
|
||||||
list <- readTerm
|
|
||||||
allspacing
|
|
||||||
char '}'
|
|
||||||
return $ T_BraceGroup list
|
|
||||||
|
|
||||||
readWhileClause = annotated "while loop" $ do
|
|
||||||
g_While
|
|
||||||
condition <- readTerm
|
|
||||||
statements <- readDoGroup
|
|
||||||
return $ T_WhileExpression condition statements
|
|
||||||
|
|
||||||
readUntilClause = annotated "until loop" $ do
|
|
||||||
g_Until
|
|
||||||
condition <- readTerm
|
|
||||||
statements <- readDoGroup
|
|
||||||
return $ T_UntilExpression condition statements
|
|
||||||
|
|
||||||
readDoGroup = do
|
|
||||||
pos <- getPosition
|
|
||||||
g_Do
|
|
||||||
allspacing
|
|
||||||
(eof >> return []) <|>
|
|
||||||
do
|
|
||||||
commands <- readCompoundList
|
|
||||||
disregard g_Done <|> eof -- stunted support
|
|
||||||
return commands
|
|
||||||
<|> do
|
|
||||||
parseProblemAt pos ErrorC "Can't find the 'done' for this 'do'"
|
|
||||||
fail "No done"
|
|
||||||
|
|
||||||
readForClause = annotated "for loop" $ do
|
|
||||||
g_For
|
|
||||||
spacing
|
|
||||||
name <- readVariableName
|
|
||||||
allspacing
|
|
||||||
values <- readInClause <|> (readSequentialSep >> return [])
|
|
||||||
group <- readDoGroup <|> (allspacing >> eof >> return []) -- stunted support
|
|
||||||
return $ T_ForIn name values group
|
|
||||||
|
|
||||||
readInClause = do
|
|
||||||
g_In
|
|
||||||
things <- (readCmdWord) `reluctantlyTill`
|
|
||||||
(disregard (g_Semi) <|> disregard linefeed <|> disregard g_Do)
|
|
||||||
|
|
||||||
do {
|
|
||||||
lookAhead (g_Do);
|
|
||||||
parseNote ErrorC "You need a line feed or semicolon before the 'do' (in Bash)";
|
|
||||||
} <|> do {
|
|
||||||
optional $ g_Semi;
|
|
||||||
disregard allspacing;
|
|
||||||
}
|
|
||||||
|
|
||||||
return things
|
|
||||||
|
|
||||||
readCaseClause = annotated "case statement" $ do
|
|
||||||
g_Case
|
|
||||||
word <- readNormalWord
|
|
||||||
spacing
|
|
||||||
g_In
|
|
||||||
readLineBreak
|
|
||||||
list <- readCaseList
|
|
||||||
g_Esac
|
|
||||||
return $ T_CaseExpression word list
|
|
||||||
|
|
||||||
readCaseList = many readCaseItem
|
|
||||||
|
|
||||||
readCaseItem = do
|
|
||||||
notFollowedBy g_Esac
|
|
||||||
optional g_Lparen
|
|
||||||
spacing
|
|
||||||
pattern <- readPattern
|
|
||||||
g_Rparen
|
|
||||||
readLineBreak
|
|
||||||
list <- ((lookAhead g_DSEMI >> return []) <|> readCompoundList)
|
|
||||||
(g_DSEMI <|> lookAhead (readLineBreak >> g_Esac))
|
|
||||||
readLineBreak
|
|
||||||
return (pattern, list)
|
|
||||||
|
|
||||||
readFunctionDefinition = annotated "function definition" $ do
|
|
||||||
name <- try readFunctionSignature
|
|
||||||
allspacing
|
|
||||||
(disregard (lookAhead g_Lbrace) <|> parseProblem ErrorC "Expected a { to open the function definition")
|
|
||||||
group <- merging readBraceGroup
|
|
||||||
return $ T_Function name group
|
|
||||||
|
|
||||||
|
|
||||||
readFunctionSignature = do
|
|
||||||
(optional $ try (string "function " >> parseNote StyleC "Don't use 'function' in front of function definitions"))
|
|
||||||
name <- readVariableName
|
|
||||||
spacing
|
|
||||||
g_Lparen
|
|
||||||
g_Rparen
|
|
||||||
return name
|
|
||||||
|
|
||||||
|
|
||||||
readPattern = (readNormalWord `thenSkip` spacing) `sepBy1` (char '|' `thenSkip` spacing)
|
|
||||||
|
|
||||||
|
|
||||||
readCompoundCommand = annotated "compound command" $ do
|
|
||||||
cmd <- merging $ choice [ readBraceGroup, readSubshell, readWhileClause, readUntilClause, readIfClause, readForClause, readCaseClause, readFunctionDefinition]
|
|
||||||
spacing
|
|
||||||
redirs <- many readIoRedirect
|
|
||||||
return $ T_Redirecting redirs $ cmd
|
|
||||||
|
|
||||||
|
|
||||||
readCompoundList = readTerm
|
|
||||||
|
|
||||||
readCmdPrefix = many1 (readIoRedirect <|> readAssignmentWord)
|
|
||||||
readCmdSuffix = many1 (readIoRedirect <|> annotated "normal word" readCmdWord)
|
|
||||||
|
|
||||||
readAssignmentWord = annotated "assignment" $ try $ do
|
|
||||||
optional (char '$' >> parseNote ErrorC "Don't use $ on the left side of assignments")
|
|
||||||
variable <- readVariableName
|
|
||||||
space <- spacing
|
|
||||||
pos <- getPosition
|
|
||||||
char '='
|
|
||||||
space2 <- spacing
|
|
||||||
value <- readNormalWord
|
|
||||||
spacing
|
|
||||||
when (space ++ space2 /= "") $ parseNoteAt pos ErrorC "Don't put spaces around the = in assignments"
|
|
||||||
return $ T_Assignment variable value
|
|
||||||
|
|
||||||
|
|
||||||
tryToken s t = try (string s >> spacing >> return t)
|
|
||||||
tryWordToken s t = tryParseWordToken (string s) t `thenSkip` spacing
|
|
||||||
tryParseWordToken parser t = try (parser >> (lookAhead (eof <|> disregard whitespace))) >> return t
|
|
||||||
|
|
||||||
g_AND_IF = tryToken "&&" T_AND_IF
|
|
||||||
g_OR_IF = tryToken "||" T_OR_IF
|
|
||||||
g_DSEMI = tryToken ";;" T_DSEMI
|
|
||||||
g_DLESS = tryToken "<<" T_DLESS
|
|
||||||
g_DGREAT = tryToken ">>" T_DGREAT
|
|
||||||
g_LESSAND = tryToken "<&" T_LESSAND
|
|
||||||
g_GREATAND = tryToken ">&" T_GREATAND
|
|
||||||
g_LESSGREAT = tryToken "<>" T_LESSGREAT
|
|
||||||
g_DLESSDASH = tryToken "<<-" T_DLESSDASH
|
|
||||||
g_CLOBBER = tryToken ">|" T_CLOBBER
|
|
||||||
g_OPERATOR = g_AND_IF <|> g_OR_IF <|> g_DSEMI <|> g_DLESSDASH <|> g_DLESS <|> g_DGREAT <|> g_LESSAND <|> g_GREATAND <|> g_LESSGREAT
|
|
||||||
|
|
||||||
g_If = tryWordToken "if" T_If
|
|
||||||
g_Then = tryWordToken "then" T_Then
|
|
||||||
g_Else = tryWordToken "else" T_Else
|
|
||||||
g_Elif = tryWordToken "elif" T_Elif
|
|
||||||
g_Fi = tryWordToken "fi" T_Fi
|
|
||||||
g_Do = tryWordToken "do" T_Do
|
|
||||||
g_Done = tryWordToken "done" T_Done
|
|
||||||
g_Case = tryWordToken "case" T_Case
|
|
||||||
g_Esac = tryWordToken "esac" T_Esac
|
|
||||||
g_While = tryWordToken "while" T_While
|
|
||||||
g_Until = tryWordToken "until" T_Until
|
|
||||||
g_For = tryWordToken "for" T_For
|
|
||||||
g_In = tryWordToken "in" T_In
|
|
||||||
g_Lbrace = tryWordToken "{" T_Lbrace
|
|
||||||
g_Rbrace = tryWordToken "}" T_Rbrace
|
|
||||||
|
|
||||||
g_Lparen = tryToken "(" T_Lparen
|
|
||||||
g_Rparen = tryToken ")" T_Rparen
|
|
||||||
g_Bang = tryToken "!" T_Bang
|
|
||||||
|
|
||||||
g_Semi = do
|
|
||||||
notFollowedBy g_DSEMI
|
|
||||||
tryToken ";" T_Semi
|
|
||||||
|
|
||||||
readKeyword = choice [ g_Then, g_Else, g_Elif, g_Fi, g_Do, g_Done, g_Esac, g_Rbrace, g_Rparen, g_DSEMI ]
|
|
||||||
|
|
||||||
ifParse p t f = do
|
|
||||||
(lookAhead (try p) >> t) <|> f
|
|
||||||
|
|
||||||
wtf = do
|
|
||||||
x <- many anyChar
|
|
||||||
parseProblem ErrorC x
|
|
||||||
|
|
||||||
readScript = do
|
|
||||||
do {
|
|
||||||
allspacing;
|
|
||||||
commands <- readTerm;
|
|
||||||
eof <|> (parseProblem WarningC "Stopping here, because I can't parse this command");
|
|
||||||
return $ T_Script commands;
|
|
||||||
} <|> do {
|
|
||||||
parseProblem WarningC "Couldn't read any commands";
|
|
||||||
wtf;
|
|
||||||
return T_EOF;
|
|
||||||
}
|
|
||||||
|
|
||||||
shpell s = rp readScript s
|
|
||||||
rp p s = Ms.runState (runParserT p StackEmpty "-" s) []
|
|
||||||
|
|
||||||
-------- Destructively simplify AST
|
|
||||||
|
|
||||||
simplify (T_Redirecting [] t) = t
|
|
||||||
simplify (T_Pipeline [x]) = dropAnnotation x
|
|
||||||
simplify (T_NormalWord [x]) = dropAnnotation x
|
|
||||||
simplify t = t
|
|
||||||
|
|
||||||
-------- Analytics
|
|
||||||
doAllAnalysis t = foldl (\v f -> doAnalysis f v) t checks
|
|
||||||
|
|
||||||
getAst s = case rp readScript s of (Right parsed, _) -> parsed
|
|
||||||
getAst2 s = case rp readScript s of (Right parsed, _) -> transform simplify parsed
|
|
||||||
lol (Right x, _) = x
|
|
||||||
|
|
||||||
deadSimple (T_NormalWord l) = [concat (concatMap (deadSimple . dropAnnotation) l)]
|
|
||||||
deadSimple (T_DoubleQuoted l) = ["\"" ++(concat (concatMap (deadSimple . dropAnnotation) l)) ++ "\""]
|
|
||||||
deadSimple (T_SingleQuoted s) = [s]
|
|
||||||
deadSimple (T_DollarVariable _) = ["${VAR}"]
|
|
||||||
deadSimple (T_DollarBraced _) = ["${VAR}"]
|
|
||||||
deadSimple (T_DollarArithmetic _) = ["${VAR}"]
|
|
||||||
deadSimple (T_DollarExpansion _) = ["${VAR}"]
|
|
||||||
deadSimple (T_Literal x) = [x]
|
|
||||||
deadSimple (T_SimpleCommand vars words) = concatMap (deadSimple . dropAnnotation) words
|
|
||||||
deadSimple (T_Redirecting _ foo) = deadSimple foo
|
|
||||||
deadSimple _ = []
|
|
||||||
|
|
||||||
|
|
||||||
checks = [checkUuoc]
|
|
||||||
checkUuoc (T_Pipeline ((Annotated _ _ x):_:_)) = case (deadSimple x) of ["cat", _] -> style "UUOC: Instead of 'cat a | b', use 'b < a'"
|
|
||||||
_ -> return ()
|
|
||||||
checkUuoc _ = return ()
|
|
||||||
|
|
||||||
|
|
||||||
main = do
|
|
||||||
s <- getContents
|
|
||||||
-- case rp readScript s of (Right parsed, _) -> putStrLn . show $ transform simplify parsed
|
|
||||||
-- (Left x, y) -> putStrLn $ "Can't parse: " ++ (show (x,y))
|
|
||||||
|
|
||||||
mapM (putStrLn . show) $ getNotes s
|
|
65
test/quackCheck.hs
Executable file
65
test/quackCheck.hs
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env runhaskell
|
||||||
|
-- #!/usr/bin/env runhugs
|
||||||
|
-- $Id: quickcheck,v 1.4 2003/01/08 15:09:22 shae Exp $
|
||||||
|
-- This file defines a command
|
||||||
|
-- quickCheck <options> <files>
|
||||||
|
-- which invokes quickCheck on all properties defined in the files given as
|
||||||
|
-- arguments, by generating an input script for hugs and then invoking it.
|
||||||
|
-- quickCheck recognises the options
|
||||||
|
-- +names print the name of each property before checking it
|
||||||
|
-- -names do not print property names (the default)
|
||||||
|
-- +verbose displays each test case before running
|
||||||
|
-- -verbose do not displays each test case before running (the default)
|
||||||
|
-- Other options (beginning with + or -) are passed unchanged to hugs.
|
||||||
|
--
|
||||||
|
-- Change the first line of this file to the location of runhaskell or runhugs
|
||||||
|
-- on your system.
|
||||||
|
-- Make the file executable.
|
||||||
|
--
|
||||||
|
-- TODO:
|
||||||
|
-- someone on #haskell asked about supporting QC tests inside LaTeX, ex. \{begin} \{end}, how?
|
||||||
|
|
||||||
|
import System.Cmd
|
||||||
|
import System.Directory (findExecutable)
|
||||||
|
import System.Environment
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe (fromJust)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do as<-getArgs
|
||||||
|
sequence_ (map (process (filter isOption as))
|
||||||
|
(filter (not.isOption) as))
|
||||||
|
|
||||||
|
-- ugly hack for .lhs files, is there a better way?
|
||||||
|
unlit [] = []
|
||||||
|
unlit x = if (head x) == '>' then (tail x) else x
|
||||||
|
|
||||||
|
process opts file =
|
||||||
|
let (namesOpt,opts') = getOption "names" "-names" opts
|
||||||
|
(verboseOpt,opts'') = getOption "verbose" "-verbose" opts' in
|
||||||
|
do xs<-readFile file
|
||||||
|
let names = nub$ filter (\x -> (("> prop_" `isPrefixOf` x) || ("prop_" `isPrefixOf` x)))
|
||||||
|
(map (fst.head.lex.unlit) (lines xs))
|
||||||
|
if null names then
|
||||||
|
putStr (file++": no properties to check\n")
|
||||||
|
else do writeFile "hugsin"$
|
||||||
|
unlines ((":load "++file):
|
||||||
|
":m +Test.QuickCheck":
|
||||||
|
"let quackCheck p = quickCheckWith (stdArgs { maxSuccess = 1 }) p ":
|
||||||
|
[(if namesOpt=="+names" then
|
||||||
|
"putStr \""++p++": \" >> "
|
||||||
|
else "") ++
|
||||||
|
("quackCheck ")
|
||||||
|
++ p | p<-names])
|
||||||
|
-- To use ghci
|
||||||
|
ghci <- findExecutable "ghci"
|
||||||
|
system (fromJust ghci ++options opts''++" <hugsin")
|
||||||
|
return ()
|
||||||
|
|
||||||
|
isOption xs = head xs `elem` "-+"
|
||||||
|
|
||||||
|
options opts = unwords ["\""++opt++"\"" | opt<-opts]
|
||||||
|
|
||||||
|
getOption name def opts =
|
||||||
|
let opt = head [opt | opt<-opts++[def], isPrefixOf name (drop 1 opt)] in
|
||||||
|
(opt, filter (/=opt) opts)
|
22
test/runQuack
Executable file
22
test/runQuack
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Todo: Find a way to make this not suck.
|
||||||
|
|
||||||
|
ulimit -t 60 # Sometimes GHC ends in a spin loop, and this is easier than debugging
|
||||||
|
|
||||||
|
[[ -e test/quackCheck.hs ]] || { echo "Are you running me from the wrong directory?"; exit 1; }
|
||||||
|
[[ $1 == -v ]] && pattern="" || pattern="FAIL"
|
||||||
|
|
||||||
|
find . -name '*.hs' -exec bash -c '
|
||||||
|
grep -v "^module " "$1" > quack.tmp.hs
|
||||||
|
./test/quackCheck.hs +names quack.tmp.hs
|
||||||
|
' -- {} \; 2>&1 | grep -i "$pattern"
|
||||||
|
result=$?
|
||||||
|
rm -f quack.tmp.hs hugsin
|
||||||
|
|
||||||
|
if [[ $result == 0 ]]
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
Reference in New Issue
Block a user