Started Work on v3
This commit is contained in:
parent
7f10a81cec
commit
7cc06eeddc
674
LICENSE
674
LICENSE
@ -1,674 +0,0 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
|
||||||
software and other kinds of works.
|
|
||||||
|
|
||||||
The licenses for most software and other practical works are designed
|
|
||||||
to take away your freedom to share and change the works. By contrast,
|
|
||||||
the GNU General Public License is intended to guarantee your freedom to
|
|
||||||
share and change all versions of a program--to make sure it remains free
|
|
||||||
software for all its users. We, the Free Software Foundation, use the
|
|
||||||
GNU General Public License for most of our software; it applies also to
|
|
||||||
any other work released this way by its authors. You can apply it to
|
|
||||||
your programs, too.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
|
||||||
have the freedom to distribute copies of free software (and charge for
|
|
||||||
them if you wish), that you receive source code or can get it if you
|
|
||||||
want it, that you can change the software or use pieces of it in new
|
|
||||||
free programs, and that you know you can do these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
|
||||||
or can get the source code. And you must show them these terms so they
|
|
||||||
know their rights.
|
|
||||||
|
|
||||||
Developers that use the GNU GPL protect your rights with two steps:
|
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
|
||||||
|
|
||||||
For the developers' and authors' protection, the GPL clearly explains
|
|
||||||
that there is no warranty for this free software. For both users' and
|
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
|
||||||
changed, so that their problems will not be attributed erroneously to
|
|
||||||
authors of previous versions.
|
|
||||||
|
|
||||||
Some devices are designed to deny users access to install or run
|
|
||||||
modified versions of the software inside them, although the manufacturer
|
|
||||||
can do so. This is fundamentally incompatible with the aim of
|
|
||||||
protecting users' freedom to change the software. The systematic
|
|
||||||
pattern of such abuse occurs in the area of products for individuals to
|
|
||||||
use, which is precisely where it is most unacceptable. Therefore, we
|
|
||||||
have designed this version of the GPL to prohibit the practice for those
|
|
||||||
products. If such problems arise substantially in other domains, we
|
|
||||||
stand ready to extend this provision to those domains in future versions
|
|
||||||
of the GPL, as needed to protect the freedom of users.
|
|
||||||
|
|
||||||
Finally, every program is threatened constantly by software patents.
|
|
||||||
States should not allow patents to restrict development and use of
|
|
||||||
software on general-purpose computers, but in those that do, we wish to
|
|
||||||
avoid the special danger that patents applied to a free program could
|
|
||||||
make it effectively proprietary. To prevent this, the GPL assures that
|
|
||||||
patents cannot be used to render the program non-free.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow.
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
0. Definitions.
|
|
||||||
|
|
||||||
"This License" refers to version 3 of the GNU General Public License.
|
|
||||||
|
|
||||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
|
||||||
works, such as semiconductor masks.
|
|
||||||
|
|
||||||
"The Program" refers to any copyrightable work licensed under this
|
|
||||||
License. Each licensee is addressed as "you". "Licensees" and
|
|
||||||
"recipients" may be individuals or organizations.
|
|
||||||
|
|
||||||
To "modify" a work means to copy from or adapt all or part of the work
|
|
||||||
in a fashion requiring copyright permission, other than the making of an
|
|
||||||
exact copy. The resulting work is called a "modified version" of the
|
|
||||||
earlier work or a work "based on" the earlier work.
|
|
||||||
|
|
||||||
A "covered work" means either the unmodified Program or a work based
|
|
||||||
on the Program.
|
|
||||||
|
|
||||||
To "propagate" a work means to do anything with it that, without
|
|
||||||
permission, would make you directly or secondarily liable for
|
|
||||||
infringement under applicable copyright law, except executing it on a
|
|
||||||
computer or modifying a private copy. Propagation includes copying,
|
|
||||||
distribution (with or without modification), making available to the
|
|
||||||
public, and in some countries other activities as well.
|
|
||||||
|
|
||||||
To "convey" a work means any kind of propagation that enables other
|
|
||||||
parties to make or receive copies. Mere interaction with a user through
|
|
||||||
a computer network, with no transfer of a copy, is not conveying.
|
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
|
||||||
to the extent that it includes a convenient and prominently visible
|
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
|
||||||
tells the user that there is no warranty for the work (except to the
|
|
||||||
extent that warranties are provided), that licensees may convey the
|
|
||||||
work under this License, and how to view a copy of this License. If
|
|
||||||
the interface presents a list of user commands or options, such as a
|
|
||||||
menu, a prominent item in the list meets this criterion.
|
|
||||||
|
|
||||||
1. Source Code.
|
|
||||||
|
|
||||||
The "source code" for a work means the preferred form of the work
|
|
||||||
for making modifications to it. "Object code" means any non-source
|
|
||||||
form of a work.
|
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
|
||||||
standard defined by a recognized standards body, or, in the case of
|
|
||||||
interfaces specified for a particular programming language, one that
|
|
||||||
is widely used among developers working in that language.
|
|
||||||
|
|
||||||
The "System Libraries" of an executable work include anything, other
|
|
||||||
than the work as a whole, that (a) is included in the normal form of
|
|
||||||
packaging a Major Component, but which is not part of that Major
|
|
||||||
Component, and (b) serves only to enable use of the work with that
|
|
||||||
Major Component, or to implement a Standard Interface for which an
|
|
||||||
implementation is available to the public in source code form. A
|
|
||||||
"Major Component", in this context, means a major essential component
|
|
||||||
(kernel, window system, and so on) of the specific operating system
|
|
||||||
(if any) on which the executable work runs, or a compiler used to
|
|
||||||
produce the work, or an object code interpreter used to run it.
|
|
||||||
|
|
||||||
The "Corresponding Source" for a work in object code form means all
|
|
||||||
the source code needed to generate, install, and (for an executable
|
|
||||||
work) run the object code and to modify the work, including scripts to
|
|
||||||
control those activities. However, it does not include the work's
|
|
||||||
System Libraries, or general-purpose tools or generally available free
|
|
||||||
programs which are used unmodified in performing those activities but
|
|
||||||
which are not part of the work. For example, Corresponding Source
|
|
||||||
includes interface definition files associated with source files for
|
|
||||||
the work, and the source code for shared libraries and dynamically
|
|
||||||
linked subprograms that the work is specifically designed to require,
|
|
||||||
such as by intimate data communication or control flow between those
|
|
||||||
subprograms and other parts of the work.
|
|
||||||
|
|
||||||
The Corresponding Source need not include anything that users
|
|
||||||
can regenerate automatically from other parts of the Corresponding
|
|
||||||
Source.
|
|
||||||
|
|
||||||
The Corresponding Source for a work in source code form is that
|
|
||||||
same work.
|
|
||||||
|
|
||||||
2. Basic Permissions.
|
|
||||||
|
|
||||||
All rights granted under this License are granted for the term of
|
|
||||||
copyright on the Program, and are irrevocable provided the stated
|
|
||||||
conditions are met. This License explicitly affirms your unlimited
|
|
||||||
permission to run the unmodified Program. The output from running a
|
|
||||||
covered work is covered by this License only if the output, given its
|
|
||||||
content, constitutes a covered work. This License acknowledges your
|
|
||||||
rights of fair use or other equivalent, as provided by copyright law.
|
|
||||||
|
|
||||||
You may make, run and propagate covered works that you do not
|
|
||||||
convey, without conditions so long as your license otherwise remains
|
|
||||||
in force. You may convey covered works to others for the sole purpose
|
|
||||||
of having them make modifications exclusively for you, or provide you
|
|
||||||
with facilities for running those works, provided that you comply with
|
|
||||||
the terms of this License in conveying all material for which you do
|
|
||||||
not control copyright. Those thus making or running the covered works
|
|
||||||
for you must do so exclusively on your behalf, under your direction
|
|
||||||
and control, on terms that prohibit them from making any copies of
|
|
||||||
your copyrighted material outside their relationship with you.
|
|
||||||
|
|
||||||
Conveying under any other circumstances is permitted solely under
|
|
||||||
the conditions stated below. Sublicensing is not allowed; section 10
|
|
||||||
makes it unnecessary.
|
|
||||||
|
|
||||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
|
||||||
|
|
||||||
No covered work shall be deemed part of an effective technological
|
|
||||||
measure under any applicable law fulfilling obligations under article
|
|
||||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
|
||||||
similar laws prohibiting or restricting circumvention of such
|
|
||||||
measures.
|
|
||||||
|
|
||||||
When you convey a covered work, you waive any legal power to forbid
|
|
||||||
circumvention of technological measures to the extent such circumvention
|
|
||||||
is effected by exercising rights under this License with respect to
|
|
||||||
the covered work, and you disclaim any intention to limit operation or
|
|
||||||
modification of the work as a means of enforcing, against the work's
|
|
||||||
users, your or third parties' legal rights to forbid circumvention of
|
|
||||||
technological measures.
|
|
||||||
|
|
||||||
4. Conveying Verbatim Copies.
|
|
||||||
|
|
||||||
You may convey verbatim copies of the Program's source code as you
|
|
||||||
receive it, in any medium, provided that you conspicuously and
|
|
||||||
appropriately publish on each copy an appropriate copyright notice;
|
|
||||||
keep intact all notices stating that this License and any
|
|
||||||
non-permissive terms added in accord with section 7 apply to the code;
|
|
||||||
keep intact all notices of the absence of any warranty; and give all
|
|
||||||
recipients a copy of this License along with the Program.
|
|
||||||
|
|
||||||
You may charge any price or no price for each copy that you convey,
|
|
||||||
and you may offer support or warranty protection for a fee.
|
|
||||||
|
|
||||||
5. Conveying Modified Source Versions.
|
|
||||||
|
|
||||||
You may convey a work based on the Program, or the modifications to
|
|
||||||
produce it from the Program, in the form of source code under the
|
|
||||||
terms of section 4, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The work must carry prominent notices stating that you modified
|
|
||||||
it, and giving a relevant date.
|
|
||||||
|
|
||||||
b) The work must carry prominent notices stating that it is
|
|
||||||
released under this License and any conditions added under section
|
|
||||||
7. This requirement modifies the requirement in section 4 to
|
|
||||||
"keep intact all notices".
|
|
||||||
|
|
||||||
c) You must license the entire work, as a whole, under this
|
|
||||||
License to anyone who comes into possession of a copy. This
|
|
||||||
License will therefore apply, along with any applicable section 7
|
|
||||||
additional terms, to the whole of the work, and all its parts,
|
|
||||||
regardless of how they are packaged. This License gives no
|
|
||||||
permission to license the work in any other way, but it does not
|
|
||||||
invalidate such permission if you have separately received it.
|
|
||||||
|
|
||||||
d) If the work has interactive user interfaces, each must display
|
|
||||||
Appropriate Legal Notices; however, if the Program has interactive
|
|
||||||
interfaces that do not display Appropriate Legal Notices, your
|
|
||||||
work need not make them do so.
|
|
||||||
|
|
||||||
A compilation of a covered work with other separate and independent
|
|
||||||
works, which are not by their nature extensions of the covered work,
|
|
||||||
and which are not combined with it such as to form a larger program,
|
|
||||||
in or on a volume of a storage or distribution medium, is called an
|
|
||||||
"aggregate" if the compilation and its resulting copyright are not
|
|
||||||
used to limit the access or legal rights of the compilation's users
|
|
||||||
beyond what the individual works permit. Inclusion of a covered work
|
|
||||||
in an aggregate does not cause this License to apply to the other
|
|
||||||
parts of the aggregate.
|
|
||||||
|
|
||||||
6. Conveying Non-Source Forms.
|
|
||||||
|
|
||||||
You may convey a covered work in object code form under the terms
|
|
||||||
of sections 4 and 5, provided that you also convey the
|
|
||||||
machine-readable Corresponding Source under the terms of this License,
|
|
||||||
in one of these ways:
|
|
||||||
|
|
||||||
a) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by the
|
|
||||||
Corresponding Source fixed on a durable physical medium
|
|
||||||
customarily used for software interchange.
|
|
||||||
|
|
||||||
b) Convey the object code in, or embodied in, a physical product
|
|
||||||
(including a physical distribution medium), accompanied by a
|
|
||||||
written offer, valid for at least three years and valid for as
|
|
||||||
long as you offer spare parts or customer support for that product
|
|
||||||
model, to give anyone who possesses the object code either (1) a
|
|
||||||
copy of the Corresponding Source for all the software in the
|
|
||||||
product that is covered by this License, on a durable physical
|
|
||||||
medium customarily used for software interchange, for a price no
|
|
||||||
more than your reasonable cost of physically performing this
|
|
||||||
conveying of source, or (2) access to copy the
|
|
||||||
Corresponding Source from a network server at no charge.
|
|
||||||
|
|
||||||
c) Convey individual copies of the object code with a copy of the
|
|
||||||
written offer to provide the Corresponding Source. This
|
|
||||||
alternative is allowed only occasionally and noncommercially, and
|
|
||||||
only if you received the object code with such an offer, in accord
|
|
||||||
with subsection 6b.
|
|
||||||
|
|
||||||
d) Convey the object code by offering access from a designated
|
|
||||||
place (gratis or for a charge), and offer equivalent access to the
|
|
||||||
Corresponding Source in the same way through the same place at no
|
|
||||||
further charge. You need not require recipients to copy the
|
|
||||||
Corresponding Source along with the object code. If the place to
|
|
||||||
copy the object code is a network server, the Corresponding Source
|
|
||||||
may be on a different server (operated by you or a third party)
|
|
||||||
that supports equivalent copying facilities, provided you maintain
|
|
||||||
clear directions next to the object code saying where to find the
|
|
||||||
Corresponding Source. Regardless of what server hosts the
|
|
||||||
Corresponding Source, you remain obligated to ensure that it is
|
|
||||||
available for as long as needed to satisfy these requirements.
|
|
||||||
|
|
||||||
e) Convey the object code using peer-to-peer transmission, provided
|
|
||||||
you inform other peers where the object code and Corresponding
|
|
||||||
Source of the work are being offered to the general public at no
|
|
||||||
charge under subsection 6d.
|
|
||||||
|
|
||||||
A separable portion of the object code, whose source code is excluded
|
|
||||||
from the Corresponding Source as a System Library, need not be
|
|
||||||
included in conveying the object code work.
|
|
||||||
|
|
||||||
A "User Product" is either (1) a "consumer product", which means any
|
|
||||||
tangible personal property which is normally used for personal, family,
|
|
||||||
or household purposes, or (2) anything designed or sold for incorporation
|
|
||||||
into a dwelling. In determining whether a product is a consumer product,
|
|
||||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
|
||||||
product received by a particular user, "normally used" refers to a
|
|
||||||
typical or common use of that class of product, regardless of the status
|
|
||||||
of the particular user or of the way in which the particular user
|
|
||||||
actually uses, or expects or is expected to use, the product. A product
|
|
||||||
is a consumer product regardless of whether the product has substantial
|
|
||||||
commercial, industrial or non-consumer uses, unless such uses represent
|
|
||||||
the only significant mode of use of the product.
|
|
||||||
|
|
||||||
"Installation Information" for a User Product means any methods,
|
|
||||||
procedures, authorization keys, or other information required to install
|
|
||||||
and execute modified versions of a covered work in that User Product from
|
|
||||||
a modified version of its Corresponding Source. The information must
|
|
||||||
suffice to ensure that the continued functioning of the modified object
|
|
||||||
code is in no case prevented or interfered with solely because
|
|
||||||
modification has been made.
|
|
||||||
|
|
||||||
If you convey an object code work under this section in, or with, or
|
|
||||||
specifically for use in, a User Product, and the conveying occurs as
|
|
||||||
part of a transaction in which the right of possession and use of the
|
|
||||||
User Product is transferred to the recipient in perpetuity or for a
|
|
||||||
fixed term (regardless of how the transaction is characterized), the
|
|
||||||
Corresponding Source conveyed under this section must be accompanied
|
|
||||||
by the Installation Information. But this requirement does not apply
|
|
||||||
if neither you nor any third party retains the ability to install
|
|
||||||
modified object code on the User Product (for example, the work has
|
|
||||||
been installed in ROM).
|
|
||||||
|
|
||||||
The requirement to provide Installation Information does not include a
|
|
||||||
requirement to continue to provide support service, warranty, or updates
|
|
||||||
for a work that has been modified or installed by the recipient, or for
|
|
||||||
the User Product in which it has been modified or installed. Access to a
|
|
||||||
network may be denied when the modification itself materially and
|
|
||||||
adversely affects the operation of the network or violates the rules and
|
|
||||||
protocols for communication across the network.
|
|
||||||
|
|
||||||
Corresponding Source conveyed, and Installation Information provided,
|
|
||||||
in accord with this section must be in a format that is publicly
|
|
||||||
documented (and with an implementation available to the public in
|
|
||||||
source code form), and must require no special password or key for
|
|
||||||
unpacking, reading or copying.
|
|
||||||
|
|
||||||
7. Additional Terms.
|
|
||||||
|
|
||||||
"Additional permissions" are terms that supplement the terms of this
|
|
||||||
License by making exceptions from one or more of its conditions.
|
|
||||||
Additional permissions that are applicable to the entire Program shall
|
|
||||||
be treated as though they were included in this License, to the extent
|
|
||||||
that they are valid under applicable law. If additional permissions
|
|
||||||
apply only to part of the Program, that part may be used separately
|
|
||||||
under those permissions, but the entire Program remains governed by
|
|
||||||
this License without regard to the additional permissions.
|
|
||||||
|
|
||||||
When you convey a copy of a covered work, you may at your option
|
|
||||||
remove any additional permissions from that copy, or from any part of
|
|
||||||
it. (Additional permissions may be written to require their own
|
|
||||||
removal in certain cases when you modify the work.) You may place
|
|
||||||
additional permissions on material, added by you to a covered work,
|
|
||||||
for which you have or can give appropriate copyright permission.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, for material you
|
|
||||||
add to a covered work, you may (if authorized by the copyright holders of
|
|
||||||
that material) supplement the terms of this License with terms:
|
|
||||||
|
|
||||||
a) Disclaiming warranty or limiting liability differently from the
|
|
||||||
terms of sections 15 and 16 of this License; or
|
|
||||||
|
|
||||||
b) Requiring preservation of specified reasonable legal notices or
|
|
||||||
author attributions in that material or in the Appropriate Legal
|
|
||||||
Notices displayed by works containing it; or
|
|
||||||
|
|
||||||
c) Prohibiting misrepresentation of the origin of that material, or
|
|
||||||
requiring that modified versions of such material be marked in
|
|
||||||
reasonable ways as different from the original version; or
|
|
||||||
|
|
||||||
d) Limiting the use for publicity purposes of names of licensors or
|
|
||||||
authors of the material; or
|
|
||||||
|
|
||||||
e) Declining to grant rights under trademark law for use of some
|
|
||||||
trade names, trademarks, or service marks; or
|
|
||||||
|
|
||||||
f) Requiring indemnification of licensors and authors of that
|
|
||||||
material by anyone who conveys the material (or modified versions of
|
|
||||||
it) with contractual assumptions of liability to the recipient, for
|
|
||||||
any liability that these contractual assumptions directly impose on
|
|
||||||
those licensors and authors.
|
|
||||||
|
|
||||||
All other non-permissive additional terms are considered "further
|
|
||||||
restrictions" within the meaning of section 10. If the Program as you
|
|
||||||
received it, or any part of it, contains a notice stating that it is
|
|
||||||
governed by this License along with a term that is a further
|
|
||||||
restriction, you may remove that term. If a license document contains
|
|
||||||
a further restriction but permits relicensing or conveying under this
|
|
||||||
License, you may add to a covered work material governed by the terms
|
|
||||||
of that license document, provided that the further restriction does
|
|
||||||
not survive such relicensing or conveying.
|
|
||||||
|
|
||||||
If you add terms to a covered work in accord with this section, you
|
|
||||||
must place, in the relevant source files, a statement of the
|
|
||||||
additional terms that apply to those files, or a notice indicating
|
|
||||||
where to find the applicable terms.
|
|
||||||
|
|
||||||
Additional terms, permissive or non-permissive, may be stated in the
|
|
||||||
form of a separately written license, or stated as exceptions;
|
|
||||||
the above requirements apply either way.
|
|
||||||
|
|
||||||
8. Termination.
|
|
||||||
|
|
||||||
You may not propagate or modify a covered work except as expressly
|
|
||||||
provided under this License. Any attempt otherwise to propagate or
|
|
||||||
modify it is void, and will automatically terminate your rights under
|
|
||||||
this License (including any patent licenses granted under the third
|
|
||||||
paragraph of section 11).
|
|
||||||
|
|
||||||
However, if you cease all violation of this License, then your
|
|
||||||
license from a particular copyright holder is reinstated (a)
|
|
||||||
provisionally, unless and until the copyright holder explicitly and
|
|
||||||
finally terminates your license, and (b) permanently, if the copyright
|
|
||||||
holder fails to notify you of the violation by some reasonable means
|
|
||||||
prior to 60 days after the cessation.
|
|
||||||
|
|
||||||
Moreover, your license from a particular copyright holder is
|
|
||||||
reinstated permanently if the copyright holder notifies you of the
|
|
||||||
violation by some reasonable means, this is the first time you have
|
|
||||||
received notice of violation of this License (for any work) from that
|
|
||||||
copyright holder, and you cure the violation prior to 30 days after
|
|
||||||
your receipt of the notice.
|
|
||||||
|
|
||||||
Termination of your rights under this section does not terminate the
|
|
||||||
licenses of parties who have received copies or rights from you under
|
|
||||||
this License. If your rights have been terminated and not permanently
|
|
||||||
reinstated, you do not qualify to receive new licenses for the same
|
|
||||||
material under section 10.
|
|
||||||
|
|
||||||
9. Acceptance Not Required for Having Copies.
|
|
||||||
|
|
||||||
You are not required to accept this License in order to receive or
|
|
||||||
run a copy of the Program. Ancillary propagation of a covered work
|
|
||||||
occurring solely as a consequence of using peer-to-peer transmission
|
|
||||||
to receive a copy likewise does not require acceptance. However,
|
|
||||||
nothing other than this License grants you permission to propagate or
|
|
||||||
modify any covered work. These actions infringe copyright if you do
|
|
||||||
not accept this License. Therefore, by modifying or propagating a
|
|
||||||
covered work, you indicate your acceptance of this License to do so.
|
|
||||||
|
|
||||||
10. Automatic Licensing of Downstream Recipients.
|
|
||||||
|
|
||||||
Each time you convey a covered work, the recipient automatically
|
|
||||||
receives a license from the original licensors, to run, modify and
|
|
||||||
propagate that work, subject to this License. You are not responsible
|
|
||||||
for enforcing compliance by third parties with this License.
|
|
||||||
|
|
||||||
An "entity transaction" is a transaction transferring control of an
|
|
||||||
organization, or substantially all assets of one, or subdividing an
|
|
||||||
organization, or merging organizations. If propagation of a covered
|
|
||||||
work results from an entity transaction, each party to that
|
|
||||||
transaction who receives a copy of the work also receives whatever
|
|
||||||
licenses to the work the party's predecessor in interest had or could
|
|
||||||
give under the previous paragraph, plus a right to possession of the
|
|
||||||
Corresponding Source of the work from the predecessor in interest, if
|
|
||||||
the predecessor has it or can get it with reasonable efforts.
|
|
||||||
|
|
||||||
You may not impose any further restrictions on the exercise of the
|
|
||||||
rights granted or affirmed under this License. For example, you may
|
|
||||||
not impose a license fee, royalty, or other charge for exercise of
|
|
||||||
rights granted under this License, and you may not initiate litigation
|
|
||||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
|
||||||
any patent claim is infringed by making, using, selling, offering for
|
|
||||||
sale, or importing the Program or any portion of it.
|
|
||||||
|
|
||||||
11. Patents.
|
|
||||||
|
|
||||||
A "contributor" is a copyright holder who authorizes use under this
|
|
||||||
License of the Program or a work on which the Program is based. The
|
|
||||||
work thus licensed is called the contributor's "contributor version".
|
|
||||||
|
|
||||||
A contributor's "essential patent claims" are all patent claims
|
|
||||||
owned or controlled by the contributor, whether already acquired or
|
|
||||||
hereafter acquired, that would be infringed by some manner, permitted
|
|
||||||
by this License, of making, using, or selling its contributor version,
|
|
||||||
but do not include claims that would be infringed only as a
|
|
||||||
consequence of further modification of the contributor version. For
|
|
||||||
purposes of this definition, "control" includes the right to grant
|
|
||||||
patent sublicenses in a manner consistent with the requirements of
|
|
||||||
this License.
|
|
||||||
|
|
||||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
|
||||||
patent license under the contributor's essential patent claims, to
|
|
||||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
|
||||||
propagate the contents of its contributor version.
|
|
||||||
|
|
||||||
In the following three paragraphs, a "patent license" is any express
|
|
||||||
agreement or commitment, however denominated, not to enforce a patent
|
|
||||||
(such as an express permission to practice a patent or covenant not to
|
|
||||||
sue for patent infringement). To "grant" such a patent license to a
|
|
||||||
party means to make such an agreement or commitment not to enforce a
|
|
||||||
patent against the party.
|
|
||||||
|
|
||||||
If you convey a covered work, knowingly relying on a patent license,
|
|
||||||
and the Corresponding Source of the work is not available for anyone
|
|
||||||
to copy, free of charge and under the terms of this License, through a
|
|
||||||
publicly available network server or other readily accessible means,
|
|
||||||
then you must either (1) cause the Corresponding Source to be so
|
|
||||||
available, or (2) arrange to deprive yourself of the benefit of the
|
|
||||||
patent license for this particular work, or (3) arrange, in a manner
|
|
||||||
consistent with the requirements of this License, to extend the patent
|
|
||||||
license to downstream recipients. "Knowingly relying" means you have
|
|
||||||
actual knowledge that, but for the patent license, your conveying the
|
|
||||||
covered work in a country, or your recipient's use of the covered work
|
|
||||||
in a country, would infringe one or more identifiable patents in that
|
|
||||||
country that you have reason to believe are valid.
|
|
||||||
|
|
||||||
If, pursuant to or in connection with a single transaction or
|
|
||||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
|
||||||
covered work, and grant a patent license to some of the parties
|
|
||||||
receiving the covered work authorizing them to use, propagate, modify
|
|
||||||
or convey a specific copy of the covered work, then the patent license
|
|
||||||
you grant is automatically extended to all recipients of the covered
|
|
||||||
work and works based on it.
|
|
||||||
|
|
||||||
A patent license is "discriminatory" if it does not include within
|
|
||||||
the scope of its coverage, prohibits the exercise of, or is
|
|
||||||
conditioned on the non-exercise of one or more of the rights that are
|
|
||||||
specifically granted under this License. You may not convey a covered
|
|
||||||
work if you are a party to an arrangement with a third party that is
|
|
||||||
in the business of distributing software, under which you make payment
|
|
||||||
to the third party based on the extent of your activity of conveying
|
|
||||||
the work, and under which the third party grants, to any of the
|
|
||||||
parties who would receive the covered work from you, a discriminatory
|
|
||||||
patent license (a) in connection with copies of the covered work
|
|
||||||
conveyed by you (or copies made from those copies), or (b) primarily
|
|
||||||
for and in connection with specific products or compilations that
|
|
||||||
contain the covered work, unless you entered into that arrangement,
|
|
||||||
or that patent license was granted, prior to 28 March 2007.
|
|
||||||
|
|
||||||
Nothing in this License shall be construed as excluding or limiting
|
|
||||||
any implied license or other defenses to infringement that may
|
|
||||||
otherwise be available to you under applicable patent law.
|
|
||||||
|
|
||||||
12. No Surrender of Others' Freedom.
|
|
||||||
|
|
||||||
If conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot convey a
|
|
||||||
covered work so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you may
|
|
||||||
not convey it at all. For example, if you agree to terms that obligate you
|
|
||||||
to collect a royalty for further conveying from those to whom you convey
|
|
||||||
the Program, the only way you could satisfy both those terms and this
|
|
||||||
License would be to refrain entirely from conveying the Program.
|
|
||||||
|
|
||||||
13. Use with the GNU Affero General Public License.
|
|
||||||
|
|
||||||
Notwithstanding any other provision of this License, you have
|
|
||||||
permission to link or combine any covered work with a work licensed
|
|
||||||
under version 3 of the GNU Affero General Public License into a single
|
|
||||||
combined work, and to convey the resulting work. The terms of this
|
|
||||||
License will continue to apply to the part which is the covered work,
|
|
||||||
but the special requirements of the GNU Affero General Public License,
|
|
||||||
section 13, concerning interaction through a network will apply to the
|
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
|
||||||
the GNU General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Program specifies that a certain numbered version of the GNU General
|
|
||||||
Public License "or any later version" applies to it, you have the
|
|
||||||
option of following the terms and conditions either of that numbered
|
|
||||||
version or of any later version published by the Free Software
|
|
||||||
Foundation. If the Program does not specify a version number of the
|
|
||||||
GNU General Public License, you may choose any version ever published
|
|
||||||
by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
|
||||||
versions of the GNU General Public License can be used, that proxy's
|
|
||||||
public statement of acceptance of a version permanently authorizes you
|
|
||||||
to choose that version for the Program.
|
|
||||||
|
|
||||||
Later license versions may give you additional or different
|
|
||||||
permissions. However, no additional obligations are imposed on any
|
|
||||||
author or copyright holder as a result of your choosing to follow a
|
|
||||||
later version.
|
|
||||||
|
|
||||||
15. Disclaimer of Warranty.
|
|
||||||
|
|
||||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
|
||||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
|
||||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
|
||||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
|
||||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
|
||||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. Limitation of Liability.
|
|
||||||
|
|
||||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
|
||||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
|
||||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
|
||||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
|
||||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
|
||||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
|
||||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
|
||||||
SUCH DAMAGES.
|
|
||||||
|
|
||||||
17. Interpretation of Sections 15 and 16.
|
|
||||||
|
|
||||||
If the disclaimer of warranty and limitation of liability provided
|
|
||||||
above cannot be given local legal effect according to their terms,
|
|
||||||
reviewing courts shall apply local law that most closely approximates
|
|
||||||
an absolute waiver of all civil liability in connection with the
|
|
||||||
Program, unless a warranty or assumption of liability accompanies a
|
|
||||||
copy of the Program in return for a fee.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Programs
|
|
||||||
|
|
||||||
If you develop a new program, and you want it to be of the greatest
|
|
||||||
possible use to the public, the best way to achieve this is to make it
|
|
||||||
free software which everyone can redistribute and change under these terms.
|
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest
|
|
||||||
to attach them to the start of each source file to most effectively
|
|
||||||
state the exclusion of warranty; and each file should have at least
|
|
||||||
the "copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
{one line to give the program's name and a brief idea of what it does.}
|
|
||||||
Copyright (C) {year} {name of author}
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
|
||||||
notice like this when it starts in an interactive mode:
|
|
||||||
|
|
||||||
{project} Copyright (C) {year} {fullname}
|
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
|
||||||
This is free software, and you are welcome to redistribute it
|
|
||||||
under certain conditions; type `show c' for details.
|
|
||||||
|
|
||||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
|
||||||
parts of the General Public License. Of course, your program's commands
|
|
||||||
might be different; for a GUI interface, you would use an "about box".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
|
||||||
<http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
|
||||||
may consider it more useful to permit linking proprietary applications with
|
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
|
||||||
Public License instead of this License. But first, please read
|
|
||||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
12
Makefile
Normal file
12
Makefile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
boltbrowser:
|
||||||
|
go build -o build/boltbrowser
|
||||||
|
|
||||||
|
run: ./build/boltbrowser
|
||||||
|
cd build && ./boltbrowser
|
||||||
|
|
||||||
|
install:
|
||||||
|
go install
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm build/boltbrowser
|
54
cmd/root.go
Normal file
54
cmd/root.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/boltbrowser/ui"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AppVersion = 3.0
|
||||||
|
)
|
||||||
|
|
||||||
|
// rootCmd represents the base command when called without any subcommands
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "boltbrowser",
|
||||||
|
Short: "A browser and editor for bolt databases",
|
||||||
|
Long: ``,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.New("No database given.")
|
||||||
|
}
|
||||||
|
viper.Set("dbs", args)
|
||||||
|
if err := ui.NewUi().Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
|
func Execute() {
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.Flags().BoolP("readonly", "ro", false, "Open database in read only mode")
|
||||||
|
viper.BindPFlag("readonly", rootCmd.Flags().Lookup("readonly"))
|
||||||
|
rootCmd.Flags().Bool("no-value", false, "Do not output values in the left pane")
|
||||||
|
viper.BindPFlag("no-value", rootCmd.Flags().Lookup("no-value"))
|
||||||
|
rootCmd.Flags().String("timeout", "1s", "Amount of time to wait for the DB to open, otherwise error. Given in 'duration' format (e.g. 1s for one second, 1m for one minute)")
|
||||||
|
viper.BindPFlag("timeout", rootCmd.Flags().Lookup("timeout"))
|
||||||
|
viper.Set("version", AppVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printUsage() {
|
||||||
|
|
||||||
|
}
|
37
go.mod
37
go.mod
@ -1,11 +1,34 @@
|
|||||||
module github.com/br0xen/boltbrowser
|
module git.bullercodeworks.com/brian/boltbrowser
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/boltdb/bolt v1.3.1
|
git.bullercodeworks.com/brian/wandle v1.0.1
|
||||||
github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e
|
git.bullercodeworks.com/brian/widdles v1.1.0
|
||||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
github.com/nsf/termbox-go v1.1.1
|
||||||
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e
|
github.com/spf13/cobra v1.4.0
|
||||||
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed // indirect
|
github.com/spf13/viper v1.11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/spf13/afero v1.8.2 // indirect
|
||||||
|
github.com/spf13/cast v1.4.1 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
|
go.etcd.io/bbolt v1.3.6 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
504
go.sum
504
go.sum
@ -1,10 +1,494 @@
|
|||||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e h1:PF4gYXcZfTbAoAk5DPZcvjmq8gyg4gpcmWdT8W+0X1c=
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
github.com/br0xen/termbox-util v0.0.0-20170904143325-de1d4c83380e/go.mod h1:x9wJlgOj74OFTOBwXOuO8pBguW37EgYNx51Dbjkfzo4=
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY=
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X1OdvBDUnaZaKKyQtkY=
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||||
|
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||||
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
|
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||||
|
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||||
|
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
git.bullercodeworks.com/brian/wandle v1.0.1 h1:qzqlTgWulIztyTtnP7SKKoO0RYSrDy4oymCYOhVcL34=
|
||||||
|
git.bullercodeworks.com/brian/wandle v1.0.1/go.mod h1:jyY6L/ubxvPPE6ykBca/b9clJIBUv1fhlqC8MJthKuA=
|
||||||
|
git.bullercodeworks.com/brian/widdles v1.1.0 h1:H1fCPdf3Vu8/6Ky8+TsPeb9wJ2U2p7ecmlcXERBPfF0=
|
||||||
|
git.bullercodeworks.com/brian/widdles v1.1.0/go.mod h1:gHNRaUPauffZXWouF2kVcHeZ/qKjRAGFK0u79wnAlOY=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||||
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
|
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||||
|
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
|
||||||
|
github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
|
||||||
|
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
||||||
|
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
|
||||||
|
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||||
|
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||||
|
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
|
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||||
|
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44=
|
||||||
|
github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
||||||
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||||
|
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||||
|
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||||
|
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
143
main.go
143
main.go
@ -1,144 +1,13 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||||
|
|
||||||
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"git.bullercodeworks.com/brian/boltbrowser/cmd"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ProgramName = "boltbrowser"
|
|
||||||
var VersionNum = 2.0
|
|
||||||
|
|
||||||
var databaseFiles []string
|
|
||||||
var db *bolt.DB
|
|
||||||
var memBolt *BoltDB
|
|
||||||
|
|
||||||
var currentFilename string
|
|
||||||
|
|
||||||
const DefaultDBOpenTimeout = time.Second
|
|
||||||
|
|
||||||
var AppArgs struct {
|
|
||||||
DBOpenTimeout time.Duration
|
|
||||||
ReadOnly bool
|
|
||||||
NoValue bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
AppArgs.DBOpenTimeout = DefaultDBOpenTimeout
|
|
||||||
AppArgs.ReadOnly = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseArgs() {
|
|
||||||
var err error
|
|
||||||
if len(os.Args) == 1 {
|
|
||||||
printUsage(nil)
|
|
||||||
}
|
|
||||||
parms := os.Args[1:]
|
|
||||||
for i := range parms {
|
|
||||||
// All 'option' arguments start with "-"
|
|
||||||
if !strings.HasPrefix(parms[i], "-") {
|
|
||||||
databaseFiles = append(databaseFiles, parms[i])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(parms[i], "=") {
|
|
||||||
// Key/Value pair Arguments
|
|
||||||
pts := strings.Split(parms[i], "=")
|
|
||||||
key, val := pts[0], pts[1]
|
|
||||||
switch key {
|
|
||||||
case "-timeout":
|
|
||||||
AppArgs.DBOpenTimeout, err = time.ParseDuration(val)
|
|
||||||
if err != nil {
|
|
||||||
// See if we can successfully parse by adding a 's'
|
|
||||||
AppArgs.DBOpenTimeout, err = time.ParseDuration(val + "s")
|
|
||||||
}
|
|
||||||
// If err is still not nil, print usage
|
|
||||||
if err != nil {
|
|
||||||
printUsage(err)
|
|
||||||
}
|
|
||||||
case "-readonly", "-ro":
|
|
||||||
if val == "true" {
|
|
||||||
AppArgs.ReadOnly = true
|
|
||||||
}
|
|
||||||
case "-no-value":
|
|
||||||
if val == "true" {
|
|
||||||
AppArgs.NoValue = true
|
|
||||||
}
|
|
||||||
case "-help":
|
|
||||||
printUsage(nil)
|
|
||||||
default:
|
|
||||||
printUsage(errors.New("Invalid option"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Single-word arguments
|
|
||||||
switch parms[i] {
|
|
||||||
case "-readonly", "-ro":
|
|
||||||
AppArgs.ReadOnly = true
|
|
||||||
case "-no-value":
|
|
||||||
AppArgs.NoValue = true
|
|
||||||
case "-help":
|
|
||||||
printUsage(nil)
|
|
||||||
default:
|
|
||||||
printUsage(errors.New("Invalid option"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printUsage(err error) {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] <filename(s)>\nOptions:\n", ProgramName)
|
|
||||||
fmt.Fprintf(os.Stderr, " -timeout=duration\n DB file open timeout (default 1s)\n")
|
|
||||||
fmt.Fprintf(os.Stderr, " -ro, -readonly \n Open the DB in read-only mode\n")
|
|
||||||
fmt.Fprintf(os.Stderr, " -no-value \n Do not display a value in left pane\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var err error
|
cmd.Execute()
|
||||||
|
|
||||||
parseArgs()
|
|
||||||
|
|
||||||
err = termbox.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer termbox.Close()
|
|
||||||
style := defaultStyle()
|
|
||||||
termbox.SetOutputMode(termbox.Output256)
|
|
||||||
|
|
||||||
for _, databaseFile := range databaseFiles {
|
|
||||||
currentFilename = databaseFile
|
|
||||||
db, err = bolt.Open(databaseFile, 0600, &bolt.Options{Timeout: AppArgs.DBOpenTimeout})
|
|
||||||
if err == bolt.ErrTimeout {
|
|
||||||
termbox.Close()
|
|
||||||
fmt.Printf("File %s is locked. Make sure it's not used by another app and try again\n", databaseFile)
|
|
||||||
os.Exit(1)
|
|
||||||
} else if err != nil {
|
|
||||||
if len(databaseFiles) > 1 {
|
|
||||||
mainLoop(nil, style)
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
termbox.Close()
|
|
||||||
fmt.Printf("Error reading file: %q\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// First things first, load the database into memory
|
|
||||||
memBolt.refreshDatabase()
|
|
||||||
if AppArgs.ReadOnly {
|
|
||||||
// If we're opening it in readonly mode, close it now
|
|
||||||
db.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kick off the UI loop
|
|
||||||
mainLoop(memBolt, style)
|
|
||||||
defer db.Close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
37
mainloop.go
37
mainloop.go
@ -1,37 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mainLoop(memBolt *BoltDB, style Style) {
|
|
||||||
screens := defaultScreensForData(memBolt)
|
|
||||||
displayScreen := screens[BrowserScreenIndex]
|
|
||||||
layoutAndDrawScreen(displayScreen, style)
|
|
||||||
for {
|
|
||||||
event := termbox.PollEvent()
|
|
||||||
if event.Type == termbox.EventKey {
|
|
||||||
if event.Key == termbox.KeyCtrlZ {
|
|
||||||
process, _ := os.FindProcess(os.Getpid())
|
|
||||||
termbox.Close()
|
|
||||||
process.Signal(syscall.SIGSTOP)
|
|
||||||
termbox.Init()
|
|
||||||
}
|
|
||||||
newScreenIndex := displayScreen.handleKeyEvent(event)
|
|
||||||
if newScreenIndex < len(screens) {
|
|
||||||
displayScreen = screens[newScreenIndex]
|
|
||||||
layoutAndDrawScreen(displayScreen, style)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if event.Type == termbox.EventResize {
|
|
||||||
layoutAndDrawScreen(displayScreen, style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
package main
|
|
||||||
|
|
||||||
// Windows doesn't support process backgrounding like *nix.
|
|
||||||
// So we have a separate loop for it.
|
|
||||||
|
|
||||||
import "github.com/nsf/termbox-go"
|
|
||||||
|
|
||||||
func mainLoop(memBolt *BoltDB, style Style) {
|
|
||||||
screens := defaultScreensForData(memBolt)
|
|
||||||
displayScreen := screens[BrowserScreenIndex]
|
|
||||||
layoutAndDrawScreen(displayScreen, style)
|
|
||||||
for {
|
|
||||||
event := termbox.PollEvent()
|
|
||||||
if event.Type == termbox.EventKey {
|
|
||||||
newScreenIndex := displayScreen.handleKeyEvent(event)
|
|
||||||
if newScreenIndex < len(screens) {
|
|
||||||
displayScreen = screens[newScreenIndex]
|
|
||||||
layoutAndDrawScreen(displayScreen, style)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if event.Type == termbox.EventResize {
|
|
||||||
layoutAndDrawScreen(displayScreen, style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -6,46 +6,55 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/boltdb/bolt"
|
"git.bullercodeworks.com/brian/boltbrowser/util"
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
BoltDB A Database, basically a collection of buckets
|
BoltDB A Database, basically a collection of buckets
|
||||||
*/
|
*/
|
||||||
type BoltDB struct {
|
type BoltDB struct {
|
||||||
|
db *bolt.DB
|
||||||
buckets []BoltBucket
|
buckets []BoltBucket
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func (bd *BoltDB) RefreshDatabase() {
|
||||||
BoltBucket is just a struct representation of a Bucket in the Bolt DB
|
// Reload the database from the file
|
||||||
*/
|
bd.buckets = []BoltBucket{}
|
||||||
type BoltBucket struct {
|
bd.db.View(func(tx *bolt.Tx) error {
|
||||||
name string
|
err := tx.ForEach(func(nm []byte, b *bolt.Bucket) error {
|
||||||
pairs []BoltPair
|
bb, err := bd.ReadBucket(b)
|
||||||
buckets []BoltBucket
|
if err == nil {
|
||||||
parent *BoltBucket
|
bb.name = string(nm)
|
||||||
expanded bool
|
bb.expanded = false
|
||||||
errorFlag bool
|
bd.buckets = append(bd.buckets, *bb)
|
||||||
isRoot bool
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// Check if there are key/values directly in the root
|
||||||
|
bb, err := bd.ReadBucket(tx.Cursor().Bucket())
|
||||||
|
if err == nil {
|
||||||
|
bb.isRoot = true
|
||||||
|
bb.expanded = true
|
||||||
|
bd.buckets = append(bd.buckets, *bb)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func (bd *BoltDB) GetGenericFromPath(path []string) (*BoltBucket, *BoltPair, error) {
|
||||||
BoltPair is just a struct representation of a Pair in the Bolt DB
|
|
||||||
*/
|
|
||||||
type BoltPair struct {
|
|
||||||
parent *BoltBucket
|
|
||||||
key string
|
|
||||||
val string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bd *BoltDB) getGenericFromPath(path []string) (*BoltBucket, *BoltPair, error) {
|
|
||||||
// Check if 'path' leads to a pair
|
// Check if 'path' leads to a pair
|
||||||
p, err := bd.getPairFromPath(path)
|
p, err := bd.GetPairFromPath(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil, p, nil
|
return nil, p, nil
|
||||||
}
|
}
|
||||||
// Nope, check if it leads to a bucket
|
// Nope, check if it leads to a bucket
|
||||||
b, err := bd.getBucketFromPath(path)
|
b, err := bd.GetBucketFromPath(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return b, nil, nil
|
return b, nil, nil
|
||||||
}
|
}
|
||||||
@ -53,19 +62,19 @@ func (bd *BoltDB) getGenericFromPath(path []string) (*BoltBucket, *BoltPair, err
|
|||||||
return nil, nil, errors.New("Invalid Path")
|
return nil, nil, errors.New("Invalid Path")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) getBucketFromPath(path []string) (*BoltBucket, error) {
|
func (bd *BoltDB) GetBucketFromPath(path []string) (*BoltBucket, error) {
|
||||||
if len(path) > 0 {
|
if len(path) > 0 {
|
||||||
// Find the BoltBucket with a path == path
|
// Find the BoltBucket with a path == path
|
||||||
var b *BoltBucket
|
var b *BoltBucket
|
||||||
var err error
|
var err error
|
||||||
// Find the root bucket
|
// Find the root bucket
|
||||||
b, err = memBolt.getBucket(path[0])
|
b, err = bd.GetBucket(path[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(path) > 1 {
|
if len(path) > 1 {
|
||||||
for p := 1; p < len(path); p++ {
|
for p := 1; p < len(path); p++ {
|
||||||
b, err = b.getBucket(path[p])
|
b, err = b.GetBucket(path[p])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -76,32 +85,32 @@ func (bd *BoltDB) getBucketFromPath(path []string) (*BoltBucket, error) {
|
|||||||
return nil, errors.New("Invalid Path")
|
return nil, errors.New("Invalid Path")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) getPairFromPath(path []string) (*BoltPair, error) {
|
func (bd *BoltDB) GetPairFromPath(path []string) (*BoltPair, error) {
|
||||||
if len(path) <= 0 {
|
if len(path) <= 0 {
|
||||||
return nil, errors.New("No Path")
|
return nil, errors.New("No Path")
|
||||||
}
|
}
|
||||||
b, err := bd.getBucketFromPath(path[:len(path)-1])
|
b, err := bd.GetBucketFromPath(path[:len(path)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Found the bucket, pull out the pair
|
// Found the bucket, pull out the pair
|
||||||
p, err := b.getPair(path[len(path)-1])
|
p, err := b.GetPair(path[len(path)-1])
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) getVisibleItemCount(path []string) (int, error) {
|
func (bd *BoltDB) GetVisibleItemCount(path []string) (int, error) {
|
||||||
vis := 0
|
vis := 0
|
||||||
var retErr error
|
var retErr error
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
for i := range bd.buckets {
|
for i := range bd.buckets {
|
||||||
n, err := bd.getVisibleItemCount(bd.buckets[i].GetPath())
|
n, err := bd.GetVisibleItemCount(bd.buckets[i].GetPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
vis += n
|
vis += n
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b, err := bd.getBucketFromPath(path)
|
b, err := bd.GetBucketFromPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -113,7 +122,7 @@ func (bd *BoltDB) getVisibleItemCount(path []string) (int, error) {
|
|||||||
vis += len(b.pairs)
|
vis += len(b.pairs)
|
||||||
// * recurse for buckets
|
// * recurse for buckets
|
||||||
for i := range b.buckets {
|
for i := range b.buckets {
|
||||||
n, err := bd.getVisibleItemCount(b.buckets[i].GetPath())
|
n, err := bd.GetVisibleItemCount(b.buckets[i].GetPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -124,12 +133,12 @@ func (bd *BoltDB) getVisibleItemCount(path []string) (int, error) {
|
|||||||
return vis, retErr
|
return vis, retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) buildVisiblePathSlice(filter string) ([][]string, error) {
|
func (bd *BoltDB) BuildVisiblePathSlice(filter string) ([][]string, error) {
|
||||||
var retSlice [][]string
|
var retSlice [][]string
|
||||||
var retErr error
|
var retErr error
|
||||||
// The root path, recurse for root buckets
|
// The root path, recurse for root buckets
|
||||||
for i := range bd.buckets {
|
for i := range bd.buckets {
|
||||||
bktS, bktErr := bd.buckets[i].buildVisiblePathSlice([]string{}, filter)
|
bktS, bktErr := bd.buckets[i].BuildVisiblePathSlice([]string{}, filter)
|
||||||
if bktErr == nil {
|
if bktErr == nil {
|
||||||
retSlice = append(retSlice, bktS...)
|
retSlice = append(retSlice, bktS...)
|
||||||
} else {
|
} else {
|
||||||
@ -140,8 +149,8 @@ func (bd *BoltDB) buildVisiblePathSlice(filter string) ([][]string, error) {
|
|||||||
return retSlice, retErr
|
return retSlice, retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) isVisiblePath(path []string, filter string) bool {
|
func (bd *BoltDB) IsVisiblePath(path []string, filter string) bool {
|
||||||
visPaths, err := bd.buildVisiblePathSlice(filter)
|
visPaths, err := bd.BuildVisiblePathSlice(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -162,8 +171,8 @@ func (bd *BoltDB) isVisiblePath(path []string, filter string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (bd *BoltDB) getPrevVisiblePath(path []string, filter string) []string {
|
func (bd *BoltDB) GetPrevVisiblePath(path []string, filter string) []string {
|
||||||
visPaths, err := bd.buildVisiblePathSlice(filter)
|
visPaths, err := bd.BuildVisiblePathSlice(filter)
|
||||||
if path == nil {
|
if path == nil {
|
||||||
if len(visPaths) > 0 {
|
if len(visPaths) > 0 {
|
||||||
return visPaths[len(visPaths)-1]
|
return visPaths[len(visPaths)-1]
|
||||||
@ -186,8 +195,8 @@ func (bd *BoltDB) getPrevVisiblePath(path []string, filter string) []string {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (bd *BoltDB) getNextVisiblePath(path []string, filter string) []string {
|
func (bd *BoltDB) GetNextVisiblePath(path []string, filter string) []string {
|
||||||
visPaths, err := bd.buildVisiblePathSlice(filter)
|
visPaths, err := bd.BuildVisiblePathSlice(filter)
|
||||||
if path == nil {
|
if path == nil {
|
||||||
if len(visPaths) > 0 {
|
if len(visPaths) > 0 {
|
||||||
return visPaths[0]
|
return visPaths[0]
|
||||||
@ -211,34 +220,34 @@ func (bd *BoltDB) getNextVisiblePath(path []string, filter string) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) toggleOpenBucket(path []string) error {
|
func (bd *BoltDB) ToggleOpenBucket(path []string) error {
|
||||||
// Find the BoltBucket with a path == path
|
// Find the BoltBucket with a path == path
|
||||||
b, err := bd.getBucketFromPath(path)
|
b, err := bd.GetBucketFromPath(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
b.expanded = !b.expanded
|
b.expanded = !b.expanded
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) closeBucket(path []string) error {
|
func (bd *BoltDB) CloseBucket(path []string) error {
|
||||||
// Find the BoltBucket with a path == path
|
// Find the BoltBucket with a path == path
|
||||||
b, err := bd.getBucketFromPath(path)
|
b, err := bd.GetBucketFromPath(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
b.expanded = false
|
b.expanded = false
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) openBucket(path []string) error {
|
func (bd *BoltDB) OpenBucket(path []string) error {
|
||||||
// Find the BoltBucket with a path == path
|
// Find the BoltBucket with a path == path
|
||||||
b, err := bd.getBucketFromPath(path)
|
b, err := bd.GetBucketFromPath(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
b.expanded = true
|
b.expanded = true
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) getBucket(k string) (*BoltBucket, error) {
|
func (bd *BoltDB) GetBucket(k string) (*BoltBucket, error) {
|
||||||
for i := range bd.buckets {
|
for i := range bd.buckets {
|
||||||
if bd.buckets[i].name == k {
|
if bd.buckets[i].name == k {
|
||||||
return &bd.buckets[i], nil
|
return &bd.buckets[i], nil
|
||||||
@ -247,166 +256,26 @@ func (bd *BoltDB) getBucket(k string) (*BoltBucket, error) {
|
|||||||
return nil, errors.New("Bucket Not Found")
|
return nil, errors.New("Bucket Not Found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) openAllBuckets() {
|
func (bd *BoltDB) OpenAllBuckets() {
|
||||||
for i := range bd.buckets {
|
for i := range bd.buckets {
|
||||||
bd.buckets[i].openAllBuckets()
|
bd.buckets[i].OpenAllBuckets()
|
||||||
bd.buckets[i].expanded = true
|
bd.buckets[i].expanded = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) syncOpenBuckets(shadow *BoltDB) {
|
func (bd *BoltDB) SyncOpenBuckets(shadow *BoltDB) {
|
||||||
// First test this bucket
|
// First test this bucket
|
||||||
for i := range bd.buckets {
|
for i := range bd.buckets {
|
||||||
for j := range shadow.buckets {
|
for j := range shadow.buckets {
|
||||||
if bd.buckets[i].name == shadow.buckets[j].name {
|
if bd.buckets[i].name == shadow.buckets[j].name {
|
||||||
bd.buckets[i].syncOpenBuckets(&shadow.buckets[j])
|
bd.buckets[i].SyncOpenBuckets(&shadow.buckets[j])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) refreshDatabase() *BoltDB {
|
func (bd *BoltDB) DeleteKey(path []string) error {
|
||||||
// Reload the database into memBolt
|
err := bd.db.Update(func(tx *bolt.Tx) error {
|
||||||
memBolt = new(BoltDB)
|
|
||||||
db.View(func(tx *bolt.Tx) error {
|
|
||||||
err := tx.ForEach(func(nm []byte, b *bolt.Bucket) error {
|
|
||||||
bb, err := readBucket(b)
|
|
||||||
if err == nil {
|
|
||||||
bb.name = string(nm)
|
|
||||||
bb.expanded = false
|
|
||||||
memBolt.buckets = append(memBolt.buckets, *bb)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
memBolt = new(BoltDB)
|
|
||||||
// Check if there are key/values directly in the root
|
|
||||||
bb, err := readBucket(tx.Cursor().Bucket())
|
|
||||||
if err == nil {
|
|
||||||
bb.isRoot = true
|
|
||||||
bb.expanded = true
|
|
||||||
memBolt.buckets = append(memBolt.buckets, *bb)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return memBolt
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
GetPath returns the database path leading to this BoltBucket
|
|
||||||
*/
|
|
||||||
func (b *BoltBucket) GetPath() []string {
|
|
||||||
if b.parent != nil {
|
|
||||||
return append(b.parent.GetPath(), b.name)
|
|
||||||
}
|
|
||||||
return []string{b.name}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
buildVisiblePathSlice builds a slice of string slices containing all visible paths in this bucket
|
|
||||||
The passed prefix is the path leading to the current bucket
|
|
||||||
*/
|
|
||||||
func (b *BoltBucket) buildVisiblePathSlice(prefix []string, filter string) ([][]string, error) {
|
|
||||||
var retSlice [][]string
|
|
||||||
var retErr error
|
|
||||||
retSlice = append(retSlice, append(prefix, b.name))
|
|
||||||
if b.expanded {
|
|
||||||
// Add subbuckets
|
|
||||||
for i := range b.buckets {
|
|
||||||
bktS, bktErr := b.buckets[i].buildVisiblePathSlice(append(prefix, b.name), filter)
|
|
||||||
if bktErr != nil {
|
|
||||||
return retSlice, bktErr
|
|
||||||
}
|
|
||||||
retSlice = append(retSlice, bktS...)
|
|
||||||
}
|
|
||||||
// Add pairs
|
|
||||||
for i := range b.pairs {
|
|
||||||
if filter != "" && !strings.Contains(b.pairs[i].key, filter) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
retSlice = append(retSlice, append(append(prefix, b.name), b.pairs[i].key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retSlice, retErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoltBucket) syncOpenBuckets(shadow *BoltBucket) {
|
|
||||||
// First test this bucket
|
|
||||||
b.expanded = shadow.expanded
|
|
||||||
for i := range b.buckets {
|
|
||||||
for j := range shadow.buckets {
|
|
||||||
if b.buckets[i].name == shadow.buckets[j].name {
|
|
||||||
b.buckets[i].syncOpenBuckets(&shadow.buckets[j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoltBucket) openAllBuckets() {
|
|
||||||
for i := range b.buckets {
|
|
||||||
b.buckets[i].openAllBuckets()
|
|
||||||
b.buckets[i].expanded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoltBucket) getBucket(k string) (*BoltBucket, error) {
|
|
||||||
for i := range b.buckets {
|
|
||||||
if b.buckets[i].name == k {
|
|
||||||
return &b.buckets[i], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Bucket Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoltBucket) getPair(k string) (*BoltPair, error) {
|
|
||||||
for i := range b.pairs {
|
|
||||||
if b.pairs[i].key == k {
|
|
||||||
return &b.pairs[i], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Pair Not Found")
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
GetPath Returns the path of the BoltPair
|
|
||||||
*/
|
|
||||||
func (p *BoltPair) GetPath() []string {
|
|
||||||
return append(p.parent.GetPath(), p.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is a go-between function (between the boltbrowser structs
|
|
||||||
* above, and the bolt convenience functions below)
|
|
||||||
* for taking a boltbrowser bucket and recursively adding it
|
|
||||||
* and all of it's children into the database.
|
|
||||||
* Mainly used for moving a bucket from one path to another
|
|
||||||
* as in the 'renameBucket' function below.
|
|
||||||
*/
|
|
||||||
func addBucketFromBoltBucket(path []string, bb *BoltBucket) error {
|
|
||||||
if err := insertBucket(path, bb.name); err == nil {
|
|
||||||
bucketPath := append(path, bb.name)
|
|
||||||
for i := range bb.pairs {
|
|
||||||
if err = insertPair(bucketPath, bb.pairs[i].key, bb.pairs[i].val); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := range bb.buckets {
|
|
||||||
if err = addBucketFromBoltBucket(bucketPath, &bb.buckets[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteKey(path []string) error {
|
|
||||||
if AppArgs.ReadOnly {
|
|
||||||
return errors.New("DB is in Read-Only Mode")
|
|
||||||
}
|
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
|
||||||
// len(b.path)-1 is the key we need to delete,
|
// len(b.path)-1 is the key we need to delete,
|
||||||
// the rest are buckets leading to that key
|
// the rest are buckets leading to that key
|
||||||
if len(path) == 1 {
|
if len(path) == 1 {
|
||||||
@ -423,7 +292,7 @@ func deleteKey(path []string) error {
|
|||||||
for i := range path[1 : len(path)-1] {
|
for i := range path[1 : len(path)-1] {
|
||||||
b = b.Bucket([]byte(path[i+1]))
|
b = b.Bucket([]byte(path[i+1]))
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return errors.New("deleteKey: Invalid Path")
|
return errors.New("DeleteKey: Invalid Path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -437,19 +306,19 @@ func deleteKey(path []string) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return errors.New("deleteKey: Invalid Path")
|
return errors.New("DeleteKey: Invalid Path")
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func readBucket(b *bolt.Bucket) (*BoltBucket, error) {
|
func (bd *BoltDB) ReadBucket(b *bolt.Bucket) (*BoltBucket, error) {
|
||||||
bb := new(BoltBucket)
|
bb := new(BoltBucket)
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return nil, errors.New("No bucket passed")
|
return nil, errors.New("No bucket passed")
|
||||||
}
|
}
|
||||||
b.ForEach(func(k, v []byte) error {
|
b.ForEach(func(k, v []byte) error {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
tb, err := readBucket(b.Bucket(k))
|
tb, err := bd.ReadBucket(b.Bucket(k))
|
||||||
tb.parent = bb
|
tb.parent = bb
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tb.name = string(k)
|
tb.name = string(k)
|
||||||
@ -465,13 +334,13 @@ func readBucket(b *bolt.Bucket) (*BoltBucket, error) {
|
|||||||
return bb, nil
|
return bb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func renameBucket(path []string, name string) error {
|
func (bd *BoltDB) RenameBucket(path []string, name string) error {
|
||||||
if name == path[len(path)-1] {
|
if name == path[len(path)-1] {
|
||||||
// No change requested
|
// No change requested
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var bb *BoltBucket // For caching the current bucket
|
var bb *BoltBucket // For caching the current bucket
|
||||||
err := db.View(func(tx *bolt.Tx) error {
|
err := bd.db.View(func(tx *bolt.Tx) error {
|
||||||
// len(b.path)-1 is the key we need to delete,
|
// len(b.path)-1 is the key we need to delete,
|
||||||
// the rest are buckets leading to that key
|
// the rest are buckets leading to that key
|
||||||
b := tx.Bucket([]byte(path[0]))
|
b := tx.Bucket([]byte(path[0]))
|
||||||
@ -484,18 +353,18 @@ func renameBucket(path []string, name string) error {
|
|||||||
for i := range path[1:] {
|
for i := range path[1:] {
|
||||||
b = b.Bucket([]byte(path[i+1]))
|
b = b.Bucket([]byte(path[i+1]))
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return errors.New("renameBucket: Invalid Path")
|
return errors.New("RenameBucket: Invalid Path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
// Ok, cache b
|
// Ok, cache b
|
||||||
bb, err = readBucket(b)
|
bb, err = bd.ReadBucket(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return errors.New("renameBucket: Invalid Bucket")
|
return errors.New("RenameBucket: Invalid Bucket")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -503,11 +372,11 @@ func renameBucket(path []string, name string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if bb == nil {
|
if bb == nil {
|
||||||
return errors.New("renameBucket: Couldn't find Bucket")
|
return errors.New("RenameBucket: Couldn't find Bucket")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok, we have the bucket cached, now delete the current instance
|
// Ok, we have the bucket cached, now delete the current instance
|
||||||
if err = deleteKey(path); err != nil {
|
if err = bd.DeleteKey(path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Rechristen our cached bucket
|
// Rechristen our cached bucket
|
||||||
@ -515,17 +384,14 @@ func renameBucket(path []string, name string) error {
|
|||||||
// And re-add it
|
// And re-add it
|
||||||
|
|
||||||
parentPath := path[:len(path)-1]
|
parentPath := path[:len(path)-1]
|
||||||
if err = addBucketFromBoltBucket(parentPath, bb); err != nil {
|
if err = bd.AddBucketFromBoltBucket(parentPath, bb); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePairKey(path []string, k string) error {
|
func (bd *BoltDB) UpdatePairKey(path []string, k string) error {
|
||||||
if AppArgs.ReadOnly {
|
err := bd.db.Update(func(tx *bolt.Tx) error {
|
||||||
return errors.New("DB is in Read-Only Mode")
|
|
||||||
}
|
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
|
||||||
// len(b.path)-1 is the key for the pair we're updating,
|
// len(b.path)-1 is the key for the pair we're updating,
|
||||||
// the rest are buckets leading to that key
|
// the rest are buckets leading to that key
|
||||||
b := tx.Bucket([]byte(path[0]))
|
b := tx.Bucket([]byte(path[0]))
|
||||||
@ -538,7 +404,7 @@ func updatePairKey(path []string, k string) error {
|
|||||||
for i := range path[1 : len(path)-1] {
|
for i := range path[1 : len(path)-1] {
|
||||||
b = b.Bucket([]byte(path[i+1]))
|
b = b.Bucket([]byte(path[i+1]))
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return errors.New("updatePairValue: Invalid Path")
|
return errors.New("UpdatePairKey: Invalid Path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -552,16 +418,13 @@ func updatePairKey(path []string, k string) error {
|
|||||||
// Now update the last key in the path
|
// Now update the last key in the path
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return errors.New("updatePairValue: Invalid Path")
|
return errors.New("UpdatePairKey: Invalid Path")
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePairValue(path []string, v string) error {
|
func (bd *BoltDB) UpdatePairValue(path []string, v string) error {
|
||||||
if AppArgs.ReadOnly {
|
err := bd.db.Update(func(tx *bolt.Tx) error {
|
||||||
return errors.New("DB is in Read-Only Mode")
|
|
||||||
}
|
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
|
||||||
// len(b.GetPath())-1 is the key for the pair we're updating,
|
// len(b.GetPath())-1 is the key for the pair we're updating,
|
||||||
// the rest are buckets leading to that key
|
// the rest are buckets leading to that key
|
||||||
b := tx.Bucket([]byte(path[0]))
|
b := tx.Bucket([]byte(path[0]))
|
||||||
@ -574,7 +437,7 @@ func updatePairValue(path []string, v string) error {
|
|||||||
for i := range path[1 : len(path)-1] {
|
for i := range path[1 : len(path)-1] {
|
||||||
b = b.Bucket([]byte(path[i+1]))
|
b = b.Bucket([]byte(path[i+1]))
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return errors.New("updatePairValue: Invalid Path")
|
return errors.New("UpdatePairValue: Invalid Path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -582,22 +445,19 @@ func updatePairValue(path []string, v string) error {
|
|||||||
err := b.Put([]byte(path[len(path)-1]), []byte(v))
|
err := b.Put([]byte(path[len(path)-1]), []byte(v))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return errors.New("updatePairValue: Invalid Path")
|
return errors.New("UpdatePairValue: Invalid Path")
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertBucket(path []string, n string) error {
|
func (bd *BoltDB) InsertBucket(path []string, n string) error {
|
||||||
if AppArgs.ReadOnly {
|
|
||||||
return errors.New("DB is in Read-Only Mode")
|
|
||||||
}
|
|
||||||
// Inserts a new bucket named 'n' at 'path'
|
// Inserts a new bucket named 'n' at 'path'
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
err := bd.db.Update(func(tx *bolt.Tx) error {
|
||||||
if len(path) == 0 || path[0] == "" {
|
if len(path) == 0 || path[0] == "" {
|
||||||
// insert at root
|
// insert at root
|
||||||
_, err := tx.CreateBucket([]byte(n))
|
_, err := tx.CreateBucket([]byte(n))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("insertBucket: %s", err)
|
return fmt.Errorf("InsertBucket: %s", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rootBucket, path := path[0], path[1:]
|
rootBucket, path := path[0], path[1:]
|
||||||
@ -611,7 +471,7 @@ func insertBucket(path []string, n string) error {
|
|||||||
// Not a bucket, if we're out of path, just move on
|
// Not a bucket, if we're out of path, just move on
|
||||||
if len(path) != 0 {
|
if len(path) != 0 {
|
||||||
// Out of path, error
|
// Out of path, error
|
||||||
return errors.New("insertBucket: Invalid Path 1")
|
return errors.New("InsertBucket: Invalid Path 1")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b = nB
|
b = nB
|
||||||
@ -627,15 +487,12 @@ func insertBucket(path []string, n string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertPair(path []string, k string, v string) error {
|
func (bd *BoltDB) InsertPair(path []string, k string, v string) error {
|
||||||
if AppArgs.ReadOnly {
|
|
||||||
return errors.New("DB is in Read-Only Mode")
|
|
||||||
}
|
|
||||||
// Insert a new pair k => v at path
|
// Insert a new pair k => v at path
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
err := bd.db.Update(func(tx *bolt.Tx) error {
|
||||||
if len(path) == 0 {
|
if len(path) == 0 {
|
||||||
// We cannot insert a pair at root
|
// We cannot insert a pair at root
|
||||||
return errors.New("insertPair: Cannot insert pair at root")
|
return errors.New("InsertPair: Cannot insert pair at root")
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
b := tx.Bucket([]byte(path[0]))
|
b := tx.Bucket([]byte(path[0]))
|
||||||
@ -648,13 +505,13 @@ func insertPair(path []string, k string, v string) error {
|
|||||||
for i := 1; i < len(path); i++ {
|
for i := 1; i < len(path); i++ {
|
||||||
b = b.Bucket([]byte(path[i]))
|
b = b.Bucket([]byte(path[i]))
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return fmt.Errorf("insertPair: %s", err)
|
return fmt.Errorf("InsertPair: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := b.Put([]byte(k), []byte(v))
|
err := b.Put([]byte(k), []byte(v))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("insertPair: %s", err)
|
return fmt.Errorf("InsertPair: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -662,8 +519,8 @@ func insertPair(path []string, k string, v string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportValue(path []string, fName string) error {
|
func (bd *BoltDB) exportValue(path []string, fName string) error {
|
||||||
return db.View(func(tx *bolt.Tx) error {
|
return bd.db.View(func(tx *bolt.Tx) error {
|
||||||
// len(b.path)-1 is the key whose value we want to export
|
// len(b.path)-1 is the key whose value we want to export
|
||||||
// the rest are buckets leading to that key
|
// the rest are buckets leading to that key
|
||||||
b := tx.Bucket([]byte(path[0]))
|
b := tx.Bucket([]byte(path[0]))
|
||||||
@ -676,20 +533,20 @@ func exportValue(path []string, fName string) error {
|
|||||||
for i := range path[1 : len(path)-1] {
|
for i := range path[1 : len(path)-1] {
|
||||||
b = b.Bucket([]byte(path[i+1]))
|
b = b.Bucket([]byte(path[i+1]))
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return errors.New("exportValue: Invalid Path: " + strings.Join(path, "/"))
|
return errors.New("ExportValue: Invalid Path: " + strings.Join(path, "/"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bk := []byte(path[len(path)-1])
|
bk := []byte(path[len(path)-1])
|
||||||
v := b.Get(bk)
|
v := b.Get(bk)
|
||||||
return writeToFile(fName, string(v)+"\n", os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
return util.WriteToFile(fName, string(v)+"\n", os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
||||||
}
|
}
|
||||||
return errors.New("exportValue: Invalid Bucket")
|
return errors.New("ExportValue: Invalid Bucket")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportJSON(path []string, fName string) error {
|
func (bd *BoltDB) ExportJSON(path []string, fName string) error {
|
||||||
return db.View(func(tx *bolt.Tx) error {
|
return bd.db.View(func(tx *bolt.Tx) error {
|
||||||
// len(b.path)-1 is the key whose value we want to export
|
// len(b.path)-1 is the key whose value we want to export
|
||||||
// the rest are buckets leading to that key
|
// the rest are buckets leading to that key
|
||||||
b := tx.Bucket([]byte(path[0]))
|
b := tx.Bucket([]byte(path[0]))
|
||||||
@ -702,57 +559,36 @@ func exportJSON(path []string, fName string) error {
|
|||||||
for i := range path[1 : len(path)-1] {
|
for i := range path[1 : len(path)-1] {
|
||||||
b = b.Bucket([]byte(path[i+1]))
|
b = b.Bucket([]byte(path[i+1]))
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return errors.New("exportValue: Invalid Path: " + strings.Join(path, "/"))
|
return errors.New("ExportJSON: Invalid Path: " + strings.Join(path, "/"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bk := []byte(path[len(path)-1])
|
bk := []byte(path[len(path)-1])
|
||||||
if v := b.Get(bk); v != nil {
|
if v := b.Get(bk); v != nil {
|
||||||
return writeToFile(fName, "{\""+string(bk)+"\":\""+string(v)+"\"}", os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
return util.WriteToFile(fName, "{\""+string(bk)+"\":\""+string(v)+"\"}", os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
||||||
}
|
}
|
||||||
if b.Bucket(bk) != nil {
|
if b.Bucket(bk) != nil {
|
||||||
return writeToFile(fName, genJSONString(b.Bucket(bk)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
return util.WriteToFile(fName, util.ToJsonString(b.Bucket(bk)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
||||||
}
|
}
|
||||||
return writeToFile(fName, genJSONString(b), os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
return util.WriteToFile(fName, util.ToJsonString(b), os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
||||||
}
|
}
|
||||||
return errors.New("exportValue: Invalid Bucket")
|
return errors.New("ExportJSON: Invalid Bucket")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func genJSONString(b *bolt.Bucket) string {
|
func (bd *BoltDB) AddBucketFromBoltBucket(path []string, bb *BoltBucket) error {
|
||||||
ret := "{"
|
if err := bd.InsertBucket(path, bb.name); err == nil {
|
||||||
b.ForEach(func(k, v []byte) error {
|
bucketPath := append(path, bb.name)
|
||||||
ret = fmt.Sprintf("%s\"%s\":", ret, string(k))
|
for i := range bb.pairs {
|
||||||
if v == nil {
|
if err = bd.InsertPair(bucketPath, bb.pairs[i].key, bb.pairs[i].val); err != nil {
|
||||||
ret = fmt.Sprintf("%s%s,", ret, genJSONString(b.Bucket(k)))
|
|
||||||
} else {
|
|
||||||
ret = fmt.Sprintf("%s\"%s\",", ret, string(v))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
ret = fmt.Sprintf("%s}", ret[:len(ret)-1])
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func logToFile(s string) error {
|
|
||||||
return writeToFile("bolt-log", s+"\n", os.O_RDWR|os.O_APPEND)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeToFile(fn, s string, mode int) error {
|
|
||||||
var f *os.File
|
|
||||||
var err error
|
|
||||||
if f == nil {
|
|
||||||
f, err = os.OpenFile(fn, mode, 0660)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = f.WriteString(s); err != nil {
|
}
|
||||||
|
for i := range bb.buckets {
|
||||||
|
if err = bd.AddBucketFromBoltBucket(bucketPath, &bb.buckets[i]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = f.Sync(); err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
94
models/bucket.go
Normal file
94
models/bucket.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
BoltBucket is just a struct representation of a Bucket in the Bolt DB
|
||||||
|
*/
|
||||||
|
type BoltBucket struct {
|
||||||
|
name string
|
||||||
|
pairs []BoltPair
|
||||||
|
buckets []BoltBucket
|
||||||
|
parent *BoltBucket
|
||||||
|
expanded bool
|
||||||
|
errorFlag bool
|
||||||
|
isRoot bool
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetPath returns the database path leading to this BoltBucket
|
||||||
|
*/
|
||||||
|
func (b *BoltBucket) GetPath() []string {
|
||||||
|
if b.parent != nil {
|
||||||
|
return append(b.parent.GetPath(), b.name)
|
||||||
|
}
|
||||||
|
return []string{b.name}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
buildVisiblePathSlice builds a slice of string slices containing all visible paths in this bucket
|
||||||
|
The passed prefix is the path leading to the current bucket
|
||||||
|
*/
|
||||||
|
func (b *BoltBucket) BuildVisiblePathSlice(prefix []string, filter string) ([][]string, error) {
|
||||||
|
var retSlice [][]string
|
||||||
|
var retErr error
|
||||||
|
retSlice = append(retSlice, append(prefix, b.name))
|
||||||
|
if b.expanded {
|
||||||
|
// Add subbuckets
|
||||||
|
for i := range b.buckets {
|
||||||
|
bktS, bktErr := b.buckets[i].BuildVisiblePathSlice(append(prefix, b.name), filter)
|
||||||
|
if bktErr != nil {
|
||||||
|
return retSlice, bktErr
|
||||||
|
}
|
||||||
|
retSlice = append(retSlice, bktS...)
|
||||||
|
}
|
||||||
|
// Add pairs
|
||||||
|
for i := range b.pairs {
|
||||||
|
if filter != "" && !strings.Contains(b.pairs[i].key, filter) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
retSlice = append(retSlice, append(append(prefix, b.name), b.pairs[i].key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retSlice, retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltBucket) SyncOpenBuckets(shadow *BoltBucket) {
|
||||||
|
// First test this bucket
|
||||||
|
b.expanded = shadow.expanded
|
||||||
|
for i := range b.buckets {
|
||||||
|
for j := range shadow.buckets {
|
||||||
|
if b.buckets[i].name == shadow.buckets[j].name {
|
||||||
|
b.buckets[i].SyncOpenBuckets(&shadow.buckets[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltBucket) OpenAllBuckets() {
|
||||||
|
for i := range b.buckets {
|
||||||
|
b.buckets[i].OpenAllBuckets()
|
||||||
|
b.buckets[i].expanded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltBucket) GetBucket(k string) (*BoltBucket, error) {
|
||||||
|
for i := range b.buckets {
|
||||||
|
if b.buckets[i].name == k {
|
||||||
|
return &b.buckets[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("Bucket Not Found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltBucket) GetPair(k string) (*BoltPair, error) {
|
||||||
|
for i := range b.pairs {
|
||||||
|
if b.pairs[i].key == k {
|
||||||
|
return &b.pairs[i], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("Pair Not Found")
|
||||||
|
}
|
17
models/pair.go
Normal file
17
models/pair.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
/*
|
||||||
|
BoltPair is just a struct representation of a Pair in the Bolt DB
|
||||||
|
*/
|
||||||
|
type BoltPair struct {
|
||||||
|
parent *BoltBucket
|
||||||
|
key string
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetPath Returns the path of the BoltPair
|
||||||
|
*/
|
||||||
|
func (p *BoltPair) GetPath() []string {
|
||||||
|
return append(p.parent.GetPath(), p.key)
|
||||||
|
}
|
47
screen.go
47
screen.go
@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "github.com/nsf/termbox-go"
|
|
||||||
|
|
||||||
// Screen is a basic structure for all of the applications screens
|
|
||||||
type Screen interface {
|
|
||||||
handleKeyEvent(event termbox.Event) int
|
|
||||||
performLayout()
|
|
||||||
drawScreen(style Style)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// BrowserScreenIndex is the index
|
|
||||||
BrowserScreenIndex = iota
|
|
||||||
// AboutScreenIndex The idx number for the 'About' Screen
|
|
||||||
AboutScreenIndex
|
|
||||||
// ExitScreenIndex The idx number for Exiting
|
|
||||||
ExitScreenIndex
|
|
||||||
)
|
|
||||||
|
|
||||||
func defaultScreensForData(db *BoltDB) []Screen {
|
|
||||||
browserScreen := BrowserScreen{db: db, rightViewPort: ViewPort{}, leftViewPort: ViewPort{}}
|
|
||||||
aboutScreen := AboutScreen(0)
|
|
||||||
screens := [...]Screen{
|
|
||||||
&browserScreen,
|
|
||||||
&aboutScreen,
|
|
||||||
}
|
|
||||||
|
|
||||||
return screens[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawBackground(bg termbox.Attribute) {
|
|
||||||
termbox.Clear(0, bg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func layoutAndDrawScreen(screen Screen, style Style) {
|
|
||||||
screen.performLayout()
|
|
||||||
drawBackground(style.defaultBg)
|
|
||||||
screen.drawScreen(style)
|
|
||||||
termbox.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Line struct {
|
|
||||||
Text string
|
|
||||||
Fg termbox.Attribute
|
|
||||||
Bg termbox.Attribute
|
|
||||||
}
|
|
@ -1,967 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
termboxUtil "github.com/br0xen/termbox-util"
|
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
ViewPort helps keep track of what's being displayed on the screen
|
|
||||||
*/
|
|
||||||
type ViewPort struct {
|
|
||||||
bytesPerRow int
|
|
||||||
numberOfRows int
|
|
||||||
firstRow int
|
|
||||||
scrollRow int
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
BrowserScreen holds all that's going on :D
|
|
||||||
*/
|
|
||||||
type BrowserScreen struct {
|
|
||||||
db *BoltDB
|
|
||||||
leftViewPort ViewPort
|
|
||||||
rightViewPort ViewPort
|
|
||||||
queuedCommand string
|
|
||||||
currentPath []string
|
|
||||||
currentType int
|
|
||||||
message string
|
|
||||||
filter string
|
|
||||||
mode BrowserMode
|
|
||||||
inputModal *termboxUtil.InputModal
|
|
||||||
confirmModal *termboxUtil.ConfirmModal
|
|
||||||
messageTimeout time.Duration
|
|
||||||
messageTime time.Time
|
|
||||||
|
|
||||||
leftPaneBuffer []Line
|
|
||||||
rightPaneBuffer []Line
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
BrowserMode is just for designating the mode that we're in
|
|
||||||
*/
|
|
||||||
type BrowserMode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
modeBrowse = 16 // 0000 0001 0000
|
|
||||||
modeChange = 32 // 0000 0010 0000
|
|
||||||
modeChangeKey = 33 // 0000 0010 0001
|
|
||||||
modeChangeVal = 34 // 0000 0010 0010
|
|
||||||
modeFilter = 35 // 0100 0010 0011
|
|
||||||
modeInsert = 64 // 0000 0100 0000
|
|
||||||
modeInsertBucket = 65 // 0000 0100 0001
|
|
||||||
modeInsertPair = 68 // 0000 0100 0100
|
|
||||||
modeInsertPairKey = 69 // 0000 0100 0101
|
|
||||||
modeInsertPairVal = 70 // 0000 0100 0110
|
|
||||||
modeDelete = 256 // 0001 0000 0000
|
|
||||||
modeModToParent = 8 // 0000 0000 1000
|
|
||||||
modeExport = 512 // 0010 0000 0000
|
|
||||||
modeExportValue = 513 // 0010 0000 0001
|
|
||||||
modeExportJSON = 514 // 0010 0000 0010
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
BoltType is just for tracking what type of db item we're looking at
|
|
||||||
*/
|
|
||||||
type BoltType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
typeBucket = iota
|
|
||||||
typePair
|
|
||||||
)
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) handleKeyEvent(event termbox.Event) int {
|
|
||||||
if screen.mode == 0 {
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
}
|
|
||||||
if screen.mode == modeBrowse {
|
|
||||||
return screen.handleBrowseKeyEvent(event)
|
|
||||||
} else if screen.mode&modeChange == modeChange {
|
|
||||||
return screen.handleInputKeyEvent(event)
|
|
||||||
} else if screen.mode&modeInsert == modeInsert {
|
|
||||||
return screen.handleInsertKeyEvent(event)
|
|
||||||
} else if screen.mode == modeDelete {
|
|
||||||
return screen.handleDeleteKeyEvent(event)
|
|
||||||
} else if screen.mode&modeExport == modeExport {
|
|
||||||
return screen.handleExportKeyEvent(event)
|
|
||||||
}
|
|
||||||
return BrowserScreenIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
|
|
||||||
if event.Ch == '?' {
|
|
||||||
// About
|
|
||||||
return AboutScreenIndex
|
|
||||||
|
|
||||||
} else if event.Ch == 'q' || event.Key == termbox.KeyEsc || event.Key == termbox.KeyCtrlC {
|
|
||||||
// Quit
|
|
||||||
return ExitScreenIndex
|
|
||||||
|
|
||||||
} else if event.Ch == 'g' {
|
|
||||||
// Jump to Beginning
|
|
||||||
screen.currentPath = screen.db.getNextVisiblePath(nil, screen.filter)
|
|
||||||
|
|
||||||
} else if event.Ch == 'G' {
|
|
||||||
// Jump to End
|
|
||||||
screen.currentPath = screen.db.getPrevVisiblePath(nil, screen.filter)
|
|
||||||
|
|
||||||
} else if event.Key == termbox.KeyCtrlR {
|
|
||||||
screen.refreshDatabase()
|
|
||||||
|
|
||||||
} else if event.Key == termbox.KeyCtrlF {
|
|
||||||
// Jump forward half a screen
|
|
||||||
_, h := termbox.Size()
|
|
||||||
half := h / 2
|
|
||||||
screen.jumpCursorDown(half)
|
|
||||||
|
|
||||||
} else if event.Key == termbox.KeyCtrlB {
|
|
||||||
_, h := termbox.Size()
|
|
||||||
half := h / 2
|
|
||||||
screen.jumpCursorUp(half)
|
|
||||||
|
|
||||||
} else if event.Ch == 'j' || event.Key == termbox.KeyArrowDown {
|
|
||||||
screen.moveCursorDown()
|
|
||||||
|
|
||||||
} else if event.Ch == 'k' || event.Key == termbox.KeyArrowUp {
|
|
||||||
screen.moveCursorUp()
|
|
||||||
|
|
||||||
} else if event.Ch == 'J' {
|
|
||||||
screen.moveRightPaneDown()
|
|
||||||
|
|
||||||
} else if event.Ch == 'K' {
|
|
||||||
screen.moveRightPaneUp()
|
|
||||||
|
|
||||||
} else if event.Ch == 'p' {
|
|
||||||
// p creates a new pair at the current level
|
|
||||||
screen.startInsertItem(typePair)
|
|
||||||
} else if event.Ch == 'P' {
|
|
||||||
// P creates a new pair at the parent level
|
|
||||||
screen.startInsertItemAtParent(typePair)
|
|
||||||
|
|
||||||
} else if event.Ch == 'b' {
|
|
||||||
// b creates a new bucket at the current level
|
|
||||||
screen.startInsertItem(typeBucket)
|
|
||||||
} else if event.Ch == 'B' {
|
|
||||||
// B creates a new bucket at the parent level
|
|
||||||
screen.startInsertItemAtParent(typeBucket)
|
|
||||||
|
|
||||||
} else if event.Ch == 'e' {
|
|
||||||
b, p, _ := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if b != nil {
|
|
||||||
screen.setMessage("Cannot edit a bucket, did you mean to (r)ename?")
|
|
||||||
} else if p != nil {
|
|
||||||
screen.startEditItem()
|
|
||||||
}
|
|
||||||
} else if event.Ch == '/' {
|
|
||||||
screen.startFilter()
|
|
||||||
|
|
||||||
} else if event.Ch == 'r' {
|
|
||||||
screen.startRenameItem()
|
|
||||||
|
|
||||||
} else if event.Key == termbox.KeyEnter {
|
|
||||||
b, p, _ := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if b != nil {
|
|
||||||
screen.db.toggleOpenBucket(screen.currentPath)
|
|
||||||
} else if p != nil {
|
|
||||||
screen.startEditItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if event.Ch == 'l' || event.Key == termbox.KeyArrowRight {
|
|
||||||
b, p, _ := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
// Select the current item
|
|
||||||
if b != nil {
|
|
||||||
screen.db.toggleOpenBucket(screen.currentPath)
|
|
||||||
} else if p != nil {
|
|
||||||
screen.startEditItem()
|
|
||||||
} else {
|
|
||||||
screen.setMessage("Not sure what to do here...")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if event.Ch == 'h' || event.Key == termbox.KeyArrowLeft {
|
|
||||||
// If we are _on_ a bucket that's open, close it
|
|
||||||
b, _, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e == nil && b != nil && b.expanded {
|
|
||||||
screen.db.closeBucket(screen.currentPath)
|
|
||||||
} else {
|
|
||||||
if len(screen.currentPath) > 1 {
|
|
||||||
parentBucket, err := screen.db.getBucketFromPath(screen.currentPath[:len(screen.currentPath)-1])
|
|
||||||
if err == nil {
|
|
||||||
screen.db.closeBucket(parentBucket.GetPath())
|
|
||||||
// Figure out how far up we need to move the cursor
|
|
||||||
screen.currentPath = parentBucket.GetPath()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
screen.db.closeBucket(screen.currentPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if event.Ch == 'D' {
|
|
||||||
screen.startDeleteItem()
|
|
||||||
} else if event.Ch == 'x' {
|
|
||||||
// Export Value
|
|
||||||
screen.startExportValue()
|
|
||||||
} else if event.Ch == 'X' {
|
|
||||||
// Export Key/Value (or Bucket) as JSON
|
|
||||||
screen.startExportJSON()
|
|
||||||
}
|
|
||||||
return BrowserScreenIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) handleInputKeyEvent(event termbox.Event) int {
|
|
||||||
if event.Key == termbox.KeyEsc {
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
screen.inputModal.Clear()
|
|
||||||
} else {
|
|
||||||
screen.inputModal.HandleEvent(event)
|
|
||||||
if screen.inputModal.IsDone() {
|
|
||||||
b, p, _ := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if screen.mode == modeFilter {
|
|
||||||
screen.filter = screen.inputModal.GetValue()
|
|
||||||
if !screen.db.isVisiblePath(screen.currentPath, screen.filter) {
|
|
||||||
screen.currentPath = screen.currentPath[:len(screen.currentPath)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if b != nil {
|
|
||||||
if screen.mode == modeChangeKey {
|
|
||||||
newName := screen.inputModal.GetValue()
|
|
||||||
if renameBucket(screen.currentPath, newName) != nil {
|
|
||||||
screen.setMessage("Error renaming bucket.")
|
|
||||||
} else {
|
|
||||||
b.name = newName
|
|
||||||
screen.currentPath[len(screen.currentPath)-1] = b.name
|
|
||||||
screen.setMessage("Bucket Renamed!")
|
|
||||||
screen.refreshDatabase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if p != nil {
|
|
||||||
if screen.mode == modeChangeKey {
|
|
||||||
newKey := screen.inputModal.GetValue()
|
|
||||||
if err := updatePairKey(screen.currentPath, newKey); err != nil {
|
|
||||||
screen.setMessage("Error occurred updating Pair.")
|
|
||||||
} else {
|
|
||||||
p.key = newKey
|
|
||||||
screen.currentPath[len(screen.currentPath)-1] = p.key
|
|
||||||
screen.setMessage("Pair updated!")
|
|
||||||
screen.refreshDatabase()
|
|
||||||
}
|
|
||||||
} else if screen.mode == modeChangeVal {
|
|
||||||
newVal := screen.inputModal.GetValue()
|
|
||||||
if err := updatePairValue(screen.currentPath, newVal); err != nil {
|
|
||||||
screen.setMessage("Error occurred updating Pair.")
|
|
||||||
} else {
|
|
||||||
p.val = newVal
|
|
||||||
screen.setMessage("Pair updated!")
|
|
||||||
screen.refreshDatabase()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
screen.inputModal.Clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return BrowserScreenIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) handleDeleteKeyEvent(event termbox.Event) int {
|
|
||||||
screen.confirmModal.HandleEvent(event)
|
|
||||||
if screen.confirmModal.IsDone() {
|
|
||||||
if screen.confirmModal.IsAccepted() {
|
|
||||||
holdNextPath := screen.db.getNextVisiblePath(screen.currentPath, screen.filter)
|
|
||||||
holdPrevPath := screen.db.getPrevVisiblePath(screen.currentPath, screen.filter)
|
|
||||||
if deleteKey(screen.currentPath) == nil {
|
|
||||||
screen.refreshDatabase()
|
|
||||||
// Move the current path endpoint appropriately
|
|
||||||
//found_new_path := false
|
|
||||||
if holdNextPath != nil {
|
|
||||||
if len(holdNextPath) > 2 {
|
|
||||||
if holdNextPath[len(holdNextPath)-2] == screen.currentPath[len(screen.currentPath)-2] {
|
|
||||||
screen.currentPath = holdNextPath
|
|
||||||
} else if holdPrevPath != nil {
|
|
||||||
screen.currentPath = holdPrevPath
|
|
||||||
} else {
|
|
||||||
// Otherwise, go to the parent
|
|
||||||
screen.currentPath = screen.currentPath[:(len(holdNextPath) - 2)]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Root bucket deleted, set to next
|
|
||||||
screen.currentPath = holdNextPath
|
|
||||||
}
|
|
||||||
} else if holdPrevPath != nil {
|
|
||||||
screen.currentPath = holdPrevPath
|
|
||||||
} else {
|
|
||||||
screen.currentPath = screen.currentPath[:0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
screen.confirmModal.Clear()
|
|
||||||
}
|
|
||||||
return BrowserScreenIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) handleInsertKeyEvent(event termbox.Event) int {
|
|
||||||
if event.Key == termbox.KeyEsc {
|
|
||||||
if len(screen.db.buckets) == 0 {
|
|
||||||
return ExitScreenIndex
|
|
||||||
}
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
screen.inputModal.Clear()
|
|
||||||
} else {
|
|
||||||
screen.inputModal.HandleEvent(event)
|
|
||||||
if screen.inputModal.IsDone() {
|
|
||||||
newVal := screen.inputModal.GetValue()
|
|
||||||
screen.inputModal.Clear()
|
|
||||||
var insertPath []string
|
|
||||||
if len(screen.currentPath) > 0 {
|
|
||||||
_, p, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e != nil {
|
|
||||||
screen.setMessage("Error Inserting new item. Invalid Path.")
|
|
||||||
}
|
|
||||||
insertPath = screen.currentPath
|
|
||||||
// where are we inserting?
|
|
||||||
if p != nil {
|
|
||||||
// If we're sitting on a pair, we have to go to it's parent
|
|
||||||
screen.mode = screen.mode | modeModToParent
|
|
||||||
}
|
|
||||||
if screen.mode&modeModToParent == modeModToParent {
|
|
||||||
if len(screen.currentPath) > 1 {
|
|
||||||
insertPath = screen.currentPath[:len(screen.currentPath)-1]
|
|
||||||
} else {
|
|
||||||
insertPath = make([]string, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parentB, _, _ := screen.db.getGenericFromPath(insertPath)
|
|
||||||
if screen.mode&modeInsertBucket == modeInsertBucket {
|
|
||||||
err := insertBucket(insertPath, newVal)
|
|
||||||
if err != nil {
|
|
||||||
screen.setMessage(fmt.Sprintf("%s => %s", err, insertPath))
|
|
||||||
} else {
|
|
||||||
if parentB != nil {
|
|
||||||
parentB.expanded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.currentPath = append(insertPath, newVal)
|
|
||||||
|
|
||||||
screen.refreshDatabase()
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
screen.inputModal.Clear()
|
|
||||||
} else if screen.mode&modeInsertPair == modeInsertPair {
|
|
||||||
err := insertPair(insertPath, newVal, "")
|
|
||||||
if err != nil {
|
|
||||||
screen.setMessage(fmt.Sprintf("%s => %s", err, insertPath))
|
|
||||||
screen.refreshDatabase()
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
screen.inputModal.Clear()
|
|
||||||
} else {
|
|
||||||
if parentB != nil {
|
|
||||||
parentB.expanded = true
|
|
||||||
}
|
|
||||||
screen.currentPath = append(insertPath, newVal)
|
|
||||||
screen.refreshDatabase()
|
|
||||||
screen.startEditItem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return BrowserScreenIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) handleExportKeyEvent(event termbox.Event) int {
|
|
||||||
if event.Key == termbox.KeyEsc {
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
screen.inputModal.Clear()
|
|
||||||
} else {
|
|
||||||
screen.inputModal.HandleEvent(event)
|
|
||||||
if screen.inputModal.IsDone() {
|
|
||||||
b, p, _ := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
fileName := screen.inputModal.GetValue()
|
|
||||||
if screen.mode&modeExportValue == modeExportValue {
|
|
||||||
// Exporting the value
|
|
||||||
if p != nil {
|
|
||||||
if err := exportValue(screen.currentPath, fileName); err != nil {
|
|
||||||
//screen.setMessage("Error Exporting to file " + fileName + ".")
|
|
||||||
screen.setMessage(err.Error())
|
|
||||||
} else {
|
|
||||||
screen.setMessage("Value exported to file: " + fileName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if screen.mode&modeExportJSON == modeExportJSON {
|
|
||||||
if b != nil || p != nil {
|
|
||||||
if exportJSON(screen.currentPath, fileName) != nil {
|
|
||||||
screen.setMessage("Error Exporting to file " + fileName + ".")
|
|
||||||
} else {
|
|
||||||
screen.setMessage("Value exported to file: " + fileName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.mode = modeBrowse
|
|
||||||
screen.inputModal.Clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return BrowserScreenIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) jumpCursorUp(distance int) bool {
|
|
||||||
// Jump up 'distance' lines
|
|
||||||
visPaths, err := screen.db.buildVisiblePathSlice(screen.filter)
|
|
||||||
if err == nil {
|
|
||||||
findPath := screen.currentPath
|
|
||||||
for idx, pth := range visPaths {
|
|
||||||
startJump := true
|
|
||||||
for i := range pth {
|
|
||||||
if len(screen.currentPath) > i && pth[i] != screen.currentPath[i] {
|
|
||||||
startJump = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if startJump {
|
|
||||||
distance--
|
|
||||||
if distance == 0 {
|
|
||||||
screen.currentPath = visPaths[len(visPaths)-1-idx]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isCurPath := true
|
|
||||||
for i := range screen.currentPath {
|
|
||||||
if screen.currentPath[i] != findPath[i] {
|
|
||||||
isCurPath = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isCurPath {
|
|
||||||
screen.currentPath = screen.db.getNextVisiblePath(nil, screen.filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
func (screen *BrowserScreen) jumpCursorDown(distance int) bool {
|
|
||||||
visPaths, err := screen.db.buildVisiblePathSlice(screen.filter)
|
|
||||||
if err == nil {
|
|
||||||
findPath := screen.currentPath
|
|
||||||
for idx, pth := range visPaths {
|
|
||||||
startJump := true
|
|
||||||
|
|
||||||
for i := range pth {
|
|
||||||
if len(screen.currentPath) > i && pth[i] != screen.currentPath[i] {
|
|
||||||
startJump = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if startJump {
|
|
||||||
distance--
|
|
||||||
if distance == 0 {
|
|
||||||
screen.currentPath = visPaths[idx]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isCurPath := true
|
|
||||||
for i := range screen.currentPath {
|
|
||||||
if screen.currentPath[i] != findPath[i] {
|
|
||||||
isCurPath = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isCurPath {
|
|
||||||
screen.currentPath = screen.db.getNextVisiblePath(nil, screen.filter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) moveCursorUp() bool {
|
|
||||||
newPath := screen.db.getPrevVisiblePath(screen.currentPath, screen.filter)
|
|
||||||
if newPath != nil {
|
|
||||||
screen.currentPath = newPath
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
func (screen *BrowserScreen) moveCursorDown() bool {
|
|
||||||
newPath := screen.db.getNextVisiblePath(screen.currentPath, screen.filter)
|
|
||||||
if newPath != nil {
|
|
||||||
screen.currentPath = newPath
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
func (screen *BrowserScreen) moveRightPaneUp() bool {
|
|
||||||
if screen.rightViewPort.scrollRow > 0 {
|
|
||||||
screen.rightViewPort.scrollRow--
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
func (screen *BrowserScreen) moveRightPaneDown() bool {
|
|
||||||
screen.rightViewPort.scrollRow++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) performLayout() {}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) drawScreen(style Style) {
|
|
||||||
if screen.db == nil {
|
|
||||||
screen.drawHeader(style)
|
|
||||||
screen.setMessage("Invalid DB. Press 'q' to quit, '?' for help")
|
|
||||||
screen.drawFooter(style)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(screen.db.buckets) == 0 && screen.mode&modeInsertBucket != modeInsertBucket {
|
|
||||||
// Force a bucket insert
|
|
||||||
screen.startInsertItemAtParent(typeBucket)
|
|
||||||
}
|
|
||||||
if screen.message == "" {
|
|
||||||
screen.setMessageWithTimeout("Press '?' for help", -1)
|
|
||||||
}
|
|
||||||
screen.drawLeftPane(style)
|
|
||||||
screen.drawRightPane(style)
|
|
||||||
screen.drawHeader(style)
|
|
||||||
screen.drawFooter(style)
|
|
||||||
|
|
||||||
if screen.inputModal != nil {
|
|
||||||
screen.inputModal.Draw()
|
|
||||||
}
|
|
||||||
if screen.mode == modeDelete {
|
|
||||||
screen.confirmModal.Draw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) drawHeader(style Style) {
|
|
||||||
width, _ := termbox.Size()
|
|
||||||
headerStringLen := func(fileName string) int {
|
|
||||||
return len(ProgramName) + len(fileName) + 1
|
|
||||||
}
|
|
||||||
headerFileName := currentFilename
|
|
||||||
if headerStringLen(headerFileName) > width {
|
|
||||||
headerFileName = filepath.Base(headerFileName)
|
|
||||||
}
|
|
||||||
headerString := ProgramName + ": " + headerFileName
|
|
||||||
count := ((width - len(headerString)) / 2) + 1
|
|
||||||
if count < 0 {
|
|
||||||
count = 0
|
|
||||||
}
|
|
||||||
spaces := strings.Repeat(" ", count)
|
|
||||||
termboxUtil.DrawStringAtPoint(fmt.Sprintf("%s%s%s", spaces, headerString, spaces), 0, 0, style.titleFg, style.titleBg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) drawFooter(style Style) {
|
|
||||||
if screen.messageTimeout > 0 && time.Since(screen.messageTime) > screen.messageTimeout {
|
|
||||||
screen.clearMessage()
|
|
||||||
}
|
|
||||||
_, height := termbox.Size()
|
|
||||||
termboxUtil.DrawStringAtPoint(screen.message, 0, height-1, style.defaultFg, style.defaultBg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) buildLeftPane(style Style) {
|
|
||||||
screen.leftPaneBuffer = nil
|
|
||||||
if len(screen.currentPath) == 0 {
|
|
||||||
screen.currentPath = screen.db.getNextVisiblePath(nil, screen.filter)
|
|
||||||
}
|
|
||||||
for i := range screen.db.buckets {
|
|
||||||
screen.leftPaneBuffer = append(screen.leftPaneBuffer, screen.bucketToLines(&screen.db.buckets[i], style)...)
|
|
||||||
}
|
|
||||||
// Find the cursor in the leftPane
|
|
||||||
for k, v := range screen.leftPaneBuffer {
|
|
||||||
if v.Fg == style.cursorFg {
|
|
||||||
screen.leftViewPort.scrollRow = k
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) drawLeftPane(style Style) {
|
|
||||||
screen.buildLeftPane(style)
|
|
||||||
w, h := termbox.Size()
|
|
||||||
if w > 80 {
|
|
||||||
w = w / 2
|
|
||||||
}
|
|
||||||
screen.leftViewPort.bytesPerRow = w
|
|
||||||
screen.leftViewPort.numberOfRows = h - 2
|
|
||||||
termboxUtil.FillWithChar('=', 0, 1, w, 1, style.defaultFg, style.defaultBg)
|
|
||||||
startX, startY := 0, 3
|
|
||||||
screen.leftViewPort.firstRow = startY
|
|
||||||
treeOffset := 0
|
|
||||||
maxCursor := screen.leftViewPort.numberOfRows * 2 / 3
|
|
||||||
|
|
||||||
if screen.leftViewPort.scrollRow > maxCursor {
|
|
||||||
treeOffset = screen.leftViewPort.scrollRow - maxCursor
|
|
||||||
}
|
|
||||||
if len(screen.leftPaneBuffer) > 0 {
|
|
||||||
for k, v := range screen.leftPaneBuffer[treeOffset:] {
|
|
||||||
termboxUtil.DrawStringAtPoint(v.Text, startX, (startY + k - 1), v.Fg, v.Bg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) buildRightPane(style Style) {
|
|
||||||
screen.rightPaneBuffer = nil
|
|
||||||
b, p, err := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if err == nil {
|
|
||||||
if b != nil {
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{fmt.Sprintf("Path: %s", strings.Join(stringifyPath(b.GetPath()), " → ")), style.defaultFg, style.defaultBg})
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{fmt.Sprintf("Buckets: %d", len(b.buckets)), style.defaultFg, style.defaultBg})
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{fmt.Sprintf("Pairs: %d", len(b.pairs)), style.defaultFg, style.defaultBg})
|
|
||||||
} else if p != nil {
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{fmt.Sprintf("Path: %s", strings.Join(stringifyPath(p.GetPath()), " → ")), style.defaultFg, style.defaultBg})
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{fmt.Sprintf("Key: %s", stringify([]byte(p.key))), style.defaultFg, style.defaultBg})
|
|
||||||
|
|
||||||
value := strings.Split(string(formatValue([]byte(p.val))), "\n")
|
|
||||||
if len(value) == 1 {
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{fmt.Sprintf("Value: %s", value[0]), style.defaultFg, style.defaultBg})
|
|
||||||
} else {
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{"Value:", style.defaultFg, style.defaultBg})
|
|
||||||
for _, v := range value {
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{v, style.defaultFg, style.defaultBg})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{fmt.Sprintf("Path: %s", strings.Join(stringifyPath(screen.currentPath), " → ")), style.defaultFg, style.defaultBg})
|
|
||||||
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
|
|
||||||
Line{err.Error(), termbox.ColorRed, termbox.ColorBlack})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) drawRightPane(style Style) {
|
|
||||||
screen.buildRightPane(style)
|
|
||||||
w, h := termbox.Size()
|
|
||||||
if w > 80 {
|
|
||||||
screen.rightViewPort.bytesPerRow = w / 2
|
|
||||||
screen.rightViewPort.numberOfRows = h - 2
|
|
||||||
// Screen is wide enough, split it
|
|
||||||
termboxUtil.FillWithChar('=', 0, 1, w, 1, style.defaultFg, style.defaultBg)
|
|
||||||
termboxUtil.FillWithChar('|', (w / 2), screen.rightViewPort.firstRow-1, (w / 2), h, style.defaultFg, style.defaultBg)
|
|
||||||
// Clear the right pane
|
|
||||||
termboxUtil.FillWithChar(' ', (w/2)+1, screen.rightViewPort.firstRow+2, w, h, style.defaultFg, style.defaultBg)
|
|
||||||
|
|
||||||
startX := (w / 2) + 2
|
|
||||||
startY := 3
|
|
||||||
maxScroll := len(screen.rightPaneBuffer) - screen.rightViewPort.numberOfRows
|
|
||||||
if maxScroll < 0 {
|
|
||||||
maxScroll = 0
|
|
||||||
}
|
|
||||||
if screen.rightViewPort.scrollRow > maxScroll {
|
|
||||||
screen.rightViewPort.scrollRow = maxScroll
|
|
||||||
}
|
|
||||||
if len(screen.rightPaneBuffer) > 0 {
|
|
||||||
for k, v := range screen.rightPaneBuffer[screen.rightViewPort.scrollRow:] {
|
|
||||||
termboxUtil.DrawStringAtPoint(v.Text, startX, (startY + k - 1), v.Fg, v.Bg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatValue(val []byte) []byte {
|
|
||||||
// Attempt JSON parsing and formatting
|
|
||||||
out, err := formatValueJSON(val)
|
|
||||||
if err == nil {
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
return []byte(stringify([]byte(val)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatValueJSON(val []byte) ([]byte, error) {
|
|
||||||
var jsonOut interface{}
|
|
||||||
err := json.Unmarshal(val, &jsonOut)
|
|
||||||
if err != nil {
|
|
||||||
return val, err
|
|
||||||
}
|
|
||||||
out, err := json.MarshalIndent(jsonOut, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return val, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) bucketToLines(bkt *BoltBucket, style Style) []Line {
|
|
||||||
var ret []Line
|
|
||||||
bfg, bbg := style.defaultFg, style.defaultBg
|
|
||||||
if comparePaths(screen.currentPath, bkt.GetPath()) {
|
|
||||||
bfg, bbg = style.cursorFg, style.cursorBg
|
|
||||||
}
|
|
||||||
bktPrefix := strings.Repeat(" ", len(bkt.GetPath())*2)
|
|
||||||
if bkt.expanded {
|
|
||||||
ret = append(ret, Line{bktPrefix + "- " + stringify([]byte(bkt.name)), bfg, bbg})
|
|
||||||
for i := range bkt.buckets {
|
|
||||||
ret = append(ret, screen.bucketToLines(&bkt.buckets[i], style)...)
|
|
||||||
}
|
|
||||||
for _, bp := range bkt.pairs {
|
|
||||||
if screen.filter != "" && !strings.Contains(bp.key, screen.filter) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pfg, pbg := style.defaultFg, style.defaultBg
|
|
||||||
if comparePaths(screen.currentPath, bp.GetPath()) {
|
|
||||||
pfg, pbg = style.cursorFg, style.cursorBg
|
|
||||||
}
|
|
||||||
prPrefix := strings.Repeat(" ", len(bp.GetPath())*2)
|
|
||||||
var pairString string
|
|
||||||
if AppArgs.NoValue {
|
|
||||||
pairString = fmt.Sprintf("%s%s", prPrefix, stringify([]byte(bp.key)))
|
|
||||||
} else {
|
|
||||||
pairString = fmt.Sprintf("%s%s: %s", prPrefix, stringify([]byte(bp.key)), stringify([]byte(bp.val)))
|
|
||||||
}
|
|
||||||
ret = append(ret, Line{pairString, pfg, pbg})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ret = append(ret, Line{bktPrefix + "+ " + stringify([]byte(bkt.name)), bfg, bbg})
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) startDeleteItem() bool {
|
|
||||||
b, p, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e == nil {
|
|
||||||
w, h := termbox.Size()
|
|
||||||
inpW, inpH := (w / 2), 6
|
|
||||||
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
|
|
||||||
mod := termboxUtil.CreateConfirmModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
|
|
||||||
if b != nil {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Delete Bucket '%s'?", b.name), inpW-1, termboxUtil.AlignCenter))
|
|
||||||
} else if p != nil {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Delete Pair '%s'?", p.key), inpW-1, termboxUtil.AlignCenter))
|
|
||||||
}
|
|
||||||
mod.Show()
|
|
||||||
mod.SetText(termboxUtil.AlignText("This cannot be undone!", inpW-1, termboxUtil.AlignCenter))
|
|
||||||
screen.confirmModal = mod
|
|
||||||
screen.mode = modeDelete
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) startFilter() bool {
|
|
||||||
_, _, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e == nil {
|
|
||||||
w, h := termbox.Size()
|
|
||||||
inpW, inpH := (w / 2), 6
|
|
||||||
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
|
|
||||||
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
|
|
||||||
mod.SetTitle(termboxUtil.AlignText("Filter", inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.SetValue(screen.filter)
|
|
||||||
mod.Show()
|
|
||||||
screen.inputModal = mod
|
|
||||||
screen.mode = modeFilter
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) startEditItem() bool {
|
|
||||||
_, p, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e == nil {
|
|
||||||
w, h := termbox.Size()
|
|
||||||
inpW, inpH := (w / 2), 6
|
|
||||||
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
|
|
||||||
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
|
|
||||||
if p != nil {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Input new value for '%s'", p.key), inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.SetValue(p.val)
|
|
||||||
}
|
|
||||||
mod.Show()
|
|
||||||
screen.inputModal = mod
|
|
||||||
screen.mode = modeChangeVal
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) startRenameItem() bool {
|
|
||||||
b, p, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e == nil {
|
|
||||||
w, h := termbox.Size()
|
|
||||||
inpW, inpH := (w / 2), 6
|
|
||||||
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
|
|
||||||
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
|
|
||||||
if b != nil {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Rename Bucket '%s' to:", b.name), inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.SetValue(b.name)
|
|
||||||
} else if p != nil {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Rename Key '%s' to:", p.key), inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.SetValue(p.key)
|
|
||||||
}
|
|
||||||
mod.Show()
|
|
||||||
screen.inputModal = mod
|
|
||||||
screen.mode = modeChangeKey
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) startInsertItemAtParent(tp BoltType) bool {
|
|
||||||
w, h := termbox.Size()
|
|
||||||
inpW, inpH := w-1, 7
|
|
||||||
if w > 80 {
|
|
||||||
inpW, inpH = (w / 2), 7
|
|
||||||
}
|
|
||||||
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
|
|
||||||
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
|
|
||||||
screen.inputModal = mod
|
|
||||||
if len(screen.currentPath) <= 0 {
|
|
||||||
// in the root directory
|
|
||||||
if tp == typeBucket {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText("Create Root Bucket", inpW, termboxUtil.AlignCenter))
|
|
||||||
screen.mode = modeInsertBucket | modeModToParent
|
|
||||||
mod.Show()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var insPath string
|
|
||||||
_, p, e := screen.db.getGenericFromPath(screen.currentPath[:len(screen.currentPath)-1])
|
|
||||||
if e == nil && p != nil {
|
|
||||||
insPath = strings.Join(screen.currentPath[:len(screen.currentPath)-2], " → ") + " → "
|
|
||||||
} else {
|
|
||||||
insPath = strings.Join(screen.currentPath[:len(screen.currentPath)-1], " → ") + " → "
|
|
||||||
}
|
|
||||||
titlePrfx := ""
|
|
||||||
if tp == typeBucket {
|
|
||||||
titlePrfx = "New Bucket: "
|
|
||||||
} else if tp == typePair {
|
|
||||||
titlePrfx = "New Pair: "
|
|
||||||
}
|
|
||||||
titleText := titlePrfx + insPath
|
|
||||||
if len(titleText) > inpW {
|
|
||||||
truncW := len(titleText) - inpW
|
|
||||||
titleText = titlePrfx + "..." + insPath[truncW+3:]
|
|
||||||
}
|
|
||||||
if tp == typeBucket {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(titleText, inpW, termboxUtil.AlignCenter))
|
|
||||||
screen.mode = modeInsertBucket | modeModToParent
|
|
||||||
mod.Show()
|
|
||||||
return true
|
|
||||||
} else if tp == typePair {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(titleText, inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.Show()
|
|
||||||
screen.mode = modeInsertPair | modeModToParent
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) startInsertItem(tp BoltType) bool {
|
|
||||||
w, h := termbox.Size()
|
|
||||||
inpW, inpH := w-1, 7
|
|
||||||
if w > 80 {
|
|
||||||
inpW, inpH = (w / 2), 7
|
|
||||||
}
|
|
||||||
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
|
|
||||||
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
|
|
||||||
//mod.SetInputWrap(true)
|
|
||||||
screen.inputModal = mod
|
|
||||||
var insPath string
|
|
||||||
_, p, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e == nil && p != nil {
|
|
||||||
insPath = strings.Join(screen.currentPath[:len(screen.currentPath)-1], " → ") + " → "
|
|
||||||
} else {
|
|
||||||
insPath = strings.Join(screen.currentPath, " → ") + " → "
|
|
||||||
}
|
|
||||||
titlePrfx := ""
|
|
||||||
if tp == typeBucket {
|
|
||||||
titlePrfx = "New Bucket: "
|
|
||||||
} else if tp == typePair {
|
|
||||||
titlePrfx = "New Pair: "
|
|
||||||
}
|
|
||||||
titleText := titlePrfx + insPath
|
|
||||||
if len(titleText) > inpW {
|
|
||||||
truncW := len(titleText) - inpW
|
|
||||||
titleText = titlePrfx + "..." + insPath[truncW+3:]
|
|
||||||
}
|
|
||||||
if tp == typeBucket {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(titleText, inpW, termboxUtil.AlignCenter))
|
|
||||||
screen.mode = modeInsertBucket
|
|
||||||
mod.Show()
|
|
||||||
return true
|
|
||||||
} else if tp == typePair {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(titleText, inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.Show()
|
|
||||||
screen.mode = modeInsertPair
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) startExportValue() bool {
|
|
||||||
_, p, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e == nil && p != nil {
|
|
||||||
w, h := termbox.Size()
|
|
||||||
inpW, inpH := (w / 2), 6
|
|
||||||
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
|
|
||||||
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Export value of '%s' to:", p.key), inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.SetValue("")
|
|
||||||
mod.Show()
|
|
||||||
screen.inputModal = mod
|
|
||||||
screen.mode = modeExportValue
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
screen.setMessage("Couldn't do string export on " + screen.currentPath[len(screen.currentPath)-1] + "(did you mean 'X'?)")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) startExportJSON() bool {
|
|
||||||
b, p, e := screen.db.getGenericFromPath(screen.currentPath)
|
|
||||||
if e == nil {
|
|
||||||
w, h := termbox.Size()
|
|
||||||
inpW, inpH := (w / 2), 6
|
|
||||||
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
|
|
||||||
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
|
|
||||||
if b != nil {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Export JSON of '%s' to:", b.name), inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.SetValue("")
|
|
||||||
} else if p != nil {
|
|
||||||
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Export JSON of '%s' to:", p.key), inpW, termboxUtil.AlignCenter))
|
|
||||||
mod.SetValue("")
|
|
||||||
}
|
|
||||||
mod.Show()
|
|
||||||
screen.inputModal = mod
|
|
||||||
screen.mode = modeExportJSON
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) setMessage(msg string) {
|
|
||||||
screen.message = msg
|
|
||||||
screen.messageTime = time.Now()
|
|
||||||
screen.messageTimeout = time.Second * 2
|
|
||||||
}
|
|
||||||
|
|
||||||
/* setMessageWithTimeout lets you specify the timeout for the message
|
|
||||||
* setting it to -1 means it won't timeout
|
|
||||||
*/
|
|
||||||
func (screen *BrowserScreen) setMessageWithTimeout(msg string, timeout time.Duration) {
|
|
||||||
screen.message = msg
|
|
||||||
screen.messageTime = time.Now()
|
|
||||||
screen.messageTimeout = timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) clearMessage() {
|
|
||||||
screen.message = ""
|
|
||||||
screen.messageTimeout = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (screen *BrowserScreen) refreshDatabase() {
|
|
||||||
shadowDB := screen.db
|
|
||||||
screen.db = screen.db.refreshDatabase()
|
|
||||||
screen.db.syncOpenBuckets(shadowDB)
|
|
||||||
}
|
|
||||||
|
|
||||||
func comparePaths(p1, p2 []string) bool {
|
|
||||||
return strings.Join(p1, " → ") == strings.Join(p2, " → ")
|
|
||||||
}
|
|
39
stringify.go
39
stringify.go
@ -1,39 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// stringify ensures that we can print only valid characters.
|
|
||||||
// It's wrong to assume that everything is a string, since BoltDB is typeless.
|
|
||||||
func stringify(v []byte) string {
|
|
||||||
if utf8.Valid(v) {
|
|
||||||
ok := true
|
|
||||||
for _, r := range string(v) {
|
|
||||||
if r < 0x20 {
|
|
||||||
ok = false
|
|
||||||
break
|
|
||||||
} else if r >= 0x7f && r <= 0x9f {
|
|
||||||
ok = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
return string(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(v) == 8 {
|
|
||||||
return fmt.Sprintf("%v", binary.BigEndian.Uint64(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%x", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyPath(path []string) []string {
|
|
||||||
for k, v := range path {
|
|
||||||
path[k] = stringify([]byte(v))
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
27
style.go
27
style.go
@ -1,27 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "github.com/nsf/termbox-go"
|
|
||||||
|
|
||||||
/*
|
|
||||||
Style Defines the colors for the terminal display, basically
|
|
||||||
*/
|
|
||||||
type Style struct {
|
|
||||||
defaultBg termbox.Attribute
|
|
||||||
defaultFg termbox.Attribute
|
|
||||||
titleFg termbox.Attribute
|
|
||||||
titleBg termbox.Attribute
|
|
||||||
cursorFg termbox.Attribute
|
|
||||||
cursorBg termbox.Attribute
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultStyle() Style {
|
|
||||||
var style Style
|
|
||||||
style.defaultBg = termbox.ColorBlack
|
|
||||||
style.defaultFg = termbox.ColorWhite
|
|
||||||
style.titleFg = termbox.ColorBlack
|
|
||||||
style.titleBg = termbox.ColorGreen
|
|
||||||
style.cursorFg = termbox.ColorBlack
|
|
||||||
style.cursorBg = termbox.ColorGreen
|
|
||||||
|
|
||||||
return style
|
|
||||||
}
|
|
@ -1,39 +1,93 @@
|
|||||||
package main
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/br0xen/termbox-util"
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
"github.com/nsf/termbox-go"
|
"github.com/nsf/termbox-go"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
const (
|
||||||
Command is a struct for associating descriptions to keys
|
ABT_CmdGotoBrowse = AboutId | iota
|
||||||
*/
|
)
|
||||||
|
|
||||||
|
type AboutMsg struct {
|
||||||
|
source int
|
||||||
|
data interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type aboutScreen struct {
|
||||||
|
ui *Ui
|
||||||
|
|
||||||
|
commands1 []Command
|
||||||
|
commands2 []Command
|
||||||
|
}
|
||||||
|
|
||||||
type Command struct {
|
type Command struct {
|
||||||
key string
|
key string
|
||||||
description string
|
description string
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
func NewAboutScreen(u *Ui) *aboutScreen { return &aboutScreen{ui: u} }
|
||||||
AboutScreen is just a basic 'int' type that we can extend to make this screen
|
|
||||||
*/
|
|
||||||
type AboutScreen int
|
|
||||||
|
|
||||||
func drawCommandAtPoint(cmd Command, xPos int, yPos int, style Style) {
|
func (s *aboutScreen) Init() wandle.Cmd {
|
||||||
termboxUtil.DrawStringAtPoint(fmt.Sprintf("%6s", cmd.key), xPos, yPos, style.defaultFg, style.defaultBg)
|
s.commands1 = []Command{
|
||||||
termboxUtil.DrawStringAtPoint(cmd.description, xPos+8, yPos, style.defaultFg, style.defaultBg)
|
{"h,←", "close parent"},
|
||||||
|
{"j,↓", "down"},
|
||||||
|
{"k,↑", "up"},
|
||||||
|
{"l,→", "open item"},
|
||||||
|
{"J", "scroll right pane down"},
|
||||||
|
{"K", "scroll right pane up"},
|
||||||
|
{"", ""},
|
||||||
|
{"g", "goto top"},
|
||||||
|
{"G", "goto bottom"},
|
||||||
|
{"", ""},
|
||||||
|
{"ctrl+f", "jump down"},
|
||||||
|
{"ctrl+b", "jump up"},
|
||||||
|
}
|
||||||
|
|
||||||
|
s.commands2 = []Command{
|
||||||
|
{"p,P", "create pair/at parent"},
|
||||||
|
{"b,B", "create bucket/at parent"},
|
||||||
|
{"e", "edit value of pair"},
|
||||||
|
{"r", "rename pair/bucket"},
|
||||||
|
{"", ""},
|
||||||
|
{"", ""},
|
||||||
|
{"D", "delete item"},
|
||||||
|
{"x,X", "export as string/json to file"},
|
||||||
|
{"", ""},
|
||||||
|
{"?", "this screen"},
|
||||||
|
{"q", "quit program"},
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (screen *AboutScreen) handleKeyEvent(event termbox.Event) int {
|
func (s *aboutScreen) Update(msg wandle.Msg) wandle.Cmd {
|
||||||
return BrowserScreenIndex
|
switch msg := msg.(type) {
|
||||||
|
case AboutMsg:
|
||||||
|
return s.handleAboutMsg(msg)
|
||||||
|
case termbox.Event:
|
||||||
|
return s.handleTermboxEvent(msg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (screen *AboutScreen) performLayout() {}
|
func (s *aboutScreen) handleAboutMsg(msg AboutMsg) wandle.Cmd {
|
||||||
|
switch msg.source {
|
||||||
|
case ABT_CmdGotoBrowse:
|
||||||
|
return wandle.SwitchScreenCmd(s.ui.browseScreen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (screen *AboutScreen) drawScreen(style Style) {
|
func (s *aboutScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
defaultFg := style.defaultFg
|
|
||||||
defaultBg := style.defaultBg
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *aboutScreen) View(style wandle.Style) {
|
||||||
width, height := termbox.Size()
|
width, height := termbox.Size()
|
||||||
template := [...]string{
|
template := [...]string{
|
||||||
" _______ _______ ___ _______ _______ ______ _______ _ _ _______ _______ ______ ",
|
" _______ _______ ___ _______ _______ ______ _______ _ _ _______ _______ ______ ",
|
||||||
@ -65,7 +119,7 @@ func (screen *AboutScreen) drawScreen(style Style) {
|
|||||||
title := "BoltBrowser"
|
title := "BoltBrowser"
|
||||||
startY = 0
|
startY = 0
|
||||||
yPos = 0
|
yPos = 0
|
||||||
termboxUtil.DrawStringAtPoint(title, (width-len(title))/2, startY, style.titleFg, style.titleBg)
|
wandle.Print((width-len(title))/2, startY, style, title)
|
||||||
} else {
|
} else {
|
||||||
if height < 25 {
|
if height < 25 {
|
||||||
startY = 0
|
startY = 0
|
||||||
@ -74,12 +128,10 @@ func (screen *AboutScreen) drawScreen(style Style) {
|
|||||||
for _, line := range template {
|
for _, line := range template {
|
||||||
xPos = startX
|
xPos = startX
|
||||||
for _, runeValue := range line {
|
for _, runeValue := range line {
|
||||||
bg := defaultBg
|
|
||||||
displayRune := ' '
|
displayRune := ' '
|
||||||
if runeValue != ' ' {
|
if runeValue != ' ' {
|
||||||
//bg = termbox.Attribute(125)
|
|
||||||
displayRune = runeValue
|
displayRune = runeValue
|
||||||
termbox.SetCell(xPos, yPos, displayRune, defaultFg, bg)
|
termbox.SetCell(xPos, yPos, displayRune, style.Foreground, style.Background)
|
||||||
}
|
}
|
||||||
xPos++
|
xPos++
|
||||||
}
|
}
|
||||||
@ -87,47 +139,18 @@ func (screen *AboutScreen) drawScreen(style Style) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
yPos++
|
yPos++
|
||||||
versionString := fmt.Sprintf("Version: %0.1f", VersionNum)
|
versionString := fmt.Sprintf("Version: %0.1f", viper.GetFloat64("version"))
|
||||||
termboxUtil.DrawStringAtPoint(versionString, (width-len(versionString))/2, yPos, style.defaultFg, style.defaultBg)
|
wandle.Print((width-len(versionString))/2, yPos, style, versionString)
|
||||||
|
|
||||||
commands1 := []Command{
|
|
||||||
{"h,←", "close parent"},
|
|
||||||
{"j,↓", "down"},
|
|
||||||
{"k,↑", "up"},
|
|
||||||
{"l,→", "open item"},
|
|
||||||
{"J", "scroll right pane down"},
|
|
||||||
{"K", "scroll right pane up"},
|
|
||||||
{"", ""},
|
|
||||||
{"g", "goto top"},
|
|
||||||
{"G", "goto bottom"},
|
|
||||||
{"", ""},
|
|
||||||
{"ctrl+f", "jump down"},
|
|
||||||
{"ctrl+b", "jump up"},
|
|
||||||
}
|
|
||||||
|
|
||||||
commands2 := []Command{
|
|
||||||
{"p,P", "create pair/at parent"},
|
|
||||||
{"b,B", "create bucket/at parent"},
|
|
||||||
{"e", "edit value of pair"},
|
|
||||||
{"r", "rename pair/bucket"},
|
|
||||||
{"", ""},
|
|
||||||
{"", ""},
|
|
||||||
{"D", "delete item"},
|
|
||||||
{"x,X", "export as string/json to file"},
|
|
||||||
{"", ""},
|
|
||||||
{"?", "this screen"},
|
|
||||||
{"q", "quit program"},
|
|
||||||
}
|
|
||||||
var maxCmd1 int
|
var maxCmd1 int
|
||||||
for k := range commands1 {
|
for k := range s.commands1 {
|
||||||
tst := len(commands1[k].key) + 1 + len(commands1[k].description)
|
tst := len(s.commands1[k].key) + 1 + len(s.commands1[k].description)
|
||||||
if tst > maxCmd1 {
|
if tst > maxCmd1 {
|
||||||
maxCmd1 = tst
|
maxCmd1 = tst
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var maxCmd2 int
|
var maxCmd2 int
|
||||||
for k := range commands2 {
|
for k := range s.commands2 {
|
||||||
tst := len(commands2[k].key) + 1 + len(commands2[k].description)
|
tst := len(s.commands2[k].key) + 1 + len(s.commands2[k].description)
|
||||||
if tst > maxCmd2 {
|
if tst > maxCmd2 {
|
||||||
maxCmd2 = tst
|
maxCmd2 = tst
|
||||||
}
|
}
|
||||||
@ -135,12 +158,13 @@ func (screen *AboutScreen) drawScreen(style Style) {
|
|||||||
xPos = (width / 2) - ((maxCmd1 + maxCmd2) / 2)
|
xPos = (width / 2) - ((maxCmd1 + maxCmd2) / 2)
|
||||||
yPos++
|
yPos++
|
||||||
|
|
||||||
for k := range commands1 {
|
for k, cmd := range s.commands1 {
|
||||||
drawCommandAtPoint(commands1[k], xPos, yPos+1+k, style)
|
wandle.Print(xPos, yPos+1+k, style, fmt.Sprintf("%6s %s", cmd.key, cmd.description))
|
||||||
}
|
}
|
||||||
for k := range commands2 {
|
for k, cmd := range s.commands2 {
|
||||||
drawCommandAtPoint(commands2[k], xPos+40, yPos+1+k, style)
|
wandle.Print(xPos+40, yPos+1+k, style, fmt.Sprintf("%6s %s", cmd.key, cmd.description))
|
||||||
}
|
}
|
||||||
exitTxt := "Press any key to return to browser"
|
exitTxt := "Press any key to return to browser"
|
||||||
termboxUtil.DrawStringAtPoint(exitTxt, (width-len(exitTxt))/2, height-1, style.titleFg, style.titleBg)
|
st := style.Bold(true)
|
||||||
|
wandle.Print((width-len(exitTxt))/2, height-1, st, exitTxt)
|
||||||
}
|
}
|
75
ui/screen_browse.go
Normal file
75
ui/screen_browse.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
|
"git.bullercodeworks.com/brian/widdles"
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
//BS_CmdGotoAbout = BrowseId | iota
|
||||||
|
)
|
||||||
|
|
||||||
|
type BrowseMsg struct {
|
||||||
|
source int
|
||||||
|
data interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type browseScreen struct {
|
||||||
|
ui *Ui
|
||||||
|
dbPath string
|
||||||
|
|
||||||
|
menu *widdles.TopMenu
|
||||||
|
status *widdles.Text
|
||||||
|
statusTimeout time.Duration
|
||||||
|
statusTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBrowseScreen(u *Ui) *browseScreen {
|
||||||
|
w, h := termbox.Size()
|
||||||
|
return &browseScreen{
|
||||||
|
ui: u,
|
||||||
|
menu: widdles.NewTopMenu(0, 0, w),
|
||||||
|
status: widdles.NewText("", 0, h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) Init() wandle.Cmd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) Update(msg wandle.Msg) wandle.Cmd {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case BrowseMsg:
|
||||||
|
return s.handleBrowseMsg(msg)
|
||||||
|
case termbox.Event:
|
||||||
|
return s.handleTermboxEvent(msg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) handleBrowseMsg(msg BrowseMsg) wandle.Cmd {
|
||||||
|
switch msg.source {
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
|
if (msg.Type == termbox.EventKey && msg.Key == termbox.KeyEsc) || s.menu.IsActive() {
|
||||||
|
return s.menu.Update(msg)
|
||||||
|
}
|
||||||
|
switch msg.Type {
|
||||||
|
case termbox.EventKey:
|
||||||
|
if msg.Ch == '?' {
|
||||||
|
return wandle.SwitchScreenCmd(NewAboutScreen(s.ui))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) View(style wandle.Style) {
|
||||||
|
}
|
31
ui/ui.go
Normal file
31
ui/ui.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BrowseId = iota << 5
|
||||||
|
AboutId
|
||||||
|
)
|
||||||
|
|
||||||
|
type Ui struct {
|
||||||
|
config Config
|
||||||
|
wandle *wandle.Program
|
||||||
|
|
||||||
|
browseScreen *browseScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUi() *Ui {
|
||||||
|
u := new(Ui)
|
||||||
|
u.browseScreen = NewBrowseScreen(u)
|
||||||
|
u.config = Config{
|
||||||
|
sep: string(os.PathSeparator),
|
||||||
|
}
|
||||||
|
u.wandle = wandle.NewProgram(u.browseScreen)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Ui) Start() error { return u.wandle.Start() }
|
46
util/bolt_helpers.go
Normal file
46
util/bolt_helpers.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToJsonString(b *bolt.Bucket) string {
|
||||||
|
ret := "{"
|
||||||
|
b.ForEach(func(k, v []byte) error {
|
||||||
|
ret = fmt.Sprintf("%s\"%s\":", ret, string(k))
|
||||||
|
if v == nil {
|
||||||
|
ret = fmt.Sprintf("%s%s,", ret, ToJsonString(b.Bucket(k)))
|
||||||
|
} else {
|
||||||
|
ret = fmt.Sprintf("%s\"%s\",", ret, string(v))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
ret = fmt.Sprintf("%s}", ret[:len(ret)-1])
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogToFile(s string) error {
|
||||||
|
return WriteToFile("bolt-log", s+"\n", os.O_RDWR|os.O_APPEND)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteToFile(fn, s string, mode int) error {
|
||||||
|
var f *os.File
|
||||||
|
var err error
|
||||||
|
if f == nil {
|
||||||
|
f, err = os.OpenFile(fn, mode, 0660)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = f.WriteString(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = f.Sync(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user