pax_global_header00006660000000000000000000000064126321140060014505gustar00rootroot0000000000000052 comment=8d77bfd2eaf58ddfb27819dc94a48ea017f53304 pepper-0.3.3/000077500000000000000000000000001263211400600130035ustar00rootroot00000000000000pepper-0.3.3/.gitignore000066400000000000000000000004571263211400600150010ustar00rootroot00000000000000*.[oa] *.bak *.cpp.* *.h.* *.log *.out *.patch *.tar.bz2 *.tar.gz *~ .*.swp .deps Makefile Makefile.in aclocal.m4 atconfig atlocal autom4te.cache callgrind.out.* config.guess config.h config.h.in config.status config.sub configure configure.scan depcomp install-sh massif.out.* missing stamp-h1 luadoc/ pepper-0.3.3/.gitmodules000066400000000000000000000001661263211400600151630ustar00rootroot00000000000000[submodule "3rdparty/leveldb"] path = 3rdparty/leveldb url = https://code.google.com/p/leveldb/ ignore = untracked pepper-0.3.3/3rdparty/000077500000000000000000000000001263211400600145535ustar00rootroot00000000000000pepper-0.3.3/3rdparty/Makefile.am000066400000000000000000000005411263211400600166070ustar00rootroot00000000000000# # pepper - SCM statistics report generator # Copyright (C) 2010-present Jonas Gehring # # Released under the GNU General Public License, version 3. # Please see the COPYING file in the source distribution for license # terms and conditions, or see http://www.gnu.org/licenses/. # SUBDIRS = if BUILTIN_LEVELDB SUBDIRS += leveldb DIST_SUBDIRS = endif pepper-0.3.3/3rdparty/leveldb/000077500000000000000000000000001263211400600161705ustar00rootroot00000000000000pepper-0.3.3/AUTHORS000066400000000000000000000000531263211400600140510ustar00rootroot00000000000000Jonas Gehring pepper-0.3.3/COPYING000066400000000000000000001045131263211400600140420ustar00rootroot00000000000000 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 . pepper-0.3.3/CREDITS000066400000000000000000000016141263211400600140250ustar00rootroot00000000000000pepper - SCM statistics report generator ======================================== pepper utilizes several third-party libraries which are shipped with the source code distribution. They can be found at src/3rdparty with their respective READMEs and License files. For the sake of credit and completeness, here's a brief alphabetical listing: * lunar, C++ class binding for Lua Author is unknown (to me) License: Seems to be in public domain http://lua-users.org/wiki/CppBindingWithLunar * CATCH, C++ Adaptive Test Cases in Headers Author: Phil Nash License: Boost Software License https://github.com/philsquared/Catch The background image used on the pepper website (http://scm-pepper.sourceforge.net) is derived work from http://en.wikipedia.org/wiki/File:4_color_mix_of_peppercorns.jpg, and therefore licensed under the Creative Commons Attribution-Share Alike 1.0 Generic license. pepper-0.3.3/ChangeLog000066400000000000000000000152311263211400600145570ustar00rootroot000000000000002015-12-09 -- version 0.3.3 - Support for Subversion 1.9 - Numerous fixes for multi-threading - Improved Mac OS X compatibility - Bug fixes regarding the lifetime of wrapped Lua objects 2012-06-01 -- version 0.3.2 - Fixed compilation with GCC 4.7 - Fixed memory leaks - Use points instead of dots for the times report 2012-03-16 -- version 0.3.1 - Fixed Gnuplot invocation if compiled without X11 backend (e.g., on Mac OS X or headless environments). - Experimental support for LevelDB cache backend - Support for pie charts via - Fixed working copy detection for Subversion 1.7 - Fixed tree listing for Subversion repositories - New reports or report options: * Added authors_pie report (author contribution pie chart) * Added volume report (commit or contribution volume by author) * Added --pie flag to files report - Internal changes: * Directly write data to Gnuplot, i.e. don't use a temporary file * Removed rather useless safety check for report modules * Added checks for pipe I/O errors 2011-12-21 -- version 0.3 - Changes affecting the report API: * Added explicit report context that is being passed to all report functions * Added top-level pepper module * Improved report access inside of reports (listing reports, meta-data information, execution) * Reports should provide a describe() function instead of a global meta table * Added cat() function to repository class * Added optional recursion to pepper.utils.unlink() * Made revision prefetching during iteration optional (enabled by default) * Added date range command line option handling to pepper.datetime - Built-in reports: * Added a graphical user interface report, using the lgob Lua bindings to GTK * Fixed LOC calculation in CSV report * Added date range options to most reports - Backends: * Restored compatibility with Subversion 1.5 * Minor bugfixes for the Mercurial backend - Replaced gnuplot-cpp with a custom Gnuplot interface to be able to redirect Gnuplot output to an arbitrary std::ostream - Added locking of cache directories to prevent data corruption upon simultaneous access from multiple program instances 2011-08-06 -- version 0.2.4 * #3386735: Fixed Subverison log fetching for deeper subdirectories * Fixes for thresholds and messages in age report 2011-06-22 -- version 0.2.3 * Added punchcard report * #3314428: Fixed Subversion log fetching when there is no cache directory yet. * Fixed possible memory leaks in Subversion backend * #3314434: Check for Gnuplot in configure 2011-05-02 -- version 0.2.2 - Improvements and bugfixes for the Git backend: * Faster prefetching of diffstats * Added prefetching of meta-data * Fixed parsing of commit dates - New reports: * Added participation report which renders a histogram visualizing the contribution of a single author compared to the whole development team. * Added age report, printing the age and next birthday of a repository. - Added datetime Lua module 2011-04-06 -- version 0.2.1 - Improvements and bugfixes for the Subversion backend: * Added log caching * Added working copy detection * Fixed possible "too many open files" error when fetching diffstats * Fixed diffstats for deleted files * Fixed issues with subdirectory handling 2011-03-28 -- version 0.2 - Built-in reports: * Added activity report, a port of Thomas Capricelli's activity extension for Mercurial * Added commit_counts report, a general version of the former commits_per_month report with a variable time range * Added CSV report to extract repository data for further processing * Fixed possible bug in LOC report for simultaneous commits * Renamed commit_scatter report to "times" - Changes affecting the report API: * Added plotutils Lua module that facilitates common plot setup tasks * Added iterator class for flexible revision iteration, including specification of date ranges. * Many new API functions, including the ability to run other reports * Renamed API functions (note that the old ones are still available for compatibility reasons): - repository.default_branch() (instead of main_branch()) - Documentation: * Added man page, generated if AsciiDoc and xmlto are present - Implementation: * Rewrote diffstat fetching for the Subversion backend, and now diff data for deleted files is fetched, too. * The number of threads for the Subversion backend can now be changed using the --threads command-line option * Removed popen-noshell * Now using Phil Nash's CATCH for unit testing 2011-01-31 -- version 0.1.5 * Added filetypes report * New Lua API functions: gnuplot:plot_histogram() and repository:tree() * The cache can be checked by running the "check_cache" report now, which replaces the "--check-cache" flag * If the repository argument is omitted, the current directory will be used now * Fixed gnuplot invocation with some non-US locales (#3162914, reported by Dominik Geyer) * Fixed possible segmentation fault when reading from cache (#3162922, reported by Dominik Geyer) * Fixed help screen for reports without meta.title 2011-01-20 -- version 0.1.4 * Added additional arguments for controlling the output of graphical reports. The default is now stdout, using the SVG format. * Added support for custom Subversion repository layouts * Added support for searching report scripts in the directories specified via the PEPPER_REPORTS environment variable * Added lazy cache inizialization * The cache check will clear caches with a wrong version number now * Fixed problems caused by special characters in Git branches and tags 2011-01-15 -- version 0.1.3 * Added directories report, visualizing directory sizes over time * Added tag markers in authors report * Fixed ambiguous (and wrong) diffs for merge commits in Git and Mercurial repositories. * Fixed branch walking on subdirectories in Subversion repositories * Linearized branch walking in Git and Mercurial repositories * Fixed plotting in headless environments * Fixed wrong date calculations in commits_per_month report 2011-01-12 -- version 0.1.2 * Added tag access for scripts, and optional tag markers in "Lines of Code" report * Fixed tags and branches listing for Subversion repositories accessed via http:// * Fixed signal blocking during cache file writing * Removed test suite from default non-debug build 2010-12-28 -- version 0.1.1 * Fixed running built-in reports without specifying the full path * Fixed multi-threading issues * Improved Lua detection in configuration * Added status message for cache check 2010-12-20 -- version 0.1 * Initial release pepper-0.3.3/INSTALL000066400000000000000000000040251263211400600140350ustar00rootroot00000000000000pepper - SCM statistics report generator ======================================== Basic installation ------------------ pepper uses the Autoconf and Automake build systems, so the standard procedure using ./configure can be used. If you need more general information on configure scripts as produced by Autoconf, please head to http://www.gnu.org/software/autoconf/manual/autoconf.html#Running-configure-Scripts or pass `--help` to the configuration script. The rest of this document decribes special configuration options that are not covered by the standard Autoconf manuals. Available SCM backends ---------------------- pepper ships with the following backends. The respective command line switches for the configuration script are given in brackets. * Git (--[en|dis]able-git) The Git backend uses uses the standard git command line client. * Subversion (--[en|dis]able-svn) This backend uses the Subversion C API to access the respective repositories. All access methods provided by the API are supported, e.g. file://, http:// or svn://. * Mercurial (--[en|dis]able-mercurial) The hg backend uses the Python C API to access Mercurial's python interface directly. Optional features ----------------- Several additional, optional features are available (see the CREDITS file for more information about the respective libraries, if any) * Gnuplot interface (--[en|dis]able-gnuplot) A Lua interface to Gnuplot. Reports can use it to plot data graphically. This feature is enabled by default. * Man page (--[en|dis]able-man) pepper ships with a comprehensive man page, but both AsciiDoc and xmlto will be needed to generate it. This feature is enabled by default if the necessary programs are present. * LevelDB support (--[en|dis]able-leveldb, --with-leveldb) This is an experimental feature which allows you to use LevelDB for the revision cache. You can either use a propertly installed version or clone the official git repository into 3rdparty/leveldb. This feature is disabled by default. pepper-0.3.3/Makefile.am000066400000000000000000000016051263211400600150410ustar00rootroot00000000000000# # pepper - SCM statistics report generator # Copyright (C) 2010-present Jonas Gehring # # Released under the GNU General Public License, version 3. # Please see the COPYING file in the source distribution for license # terms and conditions, or see http://www.gnu.org/licenses/. # SUBDIRS = 3rdparty src reports if MANPAGE SUBDIRS += docs endif if TESTSUITE SUBDIRS += tests endif # Generate luadoc documentation LUADOCS = \ docs/lua/diffstat.luadoc \ docs/lua/gnuplot.luadoc \ docs/lua/iterator.luadoc \ docs/lua/pepper.luadoc \ docs/lua/report.luadoc \ docs/lua/repository.luadoc \ docs/lua/revision.luadoc \ docs/lua/tag.luadoc \ docs/lua/utils.luadoc \ reports/pepper/datetime.lua \ reports/pepper/plotutils.lua luadoc: $(LUADOCS) luadoc -d luadoc --nofiles $(LUADOCS) if MANPAGE htmlman: $(MAKE) $(AM_MAKEFLAGS) -C docs htmlman endif DISTCHECK_CONFIGURE_FLAGS = --enable-tests pepper-0.3.3/NEWS000066400000000000000000000000001263211400600134700ustar00rootroot00000000000000pepper-0.3.3/README000066400000000000000000000064471263211400600136760ustar00rootroot00000000000000pepper - Repository statistics and report tool ============================================== About ----- pepper is a flexible command-line tool for retrieving statistics and generating reports from source code repositories. It ships with several graphical and textual reports, and is easily extendible using the Lua scripting language. pepper includes support for multiple version control systems, including Git and Subversion. Using native language bindings, multi-threading and a local revision cache, it provides fast access to repository data. This README file contains brief configuration, installation and usage instructions. Additional documentation is available at the project homepage at . Requirements ------------ First, you need a working C++ compiler and standard library. Furthermore, pepper depends on the following third-party software: * zlib * POSIX threads * Lua 5.1 The following dependencies are optional: * Python development files and the Mercurial modules (for the Mercurial backend) * Subversion development files (for the Subversion backend) * The standard Git command line client (for the Git backend) * Gnuplot (for graphical reports) * Asciidoc and xmlto (for the man page) * LevelDB for the experimental LevelDB revision cache * lgob bindings to GTK3 (for the GUI report) pepper has been written in a hopefully platform-independent manner, and should compile and run on all major Unix platforms, e.g. Linux, BSD and Mac OS X. I'm not planning to work too hard on a Windows port. Compilation ----------- pepper uses the Autoconf and Automake build systems, so the standard way of running $ ./configure $ make # make install should be fine. If the configure script is not provided, generate it by running ./autogen.sh. Additional instructions can be found in the INSTALL file included in the source code distribution. Usage ----- If you've installed pepper somewhere in your $PATH, you can just cd to a repository of interest and invoke `pepper loc`, for example. On Mac OS X, you might want to use the aqua terminal driver for Gnuplot (i.e. `pepper loc -taqua` or `GNUTERM=aqua pepper loc`). If you haven't installed the program yet, you'll need to set the directory of available reports via the $PEPPER_REPORTS environment variable, for example using `export PEPPER_REPORTS=$PWD/reports`. An explanation of the command line switches available as well as Common usage examples can be found at the man page. If you didn't generate it while building the software, you can access it online at . License ------- pepper - Repository statistics and report tool Copyright (C) 2010-present Jonas Gehring 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 . pepper-0.3.3/ROADMAP000066400000000000000000000010331263211400600140060ustar00rootroot00000000000000A non-obligatory roadmap for pepper =================================== Planned features for 0.3.x: * Bazaar backend * libgit2 backend * Meta data pre-fetching for Subversion backend * Windows version using the GUI report * HTML module for generating custom HTML reports * Speed improvements for Subversion backend Planned features for 0.4: * Diff access * Report-specific caches * Filter module (cloc and friends) * Cache backends using "real" databases * Drop deprecated CSV report Legend: * : todo # : wontfix - : done pepper-0.3.3/autogen.sh000077500000000000000000000001031263211400600147760ustar00rootroot00000000000000#!/bin/sh aclocal autoheader automake --add-missing --gnu autoconf pepper-0.3.3/configure.ac000066400000000000000000000076701263211400600153030ustar00rootroot00000000000000# # pepper - SCM statistics report generator # Copyright (C) 2010-present Jonas Gehring # # Released under the GNU General Public License, version 3. # Please see the COPYING file in the source distribution for license # terms and conditions, or see http://www.gnu.org/licenses/. # AC_PREREQ([2.6]) AC_INIT(pepper, 0.3.3, jonas.gehring@boolsoft.org) AM_INIT_AUTOMAKE([subdir-objects]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_TESTDIR([tests]) # Custom arguments that should be listed first AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [Turn on debugging])], [debug="$enableval"], [debug="no"]) AC_ARG_ENABLE([tests], [AS_HELP_STRING([--enable-tests], [Build the test suite])], [testsuite="$enableval"], [testsuite="ifdebug"]) sinclude(m4/ax_check_zlib.m4) sinclude(m4/ax_cxx_compile_stdcxx_11.m4) sinclude(m4/ax_lua.m4) sinclude(m4/ax_pthread.m4) sinclude(m4/configure_backends.m4) sinclude(m4/configure_features.m4) # # Checks for programs # AC_LANG([C++]) AX_CXX_COMPILE_STDCXX_11() AC_PROG_INSTALL() AC_PROG_RANLIB() # # Checks for system and compiler characteristics # AC_C_BIGENDIAN() AC_CHECK_HEADER([CoreServices/CoreServices.h], [AC_DEFINE([HAVE_CORESERVICES], [1], [Define if you have the CoreServices API]) FRAMEWORKS="$FRAMEWORKS -framework CoreServices"]) # # Checks for types # AC_TYPE_UINT64_T() # # Checks for headers # AC_CHECK_HEADERS([sys/time.h]) AC_HEADER_STDBOOL() # # Checks for functions # AC_FUNC_MALLOC() AC_FUNC_MKTIME() AC_CHECK_FUNCS([atexit getcwd gettimeofday memmove mkdir realpath setenv strtol strchr vsnprintf]) AC_FUNC_STRERROR_R() if test "x$popen_noshell" = "xyes"; then AC_CHECK_FUNCS([strdup dup2 memset]) AC_FUNC_ALLOCA() AC_FUNC_REALLOC() AC_FUNC_FORK() fi # # Checks for libraries # AX_PTHREAD() LUA_SUFFIXES="$with_lua_suffix" if test "x$LUA_SUFFIXES" = "x"; then LUA_SUFFIXES="- 51 5.1 51/lua" fi LUA_FOUND="no" for with_lua_suffix in $LUA_SUFFIXES; do if test "$with_lua_suffix" = "-"; then with_lua_suffix="" fi AX_WITH_LUA() if test "x$LUA" = "x"; then continue fi AX_LUA_VERSION([501]) AX_LUA_HEADERS() AX_LUA_HEADERS_VERSION([501]) if test "x$LUA_HEADERS_IN_RANGE" != "xyes"; then continue fi AX_LUA_LIBS() if test "x$LUA_LIB" = "x"; then continue fi AC_SUBST([LUA_INCLUDE]) AC_SUBST([LUA_LIB]) LUA_FOUND="yes" break done if test "x$LUA_FOUND" = "xno"; then AC_MSG_ERROR([Lua could not be found. Please use the --with-lua-prefix option.]) fi CHECK_ZLIB() if test "x$want" = "xno"; then AC_MSG_ERROR([zlib is mandatory and cannot be disabled.]) fi # # Backend configuration # BACKENDS_CHECK() AM_CONDITIONAL([GIT_BACKEND], [test "x$git" = "xyes"]) AM_CONDITIONAL([MERCURIAL_BACKEND], [test "x$mercurial" = "xyes"]) AM_CONDITIONAL([SVN_BACKEND], [test "x$subversion" = "xyes"]) # # Feature configuration # FEATURES_CHECK() AM_CONDITIONAL([GNUPLOT], [test "x$gnuplot" = "xyes"]) AM_CONDITIONAL([MANPAGE], [test "x$manpage" = "xyes"]) AM_CONDITIONAL([LEVELDB], [test "x$leveldb" = "xyes"]) AM_CONDITIONAL([BUILTIN_LEVELDB], [test "x$builtin_leveldb" = "xyes"]) # # Debugging and unit tests # if test "x$debug" = "xyes"; then CXXFLAGS="${CXXFLAGS} -O0 -g -DDEBUG" CFLAGS="${CFLAGS} -O0 -g -DDEBUG" else CXXFLAGS="-O2 ${CXXFLAGS} -DNDEBUG" CFLAGS="-O2 ${CFLAGS} -DNDEBUG" fi AM_CONDITIONAL([DEBUG], [test "x$debug" = "xyes"]) if test "x$testsuite" = "xifdebug" -a "x$debug" = "xyes"; then testsuite="yes" fi AM_CONDITIONAL([TESTSUITE], [test "x$testsuite" = "xyes"]) # Use -rdynamic for stack traces on non-OS X AM_CONDITIONAL([RDYNAMIC], [test "x$debug" = "xyes" && test "x`uname`" != "xDarwin"]) # # Write Makefiles # AC_SUBST([FRAMEWORKS]) AC_CONFIG_FILES([ Makefile src/Makefile tests/Makefile tests/atlocal tests/diffstat/Makefile tests/units/Makefile tests/backends/Makefile reports/Makefile reports/pepper/Makefile docs/Makefile 3rdparty/Makefile ]) AC_OUTPUT() # # Report # BACKENDS_REPORT() FEATURES_REPORT() echo echo "Configuration finished, please run make now." pepper-0.3.3/contrib/000077500000000000000000000000001263211400600144435ustar00rootroot00000000000000pepper-0.3.3/contrib/packages/000077500000000000000000000000001263211400600162215ustar00rootroot00000000000000pepper-0.3.3/contrib/packages/arch/000077500000000000000000000000001263211400600171365ustar00rootroot00000000000000pepper-0.3.3/contrib/packages/arch/.gitignore000066400000000000000000000000121263211400600211170ustar00rootroot00000000000000pkg/ src/ pepper-0.3.3/contrib/packages/arch/PKGBUILD000066400000000000000000000016401263211400600202630ustar00rootroot00000000000000# Maintainer: Jonas Gehring pkgname=scm-pepper pkgver=0.3.2 pkgrel=2 pkgdesc="Repository statistics and report tool" url="http://scm-pepper.sourceforge.net" arch=('i686' 'x86_64') license=('GPL3') depends=('lua51' 'subversion>=1.5' 'mercurial' ) makedepends=('git' 'gnuplot' 'subversion' 'mercurial' 'xmlto' 'asciidoc' ) optdepends=('git: Git repository access' 'subversion: Subversion repository access' 'mercurial: Mercurial repository access' 'gnuplot: plotting support for reports' 'lgob-git: GUI for report selection and editing') source=("http://prdownloads.sourceforge.net/scm-pepper/pepper-${pkgver}.tar.bz2") sha1sums=('d1cad88093e10ba903bdbf7253d1f555f70697bb') build() { cd ${srcdir}/pepper-${pkgver} ./configure --prefix=/usr --with-lua-suffix=5.1 --disable-dependency-tracking PYTHON_VERSION=2 make } package() { cd ${srcdir}/pepper-${pkgver} make DESTDIR="$pkgdir/" install } pepper-0.3.3/docs/000077500000000000000000000000001263211400600137335ustar00rootroot00000000000000pepper-0.3.3/docs/.gitignore000066400000000000000000000000431263211400600157200ustar00rootroot00000000000000pepper.1 manpage.xml pepper.1.html pepper-0.3.3/docs/CACHE000066400000000000000000000061241263211400600144640ustar00rootroot00000000000000Description of the pepper cache format ====================================== The repository cache is actually a directory, named with the repository's UUID and containing two or more files: * index This is the index file, mapping revision identifiers to files and offsets. It is a gzipped text file with the following format: $VERSION $REVISION_1 $FILEINDEX $OFFSET $CRC $REVISION_2 $FILEINDEX $OFFSET $CRC ... $VERSION is a 32bit unsigned integer definining the format version, which is currently 5. $REVISION_i is a null-terminated string, representing a revision ID. Immediately afer the null byte, a 4-byte unsigned integer specifying the index of the cache file. Finally, there's a nother 4-byte unsigned integer that defines the offset in the cache file. The $CRC checksum is a simple CRC-32 checksum. of the compressed revision data. * cache.N where N is the file index. The cache file is just a series of gzipped revisions which can be identified using the current file offset and index and the table given in the index file. A revision contains the following data: $DATE $AUTHOR $MESSAGE $DIFFSTAT $DATE is a 64-bit integer, $AUTHOR and $MESSAGE are null-terminated strings. The diffstat data is made of the following components: $COUNT $FILE_ENTRY_1 $FILE_ENTRY_2 ... $COUNT is an unsigned 32-bit integer, representing the number of file entries that follow. Each one is given in the following format: $FILE $BYTES_ADDED $LINES_ADDED $BYTES_REMOVED $BYTES_ADDED $FILE is the name of the file, and the 4 unsigned 32-bit integers following describe the number of bytes or lines added and removed, respectively. Please note that single revisions are gzipped (using zlib's compress()), but not the whole cache file. This allows for faster seeking when selecting individual revisions. Primitive data is stored using the following conventions: * String: UTF-8 encoding, null-terminated * Integer: Big endian Description of the log interval cache for Subversion repositories ================================================================= Since retrieving the revision log for large Subversion repositories over the network is time-consuming, the Subversion backend stores revision intervals for specific paths in the cache directory of the respective repository. Here's a short description of the files used and their format: * log_$PREFIX where $PREFIX is the prefix that was used for retrieving the log data. This can represent a module, a branch or simply a subdirectory. This file is a gzipped and contains the following data. Each variable is an unsigned 64bit integer (except the unsigned 32bit version number), stored in big endian. $VERSION $STARTREV_1 $ENDREV_2 $NUMREVS_1 $REVNUM_11 $REVNUM_12 ... $STARTREV_2 $ENDREV_2 $NUMREVS_2 $REVNUM_21 $REVNUM_22 ... The current $VERSION is 1. Every file can store multiple intervals, each starting with a header of three integers defining the start and end revision used while fetching the log. The last integer defines the number of revisions that will follow. Each revision is single unsigned 64bit integer, too. pepper-0.3.3/docs/Makefile.am000066400000000000000000000013741263211400600157740ustar00rootroot00000000000000# # pepper - SCM statistics report generator # Copyright (C) 2010-present Jonas Gehring # # Released under the GNU General Public License, version 3. # Please see the COPYING file in the source distribution for license # terms and conditions, or see http://www.gnu.org/licenses/. # man_MANS = pepper.1 EXTRA_DIST = manpage.txt manpage.xml: $(srcdir)/manpage.txt $(ASCIIDOC) -b docbook -d manpage -o manpage.xml -a "version=$(VERSION)" -a "email=<\$(PACKAGE_BUGREPORT)>" $(srcdir)/manpage.txt pepper.1: manpage.xml $(XMLTO) man --skip-validation manpage.xml pepper.1.html: manpage.xml $(XMLTO) html-nochunks --skip-validation manpage.xml mv manpage.html pepper.1.html distclean-local: $(RM) -f pepper.1 $(RM) -f manpage.xml htmlman: manpage.html pepper-0.3.3/docs/lua/000077500000000000000000000000001263211400600145145ustar00rootroot00000000000000pepper-0.3.3/docs/lua/diffstat.luadoc000066400000000000000000000021131263211400600175060ustar00rootroot00000000000000--- Interface for diffstats module "pepper.diffstat" --- Constructs a copy of the source diffstat. function diffstat:new(source) --- Returns an array of all files in the diffstat. function diffstat:files() --- Returns the number of lines added. -- If the file parameter is omitted, the total number of lines -- added is being returned -- @param file An optional file name function diffstat:lines_added(file) --- Returns the number of bytes added. -- If the file parameter is omitted, the total number of bytes -- added is being returned -- @param file An optional file name function diffstat:bytes_added(file) --- Returns the number of lines removed. -- If the file parameter is omitted, the total number of lines -- removed is being returned -- @param file An optional file name function diffstat:lines_removed(file) --- Returns the number of bytes removed. -- If the file parameter is omitted, the total number of bytes -- removed is being returned -- @param file An optional file name function diffstat:bytes_removed(file) pepper-0.3.3/docs/lua/gnuplot.luadoc000066400000000000000000000077161263211400600174100ustar00rootroot00000000000000--- Gnuplot interface. -- This class can be used to generate graphical reports module "pepper.gnuplot" --- Sends a command to Gnuplot -- @param str The command string function cmd(str) --- Sets the output file for plotting -- @param filename Output filename -- @param width Optional image width (defaults to 640) -- @param height Optional image height (defaults to 480) -- @param terminal Optional explicit terminal type. This is normally -- inferred from the filename. function set_output(filename, width, height, terminal) --- Sets the plot title -- @param title The plot title function set_title(title) --- Sets the X and X2 axis range -- @param min Minimum X value -- @param max Maximum X value function set_xrange(min, max) --- Sets the X and X2 axis range -- @param min Minimum X timestamp -- @param max Maximum X timestamp function set_xrange_time(min, max) --- Plots one or more data series. -- The values parameter may contain multiple Y values per -- entry, i.e. it can be a two-dimensional array.
-- The last options parameter can either be a dictionary -- or a string. Using a dictionary, the following keys can be specified: -- -- -- -- --
KeyDescriptionDefault value
styleGnuplot style"lines"
commandPlot command string, inserted after plot "$FILE"

-- If options is a string, it will be used for the sytle key. -- @param keys The array of X values -- @param values The array of Y values -- @param titles Optional titles for the series --- @param options Additional options (see above) function plot_series(keys, values, titles, options) --- Plots multiple data series. -- The keys and values are arrays of the same -- size, each containing the same number of series. Each series (a pair -- of keys and values) will be plotted into the same graph.
-- The last options parameter can either be a dictionary -- or a string. Using a dictionary, the following keys can be specified: -- -- -- --
KeyDescriptionDefault value
styleGnuplot style"lines"

-- If options is a string, it will be used for the sytle key. -- @param keys The series array with X values -- @param values The series array with Y values -- @param titles Optional titles for the series --- @param options Additional options (see above) function plot_multi_series(keys, values, titles, style) --- Plots a histogram bar chart from data sequences. -- The values parameter may contain multiple Y values per -- entry, i.e. it can be a two-dimensional array.
-- The last options parameter can either be a dictionary -- or a string. Using a dictionary, the following keys can be specified: -- -- -- --
KeyDescriptionDefault value
styleGnuplot style

-- If options is a string, it will be used for the sytle key. -- @param keys The array of X values, used as the X-axis keys -- @param values The array of Y values -- @param titles Optional titles for the series --- @param options Additional options (see above) function plot_histogram(keys, values, titles, style) --- Plots a pie chart. -- titles is an array with titles that should be placed -- in the key. values has the same number of elements and -- defines percentage values for the titles. The values are expected to -- fall inside the interval [0,1]. -- @param titles Element titles -- @param values Element percentage values from [0,1] function plot_pie(titles, values) --- Finish plotting and restart the Gnuplot process. function flush() pepper-0.3.3/docs/lua/iterator.luadoc000066400000000000000000000007611263211400600175420ustar00rootroot00000000000000--- Revision log iterator. -- This class is used for iterating over a given revision range. -- Use pepper.repository:iterator to construct a revision iterator. module "pepper.iterator" --- Returns the next revision, or nil if there are no more revisions. function next() --- Returns a Lua iterator for the remaining revision ids. function revisions() --- Calls callback for all remaining revisions. -- @param callback The callback function function map(callback) pepper-0.3.3/docs/lua/pepper.luadoc000066400000000000000000000022521263211400600172010ustar00rootroot00000000000000--- General functions for interacting with the main program. module "pepper" --- Returns the current report context. -- @return A pepper.report instance -- @see pepper.report function current_report() --- Runs a report script, using the current repository. -- pepper tries to find a report named report (just -- like you would pass it on the command line) and runs it. -- If the options parameter is omitted, the options -- of the calling report are used.
-- You may want to use Lua's pcall() -- to wrap calls to this function. If the execution failed, an error -- will be pushed to the calling report. -- @param report Name or path of the report script -- @param options Optional dictionary with command-line options -- @return The report output on success, an error message on failure function run(report, options) --- Returns a listing of all reports in the current search directories. -- @return An array of report paths that can be used in run() function list_reports() --- Returns the version of the main program as a string -- @return A version string function version() pepper-0.3.3/docs/lua/report.luadoc000066400000000000000000000022551263211400600172240ustar00rootroot00000000000000--- Report script context. -- This module provides functions for the current program execution environment -- as well as report inspection and execution facilities. module "pepper.report" --- Constructor. -- @param path The report script path -- @param options Optional table of report options. If not given, the options -- of the current report will be used. function report:new(path, options) --- Returns a command line option for the report script. -- @param option The command line flags, separated with commas -- @param default An optional default option value function report:getopt(option, default) --- Returns the current repository. -- @return A repository instance -- @see pepper.repository function report:repository() --- Runs this report. -- This function is similar to pepper.run(), except that is uses this report -- context instead of constructing a new one -- @see pepper function report:run() --- Returns the report's accepted options. -- @return A table with 2 entries per option (synopsis and description). function report:options() --- Returns the report's name. function report:name() --- Returns the report's description. function report:description() pepper-0.3.3/docs/lua/repository.luadoc000066400000000000000000000035251263211400600201310ustar00rootroot00000000000000--- Repository interface. -- This class can used to examine a repository and gather statistics. module "pepper.repository" --- Returns the repository URL. function url() --- Returns the repository type. -- This is actually the name of the current backend, e.g. "git", -- "subversion" or "mercurial". function type() --- Determines and returns the current HEAD revision. -- @param branch An optional branch name function head(branch) --- Returns the standard branch of the repository. -- For working copies, this is the branch that is curently checked out. function default_branch() --- Determines and returns a table listing all branches. function branches() --- Determines and returns a table listing all tags as tag objects. -- @see pepper.tag function tags() --- Returns a list of all files in the repository. -- @param id Optional revision ID, defaults to current (i.e., the HEAD revision) function tree(id) --- Returns the contents of a given file. -- @param file File path, relative to repository root -- @param id Optional revision ID, defaults to current (i.e., the HEAD revision) function cat(file, id) --- Fetches a specific revision. -- @param id The revision ID -- @return The revision object -- @see pepper.revision function revision(id) --- Returns a revision iterator for the given branch. -- The following options will be added: -- -- -- -- -- --
KeyDescriptionDefault value
startMinimum time stamp for commitsnone
stopMaximum time stamp for commitsnone
prefetchTurn pre-fetching of revisions on or offtrue
-- @param branch The name of the branch -- @param options Optional table with additional parameters -- @see pepper.iterator function iterator(branch, options) pepper-0.3.3/docs/lua/revision.luadoc000066400000000000000000000016011263211400600175410ustar00rootroot00000000000000--- This object represents a single revision. module "pepper.revision" --- Returns the id of the revision. -- For Git and Mercurial repositories, this is the commit hash. For Subversion, -- this is the revision number (as a string). function id() --- Returns the id of the parent revision, if any. -- When examining a revision range, the repository history will need be linearized -- for repositories like Git and Mercurial. Thus, a revision might have an explicit -- parent revision that is being used when retrieving the diffstat. function parent_id() --- Returns the author of the revision. function author() --- Returns the date of the revision. -- The date will be returned as a UNIX timestamp (in seconds). function date() --- Returns the commit message of the revision. function message() --- Returns the diffstat of the revision. -- @see pepper.diffstat function diffstat() pepper-0.3.3/docs/lua/tag.luadoc000066400000000000000000000002051263211400600164550ustar00rootroot00000000000000--- Simple tag object module "pepper.tag" --- Returns the tag name function name() --- Returns the tag revision ID function id() pepper-0.3.3/docs/lua/utils.luadoc000066400000000000000000000026741263211400600170560ustar00rootroot00000000000000--- Miscellaneous utility functions module "pepper.utils" --- Opens a unique temporary file. -- @param template An optional filename template -- @return A pair containing the file handle and the file name function mkstemp(template) --- Removes a file or directory. -- @param path The path to the file -- @param recurse Recurse into subdirectories (default is false) function unlink(path, recurse) --- Splits the given string. -- @param string The string -- @param pattern A pattern specifying the split points (not a regular expression) function split(string, pattern) --- Wrapper for strptime(3). -- The C function strprime(3) can parse string representations of time. The -- time format will be passed directly to strptime(3), so you can check the -- corresponding man page -- for a possible field specifiers. -- @param s String describing a time -- @param format Time format -- @return A UNIX timestamp function strptime(s, format) --- Returns the directory portion of a pathname. -- This function acts like dirname(1), -- i.e. it also returns a dot if the given path contains no slash. function dirname(path) --- Returns the non-directory portion of a pathname. -- This function acts like basename(1), -- but doesn't strip suffixes. function basename(path) pepper-0.3.3/docs/manpage.txt000066400000000000000000000114011263211400600161010ustar00rootroot00000000000000pepper(1) ========= :doctype: manpage :man source: pepper :man version: {version} :man manual: User commands NAME ---- pepper - Repository statistics and report tool SYNOPSIS -------- *pepper* ['options'] 'report' ['report options'] ['repository'] DESCRIPTION ----------- *pepper* is a command-line tool for retrieving statistics and generating reports from source code repositories. If invoked with valid options,it runs a *lua*(1) script given by 'report' on the given 'repository', producing textual or graphical output. If the 'report' argument doesn't point to a *lua*(1) report script, a number of paths will be searched for a report with the given name (see *ENVIRONMENT VARIABLES*). Thus, the built-in reports can be launched by specifying their name only. Report-specific options can be passed as 'report options' following the report script name. To retrieve a listing of options supported by the respective report, pass *--help* as described in *OPTIONS*. If no 'repository' argument is present, the current directory will be used. Normally, the type of the repository is automatically detected, and an appropriate backend implementation will be selected. Some backends provide additional options, e.g. user authentication for remote repositories. Those options will be listed if you pass the *--help* flag as described in *OPTIONS*. OPTIONS ------- *-?*, *-h*, *--help*:: Print a nice help screen. If the command line includes a report script name or path, report options will be shown. Additionally, backend options will be shown for the selected repository or backend. *-q*:: *--quiet*:: Set verbosity level to minimum. Only warnings and errors will be shown. *-v*:: *--verbose*:: Increase verbosity level. Can be specified multiple times. *--no-cache*:: Neither read from nor write to the local revision cache. *--list-reports*:: List all reports that can be found in the current report search directories. *--list-backends*:: List all built-in repository backends. *-bARG, --backend=ARG*:: Force usage of backend named *ARG*. Use *--list-backends* to retrieve a list of all available backends. REVISION CACHE -------------- *pepper* uses a local revision cache, located at $HOME/.pepper/cache. It contains meta-data and diffstats of revisions that have been requested in previous invocations of the program. If the program complains that your revision cache is invalid (probably because of abnormal program termination or power failure), please run the *check_cache* report to fix it and remove faulty revisions. ENVIRONMENT VARIABLES --------------------- *PEPPER_REPORTS*:: A colon-separated list of paths used to search for report scripts. *PEPPER_CACHEDIR*:: A path that overrides the default cache location. EXAMPLES -------- Let's assume that the current directory is the repository of interest. pepper --list-reports:: This lists all reports in the current search path with their descriptions. The names from this listing can be used as the program's 'report' argument. pepper loc:: A classic "Lines of Code" graph will be generated, with the actual plotting done by *gnuplot*(1). If the user is running X11, a detached window containing the plot will be shown. Else, output in SVG format will be written to 'stdout'. All meta-data and diffstats fetched during this session will be written to the revision cache. pepper loc --type=png --output=loc.png:: The same as above, now probably significantly faster because the revisions of interest are already cached. This time, a PNG image will be generated and written to "loc.png". pepper authors -n4 --tags="2.6.[0-9]*$":: This time, the code contribution by the 4 busiest authors will be be plotted. Additionally, vertical tag marks will be show for all tags that look like releases (of the Linux kernel). pepper --username=user commit_counts --period=14d http://svn.example.org:: This generates a histogram of commit frequencies for the last 14 days. This time, a remote Subversion repository requiring authentication is being used. If a password is required, the program will prompt for it. pepper shortlog --branch=stable --summary:: This will print a simple commit summary from the "stable" branch to 'stdout', looking like *git-shortlog*(1). CUSTOM REPORTS -------------- *pepper* provides an API for writing custom report scripts in *lua*(1). The *pepper* homepage at http://scm-pepper.sourceforge.net contains more information about this topic, including a scripting tutorial covering common tasks and an API reference manual. EXIT STATUS ----------- 0 on success, 1 on failure. Any error messages, warnings and progress will be printed to 'stderr'. SEE ALSO -------- *git*(1), *svn*(1), *hg*(1), *lua*(1), *gnuplot*(1) AUTHOR ------ Copyright (C) 2010-present Jonas Gehring {email}. Released under the GNU General Public License. pepper-0.3.3/m4/000077500000000000000000000000001263211400600133235ustar00rootroot00000000000000pepper-0.3.3/m4/ax_check_zlib.m4000066400000000000000000000107271263211400600163610ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_check_zlib.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_ZLIB() # # DESCRIPTION # # This macro searches for an installed zlib library. If nothing was # specified when calling configure, it searches first in /usr/local and # then in /usr. If the --with-zlib=DIR is specified, it will try to find # it in DIR/include/zlib.h and DIR/lib/libz.a. If --without-zlib is # specified, the library is not searched at all. # # If either the header file (zlib.h) or the library (libz) is not found, # the configuration exits on error, asking for a valid zlib installation # directory or --without-zlib. # # The macro defines the symbol HAVE_LIBZ if the library is found. You # should use autoheader to include a definition for this symbol in a # config.h file. Sample usage in a C/C++ source is as follows: # # #ifdef HAVE_LIBZ # #include # #endif /* HAVE_LIBZ */ # # Changes by Jonas Gehring : # Respect the --without-zlib command line switch # # LICENSE # # Copyright (c) 2008 Loic Dachary # # 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 2 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 7 AU_ALIAS([CHECK_ZLIB], [AX_CHECK_ZLIB]) AC_DEFUN([AX_CHECK_ZLIB], # # Handle user hints # [AC_MSG_CHECKING(if zlib is wanted) AC_ARG_WITH(zlib, [ --with-zlib=DIR root directory path of zlib installation [defaults to /usr/local or /usr if not found in /usr/local] --without-zlib to disable zlib usage completely], [if test "$withval" != no ; then want="yes" AC_MSG_RESULT(yes) if test -d "$withval" then ZLIB_HOME="$withval" else AC_MSG_WARN([Sorry, $withval does not exist, checking usual places]) fi else want="no" AC_MSG_RESULT(no) fi], [want="yes" AC_MSG_RESULT(yes)]) if test "x$want" != "xno"; then if test -z "${ZLIB_HOME}"; then ZLIB_HOME=/usr/local if test ! -f "${ZLIB_HOME}/include/zlib.h"; then ZLIB_HOME=/usr fi fi # # Locate zlib, if wanted # if test -n "${ZLIB_HOME}"; then ZLIB_OLD_LDFLAGS=$LDFLAGS ZLIB_OLD_CPPFLAGS=$LDFLAGS LDFLAGS="$LDFLAGS -L${ZLIB_HOME}/lib" CPPFLAGS="$CPPFLAGS -I${ZLIB_HOME}/include" AC_LANG_SAVE AC_LANG_C AC_CHECK_LIB(z, inflateEnd, [zlib_cv_libz=yes], [zlib_cv_libz=no]) AC_CHECK_HEADER(zlib.h, [zlib_cv_zlib_h=yes], [zlib_cv_zlib_h=no]) AC_LANG_RESTORE if test "$zlib_cv_libz" = "yes" -a "$zlib_cv_zlib_h" = "yes"; then # # If both library and header were found, use them # AC_CHECK_LIB(z, inflateEnd) AC_MSG_CHECKING(zlib in ${ZLIB_HOME}) AC_MSG_RESULT(ok) else # # If either header or library was not found, revert and bomb # AC_MSG_CHECKING(zlib in ${ZLIB_HOME}) LDFLAGS="$ZLIB_OLD_LDFLAGS" CPPFLAGS="$ZLIB_OLD_CPPFLAGS" AC_MSG_RESULT(failed) AC_MSG_ERROR(either specify a valid zlib installation with --with-zlib=DIR or disable zlib usage with --without-zlib) fi fi fi ]) pepper-0.3.3/m4/ax_cxx_compile_stdcxx_11.m4000066400000000000000000000133061263211400600204700ustar00rootroot00000000000000# ============================================================================ # http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html # ============================================================================ # # SYNOPSIS # # AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the C++11 # standard; if necessary, add switches to CXXFLAGS to enable support. # # The first argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. # -std=c++11). If neither is specified, you get whatever works, with # preference for an extended mode. # # The second argument, if specified 'mandatory' or if left unspecified, # indicates that baseline C++11 support is required and that the macro # should error out if no mode with that support is found. If specified # 'optional', then configuration proceeds regardless, after defining # HAVE_CXX11 if and only if a supporting mode is found. # # LICENSE # # Copyright (c) 2008 Benjamin Kosnik # Copyright (c) 2012 Zack Weinberg # Copyright (c) 2013 Roy Stogner # Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov # Copyright (c) 2015 Paul Norman # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. #serial 13 m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ template struct check { static_assert(sizeof(int) <= sizeof(T), "not big enough"); }; struct Base { virtual void f() {} }; struct Child : public Base { virtual void f() override {} }; typedef check> right_angle_brackets; int a; decltype(a) b; typedef check check_type; check_type c; check_type&& cr = static_cast(c); auto d = a; auto l = [](){}; // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] struct use_l { use_l() { l(); } }; // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this namespace test_template_alias_sfinae { struct foo {}; template using member = typename T::member_type; template void func(...) {} template void func(member*) {} void test(); void test() { func(0); } } // Check for C++11 attribute support void noret [[noreturn]] () { throw 0; } ]]) AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl m4_if([$1], [], [], [$1], [ext], [], [$1], [noext], [], [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], [$2], [optional], [ax_cxx_compile_cxx11_required=false], [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) AC_LANG_PUSH([C++])dnl ac_success=no AC_CACHE_CHECK(whether $CXX supports C++11 features by default, ax_cv_cxx_compile_cxx11, [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], [ax_cv_cxx_compile_cxx11=yes], [ax_cv_cxx_compile_cxx11=no])]) if test x$ax_cv_cxx_compile_cxx11 = xyes; then ac_success=yes fi m4_if([$1], [noext], [], [dnl if test x$ac_success = xno; then for switch in -std=gnu++11 -std=gnu++0x; do cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, $cachevar, [ac_save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], [eval $cachevar=yes], [eval $cachevar=no]) CXXFLAGS="$ac_save_CXXFLAGS"]) if eval test x\$$cachevar = xyes; then CXXFLAGS="$CXXFLAGS $switch" ac_success=yes break fi done fi]) m4_if([$1], [ext], [], [dnl if test x$ac_success = xno; then dnl HP's aCC needs +std=c++11 according to: dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf dnl Cray's crayCC needs "-h std=c++11" for switch in -std=c++11 -std=c++0x +std=c++11 "-h std=c++11"; do cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, $cachevar, [ac_save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS $switch" AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], [eval $cachevar=yes], [eval $cachevar=no]) CXXFLAGS="$ac_save_CXXFLAGS"]) if eval test x\$$cachevar = xyes; then CXXFLAGS="$CXXFLAGS $switch" ac_success=yes break fi done fi]) AC_LANG_POP([C++]) if test x$ax_cxx_compile_cxx11_required = xtrue; then if test x$ac_success = xno; then AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) fi else if test x$ac_success = xno; then HAVE_CXX11=0 AC_MSG_NOTICE([No compiler with C++11 support was found]) else HAVE_CXX11=1 AC_DEFINE(HAVE_CXX11,1, [define if the compiler supports basic C++11 syntax]) fi AC_SUBST(HAVE_CXX11) fi ]) pepper-0.3.3/m4/ax_lua.m4000066400000000000000000000172121263211400600150410ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_lua.html # =========================================================================== # # SYNOPSIS # # AX_WITH_LUA # AX_PROG_LUA [(MIN-VERSION, [TOO-BIG-VERSION])] # AX_LUA_VERSION (MIN-VERSION, [TOO-BIG-VERSION]) # AX_LUA_HEADERS # AX_LUA_HEADERS_VERSION (MIN-VERSION, [TOO-BIG-VERSION]) # AX_LUA_LIBS # AX_LUA_READLINE # # DESCRIPTION # # Detect Lua interpreter, headers and libraries, optionally enforcing a # particular range of versions. If only one version is given, then exactly # this version is required. # # AX_WITH_LUA searches for a Lua interpreter and defines LUA if found. # # AX_PROG_LUA searches for a Lua interpreter in the given version range, # if any, and defines LUA if found, or stops with an error if not. # # AX_LUA_VERSION checks that the version of Lua is at least MIN-VERSION # and less than TOO-BIG-VERSION, if given. # # AX_LUA_HEADERS searches for Lua headers and defines HAVE_LUA_H and # HAVE_LUALIB_H if found, and defines LUA_INCLUDE to the preprocessor # flags needed, if any. # # AX_LUA_HEADERS_VERSION checks that the Lua headers' version is at least # MIN-VERSION, and less than TOO-BIG-VERSION, if given. # # AX_LUA_LIBS searches for Lua libraries and defines LUA_LIB if found. # # AX_LUA_READLINE configures Lua to be built with readline support, if # available. This macro requires AX_LIB_READLINE. # # Versions are specified as three-digit integers whose first digit is the # major version and last two are the minor version (the same format as # LUA_VERSION_NUM in lua.h); e.g. 501 for Lua 5.1. The revision (e.g. the # "3" in "5.1.3") is ignored. # # The following options are added by these macros: # # --with-lua-prefix=DIR Lua files are in DIR. # --with-lua-suffix=ARG Lua binaries and library files are # suffixed with ARG. # --with-lua-include=DIR Lua headers are in DIR # # LICENSE # # Copyright (c) 2011 Reuben Thomas # Copyright (c) 2009 Matthieu Moy # Copyright (c) 2009 Tom Payne # # 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 14 dnl Helper function to declare extra options AC_DEFUN([_AX_LUA_OPTS], [AC_ARG_WITH([lua-prefix], [AS_HELP_STRING([--with-lua-prefix=DIR], [Lua files are in DIR])]) AC_ARG_WITH([lua-suffix], [AS_HELP_STRING([--with-lua-suffix=ARG], [Lua binary and library files are suffixed with ARG])]) AC_ARG_WITH([lua-include], [AS_HELP_STRING([--with-lua-include=DIR], [Lua headers are in DIR])])])dnl AC_DEFUN([AX_WITH_LUA], [_AX_LUA_OPTS if test "x$with_lua_prefix" = x; then lua_search_path="$PATH" else lua_search_path="$with_lua_prefix/bin" fi if test "x$LUA" = x; then AC_PATH_PROG([LUA], [lua$with_lua_suffix], [], [$lua_search_path]) fi])dnl AC_DEFUN([AX_PROG_LUA], [lua_min_version=$1 lua_max_version=$2 AX_WITH_LUA if test -z "$LUA"; then AC_MSG_FAILURE([Lua not found]) fi if test -n "$lua_min_version"; then AX_LUA_VERSION($lua_min_version, $lua_max_version) fi AC_SUBST(LUA)])dnl dnl Helper function to parse minimum & maximum versions AC_DEFUN([_AX_LUA_VERSIONS], [lua_min_version=$1 lua_max_version=$2 if test "x$lua_min_version" = x; then lua_min_version=0 fi if test "x$lua_max_version" = x; then lua_max_version=$(($lua_min_version + 1)) fi]) AC_DEFUN([AX_LUA_VERSION], [_AX_LUA_OPTS _AX_LUA_VERSIONS($1, $2) AC_MSG_CHECKING([Lua version is in range $1 <= v < $2]) if test "x$LUA" != x; then lua_text_version=$(LUA_INIT= $LUA -e 'print(_VERSION)' 2>&1 | cut -d' ' -f2) case $lua_text_version in 5.2*) lua_version=502 ;; 5.1*) lua_version=501 ;; 5.0*) lua_version=500 ;; 4.0*) lua_version=400 ;; *) lua_version=-1 ;; esac if test $lua_version -ge "$lua_min_version" && test $lua_version -lt "$lua_max_version"; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) AC_MSG_FAILURE([Lua version not in desired range.]) fi else AC_MSG_RESULT([no]) AC_MSG_FAILURE([Lua version not in desired range.]) fi])dnl AC_DEFUN([AX_LUA_HEADERS], [_AX_LUA_OPTS if test x"$with_lua_include" != x; then LUA_INCLUDE="-I$with_lua_include" elif test "x$with_lua_prefix" != x; then LUA_INCLUDE="-I$with_lua_prefix/include" fi if test "x$with_lua_suffix" != x; then LUA_INCLUDE="$LUA_INCLUDE -I/usr/include/lua$with_lua_suffix" fi LUA_INCLUDE="$LUA_INCLUDE -I/usr/include" for test in $LUA_INCLUDE; do LUA_OLD_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" AC_CHECK_HEADERS([lua.h lualib.h], [header_found="yes"]) CPPFLAGS="$LUA_OLD_CPPFLAGS" if test "x$headers_found" != x; then LUA_INCLUDE="$test" break fi done])dnl AC_DEFUN([AX_LUA_LIBS], [_AX_LUA_OPTS if test "x$with_lua_prefix" != x; then LUA_LIB="-L$with_lua_prefix/lib" fi AC_CHECK_LIB([m], [exp], [lua_extra_libs="$lua_extra_libs -lm"], []) AC_CHECK_LIB([dl], [dlopen], [lua_extra_libs="$lua_extra_libs -ldl"], []) AC_CHECK_LIB([lua$with_lua_suffix], [lua_call], [LUA_LIB="$LUA_LIB -llua$with_lua_suffix $lua_extra_libs"], [], [$LUA_LIB $lua_extra_libs])])dnl AC_DEFUN([AX_LUA_HEADERS_VERSION], [_AX_LUA_OPTS _AX_LUA_VERSIONS($1, $2) AC_MSG_CHECKING([lua.h version is in range $1 <= v < $2]) LUA_HEADERS_IN_RANGE="no" LUA_OLD_LIBS="$LIBS" LIBS="$LIBS $LUA_LIB" LUA_OLD_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" AC_RUN_IFELSE([AC_LANG_SOURCE([[ #include #include #include int main() { printf("(found %s, %d)... ", LUA_VERSION, LUA_VERSION_NUM); if (LUA_VERSION_NUM >= $lua_min_version && LUA_VERSION_NUM < $lua_max_version) exit(EXIT_SUCCESS); exit(EXIT_FAILURE); } ]])], [AC_MSG_RESULT([yes]) LUA_HEADERS_IN_RANGE="yes"], [AC_MSG_RESULT([no])]) LIBS="$LUA_OLD_LIBS" CPPFLAGS="$LUA_OLD_CPPFLAGS"])dnl AC_DEFUN([AX_LUA_READLINE], [AX_LIB_READLINE if test -n "$ac_cv_header_readline_readline_h" && test -n "$ac_cv_header_readline_history_h"; then LUA_LIBS_CFLAGS="-DLUA_USE_READLINE $LUA_LIBS_CFLAGS" fi])dnl pepper-0.3.3/m4/ax_pthread.m4000066400000000000000000000257741263211400600157230ustar00rootroot00000000000000# =========================================================================== # http://www.nongnu.org/autoconf-archive/ax_pthread.html # =========================================================================== # # SYNOPSIS # # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) # # DESCRIPTION # # This macro figures out how to build C programs using POSIX threads. It # sets the PTHREAD_LIBS output variable to the threads library and linker # flags, and the PTHREAD_CFLAGS output variable to any special C compiler # flags that are needed. (The user can also force certain compiler # flags/libs to be tested by setting these environment variables.) # # Also sets PTHREAD_CC to any special C compiler that is needed for # multi-threaded programs (defaults to the value of CC otherwise). (This # is necessary on AIX to use the special cc_r compiler alias.) # # NOTE: You are assumed to not only compile your program with these flags, # but also link it with them as well. e.g. you should link with # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS # # If you are only building threads programs, you may wish to use these # variables in your default LIBS, CFLAGS, and CC: # # LIBS="$PTHREAD_LIBS $LIBS" # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # CC="$PTHREAD_CC" # # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant # has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name # (e.g. PTHREAD_CREATE_UNDETACHED on AIX). # # ACTION-IF-FOUND is a list of shell commands to run if a threads library # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it # is not found. If ACTION-IF-FOUND is not specified, the default action # will define HAVE_PTHREAD. # # Please let the authors know if this macro fails on any platform, or if # you have any other suggestions or comments. This macro was based on work # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by # Alejandro Forero Cuervo to the autoconf macro repository. We are also # grateful for the helpful feedback of numerous users. # # LICENSE # # Copyright (c) 2008 Steven G. Johnson # # 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 5 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ AC_REQUIRE([AC_CANONICAL_HOST]) AC_LANG_SAVE AC_LANG_C ax_pthread_ok=no # We used to check for pthread.h first, but this fails if pthread.h # requires special compiler flags (e.g. on True64 or Sequent). # It gets checked for in the link test anyway. # First of all, check if the user has set any of the PTHREAD_LIBS, # etcetera environment variables, and if threads linking works using # them: if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) AC_MSG_RESULT($ax_pthread_ok) if test x"$ax_pthread_ok" = xno; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" fi LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" fi # We must check for the threads library under a number of different # names; the ordering is very important because some systems # (e.g. DEC) have both -lpthread and -lpthreads, where one of the # libraries is broken (non-POSIX). # Create a list of thread flags to try. Items starting with a "-" are # C compiler flags, and other items are library names, except for "none" # which indicates that we try without any flags at all, and "pthread-config" # which is a program returning the flags for the Pth emulation library. ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" # The ordering *is* (sometimes) important. Some notes on the # individual items follow: # pthreads: AIX (must check this before -lpthread) # none: in case threads are in libc; should be tried before -Kthread and # other compiler flags to prevent continual compiler warnings # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) # -pthreads: Solaris/gcc # -mthreads: Mingw32/gcc, Lynx/gcc # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it # doesn't hurt to check since this sometimes defines pthreads too; # also defines -D_REENTRANT) # ... -mt is also the pthreads flag for HP/aCC # pthread: Linux, etcetera # --thread-safe: KAI C++ # pthread-config: use pthread-config program (for GNU Pth library) case "${host_cpu}-${host_os}" in *solaris*) # On Solaris (at least, for some versions), libc contains stubbed # (non-functional) versions of the pthreads routines, so link-based # tests will erroneously succeed. (We need to link with -pthreads/-mt/ # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather # a function called by this macro, so we could check for that, but # who knows whether they'll stub that too in a future libc.) So, # we'll just look for -pthreads and -lpthread first: ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" ;; esac if test x"$ax_pthread_ok" = xno; then for flag in $ax_pthread_flags; do case $flag in none) AC_MSG_CHECKING([whether pthreads work without any flags]) ;; -*) AC_MSG_CHECKING([whether pthreads work with $flag]) PTHREAD_CFLAGS="$flag" ;; pthread-config) AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) if test x"$ax_pthread_config" = xno; then continue; fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" ;; *) AC_MSG_CHECKING([for the pthreads library -l$flag]) PTHREAD_LIBS="-l$flag" ;; esac save_LIBS="$LIBS" save_CFLAGS="$CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we # need a special flag -Kthread to make this header compile.) # We check for pthread_join because it is in -lpthread on IRIX # while pthread_create is in libc. We check for pthread_attr_init # due to DEC craziness with -lpthreads. We check for # pthread_cleanup_push because it is one of the few pthread # functions on Solaris that doesn't have a non-functional libc stub. # We try pthread_create on general principles. AC_TRY_LINK([#include static void routine(void* a) {a=0;} static void* start_routine(void* a) {return a;}], [pthread_t th; pthread_attr_t attr; pthread_join(th, 0); pthread_attr_init(&attr); pthread_cleanup_push(routine, 0); pthread_create(&th,0,start_routine,0); pthread_cleanup_pop(0); ], [ax_pthread_ok=yes]) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" AC_MSG_RESULT($ax_pthread_ok) if test "x$ax_pthread_ok" = xyes; then break; fi PTHREAD_LIBS="" PTHREAD_CFLAGS="" done fi # Various other checks: if test "x$ax_pthread_ok" = xyes; then save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. AC_MSG_CHECKING([for joinable pthread attribute]) attr_name=unknown for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do AC_TRY_LINK([#include ], [int attr=$attr; return attr;], [attr_name=$attr; break]) done AC_MSG_RESULT($attr_name) if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, [Define to necessary symbol if this constant uses a non-standard name on your system.]) fi AC_MSG_CHECKING([if more special flags are required for pthreads]) flag=no case "${host_cpu}-${host_os}" in *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; *solaris* | *-osf* | *-hpux*) flag="-D_REENTRANT";; esac AC_MSG_RESULT(${flag}) if test "x$flag" != xno; then PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" fi LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" # More AIX lossage: must compile with xlc_r or cc_r if test x"$GCC" != xyes; then AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) else PTHREAD_CC=$CC fi else PTHREAD_CC="$CC" fi AC_SUBST(PTHREAD_LIBS) AC_SUBST(PTHREAD_CFLAGS) AC_SUBST(PTHREAD_CC) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test x"$ax_pthread_ok" = xyes; then ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) : else ax_pthread_ok=no $2 fi AC_LANG_RESTORE ])dnl AX_PTHREAD pepper-0.3.3/m4/ax_python_devel.m4000066400000000000000000000262001263211400600167550ustar00rootroot00000000000000# =========================================================================== # http://www.gnu.org/software/autoconf-archive/ax_python_devel.html # =========================================================================== # # SYNOPSIS # # AX_PYTHON_DEVEL([version], [force]) # # DESCRIPTION # # Note: Defines as a precious variable "PYTHON_VERSION". Don't override it # in your configure.ac. # # This macro checks for Python and tries to get the include path to # 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS) # output variables. It also exports $(PYTHON_EXTRA_LIBS) and # $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. # # You can search for some particular version of Python by passing a # parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please # note that you *have* to pass also an operator along with the version to # match, and pay special attention to the single quotes surrounding the # version number. Don't use "PYTHON_VERSION" for this: that environment # variable is declared as precious and thus reserved for the end-user. # # This macro should work for all versions of Python >= 2.1.0. As an end # user, you can disable the check for the python version by setting the # PYTHON_NOVERSIONCHECK environment variable to something else than the # empty string. # # If you need to use this macro for an older Python version, please # contact the authors. We're always open for feedback. # # LICENSE # # Copyright (c) 2009 Sebastian Huber # Copyright (c) 2009 Alan W. Irwin # Copyright (c) 2009 Rafael Laboissiere # Copyright (c) 2009 Andrew Collier # Copyright (c) 2009 Matteo Settenvini # Copyright (c) 2009 Horst Knorr # # 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 . # # As a special exception, the respective Autoconf Macro's copyright owner # gives unlimited permission to copy, distribute and modify the configure # scripts that are the output of Autoconf when processing the Macro. You # need not follow the terms of the GNU General Public License when using # or distributing such scripts, even though portions of the text of the # Macro appear in them. The GNU General Public License (GPL) does govern # all other use of the material that constitutes the Autoconf Macro. # # This special exception to the GPL applies to versions of the Autoconf # Macro released by the Autoconf Archive. When you make and distribute a # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. #serial 8 AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) AC_DEFUN([AX_PYTHON_DEVEL],[ python_found="yes" # # Allow the use of a (user set) custom python version # AC_ARG_VAR([PYTHON_VERSION],[The installed Python version to use, for example '2.6'. This string will be appended to the Python interpreter canonical name.]) AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) if test -z "$PYTHON"; then python_found="no" AC_MSG_WARN([Cannot find python$PYTHON_VERSION in your system path]) PYTHON_VERSION="" else # # Check for a version of Python >= 2.1.0 # AC_MSG_CHECKING([for a version of Python >= '2.1.0']) ac_supports_python_ver=`$PYTHON -c "import sys; \ ver = sys.version.split ()[[0]]; \ print (ver >= '2.1.0')"` if test "$ac_supports_python_ver" != "True"; then if test -z "$PYTHON_NOVERSIONCHECK"; then AC_MSG_RESULT([no]) python_found="no" AC_MSG_WARN([ This version of the AC@&t@_PYTHON_DEVEL macro doesn't work properly with versions of Python before 2.1.0. You may need to re-run configure, setting the variables PYTHON_CPPFLAGS, PYTHON_LDFLAGS, PYTHON_SITE_PKG, PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. Moreover, to disable this check, set PYTHON_NOVERSIONCHECK to something else than an empty string. ]) else AC_MSG_RESULT([skip at user request]) fi else AC_MSG_RESULT([yes]) fi # # if the macro parameter ``version'' is set, honour it # if test -n "$1"; then AC_MSG_CHECKING([for a version of Python $1]) ac_supports_python_ver=`$PYTHON -c "import sys; \ ver = sys.version.split ()[[0]]; \ print (ver $1)"` if test "$ac_supports_python_ver" = "True"; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) python_found="no" PYTHON_VERSION="" fi fi # # Check if you have distutils, else fail # AC_MSG_CHECKING([for the distutils Python package]) ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` if test -z "$ac_distutils_result"; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) python_found="no" AC_MSG_WARN([cannot import Python module "distutils". Please check your Python installation. The error was: $ac_distutils_result]) PYTHON_VERSION="" fi # # Check for Python include path # AC_MSG_CHECKING([for Python include path]) if test -z "$PYTHON_CPPFLAGS"; then python_path=`$PYTHON -c "import distutils.sysconfig; \ print (distutils.sysconfig.get_python_inc ());"` if test -n "${python_path}"; then python_path="-I$python_path" fi PYTHON_CPPFLAGS=$python_path fi AC_MSG_RESULT([$PYTHON_CPPFLAGS]) AC_SUBST([PYTHON_CPPFLAGS]) # # Check for Python library path # AC_MSG_CHECKING([for Python library path]) if test -z "$PYTHON_LDFLAGS"; then # (makes two attempts to ensure we've got a version number # from the interpreter) ac_python_version=`cat<]], [[Py_Initialize();]]) ],[pythonexists=yes],[pythonexists=no]) AC_LANG_POP([C]) # turn back to default flags CPPFLAGS="$ac_save_CPPFLAGS" LIBS="$ac_save_LIBS" AC_MSG_RESULT([$pythonexists]) if test ! "x$pythonexists" = "xyes"; then if test "x[$2]" = "xyes"; then AC_MSG_FAILURE([ Could not link test program to Python. Maybe the main Python library has been installed in some non-standard library path. If so, pass it to configure, via the LDFLAGS environment variable. Example: ./configure LDFLAGS="-L/usr/non-standard-path/python/lib" ============================================================================ ERROR! You probably have to install the development version of the Python package for your distribution. The exact name of this package varies among them. ============================================================================ ]) fi PYTHON_VERSION="" python_found="no" fi fi # # all done! # ]) pepper-0.3.3/m4/configure_backends.m4000066400000000000000000000106761263211400600174120ustar00rootroot00000000000000dnl dnl pepper - SCM statistics report generator dnl Copyright (C) 2010-present Jonas Gehring dnl dnl Released under the GNU General Public License, version 3. dnl Please see the COPYING file in the source distribution for license dnl terms and conditions, or see http://www.gnu.org/licenses/. dnl sinclude(m4/find_apr.m4) sinclude(m4/find_svn.m4) sinclude(m4/ax_python_devel.m4) AC_ARG_ENABLE([git], [AS_HELP_STRING([--disable-git], [Don't include the git backend])], [git="$enableval"], [git="auto"]) AC_ARG_ENABLE([mercurial], [AS_HELP_STRING([--disable-mercurial], [Don't include the mercurial backend])], [mercurial="$enableval"], [mercurial="auto"]) AC_ARG_ENABLE([svn], [AS_HELP_STRING([--disable-svn], [Don't include the subversion backend])], [subversion="$enableval"], [subversion="auto"]) dnl Run checks for the backends AC_DEFUN([BACKENDS_CHECK], [ if test "x$git" != "xno"; then AC_PATH_PROG([GIT], [git], [not found]) if test "x$GIT" = "xnot found"; then if test "x$git" = "xyes"; then AC_MSG_ERROR([git could not be located in your \$PATH]) else git="no" fi else git="yes" fi fi if test "x$subversion" != "xno"; then APR_FIND_APR(,,[1],[1]) if test "$apr_found" = "no"; then if test "x$subversion" != "xauto"; then AC_MSG_ERROR([APR could not be located. Please use the --with-apr option.]) fi subversion="no" else APR_BUILD_DIR="`$apr_config --installbuilddir`" dnl make APR_BUILD_DIR an absolute directory APR_BUILD_DIR="`cd $APR_BUILD_DIR && pwd`" APR_CFLAGS=`$apr_config --cflags` APR_CPPFLAGS=`$apr_config --cppflags` APR_INCLUDES="`$apr_config --includes`" APR_LIBS="`$apr_config --link-ld --libs`" AC_SUBST(APR_CFLAGS) AC_SUBST(APR_CPPFLAGS) AC_SUBST(APR_INCLUDES) AC_SUBST(APR_LIBS) fi if test "x$subversion" != "xno"; then FIND_SVN([1],[5]) if test "x$svn_found" != "xyes"; then if test "x$subversion" != "xauto"; then AC_MSG_ERROR([Subversion could not be located. Please use the --with-svn option.]) fi subversion="no" else OLD_LDFLAGS=$LDFLAGS OLD_LIBS=$LIBS LDFLAGS="-L$SVN_PREFIX/lib" LIBS=$LDFLAGS AC_CHECK_LIB([svn_fs-1], [svn_fs_initialize], ,[AC_MSG_ERROR([Neccessary Subversion libraries are missing])]) AC_CHECK_LIB([svn_client-1], [svn_client_diff4], ,[AC_MSG_ERROR([Neccessary Subversion libraries are missing])]) AC_CHECK_LIB([svn_ra-1], [svn_ra_get_uuid2], ,[AC_MSG_ERROR([Neccessary Subversion libraries are missing])]) AC_CHECK_LIB([svn_subr-1], [svn_cmdline_setup_auth_baton], ,[AC_MSG_ERROR([Neccessary Subversion libraries are missing])]) AC_CHECK_LIB([svn_diff-1], [svn_diff_file_diff_2], ,[AC_MSG_ERROR([Neccessary Subversion libraries are missing])]) AC_CHECK_LIB([svn_delta-1], [svn_txdelta_apply], ,[AC_MSG_ERROR([Neccessary Subversion libraries are missing])]) AC_CHECK_LIB([svn_repos-1], [svn_repos_create], ,[AC_MSG_ERROR([Neccessary Subversion libraries are missing])]) SVN_LDFLAGS=$LDFLAGS LDFLAGS=$OLD_LDFLAGS SVN_LIBS=$LIBS LIBS=$OLD_LIBS AC_SUBST(SVN_CFLAGS) AC_SUBST(SVN_LDFLAGS) AC_SUBST(SVN_LIBS) subversion="yes" fi fi fi if test "x$mercurial" != "xno"; then AX_PYTHON_DEVEL([>= '2.1'], "$mercurial") if test "x$python_found" != "xyes"; then if test "x$mercurial" != "xauto"; then AC_MSG_ERROR([Python could not be located. Please use the PYTHON_VERSION variable.]) fi mercurial="no" else # Inspiration from Stephan Peijnik # http://blog.sp.or.at/2008/08/31/autoconf-and-python-checking-for-modules/ AC_MSG_CHECKING(for mercurial Python module) MODVERSION=`$PYTHON -c "from mercurial import __version__; print __version__.version" 2> /dev/null` if test "x$?" != "x0"; then AC_MSG_RESULT(not found) if test "x$mercurial" != "xauto"; then AC_MSG_ERROR([The mercurial Python module could not be located.]) fi mercurial="no" else mercurial="yes" AC_MSG_RESULT([found $MODVERSION]) fi fi fi ]) dnl Print a backend configuration report AC_DEFUN([BACKENDS_REPORT], [ echo echo " Enabled(+) / disabled(-) SCM backends:" if test "x$git" = "xyes"; then echo " + Git"; fi if test "x$mercurial" = "xyes"; then echo " + Mercurial"; fi if test "x$subversion" = "xyes"; then echo " + Subversion"; fi if test "x$git" = "xno"; then echo " - Git"; fi if test "x$mercurial" = "xno"; then echo " - Mercurial"; fi if test "x$subversion" = "xno"; then echo " - Subversion"; fi ]) pepper-0.3.3/m4/configure_features.m4000066400000000000000000000142221263211400600174450ustar00rootroot00000000000000dnl dnl pepper - SCM statistics report generator dnl Copyright (C) 2010-present Jonas Gehring dnl dnl Released under the GNU General Public License, version 3. dnl Please see the COPYING file in the source distribution for license dnl terms and conditions, or see http://www.gnu.org/licenses/. dnl AC_ARG_ENABLE([gnuplot], [AS_HELP_STRING([--disable-gnuplot], [Disable Gnuplot backend for graphical reports])], [gnuplot="$enableval"], [gnuplot="auto"]) AC_ARG_ENABLE([man], [AS_HELP_STRING([--disable-man], [Don't generate the man page])], [manpage="$enableval"], [manpage="auto"]) AC_ARG_ENABLE([leveldb], [AS_HELP_STRING([--enable-leveldb], [Use LevelDB for caching revisions])], [leveldb="$enableval"], [leveldb="no"]) dnl Run checks for manpage programs AC_DEFUN([CHECK_MANPROGS], [ dnl Check for Asciidoc AC_ARG_VAR([ASCIIDOC], AsciiDoc executable) AC_PATH_PROG([ASCIIDOC], [asciidoc], [not found]) if test "x$ASCIIDOC" = "xnot found"; then if test "x$manpage" = "xyes"; then AC_MSG_ERROR([Asciidoc could not be located in your \$PATH]) else manpage="no" fi else ver_info=`$ASCIIDOC --version` ver_maj=`echo $ver_info | sed 's/^.* \([[0-9]]\)*\.\([[0-9]]\)*\.\([[0-9]]*\).*$/\1/'` ver_min=`echo $ver_info | sed 's/^.* \([[0-9]]\)*\.\([[0-9]]\)*\.\([[0-9]]*\).*$/\2/'` ver_rev=`echo $ver_info | sed 's/^.* \([[0-9]]\)*\.\([[0-9]]\)*\.\([[0-9]]*\).*$/\3/'` prog_version_ok="yes" if test $ver_maj -lt 8; then prog_version_ok="no" fi if test $ver_min -lt 4; then prog_version_ok="no" fi if test $ver_rev -lt 0; then prog_version_ok="no" fi if test "$prog_version_ok" != "yes"; then if test "x$manpage" = "xyes"; then AC_MSG_ERROR([Asciidoc >= 8.4 is needed. Please upgrade your installation]) else manpage="no" fi fi fi dnl Check for xmlto AC_ARG_VAR([XMLTO], xmlto executable) AC_PATH_PROG([XMLTO], [xmlto], [not found]) if test "x$XMLTO" = "xnot found"; then if test "x$manpage" = "xyes"; then AC_MSG_ERROR([xmlto could not be located in your \$PATH]) else manpage="no" fi else ver_info=`$XMLTO --version` ver_maj=`echo $ver_info | sed 's/^.* \([[0-9]]\)*\.\([[0-9]]\)*\.\([[0-9]]*\).*$/\1/'` ver_min=`echo $ver_info | sed 's/^.* \([[0-9]]\)*\.\([[0-9]]\)*\.\([[0-9]]*\).*$/\2/'` ver_rev=`echo $ver_info | sed 's/^.* \([[0-9]]\)*\.\([[0-9]]\)*\.\([[0-9]]*\).*$/\3/'` prog_version_ok="yes" if test $ver_maj -lt 0; then prog_version_ok="no" fi if test $ver_min -lt 0; then prog_version_ok="no" fi if test $ver_rev -lt 18; then prog_version_ok="no" fi if test "$prog_version_ok" != "yes"; then if test "x$manpage" = "xyes"; then AC_MSG_ERROR([xmlto >= 0.0.18 is needed. Please upgrade your installation]) else manpage="no" fi fi fi ]) dnl Run checks for LevelDB headers and libraries AC_DEFUN([CHECK_LEVELDB], [ AC_ARG_WITH([leveldb], [AC_HELP_STRING([--with-leveldb=PATH], [prefix for LevelDB installation])], [leveldb_prefix=$withval]) AC_LANG_PUSH([C++]) builtin_leveldb="no" if test "x$leveldb_prefix" = x; then dnl No prefix specified. Check system locations first, and fall back dnl to builtin version if necessary AC_CHECK_HEADER([leveldb/db.h], [header_found="yes"], [header_found="no"]) if test "x$header_found" = "xyes"; then AC_CHECK_LIB([leveldb], [leveldb_open], [lib_found="yes"], [lib_found="no"], [-lpthread]) if test "x$lib_found" = "xyes"; then LEVELDB_LIBS="-lleveldb" AC_SUBST([LEVELDB_LIBS]) fi else dnl Fall back to builtin in version, and make sure to clear dnl the cached header check. dnl TODO: A crappy way to get abs_srcdir... leveldb_prefix="`cd $srcdir && pwd`/3rdparty/leveldb" builtin_leveldb="yes" AS_UNSET(AS_TR_SH([ac_cv_header_leveldb/db.h])) fi fi if test "x$leveldb_prefix" != x; then dnl Run tests with prefix LDB_OLD_CPPFLAGS="$CPPFLAGS" CPPFLAGS="$CPPFLAGS -I$leveldb_prefix/include" AC_CHECK_HEADER([leveldb/db.h], [header_found="yes"], [header_found="no"]) CPPFLAGS="$LDB_OLD_CPPFLAGS" LEVELDB_CPPFLAGS="-I$leveldb_prefix/include" AC_SUBST([LEVELDB_CPPFLAGS]) if test "x$builtin_leveldb" != "xyes"; then LDB_OLD_LIBS="$LIBS" LIBS="$LIBS -L$leveldb_prefix/lib" AC_CHECK_LIB([leveldb], [leveldb_open], [lib_found="yes"], [lib_found="no"], [-lpthread]) LIBS="$LDB_OLD_LIBS" LEVELDB_LIBS="-L$leveldb_prefix/lib -lleveldb" AC_SUBST([LEVELDB_LIBS]) else dnl Libs are still to be built lib_found="yes" LEVELDB_LIBS="-L$leveldb_prefix -lleveldb" AC_CHECK_HEADER([snappy.h], [LEVELDB_LIBS="$LEVELDB_LIBS -lsnappy"], []) AC_SUBST([LEVELDB_LIBS]) fi fi if test "x$header_found" != "xyes" || test "x$lib_found" != "xyes"; then if test "x$leveldb" = "xyes"; then if test "x$builtin_leveldb" = "xyes"; then AC_MSG_ERROR([LevelDB headers or libraries not found. Make sure you ran 'git submodule init' and 'git submodule update' if you want to use the builtin version.]) else AC_MSG_ERROR([LevelDB headers or libraries not found.]) fi else leveldb="no" fi fi AC_LANG_POP([C++]) ]) dnl Run checks for the features AC_DEFUN([FEATURES_CHECK], [ if test "x$gnuplot" != "xno"; then AC_PATH_PROG([GNUPLOT], [gnuplot], [not found]) if test "x$GNUPLOT" = "xnot found"; then if test "x$gnuplot" = "xyes"; then AC_MSG_ERROR([gnuplot could not be located in your \$PATH]) else gnuplot="no" fi else gnuplot="yes" fi fi if test "x$manpage" != "xno"; then CHECK_MANPROGS() if test "x$manpage" != "xno"; then manpage="yes" fi fi if test "x$leveldb" != "xno"; then CHECK_LEVELDB() if test "x$leveldb" != "xno"; then leveldb="yes" fi fi ]) dnl Print a feature configuration report AC_DEFUN([FEATURES_REPORT], [ echo echo " Enabled(+) / disabled(-) features:" if test "x$gnuplot" = "xyes"; then echo " + Gnuplot"; fi if test "x$gnuplot" = "xno"; then echo " - Gnuplot"; fi if test "x$manpage" = "xyes"; then echo " + Manpage"; fi if test "x$manpage" = "xno"; then echo " - Manpage"; fi if test "x$leveldb" = "xyes"; then if test "x$builtin_leveldb" = "xyes"; then echo " + LevelDB (builtin)"; else echo " + LevelDB"; fi fi if test "x$leveldb" = "xno"; then echo " - LevelDB"; fi ]) pepper-0.3.3/m4/find_apr.m4000066400000000000000000000170521263211400600153540ustar00rootroot00000000000000dnl -------------------------------------------------------- -*- autoconf -*- dnl Licensed to the Apache Software Foundation (ASF) under one or more dnl contributor license agreements. See the NOTICE file distributed with dnl this work for additional information regarding copyright ownership. dnl The ASF licenses this file to You under the Apache License, Version 2.0 dnl (the "License"); you may not use this file except in compliance with dnl the License. You may obtain a copy of the License at dnl dnl http://www.apache.org/licenses/LICENSE-2.0 dnl dnl Unless required by applicable law or agreed to in writing, software dnl distributed under the License is distributed on an "AS IS" BASIS, dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. dnl See the License for the specific language governing permissions and dnl limitations under the License. dnl dnl find_apr.m4 : locate the APR include files and libraries dnl dnl This macro file can be used by applications to find and use the APR dnl library. It provides a standardized mechanism for using APR. It supports dnl embedding APR into the application source, or locating an installed dnl copy of APR. dnl dnl APR_FIND_APR(srcdir, builddir, implicit-install-check, acceptable-majors, dnl detailed-check) dnl dnl where srcdir is the location of the bundled APR source directory, or dnl empty if source is not bundled. dnl dnl where builddir is the location where the bundled APR will will be built, dnl or empty if the build will occur in the srcdir. dnl dnl where implicit-install-check set to 1 indicates if there is no dnl --with-apr option specified, we will look for installed copies. dnl dnl where acceptable-majors is a space separated list of acceptable major dnl version numbers. Often only a single major version will be acceptable. dnl If multiple versions are specified, and --with-apr=PREFIX or the dnl implicit installed search are used, then the first (leftmost) version dnl in the list that is found will be used. Currently defaults to [0 1]. dnl dnl where detailed-check is an M4 macro which sets the apr_acceptable to dnl either "yes" or "no". The macro will be invoked for each installed dnl copy of APR found, with the apr_config variable set appropriately. dnl Only installed copies of APR which are considered acceptable by dnl this macro will be considered found. If no installed copies are dnl considered acceptable by this macro, apr_found will be set to either dnl either "no" or "reconfig". dnl dnl Sets the following variables on exit: dnl dnl apr_found : "yes", "no", "reconfig" dnl dnl apr_config : If the apr-config tool exists, this refers to it. If dnl apr_found is "reconfig", then the bundled directory dnl should be reconfigured *before* using apr_config. dnl dnl Note: this macro file assumes that apr-config has been installed; it dnl is normally considered a required part of an APR installation. dnl dnl If a bundled source directory is available and needs to be (re)configured, dnl then apr_found is set to "reconfig". The caller should reconfigure the dnl (passed-in) source directory, placing the result in the build directory, dnl as appropriate. dnl dnl If apr_found is "yes" or "reconfig", then the caller should use the dnl value of apr_config to fetch any necessary build/link information. dnl AC_DEFUN([APR_FIND_APR], [ apr_found="no" if test "$target_os" = "os2-emx"; then # Scripts don't pass test -x on OS/2 TEST_X="test -f" else TEST_X="test -x" fi ifelse([$4], [], [ ifdef(AC_WARNING,AC_WARNING([$0: missing argument 4 (acceptable-majors): Defaulting to APR 0.x then APR 1.x])) acceptable_majors="0 1"], [acceptable_majors="$4"]) apr_temp_acceptable_apr_config="" for apr_temp_major in $acceptable_majors do case $apr_temp_major in 0) apr_temp_acceptable_apr_config="$apr_temp_acceptable_apr_config apr-config" ;; *) apr_temp_acceptable_apr_config="$apr_temp_acceptable_apr_config apr-$apr_temp_major-config" ;; esac done AC_MSG_CHECKING(for APR) AC_ARG_WITH(apr, [ --with-apr=PATH prefix for installed APR or the full path to apr-config], [ if test "$withval" = "no" || test "$withval" = "yes"; then AC_MSG_ERROR([--with-apr requires a directory or file to be provided]) fi for apr_temp_apr_config_file in $apr_temp_acceptable_apr_config do for lookdir in "$withval/bin" "$withval" do if $TEST_X "$lookdir/$apr_temp_apr_config_file"; then apr_config="$lookdir/$apr_temp_apr_config_file" ifelse([$5], [], [], [ apr_acceptable="yes" $5 if test "$apr_acceptable" != "yes"; then AC_MSG_WARN([Found APR in $apr_config, but we think it is considered unacceptable]) continue fi]) apr_found="yes" break 2 fi done done if test "$apr_found" != "yes" && $TEST_X "$withval" && $withval --help > /dev/null 2>&1 ; then apr_config="$withval" ifelse([$5], [], [apr_found="yes"], [ apr_acceptable="yes" $5 if test "$apr_acceptable" = "yes"; then apr_found="yes" fi]) fi dnl if --with-apr is used, it is a fatal error for its argument dnl to be invalid if test "$apr_found" != "yes"; then AC_MSG_ERROR([the --with-apr parameter is incorrect. It must specify an install prefix, a build directory, or an apr-config file.]) fi ],[ dnl If we allow installed copies, check those before using bundled copy. if test -n "$3" && test "$3" = "1"; then for apr_temp_apr_config_file in $apr_temp_acceptable_apr_config do if $apr_temp_apr_config_file --help > /dev/null 2>&1 ; then apr_config="$apr_temp_apr_config_file" ifelse([$5], [], [], [ apr_acceptable="yes" $5 if test "$apr_acceptable" != "yes"; then AC_MSG_WARN([skipped APR at $apr_config, version not acceptable]) continue fi]) apr_found="yes" break else dnl look in some standard places for lookdir in /usr /usr/local /usr/local/apr /opt/apr; do if $TEST_X "$lookdir/bin/$apr_temp_apr_config_file"; then apr_config="$lookdir/bin/$apr_temp_apr_config_file" ifelse([$5], [], [], [ apr_acceptable="yes" $5 if test "$apr_acceptable" != "yes"; then AC_MSG_WARN([skipped APR at $apr_config, version not acceptable]) continue fi]) apr_found="yes" break 2 fi done fi done fi dnl if we have not found anything yet and have bundled source, use that if test "$apr_found" = "no" && test -d "$1"; then apr_temp_abs_srcdir="`cd $1 && pwd`" apr_found="reconfig" apr_bundled_major="`sed -n '/#define.*APR_MAJOR_VERSION/s/^[^0-9]*\([0-9]*\).*$/\1/p' \"$1/include/apr_version.h\"`" case $apr_bundled_major in "") AC_MSG_ERROR([failed to find major version of bundled APR]) ;; 0) apr_temp_apr_config_file="apr-config" ;; *) apr_temp_apr_config_file="apr-$apr_bundled_major-config" ;; esac if test -n "$2"; then apr_config="$2/$apr_temp_apr_config_file" else apr_config="$1/$apr_temp_apr_config_file" fi fi ]) AC_MSG_RESULT($apr_found) ]) pepper-0.3.3/m4/find_svn.m4000066400000000000000000000046741263211400600154060ustar00rootroot00000000000000dnl dnl FIND_SVN([MAJOR], [MINOR]) dnl dnl Search for Subversion libraries dnl (roughly based on find_apr.m4) dnl AC_DEFUN([FIND_SVN], [ svn_found="no" svn_prefix="" ifelse([$1], [], [AC_MSG_CHECKING([for Subversion])], [ifelse([$2], [], [AC_MSG_CHECKING([for Subversion >= $1])], [AC_MSG_CHECKING([for Subversion >= $1.$2])] )]) AC_ARG_WITH(svn, [AS_HELP_STRING([--with-svn=PATH],[prefix for installed Subversion development files])], [ if test "$withval" = "no" || test "$withval" = "yes"; then AC_MSG_ERROR([--with-svn requires a directory or file to be provided]) fi if test -r "$withval/include/subversion-1/svn_version.h"; then svn_major=`grep "#define SVN_VER_MAJOR" $withval/include/subversion-1/svn_version.h |sed s/[^0-9]*//` svn_minor=`grep "#define SVN_VER_MINOR" $withval/include/subversion-1/svn_version.h |sed s/[^0-9]*//` svn_patch=`grep "#define SVN_VER_PATCH" $withval/include/subversion-1/svn_version.h |sed s/[^0-9]*//` svn_prefix=$withval if test "x$1" != "x" && test "$svn_major" -lt "$1"; then AC_MSG_RESULT([no (found $svn_major.$svn_minor.$svn_patch)]) elif test "x$2" != "x" && test "$svn_minor" -lt "$2"; then AC_MSG_RESULT([no (found $svn_major.$svn_minor.$svn_patch)]) else AC_MSG_RESULT([found $svn_major.$svn_minor.$svn_patch]) svn_found="yes" fi else AC_MSG_RESULT([no]) fi ], [ for lookdir in /usr /usr/local /usr/local/svn /opt/svn; do if test -r "$lookdir/include/subversion-1/svn_version.h"; then svn_major=`grep "#define SVN_VER_MAJOR" $lookdir/include/subversion-1/svn_version.h |sed s/[^0-9]*//` svn_minor=`grep "#define SVN_VER_MINOR" $lookdir/include/subversion-1/svn_version.h |sed s/[^0-9]*//` svn_patch=`grep "#define SVN_VER_PATCH" $lookdir/include/subversion-1/svn_version.h |sed s/[^0-9]*//` svn_prefix=$lookdir if test "x$1" != "x" && test "$svn_major" -lt "$1"; then AC_MSG_RESULT([no (found $svn_major.$svn_minor.$svn_patch in $lookdir)]) elif test "x$2" != "x" && test "$svn_minor" -lt "$2"; then AC_MSG_RESULT([no (found $svn_major.$svn_minor.$svn_patch in $lookdir)]) else AC_MSG_RESULT([found $svn_major.$svn_minor.$svn_patch in $lookdir]) svn_found="yes" fi break fi done if test "x$svn_prefix" = "x"; then AC_MSG_RESULT([no]) fi ] ) if test "$svn_found" = "yes"; then SVN_CFLAGS="-I$svn_prefix/include/subversion-1" SVN_LDFLAGS="-L$svn_prefix/lib" SVN_PREFIX="$svn_prefix" fi ]) pepper-0.3.3/reports/000077500000000000000000000000001263211400600145015ustar00rootroot00000000000000pepper-0.3.3/reports/Makefile.am000066400000000000000000000011721263211400600165360ustar00rootroot00000000000000# # pepper - SCM statistics report generator # Copyright (C) 2010-present Jonas Gehring # # Released under the GNU General Public License, version 3. # Please see the COPYING file in the source distribution for license # terms and conditions, or see http://www.gnu.org/licenses/. # SUBDIRS = pepper # Bundled scripts dist_pkgdata_DATA = \ age.lua \ activity.lua \ authors.lua \ authors_pie.lua \ branches.lua \ check_cache.lua \ commit_counts.lua \ csv.lua \ data.lua \ diffstat.lua \ directories.lua \ filetypes.lua \ gui.lua \ loc.lua \ participation.lua \ punchcard.lua \ shortlog.lua \ times.lua \ volume.lua pepper-0.3.3/reports/activity.lua000066400000000000000000000125031263211400600170410ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: activity.lua Visualizes several repository activity metrics using a smoothed curve. This is a port of http://labs.freehackers.org/wiki/hgactivity --]] require "pepper.datetime" require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.name = "Activity" r.description = "General repository activity" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"--tags[=ARG]", "Add tag markers to the graph, optionally filtered with a regular expression"}, {"-c, --changes, -l", "Count line changes instead of commit counts"}, {"--split=ARG", "Split the graph by 'authors', 'files', 'directories', or 'none' (the default)"}, {"-nARG", "Maximum number of series when using --split"} } pepper.datetime.add_daterange_options(r) pepper.plotutils.add_plot_options(r) return r end -- Revision callback function function count(r) local date = r:date() if date == 0 then return end local n = 1 if count_changes ~= nil then n = r:diffstat():lines_added() + r:diffstat():lines_removed() end if split == "none" then if activity[date] == nil then activity[date] = n else activity[date] = activity[date] + n end elseif split == "authors" then if activity[r:author()] == nil then activity[r:author()] = {} end if activity[r:author()][date] == nil then activity[r:author()][date] = n else activity[r:author()][date] = activity[r:author()][date] + n end elseif split == "directories" or split == "files" then local s = r:diffstat() local dir = (split == "directories") for i,v in ipairs(s:files()) do if count_changes ~= nil then n = s:lines_added(v) + s:lines_removed(v) end if dir then v = pepper.utils.dirname("/" .. v) end if activity[v] == nil then activity[v] = {} end if activity[v][date] == nil then activity[v][date] = n else activity[v][date] = activity[v][date] + n end end end -- Track date range if date < firstdate then firstdate = date end if date > lastdate then lastdate = date end end -- This is mostly from hg's activity extension function convolution(datemin, datemax, data) local date = datemin local wmin = 1 local wmax = 1 local number = 1000 -- number of points we want to compute local period = (datemax-datemin)/number -- period at which we compute a value local wperiod = period * 25 -- length of the convolution window local dates = {} local values = {} local mydates = {} for k,v in pairs(data) do table.insert(mydates, k) end table.sort(mydates) local length = #mydates for x = 0, number do date = date + period while wmin <= length and mydates[wmin] < date - wperiod do wmin = wmin + 1 end while wmax <= length and mydates[wmax] < date + wperiod do wmax = wmax + 1 end value = 0 for a = wmin, wmax-1 do local delta = mydates[a] - date value = value + data[mydates[a]] * (1 - delta/wperiod) end table.insert(values, value) table.insert(dates, date) end return dates, values end -- Returns the size of a table function tablesize(t) local n = 0 for k,v in pairs(t) do n = n + 1 end return n end -- Main report function function run(self) activity = {} firstdate = os.time() lastdate = 0 count_changes = self:getopt("c,changes,l") split = self:getopt("split", "none") if split == "n" then split="none" elseif split == "a" then split="authors" elseif split == "f" then split="files" elseif split == "d" then split="directories" end if ({none=1, authors=1, files=1, branches=1, directories=1})[split] == nil then error("Unknown split option '" .. split .. "'") end -- Gather data local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) repo:iterator(branch, {start=datemin, stop=datemax}):map(count) if last == 0 then error("No data on this branch") end local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p) if split == "none" then pepper.plotutils.setup_std_time(p) p:set_title("Activity (on " .. branch .. ")") else pepper.plotutils.setup_std_time(p, {key = "below"}) p:set_title("Activity by " .. string.upper(split:sub(1, 1)) .. split:sub(2) .. " (on " .. branch .. ")") end p:cmd("set noytics") if self:getopt("tags") ~= nil then pepper.plotutils.add_tagmarks(p, repo, self:getopt("tags", "*")) end -- Generate graphs p:set_xrange_time(firstdate, lastdate) if split == "none" then local dates, values = convolution(firstdate, lastdate, activity) p:plot_series(dates, values, {}, "lines smooth bezier linewidth 1.5") else -- Determine contributions (i.e. frequent entries) local freqs = {} for k,v in pairs(activity) do table.insert(freqs, {k, tablesize(v)}) end table.sort(freqs, function (a,b) return (a[2] > b[2]) end) local n = 1 + tonumber(self:getopt("n", 6)) local i = 1 local vdates = {} local values = {} local keys = {} while i <= #freqs and i < n do vdates[i], values[i] = convolution(firstdate, lastdate, activity[freqs[i][1]]) keys[i] = freqs[i][1] i = i + 1 end p:plot_multi_series(vdates, values, keys, "lines smooth bezier linewidth 1.5") end end pepper-0.3.3/reports/age.lua000066400000000000000000000063141263211400600157440ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: age.lua Prints the age of a repository or branch --]] require "pepper.datetime" -- Describes the report function describe(self) local r = {} r.name = "Age" r.description = "Repository age information" r.options = { {"-bARG, --branch=ARG", "Select branch"} } return r end -- Returns a counting string for the given number (e.g. 1 -> 1st) function count_str(s) local i = tonumber(s) if (i % 10) == 1 and i ~= 11 then return tostring(i) .. "st" elseif (i % 10) == 2 and i ~= 12 then return tostring(i) .. "nd" elseif (i % 10) == 3 and i ~= 13 then return tostring(i) .. "rd" end return tostring(i) .. "th" end -- Pretty printing of an age function print_age(url, t) local now = os.time() local span = now - t local age = "" local name = pepper.utils.basename(url) -- Easy case: Birthday in the past if span < 0 then print(name .. " is not born yet!") return end -- Easy case: Happy birthday local today = string.format("%s %s", os.date("%B", now), count_str(os.date("%d", now))) local birthday = string.format("%s %s", os.date("%B", t), count_str(os.date("%d", t))) if today == birthday then local d = os.date("%Y", now) - os.date("%Y", t) if d == 1 then print(name .. " is " .. d .. " year old") elseif d ~= 0 then print(name .. " is " .. d .. " years old") end print("Happy birthday, " .. name .. "!") return end -- Age information if span < 60 then if span == 1 then age = "one second old" else age = tostring(span) .. " seconds old" end elseif span < 120 then age = "one minute old" elseif span < 45*60 then age = tonumber(os.date("%M", span)) .. " minutes old" elseif span < 24*60*60 then age = tonumber(os.date("%H", span)) .. " hours old" elseif span < 28*24*60*60 then age = tonumber(os.date("%d", span)) .. " days old" elseif span < 365*24*60*60 then age = tonumber(os.date("%m", span)) .. " months old" else local years = (tonumber(os.date("%Y", span)) - 1970) if years == 1 then age = "1 year old" else age = years .. " years old" end end -- Birthday information local birthday_date = os.date("*t", t) birthday_date.year = tonumber(os.date("%Y")) if os.time(birthday_date) - now < 0 then birthday_date.year = birthday_date.year + 1 end local birthday_dist = pepper.datetime.humanrange(os.time(birthday_date) - now) -- Print result print(name .. " is " .. age) if today ~= birthday then local suffix = "'s" if name:sub(-1) == "s" then suffix = "'" end print(string.format("%s%s birthday is in %s (%s)", name, suffix, birthday_dist, birthday)) elseif span > 60*60*24 then end end -- Main report function function run(self) local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) if repo:type() == "mercurial" then print_age(repo:url(), repo:revision("0"):date()) else local iterator = repo:iterator(branch, {prefetch=false}) for revision in iterator:revisions() do print_age(repo:url(), revision:date()) break end end end pepper-0.3.3/reports/authors.lua000066400000000000000000000057641263211400600167050ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: authors.lua Visualizes code contribution by author. --]] require "pepper.datetime" require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.name = "Authors" r.description = "Contributed lines of code by authors" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"--tags[=ARG]", "Add tag markers to the graph, optionally filtered with a regular expression"}, {"-nARG", "Show the ARG busiest authors"} } pepper.datetime.add_daterange_options(r) pepper.plotutils.add_plot_options(r) return r end -- Revision callback function function callback(r) if r:author() == "" then return end -- Save commit and LOC count local loc = r:diffstat():lines_added() table.insert(commits, {r:date(), r:author(), loc}) if authors[r:author()] == nil then authors[r:author()] = loc else authors[r:author()] = authors[r:author()] + loc end end -- Main script function function run(self) commits = {} -- Commit list by timestamp with LOC delta authors = {} -- Total LOC by author -- Gather data, but start at the beginning of the repository -- to get a proper LOC count. local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) repo:iterator(branch, {stop=datemax}):map(callback) -- Determine the "busiest" authors (by LOC) local authorloc = {} for k,v in pairs(authors) do table.insert(authorloc, {k, v}) end table.sort(authorloc, function (a,b) return (a[2] > b[2]) end) local i = 1 + tonumber(self:getopt("n", 6)) while i <= #authorloc do authors[authorloc[i][1]] = nil authorloc[i] = nil i = i + 1 end -- Sort commits by time table.sort(commits, function (a,b) return (a[1] < b[1]) end) -- Generate data arrays for the authors, skipping data points -- prior to datemin. local keys = {} local series = {} local loc = {} for i,a in ipairs(authorloc) do loc[a[1]] = 0 end for t,v in ipairs(commits) do if datemin < 0 or v[1] >= datemin then table.insert(keys, v[1]) table.insert(series, {}); end for i,a in ipairs(authorloc) do if a[1] == v[2] then loc[a[1]] = loc[a[1]] + v[3] end if datemin < 0 or v[1] >= datemin then table.insert(series[#series], loc[a[1]]) end end end local authors = {} for i,a in ipairs(authorloc) do table.insert(authors, a[1]) end local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p) pepper.plotutils.setup_std_time(p, {key = "below"}) p:set_title("Contributed Lines of Code by Author (on " .. branch .. ")") if self:getopt("tags") ~= nil then pepper.plotutils.add_tagmarks(p, repo, self:getopt("tags", "*")) end p:set_xrange_time(keys[1], keys[#keys]) p:plot_series(keys, series, authors) end pepper-0.3.3/reports/authors_pie.lua000066400000000000000000000055441263211400600175360ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: authors_pie.lua Commit or change percentage by author. --]] require "pepper.datetime" require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.name = "Author contributions" r.description = "Commit or change percentage by authors" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"--tags[=ARG]", "Add tag markers to the graph, optionally filtered with a regular expression"}, {"-nARG", "Show the ARG busiest authors"}, {"-c, --changes, -l", "Count line changes instead of commit counts"} } pepper.datetime.add_daterange_options(r) pepper.plotutils.add_plot_options(r) return r end -- Revision callback function function callback(r) if r:author() == "" then return end local loc = r:diffstat():lines_added() if not count_lines then loc = 1 -- Count commits only end if authors[r:author()] == nil then authors[r:author()] = loc else authors[r:author()] = authors[r:author()] + loc end end -- Main script function function run(self) authors = {} -- Total LOC by author -- Gather data, but count_lines = self:getopt("c,changes,l") count_lines = self:getopt("c,changes,l") local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) if count_lines then -- Start at the beginning of the repository to get a proper LOC count. repo:iterator(branch, {stop=datemax}):map(callback) else repo:iterator(branch, {start=datemin, stop=datemax}):map(callback) end -- Sort authors by contribution and sum up total contribution local authorloc = {} local total = 0 for k,v in pairs(authors) do table.insert(authorloc, {k, v}) total = total + v end -- Sort by contribution, so we can select the busiest ones table.sort(authorloc, function (a,b) return (a[2] > b[2]) end) local i = 1 + tonumber(self:getopt("n", 6)) local missing = 0 while i <= #authorloc do authors[authorloc[i][1]] = nil authorloc[i] = nil i = i + 1 end -- Prepare series with percentage values local keys = {} local values = {} local sum = 0 for i,a in ipairs(authorloc) do table.insert(keys, a[1]) table.insert(values, a[2] / total) sum = sum + values[#values] end if sum < 0.99999 then table.insert(keys, "Others") table.insert(values, 1.0 - sum) end local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p, 600, 600) if count_lines then p:set_title("Percentage of contributed code by author (on " .. branch .. ")") else p:set_title("Percentage of total commits by author (on " .. branch .. ")") end p:plot_pie(keys, values) end pepper-0.3.3/reports/branches.lua000066400000000000000000000020151263211400600167670ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: branches.lua Shows all branches and their current heads, similar to "git branch -v". --]] -- Describes the report function describe(self) local r = {} r.name = "Branches" r.description = "Lists all repository branches" return r end -- Main script function function run(self) local repo = self:repository() local branches = repo:branches() local main = repo:default_branch() local maxlen = 0 for i,v in ipairs(branches) do if #v > maxlen then maxlen = #v end end maxlen = maxlen + 1 for i,v in ipairs(branches) do local line = "" if v == main then line = "* " .. v else line = " " .. v end j = #v while j < maxlen do line = line .. " " j = j + 1 end line = line .. repo:head(v) print(line) end end pepper-0.3.3/reports/check_cache.lua000066400000000000000000000012461263211400600174070ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: check_cache.lua Runs a cache check --]] -- Describes the report function describe(self) local r = {} r.title = "Cache check" r.description = "Checks and cleans up the revision cache" r.options = {{"-f,--force", "Force clearing of cache if necessary"}} return r end -- Main script function function run(self) pepper.internal.check_cache(self:repository(), self:getopt("f,force")) end pepper-0.3.3/reports/commit_counts.lua000066400000000000000000000123201263211400600200650ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: commits_per_month.lua Generates a histogram using commits per month and the amount of files touched. NOTE: The implementation trusts the normalizing behaviour of mktime(). --]] require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.title = "Commit counts" r.description = "Histogramm of commit counts" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"-pARG, --period=ARG", "Show counts for the last 'Ndays', 'Nweeks', 'Nmonths' or 'Nyears'. The default is '12months'"}, {"-rARG, --resolution=ARG", "Set histogram resolution to 'days', 'weeks', 'months', 'years' or 'auto' (the default)"} } pepper.plotutils.add_plot_options(r) return r end -- Subtracts a time period in text form from a timestamp function subdate(t, period) local n = tonumber(period:match("^[%d]+")) - 1 assert(n >= 0, string.format("Invalid time range: %q", period)) local m = period:match("[%a]+$") -- Round towards zero and subtract time local date = os.date("*t", t) date.hour = 0 date.min = 0 date.sec = 0 if m == "d" or m == "days" then date.day = date.day - n elseif m == "w" or m == "weeks" then date.day = date.day - date.wday + 1 date.day = date.day - 7*n elseif m == "m" or m == "months" then date.day = 1 date.month = date.month - n elseif m == "y" or m == "years" then date.day = 1 date.month = 1 date.year = date.year - n else error(string.format("Unknown time unit %q", m)) end return os.time(date) end -- Estimates a resolution for the given time span function resolution(d, arg) if arg == "d" or arg == "days" then return "days" elseif arg == "w" or arg == "weeks" then return "weeks" elseif arg == "m" or arg == "months" then return "months" elseif arg == "y" or arg == "years" then return "years" elseif arg == "a" or arg == "auto" then date = os.date("*t", d) if date.year > 1971 then return "years" elseif date.month > 3 or date.year ~= 1970 then return "months" elseif date.day > 15 or date.month ~= 1 then return "weeks" else return "days" end else error(string.format("Unknown resolution %q", arg)) end end -- Returns the correct time slot for the given resolution function timeslot(t, resolution) -- Do a simple rounding towards zero, based on the given resolution date = os.date("*t", t) date.hour = 0; date.min = 0; date.sec = 0 if resolution == "weeks" then date.day = date.day - date.wday + 1 elseif resolution == "months" then date.day = 1 elseif resolution == "years" then date.day = 1; date.month = 1 end date.isdst = false return os.time(date) end -- Returns the next time slot for the given resolution function nextslot(t, resolution) date = os.date("*t", t) if resolution == "days" then date.day = date.day + 1 elseif resolution == "weeks" then date.day = date.day + 7 elseif resolution == "months" then date.month = date.month + 1 elseif resolution == "years" then date.year = date.year + 1 end date.isdst = false return os.time(date) end -- Returns an appropriate textual description of the date function slotkey(t, resolution) if resolution == "days" then return os.date("%Y-%m-%d", t) elseif resolution == "weeks" then return "Week " .. math.floor(1 + (os.date("*t", t).yday / 7)) .. ", " .. os.date("%Y", t) elseif resolution == "months" then return os.date("%b %y", t) elseif resolution == "years" then return os.date("%Y", t) end return tostring(t) end -- Main report function function run(self) -- First, determine time range for the revision iterator, according -- to the --period argument local period = self:getopt("p,period", "12months") local now = os.time() local start = subdate(now, period) -- Determine time resolution local resolution = resolution(now - start, self:getopt("r,resolution", "auto")) start = timeslot(start, resolution) -- Gather data local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local data = {} -- {commits, changes} repo:iterator(branch, {start=start}):map( function (r) local slot = timeslot(r:date(), resolution) if data[slot] == nil then data[slot] = {0, 0} end data[slot][1] = data[slot][1] + 1 data[slot][2] = data[slot][2] + #r:diffstat():files() end ) -- Generate plot data by iterating over all possible time slots local keys = {} local values = {} local slot = start while slot < now do table.insert(keys, slotkey(slot, resolution)) if data[slot] ~= nil then table.insert(values, data[slot]) else table.insert(values, {0, 0}) end slot = timeslot(nextslot(slot, resolution)) end -- Generate graph local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p, 800, 480) p:set_title(string.format("Commit Counts per %s (on %s)", resolution:sub(0, #resolution-1), branch)) p:cmd([[ set format y "%'.0f" set yrange [0:*] set grid ytics set ytics scale 0 set key box set key below set xtics nomirror set xtics rotate by -45 set style fill solid border -1 set style histogram cluster gap 1 ]]) p:plot_histogram(keys, values, {"Commits", "Changes"}) end pepper-0.3.3/reports/csv.lua000066400000000000000000000070571263211400600160100ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: csv.lua Dumps commit data in a CSV format --]] require "pepper.datetime" -- Describes the report function describe(self) local r = {} r.title = "CSV" r.description = "Dumps commit data in CSV format" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"-cARG, --columns=ARG", "Comma-seperated list of columns. " .. "Possible values (and abbreviations) are 'id', " .. "'author' (a), 'added' (+), 'removed' (-), " .. "'delta' (d), 'total' (t), 'files' (f), " .. "'message' (m)"} } pepper.datetime.add_daterange_options(r) return r end -- Returns the name of the given code function codename(code) if code == "id" then return "Commit ID" elseif code == "a" or code == "author" then return "Author" elseif code == "+" or code == "added" then return "Lines added" elseif code == "-" or code == "removed" then return "Lines removed" elseif code == "f" or code == "files" then return "Changed Files" elseif code == "t" or code == "total" then return "Total lines" elseif code == "d" or code == "delta" then return "Line delta" elseif code == "m" or code == "message" then return "Message" else error("Unknown column code: " .. code) end end -- Returns revision information given a field code function info(r, code) if code == "id" then return r:id() elseif code == "a" or code == "author" then return r:author() elseif code == "+" or code == "added" then return r:diffstat():lines_added() elseif code == "-" or code == "removed" then return r:diffstat():lines_removed() elseif code == "f" or code == "files" then local str = "" for i,v in ipairs(r:diffstat():files()) do str = str .. v .. ":" end return str:sub(0, #str-1) elseif code == "t" or code == "total" then return loc elseif code == "d" or code == "delta" then return r:diffstat():lines_added() - r:diffstat():lines_removed() elseif code == "m" or code == "message" then local str = r:message() str = str:gsub('[,"]', "\\%1") str = str:gsub('\n', "\\n") str = str:gsub('\r', "\\r") return '"' .. str .. '"' else error("Unknown column code: " .. code) end end -- Checks if a table contains a value function contains(t, v) for _,u in ipairs(t) do if u == v then return true end end return false end -- Main report function function run(self) -- Global counters loc = 0 local columns = pepper.utils.split(self:getopt("c,columns", "a,d,t"), ",") local printheader = true -- Gather data, but start at the beginning of the repository -- to get a proper LOC count if needed local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) local itstart = datemin if contains(columns, "t") or contains(columns, "total") then itstart = -1 end repo:iterator(branch, {start=itstart, stop=datemax}):map( function (r) if printheader then local header = "# Timestamp, " for i,v in ipairs(columns) do header = header .. codename(v) .. ", " end print(header:sub(0, #header-2)) printheader = false end loc = loc + r:diffstat():lines_added() - r:diffstat():lines_removed() if r:date() >= datemin then local str = tostring(r:date()) for i,v in ipairs(columns) do str = str .. "," .. info(r, v) end print(str) end end ) end pepper-0.3.3/reports/data.lua000066400000000000000000000114341263211400600161200ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: data.lua Dumps commit data --]] require "pepper.datetime" -- Describes the report function describe(self) local r = {} r.title = "Data dump" r.description = "Dumps commit data in various formats" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"-fARG, --format=ARG", "Select format (CSV, JSON)"}, {"-cARG, --columns=ARG", "Comma-seperated list of columns. Possible values " .. "(and abbreviations) are 'id', 'author' (a), " .. "'added' (+), 'removed' (-), 'delta' (d), 'total' (t), " .. "'files' (f), 'message' (m)." } } pepper.datetime.add_daterange_options(r) return r end -- Returns the name of the given code function codename(code) if code == "id" then return "Commit ID" elseif code == "a" or code == "author" then return "Author" elseif code == "+" or code == "added" then return "Lines added" elseif code == "-" or code == "removed" then return "Lines removed" elseif code == "f" or code == "files" then return "Changed Files" elseif code == "t" or code == "total" then return "Total lines" elseif code == "d" or code == "delta" then return "Line delta" elseif code == "m" or code == "message" then return "Message" else error("Unknown column code: " .. code) end end -- Returns revision information given a field code function info(r, code) if code == "id" then return r:id() elseif code == "a" or code == "author" then return r:author() elseif code == "+" or code == "added" then return r:diffstat():lines_added() elseif code == "-" or code == "removed" then return r:diffstat():lines_removed() elseif code == "f" or code == "files" then return r:diffstat():files() elseif code == "t" or code == "total" then return loc elseif code == "d" or code == "delta" then return r:diffstat():lines_added() - r:diffstat():lines_removed() elseif code == "m" or code == "message" then return r:message() else error("Unknown column code: " .. code) end end -- Checks if a table contains a value function contains(t, v) for _,u in ipairs(t) do if u == v then return true end end return false end -- Escape and quotes a string function mytostring(v) return tostring(v):gsub("[\\\"]", "\\%1"):gsub('\n', "\\n"):gsub("\t", "\\t"):gsub("\r", "\\r") end -- Sets up the data renderer function setup_renderer(format) local r = {} if format == "csv" then r.head = function (columns) local header = "# Timestamp, " for i,v in ipairs(columns) do header = header .. codename(v) .. ", " end print(header:sub(0, #header-2)) end r.tail = function () end r.row = function (data, first) local s = "" for _,v in ipairs(data) do if type(v) == "table" then v = table.concat(v, ":") end if type(v) == "string" then v = '"' .. mytostring(v):gsub('[,]', "\\%1") .. '"' end s = s .. v .. "," end print(s:sub(0, #s-1)) end elseif format == "json" then r.head = function (columns) r.bufs = {} end r.tail = function () print("[" .. table.concat(r.bufs, ",") .. "]") end r.row = function (data, first) local s = "[" local f = true for _,v in ipairs(data) do if not f then s = s .. "," end if type(v) == "table" then local str = "[" for _,f in ipairs(v) do str = str .. '"' .. mytostring(f) .. "\"," end v = str:sub(0, #str-1) .. "]" elseif type(v) == "string" then v = '"' .. mytostring(v) .. '"' end s = s .. v f = false end table.insert(r.bufs, s .. "]") end else return nil end return r end -- Main report function function run(self) -- Global counters loc = 0 local columns = pepper.utils.split(self:getopt("c,columns", "a,d,t"), ",") -- Gather data, but start at the beginning of the repository -- to get a proper LOC count if needed local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) local itstart = datemin if contains(columns, "t") or contains(columns, "total") then itstart = -1 end local format = self:getopt("f,format", "csv"):lower() local renderer = setup_renderer(format) if not renderer then error("Unknown format: " .. format) end renderer.head(columns) local first = true repo:iterator(branch, {start=itstart, stop=datemax}):map( function (r) loc = loc + r:diffstat():lines_added() - r:diffstat():lines_removed() if r:date() >= datemin then local data = {} table.insert(data, r:date()) for i,v in ipairs(columns) do table.insert(data, info(r, v)) end renderer.row(data, first) first = false end end ) renderer.tail() end pepper-0.3.3/reports/diffstat.lua000066400000000000000000000055061263211400600170160ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: diffstat.lua Pretty-prints a diffstat for a specified revision. The histogram rendering is inspired by the git implementation. --]] -- Describes the report function describe(self) local r = {} r.title = "Diffstat" r.description = "Print diffstat of given revision" r.options = {{"-rARG, --revision=ARG", "Revision ID"}} return r end -- Graph scaling function scale(i, width, max) if max < 2 then return i end return math.floor(((i - 1) * (width - 1) + max - 1) / (max - 1)) end -- "Render" and return a graph function graph(c, i, width, max) local n = i if max >= 2 then n = math.floor(((i - 1) * (width - 1) + max - 1) / (max - 1)) end local str = "" local t = 0 while t < n do str = str .. c t = t + 1 end return str end -- File name scaling function scale_name(name, width) local str = name if #str > width then local temp = str:sub(-(width - 3)) local spos = temp:find("/") if spos ~= nil then temp = temp:sub(spos) end str = "..." .. temp end local t = #str while t < width do str = str .. " " t = t + 1 end return str end -- Main report function function run(self) local id = self:getopt("r,revision") assert(id ~= nil, "Please specify a revision ID") local stat = self:repository():revision(id):diffstat() -- Determine maximum filename length and amount of change local width = 80 local max_len = 0 local max_change = 0 for k,v in ipairs(stat:files()) do if max_len < #v then max_len = #v end local change = (stat:lines_added(v) + stat:lines_removed(v)) if max_change < change then max_change = change end end -- Calculate name and graph widths local space = 10 local min_graph_width = 15 local name_width = max_len local graph_width = max_change if name_width + space + graph_width ~= 80 then graph_width = 80 - (name_width + space) end if graph_width < min_graph_width then name_width = (name_width + graph_width - min_graph_width) graph_width = min_graph_width end if graph_width > max_change then graph_width = max_change end -- Print histogram local line = "" for k,v in ipairs(stat:files()) do line = scale_name(v, name_width) line = line .. " | " .. string.format("%4d ", (stat:lines_added(v) + stat:lines_removed(v))) line = line .. graph("+", stat:lines_added(v), graph_width, max_change) line = line .. graph("-", stat:lines_removed(v), graph_width, max_change) print(" " .. line) end -- Print summary print(" " .. #stat:files() .. " files changed, " .. stat:lines_added() .. " insertions(+), " .. stat:lines_removed() .. " deletions(-)") end pepper-0.3.3/reports/directories.lua000066400000000000000000000070001263211400600175150ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: diffstat.lua Visualizes directory size changes on a given branch. --]] require "pepper.datetime" require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.title = "Directories" r.description = "Directory sizes" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"--tags[=ARG]", "Add tag markers to the graph, optionally filtered with a regular expression"}, {"-nARG", "Show the ARG largest directories"} } pepper.datetime.add_daterange_options(r) pepper.plotutils.add_plot_options(r) return r end -- Revision callback function function count(r) local s = r:diffstat() -- Save commit and file paths table.insert(commits, {r:date(), pepper.diffstat(s)}) -- Update directory sizes for i,v in ipairs(s:files()) do local dir = pepper.utils.dirname("/" .. v) local old = 0 if directories[dir] == nil then directories[dir] = s:lines_added(v) - s:lines_removed(v) else old = directories[dir] directories[dir] = directories[dir] + s:lines_added(v) - s:lines_removed(v) end end end -- Compares directory sizes of a and b function dircmp(a, b) return (a[2] > b[2]) end -- Checks whether commit a has been earlier than b function commitcmp(a, b) return (a[1] < b[1]) end -- Main report function function main(self) commits = {} -- Commit list by timestamp with diffstat directories = {} -- Total LOC by directory -- Gather data, but start at the beginning of the repository -- to get a proper LOC count. local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) repo:iterator(branch, {stop=datemax}):map(count) -- Determine the largest directories (by current LOC) local dirloc = {} for k,v in pairs(directories) do table.insert(dirloc, {k, v}) end table.sort(dirloc, dircmp) local i = 1 + tonumber(self:getopt("n", 6)) while i <= #dirloc do directories[dirloc[i][1]] = nil dirloc[i] = nil i = i + 1 end -- Sort commits by time table.sort(commits, commitcmp) -- Generate data arrays for the directories, skipping data points -- prior to datemin. local keys = {} local series = {} local loc = {} for i,a in ipairs(dirloc) do loc[a[1]] = 0 end for t,v in ipairs(commits) do if datemin < 0 or v[1] >= datemin then table.insert(keys, v[1]) table.insert(series, {}); end -- Update directory sizes local s = v[2]; for i,v in ipairs(s:files()) do local dir = pepper.utils.dirname("/" .. v) if loc[dir] ~= nil then loc[dir] = loc[dir] + s:lines_added(v) - s:lines_removed(v) end end if datemin < 0 or v[1] >= datemin then for i,a in ipairs(dirloc) do table.insert(series[#series], loc[a[1]]) end end end local directories = {} for i,a in ipairs(dirloc) do if #a[1] > 1 then table.insert(directories, a[1]:sub(2)) else table.insert(directories, a[1]) end end local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p) pepper.plotutils.setup_std_time(p, {key = "below"}) p:set_title("Directory Sizes (on " .. branch .. ")") if self:getopt("tags") ~= nil then pepper.plotutils.add_tagmarks(p, repo, self:getopt("tags", "*")) end p:set_xrange_time(keys[1], keys[#keys]) p:plot_series(keys, series, directories) end pepper-0.3.3/reports/filetypes.lua000066400000000000000000000074261263211400600172210ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: filetypes.lua Visualizes the file type distribution using a histogram. --]] require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.title = "Files" r.description = "Histogram of the file types distribution" r.options = { {"-rARG, --revision=ARG", "Select revision (defaults to HEAD)"}, {"-nARG", "Show the ARG most frequent file types"}, {"-p, --pie", "Generate pie chart instead of histogram"}, } pepper.plotutils.add_plot_options(r) return r end -- Maps file extensions to languages extmap = { [".h"] = "C/C++", [".hxx"] = "C/C++", [".hpp"] = "C/C++", [".c"] = "C/C++", [".cc"] = "C/C++", [".cxx"] = "C/C++", [".cpp"] = "C/C++", [".txx"] = "C/C++", [".java"] = "Java", [".lua"] = "Lua", [".s"] = "Assembly", [".pl"] = "Perl", [".pm"] = "Perl", [".sh"] = "Shell", [".py"] = "Python", [".txt"] = "Text", [".po"] = "Gettext", [".m4"] = "Autotools", [".at"] = "Autotools", [".ac"] = "Autotools", [".am"] = "Autotools", [".htm"] = "HTML", [".html"] = "HTML", [".xhtml"] = "HTML", [".bat"] = "Batch", [".cs"] = "C#", [".hs"] = "Haskell", [".lhs"] = "Haskell", [".js"] = "JavaScript", [".php"] = "PHP", [".php4"] = "PHP", [".php5"] = "PHP", [".scm"] = "Scheme", [".ss"] = "Scheme", [".vb"] = "Visual Basic", [".vbs"] = "Visual Basic", [".go"] = "Go", [".rb"] = "Ruby", [".m"] = "Objective-C" } -- Returns the extension of a file function extension(filename) local file,n = string.gsub(filename, "(.*/)(.*)", "%2") local ext,n = string.gsub(file, ".*(%..+)", "%1") if #ext == #file then return nil end return string.lower(ext) end -- Main report function function run(self) local rev = self:getopt("r, revision", "") local tree = self:repository():tree(rev) -- For now, simply count the number of files for each extension local count = {} for i,v in ipairs(tree) do local ext = extension(v) local nam = "" if ext ~= nil then nam = extmap[ext] if nam == nil then nam = string.upper(ext:sub(2)) end else nam = "unknown" end if count[nam] == nil then count[nam] = 0 end count[nam] = count[nam] + 1 end -- Sort by number of files local extcount = {} for k,v in pairs(count) do table.insert(extcount, {k, v}) end table.sort(extcount, function (a, b) return a[2] > b[2] end) -- Use descriptive titles local keys = {} local values = {} local n = tonumber(self:getopt("n", 6)) local others = 0 local unknown_idx = -1 for i,v in pairs(extcount) do if i <= n then table.insert(keys, v[1]) table.insert(values, v[2]) if v[1] == "unknown" then unknown_idx = #keys end else others = others + v[2] end end -- "unkown" should go last in the list if unknown_idx >= 0 then keys[unknown_idx],keys[#keys] = keys[#keys],keys[unknown_idx] values[unknown_idx],values[#values] = values[#values],values[unknown_idx] end local p = pepper.gnuplot:new() if self:getopt("p, pie") then pepper.plotutils.setup_output(p, 740, 600) else pepper.plotutils.setup_output(p, 600, 300) end if #rev > 0 then p:set_title("File types (at " .. rev .. ")") else p:set_title("File types") end if self:getopt("p, pie") then if others > 0 then table.insert(keys, "Other") table.insert(values, others) end p:cmd([[ set key box set key right outside ]]) p:plot_pie(keys, pepper.plotutils.normalize_pie(values)) else p:cmd([[ set style fill solid border -1 set style histogram cluster gap 1 set xtics nomirror set yrange [0:] set ylabel "Number of files" ]]) p:plot_histogram(keys, values) end end pepper-0.3.3/reports/gui.lua000066400000000000000000001004031263211400600157660ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: gui.lua Graphical user interface using GTK via lgob --]] require "lgob.gdk" require "lgob.gtk" require "lgob.gtksourceview" require "lgob.pango" require "lgob.cairo" -- Describes the report function describe(self) local r = {} r.title = "GUI" r.description = "Interactive report selection" return r end -- Main report function function run(self) local inst = App.new() inst:search_reports(); inst.window:show_all() gtk.main() end --[[ Utility functions --]] -- Returns the name of a report function get_report_name(path) local r = pepper.report(path) return r:name() end -- Returns the description of a report function get_report_description(path) local r = pepper.report(path) return r:description() end -- Returns the arguments of a report function get_report_arguments(path) local r = pepper.report(path) return r:options() end -- Qucikly shows an error messsage function error_message(window, title, text) local msg = gtk.MessageDialog:new(window, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE) msg:set("title", title) msg:set("text", text) msg:connect("response", gtk.Widget.destroy, msg) msg:run() end --[[ Application "class" and methods --]] App = {} -- Constructor function App.new() local self = {} setmetatable(self, {__index = App}) -- Setup window self.window = gtk.Window.new(gtk.WINDOW_TOPLEVEL) self.icon = load_window_icon() self.statusbar = gtk.Statusbar.new() self.iter = gtk.TreeIter.new() self.window:set("title", "pepper GUI", "icon", self.icon, "width-request", 600, "height-request", 400) -- Report list self.report_list = gtk.TreeView.new() self.report_list:set("width-request", 200) self.report_model = gtk.ListStore.new('gchararray', 'gchararray', 'gchararray') local r1, r2 = gtk.CellRendererText.new(), gtk.CellRendererText.new() local c1 = gtk.TreeViewColumn.new_with_attributes("Title", r1, 'text', 0) local c2 = gtk.TreeViewColumn.new_with_attributes("Path", r2, 'text', 1) self.report_list:append_column(c1) self.report_list:set("model", self.report_model) self.report_selection = self.report_list:get_selection() self.report_selection:set_mode(gtk.SELECTION_SINGLE) self.report_selection:connect("changed", self.load_report, self) self.report_list:connect("row-activated", self.run_clicked_report, self) self.report_list:set("has-tooltip", true) self.report_list:connect("query-tooltip", self.report_list_tooltip, self) -- Report argument list self.args_list = gtk.TreeView.new(false) self.args_list:set("height-request", 200) self.args_model = gtk.ListStore.new('gchararray', 'gchararray', 'gchararray', 'gchararray') r1, r2 = gtk.CellRendererText.new(), gtk.CellRendererText.new() r2:set("editable", true) r2:connect("edited", self.arg_edited, self) c1 = gtk.TreeViewColumn.new_with_attributes("Argument\t", r1, 'text', 0) c2 = gtk.TreeViewColumn.new_with_attributes("Value", r2, 'text', 1) self.args_list:append_column(c1) self.args_list:append_column(c2) self.args_list:set("model", self.args_model) self.args_list:set("has-tooltip", true) self.args_list:connect("query-tooltip", self.args_list_tooltip, self) -- Buttons self.edit_button = gtk.Button.new_from_stock("gtk-edit") self.edit_button:connect("clicked", self.edit_selected_report, self) self.run_button = gtk.Button.new_from_stock("gtk-execute") self.run_button:connect("clicked", self.run_selected_report, self) -- Actions --[[ self.a_open = gtk.Action.new("Load...", nil, "Open a session", "gtk-open") self.a_open:set("label", "Load...") -- self.a_open:connect("activate", self.open_report, self) self.a_save = gtk.Action.new("Save", nil, "Save this session", "gtk-save") self.a_save:set("label", "Save") -- self.a_save:connect("activate", self.save_report, self) self.a_save_as = gtk.Action.new("Save As...", nil, "Save this session", "gtk-save-as") self.a_save_as:set("label", "Save As...") -- self.a_save_as:connect("activate", self.save_report_as, self) self.a_quit = gtk.Action.new("Quit", nil, "Quit application", "gtk-quit") self.a_quit:connect("activate", gtk.main_quit) --]] self.a_open_report = gtk.Action.new("Add...", nil, "Open a report", "gtk-open") self.a_open_report:set("label", "Add...") self.a_open_report:connect("activate", self.add_report, self) self.a_edit = gtk.Action.new("Edit...", nil, "Edit the current report", "gtk-edit") self.a_edit:connect("activate", self.edit_selected_report, self) self.a_run = gtk.Action.new("Run...", nil, "Run the current report", "gtk-execute") self.a_run:connect("activate", self.run_selected_report, self) self.a_about = gtk.Action.new("About", nil, "About this application...", "gtk-about") self.a_about:connect("activate", self.show_about, self) -- Shortcuts self.accel_group = gtk.AccelGroup.new() self.window:add_accel_group(self.accel_group) --[[ self.a_open = gtk.Action.new("Load...", nil, "Open a session", "gtk-open") self.session_group = gtk.ActionGroup.new("Session") self.session_group:add_action_with_accel(self.a_open) self.a_open:set_accel_group(self.accel_group) self.session_group:add_action_with_accel(self.a_save) self.a_save:set_accel_group(self.accel_group) self.session_group:add_action_with_accel(self.a_save_as) self.a_save_as:set_accel_group(self.accel_group) self.session_group:add_action_with_accel(self.a_quit) self.a_quit:set_accel_group(self.accel_group) --]] self.report_group = gtk.ActionGroup.new("Run") self.report_group:add_action_with_accel(self.a_open_report, "A") self.a_open_report:set_accel_group(self.accel_group) self.report_group:add_action_with_accel(self.a_edit, "E") self.a_edit:set_accel_group(self.accel_group) self.report_group:add_action_with_accel(self.a_run, "F5") self.a_run:set_accel_group(self.accel_group) -- Menus --[[ self.session_menu = gtk.Menu.new() self.session_menu:add( self.a_open:create_menu_item(), self.a_save:create_menu_item(), self.a_save_as:create_menu_item(), gtk.SeparatorMenuItem.new(), self.a_quit:create_menu_item()) self.session_item = gtk.MenuItem.new_with_mnemonic("_Session") self.session_item:set_submenu(self.session_menu) --]] self.report_menu = gtk.Menu.new() self.report_menu:add( self.a_open_report:create_menu_item(), self.a_edit:create_menu_item(), self.a_run:create_menu_item()) self.report_item = gtk.MenuItem.new_with_mnemonic("_Report") self.report_item:set_submenu(self.report_menu) self.help_menu = gtk.Menu.new() self.help_item = gtk.MenuItem.new_with_mnemonic("_Help") self.help_menu:append(self.a_about:create_menu_item()) self.help_item:set_submenu(self.help_menu) -- Menubar self.menubar = gtk.MenuBar.new() -- self.menubar:append(self.session_item) self.menubar:append(self.report_item) self.menubar:append(self.help_item) -- Pack elements into layouts buttons = gtk.Box.new(gtk.ORIENTATION_HORIZONTAL, false, 2) buttons:pack_end(self.run_button, false, false, 4) buttons:pack_end(self.edit_button, false, false, 0) right_side = gtk.Box.new(gtk.ORIENTATION_VERTICAL, false, 2) self.label = gtk.Label.new() self.label:set("justify", gtk.JUSTIFY_LEFT, "halign", gtk.ALIGN_LEFT, "ellipsize", true) right_side:pack_start(self.label, false, false, 0) scroll = gtk.ScrolledWindow.new() scroll:add_with_viewport(self.args_list) right_side:pack_start(scroll, true, true, 0) right_side:pack_start(buttons, false, false, 0) self.hbox = gtk.Box.new(gtk.ORIENTATION_HORIZONTAL, false, 2) scroll = gtk.ScrolledWindow.new() scroll:add_with_viewport(self.report_list) self.hbox:pack_start(scroll, true, true, 0) self.hbox:pack_start(right_side, true, true, 0) self.vbox = gtk.Box.new(gtk.ORIENTATION_VERTICAL, false, 2) self.vbox:pack_start(self.menubar, false, false, 0) self.vbox:pack_start(self.hbox, true, true, 0) self.vbox:pack_start(self.statusbar, false, false, 2) self.window:add(self.vbox) -- About dialog self.about = gtk.AboutDialog.new() self.about:set( "program-name", "pepper", "authors", {"Jonas Gehring "}, "comments", "version " .. pepper.version() .. "\n\nA GUI report for the repository statistics tool.", "website", "http://scm-pepper.sourceforge.net", "license", license_header(), "logo", self.icon, "title", "About") self.window:connect("delete-event", gtk.main_quit) self.window:set_default("widget", self.run_button) return self end -- Shows the about dialog function App:show_about() self.about:run() self.about:hide() end -- Lists reports in the search paths and fills the report_model function App:search_reports() self.report_model:clear(); local reports = pepper.list_reports() for i,v in ipairs(reports) do local ok1, name = pcall(get_report_name, v) local ok2, description = pcall(get_report_description, v) if ok1 and ok2 then if name ~= "GUI" then self.report_model:append(self.iter) self.report_model:set(self.iter, 0, name, 1, description, 2, v) end else print("Error loading " .. v .. ": " .. name .. ", " .. description) end end if self.report_model:get_iter_first(self.iter) then self.report_selection:select_iter(self.iter) end end -- Adds a report to the report model function App:add_report() local dialog = gtk.FileChooserDialog.new('Save result', self.window, gtk.FILE_CHOOSER_ACTION_OPEN, 'gtk-cancel', gtk.RESPONSE_CANCEL, 'gtk-ok', gtk.RESPONSE_OK) local filter = gtk.FileFilter.new() filter:add_pattern("*.lua") filter:set_name("Lua scripts") dialog:add_filter(filter) local res = dialog:run() dialog:hide() if res ~= gtk.RESPONSE_OK then return end local path = dialog:get_filename() local ok1, name = pcall(get_report_name, path) local ok2, description = pcall(get_report_description, path) if ok1 and ok2 then self.report_model:append(self.iter) self.report_model:set(self.iter, 0, name, 1, description, 2, path) self.report_selection:select_iter(self.iter) else print("Error loading " .. path .. ": " .. name .. ", " .. description) end end function App:run_clicked_report(path, col) self.report_model:get_iter(self.iter, path) self:run_report(self.report_model:get(self.iter, 2), self.report_model:get(self.iter, 0)) end function App:run_selected_report() local res, m = self.report_selection:get_selected(self.iter) if not res then return end self:run_report(m:get(self.iter, 2), m:get(self.iter, 0)) end function App:run_report(path, name) self.run_button:set("sensitive", false) if not name then name = pepper.utils.basename(path) end self:push_status("Running " .. name .. "... (this might take some time)") -- Options local options = {} local valid = self.args_model:get_iter_first(self.iter) while valid do if #self.args_model:get(self.iter, 1) > 0 then options[self.args_model:get(self.iter, 0)] = self.args_model:get(self.iter, 1) end valid = self.args_model:iter_next(self.iter) end local status, out = pcall(pepper.run, path, options) self.run_button:set("sensitive", true) if not status then self:pop_status() self:push_status(out) error_message(self.window, "Error running report", out) return end self:show_output(out) self:pop_status() end function App:edit_selected_report() local res, m = self.report_selection:get_selected(self.iter) if not res then return end editor = Editor.new(m:get(self.iter, 2)) editor.window:show_all() end function App:load_report() local res, m = self.report_selection:get_selected(self.iter) if not res then return end -- Load source local path = m:get(self.iter, 2) local description = m:get(self.iter, 1) local name = m:get(self.iter, 0) local f = io.open(path, "r") if f then f:close() else -- TODO: Show error message print("error!") end -- Load meta data self.args_model:clear() local ok, out = pcall(get_report_arguments, path) if not ok then -- TODO: Show error message print("Error, " .. out) self.label:set_text("") return end for i,v in pairs(out) do self.args_model:append(self.iter) -- Extract long argument name if possible local i,j = v[1]:find("%-%-%w+=?") local name, n if i and j then name, n = v[1]:sub(i+2, j):gsub("=$", "") else -- Use short argument name i,j = v[1]:find("%-%w") name = v[1]:sub(i+1, j) end self.args_model:set(self.iter, 0, name, 1, "", 2, v[1], 3, v[2]) end self:clear_status(name) self:push_status(name) if description then self.label:set_text(name .. ": " .. description) else self.label:set_text(name) end end -- Returns the tooltip for an entry in report_list function App:report_list_tooltip(x, y, key, tool) local tx, ty = self.report_list:convert_widget_to_bin_window_coords(x, y) local res, path = self.report_list:get_path_at_pos(tx, ty) if res then self.report_model:get_iter(self.iter, gtk.TreePath.new_from_string(path)) tool:set_text(self.report_model:get(self.iter, 2) .. "\n" .. self.report_model:get(self.iter, 1)) return true end return false end function App:arg_edited(path, new) self.args_model:get_iter_from_string(self.iter, path) self.args_model:set(self.iter, 1, new) end function App:args_list_tooltip(x, y, key, tool) local tx, ty = self.args_list:convert_widget_to_bin_window_coords(x, y) local res, path = self.args_list:get_path_at_pos(tx, ty) if res then self.args_model:get_iter(self.iter, gtk.TreePath.new_from_string(path)) tool:set_text(self.args_model:get(self.iter, 2) .. "\n" .. self.args_model:get(self.iter, 3)) return true end return false end -- Shows the output of a report in a new window function App:show_output(out) dialog = OutputDialog.new(out) dialog.window:show_all() end -- Shows the given text in the status bar function App:push_status(text) self.statusbar:push(self.statusbar:get_context_id("default"), text) while gtk.events_pending() do gtk.main_iteration() end end -- Removes the last text shown in the status bar function App:pop_status() self.statusbar:pop(self.statusbar:get_context_id("default")) while gtk.events_pending() do gtk.main_iteration() end end function App:clear_status() self.statusbar:remove_all(self.statusbar:get_context_id("default")) end --[[ Output dialog "class" and methods --]] OutputDialog = {} -- Constructor function OutputDialog.new(data) local self = {} setmetatable(self, {__index = OutputDialog}) self.window = gtk.Dialog.new(gtk.WINDOW_TOPLEVEL) self.zoom_in_button = gtk.Button.new_from_stock("gtk-zoom-in") self.zoom_in_button:connect("clicked", self.zoom_in, self) self.zoom_out_button = gtk.Button.new_from_stock("gtk-zoom-out") self.zoom_out_button:connect("clicked", self.zoom_out, self) self.ok_button = gtk.Button.new_from_stock("gtk-ok") self.ok_button:connect("clicked", self.window.hide, self.window) self.save_button = gtk.Button.new_from_stock("gtk-save") self.save_button:connect("clicked", self.save, self) self.window:set_default("widget", self.ok_button) self.window:get_action_area():pack_start(self.zoom_in_button, false, false, 0) self.window:get_action_area():pack_start(self.zoom_out_button, false, false, 0) self.window:get_action_area():pack_start(gtk.Alignment.new(), true, true, 0) self.window:get_action_area():pack_end(self.save_button, false, false, 0) self.window:get_action_area():pack_end(self.ok_button, false, false, 0) -- Setup output views self.output_textual = gtk.TextView.new() self.output_textual:set("editable", false) self.output_textual:modify_font(pango.FontDescription.from_string("mono 10")) self.output_graphical = gtk.Image.new() self.output_graphical:connect("size-allocate", self.resize_image, self) self.views = gtk.Notebook.new() scroll = gtk.ScrolledWindow.new() scroll:add_with_viewport(self.output_textual) self.views:insert_page(scroll, gtk.Label.new("Textual"), 0) scroll:show() scroll = gtk.ScrolledWindow.new() self.aspect_graphical = gtk.AspectFrame.new() self.aspect_graphical:add(self.output_graphical) scroll:add_with_viewport(self.aspect_graphical) self.views:insert_page(scroll, gtk.Label.new("Graphical"), 1) scroll:show() if glib.utf8_validate(data, data:len()) then self.output_textual:get("buffer"):set("text", data) else self.output_textual:get("buffer"):set("text", "") end self.window:get_content_area():pack_start(self.views, true, true, 0) self.window:set("title", "pepper GUI - Results", "icon", load_window_icon(), "width-request", 400, "height-request", 400) -- Try to load image data from output local loader = gdk.PixbufLoader.new() loader:write(data, data:len()) loader:close() self.original_image = loader:get_pixbuf() if self.original_image then -- Initial use of downscaled image to make the output fit into the dialog window self.output_graphical:set_from_pixbuf(self.original_image.scale_simple(20, 20, 2)) self.aspect_graphical:set(gtk.ALIGN_LEFT, gtk.ALIGN_TOP, self.original_image:get_width() / self.original_image:get_height()) self.views:set_current_page(1) else self.views:set_current_page(0) end self.data = data return self end function OutputDialog:zoom_out() if self.original_image then local w = self.output_graphical:get_pixbuf():get_width() * 0.8 local h = self.output_graphical:get_pixbuf():get_height() * 0.8 if (w > 20) then self.output_graphical:set_from_pixbuf(self.original_image:scale_simple(w, h, 2)) end end end function OutputDialog:zoom_in() if self.original_image then local w = self.output_graphical:get_pixbuf():get_width() * 1.2 local h = self.output_graphical:get_pixbuf():get_height() * 1.2 if (w < 2000) then self.output_graphical:set_from_pixbuf(self.original_image:scale_simple(w, h, 2)) end end end function OutputDialog:resize_image() if self.original_image then local w = self.output_graphical:get_allocated_width() local h = self.output_graphical:get_allocated_height() self.output_graphical:set_from_pixbuf(self.original_image:scale_simple(w, h, 2)) end end function OutputDialog:save() local dialog = gtk.FileChooserDialog.new('Add report', self.window, gtk.FILE_CHOOSER_ACTION_SAVE, 'gtk-cancel', gtk.RESPONSE_CANCEL, 'gtk-ok', gtk.RESPONSE_OK) local filter = gtk.FileFilter.new() if self.views:get_current_page() == 0 then filter:add_pattern("*.txt") filter:set_name("Text files") else filter:add_pattern("*.svg") filter:set_name("SVG images") end dialog:add_filter(filter) local res = dialog:run() dialog:hide() if res ~= gtk.RESPONSE_OK then return end local file = io.open(dialog:get_filename(), "w") if not file then error_message(self.window, "Error saving result", "Error: Can't write to " .. dialog:get_filename()) return end for i = 1,self.data:len() do c = self.data:sub(i,i) if c == 0 then file:write('\00') else file:write(c) end end file:close() end --[[ Editor "class" and methods Heavily inspired by the Editor example from lgob http://gitorious.org/lgob/mainline/blobs/master/examples/Editor.lua --]] Editor = {} local __BACK, __FORWARD, __CLOSE = 0, 1, 2 -- Constructor function Editor.new(path) local self = {} setmetatable(self, {__index = Editor}) self.window = gtk.Window.new() self.vbox = gtk.Box.new(gtk.ORIENTATION_VERTICAL, 0) self.scroll = gtk.ScrolledWindow.new() self.scroll:set('hscrollbar-policy', gtk.POLICY_AUTOMATIC, 'vscrollbar-policy', gtk.POLICY_AUTOMATIC) self.statusbar = gtk.Statusbar.new() self.context = self.statusbar:get_context_id('default') self.statusbar:push(self.context, "Untitled document") -- Source view self.editor = gtk.SourceView.new() self.editor:modify_font(pango.FontDescription.from_string("mono 9")) local manager = gtk.source_language_manager_get_default() local lang = manager:get_language("lua") self.editor:set( "show-line-numbers", true, "highlight-current-line", true, "auto-indent", true, "tab-width", 4) self.buffer = self.editor:get("buffer") self.buffer:set("language", lang) self.buffer:get_undo_manager():connect("can-undo-changed", self.update_actions, self) self.buffer:get_undo_manager():connect("can-redo-changed", self.update_actions, self) -- Find dialog self.find_dialog, self.find_entry = self:build_find_dialog() -- Clipboard self.clip = gtk.clipboard_get(gdk.Atom.intern('CLIPBOARD')) -- File dialog self.dialog = gtk.FileChooserDialog.new('Select the file', self.window, gtk.FILE_CHOOSER_ACTION_OPEN, 'gtk-cancel', gtk.RESPONSE_CANCEL, 'gtk-ok', gtk.RESPONSE_OK) local filter = gtk.FileFilter.new() filter:add_pattern("*.lua") filter:set_name("Lua scripts") self.dialog:add_filter(filter) -- Accelerators (shortcuts) self.accel_group = gtk.AccelGroup.new() self.window:add_accel_group(self.accel_group) self.action_group = gtk.ActionGroup.new('Default') -- Actions self.a_newfile = gtk.Action.new("New file", nil, "Create a new file", "gtk-new") self.a_newfile:connect("activate", self.new_file, self) self.action_group:add_action_with_accel(self.a_newfile) self.a_newfile:set_accel_group(self.accel_group) self.a_loadfile = gtk.Action.new("Open file", nil, "Open a file", "gtk-open") self.a_loadfile:connect("activate", self.load_file, self) self.action_group:add_action_with_accel(self.a_loadfile) self.a_loadfile:set_accel_group(self.accel_group) self.a_savefile = gtk.Action.new("Save file", nil, "Save the current file", "gtk-save") self.a_savefile:connect("activate", self.save_file, self) self.action_group:add_action_with_accel(self.a_savefile) self.a_savefile:set_accel_group(self.accel_group) self.a_find = gtk.Action.new("Find", nil, "Searchs the text", "gtk-find") self.a_find:connect("activate", self.show_find, self) self.action_group:add_action_with_accel(self.a_find) self.a_find:set_accel_group(self.accel_group) self.a_quit = gtk.Action.new("Close", nil, "Close the editor", "gtk-close") self.a_quit:connect("activate", self.window.hide, self.window) self.action_group:add_action_with_accel(self.a_quit) self.a_quit:set_accel_group(self.accel_group) self.a_undo = gtk.Action.new("Undo", nil, "Undo last edit", "gtk-undo") self.a_undo:connect("activate", self.buffer.undo, self.buffer) self.action_group:add_action_with_accel(self.a_undo) self.a_undo:set_accel_group(self.accel_group) self.a_redo = gtk.Action.new("Redo", nil, "Redo last undone edit", "gtk-redo") self.a_redo:connect("activate", self.buffer.redo, self.buffer) self.action_group:add_action_with_accel(self.a_redo) self.a_redo:set_accel_group(self.accel_group) self.a_cut = gtk.Action.new("Cut", nil, "Cut the selection to the clipboard", "gtk-cut") self.a_cut:connect("activate", self.cut, self) self.action_group:add_action_with_accel(self.a_cut) self.a_cut:set_accel_group(self.accel_group) self.a_copy = gtk.Action.new("Copy", nil, "Copy the selection to the clipboard", "gtk-copy") self.a_copy:connect("activate", self.copy, self) self.action_group:add_action_with_accel(self.a_copy) self.a_copy:set_accel_group(self.accel_group) self.a_paste = gtk.Action.new("Paste", nil, "Paste the clipboard info to the buffer", "gtk-paste") self.a_paste:connect("activate", self.paste, self) self.action_group:add_action_with_accel(self.a_paste) self.a_paste:set_accel_group(self.accel_group) self.a_delete = gtk.Action.new("Delete", nil, "Delete the selection", "gtk-delete") self.a_delete:connect("activate", self.delete, self) self.action_group:add_action_with_accel(self.a_delete) self.a_delete:set_accel_group(self.accel_group) -- Toolbar self.toolbar = gtk.Toolbar.new() self.toolbar:set("toolbar-style", gtk.TOOLBAR_ICONS) self.toolbar:add( self.a_newfile:create_tool_item(), self.a_loadfile:create_tool_item(), self.a_savefile:create_tool_item(), gtk.SeparatorToolItem.new(), self.a_undo:create_tool_item(), self.a_redo:create_tool_item(), gtk.SeparatorToolItem.new(), self.a_find:create_tool_item() ) -- Menu self.menubar = gtk.MenuBar.new() self.file = gtk.Menu.new() self.file_item = gtk.MenuItem.new_with_mnemonic("_File") self.file:add( self.a_newfile:create_menu_item(), gtk.SeparatorMenuItem.new(), self.a_loadfile:create_menu_item(), self.a_savefile:create_menu_item(), self.a_find:create_menu_item(), gtk.SeparatorMenuItem.new(), self.a_quit:create_menu_item() ) self.file_item:set_submenu(self.file) self.edit = gtk.Menu.new() self.edit_item = gtk.MenuItem.new_with_mnemonic("_Edit") self.edit_item:connect("activate", self.update_actions, self) self.edit:add( self.a_undo:create_menu_item(), self.a_redo:create_menu_item(), gtk.SeparatorMenuItem.new(), self.a_cut:create_menu_item(), self.a_copy:create_menu_item(), self.a_paste:create_menu_item(), self.a_delete:create_menu_item()) self.edit_item:set_submenu(self.edit) self.menubar:add(self.file_item, self.edit_item) -- Packing it! self.vbox:pack_start(self.menubar, false, false, 0) self.vbox:pack_start(self.toolbar, false, false, 0) self.scroll:add(self.editor) self.vbox:pack_start(self.scroll, true, true, 0) self.vbox:pack_start(self.statusbar, false, false, 0) self.window:add(self.vbox) self.window:set("icon", load_window_icon(), "width-request", 800, "height-request", 600) self.editor:grab_focus() local file = io.open(path) if file then self.buffer:set("text", file:read("*a")) file:close() self.opened_file = path local basepath = pepper.utils.basename(path) self:log("Loaded from " .. basepath) self.window:set("title", "pepper GUI - Editor: " .. basepath) else self.window:set("title", "pepper GUI - Editor: Unititled") end self:update_actions() return self end -- Logs a message in the statusbar function Editor:log(msg) self.statusbar:pop(self.context) self.statusbar:push(self.context, msg) end -- Creates a new document function Editor:new_file() self.buffer:set("text", "") self.opened_file = nil self:log("Untitled") self.window:set("title", "pepper GUI - Editor: Unititled") end -- Loads the contents of a file and shows it in the editor function Editor:load_file() self.dialog:set("action", gtk.FILE_CHOOSER_ACTION_OPEN) local res = self.dialog:run() self.dialog:hide() if res == gtk.RESPONSE_OK then local filename = self.dialog:get_filename() local file = io.open(filename) self.buffer:set("text", file:read("*a")) file:close() self.opened_file = filename filename = pepper.utils.basename(filename) self:log("Loaded from " .. filename) self.window:set("title", "pepper GUI - Editor: " .. filename) end end -- Saves the content of the editor into a file function Editor:save_file() local destination if self.opened_file then destination = self.opened_file else self.dialog:set("action", gtk.FILE_CHOOSER_ACTION_SAVE) local res = self.dialog:run() self.dialog:hide() if res == gtk.RESPONSE_OK then destination = self.dialog:get_filename() end end if destination then self:do_save(destination) end end -- Do the real save. function Editor:do_save(destination) local file = io.open(destination, "w") file:write(self.buffer:get("text")) file:close() self.opened_file = destination local basepath = pepper.utils.basename(destination) self:log("Saved to " .. basepath) self.window:set("title", "pepper GUI - Editor: " .. basepath) end -- Copy the selected info to the clipboard function Editor:copy() self.buffer:copy_clipboard(self.clip) end -- Cut the selected info to the clipboard function Editor:cut() self.buffer:cut_clipboard(self.clip, true) end -- Paste the clipboard info to the buffer function Editor:paste() self.buffer:paste_clipboard(self.clip, nil, true) end -- Delete the selected info from the buffer function Editor:delete() self.buffer:delete_selection(true, true) end -- Updates the actions. function Editor:update_actions() local selected = self.buffer:get_selection_bounds() local paste = self.clip:wait_is_text_available() self.a_undo:set("sensitive", self.buffer:can_undo()) self.a_redo:set("sensitive", self.buffer:can_redo()) self.a_cut:set("sensitive", selected) self.a_delete:set("sensitive", selected) self.a_copy:set("sensitive", selected) self.a_paste:set("sensitive", paste) end -- Builds a find dialog. function Editor:build_find_dialog() local dialog = gtk.Dialog.new() local vbox = dialog:get_content_area() local entry = gtk.Entry.new() vbox:pack_start(gtk.Label.new("Search for:"), true, false, 5) vbox:pack_start(entry, true, true, 5) vbox:show_all() dialog:set("title", "Find", "window-position", gtk.WIN_POS_CENTER) dialog:add_buttons("gtk-go-back", __BACK, "gtk-go-forward", __FORWARD, "gtk-close", __CLOSE) return dialog, entry end -- Shows the find dialog function Editor:show_find() local iter, istart, iend = gtk.TextIter.new(), gtk.TextIter.new(), gtk.TextIter.new() self.buffer:get_start_iter(iter) -- Run until closed while true do local res = self.find_dialog:run() if res == __BACK or res == __FORWARD then local found, text, li = false, self.find_entry:get("text") if res == __BACK then if mark1 then self.buffer:get_iter_at_mark(iter, mark1) end found = gtk.TextIter.backward_search(iter, text, 0, istart, iend) else if mark2 then self.buffer:get_iter_at_mark(iter, mark2) end found = gtk.TextIter.forward_search(iter, text, 0, istart, iend) end -- Found the text? if found then self.buffer:select_range(istart, iend) mark1 = self.buffer:create_mark("last_start", istart, false) mark2 = self.buffer:create_mark("last_end", iend, false) self.editor:scroll_to_mark(mark1) found = false end else break end end self.find_dialog:hide() end --[[ Built-in data --]] -- Constructs a window icon function load_window_icon() -- PNG data in base64 local data = [[ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAEuklE QVRYheWXTWgcZRzGn3dm3tnZmcnuZNskStIeNqZLbF2CQrB+QNBDCfEQJD3l1oNQU9uLINjcFBFB bI8iYlVC8VBLCtbES4u1paFNW/tBv2zMJqkmm+5mZ2d2dj7ed8aDtIiHzppVWvCBuT08zw/e9/8f XuD/LtKIaevWrfKOHTv2ZLPZ1zOZTC6dTuszMzPyyMgIFhYWoOs6z+fz9Xq9vjQ3N3fyyJEjnx84 cOBCI9lCnGF0dHRkdHS0rKrqx5Zlvahp2kZFURRBEARKqSDLskAppel0OmUYxtNXrlx5c+/evbMn Tpz4ZWxs7NlmAcSenp6vk8mkJooiRFHE4uIiFhcXoSgKpqenkc/n0dfXh4mJCTDGwBgD5xyGYXQP Dw+fjOuIA2ghhCiUUkiS9OATRRFDQ0MYHh7Grl27QCl9UB5FEYIguA/SAiDVDABs2wbn/EF5LpdD b28vpqamQCnF+Pg4bty4gZ07dyIIAgRBAM45HMeBaZpx8ZDiDLqug1IKVVVhGAYcx0EymUQURSCE IIoiuK4Lx3FQrVYxMDCAUqkEwzDgum7zAPeVyWSg6zpkWUYURRgcHITv+8jlctB1HYQQyLIMTdNQ q9XgOE5DubFH8F/rsQdoO3fu/N31hk9OHlsC0NYMQFjzgq9O/XR6tlqt8kaLy+Uym5g4fP5OYelL AOHDvLGXsH/7qzNv7N539L39+wb8M2efS6VbeoxUqsMwDK2rq6tFURRYlmXZtl0rlSsrq8WVWxGR Zt9+56MfP/3s4JPT300+NL/hKXj/g4OnKCWnCYWYpJKwMHdHvXz5QmsQhOjvf3ltc7bbqQcsjALw IIhCP2gst2GAvyub7fa2bOkuAgBjCNk6cx77KXj0ANzzk+sN9+qO2jTArZs/j60XoDB/862mASqV ey998uH+wz9MH8s1Wvz98W97x9/d/Y1dWt0e542dgpADpXvF7qnjR7+4dvnsksf41c6uTVdf6H/+ al9fvggAly5dbJ+9+PO2cqX8TLlY3FYqVzoDxkmdx++uWABKKUzTxBPtG8n1m7c3GYaxqbi8Mnj7 +jUcOgRwHqBWqyOzYQOW7t6FIAhYK1cgy0moSS0WIPYIBIGAShIcL4BMKVzXhSQJYIxDkkRQSkGp BMuy0dG+EYiAjo4OZDIpMBa/HWIBVE0DCznMiokwDKFpGjjn8DwXa+U1cMYRBAyMc1h2HZIkwfd8 EAjg/F8AME0TUQQkZIqAMawUV6GqGgQiorW1FQIRoCgKopBDVZMQRRGykkDNcSGQ+DUT6wgZh0QI PM+DpqnQdRWuW4ckS+BRCN/3wDmHpmqw7Ro4Z+jq7IRpVsB4/A8hDuB3USKW43qQZRm6pkNTVfh+ AEkUUa2sAaL4J2gUglIJIQ9RLpeh6Rq8er0K4LeHFYgxAIGSSC4T4BVFURKMMcgJGYQQSJIAQRAQ RiEEQULAOAgRoKoqbLsGq7pWrViVPfeKxdlmAFAqrV7yvPqhMORPeZ7fJlApmUwkyP1XXRQB7W3t sCwLplmJnFqttLRYmJ7/dW6wMD9/Ji6/obfhX/2bN2eH9JT6mpFK99i2LbdmWhHyyF8uLt/2XXey UChMAYj+Ye6j0x+q6x5nD2AfqwAAAABJRU5ErkJggg==]] -- base64 decoding (from http://lua-users.org/wiki/BaseSixtyFour) function dec64(data) local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' data = string.gsub(data, '[^'..b..'=]', '') return (data:gsub('.', function(x) if (x == '=') then return '' end local r,f='',(b:find(x)-1) for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end return r; end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) if (#x ~= 8) then return '' end local c=0 for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end return string.char(c) end)) end local loader = gdk.PixbufLoader.new() local dec = dec64(data) loader:write(dec, #dec) loader:close() return loader:get_pixbuf() end -- Returns a short GPL license header function license_header() return [[ Pepper 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. Pepper 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 pepper. If not, see http://www.gnu.org/licenses/. ]] end pepper-0.3.3/reports/iteration_time.lua000066400000000000000000000012661263211400600202250ustar00rootroot00000000000000require "pepper.datetime" -- Describes the report function describe(self) local r = {} r.name = "iteration-time" r.description = "Measures rveision iteration time" r.options = { {"-bARG, --branch=ARG", "Select branch"} } pepper.datetime.add_daterange_options(r) return r end -- Main report function function run(self) local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) local w = pepper.watch() repo:iterator(branch, {start=datemin, stop=datemax}):map(function (r) print("* Fetched revision " .. r:id() .. ".") end) print(string.format("elapsed time: %.4fs", w:elapsed())) end pepper-0.3.3/reports/loc.lua000066400000000000000000000040331263211400600157610ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: loc.lua Visualizes lines of code changes on a given branch. --]] require "pepper.datetime" require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.name = "LOC" r.description = "Lines of code" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"--tags[=ARG]", "Add tag markers to the graph, optionally filtered with Lua pattern ARG"} } pepper.datetime.add_daterange_options(r) pepper.plotutils.add_plot_options(r) return r end -- Revision callback function function callback(r) if r:date() == 0 then return end s = r:diffstat() local delta = s:lines_added() - s:lines_removed() if locdeltas[r:date()] == nil then locdeltas[r:date()] = delta table.insert(dates, r:date()) else locdeltas[r:date()] = locdeltas[r:date()] + delta end end -- Main report function function run(self) dates = {} locdeltas = {} -- Gather data, but start at the beginning of the repository -- to get a proper LOC count. local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) repo:iterator(branch, {stop=datemax}):map(callback) -- Sort loc data by date table.sort(dates) local loc = {} local keys = {} local total = 0 for k,v in ipairs(dates) do total = total + locdeltas[v] if datemin < 0 or v >= datemin then table.insert(keys, v) table.insert(loc, total) end end -- Generate graph local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p) pepper.plotutils.setup_std_time(p) p:set_title("Lines of Code (on " .. branch .. ")") if self:getopt("tags") ~= nil then pepper.plotutils.add_tagmarks(p, repo, self:getopt("tags", "*")) end p:set_xrange_time(keys[1], keys[#keys]) p:plot_series(keys, loc) end pepper-0.3.3/reports/participation.lua000066400000000000000000000137551263211400600200650ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: authors.lua Visualizes the participation of a single author. --]] require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.name = "Participation" r.description = "Participation of a single author" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"-aARG, --author=ARG", "Show participation of author ARG"}, {"-c, --changes, -l", "Count line changes instead of commit counts"}, {"-pARG, --period=ARG", "Show counts for the last 'Ndays', 'Nweeks', 'Nmonths' or 'Nyears'. The default is '12months'"}, {"-rARG, --resolution=ARG", "Set histogram resolution to 'days', 'weeks', 'months', 'years' or 'auto' (the default)"} } pepper.plotutils.add_plot_options(r) return r end -- Subtracts a time period in text form from a timestamp function subdate(t, period) n = tonumber(period:match("^[%d]+")) - 1 assert(n >= 0, string.format("Invalid time range: %q", period)) m = period:match("[%a]+$") -- Round towards zero and subtract time date = os.date("*t", t) date.hour = 0 date.min = 0 date.sec = 0 if m == "d" or m == "days" then date.day = date.day - n elseif m == "w" or m == "weeks" then date.day = date.day - date.wday + 1 date.day = date.day - 7*n elseif m == "m" or m == "months" then date.day = 1 date.month = date.month - n elseif m == "y" or m == "years" then date.day = 1 date.month = 1 date.year = date.year - n else error(string.format("Unknown time unit %q", m)) end return os.time(date) end -- Estimates a resolution for the given time span function resolution(d, arg) if arg == "d" or arg == "days" then return "days" elseif arg == "w" or arg == "weeks" then return "weeks" elseif arg == "m" or arg == "months" then return "months" elseif arg == "y" or arg == "years" then return "years" elseif arg == "a" or arg == "auto" then date = os.date("*t", d) if date.year > 1974 then return "years" elseif (date.month > 6 and date.year == 1971) or date.year > 1971 then return "months" elseif (date.day > 15 and date.month == 2) or date.month > 2 then return "weeks" else return "days" end else error(string.format("Unknown resolution %q", arg)) end end -- Returns the correct time slot for the given resolution function timeslot(t, resolution) -- Do a simple rounding towards zero, based on the given resolution date = os.date("*t", t) date.hour = 0; date.min = 0; date.sec = 0 if resolution == "weeks" then date.day = date.day - date.wday + 1 elseif resolution == "months" then date.day = 1 elseif resolution == "years" then date.day = 1; date.month = 1 end date.isdst = false return os.time(date) end -- Returns the next time slot for the given resolution function nextslot(t, resolution) date = os.date("*t", t) if resolution == "days" then date.day = date.day + 1 elseif resolution == "weeks" then date.day = date.day + 7 elseif resolution == "months" then date.month = date.month + 1 elseif resolution == "years" then date.year = date.year + 1 end date.isdst = false return os.time(date) end -- Returns an appropriate textual description of the date function slotkey(t, resolution) if resolution == "days" then return os.date("%Y-%m-%d", t) elseif resolution == "weeks" then return "Week " .. math.floor(1 + (os.date("*t", t).yday / 7)) .. ", " .. os.date("%Y", t) elseif resolution == "months" then return os.date("%b %y", t) elseif resolution == "years" then return os.date("%Y", t) end return tostring(t) end -- Tries to determine the currently used credentials of the given repo function determine_author(repo) local author = nil if repo:type() == "git" then local f = io.popen("git config user.name"); -- GIT_DIR already set by pepper author = f:read() f:close() end return author end -- Main report function function run(self) local repo = self:repository() -- Which author should be tracked local author = self:getopt("a,author") if author == nil then author = determine_author(repo) assert(author ~= nil, "Please specifiy an author name") end -- First, determine time range for the revision iterator, according -- to the --period argument local period = self:getopt("p,period", "12months") local now = os.time() local start = subdate(now, period) -- Determine time resolution local resolution = resolution(now - start, self:getopt("r,resolution", "auto")) start = timeslot(start, resolution) local count_changes = self:getopt("c,changes,l") -- Gather data local branch = self:getopt("b,branch", repo:default_branch()) local data = {} -- {commits, changes} repo:iterator(branch, {start=start}):map( function (r) local slot = timeslot(r:date(), resolution) if data[slot] == nil then data[slot] = {0, 0} end local n = 1 if count_changes then n = r:diffstat():lines_added() end if r:author() == author then data[slot][1] = data[slot][1] + n else data[slot][2] = data[slot][2] + n end end ) -- Generate plot data by iterating over all possible time slots local keys = {} local values = {} local slot = start while slot < now do table.insert(keys, slotkey(slot, resolution)) if data[slot] ~= nil then table.insert(values, data[slot]) else table.insert(values, {0, 0}) end slot = timeslot(nextslot(slot, resolution)) end -- Generate graph local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p, 800, 200) p:set_title(string.format("Participation of %s per %s (on %s)", author, resolution:sub(0, #resolution-1), branch)) p:cmd([[ set noytics set key below set noxtics set style fill solid set style histogram rowstacked set boxwidth 0.8 relative ]]) if count_changes then p:plot_histogram(keys, values, {string.format("Contributions by %s", author), "All Contributions"}) else p:plot_histogram(keys, values, {string.format("Commits by %s", author), "All Commits"}) end end pepper-0.3.3/reports/pepper/000077500000000000000000000000001263211400600157745ustar00rootroot00000000000000pepper-0.3.3/reports/pepper/Makefile.am000066400000000000000000000006141263211400600200310ustar00rootroot00000000000000# # pepper - SCM statistics report generator # Copyright (C) 2010-present Jonas Gehring # # Released under the GNU General Public License, version 3. # Please see the COPYING file in the source distribution for license # terms and conditions, or see http://www.gnu.org/licenses/. # pkgdatadir = $(datadir)/@PACKAGE@/pepper # Bundled modules dist_pkgdata_DATA = \ datetime.lua \ plotutils.lua pepper-0.3.3/reports/pepper/datetime.lua000066400000000000000000000065661263211400600203100ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file pepper/datetime.lua Common utility functions for date and time handling --]] --- Common utility functions for date and time handling. -- Please note that this is a Lua module. If you want to use it, add --
require "pepper.datetime"
to your script. module("pepper.datetime", package.seeall) --- Adds command-line options for start and end date to meta.options. -- @param r If not null, options will be added to this table function add_daterange_options(r) if r then if not r.options then r.options = {} end table.insert(r.options, {"--datemin=ARG", "Start date (format is YYYY-MM-DD)"}) table.insert(r.options, {"--datemax=ARG", "End date (format is YYYY-MM-DD)"}) else if meta.options == nil then meta.options = {} end table.insert(meta.options, {"--datemin=ARG", "Start date (format is YYYY-MM-DD)"}) table.insert(meta.options, {"--datemax=ARG", "End date (format is YYYY-MM-DD)"}) end end --- Parses the date range from the report's command-line options. -- If an option isn't present, -1 is returned in its place. -- @param report The report. If \p null, pepper.current_report() will be used. -- @returns start, end as seconds function date_range(report) if report == nil then report = pepper.current_report() end local datemin = report:getopt("datemin") if datemin ~= nil then datemin = pepper.utils.strptime(datemin, "%Y-%m-%d") else datemin = -1 end local datemax = report:getopt("datemax") if datemax ~= nil then datemax = pepper.utils.strptime(datemax, "%Y-%m-%d") else datemax = -1 end return datemin, datemax end --- Returns a human-friendly description of a time range. -- This is a port of the Rails funtion -- distance_of_time_in_words. -- @param secs Time range in seconds, e.g. generated using os.difftime(). function humanrange(secs) local mins = math.floor(secs / 60 + 0.5) if mins < 2 then if secs < 5 then return "less than 5 seconds" elseif secs < 10 then return "less than 10 seconds" elseif secs < 20 then return "less than 20 seconds" elseif secs < 40 then return "half a minute" elseif secs < 60 then return "less than a minute" else return "1 minute" end elseif mins < 45 then return tostring(mins) .. " minutes" elseif mins < 90 then return "about 1 hour" elseif mins < 1440 then return "about " .. math.floor(mins / 60) .. " hours" elseif mins < 2530 then return "1 day" elseif mins < 43200 then return math.floor(mins / 1440) .. " days" elseif mins < 86400 then return "about 1 month" elseif mins < 525600 then return math.floor(mins / 43200) .. " months" else local years = math.floor(mins / 525600) local leap_offset = math.floor(years / 4) * 1440 local remainder = ((mins - leap_offset) % 525600) local suffix = "s" if years == 1 then suffix = "" end if remainder < 131400 then return "about " .. years .. " year" .. suffix elseif remainder < 394200 then return "over " .. years .. " year " ..suffix else return "over " .. years .. " years" end end end pepper-0.3.3/reports/pepper/plotutils.lua000066400000000000000000000135301263211400600205400ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file pepper/plotutils.lua Common utility functions for plotting --]] -- Check for required modules assert(pepper.gnuplot ~= nil, "pepper built without Gnuplot support") --- Common utility functions for plotting. -- Please note that this is a Lua module. If you want to use it, add --
require "pepper.plotutils"
to your script. The functions -- in this module are provided to facilate common plotting tasks and -- to remove duplicate code for the built-in reports. module("pepper.plotutils", package.seeall) --- Adds common options for graphical reports to the given table. -- The following options will be added: -- -- -- -- -- --
OptionDescription
-oARG, --output=ARGSelect output file
-tARG, --type=ARGExplicitly set image type
-sW[xH], --size=W[xH]Set image size to width W and height H
-- @param r If not null, options will be added to this table. Else, the global meta.options table will be used. function add_plot_options(r) if r then if not r.options then r.options = {} end table.insert(r.options, {"-oARG, --output=ARG", "Select output file"}) table.insert(r.options, {"-tARG, --type=ARG", "Explicitly set image type"}) table.insert(r.options, {"-sW[xH], --size=W[xH]", "Set image size to width W and height H"}) else if meta.options == nil then meta.options = {} end table.insert(meta.options, {"-oARG, --output=ARG", "Select output file"}) table.insert(meta.options, {"-tARG, --type=ARG", "Explicitly set image type"}) table.insert(meta.options, {"-sW[xH], --size=W[xH]", "Set image size to width W and height H"}) end end --- Converts from UNIX to Gnuplot epoch. -- @param time UNIX timestamp function convepoch(time) return time - 946684800 end --- Sets the plot output size, type and filename from command-line options. -- @param plot pepper.plot object -- @param width Optional width, 640 by default -- @param height Optional height, 480 by default function setup_output(plot, width, height) if width == nil then width = 640 end if height == nil then height = 480 end local ratio = height / width local self = pepper.current_report() local file = self:getopt("o, output", "") local size = pepper.utils.split(self:getopt("s, size", "" .. width .. "x" .. height), "x") local terminal = self:getopt("t, type") width = tonumber(size[1]) height = width * ratio if (#size > 1) then height = tonumber(size[2]) end if terminal ~= nil then plot:set_output(file, width, height, terminal) else plot:set_output(file, width, height) end end --- Performs a standard plot setup for time data. -- Basically, this evaluates to the following GNUPlot commands (without comments): --
 set xdata time           # X values of data are time values
-- set timefmt "%s" # Time values are given as UNIX timestamps
-- set yrange [0:*] # Start Y axis at 0
-- set xtics nomirror # Don't mirror X axis tics
-- set xtics rotate by -45 # Rotate X axis labels
-- set rmargin 8 # Make sure there's enough space for the rotated labels
-- set grid ytics # Show grid lines for the Y axis tics
-- The options parameter can be used to customize the -- plot. The following keys are supported: -- -- -- -- -- --
KeyDescriptionDefault value
keyKey positionNo key
xformatX axis labels format"%b %y"
yformatY axis labels format"%'.0f"

-- @param plot pepper.plot object -- @param options Optional dictionary with additional options function setup_std_time(plot, options) plot:cmd([[ set xdata time set timefmt "%s" set yrange [0:*] set xtics nomirror set xtics rotate by -45 set rmargin 8 set grid ytics ]]) if options == nil then options = {} end if options.key ~= nil then plot:cmd("set key box; set key " .. options.key) end if options.xformat == nil then options.xformat = "%b %y" end plot:cmd("set format x \"" .. options.xformat .. "\"") if options.yformat == nil then options.yformat = "%'.0f" end plot:cmd("set format y \"" .. options.yformat .. "\"") end --- Adds x2tics for repository tags. -- If pattern is not nil, only tags matching -- the pattern will be added. -- @param plot pepper.plot obejct -- @param repo pepper.repository object -- @param pattern Optional pattern for filtering tags function add_tagmarks(plot, repo, pattern) -- Fetch tags and generate tic data local tags = repo:tags() if #tags == 0 then return end local x2tics = "(" for k,v in ipairs(tags) do if pattern == nil or v:name():find(pattern) ~= nil then x2tics = x2tics .. "\"" .. v:name() .. "\" " .. convepoch(repo:revision(v:id()):date()) .. "," end end if #x2tics == 1 then return end x2tics = x2tics:sub(0, #x2tics-1) .. ")" plot:cmd([[ set x2data time set format x2 "%s" set x2tics scale 0 set x2tics border rotate by 60 set x2tics font "Helvetica,8" set grid x2tics ]]) plot:cmd("set x2tics " .. x2tics) end --- Normalizes the given values for using the in a pie chart. -- @param values A table of values -- @returns A table with normalized values that sum up to one function normalize_pie(values) local sum = 0 for i,v in ipairs(values) do sum = sum + v end if sum == 0 then return values end nvalues = {} for i,v in ipairs(values) do table.insert(nvalues, v / sum) end return nvalues end pepper-0.3.3/reports/punchcard.lua000066400000000000000000000045431263211400600171610ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: punchcard.lua Commit activity, as display by Github (https://github.com/blog/159-one-more-thing) or "git timecard" (http://dustin.github.com/2009/01/11/timecard.html) --]] require "pepper.datetime" require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.title = "Punchcard" r.description = "Commit activity by day and hour" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"-aARG, --author=ARG", "Show commits of author ARG"} } pepper.datetime.add_daterange_options(r) pepper.plotutils.add_plot_options(r) return r end -- Revision callback function function callback(r) if r:date() == 0 then return end if author and r:author() ~= author then return end local date = os.date("%w %H", r:date()) if commits[date] == nil then commits[date] = 1 else commits[date] = commits[date] + 1 end if commits[date] > max then max = commits[date] end end -- Main report function function run(self) commits = {} -- Number of commits: (hour, wday) -> num author = self:getopt("a,author") max = 0 -- Gather data local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) repo:iterator(branch, {start=datemin, stop=datemax}):map(callback) max = max * 2 -- Extract data local hours = {} local days_nums = {} for k,v in pairs(commits) do local a,b,day,hour = string.find(k, "(%d+) (%d+)") table.insert(hours, hour) table.insert(days_nums, {{day, v / max}}) -- Two values for a single series end -- Generate graph local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p, 800, 300) if author then p:set_title("Commit Activity by Day and Hour (by " .. author .. " on " .. branch .. ")") else p:set_title("Commit Activity by Day and Hour (on " .. branch .. ")") end p:cmd([[ set xrange [-1:24] set xtics 0,1, 23 set yrange [-1:7] set ytics ("Sun" 0, "Mon" 1, "Tue" 2, "Wed" 3, "Thu" 4, "Fri" 5, "Sat" 6) set nokey ]]) p:plot_series(hours, days_nums, {}, {command = "with circles lc rgb \"black\" fs solid noborder"}); end pepper-0.3.3/reports/revdump.lua000066400000000000000000000025221263211400600166670ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: revdump.lua Dumps a revision NOTE: This report is mainly used for testing purposes --]] -- Describes the report function describe(self) local r = {} r.title = "Revision dump" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"-rARG, --revision=ARG", "Select revision"} } return r end -- Revision dump function function revdump(r) print("-- Revision") print(r:id()) print("-- Date") print(r:date()) print("-- Author") print(r:author()) print("-- Message ") print(r:message()) print("-- Diffstat") local d = r:diffstat() for i,f in ipairs(d:files()) do print(f .. " +" .. d:lines_added(f) .. " -" .. d:lines_removed(f) .. " +" .. d:bytes_added(f) .. " -" .. d:lines_removed(f)) end print("==================================================================") end -- Main script function function run(self) local repo = self:repository() local rev = self:getopt("r,revision") if rev ~= nil then revdump(repo:revision(rev)) else local branch = self:getopt("b,branch", repo:default_branch()) repo:iterator(branch):map(revdump) end end pepper-0.3.3/reports/shortlog.lua000066400000000000000000000044431263211400600170520ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: shortlog.lua Generates a summarized revision log (like "git shortlog") --]] require "pepper.datetime" -- Describes the report function describe(self) local r = {} r.title = "Shortlog" r.description = "Summarized revision log" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"-s, --summary", "Print commit count summary only"}, {"-n, --numbered", "Sort output according to number of commits"}, } pepper.datetime.add_daterange_options(r) return r end -- Revision callback function function callback(r) if r:author() ~= "" then if messages[r:author()] == nil then messages[r:author()] = {} end table.insert(messages[r:author()], r:message()) end end -- Main report function function run(self) -- Commit message dictionary, indexed by author messages = {} -- Gather data local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) repo:iterator(branch, {start=datemin, stop=datemax}):map(callback) -- Sort commit dictionary local authors = {} for author,revisions in pairs(messages) do table.insert(authors, author) end if self:getopt("n,numbered") then table.sort(authors, function (a,b) return #messages[a] > #messages[b] end) else table.sort(authors) end -- Print results if self:getopt("s,summary") == nil then for i,author in ipairs(authors) do print(author .. " (" .. #messages[author] .. "):") for j,msg in ipairs(messages[author]) do split = string.find(msg, "\n\n") if split ~= nil then msg = string.sub(msg, 1, split) else split = string.find(msg, "\n") if split ~= nil then msg = string.sub(msg, 1, split) end end msg = string.gsub(msg, "\n", " ") msg = string.gsub(msg, "^%s*(.-)%s*$", "%1") print(" " .. msg) end print() end else for i,author in ipairs(authors) do local str = tostring(#messages[author]) while #str < 6 do str = " " .. str end print(str .. "\t" .. author) end end end pepper-0.3.3/reports/tags.lua000066400000000000000000000016461263211400600161510ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: tags.lua Shows all tags and their corresponding revisions. NOTE: This report is mainly used for testing purposes --]] -- Describes the report function describe(self) local r = {} r.name = "Tags" r.description = "Lists all tags" return r end -- Main script function function main() local repo = pepper.current_report():repository() local tags = repo:tags() local maxlen = 0 for i,v in ipairs(tags) do if #v:name() > maxlen then maxlen = #v:name() end end maxlen = maxlen + 2 for i,v in ipairs(tags) do local line = v:name() while #line < maxlen do line = line .. " " end line = line .. v:id() print(line) end end pepper-0.3.3/reports/times.lua000066400000000000000000000034101263211400600163230ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: times.lua Visualizes commit times using a scatter plot. --]] require "pepper.datetime" require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.title = "Times" r.description = "Scatter plot of commit times" r.options = {{"-bARG, --branch=ARG", "Select branch"}} pepper.datetime.add_daterange_options(r) pepper.plotutils.add_plot_options(r) return r end -- Revision callback function function callback(r) if r:date() == 0 then return end local date = os.date("*t", r:date()) table.insert(dates, r:date()) table.insert(daytimes, date["hour"] + date["min"] / 60) -- Track date range if r:date() < firstdate then firstdate = r:date() end if r:date() > lastdate then lastdate = r:date() end end -- Main report function function run(self) dates = {} -- Commit timestamps daytimes = {} -- Time in hours and hour fractions -- Date range firstdate = os.time() lastdate = 0 -- Gather data local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) local datemin, datemax = pepper.datetime.date_range(self) repo:iterator(branch, {start=datemin, stop=datemax}):map(callback) -- Generate graph local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p, 640, 240) pepper.plotutils.setup_std_time(p) p:set_title("Commit Times (on " .. branch .. ")") p:cmd([[ set yrange [0:24] set ytics 6 set grid set pointsize 1 ]]) p:set_xrange_time(firstdate, lastdate) p:plot_series(dates, daytimes, {}, "points") end pepper-0.3.3/reports/volume.lua000066400000000000000000000133511263211400600165160ustar00rootroot00000000000000--[[ pepper - SCM statistics report generator Copyright (C) 2010-present Jonas Gehring Released under the GNU General Public License, version 3. Please see the COPYING file in the source distribution for license terms and conditions, or see http://www.gnu.org/licenses/. file: commit_volume.lua Stacked bar plots for commit volumes per author. Idea from Ohloh, e.g. http://www.ohloh.net/p/ohcount/analyses/latest --]] require "pepper.plotutils" -- Describes the report function describe(self) local r = {} r.title = "Commit volume" r.description = "Stacked bar plots for commit volumes" r.options = { {"-bARG, --branch=ARG", "Select branch"}, {"--slots=ARG", "Comma-separated list of time slots ('all','Ndays, 'Nweeks, 'Nmonths' or 'Nyears')"}, {"-c, --changes, -l", "Count line changes instead of commit counts"}, {"-nARG", "Show the ARG busiest authors"}, } pepper.plotutils.add_plot_options(r) return r end -- Subtracts a time period in text form from a timestamp function subdate(t, period) n = tonumber(period:match("^[%d]+")) - 1 assert(n >= 0, string.format("Invalid time range: %q", period)) m = period:match("[%a]+$") -- Round towards zero and subtract time date = os.date("*t", t) date.hour = 0 date.min = 0 date.sec = 0 if m == "d" or m == "days" then date.day = date.day - n elseif m == "w" or m == "weeks" then date.day = date.day - date.wday + 1 date.day = date.day - 7*n elseif m == "m" or m == "months" then date.day = 1 date.month = date.month - n elseif m == "y" or m == "years" then date.day = 1 date.month = 1 date.year = date.year - n else error(string.format("Unkown time unit %q", m)) end return os.time(date) end -- Parses timeslot information -- Timeslot format: { caption, min_time } function timeslots(str) local slots = {} local now = os.time() for _,v in ipairs(pepper.utils.split(str, ",")) do if not v:match("^[%d]+") then v = "1" .. v end local n = tonumber(v:match("^[%d]+")) - 1 assert(n >= 0, string.format("Invalid time range: %q", v)) local m = v:match("[%a]+$") local date = os.date("*t", t) date.hour = 0 date.min = 0 date.sec = 0 if m == "a" or m == "all" then table.insert(slots, {"All Time", -1}) elseif m == "d" or m == "days" then date.day = date.day - n if n == 0 then table.insert(slots, {"Today", os.time(date)}) else table.insert(slots, {"Past " .. n+1 .. " days", os.time(date)}) end elseif m == "w" or m == "weeks" then date.day = date.day - date.wday + 1 date.day = date.day - 7*n if n == 0 then table.insert(slots, {"This week", os.time(date)}) else table.insert(slots, {"Past " .. n+1 .. " weeks", os.time(date)}) end elseif m == "m" then date.day = 1 date.month = date.month - n if n == 0 then table.insert(slots, {"This month", os.time(date)}) else table.insert(slots, {"Past " .. n+1 .. " months", os.time(date)}) end elseif m == "y" or m == "years" then date.day = 1 date.month = 1 date.year = date.year - n if n == 0 then table.insert(slots, {"This year", os.time(date)}) else table.insert(slots, {"Past " .. n+1 .. " years", os.time(date)}) end end end return slots end -- Main report function function run(self) -- Determine timeslots local slots = timeslots(self:getopt("slots", "all,12m,m")) local data = {} local total = {} local authors = {} local author_names = {} for i,v in ipairs(slots) do data[v] = {} total[v] = 0 end local count_changes = self:getopt("c,changes,l") -- Determine minimum date local datemin = -1 for _,v in ipairs(slots) do if v[2] < datemin then datemin = v[2] end end -- Gather data local repo = self:repository() local branch = self:getopt("b,branch", repo:default_branch()) repo:iterator(branch, {start=datemin}):map( function (r) local n = 1 if count_changes ~= nil then n = r:diffstat():lines_added() + r:diffstat():lines_removed() end for i,v in ipairs(slots) do if r:date() >= v[2] then if data[v][r:author()] == nil then data[v][r:author()] = n else data[v][r:author()] = data[v][r:author()] + n end total[v] = total[v] + n if authors[r:author()] == nil then authors[r:author()] = n else authors[r:author()] = authors[r:author()] + n end end end end ) -- Sort authors by contribution local author_names = {} for k,v in pairs(authors) do table.insert(author_names, k) end table.sort(author_names, function (a,b) return authors[a] > authors[b] end) local num_authors = 1 + tonumber(self:getopt("n", 6)) if num_authors <= #author_names then author_names[num_authors] = nil end local keys = {} local values = {} local sums = {} local fill_others = false for i,v in ipairs(slots) do table.insert(keys, v[1]) local vals = {} local sum = 0.0 for j,author in ipairs(author_names) do if data[v][author] then table.insert(vals, 100.0 * data[v][author] / total[v]) else table.insert(vals, 0.0) end sum = sum + vals[#vals] end table.insert(values, vals) table.insert(sums, sum) if sum < 100 then fill_others = true end end if fill_others then table.insert(author_names, num_authors, "Others") for i,v in ipairs(values) do table.insert(v, 100.0 - sums[i]) end end local p = pepper.gnuplot:new() pepper.plotutils.setup_output(p, 800, 480) p:cmd([[ set style histogram rowstacked set style fill solid border -1 set style fill solid border -1 set style data histogram set style histogram rowstacked set boxwidth 0.75 set key box set key right outside set yrange [0:100] ]]) if count_changes then p:cmd("set ylabel \"% of Contribution\"") p:set_title("Contribution Volume by Author (on " .. branch .. ")") else p:cmd("set ylabel \"% of Commits\"") p:set_title("Commit Volume by Author (on " .. branch .. ")") end p:plot_histogram(keys, values, author_names) end pepper-0.3.3/src/000077500000000000000000000000001263211400600135725ustar00rootroot00000000000000pepper-0.3.3/src/.gitignore000066400000000000000000000000071263211400600155570ustar00rootroot00000000000000pepper pepper-0.3.3/src/3rdparty/000077500000000000000000000000001263211400600153425ustar00rootroot00000000000000pepper-0.3.3/src/3rdparty/lunar/000077500000000000000000000000001263211400600164635ustar00rootroot00000000000000pepper-0.3.3/src/3rdparty/lunar/lunar.h000066400000000000000000000211511263211400600177550ustar00rootroot00000000000000/* * Lunar - improved version of Luna for find binding C++-objects to Lua * From http://lua-users.org/wiki/CppBindingWithLunar * * Changes by Jonas Gehring : * * An optional table argument has been added to Lunar::Register() * * Store instances in std::shared_ptr. This is handy for sharing object * ownership between C++ and Lua. */ #ifndef LUNAR_H_ #define LUNAR_H_ #include extern "C" { #include #include #include } template class Lunar { typedef struct { std::shared_ptr pT; } userdataType; public: typedef int (T::*mfp)(lua_State *L); typedef struct { const char *name; mfp mfunc; } RegType; static void Register(lua_State *L, const char *table = NULL) { lua_newtable(L); int methods = lua_gettop(L); luaL_newmetatable(L, T::className); int metatable = lua_gettop(L); // store method table in globals so that // scripts can add functions written in Lua. if (table == NULL) { lua_pushvalue(L, methods); set(L, LUA_GLOBALSINDEX, T::className); } else { lua_pushstring(L, table); lua_gettable(L, LUA_GLOBALSINDEX); if (lua_isnil(L, -1)) { // Don't create the table, but fail silently lua_pop(L, 1); lua_pushvalue(L, methods); set(L, LUA_GLOBALSINDEX, T::className); } else { lua_pushstring(L, T::className); lua_pushvalue(L, methods); lua_settable(L, -3); } } // hide metatable from Lua getmetatable() lua_pushvalue(L, methods); set(L, metatable, "__metatable"); lua_pushvalue(L, methods); set(L, metatable, "__index"); lua_pushcfunction(L, tostring_T); set(L, metatable, "__tostring"); lua_pushcfunction(L, gc_T); set(L, metatable, "__gc"); lua_newtable(L); // mt for method table lua_pushcfunction(L, new_T); lua_pushvalue(L, -1); // dup new_T function set(L, methods, "new"); // add new_T to method table set(L, -3, "__call"); // mt.__call = new_T lua_setmetatable(L, methods); // fill method table with methods from class T for (RegType *l = T::methods; l->name; l++) { lua_pushstring(L, l->name); lua_pushlightuserdata(L, (void*)l); lua_pushcclosure(L, thunk, 1); lua_settable(L, methods); } lua_pop(L, 2); // drop metatable and method table } // call named lua method from userdata method table static int call(lua_State *L, const char *method, int nargs=0, int nresults=LUA_MULTRET, int errfunc=0) { int base = lua_gettop(L) - nargs; // userdata index if (!luaL_checkudata(L, base, T::className)) { lua_settop(L, base-1); // drop userdata and args lua_pushfstring(L, "not a valid %s userdata", T::className); return -1; } lua_pushstring(L, method); // method name lua_gettable(L, base); // get method from userdata if (lua_isnil(L, -1)) { // no method? lua_settop(L, base-1); // drop userdata and args lua_pushfstring(L, "%s missing method '%s'", T::className, method); return -1; } lua_insert(L, base); // put method under userdata, args int status = lua_pcall(L, 1+nargs, nresults, errfunc); // call method if (status) { const char *msg = lua_tostring(L, -1); if (msg == NULL) msg = "(error with no message)"; lua_pushfstring(L, "%s:%s status = %d\n%s", T::className, method, status, msg); lua_remove(L, base); // remove old message return -1; } return lua_gettop(L) - base + 1; // number of results } // push onto the Lua stack a userdata containing a pointer to T object. // This will wrap the pointer in a shared_ptr instance. If gc is false, the // shared_ptr will not own the object pointer (this is realized by not // calling the shared pointers' destructor in gc_T()). static int push(lua_State *L, T *obj, bool gc=false) { if (!obj) { lua_pushnil(L); return 0; } userdataType *ud = NULL; int mt = preparePush(L, obj, &ud); if (ud) { new (&ud->pT) std::shared_ptr(obj); mt = doPush(L, mt, gc); } return mt; } // push onto the Lua stack a userdata containing a pointer to T object. // This will create another referencing shared_ptr that will be destroyed // once the userdata is garbage collected. static int push(lua_State *L, std::shared_ptr ptr) { if (!ptr) { lua_pushnil(L); return 0; } userdataType *ud = NULL; int mt = preparePush(L, ptr.get(), &ud); if (ud) { new (&ud->pT) std::shared_ptr(ptr); mt = doPush(L, mt, true); } return mt; } // get userdata from Lua stack and return pointer to T object static T *check(lua_State *L, int narg) { userdataType *ud = static_cast(luaL_checkudata(L, narg, T::className)); if(!ud) { luaL_typerror(L, narg, T::className); return NULL; } return ud->pT.get(); // pointer to T object } private: Lunar(); // hide default constructor // member function dispatcher static int thunk(lua_State *L) { // stack has userdata, followed by method args T *obj = check(L, 1); // get 'self', or if you prefer, 'this' // In order to prevent garbage collection of this object while the function // is running, store a reference in the global index. char buf[34]; sprintf(buf, "__%p", (void*)obj); lua_pushvalue(L, 1); lua_setglobal(L, buf); lua_remove(L, 1); // remove self so member function args start at index 1 // get member function from upvalue RegType *l = static_cast(lua_touserdata(L, lua_upvalueindex(1))); int ret = (obj->*(l->mfunc))(L); // call member function // Remove temporary reference lua_pushnil(L); lua_setglobal(L, buf); return ret; } // create a new T object and // push onto the Lua stack a userdata containing a pointer to T object static int new_T(lua_State *L) { lua_remove(L, 1); // use classname:new(), instead of classname.new() T *obj = new T(L); // call constructor for T objects push(L, obj, true); // gc_T will delete this object return 1; // userdata containing pointer to T object } // garbage collection metamethod static int gc_T(lua_State *L) { if (luaL_getmetafield(L, 1, "do not trash")) { lua_pushvalue(L, 1); // dup userdata lua_gettable(L, -2); if (!lua_isnil(L, -1)) return 0; // do not delete object } userdataType *ud = static_cast(lua_touserdata(L, 1)); ud->pT.reset(); // remove shared_ptr reference return 0; } static int tostring_T (lua_State *L) { char buff[32]; userdataType *ud = static_cast(lua_touserdata(L, 1)); T *obj = ud->pT.get(); sprintf(buff, "%p", (void*)obj); lua_pushfstring(L, "%s (%s)", T::className, buff); return 1; } static int preparePush(lua_State *L, T *obj, userdataType **ud) { luaL_getmetatable(L, T::className); // lookup metatable in Lua registry if (lua_isnil(L, -1)) luaL_error(L, "%s missing metatable", T::className); int mt = lua_gettop(L); subtable(L, mt, "userdata", "v"); *ud = static_cast(pushuserdata(L, obj, sizeof(userdataType))); return mt; } static int doPush(lua_State *L, int mt, bool gc=false) { lua_pushvalue(L, mt); lua_setmetatable(L, -2); if (gc == false) { lua_checkstack(L, 3); subtable(L, mt, "do not trash", "k"); lua_pushvalue(L, -2); lua_pushboolean(L, 1); lua_settable(L, -3); lua_pop(L, 1); } lua_replace(L, mt); lua_settop(L, mt); return mt; // index of userdata containing pointer to T object } static void set(lua_State *L, int table_index, const char *key) { lua_pushstring(L, key); lua_insert(L, -2); // swap value and key lua_settable(L, table_index); } static void weaktable(lua_State *L, const char *mode) { lua_newtable(L); lua_pushvalue(L, -1); // table is its own metatable lua_setmetatable(L, -2); lua_pushliteral(L, "__mode"); lua_pushstring(L, mode); lua_settable(L, -3); // metatable.__mode = mode } static void subtable(lua_State *L, int tindex, const char *name, const char *mode) { lua_pushstring(L, name); lua_gettable(L, tindex); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_checkstack(L, 3); weaktable(L, mode); lua_pushstring(L, name); lua_pushvalue(L, -2); lua_settable(L, tindex); } } static void *pushuserdata(lua_State *L, void *key, size_t sz) { void *ud = 0; lua_pushlightuserdata(L, key); lua_gettable(L, -2); // lookup[key] if (lua_isnil(L, -1)) { lua_pop(L, 1); // drop nil lua_checkstack(L, 3); ud = lua_newuserdata(L, sz); // create new userdata lua_pushlightuserdata(L, key); lua_pushvalue(L, -2); // dup userdata lua_settable(L, -4); // lookup[key] = userdata } return ud; } }; #define LUNAR_DECLARE_METHOD(Class, Name) {#Name, &Class::Name} #endif // LUNAR_H_ pepper-0.3.3/src/Makefile.am000066400000000000000000000050021263211400600156230ustar00rootroot00000000000000# # pepper - SCM statistics report generator # Copyright (C) 2010-present Jonas Gehring # # Released under the GNU General Public License, version 3. # Please see the COPYING file in the source distribution for license # terms and conditions, or see http://www.gnu.org/licenses/. # # Pack all code into a static library, so the unit test programs # can use it easily. noinst_LIBRARIES = libpepper.a bin_PROGRAMS = pepper libpepper_a_SOURCES = \ abstractcache.h abstractcache.cpp \ backend.h backend.cpp \ bstream.h bstream.cpp \ cache.h cache.cpp \ diffstat.h diffstat.cpp \ jobqueue.h \ logger.h logger.cpp \ luahelpers.h \ luamodules.h luamodules.cpp \ main.h \ options.h options.cpp \ pex.h pex.cpp \ report.h report.cpp \ repository.h repository.cpp \ revision.h revision.cpp \ revisioniterator.h revisioniterator.cpp \ strlib.h strlib.cpp \ tag.h tag.cpp \ utils.h utils.cpp \ \ syslib/fs.h syslib/fs.cpp \ syslib/io.h syslib/io.cpp \ syslib/parallel.h syslib/parallel.cpp \ syslib/sigblock.h syslib/sigblock.cpp \ syslib/datetime.h syslib/datetime.cpp \ \ 3rdparty/lunar/lunar.h # This is the main program pepper_SOURCES = \ main.cpp pepper_LDADD = \ libpepper.a \ $(PTHREAD_LIBS) \ $(LUA_LIB) \ $(FRAMEWORKS) AM_CXXFLAGS = \ -Wall -W -pipe \ $(PTHREAD_CFLAGS) AM_CPPFLAGS = \ -I@top_srcdir@/src/3rdparty AM_CPPFLAGS += \ $(LUA_INCLUDE) \ -DDATADIR=\"$(pkgdatadir)\" AM_LDFLAGS = if RDYNAMIC AM_CXXFLAGS += \ -rdynamic AM_LDFLAGS += \ -rdynamic endif # Optional features if GIT_BACKEND libpepper_a_SOURCES += \ backends/git.h backends/git.cpp AM_CPPFLAGS += \ -DUSE_GIT endif if MERCURIAL_BACKEND libpepper_a_SOURCES += \ backends/mercurial.h backends/mercurial.cpp AM_CPPFLAGS += \ -DUSE_MERCURIAL AM_CXXFLAGS += \ $(PYTHON_CPPFLAGS) pepper_LDADD += \ $(PYTHON_LDFLAGS) endif if SVN_BACKEND libpepper_a_SOURCES += \ backends/subversion.h backends/subversion.cpp \ backends/subversion_p.h \ backends/subversion_delta.cpp AM_CPPFLAGS += \ -DUSE_SUBVERSION $(APR_CPPFLAGS) $(APR_INCLUDES) AM_CXXFLAGS += \ -Wno-deprecated-declarations \ $(APR_CFLAGS) \ $(SVN_CFLAGS) AM_LDFLAGS += \ $(SVN_LDFLAGS) pepper_LDADD += \ $(SVN_LIBS) \ $(APR_LIBS) endif if GNUPLOT libpepper_a_SOURCES += \ plot.h plot.cpp \ gnuplot.h gnuplot.cpp AM_CPPFLAGS += \ -DUSE_GNUPLOT endif if LEVELDB libpepper_a_SOURCES += \ ldbcache.h ldbcache.cpp AM_CXXFLAGS += \ $(LEVELDB_CPPFLAGS) LIBS += \ $(LEVELDB_LIBS) AM_CPPFLAGS += \ -DUSE_LDBCACHE endif # Last but not least, the CFLAGS AM_CFLAGS = $(AM_CXXFLAGS) pepper-0.3.3/src/abstractcache.cpp000066400000000000000000000054061263211400600170720ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: abstractcache.cpp * Abstract base class for revision caches */ #include "main.h" #include "bstream.h" #include "diffstat.h" #include "logger.h" #include "options.h" #include "revision.h" #include "strlib.h" #include "utils.h" #include "syslib/fs.h" #include "abstractcache.h" // Constructor AbstractCache::AbstractCache(Backend *backend, const Options &options) : Backend(options), m_backend(backend) { } // Destructor AbstractCache::~AbstractCache() { } // Returns a diffstat for the specified revision DiffstatPtr AbstractCache::diffstat(const std::string &id) { if (!lookup(id)) { PTRACE << "Cache miss: " << id << endl; return m_backend->diffstat(id); } PTRACE << "Cache hit: " << id << endl; Revision *r = get(id); DiffstatPtr stat = r->diffstat(); delete r; return stat; } // Tells the wrapped backend to pre-fetch revisions that are not cached yet void AbstractCache::prefetch(const std::vector &ids) { std::vector missing; for (unsigned int i = 0; i < ids.size(); i++) { if (!lookup(ids[i])) { missing.push_back(ids[i]); } } PDEBUG << "Cache: " << (ids.size() - missing.size()) << " of " << ids.size() << " revisions already cached, prefetching " << missing.size() << endl; if (!missing.empty()) { m_backend->prefetch(missing); } } // Returns the revision data for the given ID Revision *AbstractCache::revision(const std::string &id) { if (!lookup(id)) { PTRACE << "Cache miss: " << id << endl; Revision *r = m_backend->revision(id); put(id, *r); return r; } PTRACE << "Cache hit: " << id << endl; return get(id); } // Returns the full path for a cache file for the given backend std::string AbstractCache::cacheFile(Backend *backend, const std::string &name) { std::string dir = backend->options().cacheDir() + "/" + backend->uuid(); checkDir(dir); return dir + "/" + sys::fs::escape(name); } // Returns the current cache directory std::string AbstractCache::cacheDir() { return m_opts.cacheDir() + "/" + uuid(); } // Ensures that the cache dir is writable and exists void AbstractCache::checkDir(const std::string &path, bool *created) { if (!sys::fs::dirExists(path)) { // Create the cache directory try { sys::fs::mkpath(path); } catch (const std::exception &ex) { throw PEX(str::printf("Unable to create cache directory: %s", ex.what())); } PDEBUG << "Cache: Creating cache directory '" << path << '\'' << endl; if (created) { *created = true; } } else if (created) { *created = false; } } pepper-0.3.3/src/abstractcache.h000066400000000000000000000045441263211400600165410ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: abstractcache.h * Abstract base class for revision caches (interface) */ #ifndef ABSTRACTCACHE_H_ #define ABSTRACTCACHE_H_ #include "backend.h" class Revision; // This cache should be transparent and inherits the wrapped class class AbstractCache : public Backend { public: AbstractCache(Backend *backend, const Options &options); virtual ~AbstractCache(); void init() { } void open() { m_backend->open(); } void close() { flush(); m_backend->close(); } std::string name() const { return m_backend->name(); } std::string uuid() { if (m_uuid.empty()) m_uuid = m_backend->uuid(); return m_uuid; } std::string head(const std::string &branch = std::string()) { return m_backend->head(branch); } std::string mainBranch() { return m_backend->mainBranch(); } std::vector branches() { return m_backend->branches(); } std::vector tags() { return m_backend->tags(); } DiffstatPtr diffstat(const std::string &id); void filterDiffstat(DiffstatPtr stat) { m_backend->filterDiffstat(stat); } std::vector tree(const std::string &id = std::string()) { return m_backend->tree(id); } std::string cat(const std::string &path, const std::string &id = std::string()) { return m_backend->cat(path, id); }; LogIterator *iterator(const std::string &branch = std::string(), int64_t start = -1, int64_t end = -1) { return m_backend->iterator(branch, start, end); } void prefetch(const std::vector &ids); Revision *revision(const std::string &id); void finalize() { m_backend->finalize(); } static std::string cacheFile(Backend *backend, const std::string &name); virtual void flush() = 0; virtual void check(bool force = false) = 0; protected: std::string cacheDir(); virtual bool lookup(const std::string &id) = 0; virtual void put(const std::string &id, const Revision &rev) = 0; virtual Revision *get(const std::string &id) = 0; static void checkDir(const std::string &path, bool *created = NULL); protected: Backend *m_backend; std::string m_uuid; // Cached backend UUID }; #endif // ABSTRACTCACHE_H_ pepper-0.3.3/src/backend.cpp000066400000000000000000000075641263211400600157010ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: backend.cpp * Abstract repository backend */ #include "main.h" #include "options.h" #include "backend.h" #ifdef USE_GIT #include "backends/git.h" #endif #ifdef USE_MERCURIAL #include "backends/mercurial.h" #endif #ifdef USE_SUBVERSION #include "backends/subversion.h" #endif // Constructor Backend::LogIterator::LogIterator(const std::vector &ids) : sys::parallel::Thread(), m_ids(ids), m_atEnd(false) { } // Destructor Backend::LogIterator::~LogIterator() { } // Adds the next revision IDs to the queue or returns false bool Backend::LogIterator::nextIds(std::queue *queue) { if (m_atEnd) { return false; } for (unsigned int i = 0; i < m_ids.size(); i++) { queue->push(m_ids[i]); } m_atEnd = true; m_ids.clear(); return true; } // Main thread loop void Backend::LogIterator::run() { // Do nothing here } // Protected constructor Backend::Backend(const Options &options) : m_opts(options) { } // Default virtual destructor Backend::~Backend() { } // Allocates a backend for the given options Backend *Backend::backendFor(const Options &options) { std::string forced = options.forcedBackend(); if (!forced.empty()) { return backendForName(forced, options); } return backendForUrl(options.repository(), options); } // Prints a listing of available backends to void Backend::listBackends(std::ostream &out) { out << "Available backends (with abbreviations):" << std::endl; #ifdef USE_SUBVERSION Options::print("subversion, svn", "Subversion", out); #endif #ifdef USE_GIT Options::print("git", "Git", out); #endif #ifdef USE_MERCURIAL Options::print("mercurial, hg", "Mercurial", out); #endif } // Initializes the backend void Backend::init() { // The default implementation does nothing } // Called prior to Report::run() void Backend::open() { // The default implementation does nothing } // Called after to Report::run() void Backend::close() { // The default implementation does nothing } // Gives the backend the possibility to pre-fetch the given revisions void Backend::prefetch(const std::vector &) { // The default implementation does nothing } // Optional diffstat filtering before it is presented to the report script void Backend::filterDiffstat(DiffstatPtr) { // The default implementation does nothing } // Cleans up the backend after iteration has finished void Backend::finalize() { // The default implementation does nothing } // Returns a const reference to the program options const Options &Backend::options() const { return m_opts; } // Prints a help screen void Backend::printHelp() const { // The default implementation does nothing } // Allocates a backend of a specific repository type Backend *Backend::backendForName(const std::string &name, const Options &options) { #ifdef USE_SUBVERSION if (name == "svn" || name == "subversion") { return new SubversionBackend(options); } #endif #ifdef USE_GIT if (name == "git") { return new GitBackend(options); } #endif #ifdef USE_MERCURIAL if (name == "hg" || name == "mercurial") { return new MercurialBackend(options); } #endif throw PEX(std::string("No such backend: " + name)); } // Tries to guess a backend by examining the repository URL Backend *Backend::backendForUrl(const std::string &url, const Options &options) { #ifdef USE_SUBVERSION if (SubversionBackend::handles(url)) { return new SubversionBackend(options); } #endif #ifdef USE_GIT if (GitBackend::handles(url)) { return new GitBackend(options); } #endif #ifdef USE_MERCURIAL if (MercurialBackend::handles(url)) { return new MercurialBackend(options); } #endif // Hmm, dunno return NULL; } pepper-0.3.3/src/backend.h000066400000000000000000000046661263211400600153460ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: backend.h * Abstract repository backend (interface) */ #ifndef BACKEND_H_ #define BACKEND_H_ #include #include #include #include #include "diffstat.h" #include "tag.h" #include "syslib/parallel.h" class Options; class Revision; class Backend { public: // Offers access to the repository history // The default implementation supports iterating through a pre-fetched vector class LogIterator : public sys::parallel::Thread { public: LogIterator(const std::vector &ids = std::vector()); virtual ~LogIterator(); virtual bool nextIds(std::queue *queue); protected: virtual void run(); PEPPER_PROTVARS: std::vector m_ids; bool m_atEnd; }; public: virtual ~Backend(); static Backend *backendFor(const Options &options); static void listBackends(std::ostream &out = std::cout); virtual void init(); virtual void open(); virtual void close(); virtual std::string name() const = 0; virtual std::string uuid() = 0; virtual std::string head(const std::string &branch = std::string()) = 0; virtual std::string mainBranch() = 0; virtual std::vector branches() = 0; virtual std::vector tags() = 0; virtual DiffstatPtr diffstat(const std::string &id) = 0; virtual void filterDiffstat(DiffstatPtr stat); virtual std::vector tree(const std::string &id = std::string()) = 0; virtual std::string cat(const std::string &path, const std::string &id = std::string()) = 0; virtual LogIterator *iterator(const std::string &branch = std::string(), int64_t start = -1, int64_t end = -1) = 0; virtual void prefetch(const std::vector &ids); virtual Revision *revision(const std::string &id) = 0; virtual void finalize(); const Options &options() const; virtual void printHelp() const; protected: Backend(const Options &options); protected: const Options &m_opts; private: static Backend *backendForName(const std::string &name, const Options &options); static Backend *backendForUrl(const std::string &url, const Options &options); }; #endif // BACKEND_H_ pepper-0.3.3/src/backends/000077500000000000000000000000001263211400600153445ustar00rootroot00000000000000pepper-0.3.3/src/backends/git.cpp000066400000000000000000000533721263211400600166450ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: git.cpp * Git repository backend */ #include "main.h" #include #include #include #include "jobqueue.h" #include "logger.h" #include "options.h" #include "revision.h" #include "strlib.h" #include "utils.h" #include "syslib/datetime.h" #include "syslib/fs.h" #include "syslib/io.h" #include "syslib/parallel.h" #include "backends/git.h" // Diffstat fetching worker thread, using a pipe to write data to "git diff-tree" class GitDiffstatPipe : public sys::parallel::Thread { public: GitDiffstatPipe(const std::string &gitpath, JobQueue *queue) : m_gitpath(gitpath), m_queue(queue) { } static DiffstatPtr diffstat(const std::string &gitpath, const std::string &id, const std::string &parent = std::string()) { DiffstatPtr stat; if (!parent.empty()) { sys::io::PopenStreambuf buf((gitpath+"/git-diff-tree").c_str(), "-U0", "--no-renames", parent.c_str(), id.c_str()); std::istream in(&buf); stat = DiffParser::parse(in); if (buf.close() != 0) { throw PEX("git diff-tree command failed"); } } else { sys::io::PopenStreambuf buf((gitpath+"/git-diff-tree").c_str(), "-U0", "--no-renames", "--root", id.c_str()); std::istream in(&buf); stat = DiffParser::parse(in); if (buf.close() != 0) { throw PEX("git diff-tree command failed"); } } return stat; } protected: void run() { // TODO: Error checking sys::io::PopenStreambuf buf((m_gitpath+"/git-diff-tree").c_str(), "-U0", "--no-renames", "--stdin", "--root", NULL, NULL, NULL, std::ios::in | std::ios::out); std::istream in(&buf); std::ostream out(&buf); std::string revision; while (m_queue->getArg(&revision)) { std::vector revs = str::split(revision, ":"); if (revs.size() < 2) { out << revs[0] << '\n'; } else { out << revs[1] << " " << revs[0] << '\n'; } // We use EOF characters to mark the end of a revision for // the diff parser. git diff-tree won't understand this line // and simply write the EOF. out << (char)EOF << '\n' << std::flush; DiffstatPtr stat = DiffParser::parse(in); m_queue->done(revision, stat); } } private: std::string m_gitpath; JobQueue *m_queue; }; // Meta-data fetching worker thread, passing multiple revisions to git-rev-list at once. // Note that all IDs coming from the JobQueue are expected to contain a single hash, // i.e. no parent:child ID spec. class GitMetaDataThread : public sys::parallel::Thread { public: struct Data { int64_t date; std::string author; std::string message; }; public: GitMetaDataThread(const std::string &gitpath, JobQueue *queue) : m_gitpath(gitpath), m_queue(queue) { } static void parseHeader(const std::vector &header, Data *dest) { // TODO: Proper exception descriptions if (header.size() < 5) { throw PEX(str::printf("Unable to parse meta-data")); } // Here's the raw header format. The 'parent' line is not present for // non-root commits. // $ID_HASH // tree $TREE_HASH // parent $PARENT_HASH // author $AUTHOR_NAME $AUTHOR_EMAIL $DATE $OFFSET // committer $AUTHOR_NAME $AUTHOR_EMAIL $DATE $OFFSET // // $MESSAGE_INDENTED_BY_4_SPACES // Find author line size_t line = 0; while (line < header.size() && header[line].compare(0, 7, "author ")) { ++line; } if (line >= header.size()) { PDEBUG << "Invalid header:" << endl; for (size_t i = 0; i < header.size(); i++) { PDEBUG << header[i] << endl; } throw PEX(str::printf("Unable to parse meta-data")); } // Author information dest->author = header[line].substr(7); // Strip email address and date, assuming a start at the last "<" (not really compliant with RFC2882) size_t pos = dest->author.find_last_of('<'); if (pos != std::string::npos) { dest->author = dest->author.substr(0, pos); } dest->author = str::trim(dest->author); // Commiter date if (header[++line].compare(0, 10, "committer ")) { throw PEX(str::printf("Unable to parse commit date from line: %s", header[line].c_str())); } if ((pos = header[line].find_last_of(' ')) == std::string::npos) { throw PEX(str::printf("Unable to parse commit date from line: %s", header[line].c_str())); } size_t pos2 = header[line].find_last_of(' ', pos - 1); if (pos2 == std::string::npos || !str::str2int(header[line].substr(pos2, pos - pos2), &(dest->date), 10)) { throw PEX(str::printf("Unable to parse commit date from line: %s", header[line].c_str())); } int64_t offset_hr = 0, offset_min = 0; if (!str::str2int(header[line].substr(pos+1, 3), &offset_hr, 10) || !str::str2int(header[line].substr(pos+4, 2), &offset_min, 10)) { throw PEX(str::printf("Unable to parse commit date from line: %s", header[line].c_str())); } dest->date += offset_hr * 60 * 60 + offset_min * 60; // Commit message dest->message.clear(); for (size_t i = line+1; i < header.size(); i++) { if (header[i].length() > 4) { dest->message += header[i].substr(4); } if (!header[i].empty() && header[i][0] != '\0') { dest->message += "\n"; } } } static void metaData(const std::string &gitpath, const std::string &id, Data *dest) { int ret; std::string header = sys::io::exec(&ret, (gitpath+"/git-rev-list").c_str(), "-1", "--header", id.c_str()); if (ret != 0) { throw PEX(str::printf("Unable to retrieve meta-data for revision '%s' (%d, %s)", id.c_str(), ret, header.c_str())); } parseHeader(str::split(header, "\n"), dest); } protected: void run() { Data data; const size_t maxids = 64; const char **args = new const char *[maxids + 4]; std::vector ids; std::string revlist = m_gitpath + "/git-rev-list"; args[0] = "--no-walk"; args[1] = "--header"; // No support for message bodies in old git versions // Try to fetch the revision headers for maxrevs revisions at once while (m_queue->getArgs(&ids, maxids)) { for (size_t i = 0; i < ids.size(); i++) { args[i+2] = ids[i].c_str(); } args[ids.size()+2] = NULL; sys::io::PopenStreambuf buf(revlist.c_str(), args); std::istream in(&buf); // Parse headers std::string str; std::vector header; while (in.good()) { std::getline(in, str); if (!str.empty() && str[0] == '\0') { try { parseHeader(header, &data); m_queue->done(header[0], data); } catch (const std::exception &ex) { PDEBUG << "Error parsing revision header: " << ex.what() << endl; m_queue->failed(header[0]); } header.clear(); header.push_back(str.substr(1)); } else { header.push_back(str); } } int ret = buf.close(); if (ret != 0) { PDEBUG << "Error running rev-list, ret = " << ret << endl; // Try all IDs one by one, so the incorrect ones can be identified for (size_t i = 0; i < ids.size(); i++) { try { metaData(m_gitpath, ids[i], &data); m_queue->done(ids[i], data); } catch (const std::exception &ex) { PDEBUG << "Error parsing revision header: " << ex.what() << endl; m_queue->failed(ids[i]); } } } } delete[] args; } private: std::string m_gitpath; JobQueue *m_queue; }; // Handles the prefetching of revision meta-data and diffstats class GitRevisionPrefetcher { public: GitRevisionPrefetcher(const std::string &git, int n = -1) : m_metaQueue(4096) { if (n < 0) { n = std::max(1, sys::parallel::idealThreadCount() / 2); } for (int i = 0; i < n; i++) { sys::parallel::Thread *thread = new GitDiffstatPipe(git, &m_diffQueue); thread->start(); m_threads.push_back(thread); } // Limit to 4 threads to prevent meta queue congestions n = std::min(n, 4); for (int i = 0; i < n; i++) { sys::parallel::Thread *thread = new GitMetaDataThread(git, &m_metaQueue); thread->start(); m_threads.push_back(thread); } Logger::info() << "GitBackend: Using " << m_threads.size() << " threads for prefetching diffstats (" << m_threads.size()-n << ") / meta-data (" << n << ")" << endl; } ~GitRevisionPrefetcher() { for (unsigned int i = 0; i < m_threads.size(); i++) { delete m_threads[i]; } } void stop() { m_diffQueue.stop(); m_metaQueue.stop(); } void wait() { for (unsigned int i = 0; i < m_threads.size(); i++) { m_threads[i]->wait(); } } void prefetch(const std::vector &revisions) { m_diffQueue.put(revisions); // Put child commits only to the meta queue std::vector children; children.resize(revisions.size()); for (size_t i = 0; i < revisions.size(); i++) { children[i] = utils::childId(revisions[i]); } m_metaQueue.put(children); } bool getDiffstat(const std::string &revision, DiffstatPtr *dest) { return m_diffQueue.getResult(revision, dest); } bool getMeta(const std::string &revision, GitMetaDataThread::Data *dest) { return m_metaQueue.getResult(utils::childId(revision), dest); } bool willFetchDiffstat(const std::string &revision) { return m_diffQueue.hasArg(revision); } bool willFetchMeta(const std::string &revision) { return m_metaQueue.hasArg(utils::childId(revision)); } private: JobQueue m_diffQueue; JobQueue m_metaQueue; std::vector m_threads; }; // Constructor GitBackend::GitBackend(const Options &options) : Backend(options), m_prefetcher(NULL) { } // Destructor GitBackend::~GitBackend() { close(); } // Initializes the backend void GitBackend::init() { std::string repo = m_opts.repository(); if (sys::fs::exists(repo + "/HEAD")) { setenv("GIT_DIR", repo.c_str(), 1); } else if (sys::fs::exists(repo + "/.git/HEAD")) { setenv("GIT_DIR", (repo + "/.git").c_str(), 1); } else if (sys::fs::fileExists(repo + "/.git")) { PDEBUG << "Parsing .git file" << endl; std::ifstream in((repo + "/.git").c_str(), std::ios::in); if (!in.good()) { throw PEX(str::printf("Unable to read from .git file: %s", repo.c_str())); } std::string str; std::getline(in, str); std::vector parts = str::split(str, ":"); if (parts.size() < 2) { throw PEX(str::printf("Unable to parse contents of .git file: %s", str.c_str())); } setenv("GIT_DIR", str::trim(parts[1]).c_str(), 1); } else { throw PEX(str::printf("Not a git repository: %s", repo.c_str())); } // Search for git executable std::string git = sys::fs::which("git");; PDEBUG << "git executable is " << git << endl; int ret; m_gitpath = str::trim(sys::io::exec(&ret, git.c_str(), "--exec-path")); if (ret != 0) { throw PEX("Unable to determine git exec-path"); } PDEBUG << "git exec-path is " << m_gitpath << endl; PDEBUG << "GIT_DIR has been set to " << getenv("GIT_DIR") << endl; } // Called after Report::run() void GitBackend::close() { // Clean up any prefetching threads finalize(); } // Returns true if this backend is able to access the given repository bool GitBackend::handles(const std::string &url) { if (sys::fs::dirExists(url+"/.git")) { return true; } else if (sys::fs::fileExists(url+"/.git")) { PDEBUG << "Detached repository detected" << endl; return true; } else if (sys::fs::dirExists(url) && sys::fs::fileExists(url+"/HEAD") && sys::fs::dirExists(url+"/objects")) { PDEBUG << "Bare repository detected" << endl; return true; } return false; } // Returns a unique identifier for this repository std::string GitBackend::uuid() { // Determine current main branch and the HEAD revision std::string branch = mainBranch(); std::string headrev = head(branch); std::string oldroot, oldhead; int ret; // The $GIT_DIR/pepper.cache file caches branch names and their root // commits. It consists of lines of the form // $BRANCH_NAME $HEAD $ROOT std::string cachefile = std::string(getenv("GIT_DIR")) + "/pepper.cache"; { std::ifstream in((std::string(getenv("GIT_DIR")) + "/pepper.cache").c_str()); while (in.good()) { std::string str; std::getline(in, str); if (str.compare(0, branch.length(), branch)) { continue; } std::vector parts = str::split(str, " "); if (parts.size() == 3) { oldhead = parts[1]; oldroot = parts[2]; if (oldhead == headrev) { PDEBUG << "Found cached root commit" << endl; return oldroot; } } break; } } // Check if the old root commit is still valid by checking if the old head revision // is an ancestory of the current one std::string root; if (!oldroot.empty()) { std::string ref = sys::io::exec(&ret, (m_gitpath+"/git-rev-list").c_str(), "-1", (oldhead + ".." + headrev).c_str()); if (ret == 0 && !ref.empty()) { PDEBUG << "Old head " << oldhead << " is a valid ancestor, updating cached head" << endl; root = oldroot; } } // Get ID of first commit of the selected branch // Unfortunatley, the --max-count=n option results in n revisions counting from the HEAD. // This way, we'll always get the HEAD revision with --max-count=1. if (root.empty()) { sys::datetime::Watch watch; std::string id = sys::io::exec(&ret, (m_gitpath+"/git-rev-list").c_str(), "--reverse", branch.c_str(), "--"); if (ret != 0) { throw PEX(str::printf("Unable to determine the root commit for branch '%s' (%d)", branch.c_str(), ret)); } size_t pos = id.find_first_of('\n'); if (pos == std::string::npos) { throw PEX(str::printf("Unable to determine the root commit for branch '%s' (%d)", branch.c_str(), ret)); } root = id.substr(0, pos); PDEBUG << "Determined root commit in " << watch.elapsedMSecs() << " ms" << endl; } // Update the cache file std::string newfile = cachefile + ".tmp"; FILE *out = fopen(newfile.c_str(), "w"); if (out == NULL) { throw PEX_ERRNO(); } fprintf(out, "%s %s %s\n", branch.c_str(), headrev.c_str(), root.c_str()); { std::ifstream in(cachefile.c_str()); while (in.good()) { std::string str; std::getline(in, str); if (str.empty() || !str.compare(0, branch.length(), branch)) { continue; } fprintf(out, "%s\n", str.c_str()); } fsync(fileno(out)); fclose(out); } sys::fs::rename(newfile, cachefile); return root; } // Returns the HEAD revision for the given branch std::string GitBackend::head(const std::string &branch) { int ret; std::string out = sys::io::exec(&ret, (m_gitpath+"/git-rev-list").c_str(), "-1", (branch.empty() ? "HEAD" : branch).c_str(), "--"); if (ret != 0) { throw PEX(str::printf("Unable to retrieve head commit for branch %s (%d)", branch.c_str(), ret)); } return str::trim(out); } // Returns the currently checked out branch std::string GitBackend::mainBranch() { int ret; std::string out = sys::io::exec(&ret, (m_gitpath+"/git-branch").c_str()); if (ret != 0) { throw PEX(str::printf("Unable to retrieve the list of branches (%d)", ret)); } std::vector branches = str::split(out, "\n"); for (unsigned int i = 0; i < branches.size(); i++) { if (branches[i].empty()) { continue; } if (branches[i][0] == '*') { return branches[i].substr(2); } branches[i] = branches[i].substr(2); } if (std::search_n(branches.begin(), branches.end(), 1, "master") != branches.end()) { return "master"; } else if (std::search_n(branches.begin(), branches.end(), 1, "remotes/origin/master") != branches.end()) { return "remotes/origin/master"; } // Fallback return "master"; } // Returns a list of available local branches std::vector GitBackend::branches() { int ret; std::string out = sys::io::exec(&ret, (m_gitpath+"/git-branch").c_str()); if (ret != 0) { throw PEX(str::printf("Unable to retrieve the list of branches (%d)", ret)); } std::vector branches = str::split(out, "\n"); for (unsigned int i = 0; i < branches.size(); i++) { if (branches[i].empty()) { branches.erase(branches.begin()+i); --i; continue; } branches[i] = branches[i].substr(2); } return branches; } // Returns a list of available tags std::vector GitBackend::tags() { int ret; // Fetch list of tag names std::string out = sys::io::exec(&ret, (m_gitpath+"/git-tag").c_str()); if (ret != 0) { throw PEX(str::printf("Unable to retrieve the list of tags (%d)", ret)); } std::vector names = str::split(out, "\n"); std::vector tags; // Determine corresponding commits for (unsigned int i = 0; i < names.size(); i++) { if (names[i].empty()) { continue; } std::string out = sys::io::exec(&ret, (m_gitpath+"/git-rev-list").c_str(), "-1", names[i].c_str()); if (ret != 0) { throw PEX(str::printf("Unable to retrieve the list of tags (%d)", ret)); } std::string id = str::trim(out); if (!id.empty()) { tags.push_back(Tag(id, names[i])); } } return tags; } // Returns a diffstat for the specified revision DiffstatPtr GitBackend::diffstat(const std::string &id) { // Maybe it's prefetched if (m_prefetcher && m_prefetcher->willFetchDiffstat(id)) { DiffstatPtr stat; if (!m_prefetcher->getDiffstat(id, &stat)) { throw PEX(str::printf("Failed to retrieve diffstat for revision %s", id.c_str())); } return stat; } PDEBUG << "Fetching revision " << id << " manually" << endl; std::vector revs = str::split(id, ":"); if (revs.size() > 1) { return GitDiffstatPipe::diffstat(m_gitpath, revs[1], revs[0]); } return GitDiffstatPipe::diffstat(m_gitpath, revs[0]); } // Returns a file listing for the given revision (defaults to HEAD) std::vector GitBackend::tree(const std::string &id) { int ret; std::string out = sys::io::exec(&ret, (m_gitpath+"/git-ls-tree").c_str(), "-r", "--full-name", "--name-only", (id.empty() ? "HEAD" : id.c_str())); if (ret != 0) { throw PEX(str::printf("Unable to retrieve tree listing for ID '%s' (%d)", id.c_str(), ret)); } std::vector contents = str::split(out, "\n"); while (!contents.empty() && contents[contents.size()-1].empty()) { contents.pop_back(); } return contents; } // Returns the file contents of the given path at the given revision (defaults to HEAD) std::string GitBackend::cat(const std::string &path, const std::string &id) { int ret; std::string out = sys::io::exec(&ret, (m_gitpath+"/git-show").c_str(), ((id.empty() ? std::string("HEAD") : id)+":"+path).c_str()); if (ret != 0) { throw PEX(str::printf("Unable to get file contents of %s@%s (%d)", path.c_str(), id.c_str(), ret)); } return out; } // Returns a revision iterator for the given branch Backend::LogIterator *GitBackend::iterator(const std::string &branch, int64_t start, int64_t end) { int ret; std::string out; if (start >= 0) { std::string maxage = str::printf("--max-age=%lld", start); if (end >= 0) { std::string minage = str::printf("--min-age=%lld", end); out = sys::io::exec(&ret, (m_gitpath+"/git-rev-list").c_str(), "--first-parent", "--reverse", maxage.c_str(), minage.c_str(), branch.c_str(), "--"); } else { out = sys::io::exec(&ret, (m_gitpath+"/git-rev-list").c_str(), "--first-parent", "--reverse", maxage.c_str(), branch.c_str(), "--"); } } else { if (end >= 0) { std::string minage = str::printf("--min-age=%lld", end); out = sys::io::exec(&ret, (m_gitpath+"/git-rev-list").c_str(), "--first-parent", "--reverse", minage.c_str(), branch.c_str(), "--"); } else { out = sys::io::exec(&ret, (m_gitpath+"/git-rev-list").c_str(), "--first-parent", "--reverse", branch.c_str(), "--"); } } if (ret != 0) { throw PEX(str::printf("Unable to retrieve log for branch '%s' (%d)", branch.c_str(), ret)); } std::vector revisions = str::split(out, "\n"); while (!revisions.empty() && revisions[revisions.size()-1].empty()) { revisions.pop_back(); } // Add parent revisions, so diffstat fetching will give correct results for (ssize_t i = revisions.size()-1; i > 0; i--) { revisions[i] = revisions[i-1] + ":" + revisions[i]; } return new LogIterator(revisions); } // Starts prefetching the given revision IDs void GitBackend::prefetch(const std::vector &ids) { if (m_prefetcher == NULL) { m_prefetcher = new GitRevisionPrefetcher(m_gitpath); } m_prefetcher->prefetch(ids); PDEBUG << "Started prefetching " << ids.size() << " revisions" << endl; } // Returns the revision data for the given ID Revision *GitBackend::revision(const std::string &id) { // Unfortunately, older git versions don't have the %B format specifier // for unwrapped subject and body, so the raw commit headers will be parsed instead. #if 0 int ret; std::string meta = sys::io::exec(&ret, m_git.c_str(), "log", "-1", "--pretty=format:%ct\n%aN\n%B", id.c_str()); if (ret != 0) { throw PEX(str::printf("Unable to retrieve meta-data for revision '%s' (%d, %s)", id.c_str(), ret, meta.c_str())); } std::vector lines = str::split(meta, "\n"); int64_t date = 0; std::string author; if (!lines.empty()) { str::str2int(lines[0], &date); lines.erase(lines.begin()); } if (!lines.empty()) { author = lines[0]; lines.erase(lines.begin()); } std::string msg = str::join(lines, "\n"); return new Revision(id, date, author, msg, diffstat(id)); #else // Check for pre-fetched meta data first if (m_prefetcher && m_prefetcher->willFetchMeta(id)) { GitMetaDataThread::Data data; if (!m_prefetcher->getMeta(id, &data)) { throw PEX(str::printf("Failed to retrieve meta-data for revision %s", id.c_str())); } return new Revision(id, data.date, data.author, data.message, diffstat(id)); } GitMetaDataThread::Data data; GitMetaDataThread::metaData(m_gitpath, utils::childId(id), &data); return new Revision(id, data.date, data.author, data.message, diffstat(id)); #endif } // Handle cleanup of diffstat scheduler void GitBackend::finalize() { if (m_prefetcher) { PDEBUG << "Waiting for prefetcher... " << endl; m_prefetcher->stop(); m_prefetcher->wait(); delete m_prefetcher; m_prefetcher = NULL; PDEBUG << "done" << endl; } } pepper-0.3.3/src/backends/git.h000066400000000000000000000025531263211400600163050ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: git.h * Git repository backend (interface) */ #ifndef GIT_BACKEND_H_ #define GIT_BACKEND_H_ #include "backend.h" class GitRevisionPrefetcher; class GitBackend : public Backend { public: GitBackend(const Options &options); ~GitBackend(); void init(); void close(); std::string name() const { return "git"; } static bool handles(const std::string &url); std::string uuid(); std::string head(const std::string &branch = std::string()); std::string mainBranch(); std::vector branches(); std::vector tags(); DiffstatPtr diffstat(const std::string &id); std::vector tree(const std::string &id = std::string()); std::string cat(const std::string &path, const std::string &id = std::string()); LogIterator *iterator(const std::string &branch = std::string(), int64_t start = -1, int64_t end = -1); void prefetch(const std::vector &ids); Revision *revision(const std::string &id); void finalize(); private: std::string m_gitpath; GitRevisionPrefetcher *m_prefetcher; }; #endif // GIT_BACKEND_H_ pepper-0.3.3/src/backends/mercurial.cpp000066400000000000000000000220671263211400600200420ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: mercurial.cpp * Mercurial repository backend */ #include "Python.h" #include "main.h" // Avoid compilation warnings #include #include #include "logger.h" #include "options.h" #include "revision.h" #include "strlib.h" #include "syslib/fs.h" #include "backends/mercurial.h" // Constructor MercurialBackend::MercurialBackend(const Options &options) : Backend(options) { Py_Initialize(); } // Destructor MercurialBackend::~MercurialBackend() { Py_Finalize(); } // Initializes the backend void MercurialBackend::init() { std::string repo = m_opts.repository(); if (!sys::fs::dirExists(repo + "/.hg")) { throw PEX(str::printf("Not a mercurial repository: %s", repo.c_str())); } int res = simpleString(str::printf("\ import sys \n\ from cStringIO import StringIO\n\ from mercurial import ui,hg,commands \n\ sys.stderr = stderr = StringIO()\n\ myui = ui.ui() \n\ myui.quiet = True \n\ repo = hg.repository(myui, '%s') \n\n", repo.c_str())); if (res != 0) { throw PEX(str::printf("Unable to load or setup Python modules (%d)", res)); } } // Returns true if this backend is able to access the given repository bool MercurialBackend::handles(const std::string &url) { return sys::fs::dirExists(url+"/.hg"); } // Returns a unique identifier for this repository std::string MercurialBackend::uuid() { // Use the ID of the first commit of the master branch as the UUID. std::string out = hgcmd("log", "date=None, user=None, quiet=None, rev=\"0\""); size_t pos = out.find(':'); if (pos != std::string::npos) { out = out.substr(pos+1); } return str::trim(out); } // Returns the HEAD revision for the current branch std::string MercurialBackend::head(const std::string &branch) { std::string out = hgcmd("log", str::printf("date=None, rev=None, user=None, quiet=None, limit=1, branch=[\"%s\"]", branch.c_str())); size_t pos = out.find(':'); if (pos != std::string::npos) { out = out.substr(pos+1); } return str::trim(out); } // Returns the currently checked out branch std::string MercurialBackend::mainBranch() { std::string out = hgcmd("branch"); return str::trim(out); } // Returns a list of available branches std::vector MercurialBackend::branches() { #if 1 std::string out = hgcmd("branches"); #else int ret; std::string out = sys::io::exec(&ret, "hg", "--noninteractive", "--repository", m_opts.repository().c_str(), "branches", "--quiet"); if (ret != 0) { throw PEX(str::printf("Unable to retreive the list of branches (%d)", ret)); } #endif std::vector branches = str::split(out, "\n"); while (!branches.empty() && branches[branches.size()-1].empty()) { branches.pop_back(); } return branches; } // Returns a list of available tags std::vector MercurialBackend::tags() { // TODO: Determine correct keyword for non-quiet output, if any simpleString("myui.quiet = False\n"); std::string out = hgcmd("tags"); simpleString("myui.quiet = True\n"); std::vector lines = str::split(out, "\n"); std::vector tags; for (unsigned int i = 0; i < lines.size(); i++) { size_t pos = lines[i].find(" "); if ((pos = lines[i].find(" ")) == std::string::npos) { continue; } std::string name = lines[i].substr(0, pos); if ((pos = lines[i].find(":", pos)) == std::string::npos) { continue; } std::string id = lines[i].substr(pos+1); tags.push_back(Tag(id, name)); } return tags; } // Returns a diffstat for the specified revision DiffstatPtr MercurialBackend::diffstat(const std::string &id) { std::vector ids = str::split(id, ":"); #if 1 std::string out; if (ids.size() > 1) { out = hgcmd("diff", str::printf("rev=[\"%s:%s\"]", ids[0].c_str(), ids[1].c_str())); } else { out = hgcmd("diff", str::printf("change=\"%s\"", ids[0].c_str())); } #else std::string out = sys::io::exec(hgcmd()+" diff --change "+id); #endif std::istringstream in(out); return DiffParser::parse(in); } // Returns a file listing for the given revision (defaults to HEAD) std::vector MercurialBackend::tree(const std::string &id) { std::string out; if (id.empty()) { out = hgcmd("status", str::printf("change=\"%s\", all=True, no_status=True", id.c_str())); } else { out = hgcmd("status", str::printf("all=True, no_status=True", id.c_str())); } std::vector contents = str::split(out, "\n"); if (!contents.empty() && contents[contents.size()-1].empty()) { contents.pop_back(); } return contents; } // Returns the file contents of the given path at the given revision (defaults to HEAD) std::string MercurialBackend::cat(const std::string &path, const std::string &id) { // stdout redirection to a cStringIO object doesn't work here, // because Mercurial's cat will close the file after writing, // which discards all contents of a cStringIO object. std::string filename; FILE *f = sys::fs::mkstemp(&filename); hgcmd("cat", str::printf("\"%s\", output=\"%s\", rev=\"%s\"", (m_opts.repository() + "/" + path).c_str(), filename.c_str(), id.c_str())); char buf[4096]; size_t size; std::string out; while ((size = fread(buf, 1, sizeof(buf), f)) != 0) { out += std::string(buf, size); } if (ferror(f)) { throw PEX("Error reading stream"); } fclose(f); sys::fs::unlink(filename); return out; } // Returns a revision iterator for the given branch Backend::LogIterator *MercurialBackend::iterator(const std::string &branch, int64_t start, int64_t end) { std::string date = "None"; if (start >= 0) { if (end >= 0) { date = str::printf("\"%lld 0 to %lld 0\"", start, end); } else { date = str::printf("\">%lld 0\"", start); } } else if (end >= 0) { date = str::printf("\"<%lld 0\"", end); } // Request log from HEAD to 0, so follow_first is effective std::string out = hgcmd("log", str::printf("date=%s, user=None, follow_first=True, quiet=None, rev=[\"%s:0\"]", date.c_str(), (head(branch)).c_str())); std::vector revisions = str::split(out, "\n"); if (!revisions.empty()) { revisions.pop_back(); } for (unsigned int i = 0; i < revisions.size(); i++) { size_t pos = revisions[i].find(':'); if (pos != std::string::npos) { revisions[i] = revisions[i].substr(pos+1); } } std::reverse(revisions.begin(), revisions.end()); // Add parent revisions, so diffstat fetching will give correct results for (int i = revisions.size()-1; i > 0; i--) { revisions[i] = revisions[i-1] + ":" + revisions[i]; } return new LogIterator(revisions); } // Returns the revision data for the given ID Revision *MercurialBackend::revision(const std::string &id) { std::vector ids = str::split(id, ":"); #if 1 std::string meta = hgcmd("log", str::printf("rev=[\"%s\"], date=None, user=None, template=\"{date|hgdate}\\n{author|person}\\n{desc}\"", ids.back().c_str())); #else std::string meta = sys::io::exec(hgcmd()+" log -r "+id+" --template=\"{date|hgdate}\n{author|person}\n{desc}\""); #endif std::vector lines = str::split(meta, "\n"); int64_t date = 0; std::string author; if (!lines.empty()) { // Date is given as seconds and timezone offset from UTC std::vector parts = str::split(lines[0], " "); if (parts.size() > 1) { int64_t offset = 0; str::str2int(parts[0], &date); str::str2int(parts[1], &offset); date += offset; } lines.erase(lines.begin()); } if (!lines.empty()) { author = lines[0]; lines.erase(lines.begin()); } std::string msg = str::join(lines, "\n"); return new Revision(id, date, author, msg, diffstat(id)); } // Returns the hg command with the correct --repository command line switch std::string MercurialBackend::hgcmd() const { return std::string("hg --noninteractive --repository ") + m_opts.repository(); } // Returns the Python code for a hg command std::string MercurialBackend::hgcmd(const std::string &cmd, const std::string &args) const { PyObject *pModule = PyImport_AddModule("__main__"); simpleString(str::printf("\ stdout = \"\"\n\ stderr.truncate(0)\n\ res = -127\n\ try:\n\ myui.pushbuffer()\n\ res = commands.%s(myui, repo, %s)\n\ stdout = myui.popbuffer()\n\ except:\n\ res = -127\n\ stderr.write(sys.exc_info()[0])\n\ ", cmd.c_str(), args.c_str())); // Check return value PyObject *object = PyObject_GetAttrString(pModule, "res"); Py_ssize_t res = (object == Py_None ? 0 : PyNumber_AsSsize_t(object, NULL)); if (res != 0) { // Throw exception object = PyObject_GetAttrString(pModule, "stderr"); char *getvalue = strdup("getvalue"); PyObject *output = PyObject_CallMethod(object, getvalue, NULL); free(getvalue); assert(output != NULL); throw PEX(str::trim(PyString_AsString(output))); } // Return stdout object = PyObject_GetAttrString(pModule, "stdout"); assert(object != NULL); return std::string(PyString_AsString(object)); } // Wrapper for PyRun_SimpleString() int MercurialBackend::simpleString(const std::string &str) const { PTRACE << str; return PyRun_SimpleString(str.c_str()); } pepper-0.3.3/src/backends/mercurial.h000066400000000000000000000026251263211400600175050ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: mercurial.h * Mercurial repository backend (interface) */ #ifndef MERCURIAL_BACKEND_H_ #define MERCURIAL_BACKEND_H_ #include "backend.h" class MercurialBackend : public Backend { public: MercurialBackend(const Options &options); ~MercurialBackend(); void init(); std::string name() const { return "mercurial"; } static bool handles(const std::string &url); std::string uuid(); std::string head(const std::string &branch = std::string()); std::string mainBranch(); std::vector branches(); std::vector tags(); DiffstatPtr diffstat(const std::string &id); std::vector tree(const std::string &id = std::string()); std::string cat(const std::string &path, const std::string &id = std::string()); LogIterator *iterator(const std::string &branch = std::string(), int64_t start = -1, int64_t end = -1); Revision *revision(const std::string &id); private: std::string hgcmd() const; std::string hgcmd(const std::string &cmd, const std::string &args = std::string()) const; int simpleString(const std::string &str) const; }; #endif // MERCURIAL_BACKEND_H_ pepper-0.3.3/src/backends/subversion.cpp000066400000000000000000001032101263211400600202440ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: subversion.cpp * Subversion repository backend */ #define __STDC_CONSTANT_MACROS // For INT64_C #include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include "bstream.h" #include "cache.h" #include "jobqueue.h" #include "logger.h" #include "options.h" #include "revision.h" #include "strlib.h" #include "syslib/fs.h" #include "syslib/parallel.h" #include "backends/subversion.h" #include "backends/subversion_p.h" namespace { struct HashKey { const char *data; apr_ssize_t len; HashKey(const char *data, apr_ssize_t len) : data(data), len(len) { } bool operator<(const HashKey &other) const { return memcmp(data, other.data, std::min(len, other.len)) < 0; } }; // Returns a vector of all keys in a APR hash std::vector getHashKeys(apr_hash_t *hash, apr_pool_t *pool) { std::vector keys; for (apr_hash_index_t *hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi)) { keys.push_back(HashKey((const char *)apr_hash_this_key(hi), apr_hash_this_key_len(hi))); } return keys; } } // anonymous namespace // Constructor SvnConnection::SvnConnection() : pool(NULL), ctx(NULL), ra(NULL), url(NULL), root(NULL), prefix(NULL) { } // Destructor SvnConnection::~SvnConnection() { if (pool) { svn_pool_destroy(pool); } } // Opens the connection to the Subversion repository void SvnConnection::open(const std::string &url, const std::map &options) { PTRACE << "Opening connection to " << url << endl; pool = svn_pool_create(NULL); init(); // Canoicalize the repository URL this->url = svn_path_uri_encode(svn_path_canonicalize(url.c_str(), pool), pool); svn_error_t *err; // Create the client context if ((err = svn_client_create_context(&ctx, pool))) { throw PEX(strerr(err)); } if ((err = svn_config_get_config(&(ctx->config), NULL, pool))) { throw PEX(strerr(err)); } // Setup the authentication data svn_auth_baton_t *auth_baton; svn_config_t *config = hashget(ctx->config, SVN_CONFIG_CATEGORY_CONFIG); const char *user = NULL, *pass = NULL; if (options.find("username") != options.end()) { user = apr_pstrdup(pool, options.find("username")->second.c_str()); } if (options.find("password") != options.end()) { pass = apr_pstrdup(pool, options.find("password")->second.c_str()); } if ((err = svn_cmdline_setup_auth_baton(&auth_baton, (options.find("non-interactive") != options.end()), user, pass, NULL, (options.find("no-auth-cache") != options.end()), config, NULL, NULL, pool))) { throw PEX(strerr(err)); } ctx->auth_baton = auth_baton; // Setup the RA session if ((err = svn_client_open_ra_session(&ra, this->url, ctx, pool))) { throw PEX(strerr(err)); } // Determine the repository root and reparent if ((err = svn_ra_get_repos_root2(ra, &root, pool))) { throw PEX(strerr(err)); } prefix = apr_pstrdup(pool, this->url + strlen(root)); if (prefix && *prefix == '/') { ++prefix; // Original adress is tracked in pool } PDEBUG << "Root is " << root << " -> prefix is " << prefix << endl; PTRACE << "Reparent to " << root << endl; if ((err = svn_ra_reparent(ra, root, pool))) { throw PEX(strerr(err)); } } // Opens the connection to the Subversion repository, using the client context // of the given parent connection void SvnConnection::open(SvnConnection *parent) { if (parent->ctx == NULL) { throw PEX("Parent connection not open yet."); } PTRACE << "Opening child connection to " << parent->root << endl; pool = svn_pool_create(NULL); init(); // Copy connection data from parent ctx = parent->ctx; url = apr_pstrdup(pool, parent->url); root = apr_pstrdup(pool, parent->root); prefix = apr_pstrdup(pool, parent->prefix); // Setup the RA session svn_error_t *err; if ((err = svn_client_open_ra_session(&ra, root, ctx, pool))) { throw PEX(strerr(err)); } } // Similar to svn_handle_error2(), but returns the error description as a std::string std::string SvnConnection::strerr(svn_error_t *err) { apr_pool_t *pool; svn_error_t *tmp_err; apr_array_header_t *empties; apr_pool_create(&pool, NULL); empties = apr_array_make(pool, 0, sizeof(apr_status_t)); std::string str; tmp_err = err; while (tmp_err) { int i; bool printed_already = false; if (!tmp_err->message) { for (i = 0; i < empties->nelts; i++) { if (tmp_err->apr_err == APR_ARRAY_IDX(empties, i, apr_status_t)) { printed_already = true; break; } } } if (!printed_already) { char errbuf[256]; const char *err_string; svn_error_t *tmp_err2 = NULL; if (tmp_err->message) { if (!str.empty()) { str += ", "; } str += std::string(tmp_err->message); } else { if ((tmp_err->apr_err > APR_OS_START_USEERR) && (tmp_err->apr_err <= APR_OS_START_CANONERR)) { err_string = svn_strerror(tmp_err->apr_err, errbuf, sizeof(errbuf)); } else if ((tmp_err2 = svn_utf_cstring_from_utf8(&err_string, apr_strerror(tmp_err->apr_err, errbuf, sizeof(errbuf)), tmp_err->pool))) { svn_error_clear(tmp_err2); err_string = "Can't recode error string from APR"; } if (!str.empty()) { str += ", "; } str += std::string(err_string); APR_ARRAY_PUSH(empties, apr_status_t) = tmp_err->apr_err; } } tmp_err = tmp_err->child; } apr_pool_destroy(pool); return str; } void SvnConnection::init() { svn_error_t *err; if ((err = svn_fs_initialize(pool))) { throw PEX(strerr(err)); } if ((err = svn_ra_initialize(pool))) { throw PEX(strerr(err)); } if ((err = svn_config_ensure(NULL, pool))) { throw PEX(strerr(err)); } } // Handles the prefetching of diffstats class SvnDiffstatPrefetcher { public: SvnDiffstatPrefetcher(SvnConnection *connection, int n = 4) { Logger::info() << "SubversionBackend: Using " << n << " threads for prefetching diffstats" << endl; for (int i = 0; i < n; i++) { SvnDiffstatThread *thread = new SvnDiffstatThread(connection, &m_queue); thread->start(); m_threads.push_back(thread); } } ~SvnDiffstatPrefetcher() { for (unsigned int i = 0; i < m_threads.size(); i++) { delete m_threads[i]; } } void stop() { m_queue.stop(); } void wait() { for (unsigned int i = 0; i < m_threads.size(); i++) { m_threads[i]->wait(); } } void prefetch(const std::vector &revisions) { m_queue.put(revisions); } bool get(const std::string &revision, DiffstatPtr *dest) { return m_queue.getResult(revision, dest); } bool willFetch(const std::string &revision) { return m_queue.hasArg(revision); } private: JobQueue m_queue; std::vector m_threads; }; // Static mutex for reading and writing log interavals sys::parallel::Mutex SubversionBackend::SvnLogIterator::s_cacheMutex; // Constructor SubversionBackend::SvnLogIterator::SvnLogIterator(SubversionBackend *backend, const std::string &prefix, uint64_t startrev, uint64_t endrev) : Backend::LogIterator(), m_backend(backend), d(new SvnConnection()), m_prefix(prefix), m_startrev(startrev), m_endrev(endrev), m_index(0), m_finished(false), m_failed(false) { d->open(m_backend->d); } // Desctructor SubversionBackend::SvnLogIterator::~SvnLogIterator() { delete d; } // Returns the next revision IDs, or an empty vector bool SubversionBackend::SvnLogIterator::nextIds(std::queue *queue) { sys::parallel::MutexLocker locker(&m_mutex); while (m_index >= m_ids.size() && !m_finished) { m_cond.wait(locker.mutex()); } if (m_failed) { throw PEX("Error fetching server log"); } if (m_index == m_ids.size()) { return false; } while (m_index < m_ids.size()) { queue->push(m_ids[m_index++]); } return true; } struct logReceiverBaton { sys::parallel::Mutex *mutex; sys::parallel::WaitCondition *cond; std::vector temp; std::vector *ids; uint64_t latest; }; // Subversion callback for log messages static svn_error_t *logReceiver(void *baton, svn_log_entry_t *entry, apr_pool_t *) { logReceiverBaton *b = static_cast(baton); b->latest = entry->revision; b->temp.push_back(str::itos(b->latest)); if (b->temp.size() > 64) { b->mutex->lock(); for (size_t i = 0; i < b->temp.size(); i++) { b->ids->push_back(b->temp[i]); } b->temp.clear(); b->cond->wakeAll(); b->mutex->unlock(); } return SVN_NO_ERROR; } // Main thread function void SubversionBackend::SvnLogIterator::run() { apr_pool_t *pool = svn_pool_create(d->pool); apr_pool_t *iterpool = svn_pool_create(pool); apr_array_header_t *path = apr_array_make(pool, 1, sizeof (const char *)); std::string sessionPrefix = d->prefix; if (m_prefix.empty()) { APR_ARRAY_PUSH(path, const char *) = svn_path_canonicalize(sessionPrefix.c_str(), pool); } else if (sessionPrefix.empty()) { APR_ARRAY_PUSH(path, const char *) = svn_path_canonicalize(m_prefix.c_str(), pool); } else { APR_ARRAY_PUSH(path, const char *) = svn_path_canonicalize((sessionPrefix+"/"+m_prefix).c_str(), pool); } apr_array_header_t *props = apr_array_make(pool, 1, sizeof (const char *)); // Intentionally empty int windowSize = 1024; if (!strncmp(d->url, "file://", strlen("file://"))) { windowSize = 0; } PDEBUG << "Path is " << APR_ARRAY_IDX(path, 0, const char *) << ", range is [" << m_startrev << ":" << m_endrev << "]" << endl; // This is a vector of intervals that should be fetched. The revisions of // each interval will be appended after fetching, so it can be used to store // revisions that are already cached. std::vector fetch; fetch.push_back(Interval(m_startrev, m_endrev)); std::string cachefile = str::printf("log_%s", APR_ARRAY_IDX(path, 0, const char *)); if (m_backend->options().useCache()) { readIntervalsFromCache(cachefile); // Check if a cached interval intersects with the interval that should be fetched. It's // assumed that m_cachedIntervals doesn't contain intersecting intervals, which is assured // by the recursive merging in mergeInterval(). std::vector fetchnew = missingIntervals(m_startrev, m_endrev); if (!fetchnew.empty()) { fetch = fetchnew; } } logReceiverBaton baton; baton.mutex = &m_mutex; baton.cond = &m_cond; baton.ids = &m_ids; baton.latest = 0; // Fetch all revision intervals that are required for (size_t i = 0; i < fetch.size(); i++) { svn_pool_clear(iterpool); uint64_t wstart = fetch[i].start, lastStart = fetch[i].start; while (wstart <= fetch[i].end) { PDEBUG << "Fetching log from " << wstart << " to " << fetch[i].end << " with window size " << windowSize << endl; svn_error_t *err = svn_ra_get_log2(d->ra, path, wstart, fetch[i].end, windowSize, FALSE, FALSE /* otherwise, copy history will be ignored */, FALSE, props, &logReceiver, &baton, iterpool); if (err != NULL) { Logger::err() << "Error: Unable to fetch server log: " << SvnConnection::strerr(err) << endl; m_mutex.lock(); m_failed = true; m_finished = true; m_cond.wakeAll(); m_mutex.unlock(); svn_pool_destroy(pool); return; } if (baton.latest + 1 > lastStart) { lastStart = baton.latest + 1; } else { lastStart += std::max(windowSize, 1); } wstart = lastStart; } m_mutex.lock(); Logger &l = PTRACE << "Appending " << baton.temp.size() << " fetched revisions: "; for (size_t j = 0; j < baton.temp.size(); j++) { l << baton.temp[j] << " "; m_ids.push_back(baton.temp[j]); } l << endl; baton.temp.clear(); // Append cached revisions if (!fetch[i].revisions.empty()) { l << "Appending " << fetch[i].revisions.size() << " cached revisions: "; for (size_t j = 0; j < fetch[i].revisions.size(); j++) { l << fetch[i].revisions[j] << " "; m_ids.push_back(str::itos(fetch[i].revisions[j])); } l << endl; } m_cond.wakeAll(); m_mutex.unlock(); } m_mutex.lock(); m_finished = true; m_cond.wakeAll(); m_mutex.unlock(); if (m_backend->options().useCache()) { // Add current interval Interval current(m_startrev, m_endrev); for (size_t i = 0; i < m_ids.size(); i++) { uint64_t rev; str::stoi(m_ids[i], &rev); current.revisions.push_back(rev); } mergeInterval(current); writeIntervalsToCache(cachefile); } svn_pool_destroy(pool); } // Reads previous log intervals from the cache void SubversionBackend::SvnLogIterator::readIntervalsFromCache(const std::string &file) { sys::parallel::MutexLocker locker(&s_cacheMutex); std::string cachefile = Cache::cacheFile(m_backend, file); m_cachedIntervals.clear(); if (!sys::fs::fileExists(cachefile)) { return; } GZIStream in(cachefile); uint32_t version; in >> version; if (version != 1) { Logger::warn() << "Unknown version number in cache file " << cachefile << ": " << version << endl; return; } Interval interval; while (!(in >> interval.start).eof()) { uint64_t i = 0, num; in >> interval.end >> num; interval.revisions.resize(num); while (i < num) { in >> interval.revisions[i++]; } if (!in.ok() || interval.revisions.empty() || interval.start >= interval.end) { PTRACE << "Skipping bogus interval: [" << interval.start << ":" << interval.end << "] with " << interval.revisions.size() << " revisions" << endl; continue; } PTRACE << "New revision range: [" << interval.start << ":" << interval.end << "] with " << interval.revisions.size() << " revisions" << endl; m_cachedIntervals.push_back(interval); } if (!in.ok() && !in.eof()) { m_cachedIntervals.clear(); Logger::warn() << "Error reading from cache file " << cachefile << endl; } } // Writes the current intervals to the cache void SubversionBackend::SvnLogIterator::writeIntervalsToCache(const std::string &file) { sys::parallel::MutexLocker locker(&s_cacheMutex); std::string cachefile = Cache::cacheFile(m_backend, file); PDEBUG << "Writing log intervals to cache file " << cachefile << endl; GZOStream *out = new GZOStream(cachefile); *out << (uint32_t)1; // Version number for (size_t i = 0; i < m_cachedIntervals.size() && out->ok(); i++) { const std::vector &revisions = m_cachedIntervals[i].revisions; *out << m_cachedIntervals[i].start << m_cachedIntervals[i].end << (uint64_t)revisions.size(); for (size_t j = 0; j < revisions.size() && out->ok(); j++) { *out << revisions[j]; } } if (!out->ok()) { Logger::warn() << "Error writing to cache file: " << cachefile << endl; delete out; out = NULL; sys::fs::unlink(cachefile); } delete out; } // Merges the given interval into the intervals that have been already known void SubversionBackend::SvnLogIterator::mergeInterval(const Interval &interval) { PDEBUG << "Merging new interval [" << interval.start << ":" << interval.end << "]" << endl; for (size_t i = 0; i < m_cachedIntervals.size(); i++) { const Interval &candidate = m_cachedIntervals[i]; if ((interval.start <= candidate.start && interval.end >= candidate.start) || (interval.start <= candidate.end && interval.end >= candidate.end)) { // Intervals intersect, let's merge them. // NOTE: It's assumed that the repository is immutable, i.e. no // revisions will ever be deleted. PDEBUG << "Intervals [" << interval.start << ":" << interval.end << "] and [" << candidate.start << ":" << candidate.end << "] intersect" << endl; Interval merged(std::min(interval.start, candidate.start), std::max(interval.end, candidate.end)); merged.revisions.insert(merged.revisions.begin(), interval.revisions.begin(), interval.revisions.end()); merged.revisions.insert(merged.revisions.end(), candidate.revisions.begin(), candidate.revisions.end()); std::sort(merged.revisions.begin(), merged.revisions.end()); // Remove duplicates for (size_t j = 0; j < merged.revisions.size()-1; j++) { if (merged.revisions[j] == merged.revisions[j+1]) { merged.revisions.erase(merged.revisions.begin()+j); --j; } } // Merge the merged interval m_cachedIntervals.erase(m_cachedIntervals.begin()+i); mergeInterval(merged); return; } } PDEBUG << "No intersections" << endl; // No merge with previous intervals m_cachedIntervals.push_back(interval); } // Returns missing intervals, including following cached revision numbers std::vector SubversionBackend::SvnLogIterator::missingIntervals(uint64_t start, uint64_t end) { std::vector missing; for (size_t i = 0; i < m_cachedIntervals.size(); i++) { const Interval &candidate = m_cachedIntervals[i]; if (start >= candidate.start && end <= candidate.end ) { // Completely inside candidate interval. No need to fetch any logs from the repository, // but store the relevant revisions in a pseudo interval Interval interval(start+1, start); size_t j = 0, n = candidate.revisions.size(); while (j < n && candidate.revisions[j] < start) ++j; while (j < n && candidate.revisions[j] <= end) { interval.revisions.push_back(candidate.revisions[j]); ++j; } missing.push_back(interval); return missing; } else if (start <= candidate.start && end >= candidate.end) { // Completely including candidate interval. Two intervals need to be fetched from // the server now, but both will be splitted further. std::vector recurse = missingIntervals(start, (candidate.start == 0 ? 0 : candidate.start-1)); recurse.back().revisions.insert(recurse.back().revisions.end(), candidate.revisions.begin(), candidate.revisions.end()); missing.insert(missing.begin(), recurse.begin(), recurse.end()); recurse = missingIntervals(candidate.end+1, end); missing.insert(missing.end(), recurse.begin(), recurse.end()); return missing; } else if (start >= candidate.start && start <= candidate.end) { // Intersecting at start. Add a pseudo interval for the cached revisions // and an interval for the remaining ones after candidate.end Interval interval1(start+1, start); size_t j = 0, n = candidate.revisions.size(); while (j < n && candidate.revisions[j] < start) ++j; interval1.revisions.insert(interval1.revisions.begin(), candidate.revisions.begin() + j, candidate.revisions.end()); missing.push_back(interval1); std::vector recurse = missingIntervals(candidate.end+1, end); missing.insert(missing.end(), recurse.begin(), recurse.end()); return missing; } else if (end >= candidate.start && end <= candidate.end) { // Intersectiong at end. Add a single interval, including the first // revisions in candidate std::vector recurse = missingIntervals(start, (candidate.start == 0 ? 0 : candidate.start-1)); for (size_t j = 0; j < candidate.revisions.size() && candidate.revisions[j] <= end; j++) { recurse.back().revisions.push_back(candidate.revisions[j]); } missing.insert(missing.begin(), recurse.begin(), recurse.end()); return missing; } } // No intersections, so return the complete interval missing.push_back(Interval(start, end)); return missing; } // Constructor SubversionBackend::SubversionBackend(const Options &options) : Backend(options), d(new SvnConnection()), m_prefetcher(NULL) { } // Destructor SubversionBackend::~SubversionBackend() { close(); delete d; } // Opens the connection to the repository void SubversionBackend::init() { // Initialize the Subversion C library #ifndef WIN32 if (svn_cmdline_init(PACKAGE, stderr) != EXIT_SUCCESS) { throw PEX("Failed to initialize Subversion library"); } atexit(apr_terminate); #else // !WIN32 if (svn_cmdline_init(PACKAGE, NULL) != EXIT_SUCCESS) { throw PEX("Failed to initialize Subversion library"); } #endif // !WIN32 PDEBUG << "Subversion library initialized, opening connection" << endl; std::string url = m_opts.repository(); if (!url.compare(0, 1, "/")) { // Detect working copy apr_pool_t *pool = svn_pool_create(NULL); const char *repo; svn_error_t *err = svn_client_url_from_path(&repo, url.c_str(), pool); if (err == NULL) { url = std::string(repo); } else { // Local repository url = std::string("file://") + url; } svn_pool_destroy(pool); } d->open(url, m_opts.options()); } // Called after Report::run() void SubversionBackend::close() { // Clean up any prefetching threads finalize(); } // Returns true if this backend is able to access the given repository bool SubversionBackend::handles(const std::string &url) { const char *schemes[] = {"svn://", "svn+ssh://", "http://", "https://", "file://"}; for (unsigned int i = 0; i < sizeof(schemes)/sizeof(schemes[0]); i++) { if (str::startsWith(url, schemes[i])) { return true; } } // Local repository without the 'file://' prefix if (sys::fs::dirExists(url) && sys::fs::dirExists(url+"/locks") && sys::fs::exists(url+"/db/uuid")) { return true; } // Working copy with .svn directory here or in a parent directory std::string dir = url; while (!dir.empty()) { if (sys::fs::dirExists(dir + "/.svn")) { return true; } std::string parent = sys::fs::dirname(dir); if (parent == dir) { break; } dir = parent; } return false; } // Returns a unique identifier for this repository std::string SubversionBackend::uuid() { apr_pool_t *pool = svn_pool_create(d->pool); const char *id; svn_error_t *err = svn_ra_get_uuid2(d->ra, &id, pool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } std::string sid(id); svn_pool_destroy(pool); return sid; } // Returns the HEAD revision for the current branch std::string SubversionBackend::head(const std::string &branch) { apr_pool_t *pool = svn_pool_create(d->pool); std::string prefix = this->prefix(branch, pool); svn_dirent_t *dirent; svn_error_t *err = svn_ra_stat(d->ra, svn_path_join(d->prefix, prefix.c_str(), pool), SVN_INVALID_REVNUM, &dirent, pool); if (err == NULL && dirent == NULL && branch == "trunk") { err = svn_ra_stat(d->ra, "", SVN_INVALID_REVNUM, &dirent, pool); } if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } if (dirent == NULL) { throw PEX(std::string("Unable to determine HEAD revision of ")+d->url+"/"+prefix); } svn_revnum_t rev = dirent->created_rev; PDEBUG << "Head revision for branch " << prefix << " is " << rev << endl; svn_pool_destroy(pool); return str::itos((long int)rev); } // Returns the standard branch (i.e., trunk) std::string SubversionBackend::mainBranch() { return "trunk"; } // Returns a list of available branches std::vector SubversionBackend::branches() { std::string prefix = m_opts.value("branches", "branches"); std::vector branches; apr_pool_t *pool = svn_pool_create(d->pool); char *absPrefix = svn_path_join(d->prefix, prefix.c_str(), pool); // Check if branches directoy is present svn_dirent_t *dirent; svn_error_t *err = svn_ra_stat(d->ra, absPrefix, SVN_INVALID_REVNUM, &dirent, pool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } if (dirent == NULL || dirent->kind != svn_node_dir) { // It's not here; let's call the main branch "trunk" branches.push_back("trunk"); return branches; } // Get directory entries apr_hash_t *dirents; err = svn_ra_get_dir2(d->ra, &dirents, NULL, NULL, absPrefix, dirent->created_rev, SVN_DIRENT_KIND, pool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } branches.push_back("trunk"); for (apr_hash_index_t *hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) { const char *entry; svn_dirent_t *dirent; apr_hash_this(hi, (const void **)(void *)&entry, NULL, (void **)(void *)&dirent); if (dirent->kind == svn_node_dir) { branches.push_back(std::string(entry)); } } // Let's be nice std::sort(branches.begin()+1, branches.end()); svn_pool_destroy(pool); return branches; } // Returns a list of available tags std::vector SubversionBackend::tags() { std::string prefix = m_opts.value("tags", "tags"); std::vector tags; apr_pool_t *pool = svn_pool_create(d->pool); char *absPrefix = svn_path_join(d->prefix, prefix.c_str(), pool); // Check if tags directoy is present svn_dirent_t *dirent; svn_error_t *err = svn_ra_stat(d->ra, absPrefix, SVN_INVALID_REVNUM, &dirent, pool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } if (dirent == NULL || dirent->kind != svn_node_dir) { // No tags here return tags; } // Get directory entries apr_hash_t *dirents; err = svn_ra_get_dir2(d->ra, &dirents, NULL, NULL, absPrefix, dirent->created_rev, SVN_DIRENT_KIND | SVN_DIRENT_CREATED_REV, pool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } for (apr_hash_index_t *hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) { const char *key; svn_dirent_t *dirent; apr_hash_this(hi, (const void **)&key, NULL, (void **)&dirent); if (dirent->kind == svn_node_dir) { tags.push_back(Tag(str::itos(dirent->created_rev), key)); } } std::sort(tags.begin(), tags.end()); svn_pool_destroy(pool); return tags; } // Returns a diffstat for the specified revision DiffstatPtr SubversionBackend::diffstat(const std::string &id) { if (m_prefetcher && m_prefetcher->willFetch(id)) { PTRACE << "Revision " << id << " will be prefetched" << endl; DiffstatPtr stat; if (!m_prefetcher->get(id, &stat)) { throw PEX(std::string("Failed to retrieve diffstat for revision ") + id); } return stat; } std::vector revs = str::split(id, ":"); svn_revnum_t r1, r2; if (revs.size() > 1) { if (!str::stoi(revs[0], &r1) || !str::stoi(revs[1], &r2)) { throw PEX(std::string("Error parsing revision number ") + id); } } else { if (!str::stoi(revs[0], &r2)) { throw PEX(std::string("Error parsing revision number ") + id); } r1 = r2 - 1; } PDEBUG << "Fetching revision " << id << " manually" << endl; apr_pool_t *pool = svn_pool_create(d->pool); DiffstatPtr stat = SvnDiffstatThread::diffstat(d, r1, r2, pool); svn_pool_destroy(pool); return stat; } // Filters a diffstat by prefix void SubversionBackend::filterDiffstat(DiffstatPtr stat) { // Strip prefix if (d->prefix && strlen(d->prefix)) { stat->filter(d->prefix); } } // Returns a file listing for the given revision (defaults to HEAD) std::vector SubversionBackend::tree(const std::string &id) { svn_revnum_t revision; if (id.empty()) { revision = SVN_INVALID_REVNUM; } else if (!str::stoi(id, &revision)) { throw PEX(std::string("Error parsing revision number ") + id); } apr_pool_t *pool = svn_pool_create(d->pool); apr_pool_t *iterpool = svn_pool_create(pool); std::vector contents; // Pseudo-recursively list directory entries std::stack > stack; stack.push(std::pair(d->prefix, svn_node_dir)); while (!stack.empty()) { svn_pool_clear(iterpool); std::string node = stack.top().first; if (stack.top().second != svn_node_dir) { contents.push_back(node); stack.pop(); continue; } stack.pop(); PDEBUG << "Listing directory contents in " << node << "@" << revision << endl; apr_hash_t *dirents; svn_error_t *err = svn_ra_get_dir2(d->ra, &dirents, NULL, NULL, node.c_str(), revision, SVN_DIRENT_KIND, iterpool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } std::string prefix = (node.empty() ? "" : node + "/"); std::stack > next; std::vector keys = getHashKeys(dirents, iterpool); std::sort(keys.begin(), keys.end()); for (size_t i = 0; i < keys.size(); i++) { svn_dirent_t *dirent = (svn_dirent_t *)apr_hash_get(dirents, keys[i].data, keys[i].len); if (str::endsWith(node, keys[i].data) || str::endsWith(d->url, keys[i].data)) { continue; } if (dirent->kind == svn_node_file || dirent->kind == svn_node_dir) { next.push(std::pair(prefix + keys[i].data, dirent->kind)); } } while (!next.empty()) { stack.push(next.top()); next.pop(); } } svn_pool_destroy(pool); return contents; } // Returns the file contents of the given path at the given revision (defaults to HEAD) std::string SubversionBackend::cat(const std::string &path, const std::string &id) { svn_revnum_t revision; if (id.empty()) { revision = SVN_INVALID_REVNUM; } else if (!str::stoi(id, &revision)) { throw PEX(std::string("Error parsing revision number ") + id); } apr_pool_t *pool = svn_pool_create(d->pool); svn_stringbuf_t *buf = svn_stringbuf_create("", pool); svn_stream_t *stream = svn_stream_from_stringbuf(buf, pool); svn_error_t *err = svn_ra_get_file(d->ra, path.c_str(), revision, stream, NULL, NULL, pool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } // Subversion 1.6 offers this in svn_string_from_stream(), but for 1.5 we // must copy the string manually. svn_stringbuf_t *work = svn_stringbuf_create("", pool); char *buffer = (char *)apr_palloc(pool, SVN__STREAM_CHUNK_SIZE); while (1) { apr_size_t len = SVN__STREAM_CHUNK_SIZE; if ((err = svn_stream_read(stream, buffer, &len))) { throw PEX(SvnConnection::strerr(err)); } svn_stringbuf_appendbytes(work, buffer, len); if (len < SVN__STREAM_CHUNK_SIZE) { break; } } if ((err = svn_stream_close(stream))) { throw PEX(SvnConnection::strerr(err)); } std::string content = std::string(work->data, work->len); svn_pool_destroy(pool); return content; } // Returns a log iterator for the given branch Backend::LogIterator *SubversionBackend::iterator(const std::string &branch, int64_t start, int64_t end) { // Check if the branch exists apr_pool_t *pool = svn_pool_create(d->pool); std::string prefix = this->prefix(branch, pool);; svn_dirent_t *dirent; svn_error_t *err = svn_ra_stat(d->ra, svn_path_join(d->prefix, prefix.c_str(), pool), SVN_INVALID_REVNUM, &dirent, pool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } if (dirent == NULL) { if (prefix == "trunk") { prefix.clear(); } else { throw PEX(str::printf("No such branch: %s", branch.c_str())); } } svn_revnum_t startrev = 0, endrev; apr_time_t time; if (start >= 0) { apr_time_ansi_put(&time, start); if ((err = svn_ra_get_dated_revision(d->ra, &startrev, time, pool)) != NULL) { throw PEX(SvnConnection::strerr(err)); } // startrev has been set to the HEAD revision at the given start time, but // we're interested in all revisions after this date. ++startrev; } if (end >= 0) { apr_time_ansi_put(&time, end); if ((err = svn_ra_get_dated_revision(d->ra, &endrev, time, pool)) != NULL) { throw PEX(SvnConnection::strerr(err)); } } else { str::stoi(head(branch), &endrev); } PDEBUG << "Revision range: [ " << start << " : " << end << "] -> [" << startrev << " : " << endrev << "]" << endl; svn_pool_destroy(pool); return new SvnLogIterator(this, prefix, startrev, endrev); } // Adds the given revision IDs to the diffstat scheduler void SubversionBackend::prefetch(const std::vector &ids) { if (m_prefetcher == NULL) { std::string numthreads = m_opts.value("threads", "10"); int nthreads = 10; if (!str::stoi(numthreads, &nthreads)) { throw PEX(std::string("Expected number for --threads parameter: ") + numthreads); } if (nthreads == 0) { return; } // Don't use that many threads for local repositories if (!strncmp(d->url, "file://", strlen("file://"))) { nthreads = std::max(1, sys::parallel::idealThreadCount() / 2); } m_prefetcher = new SvnDiffstatPrefetcher(d, nthreads); } m_prefetcher->prefetch(ids); } // Handle cleanup of diffstat scheduler void SubversionBackend::finalize() { if (m_prefetcher) { m_prefetcher->stop(); m_prefetcher->wait(); delete m_prefetcher; m_prefetcher = NULL; } } // Prints a help screen void SubversionBackend::printHelp() const { Options::print("--username=ARG", "Specify a username ARG"); Options::print("--password=ARG", "Specify a password ARG"); Options::print("--no-auth-cache", "Do not cache authentication tokens"); Options::print("--non-interactive", "Do no interactive prompting"); Options::print("--trunk=ARG", "Trunk is at subdirectory ARG"); Options::print("--branches=ARG", "Branches are in subdirectory ARG"); Options::print("--tags=ARG", "Tags are in subdirectory ARG"); Options::print("--threads=ARG", "Use ARG threads for requesting diffstats"); } // Returns the prefix for the given branch std::string SubversionBackend::prefix(const std::string &branch, apr_pool_t *pool) { std::string p; if (branch == "trunk") { p = m_opts.value("trunk", "trunk"); } else if (!branch.empty()) { p = m_opts.value("branches", "branches"); p += "/"; p += branch; } PDEBUG << "Iterator requested for branch " << branch << " -> prefix = " << p << endl; return std::string(svn_path_canonicalize(p.c_str(), pool)); } // Returns the revision data for the given ID Revision *SubversionBackend::revision(const std::string &id) { std::map data; std::string rev = str::split(id, ":").back(); svn_revnum_t revnum; if (!str::stoi(rev, &(revnum))) { throw PEX(std::string("Error parsing revision number ") + id); } apr_pool_t *pool = svn_pool_create(d->pool); apr_hash_t *props; svn_error_t *err = svn_ra_rev_proplist(d->ra, revnum, &props, pool); if (err != NULL) { throw PEX(SvnConnection::strerr(err)); } svn_string_t *value; std::string author, message; int64_t date = 0; if ((value = static_cast(apr_hash_get(props, "svn:author", APR_HASH_KEY_STRING)))) { author = value->data; } if ((value = static_cast(apr_hash_get(props, "svn:date", APR_HASH_KEY_STRING)))) { apr_time_t when; if ((err = svn_time_from_cstring(&when, value->data, pool)) != NULL) { throw PEX(SvnConnection::strerr(err)); } date = apr_time_sec(when); } if ((value = static_cast(apr_hash_get(props, "svn:log", APR_HASH_KEY_STRING)))) { message = value->data; } svn_pool_destroy(pool); return new Revision(id, date, author, message, diffstat(id)); } pepper-0.3.3/src/backends/subversion.h000066400000000000000000000052661263211400600177250ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: subversion.h * Subversion repository backend (interface) */ #ifndef SUBVERSION_BACKEND_H_ #define SUBVERSION_BACKEND_H_ #include "backend.h" class SvnConnection; class SvnDiffstatPrefetcher; class SubversionBackend : public Backend { public: class SvnLogIterator : public LogIterator { struct Interval { Interval() : start(0), end(0) { } Interval(uint64_t start, uint64_t end) : start(start), end(end) { } uint64_t start, end; std::vector revisions; }; public: SvnLogIterator(SubversionBackend *backend, const std::string &prefix, uint64_t startrev, uint64_t endrev); ~SvnLogIterator(); bool nextIds(std::queue *queue); protected: void run(); private: void readIntervalsFromCache(const std::string &file); void writeIntervalsToCache(const std::string &file); void mergeInterval(const Interval &interval); std::vector missingIntervals(uint64_t start, uint64_t end); private: SubversionBackend *m_backend; SvnConnection *d; std::string m_prefix; uint64_t m_startrev, m_endrev; sys::parallel::Mutex m_mutex; sys::parallel::WaitCondition m_cond; std::vector::size_type m_index; std::vector m_cachedIntervals; bool m_finished, m_failed; static sys::parallel::Mutex s_cacheMutex; }; public: SubversionBackend(const Options &options); ~SubversionBackend(); void init(); void close(); std::string name() const { return "subversion"; } static bool handles(const std::string &url); std::string uuid(); std::string head(const std::string &branch = std::string()); std::string mainBranch(); std::vector branches(); std::vector tags(); DiffstatPtr diffstat(const std::string &id); void filterDiffstat(DiffstatPtr stat); std::vector tree(const std::string &id = std::string()); std::string cat(const std::string &path, const std::string &id = std::string()); LogIterator *iterator(const std::string &branch = std::string(), int64_t start = -1, int64_t end = -1); void prefetch(const std::vector &ids); Revision *revision(const std::string &id); void finalize(); void printHelp() const; private: std::string prefix(const std::string &branch, struct apr_pool_t *pool); private: SvnConnection *d; SvnDiffstatPrefetcher *m_prefetcher; }; #endif // SUBVERSION_BACKEND_H_ pepper-0.3.3/src/backends/subversion_delta.cpp000066400000000000000000000417251263211400600214310ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: subversion_delta.cpp * Diff retrieval for the subversion repository backend */ #define __STDC_CONSTANT_MACROS // For INT64_C #include "main.h" #include #include #include #include #include #include "jobqueue.h" #include "logger.h" #include "strlib.h" #include "backends/subversion_p.h" // Statement macro for checking APR calls, similar to SVN_ERR #define APR_ERR(expr) \ do { \ apr_status_t apr_status__temp = (expr); \ if (apr_status__temp) \ return svn_error_wrap_apr(apr_status__temp, NULL); \ } while (0) // streambuf implementation for apr_file_t class AprStreambuf : public std::streambuf { public: explicit AprStreambuf(apr_file_t *file) : std::streambuf(), f(file), m_putback(8), m_buffer(4096 + 8) { char *end = &m_buffer.front() + m_buffer.size(); setg(end, end, end); } private: int_type underflow() { if (gptr() < egptr()) // buffer not exhausted return traits_type::to_int_type(*gptr()); char *base = &m_buffer.front(); char *start = base; if (eback() == base) // true when this isn't the first fill { // Make arrangements for putback characters memmove(base, egptr() - m_putback, m_putback); start += m_putback; } // start is now the start of the buffer, proper. // Read from fptr_ in to the provided buffer size_t n = m_buffer.size() - (start - base); apr_status_t status = apr_file_read(f, start, &n); if (n == 0 || status == APR_EOF) return traits_type::eof(); // Set buffer pointers setg(base, start, start + n); return traits_type::to_int_type(*gptr()); } private: apr_file_t *f; const std::size_t m_putback; std::vector m_buffer; private: // Not allowed AprStreambuf(const AprStreambuf &); AprStreambuf &operator=(const AprStreambuf &); }; // Extra namespace for local structures namespace SvnDelta { // Baton for delta editor struct Baton { const char *target; apr_file_t *out; svn_ra_session_t *ra; svn_revnum_t revision; svn_revnum_t base_revision; svn_revnum_t target_revision; apr_hash_t *deleted_paths; const char *tempdir; const char *empty_file; apr_pool_t *pool; static Baton *make(svn_revnum_t baserev, svn_revnum_t rev, apr_file_t *outfile, apr_pool_t *pool) { Baton *baton = (Baton *)apr_pcalloc(pool, sizeof(Baton)); baton->target = ""; baton->out = outfile; baton->base_revision = baserev; baton->revision = rev; baton->deleted_paths = apr_hash_make(pool); svn_io_temp_dir(&(baton->tempdir), pool); baton->empty_file = NULL; // TODO baton->pool = pool; return baton; } }; struct DirBaton { const char *path; DirBaton *dir_baton; Baton *edit_baton; apr_pool_t *pool; static DirBaton *make(const char *path, DirBaton *parent_baton, Baton *edit_baton, apr_pool_t *pool) { DirBaton *dir_baton = (DirBaton *)apr_pcalloc(pool, sizeof(DirBaton)); dir_baton->dir_baton = parent_baton; dir_baton->edit_baton = edit_baton; dir_baton->pool = pool; dir_baton->path = apr_pstrdup(pool, path); return dir_baton; } }; struct FileBaton { const char *path; const char *path_start_revision; apr_file_t *file_start_revision; apr_hash_t *pristine_props; apr_array_header_t *propchanges; const char *path_end_revision; apr_file_t *file_end_revision; svn_txdelta_window_handler_t apply_handler; void *apply_baton; Baton *edit_baton; apr_pool_t *pool; static FileBaton *make(const char *path, void *edit_baton, apr_pool_t *pool) { FileBaton *file_baton = (FileBaton *)apr_pcalloc(pool, sizeof(FileBaton)); Baton *eb = static_cast(edit_baton); file_baton->edit_baton = eb; file_baton->pool = pool; file_baton->path = apr_pstrdup(pool, path); file_baton->propchanges = apr_array_make(pool, 1, sizeof(svn_prop_t)); return file_baton; } }; // Utility funtions svn_error_t *open_tempfile(apr_file_t **file, const char **path, FileBaton *b) { const char *temp = svn_path_join(b->edit_baton->tempdir, "tempfile", b->pool); return svn_io_open_unique_file2(file, path, temp, ".tmp", svn_io_file_del_on_pool_cleanup, b->pool); } svn_error_t *get_file_from_ra(FileBaton *b, svn_revnum_t revision) { PTRACE << b->path << "@" << revision << endl; svn_stream_t *fstream; apr_file_t *file; SVN_ERR(open_tempfile(&file, &(b->path_start_revision), b)); fstream = svn_stream_from_aprfile2(file, FALSE, b->pool); SVN_ERR(svn_ra_get_file(b->edit_baton->ra, b->path, revision, fstream, NULL, &(b->pristine_props), b->pool)); return svn_stream_close(fstream); } // Delta editor callback functions svn_error_t *set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t * /*pool*/) { Baton *eb = static_cast(edit_baton); eb->target_revision = target_revision; return SVN_NO_ERROR; } svn_error_t *open_root(void *edit_baton, svn_revnum_t /*base_revision*/, apr_pool_t *pool, void **root_baton) { PTRACE << endl; Baton *eb = static_cast(edit_baton); DirBaton *b = DirBaton::make("", NULL, eb, pool); *root_baton = b; return SVN_NO_ERROR; } // Prototype svn_error_t *close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool); svn_error_t *delete_entry(const char *path, svn_revnum_t target_revision, void *parent_baton, apr_pool_t *pool) { DirBaton *pb = static_cast(parent_baton); Baton *eb = pb->edit_baton; // File or directory? svn_dirent_t *dirent; SVN_ERR(svn_ra_stat(eb->ra, path, eb->base_revision, &dirent, pool)); if (dirent == NULL) { PDEBUG << "Error: Unable to stat " << path << "@" << eb->base_revision << endl; return SVN_NO_ERROR; } PTRACE << path << "@" << eb->base_revision <<" is a " << (dirent->kind == svn_node_dir ? "directory" : "file") << endl; if (dirent->kind == svn_node_file) { FileBaton *b = FileBaton::make(path, eb, pool); SVN_ERR(get_file_from_ra(b, eb->base_revision)); SVN_ERR(open_tempfile(&(b->file_end_revision), &(b->path_end_revision), b)); SVN_ERR(close_file(b, "", pool)); } else { PTRACE << "Listing " << path << "@" << eb->base_revision << endl; apr_hash_t *dirents; apr_pool_t *iterpool = svn_pool_create(pool); SVN_ERR(svn_ra_get_dir2(eb->ra, &dirents, NULL, NULL, path, eb->base_revision, 0, pool)); // "Delete" directory recursively for (apr_hash_index_t *hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) { svn_pool_clear(iterpool); const char *entry; svn_dirent_t *dirent; apr_hash_this(hi, (const void **)(void *)&entry, NULL, (void **)(void *)&dirent); SVN_ERR(delete_entry((const char *)svn_path_join(path, entry, pool), target_revision, parent_baton, iterpool)); } svn_pool_destroy(iterpool); } return SVN_NO_ERROR; } svn_error_t *add_directory(const char *path, void *parent_baton, const char *, svn_revnum_t, apr_pool_t *pool, void **child_baton) { PTRACE << path << endl; DirBaton *pb = static_cast(parent_baton); DirBaton *b = DirBaton::make(path, pb, pb->edit_baton, pool); *child_baton = b; return SVN_NO_ERROR; } svn_error_t *open_directory(const char *path, void *parent_baton, svn_revnum_t /*base_revision*/, apr_pool_t *pool, void **child_baton) { PTRACE << path << endl; DirBaton *pb = static_cast(parent_baton); DirBaton *b = DirBaton::make(path, pb, pb->edit_baton, pool); *child_baton = b; return SVN_NO_ERROR; } svn_error_t *add_file(const char *path, void *parent_baton, const char * /*copyfrom_path*/, svn_revnum_t /*copyfrom_revision*/, apr_pool_t *pool, void **file_baton) { PTRACE << path << endl; DirBaton *db = static_cast(parent_baton); FileBaton *b = FileBaton::make(path, db->edit_baton, pool); *file_baton = b; b->pristine_props = apr_hash_make(pool); return open_tempfile(&(b->file_start_revision), &(b->path_start_revision), b); } svn_error_t *open_file(const char *path, void *parent_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **file_baton) { PTRACE << path << ", base = " << base_revision << endl; DirBaton *db = static_cast(parent_baton); FileBaton *b = FileBaton::make(path, db->edit_baton, pool); *file_baton = b; // TODO: No need to get the whole file if it is binary... return get_file_from_ra(b, base_revision); } svn_error_t *window_handler(svn_txdelta_window_t *window, void *window_baton) { FileBaton *b = static_cast(window_baton); SVN_ERR(b->apply_handler(window, b->apply_baton)); if (!window) { SVN_ERR(svn_io_file_close(b->file_start_revision, b->pool)); SVN_ERR(svn_io_file_close(b->file_end_revision, b->pool)); } return SVN_NO_ERROR; } svn_error_t *apply_textdelta(void *file_baton, const char * /*base_checksum*/, apr_pool_t * /*pool*/, svn_txdelta_window_handler_t *handler, void **handler_baton) { FileBaton *b = static_cast(file_baton); PTRACE << "base is " << b->path_start_revision << endl; SVN_ERR(svn_io_file_open(&(b->file_start_revision), b->path_start_revision, APR_READ, APR_OS_DEFAULT, b->pool)); SVN_ERR(open_tempfile(&(b->file_end_revision), &(b->path_end_revision), b)); svn_txdelta_apply(svn_stream_from_aprfile2(b->file_start_revision, TRUE, b->pool), svn_stream_from_aprfile2(b->file_end_revision, TRUE, b->pool), NULL, b->path, b->pool, &(b->apply_handler), &(b->apply_baton)); *handler = window_handler; *handler_baton = file_baton; return SVN_NO_ERROR; } svn_error_t *close_file(void *file_baton, const char * /*text_checksum*/, apr_pool_t * /*pool*/) { FileBaton *b = static_cast(file_baton); Baton *eb = b->edit_baton; if (b->path_start_revision == NULL || b->path_end_revision == NULL) { PDEBUG << b->path << "@" << eb->target_revision << " Insufficient diff data (nothing has changed)" << endl; return SVN_NO_ERROR; } PTRACE << b->path_start_revision << " -> " << b->path_end_revision << endl; // Skip binary diffs const char *mimetype1 = NULL, *mimetype2 = NULL; if (b->pristine_props) { svn_string_t *pristine_val; pristine_val = (svn_string_t *)apr_hash_get(b->pristine_props, SVN_PROP_MIME_TYPE, strlen(SVN_PROP_MIME_TYPE)); if (pristine_val) { mimetype1 = pristine_val->data; } } if (b->propchanges) { int i; svn_prop_t *propchange; for (i = 0; i < b->propchanges->nelts; i++) { propchange = &APR_ARRAY_IDX(b->propchanges, i, svn_prop_t); if (strcmp(propchange->name, SVN_PROP_MIME_TYPE) == 0) { if (propchange->value) { mimetype2 = propchange->value->data; } break; } } } // TODO: Proper handling of mime-type changes if ((mimetype1 && svn_mime_type_is_binary(mimetype1)) || (mimetype2 && svn_mime_type_is_binary(mimetype2))) { PDEBUG << "Skipping binary files" << endl; return SVN_NO_ERROR; } // Finally, perform the diff static const char equal_string[] = "==================================================================="; svn_diff_t *diff; svn_stream_t *os; os = svn_stream_from_aprfile2(eb->out, TRUE, b->pool); svn_diff_file_options_t *opts = svn_diff_file_options_create(b->pool); SVN_ERR(svn_diff_file_diff_2(&diff, b->path_start_revision, b->path_end_revision, opts, b->pool)); // Print out the diff header SVN_ERR(svn_stream_printf_from_utf8(os, APR_LOCALE_CHARSET, b->pool, "Index: %s" APR_EOL_STR "%s" APR_EOL_STR, b->path, equal_string)); // Output the actual diff SVN_ERR(svn_diff_file_output_unified3(os, diff, b->path_start_revision, b->path_end_revision, b->path, b->path, APR_LOCALE_CHARSET, NULL, FALSE, b->pool)); return SVN_NO_ERROR; } svn_error_t *close_directory(void *, apr_pool_t *) { return SVN_NO_ERROR; } svn_error_t *change_file_prop(void *file_baton, const char *name, const svn_string_t *value, apr_pool_t * /*pool*/) { FileBaton *b = static_cast(file_baton); svn_prop_t *propchange = (svn_prop_t *)apr_array_push(b->propchanges); propchange->name = apr_pstrdup(b->pool, name); propchange->value = value ? svn_string_dup(value, b->pool) : NULL; return SVN_NO_ERROR; } svn_error_t *change_dir_prop(void *, const char *, const svn_string_t *, apr_pool_t *) { return SVN_NO_ERROR; } svn_error_t *absent_directory(const char *path, void * /*parent_baton*/, apr_pool_t * /*pool*/) { PDEBUG << path << endl; return SVN_NO_ERROR; } svn_error_t *absent_file(const char *path, void * /*parent_baton*/, apr_pool_t * /*pool*/) { PDEBUG << path << endl; return SVN_NO_ERROR; } svn_error_t *close_edit(void * /*edit_baton*/, apr_pool_t * /*pool*/) { return SVN_NO_ERROR; } } // namespace SvnDelta // Constructor SvnDiffstatThread::SvnDiffstatThread(SvnConnection *connection, JobQueue *queue) : d(new SvnConnection()), m_queue(queue) { d->open(connection); } // Destructor SvnDiffstatThread::~SvnDiffstatThread() { delete d; } // This function will always perform a diff on the full repository in // order to avoid errors due to non-existent paths and to cache consistency. DiffstatPtr SvnDiffstatThread::diffstat(SvnConnection *c, svn_revnum_t r1, svn_revnum_t r2, apr_pool_t *pool) { if (r2 <= 0) { return std::make_shared(); } svn_opt_revision_t rev1, rev2; rev1.kind = rev2.kind = svn_opt_revision_number; rev1.value.number = r1; rev2.value.number = r2; svn_error_t *err; apr_file_t *infile = NULL, *outfile = NULL, *errfile = NULL; if (apr_file_open_stderr(&errfile, pool) != APR_SUCCESS) { throw PEX("Unable to open stderr"); } if (apr_file_pipe_create(&infile, &outfile, pool) != APR_SUCCESS) { throw PEX("Unable to create pipe for reading diff data"); } AprStreambuf buf(infile); std::istream in(&buf); DiffParser parser(in); parser.start(); PTRACE << "Fetching diffstat for revision " << r1 << ":" << r2 << endl; // Setup the diff editor apr_pool_t *subpool = svn_pool_create(pool); svn_delta_editor_t *editor = svn_delta_default_editor(subpool); SvnDelta::Baton *baton = SvnDelta::Baton::make(r1, r2, outfile, subpool); // Open RA session for extra calls during diff err = svn_client_open_ra_session(&baton->ra, c->root, c->ctx, pool); if (err != NULL) { apr_file_close(outfile); apr_file_close(infile); throw PEX(str::printf("Diffstat fetching of revision %ld:%ld failed: %s", r1, r2, SvnConnection::strerr(err).c_str())); } editor->set_target_revision = SvnDelta::set_target_revision; editor->open_root = SvnDelta::open_root; editor->delete_entry = SvnDelta::delete_entry; editor->add_directory = SvnDelta::add_directory; editor->open_directory = SvnDelta::open_directory; editor->add_file = SvnDelta::add_file; editor->open_file = SvnDelta::open_file; editor->apply_textdelta = SvnDelta::apply_textdelta; editor->close_file = SvnDelta::close_file; editor->close_directory = SvnDelta::close_directory; editor->change_file_prop = SvnDelta::change_file_prop; editor->change_dir_prop = SvnDelta::change_dir_prop; editor->close_edit = SvnDelta::close_edit; editor->absent_directory = SvnDelta::absent_directory; editor->absent_file = SvnDelta::absent_file; const svn_ra_reporter3_t *reporter; void *report_baton; err = svn_ra_do_diff3(c->ra, &reporter, &report_baton, rev2.value.number, "", svn_depth_infinity, TRUE, TRUE, c->root, editor, baton, pool); if (err != NULL) { apr_file_close(outfile); apr_file_close(infile); throw PEX(str::printf("Diffstat fetching of revision %ld:%ld failed: %s", r1, r2, SvnConnection::strerr(err).c_str())); } err = reporter->set_path(report_baton, "", rev1.value.number, svn_depth_infinity, FALSE, NULL, pool); if (err != NULL) { apr_file_close(outfile); apr_file_close(infile); throw PEX(str::printf("Diffstat fetching of revision %ld:%ld failed: %s", r1, r2, SvnConnection::strerr(err).c_str())); } err = reporter->finish_report(report_baton, pool); if (err != NULL) { apr_file_close(outfile); apr_file_close(infile); throw PEX(str::printf("Diffstat fetching of revision %ld:%ld failed: %s", r1, r2, SvnConnection::strerr(err).c_str())); } if (apr_file_close(outfile) != APR_SUCCESS) { throw PEX("Unable to close outfile"); } parser.wait(); if (apr_file_close(infile) != APR_SUCCESS) { throw PEX("Unable to close infile"); } return parser.stat(); } // Main Thread function for fetching diffstats from a job queue void SvnDiffstatThread::run() { apr_pool_t *pool = svn_pool_create(d->pool); std::string revision; while (m_queue->getArg(&revision)) { apr_pool_t *subpool = svn_pool_create(pool); std::vector revs = str::split(revision, ":"); svn_revnum_t r1, r2; if (revs.size() > 1) { if (!str::stoi(revs[0], &r1) || !str::stoi(revs[1], &r2)) { goto parse_error; } } else { if (!str::stoi(revs[0], &r2)) { goto parse_error; } r1 = r2 - 1; } try { DiffstatPtr stat = diffstat(d, r1, r2, subpool); m_queue->done(revision, stat); } catch (const PepperException &ex) { Logger::err() << "Error: " << ex.where() << ": " << ex.what() << endl; m_queue->failed(revision); } goto next; parse_error: PEX(std::string("Error parsing revision number ") + revision); m_queue->failed(revision); next: svn_pool_destroy(subpool); } svn_pool_destroy(pool); } pepper-0.3.3/src/backends/subversion_p.h000066400000000000000000000031731263211400600202370ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: subversion_p.h * Interfaces of internal classes for the Subversion backend */ #ifndef SUBVERSION_BACKEND_P_H_ #define SUBVERSION_BACKEND_P_H_ #include #include #include #include "diffstat.h" template class JobQueue; // Repository connection class SvnConnection { public: SvnConnection(); ~SvnConnection(); void open(const std::string &url, const std::map &options); void open(SvnConnection *parent); static std::string strerr(svn_error_t *err); private: void init(); template static T hashget(apr_hash_t *hash, const char *key) { return static_cast(apr_hash_get(hash, key, APR_HASH_KEY_STRING)); } public: apr_pool_t *pool; svn_client_ctx_t *ctx; svn_ra_session_t *ra; const char *url, *root, *prefix; }; // The diffstat thread is implemented in subversion_delta.cpp class SvnDiffstatThread : public sys::parallel::Thread { public: SvnDiffstatThread(SvnConnection *connection, JobQueue *queue); ~SvnDiffstatThread(); static DiffstatPtr diffstat(SvnConnection *c, svn_revnum_t r1, svn_revnum_t r2, apr_pool_t *pool); protected: void run(); private: SvnConnection *d; JobQueue *m_queue; }; #endif // SUBVERSION_BACKEND_P_H_ pepper-0.3.3/src/bstream.cpp000066400000000000000000000120001263211400600157240ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: bstream.cpp * Binary input and output streams (implementations) */ #include "main.h" #include #ifdef HAVE_LIBZ #include #endif #include "bstream.h" // RawStream implementation for cstdio class FileStream : public BStream::RawStream { public: FileStream(FILE *f) : f(f) { } ~FileStream() { if (f) fclose(f); } bool ok() const { return !(f == NULL || ferror(f) != 0); } bool eof() const { return feof(f); } size_t tell() const { return ftell(f); } bool seek(size_t offset) { return (fseek(f, offset, SEEK_SET) >= 0); } ssize_t read(void *ptr, size_t n) { if (feof(f)) { return 0; } return fread(ptr, 1, n, f); } ssize_t write(const void *ptr, size_t n) { return fwrite(ptr, 1, n, f); } FILE *f; }; // RawStream implementation for memory buffers class MemoryStream : public BStream::RawStream { public: MemoryStream() : p(0), size(0), asize(512) { m_buffer = new char[asize]; } MemoryStream(const char *data, size_t n) : p(0), size(n) { m_buffer = new char[n]; memcpy(m_buffer, data, n); } ~MemoryStream() { delete[] m_buffer;} bool ok() const { return true; } bool eof() const { return !(p < size); } size_t tell() const { return p; } bool seek(size_t offset) { if (offset > size) { return false; } p = offset; return true; } ssize_t read(void *ptr, size_t n) { if (p == size) { return 0; } size_t nr = std::min(size - p, n); memcpy(ptr, m_buffer + p, nr); p += nr; return nr; } ssize_t write(const void *ptr, size_t n) { if (p + n >= asize) { size_t oldsize = size; do { asize *= 2; } while (p + n >= asize); char *newbuf = new char[asize]; memcpy(newbuf, m_buffer, oldsize); delete[] m_buffer; m_buffer = newbuf; } memcpy(m_buffer + p, ptr, n); p += n; size += n; return n; } char *m_buffer; size_t p, size, asize; }; #ifdef HAVE_LIBZ // RawStream implementation for zlib class GzStream : public BStream::RawStream { public: GzStream(gzFile f) : f(f) { } ~GzStream() { if (f) gzclose(f); } bool ok() const { if (f == NULL) { return false; } int err; gzerror(f, &err); return (err == 0); } bool eof() const { return gzeof(f); } size_t tell() const { return gztell(f); } bool seek(size_t offset) { return (gzseek(f, offset, SEEK_SET) >= 0); } ssize_t read(void *ptr, size_t n) { return gzread(f, ptr, n); } ssize_t write(const void *ptr, size_t n) { return gzwrite(f, ptr, n); } gzFile f; }; #endif // HAVE_LIBZ // Constructors BIStream::BIStream(const std::string &path) #if (__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 3) : BStream(new FileStream(fopen(path.c_str(), "rbm"))) #else : BStream(new FileStream(fopen(path.c_str(), "rb"))) #endif { } BIStream::BIStream(FILE *f) : BStream(new FileStream(f)) { } BIStream::BIStream(RawStream *stream) : BStream(stream) { } BOStream::BOStream(const std::string &path, bool append) : BStream(new FileStream(fopen(path.c_str(), (append ? "ab" : "wb")))) { } BOStream::BOStream(RawStream *stream) : BStream(stream) { } MIStream::MIStream(const char *data, size_t n) : BIStream(new MemoryStream(data, n)) { } MIStream::MIStream(const std::vector &data) : BIStream(new MemoryStream(&data[0], data.size())) { } MOStream::MOStream() : BOStream(new MemoryStream()) { } // Returns the stream's internal buffer std::vector MOStream::data() const { MemoryStream *ms = (MemoryStream *)m_stream; return std::vector(ms->m_buffer, ms->m_buffer + ms->size); } // Constructors GZIStream::GZIStream(const std::string &path) #ifdef HAVE_LIBZ : BIStream(new GzStream(gzopen(path.c_str(), "rb"))) #else : BIStream(path) #endif { } #ifdef HAVE_LIBZ // Single-linked buffer struct LBuffer { char *data; int size; LBuffer *next; LBuffer() : data(NULL), size(0), next(NULL) { } ~LBuffer() { delete[] data; } }; #endif // HAVE_LIBZ // Constructor GZOStream::GZOStream(const std::string &path, bool append) #ifdef HAVE_LIBZ : BOStream(append ? NULL : new GzStream(gzopen(path.c_str(), "wb"))) #else : BOStream(path, append) #endif { #ifdef HAVE_LIBZ // Appending to gzipped files does not work. Instead, read the whole // file and re-write it. if (append) { LBuffer *buf = NULL, *first = NULL; gzFile in = gzopen(path.c_str(), "rb"); if (in) { while (!gzeof(in)) { if (buf) { buf->next = new LBuffer(); buf = buf->next; } else { buf = new LBuffer(); first = buf; } buf->data = new char[4096]; buf->size = gzread(in, buf->data, 4096); } gzclose(in); } gzFile gz = gzopen(path.c_str(), "wb"); buf = first; while (buf != NULL) { gzwrite(gz, buf->data, buf->size); LBuffer *tmp = buf; buf = buf->next; delete tmp; } m_stream = new GzStream(gz); } #endif // HAVE_LIBZ } pepper-0.3.3/src/bstream.h000066400000000000000000000137421263211400600154070ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: bstream.h * Binary input and output streams (interfaces) */ #ifndef BSTREAM_H_ #define BSTREAM_H_ #include #include #include #include #include "main.h" // read()/write() macros with assertions #if defined(NDEBUG) || 1 // Don't use assertions, since realiable EOF testing requires // stream operators to fail silently. #define ASSERT_READ(p, n) do { read(p, n); } while (0) #define ASSERT_WRITE(p, n) do { write(p, n); } while (0) #else #define ASSERT_READ(p, n) do { assert(read(p, n) == (ssize_t)n); } while (0) #define ASSERT_WRITE(p, n) do { assert(write(p, n) == (ssize_t)n); } while (0) #endif // Base class for all streams class BStream { public: // Abstract base class for raw streams class RawStream { public: RawStream() { } virtual ~RawStream() { } virtual bool ok() const = 0; virtual bool eof() const = 0; virtual size_t tell() const = 0; virtual bool seek(size_t offset) = 0; virtual ssize_t read(void *ptr, size_t n) = 0; virtual ssize_t write(const void *ptr, size_t n) = 0; }; BStream(RawStream *stream) : m_stream(stream) { } virtual ~BStream() { delete m_stream; } inline bool ok() const { return m_stream != NULL && m_stream->ok(); } inline bool eof() const { return m_stream == NULL || m_stream->eof(); } inline size_t tell() const { return (m_stream ? m_stream->tell() : 0); } inline bool seek(size_t offset) { return (m_stream ? m_stream->seek(offset) : false); } inline ssize_t read(void *ptr, size_t n) { return (m_stream ? m_stream->read(ptr, n) : 0); } inline ssize_t write(const void *ptr, size_t n) { return (m_stream ? m_stream->write(ptr, n) : 0); } // Byte swapping (from Qt) static inline uint32_t bswap(uint32_t source) { return 0 | ((source & 0x000000ff) << 24) | ((source & 0x0000ff00) << 8) | ((source & 0x00ff0000) >> 8) | ((source & 0xff000000) >> 24); } static inline uint64_t bswap(uint64_t source) { char *t = (char *)&source; std::swap(t[0], t[7]), std::swap(t[1], t[6]); std::swap(t[2], t[5]), std::swap(t[3], t[4]); return source; } protected: RawStream *m_stream; }; // Input stream class BIStream : public BStream { public: BIStream(const std::string &path); BIStream(FILE *f); BIStream &operator>>(char &c); BIStream &operator>>(uint32_t &i); BIStream &operator>>(uint64_t &i); inline BIStream &operator>>(int64_t &i) { return (*this >> reinterpret_cast(i)); } BIStream &operator>>(std::string &s); BIStream &operator>>(std::vector &v); template BIStream &operator>>(std::vector &v) { uint32_t size; (*this) >> size; v.resize(size); for (uint32_t i = 0; i < size && !eof(); i++) { (*this) >> v[i]; } return *this; } protected: BIStream(RawStream *stream); }; // Output stream class BOStream : public BStream { public: BOStream(const std::string &path, bool append = false); BOStream(FILE *f); BOStream &operator<<(char c); BOStream &operator<<(uint32_t i); BOStream &operator<<(uint64_t i); inline BOStream &operator<<(int64_t i) { return (*this << static_cast(i)); } BOStream &operator<<(const std::string &s); BOStream &operator<<(const std::vector &v); template BOStream &operator<<(const std::vector &v) { (*this) << (uint32_t)v.size(); for (uint32_t i = 0; i < v.size(); i++) { (*this) << v[i]; } return *this; } protected: BOStream(RawStream *stream); }; // Memory input stream class MIStream : public BIStream { public: MIStream(const char *data, size_t n); MIStream(const std::vector &data); }; // Memory output stream class MOStream : public BOStream { public: MOStream(); std::vector data() const; }; // Compressed input stream class GZIStream : public BIStream { public: GZIStream(const std::string &path); }; // Compressed output stream class GZOStream : public BOStream { public: GZOStream(const std::string &path, bool append = false); }; // Inlined functions inline BOStream &BOStream::operator<<(char c) { ASSERT_WRITE((char *)&c, 1); return *this; } inline BOStream &BOStream::operator<<(uint32_t i) { #ifndef WORDS_BIGENDIAN i = bswap(i); #endif ASSERT_WRITE((char *)&i, 4); return *this; } inline BOStream &BOStream::operator<<(uint64_t i) { #ifndef WORDS_BIGENDIAN i = bswap(i); #endif ASSERT_WRITE((char *)&i, 8); return *this; } inline BOStream &BOStream::operator<<(const std::string &s) { ASSERT_WRITE(s.data(), s.length()); return (*this << '\0'); } inline BOStream &BOStream::operator<<(const std::vector &v) { (*this) << (uint32_t)v.size(); ASSERT_WRITE(&(v[0]), v.size()); return *this; } inline BIStream &BIStream::operator>>(char &c) { ASSERT_READ((char *)&c, 1); return *this; } inline BIStream &BIStream::operator>>(uint32_t &i) { ASSERT_READ((char *)&i, 4); #ifndef WORDS_BIGENDIAN i = bswap(i); #endif return *this; } inline BIStream &BIStream::operator>>(uint64_t &i) { ASSERT_READ((char *)&i, 8); #ifndef WORDS_BIGENDIAN i = bswap(i); #endif return *this; } inline BIStream &BIStream::operator>>(std::string &s) { char buffer[120], *bptr = buffer; char c; s.clear(); do { if (eof()) { s.clear(); return *this; } (*this) >> c; switch (c) { case 0: break; default: { *bptr = c; if (bptr-buffer == sizeof(buffer)-1) { s.append(buffer, sizeof(buffer)); bptr = buffer; } else { ++bptr; } continue; } } break; } while (true); if (bptr != buffer) { s.append(buffer, bptr-buffer); } return *this; } inline BIStream &BIStream::operator>>(std::vector &v) { uint32_t size; *this >> size; v.resize(size); ASSERT_READ(&v[0], size); return *this; } #endif // BSTREAM_H_ pepper-0.3.3/src/cache.cpp000066400000000000000000000251721263211400600153500ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: cache.cpp * Revision cache with custom binary format */ #include "main.h" #include #include #include #include "bstream.h" #include "logger.h" #include "options.h" #include "revision.h" #include "strlib.h" #include "utils.h" #include "syslib/datetime.h" #include "syslib/fs.h" #include "syslib/sigblock.h" #include "cache.h" #define CACHE_VERSION (uint32_t)5 #define MAX_CACHEFILE_SIZE 4194304 // Constructor Cache::Cache(Backend *backend, const Options &options) : AbstractCache(backend, options), m_iout(NULL), m_cout(NULL), m_cin(0), m_coindex(0), m_ciindex(0), m_loaded(false), m_lock(-1) { } // Destructor Cache::~Cache() { flush(); unlock(); } // Flushes and closes the cache streams void Cache::flush() { PTRACE << "Flushing cache..." << endl; delete m_iout; m_iout = NULL; delete m_cout; m_cout = NULL; delete m_cin; m_cin = NULL; PTRACE << "Cache flushed" << endl; } // Checks if the diffstat of the given revision is already cached bool Cache::lookup(const std::string &id) { if (!m_loaded) { load(); } return (m_index.find(id) != m_index.end()); } // Adds the revision to the cache void Cache::put(const std::string &id, const Revision &rev) { if (!m_loaded) { load(); } // Defer any signals while writing to the cache SIGBLOCK_DEFER(); // Add revision to cache std::string dir = m_opts.cacheDir() + "/" + uuid(), path; if (m_cout == NULL) { m_coindex = 0; do { path = str::printf("%s/cache.%u", dir.c_str(), m_coindex); if (!sys::fs::fileExists(path) || sys::fs::filesize(path) < MAX_CACHEFILE_SIZE) { break; } ++m_coindex; } while (true); delete m_cout; m_cout = new BOStream(path, true); } else if (m_cout->tell() >= MAX_CACHEFILE_SIZE) { delete m_cout; path = str::printf("%s/cache.%u", dir.c_str(), ++m_coindex); m_cout = new BOStream(path, true); } uint32_t offset = m_cout->tell(); MOStream rout; rev.write03(rout); std::vector compressed = utils::compress(rout.data()); *m_cout << compressed; // Add revision to index if (m_iout == NULL) { if (sys::fs::exists(dir + "/index")) { m_iout = new GZOStream(dir + "/index", true); } else { m_iout = new GZOStream(dir + "/index", false); // Version number *m_iout << CACHE_VERSION; } } *m_iout << id; *m_iout << m_coindex << offset << utils::crc32(compressed); // Update cached index m_index[id] = std::pair(m_coindex, offset); } // Loads a revision from the cache Revision *Cache::get(const std::string &id) { if (!m_loaded) { load(); } std::string dir = cacheDir(); std::pair offset = m_index[id]; std::string path = str::printf("%s/cache.%u", dir.c_str(), offset.first); if (m_cin == NULL || offset.first != m_ciindex) { delete m_cin; m_cin = new BIStream(path); m_ciindex = offset.first; if (!m_cin->ok()) { throw PEX(str::printf("Unable to read from cache file: %s", path.c_str())); } } if (!m_cin->seek(offset.second)) { throw PEX(str::printf("Unable to read from cache file: %s", path.c_str())); } Revision *rev = new Revision(id); std::vector data; *m_cin >> data; data = utils::uncompress(data); if (data.empty()) { throw PEX(str::printf("Unable to read from cache file: %s", path.c_str())); } MIStream rin(data); if (!rev->load03(rin)) { throw PEX(str::printf("Unable to read from cache file: %s", path.c_str())); } return rev; } // Loads the index file void Cache::load() { std::string path = cacheDir(); PDEBUG << "Using cache dir: " << path << endl; m_index.clear(); m_loaded = true; bool created; checkDir(path, &created); lock(); if (created) { return; } // For git repositories, the hardest part is calling uuid() sys::datetime::Watch watch; GZIStream *in = new GZIStream(path+"/index"); if (!in->ok()) { Logger::info() << "Cache: Empty cache for '" << uuid() << '\'' << endl; return; } uint32_t version; *in >> version; switch (checkVersion(version)) { case OutOfDate: delete in; throw PEX(str::printf("Cache is out of date - please run the check_cache report", version)); case UnknownVersion: delete in; throw PEX(str::printf("Unknown cache version number %u - please run the check_cache report", version)); default: break; } Logger::status() << "Loading cache index... " << ::flush; std::string buffer; std::pair pos; uint32_t crc; while (!(*in >> buffer).eof()) { if (buffer.empty()) { break; } *in >> pos.first >> pos.second; *in >> crc; m_index[buffer] = pos; } Logger::status() << "done" << endl; delete in; Logger::info() << "Cache: Loaded " << m_index.size() << " revisions in " << watch.elapsedMSecs() << " ms" << endl; } // Clears all cache files void Cache::clear() { flush(); std::string path = cacheDir(); if (!sys::fs::dirExists(path)) { return; } PDEBUG << "Clearing cache in dir: " << path << endl; std::vector files = sys::fs::ls(path); for (size_t i = 0; i < files.size(); i++) { std::string fullpath = path + "/" + files[i]; PDEBUG << "Unlinking " << fullpath << endl; sys::fs::unlink(fullpath); } } // Locks the cache directory for this process void Cache::lock() { if (m_lock >= 0) { PDEBUG << "Already locked (" << m_lock << ")" << endl; return; } std::string path = cacheDir(); std::string lock = path + "/lock"; m_lock = ::open(lock.c_str(), O_WRONLY | O_CREAT, S_IWUSR); if (m_lock == -1) { throw PEX(str::printf("Unable to lock cache %s: %s", path.c_str(), PepperException::strerror(errno).c_str())); } PTRACE << "Locking file " << lock << endl; struct flock flck; memset(&flck, 0x00, sizeof(struct flock)); flck.l_type = F_WRLCK; if (fcntl(m_lock, F_SETLK, &flck) == -1) { throw PEX(str::printf("Unable to lock cache %s, it may be used by another instance", path.c_str())); } } // Unlocks the cache directory void Cache::unlock() { if (m_lock < 0) { PDEBUG << "Not locked yet (" << m_lock << ")" << endl; return; } std::string path = cacheDir(); PTRACE << "Unlocking file " << path + "/lock" << endl; struct flock flck; memset(&flck, 0x00, sizeof(struct flock)); flck.l_type = F_UNLCK; if (fcntl(m_lock, F_SETLK, &flck) == -1) { throw PEX(str::printf("Unable to unlock cache, please delete %s/lock manually if required", path.c_str())); } if (::close(m_lock) == -1) { throw PEX_ERRNO(); } } // Checks the cache version Cache::VersionCheckResult Cache::checkVersion(int version) { if (version <= 0) { return UnknownVersion; } if (version <= 1) { // The diffstats for Mercurial and Git have been flawed in version 1. // The Subversion backend uses repository-wide diffstats now. return OutOfDate; } if (version <= 2 && m_backend->name() == "subversion") { // Invalid diffstats for deleted files in version 2 (Subversion backend) return OutOfDate; } if (version <= 4 && m_backend->name() == "git") { // Invalid commit times in version 3 (Git backend) return OutOfDate; } if ((uint32_t)version <= CACHE_VERSION) { return Ok; } return UnknownVersion; } // Checks cache entries and removes invalid ones from the index file void Cache::check(bool force) { std::map > index; std::string path = cacheDir(); PDEBUG << "Checking cache in dir: " << path << endl; bool created; checkDir(path, &created); if (created) { Logger::info() << "Cache: Created empty cache for '" << uuid() << '\'' << endl; return; } sys::datetime::Watch watch; GZIStream *in = new GZIStream(path+"/index"); if (!in->ok()) { Logger::info() << "Cache: Empty cache for '" << uuid() << '\'' << endl; delete in; return; } BIStream *cache_in = NULL; uint32_t cache_index = -1; uint32_t version; *in >> version; switch (checkVersion(version)) { case OutOfDate: delete in; Logger::warn() << "Cache: Cache is out of date"; if (!force) { Logger::warn() << " - won't clear it until forced to do so" << endl; } else { Logger::warn() << ", clearing" << endl; clear(); } return; case UnknownVersion: delete in; Logger::warn() << "Cache: Unknown cache version number " << version; if (!force) { Logger::warn() << " - won't clear it until forced to do so" << endl; } else { Logger::warn() << ", clearing" << endl; clear(); } return; default: break; } Logger::status() << "Checking all indexed revisions... " << ::flush; std::string id; std::pair pos; uint32_t crc; std::map crcs; std::vector corrupted; while (!(*in >> id).eof()) { if (!in->ok()) { goto corrupt; } *in >> pos.first >> pos.second; *in >> crc; if (!in->ok()) { goto corrupt; } index[id] = pos; crcs[id] = crc; if (cache_in == NULL || cache_index != pos.first) { delete cache_in; cache_in = new BIStream(str::printf("%s/cache.%u", path.c_str(), pos.first)); cache_index = pos.first; if (!cache_in->ok()) { delete cache_in; cache_in = NULL; goto corrupt; } } if (cache_in != NULL) { if (!cache_in->seek(pos.second)) { goto corrupt; } else { std::vector data; *cache_in >> data; if (utils::crc32(data) != crc) { goto corrupt; } } } PTRACE << "Revision " << id << " ok" << endl; continue; corrupt: PTRACE << "Revision " << id << " corrupted!" << endl; std::cerr << "Cache: Revision " << id << " is corrupted, removing from index file" << std::endl; corrupted.push_back(id); } delete cache_in; delete in; Logger::status() << "done" << endl; Logger::info() << "Cache: Checked " << index.size() << " revisions in " << watch.elapsedMSecs() << " ms" << endl; if (corrupted.empty()) { Logger::info() << "Cache: Everything's alright" << endl; return; } Logger::info() << "Cache: " << corrupted.size() << " corrupted revisions, rewriting index file" << endl; // Remove corrupted revisions from index file for (unsigned int i = 0; i < corrupted.size(); i++) { std::map >::iterator it = index.find(corrupted[i]); if (it != index.end()) { index.erase(it); } } // Defer any signals while writing to the cache { SIGBLOCK_DEFER(); // Rewrite index file GZOStream out(path+"/index"); out << CACHE_VERSION; for (std::map >::iterator it = index.begin(); it != index.end(); ++it) { out << it->first; out << it->second.first << it->second.second; out << crcs[it->first]; } } } pepper-0.3.3/src/cache.h000066400000000000000000000023561263211400600150140ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: cache.h * Revision cache with custom binary format (interface) */ #ifndef CACHE_H_ #define CACHE_H_ #include "abstractcache.h" class BIStream; class BOStream; class Cache : public AbstractCache { friend class LdbCache; // For importing revisions private: typedef enum { Ok, Abort, UnknownVersion, OutOfDate } VersionCheckResult; public: Cache(Backend *backend, const Options &options); ~Cache(); void flush(); void check(bool force = false); protected: bool lookup(const std::string &id); void put(const std::string &id, const Revision &rev); Revision *get(const std::string &id); private: void load(); void clear(); void lock(); void unlock(); VersionCheckResult checkVersion(int version); private: BOStream *m_iout, *m_cout; BIStream *m_cin; uint32_t m_coindex, m_ciindex; bool m_loaded; int m_lock; std::map > m_index; }; #endif // CACHE_H_ pepper-0.3.3/src/diffstat.cpp000066400000000000000000000154051263211400600161070ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: diffstat.cpp * Diffstat object and parser */ #include "main.h" #include "bstream.h" #include "logger.h" #include "luahelpers.h" #include "strlib.h" #include "diffstat.h" // Constructor Diffstat::Diffstat() { } // Destructor Diffstat::~Diffstat() { } // Returns the actual stats std::map Diffstat::stats() const { return m_stats; } // Removes all paths not matching the given filter void Diffstat::filter(const std::string &prefix) { std::map::iterator it = m_stats.begin(); while (it != m_stats.end()) { if (it->first.compare(0, prefix.length(), prefix)) { PTRACE << "Removed " << it->first << " from diffstat" << endl; m_stats.erase(it++); } else { ++it; } } } // Writes the stat to a binary stream void Diffstat::write(BOStream &out) const { out << (uint32_t)m_stats.size(); for (std::map::const_iterator it = m_stats.begin(); it != m_stats.end(); ++it) { out << it->first.data(); out << it->second.cadd << it->second.ladd << it->second.cdel << it->second.ldel; } } // Loads the stat from a binary stream bool Diffstat::load(BIStream &in) { m_stats.clear(); uint32_t i = 0, n; in >> n; std::string buffer; Stat stat; while (i++ < n && !in.eof()) { in >> buffer; if (buffer.empty()) { return false; } in >> stat.cadd >> stat.ladd >> stat.cdel >> stat.ldel; m_stats[buffer] = stat; } return true; } /* * Lua binding */ const char Diffstat::className[] = "diffstat"; Lunar::RegType Diffstat::methods[] = { LUNAR_DECLARE_METHOD(Diffstat, files), LUNAR_DECLARE_METHOD(Diffstat, lines_added), LUNAR_DECLARE_METHOD(Diffstat, bytes_added), LUNAR_DECLARE_METHOD(Diffstat, lines_removed), LUNAR_DECLARE_METHOD(Diffstat, bytes_removed), {0,0} }; Diffstat::Diffstat(lua_State *L) { Diffstat *other = Lunar::check(L, 1); if (other == NULL) { return; } m_stats = other->m_stats; } int Diffstat::files(lua_State *L) { std::vector v(m_stats.size()); int i = 0; for (std::map::const_iterator it = m_stats.begin(); it != m_stats.end(); ++it) { v[i++] = it->first; } return LuaHelpers::push(L, v); } int Diffstat::lines_added(lua_State *L) { if (lua_gettop(L) >= 1) { std::string file = LuaHelpers::pops(L); if (m_stats.find(file) != m_stats.end()) { return LuaHelpers::push(L, m_stats[file].ladd); } return LuaHelpers::push(L, 0); } // Return total int n = 0; for (std::map::const_iterator it = m_stats.begin(); it != m_stats.end(); ++it) { n += it->second.ladd; } return LuaHelpers::push(L, n); } int Diffstat::bytes_added(lua_State *L) { if (lua_gettop(L) >= 1) { std::string file = LuaHelpers::pops(L); if (m_stats.find(file) != m_stats.end()) { return LuaHelpers::push(L, m_stats[file].cadd); } return LuaHelpers::push(L, 0); } // Return total int n = 0; for (std::map::const_iterator it = m_stats.begin(); it != m_stats.end(); ++it) { n += it->second.cadd; } return LuaHelpers::push(L, n); } int Diffstat::lines_removed(lua_State *L) { if (lua_gettop(L) >= 1) { std::string file = LuaHelpers::pops(L); if (m_stats.find(file) != m_stats.end()) { return LuaHelpers::push(L, m_stats[file].ldel); } return LuaHelpers::push(L, 0); } // Return total int n = 0; for (std::map::const_iterator it = m_stats.begin(); it != m_stats.end(); ++it) { n += it->second.ldel; } return LuaHelpers::push(L, n); } int Diffstat::bytes_removed(lua_State *L) { if (lua_gettop(L) >= 1) { std::string file = LuaHelpers::pops(L); if (m_stats.find(file) != m_stats.end()) { return LuaHelpers::push(L, m_stats[file].cdel); } return LuaHelpers::push(L, 0); } // Return total int n = 0; for (std::map::const_iterator it = m_stats.begin(); it != m_stats.end(); ++it) { n += it->second.cdel; } return LuaHelpers::push(L, n); } // Constructor DiffParser::DiffParser(std::istream &in) : sys::parallel::Thread(), m_in(in) { } // Returns the internal diffstat object DiffstatPtr DiffParser::stat() const { return m_stat; } // Static diff parsing function for unified diffs DiffstatPtr DiffParser::parse(std::istream &in) { static const char marker[] = "==================================================================="; std::string str, file; DiffstatPtr ds = std::make_shared(); Diffstat::Stat stat; int chunk[2] = {0, 0}; while (in.good()) { std::getline(in, str); if (chunk[0] <= 0 && chunk[1] <= 0 && (!str.compare(0, 4, "--- ") || !str.compare(0, 4, "+++ "))) { if (!file.empty() && !stat.empty()) { ds->m_stats[file] = stat; file = std::string(); } stat = Diffstat::Stat(); std::vector header = str::split(str.substr(4), "\t"); if (header.empty()) { throw PEX(std::string("EMPTY HEADER: ")+str); } if (header[0] != "/dev/null") { file = header[0]; if (file[0] == '"' && file[file.length()-1] == '"') { file = file.substr(1, file.length()-2); } if (!file.compare(0, 2, "a/") || !file.compare(0, 2, "b/")) { file = file.substr(2); } } } else if (!str.compare(0, 2, "@@")) { std::vector header = str::split(str.substr(2), "@@", true); if (header.empty()) { throw PEX(std::string("EMPTY HEADER: ")+str); } std::vector ranges = str::split(header[0], " ", true); if (ranges.size() < 2 || ranges[0].empty() || ranges[1].empty()) { throw PEX(std::string("EMPTY HEADER: ")+str); } size_t pos; if ((pos = ranges[0].find(',')) != std::string::npos) { str::str2int(ranges[0].substr(pos+1), &chunk[(ranges[0][0] == '-' ? 0 : 1)]); } else { chunk[(ranges[0][0] == '-' ? 0 : 1)] = 1; } if ((pos = ranges[1].find(',')) != std::string::npos) { str::str2int(ranges[1].substr(pos+1), &chunk[(ranges[1][0] == '-' ? 0 : 1)]); } else { chunk[(ranges[1][0] == '-' ? 0 : 1)] = 1; } } else if (!str.empty() && str[0] == '-') { stat.cdel += str.length(); ++stat.ldel; --chunk[0]; } else if (!str.empty() && str[0] == '+') { stat.cadd += str.length(); ++stat.ladd; --chunk[1]; } else if (str == marker) { chunk[0] = chunk[1] = 0; } else if (!str.empty() && str[0] == (char)EOF) { // git diff-tree pipe prints EOF after diff data break; } else { if (chunk[0] > 0) --chunk[0]; if (chunk[1] > 0) --chunk[1]; } } if (!file.empty() && !stat.empty()) { ds->m_stats[file] = stat; } return ds; } // Main thread function void DiffParser::run() { m_stat = parse(m_in); } pepper-0.3.3/src/diffstat.h000066400000000000000000000032771263211400600155600ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: diffstat.h * Diffstat object and parser (interface) */ #ifndef DIFFSTAT_H_ #define DIFFSTAT_H_ #include #include #include #include #include "main.h" #include "lunar/lunar.h" #include "syslib/parallel.h" class BIStream; class BOStream; class Diffstat { friend class DiffParser; public: struct Stat { uint64_t cadd, ladd; uint64_t cdel, ldel; Stat() : cadd(0), ladd(0), cdel(0), ldel(0) { } inline bool empty() const { return (cadd == 0 && ladd == 0 && cdel == 0 && ldel == 0); } }; public: Diffstat(); ~Diffstat(); std::map stats() const; void filter(const std::string &prefix); void write(BOStream &out) const; bool load(BIStream &in); PEPPER_PVARS: std::map m_stats; // Lua binding public: Diffstat(lua_State *L); int files(lua_State *L); int lines_added(lua_State *L); int bytes_added(lua_State *L); int lines_removed(lua_State *L); int bytes_removed(lua_State *L); static const char className[]; static Lunar::RegType methods[]; }; typedef std::shared_ptr DiffstatPtr; class DiffParser : public sys::parallel::Thread { public: DiffParser(std::istream &in); DiffstatPtr stat() const; static DiffstatPtr parse(std::istream &in); protected: void run(); private: std::istream &m_in; DiffstatPtr m_stat; }; #endif // DIFFSTAT_H_ pepper-0.3.3/src/gnuplot.cpp000066400000000000000000000031511263211400600157660ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: gnuplot.cpp * Bidirectional Gnuplot pipe */ #include "gnuplot.h" #include "syslib/fs.h" #include "syslib/parallel.h" using sys::io::PopenStreambuf; // Internal reader thread class StreambufReader : public sys::parallel::Thread { public: StreambufReader(PopenStreambuf *buf, std::ostream &out) : m_buf(buf), m_out(out) { } protected: void run() { char buffer[1024]; std::istream in(m_buf); while (in.good()) { in.read(buffer, sizeof(buffer)); m_out.write(buffer, in.gcount()); } } private: PopenStreambuf *m_buf; std::ostream &m_out; }; // Standard output terminal std::string Gnuplot::s_stdTerminal; // Constructor Gnuplot::Gnuplot(const char * const *args, std::ostream &out) { std::string path = sys::fs::which("gnuplot"); m_buf = new PopenStreambuf(path.c_str(), args, std::ios::in | std::ios::out); m_reader = new StreambufReader(m_buf, out); m_reader->start(); m_pipe = new std::ostream(m_buf); } // Destructor Gnuplot::~Gnuplot() { delete m_pipe; m_buf->closeWrite(); m_reader->wait(); delete m_reader; delete m_buf; } // Writes a command to the Gnuplot pipe void Gnuplot::cmd(const std::string &str) { *m_pipe << str << "\n" << std::flush; } // Writes a command to the Gnuplot pipe void Gnuplot::cmd(const char *str, size_t len) { m_pipe->write(str, len); *m_pipe << std::flush; } pepper-0.3.3/src/gnuplot.h000066400000000000000000000016541263211400600154410ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: gnuplot.h * Bidirectional Gnuplot pipe (interface) */ #ifndef _GNUPLOT_H #define _GNUPLOT_H #include #include #include #include "syslib/io.h" class StreambufReader; class Gnuplot { public: Gnuplot(const char * const *args, std::ostream &out = std::cout); ~Gnuplot(); void cmd(const std::string &str); void cmd(const char *ptr, size_t len); inline Gnuplot& operator<<(const std::string &str) { cmd(str); return (*this); } private: sys::io::PopenStreambuf *m_buf; StreambufReader *m_reader; std::ostream *m_pipe; static std::string s_stdTerminal; }; #endif // _GNUPLOT_H pepper-0.3.3/src/jobqueue.h000066400000000000000000000057351263211400600155740ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: jobqueue.h * Simple thread-safe job queue */ #ifndef JOBQUEUE_H_ #define JOBQUEUE_H_ #include #include #include #include "logger.h" #include "syslib/parallel.h" template class JobQueue { public: JobQueue(size_t max = 512) : m_max(max), m_end(false) { } void put(const std::vector &args) { m_mutex.lock(); for (unsigned int i = 0; i < args.size(); i++) { m_queue.push(args[i]); m_status[args[i]] = -1; } m_mutex.unlock(); m_argWait.wakeAll(); } void stop() { m_mutex.lock(); m_end = true; m_mutex.unlock(); m_argWait.wakeAll(); m_resultWait.wakeAll(); } bool getArg(Arg *arg) { m_mutex.lock(); while (!m_end && (m_queue.empty() || m_results.size() > m_max)) { m_argWait.wait(&m_mutex); } if (m_end) { m_mutex.unlock(); return false; } *arg = m_queue.front(); m_queue.pop(); m_mutex.unlock(); return true; } bool getArgs(std::vector *args, size_t max) { m_mutex.lock(); while (!m_end && (m_queue.empty() || m_results.size() > m_max)) { m_argWait.wait(&m_mutex); } if (m_end) { m_mutex.unlock(); return false; } args->clear(); while (args->size() < max && !m_queue.empty()) { args->push_back(m_queue.front()); m_queue.pop(); } m_mutex.unlock(); return true; } bool hasArg(const Arg &arg) { m_mutex.lock(); bool has = (m_status.find(arg) != m_status.end()); m_mutex.unlock(); return has; } bool getResult(const Arg &arg, Result *res) { m_mutex.lock(); if (m_status.find(arg) == m_status.end()) { m_mutex.unlock(); return false; } while (!m_end && m_status[arg] < 0) { m_resultWait.wait(&m_mutex); } if (m_end || !m_status[arg]) { m_mutex.unlock(); return false; } *res = m_results[arg]; m_results.erase(arg); m_status.erase(arg); m_mutex.unlock(); m_argWait.wake(); return true; } void done(const Arg &arg, const Result &result) { m_mutex.lock(); m_results[arg] = result; m_status[arg] = 1; #ifdef DEBUG PTRACE << arg << " ok, " << m_results.size() << " results in queue" << endl; #endif m_mutex.unlock(); m_resultWait.wakeAll(); } void failed(const Arg &arg) { m_mutex.lock(); m_status[arg] = 0; #ifdef DEBUG PTRACE << arg << " FAILED, " << m_results.size() << " results in queue" << endl; #endif m_mutex.unlock(); m_resultWait.wakeAll(); } private: sys::parallel::Mutex m_mutex; sys::parallel::WaitCondition m_argWait, m_resultWait; std::queue m_queue; std::map m_results; std::map m_status; size_t m_max; bool m_end; }; #endif // JOBQUEUE_H_ pepper-0.3.3/src/ldbcache.cpp000066400000000000000000000124501263211400600160250ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: ldbcache.cpp * Revision cache using Leveldb */ #include "main.h" #include #include "bstream.h" #include "cache.h" #include "logger.h" #include "revision.h" #include "strlib.h" #include "utils.h" #include "syslib/fs.h" #include "ldbcache.h" // Constructor LdbCache::LdbCache(Backend *backend, const Options &options) : AbstractCache(backend, options), m_db(NULL) { } // Destructor LdbCache::~LdbCache() { closedb(); } // Flushes the cache to disk void LdbCache::flush() { } // Checks cache consistency void LdbCache::check(bool force) { try { if (!m_db) opendb(); } catch (const std::exception &ex) { PDEBUG << "Exception while opening database: " << ex.what() << endl; Logger::info() << "LdbCache: Database can't be opened, trying to repair it" << endl; } if (force || !m_db) { std::string path = cacheDir() + "/ldb"; leveldb::Status status = leveldb::RepairDB(path, leveldb::Options()); if (!status.ok()) { Logger::err() << "Error repairing database: " << status.ToString() << endl; } else { Logger::info() << "LdbCache: Database repaired" << endl; } closedb(); opendb(); } // Simply try to read all revisions Logger::info() << "LdbCache: Checking revisions..." << endl; std::vector corrupted; size_t n = 0; leveldb::Iterator* it = m_db->NewIterator(leveldb::ReadOptions()); for (it->SeekToFirst(); it->Valid(); it->Next()) { Revision rev(it->key().ToString()); std::string value = it->value().ToString(); MIStream rin(value.c_str(), value.length()); if (!rev.load(rin)) { PDEBUG << "Revision " << it->key().ToString() << " corrupted!" << endl; corrupted.push_back(it->key().ToString()); } ++n; } if (!it->status().ok()) { Logger::err() << "Error iterating over cached revisions: " << it->status().ToString() << endl; Logger::err() << "Please re-run with --force to repair the database (might cause data loss)" << endl; return; } Logger::info() << "LdbCache: Checked " << n << " revisions, found " << corrupted.size() << " to be corrupted" << endl; for (size_t i = 0; i < corrupted.size(); i++) { Logger::err() << "LdbCache: Revision " << corrupted[i] << " is corrupted, removing from index file" << endl; leveldb::Status status = m_db->Delete(leveldb::WriteOptions(), corrupted[i]); if (!status.ok()) { Logger::err() << "Error: Can't remove from revision " << corrupted[i] << " from database: " << status.ToString() << endl; return; } } } // Checks if the diffstat of the given revision is already cached bool LdbCache::lookup(const std::string &id) { if (!m_db) opendb(); std::string value; leveldb::Status s = m_db->Get(leveldb::ReadOptions(), id, &value); if (s.IsNotFound()) { return false; } if (!s.ok()) { throw PEX(str::printf("Error reading from cache: %s", s.ToString().c_str())); } return true; } // Adds the revision to the cache void LdbCache::put(const std::string &id, const Revision &rev) { if (!m_db) opendb(); MOStream rout; rev.write(rout); std::vector data(rout.data()); leveldb::Status s = m_db->Put(leveldb::WriteOptions(), id, std::string(data.begin(), data.end())); if (!s.ok()) { throw PEX(str::printf("Error writing to cache: %s", s.ToString().c_str())); } } Revision *LdbCache::get(const std::string &id) { if (!m_db) opendb(); std::string value; leveldb::Status s = m_db->Get(leveldb::ReadOptions(), id, &value); if (!s.ok()) { throw PEX(str::printf("Error reading from cache: %s", s.ToString().c_str())); } Revision *rev = new Revision(id); MIStream rin(value.c_str(), value.length()); if (!rev->load(rin)) { throw PEX(str::printf("Unable to read from cache: Data corrupted")); } return rev; } // Opens the database connection void LdbCache::opendb() { if (m_db) return; std::string path = cacheDir() + "/ldb"; PDEBUG << "Using cache dir: " << path << endl; if (!sys::fs::dirExists(path)) { sys::fs::mkpath(path); } leveldb::Options options; options.create_if_missing = false; leveldb::Status s = leveldb::DB::Open(options, path, &m_db); if (!s.ok()) { // New cache: Import revisions from old cache options.create_if_missing = true; leveldb::Status s = leveldb::DB::Open(options, path, &m_db); if (!s.ok()) { throw PEX(str::printf("Unable to open database %s: %s", path.c_str(), s.ToString().c_str())); } Cache c(m_backend, m_opts); import(&c); } } // Closes the database connection void LdbCache::closedb() { delete m_db; m_db = NULL; } // Imports all revisions from the given cache void LdbCache::import(Cache *cache) { try { cache->load(); } catch (const std::exception &ex) { PDEBUG << "Error loading old cache for import: " << ex.what() << endl; return; } Logger::info() << "LdbCache: Found old cache, importing revisions..." << endl; std::map >::const_iterator it; size_t n = 0; for (it = cache->m_index.begin(); it != cache->m_index.end(); ++it) { Revision *rev = cache->get(it->first); put(it->first, *rev); delete rev; ++n; } Logger::info() << "LdbCache: Imported " << n << " revisions" << endl; } pepper-0.3.3/src/ldbcache.h000066400000000000000000000016361263211400600154760ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: ldbcache.h * Revision cache using Leveldb (interface) */ #ifndef LDBCACHE_H_ #define LDBCACHE_H_ #include "abstractcache.h" class Cache; namespace leveldb { class DB; } class LdbCache : public AbstractCache { public: LdbCache(Backend *backend, const Options &options); ~LdbCache(); void flush(); void check(bool force = false); protected: bool lookup(const std::string &id); void put(const std::string &id, const Revision &rev); Revision *get(const std::string &id); private: void opendb(); void closedb(); void import(Cache *cache); private: leveldb::DB *m_db; }; #endif // CACHE_H_ pepper-0.3.3/src/logger.cpp000066400000000000000000000031301263211400600155520ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: logger.cpp * Logging facility class (implementation) */ #include "main.h" #include "logger.h" // Static variables Logger Logger::s_instances[Logger::NumLevels] = { Logger(Logger::None, std::cerr), Logger(Logger::Error, std::cerr), Logger(Logger::Warn, std::cerr), Logger(Logger::Status, std::cerr), Logger(Logger::Info, std::cerr), Logger(Logger::Debug, std::cerr), Logger(Logger::Trace, std::cerr) }; #ifdef DEBUG int Logger::s_level = Logger::Debug; #else int Logger::s_level = Logger::None; #endif sys::parallel::Mutex Logger::s_mutex; // Constructor Logger::Logger(int level, std::ostream &out) : m_level(level), m_out(&out) { } // Sets the output device for the logger instances void Logger::setOutput(std::ostream &out, int level) { if (level < 0) { for (int i = 0; i < Logger::NumLevels; i++) { if (i != Logger::Error && i != Logger::Warn) { s_instances[i].m_out = &out; } } } else if (level != Logger::Error && level != Logger::Warn) { s_instances[level].m_out = &out; } } // Sets the log level void Logger::setLevel(int level) { s_level = std::max((int)Logger::Warn, level); } // Returns the log level int Logger::level() { return s_level; } // Flushes all loggers void Logger::flush() { for (int i = 0; i < Logger::NumLevels; i++) { s_instances[i].m_out->flush(); } } pepper-0.3.3/src/logger.h000066400000000000000000000057211263211400600152270ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: logger.h * Logging facility class (interface) */ #ifndef LOGGER_H_ #define LOGGER_H_ #include #include "syslib/parallel.h" // Easy logging #ifndef POS_WIN #define PDEBUG (Logger::debug() << __PRETTY_FUNCTION__ << " [" << __LINE__ << "]: ") #define PTRACE (Logger::trace() << __PRETTY_FUNCTION__ << " [" << __LINE__ << "]: ") #else #define PDEBUG (Logger::debug() << __FUNCTION__ << " [" << __LINE__ << "]: ") #define PTRACE (Logger::trace() << __FUNCTION__ << " [" << __LINE__ << "]: ") #endif enum LogModifier { endl, flush }; class Logger { friend struct SignalHandler; public: enum Level { None = 0, Error, Warn, Status, Info, Debug, Trace, NumLevels }; public: static void setOutput(std::ostream &out, int level = -1); static void setLevel(int level); static int level(); static void flush(); static inline Logger &err() { return s_instances[Error]; } static inline Logger &warn() { return s_instances[Warn]; } static inline Logger &status() { return s_instances[Status]; } static inline Logger &info() { return s_instances[Info]; } static inline Logger &debug() { return s_instances[Debug]; } static inline Logger &trace() { return s_instances[Trace]; } template inline Logger &operator<<(const T &s) { if (m_level > s_level) return *this; s_mutex.lock(); *m_out << s; s_mutex.unlock(); return *this; } inline Logger &operator<<(const char *s) { if (m_level > s_level) return *this; s_mutex.lock(); *m_out << (s ? s : "(null)"); s_mutex.unlock(); return *this; } inline Logger &operator<<(std::ios_base& (* pf)(std::ios_base &)) { if (m_level > s_level) return *this; s_mutex.lock(); *m_out << pf; s_mutex.unlock(); return *this; } inline Logger &operator<<(std::ios& (* pf)(std::ios &)) { if (m_level > s_level) return *this; s_mutex.lock(); *m_out << pf; s_mutex.unlock(); return *this; } inline Logger &operator<<(std::ostream& (* pf)(std::ostream &)) { if (m_level > s_level) return *this; s_mutex.lock(); *m_out << pf; s_mutex.unlock(); return *this; } inline Logger &operator<<(LogModifier mod) { if (m_level > s_level) return *this; s_mutex.lock(); switch (mod) { case endl: *m_out << std::endl; break; case ::flush: *m_out << std::flush; break; default: break; } s_mutex.unlock(); return *this; } public: Logger(int level, std::ostream &out); private: static inline void unlock() { s_mutex.unlock(); } private: int m_level; std::ostream *m_out; static Logger s_instances[NumLevels]; static int s_level; static sys::parallel::Mutex s_mutex; }; #endif // LOGGER_H_ pepper-0.3.3/src/luahelpers.h000066400000000000000000000165701263211400600161200ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: luahelpersq.h * Helper functions for interacting with the Lua API */ #ifndef LUAHELPERS_H_ #define LUAHELPERS_H_ #include "main.h" #include #include #include #include #include #include "strlib.h" #include "lunar/lunar.h" namespace LuaHelpers { inline void stackdump(lua_State *L, std::ostream &out = std::cout) { int top = lua_gettop(L); out << "Stack size: " << top << std::endl; for (int i = top; i >= 1; i--) { out << " -" << (top-i+1) << " "; int t = lua_type(L, i); switch (t) { case LUA_TSTRING: out << "string: '" << lua_tostring(L, i) << "'"; break; case LUA_TBOOLEAN: out << "boolean: " << (lua_toboolean(L, i) ? "true" : "false"); break; case LUA_TNUMBER: out << "number: " << lua_tonumber(L, i); break; default: out << lua_typename(L, t); break; } out << std::endl; } } inline int push(lua_State *L, int i) { lua_pushinteger(L, i); return 1; } inline int push(lua_State *L, int64_t i) { lua_pushinteger(L, i); return 1; } inline int push(lua_State *L, uint64_t i) { lua_pushnumber(L, i); return 1; } inline int push(lua_State *L, double i) { lua_pushnumber(L, i); return 1; } inline int push(lua_State *L, bool i) { lua_pushboolean(L, (int)i); return 1; } inline int push(lua_State *L, const char *s) { lua_pushstring(L, s); return 1; } inline int push(lua_State *L, const std::string &s) { lua_pushlstring(L, s.c_str(), s.length()); return 1; } inline int push(lua_State *L, int (*f)(lua_State *L)) { lua_pushcfunction(L, f); return 1; } template inline int push(lua_State *L, const std::vector &v) { lua_createtable(L, v.size(), 0); int table = lua_gettop(L); int i = 1; typename std::vector::const_iterator it = v.begin(); while (it != v.end()) { push(L, *it); lua_rawseti(L, table, i++); ++it; } return 1; } template inline int push(lua_State *L, const std::map &m) { lua_createtable(L, m.size(), 0); int table = lua_gettop(L); typename std::map::const_iterator it = m.begin(); while (it != m.end()) { push(L, it->first); push(L, it->second); lua_settable(L, table); ++it; } return 1; } template inline int push(lua_State *L, std::shared_ptr i) { Lunar::push(L, i); return 1; } template inline int push(lua_State *L, T *i, bool gc = false) { Lunar::push(L, i, gc); return 1; } inline int pushNil(lua_State *L) { lua_pushnil(L); return 0; } inline int pushError(lua_State *L, const std::string &e) { // Make sure that the string is passed properly return luaL_error(L, "%s", e.c_str()); } inline int pushError(lua_State *L, const char *what, const char *where) { std::string s(where); s += ": "; s += what; return pushError(L, s); } inline int topi(lua_State *L, int index = -1) { return luaL_checkinteger(L, index); } inline bool topb(lua_State *L, int index = -1) { luaL_checktype(L, index, LUA_TBOOLEAN); return (bool)lua_toboolean(L, index); } inline double topd(lua_State *L, int index = -1) { return luaL_checknumber(L, index); } inline std::string tops(lua_State *L, int index = -1) { return luaL_checkstring(L, index); } template inline T *topl(lua_State *L, int index = -1) { return Lunar::check(L, index); } inline int popi(lua_State *L) { int i = topi(L, -1); lua_pop(L, 1); return i; } inline bool popb(lua_State *L) { bool b = topb(L, -1); lua_pop(L, 1); return b; } inline double popd(lua_State *L) { double d = topd(L, -1); lua_pop(L, 1); return d; } inline std::string pops(lua_State *L) { std::string s = tops(L, -1); lua_pop(L, 1); return s; } template inline T* popl(lua_State *L) { T *t = topl(L); lua_pop(L, 1); return t; } inline std::vector topvd(lua_State *L, int index = -1) { std::vector t; luaL_checktype(L, index, LUA_TTABLE); lua_pushvalue(L, index); lua_pushnil(L); while (lua_next(L, -2) != 0) { t.push_back(popd(L)); } lua_pop(L, 1); return t; } inline std::vector topvs(lua_State *L, int index = -1) { std::vector t; luaL_checktype(L, index, LUA_TTABLE); lua_pushvalue(L, index); lua_pushnil(L); while (lua_next(L, -2) != 0) { t.push_back(pops(L)); } lua_pop(L, 1); return t; } inline std::map topms(lua_State *L, int index = -1) { std::map t; luaL_checktype(L, index, LUA_TTABLE); lua_pushvalue(L, index); lua_pushnil(L); while (lua_next(L, -2) != 0) { t[tops(L, -2)] = tops(L, -1); lua_pop(L, 1); } lua_pop(L, 1); return t; } inline std::vector popvd(lua_State *L) { std::vector t = topvd(L, -1); lua_pop(L, 1); return t; } inline std::vector popvs(lua_State *L) { std::vector t = topvs(L, -1); lua_pop(L, 1); return t; } inline std::map popms(lua_State *L) { std::map t = topms(L, -1); lua_pop(L, 1); return t; } template inline void call(lua_State *L, const T1 &arg1, int nresults) { push(L, arg1); lua_call(L, 1, nresults); } template inline void call(lua_State *L, const T1 &arg1, const T2 &arg2, int nresults) { push(L, arg1); push(L, arg2); lua_call(L, 2, nresults); } template inline void call(lua_State *L, const T1 &arg1, const T2 &arg2, const T3 &arg3, int nresults) { push(L, arg1); push(L, arg2); push(L, arg3); lua_call(L, 3, nresults); } template inline void calls(lua_State *L, const std::string &name, const T1 &arg1, const T2 &arg2) { // Find function std::vector parts = str::split(name, "."); lua_getglobal(L, parts[0].c_str()); if (parts.size() > 1) { for (size_t i = 1; i < parts.size(); i++) { if (lua_type(L, -1) != LUA_TTABLE) { throw PEX("No such module"); } lua_getfield(L, -1, parts[i].c_str()); lua_remove(L, -2); } } if (lua_type(L, -1) != LUA_TFUNCTION) { throw PEX("No such function"); } push(L, arg1); push(L, arg2); lua_call(L, 2, 1); } inline int tablevi(lua_State *L, const std::string &key, int def = -1, int index = -1) { luaL_checktype(L, index, LUA_TTABLE); push(L, key); lua_gettable(L, index-1); if (lua_isnil(L, -1)) { lua_pop(L, 1); return def; } return popi(L); } inline bool tablevb(lua_State *L, const std::string &key, bool def = false, int index = -1) { luaL_checktype(L, index, LUA_TTABLE); push(L, key); lua_gettable(L, index-1); if (lua_isnil(L, -1)) { lua_pop(L, 1); return def; } return popb(L); } inline std::string tablevb(lua_State *L, const std::string &key, const std::string &def = std::string(), int index = -1) { luaL_checktype(L, index, LUA_TTABLE); push(L, key); lua_gettable(L, index-1); if (lua_isnil(L, -1)) { lua_pop(L, 1); return def; } return pops(L); } inline size_t tablesize(lua_State *L, int index = -1) { return lua_objlen(L, index); } inline bool hasFunction(lua_State *L, const std::string &name) { lua_getglobal(L, name.c_str()); bool has = (lua_type(L, -1) == LUA_TFUNCTION); lua_pop(L, 1); return has; } } // namespace LuaHelpers #endif // LUAHELPERS_H_ pepper-0.3.3/src/luamodules.cpp000066400000000000000000000141551263211400600164560ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: luamodules.h * Extra C modules for the Lua API (implementation) */ #include "main.h" #include #include "cache.h" #include "luahelpers.h" #include "report.h" #include "repository.h" #include "strlib.h" #include "syslib/datetime.h" #include "syslib/fs.h" namespace LuaModules { // "Main" module namespace pepper { // Returns the current report context int current_report(lua_State *L) { if (Report::current() == NULL) { return LuaHelpers::pushNil(L); } return LuaHelpers::push(L, Report::current()); } // Runs another report int run(lua_State *L) { Report r(L); return r.run(L); } // Returns a list of all reachable reports int list_reports(lua_State *L) { // Return only the report paths std::vector > reports = Report::listReports(); std::vector paths(reports.size()); for (size_t i = 0; i < reports.size(); i++) { paths[i] = reports[i].first; } return LuaHelpers::push(L, paths); } // Returns the program version string int version(lua_State *L) { return LuaHelpers::push(L, PACKAGE_VERSION); } // Function table of main functions const struct luaL_reg table[] = { {"current_report", current_report}, {"run", run}, {"list_reports", list_reports}, {"version", version}, {NULL, NULL} }; } // namespace pepper // Utility functions namespace utils { // Custom fclose() handler for lua file handles int fclose(lua_State *L) { FILE **p = (FILE **)lua_touserdata(L, 1); int rc = fclose(*p); if (rc == 0) *p = NULL; return 1; } // Generates a temporary file, and returns a file handle as well as the file name int mkstemp(lua_State *L) { std::string templ; if (lua_gettop(L) > 0) { templ = LuaHelpers::pops(L); } FILE **pf = (FILE **)lua_newuserdata(L, sizeof *pf); *pf = 0; luaL_getmetatable(L, LUA_FILEHANDLE); lua_setmetatable(L, -2); // Register custom __close() function // (From lua posix module by Luiz Henrique de Figueiredo) lua_getfield(L, LUA_REGISTRYINDEX, "PEPPER_UTILS_FILE"); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_pushvalue(L, -1); lua_pushcfunction(L, fclose); lua_setfield(L, -2, "__close"); lua_setfield(L, LUA_REGISTRYINDEX, "PEPPER_UTILS_FILE"); } lua_setfenv(L, -2); // Gemerate the file std::string filename; try { *pf = sys::fs::mkstemp(&filename, templ); } catch (const PepperException &ex) { return LuaHelpers::pushError(L, ex.what(), ex.where()); } LuaHelpers::push(L, filename); return 2; } // Removes a file int unlink(lua_State *L) { bool recurse = false; if (lua_gettop(L) > 1) { recurse = LuaHelpers::popb(L); } try { if (recurse) { sys::fs::unlink(LuaHelpers::tops(L).c_str()); } else { sys::fs::unlinkr(LuaHelpers::tops(L).c_str()); } } catch (const std::exception &ex) { return LuaHelpers::pushError(L, ex.what()); } return 0; } // Splits a string int split(lua_State *L) { std::string pattern = LuaHelpers::pops(L); std::string string = LuaHelpers::pops(L); return LuaHelpers::push(L, str::split(string, pattern)); } // Wrapper for strptime int strptime(lua_State *L) { std::string format = LuaHelpers::pops(L); std::string str = LuaHelpers::pops(L); int64_t time; try { time = sys::datetime::ptime(str, format); } catch (const std::exception &ex) { return LuaHelpers::pushError(L, ex.what()); } return LuaHelpers::push(L, time); } // Wrapper for dirname() int dirname(lua_State *L) { return LuaHelpers::push(L, sys::fs::dirname(LuaHelpers::pops(L))); } // Wrapper for basename() int basename(lua_State *L) { return LuaHelpers::push(L, sys::fs::basename(LuaHelpers::pops(L))); } // Function table of the utils library const struct luaL_reg table[] = { {"mkstemp", mkstemp}, {"unlink", unlink}, {"split", split}, {"strptime", strptime}, {"dirname", dirname}, {"basename", basename}, {NULL, NULL} }; } // namespace utils // Internal, undocumented functions namespace internal { // Runs a cache check for the given repository int check_cache(lua_State *L) { bool force = false; if (lua_gettop(L) != 1 && lua_gettop(L) != 2) { return LuaHelpers::pushError(L, "Invalid number of arguments (1 or 2 expected)"); } if (lua_gettop(L) > 1) { force = lua_type(L, 0) != LUA_TNIL; lua_pop(L, 1); } Repository *repo = LuaHelpers::popl(L); AbstractCache *cache = dynamic_cast(repo->backend()); if (cache == NULL) { return LuaHelpers::pushError(L, "No active cache found"); } try { cache->check(force); } catch (const PepperException &ex) { return LuaHelpers::pushError(L, str::printf("Error checking cache: %s: %s", ex.where(), ex.what())); } catch (const std::exception &ex) { return LuaHelpers::pushError(L, str::printf("Error checking cache: %s", ex.what())); } return LuaHelpers::pushNil(L); } // Lua wrapper for sys::datetime::Watch class Watch : public sys::datetime::Watch { public: Watch(lua_State *) : sys::datetime::Watch() { } int start(lua_State *) { sys::datetime::Watch::start(); return 0; } int elapsed(lua_State *L) { return LuaHelpers::push(L, sys::datetime::Watch::elapsed()); } int elapsedMSecs(lua_State *L) { return LuaHelpers::push(L, sys::datetime::Watch::elapsedMSecs()); } static const char className[]; static Lunar::RegType methods[]; }; const char Watch::className[] = "watch"; Lunar::RegType Watch::methods[] = { LUNAR_DECLARE_METHOD(Watch, start), LUNAR_DECLARE_METHOD(Watch, elapsed), LUNAR_DECLARE_METHOD(Watch, elapsedMSecs), {0,0} }; // Function table of internal functions const struct luaL_reg table[] = { {"check_cache", check_cache}, {NULL, NULL} }; } // namespace internal // Registers all modules in the given Lua context void registerModules(lua_State *L) { luaL_register(L, "pepper", pepper::table); luaL_register(L, "pepper.utils", utils::table); luaL_register(L, "pepper.internal", internal::table); Lunar::Register(L, "pepper"); } } // namespace LuaModules pepper-0.3.3/src/luamodules.h000066400000000000000000000010121263211400600161070ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: luamodules.h * Extra C modules for the Lua API (interface) */ #ifndef LUAMODULES_H_ #define LUAMODULES_H_ struct lua_State *L; namespace LuaModules { void registerModules(lua_State *L); }; #endif // LUAMODULES_H_ pepper-0.3.3/src/main.cpp000066400000000000000000000161561263211400600152330ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: main.cpp * Program entry point */ #include "main.h" #include #include #include #if defined(POS_LINUX) && defined(DEBUG) #include #endif #include "backend.h" #include "abstractcache.h" #include "logger.h" #include "options.h" #include "report.h" #ifdef USE_LDBCACHE #include "ldbcache.h" #else #include "cache.h" #endif #include "syslib/sigblock.h" // Signal handler struct SignalHandler : public sys::sigblock::Handler { SignalHandler(AbstractCache *cache = NULL) : cache(cache) { } void operator()(int signum) { // This is very unclean, but functions called from here should not be // blocked by logging functions. And this is more useful than turning // logging off. Logger::unlock(); if (cache) { Logger::status() << "Catched signal " << signum << ", flushing cache" << endl; cache->flush(); } } AbstractCache *cache; }; // Prints a short footer for help screens and listings static void printFooter() { std::cout << std::endl; std::cout << "Report bugs to " << "<" << PACKAGE_BUGREPORT ">" << std::endl; } // Prints program usage information static void printHelp(const Options &opts) { std::cout << "USAGE: " << PACKAGE_NAME << " [options] [report options] [repository]" << std::endl << std::endl; std::cout << "Main options:" << std::endl; Options::printHelp(); if (!opts.repository().empty() || !opts.forcedBackend().empty()) { std::cout << std::endl; try { Backend *backend = Backend::backendFor(opts); if (backend == NULL) { throw PEX("No backend found"); } std::cout << "Options for the " << backend->name() << " backend:" << std::endl; backend->printHelp(); delete backend; } catch (const std::exception &ex) { std::cout << "Sorry, unable to find a backend for '" << opts.repository() << "'" << std::endl; } } if (!opts.report().empty()) { std::cout << std::endl; try { Report r(opts.report()); r.printHelp(); } catch (const PepperException &ex) { std::cerr << ex.where() << ": " << ex.what() << std::endl; return; } } printFooter(); } // Prints the program version static void printVersion() { std::cout << PACKAGE_NAME << " " << PACKAGE_VERSION << std::endl; std::cout << "Copyright (C) 2010-present " << "Jonas Gehring <" << PACKAGE_BUGREPORT << ">" << std::endl; std::cout << "Released under the GNU General Public License." << std::endl; } // Configures the global logging streams for the given options static void setupLogger(std::vector *streams, const Options &) { // Note that the log level has already been set by Options::parse() #ifdef DEBUG // In debug mode, write log data to files if the log level is not high enough std::string files[Logger::NumLevels] = {"", "error.out", "warn.out", "status.out", "info.out", "debug.out", "trace.out"}; for (int i = Logger::level()+1; i < Logger::NumLevels; i++) { if (!files[i].empty()) { std::ofstream *out = new std::ofstream(); out->open(files[i].c_str(), std::ios::out); assert(out->good()); streams->push_back(out); Logger::setOutput(*out, i); } } // Turn log level to maximum Logger::setLevel(Logger::NumLevels); #else (void)streams; // No compiler warnings, please #endif } // Runs the program according to the given actions int start(const Options &opts) { // Print requested help screens or listings if (opts.helpRequested()) { printHelp(opts); return EXIT_SUCCESS; } else if (opts.versionRequested()) { printVersion(); return EXIT_SUCCESS; } else if (opts.backendListRequested()) { Backend::listBackends(); printFooter(); return EXIT_SUCCESS; } else if (opts.reportListRequested()) { Report::printReportListing(); printFooter(); return EXIT_SUCCESS; } else if (opts.repository().empty() || opts.report().empty()) { printHelp(opts); return EXIT_FAILURE; } // Setup backend Backend *backend; try { backend = Backend::backendFor(opts); if (backend == NULL) { std::cerr << "Error: No backend found for url: " << opts.repository() << std::endl; return EXIT_FAILURE; } } catch (const PepperException &ex) { std::cerr << "Error detecting repository type: " << ex.where() << ": " << ex.what() << std::endl; Logger::flush(); return EXIT_FAILURE; } SignalHandler sighandler; AbstractCache *cache = NULL; try { if (opts.useCache()) { backend->init(); #ifdef USE_LDBCACHE cache = new LdbCache(backend, opts); #else cache = new Cache(backend, opts); #endif sighandler.cache = cache; cache->init(); } else { backend->init(); } } catch (const PepperException &ex) { std::cerr << "Error initializing backend: " << ex.where() << ": " << ex.what() << std::endl; Logger::flush(); return EXIT_FAILURE; } int signums[] = {SIGINT, SIGTERM}; sys::sigblock::block(2, signums, &sighandler); sys::sigblock::ignore(SIGPIPE); int ret; try { Report r(opts.report(), (cache ? cache : backend)); ret = r.run(); } catch (const PepperException &ex) { std::cerr << "Received exception while running report:" << std::endl; std::cerr << " what(): " << ex.what() << std::endl; std::cerr << " where(): " << ex.where() << std::endl; std::cerr << " trace(): " << ex.trace() << std::endl; ret = EXIT_FAILURE; } catch (const std::exception &ex) { std::cerr << "Received exception while running report:" << std::endl; std::cerr << " what(): " << ex.what() << std::endl; ret = EXIT_FAILURE; } delete cache; // This will also flush the cache delete backend; return ret; } // Program entry point int main(int argc, char **argv) { #ifdef WIN32 // On windows, we need to change the mode for stdout to binary to avoid // newlines being automatically translated to CRLF. _setmode(_fileno(stdout), _O_BINARY); #endif // WIN32 #if defined(POS_LINUX) && defined(DEBUG) // Enable core dumps { struct rlimit rl; rl.rlim_cur = RLIM_INFINITY; rl.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_CORE, &rl); } #endif Options opts; try { opts.parse(argc, argv); } catch (const std::exception &ex) { std::cerr << "Error parsing arguments: " << ex.what() << std::endl; std::cerr << "Run with --help for usage information" << std::endl; return EXIT_FAILURE; } std::vector streams; setupLogger(&streams, opts); int ret; try { ret = start(opts); } catch (const PepperException &ex) { std::cerr << "Received unhandled exception:" << std::endl; std::cerr << " what(): " << ex.what() << std::endl; std::cerr << " where(): " << ex.where() << std::endl; std::cerr << " trace(): " << ex.trace() << std::endl; ret = EXIT_FAILURE; } catch (const std::exception &ex) { std::cerr << "Received unhandled exception:" << std::endl; std::cerr << " what(): " << ex.what() << std::endl; ret = EXIT_FAILURE; } Logger::flush(); // Close log files for (unsigned int i = 0; i < streams.size(); i++) { streams[i]->close(); delete streams[i]; } return ret; } pepper-0.3.3/src/main.h000066400000000000000000000024121263211400600146660ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: main.h * Global header file */ #ifndef MAIN_H_ #define MAIN_H_ #include "config.h" // Operating system detection (mostly from Qt, qglobal.h) #if defined(__APPLE__) && (defined(__GNUC__) || defined(__xlC__) || defined(__xlc__)) #define POS_DARWIN #define POS_BSD #elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) #define POS_BSD #elif defined(__linux__) || defined(__linux) #define POS_LINUX #elif defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) #define POS_WIN #endif // Private variables can be declared like this to enable easy unit testing #ifdef PEPPER_UNIT_TESTS #define PEPPER_PVARS public #define PEPPER_PROTVARS public #else #define PEPPER_PVARS private #define PEPPER_PROTVARS protected #endif // Standard integer types #include // Custom exception class #include "pex.h" #endif // MAIN_H_ pepper-0.3.3/src/options.cpp000066400000000000000000000154561263211400600160040ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: options.cpp * Command-line option parsing */ #include "main.h" #include #include #include "logger.h" #include "strlib.h" #include "syslib/fs.h" #include "options.h" // Constructor Options::Options() { reset(); } // Option parsing void Options::parse(int argc, char **argv) { // Convert to std::string for convenience, skip program name std::vector args; for (int i = 1; i < argc; i++) { args.push_back(argv[i]); } parse(args); } // Queries bool Options::helpRequested() const { return (value("help") == "true"); } bool Options::versionRequested() const { return (value("version") == "true"); } bool Options::backendListRequested() const { return (value("list_backends") == "true"); } bool Options::reportListRequested() const { return (value("list_reports") == "true"); } bool Options::useCache() const { return (value("cache") == "true"); } std::string Options::cacheDir() const { return value("cache_dir"); } std::string Options::forcedBackend() const { return value("backend"); } std::string Options::repository() const { return value("repository"); } std::string Options::value(const std::string &key, const std::string &defvalue) const { std::map::const_iterator it = m_options.find(key); if (it != m_options.end()) { return it->second; } return defvalue; } std::map Options::options() const { return m_options; } std::string Options::report() const { return value("report"); } std::map Options::reportOptions() const { return m_reportOptions; } // Pretty-prints a help screen option void Options::print(const std::string &option, const std::string &text, std::ostream &out) { const size_t offset = 34; const size_t textlen = 78 - offset; out << " " << option; if (option.length() < offset-4) { for (size_t i = option.length(); i < offset-2; i++) { out << " "; } } else { out << std::endl; for (size_t i = 0; i < offset; i++) { out << " "; } } if (text.length() < textlen) { out << text << std::endl; return; } // Word-wrap text std::vector words = str::split(text, " "); int pos = 0; for (size_t j = 0; j < words.size(); j++) { if (pos + words[j].length() > textlen-1) { out << std::endl; for (size_t i = 0; i < offset; i++) { out << " "; } pos = 0; } out << words[j] << " "; pos += words[j].length() + 1; } out << std::endl; } // Prints the main program options void Options::printHelp(std::ostream &out) { print("-h, --help, -?", "Print basic usage information", out); print("--version", "Print version information", out); print("-v, --verbose", "Increase verbosity", out); print("-q, --quiet", "Set verbosity to minimum", out); print("-bARG, --backend=ARG", "Force usage of backend named ARG", out); print("--no-cache", "Disable revision cache usage", out); out << std::endl; print("--list-reports", "List report scrtips in search paths", out); print("--list-backends", "List available backends", out); } // Resets the options to default values void Options::reset() { m_options.clear(); m_reportOptions.clear(); m_options["repository"] = sys::fs::cwd(); m_options["cache"] = "true"; #ifdef DEBUG Logger::setLevel(Logger::Info); #else Logger::setLevel(Logger::Status); #endif // TODO: Where on Windows? if (char *cachedir = getenv("PEPPER_CACHEDIR")) { m_options["cache_dir"] = std::string(cachedir); } else { m_options["cache_dir"] = str::printf("%s/.%s/cache", getenv("HOME"), PACKAGE_NAME, "cache"); } PDEBUG << "Default cache dir set to " << m_options["cache_dir"] << endl; } // The actual parsing void Options::parse(const std::vector &args) { struct option_t { const char *flag, *key, *value; } static mainopts[] = { {"-?", "help", "true"}, {"-h", "help", "true"}, {"--help", "help", "true"}, {"--version", "version", "true"}, {"--no-cache", "cache", "false"}, {"--list-backends", "list_backends", "true"}, {"--list-reports", "list_reports", "true"} }; unsigned int i = 0; std::string key, value; bool compat01 = (strncmp(PACKAGE_VERSION, "0.1", 3) == 0); // Parse main options while (i < args.size()) { bool ok = false; for (unsigned int j = 0; j < sizeof(mainopts) / sizeof(option_t); j++) { if (args[i] == mainopts[j].flag) { m_options[mainopts[j].key] = mainopts[j].value; ok = true; break; } } if (!ok) { if (args[i] == "-v" || args[i] == "--verbose") { Logger::setLevel(Logger::level()+1); } else if (args[i] == "-q" || args[i] == "--quiet") { Logger::setLevel(Logger::None); // Compability notes } else if (compat01 && args[i] == "--check-cache") { Logger::warn() << "NOTE: The --check-cache option is deprecated, plase use the cache_check report" << endl; } else if (parseOpt(args[i], &key, &value)) { if (key == "b") { key = "backend"; } m_options[key] = value; } else { m_options["report"] = args[i]; ++i; break; } } ++i; } // Parse report options while (i < args.size()) { bool ok = false; for (unsigned int j = 0; j < sizeof(mainopts) / sizeof(option_t); j++) { if (args[i] == mainopts[j].flag) { m_options[mainopts[j].key] = mainopts[j].value; ok = true; break; } } if (!ok) { if (parseOpt(args[i], &key, &value)) { m_reportOptions[key] = value; } else { break; } } ++i; } // Repository URL if (i < args.size()) { try { m_options["repository"] = sys::fs::makeAbsolute(args[i]); } catch (...) { m_options["repository"] = args[i]; } } // Parse additional options while (i < args.size()) { for (unsigned int j = 0; j < sizeof(mainopts) / sizeof(option_t); j++) { if (args[i] == mainopts[j].flag) { m_options[mainopts[j].key] = mainopts[j].value; } } ++i; } } // Parses a single option bool Options::parseOpt(const std::string &arg, std::string *key, std::string *value) { /* * Valid option syntax: * -f * --flag * -fvalue * --flag=value */ if (!arg.compare(0, 2, "--") && arg.find("=") != std::string::npos && arg.find("=") > 2) { size_t idx = arg.find("="); *key = arg.substr(2, idx - 2); *value = arg.substr(idx+1); return true; } else if (!arg.compare(0, 2, "--")) { *key = arg.substr(2); *value = std::string(); return true; } else if (!arg.compare(0, 1, "-") && arg.length() > 2) { *key = arg.substr(1, 1); *value = arg.substr(2); return true; } else if (!arg.compare(0, 1, "-") && arg.length() == 2) { *key = arg.substr(1); *value = std::string(); return true; } return false; } pepper-0.3.3/src/options.h000066400000000000000000000030011263211400600154300ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: options.h * Command-line option parsing (interface) */ #ifndef OPTIONS_H_ #define OPTIONS_H_ #include "main.h" #include #include #include #include class Options { public: Options(); void parse(int argc, char **argv); bool helpRequested() const; bool versionRequested() const; bool backendListRequested() const; bool reportListRequested() const; bool useCache() const; std::string cacheDir() const; std::string forcedBackend() const; std::string repository() const; std::string value(const std::string &key, const std::string &defvalue = std::string()) const; std::map options() const; std::string report() const; std::map reportOptions() const; static void print(const std::string &option, const std::string &text, std::ostream &out = std::cout); static void printHelp(std::ostream &out = std::cout); private: void reset(); void parse(const std::vector &args); bool parseOpt(const std::string &arg, std::string *key, std::string *value); PEPPER_PVARS: std::map m_options; std::map m_reportOptions; }; #endif // OPTIONS_H_ pepper-0.3.3/src/pex.cpp000066400000000000000000000110641263211400600150740ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: pex.cpp * Custom exception class and macros */ // Enforce XSI-compliant strerror_r() #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE<600 #undef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #include "main.h" #include #include #include #include #if defined(__GLIBC__) || defined(POS_DARWIN) #include #include #endif #include "pex.h" // Constructor PepperException::PepperException(const std::string &str, const char *file, int line, const std::string &trace) throw() : m_str(str), m_trace(trace) { if (file) { snprintf(m_where, sizeof(m_where), "%s:%d", file, line); } else { m_where[0] = 0; } } // Constructor PepperException::PepperException(int code, const char *file, int line, const std::string &trace) throw() : m_trace(trace) { if (file) { snprintf(m_where, sizeof(m_where), "%s:%d", file, line); } else { m_where[0] = 0; } m_str = strerror(code); } // Destructor PepperException::~PepperException() throw() { } // Queries const char *PepperException::what() const throw() { return m_str.c_str(); } const char *PepperException::where() const throw() { return m_where; } const char *PepperException::trace() const throw() { return m_trace.c_str(); } // Returns a formatted stack trace (with g++ only). Note that it should _not_ throw an exception! // Credits go out to Timo Bingmann (http://idlebox.net/2008/0901-stacktrace-demangled/) std::string PepperException::stackTrace() { #if defined(__GLIBC__) || defined(POS_DARWIN) try { std::string str = "Stack trace:\n"; // storage array for stack trace address data void* addrlist[60+1]; // retrieve current stack addresses int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*)); if (addrlen == 0) { str += " \n"; return str; } // resolve addresses into strings containing "filename(function+address)", // this array must be free()-ed char** symbollist = backtrace_symbols(addrlist, addrlen); // allocate string which will be filled with the demangled function name size_t funcnamesize = 256; char *funcname = new char[funcnamesize]; // iterate over the returned symbol lines. skip the first, it is the // address of this function. for (int i = 1; i < addrlen; i++) { char *begin_name = 0, *begin_offset = 0, *end_offset = 0; // find parentheses and +address offset surrounding the mangled name: // ./module(function+0x15c) [0x8048a6d] for (char *p = symbollist[i]; *p; ++p) { if (*p == '(') begin_name = p; else if (*p == '+') begin_offset = p; else if (*p == ')' && begin_offset) { end_offset = p; break; } } if (begin_name && begin_offset && end_offset && begin_name < begin_offset) { *begin_name++ = '\0'; *begin_offset++ = '\0'; *end_offset = '\0'; // mangled name is now in [begin_name, begin_offset) and caller // offset in [begin_offset, end_offset). now apply // __cxa_demangle(): int status; char* ret = abi::__cxa_demangle(begin_name, funcname, &funcnamesize, &status); if (status == 0) { funcname = ret; // use possibly realloc()-ed string str += " "; str += std::string(symbollist[i])+": "+(const char *)funcname+"+"+(const char *)begin_offset; } else { // demangling failed. Output function name as a C function with // no arguments. str += " "; str += std::string(symbollist[i])+": "+(const char *)begin_name+"()+"+(const char *)begin_offset; } } else { // couldn't parse the line? print the whole line. str += std::string(" ")+(const char *)symbollist[i]; } str += '\n'; } delete[] funcname; free(symbollist); return str; } catch (...) { return std::string(); } #else return std::string(); #endif } // Wrapper for strerror_r std::string PepperException::strerror(int code) { std::string str; char buf[512]; #ifdef HAVE_STRERROR_R #ifdef _GNU_SOURCE std::ostringstream s; s << strerror_r(code, buf, sizeof(buf)); s << " (" << code << ")"; str = s.str(); #else if (strerror_r(code, buf, sizeof(buf)) == 0) { std::ostringstream s; s << buf << " (" << code << ")"; str = s.str(); } else { sprintf(buf, "System error code %d", code); str = std::string(buf); } #endif #else sprintf(buf, "System error code %d", code); str = std::string(buf); #endif return str; } pepper-0.3.3/src/pex.h000066400000000000000000000031301263211400600145340ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: pex.h * Custom exception class and macros (interface) */ #ifndef PEX_H_ #define PEX_H_ #include #include #include class PepperException : public std::exception { public: PepperException(const std::string &str, const char *file = NULL, int line = 0, const std::string &trace = std::string()) throw(); PepperException(int code, const char *file = NULL, int line = 0, const std::string &trace = std::string()) throw(); virtual ~PepperException() throw(); virtual const char *what() const throw(); const char *where() const throw(); const char *trace() const throw(); static std::string stackTrace(); static std::string strerror(int code); private: std::string m_str; std::string m_trace; char m_where[512]; }; // Generates an exception with where() information #ifdef DEBUG #define PEX(str) PepperException(str, __FILE__, __LINE__, PepperException::stackTrace()) #define PEX_ERR(c) PepperException(c, __FILE__, __LINE__, PepperException::stackTrace()) #define PEX_ERRNO() PepperException(errno, __FILE__, __LINE__, PepperException::stackTrace()) #else #define PEX(str) PepperException(str, __FILE__, __LINE__) #define PEX_ERR(c) PepperException(c, __FILE__, __LINE__) #define PEX_ERRNO() PepperException(errno, __FILE__, __LINE__) #endif #endif // PEX_H_ pepper-0.3.3/src/plot.cpp000066400000000000000000000400401263211400600152520ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: plot.cpp * Lua plotting interface using gnuplot */ #include "main.h" #include #include #include #include "gnuplot.h" #include "logger.h" #include "luahelpers.h" #include "options.h" #include "report.h" #include "strlib.h" #include "syslib/io.h" #include "syslib/fs.h" #include "plot.h" // Converts from UNIX to Gnuplot epoch static inline int64_t convepoch(int64_t t) { return t - 946684800; } // Gnuplot arguments (-persist only works with the X11 or wxt terminal) static const char *gp_args[] = {NULL}; static const char *gp_args_persist[] = {"-persist", NULL}; // Static variables for the lua bindings const char Plot::className[] = "gnuplot"; Lunar::RegType Plot::methods[] = { LUNAR_DECLARE_METHOD(Plot, cmd), LUNAR_DECLARE_METHOD(Plot, set_output), LUNAR_DECLARE_METHOD(Plot, set_title), LUNAR_DECLARE_METHOD(Plot, set_xrange), LUNAR_DECLARE_METHOD(Plot, set_xrange_time), LUNAR_DECLARE_METHOD(Plot, plot_series), LUNAR_DECLARE_METHOD(Plot, plot_multi_series), LUNAR_DECLARE_METHOD(Plot, plot_histogram), LUNAR_DECLARE_METHOD(Plot, plot_pie), LUNAR_DECLARE_METHOD(Plot, flush), {0,0} }; bool Plot::s_hasX11Term = false; bool Plot::s_detectTerminals = true; // Constructor Plot::Plot(lua_State *L) { m_standardTerminal = "svg"; m_args = gp_args; #if ( defined(unix) || defined(__unix) || defined(__unix__) ) && !defined(__APPLE__) if (getenv("DISPLAY") && sys::io::isterm(stdout) && !Report::current()->outputRedirected()) { m_standardTerminal = "x11"; detectTerminals(); } #endif if (getenv("GNUTERM")) { m_standardTerminal = getenv("GNUTERM"); } if (m_standardTerminal == "x11" && s_hasX11Term) { m_args = gp_args_persist; } try { g = new Gnuplot(m_args, Report::current()->out()); } catch (const PepperException &ex) { LuaHelpers::pushError(L, ex.what(), ex.where()); } } // Destructor Plot::~Plot() { delete g; removeTempfiles(); } // Writes a Gnuplot command int Plot::cmd(lua_State *L) { gcmd(LuaHelpers::pops(L)); return 0; } // Sets the output file name and optionally the terminal type int Plot::set_output(lua_State *L) { std::string file, terminal; int width = 640, height = 480; if (lua_gettop(L) > 4) { return LuaHelpers::pushError(L, str::printf("Invalid number of arguments (expected 1-4, got %d)", lua_gettop(L))); } switch (lua_gettop(L)) { case 4: terminal = LuaHelpers::pops(L); case 3: height = LuaHelpers::popi(L); case 2: width = LuaHelpers::popi(L); default: file = LuaHelpers::pops(L); } if (terminal.empty()) { // Determine terminal type from extension or fall back to SVG size_t pos = file.find_last_of("."); if (pos != std::string::npos) { terminal = file.substr(pos+1); if (terminal.empty()) { terminal = m_standardTerminal; } } else { terminal = m_standardTerminal; } } if (terminal == "ps" || terminal == "eps") { terminal = "postscript eps color enhanced"; } else if (terminal == "jpg") { terminal = "jpeg"; } if (!file.empty()) { gcmd(str::printf("set output \"%s\"", file.c_str())); } else { gcmd(str::printf("set output")); } gcmd(str::printf("set terminal %s size %d,%d", terminal.c_str(), width, height)); return 0; } // Sets the plot title int Plot::set_title(lua_State *L) { std::string title = LuaHelpers::pops(L); gcmd(str::printf("set title \"%s\"", title.c_str())); return 0; } // Sets the xaxis and x2axis range int Plot::set_xrange(lua_State *L) { double end = LuaHelpers::popd(L); double start = LuaHelpers::popd(L); double d = end - start; double range[2]; range[0] = 1000 * floor(double(start) - 0.05 * d) / 1000; range[1] = 1000 * ceil(double(end) + 0.05 * d) / 1000; gcmd(str::printf("set xrange [%f:%f]", range[0], range[1])); gcmd(str::printf("set x2range [%f:%f]", range[0], range[1])); return 0; } // Sets the xaxis and x2axis range int Plot::set_xrange_time(lua_State *L) { int64_t end = LuaHelpers::popi(L); int64_t start = LuaHelpers::popi(L); int64_t d = end - start; int64_t range[2]; range[0] = convepoch(1000 * floor(double(start) - 0.05 * d) / 1000); range[1] = convepoch(1000 * ceil(double(end) + 0.05 * d) / 1000); gcmd(str::printf("set xrange [%lld:%lld]", range[0], range[1])); gcmd(str::printf("set x2range [%lld:%lld]", range[0], range[1])); return 0; } // Plots normal XY series int Plot::plot_series(lua_State *L) { // Validate arguments int index = -1; if (lua_gettop(L) > 4) { return LuaHelpers::pushError(L, str::printf("Invalid number of arguments (expected 2-4, got %d)", lua_gettop(L))); } std::map options; options.insert(std::pair("style", "lines")); switch (lua_gettop(L)) { case 4: { if (lua_type(L, -1) == LUA_TTABLE) { options = LuaHelpers::popms(L); } else { options["style"] = LuaHelpers::pops(L); } } case 3: luaL_checktype(L, index--, LUA_TTABLE); default: luaL_checktype(L, index--, LUA_TTABLE); luaL_checktype(L, index--, LUA_TTABLE); break; } // First, read the keys (at index) ++index; std::vector keys = LuaHelpers::topvd(L, index); // Check data entries size_t nseries = 0; ++index; if (LuaHelpers::tablesize(L, index) != keys.size()) { return LuaHelpers::pushError(L, str::printf("Number of keys and values doesn't match (%d != %d)", LuaHelpers::tablesize(L, index), keys.size())); } lua_pushvalue(L, index); lua_pushnil(L); if (lua_next(L, -2) != 0) { if (lua_type(L, -1) == LUA_TTABLE) { nseries = LuaHelpers::tablesize(L, -1); } else { nseries = 1; } lua_pop(L, 2); } lua_pop(L, 1); // Read titles (if any) ++index; std::vector titles; if (index < 0) { titles = LuaHelpers::topvs(L, index); } std::ostringstream cmd; cmd << "plot "; if (options.find("command") == options.end()) { for (size_t i = 0; i < nseries; i++) { cmd << (i == 0 ? "'-'" : "''") << " using 1:2"; if (titles.size() > i) { cmd << " title \"" << titles[i] << "\""; } else { cmd << " notitle"; } if (options.find("style") != options.end()) { cmd << " with " << options["style"]; } if (i < nseries-1) { cmd << ", "; } } } else { cmd << "'-' " << options["command"]; } PDEBUG << "Running plot with command: " << cmd.str() << endl; gcmd(cmd.str()); // Write data to pipe, separately for each series --index; std::ostringstream ss; for (size_t i = 0; i < nseries; i++) { ss.clear(); // Reset stringstream, but keep buffer ss.seekp(0); lua_pushvalue(L, index); lua_pushnil(L); int j = 0; while (lua_next(L, -2) != 0) { ss << keys[j++] << " "; if (lua_type(L, -1) == LUA_TTABLE) { if (nseries != LuaHelpers::tablesize(L, -1)) { return LuaHelpers::pushError(L, "Inconsistent number of series"); } lua_pushnumber(L, i+1); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TTABLE) { lua_pushnil(L); while (lua_next(L, -2) != 0) { ss << LuaHelpers::popd(L) << " "; } lua_pop(L, 1); } else { ss << LuaHelpers::popd(L); } lua_pop(L, 1); } else { ss << LuaHelpers::popd(L); } ss << "\n"; } lua_pop(L, 1); ss << "e\n"; // Marks end of data g->cmd(ss.str().c_str(), ss.tellp()); } return 0; } // Plots multiple XY series int Plot::plot_multi_series(lua_State *L) { // Validate arguments int index = -1; if (lua_gettop(L) > 4) { return LuaHelpers::pushError(L, str::printf("Invalid number of arguments (expected 2-4, got %d)", lua_gettop(L))); } std::map options; options.insert(std::pair("style", "lines")); switch (lua_gettop(L)) { case 4: { if (lua_type(L, -1) == LUA_TTABLE) { options = LuaHelpers::popms(L); } else { options["style"] = LuaHelpers::pops(L); } } case 3: luaL_checktype(L, index--, LUA_TTABLE); default: luaL_checktype(L, index--, LUA_TTABLE); luaL_checktype(L, index--, LUA_TTABLE); break; } // Read data ++index; size_t nseries = LuaHelpers::tablesize(L, index); std::ostringstream *outs = new std::ostringstream[nseries]; for (size_t i = 0; i < nseries; i++) { std::ostringstream &out = outs[i]; // Read keys lua_rawgeti(L, index, i+1); std::vector keys = LuaHelpers::popvd(L); // Check number of values ++index; lua_rawgeti(L, index, i+1); if (LuaHelpers::tablesize(L) != keys.size()) { return LuaHelpers::pushError(L, str::printf("Number of keys and values doesn't match (%d != %d)", LuaHelpers::tablesize(L, index), keys.size())); } // Avoid copying values via popvd() and read the directly luaL_checktype(L, -1, LUA_TTABLE); lua_pushvalue(L, -1); lua_pushnil(L); size_t j = 0; while (lua_next(L, -2) != 0) { out << keys[j++] << " " << LuaHelpers::popd(L) << std::endl; } lua_pop(L, 2); // Reset index back to keys --index; } // Read titles (if any) index += 2; std::vector titles; if (index < 0) { titles = LuaHelpers::topvs(L, index); } std::ostringstream cmd; cmd << "plot "; for (size_t i = 0; i < nseries; i++) { cmd << "'-' using 1:2"; if (titles.size() > i) { cmd << " title \"" << titles[i] << "\""; } else { cmd << " notitle"; } if (options.find("style") != options.end()) { cmd << " with " << options["style"]; } if (i < nseries-1) { cmd << ", "; } } PDEBUG << "Running plot with command: " << cmd.str() << endl; gcmd(cmd.str()); // Write data for (size_t i = 0; i < nseries; i++) { g->cmd(outs[i].str()); g->cmd("e"); // Marks end of data } delete[] outs; return 0; } // Plots a histogram int Plot::plot_histogram(lua_State *L) { // Validate arguments int index = -1; if (lua_gettop(L) > 4) { return LuaHelpers::pushError(L, str::printf("Invalid number of arguments (expected 2-4, got %d)", lua_gettop(L))); } std::map options; switch (lua_gettop(L)) { case 4: { if (lua_type(L, -1) == LUA_TTABLE) { options = LuaHelpers::popms(L); } else { options["style"] = LuaHelpers::pops(L); } } case 3: luaL_checktype(L, index--, LUA_TTABLE); default: luaL_checktype(L, index--, LUA_TTABLE); luaL_checktype(L, index--, LUA_TTABLE); break; } // First, read the keys (at index) ++index; std::vector keys = LuaHelpers::topvs(L, index); // Check data entries size_t nseries = 0; ++index; if (LuaHelpers::tablesize(L, index) != keys.size()) { return LuaHelpers::pushError(L, str::printf("Number of keys and values doesn't match (%d != %d)", LuaHelpers::tablesize(L, index), keys.size())); } lua_pushvalue(L, index); lua_pushnil(L); if (lua_next(L, -2) != 0) { if (lua_type(L, -1) == LUA_TTABLE) { nseries = LuaHelpers::tablesize(L, -1); } else { nseries = 1; } lua_pop(L, 2); } lua_pop(L, 1); // Read titles (if any) ++index; std::vector titles; if (index < 0) { titles = LuaHelpers::topvs(L, index); } gcmd("set style data histogram"); std::ostringstream cmd; cmd << "plot "; for (size_t i = 0; i < nseries; i++) { cmd << (i == 0 ? "'-'" : "''") << " using 2:xtic(1)"; if (titles.size() > i) { cmd << " title \"" << titles[i] << "\""; } else { cmd << " notitle"; } if (options.find("style") != options.end()) { cmd << " with " << options["style"]; } if (i < nseries-1) { cmd << ", "; } } PDEBUG << "Running plot with command: " << cmd.str() << endl; gcmd(cmd.str()); // Write data to pipe, separately for each series --index; std::ostringstream ss; for (size_t i = 0; i < nseries; i++) { ss.clear(); // Reset stringstream, but keep buffer ss.seekp(0); lua_pushvalue(L, index); lua_pushnil(L); int j = 0; while (lua_next(L, -2) != 0) { ss << '"' << keys[j++] << "\" "; if (lua_type(L, -1) == LUA_TTABLE) { if (nseries != LuaHelpers::tablesize(L, -1)) { return LuaHelpers::pushError(L, "Inconsistent number of series"); } lua_pushnumber(L, i+1); lua_gettable(L, -2); if (lua_type(L, -1) == LUA_TTABLE) { lua_pushnil(L); while (lua_next(L, -2) != 0) { ss << LuaHelpers::popd(L) << " "; } lua_pop(L, 1); } else { ss << LuaHelpers::popd(L); } lua_pop(L, 1); } else { ss << LuaHelpers::popd(L); } ss << "\n"; } lua_pop(L, 1); ss << "e\n"; // Marks end of data g->cmd(ss.str().c_str(), ss.tellp()); } return 0; } // Plots a pie chart int Plot::plot_pie(lua_State *L) { // Validate arguments if (lua_gettop(L) != 2) { return LuaHelpers::pushError(L, str::printf("Invalid number of arguments (expected 2, got %d)", lua_gettop(L))); } std::vector values; std::vector keys; switch (lua_gettop(L)) { default: values = LuaHelpers::popvd(L); keys = LuaHelpers::popvs(L); break; } if (keys.size() != values.size()) { return LuaHelpers::pushError(L, str::printf("Argument dimensions don't match (%d != %d)", keys.size(), values.size())); } // Prepare data, i.e. accumulate values to get [from,to] intervals size_t n = keys.size(); for (size_t i = 1; i < n; i++) { values[i] += values[i-1]; } // Open stream to data file std::ostringstream cmd; // First, print plot-specific data and helper functions to the file cmd << "set parametric\n" "set trange [0:1]\n" "set xrange [-1:1]\n" "set yrange [-1:1]\n" "set offsets 0.25,0.25,0.25,0.25\n" // For percentage labels "unset border\n" "unset tics\n" "p2rad(x)=pi * (x/0.5)\n" "fs(t,s,e)=sin(t * p2rad((e-s)) + p2rad(s))\n" "fc(t,s,e)=cos(t * p2rad((e-s)) + p2rad(s))" << std::endl; // Print percentage labels double last = 0; for (size_t i = 0; i < n; i++) { double m = last + (values[i] - last) / 2.0; cmd << "set label " << i+1 << " \"" << int(100 * (values[i] - last) + 0.5) << "%\" at first " << "1.05*cos(p2rad(" << m << ")),1.05*sin(p2rad(" << m << ")) front " << (m > 0.25 && m < 0.76 ? "right" : "left") << std::endl; last = values[i]; } // Plot arcs cmd << "plot \\" << std::endl; last = 0; for (size_t i = 0; i < n; i++) { cmd << " fc(t," << last << "," << values[i] << "),fs(t," << last << "," << values[i] << ") w filledcu xy=0,0 title " << "\"" << keys[i] << "\"" << (i != n-1 ? ",\\" : "") << std::endl; last = values[i]; } gcmd(cmd.str()); return 0; } // Closes and reopens the Gnuplot connection. This will force // plotting to finish and temporary files to be closed int Plot::flush(lua_State *L) { try { delete g; g = new Gnuplot(m_args, Report::current()->out()); } catch (const PepperException &ex) { return LuaHelpers::pushError(L, ex.what(), ex.where()); } return 0; } // Sends a command to GNUPlot (and logs it) void Plot::gcmd(const std::string &c) { PTRACE << c << endl; g->cmd(c); } // Creates a temporary file std::string Plot::tempfile(std::ofstream &out) { std::string path; sys::fs::mkstemp(&path); out.open(path.c_str()); if (out.bad()) { throw PEX(str::printf("Unable to open temporary file '%s'", path.c_str())); } m_tempfiles.push_back(path); return path; } // Removes all temporary files void Plot::removeTempfiles() { for (size_t i = 0; i < m_tempfiles.size(); i++) { sys::fs::unlink(m_tempfiles[i]); } m_tempfiles.clear(); } // Detects Gnuplot terminals, and checks wheter the X11 terminal is available void Plot::detectTerminals() { if (!s_detectTerminals) { // Run only once return; } std::string terms; try { // Set dummy pager for terminal listing char *oldPager = getenv("PAGER"); if (oldPager) { oldPager = strdup(oldPager); } setenv("PAGER", "cat", 1); int ret; std::string path = sys::fs::which("gnuplot"); terms = sys::io::exec(&ret, path.c_str(), "-e", "set terminal; quit"); if (ret != 0) { throw PEX(terms); } if (!oldPager) { unsetenv("PAGER"); } else { setenv("PAGER", oldPager, 1); free(oldPager); } } catch (const std::exception &ex) { Logger::info() << "Can't query list of supported Gnuplot terminals (" << ex.what() << ")" << std::endl; } s_hasX11Term = (terms.find("x11") != std::string::npos); s_detectTerminals = false; } pepper-0.3.3/src/plot.h000066400000000000000000000024231263211400600147220ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: plot.h * Lua plotting interface using gnuplot (interface) */ #ifndef PLOT_H_ #define PLOT_H_ #include #include #include "lunar/lunar.h" class Gnuplot; class Plot { public: Plot(lua_State *L); ~Plot(); int cmd(lua_State *L); int set_output(lua_State *L); int set_title(lua_State *L); int set_xrange(lua_State *L); int set_xrange_time(lua_State *L); int plot_series(lua_State *L); int plot_multi_series(lua_State *L); int plot_histogram(lua_State *L); int plot_pie(lua_State *L); int flush(lua_State *L); public: static const char className[]; static Lunar::RegType methods[]; private: void gcmd(const std::string &c); std::string tempfile(std::ofstream &out); void removeTempfiles(); static void detectTerminals(); private: Gnuplot *g; std::vector m_tempfiles; std::string m_standardTerminal; const char **m_args; static bool s_hasX11Term; static bool s_detectTerminals; }; #endif // PLOT_H_ pepper-0.3.3/src/report.cpp000066400000000000000000000371651263211400600156250ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: report.cpp * Report script context */ #include "main.h" #include #include #include #include #include "backend.h" #include "diffstat.h" #include "logger.h" #include "luahelpers.h" #include "luamodules.h" #include "options.h" #include "repository.h" #include "revision.h" #include "revisioniterator.h" #include "strlib.h" #include "tag.h" #ifdef USE_GNUPLOT #include "plot.h" #endif #include "syslib/fs.h" #include "report.h" #define PEPPER_MAX_STACK_SIZE 64 /* * Stack helper functions */ namespace { // Report entry function names const char *funcs[] = {"run", "main", NULL}; // Checks if the given report is "executable", i.e. contains a main() function bool isExecutable(lua_State *L, std::string *entryPoint = NULL) { int i = 0; while (funcs[i] != NULL && !LuaHelpers::hasFunction(L, funcs[i])) { ++i; } if (funcs[i] != NULL && entryPoint != NULL) { *entryPoint = funcs[i]; } return funcs[i] != NULL; } // Prints a backtrace if Lua is panicking int atpanic(lua_State *L) { std::cerr << "Lua PANIC: " << LuaHelpers::tops(L) << std::endl; #ifdef DEBUG std::cerr << PepperException::stackTrace(); #endif return 0; } // Returns all paths that may contains reports std::vector reportDirs() { std::vector dirs; // Read the PEPPER_REPORTS environment variable char *env = getenv("PEPPER_REPORTS"); if (env != NULL) { #ifdef POS_WIN std::vector parts = str::split(env, ";"); #else std::vector parts = str::split(env, ":"); #endif for (size_t i = 0; i < parts.size(); i++) { dirs.push_back(parts[i]); } } #ifdef DATADIR dirs.push_back(DATADIR); #endif return dirs; } // Returns the full path to the given script std::string findScript(const std::string &script) { if (sys::fs::fileExists(script)) { return script; } if (sys::fs::fileExists(script + ".lua")) { return script + ".lua"; } std::vector dirs = reportDirs(); for (size_t i = 0; i < dirs.size(); i++) { std::string path = dirs[i] + "/" + script; if (sys::fs::fileExists(path)) { return path; } path += ".lua"; if (sys::fs::fileExists(path)) { return path; } } return script; } // Sets up the lua context lua_State *setupLua() { // Setup lua context lua_State *L = lua_open(); luaL_openlibs(L); lua_atpanic(L, atpanic); // Register extra modules functions LuaModules::registerModules(L); // Register binding classes Lunar::Register(L, "pepper"); Lunar::Register(L, "pepper"); Lunar::Register(L, "pepper"); Lunar::Register(L, "pepper"); Lunar::Register(L, "pepper"); Lunar::Register(L, "pepper"); #ifdef USE_GNUPLOT Lunar::Register(L, "pepper"); #endif // Setup package path to include built-in modules lua_getglobal(L, "package"); lua_getfield(L, -1, "path"); std::string path = LuaHelpers::pops(L); std::vector dirs = reportDirs(); for (size_t i = 0; i < dirs.size(); i++) { path += ";"; path += dirs[i] + "/?.lua"; } LuaHelpers::push(L, path); lua_setfield(L, -2, "path"); lua_pop(L, 1); PTRACE << "Lua package path has been set to " << path << endl; // Setup (deprecated) meta table luaL_newmetatable(L, "meta"); lua_setglobal(L, "meta"); return L; } // Opens a lua script and returns its entry point std::string loadReport(lua_State *L, const std::string &path) { // Check script syntax by loading the file if (luaL_loadfile(L, path.c_str()) != 0) { throw PEX(lua_tostring(L, -1)); } // Execute the main chunk if (lua_pcall(L, 0, LUA_MULTRET, 0) != 0) { throw PEX(lua_tostring(L, -1)); } std::string main; if (!isExecutable(L, &main)) { throw PEX("Not executable (no run function)"); } return main; } // Wrapper for print(), mostly from VIM - Vi IMproved by Bram Moolenaar int printWrapper(lua_State *L) { std::string str; int n = lua_gettop(L); lua_getglobal(L, "tostring"); for (int i = 1; i <= n; i++) { lua_pushvalue(L, -1); /* tostring */ lua_pushvalue(L, i); /* arg */ lua_call(L, 1, 1); size_t l; const char *s = lua_tolstring(L, -1, &l); if (s == NULL) { return luaL_error(L, "cannot convert to string"); } if (i > 1) { str += '\t'; } str += std::string(s, l); lua_pop(L, 1); } Report *c = Report::current(); (c == NULL ? std::cout : c->out()) << str << std::endl; return 0; } } // anonymous namespace /* * Report class implementation */ // Static report stack std::stack Report::s_stack; // Constructor Report::Report(const std::string &script, Backend *backend) : m_repo(NULL), m_script(script), m_out(&std::cout), m_metaDataRead(false) { if (backend) { m_repo = new Repository(backend); m_options = backend->options().reportOptions(); } } // Constructor Report::Report(const std::string &script, const std::map &options, Backend *backend) : m_repo(NULL), m_script(script), m_options(options), m_out(&std::cout), m_metaDataRead(false) { if (backend) { m_repo = new Repository(backend); } } // Destructor Report::~Report() { delete m_repo; } // Runs the report, printing error messages to the given stream int Report::run(std::ostream &out, std::ostream &err) { if (s_stack.size() == PEPPER_MAX_STACK_SIZE) { err << "Error running report: maximum stack size ("; err << PEPPER_MAX_STACK_SIZE << ") exceeded" << std::endl; return EXIT_FAILURE; } // Push this context on top of the stack std::string path = findScript(m_script); PDEBUG << "Report path resolved: " << m_script << " -> " << path << endl; PTRACE << "Pushing report context for " << path << endl; s_stack.push(this); PDEBUG << "Stack size is " << s_stack.size() << endl; std::ostream *prevout = m_out; m_out = &out; // Ensure the backend is ready m_repo->backend()->open(); lua_State *L = setupLua(); // Wrap print() function to use custom output stream lua_pushcfunction(L, printWrapper); lua_setglobal(L, "print"); // Run the script std::string main; int ret = EXIT_SUCCESS; try { main = loadReport(L, path); } catch (const std::exception &ex) { err << "Error opening report: " << ex.what() << std::endl; goto error; } // Call the report function, with the current report context as an argument lua_getglobal(L, main.c_str()); LuaHelpers::push(L, this); if (lua_pcall(L, 1, 1, 0) != 0) { err << "Error running report: " << lua_tostring(L, -1) << std::endl; goto error; } goto cleanup; error: ret = EXIT_FAILURE; cleanup: lua_gc(L, LUA_GCCOLLECT, 0); lua_close(L); // Inform backend that the report is done m_repo->backend()->close(); PTRACE << "Popping report context for " << path << endl; s_stack.pop(); m_out = prevout; return ret; } // Pretty-prints report options void Report::printHelp() { if (!m_metaDataRead) { readMetaData(); } std::cout << "Options for report '" << m_metaData.name << "':" << std::endl; for (size_t i = 0; i < m_metaData.options.size(); i++) { Options::print(m_metaData.options[i].synopsis, m_metaData.options[i].description); } } // Returns the report's meta data Report::MetaData Report::metaData() { if (!m_metaDataRead) { readMetaData(); } return m_metaData; } // Checks if the report is valid (e.g., executable) bool Report::valid() { std::string path = findScript(m_script); lua_State *L = setupLua(); bool valid = true; try { loadReport(L, path); } catch (const std::exception &ex) { PDEBUG << "Invalid report: " << ex.what() << endl; valid = false; } // Clean up lua_gc(L, LUA_GCCOLLECT, 0); lua_close(L); return valid; } // Returns the report output stream std::ostream &Report::out() const { return *m_out; } // Returns whether the standard output is redirected bool Report::outputRedirected() const { return (m_out != &std::cout); } // Lists all report scripts and their descriptions std::vector > Report::listReports() { std::vector > reports; std::vector dirs = reportDirs(); for (size_t j = 0; j < dirs.size(); j++) { std::string builtin = dirs[j]; if (!sys::fs::dirExists(builtin)) { continue; } std::vector contents = sys::fs::ls(builtin); std::sort(contents.begin(), contents.end()); for (size_t i = 0; i < contents.size(); i++) { if (contents[i].empty() || contents[i][contents[i].length()-1] == '~' || !sys::fs::fileExists(builtin + "/" + contents[i])) { continue; } std::string path = builtin + "/" + contents[i]; Report report(path); if (report.valid()) { reports.push_back(std::pair(path, report.metaData().description)); } } } return reports; } // Prints a listing of all report scripts to stdout void Report::printReportListing(std::ostream &out) { std::vector > reports = listReports(); std::string dirname; for (size_t i = 0; i < reports.size(); i++) { std::string nextdir = sys::fs::dirname(reports[i].first); if (dirname != nextdir) { dirname = nextdir; if (i < reports.size()-1) { out << std::endl; } out << "Available reports in " << dirname << ":" << std::endl; } std::string name = sys::fs::basename(reports[i].first); if (name.length() > 4 && name.compare(name.length()-4, 4, ".lua") == 0) { name = name.substr(0, name.length()-4); } Options::print(name, reports[i].second, out); } #ifndef USE_GNUPLOT out << std::endl << "NOTE: Built without Gnuplot support. "; out << "Graphical reports are not shown." << std::endl; #endif } // Returns the backend wrapper Repository *Report::repository() const { return m_repo; } // Returns the report options std::map Report::options() const { return m_options; } // Returns the current context Report *Report::current() { return (s_stack.empty() ? NULL : s_stack.top()); } // Reads the report script's meta data void Report::readMetaData() { lua_State *L = setupLua(); // Open the script std::string path = findScript(m_script); try { loadReport(L, path); } catch (const std::exception &ex) { throw PEX(str::printf("Error opening report: %s", ex.what())); } // Try to run describe() lua_getglobal(L, "describe"); if (lua_type(L, -1) == LUA_TFUNCTION) { LuaHelpers::push(L, this); if (lua_pcall(L, 1, 1, 0) != 0) { throw PEX(str::printf("Error opening report: %s", lua_tostring(L, -1))); } if (lua_type(L, -1) != LUA_TTABLE) { throw PEX("Error opening report: Expected table from describe()"); } } else { lua_pop(L, 1); // Else, use the meta table lua_getglobal(L, "meta"); if (lua_type(L, -1) != LUA_TTABLE) { throw PEX("Error opening report: Neither describe() nor meta-table found"); } } // Read the report name m_metaData.name = sys::fs::basename(m_script); lua_getfield(L, -1, "title"); if (lua_type(L, -1) == LUA_TSTRING) { m_metaData.name = LuaHelpers::tops(L); } else { lua_pop(L, 1); // "meta.name" has been used in the first version of the scripting tutorial lua_getfield(L, -1, "name"); if (lua_type(L, -1) == LUA_TSTRING) { m_metaData.name = LuaHelpers::tops(L); } } lua_pop(L, 1); // Read the report description lua_getfield(L, -1, "description"); if (lua_type(L, -1) == LUA_TSTRING) { m_metaData.description = LuaHelpers::tops(L); } lua_pop(L, 1); // Check for possible options lua_getfield(L, -1, "options"); if (lua_type(L, -1) == LUA_TTABLE) { lua_pushnil(L); while (lua_next(L, -2) != 0) { if (lua_type(L, -1) != LUA_TTABLE) { lua_pop(L, 1); continue; } int i = 0; std::string arg, text; lua_pushnil(L); while (lua_next(L, -2) != 0) { if (lua_type(L, -1) == LUA_TSTRING) { if (i == 0) { arg = luaL_checkstring(L, -1); } else if (i == 1) { text = luaL_checkstring(L, -1); } } ++i; lua_pop(L, 1); } lua_pop(L, 1); if (i >= 2) { m_metaData.options.push_back(MetaData::Option(arg, text)); } } } lua_pop(L, 1); // Clean up lua_gc(L, LUA_GCCOLLECT, 0); lua_close(L); m_metaDataRead = true; } /* * Lua binding */ const char Report::className[] = "report"; Lunar::RegType Report::methods[] = { LUNAR_DECLARE_METHOD(Report, getopt), LUNAR_DECLARE_METHOD(Report, repository), LUNAR_DECLARE_METHOD(Report, run), LUNAR_DECLARE_METHOD(Report, name), LUNAR_DECLARE_METHOD(Report, description), LUNAR_DECLARE_METHOD(Report, options), {0,0} }; Report::Report(lua_State *L) : m_repo(NULL), m_out(&std::cout), m_metaDataRead(false) { if (lua_gettop(L) != 1 && lua_gettop(L) != 2) { LuaHelpers::pushError(L, "Invalid number of arguments (1 or 2 expected)"); return; } if (!Report::current()) { LuaHelpers::pushError(L, "No current report"); return; } m_options = Report::current()->options(); if (lua_gettop(L) == 2) { m_options = LuaHelpers::popms(L); } m_script = LuaHelpers::pops(L); m_repo = new Repository(Report::current()->repository()->backend()); } int Report::repository(lua_State *L) { return LuaHelpers::push(L, m_repo); } int Report::getopt(lua_State *L) { if (lua_gettop(L) != 1 && lua_gettop(L) != 2) { return luaL_error(L, "Invalid number of arguments (1 or 2 expected)"); } bool defaultProvided = (lua_gettop(L) == 2); std::string defaultValue = (lua_gettop(L) == 2 ? LuaHelpers::pops(L) : std::string()); std::vector keys = str::split(LuaHelpers::pops(L), ",", true); std::string value; bool valueFound = false; for (unsigned int i = 0; i < keys.size(); i++) { if (m_options.find(keys[i]) != m_options.end()) { value = m_options.find(keys[i])->second; valueFound = true; break; } } if (valueFound) { return LuaHelpers::push(L, value); } else if (defaultProvided) { return LuaHelpers::push(L, defaultValue); } return LuaHelpers::pushNil(L); } int Report::run(lua_State *L) { try { std::stringstream out, err; if (run(out, err) != 0) { return LuaHelpers::pushError(L, str::trim(err.str())); } return LuaHelpers::push(L, out.str()); } catch (const PepperException &ex) { return LuaHelpers::pushError(L, ex.what(), ex.where()); } catch (const std::exception &ex) { return LuaHelpers::pushError(L, ex.what()); } return LuaHelpers::pushNil(L); } int Report::name(lua_State *L) { try { if (!m_metaDataRead) { readMetaData(); } return LuaHelpers::push(L, m_metaData.name); } catch (const PepperException &ex) { return LuaHelpers::pushError(L, ex.what(), ex.where()); } catch (const std::exception &ex) { return LuaHelpers::pushError(L, ex.what()); } return LuaHelpers::pushNil(L); } int Report::description(lua_State *L) { try { if (!m_metaDataRead) { readMetaData(); } return LuaHelpers::push(L, m_metaData.description); } catch (const PepperException &ex) { return LuaHelpers::pushError(L, ex.what(), ex.where()); } catch (const std::exception &ex) { return LuaHelpers::pushError(L, ex.what()); } return LuaHelpers::pushNil(L); } int Report::options(lua_State *L) { try { if (!m_metaDataRead) { readMetaData(); } // Convert each option into a 2-dimensional vector std::vector > opvec; for (size_t i = 0; i < m_metaData.options.size(); i++) { std::vector v; v.push_back(m_metaData.options[i].synopsis); v.push_back(m_metaData.options[i].description); opvec.push_back(v); } return LuaHelpers::push(L, opvec); } catch (const PepperException &ex) { return LuaHelpers::pushError(L, ex.what(), ex.where()); } catch (const std::exception &ex) { return LuaHelpers::pushError(L, ex.what()); } return LuaHelpers::pushNil(L); } pepper-0.3.3/src/report.h000066400000000000000000000040711263211400600152600ustar00rootroot00000000000000/* * pepper - SCM statistics report generator * Copyright (C) 2010-present Jonas Gehring * * Released under the GNU General Public License, version 3. * Please see the COPYING file in the source distribution for license * terms and conditions, or see http://www.gnu.org/licenses/. * * file: report.h * Report script context (interface) */ #ifndef REPORT_H_ #define REPORT_H_ #include #include #include #include #include "lunar/lunar.h" class Backend; class Repository; // Report context class Report { public: struct MetaData { struct Option { std::string synopsis; std::string description; Option(const std::string &synopsis, const std::string &description) : synopsis(synopsis), description(description) { } }; std::string name; std::string description; std::vector