pax_global_header 0000666 0000000 0000000 00000000064 12621437732 0014521 g ustar 00root root 0000000 0000000 52 comment=33f0055e30b06157ae3cdc34915b79b2272896ba
flashbake-0.27.1/ 0000775 0000000 0000000 00000000000 12621437732 0013530 5 ustar 00root root 0000000 0000000 flashbake-0.27.1/.gitignore 0000664 0000000 0000000 00000000130 12621437732 0015512 0 ustar 00root root 0000000 0000000 *.pyc
.*.swp
build
dist
MANIFEST
flashbake.egg-info
.project
.pydevproject
.ropeproject
flashbake-0.27.1/COPYING.txt 0000664 0000000 0000000 00000107620 12621437732 0015407 0 ustar 00root root 0000000 0000000 ----- begin license block -----
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
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.
Copyright (C)
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 .
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:
Copyright (C)
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
.
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
.
----- end license block -----
Universal Feed Parser (feedparser.py), its testing harness (feedparsertest.py),
and its unit tests (everything in the tests/ directory) are released under the
following license:
----- begin license block -----
Copyright (c) 2002-2005, Mark Pilgrim
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
----- end license block -----
flashbake-0.27.1/CREDITS.txt 0000664 0000000 0000000 00000002034 12621437732 0015365 0 ustar 00root root 0000000 0000000 First and foremost, I want to thank Cory Doctorow. His simple inquiries into
using source control for his writing projects served as the original genesis
of this project.
I also want to thank all of the early adopters who helped improve the code and
the documentation by simply asking questions and using the software.
To date, I've also had a few contributors who have helped by submitting patches
and working directly on the project:
* Vaskin Kissoyan - docs, testing
* Ben Snider, bensnider.com - the original code for microblogs.py, docs
* garthrk, github.org/garthrk - random fixes and tests, docs
* Jason Penney, jasonpenney.net - brain storming, random fixes and enhancements
* Tony Giunta - alternate implementation of uptime that uses uptime command
I want to give a special thanks to Jonathan Coulton, Brad Turcotte and Beatnik
Turtle for freely offering their songs for download. Being able to grab a
handful of tracks and import them into Banshee let me build that plugin very
quickly, whislt listening to their awesome tunes to boot.
flashbake-0.27.1/plugins/ 0000775 0000000 0000000 00000000000 12621437732 0015211 5 ustar 00root root 0000000 0000000 flashbake-0.27.1/plugins/hellodolly.py 0000664 0000000 0000000 00000001712 12621437732 0017733 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
from flashbake.plugins import AbstractMessagePlugin
class HelloDolly(AbstractMessagePlugin):
""" Sample plugin. """
def addcontext(self, message_file, config):
""" Stub. """
message_file.write('Hello, dolly.\n')
flashbake-0.27.1/setup.py 0000664 0000000 0000000 00000003502 12621437732 0015242 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
#
# setup.py for flashbake
from setuptools import setup, find_packages
setup(name='flashbake',
version='0.27.1',
author="Thomas Gideon",
author_email="cmdln@thecommandline.net",
maintainer="Thomas Gideon",
maintainer_email="cmdln@thecommandline.net",
description="Automation to feed life log into version control message stream.",
long_description=""" Flashbake was designed to help technically savvy
writers use version control by compiling information from the variety of
sources that make up the user's life log and automating the inclusion of
that information in a commit stream, as part of the messages in the
history.""",
platforms=[ "noarch" ],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Console',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Operating System :: MacOS :: MacOS X',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Topic :: Artistic Software'
],
url="http://thecommandline.net",
download_url="https://github.com/commandline/flashbake/downloads",
license="GPLv3",
package_dir={'': 'src'},
packages=find_packages(where='./src/', exclude=('./test/')),
install_requires='''
enum34 >=1.0.3
feedparser >=4.1
''',
entry_points={
'console_scripts': [ 'flashbake = flashbake.console:main',
'flashbakeall = flashbake.console:multiple_projects' ]
},
include_package_data = True,
exclude_package_data = { '' : [ 'test/*' ] },
test_suite="test",
)
flashbake-0.27.1/src/ 0000775 0000000 0000000 00000000000 12621437732 0014317 5 ustar 00root root 0000000 0000000 flashbake-0.27.1/src/flashbake/ 0000775 0000000 0000000 00000000000 12621437732 0016237 5 ustar 00root root 0000000 0000000 flashbake-0.27.1/src/flashbake/__init__.py 0000664 0000000 0000000 00000037407 12621437732 0020363 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' __init__.py - Shared classes and functions for the flashbake package.'''
from flashbake.plugins import PluginError, PLUGIN_ERRORS
from flashbake.compat import relpath, next_, iglob
from types import *
import commands
import flashbake.plugins #@UnresolvedImport
import glob
import logging
import os
import os.path
import re
import sys #@Reimport
import __builtin__
__version__ = '0.27.1'
class ConfigError(Exception):
pass
class ControlConfig:
""" Accumulates options from a control file for use by the core modules as
well as for the plugins. Also handles boot strapping the configured
plugins. """
def __init__(self):
self.initialized = False
self.dry_run = False
self.extra_props = dict()
self.prop_types = dict()
self.plugin_names = list()
self.msg_plugins = list()
self.file_plugins = list()
self.notify_plugins = list()
self.git_path = None
self.git_origin = None
self.project_name = None
def capture(self, line):
''' Parse a line from the control file if it is relevant to plugin configuration. '''
# grab comments but don't do anything
if line.startswith('#'):
return True
# grab blanks but don't do anything
if len(line.strip()) == 0:
return True
if line.find(':') > 0:
prop_tokens = line.split(':', 1)
prop_name = prop_tokens[0].strip()
prop_value = prop_tokens[1].strip()
if 'plugins' == prop_name:
self.add_plugins(prop_value.split(','))
return True
# hang onto any extra propeties in case plugins use them
if not prop_name in self.__dict__:
self.extra_props[prop_name] = prop_value;
return True
try:
if prop_name in self.prop_types:
prop_value = self.prop_types[prop_name](prop_value)
self.__dict__[prop_name] = prop_value
except:
raise ConfigError(
'The value, %s, for option, %s, could not be parse as %s.'
% (prop_value, prop_name, self.prop_types[prop_name]))
return True
return False
def init(self):
""" Do any property clean up, after parsing but before use """
if self.initialized == True:
return
self.initialized = True
if len(self.plugin_names) == 0:
raise ConfigError('No plugins configured!')
self.share_property('git_path')
self.share_property('project_name')
all_plugins = list()
with_deps = dict()
for plugin_name in self.plugin_names:
logging.debug("initalizing plugin: %s" % plugin_name)
try:
plugin = self.create_plugin(plugin_name)
if len(plugin.dependencies()) == 0:
all_plugins.append(plugin)
else:
dep = Dependency(plugin)
dep.map(with_deps)
if isinstance(plugin, flashbake.plugins.AbstractMessagePlugin):
logging.debug("Message Plugin: %s" % plugin_name)
# TODO add notion of dependency for ordering
if 'flashbake.plugins.location:Location' == plugin_name:
self.msg_plugins.insert(0, plugin)
else:
self.msg_plugins.append(plugin)
if isinstance(plugin, flashbake.plugins.AbstractFilePlugin):
logging.debug("File Plugin: %s" % plugin_name)
self.file_plugins.append(plugin)
if isinstance(plugin, flashbake.plugins.AbstractNotifyPlugin):
logging.debug('Notify Plugin: %s' % plugin_name)
self.notify_plugins.append(plugin)
except PluginError, e:
# re-raise critical plugin error
if not e.reason == PLUGIN_ERRORS.ignorable_error: #@UndefinedVariable
raise e
# allow ignorable errors through with a warning
logging.warning('Skipping plugin, %s, ignorable error: %s' %
(plugin_name, e.name))
for plugin in all_plugins:
plugin.share_properties(self)
if plugin.plugin_spec in with_deps:
for dep in with_deps[plugin.plugin_spec]:
dep.satisfy(plugin, all_plugins)
if len(Dependency.all) > 0:
logging.error('Unsatisfied dependencies!')
for plugin in all_plugins:
plugin.capture_properties(self)
plugin.init(self)
def share_property(self, name, type=None):
""" Declare a shared property, this way multiple plugins can share some
value through the config object. """
if name in self.__dict__:
return
value = None
if name in self.extra_props:
value = self.extra_props[name]
del self.extra_props[name]
if type != None:
try:
value = type(value)
except:
raise ConfigError('Problem parsing %s for option %s'
% (name, value))
self.__dict__[name] = value
def add_plugins(self, plugin_names):
# use a comprehension to ensure uniqueness
[self.__add_last(inbound_name) for inbound_name in plugin_names]
def create_plugin(self, plugin_spec):
""" Initialize a plugin, including vetting that it meets the correct
protocol; not private so it can be used in testing. """
if plugin_spec.find(':') < 0:
logging.debug('Plugin spec not validly formed, %s.' % plugin_spec)
raise PluginError(PLUGIN_ERRORS.invalid_plugin, plugin_spec) #@UndefinedVariable
tokens = plugin_spec.split(':')
module_name = tokens[0]
plugin_name = tokens[1]
try:
__import__(module_name)
except ImportError:
logging.warn('Invalid module, %s' % plugin_name)
raise PluginError(PLUGIN_ERRORS.unknown_plugin, plugin_spec) #@UndefinedVariable
try:
plugin_class = self.__forname(module_name, plugin_name)
plugin = plugin_class(plugin_spec)
except Exception, e:
logging.debug(e)
logging.debug('Couldn\'t load class %s' % plugin_spec)
raise PluginError(PLUGIN_ERRORS.unknown_plugin, plugin_spec) #@UndefinedVariable
is_message_plugin = isinstance(plugin, flashbake.plugins.AbstractMessagePlugin)
is_file_plugin = isinstance(plugin, flashbake.plugins.AbstractFilePlugin)
is_notify_plugin = isinstance(plugin, flashbake.plugins.AbstractNotifyPlugin)
if not is_message_plugin and not is_file_plugin and not is_notify_plugin:
raise PluginError(PLUGIN_ERRORS.invalid_type, plugin_spec) #@UndefinedVariable
if is_message_plugin:
self.__checkattr(plugin_spec, plugin, 'connectable', bool)
self.__checkattr(plugin_spec, plugin, 'addcontext', MethodType)
if is_file_plugin:
self.__checkattr(plugin_spec, plugin, 'pre_process', MethodType)
if is_notify_plugin:
self.__checkattr(plugin_spec, plugin, 'warn', MethodType)
return plugin
def __add_last(self, plugin_name):
if plugin_name in self.plugin_names:
self.plugin_names.remove(plugin_name)
self.plugin_names.append(plugin_name)
def __checkattr(self, plugin_spec, plugin, name, expected_type):
try:
attrib = eval('plugin.%s' % name)
except AttributeError:
raise PluginError(PLUGIN_ERRORS.missing_attribute, plugin_spec, name) #@UndefinedVariable
if not isinstance(attrib, expected_type):
raise PluginError(PLUGIN_ERRORS.invalid_attribute, plugin_spec, name) #@UndefinedVariable
# with thanks to Ben Snider
# http://www.bensnider.com/2008/02/27/dynamically-import-and-instantiate-python-classes/
def __forname(self, module_name, plugin_name):
''' Returns a class of "plugin_name" from module "module_name". '''
__import__(module_name)
module = sys.modules[module_name]
classobj = getattr(module, plugin_name)
return classobj
class Dependency:
all = list()
def __init__(self, plugin):
self.plugin
self.dep_count = len(plugin.dependencies)
def map(self, dep_map):
for spec in self.plugin.dependencies():
if spec not in dep_map:
dep_map[spec] = list()
dep_map[spec].append(self)
def satisfy(self, plugin, all_plugins):
self.dep_count -= 1
if self.dep_count == 0:
pos = all_plugins.index(plugin)
all_plugins.insert(pos + 1)
all.remove(self)
class HotFiles:
"""
Track the files as they are parsed and manipulated with regards to their git
status and the dot-control file.
"""
def __init__(self, project_dir):
self.project_dir = os.path.realpath(project_dir)
self.linked_files = dict()
self.outside_files = set()
self.control_files = set()
self.not_exists = set()
self.to_add = set()
self.globs = dict()
self.deleted = set()
def addfile(self, filename):
to_expand = os.path.join(self.project_dir, filename)
file_exists = False
logging.debug('%s: %s'
% (filename, glob.glob(to_expand)))
pattern = re.compile('(\[.+\]|\*|\?)')
if pattern.search(filename):
glob_re = re.sub('\*', '.*', filename)
glob_re = re.sub('\?', '.', glob_re)
self.globs[filename] = glob_re
for expanded_file in iglob(to_expand):
# track whether iglob iterates at all, if it does not, then the line
# didn't expand to anything meaningful
if not file_exists:
file_exists = True
# skip the file if some previous glob hit it
if (expanded_file in self.outside_files
or expanded_file in self.linked_files.keys()):
continue
# the commit code expects a relative path
rel_file = self.__make_rel(expanded_file)
# skip the file if some previous glob hit it
if rel_file in self.control_files:
continue
# checking this after removing the expanded project directory
# catches absolute paths to files outside the project directory
if rel_file == expanded_file:
self.outside_files.add(expanded_file)
continue
link = self.__check_link(expanded_file)
if link == None:
self.control_files.add(rel_file)
else:
self.linked_files[expanded_file] = link
if not file_exists:
self.putabsent(filename)
def contains(self, filename):
return filename in self.control_files
def remove(self, filename):
if filename in self.control_files:
self.control_files.remove(filename)
def putabsent(self, filename):
self.not_exists.add(filename)
def putneedsadd(self, filename):
self.to_add.add(filename)
def put_deleted(self, filename):
def __in_target(file_spec):
return file_spec in self.not_exists
to_delete = self.from_glob(filename)
logging.debug('To delete after matching %s' % to_delete)
to_delete.append(filename)
to_delete = filter(__in_target, to_delete)
[self.not_exists.remove(file_spec) for file_spec in to_delete]
self.deleted.add(filename)
def from_glob(self, filename):
""" Returns any original glob-based file specifications from the control file that would match
the input filename. Useful for file plugins that add their own globs and need to correlate
actual files that match their globs. """
def __match(file_tuple):
return re.match(file_tuple[1], filename) != None
matches = filter(__match, self.globs.iteritems())
matches = dict(matches)
return matches.keys()
def warnproblems(self):
# print warnings for linked files
for filename in self.linked_files.keys():
logging.info('%s is a link or its directory path contains a link.' % filename)
# print warnings for files outside the project
for filename in self.outside_files:
logging.info('%s is outside the project directory.' % filename)
# print warnings for files that do not exists
for filename in self.not_exists:
logging.info('%s does not exist.' % filename)
# print warnings for files that were once under version control but have been deleted
for filename in self.deleted:
logging.info('%s has been deleted from version control.' % filename)
def addorphans(self, git_obj, control_config):
if len(self.to_add) == 0:
return
message_file = flashbake.context.buildmessagefile(control_config)
to_commit = list()
for orphan in self.to_add:
logging.debug('Adding %s.' % orphan)
add_output = git_obj.add(orphan)
logging.debug('Add output, %s' % add_output)
to_commit.append(orphan)
logging.info('Adding new files, %s.' % to_commit)
# consolidate the commit to be friendly to how git normally works
if not control_config.dry_run:
commit_output = git_obj.commit(message_file, to_commit)
logging.debug('Commit output, %s' % commit_output)
os.remove(message_file)
def needs_warning(self):
return (len(self.not_exists) > 0
or len(self.linked_files) > 0
or len(self.outside_files) > 0
or len(self.deleted) > 0)
def __check_link(self, filename):
# add, above, makes sure filename is always relative
if os.path.islink(filename):
return filename
directory = os.path.dirname(filename)
while (len(directory) > 0):
# stop at the project directory, if it is in the path
if directory == self.project_dir:
break
# stop at root, as a safety check though it should not happen
if directory == os.sep:
break
if os.path.islink(directory):
return directory
directory = os.path.dirname(directory)
return None
def __make_rel(self, filepath):
return self.__drop_prefix(self.project_dir, filepath)
def __drop_prefix(self, prefix, filepath):
return relpath(filepath, prefix)
def find_executable(executable):
ex_paths = (os.path.join(path, executable) for path in \
os.getenv('PATH').split(os.pathsep))
paths = (ex_path for ex_path in ex_paths \
if os.path.exists(ex_path))
return next_(paths, None)
def executable_available(executable):
return find_executable(executable) != None
flashbake-0.27.1/src/flashbake/commit.py 0000775 0000000 0000000 00000020123 12621437732 0020102 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' commit.py - Parses a project's control file and wraps git operations, calling the context
script to build automatic commit messages as needed.'''
import context
import datetime
import git
import logging
import os
import re
import sys
DELETED_RE = re.compile('#\s*deleted:.*')
def commit(control_config, hot_files, quiet_mins):
# change to the project directory, necessary to find the .flashbake file and
# to correctly refer to the project files by relative paths
os.chdir(hot_files.project_dir)
git_obj = git.Git(hot_files.project_dir, control_config.git_path)
# the wrapper object ensures git is on the path
# get the git status for the project
git_status = git_obj.status()
_handle_fatal(hot_files, git_status)
# in particular find the existing entries that need a commit
pending_re = re.compile('\s*(renamed|copied|modified|new file):.*')
now = datetime.datetime.today()
quiet_period = datetime.timedelta(minutes=quiet_mins)
to_commit = list()
# first look in the files git already knows about
logging.debug("Examining git status.")
for line in git_status.splitlines():
if pending_re.match(line):
pending_file = _trimgit(line)
# not in the dot-control file, skip it
if not (hot_files.contains(pending_file)):
continue
logging.debug('Parsing status line %s to determine commit action' % line)
# remove files that will be considered for commit
hot_files.remove(pending_file)
# check the quiet period against mtime
last_mod = os.path.getmtime(pending_file)
pending_mod = datetime.datetime.fromtimestamp(last_mod)
pending_mod += quiet_period
# add the file to the list to include in the commit
if pending_mod < now:
to_commit.append(pending_file)
logging.debug('Flagging file, %s, for commit.' % pending_file)
else:
logging.debug('Change for file, %s, is too recent.' % pending_file)
_capture_deleted(hot_files, line)
logging.debug('Examining unknown or unchanged files.')
hot_files.warnproblems()
# figure out what the status of the remaining files is
for control_file in hot_files.control_files:
# this shouldn't happen since HotFiles.addfile uses glob.iglob to expand
# the original file lines which does so based on what is in project_dir
if not os.path.exists(control_file):
logging.debug('%s does not exist yet.' % control_file)
hot_files.putabsent(control_file)
continue
status_output = git_obj.status(control_file)
# needed for git >= 1.7.0.4
if status_output.find('Untracked files') > 0:
hot_files.putneedsadd(control_file)
continue
if status_output.startswith('error'):
# needed for git < 1.7.0.4
if status_output.find('did not match') > 0:
hot_files.putneedsadd(control_file)
logging.debug('%s exists but is unknown by git.' % control_file)
else:
logging.error('Unknown error occurred!')
logging.error(status_output)
continue
# use a regex to match so we can enforce whole word rather than
# substring matchs, otherwise 'foo.txt~' causes a false report of an
# error
control_re = re.compile('\<' + re.escape(control_file) + '\>')
if control_re.search(status_output) == None:
logging.debug('%s has no uncommitted changes.' % control_file)
# if anything hits this block, we need to figure out why
else:
logging.error('%s is in the status message but failed other tests.' % control_file)
logging.error('Try \'git status "%s"\' for more info.' % control_file)
hot_files.addorphans(git_obj, control_config)
for plugin in control_config.file_plugins:
plugin.post_process(to_commit, hot_files, control_config)
if len(to_commit) > 0:
logging.info('Committing changes to known files, %s.' % to_commit)
message_file = context.buildmessagefile(control_config)
if not control_config.dry_run:
# consolidate the commit to be friendly to how git normally works
commit_output = git_obj.commit(message_file, to_commit)
logging.debug(commit_output)
os.remove(message_file)
_send_commit_notice(control_config, hot_files, to_commit)
logging.info('Commit for known files complete.')
else:
logging.info('No changes to known files found to commit.')
if hot_files.needs_warning():
_send_warning(control_config, hot_files)
else:
logging.info('No missing or untracked files found, not sending warning notice.')
def purge(control_config, hot_files):
# change to the project directory, necessary to find the .flashbake file and
# to correctly refer to the project files by relative paths
os.chdir(hot_files.project_dir)
git_obj = git.Git(hot_files.project_dir, control_config.git_path)
# the wrapper object ensures git is on the path
git_status = git_obj.status()
_handle_fatal(hot_files, git_status)
logging.debug("Examining git status.")
for line in git_status.splitlines():
_capture_deleted(hot_files, line)
if len(hot_files.deleted) > 0:
logging.info('Committing removal of known files, %s.' % hot_files.deleted)
message_file = context.buildmessagefile(control_config)
if not control_config.dry_run:
# consolidate the commit to be friendly to how git normally works
commit_output = git_obj.commit(message_file, hot_files.deleted)
logging.debug(commit_output)
os.remove(message_file)
logging.info('Commit for deleted files complete.')
else:
logging.info('No deleted files to purge')
def _capture_deleted(hot_files, line):
if DELETED_RE.match(line):
deleted_file = _trimgit(line)
# remove files that will are known to have been deleted
hot_files.remove(deleted_file)
hot_files.put_deleted(deleted_file)
def _handle_fatal(hot_files, git_status):
if git_status.startswith('fatal'):
logging.error('Fatal error from git.')
if 'fatal: Not a git repository' == git_status:
logging.error('Make sure "git init" was run in %s'
% os.path.realpath(hot_files.project_dir))
else:
logging.error(git_status)
sys.exit(1)
def _trimgit(status_line):
if status_line.find('->') >= 0:
tokens = status_line.split('->')
return tokens[1].strip()
tokens = status_line.split(':')
return tokens[1].strip()
def _send_warning(control_config, hot_files):
if (len(control_config.notify_plugins) == 0
and not control_config.dry_run):
logging.info('Skipping notice, no notify plugins configured.')
return
for plugin in control_config.notify_plugins:
plugin.warn(hot_files, control_config)
def _send_commit_notice(control_config, hot_files, to_commit):
if (len(control_config.notify_plugins) == 0
and not control_config.dry_run):
logging.info('Skipping notice, no notify plugins configured.')
return
for plugin in control_config.notify_plugins:
plugin.notify_commit(to_commit, hot_files, control_config)
flashbake-0.27.1/src/flashbake/compat.py 0000664 0000000 0000000 00000005571 12621437732 0020104 0 ustar 00root root 0000000 0000000 # copyright 2009-2011 Thomas Gideon, Jason Penney
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' compat.py - compatability layer for different versions of python '''
import os.path
import __builtin__
import sys
__all__ = [ 'relpath', 'next_', 'iglob', 'MIMEText' ]
relpath = None
next_ = None
try:
from glob import iglob
except ImportError:
from glob import glob as iglob
try:
import cPickle as pickle
except ImportError:
import pickle
# Import the email modules we'll need
if sys.hexversion < 0x2050000:
from email.MIMEText import MIMEText #@UnusedImport
else:
from email.mime.text import MIMEText #@Reimport
def __fallback_relpath(path, start='.'):
"""Returns a relative version of the path."""
path = os.path.realpath(path)
start = os.path.realpath(start)
if not path.startswith(start):
raise Exception("unable to calculate paths")
if os.path.samefile(path, start):
return "."
if not start.endswith(os.path.sep):
start += os.path.sep
return path[len(start):]
def __fallback_next(*args):
"""next_(iterator[, default])
Return the next item from the iterator. If default is given and
the iterator is exhausted, it is returned instead of
raising StopIteration."""
args_len = len(args)
if (args_len < 1):
raise TypeError("expected at least 1 argument, got %d" %
args_len)
if (args_len > 2):
raise TypeError("expected at most 2 arguments, got %d" %
args_len)
iterator = args[0]
try:
if hasattr(iterator, '__next__'):
return iterator.__next__()
elif hasattr(iterator, 'next'):
return iterator.next()
else:
raise TypeError('%s object is not an iterator'
% type(iterator).__name__)
except StopIteration:
if args_len == 2:
return args[1]
raise
# relpath
if hasattr(os.path, "relpath"):
relpath = os.path.relpath
else:
try:
import pathutils #@UnresolvedImport
relpath = pathutils.relative
except:
relpath = __fallback_relpath
#next_
if hasattr(__builtin__, 'next'):
next_=__builtin__.next
else:
next_ = __fallback_next
flashbake-0.27.1/src/flashbake/console.py 0000775 0000000 0000000 00000026357 12621437732 0020273 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
''' flashbake - wrapper script that will get installed by setup.py into the execution path '''
# copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
from flashbake import commit, context, control
from flashbake.plugins import PluginError, PLUGIN_ERRORS
from optparse import OptionParser
from os.path import join, realpath
import flashbake.git
import fnmatch
import logging
import os.path
import sys
VERSION = flashbake.__version__
pattern = '.flashbake'
def main():
''' Entry point used by the setup.py installation script. '''
# handle options and arguments
parser = _build_main_parser()
(options, args) = parser.parse_args()
if options.quiet and options.verbose:
parser.error('Cannot specify both verbose and quiet')
# configure logging
level = logging.INFO
if options.verbose:
level = logging.DEBUG
if options.quiet:
level = logging.ERROR
logging.basicConfig(level=level,
format='%(message)s')
home_dir = os.path.expanduser('~')
# look for plugin directory
_load_plugin_dirs(options, home_dir)
if len(args) < 1:
parser.error('Must specify project directory.')
sys.exit(1)
project_dir = args[0]
# look for user's default control file
hot_files, control_config = _load_user_control(home_dir, project_dir, options)
# look for project control file
control_file = _find_control(parser, project_dir)
if None == control_file:
sys.exit(1)
# emit the context message and exit
if options.context_only:
sys.exit(_context_only(options, project_dir, control_file, control_config, hot_files))
quiet_period = 0
if len(args) == 2:
try:
quiet_period = int(args[1])
except:
parser.error('Quiet minutes, "%s", must be a valid number.' % args[1])
sys.exit(1)
try:
(hot_files, control_config) = control.parse_control(project_dir, control_file, control_config, hot_files)
control_config.context_only = options.context_only
control_config.dry_run = options.dryrun
if (options.dryrun):
logging.info('========================================')
logging.info('!!! Running in dry run mode. !!!')
logging.info('!!! No changes will be committed. !!!')
logging.info('========================================\n\n')
(hot_files, control_config) = control.prepare_control(hot_files, control_config)
if options.purge:
commit.purge(control_config, hot_files)
else:
commit.commit(control_config, hot_files, quiet_period)
if (options.dryrun):
logging.info('\n\n========================================')
logging.info('!!! Running in dry run mode. !!!')
logging.info('!!! No changes will be committed. !!!')
logging.info('========================================')
except (flashbake.git.VCError, flashbake.ConfigError), error:
logging.error('Error: %s' % str(error))
sys.exit(1)
except PluginError, error:
_handle_bad_plugin(error)
sys.exit(1)
def multiple_projects():
parser = _build_multi_parser()
(options, args) = parser.parse_args()
if len(args) < 1:
parser.error('Must specify root search directory.')
sys.exit(1)
flashbake_opts = options.flashbake_options.split()
# verify --options will pass to main flashbake program
test_argv = sys.argv[0:1] + flashbake_opts + ['.'] + args[1:]
main_parser = _build_main_parser()
main_parser.suppress_exit = True
try:
(test_options, test_args) = main_parser.parse_args(test_argv)
except ParserError, err:
msg = "error with arguments passed to main flashbake: %s\n%s" % (
"'" + "' '".join(
flashbake_opts + [''] + args[1:]) + "'",
err.msg.replace(parser.get_prog_name() + ':', '> '))
parser.exit(err.code, msg)
exit_code = 0
for project in _locate_projects(args[0]):
print "project: %s" % project
sys.argv = sys.argv[0:1] + flashbake_opts + [project] + args[1:]
try:
main()
except SystemExit, err:
if err.code != 0:
exit_code = err.code
logging.error("Error: 'flashbake' had an error for '%s'"
% project)
sys.exit(exit_code)
def _locate_projects(root):
for path, dirs, files in os.walk(root): #@UnusedVariable
for project_path in (
os.path.normpath(path) for filename in files \
if fnmatch.fnmatch(filename, pattern)):
yield project_path
class ParserError(RuntimeError):
def __init__(self, code=0, msg=''):
RuntimeError.__init__(self, code, msg)
def _get_msg(self):
return self.args[1]
def _get_code(self):
return self.args[0]
msg = property(_get_msg)
code = property(_get_code)
class FlashbakeOptionParser(OptionParser):
def __init__(self, *args, **kwargs):
OptionParser.__init__(self, *args, **kwargs)
self.suppress_exit = False
def print_usage(self, file=None):
if not self.suppress_exit:
OptionParser.print_usage(self, file)
def exit(self, status=0, msg=None):
if self.suppress_exit:
raise ParserError(status, msg)
else:
OptionParser.exit(self, status, msg)
def _build_main_parser():
usage = "usage: %prog [options] [quiet_min]"
parser = FlashbakeOptionParser(
usage=usage, version='%s %s' % ('%prog', VERSION))
parser.add_option('-c', '--context', dest='context_only',
action='store_true', default=False,
help='just generate and show the commit message, don\'t check for changes')
parser.add_option('-v', '--verbose', dest='verbose',
action='store_true', default=False,
help='include debug information in the output')
parser.add_option('-q', '--quiet', dest='quiet',
action='store_true', default=False,
help='disable all output excepts errors')
parser.add_option('-d', '--dryrun', dest='dryrun',
action='store_true', default=False,
help='execute a dry run')
parser.add_option('-p', '--plugins', dest='plugin_dir',
action='store', type='string', metavar='PLUGIN_DIR',
help='specify an additional location for plugins')
parser.add_option('-r', '--purge', dest='purge',
action='store_true', default=False,
help='purge any files that have been deleted from source control')
return parser
def _build_multi_parser():
usage = "usage: %prog [options] [quiet_min]"
parser = FlashbakeOptionParser(
usage=usage, version='%s %s' % ('%prog', VERSION))
parser.add_option('-o', '--options', dest='flashbake_options', default='',
action='store', type='string', metavar='FLASHBAKE_OPTS',
help=("options to pass through to the 'flashbake' "
"command. Use quotes to pass multiple arguments."))
return parser
def _load_plugin_dirs(options, home_dir):
plugin_dir = join(home_dir, '.flashbake', 'plugins')
if os.path.exists(plugin_dir):
real_plugin_dir = realpath(plugin_dir)
logging.debug('3rd party plugin directory exists, adding: %s' % real_plugin_dir)
sys.path.insert(0, real_plugin_dir)
else:
logging.debug('3rd party plugin directory doesn\'t exist, skipping.')
logging.debug('Only stock plugins will be available.')
if options.plugin_dir != None:
if os.path.exists(options.plugin_dir):
logging.debug('Adding plugin directory, %s.' % options.plugin_dir)
sys.path.insert(0, realpath(options.plugin_dir))
else:
logging.warn('Plugin directory, %s, doesn\'t exist.' % options.plugin_dir)
def _load_user_control(home_dir, project_dir, options):
control_file = join(home_dir, '.flashbake', 'config')
if os.path.exists(control_file):
(hot_files, control_config) = control.parse_control(project_dir, control_file)
control_config.context_only = options.context_only
else:
hot_files = None
control_config = None
return hot_files, control_config
def _find_control(parser, project_dir):
control_file = join(project_dir, '.flashbake')
# look for .control for backwards compatibility
if not os.path.exists(control_file):
control_file = join(project_dir, '.control')
if not os.path.exists(control_file):
parser.error('Could not find .flashbake or .control file in directory, "%s".' % project_dir)
return None
else:
return control_file
def _context_only(options, project_dir, control_file, control_config, hot_files):
try:
(hot_files, control_config) = control.parse_control(project_dir, control_file, control_config, hot_files)
control_config.context_only = options.context_only
(hot_files, control_config) = control.prepare_control(hot_files, control_config)
msg_filename = context.buildmessagefile(control_config)
message_file = open(msg_filename, 'r')
try:
for line in message_file:
print line.strip()
finally:
message_file.close()
os.remove(msg_filename)
return 0
except (flashbake.git.VCError, flashbake.ConfigError), error:
logging.error('Error: %s' % str(error))
return 1
except PluginError, error:
_handle_bad_plugin(error)
return 1
def _handle_bad_plugin(plugin_error):
logging.debug('Plugin error, %s.' % plugin_error)
if plugin_error.reason == PLUGIN_ERRORS.unknown_plugin or plugin_error.reason == PLUGIN_ERRORS.invalid_plugin: #@UndefinedVariable
logging.error('Cannot load plugin, %s.' % plugin_error.plugin_spec)
return
if plugin_error.reason == PLUGIN_ERRORS.missing_attribute: #@UndefinedVariable
logging.error('Plugin, %s, doesn\'t have the needed plugin attribute, %s.' \
% (plugin_error.plugin_spec, plugin_error.name))
return
if plugin_error.reason == PLUGIN_ERRORS.invalid_attribute: #@UndefinedVariable
logging.error('Plugin, %s, has an invalid plugin attribute, %s.' \
% (plugin_error.plugin_spec, plugin_error.name))
return
if plugin_error.reason == PLUGIN_ERRORS.missing_property:
logging.error('Plugin, %s, requires the config option, %s, but it was missing.' \
% (plugin_error.plugin_spec, plugin_error.name))
return
flashbake-0.27.1/src/flashbake/context.py 0000775 0000000 0000000 00000003637 12621437732 0020311 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' context.py - Build up some descriptive context for automatic commit to git'''
import os.path
import random
def buildmessagefile(config):
""" Build a commit message that uses the provided ControlConfig object and
return a reference to the resulting file. """
config.init()
msg_filename = '/tmp/git_msg_%d' % random.randint(0,1000)
# try to avoid clobbering another process running this script
while os.path.exists(msg_filename):
msg_filename = '/tmp/git_msg_%d' % random.randint(0,1000)
connectable = False
connected = False
message_file = open(msg_filename, 'w')
try:
for plugin in config.msg_plugins:
plugin_success = plugin.addcontext(message_file, config)
# let each plugin say which ones attempt network connections
if plugin.connectable:
connectable = True
connected = connected or plugin_success
if connectable and not connected:
message_file.write('All of the plugins that use the network failed.\n')
message_file.write('Your computer may not be connected to the network.')
finally:
message_file.close()
return msg_filename
flashbake-0.27.1/src/flashbake/control.py 0000664 0000000 0000000 00000003641 12621437732 0020275 0 ustar 00root root 0000000 0000000 '''
Created on Aug 3, 2009
control.py - control file parsing and preparation.
@author: cmdln
'''
# copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
import flashbake
import logging
def parse_control(project_dir, control_file, config=None, results=None):
""" Parse the dot-control file to get config options and hot files. """
logging.debug('Checking %s' % control_file)
if None == results:
hot_files = flashbake.HotFiles(project_dir)
else:
hot_files = results
if None == config:
control_config = flashbake.ControlConfig()
else:
control_config = config
control_file = open(control_file, 'r')
try:
for line in control_file:
# skip anything else if the config consumed the line
if control_config.capture(line):
continue
hot_files.addfile(line.strip())
finally:
control_file.close()
return (hot_files, control_config)
def prepare_control(hot_files, control_config):
control_config.init()
logging.debug("loading file plugins")
for plugin in control_config.file_plugins:
logging.debug("running plugin %s" % plugin)
plugin.pre_process(hot_files, control_config)
return (hot_files, control_config)
flashbake-0.27.1/src/flashbake/git.py 0000664 0000000 0000000 00000007431 12621437732 0017401 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' git.py - Wrap the call outs to git, adding sanity checks and environment set up if
needed.'''
import logging
import os
import subprocess
class VCError(Exception):
""" Error when the version control wrapper object cannot be set up. """
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class Git:
def __init__(self, cwd, git_path=None):
# look for git in the environment's PATH var
path_env = os.getenv('PATH')
if (len(path_env) == 0):
path_env = os.defpath
path_tokens = path_env.split(os.pathsep)
git_exists = False
# if there is a git_path option, that takes precedence
if git_path != None:
if git_path.endswith('git'):
git_path = os.path.dirname(git_path)
if os.path.exists(os.path.join(git_path, 'git')):
git_exists = True
else:
for path_token in path_tokens:
if os.path.exists(os.path.join(path_token, 'git')):
git_exists = True
# fail much sooner and more quickly then if git calls are made later,
# naively assuming it is available
if not git_exists:
raise VCError('Could not find git executable on PATH.')
# set up an environment mapping suitable for use with the subprocess
# module
self.__init_env(git_path)
self.__cwd = cwd
def status(self, filename=None):
""" Get the git status for the specified files, or the entire current
directory. """
if filename != None:
files = list()
files.append(filename)
return self.__run('status', files=files)
else:
return self.__run('status')
def add(self, file):
""" Add an unknown but existing file. """
files = [ file ]
return self.__run('add', files=files)
def commit(self, messagefile, files):
""" Commit a list of files, the files should be strings and quoted. """
options = ['-F', messagefile]
return self.__run('commit', options, files)
def __run(self, cmd, options=None, files=None):
cmds = list()
cmds.append('git')
cmds.append(cmd)
if options != None:
cmds += options
if files != None:
cmds += files
proc = subprocess.Popen(cmds, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, cwd=self.__cwd, env=self.env)
return proc.communicate()[0]
def __init_env(self, git_path):
self.env = dict()
self.env.update(os.environ)
if git_path != None:
new_path = self.env['PATH']
new_path = '%s%s%s' % (git_path, os.pathsep, new_path)
self.env['PATH'] = new_path
if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG,
format='%(message)s')
git = Git('../foo', '/opt/local/bin')
try:
git = Git('../foo')
except VCError, e:
logging.info(e)
os.chdir('../foo')
logging.info(git.status())
flashbake-0.27.1/src/flashbake/plugins/ 0000775 0000000 0000000 00000000000 12621437732 0017720 5 ustar 00root root 0000000 0000000 flashbake-0.27.1/src/flashbake/plugins/__init__.py 0000664 0000000 0000000 00000014361 12621437732 0022036 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
from enum import Enum
import flashbake
import logging
class PLUGIN_ERRORS(Enum):
invalid_plugin = 1
invalid_type = 2
unknown_plugin = 3
missing_attribute = 4
invalid_attribute = 5
missing_property = 6
ignorable_error = 7
def __str__(self):
return self._name_
class PluginError(Exception):
def __init__(self, reason, plugin_spec, name=None):
self.plugin_spec = plugin_spec
self.reason = reason
self.name = name
def __str__(self):
if self.name == None:
return '%s: %s' % (self.reason, self.plugin_spec)
else:
return '%s, %s: %s' % (self.plugin_spec, self.reason, self.name)
def service_and_prefix(plugin_spec):
service_name = plugin_spec.split(':')[-1]
property_prefix = '_'.join(service_name.lower().strip().split(' '))
return service_name, property_prefix
class AbstractPlugin:
""" Common parent for all kinds of plugins, mostly to share option handling
code. """
def __init__(self, plugin_spec):
self.plugin_spec = plugin_spec
self.service_name, self.property_prefix = service_and_prefix(plugin_spec)
self.__property_defs = []
self.__shared_prop_defs = []
def define_property(self, name, type=None, required=False, default=None):
try:
self.__property_defs.append((name, type, required, default))
except AttributeError:
raise Exception('Call AbstractPlugin.__init__ in your plugin\'s __init__.')
def share_property(self, name, type=None, plugin_spec=None):
try:
if plugin_spec:
parsed = service_and_prefix(plugin_spec)
property_prefix = parsed[1]
self.__shared_prop_defs.append(('%s_%s' % (property_prefix, name), type))
else:
self.__shared_prop_defs.append((name, type))
except AttributeError:
raise Exception('Call AbstractPlugin.__init__ in your plugin\'s __init__.')
def share_properties(self, config):
for name, type in self.__shared_prop_defs:
config.share_property(name, type)
def capture_properties(self, config):
try:
for prop in self.__property_defs:
assert len(prop) == 4, "Property definition, %s, is invalid" % (prop,)
self.__capture_property(config, *prop)
except AttributeError:
raise Exception('Call AbstractPlugin.__init__ in your plugin\'s __init__.')
def init(self, config):
""" This method is optional. """
pass
def dependencies(self):
""" Optional method via which a plugin can express a dependency on another plugin. """
return list()
def __capture_property(self, config, name, type=None, required=False, default=None):
""" Move a property, if present, from the ControlConfig to the daughter
plugin. """
config_name = '%s_%s' % (self.property_prefix, name)
if required and not config_name in config.extra_props:
raise PluginError(PLUGIN_ERRORS.missing_property, self.plugin_spec, config_name)
value = default
if config_name in config.extra_props:
value = config.extra_props[config_name]
del config.extra_props[config_name]
if type != None and value != None:
try:
value = type(value)
except:
raise flashbake.ConfigError(
'The value, %s, for option, %s, could not be parsed as %s.'
% (value, name, type))
self.__dict__[name] = value
def abstract(self):
""" borrowed this from Norvig
http://norvig.com/python-iaq.html """
import inspect
caller = inspect.getouterframes(inspect.currentframe())[1][3]
raise NotImplementedError('%s must be implemented in subclass' % caller)
class AbstractMessagePlugin(AbstractPlugin):
""" Common parent class for all message plugins, will try to help enforce
the plugin protocol at runtime. """
def __init__(self, plugin_spec, connectable=False):
AbstractPlugin.__init__(self, plugin_spec)
self.connectable = connectable
def addcontext(self, message_file, config):
""" This method is required, it will asplode if not overridden by
daughter classes. """
self.abstract()
class AbstractFilePlugin(AbstractPlugin):
""" Common parent class for all file plugins, will try to help enforce
the plugin protocol at runtime. """
def pre_process(self, hot_files, config):
""" This method is required, it will asplode if not overridden by
daughter classes. """
self.abstract()
def post_process(self, to_commit, hot_files, config):
""" This method is optional, it will be run after status processing but before commit so the
plugin may shuffle files into the commit. """
pass
class AbstractNotifyPlugin(AbstractPlugin):
""" Common parent class for all notification plugins. """
def warn(self, hot_files, config):
''' Implementations will provide messages about the problem files in the
hot_files argument through different mechanisms.
N.B. This method is required, it will asplode if not overridden by
daughter classes. '''
self.abstract()
def notify_commit(self, to_commit, hot_files, config):
''' Option method to notify when a commit is performed, probably most useful
for services like desktop notifiers. '''
pass
flashbake-0.27.1/src/flashbake/plugins/current_track.scpt 0000664 0000000 0000000 00000003405 12621437732 0023463 0 ustar 00root root 0000000 0000000 if checkProcess("iTunes") then
tell application "iTunes"
if player state is playing then
set trck to current track
set title_text to (get name of trck)
set artist_text to (get artist of trck)
set album_text to (get album of trck)
set playpos to (get player position)
set displayTime to (my calc_total_time(playpos))
set title_time to (get time of trck)
set rate to (get rating of trck) / 20
set rate_text to ""
repeat rate times
set rate_text to rate_text & " * "
end repeat
set body_text to title_text & "
" & artist_text & " - " & album_text & "
" & displayTime & "/" & title_time & " - " & rate_text
else
set body_text to "Nothing playing in iTunes"
end if
end tell
else
set body_text to "iTunes is not open"
end if
----------------------------------------------------------------
to calc_total_time(totalSeconds)
set theHour to totalSeconds div 3600
if theHour is not 0 then
copy (theHour as string) & ":" to theHour
else
set theHour to ""
end if
set theMinutes to (totalSeconds mod 3600) div 60
if theMinutes is not 0 then
--if theMinutes is less than 10 then set theMinutes to "0" & (theMinutes as string)
copy (theMinutes as string) & ":" to theMinutes
else
set theMinutes to "0:"
end if
set theSeconds to totalSeconds mod 60
if theSeconds is less than 10 then set theSeconds to "0" & (theSeconds as string)
return theHour & theMinutes & theSeconds as string
end calc_total_time
----------------------------------------------------------------
on checkProcess(processName)
tell application "System Events"
set isRunning to ((application processes whose (name is equal to processName)) count)
end tell
if isRunning is greater than 0 then
return true
else
return false
end if
end checkProcess flashbake-0.27.1/src/flashbake/plugins/default.py 0000664 0000000 0000000 00000002430 12621437732 0021715 0 ustar 00root root 0000000 0000000 # copyright 2010 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' default.py - Stock plugin to add in some statically configured text into a commit message.'''
from flashbake.plugins import AbstractMessagePlugin
import flashbake
class Default(AbstractMessagePlugin):
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec, False)
self.define_property('message')
def addcontext(self, message_file, config):
""" Add a static message to the commit context. """
if self.message is not None:
message_file.write('%s\n' % self.message)
return True
flashbake-0.27.1/src/flashbake/plugins/feed.py 0000664 0000000 0000000 00000006667 12621437732 0021214 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' feed.py - Stock plugin that pulls latest n items from a feed by a given author. '''
import feedparser
import logging
from urllib2 import HTTPError, URLError
from flashbake.plugins import AbstractMessagePlugin
class Feed(AbstractMessagePlugin):
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec, True)
self.define_property('url', required=True)
self.define_property('author')
self.define_property('limit', int, False, 5)
def addcontext(self, message_file, config):
""" Add the matching items to the commit context. """
# last n items for m creator
(title, last_items) = self.__fetchfeed()
if len(last_items) > 0:
if self.author == None:
message_file.write('Last %(item_count)d entries from %(feed_title)s:\n'\
% {'item_count' : len(last_items), 'feed_title' : title})
else:
message_file.write('Last %(item_count)d entries from %(feed_title)s by %(author)s:\n'\
% {'item_count' : len(last_items), 'feed_title' : title, 'author': self.author})
for item in last_items:
# edit the '%s' if you want to add a label, like 'Title %s' to the output
message_file.write('%s\n' % item['title'])
message_file.write('%s\n' % item['link'])
else:
message_file.write('Couldn\'t fetch entries from feed, %s.\n' % self.url)
return len(last_items) > 0
def __fetchfeed(self):
""" Fetch up to the limit number of items from the specified feed with the specified
creator. """
try:
feed = feedparser.parse(self.url)
if not 'title' in feed.feed:
logging.info('Feed title is empty, feed is either malformed or unavailable.')
return (None, {})
feed_title = feed.feed.title
by_creator = []
for entry in feed.entries:
if self.author != None and entry.author != self.author:
continue
title = entry.title
title = title.encode('ascii', 'replace')
link = entry.link
by_creator.append({"title" : title, "link" : link})
if self.limit <= len(by_creator):
break
return (feed_title, by_creator)
except HTTPError, e:
logging.error('Failed with HTTP status code %d' % e.code)
return (None, {})
except URLError, e:
logging.error('Plugin, %s, failed to connect with network.' % self.__class__)
logging.debug('Network failure reason, %s.' % e.reason)
return (None, {})
flashbake-0.27.1/src/flashbake/plugins/growl.py 0000664 0000000 0000000 00000012235 12621437732 0021427 0 ustar 00root root 0000000 0000000 # copyright 2009 Jason Penney
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
# growl.py - Growl notification flashbake plugin
from flashbake import plugins
import flashbake
import logging
import os
import re
import subprocess
class Growl(plugins.AbstractNotifyPlugin):
def __init__(self, plugin_spec):
plugins.AbstractPlugin.__init__(self, plugin_spec)
self.define_property('host')
self.define_property('port')
self.define_property('password')
self.define_property('growlnotify')
self.define_property('title_prefix', default='fb:')
def init(self, config):
if self.growlnotify == None:
self.growlnotify = flashbake.find_executable('growlnotify')
if self.growlnotify == None:
raise plugins.PluginError(plugins.PLUGIN_ERRORS.ignorable_error, #@UndefinedVariable
self.plugin_spec,
'Could not find command, growlnotify.')
# TODO: use netgrowl.py (or wait for GNTP support to be finalized
# so it will support Growl for Windows as well)
def growl_notify(self, title, message):
args = [ self.growlnotify, '--name', 'flashbake' ]
if self.host != None:
args += [ '--udp', '--host', self.host]
if self.port != None:
args += [ '--port', self.port ]
if self.password != None:
args += [ '--password', self.password ]
title = ' '.join([self.title_prefix, title])
args += ['--message', message, '--title', title]
subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
close_fds=True)
def warn(self, hot_files, config):
''' Emits one message per file, with less explanation than the email plugin.
The most common case is that one or two files will be off, a large number
of them can be considered pathological, e.g. someone who didn't read the
documentation about lack of support for symlinks, for instance. '''
# if calling growl locally, then the current user must be logged into the console
if self.host == None and not self.__active_console():
logging.debug('Current user does not have console access.')
return
logging.debug('Trying to warn via growl.')
project_name = os.path.basename(hot_files.project_dir)
[self.growl_notify('Missing in project, %s' % project_name,
'The file, "%s", is missing.' % file)
for file in hot_files.not_exists]
[self.growl_notify('Deleted in project, %s' % project_name,
'The file, "%s", has been deleted from version control.' % file)
for file in hot_files.deleted]
[self.growl_notify('Link in project, %s' % project_name,
'The file, "%s", is a link.' % file)
for (file, link) in hot_files.linked_files.iteritems()
if file == link]
[self.growl_notify('Link in project, %s' % project_name,
'The file, "%s", is a link to %s.' % (link, file))
for (file, link) in hot_files.linked_files.iteritems()
if file != link]
[self.growl_notify('External file in project, %s' % project_name,
'The file, "%s", exists outside of the project directory.' % file)
for file in hot_files.outside_files]
def notify_commit(self, to_commit, hot_files, config):
logging.debug('Trying to notify via growl.')
self.growl_notify(os.path.basename(hot_files.project_dir),
'Tracking changes to:\n' + '\n'.join(to_commit))
def __whoami(self):
cmd = flashbake.find_executable('whoami')
if cmd:
proc = subprocess.Popen([cmd], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
return proc.communicate()[0].strip()
else:
return None
def __active_console(self):
user = self.__whoami()
if not user:
return False
cmd = flashbake.find_executable('who')
if not cmd:
return False
proc = subprocess.Popen([cmd], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
active = False
for line in proc.communicate()[0].splitlines():
m = re.match('^%s\s+console.*$' % user, line)
if m:
active = True
break
return active
flashbake-0.27.1/src/flashbake/plugins/lastfm.py 0000664 0000000 0000000 00000004247 12621437732 0021567 0 ustar 00root root 0000000 0000000 # copyright 2011 Og Maciel
# copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' lastfm.py - Plugin that pulls latest n items from your last.fm account. '''
import logging
import urllib
import json
from flashbake.plugins import AbstractMessagePlugin
LASTFM = "http://ws.audioscrobbler.com/2.0/?method="
PLUGIN_SPEC = 'flashbake.plugins.lastfm:LastFM'
class LastFM(AbstractMessagePlugin):
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec, True)
self.define_property('user_name', required=True)
self.define_property('api_key', required=True)
self.define_property('limit', int, False, 5)
def addcontext(self, message_file, config):
""" Add the matching items to the commit context. """
# last n items for m creator
url = "%suser.getrecentTracks&user=%s&api_key=%s&limit=%s&format=json" % (LASTFM, self.user_name, self.api_key, self.limit)
logging.info('API call: %s' % url)
raw_data = self._fetch_data(url)
tracks = raw_data['recenttracks']['track']
if not type(tracks) == list:
tracks = [tracks]
for trackdic in tracks:
track = unicode(trackdic['name']).encode("utf-8")
artist = unicode(trackdic['artist']['#text']).encode("utf-8")
message_file.write("Track from Last.fm: %s by %s\n" % (track, artist))
def _fetch_data(self, url):
raw_data = urllib.urlopen(url)
data = json.loads(raw_data.read())
return data
flashbake-0.27.1/src/flashbake/plugins/location.py 0000664 0000000 0000000 00000014503 12621437732 0022105 0 ustar 00root root 0000000 0000000 # location.py
# Net location plugin.
#
# copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
from flashbake.plugins import AbstractMessagePlugin
from urllib2 import HTTPError, URLError
from xml.dom import minidom
import logging
import os.path
import re
import urllib
import urllib2
class Location(AbstractMessagePlugin):
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec, True)
self.share_property('location_location')
def addcontext(self, message_file, config):
ip_addr = self.__get_ip()
if ip_addr == None:
message_file.write('Failed to get public IP for geo location.\n')
return False
location = self.__locate_ip(ip_addr)
if len(location) == 0:
message_file.write('Failed to parse location data for IP address.\n')
return False
logging.debug(location)
location_str = '%(cityName)s, %(regionName)s' % location
config.location_location = location_str
message_file.write('Current location is %s based on IP %s.\n' % (location_str, ip_addr))
return True
def __locate_ip(self, ip_addr):
cached = self.__load_cache()
if cached.get('ip_addr','') == ip_addr:
del cached['ip_addr']
return cached
base_url = 'http://api.ipinfodb.com/v3/ip-city/?'
for_ip = base_url + urllib.urlencode({'key': 'd2e4d26478b0759c225fd4b9113240e1ab7c1bf4f8fb673cba0a2ed52a351916',
'ip': ip_addr,
'format': 'xml'})
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
try:
logging.debug('Requesting page for %s.' % for_ip)
# open the location API page
location_xml = opener.open(urllib2.Request(for_ip)).read()
# the weather API returns some nice, parsable XML
location_dom = minidom.parseString(location_xml)
# just interested in the conditions at the moment
response = location_dom.getElementsByTagName("Response")
if response == None or len(response) == 0:
return dict()
location = dict()
for child in response[0].childNodes:
if child.localName == None:
continue
key = child.localName
key = key.encode('ASCII', 'replace')
location[key] = self.__get_text(child.childNodes)
self.__save_cache(ip_addr, location)
return location
except HTTPError, e:
logging.error('Failed with HTTP status code %d' % e.code)
return {}
except URLError, e:
logging.error('Plugin, %s, failed to connect with network.' % self.__class__)
logging.debug('Network failure reason, %s.' % e.reason)
return {}
def __load_cache(self):
home_dir = os.path.expanduser('~')
# look for flashbake directory
fb_dir = os.path.join(home_dir, '.flashbake')
cache = dict()
if not os.path.exists(fb_dir):
return cache
cache_name = os.path.join(fb_dir, 'ip_cache')
if not os.path.exists(cache_name):
return cache
cache_file = open(cache_name, 'r')
try:
for line in cache_file:
tokens = line.split(':')
key = tokens[0]
value = tokens[1].strip()
if key.startswith('location.'):
key = key.replace('location.', '')
cache[key] = value
logging.debug('Loaded cache %s' % cache)
finally:
cache_file.close()
return cache
def __save_cache(self, ip_addr, location):
home_dir = os.path.expanduser('~')
# look for flashbake directory
fb_dir = os.path.join(home_dir, '.flashbake')
if not os.path.exists(fb_dir):
os.mkdir(fb_dir)
cache_file = open(os.path.join(fb_dir, 'ip_cache'), 'w')
try:
cache_file.write('ip_addr:%s\n' % ip_addr)
for key in location.iterkeys():
cache_file.write('location.%s:%s\n' % (key, location[key]))
finally:
cache_file.close()
def __get_text(self, node_list):
text_value = ''
for node in node_list:
if node.nodeType != node.TEXT_NODE:
continue;
text_value += node.data
return text_value
def __get_ip(self):
no_reply = 'http://www.noreply.org'
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
try:
# open the weather API page
ping_reply = opener.open(urllib2.Request(no_reply)).read()
hello_line = None
for line in ping_reply.split('\n'):
if line.find('Hello') > 0:
hello_line = line.strip()
break
if hello_line is None:
logging.error('Failed to parse Hello with public IP address.')
return None
logging.debug(hello_line)
m = re.search('([0-9]+\.){3}([0-9]+){1}', hello_line)
if m is None:
logging.error('Failed to parse Hello with public IP address.')
return None
ip_addr = m.group(0)
return ip_addr
except HTTPError, e:
logging.error('Failed with HTTP status code %d' % e.code)
return None
except URLError, e:
logging.error('Plugin, %s, failed to connect with network.' % self.__class__)
logging.debug('Network failure reason, %s.' % e.reason)
return None
flashbake-0.27.1/src/flashbake/plugins/mail.py 0000664 0000000 0000000 00000010162 12621437732 0021214 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
'''
Created on Jul 23, 2009
mail.py - plug-in to send notices via smtp.
@author: cmdln
'''
from flashbake import plugins
from flashbake.compat import MIMEText
import logging
import os
import smtplib
import sys
class Email(plugins.AbstractNotifyPlugin):
def __init__(self, plugin_spec):
plugins.AbstractPlugin.__init__(self, plugin_spec)
self.define_property('notice_to', required=True)
self.define_property('notice_from')
self.define_property('smtp_host', default='localhost')
self.define_property('smtp_port', int, default=25)
def init(self, config):
if self.notice_from == None:
self.notice_from = self.notice_to
def warn(self, hot_files, control_config):
body = ''
if len(hot_files.not_exists) > 0:
body += '\nThe following files do not exist:\n\n'
for file in hot_files.not_exists:
body += '\t' + file + '\n'
body += '\nMake sure there is not a typo in .flashbake and that you created/saved the file.\n'
if len(hot_files.deleted) > 0:
body += '\nThe following files have been deleted from version control:\n\n'
for file in hot_files.deleted:
body += '\t' + file + '\n'
body += '\nYou may restore these files or remove them from .flashbake after running flashbake --purge '
body += 'in your project directory.\n'
if len(hot_files.linked_files) > 0:
body += '\nThe following files in .flashbake are links or have a link in their directory path.\n\n'
for (file, link) in hot_files.linked_files.iteritems():
if file == link:
body += '\t' + file + ' is a link\n'
else:
body += '\t' + link + ' is a link on the way to ' + file + '\n'
body += '\nMake sure the physical file and its parent directories reside in the project directory.\n'
if len(hot_files.outside_files) > 0:
body += '\nThe following files in .flashbake are not in the project directory.\n\n'
for file in hot_files.outside_files:
body += '\t' + file + '\n'
body += '\nOnly files in the project directory can be tracked and committed.\n'
if control_config.dry_run:
logging.debug(body)
if self.notice_to != None:
logging.info('Dry run, skipping email notice.')
return
# Create a text/plain message
msg = MIMEText(body, 'plain')
msg['Subject'] = ('Some files in %s do not exist'
% os.path.realpath(hot_files.project_dir))
msg['From'] = self.notice_from
msg['To'] = self.notice_to
# Send the message via our own SMTP server, but don't include the
# envelope header.
logging.debug('\nConnecting to SMTP on host %s, port %d'
% (self.smtp_host, self.smtp_port))
try:
s = smtplib.SMTP()
s.connect(host=self.smtp_host, port=self.smtp_port)
logging.info('Sending notice to %s.' % self.notice_to)
logging.debug(body)
s.sendmail(self.notice_from, [self.notice_to], msg.as_string())
logging.info('Notice sent.')
s.close()
except Exception, e:
logging.error('Couldn\'t connect, will send later.')
logging.debug("SMTP Error:\n" + str(e));
flashbake-0.27.1/src/flashbake/plugins/microblog.py 0000664 0000000 0000000 00000015123 12621437732 0022251 0 ustar 00root root 0000000 0000000 # copyright 2009 Ben Snider (bensnider.com), Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' microblog.py - microblog plugins by Ben Snider, bensnider.com '''
from flashbake.plugins import AbstractMessagePlugin
from urllib2 import HTTPError, URLError
from xml.etree.ElementTree import ElementTree
import logging
import urllib
class Twitter(AbstractMessagePlugin):
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec, True)
self.service_url = 'http://twitter.com'
self.optional_field_info = { \
'source':{'path':'source', 'transform':propercase}, \
'location':{'path':'user/location', 'transform':propercase}, \
'favorited':{'path':'favorited', 'transform':propercase}, \
'tweeted_on': {'path':'created_at', 'transform':utc_to_local}, \
}
self.define_property('user', required=True)
self.define_property('limit', int, False, 5)
self.define_property('optional_fields')
def init(self, config):
if self.limit > 200:
logging.warn('Please use a limit <= 200.');
self.limit = 200
self.__setoptionalfields(config)
# simple user xml feed
self.twitter_url = '%(url)s/statuses/user_timeline/%(user)s.xml?count=%(limit)d' % {
'url':self.service_url,
'user':self.user,
'limit':self.limit}
def __setoptionalfields(self, config):
# We don't have to worry about a KeyError here since this property
# should have been set to None by self.setoptionalproperty.
if (self.optional_fields == None):
self.optional_fields = []
else:
# get the optional fields, split on commas
fields = self.optional_fields.strip().split(',')
newFields = []
for field in fields:
field = field.strip()
# check if they are allowed and not a dupe
if (field in self.optional_field_info and field not in newFields):
# if so we push them onto the optional fields array, otherwise ignore
newFields.append(field)
# finally sort the list so its the same each run, provided the config is the same
newFields.sort()
self.optional_fields = newFields
def addcontext(self, message_file, config):
(title, last_tweets) = self.__fetchitems(config)
if (len(last_tweets) > 0 and title != None):
to_file = ('Last %(item_count)d %(service_name)s messages from %(twitter_title)s:\n' \
% {'item_count' : len(last_tweets), 'twitter_title' : title, 'service_name':self.service_name})
i = 1
for item in last_tweets:
to_file += ('%d) %s\n' % (i, item['tweet']))
for field in self.optional_fields:
to_file += ('\t%s: %s\n' % (propercase(field), item[field]))
i += 1
logging.debug(to_file.encode('UTF-8'))
message_file.write(to_file.encode('UTF-8'))
else:
message_file.write('Couldn\'t fetch entries from feed, %s.\n' % self.twitter_url)
return len(last_tweets) > 0
def __fetchitems(self, config):
''' We fetch the tweets from the configured url in self.twitter_url,
and return a list containing the formatted title and an array of
tweet dictionaries that contain at least the 'tweet' key along with
any optional fields. The
'''
results = [None, []]
try:
twitter_xml = urllib.urlopen(self.twitter_url)
except HTTPError, e:
logging.error('Failed with HTTP status code %d' % e.code)
return results
except URLError, e:
logging.error('Plugin, %s, failed to connect with network.' % self.__class__)
logging.debug('Network failure reason, %s.' % e.reason)
return results
except IOError:
logging.error('Plugin, %s, failed to connect with network.' % self.__class__)
logging.debug('Socket error.')
return results
tree = ElementTree()
tree.parse(twitter_xml)
status = tree.find('status')
if (status == None):
return results
# after this point we are pretty much guaranteed that we won't get an
# exception or None value, provided the twitter xml stays the same
results[0] = propercase(status.find('user/name').text)
for status in tree.findall('status'):
tweet = {}
tweet['tweet'] = status.find('text').text
for field in self.optional_fields:
tweet[field] = status.find(self.optional_field_info[field]['path']).text
if ('transform' in self.optional_field_info[field]):
tweet[field] = self.optional_field_info[field]['transform'](tweet[field])
results[1].append(tweet)
return results
class Identica(Twitter):
def __init__(self, plugin_spec):
Twitter.__init__(self, plugin_spec)
self.service_url = 'http://identi.ca/api'
self.optional_field_info['created_on'] = self.optional_field_info['tweeted_on']
del self.optional_field_info['tweeted_on']
def propercase(string):
''' Returns the string with _ replaced with spaces and the whole string
should be title cased. '''
string = string.replace('_', ' ')
string = string.title()
return string
def utc_to_local(t):
''' ganked from http://feihonghsu.blogspot.com/2008/02/converting-from-local-time-to-utc.html '''
import calendar, datetime
# Discard the timezone, python dont like it, and it seems to always be
# set to UTC, even if the user has their timezone set.
t = t.replace('+0000 ', '')
# might asplode
return datetime.datetime.fromtimestamp((calendar.timegm(datetime.datetime.strptime(t, '%a %b %d %H:%M:%S %Y').timetuple()))).strftime("%A, %b. %d, %Y at %I:%M%p %z")
flashbake-0.27.1/src/flashbake/plugins/music.py 0000664 0000000 0000000 00000007762 12621437732 0021426 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
#
# the iTunes class is based on the itunes.py by Andrew Wheiss, originally
# licensed under an MIT License
''' music.py - Plugin for gathering last played tracks from music player. '''
from flashbake.plugins import AbstractMessagePlugin
import flashbake
import logging
import os.path
import sqlite3
import subprocess
import time
class Banshee(AbstractMessagePlugin):
def __init__(self, plugin_spec):
""" Add an optional property for specifying a different location for the
Banshee database. """
AbstractMessagePlugin.__init__(self, plugin_spec)
self.define_property('db', default=os.path.join(os.path.expanduser('~'), '.config', 'banshee-1', 'banshee.db'))
self.define_property('limit', int, default=3)
self.define_property('last_played_format')
def addcontext(self, message_file, config):
""" Open the Banshee database and query for the last played tracks. """
query = """\
select t.Title, a.Name, t.LastPlayedStamp
from CoreTracks t
join CoreArtists a on t.ArtistID = a.ArtistID
order by LastPlayedStamp desc
limit %d"""
query = query.strip() % self.limit
conn = sqlite3.connect(self.db)
try:
cursor = conn.cursor()
logging.debug('Executing %s' % query)
cursor.execute(query)
results = cursor.fetchall()
message_file.write('Last %d track(s) played in Banshee:\n' % len(results))
for result in results:
last_played = time.localtime(result[2])
if self.last_played_format != None:
logging.debug('Using format %s' % self.last_played_format)
last_played = time.strftime(self.last_played_format,
last_played)
else:
last_played = time.ctime(result[2])
message_file.write('"%s", by %s (%s)' %
(result[0], result[1], last_played))
message_file.write('\n')
except Exception, error:
logging.error(error)
conn.close()
return True
class iTunes(AbstractMessagePlugin):
''' Based on Andrew Heiss' plugin which is MIT licensed which should be compatible. '''
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec)
self.define_property('osascript')
def init(self, config):
if self.osascript is None:
self.osascript = flashbake.find_executable('osascript')
def addcontext(self, message_file, config):
""" Get the track info and write it to the commit message """
info = self.trackinfo()
if info is None:
message_file.write('Couldn\'t get current track.\n')
else:
message_file.write('Currently playing in iTunes:\n%s' % info)
return True
def trackinfo(self):
''' Call the AppleScript file. '''
if self.osascript is None:
return None
directory = os.path.dirname(__file__)
script_path = os.path.join(directory, 'current_track.scpt')
args = [self.osascript, script_path]
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
close_fds=True)
return proc.communicate()[0]
flashbake-0.27.1/src/flashbake/plugins/scrivener.py 0000664 0000000 0000000 00000020150 12621437732 0022270 0 ustar 00root root 0000000 0000000 # copyright 2009-2011 Thomas Gideon, Jason Penney
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' scrivener.py - Scrivener flashbake plugin
by Jason Penney, jasonpenney.net'''
from flashbake.plugins import (
AbstractFilePlugin, AbstractMessagePlugin, PluginError,
PLUGIN_ERRORS)
import flashbake #@UnusedImport
import fnmatch
import glob
import logging
import os
import os.path
import subprocess
import string
import re
from flashbake.compat import relpath, pickle
def find_scrivener_projects(hot_files, config, flush_cache=False):
if flush_cache:
config.scrivener_projects = None
if config.scrivener_projects == None:
scrivener_projects = list()
for f in hot_files.control_files:
if fnmatch.fnmatch(f, '*.scriv'):
scrivener_projects.append(f)
config.scrivener_projects = scrivener_projects
return config.scrivener_projects
def find_scrivener_project_contents(hot_files, scrivener_project):
for path, dirs, files in os.walk(os.path.join( # @UnusedVariable
hot_files.project_dir, scrivener_project)):
rpath = relpath(path, hot_files.project_dir)
for filename in files:
yield os.path.join(rpath, filename)
def get_logfile_name(scriv_proj_dir):
return os.path.join(os.path.dirname(scriv_proj_dir),
".%s.flashbake.wordcount" % os.path.basename(
scriv_proj_dir))
## TODO: deal with deleted files
class ScrivenerFile(AbstractFilePlugin):
def __init__(self, plugin_spec):
AbstractFilePlugin.__init__(self, plugin_spec)
self.share_property('scrivener_projects')
def pre_process(self, hot_files, config):
for f in find_scrivener_projects(hot_files, config):
logging.debug("ScrivenerFile: adding '%s'" % f)
for hotfile in find_scrivener_project_contents(hot_files, f):
#logging.debug(" - %s" % hotfile)
hot_files.control_files.add(hotfile)
def post_process(self, to_commit, hot_files, config):
flashbake.commit.purge(config, hot_files)
class ScrivenerWordcountFile(AbstractFilePlugin):
""" Record Wordcount for Scrivener Files """
def __init__(self, plugin_spec):
AbstractFilePlugin.__init__(self, plugin_spec)
self.define_property('use_textutil', type=bool, default=False)
self.share_property('scrivener_projects')
self.share_property('scrivener_project_count')
def init(self, config):
self.get_count = self._get_count_python
if self.use_textutil:
if flashbake.executable_available('textutil'):
self.get_count = self._get_count_textutil
else:
logging.warn("unable to find textutil, will use python "
"wordcount calculation")
def pre_process(self, hot_files, config):
config.scrivener_project_count = dict()
for f in find_scrivener_projects(hot_files, config):
scriv_proj_dir = os.path.join(hot_files.project_dir, f)
hot_logfile = get_logfile_name(f)
logfile = os.path.join(hot_files.project_dir, hot_logfile)
if os.path.exists(logfile):
logging.debug("logifile exists %s" % logfile)
log = open(logfile, 'r')
oldCount = pickle.load(log)
log.close()
else:
oldCount = {
'Content': 0,
'Synopsis': 0,
'Notes': 0,
'All': 0}
search_path = os.path.join(scriv_proj_dir, 'Files', 'Docs')
if os.path.exists(os.path.join(search_path)):
newCount = {
'Content': self.get_count(search_path, ["*[0-9].rtf"]),
'Synopsis': self.get_count(
search_path, ['*_synopsis.txt']),
'Notes': self.get_count(
search_path, ['*_notes.rtf']),
'All': self.get_count(
search_path, ['*.rtf', '*.txt'])}
else:
newCount = {
'Content': self.get_count(scriv_proj_dir, ["*[0-9].rtfd"]),
'Synopsis': self.get_count(
scriv_proj_dir, ['*_synopsis.txt']),
'Notes': self.get_count(
scriv_proj_dir, ['*_notes.rtfd']),
'All': self.get_count(
scriv_proj_dir, ['*.rtfd', '*.txt'])}
config.scrivener_project_count[f] = {
'old': oldCount,
'new': newCount}
if not config.context_only:
log = open(logfile, 'w')
pickle.dump(config.scrivener_project_count[f]['new'], log)
log.close()
if not hot_logfile in hot_files.control_files:
hot_files.control_files.add(logfile)
RTF_RE = re.compile('(\{[^}]+\}|\\\\\\\\END_SCRV[^\}]+\}|'
'\\\\\'\d+|\\\\(\\\\|[-=A-Za-z0-9\.])*|\}$|'
'\W[%s]\W)' % (re.escape(string.punctuation)),
re.MULTILINE | re.IGNORECASE)
def _get_count_python(self, file, matches):
count = 0
for match in matches:
for f in glob.glob(os.path.normpath(os.path.join(file, match))):
if f.endswith('.rtfd'):
new_f = os.path.join(f, 'TXT.rtf')
if os.path.exists(new_f):
f = new_f
if f.endswith('.txt'):
count += len(open(f).read().split(None))
elif f.endswith('.rtf'):
words = self.RTF_RE.sub('', open(f).read()).split(None)
count += len(words)
else:
raise PluginError(
PLUGIN_ERRORS.ignorable_error,
self.plugin_spec,
'Unsupported file type: %s' % f)
return count
def _get_count_textutil(self, file, matches):
count = 0
args = ['textutil', '-stdout', '-cat', 'txt']
do_count = False
for match in matches:
for f in glob.glob(os.path.normpath(os.path.join(file, match))):
do_count = True
args.append(f)
if do_count:
p = subprocess.Popen(args, stdout=subprocess.PIPE,
close_fds=True)
count += len(p.stdout.read().split(None))
return count
class ScrivenerWordcountMessage(AbstractMessagePlugin):
""" Display Wordcount for Scrivener Files """
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec, False)
self.share_property('scrivener_project_count')
def addcontext(self, message_file, config):
to_file = ''
if 'scrivener_project_count' in config.__dict__:
for proj in config.scrivener_project_count:
to_file += "Wordcount: %s\n" % proj
for key in ['Content', 'Synopsis', 'Notes', 'All']:
new = config.scrivener_project_count[proj]['new'][key]
old = config.scrivener_project_count[proj]['old'][key]
diff = new - old
to_file += "- " + key.ljust(10, ' ') + str(new).rjust(20)
if diff != 0:
to_file += " (%+d)" % (new - old)
to_file += "\n"
message_file.write(to_file)
flashbake-0.27.1/src/flashbake/plugins/timezone.py 0000664 0000000 0000000 00000005276 12621437732 0022136 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' timezone.py - Stock plugin to find the system's time zone add to the commit message.'''
from flashbake.plugins import AbstractMessagePlugin
import logging
import os
PLUGIN_SPEC = 'flashbake.plugins.timezone:TimeZone'
class TimeZone(AbstractMessagePlugin):
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec, False)
self.share_property('tz', plugin_spec=PLUGIN_SPEC)
def addcontext(self, message_file, config):
""" Add the system's time zone to the commit context. """
zone = findtimezone(config)
if zone == None:
message_file.write('Couldn\'t determine time zone.\n')
else:
message_file.write('Current time zone is %s\n' % zone)
return True
def findtimezone(config):
# check the environment for the zone value
zone = os.environ.get("TZ")
logging.debug('Zone from env is %s.' % zone)
# some desktops don't set the env var but /etc/timezone should
# have the value regardless
if None != zone:
logging.debug('Returning env var value.')
return zone
# this is common on many *nix variatns
logging.debug('Checking /etc/timezone')
if os.path.exists('/etc/timezone'):
zone_file = open('/etc/timezone')
try:
zone = zone_file.read()
finally:
zone_file.close()
zone = zone.replace("\n", "")
return zone
# this is specific to OS X
logging.debug('Checking /etc/localtime')
if os.path.exists('/etc/localtime'):
zone = os.path.realpath('/etc/localtime')
(zone, city) = os.path.split(zone);
(zone, continent) = os.path.split(zone);
zone = os.path.join(continent, city)
return zone
logging.debug('Checking .flashbake')
if 'timezone' in config.__dict__:
zone = config.timezone
return zone
logging.warn('Could not get TZ from env var, /etc/timezone, or .flashbake.')
zone = None
return zone
flashbake-0.27.1/src/flashbake/plugins/uptime.py 0000664 0000000 0000000 00000010527 12621437732 0021602 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' uptime.py - Stock plugin to calculate the system's uptime and add to the commit message.'''
from flashbake.plugins import AbstractMessagePlugin
from subprocess import Popen, PIPE
import flashbake
import logging
import os.path
class UpTime(AbstractMessagePlugin):
def addcontext(self, message_file, config):
""" Add the system's up time to the commit context. """
uptime = self.__calcuptime()
if uptime == None:
message_file.write('Couldn\'t determine up time.\n')
else:
message_file.write('System has been up %s\n' % uptime)
return True
def __calcuptime(self):
""" copied with blanket permission from
http://thesmithfam.org/blog/2005/11/19/python-uptime-script/ """
if not os.path.exists('/proc/uptime'):
return self.__run_uptime()
f = open("/proc/uptime")
try:
contents = f.read().split()
except:
return None
finally:
f.close()
total_seconds = float(contents[0])
# Helper vars:
MINUTE = 60
HOUR = MINUTE * 60
DAY = HOUR * 24
# Get the days, hours, etc:
days = int(total_seconds / DAY)
hours = int((total_seconds % DAY) / HOUR)
minutes = int((total_seconds % HOUR) / MINUTE)
seconds = int(total_seconds % MINUTE)
# Build up the pretty string (like this: "N days, N hours, N minutes, N seconds")
string = ""
if days > 0:
string += str(days) + " " + (days == 1 and "day" or "days") + ", "
if len(string) > 0 or hours > 0:
string += str(hours) + " " + (hours == 1 and "hour" or "hours") + ", "
if len(string) > 0 or minutes > 0:
string += str(minutes) + " " + (minutes == 1 and "minute" or "minutes") + ", "
string += str(seconds) + " " + (seconds == 1 and "second" or "seconds")
return string
def __run_uptime(self):
""" For OSes that don't provide procfs, then try to use the updtime command.
Thanks to Tony Giunta for this contribution. """
if not flashbake.executable_available('uptime'):
return None
# Try to capture output of 'uptime' command,
# if not found, catch OSError, log and return None
try:
output = Popen("uptime", stdout=PIPE).communicate()[0].split()
except OSError:
logging.warn("Can't find 'uptime' command in $PATH")
return None
# Parse uptime output string
# if len == 10 or 11, uptime is less than a day
if len(output) in [10, 11]:
days = "00"
hours_and_minutes = output[2].strip(",")
elif len(output) == 12:
days = output[2]
hours_and_minutes = output[4].strip(",")
else:
return None
# If time is exactly x hours/mins, no ":" in "hours_and_minutes"
# and the interpreter will throw a ValueError
try:
hours, minutes = hours_and_minutes.split(":")
except ValueError:
if output[3].startswith("hr"):
hours = hours_and_minutes
minutes = "00"
elif output[3].startwwith("min"):
hours = "00"
minutes = hours_and_minutes
else:
return None
# Build up output string, might require Python 2.5+
uptime = (days + (" day, " if days == "1" else " days, ") +
hours + (" hour, " if hours == "1" else " hours, ") +
minutes + (" minute" if minutes == "1" else " minutes"))
return uptime
flashbake-0.27.1/src/flashbake/plugins/weather.py 0000664 0000000 0000000 00000011227 12621437732 0021734 0 ustar 00root root 0000000 0000000 # copyright 2009 Thomas Gideon
# Modified 2013 Bryan Fordham
#
# This file is part of flashbake.
#
# flashbake 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.
#
# flashbake 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 flashbake. If not, see .
''' weather.py - Stock plugin for adding weather information to context, must have TZ or
/etc/localtime available to determine city from ISO ID. '''
from flashbake.plugins import AbstractMessagePlugin
from flashbake.plugins.timezone import findtimezone
from flashbake.plugins import timezone
from urllib2 import HTTPError, URLError
import json
import logging
import re
import urllib
import urllib2
class Weather(AbstractMessagePlugin):
def __init__(self, plugin_spec):
AbstractMessagePlugin.__init__(self, plugin_spec, True)
self.define_property('city')
self.define_property('units', required=False, default='imperial')
self.share_property('tz', plugin_spec=timezone.PLUGIN_SPEC)
## plugin uses location_location from Location plugin
self.share_property('location_location')
def addcontext(self, message_file, config):
""" Add weather information to the commit message. Looks for
weather_city: first in the config information but if that is not
set, will try to use the system time zone to identify a city. """
if config.location_location == None and self.city == None:
zone = findtimezone(config)
if zone == None:
city = None
else:
city = self.__parsecity(zone)
else:
if config.location_location == None:
city = self.city
else:
city = config.location_location
if None == city:
message_file.write('Couldn\'t determine city to fetch weather.\n')
return False
# call the open weather map API with the city
weather = self.__getweather(city, self.units)
if len(weather) > 0:
message_file.write('Current weather for %(city)s: %(description)s. %(temp)i%(temp_units)s. %(humidity)s%% humidity\n'
% weather)
else:
message_file.write('Couldn\'t fetch current weather for city, %s.\n' % city)
return len(weather) > 0
def __getweather(self, city, units='imperial'):
""" This relies on Open Weather Map's API which may change without notice. """
baseurl = 'http://api.openweathermap.org/data/2.5/weather?'
# encode the parameters
for_city = baseurl + urllib.urlencode({'q': city, 'units': units})
try:
logging.debug('Requesting page for %s.' % for_city)
# Get the json-encoded string
raw = urllib2.urlopen(for_city).read()
# Convert it to something usable
data = json.loads(raw)
# Grab the information we want
weather = dict()
for k,v in (data['weather'][0]).items():
weather[k] = v
for k,v in data['main'].items():
weather[k] = v
weather['city'] = city
if units == 'imperial':
weather['temp_units'] = 'F'
elif units == 'metric':
weather['temp_units'] = 'C'
else:
weather['temp_units'] = 'K'
return weather
except HTTPError, e:
logging.error('Failed with HTTP status code %d' % e.code)
return {}
except URLError, e:
logging.error('Plugin, %s, failed to connect with network.' % self.__class__)
logging.debug('Network failure reason, %s.' % e.reason)
return {}
def __parsecity(self, zone):
if None == zone:
return None
tokens = zone.split("/")
if len(tokens) != 2:
logging.warning('Zone id, "%s", doesn''t appear to contain a city.' % zone)
# return non-zero so calling shell script can catch
return None
city = tokens[1]
# ISO id's have underscores, convert to spaces for the Google API
return city.replace("_", " ")
flashbake-0.27.1/test/ 0000775 0000000 0000000 00000000000 12621437732 0014507 5 ustar 00root root 0000000 0000000 flashbake-0.27.1/test/__init__.py 0000664 0000000 0000000 00000000000 12621437732 0016606 0 ustar 00root root 0000000 0000000 flashbake-0.27.1/test/config.py 0000664 0000000 0000000 00000010373 12621437732 0016332 0 ustar 00root root 0000000 0000000 from flashbake import ControlConfig
from flashbake.plugins import PluginError
import unittest
class ConfigTestCase(unittest.TestCase):
def setUp(self):
self.config = ControlConfig()
def testinvalidspec(self):
try:
self.config.create_plugin('test.foo')
self.fail('Should not be able to use unknown')
except PluginError, error:
self.assertEquals(str(error.reason), 'invalid_plugin',
'Should not be able to load invalid plugin.')
def testnoplugin(self):
try:
self.config.create_plugin('test.foo:Foo')
self.fail('Should not be able to use unknown')
except PluginError, error:
self.assertEquals(str(error.reason), 'unknown_plugin',
'Should not be able to load unknown plugin.')
def testmissingparent(self):
try:
plugin_name = 'test.plugins:MissingParent'
self.config.create_plugin(plugin_name)
self.fail('Should not have initialized plugin, %s' % plugin_name)
except PluginError, error:
reason = 'invalid_type'
self.assertEquals(str(error.reason), reason,
'Error should specify failure reason, %s.' % reason)
def testnoconnectable(self):
self.__testattr('test.plugins:NoConnectable', 'connectable', 'missing_attribute')
def testwrongconnectable(self):
self.__testattr('test.plugins:WrongConnectable', 'connectable', 'invalid_attribute')
def testnoaddcontext(self):
try:
self.config.plugin_names = ['test.plugins:NoAddContext']
from flashbake.context import buildmessagefile
buildmessagefile(self.config)
self.fail('Should raise a NotImplementedError.')
except NotImplementedError:
pass
def testwrongaddcontext(self):
self.__testattr('test.plugins:WrongAddContext', 'addcontext', 'invalid_attribute')
def teststockplugins(self):
self.config.extra_props['feed_url'] = "http://random.com/feed"
plugins = ('flashbake.plugins.weather:Weather',
'flashbake.plugins.uptime:UpTime',
'flashbake.plugins.timezone:TimeZone',
'flashbake.plugins.feed:Feed')
for plugin_name in plugins:
plugin = self.config.create_plugin(plugin_name)
plugin.capture_properties(self.config)
plugin.init(self.config)
def testnoauthorfail(self):
"""Ensure that accessing feeds with no entry.author doesn't cause failures if the
feed_author config property isn't set."""
self.config.plugin_names = ['flashbake.plugins.feed:Feed']
self.config.extra_props['feed_url'] = "http://twitter.com/statuses/user_timeline/704593.rss"
from flashbake.context import buildmessagefile
buildmessagefile(self.config)
def testfeedfail(self):
try:
plugin = self.config.create_plugin('flashbake.plugins.feed:Feed')
plugin.capture_properties(self.config)
self.fail('Should not be able to initialize without full plugin props.')
except PluginError, error:
self.assertEquals(str(error.reason), 'missing_property',
'Feed plugin should fail missing property.')
self.assertEquals(error.name, 'feed_url',
'Missing property should be feed.')
self.config.extra_props['feed_url'] = "http://random.com/feed"
try:
plugin = self.config.create_plugin('flashbake.plugins.feed:Feed')
plugin.capture_properties(self.config)
except PluginError, error:
self.fail('Should be able to initialize with just the url.')
def __testattr(self, plugin_name, name, reason):
try:
plugin = self.config.create_plugin(plugin_name)
plugin.capture_properties(self.config)
plugin.init(self.config)
self.fail('Should not have initialized plugin, %s' % plugin_name)
except PluginError, error:
self.assertEquals(str(error.reason), reason,
'Error should specify failure reason, %s.' % reason)
self.assertEquals(error.name, name,
'Error should specify failed name, %s' % name)
flashbake-0.27.1/test/files.py 0000664 0000000 0000000 00000004341 12621437732 0016165 0 ustar 00root root 0000000 0000000 import commands
import flashbake
import os.path
import unittest
class FilesTestCase(unittest.TestCase):
def setUp(self):
test_dir = os.path.join(os.getcwd(), 'test')
test_zip = os.path.join(test_dir, 'project.zip')
commands.getoutput('unzip -d %s %s' % (test_dir, test_zip))
self.files = flashbake.HotFiles(os.path.join(test_dir, 'project'))
self.project_files = [ 'todo.txt', 'stickies.txt', 'my stuff.txt',
'bar/novel.txt', 'baz/novel.txt', 'quux/novel.txt' ]
def tearDown(self):
commands.getoutput('rm -rf %s' % self.files.project_dir)
def testrelative(self):
for file in self.project_files:
self.files.addfile(file)
self.assertTrue(file in self.files.control_files,
'Should contain relative file, %s' % file)
count = len(self.files.control_files)
self.files.addfile('*add*')
self.assertEquals(len(self.files.control_files), count + 3,
'Should have expanded glob.')
def testabsolute(self):
for file in self.project_files:
abs_file = os.path.join(self.files.project_dir, file)
self.files.addfile(abs_file)
self.assertTrue(file in self.files.control_files,
'Should contain absolute file, %s, as relative path, %s.'
% (abs_file, file))
count = len(self.files.control_files)
self.files.addfile(os.path.join(self.files.project_dir, '*add*'))
self.assertEquals(len(self.files.control_files), count + 3,
'Should have expanded glob.')
def testabsent(self):
self.files.addfile('does not exist.txt')
self.files.addfile('doesn\'t exist.txt')
self.files.addfile('does{not}exist.txt')
self.assertEquals(len(self.files.not_exists), 3,
'None of the provided files should exist')
def testoutside(self):
self.files.addfile('/tmp')
self.assertEquals(len(self.files.outside_files), 1,
'Outside files should get caught')
def testlinks(self):
self.files.addfile('link/novel.txt')
self.assertEquals(len(self.files.linked_files.keys()), 1,
'Linked files should get caught')
flashbake-0.27.1/test/plugins.py 0000664 0000000 0000000 00000003473 12621437732 0016551 0 ustar 00root root 0000000 0000000 from flashbake import ControlConfig
import flashbake.plugins
import logging
import unittest
class FilesTestCase(unittest.TestCase):
def setUp(self):
self.config = ControlConfig()
def testrelative(self):
pass
class MissingParent():
def __init__(self, plugin_spec):
pass
def addcontext(self, message_file, control_config):
logging.debug('do nothing')
class NoConnectable(flashbake.plugins.AbstractMessagePlugin):
def __init__(self, plugin_spec):
pass
def addcontext(self, message_file, control_config):
logging.debug('do nothing')
class NoAddContext(flashbake.plugins.AbstractMessagePlugin):
def __init__(self, plugin_spec):
flashbake.plugins.AbstractMessagePlugin.__init__(self, plugin_spec, True)
class WrongConnectable(flashbake.plugins.AbstractMessagePlugin):
def __init__(self, plugin_spec):
self.connectable = 1
def addcontext(self, message_file, control_config):
logging.debug('do nothing')
class WrongAddContext(flashbake.plugins.AbstractMessagePlugin):
def __init__(self, plugin_spec):
self.connectable = True
self.addcontext = 1
class Plugin1(flashbake.plugins.AbstractMessagePlugin):
""" Sample plugin. """
def addcontext(self, message_file, config):
""" Stub. """
pass
class Plugin2(flashbake.plugins.AbstractMessagePlugin):
""" Sample plugin. """
def dependencies(self):
return ['test.plugins:Plugin1']
def addcontext(self, message_file, config):
""" Stub. """
pass
class Plugin3(flashbake.plugins.AbstractMessagePlugin):
""" Sample plugin. """
def dependencies(self):
return ['test.plugins:Plugin1', 'text.plugins:Plugin2']
def addcontext(self, message_file, config):
""" Stub. """
pass
flashbake-0.27.1/test/project.zip 0000664 0000000 0000000 00000007133 12621437732 0016705 0 ustar 00root root 0000000 0000000 PK
'wm: project/UT IIUx PK
XE: project/baz/UT IIUx PK XE:` project/baz/novel.txtUT IIUx En0C
>v}fTnE/D>T4bQOZ@;>MFx>gP} YeV6:jzJWQƉg>j B?pEoX!bsxmWJMI4s,KXzF\Րi4Ml%V;>PK ycl:-$ w project/.flashbakeUT 8IIUx }MO0zL .9!А\eDK8c'FW>K~qڸ0>ʠr 1SqE8I:v^Go:YiZb0>h vA (GUD