Showing preview only (241K chars total). Download the full file or copy to clipboard to get everything.
Repository: xenomachina/kotlin-argparser
Branch: master
Commit: 9367da9e757c
Files: 26
Total size: 230.5 KB
Directory structure:
gitextract_f8jhxc_z/
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── COPYING
├── README.md
├── build.gradle
├── codecov.sh
├── detekt.yml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src/
├── main/
│ └── kotlin/
│ └── com/
│ └── xenomachina/
│ └── argparser/
│ ├── ArgParser.kt
│ ├── Default.kt
│ ├── DefaultHelpFormatter.kt
│ ├── Exceptions.kt
│ ├── HelpFormatter.kt
│ ├── OptionDelegate.kt
│ ├── ParsingDelegate.kt
│ ├── PositionalDelegate.kt
│ ├── PosixNaming.kt
│ ├── SystemExitException.kt
│ └── WrappingDelegate.kt
└── test/
└── kotlin/
└── com/
└── xenomachina/
└── argparser/
└── ArgParserTest.kt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
### Gradle ###
.gradle
/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.propertiesgradletasknamecache
### IntelliJ IDEA ###
.idea/
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
================================================
FILE: .travis.yml
================================================
language: java
jdk:
- openjdk8
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/
after_success:
- ./gradlew jacocoTestReport && ./codecov.sh
================================================
FILE: CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## 2.0.7 - 2018-05-08
### Fixed
- [Issue #54](https://github.com/xenomachina/kotlin-argparser/issues/54)
Allow the dot character in options and arguments. Thanks @tgockel!
- [Issue #47](https://github.com/xenomachina/kotlin-argparser/issues/47)
If an option looks at the value of another option it can see the
current value. If no value has been set a `MissingValueException` is thrown.
## 2.0.6 - 2018-03-26
### Changed
- Help text formatting now treats multi-newlines as paragraph separators, while
single newlines are still treated like spaces. Thanks @leomillon!
## 2.0.5 - 2018-03-20
### Changed
- Releasing to Maven Central in addition to Bintray. This is probably the only
really externally visible change.
- Upgraded a bunch of dependencies, including gradlew.
- gradle -> 4.5.1
- dokka -> = 0.9.16
- gradle_bintray -> = 1.8.0
- gradle_release -> = 2.6.0
- kotlin -> 1.2.30
- xenocom -> 0.0.6
## 2.0.4 - 2018-01-18
### Added
- If the `programName` passed to `mainBody` is null, then the
system property `com.xenomachina.argparser.programName` is used, if set.
- The `parseInto` method can be used as an inline alternative to `force`.
Thanks @shanethehat!
- [Issue #24](https://github.com/xenomachina/kotlin-argparser/issues/18):
`default` can now accept a lambda, making it possible to defer computation of
defaults until actually required.
Thanks @shanethehat!
### Changed
- All instances of `progName` have been renamed to `programName`.
- The gradle wrapper has been updated.
Thanks @ColinHebert!
## 2.0.3 - 2017-06-12
### Fixed
- [Issue #18](https://github.com/xenomachina/kotlin-argparser/issues/18)
## 2.0.2 - 2017-06-12
### Fixed
- [Issue #19](https://github.com/xenomachina/kotlin-argparser/issues/19) by
updating xenocom dependency to 0.0.5. Also updated kotlin to 1.1.2-5 for good
measure.
## 2.0.1 - 2017-05-15
### Changed
- [Issue #14](https://github.com/xenomachina/kotlin-argparser/issues/14) —
previously, automatic option naming would turn "camelCase" into
"--camelCase". Now it is converted to "--camel-case".
- Likewise, positinal argument auto-naming used to convert "camelCase" into
"CAMELCASE". Now it is converted to "CAMEL-CASE".
- Improve help formatting w/long program names
- README formatting improved.
Thanks @konfilios!
### Fixed
- [Issue #17](https://github.com/xenomachina/kotlin-argparser/issues/17) —
specifying 0 for the columns should format help without line wrapping.
Previously, this did not work as documented, and would instead wrap text in
very narrow columns.
- [Issue #15](https://github.com/xenomachina/kotlin-argparser/issues/15)
— make it possible to specify 'argName' on all variants of 'storing' and
`adding`
## 2.0.0 - 2017-04-21
### Added
- `ArgParser.option` is now a public method, so it's possible to create many
new option types that were not previously possible. The existing option types
are all written in terms of `option`, so they can be used to get an idea of
how it works.
- More tests have been added.
- Started using keepachangelog.com format for CHANGELOG.md
- Made minor improvements to release process
### Changed
- The `storing`, `adding` and `positionalList` methods of `ArgParser` have had
their parameters slightly reordered to be consistent with the other methods.
This is an incompatible change. The name(s) come first, if any, followed by
`help`. Other parameters appear after `help`, with the `transform` function,
if any, last. It is recommended that clients either pass the transform as a
block (ie: with braces) or as a named parameter, as any future new parameters
will necessarily change its position in the list.
- Delegate and DelegateProvider are now abstract classes with internal
constructors. This makes it much easier for me to separate internal and
public parts of their API. This is an incompatible change, however it
shouldn't really affect you unless you were trying to implement `Delegate`,
which was't supported to begin with.
- `default` methods on both `Delegate` and `DelegateProvider` are now extension
methods. This makes it possible to generalize the type when adding a
default. This is most noticable when using a nullable value (or `null`
itself) for the default, though may also be useful in other cases (eg: a
"storing" that always produces a `Rectangle`, but you want the default to be
a `Circle`. The resulting delegate will be a `Delegate<Shape>`.)
- Registration of delegates now takes place at binding-time rather than
construction time. This should be pretty indistinguishable from the old
behavior unless you're creating delegates without binding them.
- Help formatting has been improved so that it's far less likely to wrap option
names in the usage table.
- There have been numerous bugfixes, particularly around positionals
## 1.1.0 - 2017-03-09
### Added
- Auto-naming of options and positionals.
- Each of the ArgParser methods that takes names and returns a Delegate<T> has
an overload that takes no name, but returns a DelegateProvider<T>.
- A DelegateProvider<T> has an `operator fun provideDelegate` that returns a
Delegate<T>, using a name derived from the name of the property the
DelegateProvider is being bound to.
### Removed
- `addValidtator` is now deprecated. Use `addValidator` instead.
### Fixed
- Removed documentation of `option` from `README.md`, as it is internal
- Corrected spelling of `addValidator`. `addValidtator` is still there, but
deprecated. It'll probably be removed in the next release, barring the
addition of potato functionality.
## 1.0.2 - 2017-03-07
### Changed
- Upgrade to Kotlin 1.1, extract xenocom package.
## 1.0.1 - 2017-01-30
### Fixed
- Fix small bug where `runMain` didn't have a default value for `columns`
parameter. (Now defaults to null.)
## 1.0.0 - 2017-01-27
### Added
- Initial release
================================================
FILE: COPYING
================================================
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
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 this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
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
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser 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 Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "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
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY 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
LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey 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 library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
================================================
FILE: README.md
================================================
# <img alt="Kotlin --argparser" src="https://rawgit.com/xenomachina/kotlin-argparser/master/logo.svg" style="transform:scale(.6)" >
<!--
Removed until either Bintray makes a badge with a configurable label, or
shields.io can make a reliable bintray badge.
[](https://bintray.com/xenomachina/maven/kotlin-argparser/%5FlatestVersion)
-->
[](https://mvnrepository.com/artifact/com.xenomachina/kotlin-argparser)
[](https://travis-ci.org/xenomachina/kotlin-argparser)
[](https://codebeat.co/projects/github-com-xenomachina-kotlin-argparser-master)
[](https://github.com/KotlinBy/awesome-kotlin)
[](https://www.javadoc.io/doc/com.xenomachina/kotlin-argparser)
[](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html)
This is a library for parsing command-line arguments. It can parse both
options and positional arguments. It aims to be easy to use and concise yet
powerful and robust.
## Overview
Defining options and positional arguments is as simple as:
```kotlin
import com.xenomachina.argparser.ArgParser
class MyArgs(parser: ArgParser) {
val v by parser.flagging("enable verbose mode")
val name by parser.storing("name of the user")
val count by parser.storing("number of the widgets") { toInt() }
val source by parser.positional("source filename")
val destination by parser.positional("destination filename")
}
```
An instance of `MyArgs` will represent the set of parsed arguments. Each option
and positional argument is declared as a property that delegates through a
delegate factory method on an instance of `ArgParser`.
The name of an option is inferred from the name of the property it is bound to.
The options above are named `-v`, `--name` and `--count`, respectively. There
are also two positional arguments.
Direct control over an option's name is also possible, and for most types of
options it is also possible to have multiple names, like a short and long name:
```kotlin
class MyArgs(parser: ArgParser) {
val verbose by parser.flagging(
"-v", "--verbose",
help = "enable verbose mode")
val name by parser.storing(
"-N", "--name",
help = "name of the user")
val count by parser.storing(
"-c", "--count",
help = "number of widgets") { toInt() }
val source by parser.positional(
"SOURCE",
help = "source filename")
val destination by parser.positional(
"DEST",
help = "destination filename")
}
```
The unparsed command-line arguments are passed to the `ArgParser` instance at
construction:
```kotlin
fun main(args: Array<String>) = mainBody {
ArgParser(args).parseInto(::MyArgs).run {
println("Hello, ${name}!")
println("I'm going to move ${count} widgets from ${source} to ${destination}.")
// TODO: move widgets
}
}
```
See [kotlin-argparser-example](https://github.com/xenomachina/kotlin-argparser-example)
for a complete example project.
## Nomenclature
Options, arguments, flags... what's the difference?
An application's `main` function is passed an array of strings. These are
the *unparsed command-line arguments*, or *unparsed arguments* for short.
The unparsed arguments can then be parsed into *options*, which start
with a hyphen ("`-`"), and *positional arguments*. For example, in the command
`ls -l /tmp/`, the unparsed arguments would be `"-l", "/tmp"` where `-l`
is an option, while `/tmp/` is a positional argument.
Options can also have *option arguments*. In the command `ls --time-style=iso`,
the option is `--time-style` and that options argument is `iso`. Note that in
parsing a single unparsed argument can be split into an option and an option
argument, or even into multiple options in some cases.
A *flag* is a boolean option which has no arguments and which is false if not
provided, but true if provided. The `-l` option of `ls` is a flag.
## Option Types
### Boolean Flags
Boolean flags are created by asking the parser for a `flagging` delegate. One
or more option names, may be provided:
```kotlin
val verbose by parser.flagging("-v", "--verbose",
help = "enable verbose mode")
```
Here the presence of either `-v` or `--verbose` options in the
arguments will cause the `Boolean` property `verbose` to be `true`, otherwise
it will be `false`.
### Storing a Single Argument
Single argument options are created by asking the parser for a
`storing` delegate.
```kotlin
val name by parser.storing("-N", "--name",
help = "name of the user")
```
Here either `-N` or `--name` with an argument will cause the `name` property to
have that argument as its value.
A function can also be supplied to transform the argument into the desired
type. Here the `size` property will be an `Int` rather than a `String`:
```kotlin
val size by parser.storing("-c", "--count",
help = "number of widgets") { toInt() }
```
### Adding to a Collection
Options that add to a `Collection` each time they appear in the arguments are
created with using the `adding` delegate. Just like `storing` delegates, a
transform function may optionally be supplied:
```kotlin
val includeDirs by parser.adding(
"-I", help = "directory to search for header files") { File(this) }
```
Now each time the `-I` option appears, its transformed argument is appended to
`includeDirs`.
### Mapping from an option to a fixed value
For choosing between a fixed set of values (typically, but not necessarily,
from an enum), a `mapping` delegate can be used:
```kotlin
val mode by parser.mapping(
"--fast" to Mode.FAST,
"--small" to Mode.SMALL,
"--quiet" to Mode.QUIET,
help = "mode of operation")
```
Here the `mode` property will be set to the corresponding `ArgParser.Mode` value depending
on which of `--fast`, `--small`, and `--quiet` appears (last) in the arguments.
`mapping` is one of the few cases where it is not possible to infer the option
name from the property name.
### More advanced options
For all other types of options, the `option` method should be used. The
methods mentioned above are, in fact, convenience methods built on top of the
`option` method.
For example, it is possible to create an option that has multiple arguments:
```kotlin
fun ArgParser.putting(vararg names: String, help: String) =
option<MutableMap<String, String>>(*names,
argNames = listOf("KEY", "VALUE"),
help = help) {
value.orElse { mutableMapOf<String, String>() }.apply {
put(arguments.first(), arguments.last()) }
}
```
Note that the `option` method does not have an auto-naming overload. If you
need this capability, create a `DelegateProvider` that creates your `Delegate`:
```kotlin
fun ArgParser.putting(help: String) =
ArgParser.DelegateProvider { identifier ->
putting(identifierToOptionName(identifier), help = help) }
```
## Positional Arguments
Positional arguments are collected by using the `positional` and
`positionalList` methods.
For a single positional argument:
```kotlin
val destination by parser.positional("destination filename")
```
An explicit name may also be specified:
```kotlin
val destination by parser.positional("DEST",
help = "destination filename")
```
The name ("DEST", here) is used in error handling and help text.
For a list of positional arguments:
```kotlin
val sources by parser.positionalList("SOURCE", 1..Int.MAX_VALUE,
help = "source filename")
```
The range indicates how many arguments should be collected, and defaults to the
value shown in this example. As the name suggests, the resulting property will
be a `List`.
Both of these methods accept an optional transform function for converting
arguments from `String` to whatever type is actually desired:
```kotlin
val destination by parser.positional("DEST",
help = "...") { File(this) }
val sources by parser.positionalList("SOURCE", 1..Int.MAX_VALUE,
help = "...") { File(this) }
```
## Modifying Delegates
The delegates returned by any of these methods also have a few methods for setting
optional attributes:
### Adding a Default Value
Certain types of delegates (notably `storing`, `mapping`, and `positional`)
have no default value, and hence will be required options unless a default
value is provided. This is done with the `default` method:
```kotlin
val name by parser.storing("-N", "--name", help = "...").default("John Doe")
```
Note that it *is* possible to use `null` for the default, though this may
require specifying the type parameter for `default` explicitly:
```kotlin
val name by parser.storing("-N", "--name", help = "...").default<String?>(null)
```
The type of the resulting property be nullable (a `String?` in this case).
### Adding a Validator
Sometimes it's easier to validate an option at the end of parsing, in which
case the `addValidator` method can be used.
```kotlin
val percentages by parser.adding("--percentages", help = "...") { toInt() }
.addValidator {
if (value.sum() != 100)
throw InvalidArgumentException(
"Percentages must add up to 100%")
}
```
## Error Handling
If the parser determines that execution should not continue it will throw a
`SystemExitException` which has a status code appropriate for passing to
`exitProcess` as well as a message for the user.
These exceptions can be caused by user error, or even if the user requests help
(eg: via the `--help` option).
It is recommended that transform functions (given to `storing`,
`positionalList`, etc.) and post-parsing validation, including that performed
via, `addValidator` also throw a `SystemExitException` on failure.
As a convenience, these exceptions can be handled by using the `mainBody`
function:
```kotlin
class ParsedArgs(parser: ArgParser) {
val name by positional("The user's name").default("world")
}
fun main(args: Array<String>) = mainBody {
ArgParser(args).parseInto(::ParsedArgs).run {
println("Hello, {name}!")
}
}
```
## Parsing
Parsing of command-line arguments is performed sequentially. So long as
option-processing is enabled, each not-yet-processed command-line argument that
starts with a hyphen (`-`) is treated as an option.
### Short Options
Short options start with a single hyphen. If the option takes an argument, the
argument can either be appended:
```bash
# "-o" with argument "ARGUMENT"
my_program -oARGUMENT
```
or can be the following command-line argument:
```bash
# "-o" with argument "ARGUMENT"
my_program -o ARGUMENT
```
Zero argument short options can also be appended to each other without
intermediate hyphens:
```bash
# "-x", "-y" and "-z" options
my_program -xyz
```
An option that accepts arguments is also allowed at the end of such a chain:
```bash
# "-x", "-y" and "-z" options, with argument for "-z"
my_program -xyzARGUMENT
```
### Long Options
Long options start with a double hyphen (`--`). An argument to a long option
can
either be delimited with an equal sign (`=`):
```bash
# "--foo" with argument "ARGUMENT"
my_program --foo=ARGUMENT
```
or can be the following command-line argument:
```bash
# "--foo" with argument "ARGUMENT"
my_program --foo ARGUMENT
```
### Multi-argument Options
Multi-argument options are supported, though currently not by any of the
convenience methods. Option-arguments after the first must be separate
command-line arguments, for both an long and short forms of an option.
### Positional Arguments
In GNU mode (the default), options can be interspersed with positional
arguments, but in POSIX mode the first positional argument that is encountered
disables option processing for the remaining arguments. In either mode, if the
argument "--" is encountered while option processing is enabled, then option
processing is disabled for the rest of the command-line. Once the options and
option-arguments have been eliminated, what remains are considered to be
positional arguments.
Each positional argument delegate can specify a minimum and maximum number of
arguments it is willing to collect.
The positional arguments are distributed to the delegates by allocating each
positional delegate at least as many arguments as it requires. If more than the
minimum number of positional arguments have been supplied then additional
arguments will be allocated to the first delegate up to its maximum, then the
second, and so on, until all arguments have been allocated to a delegate.
This makes it easy to create a program that behaves like `grep`:
```kotlin
class Args(parser: ArgParser) {
// accept 1 regex followed by n filenames
val regex by parser.positional("REGEX",
help = "regular expression to search for")
val files by parser.positionalList("FILE",
help = "file to search in")
}
```
And equally easy to create a program that behaves like `cp`:
```kotlin
class Args(parser: ArgParser) {
// accept n source files followed by 1 destination
val sources by parser.positionalList("SOURCE",
help = "source file")
val destination by parser.positional("DEST",
help = "destination file")
}
```
## Forcing Parsing
Parsing normally does not begin until a delegate's value is accessed. Sometimes
this is not desirable, so it is possible to enforce the parsing of arguments
into a class of values. This ensures that all arguments that are required are
provided, and all arguments provided are consumed.
Forcing can be done in a separate step using the `force` method:
```kotlin
val parser = ArgParser(args)
val parsedArgs = ParsedArgs(parser)
parser.force()
// now you can use parsedArgs
```
Alternatively, forcing can be done inline via the `parseInto` method:
```kotlin
val parsedArgs = ArgParser(args).parseInto(::ParsedArgs)
// now you can use parsedArgs
```
In both cases exceptions will be thrown where parsing or validation errors are found.
## Help Formatting
By default, `ArgParser` will add a `--help` option (short name `-h`) for
displaying usage information. If this option is present a `ShowHelpException` will be thrown.
If the default exception handling is being used (see [Error Handling](#error-handling)) the
program will halt and print a help message like the one below, based on the `ArgParser`
configuration:
usage: program_name [-h] [-n] [-I INCLUDE]... -o OUTPUT
[-v]... SOURCE... DEST
This is the prologue. Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Aliquam malesuada maximus eros. Fusce
luctus risus eget quam consectetur, eu auctor est
ullamcorper. Maecenas eget suscipit dui, sed sodales erat.
Phasellus.
required arguments:
-o OUTPUT, directory in which all output should
--output OUTPUT be generated
optional arguments:
-h, --help show this help message and exit
-n, --dry-run don't do anything
-I INCLUDE, search in this directory for header
--include INCLUDE files
-v, --verbose increase verbosity
positional arguments:
SOURCE source file
DEST destination file
This is the epilogue. Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Donec vel tortor nunc. Sed eu
massa sed turpis auctor faucibus. Donec vel pellentesque
tortor. Ut ultrices tempus lectus fermentum vestibulum.
Phasellus.
The creation of the `--help` option can be disabled by passing `null` as the
`helpFormatter` when constructing the `ArgParser`, or configured by manually
constructing a `HelpFormatter` instance. In the above example a
`DefaultHelpFormatter` was created with the prologue and epilogue.
## Caveats
- This library should be considered to be *very beta*. While there are no plans
to make any breaking changes to the API, it's possible that there may be some
until it is mature.
- Upon reading the value any of the delegated properties created by an
`ArgParser`, the arguments used to construct that `ArgParser` will be
parsed. This means it's important that you don't attempt to create delegates
on an `ArgParser` after any of its existing delegated properties have been
read. Attempting to do so will cause an `IllegalStateException`. It would be
nice if Kotlin had facilities for doing some of the work of `ArgParser` at
compile time rather than run time, but so far the run time errors seem to be
reasonably easy to avoid.
## Configuring Your Build
<!-- TODO move detailed instructions elsewhere, just have brief instructions here -->
Kotlin-argparser binaries are hosted on Maven Central and also Bintray's
JCenter.
In Gradle, add something like this in your `build.gradle`:
```groovy
// you probably already have this part
buildscript {
repositories {
mavenCentral() // or jcenter()
}
}
dependencies {
compile "com.xenomachina:kotlin-argparser:$kotlin_argparser_version"
}
```
In Maven add something like this to your `pom.xml`:
```xml
<dependency>
<groupId>com.xenomachina</groupId>
<artifactId>kotlin-argparser</artifactId>
<version>VERSION</version>
</dependency>
```
Information on setting up other build systems, as well as the current version
number, can be found on
[MVN Repository's page for Kotlin-argparser](https://mvnrepository.com/artifact/com.xenomachina/kotlin-argparser/latest).
## Thanks
Thanks to the creators of Python's
[`argparse`](https://docs.python.org/3/library/argparse.html) module, which
provided the initial inspiration for this library.
Thanks also to the team behind [Kotlin](https://kotlinlang.org/).
Finally, thanks to all of the people who have contributed
[code](https://github.com/xenomachina/kotlin-argparser/graphs/contributors)
and/or
[issues](https://github.com/xenomachina/kotlin-argparser/issues).
================================================
FILE: build.gradle
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
buildscript {
repositories {
mavenCentral()
jcenter()
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_version"
classpath "gradle.plugin.io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detekt_version"
}
}
plugins {
id 'com.palantir.git-version' version '0.12.0-rc2'
id 'org.jmailen.kotlinter' version '1.12.0'
}
apply plugin: 'maven'
apply plugin: 'kotlin'
apply plugin: 'java'
apply plugin: 'org.jetbrains.dokka'
apply plugin: 'signing'
apply plugin: 'com.jfrog.bintray'
apply plugin: 'maven-publish'
apply plugin: 'jacoco'
apply plugin: "io.gitlab.arturbosch.detekt"
assert name == 'kotlin-argparser'
description = 'Concise, easy, powerful and robust command line argument parsing for Kotlin'
group 'com.xenomachina'
version = gitVersion()
project.ext {
githubUser = "xenomachina"
vcsDev = "https://github.com/$githubUser"
githubRepo = "$githubUser/$name"
vcsUrl = "https://github.com/$githubRepo"
}
sourceCompatibility = 1.6
repositories {
mavenCentral()
jcenter()
}
test {
useJUnitPlatform()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "com.xenomachina:xenocom:$xenocom_version"
testCompile "io.kotlintest:kotlintest-runner-junit5:$kotlintest_version"
testCompile "org.slf4j:slf4j-simple:$slf4j_version"
// This is to ensure that kotlintest uses the correct version of
// kotlin-reflect
testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
//////////////////////////////////////////////////////////////////////////////
// Detekt config
//////////////////////////////////////////////////////////////////////////////
detekt {
// remove this when plugin stops choosing latest version by default
version = "$detekt_version"
defaultProfile {
input = file("src/main/kotlin")
filters = ".*/resources/.*,.*/build/.*"
config = file("detekt.yml")
}
}
//////////////////////////////////////////////////////////////////////////////
// Dokka config
//////////////////////////////////////////////////////////////////////////////
dokka {
moduleName = project.name
// TODO: includes = ['Module.md']
linkMapping {
dir = "src/main/kotlin"
url = "$project.ext.vcsUrl/blob/master/src/main/kotlin"
suffix = "#L"
}
sourceDirs = files('src/main/kotlin')
}
task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) {
outputFormat = "javadoc"
outputDirectory = javadoc.destinationDir
linkMapping {
dir = "src/main/kotlin"
url = "$project.ext.vcsUrl/blob/master/src/main/kotlin"
suffix = "#L"
}
sourceDirs = files('src/main/kotlin')
}
//Based on comment by @jnizet at https://github.com/Kotlin/dokka/issues/42
task javadocJar(type: Jar, dependsOn: dokkaJavadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
artifacts {
archives javadocJar, sourcesJar
}
if (System.env.CI != "true") {
// Some of the stuff in here can break continuous integration, and none of
// it is necessary in CI.
//////////////////////////////////////////////////////////////////////////////
// POM config
//////////////////////////////////////////////////////////////////////////////
def pomConfig = {
packaging 'jar'
name project.name
description project.description
url project.ext.vcsUrl
scm {
connection "scm:git:$project.ext.vcsUrl"
developerConnection "scm:git:$project.ext.vcsDev"
url "$project.ext.vcsUrl"
}
licenses {
license {
name "GNU Lesser General Public License, Version 2.1"
url "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt"
distribution "repo"
}
}
developers {
developer {
id "xenomachina"
name "Laurence Gonsalves"
// State-of-the art anti-scraper encryption. ;-)
email "moc.anihcamonex@ecnerual".reverse()
}
}
}
//////////////////////////////////////////////////////////////////////////////
// Maven Central upload
//////////////////////////////////////////////////////////////////////////////
signing {
useGpgCmd()
sign configurations.archives
}
def ossrhUsernameOrEmpty = hasProperty('ossrhUsername') ? ossrhUsername : ''
def ossrhPasswordOrEmpty = hasProperty('ossrhPassword') ? ossrhPassword : ''
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
authentication(userName: ossrhUsernameOrEmpty, password: ossrhPasswordOrEmpty)
}
snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
authentication(userName: ossrhUsernameOrEmpty, password: ossrhPasswordOrEmpty)
}
pom.project pomConfig
}
}
}
//////////////////////////////////////////////////////////////////////////////
// bintray upload
//////////////////////////////////////////////////////////////////////////////
// Based on https://github.com/bintray/gradle-bintray-plugin
bintray {
user = System.getenv('BINTRAY_USER')
key = System.getenv('BINTRAY_KEY')
pkg {
repo = 'maven'
name = project.name
desc = project.description
licenses = ['LGPL-2.1']
vcsUrl = "$project.ext.vcsUrl"
websiteUrl = "$project.ext.vcsUrl"
issueTrackerUrl = "$project.ext.vcsUrl/issues"
githubRepo = project.ext.githubRepo
githubReleaseNotesFile = 'CHANGELOG.md'
version {
name = project.version
desc = "$project.name version $project.version"
released = new Date()
vcsTag = "$project.version"
}
}
publications = ['MyPublication']
}
// Create the publication with the pom configuration:
publishing {
publications {
MyPublication(MavenPublication) {
from components.java
artifact sourcesJar
artifact javadocJar
pom.withXml {
def root = asNode()
root.children().last() + pomConfig
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////////
// jacoco (codecov.io) plugin config
//////////////////////////////////////////////////////////////////////////////
jacocoTestReport {
reports {
xml.enabled true
html.enabled false
}
}
================================================
FILE: codecov.sh
================================================
#!/usr/bin/env bash
set -e +o pipefail
VERSION="8fda091"
url="https://codecov.io"
env="$CODECOV_ENV"
service=""
token=""
search_in=""
flags=""
exit_with=0
curlargs=""
curlawsargs=""
dump="0"
clean="0"
curl_s="-s"
name="$CODECOV_NAME"
include_cov=""
exclude_cov=""
ddp="$(echo ~)/Library/Developer/Xcode/DerivedData"
xp=""
files=""
cacert="$CODECOV_CA_BUNDLE"
gcov_ignore=""
gcov_include=""
ft_gcov="1"
ft_coveragepy="1"
ft_fix="1"
ft_search="1"
ft_s3="1"
ft_xcode="1"
ft_network="1"
ft_xcodeplist="0"
_git_root=$(git rev-parse --show-toplevel 2>/dev/null || hg root 2>/dev/null || echo $PWD)
git_root="$_git_root"
remote_addr=""
if [ "$git_root" = "$PWD" ];
then
git_root="."
fi
url_o=""
pr_o=""
build_o=""
commit_o=""
search_in_o=""
tag_o=""
branch_o=""
slug_o=""
commit="$VCS_COMMIT_ID"
branch="$VCS_BRANCH_NAME"
pr="$VCS_PULL_REQUEST"
slug="$VCS_SLUG"
tag="$VCS_TAG"
build_url="$CI_BUILD_URL"
build="$CI_BUILD_ID"
job="$CI_JOB_ID"
beta_xcode_partials=""
proj_root="$git_root"
gcov_exe="gcov"
gcov_arg=""
b="\033[0;36m"
g="\033[0;32m"
r="\033[0;31m"
e="\033[0;90m"
x="\033[0m"
show_help() {
cat << EOF
Codecov Bash $VERSION
Global report uploading tool for Codecov
Documentation at https://docs.codecov.io/docs
Contribute at https://github.com/codecov/codecov-bash
-h Display this help and exit
-f FILE Target file(s) to upload
-f "path/to/file" only upload this file
skips searching unless provided patterns below
-f '!*.bar' ignore all files at pattern *.bar
-f '*.foo' include all files at pattern *.foo
Must use single quotes.
This is non-exclusive, use -s "*.foo" to match specific paths.
-s DIR Directory to search for coverage reports.
Already searches project root and artifact folders.
-t TOKEN Set the private repository token
(option) set environment variable CODECOV_TOKEN=:uuid
-t @/path/to/token_file
-t uuid
-n NAME Custom defined name of the upload. Visible in Codecov UI
-e ENV Specify environment variables to be included with this build
Also accepting environment variables: CODECOV_ENV=VAR,VAR2
-e VAR,VAR2
-X feature Toggle functionalities
-X gcov Disable gcov
-X coveragepy Disable python coverage
-X fix Disable report fixing
-X search Disable searching for reports
-X xcode Disable xcode processing
-X network Disable uploading the file network
-R root dir Used when not in git/hg project to identify project root directory
-F flag Flag the upload to group coverage metrics
-F unittests This upload is only unittests
-F integration This upload is only integration tests
-F ui,chrome This upload is Chrome - UI tests
-c Move discovered coverage reports to the trash
-Z Exit with 1 if not successful. Default will Exit with 0
-- xcode --
-D Custom Derived Data Path for Coverage.profdata and gcov processing
Default '~/Library/Developer/Xcode/DerivedData'
-J Specify packages to build coverage.
This can significantly reduces time to build coverage reports.
-J 'MyAppName' Will match "MyAppName" and "MyAppNameTests"
-J '^ExampleApp$' Will match only "ExampleApp" not "ExampleAppTests"
-- gcov --
-g GLOB Paths to ignore during gcov gathering
-G GLOB Paths to include during gcov gathering
-p dir Project root directory
Also used when preparing gcov
-x gcovexe gcov executable to run. Defaults to 'gcov'
-a gcovargs extra arguments to pass to gcov
-- Override CI Environment Variables --
These variables are automatically detected by popular CI providers
-B branch Specify the branch name
-C sha Specify the commit sha
-P pr Specify the pull request number
-b build Specify the build number
-T tag Specify the git tag
-- Enterprise --
-u URL Set the target url for Enterprise customers
Not required when retrieving the bash uploader from your CCE
(option) Set environment variable CODECOV_URL=https://my-hosted-codecov.com
-r SLUG owner/repo slug used instead of the private repo token in Enterprise
(option) set environment variable CODECOV_SLUG=:owner/:repo
(option) set in your codecov.yml "codecov.slug"
-S PATH File path to your cacert.pem file used to verify ssl with Codecov Enterprise (optional)
(option) Set environment variable: CODECOV_CA_BUNDLE="/path/to/ca.pem"
-U curlargs Extra curl arguments to communicate with Codecov. e.g., -U "--proxy http://http-proxy"
-A curlargs Extra curl arguments to communicate with AWS.
-- Debugging --
-d Dont upload and dump to stdout
-K Remove color from the output
-v Verbose mode
EOF
}
say() {
echo -e "$1"
}
urlencode() {
echo "$1" | curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3- | sed -e 's/%0A//'
}
swiftcov() {
_dir=$(dirname "$1")
for _type in app framework xctest
do
find "$_dir" -name "*.$_type" | while read f
do
_proj=${f##*/}
_proj=${_proj%."$_type"}
if [ "$2" = "" ] || [ "$(echo "$_proj" | grep -i "$2")" != "" ];
then
say " $g+$x Building reports for $_proj $_type"
dest=$([ -f "$f/$_proj" ] && echo "$f/$_proj" || echo "$f/Contents/MacOS/$_proj")
_proj_name=$(echo "$_proj" | sed -e 's/[[:space:]]//g')
xcrun llvm-cov show $beta_xcode_partials -instr-profile "$1" "$dest" > "$_proj_name.$_type.coverage.txt" \
|| say " ${r}x>${x} llvm-cov failed to produce results for $dest"
fi
done
done
}
# Credits to: https://gist.github.com/pkuczynski/8665367
parse_yaml() {
local prefix=$2
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 |
awk -F$fs '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; if (indent > 0) {vn=(vn)(vname[0])("_")}
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
}
}'
}
if [ $# != 0 ];
then
while getopts "a:A:b:B:cC:dD:e:f:F:g:G:hJ:Kn:p:P:r:R:s:S:t:T:u:U:vx:X:Z" o
do
case "$o" in
"a")
gcov_arg=$OPTARG
;;
"A")
curlawsargs="$OPTARG"
;;
"b")
build_o="$OPTARG"
;;
"B")
branch_o="$OPTARG"
;;
"c")
clean="1"
;;
"C")
commit_o="$OPTARG"
;;
"d")
dump="1"
;;
"D")
ddp="$OPTARG"
;;
"e")
env="$env,$OPTARG"
;;
"f")
if [ "${OPTARG::1}" = "!" ];
then
exclude_cov="$exclude_cov -not -path '${OPTARG:1}'"
elif [[ "$OPTARG" = *"*"* ]];
then
include_cov="$include_cov -or -name '$OPTARG'"
else
ft_search=0
if [ "$files" = "" ];
then
files="$OPTARG"
else
files="$files
$OPTARG"
fi
fi
;;
"F")
if [ "$flags" = "" ];
then
flags="$OPTARG"
else
flags="$flags,$OPTARG"
fi
;;
"g")
gcov_ignore="$gcov_ignore -not -path '$OPTARG'"
;;
"G")
gcov_include="$gcov_include -path '$OPTARG'"
;;
"h")
show_help
exit 0;
;;
"J")
if [ "$xp" = "" ];
then
xp="$OPTARG"
else
xp="$xp\|$OPTARG"
fi
;;
"K")
b=""
g=""
r=""
e=""
x=""
;;
"n")
name="$OPTARG"
;;
"p")
proj_root="$OPTARG"
;;
"P")
pr_o="$OPTARG"
;;
"r")
slug_o="$OPTARG"
;;
"R")
git_root="$OPTARG"
;;
"s")
if [ "$search_in_o" = "" ];
then
search_in_o="$OPTARG"
else
search_in_o="$search_in_o $OPTARG"
fi
;;
"S")
cacert="--cacert \"$OPTARG\""
;;
"t")
if [ "${OPTARG::1}" = "@" ];
then
token=$(cat "${OPTARG:1}" | tr -d ' \n')
else
token="$OPTARG"
fi
;;
"T")
tag_o="$OPTARG"
;;
"u")
url_o=$(echo "$OPTARG" | sed -e 's/\/$//')
;;
"U")
curlargs="$OPTARG"
;;
"v")
set -x
curl_s=""
;;
"x")
gcov_exe=$OPTARG
;;
"X")
if [ "$OPTARG" = "gcov" ];
then
ft_gcov="0"
elif [ "$OPTARG" = "coveragepy" ] || [ "$OPTARG" = "py" ];
then
ft_coveragepy="0"
elif [ "$OPTARG" = "xcodeplist" ];
then
ft_xcodeplist="1"
ft_xcode="0"
elif [ "$OPTARG" = "fix" ] || [ "$OPTARG" = "fixes" ];
then
ft_fix="0"
elif [ "$OPTARG" = "xcode" ];
then
ft_xcode="0"
elif [ "$OPTARG" = "search" ];
then
ft_search="0"
elif [ "$OPTARG" = "xcodepartials" ];
then
beta_xcode_partials="-use-color"
elif [ "$OPTARG" = "network" ];
then
ft_network="0"
fi
;;
"Z")
exit_with=1
;;
esac
done
fi
say "
_____ _
/ ____| | |
| | ___ __| | ___ ___ _____ __
| | / _ \\ / _\` |/ _ \\/ __/ _ \\ \\ / /
| |___| (_) | (_| | __/ (_| (_) \\ V /
\\_____\\___/ \\__,_|\\___|\\___\\___/ \\_/
Bash-$VERSION
"
search_in="$proj_root"
if [ "$JENKINS_URL" != "" ];
then
say "$e==>$x Jenkins CI detected."
# https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project
# https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin#GitHubpullrequestbuilderplugin-EnvironmentVariables
service="jenkins"
branch=$([ ! -z "$ghprbSourceBranch" ] && echo "$ghprbSourceBranch" || \
[ ! -z "$GIT_BRANCH" ] && echo "$GIT_BRANCH" || \
[ ! -z "$BRANCH_NAME" ] && echo "$BRANCH_NAME")
commit=$([ ! -z "$ghprbActualCommit" ] && echo "$ghprbActualCommit" || \
echo "$GIT_COMMIT")
build="$BUILD_NUMBER"
pr="${ghprbPullId:=$CHANGE_ID}"
build_url=$(urlencode "$BUILD_URL")
elif [ "$CI" = "true" ] && [ "$TRAVIS" = "true" ] && [ "$SHIPPABLE" != "true" ];
then
say "$e==>$x Travis CI detected."
# http://docs.travis-ci.com/user/ci-environment/#Environment-variables
service="travis"
branch="$TRAVIS_BRANCH"
commit="$TRAVIS_COMMIT"
build="$TRAVIS_JOB_NUMBER"
pr="$TRAVIS_PULL_REQUEST"
job="$TRAVIS_JOB_ID"
slug="$TRAVIS_REPO_SLUG"
tag="$TRAVIS_TAG"
env="$env,TRAVIS_OS_NAME"
language=$(printenv | grep "TRAVIS_.*_VERSION" | head -1)
if [ "$language" != "" ];
then
env="$env,${language%=*}"
fi
elif [ "$CI" = "true" ] && [ "$CI_NAME" = "codeship" ];
then
say "$e==>$x Codeship CI detected."
# https://www.codeship.io/documentation/continuous-integration/set-environment-variables/
service="codeship"
branch="$CI_BRANCH"
build="$CI_BUILD_NUMBER"
build_url=$(urlencode "$CI_BUILD_URL")
commit="$CI_COMMIT_ID"
elif [ "$TEAMCITY_VERSION" != "" ];
then
say "$e==>$x TeamCity CI detected."
# https://confluence.jetbrains.com/display/TCD8/Predefined+Build+Parameters
# https://confluence.jetbrains.com/plugins/servlet/mobile#content/view/74847298
if [ "$TEAMCITY_BUILD_BRANCH" = '' ];
then
echo " Teamcity does not automatically make build parameters available as environment variables."
echo " Add the following environment parameters to the build configuration"
echo " env.TEAMCITY_BUILD_BRANCH = %teamcity.build.branch%"
echo " env.TEAMCITY_BUILD_ID = %teamcity.build.id%"
echo " env.TEAMCITY_BUILD_URL = %teamcity.serverUrl%/viewLog.html?buildId=%teamcity.build.id%"
echo " env.TEAMCITY_BUILD_COMMIT = %system.build.vcs.number%"
echo " env.TEAMCITY_BUILD_REPOSITORY = %vcsroot.<YOUR TEAMCITY VCS NAME>.url%"
fi
service="teamcity"
branch="$TEAMCITY_BUILD_BRANCH"
build="$TEAMCITY_BUILD_ID"
build_url=$(urlencode "$TEAMCITY_BUILD_URL")
if [ "$TEAMCITY_BUILD_COMMIT" != "" ];
then
commit="$TEAMCITY_BUILD_COMMIT"
else
commit="$BUILD_VCS_NUMBER"
fi
remote_addr="$TEAMCITY_BUILD_REPOSITORY"
elif [ "$CI" = "true" ] && [ "$CIRCLECI" = "true" ];
then
say "$e==>$x Circle CI detected."
# https://circleci.com/docs/environment-variables
service="circleci"
branch="$CIRCLE_BRANCH"
build="$CIRCLE_BUILD_NUM"
job="$CIRCLE_NODE_INDEX"
if [ "$CIRCLE_PROJECT_REPONAME" != "" ];
then
slug="$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
else
# git@github.com:owner/repo.git
slug="${CIRCLE_REPOSITORY_URL##*:}"
# owner/repo.git
slug="${slug%%.git}"
fi
pr="$CIRCLE_PR_NUMBER"
commit="$CIRCLE_SHA1"
search_in="$search_in $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS"
elif [ "$BUDDYBUILD_BRANCH" != "" ];
then
say "$e==>$x buddybuild detected"
# http://docs.buddybuild.com/v6/docs/custom-prebuild-and-postbuild-steps
service="buddybuild"
branch="$BUDDYBUILD_BRANCH"
build="$BUDDYBUILD_BUILD_NUMBER"
build_url="https://dashboard.buddybuild.com/public/apps/$BUDDYBUILD_APP_ID/build/$BUDDYBUILD_BUILD_ID"
# BUDDYBUILD_TRIGGERED_BY
if [ "$ddp" = "$(echo ~)/Library/Developer/Xcode/DerivedData" ];
then
ddp="/private/tmp/sandbox/${BUDDYBUILD_APP_ID}/bbtest"
fi
elif [ "${bamboo_planRepository_revision}" != "" ];
then
say "$e==>$x Bamboo detected"
# https://confluence.atlassian.com/bamboo/bamboo-variables-289277087.html#Bamboovariables-Build-specificvariables
service="bamboo"
commit="${bamboo_planRepository_revision}"
branch="${bamboo_planRepository_branch}"
build="${bamboo_buildNumber}"
build_url="${bamboo_buildResultsUrl}"
remote_addr="${bamboo_planRepository_repositoryUrl}"
elif [ "$CI" = "true" ] && [ "$BITRISE_IO" = "true" ];
then
# http://devcenter.bitrise.io/faq/available-environment-variables/
say "$e==>$x Bitrise CI detected."
service="bitrise"
branch="$BITRISE_GIT_BRANCH"
build="$BITRISE_BUILD_NUMBER"
build_url=$(urlencode "$BITRISE_BUILD_URL")
pr="$BITRISE_PULL_REQUEST"
if [ "$GIT_CLONE_COMMIT_HASH" != "" ];
then
commit="$GIT_CLONE_COMMIT_HASH"
fi
elif [ "$CI" = "true" ] && [ "$SEMAPHORE" = "true" ];
then
say "$e==>$x Semaphore CI detected."
# https://semaphoreapp.com/docs/available-environment-variables.html
service="semaphore"
branch="$BRANCH_NAME"
build="$SEMAPHORE_BUILD_NUMBER"
job="$SEMAPHORE_CURRENT_THREAD"
pr="$PULL_REQUEST_NUMBER"
slug="$SEMAPHORE_REPO_SLUG"
commit="$REVISION"
env="$env,SEMAPHORE_TRIGGER_SOURCE"
elif [ "$CI" = "true" ] && [ "$BUILDKITE" = "true" ];
then
say "$e==>$x Buildkite CI detected."
# https://buildkite.com/docs/guides/environment-variables
service="buildkite"
branch="$BUILDKITE_BRANCH"
build="$BUILDKITE_BUILD_NUMBER"
job="$BUILDKITE_JOB_ID"
build_url=$(urlencode "$BUILDKITE_BUILD_URL")
slug="$BUILDKITE_PROJECT_SLUG"
commit="$BUILDKITE_COMMIT"
elif [ "$CI" = "true" ] && [ "$DRONE" = "true" ];
then
say "$e==>$x Drone CI detected."
# http://docs.drone.io/env.html
# drone commits are not full shas
service="drone.io"
branch="$DRONE_BRANCH"
build="$DRONE_BUILD_NUMBER"
build_url=$(urlencode "${DRONE_BUILD_URL:-$CI_BUILD_URL}")
pr="$DRONE_PULL_REQUEST"
job="$DRONE_JOB_NUMBER"
tag="$DRONE_TAG"
elif [ "$HEROKU_TEST_RUN_BRANCH" != "" ];
then
say "$e==>$x Heroku CI detected."
# https://devcenter.heroku.com/articles/heroku-ci#environment-variables
service="heroku"
branch="$HEROKU_TEST_RUN_BRANCH"
build="$HEROKU_TEST_RUN_ID"
elif [ "$CI" = "True" ] && [ "$APPVEYOR" = "True" ];
then
say "$e==>$x Appveyor CI detected."
# http://www.appveyor.com/docs/environment-variables
service="appveyor"
branch="$APPVEYOR_REPO_BRANCH"
build=$(urlencode "$APPVEYOR_JOB_ID")
pr="$APPVEYOR_PULL_REQUEST_NUMBER"
job="$APPVEYOR_ACCOUNT_NAME%2F$APPVEYOR_PROJECT_SLUG%2F$APPVEYOR_BUILD_VERSION"
slug="$APPVEYOR_REPO_NAME"
commit="$APPVEYOR_REPO_COMMIT"
elif [ "$CI" = "true" ] && [ "$WERCKER_GIT_BRANCH" != "" ];
then
say "$e==>$x Wercker CI detected."
# http://devcenter.wercker.com/articles/steps/variables.html
service="wercker"
branch="$WERCKER_GIT_BRANCH"
build="$WERCKER_MAIN_PIPELINE_STARTED"
slug="$WERCKER_GIT_OWNER/$WERCKER_GIT_REPOSITORY"
commit="$WERCKER_GIT_COMMIT"
elif [ "$CI" = "true" ] && [ "$MAGNUM" = "true" ];
then
say "$e==>$x Magnum CI detected."
# https://magnum-ci.com/docs/environment
service="magnum"
branch="$CI_BRANCH"
build="$CI_BUILD_NUMBER"
commit="$CI_COMMIT"
elif [ "$CI" = "true" ] && [ "$SNAP_CI" = "true" ];
then
say "$e==>$x Snap CI detected."
# https://docs.snap-ci.com/environment-variables/
service="snap"
branch=$([ "$SNAP_BRANCH" != "" ] && echo "$SNAP_BRANCH" || echo "$SNAP_UPSTREAM_BRANCH")
build="$SNAP_PIPELINE_COUNTER"
job="$SNAP_STAGE_NAME"
pr="$SNAP_PULL_REQUEST_NUMBER"
commit=$([ "$SNAP_COMMIT" != "" ] && echo "$SNAP_COMMIT" || echo "$SNAP_UPSTREAM_COMMIT")
env="$env,DISPLAY"
elif [ "$SHIPPABLE" = "true" ];
then
say "$e==>$x Shippable CI detected."
# http://docs.shippable.com/ci_configure/
service="shippable"
branch=$([ "$HEAD_BRANCH" != "" ] && echo "$HEAD_BRANCH" || echo "$BRANCH")
build="$BUILD_NUMBER"
build_url=$(urlencode "$BUILD_URL")
pr="$PULL_REQUEST"
slug="$REPO_FULL_NAME"
commit="$COMMIT"
elif [ "$TDDIUM" = "true" ];
then
say "Solano CI detected."
# http://docs.solanolabs.com/Setup/tddium-set-environment-variables/
service="solano"
commit="$TDDIUM_CURRENT_COMMIT"
branch="$TDDIUM_CURRENT_BRANCH"
build="$TDDIUM_TID"
pr="$TDDIUM_PR_ID"
elif [ "$GREENHOUSE" = "true" ];
then
say "$e==>$x Greenhouse CI detected."
# http://docs.greenhouseci.com/docs/environment-variables-files
service="greenhouse"
branch="$GREENHOUSE_BRANCH"
build="$GREENHOUSE_BUILD_NUMBER"
build_url=$(urlencode "$GREENHOUSE_BUILD_URL")
pr="$GREENHOUSE_PULL_REQUEST"
commit="$GREENHOUSE_COMMIT"
search_in="$search_in $GREENHOUSE_EXPORT_DIR"
elif [ "$GITLAB_CI" != "" ];
then
say "$e==>$x GitLab CI detected."
# http://doc.gitlab.com/ce/ci/variables/README.html
service="gitlab"
branch="$CI_BUILD_REF_NAME"
build="$CI_BUILD_ID"
remote_addr="${CI_BUILD_REPO:-$CI_REPOSITORY_URL}"
commit="$CI_BUILD_REF"
else
say "${r}x>${x} No CI provider detected."
say " Testing inside Docker? ${b}http://docs.codecov.io/docs/testing-with-docker${x}"
say " Testing with Tox? ${b}https://docs.codecov.io/docs/python#section-testing-with-tox${x}"
fi
say " ${e}project root:${x} $git_root"
# find branch, commit, repo from git command
if [ "$GIT_BRANCH" != "" ];
then
branch="$GIT_BRANCH"
elif [ "$branch" = "" ];
then
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || hg branch 2>/dev/null || echo "")
if [ "$branch" = "HEAD" ];
then
branch=""
fi
fi
if [ "$commit_o" = "" ];
then
# merge commit -> actual commit
mc=$(git log -1 --pretty=%B 2>/dev/null | tr -d '[[:space:]]' || true)
if [[ "$mc" =~ ^Merge[[:space:]][a-z0-9]{40}[[:space:]]into[[:space:]][a-z0-9]{40}$ ]];
then
# Merge xxx into yyy
say " Fixing merge commit sha"
commit=$(echo "$mc" | cut -d' ' -f2)
elif [ "$GIT_COMMIT" != "" ];
then
commit="$GIT_COMMIT"
elif [ "$commit" = "" ];
then
commit=$(git log -1 --format="%H" 2>/dev/null || hg id -i --debug 2>/dev/null | tr -d '+' || echo "")
fi
else
commit="$commit_o"
fi
if [ "$CODECOV_TOKEN" != "" ] && [ "$token" = "" ];
then
say "${e}-->${x} token set from env"
token="$CODECOV_TOKEN"
fi
if [ "$CODECOV_URL" != "" ] && [ "$url_o" = "" ];
then
say "${e}-->${x} url set from env"
url_o=$(echo "$CODECOV_URL" | sed -e 's/\/$//')
fi
if [ "$CODECOV_SLUG" != "" ];
then
say "${e}-->${x} slug set from env"
slug_o="$CODECOV_SLUG"
elif [ "$slug" = "" ];
then
if [ "$remote_addr" = "" ];
then
remote_addr=$(git config --get remote.origin.url || hg paths default || echo '')
fi
if [ "$remote_addr" != "" ];
then
if echo "$remote_addr" | grep -q "//"; then
# https
slug=$(echo "$remote_addr" | cut -d / -f 4,5 | sed -e 's/\.git$//')
else
# ssh
slug=$(echo "$remote_addr" | cut -d : -f 2 | sed -e 's/\.git$//')
fi
fi
if [ "$slug" = "/" ];
then
slug=""
fi
fi
yaml=$(cd "$git_root" && \
git ls-files "*codecov.yml" "*codecov.yaml" 2>/dev/null \
|| hg locate "*codecov.yml" "*codecov.yaml" 2>/dev/null \
|| echo '')
yaml=$(echo "$yaml" | head -1)
if [ "$yaml" != "" ];
then
say " ${e}Yaml found at:${x} $yaml"
config=$(parse_yaml "$git_root/$yaml" || echo '')
# TODO validate the yaml here
if [ "$(echo "$config" | grep 'codecov_token="')" != "" ] && [ "$token" = "" ];
then
say "${e}-->${x} token set from yaml"
token="$(echo "$config" | grep 'codecov_token="' | sed -e 's/codecov_token="//' | sed -e 's/"\.*//')"
fi
if [ "$(echo "$config" | grep 'codecov_url="')" != "" ] && [ "$url_o" = "" ];
then
say "${e}-->${x} url set from yaml"
url_o="$(echo "$config" | grep 'codecov_url="' | sed -e 's/codecov_url="//' | sed -e 's/"\.*//')"
fi
if [ "$(echo "$config" | grep 'codecov_slug="')" != "" ] && [ "$slug_o" = "" ];
then
say "${e}-->${x} slug set from yaml"
slug_o="$(echo "$config" | grep 'codecov_slug="' | sed -e 's/codecov_slug="//' | sed -e 's/"\.*//')"
fi
else
say " ${g}Yaml not found, that's ok! Learn more at${x} ${b}http://docs.codecov.io/docs/codecov-yaml${x}"
fi
if [ "$branch_o" != "" ];
then
branch=$(urlencode "$branch_o")
else
branch=$(urlencode "$branch")
fi
query="branch=$branch\
&commit=$commit\
&build=$([ "$build_o" = "" ] && echo "$build" || echo "$build_o")\
&build_url=$build_url\
&name=$(urlencode "$name")\
&tag=$([ "$tag_o" = "" ] && echo "$tag" || echo "$tag_o")\
&slug=$([ "$slug_o" = "" ] && urlencode "$slug" || urlencode "$slug_o")\
&yaml=$(urlencode "$yaml")\
&service=$service\
&flags=$flags\
&pr=$([ "$pr_o" = "" ] && echo "${pr##\#}" || echo "${pr_o##\#}")\
&job=$job"
if [ "$ft_search" = "1" ];
then
# detect bower comoponents location
bower_components="bower_components"
bower_rc=$(cd "$git_root" && cat .bowerrc 2>/dev/null || echo "")
if [ "$bower_rc" != "" ];
then
bower_components=$(echo "$bower_rc" | tr -d '\n' | grep '"directory"' | cut -d'"' -f4 | sed -e 's/\/$//')
if [ "$bower_components" = "" ];
then
bower_components="bower_components"
fi
fi
# Swift Coverage
if [ "$ft_xcode" = "1" ] && [ -d "$ddp" ];
then
say "${e}==>${x} Processing Xcode reports"
say " DerivedData folder: $ddp"
profdata_files=$(find "$ddp" -name '*.profdata' 2>/dev/null || echo '')
if [ "$profdata_files" != "" ];
then
# xcode via profdata
if [ "$xp" = "" ];
then
# xp=$(xcodebuild -showBuildSettings 2>/dev/null | grep -i "^\s*PRODUCT_NAME" | sed -e 's/.*= \(.*\)/\1/')
# say " ${e}->${x} Speed up Xcode processing by adding ${e}-J '$xp'${x}"
say " ${g}hint${x} Speed up Swift processing by using use ${g}-J 'AppName'${x} (regexp accepted)"
say " ${g}hint${x} This will remove Pods/ from your report. Also ${b}https://docs.codecov.io/docs/ignoring-paths${x}"
fi
while read -r profdata;
do
if [ "$profdata" != "" ];
then
swiftcov "$profdata" "$xp"
fi
done <<< "$profdata_files"
else
say " ${e}->${x} No Swift coverage found"
fi
# Obj-C Gcov Coverage
if [ "$ft_gcov" = "1" ];
then
say " ${e}->${x} Running $gcov_exe for Obj-C"
bash -c "find $ddp -type f -name '*.gcda' $gcov_include $gcov_ignore -exec $gcov_exe -pbcu $gcov_arg {} +" || true
fi
fi
if [ "$ft_xcodeplist" = "1" ] && [ -d "$ddp" ];
then
say "${e}==>${x} Processing Xcode plists"
plists_files=$(find "$ddp" -name '*.xccoverage' 2>/dev/null || echo '')
if [ "$plists_files" != "" ];
then
while read -r plist;
do
if [ "$plist" != "" ];
then
say " ${g}Found${x} plist file at $plist"
plutil -convert xml1 -o "$(basename "$plist").plist" -- $plist
fi
done <<< "$plists_files"
fi
fi
# Gcov Coverage
if [ "$ft_gcov" = "1" ];
then
say "${e}==>${x} Running gcov in $proj_root ${e}(disable via -X gcov)${x}"
bash -c "find $proj_root -type f -name '*.gcno' $gcov_include $gcov_ignore -exec $gcov_exe -pb $gcov_arg {} +" || true
else
say "${e}==>${x} gcov disabled"
fi
# Python Coverage
if [ "$ft_coveragepy" = "1" ];
then
if [ ! -f coverage.xml ];
then
if which coverage >/dev/null 2>&1;
then
say "${e}==>${x} Python coveragepy exists ${e}disable via -X coveragepy${x}"
dotcoverage=$(find "$git_root" -name '.coverage' -or -name '.coverage.*' | head -1 || echo '')
if [ "$dotcoverage" != "" ];
then
cd "$(dirname "$dotcoverage")"
if [ ! -f .coverage ];
then
say " ${e}->${x} Running coverage combine"
coverage combine
fi
say " ${e}->${x} Running coverage xml"
if [ "$(coverage xml -i)" != "No data to report." ];
then
files="$files
$PWD/coverage.xml"
else
say " ${r}No data to report.${x}"
fi
cd "$proj_root"
else
say " ${r}No .coverage file found.${x}"
fi
else
say "${e}==>${x} Python coveragepy not found"
fi
fi
else
say "${e}==>${x} Python coveragepy disabled"
fi
if [ "$search_in_o" != "" ];
then
# location override
search_in="$search_in_o"
fi
say "$e==>$x Searching for coverage reports in:"
for _path in $search_in
do
say " ${g}+${x} $_path"
done
patterns="find $search_in -type f \( -name '*coverage*.*' \
-or -name 'nosetests.xml' \
-or -name 'jacoco*.xml' \
-or -name 'clover.xml' \
-or -name 'report.xml' \
-or -name '*.codecov.*' \
-or -name 'codecov.*' \
-or -name 'cobertura.xml' \
-or -name 'excoveralls.json' \
-or -name 'luacov.report.out' \
-or -name 'coverage-final.json' \
-or -name 'naxsi.info' \
-or -name 'lcov.info' \
-or -name 'lcov.dat' \
-or -name '*.lcov' \
-or -name '*.clover' \
-or -name 'cover.out' \
-or -name 'gcov.info' \
-or -name '*.gcov' \
-or -name '*.lst' \
$include_cov \) \
$exclude_cov \
-not -name '*.profdata' \
-not -name 'coverage-summary.json' \
-not -name 'phpunit-code-coverage.xml' \
-not -name 'remapInstanbul.coverage*.json' \
-not -name 'phpunit-coverage.xml' \
-not -name '*codecov.yml' \
-not -name '*.serialized' \
-not -name '.coverage*' \
-not -name '.*coveragerc' \
-not -name '*.sh' \
-not -name '*.bat' \
-not -name '*.ps1' \
-not -name '*.cmake' \
-not -name '*.dox' \
-not -name '*.ec' \
-not -name '*.rst' \
-not -name '*.h' \
-not -name '*.scss' \
-not -name '*.o' \
-not -name '*.proto' \
-not -name '*.sbt' \
-not -name '*.xcoverage.*' \
-not -name '*.gz' \
-not -name '*.conf' \
-not -name '*.p12' \
-not -name '*.csv' \
-not -name '*.rsp' \
-not -name '*.m4' \
-not -name '*.pem' \
-not -name '*~' \
-not -name '*.exe' \
-not -name '*.am' \
-not -name '*.template' \
-not -name '*.cp' \
-not -name '*.bw' \
-not -name '*.crt' \
-not -name '*.log' \
-not -name '*.cmake' \
-not -name '*.pth' \
-not -name '*.in' \
-not -name '*.jar*' \
-not -name '*.pom*' \
-not -name '*.png' \
-not -name '*.jpg' \
-not -name '*.sql' \
-not -name '*.jpeg' \
-not -name '*.svg' \
-not -name '*.gif' \
-not -name '*.csv' \
-not -name '*.snapshot' \
-not -name '*.mak*' \
-not -name '*.bash' \
-not -name '*.data' \
-not -name '*.py' \
-not -name '*.class' \
-not -name '*.xcconfig' \
-not -name '*.ec' \
-not -name '*.coverage' \
-not -name '*.pyc' \
-not -name '*.cfg' \
-not -name '*.egg' \
-not -name '*.ru' \
-not -name '*.css' \
-not -name '*.less' \
-not -name '*.pyo' \
-not -name '*.whl' \
-not -name '*.html' \
-not -name '*.ftl' \
-not -name '*.erb' \
-not -name '*.rb' \
-not -name '*.js' \
-not -name '*.jade' \
-not -name '*.db' \
-not -name '*.md' \
-not -name '*.cpp' \
-not -name '*.gradle' \
-not -name '*.tar.tz' \
-not -name '*.scss' \
-not -name 'include.lst' \
-not -name 'fullLocaleNames.lst' \
-not -name 'inputFiles.lst' \
-not -name 'createdFiles.lst' \
-not -name 'scoverage.measurements.*' \
-not -name 'test_*_coverage.txt' \
-not -name 'testrunner-coverage*' \
-not -path '*/vendor/*' \
-not -path '*/htmlcov/*' \
-not -path '*/virtualenv/*' \
-not -path '*/js/generated/coverage/*' \
-not -path '*/.virtualenv/*' \
-not -path '*/virtualenvs/*' \
-not -path '*/.virtualenvs/*' \
-not -path '*/.env/*' \
-not -path '*/.envs/*' \
-not -path '*/env/*' \
-not -path '*/envs/*' \
-not -path '*/.venv/*' \
-not -path '*/.venvs/*' \
-not -path '*/venv/*' \
-not -path '*/venvs/*' \
-not -path '*/.git/*' \
-not -path '*/.hg/*' \
-not -path '*/.tox/*' \
-not -path '*/__pycache__/*' \
-not -path '*/.egg-info*' \
-not -path '*/$bower_components/*' \
-not -path '*/node_modules/*' \
-not -path '*/conftest_*.c.gcov' 2>/dev/null"
files=$(eval "$patterns" || echo '')
elif [ "$include_cov" != "" ];
then
files=$(eval "find $search_in -type f \( ${include_cov:5} \)$exclude_cov 2>/dev/null" || echo '')
fi
num_of_files=$(echo "$files" | wc -l | tr -d ' ')
if [ "$num_of_files" != '' ] && [ "$files" != '' ];
then
say " ${e}->${x} Found $num_of_files reports"
fi
# no files found
if [ "$files" = "" ];
then
say "${r}-->${x} No coverage report found."
say " Please visit ${b}http://docs.codecov.io/docs/supported-languages${x}"
exit ${exit_with};
fi
if [ "$ft_network" == "1" ];
then
say "${e}==>${x} Detecting git/mercurial file structure"
network=$(cd "$git_root" && git ls-files 2>/dev/null || hg locate 2>/dev/null || echo "")
if [ "$network" = "" ];
then
network=$(find "$git_root" -type f \
-not -path '*/virtualenv/*' \
-not -path '*/.virtualenv/*' \
-not -path '*/virtualenvs/*' \
-not -path '*/.virtualenvs/*' \
-not -path '*.png' \
-not -path '*.gif' \
-not -path '*.jpg' \
-not -path '*.jpeg' \
-not -path '*.md' \
-not -path '*/.env/*' \
-not -path '*/.envs/*' \
-not -path '*/env/*' \
-not -path '*/envs/*' \
-not -path '*/.venv/*' \
-not -path '*/.venvs/*' \
-not -path '*/venv/*' \
-not -path '*/venvs/*' \
-not -path '*/build/lib/*' \
-not -path '*/.git/*' \
-not -path '*/.egg-info/*' \
-not -path '*/shunit2-2.1.6/*' \
-not -path '*/vendor/*' \
-not -path '*/js/generated/coverage/*' \
-not -path '*/__pycache__/*' \
-not -path '*/node_modules/*' \
-not -path "*/$bower_components/*" 2>/dev/null || echo '')
fi
fi
upload_file=`mktemp /tmp/codecov.XXXXXX`
adjustments_file=`mktemp /tmp/codecov.adjustments.XXXXXX`
cleanup() {
rm -f $upload_file $adjustments_file
}
trap cleanup INT ABRT TERM
if [ "$env" != "" ];
then
inc_env=""
say "${e}==>${x} Appending build variables"
for varname in $(echo "$env" | tr ',' ' ')
do
if [ "$varname" != "" ];
then
say " ${g}+${x} $varname"
inc_env="${inc_env}${varname}=$(eval echo "\$${varname}")
"
fi
done
echo "$inc_env<<<<<< ENV" >> $upload_file
fi
if [ "$ft_network" == "1" ];
then
i="woff|eot|otf" # fonts
i="$i|gif|png|jpg|jpeg|psd" # images
i="$i|ptt|pptx|numbers|pages|md|txt|xlsx|docx|doc|pdf" # docs
i="$i|yml|yaml|.gitignore" # supporting docs
echo "$network" | grep -vwE "($i)$" >> $upload_file
echo "<<<<<< network" >> $upload_file
fi
fr=0
say "${e}==>${x} Reading reports"
while IFS='' read -r file;
do
# read the coverage file
if [ "$(echo "$file" | tr -d ' ')" != '' ];
then
if [ -f "$file" ];
then
report_len=$(wc -c < "$file")
if [ "$report_len" -ne 0 ];
then
say " ${g}+${x} $file ${e}bytes=$(echo "$report_len" | tr -d ' ')${x}"
# append to to upload
echo "# path=$(echo "$file" | sed "s|^$git_root/||")" >> $upload_file
cat "$file" >> $upload_file
echo "<<<<<< EOF" >> $upload_file
fr=1
if [ "$clean" = "1" ];
then
rm "$file"
fi
else
say " ${r}-${x} Skipping empty file $file"
fi
else
say " ${r}-${x} file not found at $file"
fi
fi
done <<< "$(echo -e "$files")"
if [ "$fr" = "0" ];
then
say "${r}-->${x} No coverage data found."
say " Please visit ${b}http://docs.codecov.io/docs/supported-languages${x}"
say " search for your projects language to learn how to collect reports."
exit ${exit_with};
fi
if [ "$ft_fix" = "1" ];
then
say "${e}==>${x} Appending adjustments"
say " ${b}http://docs.codecov.io/docs/fixing-reports${x}"
empty_line='^[[:space:]]*$'
# //
syntax_comment='^[[:space:]]*//.*'
# /* or */
syntax_comment_block='^[[:space:]]*(\/\*|\*\/)[[:space:]]*$'
# { or }
syntax_bracket='^[[:space:]]*[\{\}][[:space:]]*(//.*)?$'
# [ or ]
syntax_list='^[[:space:]]*[][][[:space:]]*(//.*)?$'
skip_dirs="-not -path '*/$bower_components/*' \
-not -path '*/node_modules/*'"
cut_and_join() {
awk 'BEGIN { FS=":" }
$3 ~ /\/\*/ || $3 ~ /\*\// { print $0 ; next }
$1!=key { if (key!="") print out ; key=$1 ; out=$1":"$2 ; next }
{ out=out","$2 }
END { print out }' 2>/dev/null
}
if echo "$network" | grep -m1 '.kt$' 1>/dev/null;
then
# skip brackets and comments
find "$git_root" -type f \
-name '*.kt' \
-exec \
grep -nIHE -e $syntax_bracket \
-e $syntax_comment_block {} \; \
| cut_and_join \
>> $adjustments_file \
|| echo ''
# last line in file
find "$git_root" -type f \
-name '*.kt' -exec \
wc -l {} \; \
| while read l; do echo "EOF: $l"; done \
2>/dev/null \
>> $adjustments_file \
|| echo ''
fi
if echo "$network" | grep -m1 '.go$' 1>/dev/null;
then
# skip empty lines, comments, and brackets
find "$git_root" -type f \
-not -path '*/vendor/*' \
-name '*.go' \
-exec \
grep -nIHE \
-e $empty_line \
-e $syntax_comment \
-e $syntax_comment_block \
-e $syntax_bracket \
{} \; \
| cut_and_join \
>> $adjustments_file \
|| echo ''
fi
if echo "$network" | grep -m1 '.jsx$' 1>/dev/null;
then
# skip empty lines, comments, and brackets
find "$git_root" -type f \
-name '*.jsx' \
$skip_dirs \
-exec \
grep -nIHE \
-e $empty_line \
-e $syntax_comment \
-e $syntax_bracket \
{} \; \
| cut_and_join \
>> $adjustments_file \
|| echo ''
fi
if echo "$network" | grep -m1 '.php$' 1>/dev/null;
then
# skip empty lines, comments, and brackets
find "$git_root" -type f \
-not -path '*/vendor/*' \
-name '*.php' \
-exec \
grep -nIHE \
-e $syntax_list \
-e $syntax_bracket \
-e '^[[:space:]]*\);[[:space:]]*(//.*)?$' \
{} \; \
| cut_and_join \
>> $adjustments_file \
|| echo ''
fi
if echo "$network" | grep -m1 '\(.cpp\|.h\|.cxx\|.c\|.hpp\|.m\)$' 1>/dev/null;
then
# skip brackets
find "$git_root" -type f \
$skip_dirs \
\( \
-name '*.h' \
-or -name '*.cpp' \
-or -name '*.cxx' \
-or -name '*.m' \
-or -name '*.c' \
-or -name '*.hpp' \
\) -exec \
grep -nIHE \
-e $empty_line \
-e $syntax_bracket \
-e '// LCOV_EXCL' \
{} \; \
| cut_and_join \
>> $adjustments_file \
|| echo ''
# skip brackets
find "$git_root" -type f \
$skip_dirs \
\( \
-name '*.h' \
-or -name '*.cpp' \
-or -name '*.cxx' \
-or -name '*.m' \
-or -name '*.c' \
-or -name '*.hpp' \
\) -exec \
grep -nIH '// LCOV_EXCL' \
{} \; \
>> $adjustments_file \
|| echo ''
fi
found=$(cat $adjustments_file | tr -d ' ')
if [ "$found" != "" ];
then
say " ${g}+${x} Found adjustments"
echo "# path=fixes" >> $upload_file
cat $adjustments_file >> $upload_file
echo "<<<<<< EOF" >> $upload_file
rm -rf $adjustments_file
else
say " ${e}->${x} No adjustments found"
fi
fi
if [ "$url_o" != "" ];
then
url="$url_o"
fi
if [ "$dump" != "0" ];
then
# trim whitespace from query
echo "$url/upload/v4?$(echo "package=bash-$VERSION&token=$token&$query" | tr -d ' ')"
cat $upload_file
else
query=$(echo "${query}" | tr -d ' ')
say "${e}==>${x} Uploading reports"
say " ${e}url:${x} $url"
say " ${e}query:${x} $query"
# now add token to query
query=$(echo "package=bash-$VERSION&token=$token&$query" | tr -d ' ')
if [ "$ft_s3" = "1" ];
then
i="0"
while [ $i -lt 4 ]
do
i=$[$i+1]
say " ${e}->${x} Pinging Codecov"
res=$(curl $curl_s -X POST $curlargs $cacert "$url/upload/v4?$query" -H 'Accept: text/plain' || true)
# a good replay is "https://codecov.io" + "\n" + "https://codecov.s3.amazonaws.com/..."
status=$(echo "$res" | head -1 | grep 'HTTP ' | cut -d' ' -f2)
if [ "$status" = "" ];
then
s3target=$(echo "$res" | sed -n 2p)
say " ${e}->${x} Uploading to S3 $(echo "$s3target" | cut -c1-32)"
s3=$(curl $curl_s -fiX PUT $curlawsargs \
--data-binary @$upload_file \
-H 'Content-Type: text/plain' \
-H 'x-amz-acl: public-read' \
-H 'x-amz-storage-class: REDUCED_REDUNDANCY' \
"$s3target" || true)
if [ "$s3" != "" ];
then
say " ${g}->${x} View reports at ${b}$(echo "$res" | sed -n 1p)${x}"
exit 0
else
say " ${r}X>${x} Failed to upload to S3"
fi
elif [ "$status" = "400" ];
then
# 400 Error
say "${g}${res}${x}"
exit ${exit_with}
fi
say " ${e}->${x} Sleeping for 30s and trying again..."
sleep 30
done
fi
say " ${e}->${x} Uploading to Codecov"
i="0"
while [ $i -lt 4 ]
do
i=$[$i+1]
res=$(curl $curl_s -X POST $curlargs $cacert --data-binary @$upload_file "$url/upload/v2?$query" -H 'Accept: text/plain' || echo 'HTTP 500')
# HTTP 200
# http://....
status=$(echo "$res" | head -1 | cut -d' ' -f2)
if [ "$status" = "" ];
then
say " View reports at ${b}$(echo "$res" | head -2 | tail -1)${x}"
exit 0
elif [ "${status:0:1}" = "5" ];
then
say " ${e}->${x} Sleeping for 30s and trying again..."
sleep 30
else
say " ${g}${res}${x}"
exit 0
exit ${exit_with}
fi
done
fi
say " ${r}X> Failed to upload coverage reports${x}"
exit ${exit_with}
# EOF
================================================
FILE: detekt.yml
================================================
# `git diff detekt-default detekt.yml` to see what's changed from default
# settings
test-pattern: # Configure exclusions for test sources
patterns: # Test file regexes
- '.*/test/.*'
exclude-rule-sets:
- 'comments'
exclude-rules:
- 'NamingRules'
- 'WildcardImport'
- 'MagicNumber'
- 'MaxLineLength'
- 'LateinitUsage'
- 'StringLiteralDuplication'
- 'SpreadOperator'
- 'TooManyFunctions'
- 'ForEachOnRange'
build:
maxIssues: 10 # TODO: reduce this threshhold
weights:
# complexity: 2
# LongParameterList: 1
# style: 1
# comments: 1
processors:
active: true
exclude:
# - 'FunctionCountProcessor'
# - 'PropertyCountProcessor'
# - 'ClassCountProcessor'
# - 'PackageCountProcessor'
# - 'KtFileCountProcessor'
console-reports:
active: true
exclude:
# - 'ProjectStatisticsReport'
# - 'ComplexityReport'
# - 'NotificationReport'
# - 'FindingsReport'
# - 'BuildFailureReport'
output-reports:
active: true
exclude:
# - 'HtmlOutputReport'
# - 'PlainOutputReport'
# - 'XmlOutputReport'
comments:
active: true
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!]$)
UndocumentedPublicClass:
active: false
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
UndocumentedPublicFunction:
active: false
complexity:
active: true
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
active: false
threshold: 10
includeStaticDeclarations: false
ComplexMethod:
active: true
threshold: 10
ignoreSingleWhenExpression: false
LabeledExpression:
active: false
LargeClass:
active: true
threshold: 150
LongMethod:
active: true
threshold: 20
LongParameterList:
active: true
threshold: 6
ignoreDefaultParameters: false
MethodOverloading:
active: false
threshold: 6
NestedBlockDepth:
active: true
threshold: 4
StringLiteralDuplication:
active: false
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
thresholdInObjects: 11
thresholdInEnums: 11
ignoreDeprecated: false
empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
ignoreOverriddenFunctions: false
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: false
methodNames: 'toString,hashCode,equals,finalize'
InstanceOfCheckForException:
active: false
NotImplementedDeclaration:
active: false
PrintStackTrace:
active: false
RethrowCaughtException:
active: false
ReturnFromFinally:
active: false
SwallowedException:
active: false
ThrowingExceptionFromFinally:
active: false
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: false
exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
ThrowingNewInstanceOfSameException:
active: false
TooGenericExceptionCaught:
active: true
exceptionNames:
- ArrayIndexOutOfBoundsException
- Error
- Exception
- IllegalMonitorStateException
- NullPointerException
- IndexOutOfBoundsException
- RuntimeException
- Throwable
TooGenericExceptionThrown:
active: true
exceptionNames:
- Error
- Exception
- Throwable
- RuntimeException
formatting:
active: true
android: false
autoCorrect: true
ChainWrapping:
active: true
autoCorrect: true
CommentSpacing:
active: true
autoCorrect: true
Filename:
active: true
FinalNewline:
active: true
autoCorrect: true
ImportOrdering:
active: true
autoCorrect: true
Indentation:
active: true
autoCorrect: true
indentSize: 4
continuationIndentSize: 4
MaximumLineLength:
active: true
maxLineLength: 120
ModifierOrdering:
active: true
autoCorrect: true
NoBlankLineBeforeRbrace:
active: true
autoCorrect: true
NoConsecutiveBlankLines:
active: true
autoCorrect: true
NoEmptyClassBody:
active: true
autoCorrect: true
NoItParamInMultilineLambda:
active: true
NoLineBreakAfterElse:
active: true
autoCorrect: true
NoLineBreakBeforeAssignment:
active: true
autoCorrect: true
NoMultipleSpaces:
active: true
autoCorrect: true
NoSemicolons:
active: true
autoCorrect: true
NoTrailingSpaces:
active: true
autoCorrect: true
NoUnitReturn:
active: true
autoCorrect: true
NoUnusedImports:
active: true
autoCorrect: true
NoWildcardImports:
active: true
autoCorrect: true
ParameterListWrapping:
active: true
autoCorrect: true
indentSize: 4
SpacingAroundColon:
active: true
autoCorrect: true
SpacingAroundComma:
active: true
autoCorrect: true
SpacingAroundCurly:
active: true
autoCorrect: true
SpacingAroundKeyword:
active: true
autoCorrect: true
SpacingAroundOperators:
active: true
autoCorrect: true
SpacingAroundRangeOperator:
active: true
autoCorrect: true
StringTemplate:
active: true
autoCorrect: true
naming:
active: true
ClassNaming:
active: true
classPattern: '[A-Z$][a-zA-Z0-9$]*'
EnumNaming:
active: true
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
forbiddenName: ''
FunctionMaxLength:
active: false
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
minimumFunctionNameLength: 3
FunctionNaming:
active: true
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
MatchingDeclarationName:
active: true
MemberNameEqualsClassName:
active: false
ignoreOverriddenFunction: true
ObjectPropertyNaming:
active: true
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '^[a-z]+(\.[a-z][a-z0-9]*)*$'
TopLevelPropertyNaming:
active: true
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
maximumVariableNameLength: 64
VariableMinLength:
active: false
minimumVariableNameLength: 1
VariableNaming:
active: true
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
performance:
active: true
ForEachOnRange:
active: true
SpreadOperator:
active: false
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
DuplicateCaseInWhenExpression:
active: true
EqualsAlwaysReturnsTrueOrFalse:
active: false
EqualsWithHashCodeExist:
active: true
ExplicitGarbageCollectionCall:
active: true
InvalidRange:
active: false
IteratorHasNextCallsNextMethod:
active: false
IteratorNotThrowingNoSuchElementException:
active: false
LateinitUsage:
active: false
excludeAnnotatedProperties: ""
ignoreOnClassesPattern: ""
UnconditionalJumpStatementInLoop:
active: false
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: false
UnsafeCast:
active: false
UselessPostfixExpression:
active: false
WrongEqualsTypeParameter:
active: false
style:
active: true
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
conversionFunctionPrefix: 'to'
EqualsNullCall:
active: false
ExpressionBodySyntax:
active: false
ForbiddenComment:
active: true
values: 'FIXME:,STOPSHIP:'
ForbiddenImport:
active: false
imports: ''
FunctionOnlyReturningConstant:
active: false
ignoreOverridableFunction: true
excludedFunctions: 'describeContents'
LoopWithTooManyJumpStatements:
active: false
maxJumpCount: 1
MagicNumber:
active: true
ignoreNumbers: '-1,0,1,2'
ignoreHashCodeFunction: false
ignorePropertyDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: false
excludeImportStatements: false
excludeCommentStatements: false
MayBeConst:
active: false
ModifierOrder:
active: true
NestedClassesVisibility:
active: false
NewLineAtEndOfFile:
active: true
NoTabs:
active: false
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
OptionalWhenBraces:
active: false
ProtectedMemberInFinalClass:
active: false
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
excludedFunctions: "equals"
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: false
SpacingBetweenPackageAndImports:
active: false
ThrowsCount:
active: true
max: 2
TrailingWhitespace:
active: false
UnnecessaryAbstractClass:
active: false
UnnecessaryInheritance:
active: false
UnnecessaryParentheses:
active: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedPrivateMember:
active: false
allowedNames: "(_|ignored|expected)"
UseDataClass:
active: false
excludeAnnotatedClasses: ""
UtilityClassWithPublicConstructor:
active: false
WildcardImport:
active: true
excludeImports: 'java.util.*,kotlinx.android.synthetic.*'
# vim: sw=2
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle.properties
================================================
detekt_version = 1.0.0.RC7
dokka_version = 0.9.17
gradle_bintray_version = 1.8.0
kotlintest_version = 3.1.0
kotlin_version = 1.2.41
slf4j_version = 1.7.25
xenocom_version = 0.0.7
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
rootProject.name = "kotlin-argparser"
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/ArgParser.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
import com.xenomachina.argparser.PosixNaming.identifierToArgName
import com.xenomachina.argparser.PosixNaming.identifierToOptionName
import com.xenomachina.argparser.PosixNaming.optionNameToArgName
import com.xenomachina.argparser.PosixNaming.selectRepresentativeOptionName
import com.xenomachina.common.Holder
import com.xenomachina.common.orElse
import java.util.LinkedHashSet
import kotlin.reflect.KProperty
/**
* A command-line option/argument parser.
*
* @param args the command line arguments to parse
* @param mode parsing mode, defaults to GNU-style parsing
* @param helpFormatter if non-null, creates `--help` and `-h` options that trigger a [ShowHelpException] which will use
* the supplied [HelpFormatter] to generate a help message.
*/
class ArgParser(
args: Array<out String>,
mode: Mode = Mode.GNU,
helpFormatter: HelpFormatter? = DefaultHelpFormatter()
) {
enum class Mode {
/** For GNU-style option parsing, where options may appear after positional arguments. */
GNU,
/** For POSIX-style option parsing, where options must appear before positional arguments. */
POSIX
}
/**
* Creates a Delegate for a zero-argument option that returns true if and only the option is present in args.
*/
fun flagging(vararg names: String, help: String): Delegate<Boolean> =
option<Boolean>(
*names,
help = help) { true }.default(false)
/**
* Creates a DelegateProvider for a zero-argument option that returns true if and only the option is present in
* args.
*/
fun flagging(help: String) =
DelegateProvider { identifier -> flagging(identifierToOptionName(identifier), help = help) }
/**
* Creates a Delegate for a zero-argument option that returns the count of how many times the option appears in
* args.
*/
fun counting(vararg names: String, help: String): Delegate<Int> =
option<Int>(
*names,
isRepeating = true,
help = help) { value.orElse { 0 } + 1 }.default(0)
/**
* Creates a DelegateProvider for a zero-argument option that returns the count of how many times the option appears
* in args.
*/
fun counting(help: String) = DelegateProvider {
identifier -> counting(identifierToOptionName(identifier), help = help)
}
/**
* Creates a Delegate for a single-argument option that stores and returns the option's (transformed) argument.
*/
fun <T> storing(
vararg names: String,
help: String,
argName: String? = null,
transform: String.() -> T
): Delegate<T> {
val nonNullArgName = argName ?: optionNameToArgName(selectRepresentativeOptionName(names))
return option(
*names,
errorName = nonNullArgName,
argNames = listOf(nonNullArgName),
help = help) { transform(arguments.first()) }
}
/**
* Creates a DelegateProvider for a single-argument option that stores and returns the option's (transformed)
* argument.
*/
fun <T> storing(
help: String,
argName: String? = null,
transform: String.() -> T
) = DelegateProvider { identifier ->
storing(identifierToOptionName(identifier), help = help, argName = argName, transform = transform)
}
/**
* Creates a Delegate for a single-argument option that stores and returns the option's argument.
*/
fun storing(vararg names: String, help: String, argName: String? = null): Delegate<String> =
storing(*names, help = help, argName = argName) { this }
/**
* Creates a DelegateProvider for a single-argument option that stores and returns the option's argument.
*/
fun storing(help: String, argName: String? = null) =
DelegateProvider { identifier ->
storing(identifierToOptionName(identifier), help = help, argName = argName) }
/**
* Creates a Delegate for a single-argument option that adds the option's (transformed) argument to a
* MutableCollection each time the option appears in args, and returns said MutableCollection.
*/
fun <E, T : MutableCollection<E>> adding(
vararg names: String,
help: String,
argName: String? = null,
initialValue: T,
transform: String.() -> E
): Delegate<T> {
val nonNullArgName = argName ?: optionNameToArgName(selectRepresentativeOptionName(names))
return option<T>(
*names,
help = help,
argNames = listOf(nonNullArgName),
isRepeating = true) {
val result = value.orElse { initialValue }
result.add(transform(arguments.first()))
result
}.default(initialValue)
}
/**
* Creates a DelegateProvider for a single-argument option that adds the option's (transformed) argument to a
* MutableCollection each time the option appears in args, and returns said MutableCollection.
*/
fun <E, T : MutableCollection<E>> adding(
help: String,
argName: String? = null,
initialValue: T,
transform: String.() -> E
) = DelegateProvider { identifier ->
adding(
identifierToOptionName(identifier),
help = help,
argName = argName,
initialValue = initialValue,
transform = transform)
}
/**
* Creates a Delegate for a single-argument option that adds the option's (transformed) argument to a
* MutableList each time the option appears in args, and returns said MutableCollection.
*/
fun <T> adding(
vararg names: String,
help: String,
argName: String? = null,
transform: String.() -> T
) = adding(*names, help = help, argName = argName, initialValue = mutableListOf(), transform = transform)
/**
* Creates a DelegateProvider for a single-argument option that adds the option's (transformed) argument to a
* MutableList each time the option appears in args, and returns said MutableCollection.
*/
fun <T> adding(
help: String,
argName: String? = null,
transform: String.() -> T
) = DelegateProvider { identifier ->
adding(identifierToOptionName(identifier), help = help, argName = argName, transform = transform) }
/**
* Creates a Delegate for a single-argument option that adds the option's argument to a MutableList each time the
* option appears in args, and returns said MutableCollection.
*/
fun adding(vararg names: String, help: String, argName: String? = null): Delegate<MutableList<String>> =
adding(*names, help = help, argName = argName) { this }
/**
* Creates a DelegateProvider for a single-argument option that adds the option's argument to a MutableList each
* time the option appears in args, and returns said MutableCollection.
*/
fun adding(help: String) = DelegateProvider { identifier ->
adding(identifierToOptionName(identifier), help = help) }
/**
* Creates a Delegate for a zero-argument option that maps from the option's name as it appears in args to one of a
* fixed set of values.
*/
fun <T> mapping(vararg pairs: Pair<String, T>, help: String): Delegate<T> =
mapping(mapOf(*pairs), help = help)
/**
* Creates a Delegate for a zero-argument option that maps from the option's name as it appears in args to one of a
* fixed set of values.
*/
fun <T> mapping(map: Map<String, T>, help: String): Delegate<T> {
val names = map.keys.toTypedArray()
return option(*names,
errorName = map.keys.joinToString("|"),
help = help) {
// This cannot be null, because the optionName was added to the map
// at the same time it was registered with the ArgParser.
map[optionName]!!
}
}
/**
* Creates a Delegate for an option with the specified names.
* @param names names of options, with leading "-" or "--"
* @param errorName name to use when talking about this option in error messages, or null to base it upon the
* option names
* @param help the help text for this option
* @param argNames names of this option's arguments
* @param isRepeating whether or not it makes sense to repeat this option -- usually used for options where
* specifying the option more than once yields a value than cannot be expressed by specifying the option only once
* @param handler a function that computes the value of this option from an [OptionInvocation]
*/
fun <T> option(
// TODO: add optionalArg: Boolean
vararg names: String,
help: String,
errorName: String? = null,
argNames: List<String> = emptyList(),
isRepeating: Boolean = false,
handler: OptionInvocation<T>.() -> T
): Delegate<T> {
val delegate = OptionDelegate<T>(
parser = this,
errorName = errorName ?: optionNameToArgName(selectRepresentativeOptionName(names)),
help = help,
optionNames = listOf(*names),
argNames = argNames.toList(),
isRepeating = isRepeating,
handler = handler)
return delegate
}
/**
* Creates a Delegate for a single positional argument which returns the argument's value.
*/
fun positional(name: String, help: String) = positional(name, help = help) { this }
/**
* Creates a DelegateProvider for a single positional argument which returns the argument's value.
*/
fun positional(help: String) =
DelegateProvider { identifier -> positional(identifierToArgName(identifier), help = help) }
/**
* Creates a Delegate for a single positional argument which returns the argument's transformed value.
*/
fun <T> positional(
name: String,
help: String,
transform: String.() -> T
): Delegate<T> {
return WrappingDelegate(
positionalList(name, help = help, sizeRange = 1..1, transform = transform)
) { it[0] }
}
/**
* Creates a DelegateProvider for a single positional argument which returns the argument's transformed value.
*/
fun <T> positional(
help: String,
transform: String.() -> T
) = DelegateProvider { identifier ->
positional(identifierToArgName(identifier), help = help, transform = transform)
}
/**
* Creates a Delegate for a sequence of positional arguments which returns a List containing the arguments.
*/
fun positionalList(
name: String,
help: String,
sizeRange: IntRange = 1..Int.MAX_VALUE
) = positionalList(name, help = help, sizeRange = sizeRange) { this }
/**
* Creates a DelegateProvider for a sequence of positional arguments which returns a List containing the arguments.
*/
fun positionalList(
help: String,
sizeRange: IntRange = 1..Int.MAX_VALUE
) = DelegateProvider { identifier ->
positionalList(identifierToArgName(identifier), help = help, sizeRange = sizeRange)
}
/**
* Creates a Delegate for a sequence of positional arguments which returns a List containing the transformed
* arguments.
*/
fun <T> positionalList(
name: String,
help: String,
sizeRange: IntRange = 1..Int.MAX_VALUE,
transform: String.() -> T
): Delegate<List<T>> {
sizeRange.run {
require(step == 1) { "step must be 1, not $step" }
require(first <= last) { "backwards ranges are not allowed: $first > $last" }
require(first >= 0) { "sizeRange cannot start at $first, must be non-negative" }
// Technically, last == 0 is ok but not especially useful, so we
// disallow it as it's probably unintentional.
require(last > 0) { "sizeRange only allows $last arguments, must allow at least 1" }
}
return PositionalDelegate<T>(this, name, sizeRange, help = help, transform = transform)
}
/**
* Creates a DelegateProvider for a sequence of positional arguments which returns a List containing the transformed
* arguments.
*/
fun <T> positionalList(
help: String,
sizeRange: IntRange = 1..Int.MAX_VALUE,
transform: String.() -> T
) = DelegateProvider { identifier -> positionalList(identifierToArgName(identifier), help, sizeRange, transform) }
abstract class Delegate<out T> internal constructor() {
/** The value associated with this delegate */
abstract val value: T
/** The name used to refer to this delegate's value in error messages */
abstract val errorName: String
/** The user-visible help text for this delegate */
abstract val help: String
/** Add validation logic. Validator should throw a [SystemExitException] on failure. */
abstract fun addValidator(validator: Delegate<T>.() -> Unit): Delegate<T>
/** Allows this object to act as a property delegate */
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
/**
* Allows this object to act as a property delegate provider.
*
* It provides itself, and also registers itself with the [ArgParser] at that time.
*/
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): ArgParser.Delegate<T> {
registerRoot()
return this
}
internal abstract val parser: ArgParser
/**
* Indicates whether or not a value has been set for this delegate
*/
internal abstract val hasValue: Boolean
internal fun checkHasValue() {
if (!hasValue) throw MissingValueException(errorName)
}
internal abstract fun validate()
internal abstract fun toHelpFormatterValue(): HelpFormatter.Value
internal fun registerRoot() {
parser.checkNotParsed()
parser.delegates.add(this)
registerLeaf(this)
}
internal abstract fun registerLeaf(root: Delegate<*>)
internal abstract val hasValidators: Boolean
}
/**
* Provides a [Delegate] when given a name. This makes it possible to infer
* a name for the `Delegate` based on the name it is bound to, rather than
* specifying a name explicitly.
*/
class DelegateProvider<out T>(
private val default: (() -> T)? = null,
internal val ctor: (identifier: String) -> Delegate<T>
) {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): Delegate<T> {
val delegate = ctor(prop.name)
return (if (default == null) delegate
else delegate.default(default)).provideDelegate(thisRef, prop)
}
}
/**
* @property value a Holder containing the current value associated with this option, or null if unset
* @property optionName the name used for this option in this invocation
* @property arguments the arguments supplied for this option
*/
data class OptionInvocation<T> internal constructor(
// Internal constructor so future versions can add properties
// without breaking compatibility.
val value: Holder<T>?,
val optionName: String,
val arguments: List<String>
)
private val shortOptionDelegates = mutableMapOf<Char, OptionDelegate<*>>()
private val longOptionDelegates = mutableMapOf<String, OptionDelegate<*>>()
private val positionalDelegates = mutableListOf<Pair<PositionalDelegate<*>, Boolean>>()
private val delegates = LinkedHashSet<Delegate<*>>()
internal fun registerOption(name: String, delegate: OptionDelegate<*>) {
if (name.startsWith("--")) {
require(name.length > 2) { "long option '$name' must have at least one character after hyphen" }
require(name !in longOptionDelegates) { "long option '$name' already in use" }
longOptionDelegates.put(name, delegate)
} else if (name.startsWith("-")) {
require(name.length == 2) { "short option '$name' can only have one character after hyphen" }
val key = name.get(1)
require(key !in shortOptionDelegates) { "short option '$name' already in use" }
shortOptionDelegates.put(key, delegate)
} else {
throw IllegalArgumentException("illegal option name '$name' -- must start with '-' or '--'")
}
}
internal fun registerPositional(delegate: PositionalDelegate<*>, hasDefault: Boolean) {
positionalDelegates.add(delegate to hasDefault)
}
private var inValidation = false
private var finished = false
/**
* Ensures that arguments have been parsed and validated.
*
* @throws SystemExitException if parsing or validation failed.
*/
fun force() {
if (!inParse) {
if (!finished) {
parseOptions
if (!inValidation) {
inValidation = true
try {
for (delegate in delegates) delegate.checkHasValue()
for (delegate in delegates) delegate.validate()
} finally {
inValidation = false
}
}
}
}
}
/**
* Provides an instance of T, where all arguments have already been parsed and validated
*/
fun <T> parseInto(constructor: (ArgParser) -> T): T {
if (builtinDelegateCount != delegates.size) {
throw IllegalStateException("You can only use the parseInto function with a clean ArgParser instance")
}
val provided = constructor(this)
force()
return provided
}
private var inParse = false
internal fun checkNotParsed() {
if (inParse || finished) throw IllegalStateException("arguments have already been parsed")
}
private val parseOptions by lazy {
val positionalArguments = mutableListOf<String>()
inParse = true
try {
var i = 0
optionLoop@ while (i < args.size) {
val arg = args[i]
i += when {
arg == "--" -> {
i++
break@optionLoop
}
arg.startsWith("--") ->
parseLongOpt(i, args)
arg.startsWith("-") ->
parseShortOpts(i, args)
else -> {
positionalArguments.add(arg)
when (mode) {
Mode.GNU -> 1
Mode.POSIX -> {
i++
break@optionLoop
}
}
}
}
}
// Collect remaining arguments as positional-only arguments
positionalArguments.addAll(args.slice(i..args.size - 1))
parsePositionalArguments(positionalArguments)
finished = true
} finally {
inParse = false
}
}
private fun parsePositionalArguments(args: List<String>) {
var lastValueName: String? = null
var index = 0
var remaining = args.size
var extra = (remaining - positionalDelegates.map {
if (it.second) 0 else it.first.sizeRange.first
}.sum()).coerceAtLeast(0)
for ((delegate, hasDefault) in positionalDelegates) {
val minSize = if (hasDefault) 0 else delegate.sizeRange.first
val sizeRange = delegate.sizeRange
val chunkSize = (minSize + extra).coerceAtMost(sizeRange.endInclusive)
if (chunkSize > remaining) {
throw MissingRequiredPositionalArgumentException(delegate.errorName)
}
if (chunkSize != 0 || !hasDefault) {
delegate.parseArguments(args.subList(index, index + chunkSize))
}
lastValueName = delegate.errorName
index += chunkSize
remaining -= chunkSize
extra -= chunkSize - minSize
}
if (remaining > 0) {
throw UnexpectedPositionalArgumentException(lastValueName)
}
}
/**
* @param index index into args, starting at a long option, eg: "--verbose"
* @param args array of command-line arguments
* @return number of arguments that have been processed
*/
private fun parseLongOpt(index: Int, args: Array<out String>): Int {
val name: String
val firstArg: String?
val m = NAME_EQUALS_VALUE_REGEX.matchEntire(args[index])
if (m == null) {
name = args[index]
firstArg = null
} else {
// if NAME_EQUALS_VALUE_REGEX then there must be groups 1 and 2
name = m.groups[1]!!.value
firstArg = m.groups[2]!!.value
}
val delegate = longOptionDelegates.get(name)
if (delegate == null) {
throw UnrecognizedOptionException(name)
} else {
var consumedArgs = delegate.parseOption(name, firstArg, index + 1, args)
if (firstArg != null) {
if (consumedArgs < 1) throw UnexpectedOptionArgumentException(name)
consumedArgs -= 1
}
return 1 + consumedArgs
}
}
/**
* @param index index into args, starting at a set of short options, eg: "-abXv"
* @param args array of command-line arguments
* @return number of arguments that have been processed
*/
private fun parseShortOpts(index: Int, args: Array<out String>): Int {
val opts = args[index]
var optIndex = 1
while (optIndex < opts.length) {
val optKey = opts[optIndex]
val optName = "-$optKey"
optIndex++ // optIndex now points just after optKey
val delegate = shortOptionDelegates.get(optKey)
if (delegate == null) {
throw UnrecognizedOptionException(optName)
} else {
val firstArg = if (optIndex >= opts.length) null else opts.substring(optIndex)
val consumed = delegate.parseOption(optName, firstArg, index + 1, args)
if (consumed > 0) {
return consumed + (if (firstArg == null) 1 else 0)
}
}
}
return 1
}
init {
if (helpFormatter != null) {
option<Unit>("-h", "--help",
errorName = "HELP", // This should never be used, but we need to say something
help = "show this help message and exit") {
throw ShowHelpException(helpFormatter, delegates.toList())
}.default(Unit).registerRoot()
}
}
private val builtinDelegateCount = delegates.size
}
private val NAME_EQUALS_VALUE_REGEX = Regex("^([^=]+)=(.*)$")
internal val LEADING_HYPHENS_REGEX = Regex("^-{1,2}")
private const val OPTION_CHAR_CLASS = "[a-zA-Z0-9]"
internal val OPTION_NAME_RE = Regex("^(-$OPTION_CHAR_CLASS)|(--$OPTION_CHAR_CLASS+([-_\\.]$OPTION_CHAR_CLASS+)*)$")
private const val ARG_INITIAL_CHAR_CLASS = "[A-Z]"
private const val ARG_CHAR_CLASS = "[A-Z0-9]"
internal val ARG_NAME_RE = Regex("^$ARG_INITIAL_CHAR_CLASS+([-_\\.]$ARG_CHAR_CLASS+)*$")
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/Default.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
/**
* Returns a new `DelegateProvider` with the specified default value.
*
* @param newDefault the default value for the resulting [ArgParser.Delegate]
*/
fun <T> ArgParser.DelegateProvider<T>.default(newDefault: T): ArgParser.DelegateProvider<T> {
return ArgParser.DelegateProvider(ctor = ctor, default = { newDefault })
}
/**
* Returns a new `DelegateProvider` with the specified default value from a lambda.
*
* @param newDefault the default value for the resulting [ArgParser.Delegate]
*/
fun <T> ArgParser.DelegateProvider<T>.default(newDefault: () -> T): ArgParser.DelegateProvider<T> {
return ArgParser.DelegateProvider(ctor = ctor, default = newDefault)
}
/**
* Returns a new `Delegate` with the specified default value.
*
* @param newDefault the default value for the resulting [ArgParser.Delegate]
*/
fun <T> ArgParser.Delegate<T>.default(defaultValue: T): ArgParser.Delegate<T> = default { defaultValue }
/**
* Returns a new `Delegate` with the specified default value as a lambda.
*
* @param newDefault the default value for the resulting [ArgParser.Delegate]
*/
fun <T> ArgParser.Delegate<T>.default(defaultValue: () -> T): ArgParser.Delegate<T> {
if (hasValidators) {
throw IllegalStateException("Cannot add default after adding validators")
}
val inner = this
return object : ArgParser.Delegate<T>() {
override val hasValidators: Boolean
get() = inner.hasValidators
override fun toHelpFormatterValue(): HelpFormatter.Value = inner.toHelpFormatterValue().copy(isRequired = false)
override fun validate() {
inner.validate()
}
override val parser: ArgParser
get() = inner.parser
override val value: T
get() {
inner.parser.force()
return if (inner.hasValue) inner.value else defaultValue()
}
override val hasValue: Boolean
get() = true
override val errorName: String
get() = inner.errorName
override val help: String
get() = inner.help
override fun addValidator(validator: ArgParser.Delegate<T>.() -> Unit): ArgParser.Delegate<T> =
apply { inner.addValidator { validator(this@apply) } }
override fun registerLeaf(root: ArgParser.Delegate<*>) {
inner.registerLeaf(root)
}
}
}
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/DefaultHelpFormatter.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
import com.xenomachina.text.NBSP_CODEPOINT
import com.xenomachina.text.term.codePointWidth
import com.xenomachina.text.term.columnize
import com.xenomachina.text.term.wrapText
/**
* Default implementation of [HelpFormatter]. Output is modelled after that of common UNIX utilities and looks
* something like this:
*
* ```
* usage: program_name [-h] [-n] [-I INCLUDE]... -o OUTPUT
* [-v]... SOURCE... DEST
*
* Does something really useful.
*
* required arguments:
* -o OUTPUT, directory in which all output should
* --output OUTPUT be generated
*
* optional arguments:
* -h, --help show this help message and exit
*
* -n, --dry-run don't do anything
*
* -I INCLUDE, search in this directory for header
* --include INCLUDE files
*
* -v, --verbose increase verbosity
*
* positional arguments:
* SOURCE source file
*
* DEST destination file
*
* More info is available at http://program-name.example.com/
* ```
*
* @property prologue Text that should appear near the beginning of the help, immediately after the usage summary.
* @property epilogue Text that should appear at the end of the help.
*/
class DefaultHelpFormatter(
val prologue: String? = null,
val epilogue: String? = null
) : HelpFormatter {
val indent = " "
val indentWidth = indent.codePointWidth()
override fun format(
programName: String?,
columns: Int,
values: List<HelpFormatter.Value>
): String {
val effectiveColumns = when {
columns < 0 -> throw IllegalArgumentException("columns must be non-negative")
columns == 0 -> Int.MAX_VALUE
else -> columns
}
val sb = StringBuilder()
appendUsage(sb, effectiveColumns, programName, values)
sb.append("\n")
if (!prologue.isNullOrEmpty()) {
sb.append("\n\n")
// we just checked that prologue is non-null
sb.append(prologue!!.wrapText(effectiveColumns))
sb.append("\n\n")
}
val required = mutableListOf<HelpFormatter.Value>()
val optional = mutableListOf<HelpFormatter.Value>()
val positional = mutableListOf<HelpFormatter.Value>()
for (value in values) {
when {
value.isPositional -> positional
value.isRequired -> required
else -> optional
}.add(value)
}
val usageColumns = 2 * indentWidth - 1 + if (columns == 0) {
values.map { usageText(it).length }.max() ?: 0
} else {
// Make left column as narrow as possible without wrapping any of the individual usages, though no wider
// than half the screen.
(values.map {
usageText(it).split(" ").map { it.length }.max() ?: 0
}.max() ?: 0).coerceAtMost(effectiveColumns / 2)
}
appendSection(sb, usageColumns, effectiveColumns, "required", required)
appendSection(sb, usageColumns, effectiveColumns, "optional", optional)
appendSection(sb, usageColumns, effectiveColumns, "positional", positional)
if (!epilogue?.trim().isNullOrEmpty()) {
sb.append("\n")
// we just checked that epilogue is non-null
sb.append(epilogue!!.trim().wrapText(effectiveColumns))
sb.append("\n")
}
return sb.toString()
}
private fun appendSection(
sb: StringBuilder,
usageColumns: Int,
columns: Int,
name: String,
values: List<HelpFormatter.Value>
) {
if (!values.isEmpty()) {
sb.append("\n")
sb.append("$name arguments:\n")
for (value in values) {
val left = usageText(value).wrapText(usageColumns - indentWidth).prependIndent(indent)
val right = value.help.wrapText(columns - usageColumns - 2 * indentWidth).prependIndent(indent)
sb.append(columnize(left, right, minWidths = intArrayOf(usageColumns)))
sb.append("\n\n")
}
}
}
private fun usageText(value: HelpFormatter.Value) =
value.usages.map { it.replace(' ', '\u00a0') }.joinToString(", ")
private fun appendUsage(sb: StringBuilder, columns: Int, programName: String?, values: List<HelpFormatter.Value>) {
var usageStart = USAGE_PREFIX + (if (programName != null) " $programName" else "")
val valueSB = StringBuilder()
for (value in values) value.run {
if (!usages.isEmpty()) {
val usage = usages[0].replace(' ', NBSP_CODEPOINT.toChar())
if (isRequired) {
valueSB.append(" $usage")
} else {
valueSB.append(" [$usage]")
}
if (isRepeating) {
valueSB.append("...")
}
}
}
if (usageStart.length > columns / 2) {
sb.append(usageStart)
sb.append("\n")
val valueIndent = (USAGE_PREFIX + " " + indent).codePointWidth()
val valueColumns = columns - valueIndent
sb.append(valueSB.toString().wrapText(valueColumns).prependIndent(" ".repeat(valueIndent)))
} else {
usageStart += " "
val valueColumns = columns - usageStart.length
sb.append(columnize(usageStart, valueSB.toString().wrapText(valueColumns)))
}
}
}
private const val USAGE_PREFIX = "usage:"
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/Exceptions.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
import java.io.Writer
/**
* Indicates that the user requested that help should be shown (with the
* `--help` option, for example).
*/
class ShowHelpException internal constructor(
private val helpFormatter: HelpFormatter,
private val delegates: List<ArgParser.Delegate<*>>
) : SystemExitException("Help was requested", 0) {
override fun printUserMessage(writer: Writer, programName: String?, columns: Int) {
writer.write(helpFormatter.format(programName, columns, delegates.map { it.toHelpFormatterValue() }))
}
}
/**
* Indicates that an unrecognized option was supplied.
*
* @property optName the name of the option
*/
open class UnrecognizedOptionException(val optName: String) :
SystemExitException("unrecognized option '$optName'", 2)
/**
* Indicates that a value is missing after parsing has completed.
*
* @property valueName the name of the missing value
*/
open class MissingValueException(val valueName: String) :
SystemExitException("missing $valueName", 2)
/**
* Indicates that the value of a supplied argument is invalid.
*/
open class InvalidArgumentException(message: String) : SystemExitException(message, 2)
/**
* Indicates that a required option argument was not supplied.
*
* @property optName the name of the option
* @property argName the name of the missing argument, or null
*/
open class OptionMissingRequiredArgumentException(val optName: String, val argName: String? = null) :
SystemExitException(
"option '$optName' is missing " + (
if (argName == null) "a required argument"
else "the required argument $argName"),
2)
/**
* Indicates that a required positional argument was not supplied.
*
* @property argName the name of the positional argument
*/
open class MissingRequiredPositionalArgumentException(val argName: String) :
SystemExitException("missing $argName operand", 2)
/**
* Indicates that an argument was forced upon an option that does not take one.
*
* For example, if the arguments contained "--foo=bar" and the "--foo" option does not consume any arguments.
*
* @property optName the name of the option
*/
open class UnexpectedOptionArgumentException(val optName: String) :
SystemExitException("option '$optName' doesn't allow an argument", 2)
/**
* Indicates that there is an unhandled positional argument.
*
* @property valueName the name of the missing value
*/
open class UnexpectedPositionalArgumentException(val valueName: String?) :
SystemExitException("unexpected argument${if (valueName == null) "" else " after $valueName"}", 2)
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/HelpFormatter.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
/**
* Formats help for an [ArgParser].
*/
interface HelpFormatter {
/**
* Formats a help message.
*
* @param programName name of the program as it should appear in usage information, or null if
* program name is unknown.
* @param columns width of display help should be formatted for, measured in character cells, or 0 for infinite
* width.
* @param values [Value] objects describing the arguments types available.
*/
fun format(programName: String?, columns: Int, values: List<Value>): String
/**
* An option or positional argument type which should be formatted for help
*
* @param usages possible usage strings for this argument type
* @param isRequired indicates whether this is required
* @param isRepeating indicates whether it makes sense to repeat this argument
* @param isPositional indicates whether this is a positional argument
* @param help help text provided at Delegate construction time
*/
data class Value(
val usages: List<String>,
val isRequired: Boolean,
val isRepeating: Boolean,
val isPositional: Boolean,
val help: String
)
}
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/OptionDelegate.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
import com.xenomachina.common.Holder
internal class OptionDelegate<T>(
parser: ArgParser,
errorName: String,
help: String,
val optionNames: List<String>,
val argNames: List<String>,
val isRepeating: Boolean,
val handler: ArgParser.OptionInvocation<T>.() -> T
) : ParsingDelegate<T>(parser, errorName, help) {
init {
for (optionName in optionNames) {
if (!OPTION_NAME_RE.matches(optionName)) {
throw IllegalArgumentException("$optionName is not a valid option name")
}
}
for (argName in argNames) {
if (!ARG_NAME_RE.matches(argName)) {
throw IllegalArgumentException("$argName is not a valid argument name")
}
}
}
fun parseOption(name: String, firstArg: String?, index: Int, args: Array<out String>): Int {
val arguments = mutableListOf<String>()
if (!argNames.isEmpty()) {
if (firstArg != null) arguments.add(firstArg)
val required = argNames.size - arguments.size
if (required + index > args.size) {
// Only pass an argName if more than one argument.
// Naming it when there's just one seems unnecessarily verbose.
val argName = if (argNames.size > 1) argNames[args.size - index] else null
throw OptionMissingRequiredArgumentException(name, argName)
}
for (i in 0 until required) {
arguments.add(args[index + i])
}
}
val input = ArgParser.OptionInvocation(holder, name, arguments)
holder = Holder(handler(input))
return argNames.size
}
override fun toHelpFormatterValue(): HelpFormatter.Value {
return HelpFormatter.Value(
isRequired = (holder == null),
isRepeating = isRepeating,
usages = if (!argNames.isEmpty()) {
optionNames.map { "$it ${argNames.joinToString(" ")}" }
} else {
optionNames
},
isPositional = false,
help = help)
}
override fun registerLeaf(root: ArgParser.Delegate<*>) {
for (name in optionNames) {
parser.registerOption(name, this)
}
}
}
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/ParsingDelegate.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
import com.xenomachina.common.Holder
internal abstract class ParsingDelegate<T>(
override val parser: ArgParser,
override val errorName: String,
override val help: String
) : ArgParser.Delegate<T>() {
protected var holder: Holder<T>? = null
override fun addValidator(validator: ArgParser.Delegate<T>.() -> Unit): ArgParser.Delegate<T> = apply {
validators.add(validator)
}
override val hasValidators: Boolean
get() = validators.isNotEmpty()
override val value: T
get() {
parser.force()
checkHasValue()
return holder!!.value
}
override val hasValue: Boolean
get() = holder != null
override fun validate() {
for (validator in validators) validator()
}
private val validators = mutableListOf<ArgParser.Delegate<T>.() -> Unit>()
}
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/PositionalDelegate.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
import com.xenomachina.common.Holder
internal class PositionalDelegate<T>(
parser: ArgParser,
argName: String,
val sizeRange: IntRange,
help: String,
val transform: String.() -> T
) : ParsingDelegate<List<T>>(parser, argName, help) {
init {
require(ARG_NAME_RE.matches(argName)) { "$argName is not a valid argument name" }
}
override fun registerLeaf(root: ArgParser.Delegate<*>) {
assert(holder == null)
val hasDefault = root.hasValue
if (hasDefault && sizeRange.first != 1) {
throw IllegalStateException(
"default value can only be applied to positional that requires a minimum of 1 arguments")
}
// TODO: this feels like a bit of a kludge. Consider making .default only work on positional and not
// postionalList by having them return different types?
parser.registerPositional(this, hasDefault)
}
fun parseArguments(args: List<String>) {
holder = Holder(args.map(transform))
}
override fun toHelpFormatterValue(): HelpFormatter.Value {
return HelpFormatter.Value(
isRequired = sizeRange.first > 0,
isRepeating = sizeRange.last > 1,
usages = listOf(errorName),
isPositional = true,
help = help)
}
}
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/PosixNaming.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
/**
* Defines rules for POSIX-style argument and option naming.
*/
internal object PosixNaming {
fun identifierToOptionName(identifier: String): String {
return when (identifier.length) {
1 -> "-" + identifier
else -> "--" + identifier.camelCaseToUnderscored()
}
}
fun String.camelCaseToUnderscored(): String {
return replace('_', '-')
.replace(Regex("(\\p{javaLowerCase})(\\p{javaUpperCase})")) { m ->
m.groups[1]!!.value + "-" + m.groups[2]!!.value.toLowerCase()
}
}
fun identifierToArgName(identifier: String): String {
return identifier.camelCaseToUnderscored().toUpperCase()
}
fun selectRepresentativeOptionName(names: Array<out String>): String {
if (names.size < 1)
throw IllegalArgumentException("need at least one option name")
// Return first long option...
for (name in names) {
if (name.startsWith("--")) {
return name
}
}
// ... but failing that just return first option.
return names[0]
}
fun optionNameToArgName(name: String) =
LEADING_HYPHENS_REGEX.replace(name, "").toUpperCase().replace('-', '_')
}
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/SystemExitException.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
import java.io.OutputStreamWriter
import java.io.Writer
import kotlin.system.exitProcess
/**
* An exception that wants the process to terminate with a specific status code, and also (optionally) wants to display
* a message to [System.out] or [System.err].
*
* @property returnCode the return code that this process should exit with
*/
open class SystemExitException(message: String, val returnCode: Int) : Exception(message) {
/**
* Prints a message for the user to either `System.err` or `System.out`, and then exits with the appropriate
* return code.
*
* @param programName the name of this program as invoked, or null if not known
* @param columns the number of columns to wrap at, or 0 if not to wrap at all
*/
fun printAndExit(programName: String? = null, columns: Int = 0): Nothing {
val writer = OutputStreamWriter(if (returnCode == 0) System.out else System.err)
printUserMessage(writer, programName, columns)
writer.flush()
exitProcess(returnCode)
}
/**
* Prints a message for the user to the specified `Writer`.
*
* @param writer where to write message for the user
* @param programName the name of this program as invoked, or null if not known
* @param columns the number of columns to wrap at, or 0 if not to wrap at all
*/
open fun printUserMessage(writer: Writer, programName: String?, columns: Int) {
val leader = if (programName == null) "" else "$programName: "
writer.write("$leader$message\n")
}
}
/**
* Calls [SystemExitException.printAndExit] on any `SystemExitException` that
* is caught.
*
* @param programName the name of the program. If null, the system property com.xenomachina.argparser.programName will
* be used, if set.
* @param columns the number of columns to wrap any caught
* `SystemExitException` to. Specify null for reasonable defaults, or 0 to not
* wrap at all.
* @param body the code that may throw a `SystemExitException`
*/
fun <R> mainBody(programName: String? = null, columns: Int? = null, body: () -> R): R {
try {
return body()
} catch (e: SystemExitException) {
e.printAndExit(
programName ?: System.getProperty(PROGRAM_NAME_PROPERTY),
columns ?: System.getenv("COLUMNS")?.toInt() ?: DEFAULT_COLUMNS)
}
}
private const val PROGRAM_NAME_PROPERTY = "com.xenomachina.argparser.programName"
private const val DEFAULT_COLUMNS = 80
================================================
FILE: src/main/kotlin/com/xenomachina/argparser/WrappingDelegate.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
internal class WrappingDelegate<U, W>(
private val inner: ArgParser.Delegate<U>,
private val wrap: (U) -> W
) : ArgParser.Delegate<W>() {
override val parser: ArgParser
get() = inner.parser
override val value: W
get() = wrap(inner.value)
override val hasValue: Boolean
get() = inner.hasValue
override val errorName: String
get() = inner.errorName
override val help: String
get() = inner.help
override fun validate() {
inner.validate()
}
override fun toHelpFormatterValue(): HelpFormatter.Value = inner.toHelpFormatterValue()
override fun addValidator(validator: ArgParser.Delegate<W>.() -> Unit): ArgParser.Delegate<W> =
apply { inner.addValidator { validator(this@WrappingDelegate) } }
override val hasValidators: Boolean
get() = inner.hasValidators
override fun registerLeaf(root: ArgParser.Delegate<*>) {
inner.registerLeaf(root)
}
}
================================================
FILE: src/test/kotlin/com/xenomachina/argparser/ArgParserTest.kt
================================================
// Copyright © 2016 Laurence Gonsalves
//
// This file is part of kotlin-argparser, a library which can be found at
// http://github.com/xenomachina/kotlin-argparser
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 2.1 of the License, or (at your
// option) any later version.
//
// This library 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 Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, see http://www.gnu.org/licenses/
package com.xenomachina.argparser
import com.xenomachina.argparser.PosixNaming.identifierToOptionName
import com.xenomachina.common.orElse
import io.kotlintest.matchers.beOfType
import io.kotlintest.should
import io.kotlintest.shouldBe
import io.kotlintest.shouldThrow
import io.kotlintest.specs.FunSpec
import java.io.File
import java.io.StringWriter
class ArgParserTest : FunSpec({
test("Option name validation") {
val parser = parserOf()
// These are all acceptable.
parser.option<Int>("-x", help = TEST_HELP) { 0 }
parser.option<Int>("--x", help = TEST_HELP) { 0 }
parser.option<Int>("--xy", help = TEST_HELP) { 0 }
parser.option<Int>("-X", help = TEST_HELP) { 0 }
parser.option<Int>("--X", help = TEST_HELP) { 0 }
parser.option<Int>("--XY", help = TEST_HELP) { 0 }
parser.option<Int>("--X-Y", help = TEST_HELP) { 0 }
parser.option<Int>("--X_Y", help = TEST_HELP) { 0 }
parser.option<Int>("-5", help = TEST_HELP) { 0 }
parser.option<Int>("--5", help = TEST_HELP) { 0 }
parser.option<Int>("--5Y", help = TEST_HELP) { 0 }
parser.option<Int>("--X5", help = TEST_HELP) { 0 }
parser.option<Int>("--x.y", help = TEST_HELP) { 0 }
shouldThrow<IllegalArgumentException> {
parser.option<Int>("-_", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("---x", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("x", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("-xx", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("--foo bar", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("--foo--bar", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("--f!oobar", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("--.", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("--.foo", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.option<Int>("--foo.", help = TEST_HELP) { 0 }
}
}
test("Positional name validation") {
val parser = parserOf()
// These are all acceptable.
parser.positional<Int>("X", help = TEST_HELP) { 0 }
parser.positional<Int>("XYZ", help = TEST_HELP) { 0 }
parser.positional<Int>("XY-Z", help = TEST_HELP) { 0 }
parser.positional<Int>("XY_Z", help = TEST_HELP) { 0 }
parser.positional<Int>("XY.Z", help = TEST_HELP) { 0 }
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("-", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("_", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("x", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("-X", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("X-", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("X--Y", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("X!", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("5", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>(".", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>(".XY", help = TEST_HELP) { 0 }
}
shouldThrow<IllegalArgumentException> {
parser.positional<Int>("XY.", help = TEST_HELP) { 0 }
}
// This should be acceptable
parser.option<Int>("--foobar", argNames = listOf("X-Y"), help = TEST_HELP) { 0 }
// This should not
shouldThrow<IllegalArgumentException> {
parser.option<Int>("--foobar", argNames = listOf("X--Y"), help = TEST_HELP) { 0 }
}
}
test("Argless short options") {
class Args(parser: ArgParser) {
val xyz by parser.option<MutableList<String>>("-x", "-y", "-z",
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName")
}
}
}
Args(parserOf("-x", "-y", "-z", "-z", "-y")).xyz shouldBe listOf("-x", "-y", "-z", "-z", "-y")
Args(parserOf("-xyz")).xyz shouldBe listOf("-x", "-y", "-z")
}
test("Short options with args") {
class Args(parser: ArgParser) {
val a by parser.flagging("-a", help = TEST_HELP)
val b by parser.flagging("-b", help = TEST_HELP)
val c by parser.flagging("-c", help = TEST_HELP)
val xyz by parser.option<MutableList<String>>("-x", "-y", "-z",
argNames = oneArgName, help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName:${arguments.first()}")
}
}
}
// Test with value as separate arg
Args(parserOf("-x", "0", "-y", "1", "-z", "2", "-z", "3", "-y", "4")).xyz shouldBe listOf("-x:0", "-y:1", "-z:2", "-z:3", "-y:4")
// Test with value concatenated
Args(parserOf("-x0", "-y1", "-z2", "-z3", "-y4")).xyz shouldBe listOf("-x:0", "-y:1", "-z:2", "-z:3", "-y:4")
// Test with = between option and value. Note that the "=" is treated as part of the option value for short options.
Args(parserOf("-x=0", "-y=1", "-z=2", "-z=3", "-y=4")).xyz shouldBe listOf("-x:=0", "-y:=1", "-z:=2", "-z:=3", "-y:=4")
// Test chained options. Note that an option with arguments must be last in the chain
val chain1 = Args(parserOf("-abxc"))
chain1.a shouldBe true
chain1.b shouldBe true
chain1.c shouldBe false
chain1.xyz shouldBe listOf("-x:c")
val chain2 = Args(parserOf("-axbc"))
chain2.a shouldBe true
chain2.b shouldBe false
chain2.c shouldBe false
chain2.xyz shouldBe listOf("-x:bc")
}
test("Mixed short options") {
class Args(parser: ArgParser) {
val def by parser.option<MutableList<String>>("-d", "-e", "-f",
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName")
}
}
val abc by parser.option<MutableList<String>>("-a", "-b", "-c",
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName")
}
}
}
Args(parserOf("-adbefccbafed")).run {
def shouldBe listOf("-d", "-e", "-f", "-f", "-e", "-d")
abc shouldBe listOf("-a", "-b", "-c", "-c", "-b", "-a")
}
}
test("Mixed short options with args") {
class Args(parser: ArgParser) {
val def by parser.option<MutableList<String>>("-d", "-e", "-f",
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName")
}
}
val abc by parser.option<MutableList<String>>("-a", "-b", "-c",
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName")
}
}
val xyz by parser.option<MutableList<String>>("-x", "-y", "-z",
argNames = oneArgName,
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName:${arguments.first()}")
}
}
}
Args(parserOf("-adecfy5", "-x0", "-bzxy")).run {
abc shouldBe listOf("-a", "-c", "-b")
def shouldBe listOf("-d", "-e", "-f")
xyz shouldBe listOf("-y:5", "-x:0", "-z:xy")
}
}
test("Argless long options") {
class Args(parser: ArgParser) {
val xyz by parser.option<MutableList<String>>("--xray", "--yellow", "--zebra",
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName")
}
}
}
Args(parserOf("--xray", "--yellow", "--zebra", "--zebra", "--yellow")).xyz shouldBe listOf("--xray", "--yellow", "--zebra", "--zebra", "--yellow")
Args(parserOf("--xray", "--yellow", "--zebra")).xyz shouldBe listOf("--xray", "--yellow", "--zebra")
}
test("Dotted long options") {
class Args(parser: ArgParser) {
val xyz by parser.option<MutableList<String>>("--x.ray", "--color.yellow", "--animal.zebra",
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName")
}
}
}
Args(parserOf("--x.ray", "--animal.zebra", "--color.yellow", "--x.ray")).xyz shouldBe listOf("--x.ray", "--animal.zebra", "--color.yellow", "--x.ray")
}
test("Long options with one arg") {
class Args(parser: ArgParser) {
val xyz by parser.option<MutableList<String>>("--xray", "--yellow", "--zaphod",
argNames = oneArgName,
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply {
add("$optionName:${arguments.first()}")
}
}
}
// Test with value as separate arg
Args(parserOf("--xray", "0", "--yellow", "1", "--zaphod", "2", "--zaphod", "3", "--yellow", "4")).xyz shouldBe listOf("--xray:0", "--yellow:1", "--zaphod:2", "--zaphod:3", "--yellow:4")
// Test with = between option and value
Args(parserOf("--xray=0", "--yellow=1", "--zaphod=2", "--zaphod=3", "--yellow=4")).xyz shouldBe listOf("--xray:0", "--yellow:1", "--zaphod:2", "--zaphod:3", "--yellow:4")
shouldThrow<UnrecognizedOptionException> {
Args(parserOf("--xray0", "--yellow1", "--zaphod2", "--zaphod3", "--yellow4")).xyz
}.run {
message shouldBe "unrecognized option '--xray0'"
}
}
test("Long options with multiple args") {
class Args(parser: ArgParser) {
val xyz by parser.option<MutableList<String>>("--xray", "--yak", "--zaphod",
argNames = listOf("COLOR", "SIZE", "FLAVOR"),
help = TEST_HELP) {
value.orElse { mutableListOf<String>() }.apply { add("$optionName:$arguments") }
}
}
// Test with value as separate arg
Args(parserOf("--xray", "red", "5", "salty")).xyz shouldBe listOf("--xray:[red, 5, salty]")
Args(parserOf("--zaphod", "green", "42", "sweet", "--yak", "blue", "7", "bitter")).xyz shouldBe listOf(
"--zaphod:[green, 42, sweet]", "--yak:[blue, 7, bitter]")
// Note that something that looks like an option is consumed as an argument if it appears where an argument
// should be. This is expected behavior.
Args(parserOf("--zaphod", "green", "42", "--yak")).xyz shouldBe listOf(
"--zaphod:[green, 42, --yak]")
shouldThrow<OptionMissingRequiredArgumentException> {
Args(parserOf("--zaphod", "green", "42", "sweet", "--yak", "blue", "7")).xyz
}.run {
message shouldBe "option '--yak' is missing the required argument FLAVOR"
}
shouldThrow<OptionMissingRequiredArgumentException> {
Args(parserOf("--zaphod", "green")).xyz
}.run {
message shouldBe "option '--zaphod' is missing the required argument SIZE"
}
shouldThrow<OptionMissingRequiredArgumentException> {
Args(parserOf("--xray")).xyz
}.run {
message shouldBe "option '--xray' is missing the required argument COLOR"
}
}
test("Delegate provider") {
fun ArgParser.putting(vararg names: String, help: String) =
option<MutableMap<String, String>>(*names,
argNames = listOf("KEY", "VALUE"),
help = help) {
value.orElse { mutableMapOf<String, String>() }.apply {
put(arguments.first(), arguments.last()) }
}
fun ArgParser.putting(help: String) =
ArgParser.DelegateProvider { identifier ->
putting(identifierToOptionName(identifier), help = help) }
class Args(parser: ArgParser) {
val dict by parser.putting(TEST_HELP)
}
// Test with value as separate arg
Args(parserOf("--dict", "red", "5")).dict shouldBe mapOf("red" to "5")
Args(parserOf(
"--dict", "green", "42",
"--dict", "blue", "7"
)).dict shouldBe mapOf(
"green" to "42",
"blue" to "7")
// Note that something that looks like an option is consumed as an argument if it appears where an argument
// should be. This is expected behavior.
Args(parserOf("--dict", "green", "--dict")).dict shouldBe mapOf("green" to "--dict")
shouldThrow<OptionMissingRequiredArgumentException> {
Args(parserOf("--dict", "green", "42", "--dict", "blue")).dict
}.run {
message shouldBe "option '--dict' is missing the required argument VALUE"
}
shouldThrow<OptionMissingRequiredArgumentException> {
Args(parserOf("--dict")).dict
}.run {
message shouldBe "option '--dict' is missing the required argument KEY"
}
}
test("Default") {
class Args(parser: ArgParser) {
val x by parser.storing("-x",
help = TEST_HELP) { toInt() }.default(5)
}
// Test with no value
Args(parserOf()).x shouldBe 5
// Test with value
Args(parserOf("-x6")).x shouldBe 6
// Test with value as separate arg
Args(parserOf("-x", "7")).x shouldBe 7
// Test with multiple values
Args(parserOf("-x9", "-x8")).x shouldBe 8
}
test("DDefault with providerefault") {
class Args(parser: ArgParser) {
val x by parser.storing(help = TEST_HELP) { toInt() }.default(5)
}
// Test with no value
Args(parserOf()).x shouldBe 5
// Test with value
Args(parserOf("-x6")).x shouldBe 6
// Test with value as separate arg
Args(parserOf("-x", "7")).x shouldBe 7
// Test with multiple values
Args(parserOf("-x9", "-x8")).x shouldBe 8
}
test("Default with la") {
class Args(parser: ArgParser) {
var defaultCalled = false
val x by parser.storing(help = TEST_HELP) { toInt() }.default { defaultCalled = true; 5 }
}
// Test default hasn't been called
Args(parserOf("-x6")).defaultCalled shouldBe false
// Test with no value
val args = Args(parserOf())
args.x shouldBe 5
args.defaultCalled shouldBe true
}
test("Flag") {
class Args(parser: ArgParser) {
val x by parser.flagging("-x", "--ecks",
help = TEST_HELP)
val y by parser.flagging("-y",
help = TEST_HELP)
val z by parser.flagging("--zed",
help = TEST_HELP)
}
Args(parserOf("-x", "-y", "--zed", "--zed", "-y")).run {
x shouldBe true
y shouldBe true
z shouldBe true
}
Args(parserOf()).run {
x shouldBe false
y shouldBe false
z shouldBe false
}
Args(parserOf("-y", "--ecks")).run {
x shouldBe true
y shouldBe true
}
Args(parserOf("--zed")).run {
x shouldBe false
y shouldBe false
z shouldBe true
}
}
test("Argument no parser") {
class Args(parser: ArgParser) {
val x by parser.storing("--ecks", "-x",
help = TEST_HELP)
}
Args(parserOf("-x", "foo")).x shouldBe "foo"
Args(parserOf("-x", "bar", "-x", "baz")).x shouldBe "baz"
Args(parserOf("--ecks", "long", "-x", "short")).x shouldBe "short"
Args(parserOf("-x", "short", "--ecks", "long")).x shouldBe "long"
val args = Args(parserOf())
shouldThrow<MissingValueException> {
args.x
}.run {
message shouldBe "missing ECKS"
}
}
test("Argument missing long") {
class Args(parser: ArgParser) {
val x by parser.storing("--ecks",
help = TEST_HELP)
}
val args = Args(parserOf())
shouldThrow<MissingValueException> {
args.x
}.run {
message shouldBe "missing ECKS"
}
}
test("Argument missing short") {
class Args(parser: ArgParser) {
val x by parser.storing("-x",
help = TEST_HELP)
}
val args = Args(parserOf())
shouldThrow<MissingValueException> {
args.x
}.run {
message shouldBe "missing X"
}
}
test("Argument withParser") {
class Args(parser: ArgParser) {
val x by parser.storing("-x", "--ecks",
help = TEST_HELP) { toInt() }
}
val opts1 = Args(parserOf("-x", "5"))
opts1.x shouldBe 5
val opts2 = Args(parserOf("-x", "1", "-x", "2"))
opts2.x shouldBe 2
val opts3 = Args(parserOf("--ecks", "3", "-x", "4"))
opts3.x shouldBe 4
val opts4 = Args(parserOf("-x", "5", "--ecks", "6"))
opts4.x shouldBe 6
val opts6 = Args(parserOf())
shouldThrow<MissingValueException> {
opts6.x
}.run {
message shouldBe "m
gitextract_f8jhxc_z/
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── COPYING
├── README.md
├── build.gradle
├── codecov.sh
├── detekt.yml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src/
├── main/
│ └── kotlin/
│ └── com/
│ └── xenomachina/
│ └── argparser/
│ ├── ArgParser.kt
│ ├── Default.kt
│ ├── DefaultHelpFormatter.kt
│ ├── Exceptions.kt
│ ├── HelpFormatter.kt
│ ├── OptionDelegate.kt
│ ├── ParsingDelegate.kt
│ ├── PositionalDelegate.kt
│ ├── PosixNaming.kt
│ ├── SystemExitException.kt
│ └── WrappingDelegate.kt
└── test/
└── kotlin/
└── com/
└── xenomachina/
└── argparser/
└── ArgParserTest.kt
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (249K chars).
[
{
"path": ".gitignore",
"chars": 701,
"preview": "### Gradle ###\n.gradle\n/build/\n\n# Ignore Gradle GUI config\ngradle-app.setting\n\n# Avoid ignoring Gradle wrapper jar file "
},
{
"path": ".travis.yml",
"chars": 279,
"preview": "language: java\n\njdk:\n - openjdk8\n\nbefore_cache:\n - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock\n - rm -"
},
{
"path": "CHANGELOG.md",
"chars": 6196,
"preview": "# Change Log\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Chang"
},
{
"path": "COPYING",
"chars": 26530,
"preview": " GNU LESSER GENERAL PUBLIC LICENSE\n Version 2.1, February 1999\n\n Copyright (C) 19"
},
{
"path": "README.md",
"chars": 18640,
"preview": "# <img alt=\"Kotlin --argparser\" src=\"https://rawgit.com/xenomachina/kotlin-argparser/master/logo.svg\" style=\"transform:s"
},
{
"path": "build.gradle",
"chars": 8217,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "codecov.sh",
"chars": 43185,
"preview": "#!/usr/bin/env bash\n\nset -e +o pipefail\n\nVERSION=\"8fda091\"\n\nurl=\"https://codecov.io\"\nenv=\"$CODECOV_ENV\"\nservice=\"\"\ntoken"
},
{
"path": "detekt.yml",
"chars": 10458,
"preview": "# `git diff detekt-default detekt.yml` to see what's changed from default\n# settings\n\ntest-pattern: # Configure exclusio"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 200,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
},
{
"path": "gradle.properties",
"chars": 179,
"preview": "detekt_version = 1.0.0.RC7\ndokka_version = 0.9.17\ngradle_bintray_version = 1.8.0\nkotlintest_version = 3.1.0\nkotlin_versi"
},
{
"path": "gradlew",
"chars": 5296,
"preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n## Gradle start up"
},
{
"path": "gradlew.bat",
"chars": 2260,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "settings.gradle",
"chars": 38,
"preview": "rootProject.name = \"kotlin-argparser\"\n"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/ArgParser.kt",
"chars": 24710,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/Default.kt",
"chars": 3273,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/DefaultHelpFormatter.kt",
"chars": 6513,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/Exceptions.kt",
"chars": 3516,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/HelpFormatter.kt",
"chars": 2061,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/OptionDelegate.kt",
"chars": 3193,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/ParsingDelegate.kt",
"chars": 1736,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/PositionalDelegate.kt",
"chars": 2221,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/PosixNaming.kt",
"chars": 2148,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/SystemExitException.kt",
"chars": 3356,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/main/kotlin/com/xenomachina/argparser/WrappingDelegate.kt",
"chars": 1848,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
},
{
"path": "src/test/kotlin/com/xenomachina/argparser/ArgParserTest.kt",
"chars": 59283,
"preview": "// Copyright © 2016 Laurence Gonsalves\n//\n// This file is part of kotlin-argparser, a library which can be found at\n// h"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the xenomachina/kotlin-argparser GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (230.5 KB), approximately 58.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.