mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-09-30 00:39:19 +08:00
Compare commits
531 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f25ae90746 | ||
|
3daa47c0f2 | ||
|
ed56a837c3 | ||
|
80cf5d9852 | ||
|
8e554ae3d4 | ||
|
0a80188363 | ||
|
0e1a64b6ba | ||
|
0a2cf208c8 | ||
|
dcc10bbdf6 | ||
|
2c2e41952f | ||
|
0d74140650 | ||
|
955ad60823 | ||
|
2573332d77 | ||
|
00c470f323 | ||
|
63188282e9 | ||
|
61b4b65184 | ||
|
39b2bf4378 | ||
|
2fe117728d | ||
|
cde3ba8769 | ||
|
33c78b7c95 | ||
|
a485482979 | ||
|
895d83afc5 | ||
|
39bc011757 | ||
|
fe0a398239 | ||
|
1be0f1ea75 | ||
|
c9aa133282 | ||
|
7b70500d41 | ||
|
8bed447411 | ||
|
22710bf4d8 | ||
|
a354685ab1 | ||
|
a8ff7a02fd | ||
|
c5479b8ca3 | ||
|
d9dd58bec8 | ||
|
af1bb93aba | ||
|
e909c8ac42 | ||
|
93140e31a0 | ||
|
97f3834852 | ||
|
0369f43bac | ||
|
eb2eae2888 | ||
|
30c0c1f27d | ||
|
bff5d11566 | ||
|
eccb9f3f71 | ||
|
2814572116 | ||
|
90bafb9aba | ||
|
39b88bbaac | ||
|
39805ab200 | ||
|
9dadce96c0 | ||
|
1a0e208cc3 | ||
|
a69e27b774 | ||
|
b05c12223f | ||
|
38ead0385b | ||
|
9e8a11e57c | ||
|
6b84b35ec0 | ||
|
669fdf8e5e | ||
|
dccfb3c4a1 | ||
|
40ce949a56 | ||
|
9f3802138f | ||
|
2f3533fff6 | ||
|
f9c346cfd7 | ||
|
5f7419ca37 | ||
|
8494509150 | ||
|
8ba1f2fdf2 | ||
|
dbadca9f61 | ||
|
0347ce1b7a | ||
|
7fbe66e1c6 | ||
|
b000b05507 | ||
|
39423ddf81 | ||
|
875c2d2aad | ||
|
64cc7c691a | ||
|
b9784cbcc0 | ||
|
1a3f6aadaf | ||
|
35756c2cd6 | ||
|
0fd351404f | ||
|
4caa7e7900 | ||
|
c11c0196d5 | ||
|
b035331d4a | ||
|
d13253973b | ||
|
d9c622ae33 | ||
|
aac7d76047 | ||
|
fc421adb45 | ||
|
e0d3c6923a | ||
|
9772ba9de4 | ||
|
3a944de606 | ||
|
3dd592a02a | ||
|
61531cbb10 | ||
|
d53087f056 | ||
|
39756b420e | ||
|
52d4efc951 | ||
|
5dac723593 | ||
|
2364fd58b6 | ||
|
cde364c97b | ||
|
98b790f87a | ||
|
726a4e5848 | ||
|
0a9ed917e7 | ||
|
b10d31c8b7 | ||
|
133c779701 | ||
|
b18ee3fdef | ||
|
3fcc6c44d8 | ||
|
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 |
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Created by http://www.gitignore.io
|
||||
|
||||
### Haskell ###
|
||||
dist
|
||||
cabal-dev
|
||||
*.o
|
||||
*.hi
|
||||
*.chi
|
||||
*.chs.h
|
||||
.virtualenv
|
||||
.hsenv
|
||||
.cabal-sandbox/
|
||||
cabal.sandbox.config
|
||||
cabal.config
|
||||
|
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/>.
|
88
README.md
Normal file
88
README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# ShellCheck - A shell script static analysis tool
|
||||
|
||||
http://www.shellcheck.net
|
||||
|
||||
Copyright 2012-2015, 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 is written in Haskell, and requires 2 GB of memory to compile.
|
||||
|
||||
## Installing
|
||||
|
||||
On systems with Cabal:
|
||||
|
||||
cabal update
|
||||
cabal install shellcheck
|
||||
|
||||
On Debian based distros:
|
||||
|
||||
apt-get install shellcheck
|
||||
|
||||
On OS X with homebrew:
|
||||
|
||||
brew install shellcheck
|
||||
|
||||
ShellCheck is also available as an online service:
|
||||
|
||||
http://www.shellcheck.net
|
||||
|
||||
## Building with Cabal
|
||||
|
||||
This sections describes how to build ShellCheck from a source directory.
|
||||
|
||||
First, 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
|
||||
|
||||
## Running tests
|
||||
|
||||
To run the unit test suite:
|
||||
|
||||
cabal configure --enable-tests
|
||||
cabal build
|
||||
cabal test
|
||||
|
||||
Happy ShellChecking!
|
43
Setup.hs
Normal file
43
Setup.hs
Normal file
@@ -0,0 +1,43 @@
|
||||
import Distribution.PackageDescription (
|
||||
HookedBuildInfo,
|
||||
emptyHookedBuildInfo )
|
||||
import Distribution.Simple (
|
||||
Args,
|
||||
UserHooks ( preSDist ),
|
||||
defaultMainWithHooks,
|
||||
simpleUserHooks )
|
||||
import Distribution.Simple.Setup ( SDistFlags )
|
||||
|
||||
-- | This requires the process package from,
|
||||
--
|
||||
-- https://hackage.haskell.org/package/process
|
||||
--
|
||||
import System.Process ( callCommand )
|
||||
|
||||
|
||||
-- | This will use almost the default implementation, except we switch
|
||||
-- out the default pre-sdist hook with our own, 'myPreSDist'.
|
||||
--
|
||||
main = defaultMainWithHooks myHooks
|
||||
where
|
||||
myHooks = simpleUserHooks { preSDist = myPreSDist }
|
||||
|
||||
|
||||
-- | This hook will be executed before e.g. @cabal sdist@. It runs
|
||||
-- pandoc to create the man page from shellcheck.1.md. If the pandoc
|
||||
-- command is not found, this will fail with an error message:
|
||||
--
|
||||
-- /bin/sh: pandoc: command not found
|
||||
--
|
||||
-- Since the man page is listed in the Extra-Source-Files section of
|
||||
-- our cabal file, a failure here should result in a failure to
|
||||
-- create the distribution tarball (that's a good thing).
|
||||
--
|
||||
myPreSDist :: Args -> SDistFlags -> IO HookedBuildInfo
|
||||
myPreSDist _ _ = do
|
||||
putStrLn "Building the man page..."
|
||||
putStrLn pandoc_cmd
|
||||
callCommand pandoc_cmd
|
||||
return emptyHookedBuildInfo
|
||||
where
|
||||
pandoc_cmd = "pandoc -s -t man shellcheck.1.md -o shellcheck.1"
|
86
ShellCheck.cabal
Normal file
86
ShellCheck.cabal
Normal file
@@ -0,0 +1,86 @@
|
||||
Name: ShellCheck
|
||||
Version: 0.3.6
|
||||
Synopsis: Shell script analysis tool
|
||||
License: AGPL-3
|
||||
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.
|
||||
|
||||
Extra-Source-Files:
|
||||
-- documentation
|
||||
README.md
|
||||
shellcheck.1.md
|
||||
-- built with a cabal sdist hook
|
||||
shellcheck.1
|
||||
-- tests
|
||||
test/shellcheck.hs
|
||||
|
||||
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,
|
||||
QuickCheck >= 2.7.4
|
||||
exposed-modules:
|
||||
ShellCheck.Analytics
|
||||
ShellCheck.AST
|
||||
ShellCheck.Data
|
||||
ShellCheck.Options
|
||||
ShellCheck.Parser
|
||||
ShellCheck.Simple
|
||||
other-modules:
|
||||
Paths_ShellCheck
|
||||
|
||||
executable shellcheck
|
||||
build-depends:
|
||||
ShellCheck,
|
||||
base >= 4 && < 5,
|
||||
containers,
|
||||
directory,
|
||||
json,
|
||||
mtl,
|
||||
parsec,
|
||||
regex-compat,
|
||||
transformers,
|
||||
QuickCheck >= 2.7.4
|
||||
main-is: shellcheck.hs
|
||||
|
||||
test-suite test-shellcheck
|
||||
type: exitcode-stdio-1.0
|
||||
build-depends:
|
||||
ShellCheck,
|
||||
base >= 4 && < 5,
|
||||
containers,
|
||||
directory,
|
||||
json,
|
||||
mtl,
|
||||
parsec,
|
||||
regex-compat,
|
||||
transformers,
|
||||
QuickCheck >= 2.7.4
|
||||
main-is: test/shellcheck.hs
|
||||
|
361
ShellCheck/AST.hs
Normal file
361
ShellCheck/AST.hs
Normal file
@@ -0,0 +1,361 @@
|
||||
{-
|
||||
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 CaseType = CaseBreak | CaseFallThrough | CaseContinue deriving (Show, Eq)
|
||||
|
||||
data Token =
|
||||
TA_Binary Id String Token Token
|
||||
| TA_Expansion Id [Token]
|
||||
| TA_Index Id Token
|
||||
| TA_Sequence Id [Token]
|
||||
| TA_Trinary Id Token Token Token
|
||||
| TA_Unary Id String Token
|
||||
| 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_IndexedElement Id Token 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 [(CaseType, [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 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
|
||||
| T_CoProc Id (Maybe String) Token
|
||||
| T_CoProcBody Id Token
|
||||
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_IndexedElement id t1 t2) = d2 t1 t2 $ T_IndexedElement 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 v w l) = dll w l $ T_ForIn id 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 (\(o, c, t) -> do
|
||||
x <- mapM round c
|
||||
y <- mapM round t
|
||||
return (o, 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) = dl t $ TA_Expansion id
|
||||
delve (TA_Index id t) = d1 t $ TA_Index id
|
||||
delve (T_Annotation id anns t) = d1 t $ T_Annotation id anns
|
||||
delve (T_CoProc id var body) = d1 body $ T_CoProc id var
|
||||
delve (T_CoProcBody id t) = d1 t $ T_CoProcBody id
|
||||
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_IndexedElement 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_Trinary id _ _ _ -> id
|
||||
TA_Expansion id _ -> id
|
||||
TA_Index 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
|
||||
T_CoProc id _ _ -> id
|
||||
T_CoProcBody 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
|
3274
ShellCheck/Analytics.hs
Normal file
3274
ShellCheck/Analytics.hs
Normal file
File diff suppressed because it is too large
Load Diff
75
ShellCheck/Data.hs
Normal file
75
ShellCheck/Data.hs
Normal file
@@ -0,0 +1,75 @@
|
||||
module ShellCheck.Data where
|
||||
|
||||
import Data.Version (showVersion)
|
||||
import Paths_ShellCheck (version)
|
||||
|
||||
shellcheckVersion = showVersion version
|
||||
|
||||
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", "COPROC",
|
||||
|
||||
-- Other
|
||||
"USER", "TZ", "TERM"
|
||||
]
|
||||
|
||||
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"
|
||||
]
|
14
ShellCheck/Options.hs
Normal file
14
ShellCheck/Options.hs
Normal file
@@ -0,0 +1,14 @@
|
||||
module ShellCheck.Options where
|
||||
|
||||
data Shell = Ksh | Sh | Bash
|
||||
deriving (Show, Eq)
|
||||
|
||||
data AnalysisOptions = AnalysisOptions {
|
||||
optionShellType :: Maybe Shell,
|
||||
optionExcludes :: [Integer]
|
||||
}
|
||||
|
||||
defaultAnalysisOptions = AnalysisOptions {
|
||||
optionShellType = Nothing,
|
||||
optionExcludes = []
|
||||
}
|
2168
ShellCheck/Parser.hs
Normal file
2168
ShellCheck/Parser.hs
Normal file
File diff suppressed because it is too large
Load Diff
80
ShellCheck/Simple.hs
Normal file
80
ShellCheck/Simple.hs
Normal file
@@ -0,0 +1,80 @@
|
||||
{-
|
||||
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/>.
|
||||
-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
module ShellCheck.Simple (shellCheck, ShellCheckComment, scLine, scColumn, scSeverity, scCode, scMessage, runTests) where
|
||||
|
||||
import Data.List
|
||||
import Data.Maybe
|
||||
import ShellCheck.Analytics hiding (runTests)
|
||||
import ShellCheck.Options
|
||||
import ShellCheck.Parser hiding (runTests)
|
||||
import Test.QuickCheck.All (quickCheckAll)
|
||||
import Text.Parsec.Pos
|
||||
|
||||
shellCheck :: AnalysisOptions -> String -> [ShellCheckComment]
|
||||
shellCheck options script =
|
||||
let (ParseResult result notes) = parseShell options "-" 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
|
||||
|
||||
testCheck = shellCheck defaultAnalysisOptions { optionExcludes = [2148] } -- Ignore #! warnings
|
||||
prop_findsParseIssue =
|
||||
let comments = testCheck "echo \"$12\"" in
|
||||
length comments == 1 && scCode (head comments) == 1037
|
||||
prop_commentDisablesParseIssue1 =
|
||||
null $ testCheck "#shellcheck disable=SC1037\necho \"$12\""
|
||||
prop_commentDisablesParseIssue2 =
|
||||
null $ testCheck "#shellcheck disable=SC1037\n#lol\necho \"$12\""
|
||||
|
||||
prop_findsAnalysisIssue =
|
||||
let comments = testCheck "echo $1" in
|
||||
length comments == 1 && scCode (head comments) == 2086
|
||||
prop_commentDisablesAnalysisIssue1 =
|
||||
null $ testCheck "#shellcheck disable=SC2086\necho $1"
|
||||
prop_commentDisablesAnalysisIssue2 =
|
||||
null $ testCheck "#shellcheck disable=SC2086\n#lol\necho $1"
|
||||
|
||||
prop_optionDisablesIssue1 =
|
||||
null $ shellCheck (defaultAnalysisOptions { optionExcludes = [2086, 2148] }) "echo $1"
|
||||
|
||||
prop_optionDisablesIssue2 =
|
||||
null $ shellCheck (defaultAnalysisOptions { optionExcludes = [2148, 1037] }) "echo \"$10\""
|
||||
|
||||
return []
|
||||
runTests = $quickCheckAll
|
||||
|
148
shellcheck.1.md
Normal file
148
shellcheck.1.md
Normal file
@@ -0,0 +1,148 @@
|
||||
% 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.
|
||||
|
||||
ShellCheck gives shell specific advice. Consider the line:
|
||||
|
||||
(( area = 3.14*r*r ))
|
||||
|
||||
+ For scripts starting with `#!/bin/sh` (or when using `-s sh`), ShellCheck
|
||||
will warn that `(( .. ))` is not POSIX compliant (similar to checkbashisms).
|
||||
|
||||
+ For scripts starting with `#!/bin/bash` (or using `-s bash`), ShellCheck
|
||||
will warn that decimals are not supported.
|
||||
|
||||
+ For scripts starting with `#!/bin/ksh` (or using `-s ksh`), ShellCheck will
|
||||
not warn at all, as `ksh` supports decimals in arithmetic contexts.
|
||||
|
||||
|
||||
# OPTIONS
|
||||
|
||||
**-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.
|
||||
|
||||
**-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.
|
||||
|
||||
**-s**\ *shell*,\ **--shell=***shell*
|
||||
|
||||
: Specify Bourne shell dialect. Valid values are *sh*, *bash* and *ksh*.
|
||||
The default is to use the file's shebang, or *bash* if the target shell
|
||||
can't be determined.
|
||||
|
||||
**-V**\ *version*,\ **--version**
|
||||
|
||||
: Print version and exit.
|
||||
|
||||
# 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.
|
||||
|
||||
[
|
||||
{
|
||||
"file": "filename",
|
||||
"line": lineNumber,
|
||||
"column": columnNumber,
|
||||
"level": "severitylevel",
|
||||
"code": errorCode,
|
||||
"message": "warning 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
|
||||
|
||||
Here a shell brace group is used to suppress on multiple lines:
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
{
|
||||
echo 'Modifying $PATH'
|
||||
echo 'PATH=foo:$PATH' >> ~/.bashrc
|
||||
}
|
||||
|
||||
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)
|
354
shellcheck.hs
Normal file
354
shellcheck.hs
Normal file
@@ -0,0 +1,354 @@
|
||||
{-
|
||||
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 Control.Monad.Trans
|
||||
import Control.Monad.Trans.Error
|
||||
import Control.Monad.Trans.List
|
||||
import Data.Char
|
||||
import Data.Maybe
|
||||
import Data.Monoid
|
||||
import GHC.Exts
|
||||
import GHC.IO.Device
|
||||
import Prelude hiding (catch)
|
||||
import ShellCheck.Data
|
||||
import ShellCheck.Options
|
||||
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
|
||||
data Status = NoProblems | SomeProblems | BadInput | SupportFailure | SyntaxFailure | RuntimeException deriving (Ord, Eq)
|
||||
|
||||
data JsonComment = JsonComment FilePath ShellCheckComment
|
||||
|
||||
instance Error Status where
|
||||
noMsg = RuntimeException
|
||||
|
||||
instance Monoid Status where
|
||||
mempty = NoProblems
|
||||
mappend = max
|
||||
|
||||
header = "Usage: shellcheck [OPTIONS...] FILES..."
|
||||
options = [
|
||||
Option "e" ["exclude"]
|
||||
(ReqArg (Flag "exclude") "CODE1,CODE2..") "exclude types of warnings",
|
||||
Option "f" ["format"]
|
||||
(ReqArg (Flag "format") "FORMAT") "output format",
|
||||
Option "s" ["shell"]
|
||||
(ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh)",
|
||||
Option "V" ["version"]
|
||||
(NoArg $ Flag "version" "true") "Print version information"
|
||||
]
|
||||
|
||||
printErr = hPutStrLn stderr
|
||||
|
||||
|
||||
instance JSON (JsonComment) where
|
||||
showJSON (JsonComment filename c) = makeObj [
|
||||
("file", showJSON $ filename),
|
||||
("line", showJSON $ scLine c),
|
||||
("column", showJSON $ scColumn c),
|
||||
("level", showJSON $ scSeverity c),
|
||||
("code", showJSON $ scCode c),
|
||||
("message", showJSON $ scMessage c)
|
||||
]
|
||||
readJSON = undefined
|
||||
|
||||
parseArguments :: [String] -> ErrorT Status IO ([Flag], [FilePath])
|
||||
parseArguments argv =
|
||||
case getOpt Permute options argv of
|
||||
(opts, files, []) -> return (opts, files)
|
||||
(_, _, errors) -> do
|
||||
liftIO . printErr $ concat errors ++ "\n" ++ usageInfo header options
|
||||
throwError SyntaxFailure
|
||||
|
||||
formats :: Map.Map String (AnalysisOptions -> [FilePath] -> IO Status)
|
||||
formats = Map.fromList [
|
||||
("json", forJson),
|
||||
("gcc", forGcc),
|
||||
("checkstyle", forCheckstyle),
|
||||
("tty", forTty)
|
||||
]
|
||||
|
||||
toStatus = liftM (either id (const NoProblems)) . runErrorT
|
||||
|
||||
catchExceptions :: IO Status -> IO Status
|
||||
catchExceptions action = action -- action `catch` handler
|
||||
where
|
||||
handler err = do
|
||||
printErr $ show (err :: SomeException)
|
||||
return RuntimeException
|
||||
|
||||
checkComments comments = if null comments then NoProblems else SomeProblems
|
||||
|
||||
forTty :: AnalysisOptions -> [FilePath] -> IO Status
|
||||
forTty options files = do
|
||||
output <- mapM doFile files
|
||||
return $ mconcat 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 = catchExceptions $ 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 . checkComments $ 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
|
||||
|
||||
forJson :: AnalysisOptions -> [FilePath] -> IO Status
|
||||
forJson options files = catchExceptions $ do
|
||||
comments <- runListT $ do
|
||||
file <- ListT $ return files
|
||||
comment <- ListT $ commentsFor options file
|
||||
return $ JsonComment file comment
|
||||
putStrLn $ encodeStrict comments
|
||||
return $ checkComments comments
|
||||
|
||||
-- Mimic GCC "file:line:col: (error|warning|note): message" format
|
||||
forGcc :: AnalysisOptions -> [FilePath] -> IO Status
|
||||
forGcc options files = do
|
||||
files <- mapM process files
|
||||
return $ mconcat files
|
||||
where
|
||||
process file = catchExceptions $ do
|
||||
contents <- readContents file
|
||||
let comments = makeNonVirtual (getComments options contents) contents
|
||||
mapM_ (putStrLn . format file) comments
|
||||
return $ checkComments 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 :: AnalysisOptions -> [FilePath] -> IO Status
|
||||
forCheckstyle options files = do
|
||||
putStrLn "<?xml version='1.0' encoding='UTF-8'?>"
|
||||
putStrLn "<checkstyle version='4.3'>"
|
||||
statuses <- mapM process files
|
||||
putStrLn "</checkstyle>"
|
||||
return $ mconcat statuses
|
||||
where
|
||||
process file = catchExceptions $ do
|
||||
comments <- commentsFor options file
|
||||
putStrLn (formatFile file comments)
|
||||
return $ checkComments comments
|
||||
|
||||
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 = shellCheck
|
||||
|
||||
readContents :: FilePath -> IO String
|
||||
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 =
|
||||
if scLine c > 0 && scLine c <= length ls
|
||||
then real (ls !! (scLine c - 1)) 0 0 (scColumn c)
|
||||
else 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
|
||||
status <- toStatus $ do
|
||||
(flags, files) <- parseArguments args
|
||||
process flags files
|
||||
exitWith $ statusToCode status
|
||||
|
||||
statusToCode status =
|
||||
case status of
|
||||
NoProblems -> ExitSuccess
|
||||
SomeProblems -> ExitFailure 1
|
||||
BadInput -> ExitFailure 5
|
||||
SyntaxFailure -> ExitFailure 3
|
||||
SupportFailure -> ExitFailure 4
|
||||
RuntimeException -> ExitFailure 2
|
||||
|
||||
process :: [Flag] -> [FilePath] -> ErrorT Status IO ()
|
||||
process flags files = do
|
||||
options <- foldM (flip parseOption) defaultAnalysisOptions flags
|
||||
verifyFiles files
|
||||
let format = fromMaybe "tty" $ getOption flags "format"
|
||||
case Map.lookup format formats of
|
||||
Nothing -> do
|
||||
liftIO $ do
|
||||
printErr $ "Unknown format " ++ format
|
||||
printErr "Supported formats:"
|
||||
mapM_ (printErr . write) $ Map.keys formats
|
||||
throwError SupportFailure
|
||||
where write s = " " ++ s
|
||||
Just f -> ErrorT $ liftM Left $ f options files
|
||||
|
||||
parseOption flag options =
|
||||
case flag of
|
||||
Flag "shell" str ->
|
||||
fromMaybe (die $ "Unknown shell: " ++ str) $ do
|
||||
shell <- shellForExecutable str
|
||||
return $ return options { optionShellType = Just shell }
|
||||
|
||||
Flag "exclude" str -> do
|
||||
new <- mapM parseNum $ split ',' str
|
||||
let old = optionExcludes options
|
||||
return options { optionExcludes = new ++ old }
|
||||
|
||||
Flag "version" _ -> do
|
||||
liftIO printVersion
|
||||
throwError NoProblems
|
||||
|
||||
_ -> return options
|
||||
where
|
||||
die s = do
|
||||
liftIO $ printErr s
|
||||
throwError SupportFailure
|
||||
parseNum ('S':'C':str) = parseNum str
|
||||
parseNum num = do
|
||||
unless (all isDigit num) $ do
|
||||
liftIO . printErr $ "Bad exclusion: " ++ num
|
||||
throwError SyntaxFailure
|
||||
return (Prelude.read num :: Integer)
|
||||
|
||||
verifyFiles files =
|
||||
when (null files) $ do
|
||||
liftIO $ printErr "No files specified.\n"
|
||||
liftIO $ printErr $ usageInfo header options
|
||||
throwError SyntaxFailure
|
||||
|
||||
printVersion = 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"
|
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
|
16
test/shellcheck.hs
Normal file
16
test/shellcheck.hs
Normal file
@@ -0,0 +1,16 @@
|
||||
module Main where
|
||||
|
||||
import Control.Monad
|
||||
import System.Exit
|
||||
import qualified ShellCheck.Simple
|
||||
import qualified ShellCheck.Analytics
|
||||
import qualified ShellCheck.Parser
|
||||
|
||||
main = do
|
||||
putStrLn "Running ShellCheck tests..."
|
||||
results <- sequence [ShellCheck.Simple.runTests,
|
||||
ShellCheck.Analytics.runTests,
|
||||
ShellCheck.Parser.runTests]
|
||||
if and results then exitSuccess
|
||||
else exitFailure
|
||||
|
Reference in New Issue
Block a user