taskjuggler-3.5.0/0000755000175000017500000000000012614413013013344 5ustar bernatbernattaskjuggler-3.5.0/COPYING0000644000175000017500000003543312614413013014407 0ustar bernatbernat GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE 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. END OF TERMS AND CONDITIONS taskjuggler-3.5.0/README.rdoc0000644000175000017500000001260712614413013015160 0ustar bernatbernat= About {TaskJuggler}[http://www.taskjuggler.org] TaskJuggler is a modern and powerful, Free and Open Source Software project management tool. Its new approach to project planing and tracking is more flexible and superior to the commonly used Gantt chart editing tools. TaskJuggler is project management software for serious project managers. It covers the complete spectrum of project management tasks from the first idea to the completion of the project. It assists you during project scoping, resource assignment, cost and revenue planing, risk and communication management. TaskJuggler provides an optimizing scheduler that computes your project time lines and resource assignments based on the project outline and the constraints that you have provided. The built-in resource balancer and consistency checker offload you from having to worry about irrelevant details and ring the alarm if the project gets out of hand. The flexible as-many-details-as-necessary approach allows you to plan your project as you go, making it also ideal for new management strategies such as Extreme Programming and Agile Project Management. If you are about to build a skyscraper or just want to put together the release schedule of your open source project, TaskJuggler is the right tool for you. If you just want to draw nice looking Gantt charts to impress your boss or your investors, TaskJuggler might not be right for you. You can of course create nice looking Gantt charts. But it takes a little more effort to master its power. If you are up for this, TaskJuggler will become a companion that you don't want to miss anymore. TaskJuggler is written in {Ruby}[http://www.ruby-lang.org/en/] and should be easily installable and usable on all popular operating systems. It may sound surprising at first, but this software does not need a graphical user interface. A command shell, a plain text editor (no word processor!) and a web browser is all you need for your work. = Features and Highlights == Basic Properties * Manages tasks, resources and accounts of your project * Powerful to-do list management * Detailed reference manual * Simple installation * Runs on all Linux, Unix, Windows, MacOS and several other operating systems * Full integration with Vim text editor == Advanced Scheduling * Automatic resource leveling and tasks conflict resolution * Unlimited number of scenarios (baselines) of the same project for what-if analysis * Flexible working hours and leave management * Support for shift working * Multiple time zone support == Accounting * Tasks may have initial costs, finishing costs * Resources may have usage based costs * Task and/or resource base cost models * Support for profit/loss analysis == Reporting * Comprehensive and flexible reports so you can find the information you need when you need it * Powerful filtering functions to provide the right amount of detail to the right audience * Time and status sheet reporting infrastructure * Project tracking and status reporting with dashboard support == Scaling and Enterprise Features * Projects can be combined to larger projects * Support for central resource allocation database * Manages roles and complex reporting lines * Powerful project description language with macro support * Scales well on multi-core or multi-CPU systems * Support for project management teams and revision control systems * Data export to Microsoft Project and Computer Associates Clarity == Web Publishing and Groupware Functions * HTML reports for web publishing * CSV data export for exchange with popular office software * iCalendar export for data exchange with calendar and productivity applications * Built-in web server for dynamic and interactive reports * Server based time sheet system for status and actual work reporting = Installation Installation instructions can be found {here}[http://www.taskjuggler.org/tj3/manual/Installation.html#Installation]. = Introduction and Tutorial To learn more about how to use TaskJuggler please see the {user manual}[http://www.taskjuggler.org/tj3/manual/index.html]. It also contains a {tutorial}[http://www.taskjuggler.org/tj3/manual/Tutorial.html#The_Tutorial_Your_first_Project] to get you started. It will tell you how to generate HTML reports like {these}[http://www.taskjuggler.org/tj3/examples/Tutorial/Overview.html] from such a {project description}[http://www.taskjuggler.org/tj3/examples/Tutorial/tutorial.tjp]. = Getting Help and reporting Bugs There are several mailing list for TaskJuggler users and developers. Please see {this page}[http://www.taskjuggler.org/contact.html] for details. If you have a question about TaskJuggler usage, the user list is the best place to go. Please remain subscribed and help other users when you get more proficient in TaskJuggler. If you think that you have found a bug in the software, please the the {bug reporting guidelines}[http://www.taskjuggler.org/tj3/manual/Reporting_Bugs.html#Reporting_Bugs_and_Sending_Feedback]. = Copyright and License TaskJuggler is (c) 2006, 2007, 2008, 2009, 2010, 2011 by Chris Schlaeger This program is free software; you can redistribute it and/or modify it under the terms of {version 2 of the GNU General Public License}[http://www.gnu.org/licenses/old-licenses/gpl-2.0.html] as published by the Free Software Foundation. You accept the terms of this license by distributing or using this software. TaskJuggler[http://www.taskjuggler.org] is a trademark of Chris Schlaeger. taskjuggler-3.5.0/examples/0000755000175000017500000000000012614413013015162 5ustar bernatbernattaskjuggler-3.5.0/examples/Tutorial/0000755000175000017500000000000012614413013016765 5ustar bernatbernattaskjuggler-3.5.0/examples/Tutorial/tutorial.tjp0000644000175000017500000003522712614413013021360 0ustar bernatbernat/* * This file contains an example project. It is part of the * TaskJuggler project management tool. It uses a made up software * development project to demonstrate some of the basic features of * TaskJuggler. Please see the TaskJuggler manual for a more detailed * description of the various syntax elements. */ project acso "Accounting Software" 2002-01-16 +4m { # Set the default time zone for the project. If not specified, UTC # is used. timezone "Europe/Paris" # Hide the clock time. Only show the date. timeformat "%Y-%m-%d" # Use US format for numbers numberformat "-" "" "," "." 1 # Use US financial format for currency values. Don't show cents. currencyformat "(" ")" "," "." 0 # Pick a day during the project that will be reported as 'today' in # the project reports. If not specified, the current day will be # used, but this will likely be outside of the project range, so it # can't be seen in the reports. now 2002-03-05-13:00 # The currency for all money values is the Euro. currency "USD" # We want to compare the baseline scenario to one with a slightly # delayed start. scenario plan "Plan" { scenario delayed "Delayed" } extend resource { text Phone "Phone" } } # This is not a real copyright for this file. It's just used as an example. copyright "© 2002 Crappy Software, Inc." # The daily default rate of all resources. This can be overridden for each # resource. We specify this, so that we can do a good calculation of # the costs of the project. rate 390.0 # Register Good Friday as a global holiday for all resources. leaves holiday "Good Friday" 2002-03-29 flags team # This is one way to form teams macro allocate_developers [ allocate dev1 allocate dev2 allocate dev3 ] # In order to do a simple profit and loss analysis of the project we # specify accounts. One for the development costs, one for the # documentation costs, and one account to credit the customer payments # to. account cost "Project Cost" { account dev "Development" account doc "Documentation" } account rev "Payments" # The Profit&Loss analysis should be rev - cost accounts. balance cost rev resource boss "Paul Henry Bullock" { email "phb@crappysoftware.com" Phone "x100" rate 480 } resource dev "Developers" { managers boss resource dev1 "Paul Smith" { email "paul@crappysoftware.com" Phone "x362" rate 350.0 } resource dev2 "Sébastien Bono" { email "SBono@crappysoftware.com" Phone "x234" } resource dev3 "Klaus Müller" { email "Klaus.Mueller@crappysoftware.com" Phone "x490" leaves annual 2002-02-01 - 2002-02-05 } flags team } resource misc "The Others" { managers boss resource test "Peter Murphy" { email "murphy@crappysoftware.com" Phone "x666" limits { dailymax 6.4h } rate 310.0 } resource doc "Dim Sung" { email "sung@crappysoftware.com" Phone "x482" rate 300.0 leaves annual 2002-03-11 - 2002-03-16 } flags team } # Now we specify the work packages. The whole project is described as # a task that contains subtasks. These subtasks are then broken down # into smaller tasks and so on. The innermost tasks describe the real # work and have resources allocated to them. Many attributes of tasks # are inherited from the enclosing task. This saves you a lot of typing. task AcSo "Accounting Software" { # All work-related costs will be booked to this account unless the # subtasks specify something different. chargeset dev # For the duration of the project we have running cost that are not # included in the labor cost. charge 170 perday responsible boss task spec "Specification" { # The effort to finish this task is 20 man-days. effort 20d # Now we use the macro declared above to allocate the resources # for this task. Because they can work in parallel, they may finish this # task earlier than in 20 working-days. ${allocate_developers} # Each task without subtasks must have a start or an end # criterion and a duration. For this task we use a reference to a # milestone defined further below as the start criterion. So this task # can not start before the specified milestone has been reached. # References to other tasks may be relative. Each exclamation mark (!) # means 'in the scope of the enclosing task'. To descent into a task, the # fullstop (.) together with the id of the tasks have to be specified. depends !deliveries.start } task software "Software Development" { # The software is the most critical task of the project. So we set # the priority of this task (and all its subtasks) to 1000, the top # priority. The higher the priority, the more likely the task will # get the requested resources. priority 1000 # All subtasks depend on the specification task. depends !spec responsible dev1 task database "Database coupling" { effort 20d allocate dev1, dev2 journalentry 2002-02-03 "Problems with the SQL Libary" { author dev1 alert yellow summary -8<- We ran into some compatibility problems with the SQL Library. ->8- details -8<- We have already contacted the vendor and are now waiting for their advise. ->8- } } task gui "Graphical User Interface" { effort 35d # This task has taken 5 man-days more than originally planned. # We record this as well, so that we can generate reports that # compare the delayed schedule of the project to the original plan. delayed:effort 40d depends !database, !backend allocate dev2, dev3 # Resource dev2 should only work 6 hours per day on this task. limits { dailymax 6h { resources dev2 } } } task backend "Back-End Functions" { effort 30d # This task is behind schedule, because it should have been # finished already. To document this, we specify that the task # is 95% completed. If nothing is specified, TaskJuggler assumes # that the task is on schedule and computes the completion rate # according to the current day and the plan data. complete 95 depends !database allocate dev1, dev2 } } task test "Software testing" { task alpha "Alpha Test" { # Efforts can not only be specified as man-days, but also as # man-weeks, man-hours, etc. By default, TaskJuggler assumes # that a man-week is 5 man-days or 40 man-hours. These values # can be changed, of course. effort 1w # This task depends on a task in the scope of the enclosing # task's enclosing task. So we need two exclamation marks (!!) # to get there. depends !!software allocate test, dev2 note "Hopefully most bugs will be found and fixed here." journalentry 2002-03-01 "Contract with Peter not yet signed" { author boss alert red summary -8<- The paperwork is stuck with HR and I can't hunt it down. ->8- details -8<- If we don't get the contract closed within the next week, the start of the testing is at risk. ->8- } } task beta "Beta Test" { effort 4w depends !alpha allocate test, dev1 } } task manual "Manual" { effort 10w depends !deliveries.start allocate doc, dev3 purge chargeset chargeset doc journalentry 2002-02-28 "User manual completed" { author boss summary "The doc writers did a really great job to finish on time." } } task deliveries "Milestones" { # Some milestones have customer payments associated with them. We # credit these payments to the 'rev' account. purge chargeset chargeset rev task start "Project start" { # A task that has no duration is a milestone. It only needs a # start or end criterion. All other tasks depend on this task. # Here we use the built-in macro ${projectstart} to align the # start of the task with the above specified project time frame. start ${projectstart} # For some reason the actual start of the project got delayed. # We record this, so that we can compare the planned run to the # delayed run of the project. delayed:start 2002-01-20 # At the beginning of this task we receive a payment from the # customer. This is credited to the account associated with this # task when the task starts. charge 21000.0 onstart } task prev "Technology Preview" { depends !!software.backend charge 31000.0 onstart note "All '''major''' features should be usable." } task beta "Beta version" { depends !!test.alpha charge 13000.0 onstart note "Fully functional, may contain bugs." } task done "Ship Product to Customer" { # The next line can be uncommented to trigger a warning about # the project being late. For all tasks, limits for the start and # end values can be specified. Those limits are checked after the # project has been scheduled. For all violated limits a warning # is issued. # maxend 2002-04-17 depends !!test.beta, !!manual charge 33000.0 onstart note "All priority 1 and 2 bugs must be fixed." } } } # Now the project has been specified completely. Stopping here would # result in a valid TaskJuggler file that could be processed and # scheduled. But no reports would be generated to visualize the # results. navigator navbar { hidereport @none } macro TaskTip [ tooltip istask() -8<- '''Start: ''' <-query attribute='start'-> '''End: ''' <-query attribute='end'-> ---- '''Resources:''' <-query attribute='resources'-> ---- '''Precursors: ''' <-query attribute='precursors'-> ---- '''Followers: ''' <-query attribute='followers'-> ->8- ] textreport frame "" { header -8<- == Accounting Software Project == <[navigator id="navbar"]> ->8- footer "----" textreport index "Overview" { formats html center '<[report id="overview"]>' } textreport "Status" { formats html center -8<- <[report id="status.dashboard"]> ---- <[report id="status.completed"]> ---- <[report id="status.ongoing"]> ---- <[report id="status.future"]> ->8- } textreport development "Development" { formats html center '<[report id="development"]>' } textreport "Deliveries" { formats html center '<[report id="deliveries"]>' } textreport "ContactList" { formats html title "Contact List" center '<[report id="contactList"]>' } textreport "ResourceGraph" { formats html title "Resource Graph" center '<[report id="resourceGraph"]>' } } # A traditional Gantt chart with a project overview. taskreport overview "" { header -8<- === Project Overview === The project is structured into 3 phases. # Specification # <-reportlink id='frame.development'-> # Testing === Original Project Plan === ->8- columns bsi { title 'WBS' }, name, start, end, effort, cost, revenue, chart { ${TaskTip} } # For this report we like to have the abbreviated weekday in front # of the date. %a is the tag for this. timeformat "%a %Y-%m-%d" loadunit days hideresource @all balance cost rev caption 'All effort values are in man days.' footer -8<- === Staffing === All project phases are properly staffed. See [[ResourceGraph]] for detailed resource allocations. === Current Status === The project started off with a delay of 4 days. This slightly affected the original schedule. See [[Deliveries]] for the impact on the delivery dates. ->8- } # Macro to set the background color of a cell according to the alert # level of the task. macro AlertColor [ cellcolor plan.alert = 0 "#00D000" # green cellcolor plan.alert = 1 "#D0D000" # yellow cellcolor plan.alert = 2 "#D00000" # red ] taskreport status "" { columns bsi { width 50 title 'WBS' }, name { width 150 }, start { width 100 }, end { width 100 }, effort { width 100 }, alert { tooltip plan.journal != '' "<-query attribute='journal'->" width 150 }, status { width 150 } scenarios delayed taskreport dashboard "" { headline "Project Dashboard (<-query attribute='now'->)" columns name { title "Task" ${AlertColor} width 200}, resources { width 200 ${AlertColor} listtype bullets listitem "<-query attribute='name'->" start ${projectstart} end ${projectend} }, alerttrend { title "Trend" ${AlertColor} width 50 }, journal { width 350 ${AlertColor} } journalmode status_up journalattributes headline, author, date, summary, details hidetask ~hasalert(0) sorttasks alert.down, delayed.end.up period %{${now} - 1w} +1w } taskreport completed "" { headline "Already completed tasks" hidetask ~(delayed.end <= ${now}) } taskreport ongoing "" { headline "Ongoing tasks" hidetask ~((delayed.start <= ${now}) & (delayed.end > ${now})) } taskreport future "" { headline "Future tasks" hidetask ~(delayed.start > ${now}) } } # A list of tasks showing the resources assigned to each task. taskreport development "" { scenarios delayed headline "Development - Resource Allocation Report" columns bsi { title 'WBS' }, name, start, end, effort { title "Work" }, duration, chart { ${TaskTip} scale day width 500 } timeformat "%Y-%m-%d" hideresource ~(isleaf() & isleaf_()) sortresources name.up } # A list of all tasks with the percentage completed for each task taskreport deliveries "" { headline "Project Deliverables" columns bsi { title 'WBS' }, name, start, end, note { width 150 }, complete, chart { ${TaskTip} } taskroot AcSo.deliveries hideresource @all scenarios plan, delayed } # A list of all employees with their contact details. resourcereport contactList "" { scenarios delayed headline "Contact list and duty plan" columns name, email { celltext 1 "[mailto:<-email-> <-email->]" }, Phone, managers { title "Manager" }, chart { scale day } hideresource ~isleaf() sortresources name.up hidetask @all } # A graph showing resource allocation. It identifies whether each # resource is under- or over-allocated for. resourcereport resourceGraph "" { scenarios delayed headline "Resource Allocation Graph" columns no, name, effort, rate, weekly { ${TaskTip} } loadunit shortauto # We only like to show leaf tasks for leaf resources. hidetask ~(isleaf() & isleaf_()) sorttasks plan.start.up } taskjuggler-3.5.0/examples/Fedora-20/0000755000175000017500000000000012614413013016601 5ustar bernatbernattaskjuggler-3.5.0/examples/Fedora-20/f-20.tjp0000644000175000017500000022655112614413013017777 0ustar bernatbernat# Fedora 20 # # Copyright 2011 John Poelstra # Copyright 2011 Robyn Bergeron # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # Current release version macro major [20] # Last release version macro previous_major[19] # Next release version macro next_major [21] # do not change this macro content [f] # do not change this macro content_title [Fedora] # GA date of previous release macro start_date [2013-04-30] # set to one day after "start_date" macro start_work [2013-05-01] macro end_date [2014-12-31] macro prior_project [f19] project ${content}${major} "${content_title}" "${major}" ${start_date} - ${end_date} { timeformat "%Y-%b-%d" # Based on Eastern time zone in USA timezone "America/New_York" # Setup scenarios scenario plan "Original Plan" { scenario actual "Actual" } # Limit working days workinghours sat,sun off } # ========= Cornerstones of Fedora Schedules ============= # This section also serves as a "style guide" for the source file too /* 1) EVERY entry should be in the following line order and indented consistently with the rest of the file. This makes the source file consistent and eaiser to read. a) task name and description b) dependency (if applicable) c) length (not "duration") (if applicable) d) flags (if applicable) 2) Official Release Engineering Composes happen on Thursdays, except for the final release 3) Syncing releases to mirrors ideally happens on Thursdays... end of day on Friday is worst case 4) Most tasks are scheduled with 'length' instead of 'duration' to get a sense of "work days" required--using 'length' one week = 5 days --an eternal debate could be held to discuss whether or not schedule calculations should include weekends because community members work at all times and not within strict work days. 5) Use "length" everywhere and "duration" only when something must take into account a weekend day 6) All "Blocker Bug Days" should be on Fridays 7) If a task takes one day or less--schedule with no length--this way it showes up as a milestone and gets included in iCalendar (ics) file 8) Because of bugs in the way TaskJuggler ics files get rendered in some calendars (e.g. Zimbra) we only include zero length (milestone) tasks in the ics file. As a result there are several duplicate tasks with no length that have been added so they appear in the ics file. 9) Use core schedule milestones (flags = key) as anchor points (depends/precedes) for tasks in this schedule file instead of more transient tasks like meetings, compose dates, etc. that may change (or slip) from release to release. This makes building new release schedules easier and require less maintenance and updating. 10) In ALL but limited cases task beginning and ending should be automatically calculated based on logic in this file. When using hard coded dates, explicitly call them out with a comment to hightlight their existence. Hard coded dates are particularly troublesome when slipping a schedule or branching the file to create a new release schedule because they must be adjusted and recalculated manually 11) TaskJuggler does not provide an easy way (that I am aware of) to schedule tasks to happen *before* other tasks. I've created a hack/methodology I call "shadow" tasks. These are unreported tasks that go backwards a certain period of time and serve as an anchor or starting point for the actual task to be reported. 12) The "milestone" declaration is NOT used in this source file. It is redundant and unnecessary. Do not inlude it. All tasks without 'length' or 'duration' are automatically considered "milestone" tasks. */ # Define flags for filtered reporting flags ambassadors flags bugtriage flags blocker # use to proof blocker meeting placement flags design flags devel flags docs flags elections flags fpl flags hidden # used to hide tasks that we do not displayed in Fedora reports flags infrastructure flags interface flags key /* use for report of key tasks/high level overview--reflected on Fedora wiki, e.g. https://fedoraproject.org/wiki/Releases/15/Schedule */ flags proto # used for drafting new schedules and shows tasks useful for doing this flags pm flags pr flags marketing flags roadmap # major milestones flags translation flags quality flags releng flags spins flags web task ${content}${major} "${content_title} ${major}" { start ${start_work} task first_day "First Day of Development" { flags hidden } task PlanningPhase "Planning Phase" { # add for ical task start_features_cal "Start Feature Submission" { flags key, pm, roadmap } task rawhide_spins "Start Nightly Spins Compose Based on Rawhide" { depends !!first_day {gaplength 5d} flags spins } # ADJUST FOR NEW RELEASE # HARDCODED date--approximately 2 weeks after GA of previous release task file_ticket "File ticket with RHT Eng-ops for Fedora 17 EOL bugzilla closure" { start 2013-05-15 flags pm } # ADJUST FOR NEW RELEASE # HARDCODED date--approximately 4 weeks after GA of previous release # this date is approved by FESCo in accordance with https://fedoraproject.org/wiki/LifeCycle # The process behind this task is at: https://fedoraproject.org/wiki/BugZappers/HouseKeeping task fedora17_eol "RHT Eng-Ops Fedora 17 EOL auto closure" { start 2013-05-29 flags pm, key } task clean_market_wiki "Cleanup Marketing wiki from previous releases" { depends !start_features_cal {gaplength 25d} length 5d flags marketing } task cycle_market_wiki "Cycle Marketing wiki pages for current release" { depends !start_features_cal {gaplength 25d} length 5d flags marketing } task bug_trackers "Create Tracker Bugs" { flags pm note "See details at https://fedoraproject.org/wiki/BugZappers/HouseKeeping/FirstDayDevel" } task design_concept "Conceptual Design Phase" { length 30d flags design } # ADJUST FOR NEW RELEASE # a solid start for the wallpaper is the goal for alpha # adjust length so packaging end date lands on Alpha Deadline task wallpaper_design "Wallpaper Design for Alpha" { depends !design_concept length 35d flags design } } # E N D of PlanningPhase task supplement_wallpaper "Supplemental Wallpaper Process" { flags design # ADJUST FOR NEW RELEASE # adjust length of this task so that 'package_supplemental_wallpaper' ends on Beta Deadline task supplement_wallpaper_submit "Supplemental Wallpaper Submission Period" { length 82d } task decide_supplement_wallpaper "Select Official Supplemental Wallpaper" { depends !supplement_wallpaper_submit } task supplement_license_review "Verify Supplemental Wallpaper Licenses" { depends !decide_supplement_wallpaper {gaplength 1d} length 10d } task package_supplemental_wallpaper "Package Supplemental Wallpaper" { depends !supplement_license_review length 2d } } # end supplemental wallpaper task DevelopmentPhase "Development Phase" { start ${start_work} task devel_start "Start Development" { flags devel } # ADJUST FOR NEW RELEASE--IMPORTANT # The length of this task DRIVES THE ENTIRE SCHEDULE # and determines when the testing phases starts. The testing # phase of the schedule is static (and completely automatically # generated by TaskJuggler) from release to release, but the # number of days of development varies depending when the previous # release ended (GA). As a result the length of this task also # influences where the GA date lands. # Each "5d" (5 days) is equivalent to one week. task develop "Packaging and Development (precedes Alpha)" { length 70d flags devel, proto } } # E N D of DevelopmentPhase task TestingPhase "Testing Phase" { task alpha "Alpha Release" { start ${start_work} task shadow_alpha_blocker "SHADOW: anchor for first blocker meeting" { precedes !feature_freeze {gaplength 11d} flags hidden } task remind_alpha_blocker1 "Reminder: Alpha Blocker Meeting (${content}${major}alpha) #1" { depends !shadow_alpha_blocker {gaplength 3d} flags pm } task alpha_blocker1 "Alpha Blocker Meeting (${content}${major}alpha) #1" { depends !shadow_alpha_blocker {gaplength 5d} flags releng, quality, devel, blocker, pm } task remind_alpha_blocker2 "Reminder: Alpha Blocker Meeting (${content}${major}alpha) #2" { depends !alpha_blocker1 {gaplength 3d} flags pm } task alpha_blocker2 "Alpha Blocker Meeting (${content}${major}alpha) #2" { depends !alpha_blocker1 {gaplength 5d} flags releng, quality, devel, blocker, pm } # raise awareness one week before Alpha compose task daily_alpha_blocker "Daily Review & Notification of Open Alpha Blocker Bugs" { depends !alpha_blocker2 {gaplength 1d} length 4d flags releng, quality, devel, pm, blocker } task remind_alpha_blocker3 "Reminder: Alpha Blocker Meeting (${content}${major}alpha) #3" { depends !alpha_blocker2 {gaplength 3d} flags pm } task alpha_blocker3 "Alpha Blocker Meeting (${content}${major}alpha) #3" { depends !alpha_blocker2 {gaplength 5d} flags releng, quality, devel, pm, blocker } task remind_alpha_blocker4 "Reminder: Alpha Blocker Meeting (${content}${major}alpha) #4" { depends !alpha_blocker3 {gaplength 3d} flags pm } task alpha_blocker4 "Alpha Blocker Meeting (${content}${major}alpha) #4" { depends !alpha_blocker3 {gaplength 5d} flags releng, quality, devel, pm, blocker } # placeholder if slip--otherwise comment out /* task alpha_blocker5 "Alpha Blocker Meeting (${content}${major}alpha) #5" { depends !alpha_blocker4 {gaplength 5d} flags releng, quality, devel, pm, blocker } */ # Feature Freeze is one week before Alpha Change Deadline # Automatically calculated based on Alpha Change Deadline task shadow_feature_freeze "SHADOW: Anchor Feature Freeze" { precedes !alpha_deadline {gaplength 6d} flags hidden } task feature_freeze "Feature Freeze (Testable|Complete)" { depends !shadow_feature_freeze flags releng, quality, pm, proto, devel, key, marketing, roadmap, fpl } task feature_freeze_deadline_announce "Announce Feature Freeze Reached" { depends !feature_freeze flags pm } task alpha_deadline_remind "Remind Alpha Deadline in 1 week" { depends !feature_freeze flags pm } task spins_freeze "Spins Freeze--All ${content_title} ${major} Spins Identified" { depends !shadow_feature_freeze flags releng, quality, pm, proto, devel, key, marketing, spins, fpl } task talking_points "Create Talking Points" { depends !feature_freeze {gaplength 6d} length 5d flags marketing } task feature_profiles "Feature Profiles" { depends !talking_points length 20d flags marketing } # Branch on the same Tuesday as Feature Freeze task branch_rawhide "Branch Fedora ${major} from Rawhide" { depends !shadow_feature_freeze flags releng, devel, pm, proto, key, roadmap, fpl } task bugzilla_descrption "Reflect supported versions in Bugzilla product description" { depends !shadow_feature_freeze flags pm } task rawhide_rebase "Rebase Rawhide bugs to Fedora ${major}" { depends !shadow_feature_freeze flags pm } # Create anchor for three weeks before for Feature Submission task shadow_feature_submit_remind_3_weeks "SHADOW: Three Weeks Before Feature Submission" { precedes !feature_submission_deadline {gaplength 15d} flags hidden } # Three weeks before Feature Submission Deadline task feature_check_remind "Request Feature Status Updates + Remind Submit Deadline" { depends !shadow_feature_submit_remind_3_weeks flags devel, pm } task alpha_releng_tickets "File All Release Engineering Tickets for ${content_title} ${major} Alpha" { depends !shadow_feature_submit_remind_3_weeks {gaplength 3d} flags releng } task feature_submit_remind_2_weeks "Feature Submission Deadline Two Weeks away" { depends !shadow_feature_submit_remind_3_weeks {gaplength 6d} flags devel, pm } #two weeks before spins submission deadilen get wiki pages in order task spins_wiki_update "Update All Spins Wiki Pages From Previous Releases" { depends !shadow_feature_submit_remind_3_weeks flags spins } task feature_submit_remind_1_week "Feature Submission Deadline One Week away" { depends !shadow_feature_submit_remind_3_weeks {gaplength 11d} flags devel, pm } # One day before compose task alpha_installer_build1 "Submit Installer Build for QA Compose" { depends !feature_submit_remind_1_week {gaplength 1d} flags devel } # Thursday before Feature Submission deadline task qa_alpha_compose1 "Create Installable Images for QA testing #1" { depends !feature_submit_remind_1_week {gaplength 2d} flags releng } task alpha_rawhide_install1 "Pre-Alpha Rawhide Acceptance Test Plan #1" { depends !qa_alpha_compose1 length 6d flags quality } # Create anchor for two weeks before Feature Freeze task shadow_feature_submission_deadline "SHADOW: Two weeks before Feature Freeze" { precedes !feature_freeze {gaplength 10d} flags hidden } task feature_submission_deadline "Feature Submission Deadline" { depends !shadow_feature_submission_deadline flags releng, quality, pm, proto, devel, key, roadmap, fpl } task feature_submission_deadline_announce "Announce Feature Submission Closed" { depends !shadow_feature_submission_deadline flags pm } task spins_submission_deadline "Custom Spins Submission Deadline" { depends !shadow_feature_submission_deadline flags pm, proto, key, spins, fpl } task warn_rawhide_rebase "Rawhide Rebase Warning to Package Maintainers" { depends !shadow_feature_submission_deadline flags pm } task ticket_rawhide_rebase "File Rawhide Rebase ticket with RHT Eng-ops" { depends !shadow_feature_submission_deadline flags pm } # One day before compose task alpha_installer_build2 "Submit Installer Build for QA Compose" { depends !feature_submission_deadline {gaplength 2d} flags devel } task qa_alpha_compose2 "Create Installable Images for QA testing #2" { depends !feature_submission_deadline {gaplength 3d} flags releng } task alpha_rawhide_install2 "Pre-Alpha Rawhide Acceptance Test Plan #2" { depends !qa_alpha_compose2 length 5d flags quality } # One day before compose task alpha_installer_build3 "Submit Installer Build for QA Compose" { depends !qa_alpha_compose2 {gaplength 4d} flags devel } task qa_alpha_compose3 "Create Installable Images for QA testing #3" { depends !alpha_rawhide_install2 flags releng } task alpha_rawhide_install3 "Pre-Alpha Rawhide Acceptance Test Plan #3" { depends !qa_alpha_compose3 length 5d flags quality } task feature_incomplete_nag "Remind < 85% complete Feature Owners" { depends !feature_freeze {gaplength 1d} flags pm } task feature_incomplete_fesco "Deliver Incomplete Features to FESCo " { depends !feature_freeze {gaplength 6d} flags pm } # KEY ADJUSTMENT POINT # Date of Alpha Deadline depends on length of development task alpha_deadline "Alpha Change Deadline" { depends !!!DevelopmentPhase.develop flags releng, quality, pm, devel, key, roadmap, proto, blocker, spins } task alpha_deadline_announce "Announce Alpha Change Deadline Reached" { depends !alpha_deadline flags pm } # KEY ADJUSTMENT POINT--manually adjust if length of time before Alpha ships changes task alpha_infrastructure_freeze "Alpha Infrastructure Change Freeze" { depends !alpha_deadline length 10d flags infrastructure } task alpha_spins_ks "Build spin-kickstarts package from master" { depends !alpha_deadline flags spins } # Happens the same day as Feature Freeze task orphan "Orphan Rawhide Packages" { depends !feature_freeze flags releng, devel } task finalize_alpha_wallpaper "Finalize Alpha Wallpaper" { depends !!!PlanningPhase.wallpaper_design flags design, pm length 3d } task alpha_wallpaper_deadline "Alpha Wallpaper Deadline" { depends !finalize_alpha_wallpaper flags design } task blog_alpha_wallpaper "Blog About Alpha Wallpaper" { depends !finalize_alpha_wallpaper flags design } # must land a few days before Alpha compose by Releng task package_alpha_wallpaper "Package Alpha Wallpaper" { depends !finalize_alpha_wallpaper length 2d flags design } task alpha_wallpaper_feedback "Solicit Feedback on Alpha Wallpaper" { depends !package_alpha_wallpaper length 10d flags design } # depends on nearly complete version of the wallpaper, otherwise # you have to create the splashes twice # work on until Beta freeze task start_splash_screens "Create Splash Screens" { depends !alpha_drop length 9d flags design } # for ics file task start_splash_screens_cal "Start Splash Screens" { depends !alpha_drop flags design } # work on until Beta freeze task finalize_splash_screens "Finalize Splash Screens" { depends !start_splash_screens length 4d flags design } # work on until Beta freeze task beta_wallpaper "Prepare wallpaper for Beta" { depends !alpha_drop length 13d flags design } # send reminder on Monday before Wednesday meeting task remind_alpha_go_not "Reminder: ${content_title} ${major} Alpha Go/No-Go Meeting" { depends !create_alpha_compose {gaplength 2d} flags pm } task alpha_go_not "${content_title} ${major} Alpha Go/No-Go Meeting (17:00 US Eastern)" { depends !create_alpha_compose {gaplength 4d} flags releng, quality, devel, pm, proto, blocker } task trans_software_rebuild1 "Remind f-dev-announce to Rebuild All Translated Packages" { depends !feature_freeze {gaplength 5d } flags translation } task software_string_freeze "Software String Freeze" { depends !feature_freeze {gaplength 6d } flags devel, translation, pm, proto, releng, key, roadmap } task announce_software_string_freeze "Announce Software String Freeze Reached" { depends !feature_freeze {gaplength 6d } flags pm } task software_translation "Software Translation"{ # KEY ADJUSTMENT POINT # if the alpha slips, add additional time to this task (or maybe not - this may be attached to string freeze dates increasing - double check this! -robyn) task trans_software "Software Translation Period" { depends !!software_string_freeze length 25d flags translation } # KEY ADJUSTMENT POINT # If the alpha slips, add additional time to 'gaplength' for this task which essentially extends the freeze task remind_build_trans_software "Remind f-dev-announce to build F${major} collection pkgs for trans team" { depends !!software_string_freeze {gaplength 9d} flags translation } task request_review_image "Create Rel-Eng ticket for Live Image compose for Software Review UI" { depends !remind_build_trans_software {gaplength 4d} flags translation } task build_trans_software "Build F-${major} collection packages for all language translators" { depends !request_review_image flags releng, devel } task compose_review_image "Compose of Live Image of Software Review UI for Translation" { depends !build_trans_software flags releng } task trans_software_review "Review and correct software translation in built UI" { depends !build_trans_software {gaplength 1d} length 6d flags translation } task trans_software_rebuild2 "Remind f-dev-announce to Rebuild All Translated Packages" { depends !trans_software_review flags translation } # Should land one week before the "Beta Change Deadline"--affected by "remind_build_trans_software" # Double check after adjusting "trans_software" or length of Alpha tasks for a slip task trans_software_deadline "Software: Translation Deadline (PO Files complete)" { depends !trans_software_review flags translation, roadmap, key } # for ical file task start_trans_rebuild "Software: Start Rebuild all translated packages" { depends !trans_software_deadline flags devel } task trans_rebuild "Software: Rebuild all translated packages" { depends !trans_software_deadline length 5d flags devel } } #end software_translation # Send reminder on Monday before Thursday meeting task alpha_meeting_reminder "Reminder: Alpha Release Readiness Meeting" { depends !feature_freeze {gaplength 10d} flags pm } task alpha_meeting "${content_title} ${major} Alpha Release Readiness Meeting" { depends !alpha_meeting_reminder {gaplength 3d} flags releng, pm, quality, docs, design, translation, marketing, web } # land on a Tuesday task shadow_before_alpha_compose "SHADOW: 1.5 weeks before Alpha Compose" { precedes !create_alpha_compose {gaplength 8d} flags hidden } task create_alpha_tc "Create Alpha Test Compose (TC)" { depends !shadow_before_alpha_compose flags releng, proto } # test Wed to Wed task test_alpha_tc "Test Alpha 'Test Compose'" { depends !create_alpha_tc {gaplength 1d} length 6d flags quality, proto } task alpha_kernel_build "Submit Kernel Build for Alpha RC Compose" { depends !alpha_deadline flags devel } task alpha_installer_build "Submit Installer Build for Alpha RC Compose" { depends !alpha_deadline {gaplength 1d} flags devel } task create_alpha_compose "Compose Alpha Candidate" { depends !alpha_deadline {gaplength 2d} flags releng, proto } task test_alpha_candidate "Test Alpha Candidate" { depends !create_alpha_compose length 5d flags quality, proto } # for ical file task start_stage_alpha "Start Stage & Sync Alpha to Mirrors" { depends !test_alpha_candidate flags releng } task notify_mirrors_alpha "Notify Mirrors of ${content_title} ${major} Alpha" { depends !start_stage_alpha {gaplength 1d} flags releng } task stage_alpha "Stage & Sync Alpha to Mirrors" { depends !test_alpha_candidate length 3d flags releng, proto } task alpha_export_control "Alpha Export Control Reporting" { depends !start_stage_alpha {gaplength 1d} flags releng, pm } task alpha_announce "Create Alpha Announcement (Marketing & Docs)" { depends !alpha_meeting length 2d flags docs, marketing } task alpha_drop "Alpha Public Availability" { depends !stage_alpha flags releng, docs, quality, design, pm, proto, devel, key, marketing, roadmap, spins, blocker, infrastructure, fpl } task ambassador_start "FAmSCo heads Ambassador Wide Meetings Preparing For ${content_title} ${major}" { depends !alpha_drop {gaplength 7d} length 5d flags ambassadors } task start_swag "FAmSCo and Regional teams call for Preparation of Media/SWAG" { depends !alpha_drop {gaplength 7d} flags ambassadors } task swag_poc "Regional Team Meetings and Select POC for Swag/Media production" { depends !alpha_drop {gaplength 8d} length 5d flags ambassadors } task swag_funding_request "Regional Teams Submit Funding Request For Swag/Media Production" { depends !alpha_drop {gaplength 8d} length 5d flags ambassadors } # this task was proposed by Mike McGrath and to be performed by FES task nvr_testing "NVR Update Check testing" { depends !stage_alpha length 1d flags quality } # KEY ADJUSTMENT POINT task alpha_release_notes "Alpha Release Notes" { # create for ical task start_alpha_beats "Start Alpha Beat and Feature Page Review" { depends !!feature_freeze {gaplength 6d} flags docs, quality } task validate_beat_writers "Validate Former Beat Writers" { depends !!feature_freeze length 5d flags docs } task recruite_beat_writers "Recruit New Beat Writers" { depends !validate_beat_writers length 5d flags docs } task comb_alpha_beats "Comb Beats and Feature Pages for Alpha" { depends !start_alpha_beats length 2d flags docs, quality } task notify_devel_relnotes "Notify Development About Alpha Release Notes" { depends !!alpha_deadline flags docs } # KEY ADJUSTMENT POINT # Beta release notes depend on this task # If alpha candidate is not ready on time add extra time for the release notes here task prep_alpha_notes "Prepare Alpha Release Notes (1 page)" { depends !comb_alpha_beats length 6d flags docs, quality } task post_notes "Post Alpha Release Notes One-Page" { depends !prep_alpha_notes {gaplength 1d} flags docs } } #end alpha_release_notes # two days to create web banner # one day for websites team to add to www.fedoraproject.org # should be live one day before the release task alpha_banner "Alpha Release Banner" { precedes !alpha_drop {gaplength 3d} task alpha_create_banner "Create Alpha Website Banner" { length 2d flags design } task alpha_publish_banner "Add Alpha Banner to Website" { length 1d flags web } } # KEY ADJUSTMENT POINT # Three weeks for Alpha Testing # IF "Beta Deadline" is missed the length of this task is extended-- # in that case corresponding change needs to be made to "software_translation" task task test_alpha "Alpha Testing" { depends !stage_alpha length 15d flags quality, proto } task review_bookmarks "Review Firefox Bookmarks For Update" { depends !stage_alpha length 5d flags marketing } task update_bookmarks "Update and Package Firefox Bookmarks" { depends !review_bookmarks length 2d flags marketing } task tag_bookmarks "Tag Updated Bookmarks Package for ${content_title} ${major}" { depends !update_bookmarks flags marketing } # Explicit task to mark end of alpha # Also permits subsequent tasks to cleanly depend on it using "precedes" (because it is zero length) task alpha_end "End of Alpha Testing" { depends !test_alpha flags quality } task beta_marketing_notes "Marketing: Beta One Page Release Notes" { depends !alpha_end {gaplength 5d} length 5d flags marketing } } task beta "Beta Release" { # KEY ADJUSTMENT POINT--if the Alpha slips, make sure the blocker meeting # tasks continue to line up correctly # Once the Alpha is staged, start holding Blocker meetings for the Beta task remind_beta_blocker1 "Reminder: Beta Blocker Meeting (${content}${major}beta) #1" { depends !!alpha.create_alpha_compose {gaplength 9d } flags pm } task beta_blocker1 "Beta Blocker Meeting (${content}${major}beta) #1" { depends !!alpha.stage_alpha {gaplength 3d } flags quality, releng, devel, pm, blocker } task beta_releng_tickets "File All Release Engineering Tickets for ${content_title} ${major} Beta" { depends !!alpha.stage_alpha {gaplength 2d } flags releng } task remind_beta_blocker2 "Reminder: Beta Blocker Meeting (${content}${major}beta) #2" { depends !beta_blocker1 {gaplength 3d} flags pm } task beta_blocker2 "Beta Blocker Meeting (${content}${major}beta) #2" { depends !beta_blocker1 {gaplength 5d} flags releng, quality, devel, pm, blocker } # raise awareness one week before Beta compose task daily_beta_blocker "Daily Review & Notification of Open Beta Blocker Bugs" { depends !beta_blocker2 {gaplength 1d} length 4d flags releng, quality, devel, pm, blocker } task remind_beta_blocker3 "Reminder: Beta Blocker Meeting (${content}${major}beta) #3" { depends !beta_blocker2 {gaplength 3d} flags pm } task beta_blocker3 "Beta Blocker Meeting (${content}${major}beta) #3" { depends !beta_blocker2 {gaplength 5d} flags releng, quality, devel, pm, blocker } task remind_beta_blocker4 "Reminder: Beta Blocker Meeting (${content}${major}beta) #4" { depends !beta_blocker3 {gaplength 3d} flags pm } task beta_blocker4 "Beta Blocker Meeting (${content}${major}beta) #4" { depends !beta_blocker3 {gaplength 5d} flags releng, quality, devel, pm, blocker } # Create anchor one week before for Beta Deadline task shadow_beta_deadline "SHADOW: one week before Beta Deadeline" { precedes !beta_deadline {gaplength 6d} flags hidden } task remind_beta_deadline "Remind Beta Deadline in 1 week" { depends !shadow_beta_deadline flags pm } task remind_final_freatures "Remind Features 100% Complete in 1 week" { depends !shadow_beta_deadline flags pm } task beta_spins_ks "Build spin-kickstarts package from master" { depends !shadow_beta_deadline flags spins } task coordinate_swag_design "FAmSCo Coordinate Media/Swag/Poster artwork with Design team" { depends !shadow_beta_deadline {gaplength 5d} length 10d flags ambassadors } # Two weeks before the public Beta Release task beta_deadline "Beta Change Deadline" { depends !!alpha.test_alpha flags releng, docs, quality, pm, proto, devel, key, marketing, spins, roadmap } task feature_complete "Features 100% Complete Deadline" { depends !!alpha.test_alpha flags releng, docs, quality, pm, proto, devel, key, marketing, roadmap, fpl } # KEY ADJUSTMENT POINT--manually adjust if length of time before Beta ships changes task beta_infrastructure_freeze "Beta Infrastructure Change Freeze" { depends !!alpha.test_alpha length 10d flags infrastructure, releng } task announce_beta_deadline "Announce Beta Deadline & Feature Complete" { depends !!alpha.test_alpha flags pm } task final_feature_fesco "Deliver features < 100% to FESCo" { depends !beta_deadline {gaplength 1d} flags pm } task brief_ambassadors "Brief Ambassadors on upcoming release" { depends !beta_deadline {gaplength 5d} length 5d flags marketing } # The Release Slogan is created by the Marketing team based on # the initial artwork theme. The Design Team needs to know # the slogan to create the release banners # prepare 6 weeks before GA (2 weeks of work) # go live 1 month before GA # KEY ADJUSTMENT POINT task create_countdown "Create Count Down Graphic" { depends !beta_deadline length 10d flags design } task publish_countdown "Publish Count Down Graphic" { depends !create_countdown length 1d flags web } task beta_release_notes "Beta Release Notes" { task unclaimed_beats "Write Unclaimed Wiki Beats" { depends !!!alpha.alpha_drop length 6d flags docs } task port_wiki_publican "Port Wiki to Publican" { depends !unclaimed_beats {gaplength 1d} length 3d flags docs } task remind_trans_beta_notes "Remind Translation: Beta Rel Notes POT Coming" { depends !unclaimed_beats flags docs } task start_release_notes_pot1 "Start nightly POT files all fed-rel-notes.rpm content" { depends !port_wiki_publican {gaplength 1d} flags docs } task release_notes_pot1 "Generate nightly POT files all fed-rel-notes.rpm content" { depends !port_wiki_publican {gaplength 1d} length 13d flags docs } task remind_devel_beta_notes"Remind announce-list & f-devel-announce: Wiki Freeze" { depends !unclaimed_beats {gaplength 1d} flags docs } task beta_wiki_freeze "Wiki Freeze: Beta Release Notes" { depends !remind_devel_beta_notes {gaplength 2d} flags docs } task trans_release_notes "Translate Beta Release Notes" { depends !port_wiki_publican {gaplength 1d} length 14d flags translation } # KEY ADJUSTMENT POINT task build_trans_review "Ongoing build translation review htmls" { depends !beta_wiki_freeze length 5d flags docs } # KEY ADJUSTMENT POINT task trans_review_beta "Review and correct Beta Release Notes (daily buids html)" { depends !beta_wiki_freeze length 5d flags translation } task trans_release_notes_deadline "Translation Deadline: Beta Release Notes (PO Files complete)" { depends !trans_review_beta flags translation, docs } task build_beta_relnotes "Build f-r-n.rpm and Push to updates-candidate" { depends !trans_release_notes_deadline length 2d flags docs, translation } task final_release_notes_reminder "Reminder: Send Project Wide-Final Release Notes Deadlines" { depends !!beta_deadline {gaplength 7d} flags docs } # one day before release which is 2D after meeting task web_notes "Build and Post Beta release-notes to docs.fedoraproject.org" { depends !!beta_meeting {gaplength 2d} flags docs } # one day before release which is 2D after meeting task tech_web_notes "Build and Post Fedora Technical Notes to docs.fedoraproject.org" { depends !!beta_meeting {gaplength 2d} flags docs } } # end beta_release_notes task splash_deadline "Deadline: Beta Splash Screens" { depends !!alpha.finalize_splash_screens flags design } task package_final_splash "Package: Beta Splash Screens" { depends !!alpha.finalize_splash_screens length 2d flags design } task package_beta_wallpaper "Package: Beta Wallpaper"{ depends !!alpha.beta_wallpaper length 2d flags design } task package_supplemental_wallpaper "Package: Supplemental Wallpaper"{ depends !!alpha.beta_wallpaper flags design } task beta_meeting_announce "Announce: Beta Release Readiness Meeting" { flags pm } # KEY ADJUSTMENT POINT if Beta Deadline changes task beta_meeting_reminder "Reminder: Beta Release Readiness Meeting" { depends !beta_deadline {gaplength 4d} flags pm } task beta_meeting "${content_title} ${major} Beta Release Readiness Meeting" { depends !beta_meeting_reminder {gaplength 3d} flags releng, pm, quality, docs, design, translation, marketing, web } task beta_announce "Create Beta Announcement (Docs & Marketing)" { depends !beta_meeting length 2d flags docs, marketing } # placeholder if slip # task beta_blocker4 "Beta Blocker Day (${content}${major}beta) #4" { # depends !!beta_blocker3 {gaplength 5d} # flags releng, quality, devel, pm, blocker # } task shadow_before_beta_compose "SHADOW: 1.5 weeks before Beta Compose" { precedes !create_beta_compose {gaplength 9d} # with 'precedes', gaplength pushes date backwards flags hidden } task beta_installer_build1 "Submit Installer Build for Beta TC Compose" { depends !shadow_before_beta_compose flags devel } task create_beta_tc "Create Beta Test Compose (TC)" { depends !shadow_before_beta_compose {gaplength 2d} flags releng, proto } task test_beta_tc "Test Beta 'Test Compose'" { depends !create_beta_tc length 6d flags quality, proto } task beta_rawhide_install "Pre-Beta Acceptance Test Plan" { precedes !create_beta_tc length 5d flags quality } task remind_beta_go_not "Reminder: ${content_title} ${major} Beta Go/No-Go Meeting" { depends !create_beta_compose {gaplength 2d} flags pm } task beta_go_not "${content_title} ${major} Beta Go/No-Go Meeting (17:00 US Eastern)" { depends !create_beta_compose {gaplength 4d} flags releng, quality, devel, pm, proto, blocker } task beta_kernel_build "Submit Kernel Build for Beta RC Compose" { depends !beta_deadline flags devel } task beta_installer_build "Submit Installer Build for Beta RC Compose" { depends !beta_deadline {gaplength 1d} flags devel } # KEY ADJUSTMENT POINT if Beta release date slips--add more time to this task task create_beta_compose "Compose Beta Candidate" { depends !beta_deadline {gaplength 2d} flags releng, proto } task call_for_events "FAmSCo and Regional Teams Call for Release Events" { depends !beta_deadline {gaplength 12d} flags ambassadors } task logistics_budget "Regional Teams Plan Regional Logistics for Release Events & File Budget Requests" { depends !call_for_events length 10d flags ambassadors } task test_beta2 "Test Beta Candidate" { depends !create_beta_compose length 5d flags quality, proto } task start_stage_beta "Start Stage & Sync Beta to Mirrors" { depends !test_beta2 flags releng } task notify_mirrors_beta "Notify Mirrors of ${content_title} ${major} Beta" { depends !start_stage_beta {gaplength 1d} flags releng } task stage_beta "Stage & Sync Beta to Mirrors" { depends !test_beta2 length 3d flags releng, proto } task beta_export_control "Beta Export Control Reporting" { depends !start_stage_beta {gaplength 1d} flags releng, pm } # two days to create web banner # one day for websites team to add to www.fedoraproject.org # should be live one day before the release task beta_banner "Beta Release Banner" { precedes !beta_drop {gaplength 3d} task beta_create_banner "Create Beta Website Banner" { length 2d flags design } task beta_publish_banner "Add Beta Banner to Website" { length 1d flags web } } task shadow_before_beta_drop "SHADOW: One Day before Public Beta release" { precedes !beta_drop {gaplength 2d} flags hidden } # Five weeks prior to GA task beta_drop "Beta Release Public Availability" { depends !stage_beta flags docs, releng, quality, pm, translation, proto, design, devel, key, marketing, roadmap, blocker, spins, infrastructure, fpl } task event_deadline "Release Event Submission Deadline" { depends !logistics_budget {gaplength 1d} flags ambassadors } task budget_allocations "FAmSCo Review Budget Allocations" { depends !event_deadline flags ambassadors } task irc_sessions "FAmSCo Regional IRC town halls" { depends !beta_drop {gaplength 8d} length 10d flags ambassadors } # Three weeks of public testing # Ends on a Monday and is followed by Final Release Deadline task beta_test "Beta Testing" { depends !stage_beta length 14d flags quality, proto } task websites_trans_reminder "Reminder to f-websites-list about POT/PO dates in 7 days" { depends !beta_drop flags translation, web } # two weeks to create # should be completely done and ready for hand off to Ambassadors two weeks before GA # Ambassadors should have made prior arrangements to flip artwork over to media producer # at this time. task media "Create DVD/CD label and sleeve artwork" { # not a "great" place to anchor to as it could move # needs to start four weeks before GA depends !beta_drop length 10d flags design } task rc_rawhide_install "Pre-RC Acceptance Test Plan" { depends !stage_beta {gaplength 7d} length 4d flags quality } task testmile "End of Beta Testing" { depends !beta_test flags quality } } } # E N D of TestingPhase task LaunchPhase "Launch Phase" { # four weeks before GA, ambassadors create release posters # two weeks before GA to art team does final polish to posters # posters are ready on release day task release_posters "Release Party Posters" { depends !!TestingPhase.beta.beta_drop task create_posters "FAmSCo with Design Team Create Release Party Posters" { length 10d flags ambassadors } task polish_poster "Polish/Finalize Release Party Posters" { depends !create_posters length 9d flags design } } task screenshots "Update and freeze the screenshots page" { depends !!TestingPhase.beta.stage_beta {gaplength 5d} length 5d flags marketing } task final_screenshots "Marketing: Final Screen Shots" { depends !screenshots length 5d flags marketing } task final_marketing_notes "Marketing: Final One Page Release Notes" { depends !screenshots length 5d flags marketing } task briefings "Brief news distribution network" { depends !screenshots length 5d flags marketing } task monitor "Monitor news sites to provide corrections & info" { depends !screenshots length 29d flags marketing } task rc "Release Candidate" { task final_releng_tickets "File All Release Engineering Tickets for ${content_title} ${major} GA" { depends !!!TestingPhase.beta.stage_beta {gaplength 2d } flags releng } task remind_ga_blocker1 "Reminder: Final Blocker Meeting (${content}${major}blocker) #1" { depends !!!TestingPhase.beta.create_beta_compose {gaplength 4d} flags pm } task ga_blocker1 "Final Blocker Meeting (${content}${major}blocker) #1" { depends !!!TestingPhase.beta.start_stage_beta {gaplength 1d} flags releng, quality, devel, pm, blocker } task remind_ga_blocker2 "Reminder: Final Blocker Meeting (${content}${major}blocker) #2" { depends !ga_blocker1 {gaplength 3d} flags pm } task ga_blocker2 "Final Blocker Meeting (${content}${major}blocker) #2" { depends !ga_blocker1 {gaplength 5d} flags releng, quality, devel, pm, blocker } task remind_ga_blocker3 "Reminder: Final Blocker Meeting (${content}${major}blocker) #3" { depends !ga_blocker2 {gaplength 3d} flags pm } task ga_blocker3 "Final Blocker Meeting (${content}${major}blocker) #3" { depends !ga_blocker2 {gaplength 5d} flags releng, quality, devel, pm, blocker } # two days before Final Deadline task shadow_before_final_deadline "SHADOW: one day before Final Deadline" { precedes !final_change_deadline {gaplength 2d} flags hidden } task kernel_debug "Disable Kernel debug and submit new Kernel build for RC" { depends !shadow_before_final_deadline flags devel } task final_change_deadline "Final Change Deadline" { depends !!!TestingPhase.beta.beta_test flags releng, devel, proto, pm, key, spins } task check_swag "FAmSCo and Regional Teams Meet to Address Unresolved Events/Media/Swag Issues" { depends !final_change_deadline {gaplength 1d} flags ambassadors } # one day before Final Deadline task final_wallpaper "Package Final Wallpaper" { depends !shadow_before_final_deadline flags design } # one day before Final Deadline task final_splash "Package Final Splash Screens" { depends !shadow_before_final_deadline flags design } task announce_final_change_deadline "Announce Final Freeze & Implications" { depends !final_change_deadline flags pm } # ADJUST FOR NEW RELEASE--EOL Version task eol_warning "File RHT Eng-ops ticket for Fedora 15 EOL Bugzilla warning" { depends !final_change_deadline flags pm } # KEY ADJUSTMENT POINT--manually adjust if length of time before Final release ships changes task final_infrastructure_freeze "Final Infrastructure Change Freeze" { depends !!!TestingPhase.beta.beta_test {gaplength 1d} length 10d flags infrastructure, releng } task remind_ga_blocker4 "Reminder: Final Blocker Meeting (${content}${major}blocker) #4" { depends !ga_blocker3 {gaplength 3d} flags pm } task ga_blocker4 "Final Blocker Meeting (${content}${major}blocker) #4" { depends !ga_blocker3 {gaplength 5d} flags releng, quality, devel, pm, blocker } # raise awareness one week before final compose task daily_ga_blocker "Daily Review & Notification of Open Final Blocker Bugs" { depends !ga_blocker3 {gaplength 1d} length 4d flags releng, quality, devel, pm, blocker } task ga_blocker5 "Final Blocker Meeting (${content}${major}blocker)--Blocks RC Compose" { depends !ga_blocker4 {gaplength 1d} flags releng, quality, devel, pm, blocker } task ga_release_notes "Final Release Notes" { # One day before public beta release task final_release_note_wiki_reminder "Reminder to Development: Wiki Freeze in 7 days" { depends !!!!TestingPhase.beta.shadow_before_beta_drop flags docs } task prep_ga_notes "Prepare GA Release Notes" { depends !!!!TestingPhase.beta.beta_drop flags docs, quality } task ga_release_notes_freeze "String Freeze: GA Release Notes" { depends !prep_ga_notes {gaplength 4d} flags docs } task wiki_ga_port "Port diff wiki content to Publican" { depends !ga_release_notes_freeze length 5d flags docs } task remind_trans_ga_notes "Remind Translation: RPM Freeze (no more POTs) in 5 days" { depends !ga_release_notes_freeze {gaplength 2d} flags docs } # KEY ADJUSTMENT POINT--if length of Beta changes this task length needs to change too task ga_pot_trans "Translate Final Release Notes (POT to PO)" { depends !!!!TestingPhase.beta.beta_release_notes.trans_release_notes_deadline {gaplength 1d} length 24d flags translation } task ga_release_notes_pot "Generate GA Release Notes POT files for Translation" { depends !wiki_ga_port flags docs } task build_trans_review_final "Build GA release note htmls for Translation" { depends !ga_release_notes_pot {gaplength 1d} length 4d flags docs } task build_ga_trans_review "Review and correct GA Release Notes (daily builds html)" { depends !ga_release_notes_pot {gaplength 1d} length 4d flags docs, translation } task remind_ga_trans_deadline "Remind Translators of GA Release Notes Deadline in 4 days" { depends !ga_release_notes_pot {gaplength 3d} flags docs } task ga_release_notes_po "Translation Deadline: GA rel-notes (PO Files complete)" { depends !ga_pot_trans flags translation } task ga_release_notes_rpm "Build fedora-release-notes.rpm" { depends !ga_release_notes_po length 2d flags docs } } #end final release notes # Three banners are created for the GA release (based on the Slogan from Marketing) # 1) large banner--fedoraproject.org front page # 2) "the release is out, go get it"--fedoraproject.org front page # 3) release name on start.fedoraproject.org # banners take one week to complete # banners should be completed one week before GA # banners are translated the week up until GA # translated during the week up until GA task ga_create_banners "Create Final Release Banners" { depends !!!TestingPhase.beta.testmile length 9d flags design } # Start one day before Final Change Deadline task create_ga_announce "Create GA Announcement (Docs & Marketing)" { depends !!!LaunchPhase.rc.shadow_before_final_deadline length 7d flags docs, marketing } task translate_ga_announce "GA Announcement available for translation (optional)" { depends !create_ga_announce length 5d flags translation } task ga_publish_banners "Add Final Release Banners to Website" { depends !ga_create_banners length 1d flags web } # web propertities need to be updated and translated # Tasks start at time Beta Release goes out # http://fedoraproject.org/en/index # http://fedoraproject.org/en/get-fedora # http://fedoraproject.org/en/join-fedora # http://fedoraproject.org/en/get-help task web_content_update "Update Website Content" { depends !!!TestingPhase.beta.beta_drop length 5d flags web } task web_freeze "Website String Freeze" { depends !web_content_update flags web } task web_create_pot "Create Website POT Files" { depends !web_freeze length 1d flags web } task trans_web "Translation Period for Website (POT to PO)" { depends !web_create_pot length 9d flags translation } task review_trans_web "Review and correct Website translations" { depends !trans_web length 4d flags translation, web } task finish_trans_web "Translation Deadline: Websites (POs done)" { depends !review_trans_web flags translation } task publish_trans_web "Publish Translations on Website (fedoraproject.org)" { depends !review_trans_web length 1d flags web } task final_meeting_reminder "Reminder: Final Release Readiness Meeting" { depends !!!TestingPhase.beta.beta_test {gaplength 5d} flags pm } task ga_meeting "${content_title} ${major} Final Release Readiness Meeting" { depends !final_meeting_reminder {gaplength 3d} flags releng, pm, quality, docs, design, marketing, translation, web } task shadow_before_final_compose "SHADOW: one week before RC Compose" { precedes !start_final_compose {gaplength 8d} flags hidden } task final_installer_build1 "Submit Installer Build for Final TC Compose" { depends !shadow_before_final_compose flags devel } task create_final_tc "Create 'Final' Test Compose (TC)" { depends !shadow_before_final_compose {gaplength 2d} flags releng, proto } task test_final_tc "Test 'Final' Test Compose (TC)" { depends !create_final_tc length 4d flags quality, proto } task final_installer_build "Submit Installer Build for Final RC Compose" { depends !final_change_deadline flags devel } task start_final_compose "Compose 'Final' RC: DVD, Live, Spins" { depends !final_change_deadline {gaplength 1d} length 1d flags releng, key, roadmap, proto } task early_iso "Regional Teams Obtain Final Release ISOs from Release Engineering for duplication" { depends !test_final {gaplength 2d} length 3d flags ambassadors } task regional_marketing "Regional Coordination with Marketing for Release Events" { depends !test_final {gaplength 2d} length 5d flags ambassadors } task deliver_final "Deliver RC to QA for Testing" { depends !start_final_compose flags releng, proto } task test_final "Test 'Final' RC" { depends !deliver_final length 4d flags quality } # for ics file task start_stage_final "Start Stage & Sync RC to Mirrors" { depends !test_final {gaplength 2d} flags releng } task notify_mirrors_final "Notify Mirrors of ${content_title} ${major} Final" { depends !start_stage_final {gaplength 1d} flags releng } task stage_final "Stage & Sync RC to Mirrors" { depends !test_final {gaplength 2d} length 3d flags releng, proto } task package_spins_ks "Branch spin-kickstarts and build package from new branch" { depends !create_final_tc flags spins } task freeze_spins_ks "Spins kickstart package Freeze" { depends !create_final_tc flags spins } task enable_updates "Enable ${content_title} ${major} Updates" { depends !!!TestingPhase.beta.beta_test {gaplength 2d} flags releng } task remind_final_go_not "Reminder: ${content_title} ${major} Final Go/No-Go Meeting" { depends !start_final_compose {gaplength 1d} flags pm } # Hold on Tuesday instead of Wednesday (Alpha and Beta)--this provides a little cushion # if something goes wrong. Mirrors do not have to start sync until Thursday task final_go_not "${content_title} ${major} Final Go/No-Go Meeting (17:00 US Eeastern)" { depends !start_final_compose {gaplength 4d} flags releng, quality, docs, pm, proto, blocker } task final_export_control "Final Export Control Reporting" { depends !start_stage_final {gaplength 1d} flags releng, pm } # Zero-day tasks should start two Fridays before GA and finish the day before GA task zero_day_relnotes "Zero Day Release Notes" { task shadow_zero_day "SHADOW: Seven work week days before GA" { precedes !!!final {gaplength 7d} flags hidden } task zero_day_web "0-Day rel-notes update docs.fp.org" { depends !shadow_zero_day length 6d flags docs } task zero_day_rpm "0-Day rel-notes build updated rpm" { depends !shadow_zero_day length 6d flags docs } task zero_day_pot "0-Day rel-notes generate POT" { depends !shadow_zero_day length 6d flags docs } task zero_day_trans "Translate 0-Day Release Notes" { depends !shadow_zero_day length 6d flags translation } task zero_day_deadline "Translation Deadline: 0-Day (PO Files complete)" { depends !zero_day_trans flags translation } task web_post "Add translated zero-day updates to docs.fp.org" { depends !zero_day_trans flags docs } task post_tech_notes "Update and post Fedora Technical Notes to docs.fedoraproject.org" { depends !!!final flags docs } # Monday after Tuesday GA task push_updates_rpm "Push updated rel-notes RPMs to Updates repo" { depends !!!final {gaplength 4d} flags docs } } # zero_day_relnotes } # end of rc task task bugzilla_descrption "Reflect supported versions in Bugzilla product description" { depends !rc.stage_final flags pm } task final "Final (GA) Release" { depends !rc.stage_final flags quality, releng, docs, design, pm, translation, proto, devel, key, marketing, roadmap, spins, infrastructure, fpl } # ADJUST FOR NEW RELEASE--EOL Version task remind_eol "Send Email Reminder About Fedora 16 EOL Activities" { depends !final {gaplength 2d} flags releng, pm, devel } task event_reports "Hold Release Events and Publish Event Reports" { depends !final length 23d flags ambassadors } task spins_ga_ks "Build new spin-kickstarts package for updates (if necessary)" { depends !rc.stage_final flags spins } task marketing_post "Marketing Retrospective" { depends !final length 10d flags marketing } } # Starting in Fedora 13, the docs group wanted the Guides work to span almost # the entire release cycle--they don't fit nicely into different phases. # So we put them all here in their own block. # Run from Start until one week before GA task all_guides "${content_title} ${major} Guides" { # Run from Start until "Branch Guides" task continue_guides_trans "Continue translation of guides in branch of previous release " { length 70d flags translation } task test_branch_guides "Test master branches of guides against Alpha and correct" { depends !!TestingPhase.alpha.stage_alpha length 10d flags docs } task branch_guides "Branch Guides" { depends !test_branch_guides flags docs } task guides_pot "Create POT files for All Guides" { depends !branch_guides flags docs } task notify_trans "Notify trans that new Guide POT files available " { depends !guides_pot flags docs } task trans_all_guides "Translate All Guides (POT to PO)" { depends !guides_pot flags docs } task publish_draft "Publish draft guides" { depends !branch_guides flags docs } task annouce_publish_draft "Notify announce-list and f-devel-list draft guides available" { depends !publish_draft flags docs } # ADJUST FOR NEW RELEASE--make sure this task lands one day before GA task guides_trans "Translate All Guides (POT to PO)" { depends !guides_pot length 39d flags translation } task remind_trans_pot "Reminder to Trans that new POT files are coming for all guides" { depends !!TestingPhase.alpha.stage_alpha {gaplength 8d} flags docs } # Wednesday, one week after Beta Change Deadline task srpm_review "Remind new guide owners SRPM package review" { depends !!TestingPhase.beta.beta_deadline {gaplength 6d} flags docs } task shadow_before_beta_deadline "SHADOW: for Friday before Beta deadline" { precedes !!TestingPhase.beta.beta_deadline {gaplength 1d} flags hidden } # Friday before Final Change Deadline task remind_trans "Reminder to Trans that Final Guides POT files are coming" { depends !shadow_before_beta_deadline flags docs } task guides_string_freeze "String Freeze All Guides" { depends !!LaunchPhase.rc.final_change_deadline flags docs } task generate_final_pot "Generate final POT files for Guides" { depends !guides_string_freeze flags docs } task notify_trans_final "Notify Trans of Final Guides POT availability" { depends !guides_string_freeze flags docs } # Monday to Friday, two weeks before GA task build_daily "Daily builds of Final guides for Translation" { length 9d depends !!LaunchPhase.rc.final_change_deadline flags docs } # Monday to Friday, two weeks before GA task review_daily "Review and correct Final Translated Guides (daily builds html)" { depends !!LaunchPhase.rc.final_change_deadline length 9d flags translation } # Friday before GA task shadow_guides_trans_deadline "SHADOW: Translation Deadline: All Final Guides" { precedes !!LaunchPhase.final {gaplength 2d} flags hidden } # zero duration tasks (milestones/deadlines) need a shadow preceds task so they report correctly # Also needed for ICS files which only report milestones task guides_trans_deadline "Translation Deadline: All Final Guides" { depends !shadow_guides_trans_deadline flags translation } task test_guides_beta "Test guides against Beta and correct" { depends !!TestingPhase.beta.beta_drop length 4d flags docs } task refresh_pot "Refresh POT files for all guides against Beta" { depends !test_guides_beta flags docs } task notify_trans_refresh "Notify trans that POT files updated against Beta" { depends !refresh_pot flags docs } task republish_draft "Republish draft guides for Beta" { depends !test_guides_beta flags docs } task notify_revised_draft "Notify announce-list and f-devel-list revised draft guides available" { depends !republish_draft flags docs } # Friday before GA until the day before GA task guides_final_build "Final Build All Guides: All Languages" { depends !srpm_review length 3d flags docs } # Day before GA task shadow_publish_guides "SHADOW: Publish all guides to docs.fp.o (html,html-single,pdf)" { precedes !!LaunchPhase.final {gaplength 2d} flags hidden } task publish_guides "Publish all guides to docs.fp.o (html,html-single,pdf)" { depends !shadow_publish_guides flags docs } } # end of "all_guides" # Elections http://fedoraproject.org/wiki/Elections # Board & FESCo are reelected in every release # FAmSCo Elections are held once a year near the Halloween release # ADJUST FOR NEW RELEASE task elections "${content_title} ${next_major} Election Coordination" { # manually set start date that is five weeks before GA date (on a Tuesday) # this is cleaner than using 'precedes' # use 'duration' to make it easier for some tasks to land on weekends start 2013-09-24 flags elections, fpl, pm # Don't forget to do this! Or else bad things happen. task remind "Remind advisory-board list of upcoming election schedule" { } task solicit "Solicit volunteers for questionnaire and town halls" { depends !remind duration 7d } task wiki_update "Update wiki page https://fedoraproject.org/wiki/Elections with required information" { depends !solicit } task advertise_elections "Advertise elections schedule and pages" { depends !solicit } task announce_nominations "FPL/designee announces opening of nominations" { depends !solicit {gapduration 25d} } task open_questions "Questionnaire wrangler announces opening for questions"{ depends !announce_nominations } task collect_questions "Collect question on the wiki"{ depends !announce_nominations duration 8d } task collect_answers "Candidates write questionnaire answers" { depends !collect_questions duration 7d } task announce_town "Town hall wrangler announces schedule for town hall meetings" { depends !announce_nominations } task question_deadline "Questionnaire answers due from candidates" { depends !collect_answers } # five days before GA task present_answers "Wrangler presents questionnaire answers" { depends !collect_answers {gapduration 1d} duration 2d } task post_questions "All answers posted to questionnaire page, advertise to voters" { depends !present_answers } task town_hall "Town hall period" { depends !post_questions {gapduration 1d} duration 6d } task voting_application "Finalize Voting Application" { duration 1d depends !town_hall } task voting_start "Voting Begins" { depends !voting_application } task voting "Voting for general elections" { depends !voting_application duration 6d } task voting_end "Voting Ends" { depends !voting } task announce_results "Announce Results" { depends !voting_end {gapduration 1d} } } # end elections # The release naming process for the next release should end three weeks prior # to the end of the current release # It is easiest to start this task by hardcoding the start date to be 6 weeks before GA # 2010-09-14 # 1 week -- collect names # 1 week --fedora board reviews names # 2 weeks -- names reviewed by RHT legal # 1 week -- community vote on names # ADJUST FOR NEW RELEASE task naming "Name the ${content_title} ${next_major} Release" { start 2013-09-17 # manually set this date flags pm, fpl task gather "Collect ${content_title} ${next_major} Names on Wiki" { length 5.5d } task board "Board Review of Proposed ${content_title} ${next_major} Names" { depends !gather length 3d } task legal "Legal Review" { depends !board length 5d } task vote "Voting" { depends !legal length 4d } task announce_name "${content_title} ${next_major} Release Name Announced" { depends !vote {gapduration 1d} flags design, pm } } # end release naming task pr "Public Relations" { task video "Creative team videos" { flags fpl, pr task video_schedule "Meet w/Creative to schedule videos" { precedes !video1.review_spotlight1_video {gaplength 25d} length 5d } task video1 "Make spotlight video #1" { task review_spotlight1_video "Review video #1" { precedes !release_spotlight1_video length 15d } task release_spotlight1_video "Publish spotlight video #1" { depends !!!spotlight_feature_blogs.spotlight_feature1.spotlight_feature1_drop } } task video2 "Make spotlight video #2" { task review_spotlight2_video "Review video #2" { precedes !release_spotlight2_video length 15d } task release_spotlight2_video "Publish spotlight video #2" { depends !!!spotlight_feature_blogs.spotlight_feature3.spotlight_feature3_drop } } task release_video "Make release video" { task review_release_video "Review release video" { precedes !release_video length 20d } task release_video "Publish release video" { depends !!!!LaunchPhase.final } } } #end video task beta_release_blog "Beta press blog entry" { flags fpl, pr /** * By building the final task to be a 0-duration event * (milestone), depending on another given task date like a * release date, and having each other task below precede the * one following, these tasks should auto-adjust to fall on a * given date. */ task beta_release_blog_draft "Start drafting Beta blog" { precedes !beta_release_blog_legal length 10d } task beta_release_blog_legal "Red Hat PR send Beta blog to Legal" { precedes !beta_release_blog_intl length 5d } task beta_release_blog_intl "Red Hat PR send Beta blog to intl-pr list" { precedes !beta_release_blog_drop length 6d } task beta_release_blog_drop "Red Hat PR publish and send Beta blog to media contacts" { depends !!!TestingPhase.beta.beta_drop } } task spotlight_feature_blogs "Spotlight feature press blogs" { task spotlight_feature1 "Spotlight feature #1" { task spotlight_feature1_draft "Draft spotlight #1 blog entry" { precedes !spotlight_feature1_legal length 5d flags fpl, pr } task spotlight_feature1_legal "Red Hat PR send spotlight #1 blog entry draft to legal" { precedes !spotlight_feature1_drop length 5d flags fpl, pr } task shadow_spotlight_feature1 "SHADOW: Three weeks until GA" { precedes !!!!LaunchPhase.final { gaplength 16d } length 1d flags hidden } task spotlight_feature1_drop "Red Hat PR publish spotlight #1 blog entry" { depends !shadow_spotlight_feature1 flags fpl, pr } } task spotlight_feature2 "Spotlight feature #2" { task spotlight_feature2_draft "Draft spotlight #2 blog entry" { precedes !spotlight_feature2_legal length 5d flags fpl, pr } task spotlight_feature2_legal "Red Hat PR send spotlight #2 blog entry draft to legal" { precedes !spotlight_feature2_drop length 5d flags fpl, pr } task spotlight_feature2_drop "Red Hat PR publish spotlight #2 blog entry" { depends !shadow_spotlight_feature2 flags fpl, pr } task shadow_spotlight_feature2 "SHADOW: Two weeks until GA" { precedes !!!!LaunchPhase.final { gaplength 11d } length 1d flags hidden } } task spotlight_feature3 "Spotlight feature #3" { task spotlight_feature3_draft "Draft spotlight #3 blog entry" { precedes !spotlight_feature3_legal length 5d flags fpl, pr } task spotlight_feature3_legal "Red Hat PR send spotlight #3 blog entry draft to legal" { precedes !spotlight_feature3_drop length 5d flags fpl, pr } task spotlight_feature3_drop "Red Hat PR publish spotlight #3 blog entry" { depends !shadow_spotlight_feature3 flags fpl, pr } task shadow_spotlight_feature3 "SHADOW: One week until GA" { precedes !!!!LaunchPhase.final { gaplength 6d } duration 1d flags hidden } } } task usb_keys_prebriefs "USB Keys and media pre-briefs" { flags fpl, pr task buy_usb_keys "Purchase USB Keys" { precedes !prepare_usb_keys length 5d } task assess_press_kit "Check LiveUSB press review sheet for readiness" { precedes !update_press_one_sheet length 5d } task update_press_one_sheet "Update LiveUSB press review sheet" { precedes !send_usb_keys length 5d } task prepare_usb_keys "Prep USB keys with pre-release" { precedes !send_usb_keys length 5d } task send_usb_keys "Send USB keys to Red Hat PR for distribution" { precedes !media_prebriefs {gaplength 10d} length 3d } task distribute_usb_keys "Red Hat PR distribute USB keys to media contacts" { precedes !media_prebriefs {gaplength 7d} length 5d } task media_prebriefs "Hold media prebrief interviews" { precedes !!!LaunchPhase.final length 6d } } task redhat_com_update "Update Red Hat web site" { flags fpl, pr task web_graphics_discuss "Schedule meeting with Red Hat web team to plan launch" { length 5d precedes !web_promo_to_brand } task web_promo_to_brand "Send web promo ideas to Brand" { length 5d precedes !web_copy_review } task web_copy_review "Review and update www.redhat.com/Fedora copy" { length 5d precedes !web_copy_send_update } task web_copy_send_update "Send updated copy to Web team" { precedes !rh_web_goes_live { gaplength 11d } length 5d } task rh_web_goes_live "Red Hat website changes go live" { depends !!!LaunchPhase.final } } task ga_press_release "GA press release" { flags fpl, pr task ga_press_release_draft "Start drafting GA press release" { length 10d precedes !ga_press_release_legal } task ga_press_release_legal "Red Hat PR send GA press release to Legal" { length 5d precedes !ga_press_release_intl } task ga_press_release_intl "Red Hat PR send GA press release to intl-pr list" { length 6d precedes !ga_press_release_drop } task ga_press_release_drop "Red Hat PR publish and send GA press release to media contacts" { depends !!!LaunchPhase.final } } task ceo_blog "CEO press blog entry" { flags fpl, pr task ceo_prepare_final_rc "Prepare a final RC on USB for CEO" { precedes !ceo_send_final_rc length 2d } task ceo_send_final_rc "Send final RC USB key to CEO" { precedes !ceo_solicit_feedback length 2d } task ceo_solicit_feedback "Solicit CEO feedback on pre-release" { precedes !ceo_blog_draft length 4d } task ceo_blog_draft "Draft CEO blog" { precedes !ceo_blog_legal length 3d } task ceo_blog_legal "Red Hat PR send CEO blog to Legal" { precedes !ceo_blog_drop length 3d } task ceo_blog_drop "Red Hat PR publish and send CEO blog to media contacts" { depends !!!LaunchPhase.final {gaplength 1d} } } } } # Bitter End include "reports.tji" tagfile "tags" taskjuggler-3.5.0/examples/Fedora-20/icons/0000755000175000017500000000000012614413013017714 5ustar bernatbernattaskjuggler-3.5.0/examples/Fedora-20/icons/fedoralogo.png0000644000175000017500000001123012614413013022540 0ustar bernatbernatPNG  IHDR2zGsRGBbKGD pHYs B Bd+_tIMEM6|IDATx{\U"@`@䭠U(pGTfFBQ$"8fAW|0#"V $z DbgII]֭[U!LR{duUݺܳϷђBpZ` dZ"?g-doqm(I;ff;Y]Wh%{_laum#gmOr}=]WyK,ibׯ0ZPٶ>vv=T,(޵z=RYKh>pI|X|$PQFqL<>a ̛pyp4#0 P(ޥo$̌g]kfێ]L4[ݐs_a{ Ԣ+-y5d|O_ C˿1#+^~錛9/v^ ]kVӬK_`PUY|fƌQY{^D5*qk^gN;nywaA0{yk9BN.;.F5r [+Oآ[Ű3-2b/Qcώl-JK^ 9& *P(+ŌB_,`6bt)Xdז'Q -y5db1z &vWL;l<&t\s{U!6T P)سmSԨ[dt}2#O >iO^ I=mBo}l+![~J `<ΝHG5^I?gr j.@Ǟ|:ҏ=1<66=Dŭ kkbO?̞[^ja`~k`BvҀ<׹xvd̈1P9ut[%ILEǴъQf &3n($q9(7n}֏bCAiXn%Փ'Rٛ>P+\yson?ȣF+Ƹ(+\?b#~rݔI* yكU߫mޯt%}x 8*u+m;P{_\}Đڬݳ/=h<^[JT+5<W[ί#J۱uXJe7*k$X빎 nrf>9⫖Aq>2E c*}H=/}o_f'x[y)\ny:ǜ+j ॰U*'M=ߔ Lew~0TJ#Uv ϝ?Q b3>Yebuk6bĆ)ovY$ҹ(uNS ή$q,nQO -|&&fmd#|t+w{Iioѽ7nP(Dp?75+֗?2l6 [r!ε_@Q{WF(}{o@ .vCЭ+2Tv 3[ _\sO6-i}>e<,-'ڌ"e;|1B `q><K%.lUC^ g%HFHN>܂v}5./ZAKswD*t%Ň>$dms593%(, gy78s1YX X N%\itnUS=ILrW|!u1nO|`Y9eIN%oj Rӄ?4AFJbVǢOF`ji1۫~_+Stc!}k{&qdL`bB>a1qk =1I.u%HRC2  gAJ.DאpEN+*\݌g4DٕjR~i ڣGs )ǒ,EKܱ "ތǀ`Sl*33I\Hot3YKQ*UP ^Oeoj4xs_4@ss6sTvM}-a@acp~=Ks1JC|T"=o$TPddO.dXX_0-*x>N tx*;`NHsi!ИAz\%=H玉kD:{7n;ΞA,z+Eiq`:7 6I=fWԋ1ްI,X"SlT=B43PR[\oƯDڌ@MnHs]kzawr6{'fߺsajQ6&m(< ~E0 x53C8R~*eFXdiRNZ 8O˿\t5\u ̋sWWbU/*'5<\QdL` b*4/:\I#>L#3u{%tE~**!: =߬}H8%u4XlUQ!ި-2( l3~jJ|.3}sp;]%]ᾖc\Pto&FJe_[<7q~,L?ViYx*h2Ζ&I4+c6 l&+ &s )ǷtwIp3^#uCď{$uHכ /y /˻ NOj{+<ۛF܈xG':2H;I>J(ʞ&f Mx ĭ?P # Copyright 2011 Robyn Bergeron # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # This project schedule was derived from the original Fedora 20 # schedule way before the actual project start. By the time you are # looking at this file, the real schedule might have been updated # already. This is just an example file with example dates. The real # schedule can be found at http://fedoraproject.org/wiki/Schedule # These macros are set (edited) manually each week to create # the individual team schedule reminders, created by running # $ make calendar # Sunday macro weekly_start [2013-05-05] # Wednesday falling two weeks after starting Sunday macro weekly_end [2013-05-22] navigator menu macro all_navbar [ header -8<- == [[File:icons/fedoralogo.png|bottom]] ${content_title}-${major} Project Plan == <[navigator id="menu"]> ->8- ] macro FilePrefix [${content}-${major}-] macro HLPrefix [${content_title} ${major} ] textreport "Public Reports" { formats html ${all_navbar} timeformat "%a %Y-%m-%d" scenarios actual taskreport "index" { title "Project Overview" headline "${HLPrefix} Task Overview" columns name, start, end, duration, chart { scale month } sorttasks tree, actual.start.up, seqno.up hidetask hidden taskreport "${FilePrefix}planning-phase" { title "Planning Phase" headline "${HLPrefix} Planning Phase" taskroot f20.PlanningPhase } taskreport "${FilePrefix}development-phase" { title "Development Phase" headline "${HLPrefix} Development Phase" taskroot f20.DevelopmentPhase } taskreport "${FilePrefix}alpha-phase" { title "Alpha Testing Phase" headline "${HLPrefix} Alpha Testing Phase" taskroot f20.TestingPhase.alpha } taskreport "${FilePrefix}beta-phase" { title "Beta Testing Phase" headline "${HLPrefix} Beta Testing Phase" taskroot f20.TestingPhase.beta } taskreport "${FilePrefix}launch-phase" { title "Launch Phase" headline "${HLPrefix} Launch Phase" taskroot f20.LaunchPhase } hidetask hidden | isleaf() } taskreport "${content}-${major}-all-tasks" { title "All Tasks" headline "${content_title} ${major} Tasks" columns name, start, end, duration sorttasks tree, actual.start.up, seqno.up hidetask hidden } # key tasks # this report mirrors what should be on the wiki # https://fedoraproject.org/wiki/Releases/14/Schedule taskreport "${content}-${major}-key-tasks" { title "Milestones" headline "${content_title} ${major} Key Tasks & Milestones" columns name, start, end, chart sorttasks actual.start.up hidetask ~key } textreport "Team Reports" { sorttasks tree, actual.start.up columns no, name, start, end, duration timeformat "%a %Y-%m-%d" scenarios actual # Ambassador Team Reports taskreport "${content}-${major}-ambassadors-tasks" { title "Ambassador" headline "${content_title} ${major} Ambassadors Team Tasks" hidetask ~ambassadors } # Design Team Reports taskreport "${content}-${major}-design-tasks.html" { title "Design" headline "${content_title} ${major} Design Team Tasks" hidetask ~design } # Development Team Reports taskreport "${content}-${major}-devel-tasks" { title "Development" headline "${content_title} ${major} Development Team Tasks" hidetask ~devel } # Docs Team Reports taskreport "${content}-${major}-docs-tasks" { title "Docs" headline "${content_title} ${major} Docs Team Tasks" hidetask ~docs } # Marketing Team Reports taskreport "${content}-${major}-marketing-tasks" { title "Marketing" headline "${content_title} ${major} Marketing Team Tasks" hidetask ~marketing } # Release Engineering Team Reports taskreport "${content}-${major}-releng-tasks" { title "Release Engineering" headline "${content_title} ${major} Releng Team Tasks" hidetask ~releng } # Quality Team Reports taskreport "${content}-${major}-quality-tasks" { title "Quality" headline "${content_title} ${major} Quality Tasks" hidetask ~quality } # Spins SIG Reports taskreport "${content}-${major}-spins-tasks" { title "Spins SIG" headline "${content_title} ${major} Spins SIG Tasks" hidetask ~spins } # Translation Team Reports taskreport "${content}-${major}-trans-tasks" { title "Translation" headline "${content_title} ${major} Translation Tasks" hidetask ~translation } # Web Team Reports taskreport "${content}-${major}-web-tasks" { title "Web" headline "${content_title} ${major} Web Team Tasks" hidetask ~web & ~infrastructure } # This report is just a menu entry, not a real report. purge formats } textreport "Administrative Items" { sorttasks tree, actual.start.up columns no, name, start, end, duration timeformat "%a %Y-%m-%d" scenarios actual # Elections taskreport "${content}-${major}-elections" { title "Elections" headline "${content_title} ${major} Elections " hidetask ~elections } # FPL taskreport "${content}-${major}-fpl" { title "FPL" headline "${content_title} ${major} FPL" hidetask ~fpl } # PR taskreport "${content}-${major}-pr" { title "PR" headline "${content_title} ${major} Media/PR" hidetask ~pr } # Infrastructure taskreport "${content}-${major}-infrastructure" { title "Infrastructure" headline "${content_title} ${major} Infrastructure Freezes" hidetask ~infrastructure } purge formats } ### Miscelaneous reports ### textreport "Miscellaneous Reports" { sorttasks actual.start.up columns no, name, start, end, duration taskreport "${content}-${major}-pm-tasks" { title "PM" headline "${content_title} ${major} Project Management Tasks" hidetask ~pm } taskreport "${content}-${major}-blocker-meetings" { title "Blocker" headline "${content_title} ${major} Blocker Meeting" hidetask ~blocker & ~key } taskreport "${content}-${major}-draft-schedule" { title "Primary Tasks" headline "${content_title} ${major} Primary Tasks" sorttasks actual.start.up hidetask ~proto } purge formats } purge formats } textreport "Weekly CSV Reports" { formats csv start ${weekly_start} end ${weekly_end} sorttasks actual.start.up columns start, end, name timeformat "%m/%d/%Y" scenarios actual # Reports for weekly emails to team lists # These reports are post-processed by 'format-weekly-calendar.py' taskreport "${content}-${major}-ambassadors-weekly" { hidetask ~ambassadors } taskreport "${content}-${major}-design-weekly" { hidetask ~design } taskreport "${content}-${major}-devel-weekly" { hidetask ~devel } taskreport "${content}-${major}-docs-weekly" { hidetask ~docs } taskreport "${content}-${major}-fpl-weekly" { hidetask ~fpl } taskreport "${content}-${major}-infrastructure-weekly" { hidetask ~infrastructure } taskreport "${content}-${major}-marketing-weekly" { hidetask ~marketing } taskreport "${content}-${major}-pm-weekly" { hidetask ~pm } taskreport "${content}-${major}-pr-weekly" { hidetask ~pr } taskreport "${content}-${major}-quality-weekly" { hidetask ~quality } taskreport "${content}-${major}-releng-weekly" { hidetask ~releng & ~devel } taskreport "${content}-${major}-spins-weekly" { hidetask ~spins } taskreport "${content}-${major}-trans-weekly" { hidetask ~translation } taskreport "${content}-${major}-web-weekly" { hidetask ~web } purge formats } textreport "Miscellaneous CSV Reports" { formats csv scenarios actual timeformat "%m/%d/%Y" # Text export of entire schedule for outside parsing and analysis # This is required for other teams--DO NOT REMOVE taskreport "${content}-${major}" { columns name, id, note, resources, start, end, duration } # reports for new schedule prototyping and planning # not used once the release gets rolling taskreport "${content}-${major}-plan" { sorttasks actual.start.up columns start, end, name hidetask ~releng & ~quality } taskreport "${content}-${major}-devel" { sorttasks actual.start.up columns start, end, name # TODO: tj3 does not yet support the separator attribute #separator " " hidetask ~devel } purge formats } icalreport "${content}-${major}-all-milestones" { hidetask ~ismilestone(actual) scenario actual } icalreport "${content}-${major}-ambassadors-ics" { hidetask (~ismilestone(actual) | ~ambassadors) scenario actual } icalreport "${content}-${major}-design-ics" { hidetask (~ismilestone(actual) | ~design) scenario actual } icalreport "${content}-${major}-devel-ics" { hidetask (~ismilestone(actual) | ~devel) scenario actual } icalreport "${content}-${major}-docs-ics" { hidetask (~ismilestone(actual) | ~docs) scenario actual } icalreport "${content}-${major}-fpl-ics" { scenario actual hidetask ~fpl } icalreport "${content}-${major}-pr-ics" { hidetask ~pr scenario actual } icalreport "${content}-${major}-key-ics" { hidetask (~ismilestone(actual) | ~key) scenario actual } icalreport "${content}-${major}-marketing-ics" { hidetask (~ismilestone(actual) | ~marketing) scenario actual } icalreport "${content}-${major}-releng-ics" { hidetask (~ismilestone(actual) | ~releng) scenario actual } icalreport "${content}-${major}-quality-ics" { hidetask (~ismilestone(actual) | ~quality) scenario actual } icalreport "${content}-${major}-spins-ics" { hidetask (~ismilestone(actual) | ~spins) scenario actual } icalreport "${content}-${major}-translation-ics" { hidetask (~ismilestone(actual) | ~translation) scenario actual } icalreport "${content}-${major}-web-ics" { hidetask (~ismilestone(actual) | ~web) scenario actual } icalreport "${content}-${major}-pm-ics" { hidetask (~ismilestone(actual) | ~pm) scenario actual } taskjuggler-3.5.0/examples/Scrum/0000755000175000017500000000000012614413013016253 5ustar bernatbernattaskjuggler-3.5.0/examples/Scrum/Sprint 2 Burndown.csv0000644000175000017500000000063212614413013022151 0ustar bernatbernat"Date";"product.s2:plan.opentasks" "2012-02-01";5 "2012-02-02";5 "2012-02-03";5 "2012-02-06";5 "2012-02-07";5 "2012-02-08";5 "2012-02-09";5 "2012-02-10";5 "2012-02-13";5 "2012-02-14";5 "2012-02-15";5 "2012-02-16";5 "2012-02-17";5 "2012-02-20";5 "2012-02-21";5 "2012-02-22";5 "2012-02-23";5 "2012-02-24";5 "2012-02-27";5 "2012-02-28";5 "2012-02-29";5 "2012-03-01";5 "2012-03-02";4 "2012-03-05";4 "2012-03-06";4 taskjuggler-3.5.0/examples/Scrum/Product Burndown.csv0000644000175000017500000000065512614413013022175 0ustar bernatbernat"Date";"product:plan.opentasks" "2012-02-01";14 "2012-02-02";14 "2012-02-03";14 "2012-02-06";14 "2012-02-07";14 "2012-02-08";13 "2012-02-09";13 "2012-02-10";13 "2012-02-13";13 "2012-02-14";13 "2012-02-15";13 "2012-02-16";13 "2012-02-17";12 "2012-02-20";12 "2012-02-21";12 "2012-02-22";11 "2012-02-23";11 "2012-02-24";11 "2012-02-27";11 "2012-02-28";10 "2012-02-29";10 "2012-03-01";10 "2012-03-02";9 "2012-03-05";9 "2012-03-06";9 taskjuggler-3.5.0/examples/Scrum/Sprint 3 Burndown.csv0000644000175000017500000000063212614413013022152 0ustar bernatbernat"Date";"product.s3:plan.opentasks" "2012-02-01";5 "2012-02-02";5 "2012-02-03";5 "2012-02-06";5 "2012-02-07";5 "2012-02-08";5 "2012-02-09";5 "2012-02-10";5 "2012-02-13";5 "2012-02-14";5 "2012-02-15";5 "2012-02-16";5 "2012-02-17";5 "2012-02-20";5 "2012-02-21";5 "2012-02-22";5 "2012-02-23";5 "2012-02-24";5 "2012-02-27";5 "2012-02-28";5 "2012-02-29";5 "2012-03-01";5 "2012-03-02";5 "2012-03-05";5 "2012-03-06";5 taskjuggler-3.5.0/examples/Scrum/scrum.tjp0000644000175000017500000000477712614413013020142 0ustar bernatbernatproject "Scrum Example Project" 2012-02-01 +4m { now 2012-03-07 } resource r1 "R1" resource r2 "R2" resource r3 "R3" # This example uses a very simple WBS that groups tasks by sprint. For # larger projects, a classical WBS that breaks tasks into smaller # tasks and so on is probably more appropriate. The reports can then # select the sprint context by date. task product "Product" { task s1 "Sprint 1" { task t1 "T1" { effort 5d allocate r1 } task t2 "T2" { effort 3d allocate r1 depends !t1 } task t3 "T3" { effort 7d allocate r1 } task t4 "T4" { effort 4d allocate r2 depends !t2 } } task s2 "Sprint 2" { depends !s1 task t1 "T1" { effort 3d allocate r2 } task t2 "T2" { effort 4d allocate r3 depends !t1 } task t3 "T3" { effort 6d allocate r1 } task t4 "T4" { effort 5d allocate r3 depends !t3 } task t5 "T5" { effort 3d allocate r2 depends !t1 } } task s3 "Sprint 3" { depends !s2 task t1 "T1" { effort 6d allocate r1 depends product.s1.t2 } task t2 "T2" { effort 4d allocate r3 depends !t1 } task t3 "T3" { effort 4d allocate r1 depends product.s2.t4 } task t4 "T4" { effort 7d allocate r2 depends !t3 } task t5 "T5" { effort 5d allocate r2 depends !t1 } } } navigator menu textreport "" { header -8<- == Scrum Example Project == <[navigator id='menu']> ->8- formats html textreport "" { columns name, status, effort, resources taskreport "Product Backlog" { } taskreport "Sprint 1 Backlog" { taskroot product.s1 } taskreport "Sprint 2 Backlog" { taskroot product.s2 } taskreport "Sprint 3 Backlog" { taskroot product.s3 } title "Backlogs" purge formats } textreport "" { sorttasks id.up width 800 tracereport "Product Burndown" { columns opentasks hidetask plan.id != "product" } tracereport "Sprint 1 Burndown" { columns opentasks hidetask plan.id != "product.s1" } tracereport "Sprint 2 Burndown" { columns opentasks hidetask plan.id != "product.s2" } tracereport "Sprint 3 Burndown" { columns opentasks hidetask plan.id != "product.s3" } title "Burndown Charts" purge formats } purge formats } taskjuggler-3.5.0/examples/Scrum/Sprint 1 Burndown.csv0000644000175000017500000000063212614413013022150 0ustar bernatbernat"Date";"product.s1:plan.opentasks" "2012-02-01";4 "2012-02-02";4 "2012-02-03";4 "2012-02-06";4 "2012-02-07";4 "2012-02-08";3 "2012-02-09";3 "2012-02-10";3 "2012-02-13";3 "2012-02-14";3 "2012-02-15";3 "2012-02-16";3 "2012-02-17";2 "2012-02-20";2 "2012-02-21";2 "2012-02-22";1 "2012-02-23";1 "2012-02-24";1 "2012-02-27";1 "2012-02-28";0 "2012-02-29";0 "2012-03-01";0 "2012-03-02";0 "2012-03-05";0 "2012-03-06";0 taskjuggler-3.5.0/examples/ProjectTemplate/0000755000175000017500000000000012614413013020264 5ustar bernatbernattaskjuggler-3.5.0/examples/ProjectTemplate/template.tjp0000644000175000017500000002204612614413013022622 0ustar bernatbernat/* * This file contains a project skeletton. It is part of the * TaskJuggler project management tool. You can use this as a basis to * start you own project file. */ project your_project_id "Your Project Title" 2011-11-11-0:00--0500 +4m { # Set the default time zone for the project. If not specified, UTC # is used. timezone "America/New_York" # Hide the clock time. Only show the date. timeformat "%Y-%m-%d" # Use US format for numbers numberformat "-" "" "," "." 1 # Use US financial format for currency values. Don't show cents. currencyformat "(" ")" "," "." 0 # Pick a day during the project that will be reported as 'today' in # the project reports. If not specified, the current day will be # used, but this will likely be outside of the project range, so it # can't be seen in the reports. now 2011-12-24 # The currency for all money values is the Euro. currency "USD" # You can define multiple scenarios here if you need them. #scenario plan "Plan" { # scenario actual "Actual" #} # You can define your own attributes for tasks and resources. This # is handy to capture additonal information about the project that # is not directly impacting the project schedule but you like to # keep in one place. #extend task { # reference spec "Link to Wiki page" #} #extend resource { # text Phone "Phone" #} } copyright "Claim your rights here" # If you have any text block that you need multiple times to describe # your project, you should define a macro for it. Macros can even have # variable segments that you can set upon calling the macro. # # macro Task [ # task "A ${1} task" { # } # ] # # Can be called as # ${Task "big"} # to generate # task "A big task" { # } # You can attach flags to accounts, resources and tasks. These can be # used to filter out subsets of them during reporting. flags important, hidden # If you want to do budget planning for you project, you need to # define some accounts. account cost "Project Cost" { account dev "Development" account doc "Documentation" } account rev "Customer Payments" # The Profit&Loss analysis should be rev - cost accounts. balance cost rev # Define you public holidays here. vacation "New Year's Day" 2012-01-02 vacation "Birthday of Martin Luther King, Jr." 2012-01-16 vacation "Washington's Birthday" 2012-02-20 vacation "Memorial Day" 2012-05-28 vacation "Independence Day" 2012-07-04 vacation "Labor Day" 2012-09-03 vacation "Columbus Day" 2012-10-08 vacation "Veterans Day" 2012-11-12 vacation "Thanksgiving Day" 2012-11-22 vacation "Christmas Day" 2012-12-25 # The daily default rate of all resources. This can be overridden for each # resource. We specify this, so that we can do a good calculation of # the costs of the project. rate 400.0 # This is a set of example resources. resource r1 "Resource 1" resource t1 "Team 1" { managers r1 resource r2 "Resource 2" resource r3 "Resource 3" } # This is a resource that does not do any work. resource s1 "System 1" { efficiency 0.0 rate 600.0 } task project "Project" { task wp1 "Workpackage 1" { task t1 "Task 1" task t2 "Task 2" } task wp2 "Work package 2" { depends !wp1 task t1 "Task 1" task t2 "Task 2" } task deliveries "Deliveries" { task "Item 1" { depends !!wp1 } task "Item 2" { depends !!wp2 } } } # Now the project has been specified completely. Stopping here would # result in a valid TaskJuggler file that could be processed and # scheduled. But no reports would be generated to visualize the # results. navigator navbar { hidereport 0 } macro TaskTip [ tooltip istask() -8<- '''Start: ''' <-query attribute='start'-> '''End: ''' <-query attribute='end'-> ---- '''Resources:''' <-query attribute='resources'-> ---- '''Precursors: ''' <-query attribute='precursors'-> ---- '''Followers: ''' <-query attribute='followers'-> ->8- ] textreport frame "" { header -8<- == TaskJuggler Project Template == <[navigator id="navbar"]> ->8- footer "----" textreport index "Overview" { formats html center '<[report id="overview"]>' } textreport "Status" { formats html center -8<- <[report id="status.dashboard"]> ---- <[report id="status.completed"]> ---- <[report id="status.ongoing"]> ---- <[report id="status.future"]> ->8- } textreport wps "Work packages" { textreport wp1 "Work package 1" { formats html center '<[report id="wp1"]>' } textreport wp2 "Work package 2" { formats html center '<[report id="wp2"]>' } } textreport "Deliveries" { formats html center '<[report id="deliveries"]>' } textreport "ContactList" { formats html title "Contact List" center '<[report id="contactList"]>' } textreport "ResourceGraph" { formats html title "Resource Graph" center '<[report id="resourceGraph"]>' } } # A traditional Gantt chart with a project overview. taskreport overview "" { header -8<- === Project Overview === The project is structured into 2 work packages. # Specification # <-reportlink id='frame.wps.wp1'-> # <-reportlink id='frame.wps.wp2'-> # Testing === Original Project Plan === ->8- columns bsi { title 'WBS' }, name, start, end, effort, cost, revenue, chart { ${TaskTip} } # For this report we like to have the abbreviated weekday in front # of the date. %a is the tag for this. timeformat "%a %Y-%m-%d" loadunit days hideresource 1 balance cost rev caption 'All effort values are in man days.' footer -8<- === Staffing === All project phases are properly staffed. See [[ResourceGraph]] for detailed resource allocations. === Current Status === Some blurb about the current situation. ->8- } # Macro to set the background color of a cell according to the alert # level of the task. macro AlertColor [ cellcolor plan.alert = 0 "#00D000" # green cellcolor plan.alert = 1 "#D0D000" # yellow cellcolor plan.alert = 2 "#D00000" # red ] taskreport status "" { columns bsi { width 50 title 'WBS' }, name { width 150 }, start { width 100 }, end { width 100 }, effort { width 100 }, alert { tooltip plan.journal != '' "<-query attribute='journal'->" width 150 }, status { width 150 } taskreport dashboard "" { headline "Project Dashboard (<-query attribute='now'->)" columns name { title "Task" ${AlertColor} width 200}, resources { width 200 ${AlertColor} listtype bullets listitem "<-query attribute='name'->" start ${projectstart} end ${projectend} }, alerttrend { title "Trend" ${AlertColor} width 50 }, journal { width 350 ${AlertColor} } journalmode status_up journalattributes headline, author, date, summary, details hidetask ~hasalert(0) sorttasks alert.down, plan.end.up period %{${now} - 1w} +1w } taskreport completed "" { headline "Already completed tasks" hidetask ~(plan.end <= ${now}) } taskreport ongoing "" { headline "Ongoing tasks" hidetask ~((plan.start <= ${now}) & (plan.end > ${now})) } taskreport future "" { headline "Future tasks" hidetask ~(plan.start > ${now}) } } # A list of tasks showing the resources assigned to each task. taskreport wp1 "" { headline "Work package 1 - Resource Allocation Report" columns bsi { title 'WBS' }, name, start, end, effort { title "Work" }, duration, chart { ${TaskTip} scale day width 500 } timeformat "%Y-%m-%d" hideresource ~(isleaf() & isleaf_()) sortresources name.up taskroot project.wp1 } # A list of tasks showing the resources assigned to each task. taskreport wp2 "" { headline "Work package 2 - Resource Allocation Report" columns bsi { title 'WBS' }, name, start, end, effort { title "Work" }, duration, chart { ${TaskTip} scale day width 500 } timeformat "%Y-%m-%d" hideresource ~(isleaf() & isleaf_()) sortresources name.up taskroot project.wp2 } # A list of all tasks with the percentage completed for each task taskreport deliveries "" { headline "Project Deliverables" columns bsi { title 'WBS' }, name, start, end, note { width 150 }, complete, chart { ${TaskTip} } taskroot project.deliveries hideresource 1 } # A list of all employees with their contact details. resourcereport contactList "" { headline "Contact list and duty plan" columns name, email { celltext 1 "[mailto:<-email-> <-email->]" }, managers { title "Manager" }, chart { scale day } hideresource ~isleaf() sortresources name.up hidetask 1 } # A graph showing resource allocation. It identifies whether each # resource is under- or over-allocated for. resourcereport resourceGraph "" { headline "Resource Allocation Graph" columns no, name, effort, rate, weekly { ${TaskTip} } loadunit shortauto # We only like to show leaf tasks for leaf resources. hidetask ~(isleaf() & isleaf_()) sorttasks plan.start.up } taskjuggler-3.5.0/examples/ToDo-List/0000755000175000017500000000000012614413013016740 5ustar bernatbernattaskjuggler-3.5.0/examples/ToDo-List/todolist.tjp0000644000175000017500000000350212614413013021320 0ustar bernatbernatproject "My TODO List" 2011-01-01 +5y { # The now date is only set to keep the reports constant. In a real # list you would _not_ set a now date. now 2011-12-20 } task "Errands" { priority 5 task "By some milk" { end 2011-12-13 complete 100 priority 7 } task "Pickup Jacket from dry cleaner" { end 2011-12-18 complete 0 note "Smith Dry Cleaners" } task "Buy present for wife" { end 2011-12-23 note "Have a good idea first" journalentry 2011-12-10 "Maybe a ring?" journalentry 2011-12-14 "Too expensive. Some book?" } } task "Long term projects" { priority 3 task "Buy new car" { end 2011-05-11 complete 100 priority 6 } task "Build boat" { end 2013-04-01 complete 42 } } macro cellcol [ cellcolor (plan.end < ${now}) & (plan.gauge = "behind schedule") "#FF0000" cellcolor plan.gauge = "behind schedule" "#FFFF00" ] navigator navbar textreport frame "" { formats html header -8<- == My ToDo List for ${today} == <[navigator id="navbar"]> ->8- footer "----" columns name, end { title "Due Date" ${cellcol} }, complete, priority, note, journal { celltext 1 "" tooltip 1 "<-query attribute='journal'->" width 70 } taskreport "TODOs due today" { hidetask (plan.complete >= 100) | (plan.end > %{${now} +1d}) journalattributes date, headline, summary, details } taskreport "TODOs due within a week" { hidetask (plan.complete >= 100) | (plan.end > %{${now} +1w}) journalattributes date, headline, summary, details } taskreport "All open TODOs" { hidetask plan.complete >= 100 journalattributes date, headline, summary, details } taskreport "Completed TODOs" { hidetask plan.complete < 100 } purge formats } taskjuggler-3.5.0/.gemtest0000644000175000017500000000000012614413013015003 0ustar bernatbernattaskjuggler-3.5.0/CHANGELOG0000644000175000017500000026260112614413013014565 0ustar bernatbernat= Release 3.5.0 (2013-06-29) == New Features * Only include scenario in error messages when using multiple scenarios. * Hammock tasks are now supported. * Adding search field to the online user manual. * List contended resources and tasks for runaway tasks. * Queries can now access attributes of parent properties. * Adding support for new custom attribute type 'number'. * Add --list-reports option for tj3 similar to tj3client. * Ruby 2.0 is now the recommended Ruby version. * Adding 'outputdir' attribute to set output directory for reports. * 'auxdir' attribute is now also a global attribute. == Bug Fixes * Don't crash on 'tj3man -m' command * Clarify error message when dependency is broken. * Don't ignore provided start/end date when deps are present. * Ensure that 'length' considers task 'shifts' as well. * Fixing some layout issues with Firefox. * Include optional attributes in export of extended attribute definition. * Make project freezing work on Windows again. * Don't ignore bookings for task limits. * Use proper github link to current repository in manual. * Don't crash when group resources are made persistent. * Make gem build work on Ruby 2.0.0 * columns using 'celltext' now show the correct value * Add new function to test for invalid attribute values in logical expressions. * Use correct DOCTYPE in HTML5 documents. * Make spaces after work in RichText. * Ensure that times in ICal files are always UTC. * Added warning for overwriting non-scenario-specific attributes. * Add link to logical expression page for attributes that use them. * Clarify description of 'newtask' keyword. * Don't crash when containers are marked as milestone. * Include off-hour shading in Gantt charts when end is off chart. * Adding missing 'turnover' to 'columnid' documentation. = Release 3.4.0 (2012-12-17) == New Features * 'auxdir' can be used to specify alterntive path to CSS and script files. * Trace file reader can now deal with Open/LibreOffice generated files. * Add timefomat1 and timeformat2 attributes to set calendar column headers. * Add report columns 'scheduling' to show the scheduling mode of leaf tasks. * Priority inversions now generate a warning. * 'competitors' and 'competitorcount' columns added. * Better documentation of the task scheduler in the user manual. * Support raw HTML fragments inlined into rich text strings. * Added 'rawhtmlhead' attribute to insert HTML fragments into the HTML head section. * Major performance improvements for reports using targets and inputs. * Adding --report and --reports option to tj3. == Bug Fixes * Don't crash on files with bad file encodings. * 'tagfile' cannot work without a file name. * Report correct line number for errors at end of master file. * Don't crash when adopting a top-level task. * Don't put -bookings.tji file in -o directory. * MS Project XML files respect the 'screnarios' setting now. * Timesheet reports are now based on the 'trackingscenario' instead of the top-level. * Make <-reportlink ...-> work with otherwise empty strings. * Handle *prefix settings properly across nested include files. * Properly export working hours that are not hour-aligned. * Fix link to generated tutorial reports. * Add requirement for >= Ruby 1.9.2 for non-ASCII users. * Undefined macros ${?foo} now work with TJ3. * Improving the heuristic to select a persistent resource from a set of alternatives. * Ensure that resource load bars in Gantt charts have at least a 1px width = Release 3.3.0 (2012-07-22) == New Features * Support 'purge' in export report definitions. * Allow 'export' to be used as nested report. * Export scheduled project as Microsoft Project XML format. * Some speed improvements of the TJP parser. * Support nested macro definitions. * Improved scheduling performance. * 'effortleft' and 'effortdone' columns now work for resources as well. * Adding 'status_dep' and 'alerts_dep' journalmode options. == Bug Fixes * Don't crash when 'complete' is reported for container w/o real sub-tasks. * Fix 'celltext' documentation. * Properly compute cost from tasks when reported in a resource context. * Ensure that there are no duplicated (by adoption) tasks in a report. * Clarify start/end slot clipping for tasks with 'allocate'. * Don't miss certain tasks in 'inputs' and 'targets' list. * Don't crash when gantt report interval is outside of tasks. * Allow macro definitions at end of file. * Don't silently force interval durations to align with timing resolution. * Make @none work for logical expressions. * Support 'purge' for a specific scenario. * Properly escape " in plain text strings in export reports. * Make nested 'supplement' work with absolute IDs. = Release 3.2.0 (2012-04-30) == New Features * 'leaves' attribute is now officially supported. * 'alert' can now be specified with 'journalattributes' * Adding Scrum example project. * Adding @all and @none constants to improve readability of logical expressions. * Support custom start or end dates for auto-adjusted columns * Add 'sendmail' as alternative email delivery method. * Adding 'activetasks', 'opentasks' and 'closedtasks' columns. * Web server is now a separate program: tj3webd * More consistent error reporting across the tj3 programs. * HTML reports now use HTML5 syntax. * Chart plotter for tracereport data * New 'tracereport' to track property attributes over time. == Bug Fixes * Don't show deprecated or removed attributes in 'Attributes' section of the manual. * resourcereport now has 'no, name' as default columns. * ALAP tasks in the tracking scenario no longer get allocations prior to now date. * Remove bogus warning when multiple leaveallowances are used. * 'workinghours' was ignored for all but default scenario. * Properly report effort values for resources of parent tasks in task lists. * Some deprecated or removed syntax elements were not rejected properly. * Don't warn about container tasks without resource allocations. * Make purge work for *root attributes. * Allow multiple spaces after * and # in RichText lists. * Clarify difference between TJ date format and ISO 8601. * Time and status sheets templates now work properly with non-US locale. * Including task ID again in timesheet summaries. * Property report resource efforts in calendar cells for loadunit hour. * 'vacation' is no longer ignored when multiple scenarios are used. * Use ".ics" extension instead of ".ical" for ICal reports. * Clarifying ruby installation instructions in the manual. * Make task limits that had no resource associated work properly. * Fix link to 'listitem' documentation in manual. * Fix links to tagfile documentation in Vim installation section. * Don't crash on wrongly encoded input files. * Make 'tj3 -o ' work on Ruby 1.8. * Make shortauto and longauto formats work for all numberformat options. = Release 3.1.0 (2012-01-11) == New Features * Added a new report column 'balance' for account reports. * Making the tj3man output more readable with colors. * New attribute 'leaveallowance' * New report columns to report leaves for report period * Replaced 'vacation' with more flexible 'leaves' attribute. * Adding colorized terminal output. * The rich text token now supports RGB values. * 'headcount' is now also supported for tasks. * Adding examples for textreport and attributes to manual. * Make TaskJuggler usable as a ToDo list manager. * Supported alert levels can now be freely defined. * Added new logical function 'isresponsibilityof'. * Support '-' argument for 'balance' to clear a set balance. * Gantt charts now use normal task bars for rolled-up tasks. * Adding support for new account attribute 'credits'. * Added a project template to the examples directory. * Adding query attribute 'turnover' for account reports. * Adopted tasks are now included in the reports. * Don't include deprecated keywords in vim syntax file. * RichText now also supports colored text. * Added error message for no longer supported keywords. * Clearly mark deprecated and removed keywords in syntax manual. * 'accountreports' are back again. == Bug Fixes * Abort tj3 if error occurs during --freeze operation. * Fix vim :make command to not be confused by tj3 output. * Make the fraction seperator of the 'numberformat' option work. * Prevent years in calendar column headers from being chopped-off. * Make tj3man work with Ruby 1.8 again. * Fixing a crash on bad syntax in a query for 'resources'. * Add gem dependencies to from-git install section in the manual. * Prevent crash when reports only have disabled scenarios. * Properly report syntax errors with open context at file end. * Don't crash when no tasks fit but reports are forced. * Colorize alert tags in standalone mode. * Make timesheets work with custom yearlyworkinghours. * fte and headcount column now have a proper header * Don't show any progress information when --silent is set. * 'headcount' column now shows proper values. * Fix the 'complete' value of container tasks. * Don't crash when HTML files cannot be written. * Fixed a locking bug in the batch processor. * Eliminate duplicate entries in status sheet confirmation. * Show correct documentation of alert levels in the manual. * Rich text enumerations many have a blank lines in between. * Custom alignment, font and cell color now also work in calendar cells. * Respect limits for all requested resources of that limit. * Properly position the Gantt chart container for very small bars. * Clarify the use of 'purge' in the user manual. * Don't crash when generating certail iCal reports. * Correct syntax of 'export' in reference manual. * Allocate now also supports 'shifts' instead of the deprecated 'shift'. * Make 'tj3client report' work with additional report files. = Release 3.0.0 (2011-11-01) == New Features * Add support for FTE calculation. * Use project name and report title in title of HTML reports. * Improved stylesheet for code and verbatim sections in HTML reports. * Navigator (Menu) in reports is now surrounded by rulers. * Adding instructions for install into a local directory. == Bug Fixes * Include shifts in export reports. * 0-efficiency resources are no longer cut short when effort is reached. * Include 'timingresolution' in export files. * Check file name for illegal characters. * Document the one day requirement for status in time sheets. * Don't crash on bad working hour intervals. * Don't crash when sorting is used for calculated attributes. * Don't crash when report with navigator is hidden from that navigator. * Properly escape ' in HTML popup boxes. * Don't crash on scanner errors. * Correctly document the 'tagfile' keyword. * Use padding for all menu bar entries in reports. * Months names in calendar reports are no longer cut off. * Ensure that Clarity timesheets always show all projects in timesheets. * Also process submitted sheets if the attachements were not properly detected. = Release 0.2.2 (2011-09-13) == New Features * Check all input files for encoding errors. * Adding Fedora 20 schedule as an example. * Deprecating old journal columns. * Making journal reports more configureable. * Add --html option for tj3man to show help in browser. * More elaborate explanation of the reported 'complete' values. * 'purge' is now supported in reports * Eliminate the need to set TASKJUGGLER_DATA_PATH when installed from gem. * Improved debugging output for --debuglevel and --debugmodules option. * Using a standard gemspec file to build the gem package. == Bug Fixes * Use stricter encoding checks for incoming emails. * Don't report unset min/max start/end dates as errors in reports. * <-query attribute="journal'-> now also works without a property context. * Don't crash when 'scenario' is used with 'icalreport'. * Propagate dates from scheduled tasks to dependencies with effort. * Be less agressive when inheriting project start and end dates. * Don't crash when tasks don't fit the project time frame. * Correct 'effort must be larger than 0' error message. = Release 0.2.1 (2011-07-13) == Bug Fixes * Fix scheduler deadlock problem. = Release 0.2.0 (2011-07-10) == New Features * Added warning for non-effort tasks with allocate that don't get resources. * Improved Vim integration * Re-map Ctrl-] in vim to show full ID of property in current line. * New report to generate source file infos for properties as ctags file. * Scheduler performance improvements for large projects * Added warning when accounts are present but no balance has been defined. * Add --abort-on-warnings flag to tj3. * Make sure that provided bookings don't exceed task specifications. * Resource loads can now be reported in 'quarters' as well. * Improved messages for some types of scheduling errors. * Get the TJ2 bookings per resource in export reports back. * Scheduler performance improvements * Adding support to generate iCalendar files == Bug Fixes * Tracking scenario tasks with no bookings but allocations * Ensure that tracking scenario is set after all scenario definitions. * Show last matched token in syntax error messages. * Properly handle white spaces in macro calls. * Properly handle unicode characters in macros and environment variables. * tj3d shows local times again for loaded projects. * Make --freezedate option work properly. * Flags are now properly inherited by sub properties. = Release 0.1.1 (2011-06-08) == Bug Fixes * Fix structure of generated HTML files. = Release 0.1.0 (2011-06-06) * Bumping the version to 0.1.0 * Update TJ2 -> TJ3 migration section with recent changes. * Include HEAD in changelog. * Fix error reporting for run-away multi-line tokens. * Cleaning up the code that generates the user manual. * Converting UserManual.rb to use new HTMLElements classes. * Add some syntax sugar to make HTML generation easier. * Update TODO list. * Changing default working hours to mon - fri 9am - 5pm. This cuts the size of bookings files in half. If you want the old behavior back, add workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 to your project header section. * Fix that flag filtering for journal entries. * Make sure we generate
and
as self closing HTML elements. * Adding 'ismilestone' function for logical expressions. * Update manual to reflect the changes of bookings/projection mode. * Adding --freeze option to tj3 to simplify generation of bookings. * Make time comparions more robust against undef values. * Queries now support 'journal' without a propery context. This allows journal entries to be included in a text report. * Deprecating 'projection' keyword. It is no longer supported. Please use the 'trackingscenario' feature instead. * Include flags in journal entries reports. * Adding 'ischildof' function for logical expressions. * Improve 'limits' example for multiple resources. * Fix recently introduced bug in Clarity HTML reports. * Fix crash in Clarity reports. * Adding 'hidejournalentry' attribute to filter journals in reports. * Added plumbing for journal entry filtering * Adding support for flags for time and status sheets and journal entries * Reports can now have their own 'currentformat' and 'numberformat'. * Adding description for important and fall-back tasks to manual * Slightly improved formating for the crash error messages. * Fix problem with 'start' and 'end' in queries. * Fix display of arguments with links in tj3man. * Explain ruby backtrace as application bug. Users seem to interpret them as regular tj3 error messages. * Fix declaration of tasks with absolute task IDs. * Adding support to inherit bookings to sub-tracking-scenarios. The scenarios that are derived from the 'trackingscenario' will inherit the bookings from that scenario. The 'trackingscenario' keyword has been moved to the project header section. * Make resource lists work again in report cells. Reported-by: Alexander Nagy * Fix crash when using 'hidereport' in a navigator. Reported-by: Jerry Neely * Update tutorial to reflect recent syntax changes. * Cleaning up the scenario attributes somewhat. * 'enabled' and 'disabled' have been replaced by 'active'. * 'minslackrate' didn't do anything. Removed. * Fixing a crash when reporting certain dependencies. Reported-by: Bernd Wiedemann * Replacing 'listmode' with 'listitem' attribute. The 'listmode' syntax was not very powerful nor user friendly. 'listitem' is much easier to use and offers many more features. This change is not backwards compatible. Projects that use the recently introduced 'listmode' keyword need to be updated. * Previous patch added code to wrong file. Reverting. * Fix test suite to reflect recent environment support. * Make sure that all dependencies are shown in Gantt chart. * Adding support for injecting environment variable values. Use the $(VAR) syntax to insert the value of the VAR variable. * Fix time and status sheet checking when using tj3 instead of tj3d. * Making 'id', 'name' and 'seqno' a regular property attributes. * Create property attributes on demand. This reduces the memory footprint of large projects. Unfortunately, I haven't seen much of a performance difference. * Fix dumping of 'duties' attribute. * Fixing benchmark script. * Report an error when taskroot or resourceroot are used with leaf properties. * Fix a crash when 'precedes' is used with comma separated tasks. Reported-by: Bernd Wiedemann * It's actually mail 2.2.15. * Allow all 'mail' versions after 2.1.15. Let's hope the 'mail' gem doesn't break again. * Replace 'wbs' keyword with 'bsi'. Work breakdown structure (WBS) only applies to task, not all properties. Breakdown Structure Index (BSI) is more generic. 'wbs' will still be supported, but is now deprecated. * Fix require for manual rake task * Moving Tj3Man also to the apps directory. * Adding check for minimum required Ruby version. tj3 still works with 1.8.7. All other apps need 1.9.2. * Improving the 'priority' example in the manual. * Adding 'rolluptask' and 'rollupresource' support for export reports. * Improve accuracy of line number for "Unexpected token" errors. * Don't call exit from deep inside the applications. Use an exception instead that is then cought and generates an exit. Added a spec file for status sheets. * Fixing problem with wrong report cells on Ruby 1.8. * Don't crash when no project file is provided to tj3. * Improve example for task limits in the manual. * Improving time sheet tests. * Switching email encoding to base64. Quoted printable is just causing too much trouble. * Avoid duplicate entries in 'responsible' list. * Fix a crash in tj3client check-ss. * Eliminate dependency on open4 gem. With the recent changes, we can now call Tj3Client in-process. * The ruby mail gem now seems to be stable again. I've used 2.1.15 without any problems so far. * Revert "Apply Query result priority patch." This reverts commit d42cc71d522f36610d83337b4dad8be5815c964d. * Apply Query result priority patch. = Release 0.0.11 (2011-03-21) * Update the manual with more detailed installation section. * Allowing multiple 'charge' attributes per task again. * Bumping the version to 0.0.11 * Some cleanup for time sheet test. * Discontinuing setup.rb install method. Those few who need this know how to do it manually. Gem is now the only supported install method. * Fixing the alert label in status sheet summary mails. * Fix a crash of the tj3d web server when showing reports. * Allowing multiple instances of flag attributes per property. * Use alert colors in time and status sheet summary emails. * Eliminate the 'rbconfig/datadir will be deprecated' warning. This patch hopefully works on 1.8 and 1.9 with rubygems 1.3 and later. * Clarifying the 'length' attribute in the documentation. * Make sure CSV reports work for Bignum values as well. * Fixing a Ruby 1.8 compatibility issue. * All programs now have a proper entry class that is derived of off Tj3AppBase * Converting tj3 to use Tj3AppBase class. * Moving daemon and client entry code into apps directory. * Allowing multiple 'depends' and 'precedes' attributes per task. https://github.com/taskjuggler/TaskJuggler/issues#issue/5 * Add rake tasks to generate Vim syntax file. * Cleaning up the tj3man entry script. * Swap priority of numericals and RTI on Query results. * Show alert text in the appropriate color. * Even more cleanup of new TernarySearchTree class. * Some polishing for TernarySearchTree class. * tj3man now supports partial keywords. If no exact match for the keyword is found, a list of candidates is printed. * Fixing a problem with <-query-> RichText function. * Adding tutorial reports to README. * Moving RichText classes into a separate directory. * Moving all non-entry ruby scripts into lib/taskjuggler. * Adding experimental support for task adoption. * Changing manual to reflect the use of github to contribute patches. * Make sure that the Project class can be used without the TaskJuggler class. * New column 'inputs' that lists prerequisites of a task. * Use safer default value for dateToIdx() functions. * Rename README to README.rdoc so GitHub will render it properly. * Don't crash when 'now' date is outside of the project period. Crash happened when 'effortleft' or 'effortdone' columns were used. Reported-by: Bas de Bruijn * Don't use generated README anymore. * Some code cleanup for the time keeping class. * Include resources that were on vacation in Clarity reports. * Use new text diff feature for CSV reports as well. * Resolve a Ruby 1.8.7 vs. 1.9.x compatibility issues with the test suite. * Allowing time sheets for zero efficiency resources. * Adding month and year methods for TjTime. * More time zone related work. * Resolving some issues with the test suite. * Further cleanup of the time library. * Improve example for using per resource limits for task allocations. * All projects now default to UTC (world time) zone. Unless you specify a different time zone in the project header, all dates will now be assumed to be UTC dates. The dates in the project header are also always UTC unless specified differently (with +/-XXXX) extension. * More time zone related fixes. * Allow timesheets from zero-efficiency resources. This can be used to receive status messages on tasks from automated systems like test systems and the like. * Add missing export.tji file. * Make 'fail' and 'warn' work for all scenarios. * Make 'allocate' work for multiple scenarios again. This fix also affects some other list attributes that can be used multiple times within the same context. Reported-by: Ulrich Domroese * Fix inheritance of start and end dates for derived scenarios. Reported-by: Ulrich Domroese * Fix a crash caused by the recent time zone changes. * Describe how to quickly switch between different source code version. * All reports may now use a separate time zone for their dates. * Loop tests for export reports added. * Several fixes for handling multiple time zones. * Fix setting of global time zone again. * Properly escape quoted strings in export report. * Including the timezone definition into export reports. * Don't export disabled scenarios when no scenarios were specified. * Allow different duration specs for same task in multiple scenarios. * Some fixes for the export reports. * Adding a diff class to be used in test cases. * Adding new richtext type for user-defined attributes. * Don't use intermediary files for export cycle tests. * Don't include redundant scenario attributes in export report. * Export reports now contain all scenario by default. Not just the first one. Prevent tj3 crash in TaskScenario::scheduleContainer. Reported-by: Rodolphe Jouannet * Allow bookings for all scenarios again. This is required for export reports to work properly. Bookings can be used in all scenarios, but they will not be passed to any other scenario. They are only valid for the scenario they are defined in. * Move all TextParser files into the TextParser directory. * Restrict bookings to leaf scenarios. Since bookings cannot be changed or selectively deleted in derived scenarios, it only makes sense to use them in leaf scenarios. * Properly respect most export report attributes.i They were just ignored. Reported-by: Magnus Wiklund * Some tasks with start-start or end-end dependencies could not be scheduled. * Prevent crash on tasks with too weak end spec. Reported-by: Stephan * Fix crash when 'purge' is used with a non-scenario-specific attribute. * Fixing changelog script to handle multi-digit release levels properly. * Bumping version to 0.0.9 = Release 0.0.10 (2011-01-31) * Bumping version to 0.0.10 0.0.9 was a no-go version. * Bumping version to 0.0.9 * Fix entry scripts. = Release 0.0.9 (2011-01-30) * Bumping version to 0.0.9 * Executable name and primary Ruby file name should always match. * Adding a .gitignore file. * Add development dependencies to gem spec file. * Fix alertmessages. Don't show overriden old alerts again. If a child had an alert, the parent reset it and another child added another green message after that, the old alert was showing up again. * Fixing dependency symbols in precursor and follower lists. * Supporting listmode option for precursor and follower columns as well. * Properly handle part-time workers in Clarity reports. * Fixing some layout issues in the manual. * Some code consolidation. * Include all Clarity projects in Niku reports. Also add projects with 0 effort so they show up in the actual reporting page of Clarity. * Make 'listmode' options a power of 2. * Make 'celltext' also work for calendar column cells. * Improving the cell content shortener. * Adding new attributes 'listtype' and 'listmode'. These new attributes can be used to customize the lists in report table cells. * Use accurate width even with icons and tooltips in the table. * Column width is now a fixed measure, not a minimum measure. * Adding a default landing page for built-in web server. * Clarify usage of bookings and scenarios in the manual. * Update copyright to 2011. * Make web server work again. Reported-by: Bernhard Wiedemann * Fix crash for "<-query scenario='actual' attribute=...->" queries. Reported-by: Patrick Berry * Fix time-off highlighting at report boundaries. Reported-by: Tom Schutter * vacation is a cumulative attribute. Don't issue a warning for multiple use. * Fixing "Overwriting a previously provided value" crashes. Reported-by: Bernhard Wiedemann * Fixing progress meter off-by-one bug. For long labels the progress meter started one char too far right. Reported-by: Tom Schutter * Fix handling of container tasks with dependencies. This bug was introduced by the recent onstart/onend changes. Reported-by: Sam Elstob * Prevent crashes in report generator when using -f option with tj3. * Add error message for reports without a file name. * Second part of onstart/onend fixes. * Fix serious problem with 'onstart' and 'onend' dependencies. The parser was too permissive and some illegal cases slipped through. Tasks with start-start deps must be ASAP and end-end tasks must be ALAP. Reported-by: Bernd Wiedemann * Add warning if an attribute is used twice in the same context. This is to prevent accidental overwrites of attributes in bigger scopes. This will also alert users that first assign a value to a derived scenario and then to the parent scenario. * Fixing a serious flaw in the handling of inherited scenario specific attributes. This fix will potentially change the result of existing projects that use inheritable attributes in combination with derived scenarios. The handling of these attributes was broken and derived scenarios used wrong attribute values. * Refactored the ProjectBroker API to make it cleaner. * caption, epilog, headline, prolog and title are now inheritable report attributes * tj3client now supports a --format option to request a certain output format. * Properly limit bookings in export report to report period. Reported-by: Magnus Wiklund (wiklund.magnus@comcast.net) * Adding spec for ProjectBrowser. Just a first draft. * Added a rake task to run the RSpec tests. * Fixing completion bars in Gantt charts. The bars were showing random values instead of the correct completion degree. * Show off-duty periods for task lines in Gantt charts. * Don't show global working hour zones for group resources in Gantt charts. * Adding more examples for limits to the manual. * Fix a typo in the manual. Added some comments to the code. * Explaining use of tool tips in the tutorial. * Fixing 'taskprefix' attribute for include files. Reported-by: Sam Elstob * Ensure deterministic order of projects and resources in Niku reports. * Adding test case for niku reports. * Move CSV reference reports into refs directory. * Include vacation time for each resource in Niku reports. * Generate warnings if a non-supported format is requested for a report. * Show releases for last to first in CHANGELOG = Release 0.0.8 (2010-11-24) * Bumping version to 0.0.8 * Update feature list. * Fixing end of exported bookings. There was one time slot too much at the task end. Reported-by: Dimitri Pissarenko * Fixing the booking example used in the reference manual. * Adding 'resourceroot' and 'rollupresource' attributes. * Make dependency loop error message more specific. * Properly detect illegal tokens inside of patterns. This was only working for tokens at the end of a rule. * Fixing the attributes and contexts info in the manual and tj3man output. * Fixing CSV reports with embedded tables. * Reuse RichText parser for parsing all RichText input. * Switch parser to use new state machine compiler. * Fix merge conflict. * Fix Ruby 1.8.x compatibility issue. * Some more work on the state machine compiler. * Adding a (still disabled) state machine compiler to the parser. * Looking into pre-compiled states for the parser FSM. * Clarifying the difference between ${now} and ${today}. * Minor parser code tweaks. * More parser code tweaking. * Cleaning up some parser code. * Moving the rule transition analysis into Rules. * Some speed improvements for the parsers. * Use more symbols and less Strings. * Add a warning if no report was defined or no format was specified. * Add more descriptive error message for start/gap conflict. * Cleaned-up the GanttRounter and refactored the collision detector. * Limiting the scoreboard iterations to actually used segments. * Fix missing values in 'status' column * More performance improvements for the report generator. * Fix performance regressions for the 'resources' column. * Substantial speed improvements for the report generator. * Fixing a typo. * Some cleanups of the DataCache. * Adding a global data cache to speed up report generation. This is just a test implementation. It needs further fine tuning and polishing. * Another merge conflict * Fix a merge conflict * Adding simple performance profiling support to report server * Performance improvements 'loadunit' now defaults to 'days' instead of 'shortauto'. 'shortauto' is neat, but can slow down the report generation quite a bit. * Show tried directories when TASKJUGGLER_DATA_PATH is not set properly. * Improve parser performance by sharing Workinghours scoreboards. The scoreboards now use the copy-on-write scheme. * Fixing several time zone related bugs in the context of working hours. This patch also adds much stricter error checking for time and date values. * Adding a new benchmark for report generation speed. * Added a unit test for WorkingHours::timeOff?() * Don't default time sheets that have been submitted after last tj3ts_summary. * Include list of defaulters in the status report templates. If tj3ts_summary has been used to generate the 'missing-reports' file, this will now be included in the status sheet templates. * Don't use tooltips to shorten table headers. * Fix crashes when bad sorting criteria is used. * Updating the install section with current dependencies. = Release 0.0.7 (2010-09-08) * Bumping version to 0.0.7 * Add section on tj3 web server. * Fix crashes when using the underscore variants of some functions. * Fix overflow of some HTML table cells. * Add missing file. * Document web server usage. * Only check for modified files when the project has been loaded completely. * Ruby 1.8 compatibility fixes. * Adding some documentation on how to use the dynamic reports. * Completing basic support for dynamic reports. By using sometink like <-reportlink id="foorep" attributes="hidetask plan.id != \"<-id->\""-> you can now create parameterized reports that the tj3d web server generates on demand. * Adding a proper error message when webserver can't find data dirs. Reported-by: AzenAlex * Cleaned up test cases for RichText * Clarify allocation shift documentation. * Adding a test for allocation shifts. * Add support for per allocation shifts. * Warn user when CSV format is requested for textreport. Reported-by: vijay * Use proper Exception class to catch encoding errors. * Check mail encoding even before we pass it to 'mail'. * Some more fixes for 'alertmessages' and 'alert' columns. * Fix bug with incorrect end date or remaining work values in time sheet templates. * Adding 'journal_sub' column to reports. It works like 'journal' but also includes the sub tasks. * Don't crash in multi-CPU mode when no reports are provided. * Only milestones are identified as 'targets'. This makes more sense than trying to use a date to limits the included tasks. * Don't send sheet summary emails in RichText syntax. Use proper text and HTML instead. * Targets are now also listed for container tasks. * Report completion degree for container tasks as well. * Make sure we have a proper return value. * Documenting image positioning attributes. * Adding support for SVG files to [[File:...]] tag. * More fixes for the *prefix attributes. * Fixing a problem with the '*prefix' attributes of 'include'. Reported-by: Emanuele Cannizzaro * Allow daemon to copy data directories. * Change some timeouts to retire server process more quickly. * Make sure time sheet warnings/errors always have a source file info. * Add a stylesheet to the HTML emails. * Confirmation mail for time and status sheets now HTML and plain text * Adding more debug infrastructure to the daemon. * Fix another macro-in-string problem. Reported-by: Tomas * Fix problem with multiple macro calls inside a string Reported-by: Tomas * Some more fine tuning for the vim syntax file generator * Fixing missing column header for 'freework' column. Reported-by: JohnZ * Fix some links in the user manual. Reported-by: Emanuele Cannizzaro * Generator for Vim syntax highlighting files added. * Support autoselecting the server port. * Make BatchProcess handle forks in queue() and wait() blocks. * Adding check for gap length/duration of dependencies. When the scheduling direction of a task points away from the dependency gap, TJ cannot guarantee that the condition will be met. But at least now an error is generated if the gap is too small. * Fix handling of multi-line version of HREF tokens. * Adding missing file FileList.rb. * Adding 'update' command to tj3d The 'update' command will reload all projects that have files that were modified after the project was loaded. A project that is currently reloaded will not be loaded twice. * Encoding check is now done in the TextScanner. * Cleaning up the RichText scanner RegExps. * Cleaning up the RichText tokens somewhat. * Make RichText [[..]] tokens work with multiple lines. * Refactor RichTextScanner to use TextScanner as well. Both scanners are now RegExp based. The error handling was cleaned up. * Fixing syntax * Revert "Avoid the duplicate entries in the status sheet confirmation mail." This reverts commit 84ca0e9e3767ca74ae92ed5d75bc37bdedac4dd5. * list-reports command should show all report types Not just HTML reports. * Avoid the duplicate entries in the status sheet confirmation mail. When a status sheet was re-submitted, the old and the new entries were listed in the confirmation mail. = Release 0.0.6 (2010-07-05) * Bumping version to 0.0.6. Changelog added. * Add CSV output format for NIKU reports. * The project reference can be set a lot easier. * Fix problem with macros in DQ Strings. Reported-by: Thomas * Some speed improvements for the parser. * And make it work with Ruby 1.8 again. * Fix bugs in CSV file generator and the 'precursors' column. Reported-by: Frederic Dorodie * Don't shut down a server while it's still connected to a client. * Don't have the client disconnect the ReportServer from the ProjectServer. The client already sends the termination request to the ReportServer. The ProjectServer can automatically detect (via pings) that the ReportServer is no longer there and drop it. * Make macro calls inside of numbers, IDs and strings work again. * Fix Ruby 1.8 compatibility problem. * Allow multi-line macro definition again. The macro expansion code is still broken. It needs to move into the stream handler. * Fix for Ruby 1.8.6. * Switching TextScanner to a regexp based implementation. * Ping twice a minute to avoid some spurious heartbeat losses. * Fix multi-CPU mode again. * Make report list more machine readable * Clean up the TextStream API somewhat. * Replacing some String comparisons with Fixnum comps. This improves the parser speed somewhat. * Use syntax based EOF check instead of hard-coded version. * Adding a version with loop instead of recursion. It may scale better for large projects but it is 8% slower. * Some performance improvements * More precise error location reporting for the parser. * Some speed improvements for the TextScanner. * Eliminate the TextScanner callback again. It's not precise enough when characters are pushed-back. * Make handling of include files more robust. * Using a call-back to signal end of included file. * Move line number counting to StreamHandle classes. * Eliminated sporadic problems with ObjectSpace._id2ref calls in Ruby 1.9 * Make status sheet receiver work again. * Adapting tutorial to new syntax. * Make export report less verbose by not using prefix for default scenario. * Keep Task and Resource consistent. * Syntax change: alertmessage -> journalmessages, alertsummary -> journalsummaries This keyword renaming is necessary to avoid confusion with the new 'alertmessages' and 'alertsummaries' keywords. * Passing report attributes via reportlink. Improving the STRING documentation. * Adding test for journal column. * Some fixes to better support RichText in logical expressions. * Don't use Array.find_index yet. We still support Ruby 1.8.6. * Implement checking of reports against a known reference file. * Fix task allocation for task with start and end dates. Reported-by: Thomas * Fixed crash when using deprecated allocation limits. Reported-by: Marcin Inkielman * Write some data natively to CSV files to fix time sheets. * Reworked CSVFile to make it RFC4180 compliant. * Properly mark milestones in export reports. Reported-by: Guo-Rong Koh * Try to accept properly encoded, but non UTF-8 mails as well. * Add another test for the recent 'limits' fixes. * Fixed a number of bugs in the limit handler. Reported-by: Marcin Inkielman * Change tutorial to use built-in email attribute * Fix a crash with computed attributes used for sorting. Reported-by: Guo-Rong Koh * Generating a dynamic unique ID for report segments. * Make rollup* attributes work again. * Adding 'opennodes' attribute for reports. This is for TJ internal use only. * Don't compile a summary in reminder mode. * Adding -w command line option to start web server. * Add documentation for 'reportlink' RTF generator. * Adding support for user-defined column alignments. * Add RichText attribute to reference other reports. * Add 'isfeatureof' logical function. * Don't generate all output formats for nested reports. * Don't try to copy auxilliary files for interactive reports. * Show list of reports for a selected project on web server welcome page. * Make web server port configurable via command line. * First working version of the web server mode. Still has many rough edges, but it does server interactively generated reports. * Fix crash when using some logical functions. * Create file with the resource IDs of the missing reports. * Update the 'export' documentation to reflect TJ3 features. * Move list of missing reports to beginning of summary report. * Make tj3 work for files in current directory again. * Fix test suite for ruby 1.8. * More code to support interactive HTML reports. * Add check for recursively nested reports. * Use explicite stack for ReportContext. * Add references to sorting attributes. * Make export reports less noisy by default. * Some improvements for the CSV read from file. * Try harder to send a reply to email sender. * Need string interpolation to work. * Use different subject for time and status sheets emails. * Enable level4 bullet items and numbers for 'details' section. * Don't use cut marks for attachements. * Improving the documentation of some attributes. * Fix manual generation. * Adding runtime dependencies to GEM spec file. * mail-2.2.1 changed the subject encoding again * Adding support for one more level for RT titles, bullets and number lists. * Add check for valid UTF-8 encoding of submitted sheets. * Make scenario.attribute notation more prominent in the doc. * Fix spelling error. * Make sure all files of the gem are readable. * Allow space at begin of titles in RichText. * Fix some time sheet related bugs dealing with personal notes. * Fix personal messages after newtasks. * Prevent crashed when parsing corrupted incoming mails * Add commandline option for sheet interval duration. * First code for built-in web server that can server dynamic reports. * Support project loading at daemon start. * Don't include override reports in time sheet summaries. * Improved check for use of 'scheduled' attribute. * Only allow 'scheduled' attribute for tasks that already have a start and end date. Reported-by: Olaf Seidel * Allow macro calls within macro calls. I'm not sure why I blocked this, but I can't think of any good reason anymore. Allowing it again. Reported-by: Olaf Seidel * Simplify algorithm to generate status report templates. * Fix report sorting for 'id'. * Add status section to 'newtask' template section. * Don't include events at start date in status report templates. * Get rid of spurious 'rake release' errors. * Fix crash in parsing unterminated queries. Reported-by: Cornelius Schumacher * Make sure that 'scheduled' is only set when we really have a start and end date. = Release 0.0.5 (2010-04-14) * Bumping version to 0.0.5. * Some fixes for mail-2.2. It looks like this version fixes most open bugs. * Switch custom 'Email' attribute to built-in 'email'. * Don't use message IDs as file names. Use a MD5 hash of them. * Fixing problems with manual. Reported-by: JohnZ * Complete documentation of status sheet usage. * Adding support for 'managers' attribute for resources. * Use 127.0.0.1 instead of localhost to fix busy port detection. * Completing support for user defined checks. Now errors and warnings are supported for resources and tasks. * Fix resource allocation for duration tasks. Add new 'fail' attribute to support test case for this problem. Reported-by: Cornelius Schumacher * Add method to disengage ReportServer from ProjectServer. * Make TCP port configurable via command line option * Fix the rendering of embedded Gantt charts that have scroll bars. * Raise the probability of tasks leading up to milestones to get their resources. * Draw HTML triangles with partial div borders. This uses less divs to render triangles. This patch is based on a suggestion from X Ryl. Thanks! * Prevent ProjectServer from hanging in certain states. * mail-2.1.5 contains a showstopper bug. Reverting back to 2.1.3. * Make switch to mail-2.1.5.2 and later. * Some spelling and language fixes. * Describe the time sheet process in the manual. * Starting to document the client/server and time sheet stuff * Signal errors with TjException * Send out reminder emails when time sheets were not submitted in time. * Fix error message to report correct task. * Add command to list reports to tj3client * Adding hartbeat checks to the daemon * Clean up of the tj3client code. * Some code cleanups of the daemon code * Fix performance problem when adding a project remotely. * Replacing tj3 daemon mode with real daemon. This comes with some changes to the tj3client command line interface. * Abstracting Tj3AppBase so it can be used for other apps as well. * Add flag icons to task and resource journal reports. * Support vertical alignment option for RichText images. * Add support for images in RichText. * Fix RichText internal reference to match MediaWiki syntax. [[foo bar baz]] must now be [[foo|bar baz]]. * Sort tasks by task ID in sheet templates. * Spelling fixes. * Fixing the default date of sheets. * Fix warning for new tasks in time sheets. * Status sheets headers need time period not just end date This is needed to fix a problem that status sheet answer emails contain only current dashboard entries. * Adding additional header fields to emails. * Fix test suite after recent code changes. * Fix personal status note in time sheets. * Add option to send individual reports as well. * I don't think this warning is very helpful. Remove it. * Add --force option to resend sheet templates. * Fix a crash when export reports were used before other reports. Reported-by: Eduardo Schoenknecht * Fix crash when using textreports without a header. * Encode emails as quoted-printable * Not including the minDate reports is probably more practical. * Fix crash when trying to set workinghours for shifts or resources. Reported-by: Eduardo Schoenknecht * Adding journal reporting for Tasks. * Add a link back to 'columns' from 'columnid'. Reported-by: Marcel * Don't include all old journal entries. Just the higher alerts. * Fix reporting of new tasks for delta check. * Check for garbage at end of sheet files. * Some styling for the journal reports. * Only warn once per mis-indented line. * Fix crash when both calendar and chart columns were used. Reported-by: JohnZ * Make the automatic end date for sheets work. * Add checks for default time sheet strings. * Clear time sheets and journal info before checking a time sheet. * Time sheets are due by default on Mondays, status sheets on Wednesday. * Moved common code of start scripts into new Tj3AppBase class. * Adding script to send out a summary of all time sheets. * Use shortened cell texts only for height and width limited cells. * Make sure that calendar columns always span their assigned width. * RichText plain text can now be indented. * Some bug fixes for the TextFormatter and more unit tests. * Improved version of block text formatter. * Adding commandline option for sheet tests to tj3. * Add commandline option to limit the resource list for status reports. * Started to improve the plain text output of RichText * Update for TODO list based on recent feedback. * Fix crash when reusing an extend ID. Reported-by: John Franey * Add close button to tooltips so IE user can use CTRL-C to copy. * Only include sheet in body if it was sent in body. * Fix signature handling and CR/LF removal. * Switch to quoted printable encoding for email body. * Add outlook for next period to time sheet templates. * Make email composition more robust. * Make manual build again. * Be more flexible with the detection of ->8- * Improved error reporting for unterminated cut mark strings * Save extracted sheets when they fail * Require a paragraph break to start a RichText verbatim/pre section. * Try to work around another encoding problem. * Add simple test suite for status reports. * Refactored common code of sheet senders into a base class. * Refactored common code into receiver base class. * Adding receiver for status reports. * Add reported actuals as comments to status sheet templates. * Some more improvements for the resource journal format. * Add reported time sheet number to the resulting report. * Make quote removal work for empty lines without trailing spaces. * Add check for time sheets with broken encoding. * Make percentage computation work for part-time employees too. * Added support to send status sheet templates. * Always store time sheets in UNIX text file format. * Add warnings for requested changes in time sheets. * Make time sheet receiver more flexible. It can deal with junk before and behind the time sheet and can ignore email quote signs. * Fix calculation of completion for effort based task. * Don't include full sheet in error log. * Add {} after include statements. * Enable 'journal' column for reports. Currently only works for resources, not task. * Fix SCM command for all.tji. * Fix off-by-one bug that caused efforts to be one slot short. * Some more bug fixes for the time sheet processor. * Fix syntax. * Add SCM support. * Linewrapping support for RichText paragraphs in text mode. * Try to send emails even for email delivery failures. * Make CR and CR+LF work for buffers as well. * Deal with CR/LF issues no matter where the file was created or is read. * Always generate the full list even when doing single send-outs. * Add more logging and error handling. * Adding simple test case for timesheet sender/receiver. * Change some working of the time sheet related text messages. * Don't send templates to resources on vacation. * Generate tji files that include all timesheet tji files in the directory. * Support task independent status message in time sheets. * Merge some common code into single base class. * Add a ')' after section numbers in text mode. * A slightly improved version of the race condition hack. * Add command line option to send out just specific templates. * Fix TJ2.x export section. A git checkout after 2.4.3 is required. * Improved validation of time periods. Reported-by: petermp * Allow timesheet syntax in body of email. * Include warnings in answer email. * Add --nomail option for tj3ts_receiver. Switch from open3 to open4. * Turn email sending on. * Make -c option work for tj3ts_receiver * Make tj3ts_receiver more configurable. * Make sender/receiver scripts more robust. * Added a script for the time sheet receiver. Nicer formatting of the returned time sheet. * Adding scripts to send and receive time sheets via email. * Some improvements for the client/server interactions. * Add examples for time sheet stuff to the manual. * More error checking for time sheets. * Add paragraph to generate proper HTML. * More HTML layout fixes to work around IE8 bugs. * Fixed a problem with inherited attributes in derived scenarios. Reported-by: Brendan Hyland * More HTML code fixes for IE8. * Again more tables for HTML layout to please IE8. * Fixed a crash when using shift assignments. Reported-by: JohnZ * Use some common code for table frames. * Going back to using HTML tables. There is just no way I can get the layout to work with divs on all browsers. * Some CSS fixes to make it work with Firefox and Opera. IE8 will probably still not work. * Some cleanups for the text report column layout. * Switch tutorial to new columns. * Fix textreport layout. * Replace 'depends' and 'precedes' columns with 'precursors' and 'followers'. Also: Tooltips now show up on click, not hover. * Make CSV tests work again. * Make tooltips work for charts in HTML reports. * Fix logical functions with trailing _. And some more HTML compliance fixes. * Generated HTML files should be all W3C compliant again. * Use a single function to generate all HTML head sections. * Add missing content-type meta tag. * Fix some HTML problems. * It's time to switch to strict XHTML 1.0. * Try to work around IE bugs. * Use Query attributes more frequently during report generation. This is just a first step of a larger effort. * Fix crash. * Be more explicit about operator precedence in logical expressions. * Make tooltips sticky. The icon of the name column now always has a tooltip with the ID. * Swap symbol and label in HTML report legend. * Properly handle self-closing XML tags. * Replace some HTML tables with div constructs. Start to cleanup the stylesheet namespace. * Add project ID to Niku report headers as well. * Put 0.0 in XOG files, but not in HTML reports. * Some more bug fixes for Clarity reporting. * headline is now support for nikureports. * Adding an HTML version for the Clarity reports. They are mostly useful for debugging right now. * Improve readability of code sections in user manual. * Adding an example for the Clarity XOG report. * Some cleanups for the Clarity export file. * Some cleanups for the Clarity exporter. * First draft of a Clarity export report. Just a concept for testing right now. * More work on the time sheet report generator. Currently hardcoded for percentage reporting. * Adding TJ2 with TJ3 reports howto. * Fix 'taskattributes' for export reports. Reported-by: Marcel * Add additional search path for Debian based distros. = Release 0.0.4 (2010-02-01) * Some prep for signed gems. Not yet very user friendly. * On our way to a 0.0.4 release. * Don't crash on empty tooltips. * More improvements for the installation description. * Better documentation for the install procedure. * More details on TJ2->TJ3 migration. * It's 2010 now. * Add support for 'today' built-in macro. * First working version of client/server feature. Still very basic code. * Fix nested include files with taskprefix. This patch also enforces a .tji extension for included files. Reported-by: Christian Sunkel * Make timezone test work again on glibc 2.10 and later. * More sketches for the client/server code. * First parts of a report server. * Make sure we always return an Array. * Added support for an 'alerttrend' column. * Use Query for LogicalExpressions. And some unrelated bug fixes. * Added support for status sheets. * Support multiple journal entries for same date. * Show all task in taskreports by default. Added isongoing() function to get old behavior back. * Support for custom periods for columns. * Make cell background color configurable. * Add an expiration date to 'hasalert()' * Get rid of empty tooltips. * Add option to generate self contained HTML reports. * Bug fix for ReferenceAttributes. * Cleaning up the Query mechanism. * Show name instead of id for 'responsible'. * Some fixes for the time sheet handling. * Remove scenario specification from time sheets. It's a project setting now. * Only use bold lines in tree mode. * s/count/length/ for Ruby 1.8 compatibility. * Make 'summary' and 'details' work in time sheets. * First pieces of the time sheet report generator. We can now generate time sheet templates and read the filled-out ones back in again. Still a lot of plumbing missing though. * Make the status part of time sheets work. * First code for time sheet support. * Make alertmessages work. * Use more descriptive names for the journalentry attributes. * Make 'loadunits' work again. Reported-by: Guillaume DELVIT * Add check for feature limited RichText strings. - More accurate error position reporting. - Cleaned-up alarm level descriptions. * Add support for 'alertnotice' column. * Some bug fixes for the Query mechanism. * hasalert function for LogicalExpressions added. * Added support for statically computed dates. * Some bug fixes for the report generator. * Make LogicalExpressions work with dates. * Make hyper link targets configurable. * Put HTML stylesheets into separate files. * Several bug fixes for the report generator. * Make logical expressions work with strings. * Added support for queries inside of HREF * :w * Update tutorial for new syntax. * Major change of how the cell content is generated. This commit changes the synatx in an incompatible way to earlier versions! * Initialize all RichStrings in reports for queries. * Another fix for the cell generator. * Fix manual build. * Some fixes for last commits. * Some cleanups for the cell text handling. * Some refactoring of the RichText classes. Generate proper intermediate objects so this stuff is re-entrant and the Query objects can be attached. * Project attributes can now be queried. * Support nested properties for non-leaf properties in reports. This is a major change to earlier versions. To get the old behavior back, you need to replace hideresource ~isleaf() with hideresource ~(isleaf() & isleaf_()) See manual for details what the function with _ suffix mean. * Cleaned-up the query code somewhat. * Queries now have a context. * Don't drop parent tasks too early in the filter process. * Make the != operator work for logical expressions. * Fix column width handling for table columns. * Don't delete manual dir. * Fix sporious build fails for manual. * Added support for alert level reporting. * Always show a dot after one-level-only bullet items. * Use tooltips for all fixed-width columns. * Make sure the details icon is always visible. * Get rid of empty tooltips. * Added reportable attribute 'targets' for tasks. * Improved handling of column width setting * Fix UTF-8 issue with IE8. * Started to turn list attributes into RichText cells * Make HTML reports a bit nicer. * Move copyright to page bottom. * Highlight active navigator menu. * Updated todo list. * Handle RichText columns with tooltip windows if necessary. * Cleaned up BatchProcessor to report a Ruby 1.9.x bug. * Improved tree sorting code. * Replace some clone() with dup() calls. Will probably not affect performance much, but we really only need dup() here. * Fix Ruby 1.8 compatibility issue. * Fix tree sorting. * Make tree sorting work better. The algorithm is still broken. * Fix sorting for 'tree' + something lists. * Fix isdependencyof. * Fix free time reporting for resources. * Make hidecelltext really work for standard cells. * Move doc of logical functions to syntax reference. * Make hidecelltext work with standard report cells. * New section for manual. * Added documentation of logical functions and other syntax elements. * Fix a HTML render issue with Opera. * Added isactive() LEXP function. * I shall run the test suite before committing 'trivial' fixes... * Some bug fixes. * Fix a Ruby 1.8 compatibility issue. * Added support for RichText function query. * RichTextProtocol -> RichTextFunction * Add hint that X.X.X needs to be replaced with current version. * Fix some races in the BatchProcessor. * Some more cleanup of the BatchProcessor. * Navigator now matches the report structure. * Call wait() block as soon as jobs have finished. Don't wait until all jobs have completed. * Assorted bug fixes. * Make BatchProcessor more robust. * Fix multithreaded report generation. Also don't overwrite inherited report attributes. * Improved handling of EOT within RichText tokens. * Reports can now have a title that's used by the navigators. * Accounts and reports can now be flagged. * Fix missing bookings in export report. Reported-by: Arlindo Carvalho * 'effortdone' and 'effortleft' columns added. * Fix manual generation. * Add support for ']' inside macro definitions. * Another attempt to fix the datadir problem. * Add separate config variables for software name and package name. * Some bug fixes. * HTML reports now contain icons. * Improved bug report guidelines. * Fix crash on recursive macro calls. * Fix resource load stack in Gantt charts. * Added some more features for reports. * Document 'formats' attribute more prominently. = Release 0.0.3 (2009-10-20) * It's time for a new release. * Fix multi-char operators for LogicalExpressions. * Fix for last hidecellurl patch. * Add support for hidecellurl. Fix tutorial examples. * Only the first file must be a .tjp * Try fix for IE8. * 'Overview' looks nicer in the nagivation bar. * Update example. * Started update of tutorial with new report syntax. * Convert tutorial example references to new syntax. * Implemented block and inline generators for RichText. The new syntax replaces the previously used [[protocol:name]] syntax. * Fix Ruby 1.8 compatibility problem. * Various improvemens for the HTML reports. * Fix some rendering issues of the HTML reports. * Check for @value not being set. Fix suggested by msc. * Generate more descriptiv auto-IDs. * Fix display of reference URLs in reports. Bug reported by msc. * Some code refactoring to cleanup the mode handling. * Detect recursive inclusions. * Improved input file name validation. * --output-dir option added Generated reports will be put into this directory if specified. * Update manual to include optional IDs. * Make property IDs optional. ID and version for the project declaration are also optional now. * Improved test for weak deps problem. This is a forward port of the fix that Gregoire wrote for the weak dependency problem. * Added test case from Gregoire. No need to forward port this fix. * Don't inherit maxstart and minend. * Add support for 'scenario' column. Bug reported by garyo. * Some code cleanup. * More missing pieces of the the 'fix resourceprefix' commit. * Somehow this change was left out of the last commit. * Fix 'resourceprefix' handling. This fixes a problem reported by Beni. * Fixes for export report. Make inclusion of definitions user configurable. Add new '*' and '-' shortcut for keyword lists. * Some cleanup in the RichText generator. I need to find a clean solution to handle RTPs that expand into blocks. Right now they are enclosed in

..

and that's illegal in HTML. * Fix tutorial example again. * Avoid compatibility issue with 1.8.6. * Updating the tutorial with latest syntax changes. * Several fixes for the manual. * Added documentation for types. * Added documentation for new cut-mark-strings. * Nested reports: putting it all together * First working version of nested reports. * Added support for cut-mark-strings (-8<- for bar ->8-) * Scanner cleanups. * Explicitely use current dir for 'require'. Latest ruby 1.9.2 version seems to no longer include '.' in $:. * Fix suggested by Guillaume DELVIT. * More steps towards nested reports. * Update tutorial example. * Another fix for bookings. * More refactoring to prepare composable reports. * More bug fixes. Fix problems with reference attributes defaulting to 'true'. Fix WorkingHour dumping. * Don't change start/end date of scheduled tasks with bookings. * Make sure we always show the year in the header. * Respect the user provided report period. * Some bug fixes for report generator. * Started refactoring for composable reports. This is very much WIP right now. The report syntax will change again. * Fix scenario related problem reported by Jean-Yves. We need to treat inheritance from scenarios like initial assignments, not like inheritance from parent properties. * Eliminate dead code. * Handle duration task with bookings like TJ2.x. * Fixed link to example entry. * Added journaling support. No reporting support yet. * Be more clear on inheritance in syntax reference. Attributes can be inherited from a parent property or a global attribute. The syntax reference now clearly lists for which attributes this is the case. * Fix tutorial example. * Added support for 'header' and 'footer' attributes. * Make menus more versatile. * Fix description of ID characters. * Use a well defined time zone for syntax tests. * Properly handle provided completion values. * Export report related fixes. * Older versions of zoneinfo use the English name. Pick a more static time zone that works with all systems. * Time zone related fixes. * Setting a global time zone works now. * The timing resolution is limited based on the selected time zone. * The test suite should work in all time zones now. * Added support for 'depends' and 'precedes' column. * Cosmetic changes to the report menu. * First shot at an autogenerated menu for reports. * Fix entry link for report. * Improve the bug report howto. * Don't always trust the ruby doc. * Testing the new ML archive. * Added item to TODO list. * Fix another link to the new forum page. * Set link to new forum entry page. * Set forum link to new forum page. * Updated TODO list. * Dropped support for old report definitions. The new format is now more in line with the TJP language and conflicts with the old format. A legacy support is not possible. * Rewrote the deep_clone support. This method is a lot more robust. The report syntax used in the tutorial is just a draft and will change again. * Refactored Report class. Replaced ReportElement and ReportTableElement with ReportTableBase. * Clean up exception raises. Some more prep work for the upcoming report refactoring. * Turning Report attributes into TJ attributes. * Turning Report into a TJ property. That way we get attribute inheritance for reports and we can create a structured tree of reports. = Release 0.0.2 (2009-03-04) * Time for another alpha release. Bump version to 0.0.2. * Advertise proper encoding for all HTML files. * Show off more features in the tutorial. * Fix tutorial project. Generate example/tutorial from the test case that is used for the manual. * Align custom attribute in report cells properly. * Make export reports more TJ2.x compatible. * Version without ruby-prof calls. * This version needs ruby-prof and crashes ruby 1.9. * Make sure the start date of intervals is smaller than the end date. * Generate proper HTML code for reports. * Added 'treelevel' logical function. Started to work on colored text cells. * Add commandline option to generate reports despite scheduling errors. * Documented the BatchProcess class. * Some fixes for the SMP support. * Improving the tutorial for TJ3. * Use multiple CPU cores for report generation. First draft, not working yet. * Ruby 1.9 has good UTF8 String support. Use the built-in functions. * Use 'times' instead of '0.upto'. * Open new window for external references in the manual. * Some speed improvements and code cleanup. * Cleaned-up progress information. More source code comments. * Some speed improvements by cached the onShift? result. * A few more scheduler fixes. * Code cleanup and comments. * Some booking related fixes. * Add check for project without tasks. * Lots of cleanups and speed improvements in the scheduler. Fixed manual generation. More test cases. * Bug fix and more test cases for allocations. * Some speed improvements. * Do milestone detection before inheriting start/end dates. This makes more sense and is compatible with TJ 2.x. * Fix basename computation. * Don't put -w in the shebang line. * Various fixes for the test suite. rake19 is still failing with some obscure error message around id2ref calls. But ruby19 all.rb works fine. * Improving the scheduler performance. Added progress indicator. * Fix task shifts to be compatible with TJ 2.x. Make sure all tests of the test suite work properly again. * Initialize the scoreboards only when the resource is actually used. This can drastically speed up the scheduling for projects with many unused resources. * Don't break when hitting an unscheduled task. * Show all runaway task as warning first. Then abort. * Task start or end dates can now be inherited from parent tasks or the project time frame. * 'rake release' does not work on Windows. * Finally fixed the output path in the manual task. * Don't include subdirs of manual in distribution. * Need to use hardcoded file name :( * Use variable instead of hardcoded path. * Using threads to get more performance is a dead-end with Ruby. The big VM lock kills any chance to use multiple CPUs. This makes Ruby really an anachronism in todays multi-CPU PC world. * First steps towards a multi-threaded scheduler. This version still suffers many race conditions! * Support generation of the user manual with installed version. The package-time manual generation still works, but users can now use tj3man to generate their own copy of the HTML manual. * Don't generate the manual for packaging. * Completing the move of classes into the TaskJuggler namespace. * Again putting more classes into the TaskJugler namespace. * Some cleanups. Build manual and rdoc for release. Don't include README.rb in rdoc documentation. Gem package should be called taskjuggler, not taskjuggler3. * HTMLDocument.rb does not belong in reports. * Moving report classes into reports directory. And putting them in the TaskJuggler namespace. * Combine all attribute classes into a single file. * Put more classes into the TaskJuggler namespace. * Hopefully the last one. * And another one. * More Ruby 1.8.6 compatibility fixes. * Fix compatibility issues with Ruby 1.8.6 and earlier. * fix README, gem, rdoc tasks, Rakefile handle load error * rcov task for ruby19, need small rcov patches * run_gem_test for ruby18 * Some cleanups as a result of the new rake build system. * enable test individually * test rake task * manual rake task * fix rexml/formatters/pretty for rcov task * rcov rake task * Update makedist to make better use of rake. This file will become obsolete once we have a rake task for the manual. * remove rcov rake task for ruby19 * manual rake task * Cleaned up the new include syntax documentation. * fix rexml/formatters/pretty for rcov task * enable test individually * Remove redundant computation of transitiveOptional in Rule. Added more comments. * Revert "rcov rake task" This reverts commit e7d3a424a91d2a01f7b625e7e1e0cdc619614f0d. [xpc:1] taskjuggler3> rake19 --trace (in /home/cs/src/taskjuggler/taskjuggler3) rake aborted! no such file to load -- rcov/rcovtask /home/cs/src/taskjuggler/taskjuggler3/tasks/rcov.rake:2:in `require' ... * stats rake task * rdoc rake task * rcov rake task * show rake task * Revert "test rake task" This reverts commit 7ff85849b906c504a782c52921b2516d8988d00c. This patch seem to remove the ability to run test cases individually. * test rake task * Rakefile tasks : README, gem * Don't break compatibility with Ruby 1.8. I shall test ruby 1.8 before committing. I shall test ruby 1.8 before committing. I shall test ruby 1.8 before committing. I shall test ruby 1.8 before committing. I shall test ruby 1.8 before committing. ... * Improved the handling of file inclusion in the parser. * Get rid of ugly String operation. * Make parsers work with empty input strings when syntax permits. * Bug fix. * Cleaned up new Log class. Added basic structure of a propper ARGV parser. * stats rake task * rdoc rake task * rcov rake task * show rake task * test rake task * Rakefile tasks : README, gem * Add execution trace logger. Put more classes into the TaskJuggler namespace. * Limits can now be resource specific. This is helpful to map the deprecated allocation limits to. * Limits can now be restricted to certain time intervals. * First steps to introduce namespaces. * rakefile system setup and a few changes * It's 2009! Update copyright sections and removed some Ruby 1.9 warnings. * Replace dependency on Ruby library CSV class with own CSVFile class. CSV is a major compatibility nightmare between Ruby 1.8 and 1.9. Rather than plastering the code with version specific code snippets, I decided to implement my own CSV reader and writer. * Re-add UTF8 support files. * Merging ruby-1.9 changes back to master. * No longer needed. * Added some new benchmarks to compare ruby 1.8 and 1.9 performances. * Fix TjTime.sameTimeNextMonth bug reported by pauland. http://www.taskjuggler.org/FUDforum2/index.php?t=msg&th=4003&start=0&rid=65&S=9d61c1a4a09e928b646920b7968d724c * Fix problem with latest ruby SVN. * Added a dash between dates to meet TJ 2.4.1 date interval syntax. * Finally a version that works with ruby 1.9 SVN. * Merge some selected patches back. Bring the TextScanner test back. * Revert "Improve UTF-8 support in scanners." This reverts commit f05aceac000314c380b7fc46cc6665bdf214a622. * Revert "Improved UTF8 testing." This reverts commit 3209450c18a8087b9e023ae7130fe78bf47570b2. * Revert "More UTF-8 releated cleanups." This reverts commit 88668b7980ed126c65919ac4a3d7c89ed7419be0. * Revert "Converted the String processing in the parser to more Ruby 1.9 compatible syntax." This reverts commit 5fbc96e02eab62d207598f355784a3b1d46acac4. * Revert "Some cleanup after the recent changes." This reverts commit 64c4cb2e282da5195beeed6b488b28365131362e. * Some cleanup after the recent changes. * Converted the String processing in the parser to more Ruby 1.9 compatible syntax. * More UTF-8 releated cleanups. * Improved UTF8 testing. * Improve UTF-8 support in scanners. This should also simplify a migration to Ruby 1.9. * Added test case and bug fixes for LogicalExpression. * Code cleanups and API documentation. * Test case for PropertySet added. Still very rudimentary, but does check WBS indexing and node removal. Lots of related bug fixes. * Avoid to store derived data in the PropertySet data structure. This will make PropertyTreeNode deletions from the set easier. * Add test for Project.rb. Make TjTime constructor a bit more handy. * Fix syntax reference generation problem. * Fixes for the recently added example projects in the test suite. Most of them involved 'limits'. They also triggered some bugs in tj3 which have been fixed as well. * Add examples for properties in manual. * Improved doc for 'supplement'. * Add example for 'booking' property. * Add example for 'credit' property. * Add example for 'complete' property. * Add example for 'copyright' property. * Add example for 'caption' property. * Fix problem with reference custom attributes. * Fix 'priority' attribute for tasks. * Clarify purge description. * Add description of purge and limits for allocate property. * Add support for 'hidecell' column attribute. * Make use of endIdx consistent with TjTime end objects. Lots of bug fixes for the effort computation routines. * Several bug fixes for the effort and duration reporting functions. * Add support for ? prefix for dynamic macros to allow them to be undefined. * Some bug fixes. * Add support for cellurl column attribute. * Use nil instead of string constant for empty load stack elements. * Added support for celltext column attribute. * Clarify install and contribute section of the manual. Get rid of some TJ2.x leftovers. * Fixed HTML load-stack generation. Removed junk lines under ColumnTables. Don't include parent task for taskroot reports. * Another round of checks for the parser. * Complete TaskScenario.preScheduleCheck(). * Adding accidently deleted patches again. * More parser error checks. * More error checking. * Properly handle rich text attributes in report columns. Make reports smaller by using more CSS attributes. * Port recent scheduler improvements from 2.x version. * Fixed runtime error reported by Susan. Error checking for report start and end dates added. * Started to support RichText columns in HTML reports. * Add protocol handlers for RichText. The tutorial in the manual now includes code snippets from the Syntax/Correct directory files. * The reference manual can use example code again. * Improve doc for workinghours. Start TjpExample class. * Fix documentation for arguments that are documented, single pattern rules. * Handle white spaces in Rich Text explicitely. * Correct syntax examples as test cases added. * Include RichText elements in SyntaxReference in link checking. * Add link checker for RichTextDocuments. * Improved white space handling for RichText output generators. * Property report scenario specific and inheritable optional attributes. * Added 2008 to copyright header. * Document the top-level class and add a hint to it in the README. * Merged syntax reference into new user manual. * Add infrastructure to generate user manual from MediaWiki files. * Switched calendar report columns to use Query. * Test cases and fix for dynamic attribute sorting. * Dynamic Property attributes can now be sorted as well. * Add framework for CSV report unit tests. * Make 'include' work at start of project file. * Unified query infrastructure implemented. All property attributes can now be queried via the same interface. The report generates use it already for dynamic attributes. * Some comments added. * Make logical functions with arguments work. This fix includes various other bug fixes as well. * Add 'rake' as installation dependency. * Only use forward locking path for path criticalness. Port from 2.x. * Improved error handling. * Added support for CSV reports. * Fix multiple mandatoy allocations with overlapping resource sets. * Added support for one-time and duration dependent charges. * Work around for Firefox column width bug. * More work on the accounting features. * Many fixes so that TJ3 can read TJ2 exported projects properly. * Properly connect the error handlers of the RichText parser and the TJP parser. RichText errors are now reported just like TJP errors with their proper in-file location. * Added support for RichText prologs and epilogs in reports. * Added support for some more MediaWiki markups. Most RichText features are now used for the reference manual. * Cleaned up RichText classes and added unit tests. * Added simple rich text engine to better format the documentation. Still very crude and needs lots of comments. * Various fixes for the README and the reference manual. * Fix sorting order of keywords in reference manual. * Improved the intro page for the HTML manual. * Fix problem with label. * Updated README with better install description. * Added support for functions in logical expression. Right now only isLeaf() works. * Generate all legend elements on demand and show them only if used. * Use smaller start gap for Gantt chart dependency arrows. * First work on Account support. Not much there yet, but the parser already understands most of the syntax. * Replaced all relative font sizes with absolute ones. * Warn about using 'wbs' for sorting. It's produces unexpected results. Users should use 'tree' instead. * Fix font-weight handling for reports. * Fix WBS sorting bug reported by bhoel. Added some more comments. * Several fixes/improvements for the HTML calendar and chart. * Make calendar columns scrollable as well. This patch still has many rough edges but is a first shot in the right direction. * Fix index link in reference manual HTML pages. * Fix 'TjTime.rb:113:in `local': argument out of range' error. More comments added to TjTime.rb. * Fix more bugs reported by bhoel. - WBS indexing fixed. - Add first set of checks to TaskScenario.preScheduleCheck to prevent containers with duration attributes. - Fix 'duration' column values. * Fix ReportElement.rb:376:in `[]': no implicit conversion from nil to integer bug. * Get rid of some Ruby warnings. * Complete support for 'loadunit' in reports. * Fix another cut/copy/paste-bug. * Fix bug reported by bhoel. * Fix bolding and indentation of container lines in reports. * Various fixes and improvements. - Add support for load scaling. - Simpler form of ledgend. - Assorted fixes. * Some more polishing for the HTML Gantt chart. Added now-line. Use larger padding for cells. * Improved the Gantt dependency line routing. * First draft of a dependency line router for the Gantt chart. * Added support for dependency arrows in HTML charts. Very crude arrow routing right now. Will be fixed later. More comments added all over the place. * Make HTML Gantt chart scrollable. * Show time-off intervals in HTML Gantt charts. * Some cosmetics for the HTML reference manual. * Task and resource rendering in Gantt charts now works. * More work on the HTML Gantt chart. Still lots more work to do but it is taking shape now. * First version of HTML Gantt chart. This is just draft code to test browser compatibility. * Cleanup up report attributes. Report line counting fixed. Alternating background colors fixed. * Report refactoring completed. All lose ends connected again. Should work as good/bad as before the refactoring. * Complete refactoring of report classes. This is just a snapshot with very many lose ends. * HTML documentation how has proper navigation bar. * Support HTML formatted syntax help for parser keywords. * Get rid of the Subversion tag in the header. * Hopefully final test. * Another test commit. * Just a test for post-receive. * Just a test commit. * Avoid multiple resets of unmodified Limits scoreboards. * Make parser rule definition easier. Don't require the rule name to be entered twice. Make function names a bit more consistent. * Cleaned up all code that deals with resource allocations. Fixed a number of other bugs in related classes as well. * Support for limits for resource usage and task assignments added. * Various bug fixes and documentation improvements. * Make shifts work for task assignments. Various other bug fixes and speed improvements. * Make ShiftAssignments use shared Scoreboards. This increases memory efficiency and performance for larger projects. * Make Shifts work for some more complicated test cases. Cleanup of Workinghours. Fixed lots of bugs in ShiftAssignments. * First round of Shift support. Far from complete, but resource shifts seem to work ok. * Make sure we have test cases for all syntax error messages. * Use less special cases in the syntax description. * Make tj3man work for 'allocate' and 'columns'. * Some cosmetics for the HTML reports. * Added more features to the export report. * More syntax documentation and build system improvements. Fixed Rakefile and package generation script. Added more docs for syntax keywords. * More work on tj3man. Many more keywords have been documented. * Accept interger and float values. * More tj3man fixes. More syntax documentation. * More work on tj3man. * More work on auto-documenting parser. * First implementation of syntax manual tool tj3man. * Some bug fixes and scalability improvements. * Optimizer now properly uses path criticalness heuristic. * First implementation of path priority calculation. * More fixes for 'booking'. Make logical expressions work. * Added support for 'no' column. * More test for bookings and some bug fixes. * More work on bookings. * Some work on 'booking' support. Not yet complete. * Eliminate some code dumplication. * More test cases. More bug fixes. * Fix for loop detector. * Dependency loop checker added. * Fixed 'precedes' dependencies. * Implemented some ideas how to support start->start or end->end dependencies. * Assorted improvements. Prepared dependency handling for start/start and end/end deps. HTML report beautifications. Scenario attribute support improved. * Cleaned up macro parser and added error checking. * Macro parser added to TextScanner. Error checking is still missing but it seems to work with first simple tests. * Don't revconftrol HTML files in this dir. * More infrastructure for test suite added. * Added support for real project files in the test suite. * Extended Export report generator and TJP parser so that the parser can read a generated export report. * More work on Export report generator. * Cleanup TJP syntax rules by replacing repeated pattern with convenience functions. * User helper functions to setup syntax rules for similar rules. * Support for global and resource vacations added. * New color scheme for HTML reports. Some more work to generate nicer looking HTML reports. A few bits and pieces are still missing. * Added support for task and resource sorting. Some hardining for reporting unfinished scheduling runs. The sorting stuff is completely untested. * Moved TJP syntax rules into separate file. * More work done on the report generator. We now have HTML task and resource reports that have a basic working feature set. Report columns may contain property fixed data values, computed values based on the property and report interval, as well as calendar charts. * More work on HTML task report htmltaskreports are now somewhat complete. Resources are not yet supported. * - More work on HTML task report generation. * - Cleanup of task report generator. - Added lots of time manipulation functions. * - Scheduler fixes. * - Support for logical expressions added. - Multiple scenarios for HTML reports. * - Renaming some files to be more specific. - Some more comments. * - Add missing files from last checkin. * - Fix attribute inheritance. * - Nicer and more complete reports. * Added lots of new features: - Extended attributes - Hierachic tasks - post scheduling task checking - include file support * - Support for WorkingHours added. * - Added support for more keywords in the parser. * - Documented TextParser. - Debug support for TextParser. - Added more TJP language keywords to ProjectParser. - Added FLOAT token to scanner. * - Add proper header to all ruby files. - Make taskjuggler.rb the real main program. * - First shot of a parser added. * - Fix HTML report header. * initial import * taskjuggler-3.5.0/manual/0000755000175000017500000000000012614413013014621 5ustar bernatbernattaskjuggler-3.5.0/manual/Tutorial0000644000175000017500000010160412614413013016351 0ustar bernatbernat== The Tutorial: Your first Project == We have mentioned already that TaskJuggler uses plain text files that capture the known parts of the project. As you will see now, the syntax of these files is easy to understand and very intuitive. This chapter will walk you step by step through your first project. You create the project plan for a made-up accounting software project. This project demonstrates most of the commonly used features of TaskJuggler. It also includes some of the more advanced concepts that you may or may not need for your projects. Don't get scared by them. You can use them once you are more familiar with TaskJuggler and your projects grow larger. The complete tutorial example comes with your TaskJuggler software installation. You can use the following command to find the base directory of the example projects. ruby19 -e "puts Gem::Specification.find_by_name('taskjuggler').gem_dir" The file for the tutorial project is called ''''examples/Tutorial/tutorial.tjp''''. You can use any plain text editor to view and modify it. === Starting the project === Every TaskJuggler project file must start with the [[project]] property. It tells TaskJuggler the name of your project and a start and end date. The start and end dates don't need to be exact, but must fit all tasks of the project. It is the time interval the TaskJuggler scheduler will use to fit the tasks in. So, make it large enough for all your tasks to fit in. But don't make it too large, because this will result in longer scheduling times and higher memory consumption. <[example file="tutorial" tag="header1"]> All TaskJuggler properties have a unique ID ,a name, and a set of optional attributes. The name must always be specified. The ID can be omitted if you never have to reference the property from another context. If you omit the ID, TaskJuggler will automatically generate a unique ID. The optional attributes are always enclosed in curly braces. If no optional attributes are specified, the braces can be omitted as well. In this example we will introduce a number of the attributes that may or may not matter for your specific projects. If you don't see an immediate need for a specific attribute, feel free to ignore it for now. You can always come back to them later. A full list of the supported project attributes can be found in the ''attributes'' section of the [[project]] property documentation. Attributes always start with a keyword that identifies them. The meaning and parameters of attributes depends on the property context that they are used in. A context is delimited by a set of curly braces that enclose optional attributes of a property. The area outside of any property is called the global scope. Usually, attributes have one or more arguments. These arguments can be dates, character strings, numbers or symbols. Strings must be enclosed in single or double quotes. The argument types and meaning is explained for each keyword in the syntax reference section of this manual. TaskJuggler manages all events with an accuracy of up to 15 minutes. In many cases, you don't care about this level of accuracy. Nevertheless, it's good to have it when you need it. All dates can optionally be extended by a time. By default, TaskJuggler assumes that all times are UTC (world time) times. If you prefer a different time zone, you need to use the [[timezone]] attribute. <[example file="tutorial" tag="timezone"]> Be aware that the project start and end dates in the project header are specified before you specify the time zone. The project header dates are always assumed to be UTC unless you specify differently. See [[interval2]] for details. project acso "Accounting Software" 2002-01-16-0:00-+0100 The [[currency]] attribute specifies the unit of all currency values. <[example file="tutorial" tag="currency"]> Because each culture has its own way of specifying dates and numbers, the format for these are configurable. Use the [[timeformat]] attribute to specify the default format for dates. This format is used for reports, it does not affect the way you specify dates in the project files. Here you always need to use the [[date|TaskJuggler date notation]]. <[example file="tutorial" tag="formats"]> We also can specify the way numbers or currency values are shown in the reports. Use the [[numberformat]] and [[currencyformat]] attributes for this. The attribute [[now]] is used to set the current day for the scheduler to another value than to the moment your invoke TaskJuggler. If this attribute is not present, TaskJuggler will use the current moment of time to determine where you are with your tasks. To get a defined result for the reports in this example we've picked a specific date that fits our purpose here. In your projects, you would use [[now]] to generate status reports for the date you specify. <[example file="tutorial" tag="now"]> In this tutorial we would like to compare two scenarios of the project. The first scenario is the one that we have planned. The second scenario is how it really happened. The two scenarios have the same task structure, but the start and end dates and other attributes of the task that are scenario specific may vary. In this example we assume that the project got delayed and use a second scenario that we name "Delayed" to describe the actual project. The scenario property is used to specify the scenarios. The delayed scenario is nested into the plan scenario. This tells TaskJuggler to use all values from the plan scenario also for the second scenario unless the second scenario has it's own values. This is a very easy but also powerful way to analyze the impact of certain changes to the plan of record. We'll see further below, how to specify values for a scenario and how to compare the results. <[example file="tutorial" tag="scenario"]> To summarize the above, let's look at the complete header again. Don't get scared by the wealth of attributes here. They are all optional and mostly used to illustrate the flexibility of TaskJuggler. <[example file="tutorial" tag="header2"]> === Global Attributes === For this tutorial, we also like to do a simple profit and loss analysis of the project. We will track labor cost versus customer payments. To calculate the labor costs we have to specify the default daily costs of an employee. This can be changed for certain employees later, but it illustrates an important concept of TaskJuggler – inheritance of attributes. In order to reduce the size of the TaskJuggler project file to a readable minimum, properties inherit many attributes from their enclosing scopes. We'll see further below, what this actually means. Right after the project property we are at top-level scope, so this is the default for all following properties. <[example file="tutorial" tag="rate"]> The [[rate]] attribute can be used to specify the daily costs of resources. All subsequently declared resources will get this rate. But it can certainly be changed to a different rate at group or individual resource level. You may also want to tell TaskJuggler about holidays that affect all resources. Global holidays are time periods where TaskJuggler does not do any resource assignments to tasks. <[example file="tutorial" tag="vacation"]> Use the [[leaves]] attribute to define a global holiday. Global holidays may have a name and must have a date or date range. Other leaves for individual resources or groups of resources can be defines similarly. === Macros === Macros are another TaskJuggler feature to save you typing work and to keep project files small and maintainable. Macros are text patterns that can be defined once and inserted multiple times in the project file. A [[macro]] always has a name and the text pattern is enclosed by square brackets. <[example file="tutorial" tag="macro"]> To use the macro you simply have to write ''''${allocate_developers}'''' and TaskJuggler will replace the term ''''${allocate_developers}'''' with the pattern. We will use this macro further below in the example and then explain the meaning of the pattern. === Declaring Flags === A TaskJuggler feature that you will probably make heavy use of is flags. Once declared you can attach them to any property. When you generate reports of the TaskJuggler results, you can use the flags to filter out unwanted properties and limit the report to exactly those details that you want to have included. <[example file="tutorial" tag="flags"]> This is a [[flags]] declaration. All flags need to be declared before they can be used to avoid hard to find errors due to misspelled flag names. The flags should be declared before any property at global scope. We will see further down, how we can make use of these flags. === Declaring Accounts === The use of our resources will generate costs. For a profit and loss analysis, we need to balance the cost against the customer payments. In order not to get lost with all the various amounts, we declare 3 [[account|accounts]] to credit the amounts to. We create one account for the development costs, one for the documentation costs, and one for the customer payments. <[example file="tutorial" tag="accounts"]> The account needs an ID and a name. IDs may only consist of the characters a to z, A to Z and the underscore. All but the first character may also be digits 0 to 9. The ID is necessary so that we can reference the property again later without having to write the potentially much longer name. The name may contain space characters and therefore has to be enclosed with single or double quotes. Accounts can be grouped by nesting them. You can use this feature to create sets of accounts. Such sets can then be balanced against each other to create a profit and loss analysis. When you have specified accounts in your project, you must at least define one default [[balance]]. <[example file="tutorial" tag="balance"]> === Declaring Resources === While the above introduced account property is only needed if you want to do a P&L analysis, resources are usually found in almost any project. <[example file="tutorial" tag="resources"]> This snippet of the example shows the use of the [[resource| resource property]]. Just like accounts, resources should have an ID and must have a name. Resource IDs, like account IDs must also be unique within their property class. As you can see, resource properties can be nested: ''''dev'''' is a group or container resource, a team that consists of three other resources. ''''dev1'''', alias Paul Smith, costs more than the normal employee. So the declaration of ''''dev1'''' overwrites the inherited default rate with a higher value. The default value has been inherited from the enclosing scope, resource ''''dev'''', which in turn has inherited it from the global scope. The declaration of the resource Klaus Müller uses another optional attribute. Attributes are only inherited from the parent property if the attribute was declared in the parent property before the child property declaration was started. The syntax reference lists for each property whether an attribute is inherited from the parent or the attribute in the global scope. With [[leaves]] you can specify certain time intervals where the resource is not available. Leaves are list attributes. They accumulate the declarations. If you want to get rid of inherited or previously assigned values, you can use the [[purge]] attribute to clear the list. ''''leaves'''' requires a time interval. It is important to understand how TaskJuggler handles time intervals. Internally, TaskJuggler uses the number of seconds after January 1st, 1970 to store any date. So all dates are actually stored with an accuracy of 1 second in UTC time. ''''2002-02-01'''' specifies midnight February 1st, 2002. Following the TaskJuggler concept of requiring as little information as necessary and extending the rest with sensible defaults, TaskJuggler adds the time 0:00:00 if nothing else has been specified. So the vacation ends on midnight February 5th, 2002. Well, almost. Every time you specify a time interval, the end date is not included in the interval. So Klaus Müller's vacation ends exactly at 0:00:00 on February 5th, 2002. February 5 is not part of the leave! Peter Murphy only works 6.4 hours a day. So we use the [[limits.resource|limits]] attribute to limit his daily working hours. We could also define exact working hours using the [[shift|shift property]], but we ignore this for now. Note that we have attached the flag ''''team'''' after the declaration of the sub-resources to the team resources. This way, these flags don't get passed down to the sub-resources. If we would have declared the flags before the sub-resources, then they would have the flags attached as well. === Specifying the Tasks === Let's focus on the real work now. The project should solve a problem: the creation of an accounting software. Because the job is quite complicated, we break it down into several subtasks. We need to do a specification, develop the software, test the software, and write a manual. Using the [[task|task property]], this would look as follows: <[example file="tutorial" tag="task1"]> Similar to resources, tasks are declared by using the task keyword followed by an ID and a name string. All TaskJuggler properties have their own namespaces. This means, that it is quite OK to have a resource and a task with the same ID. Tasks may have optional attributes which can be tasks again, so tasks can be nested. In contrast to all other TaskJuggler properties, task IDs inherit the ID of the enclosing task as a prefix to the ID. The full ID of the spec task is AcSo.spec. You need to use this absolute ID when you want to reference the task later on. This hierarchical name space for tasks was chosen to support large projects where multiple project managers may use the same ID in different sub tasks. To track important milestones of the project, we also added a task called Milestones. This task, like most of the other tasks will get some subtasks later on. We consider the specification task simple enough, so we don't have to break it into further subtasks. So let's add some more details to it. <[example file="tutorial" tag="spec"]> The [[effort]] to complete the task is specified with 20 man-days. Alternatively we could have used the [[length]] attribute or the [[duration]] attribute. ''''length'''' specifies the duration of the task in working days while ''''duration'''' specifies the duration in calendar days. Contrary to ''''effort'''', these two don't have to have a specification of the involved resources. Since ''''effort'''' specifies the duration in man-days, we need to say who should be allocated to the task. The task won't finish before the resources could be allocated long enough to reach the specified effort. Tasks with ''''length'''' or ''''duration'''' criteria and allocated resources will last exactly as long as requested. Resources will be allocated only if available. It's possible that such a tasks ends up with no allocations at all if the resources are always assigned to other tasks for that period. Each task can only have one of the three duration criteria. Container tasks may never have a duration specification. They are automatically adjusted to fit all sub tasks. Here we use the allocate_developers macro mentioned above. The expression ''''${allocate_developers}'''' is simply expanded to <[example file="tutorial" tag="expandedmacro"]> If you need to [[allocate]] the same bunch of people to several tasks, the macro saves you some typing. You could have written the allocate attributes directly instead of using the macro. Since the allocation of multiple resources to a task is a good place for macro usage, we found it a good idea to use it in this example as well. For TaskJuggler to schedule a task, it needs to know either the start and end criteria of a task, or one of them and a duration specification. The start and end criteria can either be fixed dates or relative dates. Relative dates are specifications of the type ''task B starts after task A has finished''. Or in other words, task B depends on task A. In this example the spec task depends on a subtasks of the deliveries task. We have not specified it yet, but it has the local ID ''''start''''. To specify the dependency between the two tasks, we use the [[depends]] attribute. This attribute must be followed by one or more task IDs. If more than one ID is specified, each ID has to be separated with a comma from the previous one. Task IDs can be either absolute IDs or relative IDs. An absolute ID of a task is the ID of this task prepended by the IDs of all enclosing tasks. The task IDs are separated by a dot from each other. The absolute ID of the specification task would be ''''AcSo.spec''''. Relative IDs always start with one or more exclamation marks. Each exclamation mark moves the scope to the next enclosing task. So ''''!deliveries.start'''' is expanded to ''''AcSo.deliveries.start'''' since ''''AcSo'''' is the enclosing task of deliveries. Relative task IDs are a little bit confusing at first, but have a real advantage over absolute IDs. Sooner or later you want to move tasks around in your project and then it's a lot less likely that you have to fix dependency specifications of relative IDs. The software development task is still too complex to specify it directly. So we split it further into subtasks. <[example file="tutorial" tag="software"]> We use the [[priority]] attribute to mark the importance of the tasks. 500 is the default priority of top-level tasks. Setting the priority to 1000 marks the task as most important task, since the possible range is 1 (not important at all) to 1000 (ultimately important). priority is an attribute that is passed down to subtasks if specified before the subtasks' declaration. So all subtasks of software have a priority of 1000 as well, unless they have their own priority definition. <[example file="tutorial" tag="database"]> The work on the database coupling should not start before the specification has been finished. So we again use the [[depends]] attribute to let TaskJuggler know about this. This time we use two exclamation marks for the relative ID. The first one puts us in the scope of the enclosing software task. The second one is to get into the AcSo scope that contains the spec tasks. For a change, we [[allocate]] resources directly without using a macro. <[example file="tutorial" tag="gui"]> One more interesting thing to note is the fact that we like the resource ''''dev2'''' only to work 6 hours each day on this task, so we use the optional attribute [[limits.resource]] to specify this. TaskJuggler can schedule your project for two different [[scenario| scenarios]]. We have called the first scenario ''''plan'''' scenario and the second ''''delayed'''' scenario. Many of the reports allow you to put the values of both scenarios side by side to each other, so you can compare the scenarios. All scenario-specific values that are not explicitly stated for the ''''delayed'''' scenario are taken from the ''''plan'''' scenario. So the user only has to specify the values that differ in the delayed scenario. The two scenarios must have the same task structure and the same dependencies. But the start and end dates of tasks as well as the duration may vary. In the example we have planned the work on the graphical user interface to be 35 man-days. It turned out that we actually needed 40 man-days. By prefixing the [[effort]] attribute with ''''delayed:'''', the effort value for the ''''delayed'''' scenario can be specified. <[example file="tutorial" tag="backend"]> By default, TaskJuggler assumes that all tasks are on schedule. Sometimes you want to generate reports that show how much of a task actually has been completed. TaskJuggler uses the current date for this, unless you have specified another date using the now attribute. If a task is ahead of schedule or late, this can be specified using the [[complete]] attribute. This specifies how many percent of the task have been completed up to the current date. In our case the back-end implementation is slightly ahead of schedule as we will see from the report. <[example file="tutorial" tag="test"]> The software testing task has been split up into an alpha and a beta test task. The interesting thing here is, that efforts can not only be specified as man-days, but also man-weeks, man-hours, etc. By default, TaskJuggler assumes a man-day is 8 hours, man-week is 40 man-hours or 5 man-days. The conversion factor can be changed using the [[dailyworkinghours]] attribute. Let's go back to the outermost task again. At the beginning of the example we stated that we want to credit all development work to one account with ID dev and all documentation work to the account doc. To achieve this, we use the attribute [[chargeset]] to credit all tasks to the ''''dev'''' account. For the duration of the ''''AcSo'''' task we also have running costs for the lease on the building and the equipment. To compensate this, we charge a daily rate of USD 170 per day using the [[charge]] attribute. <[example file="tutorial" tag="charge"]> Since we specify the attribute for the top-level task before we declare any subtasks, this attribute will be inherited by all subtasks and their subtasks and so on. The only exception is the writing of the manual. We need to change the chargeset for this task again, as it is also a subtask of AcSo and we want to use a different account for it. <[example file="tutorial" tag="manual"]> === Specifying Milestones === All tasks that have been discussed so far, had a certain duration. We did not always specify the duration explicitly, but we expect them to last for a certain period of time. Sometimes you just want to capture a certain moment in your project plan. These moments are usually called milestones, since they have some level of importance for the progress of the project. TaskJuggler has support for milestones as well. Milestones are leaf tasks that don't have a duration specification. <[example file="tutorial" tag="deliveries"]> We have put all important milestones of the project as subtasks of the deliveries task. This way they show up nicely grouped in the reports. All milestones either have a dependency or a fixed start date. For the first milestone we have used the attribute [[start]] to set a fixed start date. All other tasks have direct or indirect dependencies on this task. Moving back the start date will slip the whole project. This has actually happened, so we use the ''''delayed:'''' prefix again to specify the start date for the delayed scenario. Every milestone is linked to a customer payment. By using the [[charge]] attribute we can credit the specified amount to the account associated with this task. Since we have assigned the ''''rev'''' account to the enclosing task, all milestones will use this account as well. This time, we use the keyword ''''onstart'''' to indicate that this is not a continuous charge but a one-time charge that is credited at the begin of the task. Did you notice the line in the task done that starts with a hash? This line is commented out. If TaskJuggler finds a hash, it ignores the rest of the line. This way you can include comments in your project. The [[maxend]] attribute specifies that the task should end no later than the specified date. This information is not used for scheduling, but only for checking the schedule afterwards. Since the task will end later than the specified date, commenting out the line would trigger a warning. Now the project has been completely specified. Stopping here would result in a valid TaskJuggler file that could be processed and scheduled. But no reports would be generated to visualize the results. === Visualizing the Project === To see and share the project data you reports can be generated. You can generate any number of reports and you can select from a variety of report types and output formats. To have a report generated after the project scheduling has been completed, you need include a report definition into the project description. Report definitions are properties that are very similar to the task and resource properties that you are already familiar with. Just like these, report definitions can be nested to take advantage of the attribute inheritance mechanism. Every report definition starts with the type of the report. Each type of report has a particular focus. A [[taskreport]] lists the project data in the form of a task list. A [[resourcereport]] does the same in form of a resource list. For a more generic report, you can use the [[textreport]]. A ''''textreport'''' does not directly present the data in form of a task or resource list. It just consists of text building blocks that are described by [[Rich_Text_Attributes|Rich Text]]. There can be a building block at the top and bottom, as well as three columns in the center. The column are called ''''left'''', ''''center'''' and ''''right''''. For our first report, we'll just use the center column for now. Like every property, you need to specify a name. This name will be the base name of the generated report file. Depending on the output format, the proper suffix is appended. For this report, we only chose to generate a web page in HTML format. There is no default format defined for reports. If the [[formats]] attribute is not specified, no output file will be generated for the report specification. This may seem odd at first glance since TaskJuggler syntax always tries to use the most compact and readable syntax for the common case. As you will see in a minute, reports may be composed of several report specifications. One report specification can include the output of another report specification as well. In this case, the included report does not need to generate it's own file. The output will be included within the output of another report specification. In case of such composed reports, the output format specification of the top-level format will be used for all included reports as well. <[example file="tutorial" tag="overview_report1"]> For the main report, we choose the file name ''''Overview'''' and the format ''''html''''. So, the generated file will be called ''''Overview.html''''. As we've mentioned before, the sections of a ''''textreport'''' are defined in Rich Text format. Here we use a so called block generator to include the HTML output of another report definition. The ''''report'''' block generator allows us to compose reports by combining their output into a single report. You must provide the ''''id'''' parameter to specify which report definition you would like to use. In this case, it is a report definition with the ID ''''overview''''. Note that generator parameters need to be enclosed in single or double quotes. We are essentially marking a string within a string. This can only work out, if we don't use the same parameter for both. Let's define this report first. <[example file="tutorial" tag="overview1"]> Instead of another [[textreport]] definition we are now using a [[taskreport]]. A task report contains a list of tasks in a table structure. By default, it contains all tasks of the project. As we will see later on, we can use filter expressions to limit the content to a well defined subset of tasks. The table contains a line for each task and comes by default with a few columns like the name of the task, and the start and end dates. For this project overview report, we like to have also the effort for each task, the duration, the effort, the cost and revenue numbers included. To top it off, we also include a column with a Gantt chart. By including the cost and revenue column, we are able to do a simple profit and loss analysis on the project. This P&L is computed from the accounts that we have provided above. For this to work, we need to tell TaskJuggler which accounts are cost accounts and which are revenue accounts. We have already conveniently grouped the accounts and the [[balance]] attribute specifies which accounts are used for the P&L in this report. <[example file="tutorial" tag="overview2"]> The columns of the report can be customized. You can overwrite the default title or the cell content. See [[columns]] for a full list of available attributes. For the chart column, we'd like to have a tool tip that displays additional details when the mouse pointer is placed over a task bar. Since we use this tool tip in several reports, we have defined the ''''TaskTip'''' macro for it. <[example file="tutorial" tag="tasktip"]> The [[tooltip.column|tooltip]] attribute describes the content of the tool tip. The first parameter is a logical expression that determines when the tool tip is active. You can specify multiple tool tips. The first matching one is being displayed. The condition is evaluated for each report line. The ''''istask()'''' function only evaluates to true for task lines. See [[functions]] for a complete list of functions that can be used in [[logicalexpression|logical expressions]]. The content of the tool tip is a template that uses [[Rich_Text_Attributes#Block_and_Inline_Generators|query generators]] to include task attributes such as the start and end date. We have chosen to include the start and end date of each task in the report. By default, TaskJuggler lists dates as day, month and year. We like the format to be similar to the format that the project syntax uses, but also like to include the weekday. To change the date format, the [[timeformat]] attribute can be used. The project will last a few weeks. The most convenient unit to list efforts in is man or resource days. The [[loadunit]] attribute tells TaskJuggler to list the load of each task or resource in man days. Since this will just be a number without a unit, it is advisable to include a small hint for the reader that these values are indeed man or resource days. The caption of the table is a convenient place to put this information by using the [[caption]] attribute. <[example file="tutorial" tag="overview3"]> The ''''taskreport'''' can contain more than just the table. It is not as flexible as the ''''textreport'''', but still has support for a header and footer. Let's look at the header first. We not only like to put a headline here, but several paragraphs of text. The [[header]] attribute is a [[Rich_Text_Attributes|Rich Text]] attribute just like [[center]]. We could enclose it in single or double quotes again. But for Strings that span multiple lines and potentially include single or double quotes as well, scissor-marks or cut-here-marks are recommended. These marks look like a pair of scissors that cut along a dashed line. Use ''''-8<-'''' to begin a string and ''''->8-'''' to terminate it. The opening cut mark must be immediately followed by a line break. The indentation of the following line defines the indentation that will be ignored for all lines of the string. The following lines must have at least the same indentation. The indentation that exceeds the indentation of the first line will be kept in the resulting string. With this feature, you can define multi-line Rich Text strings without disturbing the indentation structure of your project file. <[example file="tutorial" tag="overview4"]> Section headers are surrounded by ''''==''''. The number of equal signs, define the section level. You need to start with two equal characters for the first level. Text that is surrounded by blank lines will create a paragraph. Bullet lists can be made by starting a line with a ''''#'''' character. Remember that the indentation of cut-mark strings will be ignored. Your ''''#'''' character must not be the first character in the line as long it is only preceded by the exact same number of blanks as the first line of the cut-mark string. If you want to reference other reports from this report, you can include the file name of this report by ''''[['''' and '''']]''''. Don't include the extension of the file name, it will be automatically appended. The actual representation of the reference depends on the chosen output format. For HTML output, the reference is a click-able link to the referenced report file. For the [[footer]] we can proceed accordingly. We just add a few more paragraphs of text the describe certain aspects of the project. By putting it all together, we end up with the following report definition. <[example file="tutorial" tag="overview"]> The generated report can be found [http://www.taskjuggler.org/tj3/examples/Tutorial/Overview.html here]. It servers as an entry page for the other reports. While it already contains some references, a navigator bar would be handy as well. Fortunately, there is a block generator called 'navigator' to take care of this. But before we can include the navigator in the report, we need to define it first. <[example file="tutorial" tag="navigator"]> [[hidereport]] is a filter attribute. The logical expression determines which reports will be included in the navigator bar. A logical expression of 0 means hide no reports, so all are included. The best place to put a navigator bar in the report is right at the top. We use two horizontal lines to separate the navigator from the main headline and the rest of the report. ''''----'''' at the begin of the line create such a horizontal separation line. <[example file="tutorial" tag="overview_report2"]> taskjuggler-3.5.0/manual/TaskJuggler_Internals0000644000175000017500000001670412614413013021015 0ustar bernatbernat== TaskJuggler Internals == This chapter contains information that you don't need to know to use TaskJuggler. It describes internal algorithms that are provided for the curious. === How the Scheduler works === It's important to understand that the scheduler implementation is not an optimization algorithm. It does not search a solution space and evaluates various alternative results against each other. This has been tried, but for any real-world project, the solution space becomes unmanageable and scheduling runs took hours to complete. Instead, we use a heuristic to decide when each task gets its resources assigned. This heuristic is certainly not perfect but has shown good results with fairly moderate computation costs. The following sections contain an overview of the scheduling algorithm. Users are also encouraged to read the actual source code. It can be found in ''''Project.rb'''', ''''TaskScenario.rb'''', ''''ResourceScenario.rb'''' and ''''Allocation.rb''''. All these files can be found in the ''''lib/taskjuggler'''' directory. You can also browse the sources on [https://github.com/taskjuggler/TaskJuggler github]. The scheduler needs to determine the start and end date for all tasks that don't have such dates yet. To deal with multiple concurrent time zones, all time related events are stored internally as UTC time.Additionally, it allocates resources to tasks. All events such as start or end of a task, or allocation of a resource can only happen aligned with the [[timingresolution|timing resolution]]. This determines the smallest possible allocation period that we call a time slot. The duration of the slot can be set by the user. Possible values are 5, 10, 15, 30 and 60 minutes. TaskJuggler keeps a scoreboard for each time slot for each leaf resource. Each scoreboard entry specifies whether the resource is unassigned, assigned to a specific task or on leave. This explains why the project duration and number of allocated resources determines the memory usage of the scheduler. For the scheduling of the project, the scheduler only looks at leave tasks that are not milestones. Container tasks and milestones are scheduled once all necessary information is available. During the scheduling process, leave tasks can have 3 different states. # ''''Not ready for scheduling'''': The task is missing a start or end date that depends on another task's date that hasn't been determined yet. # ''''Ready for scheduling'''': The task has at least a start or end date but one of them is still missing or resources have not yet been assigned for all time slots. The scheduling direction (ASAP or ALAP) determines whether the start or end date is needed. ASAP tasks are scheduled from start to end, so they require a start date. ALAP tasks are scheduled from end to start. # ''''Scheduling completed'''': The task has a start and end date and resources have been assigned for all time slots. The goal of the scheduler is to transfer all tasks in the completed state. Until this goal has been reached, at least one tasks needs to be in the ready state. If that's not the case, the project schedule cannot be determined and an error is raised. In case there are more than one task in the ready state, we need to have a well defined priority of the tasks. This is necessary since those ready tasks may compete for the same resource in the same time slot. The priority can be directly influenced by the user with the [[priority]] attribute. This user-defined priority always trumps the other internal criteria described below. In case two tasks have the same priority, an additional measure is used. This measure is called path criticalness. The path criticalness is calculated for each leaf task. The path criticalness is a measure for how important the task is to keep the overall project duration (start of first task to end of last task) to a minimum. To determine the path criticalness, we first need to determine the resource criticalness first. This is a measure for how likely the tasks that have this resource in their allocation list will actually get the resource. A resource criticalness larger than 1.0 means that statistically, at least one tasks will not get enough of this resource. This is just a statistical measure based on the total requested allocations and the number of available work time. Once we have determined the criticalness of all allocated resources, we can calculate the criticalness of each individual task. This really only matters for [[effort]] based tasks. These really need their allocations to be finished within a limited amount of time. For [[length]] and [[duration]] tasks, the allocations are by definition optional. The user can still influence the allocation to length and duration tasks by adjusting the priority appropriately. However, there is no guarantee that such tasks will ever get any resources assigned. The criticalness of an effort based task is defined as the average of the criticalness of the resources allocated to this task. We also assign a criticalness to milestones. Based on their priority a criticalness between 0 and 2.0 is assigned. This is done to reflect the user perception that milestones are usually some important goal of the project. The final step is now the computation of the path criticalness for each effort-based leaf task. For each possible chain of task (path) that is going through a task, the sum of the criticalness values of the tasks of the path is computed. The largest sum is the path criticalness of that task. This heuristic will favor allocations to task with critical resources and long dependency chains. As a result, the critical paths of the project are tried to be kept short. The user can use the '''criticalness''' and '''pathcriticalness''' [[columnid|columns]] to review the respective values for he project's tasks and resources. When the criticalness and pathcriticalness for all leaf resources and tasks has been determined, the leaf tasks are sorted by priority (hight to low), then by pathcricialness (high to low) and then by the index (low to high). In a loop that is terminated when all tasks have been scheduled or an error condition has been detected, the first task that is ready for scheduling is completely scheduled. This means that resources are allocated for all time slots and missing dates are being computed. The newly determined end (for ASAP tasks) or start (for ALAP tasks) date is then propagated to dependent tasks, milestones or parent tasks if needed. This can result in other tasks becoming ready for scheduling and the list is searched again for the first task that is ready for scheduling to be scheduled. A task can only be scheduled when it is ready for scheduling. This means that at least the start date for the scheduling process must be known. For ASAP (As Soon As Possible) tasks, the scheduler start date is the start date of the task. For ALAP (As Late As Possible) tasks the scheduler start date is the end date of the task. ASAP task will be scheduled from start to end, ALAP tasks from end to start. This is important to understand as resource assignments for each time slot will be determined in this order. Mixing ASAP and ALAP tasks in the same project is supported but should be used very carefully. It can lead to situations where a lower priority tasks that is earlier in the scheduling process ready for scheduling takes away resources from a higher prioritized task that competes for the same resources. This condition is known is priority inversion. If the scheduler detects such a situation, a warning is generated. taskjuggler-3.5.0/manual/fdl0000644000175000017500000005557312614413013015330 0ustar bernatbernat== GNU Free Documentation License == Version 1.3, 3 November 2008 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. The "publisher" means any person or entity that distributes copies of the Document to the public. A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements". 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. 8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License. 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, receipt of a copy of some or all of the same material does not give you any rights to use it. 10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation 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. See http://www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document. 11. RELICENSING "Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC site. "CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization. "Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document. An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008. The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing. ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this: with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. taskjuggler-3.5.0/manual/Intro0000644000175000017500000001431712614413013015645 0ustar bernatbernat== Introduction == === About TaskJuggler === TaskJuggler is a modern and powerful project management tool. Its new approach to project planning and tracking is far superior to the commonly used Gantt chart editing tools. It has already been successfully used in many projects and scales to projects with hundreds of resources and thousands of tasks. TaskJuggler is an Open Source tool for serious project managers. It covers the complete spectrum of project management tasks from the first idea to the completion of the project without enforcing certain work flows or methodologies. It assists you during project scoping, resource assignment, cost and revenue planning, risk and communication management, status tracking and reporting. TaskJuggler provides an optimizing scheduler that computes your project time lines and resource assignments based on the project outline and the constrains that you have provided. The built-in resource balancer and constrains checker offload you from having to worry about irrelevant details and ring the alarm if the project gets out of hand. The flexible "as many details as necessary"-approach allows you to still plan your project as you go, making it also ideal for new management strategies such as Extreme Programming and Agile Project Management. If you are about to build a skyscraper or just want to put together your colleague's shift plan for the next month, TaskJuggler is the right tool for you. If you just want to draw nice looking Gantt charts to impress your boss or your investors, TaskJuggler might not be right for you. It can certainly produce nice looking Gantt charts and other reports, but it takes some effort to master its power. For those that are willing to invest a few hours to get started with the software it will become a companion you don't want to miss anymore. TaskJuggler is a commandline tool that you use from a [http://en.wikipedia.org/wiki/Shell_(computing) shell]. This means that to enter your project data you will use one of the most versatile and powerful tools there is: your favorite [http://en.wikipedia.org/wiki/Text_editor text editor]. To get a first impression, you can look at this [http://www.taskjuggler.org/tj3/examples/Tutorial/tutorial.tjp project file]. The project description is fairly intuitive, but very powerful as well. The [[Tutorial]] will explain this file line by line. Please look at the [http://www.taskjuggler.org/tj3/examples/Overview.html resulting reports] that visualize the project. === License and Copyright === This program is free software; you can redistribute it and/or modify it under the terms of [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html version 2 of the GNU General Public License] as published by the Free Software Foundation. You accept the terms of this license by distributing or using this software. This manual is Copyright (c) 2006, 2007, 2008, 2009, 2010 Chris Schlaeger. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". The HTML reports use icons from the [http://www.kde.org/people/credits/ KDE Icon Team]. The icons are licensed under the [http://www.fsf.org/licenses/lgpl.html GNU Lesser General Public License]. The HTML reports use Java Script code from [http://www.walterzorn.de/en/tooltip/tooltip_e.htm Walter Zorn]. The code is licensed under the [http://www.fsf.org/licenses/lgpl.html GNU Lesser General Public License]. TaskJuggler does require other software components to operate. These components include the Ruby runtime system, operating system libraries and other components installed as Ruby gems. We have used great care to ensure that all dependencies are compatible with the TaskJuggler license and are being used as required by those licenses. But use cases may vary and you should check those licenses yourself to ensure that you use those components in accordance with their licenses. === Features and Highlights === ==== Basic Properties ==== * Manages tasks, resources and accounts of your project * Powerful to-do list management * Detailed reference manual * Simple installation * Runs on all Linux, Unix, Windows, MacOS and several other operating systems * Full integration with Vim text editor ==== Advanced Scheduling ==== * Automatic resource leveling and tasks conflict resolution * Unlimited number of scenarios (baselines) of the same project for what-if analysis * Flexible working hours and leave management * Support for shift working * Multiple time zone support ==== Accounting ==== * Tasks may have initial costs, finishing costs * Resources may have usage based costs * Task and/or resource base cost models * Support for profit/loss analysis ==== Reporting ==== * Comprehensive and flexible reports so you can find the information you need when you need it * Powerful filtering functions to provide the right amount of detail to the right audience * Time and status sheet reporting infrastructure * Project tracking and status reporting with dashboard support ==== Scaling and Enterprise Features ==== * Projects can be combined to larger projects * Support for central resource allocation database * Manages roles and complex reporting lines * Powerful project description language with macro support * Scales well on multi-core or multi-CPU systems * Support for project management teams and revision control systems * Data export to Microsoft Project and Computer Associates Clarity ==== Web Publishing and Groupware Functions ==== * HTML reports for web publishing * CSV data export for exchange with popular office software * iCalendar export for data exchange with calendar and productivity applications * Built-in web server for dynamic and interactive reports * Server based time sheet system for status and actual work reporting === TaskJuggler on the Web === The official TaskJuggler web site can be found at [http://www.taskjuggler.org]. Since the developers are mostly busy project managers themselves, we have created a [http://www.taskjuggler.org/contact.html forum] for users to help each other. taskjuggler-3.5.0/manual/Getting_Started0000644000175000017500000000514212614413013017635 0ustar bernatbernat== Getting Started == === Basics === TaskJuggler uses one or more text files to describe a project. The main project should be placed in a file with the .tjp extension. This main project may include other files. Such included files must have file names with a ''''.tji'''' extension. The graphical user interface from the 2.x version has not been ported to TaskJuggler 3.x yet. So all work with TaskJuggler needs to be done in your favorite text editor and in a command shell. The commandline version of TaskJuggler works like a compiler. You provide the source files, it computes the contents and creates the output files. Let's say you have a project file called ''''AcSo.tjp''''. It contains the tasks of your project and their dependencies. To schedule the project and create report files you have to ask TaskJuggler to process it. tj3 AcSo.tjp TaskJuggler will try to schedule all tasks with the specified conditions and generate the reports that were requested with the [[taskreport]], [[resourcereport]] or other report properties in the input file. The report files will be generated in the current directory or relative to it. If you specify file names in a project file, you need to use the ''''/'''' as directory separator. This way, projects are portable across all operating systems. Do not use the ''''\'''' or '''':'''' that are used on some operating systems. === Structure of a TJP File === Each TaskJuggler project consists of one or more text files. There is always a main project file that may [[include.properties|include]] other files. The main file name should have a ''''.tjp'''' suffix, the included files must have a ''''.tji'''' suffix. Every project must start with a [[project|project header]]. The project header must be in the main project file. All other elements may be put into include files. The project header must then be followed by any number of project properties such as [[account| accounts]], [[resource|resources]], [[task|tasks]] and reports. Each project must have at least one task defined and should have at least one report. Properties don't have to be listed in a particular order, but may have interdependencies that require such an order. If you want to assign a resource to work on a task, this resource needs to be defined first. It is therefor recommended to define them in the following sequence. * [[macro|macros]] * [[flags]] * [[account|accounts]] * [[shift|shifts]] * [[vacation|vacations]] * [[resource|resources]] * [[task|tasks]] * [[accountreport|accountreports]] * [[resourcereport|resourcereports]] * [[taskreport|taskreports]] * [[textreport|textreports]] * [[export|exports]] taskjuggler-3.5.0/manual/TaskJuggler_2x_Migration0000644000175000017500000001214112614413013021407 0ustar bernatbernat=== TaskJuggler 2.x Migration === This section will cover changes between TaskJuggler 2.x and 3.x. * The syntax for macros has changed slightly. The terminating '''']'''' must be the last character before the line break. No spaces or comments are allowed here. Parameters of macro calls must always be enclosed by double quotes. In contrast to regular strings, single quotes are not allowed here. The parameter may not span multiple lines. * The ''''projection'''' attribute has been removed. The is now provided by [[trackingscenario]]. * The default working hours have been changed to 9:00 - 17:00. * IDs for properties such as tasks, resources and reports are now optional. If you don't need to reference a property, you can omit the ID. TaskJuggler will automatically assign an ID then. * Top-level accounts no longer need a ''''cost'''' or ''''revenue'''' attribute. Any two top level accounts can now be balanced against each other using the [[balance]] attribute in the report. * The ''''shift'''' attribute for tasks and resources has been renamed to ''''shifts'''' to allow support for multiple shifts. * The global ''''limits'''' attribute has been removed. Since both tasks and resources have a ''''limits'''' attribute, a global attribute was inconsistent as only resources inherited this attribute. Use a parent resource to emulate the old behaviour. * Shifts and limits for allocations have been deprecated. The concept was bogus and not compatible with bookings. The functionality is now provided by [[shifts.task|shifts]] and [[limits.task|limits]] on the task level. Limits for a task can be [[resources.limit|selectively applied]] to certain resources. * The ''''startbuffer'''' and ''''endbuffer'''' attributes have been deprecated. They have not been widely used and had no impact on scheduling. * The project attribute ''''allowredifinitions'''' has been dropped. It was an ugly workaround for a rare corner case. Using [[supplement]] is the clean way to do this. * Camel case names for function names in logical expressions have been deprecated. Function names need to be all lower case now. Some functions have been removed as all attributes can now be accessed by scenario.attribute_id notation. * The format for report has been changed considerably. The old format was not very flexible and had some design flaws. TaskJuggler 3.x now supports report nesting and composition. A report definition can be used to generated multiple output [[formats]]. The name of a report must now be specified without the file name extension. It will be automatically added depending on the output format. * The sorting modes have been extended to include the scenario. Also, the sorting direction is no longer mangled with the attribute name. What used to be ''''startup'''' is now ''''plan.start.up''''. See [[sorttasks]] or [[sortresources]] for details. * The attribute ''''properties'''' for ''''export'''' reports is no longer supported. The naming was inconsistent with TaskJuggler lingo and did not handle tasks and resources separately. It has been replaced with [[taskattributes]] and [[resourceattributes]]. * The ''''barlabels'''' attribute for reports is no longer needed. HTML reports have always empty Gantt-chart bars and the calendar reports always have values. * Support for reading and writing XML files is no longer available. The content was redundant with the TJP file format and it was not widely used. Keeping it in sync was too much of an effort to be worth it. There is nothing in the TJ3 design that would prevent this feature from being added again, but there are no plans for this right now. === Using TaskJuggler 2.x and TaskJuggler 3.x in parallel === While TaskJuggler 3.x has many new features over TaskJuggler 2.x like the much improved HTML reports, many 2.x users will miss the graphical user interface. To ease the migration, you can continue to use the TaskJuggler 2.x front-end while using TaskJuggler 3.x for report generation. This is possible because TaskJuggler 3.x can read-in the TaskJuggler 2.x export files. Export files are fully scheduled projects that include start and end dates for all tasks and bookings for resource allocations. To export all tasks and resources into a TJP file that can be read by TaskJuggler 3.x include the following export report definition in your TaskJuggler 2.x project plan. The necessary patches to support this only made it into TaskJuggler 2.x after the 2.4.3 release. So be sure to use a recent version from the Git repository to try this. export "FullProject.tjp" { taskattributes all resourceattributes all hideresource 0 } The resulting ''''FullProject.tjp'''' file is a valid self-contained project file that can be read with TaskJuggler 2.x or TaskJuggler 3.x. The file does not contain any report definitions. To generate reports with TaskJuggler 3.x you need to create an additional file that contains the TaskJugler 3.x report definitions. Let's assume the file is called ''''tj3reports.tji''''. Start TaskJuggler 3.x with the following command: tj3 FullProject.tjp tj3reports.tji Now you have generated TaskJuggler 3.x reports from you TaskJuggler 2.x project. taskjuggler-3.5.0/manual/Installation0000644000175000017500000003647512614413013017224 0ustar bernatbernat== Installation == TaskJuggler 3.x is written in [http://www.ruby-lang.org Ruby]. It should run on any platform that Ruby is available on. It uses the standard Ruby mechanism for distribution, a package format called [http://docs.rubygems.org RubyGems]. === Requirements === Ruby applications are platform independent. There is no need to compile anything. But TaskJuggler has a very small set of dependencies that you have to take care of first. Please make sure you have the minimum required version installed. ==== Supported Operating Systems ==== * '''Linux''': Linux is the primary development platform for TaskJuggler. Releases are tested on recent openSUSE versions. * '''Other Unix OSes''': Should work as well, but releases are not tested on these OSes. * '''Windows''': Windows7 and some older version of Windows should work. There is no maintainer for this platform, so all releases are not tested on this platform. * '''MacOSX''': Will probably work as well. Releases are not tested on this OS. Older MacOS versions will likely not work. If you are interested in becoming the maintainer for any of the currently unmaintained (and untested) OSes, please contact us via the developer mailing list. ==== Other required Software ==== * '''Ruby:''' TaskJuggler 3.x is written in Ruby. You need a Ruby runtime environment to run it. This can be downloaded from [http://www.ruby-lang.org/en/downloads/ here]. Most Linux distributions usually have Ruby already included. So does MacOS X Leopard. For Windows, there is a one-click installer available. The recommended Ruby version to make full use of TaskJuggler is Ruby 2.0. Ruby 1.9.1 contains some bugs that prevent the multi-core support to work. For users that are not interested in multi-core support, the web server, the time sheet infrastructure and daemon Ruby 1.8.7 is still ok to use. On Windows you need at least Ruby 1.9.2. If you want to use non-ASCII characters, Ruby 1.9.2 or later is required as well. You must have configured your system locale to be UTF-8 to work properly with non-ASCII characters. See below for instructions on how to use the latest and greates Ruby version in parallel with your distribution Ruby. * '''RubyGems:''' If it did not come with your OS or the Ruby installation, see [http://docs.rubygems.org here] how to get and install it. RubyGems is a cross-platform package manager. It will download and install all other required software packages automatically when you install TaskJuggler. These packages are called Ruby gems. Other versions of Ruby (Rubinius, JRuby, etc.) may work but have not been tested. === Installation Steps for Users === ==== The easy way ==== ===== System Wide Installation ===== TaskJuggler is a commandline tool. It does not (yet) have a graphical user interface. To use it, you need to know how to open a command or terminal window. In this manual, we refer to it as your shell. The following paragraphs describe the commands you need to type into your [http://en.wikipedia.org/wiki/Shell_(computing) shell]. On systems that already have Ruby and the gem package manager installed you can simply type the following command as root or admin user into your shell or command window: gem install taskjuggler This will download and install the latest version from the [http://rubygems.org/gems/taskjuggler RubyGems.org] site. ===== Installation into a local Directory ===== If you don't want to install TaskJuggler for all users on the system, you can also install it into your home or data directory. This does not require root or admin permissions. The following steps are describe the installation on a Linux system with the bash shell. You may have to use slightly different commands on a different operating system. Create a new directory ''''taskjuggler'''' in your $HOME directory for the installation to go into. mkdir taskjuggler Install the gem and all dependencies. gem install --install-dir taskjuggler taskjuggler-X.X.X.gem If you must use a proxy to access the Internet, you need to set a shell environment variable so ''''gem'''' can find and use it. The setting below needs to be adapted to your local environment. Check with your admin or IT department if needed. export HTTP_PROXY=http://%USER%:%PASSWORD%@%SERVER%:%PORT% Configure your ''''PATH'''' variable to find the taskjuggler programs. export PATH="${PATH}:${HOME}/taskjuggler/bin" Configure gem to find the installed files. export GEM_HOME=${HOME}/taskjuggler The last two settings should also be added to your .profile file to make them permanent. That's it. You now should be run TaskJuggler. tj3 --version ==== The manual way ==== If the easy way doesn't work for you, you need to download and install the packages manually. Download TaskJuggler gem file from the [http://rubygems.org/gems/taskjuggler RubyGems.org] site. A gem package is an operating system and architecture independent archive file for Ruby programs. You can install it on any system that has Ruby and RubyGems installed. Normally, you should be logged-in as root or administrator to run the following installation command. Replace the X.X.X with the actual version that you have downloaded. gem install pkg/taskjuggler-X.X.X.gem It will install all components of the Gem in the appropriate place. On user friendly Linux distributions, the start scripts will be installed in a standard directory like ''''/usr/bin''''. On Debian based distributions, the start scripts end up in a place like ''''/var/lib/gems/1.8/bin/'''' that is not listed in the ''''PATH'''' variable. You either have to create a symbolic link for each start script or add the directory to your PATH variable. If you use the standard [http://en.wikipedia.org/wiki/Bash bash shell], put the following line in your ''''${HOME}/.profile'''' file. PATH=${PATH}:/var/lib/gems/1.8/bin/ Windows and MacOS platforms may require similar steps. === Update from older TaskJuggler 3.x versions === Updates work just like the installation. gem update taskjuggler For downloaded or self-built packages use the following command: gem update pkg/taskjuggler-X.X.X.gem === Installing TaskJuggler from the Git Repository === The following description is for developers and users that want to learn more about TaskJuggler or want to make improvements. TaskJuggler is [http://en.wikipedia.org/wiki/Open_source Open Source] software and you are encouraged to read and modify the source code. Before you download the source code, make sure you have all the necessary dependencies installed. You should have Ruby 1.9.2 or later and you need to have the following gems installed gem install rake mail rspec term-ansicolor rcov rcov is optional, but you must have the other gems and their dependencies installed. To get the source code, the recommended way it to check out the latest code from the developer repository. To do this, you need to have [http://www.kernel.org/pub/software/scm/git/docs/ git] installed. Then checkout the source code with the following command git clone git@github.com:taskjuggler/TaskJuggler.git Make sure, you have removed all previously installed instances of TaskJuggler from your system before doing so. It is a common mistake to have an old version of the TaskJuggler installed and then use parts of the old and new version together. If your Ruby installation does not come with the [http://rake.rubyforge.org Rake] build tool, you need to install it now. If you are interested in a code coverage analysis, you need to also install the [http://eigenclass.org/hiki.rb?rcov rcov] code coverage analysis tool. This tool is not needed for most developers. You can safely ignore the warning during rake builds if you don't have it installed. The following command will create a gem package from the source code. cd taskjuggler3; rake gem If you plan to modify the TaskJuggler files, creating and installing the gem file for every test run is not very comfortable. To run tj3 from source put the following code in your ''''.profile'''' file. This is for users of the bash shell. Adapt it accordingly if you use another shell. # Make sure the shell finds the TaskJuggler programs export PATH=${PATH}:${TASKJUGGLER_DIR}/bin === Quickly switching between various TaskJuggler 3.x versions === One of the benefits of using TaskJuggler from the Git repository is the ability to get the latest bug fixes. If a bug was reported, it is usually fixed fairly quickly, but it can take several weeks before the next official release happens. The following commands must all be executed from within the checked-out Git directory. git pull gets you the latest changes. We usually try to keep the head branch stable. Using it should not be much more risky than using a regular release. Nevertheless, problems can occur and a fixed version might take a few days. git checkout -f XXXXXXXX will switch your current working copy to the version with commit ID XXXXXXXX. Alternatively, you can also use tag names. git checkout -f release-0.0.10 This will switch to the released version 0.0.10. git tag provides you with a list of all tags. TaskJuggler 3.x is written in Ruby. There is no make or build process needed. Every code change is effective immediately. The tutorial, the manual and some other parts do require a build step. rake release will do it all and even create installable gem files again. === Installing a newer Ruby version === New Ruby versions are released usually about once or twice a year. Unfortunately, it takes some time before Linux distributions pick up the new release. Depending on your distribution, this can take anything from a few weeks to several years. Many distributions still have not yet made the switch to Ruby 2.0. The core part of TaskJuggler can be used with Ruby 1.8.9, but it is at least 3 times slower. Therefor it is recommended, that you install the latest stable release of Ruby to use TaskJuggler. This can easily and safely being done in parallel to your distribution Ruby. Both versions can be used in parallel without interfering each other. This section only covers Linux. For other operating system, please search the web for instructions. If you want to contribute the description for another OS, please see [[How_To_Contribute]]. First, you need to download the source code of the latest stable release from [http://www.ruby-lang.org/en/downloads/ www.ruby-lang.org]. The source code is distributed as zipped tarfile. You can extract it like this. Change the file name to the actual version you have downloaded. tar -Zxvf ruby-X.X.X-*.tar.gz This will create a directory with the same name as the archive, but without the ''''.tar.gz'''' extension. Before you continue, make sure you have all the necessary packages installed to compile ruby. That would be everything you need to compile C programs. That includes gcc, make, zlib and libyaml. If something is missing, you will run into problems in the next 2 steps. It's sometimes not obvious which package to install to fix the issue. Now change into this directory and configure the source code for your specific OS and compile it. We configure Ruby to append ''''19'''' to all executable names. This way, you can easily choose if you want to run the old or the new Ruby. ''''ruby'''' runs your distribution Ruby, ''''ruby19'''' runs your new ruby. cd ruby-X.X.X-* ./configure --program-suffix=19 make If all goes well, you can install it now. This requires root permission, so you need to enter the root password. All executables will be installed into ''''/usr/local/bin''''. sudo make install The TaskJuggler front-end scripts always use the ''''ruby'''' interpreter that's the first in the PATH. You need to set a link in your local ''''bin'''' directory to point to your ''''ruby19'''' executable as ''''ruby''''. ln -s /usr/local/bin/ruby19 ${HOME}/bin/ruby Make sure your ''''${HOME}/bin'''' directory is the first directory in the ''''PATH''''. This step varies a lot depending on the login shell. E. g. for ''''bash'''' put the following at the end in your ''''.profile'''' shell config file. Please make sure that ''''/usr/local/bin'''' is also in the PATH so that the ruby executables (all having a ''''19'''' suffix) will be found as well. export PATH=${HOME}/bin:${PATH} Log out and back in again. Now which ruby should show return the path to the link to your ''''${HOME}/bin/ruby''''. You now have the latest Ruby installed and are ready to use TaskJuggler. As a final step, you need to install the ''''mail'''' and ''''term-ansicolor'''' gems. sudo gem19 install mail term-ansicolor If you don't want to use TaskJuggler from the git repository, you can install the TaskJuggler gem as well. sudo gem19 install taskjuggler === Installing the Vim Support === TaskJuggler can be used with any text editor that supports UTF-8 text file editing. If you don't have a preference yet, we recommend to try the [http://www.vim.org Vim] text editor. It's a very powerful editor and it has been customized for better integration with TaskJuggler. This section describes, how to activate and use the Vim integration. Vim is provided by pretty much any Linux distribution and also works well on MacOX and Windows. See the web page for how to install it if you don't have it yet. This section describes the integration on Linux. Please see the [[How_To_Contribute]] section if you want to contribute the description for another OS. If you have never customized Vim, you need to create a few directories first. cd ${HOME} mkdir .vim mkdir .vim/syntax Then copy the syntax file ''''tjp.vim'''' into the vim syntax directory. The following command works if you have installed TaskJuggler as a gem with the system provided Ruby. For other cases, you may have to modify it accordingly. cp `gem contents taskjuggler | fgrep tjp.vim` .vim/syntax Now we have to make sure, Vim detects the file. Edit the ''''.vim/filetype.vim'''' file to contain the following section. augroup filetypedetect au BufNewFile,BufRead *.tjp,*.tji setf tjp augroup END And edit the ''''.vim/syntax.vim'''' file to contain the following line. au! Syntax tjp so ~/.vim/syntax/tjp.vim When you now open a ''''.tjp'''' or ''''.tji'''' file in Vim, you should have the following features available: * Syntax highlighting. TJP keywords should be colored in different colors. * Syntax folding. The optional parts of properties within the curly braces can be collapsed. For this to work, the opening brace needs to be on the same line as the property keyword. The closing brace must be the first non-blank character of the last line of the block. See the '''':help fold'''' Vim help command for details how to open and close folds. * Tag navigation. If you include a [[tagfile]] report in your project, Vim will know all property IDs and can jump to them. If you have a task with the ID ''''foo.bar'''', the command '''':ta foo.bar'''' will put the cursor right where task ''''foo.bar'''' was declared. * ID completion. If you include a [[tagfile]] report in your project, Vim can tell you the full hierarchical ID of a property. Just move the cursor to the first line of the property definition and press ''''Ctrl-]''''. * Run tj3 from within vim. Just type '''':make your_project.tjp'''' to start the scheduling process. In case of errors or warnings, you will be able to navigate the errors with '''':cn'''' and '''':cp''''. * Move the cursor over any TaskJuggler syntax keyword and press ''''shift-k'''' to get the manual page for this keyword. taskjuggler-3.5.0/manual/Rich_Text_Attributes0000644000175000017500000002170312614413013020646 0ustar bernatbernat=== Rich Text Attributes === TaskJuggler supports Rich Text data for some STRING attributes that are marked accordingly in the syntax reference. Rich Text means, that you can use certain markup symbols to structure the text into sections, include headlines, bullet lists and the like. The following sections describe the supported markup elements and how to use them. The markup syntax is mostly compatible to the syntax used by the popular [http://www.mediawiki.org MediaWiki]. ==== Block Markups ==== All block markups are delimited by an empty line. The markup must always start at the beginning of the first line of the block. Block markups cannot be nested. The simplest form of a block is a paragraph. It's a block of text that is separated by empty lines from other blocks. There is no markup needed to start a text block. Headlines can be inserted by using ''''='''' characters to start a line. There are 3 level of headlines. == Headline Level 1 == === Headline Level 2 === ==== Headline Level 3 ==== A line that starts with four dashes creates a horizontal line. ---- Items of a bullet list start with a star. The number of stars determines the bullet list level of the item. Three levels are supported. Bullet items may span multiple lines but cannot contain paragraphs. * Bullet 1 ** Bullet 2 *** Bullet 3 Enumerated lists are formed by using a ''''#'''' instead of ''''*''''. # Enumeration Level 1 ## Enumeration Level 2 ### Enumeration Level 3 Sections of lines that start with a space character are interpreted as pre-formatted text. The formatting will be preserved by using a fixed-width font and by not interpreting any markup characters within the text. Preformatted text start with a single space at the start of each line. ==== In-Line Markups ==== In-line markups may occur within a text block. They don't have to start at the start of the line. This is an ''italic'' word. This is a '''bold''' word. This is a ''''monospaced'''' word. This is a '''''italic and bold''''' word. The monospace format is not part of the original MediaWiki markup, but we found it useful to have for this manual. Text can be colored when enclosed in ''''fcol'''' tags. This is a green word. The following colors are supported: black, maroon, green, olive, navy, purple, teal, silver, gray, red, lime, yellow, blue, fuchsia, aqua and white. Alternatively, a hash sign followed by a 3 or 6 digit hexadecimal number can be used as well. The hexadecimal number specifies the values for the red, green and blue component of the color (i. e., #FFF for white). The above listed in-line markups cannot be nested. Links to external documents are possible as well. In the first form, the URL will appear in the readable text as well. In the second form, the text after the URL will be visible but the link will be available if the output format supports it. [http://www.taskjuggler.org] [http://www.taskjuggler.org The TaskJuggler Web Site] For local references, the second form is available as well. In this form, ''''.html'''' is appended to the first word in the reference to create the URL. [[item]] [[item|An item]] Images can be added with a similar syntax. [[File:image.jpg]] [[File:image.jpg|alt=An image]] This first version will be replaced with the file ''''image.jpg'''' when the output format supports this. Otherwise a blank space will be inserted. The second version inserts the text ''''An image'''' if the output format does not support images. The following image types are supported and detected by their file name extensions: ''''.jpg'''', ''''.gif'''', ''''.png'''' and ''''.svg''''. The vertical positioning of the embedded file can be controlled with additional attributes. [[File:image.svg|text-bottom]] The following attributes are supported: ''''top, middle, bottom, baseline, sub, super, text-top, text-bottom''''. In some situations, it is desirable to not interpret certain markup sequences and reproduce the text verbatim. Such text must be enclosed in nowiki tags. This is not '''bold''' text. You can also insert raw HTML code by enclosing it in '''...''' tags. For all other output formats, this content will be ignored. There is also no error checking if the code is valid! Use this feature very carefully. ==== Block and Inline Generators ==== Block and inline generators are a very powerful extension that allow you to insert arbitrarily complex content. Block generators create a text block whereas inline generators generate an element that fits inside a text paragraph. Block generators use the following syntax: <[generator_name parameter1="value1" ... ]> Inline generators have a very similar syntax: <-generator_name parameter1="value1" ... -> Each generator is identified by a name. See the following list for supported generators and their functionality. Generators can have one or more optional parameters. Some parameters are mandatory, other are optional. The value of a parameter must be enclosed in single or double quotes. Since your rich text content must already be enclosed by double or single quotes, make sure you don't use the same quoting marks for the parameter value. Alternatively you can put a backslash in front of the quote mark to escape it. ---- '''Block Generator''' ''''navigator'''' Parameters: * ''''id'''' : ID of a defined [[navigator]] The navigator generator inserts the referenced navigator. ---- '''Block Generator''' ''''report'''' Paramters: * ''''id'''' : ID of a defined report The report generator inserts the referenced report as a new block of this text. The referenced report inherits some context such as the report period and the property set from the referencing report. ---- '''Inline Generator''' ''''reportlink'''' Paramters: * ''''id'''' : ID of a defined report * ''''attributes'''': A set of attributes that override the original attributes of the referenced report. All report attributes are supported. Since the value of attributes already must be enclosed by single or double quotes, all single or double quotes contained in the string must be escaped with backslashes. This feature enables reports with content that is customized based on where they have been referenced from. It requires the reports to be dynamically generated and is only available when used with the ''''tj3d'''' web server. The ''''tj3'''' application will ignore the attributes setting. taskreport "All" { formats html columns name { celltext 1 -8<- <-query attribute="name"-> <-reportlink id="taskRep" attributes="hidetask plan.id != \"<-id->\""-> ->8- }, start, end } taskreport taskRep "Task" { formats html } The report link generator inserts a link to the referenced report. ---- '''Inline Generator''' ''''query'''' Paramters: * ''''family'''' : Specifies whether a ''''task'''' or a ''''resource'''' should be queried. * ''''property'''' : The ID of the task or resource to be queried. If no property is specified and no property is provided by the scope, the query will return a global project attribute. When the query is used with a scope that already provides a property, a sequence of ! can be used to move the property to the parent property. That way you can access attributes of any of the parents. * ''''scopeproperty'''' : The ID of the scope property. If the property is a task this must be a resource ID and vice versa. * ''''attribute'''' : The ID of the attribute which value should be returned by the query. If a property ID is provided, this must be one of the names that can be used as [[columnid]] values. Without a property, global attributes of the project can be requested. The following attributes are supported: ''''copyright'''', ''''currency'''', ''''end'''', ''''name'''', ''''now'''', ''''projectid'''', ''''start'''' and ''''version''''. * ''''scenario'''' : The ID of a scenario. This must be provided whenever the requested attribute is scenario specific. * ''''start'''' : The start date of the report period of the current report. * ''''end'''' : The end date of the report period of the current report. * ''''loadunit'''' : The [[loadunit]] that should be used in case the requested attribute is an effort or duration value. * ''''timeformat'''' : The [[timeformat]] used to format date attributes. * ''''numberformat'''' : The [[numberformat]] used to format arithmetic attributes. * ''''currencyformat'''' : The [[currencyformat]] used to format currency values. The query generator inserts any requested value from the project, a task or a resource. Queries are context aware. Depending on the context where the query is used, certain or all of the above parameters have already predefined values. When used in the header section of a report, the context does not provide a property or scope property. Start and end dates as well the formatting options are taken from the report context. But when used e. g. in [[celltext.column]] the cell provides, that property and the attribute and possibly even the scope property. taskjuggler-3.5.0/manual/How_To_Contribute0000644000175000017500000001604612614413013020150 0ustar bernatbernat=== How to Contribute === ==== Why contribute? ==== TaskJuggler is an Open Source Project. It was developed by volunteers mostly in their spare time. Made available under the GNU General Public license and similar licenses, TaskJuggler can be shared and used free of charge by anybody who respects the license conditions. Does that mean you can use it without worrying about anything? Clearly not! Though users have no legal obligation to contribute, you should feel a moral obligation to support Open Source in whatever way you can. This can range from helping out other users with their first Linux installation to actively contributing to the TaskJuggler Project, not just as a programmer. The following section describes, how you can contribute to any of the components that are part of the TaskJuggler software releases. ==== Preparing a contribution ==== All TaskJuggler development is coordinated using the [https://github.com/taskjuggler/TaskJuggler/issues github source code management platform]. All changes must be submitted using Git github so that we can track the authorship of each submission. There is [http://help.github.com/ excellent documentation] available on how to use github. Make sure you have followed the steps described in the [Installation.html#Installation_Steps_for_Developers Installation Steps for Developers] chapter. If you have never used Git before, you need to configure it first. You need to set your name and email address. This information will be present in all patches that you submit. git config --global user.name "Your Name" git config --global user.email "firstname.lastname@domain.org" Do not use the development snapshots or send your patches as plain diff files. We'd like to ensure that we know who contributed to TaskJuggler. Therefor we are only accepting signed-off git patches with full user names and valid email addresses. Next you need to find the files where you want to make your modifications. Sometimes files will be generated from other files. Do not change those generated files. Your changes will be overwritten the next time you call the make utility. ==== Creating a Patch ==== When you are done with your changes, it's a good idea to test them. In the taskjuggler directory run the following commands. cd taskjuggler3 rake test rake spec rake manual rake gem If there are no errors, you can check or test the result. If everything works fine, you can lock at your changes again. git diff The git-diff utility performs a line-by-line comparison of the files against the latest version in you local repository. Try to only make changes that have an impact on the generated files. Do not change indentation or line wrapping of paragraphs unless absolutely necessary. These kinds of changes increase the size of diff files and make it much harder to evaluate the patches. When making changes to the program code, please use exactly the same coding style as the rest of the code. If your contribution is large enough to justify a copyright claim, please indicate what copyright you claim in the patch. For modifications to existing files, we will assume that your contribution falls under the same license as the modified file. All new files will need to contain a license declaration, preferably GPL version 2. In any case, the license must be [http://www.opensource.org/licenses an OSI accepted license] and be compatible with the GPL version 2 used by the rest of the project. Review all changes carefully. In case you have created new source files, you need to register them with your repository. git add FILENAME If you think you are done, you can commit your changes to your local repository. git commit -a -s The -s parameter is very important. We will only accept signed-off patches. By signing off on your patches you confirm that you wrote the code and have the right to pass it on as a patch. See [http://gerrit.googlecode.com/svn/documentation/2.0/user-signedoffby.html this document] for more information! Please include a meaningful commit message. The first line (header line) should be prefixed by ''''Fix: '''' for bug fixes or ''''New: '''' for new features. This is used to automatically generate the change log from one release to another. So a bug that has been introduced after the last release and is fixed before the next release does not need to be included in the changelog. For those cases, don't use any prefix. After the header line leave a blank line and include one or more paragraphs with more detailed information about the patch. This information will also be included in the the change log if the header line has a prefix. If you fix a bug that was reported by somebody else, please also include a reported-by line: Reported-by: whoever-reported-the-bug Then push it into your forked github repository. git push The final step is to send us a [http://help.github.com/pull-requests/ pull request] for your changes. ==== Contributing to the User Manual ==== The user manual is currently a rough port of the 2.x manual. It contains many inaccuracies and does not provide much more than a tutorial and a syntax reference. Any help to turn this into a real user manual is greatly appreciated. The manual is composed from 3 different sources. # The sources for normal pages are in MediaWiki format and can be found in the ''''manual'''' directory of the source distribution. # The information in the syntax reference is extracted from the TJP parser source code. It can be found in the file ''''lib/TjpSyntaxRules.rb''''. You can ignore all but the ''''doc(...)'''', ''''arg(...)'''' and ''''example(...)'''' sections. # The TJP syntax examples are in the ''''test/TestSuite/Syntax/Correct'''' directory. The following command build the HTML files for the manual in the ''''manual/html'''' directory. rake manual ==== Contributing to the Test Suite ==== The test suite can be found in the ''''test'''' and ''''spec'''' directories. It contains unit and system tests but is very rudimentary at the moment. Adding more system tests to the test/CSV-Report directory is probably the best place to start. Originally, TaskJuggler used classic Ruby unit tests, but a migration to [http://rspec.info/|RSpec-2] is in the works now. New tests should be written as RSpec tests unless they require infrastructure only available in the ''''test'''' directory. ==== Contributing to the Ruby code ==== For the first stable TaskJuggler release we have most 2.x features supported. The few things that break backwards compatibility are documented in the [[TaskJuggler_2x_Migration]] section. In general, patches are very welcome. Please follow the coding style and naming conventions used in the existing code. Larger changes should be preceded by a discussion in the [http://www.taskjuggler.org/contact.html TaskJuggler Developer Forum]. ==== Some final words to Contributors ==== We do welcome all contributions, but please understand that we reserve the right to reject any contribution that does not follow the above guidelines or otherwise conflicts with the goals of the TaskJuggler team. It is a good idea to contact the team prior to making any larger efforts. taskjuggler-3.5.0/manual/Software0000644000175000017500000002117612614413013016345 0ustar bernatbernat== The TaskJuggler Software == After the installation of a software package the first questions that most users have is ''"How do I run it?"''. Many users expect to find an icon on their desktop or an entry in the start menu hierarchy. Don't bother looking for them, you won't find any for TaskJuggler. As we have mentioned before, TaskJuggler is a command line program. All the components are started from a shell by typing in the command name. This chapter describes the most important TaskJuggler commands and how to use them. If you haven't used command line programs before, don't worry - you will quickly get used to it. === ''''tj3'''' === ''''tj3'''' is the main program that you will probably use the most. It reads in your project files and schedules them. In case there are no errors, it will also generate all the reports that you have defined in your project files. It requires at least one parameter, the name of your main project file. In case your main project file is called ''''tutorial.tjp'''' you can process it by typing tj3 tutorial.tjp You will see an output similar to the one below. > tj3 tutorial.tjp TaskJuggler v3.0.0 - A Project Management Software Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 by Chris Schlaeger This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. Reading file tutorial.tjp [ Done ] Preparing scenario Plan [ Done ] Scheduling scenario Plan [ Done ] Checking scenario Plan [ Done ] Preparing scenario Delayed [ Done ] Scheduling scenario Delayed [ Done ] Checking scenario Delayed [ Done ] Report Overview [ Done ] Report Status [ Done ] Report Development [ Done ] Report Deliveries [ Done ] Report ContactList [ Done ] Report ResourceGraph [ Done ] ''''tj3'''' supports a number of runtime options that control the behavior. Please use the ''''--help'''' option to get more details. tj3 --help === ''''tj3man'''' === ''''tj3man'''' provides you with a quick access to the syntax reference. If you run it without further parameters it will print a list of all syntax keywords. In some cases, keywords can be used in different contexts with different meanings. These keywords are listed multiple times with the context identifier appended separated by a dot. E. g. the ''''shift'''' keyword can be used in the global context, in the [[resource]], [[task]] and [[timesheet]] context. It will be listed as shift shift.resource shift.task shift.timesheet To get more information about a specific keyword you need to provide it as parameter to ''''tj3man''''. tj3man shift.task The same information that is available in your shell is also available in your web browser. tj3man --html The latter defaults to using the [http://www.mozilla.com/en-US/firefox/new/ Mozilla Firefox] web browser . Please see tj3man --help how to use a different browser. If you omit the keyword, you will get this user manual (browser). === ''''tj3d'''' === ''''tj3d'''' is the TaskJuggler daemon. It is a program that runs in the background, disconnected from your shell to provide certain services. It can generate reports on demand and processes incoming time sheets or status reports. Depending on the size of your project the scheduling time can take several minutes or more. Since all operations need to be done on the data of a scheduled project, it makes sense to have this data readily available. This is the job of the TaskJuggler server or daemon in Linux lingo. The program is called ''''tj3d''''. When started, it automatically disconnects from the terminal and runs in the background. All interactions with the server are done via the TCP/IP protocol. For security reasons, only connections from the same machine (localhost) are accepted. To get access all clients must provide an authentication key. A TaskJuggler server can serve any number of projects. Once a project has been loaded successfully, clients can retrieve the data in form of reports. Projects are identified by their project ID. If a newly added project has the same ID such as an already loaded project, the new project will replace the old project once it was scheduled successfully. Before you start the server, you need to provide a configuration file with some basic settings. All taskjuggler components can use the same TaskJuggler configuration file. The format is a simple plain text format that follows the [http://www.yaml.org/ YAML specification]. The file should be called ''''.taskjuglerrc'''' or ''''taskjuggler.rc''''. The settings are structured by sections. Section names always start with an underscore. _global: authKey: topsecret _log: logLevel: 3 outputLevel: 3 This file sets the authentication key for all TaskJuggler components. You must replace ''topsecret'' with your own random character sequence. For the purpose of this documentation we assume you have a local user called ''taskjuggler'' and your project data in ''''/home/taskjuggler/project/prj''''. Your TaskJuggler configuration should then be put into ''''/home/taskjuggler/project/prj/.taskjugglerrc''''. The log section controls the content of the log file. Since the daemon does not have a terminal attached to it, all messages are stored in a file called ''''tj3d.log''''. For debugging purposes, you can use the ''''-d'''' option to prevent the daemon from disconnecting from the terminal. In this case the ''''outputLevel'''' configuration option controls the amount of details to be printed out. * 0: No output * 1: Only fatal errors * 2: Fatal and normal errors * 3: Like 2, but additionally with information messages * 4: Like 3, but additionally with debug messages The configuration file will be searched in the current directory, the current user's home directory or ''''/etc''''. You can also explicitly tell the server where to find the configuration file with the ''''-c'''' option. See tj3d --help for details. So far, the daemon has not received any kind of security review. We strongly advise you to only use the daemon in a trusted environment with only trusted users! === ''''tj3client'''' === To control the TaskJuggler server, you need to use the TaskJuggler client. You can use the client to add or remove projects from the server, inquire the status of loaded projects. It can also be used to show the available reports for each project and to generate report or check time or status sheets. The client must provide the correct authentication key to the server. You need to ensure that it can find the proper configuration file with the authentication key. tj3client --help will provide a full list of supported commands. To load a project simply type tj3client add yourproject.tjp In case there were no errors tj3client status should now list your project. tj3client list-reports shows a list of available reports for the project with the provided ID. To generate a report, you can type tj3client report A server that is running can be terminated with the following command. tj3client terminate === ''''tj3webd'''' === This is a web server to serve the HTML reports of a project to any web browser. The HTML reports are generated on the fly when accessed. ''''tj3webd'''' requires that ''''tj3d'''' is already running on the same machine. By default, the web server is listening on port 8080. This can be changed in the ''''_global'''' section of the config file. _global: authKey: topsecret webServerPort: 8080 To access the HTML reports point your web browser to ''''http://localhost:8080/taskjuggler''''. This assumes that the server is running on your local machine. You will then see a list of all loaded projects. Click on the project name to get a list of all reports for this project. ''WARNING: Please be aware that the web server when you have started it can be accessed by anybody on your local machine and by anybody that can reach your machine over the network! It will serve all reports of all projects that are hosted by the TaskJuggler daemon.'' taskjuggler-3.5.0/manual/The_TaskJuggler_Syntax0000644000175000017500000001206312614413013021136 0ustar bernatbernat== The TaskJuggler Syntax == === Understanding the Syntax Reference === This manual provides a comprehensive reference of the TaskJuggler syntax. It is automatically generated from the same data that the parser uses to read your project files. This ensures that the software and the manual always match. The syntax reference is organized based on the keywords of the TaskJuggler syntax. There is an entry for every keyword. In some cases, a keyword can appear in different contexts with potentially different meanings. In this case, the context is provided in brackets after the keyword for at least one of them. That way, every keyword is uniquely referenced. The syntax for each keyword is described in the syntax section of the table that is provided for each keyword. The syntax always starts with the keyword. Keywords are then followed by arguments. In some cases, an argument can be automatically generated. In such cases, the argument is enclosed in square brackets. keyword [] Arguments can be variable or picked from a predefined list of options. Variable arguments are listed with their names enclosed in angle brackets. keyword List of predefined options are enclosed in brackets, the options separated by a vertical bar. keyword ( foo | bar | foobar ) Some keywords take one or more arguments. These are known as list attributes. The arguments are comma separated. The tree dots in the syntax description mean that the sequence before the dots can be repeated if needed. Inheritable list attributes will append the new list values to the inherited list. Use can use the [[purge]] attribute to clear the list before assigning new values to the list attribute. keyword arg1 [, arg2 ... ] Variable arguments are further described in the ''Arguments'' section of the keyword syntax table. The name is listed immediately followed by the type of the variable argument. The supported types and their meaning is described in the following sections. ==== ABSOLUTE_ID ==== An absolute identifier is composed of identifiers that are concatenated by dots, e. g. ''''foo.bar''''. It is used to reference a TaskJuggler property that lives in a hierarchical name space. Accounts, Tasks, Reports are examples for such hierarchical name spaces. To reference the sub-task ''''bar'''' of task ''''foo'''' the absolute ID ''''foo.bar'''' is used. ==== ID ==== An identifier is composed of the letters ''''a'''' to ''''z'''', ''''A'''' to ''''Z'''', the underscore and the digits ''''0'''' to ''''9''''. There are no limits for the number of characters, but it may not begin with a digit. ==== INTEGER ==== An integer is any natural number, e. g. ''''0'''', ''''1'''', ''''2'''' and so on. ==== STRING ==== Strings are character sequences that are enclosed by special character marks. There are three different marks supported. For short strings that fit on one lines, you can either use single or double quotes. 'This is a single quoted string.' "This is a double quoted string." Single quoted strings may contain double quotes and vice versa. Alternatively, you can prefix the quote mark with a backslash to use it within a string. 'It\'s a string with a quote included.' If you want to use a backslash right before the included quote you need to escape the backslash with another backslash. Backslashes that aren't followed by a quote mark don't need to be escaped. 'A backslash \\\' followed by a quote.' For multi-line strings or strings with many included quotes cut mark strings are recommended. A cut-mark-string starts with ''''-8<-'''' (scissor on a dotted line) and ends with ''''->8-''''(scissors looking the other way). The start mark must be immediately followed by a line break. The indentation of the first line after the opening scissors must be repeated for all following lines of the string and is not included in the resulting strings. The terminating cut mark must only be preceded by white spaces in that line. When considering the indentation, tabs are not identical with some number of spaces. Each indented line must be prefixed by the exact same combination of tabs and spaces. -8<- This is a multi-line string. ->8- === Predefined Macros === TaskJuggler supports a few predefined macros. These are available after the project header. They values correspond to the values provided in the project header. * ''''projectstart'''' The start date of the project. * ''''projectend'''' The end date of the project. * ''''now'''' The current date. If the user does not provide a date with the [[now]] keyword, the moment of the processing of the file will be inserted. Keep in mind that this will change every time you process your project. The current setting of [[timeformat]] has no impact on the expansion of the macro. * ''''today'''' Identical to ''''now'''' but formatted according to the [[timeformat]] setting of the current context. === Environment Variable Expansions === By using the $(VAR) syntax, you can insert the value of the environment variable name VAR. The name of the variable must consists only of uppercase ASCII letters, underscore or decimal digits. taskjuggler-3.5.0/manual/Day_To_Day_Juggling0000644000175000017500000010774612614413013020365 0ustar bernatbernat== Day To Day Juggling == === Working with multiple scenarios === To analyze the impact that a small variation can have on a project, TaskJuggler supports an unlimited amount of scenarios. Each additional scenario is a slight derivation of the it's parent. The task tree structure needs to be the same for all scenarios, but most attributes can vary from one scenario to another. Several report types support comparative listing of multiple [[scenarios]]. By default, TaskJuggler knows about one scenario, called ''''plan''''. The name of this scenario can be changed just like you can add more scenarios in the [[project]] section of your project files. <[example file="Scenario" tag="header"]> This header section defines 3 different scenarios. ''''plan'''' is the top-level scenario. It has two derived scenarios, ''''actual'''' and ''''test''''. These two scenarios are identical to the ''''plan'''' scenario except for those attributes that are changed for these scenarios. Normally, all scenarios are scheduled on each ''''tj3'''' run. To temporarily disable the scheduling of a scenario, you can set the [[active]] attribute to ''''no''''. <[example file="Scenario" tag="task"]> If you prefix an attribute with the scenario ID followed immediately by a colon, you can specify a value for a particular attribute. Keep in mind that setting an attribute also sets the same value for ''all derived scenarios'' of this scenario as well! If you would specify ''''actual:start'''' first and then ''''plan:start'''', the latter would overwrite the first value again since actual is a derived scenario of plan. The syntax reference lists for each attribute whether it is scenario specific or not. === Important and fall-back Tasks === By default, the scheduler tries to guess the right priority of tasks. The higher the priority, the more likely it will get the requested resources. To override this mechanism, the [[priority]] attribute can be used. <[example file="Priority" tag="project"]> In the above example, the regular project work needs to be frequently interrupted by the ''Customer Support'' task. It's only 2 hours a day, but it's pretty important that this is done. Since the task has a higher priority than the regular work, the scheduler will try to ensure that a maximum of 2 hours per day is spent on support. There is no guarantee, that the task will always get the resource for 2 hours each day, but it's pretty likely in this setup. There is only one task that is more important, the ''Attend Conference'' task. It has fixed dates and we want to make sure ''Tux'' can attend. So we use ''''priority 1000'''', the highest possible priority. There should be only one such task. If not, you need to ensure that the top priority tasks don't compete for the same resource in the same time frame. In contrast to the support and conference task, the ''Maintenance work'' task is a fall-back task. It has a lower priority than the regular work. Tux only gets assigned to it when there is no other work. We have limited the regular work to 25 hours per week. Since we spend up to 10 hours per week on support, there should be a remainder of 5 hours per week for the maintenance task. Again, no guarantees given. If you want to ensure that a certain minimum or maximum effort is spent on a task, you can use the [[warn]] attribute. This will not affect the decisions of the scheduler, but at least it will trigger a warning if your criteria are not met. === Tracking the Project === Once the initial plan has been made and the project has started, TaskJuggler can be turned from a planning tool into a tracking tool. You don't have to change a lot to do this. After all, as the initial plan is almost always just a first guess, you need to continue planning your project as new details become evident. As the work progresses, you continuously review the state of the project and update the plan accordingly. A weekly review and update cycle seems to be pretty common for most projects. Usually the plan for the past week and the reality are mostly aligned. The future parts of the project often are more affected by necessary changes. While it is generally accepted to invest some amount of time in project planning, it is very common that once the project has been started, project managers tend to avoid a proper tracking of the project. Our bet is that the vast majority of project plans are only made to get management or investor approval. After the approval phase, many project managers only work with their project plan again when the project is getting out of control and they are desperate for any help they can get. Of course, there are projects that are done using strict project management techniques that require detailed status tracking. Both extremes probably have their fans and TaskJuggler offers good support for both extremes as well as various techniques in between. === Recording Progress === As mentioned previously, your initial project plan is only a first estimate of how the project will progress. During the course of the project you will have to make changes to the plan as new information needs to be taken into account and you probably want to track the progress of the project in a formalized form. TaskJuggler will support you during this phase of the project as well, but it needs your help. You have to provide the additional information in the project file. In return you get current status reports and an updated project plan based on the current status of the project. ==== Using completion values ==== The most simple form of capturing the current status of the project is to use the complete attribute. task impl "Implementation" { depends !spec effort 4w allocate dev1, dev2 complete 50 } This tells TaskJuggler that 50% of the task's effort has been completed by the [[now|current date]]. Tasks that have no completion specification will be assumed to be on track and TaskJuggler calculates the expected completion degree based on the current date. Completion specifications only need to be supplied for tasks that are either ahead of schedule or behind schedule. Please be aware that the completion degree does not affect the scheduling and resource allocation. It is only for reporting purposes. It also does not tell TaskJuggler which resource actually worked on the tasks, nor does it update the total or remaining effort. ==== Using bookings ==== When TaskJuggler schedules your plan, it can tell you who should work when on what. Now, that's the plan. But reality might be different. To tell TaskJuggler what really happened, you can use [[booking.task|booking statements]]. When the past is exactly described by providing booking statements, you can enable [[projection|projection mode]]. Entering all the bookings for each resource and task may sound like a daunting task at first. If you do it manually, it certainly is. Fortunately, TaskJuggler can generate them for you by using either the ''''--freeze'''' option of ''''tj3'''' or by generating a manual [[export|export report]]. Before we discuss this in more detail, we need to make sure that the plan is up-to-date. === Tracking status and actuals === Creating a good project plan is one thing. Executing it is a whole new story. Usually, the first plan is never fully correct and the only way to make sure that you are making progress according to plan is to regularly get status updates from all the project contributors. These status updates should be provided by all project contributors on a regular basis, usually once a week. The gathered information should tell project managers who really worked how much on what tasks and how much work the contributors believe is really left now. There are two categories of tasks in a project that need to be treated slightly differently. A task can either be effort based or duration based. In the former case, the contributors must tell how much effort is left. For duration based task, this doesn't make much sense. For these task, the expected end date should be reported. In addition to those numbers, managers in the reporting chain usually want to have a textual status that describes what happened and what kind of issues were encountered. Usually, these textual status reports are combined with alert levels like green, yellow and red. Green means everything is progressing according to plan, yellow means there is some schedule risk and red means the project is in serious trouble. Usually first line managers like to get all the details while people further up in the reporting chain only like to see summaries with varying level of details. All of this creates additional overhead but is usually inevitable to ensure that you complete the project within the given time and budget. As a comprehensive project management solution, TaskJuggler provides full support for all those tracking and reporting steps. It comes with a powerful email and web based communication system that simplifies the tracking process for individual contributors as well as managers. As a side note we would like to mention that the recording of the work time of employees is regulated by labor law in certain countries. You might also require approval from a Worker's Council before you can deploy any time recording tools. Please consult with your corporate counsel or legal expert for all geographic regions of your teams before you deploy a time tracking solution. We also would like to point out that introducing status reporting and time sheets is usually a big change for every staff. Don't underestimate the psychological impact and the training requirements. We also recommend to test the described process with a small group of employees first to get familiar with the process and to adapt it to your needs. Don't rush a deployment! You usually only have one chance to roll-out such a new process. ==== The reporting and tracking cycle ==== In this description, we assume that you are using a weekly reporting cycle. TaskJuggler does support arbitrary cycles, but we highly recommend the described weekly cycle. # '''Time sheets''': Every project contributor needs to fill out a [[timesheet|time sheet]] once a week. To simplify this task as much as possible, a template will be send out by email. The template already lists all tasks that were planned for this week to work on with the respective effort values and end dates. It also provides sections for textual status reports. The contributor needs to review and complete the time sheet and has to send it back via email. TaskJuggler validates the submission and returns an email with either an error message or a nicely formatted version of the time sheet. # All time sheets must be submitted by a certain deadline, e. g. midnight on Sunday. TaskJuggler will then compile a summary report and sent it out to a list of interested parties. It will also detect missing time sheets and will send out a reminder to those contributors that have not submitted their report. # On Monday the project managers need to review the time sheets and update the plan accordingly. TaskJuggler can compile a list changes compared to the plan. This makes it easy to update the plan according to the actual progress that was made. The closer the actuals match the plan the less work this is. The project managers now generate bookings for the last week and add them to the database with previous bookings. Doing so, will prevent changes to the plan to affect the past. Only the future will be modified. # Once the plan has been updated, managers will receive their status sheet templates per email. Each manager will get the information for the tasks that they are [[responsible]] for. To consolidate the information for the next manager in the reporting chain they can moderate the reports in three ways. Consolidated manager reports are called dashboard reports. ## A status report for a task can be removed from the dashboard. ## A status report for a task can be corrected or updated. ## All reports for sub tasks of a task can be summarized by creating a new status for that task. This will remove all reports for sub tasks of that particular tasks from the dashboard. # Managers than need to send back the edited status report via email. Like with time sheets, TaskJuggler will check them and return either an error message or a plain text version of the dashboard report of the manager. In addition to the plain text versions of the time sheet summaries and the dashboards, TaskJuggler provides support for publishing them as HTML pages from a web server. === Implementing the status tracking system === ==== Prerequesites ==== The .tjp and .tji files of your project plan should be managed by a revision control system. TaskJuggler does not require a particular software, but for this manual we illustrate the implementation with [http://subversion.apache.org Subversion]. It should be obvious how to do this with other software though. All communication of time sheets and status sheets is done via email. TaskJuggler has built-in support for sending emails. To receive emails and to feed them to the correct program, TaskJuggler needs support from a mail transfer agent (MTA) and a mail processor. In this documentation we describe the setup with [http://www.postfix.org/ postfix] as MTA and [http://www.procmail.org/ procmail] as mail processor. These are standard parts of any Linux distribution and should be easy to setup. It's certainly possible to use other MTAs and mail processors, but this is not the scope of this manual. Finally, you need a web server to publish your reports. This can really be any web server. The generated reports are static HTML pages that can simply be put into a directory that the web server is serving. For the email based communication you need to provide email addresses for all project contributors. This is done in the project plan in the resource definition by using the [[email]] attribute. resource joe "Joe Avergage" { email "joe@your_company.com } In this manual, we assume you have a dedicated Linux machine with a local user called ''''taskjuggler''''. Your project files (*.tjp and *.tji) is under Subversion control and the taskjuggler user has a checked-out version in ''''/home/taskjuggler/projects/prj''''. You can use another user name, another source code management system and even another operating system like Windows or MacOS. This is all possible, but not the scope of this manual. To use the tracking system, you need to setup the [[Software#tj3d|taskjuggler server]] to serve your project. ==== The Time Sheet Template Sender ==== Each project contributor needs to fill out a time sheet each week. To simplify the process each contributor will receive a template that already contains a lot of the information they need to provide. To send out the time sheets, the command ''''tj3ts_sender'''' must be used. It will call ''''tj3client'''' with appropriate parameters. To use it, you need to have a properly configured daemon running and the appropriate project loaded. Then you need to add the configuration data for ''''tj3ts_sender'''' to your TaskJuggler configuration file. The time sheet related settings have their own top-level section: _global: emailDeliveryMethod: smtp smtpServer: smtp.your_company.com authKey: topsecret scmCommand: "svn add %f ; svn commit -m '%m' %f" projectId: prj _timesheets: senderEmail: 'TaskJuggler ' _sender: hideResource: '~isleaf()' _summary: sheetRecipients: - team@your_company.com digestRecipients: - managers@your_company.com The ''''emailDeliveryMethod'''' defines how emails should be sent. Use ''''smtp'''' to directly send the emails to an SMTP server. The ''''smtpServer'''' defines which host will handle your emails. Replace the host name with your local SMTP server. Alternatively, you can use the method ''''sendmail'''' on UNIX-like systems to pass the email to the sendmail tool. In this case, the ''''smtpServer'''' line can be omitted. The 'scmCommand' setting contains the command to add and commit new and old files to the source code management system. The command in this example works for Subversion. The TaskJuggler server may serve multiple projects. With the ''''projectId'''' option you have to specify which project you would like to work with. ''''senderEmail'''' is the email address the time sheet infrastructure will use. Outgoing emails will have this address as sender so that replies will come back to this email address. We'll cover later how these are processed. The hideResource option works similarly to the [[hideresource]] attribute in the report definitions of the project plan. It allows you to restrict the sending of time sheet templates to a subset of your defined resources. In this example, we only want to send templates to individual resources and not the teams you might have defined. By default the time sheets will cover the week from Monday morning 0:00 to Sunday night 24:00. When called without the ''''-e'''' option, ''''tj3ts_sender'''' will send out templates for the current week. To call the ''''tj3ts_sender'''' command you either need to be in the ''''/home/taskjuggler/projects/prj'''' directory or use the ''''-c'''' command line option to point it to the configuration file to use. In the latter case you also need to call it with the ''''-d'''' option to change the output directory to your project directory. To test the command without sending out actual emails you can use the ''''--dryrun'''' option on the command line. To do its job, ''''tj3ts_sender'''' needs to generate a number of files and directories. A copy of the generated templates will be stored in ''''TimeSheetTemplates//'''' under ''''-date.tji''''. '''''''' is replaced with the end date of the reporting interval and '''''''' is the ID of the resource. If you re-run the command existing templates will not be regenerated nor will they be sent out again. You can use the ''''-f'''' command line option to force them to be generated and sent out again. The ''''tj3ts_sender'''' command will also add the reporting interval to a file called ''''TimeSheetTemplates/acceptable_invervals''''. We'll cover this file later on when we deal with the time sheet receiver. ==== The Time Sheet Receiver ==== To receive the filled-out time sheets and to process them automatically you need to create a special user. TaskJuggler requires a number of email addresses to be setup to receive emails. We recommend to use the following setup. Create a special user called ''''taskjuggler'''' on a dedicated Linux machine. Then create the following email aliases for this user. timesheets timesheet-request statussheets statussheet-request Your MTA must be configured to use procmail for email delivery. See the manual of your MTA for details on how to configure aliases and for using procmail for delivery. If you have a resident MTA expert you should ask him or her for support. The next step is to configure procmail to forward the incoming emails to the appropriate TaskJuggler components. Create a file called ''''.procmailrc'''' in the home directory of the taskjuggler user and put in the following content: For debugging and testing purposes, all incoming emails are archived in a directory called ''''Mail''''. If there is no such directory in the taskjuggler home directory, you need to create it now. PATH=$HOME/bin:/usr/bin:/bin:/usr/local/bin MAILDIR=$HOME/Mail/ DEFAULT=$HOME/Mail/all LOGFILE=$MAILDIR/procmail.log SHELL=/bin/sh PROJECTDIR=/home/taskjuggler/projects/prj LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 # Archive all incoming emails in a file called all :0 c all :0 * ^Subject:.*Out of Office.* /dev/null :0 * ^To:.*timesheets@taskjuggler\.your_company\.com { :0 c: timesheets :0 w: tj3ts_receiver.lock | tj3ts_receiver --silent -c $PROJECTDIR/.taskjugglerrc -d $PROJECTDIR :0 failed_sheets } :0 * ^To:.*timesheet-request@taskjuggler\.your_company\.com { ID=`formail -xSubject:` :0 c: timesheet-request :0 w: tj3ts_sender.lock | tj3ts_sender -r $ID -f --silent -c $PROJECTDIR/.taskjugglerrc -d $PROJECTDIR } :0 * ^To:.*statussheets@taskjuggler\.your_company\.com { :0 c: statussheets :0 w: tj3ss_receiver.lock | tj3ss_receiver --silent -c $PROJECTDIR/.taskjugglerrc -d $PROJECTDIR :0 failed_sheets } :0 * ^To:.*statussheet-request@taskjuggler\.your_company\.com { ID=`formail -xSubject:` :0 c: statussheet-request :0 w: tj3ss_sender.lock | tj3ss_sender -r $ID -f --silent -c $PROJECTDIR/.taskjugglerrc -d $PROJECTDIR } # Forward a copy to project admins :0 c ! taskjuggler-admin@your_company.com # Since we have archived a copy we can discard all mails here. :0 /dev/null This procmail configuration will cause incoming emails that are addressed to timesheets@taskjuggler.your_company.com to be forwarded to the ''''tj3ts_receiver'''' program. Of course you need to replace ''your_comany.com'' with whatever domain you are using. The received emails are then checked for syntactical and logical errors. If such are found, an email is sent back with an appropriate error message. The time sheet contains the resource ID of the reporting resource. As soon as this has been detected, all email communication will be sent to the email address in the project plan. Only when the resource ID could not be identified, the sender of the email will get the answer. This was implemented as a security measure so other users cannot easily retrieve project related information from other users. Correct time sheets are archived in the ''''TimeSheets//'''' directory where '''''''' is the end date of the reporting period. If the directory does not exist yet, it will be created. The file will be called ''''-.tji''''. If a SCM command was specified, the file will be automatically put under revision control. Subsequent submission of the same time sheet will simply overwrite the earlier submissions. The file name will also be added to a file called ''''all.tji'''' which consists of include statements of all time sheet files in the directory. There also is an automatically maintained file ''''all.tji'''' in the ''''TimeSheets'''' directory that includes all the ''''/all.tji'''' files. To add all the submitted time sheets to your project plan, simply include the top-level ''''all.tji''''. ''''tj3ts_receiver'''' will only accept time sheets for the time periods listed in ''''TimeSheetTemplates/acceptable_intervals''''. ''''tj3ts_sender'''' will automatically enable the current period when it sends out the templates. If you want to stop receiving time sheet updates for a certain period, simply remove the period from the ''''acceptable_intervals'''' file. ==== Time Sheet Template Requests ==== Normally, the time sheets are sent out once a week automatically. In case a project contributor leaves earlier for vacation or has lost the template, they can request the template for the current week again. By sending an email to ''''timesheet-request@taskjuggler.your_company.com'''' and putting their resource ID in the subject of the email, they will receive an email with the time sheet template. The email will be sent to the email address in the project plan, not the sender of the request email. ==== Time Sheet Summaries ==== All time sheets should be successfully submitted by Sunday 24:00. After this deadline, your can send out a summary of all submitted time sheets. This summary will also contain a list of those project contributors that have not submitted their time sheet. These individuals will also get a reminder to submit their time sheets immediately. To send out the summary report, the program ''''tj3ts_summary'''' is used. Before you can use it, you need to add a few settings to the TaskJuggler configuration file. _global: emailDeliveryMethod: smtp smtpServer: smtp.your_company.com authKey: topsecret projectId: prj _timesheets: senderEmail: 'TaskJuggler ' _summary: sheetRecipients: - team@your_company.com digestRecipients: - managers@your_company.com ''''sheetRecipients'''' is a list of email addresses that should receive a copy of the submitted time sheet. Each email address must be put on a separate, properly indented line that starts with a dash followed by a space. The emails will have the email of the original time sheet author as sender address. ==== Updating the Project Plan ==== The time sheets contain two kind of information that are intended for two sets of audiences. Project managers will be interested primarily in the scheduling related information but surely like to look at the task status as well. Managers responsible for certain parts of the project will be primarily interested in the status reports for the ongoing tasks. We'll cover the processing of the status information in the next sections. This section deals with the processing of the scheduling related information. Project contributors can specify several deviations of the current project plan. * Task may need more effort or time than was originally planned for. * They may have not worked the planned amounts on the tasks. * They may have started to work on new tasks that are not even in the project plan. The is usually a sign of project discipline and should be avoided. But in reality this will happen and TaskJuggler is able to handle it. TaskJuggler can print a summary of all the deltas between the plan and the actual reports in the time sheets. tj3 --warn-ts-deltas YourProject.tjp TimeSheets/all.tji In this example call ''''YourProject.tjp'''' is your main project file and all submitted time sheets are included by TimeSheets/all.tji. This file and all subsequent include files are automatically generated and updated by ''''tj3ts_receiver''''. Project managers should use the printed output of this command to update the project plan accordingly. The specified deltas of existing tasks must be updated in the main project plan. For new tasks in the time sheets, the task has to be created in the project plan. Then the newtask statement in the time sheet needs to be converted into a normal task report. newtask some.task.id "My new task" { ... } Needs to be converted into task some.task.id { ... } The task ID in the status sheet must match the newly created task in the project plan. To check that all deltas were properly processed, re-run the check command. tj3 --warn-ts-deltas YourProject.tjp TimeSheets/all.tji You may also want to remove the interval from the ''''TimeSheetTemplates/acceptable_intervals'''' file to prevent further submissions of time sheets for this time period. === Recording actual Resource Usage === To ensure that future changes won't change the past of the project, we need to freeze the history of the project. History in this context means which resource worked on what task from when to when. Since TaskJuggler cannot know what level of detail you want to include in the reports, this information has to be recorded with the highest possible accuracy. This means that we have to capture the exact start and end dates for every period that a resource worked on a task. Unless you use some external time tracking system to capture this information and export it to TaskJuggler, you probably want TaskJuggler to generate this data for you based on the plan information. Before you can freeze that past part of your project, you need to tell TaskJuggler which scenario should be used for tracking the actual progress. See the [[trackingscenario]] documentation for more details on this. Before you freeze your project for the first time, you should make sure that the current date is still before the project start. If that is not the case, use the [[now]] attribute to set the current date to the project start: now ${projectstart} Once you have frozen the project for the first time, you should remove the [[now]] attribute again. It will be automatically updated. To freeze your project up to a certain date, you can use the following command: tj3 --freeze yourproject.tjp --freezedate YYYY-MM-DD This will generate two files, ''''yourproject-header.tji'''' and ''''yourproject-bookings.tji''''. The header files contains the date of the freeze as a [[now]] attribute. You must [[include.project| include]] this file at the end of your project header section. The bookings file contains the resource assignment data. It usually contains many [[booking.task|booking]] entries that look similar to this: supplement task t { booking r 2010-02-19-09:00-+0000 + 3.0h, 2010-02-19-13:00-+0000 + 5.0h, 2010-02-22-09:00-+0000 + 3.0h, 2010-02-22-13:00-+0000 + 5.0h, } The booking file must be [[include.properties|included]] at the end of your main project file. In case there are still some discrepancies between the booking data and the actual assignments of the resources, you can edit the booking file to correct the data. The next time you run ''''tj3'''' with your project, all assignments prior to the date in the project header file will be taken only from the bookings file. All assignments after this date will be determined by the scheduler according to your provided constraints. When you run ''''tj3 --freeze'''' again, it will update the header and booking files. Since you have included your booking file, any modifications you have made, will be preserved. That is, the actual data will be preserved, not the formatting since the file will be completely re-generated again. ==== Status Sheets ==== For larger projects with many contributors the flood of time sheets can become hard to manage. Higher level managers are usually not interested in all the details as long as the project executes according to plan. To keep the managers on each level informed with the proper amount and content TaskJuggler provides the concept of status sheets. To use status sheets, the reporting chains must be reflected in the task hierarchy of the project. The [[responsible]] attribute must be used to assign tasks to managers. Leaf tasks or whole sub trees must be assigned to the lowest level of management. The responsibility for one or more level of parent tasks must be assigned to the next level of managers and so on. When all time sheets have been submitted, the reports for all tasks are sent to the responsible managers for these tasks. The information is generated by the ''''tj3ss_sender'''' program and is called a status report template. Each manager will get one template that includes the status reports for the tasks they are responsible for. It's not the managers task to prepare the report for the next level of management. To do this, the manager has 3 options: * Forward the status report of a task directly to the next level. The original authorship can be keep or removed. The content can also be edited if needed. * Similar reports for a task or a whole task sub-tree can be combined into just one report. To achieve this a new task report must be created for the parent tasks of these lower-level tasks. This will replace all reports for sub tasks with this newly created report. * A task report can simply be removed from the status report. The status sheet template is designed to perform all three actions in a simple manner. The original reports are commented out. To remove a report, it needs to be uncommented and the headline must be set to an empty string. To change a report, the text must be edited after the comment marks have been removed. To create a summary report for a group of tasks, a new report for the common parent task must be created. ==== The Status Sheet Template Sender ==== To send out the time sheets, the command ''''tj3ts_sender'''' must be used. It will use the ''''tj3client'''' program to do retrieve the necessary data from the TaskJuggler server. Before the program can be used, a new section must be added to the TaskJuggelr configuration file. _statussheets: projectId: prj _sender: senderEmail: 'TaskJuggler ' hideResource: '~(isleaf() & manager)' If you are using status sheets for only one level of management you can hardcode that like in the example above. For multiple level of management you need to specify which group of managers should the report templates be generated for and pass that information on the command line. Use the ''''--hideresource'''' option to specify a logicalexpression to filter away the resources you don't want templates to be generated for. The easiest way to achieve this is by using unique flags for each management level. In the example above we assume you have assigned the flag ''''manager'''' to each first-level manager. For the override mechanism to work, the manager reports must always have a newer date than the original report. So, the end date of the first-level manager status sheets must be after the time sheet interval. The second-level mangers must use a later date than the first-level managers and so on. By default ''''tj3ss_sender'''' will use the next Wednesday as end date. If you need a different date, you must use the ''''-e'''' option to specify that date. Let's say you have two levels of managers that use status sheets. The time sheets are due midnight on Sunday. The project managers can work in the deltas and new tasks on Monday. After that you generate the reports for the first level managers with and end date of Wednesday. This implies a submission deadline of midnight on Tuesday. The second level manager templates will be sent out right after this deadline with an end date of Thursday. That would be the deadline for the second-level managers. The final report can than be generated by TaskJuggler automatically right after that deadline. ==== Requesting Status Sheet Templates ==== Usually the status sheets templates should be send out automatically. But sometimes a manager needs them earlier or needs an updated version due to a late incoming downstream report. The above provided procmail configuration supports the generation of status sheets templates on request by email. By sending an email to statussheet-request@taskjuggler.your_company.com and putting their resource ID in the subject of the email, managers will receive an email with the status sheet template. The email will be sent to the email address in the project plan, not the sender of the request email. The setup described here only works for first-level managers. By adding more email addresses, this can easily be extended for more levels of management. You just need to make sure that ''''tj3ss_sender'''' is called with the proper parameters to change the resource selection and end date. ==== The Status Sheet Receiver ==== Similarly to the time sheets a the completed status sheets must be send back by email. We already described how the necessary email aliases should be configured. For status sheets the address ''''statussheets@taskjuggler.your_company.com'''' can be used. The incoming emails will then be forwarded to the ''''tj3ss_receiver'''' program that will process them. To use it, you first need to add the following settings to the ''''statussheets'''' section of your TaskJuggler configuration file: _statussheets: projectId: prj _receiver: senderEmail: 'TaskJuggler ' This will set the sender email of outgoing emails. Every incoming status sheet will be checked and either an error message will be returned or a consolidated status report for all tasks that the resource is responsible for. This report can either be directly forwarded to the next level manager or interested groups, or an HTML report can be generated and shared. This is especially useful in case the next level management is not getting status sheet templates. Usually status reports only contain task reports for the current reporting period. But if there were tasks with an elevated status, these will be carried forward until they were removed by providing an empty headline or replaced with a new report for the same task or a parent task. taskjuggler-3.5.0/manual/Reporting_Bugs0000644000175000017500000000401212614413013017472 0ustar bernatbernat=== Reporting Bugs and Feature Requests === All official releases of TaskJuggler are meant to be stable releases unless explicitly noted differently. But our test suite is still very small and some features are not tested at all. So it's very likely that your current version of TaskJuggler contains some bugs. If you find a bug, please follow this procedure: * Read this manual to make sure that it is actually a bug and not a feature. * Check the [http://www.taskjuggler.org TaskJuggler web page] and the [http://www.taskjuggler.org/contact.html discussion groups]. You should also search the [https://github.com/taskjuggler/TaskJuggler/issues issue tracker] if the problem has been reported before. If so, it's quite likely that there is already a workaround or fix available. * Try to create a test project that is as small as possible but still reproduces the bug. * If TaskJuggler has crashed you will usually get some debug output. This may not make any sense to you but it is vital information to analyze the bug. Please include it completely in your bug report. Use the following command to store the messages into a file. tj3 yourproject.tjp 2> error_message * Enter a detailed description of the problem into the [https://github.com/taskjuggler/TaskJuggler/issues?state=open issue tracker] and don't forget to attach your test project and the error log. You can paste that into the input field. * Not attaching a test project will severely limit our abilities to help you. In 95% of the reported bugs we need a test case to find and fix the problem. Please make sure you provide meaningful descriptions and a small but complete test project. Not providing this information with the first bug report means we have to follow up with you or the bug report is being ignored. * Please open a new issue for each bug. Don't include multiple problems in one bug report. This usually leads to one bug being fixed and the rest of the problems to be ignored. * If you have a feature request, please open a new issue and set the "Feature" label. taskjuggler-3.5.0/manual/html/0000755000175000017500000000000012614413013015565 5ustar bernatbernattaskjuggler-3.5.0/manual/html/duration.html0000644000175000017500000001155112614413013020303 0ustar bernatbernat duration

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< disabled << Table Of Contents >> efficiency >>


Keyword duration

Purpose

Specifies the time the task should last. This is calendar time, not working time. 7d means one week. If resources are specified they are allocated when available. Availability of resources has no impact on the duration of the task. It will always be the specified duration.

Tasks may not have subtasks if this attribute is used. Setting this attribute will reset the effort and length attributes.

Syntax duration <value> (min | h | d | w | m | y)
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context task, supplement (task)
See also effort, length

project "Duration Example" 2007-06-06 - 2007-06-26 {
  timezone "America/Denver"
}

resource tux "Tux"

task t "Enclosing" {
  start 2007-06-06
  task durationTask "Duration Task" {
    # This task is 10 calendar days long.
    duration 10d
  }

  task intervalTask "Interval Task" {
    # This task is similar to the durationTask. Instead of a start
    # date and a duration it has a fixed start and end date.
    end 2007-06-17
  }

  task lengthTask "Length Task" {
    # This task 10 working days long. So about 12 calendar days.
    length 10d
  }

  task effortTask "Effort Task" {
    # Tux will need 10 days to complete this task.
    effort 10d
    allocate tux
  }
}


<< disabled << Table Of Contents >> efficiency >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/responsible.html0000644000175000017500000000607512614413013021010 0ustar bernatbernat responsible

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< resources (limit) << Table Of Contents >> richtext (extend) >>


Keyword responsible

Purpose
The ID of the resource that is responsible for this task. This value is for documentation purposes only. It's not used by the scheduler.
Syntax responsible <resource> [, <resource>...]
Arguments resource [ID]
The ID of a defined resource
Context task, supplement (task)



<< resources (limit) << Table Of Contents >> richtext (extend) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/account.task.html0000644000175000017500000000420012614413013021044 0ustar bernatbernat account.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< account << Table Of Contents >> accountprefix >>


Keyword account (task)

This keyword is no longer supported.

Use chargeset instead.



<< account << Table Of Contents >> accountprefix >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/weekstartssunday.html0000644000175000017500000000546412614413013022104 0ustar bernatbernat weekstartssunday

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< weekstartsmonday << Table Of Contents >> width >>


Keyword weekstartssunday

Purpose
Specify that you want to base all week calculation on weeks starting on Sunday. This is common in the United States of America.
Syntax weekstartssunday
Arguments none
Context project



<< weekstartsmonday << Table Of Contents >> width >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/How_To_Contribute.html0000644000175000017500000002573612614413013022065 0ustar bernatbernat How_To_Contribute

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< Installation << Table Of Contents >> Getting_Started >>


2.8 How to Contribute

2.8.1 Why contribute?

TaskJuggler is an Open Source Project. It was developed by volunteers mostly in their spare time. Made available under the GNU General Public license and similar licenses, TaskJuggler can be shared and used free of charge by anybody who respects the license conditions. Does that mean you can use it without worrying about anything? Clearly not! Though users have no legal obligation to contribute, you should feel a moral obligation to support Open Source in whatever way you can. This can range from helping out other users with their first Linux installation to actively contributing to the TaskJuggler Project, not just as a programmer. The following section describes, how you can contribute to any of the components that are part of the TaskJuggler software releases.

2.8.2 Preparing a contribution

All TaskJuggler development is coordinated using the github source code management platform. All changes must be submitted using Git github so that we can track the authorship of each submission. There is excellent documentation available on how to use github.

Make sure you have followed the steps described in the Installation Steps for Developers chapter.

If you have never used Git before, you need to configure it first. You need to set your name and email address. This information will be present in all patches that you submit.

git config --global user.name "Your Name"
git config --global user.email "firstname.lastname@domain.org"

Do not use the development snapshots or send your patches as plain diff files. We'd like to ensure that we know who contributed to TaskJuggler. Therefor we are only accepting signed-off git patches with full user names and valid email addresses.

Next you need to find the files where you want to make your modifications. Sometimes files will be generated from other files. Do not change those generated files. Your changes will be overwritten the next time you call the make utility.

2.8.3 Creating a Patch

When you are done with your changes, it's a good idea to test them. In the taskjuggler directory run the following commands.

cd taskjuggler3
rake test
rake spec
rake manual
rake gem

If there are no errors, you can check or test the result. If everything works fine, you can lock at your changes again.

git diff

The git-diff utility performs a line-by-line comparison of the files against the latest version in you local repository. Try to only make changes that have an impact on the generated files. Do not change indentation or line wrapping of paragraphs unless absolutely necessary. These kinds of changes increase the size of diff files and make it much harder to evaluate the patches. When making changes to the program code, please use exactly the same coding style as the rest of the code. If your contribution is large enough to justify a copyright claim, please indicate what copyright you claim in the patch. For modifications to existing files, we will assume that your contribution falls under the same license as the modified file. All new files will need to contain a license declaration, preferably GPL version 2. In any case, the license must be an OSI accepted license and be compatible with the GPL version 2 used by the rest of the project.

Review all changes carefully. In case you have created new source files, you need to register them with your repository.

git add FILENAME

If you think you are done, you can commit your changes to your local repository.

git commit -a -s

The -s parameter is very important. We will only accept signed-off patches. By signing off on your patches you confirm that you wrote the code and have the right to pass it on as a patch. See this document for more information!

Please include a meaningful commit message. The first line (header line) should be prefixed by Fix: for bug fixes or New: for new features. This is used to automatically generate the change log from one release to another. So a bug that has been introduced after the last release and is fixed before the next release does not need to be included in the changelog. For those cases, don't use any prefix.

After the header line leave a blank line and include one or more paragraphs with more detailed information about the patch. This information will also be included in the the change log if the header line has a prefix.

If you fix a bug that was reported by somebody else, please also include a reported-by line:

Reported-by: whoever-reported-the-bug

Then push it into your forked github repository.

git push

The final step is to send us a pull request for your changes.

2.8.4 Contributing to the User Manual

The user manual is currently a rough port of the 2.x manual. It contains many inaccuracies and does not provide much more than a tutorial and a syntax reference. Any help to turn this into a real user manual is greatly appreciated.

The manual is composed from 3 different sources.

  1. The sources for normal pages are in MediaWiki format and can be found in the manual directory of the source distribution.
  2. The information in the syntax reference is extracted from the TJP parser source code. It can be found in the file lib/TjpSyntaxRules.rb. You can ignore all but the doc(...), arg(...) and example(...) sections.
  3. The TJP syntax examples are in the test/TestSuite/Syntax/Correct directory.

The following command build the HTML files for the manual in the manual/html directory.

rake manual

2.8.5 Contributing to the Test Suite

The test suite can be found in the test and spec directories. It contains unit and system tests but is very rudimentary at the moment. Adding more system tests to the test/CSV-Report directory is probably the best place to start. Originally, TaskJuggler used classic Ruby unit tests, but a migration to http://rspec.info/|RSpec-2 is in the works now. New tests should be written as RSpec tests unless they require infrastructure only available in the test directory.

2.8.6 Contributing to the Ruby code

For the first stable TaskJuggler release we have most 2.x features supported. The few things that break backwards compatibility are documented in the TaskJuggler_2x_Migration section.

In general, patches are very welcome. Please follow the coding style and naming conventions used in the existing code. Larger changes should be preceded by a discussion in the TaskJuggler Developer Forum.

2.8.7 Some final words to Contributors

We do welcome all contributions, but please understand that we reserve the right to reject any contribution that does not follow the above guidelines or otherwise conflicts with the goals of the TaskJuggler team. It is a good idea to contact the team prior to making any larger efforts.



<< Installation << Table Of Contents >> Getting_Started >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/footer.html0000644000175000017500000000720012614413013017750 0ustar bernatbernat footer

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< fontcolor (column) << Table Of Contents >> formats >>


Keyword footer

Purpose
Define a text section that is put at the bottom of the report. The text will be interpreted as Rich Text.
Syntax footer <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport
See also epilog, header, prolog

project "Test" 2011-12-11 +1m

task "Foo"

textreport frame "textreport" {
  taskreport r1 ""
  taskreport r2 ""

  formats html
  header -8<-
  This is the header
  ----
  ->8-
  left "<[report id='frame.r1']>"
  center "Center"
  right "<[report id='frame.r2']>"
  footer -8<-
  ----
  This is the footer
  ->8-
}


<< fontcolor (column) << Table Of Contents >> formats >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/auxdir.report.html0000644000175000017500000000674512614413013021275 0ustar bernatbernat auxdir.report

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< auxdir << Table Of Contents >> balance >>


Keyword auxdir (report)

This keyword has not yet been fully tested yet. You are welcome to try it, but it may lead to wrong results. The syntax may still change with future versions. The developers appreciate any feedback on this keyword.

Purpose
Specifies an alternative directory for the auxiliary report files such as CSS, JavaScript and icon files. If this attribute is not set, the directory will be generated automatically. If this attribute is provided, the user has to ensure that the directory exists and is filled with the proper data. The specified path can be absolute or relative to the generated report file.
Syntax auxdir <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< auxdir << Table Of Contents >> balance >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/date.html0000644000175000017500000001227612614413013017400 0ustar bernatbernat date

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< dailyworkinghours << Table Of Contents >> date (extend) >>


Keyword date

Purpose

A DATE is date and time specification similar to the ISO 8601 date format. Instead of the hard to read ISO notation with a T between the date and time sections, we simply use the more intuitive and easier to read dash: YYYY-MM-DD[-hh:mm[:ss]][-TIMEZONE]. Hour, minutes, seconds, and the TIMEZONE are optional. If not specified, the values are set to 0. TIMEZONE must be an offset to GMT or UTC, specified as +HHMM or -HHMM. Dates must always be aligned with the timingresolution.

TaskJuggler also supports simple date calculations. You can add or substract a given interval from a fixed date.

%{2009-11-01 + 8m}

This will result in an actual date of around 2009-07-01. Keep in mind that due to the varying lengths of months TaskJuggler cannot add exactly 8 calendar months. The date calculation functionality makes most sense when used with macros.

%{${now} - 2w}

This is result in a date 2 weeks earlier than the current (or specified) date. See duration for a complete list of supported time intervals. Don't forget to put at least one space character after the date to prevent TaskJuggler from interpreting the interval as an hour.

Date attributes may be invalid in some cases. This needs special care in logical expressions.

Syntax (<DATE> | % { <DATE> (+ | -) <duration> (min | h | d | w | m | y) })
Arguments duration
The duration of the interval. May not be 0 and must be a multiple of timingresolution.
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context Global scope



<< dailyworkinghours << Table Of Contents >> date (extend) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/extend.html0000644000175000017500000001220612614413013017743 0ustar bernatbernat extend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< export << Table Of Contents >> fail >>


Keyword extend

Purpose
Often it is desirable to collect more information in the project file than is necessary for task scheduling and resource allocation. To add such information to tasks, resources or accounts the user can extend these properties with user-defined attributes. The new attributes can be of various types such as text, date or reference to capture various types of data. Optionally the user can specify if the attribute value should be inherited from the enclosing property.
Syntax extend (task | resource) [{ <attributes> }]
Arguments none
Context project

Attributes date (extend), number (extend), reference (extend), richtext (extend), text (extend)

project "Extend Test" 2013-04-24 +1m {
  extend task {
    date DueDate "Due Date"
    number Count "Count"
    reference URL "URL"
    richtext Claim "Claim"
    text Intro "Intro"

    date DueDateS "Due Date" { scenariospecific inherit }
    number CountS "Count" { scenariospecific }
    reference URLS "URL" { scenariospecific }
    richtext ClaimS "Claim" { scenariospecific inherit }
    text IntroS "Intro" { scenariospecific }
  }
  extend resource {
    date Birthday "Birthday"
    number Count "Count"
    reference URL "URL"
    richtext Claim "Claim"
    text Intro "Intro"

    date BirthdayS "Birthday" { scenariospecific inherit }
    number CountS "Count" { scenariospecific }
    reference URLS "URL" { scenariospecific }
    richtext ClaimS "Claim" { scenariospecific inherit }
    text IntroS "Intro" { scenariospecific }
  }
  scenario one "One" {
    scenario two "Two"
  }
}

resource "R" {
  Birthday 2000-05-01
  Count 42
  URL "http://www.taskjuggler.org"
  Claim "A '''big''' statement."
  Intro "Let's think about this..."

  two:BirthdayS 2000-05-01
  two:CountS 42
  two:URLS "http://www.taskjuggler.org" { label "TJ Web" }
  two:ClaimS "A '''big''' statement."
  two:IntroS "Let's think about this..."
}

task "T" {
  DueDate 2013-05-01
  Count 42
  URL "http://www.taskjuggler.org"
  Claim "A '''big''' statement."
  Intro "Let's think about this..."

  two:DueDateS 2013-05-01
  two:CountS 42
  two:URLS "http://www.taskjuggler.org" { label "TJ Web" }
  two:ClaimS "A '''big''' statement."
  two:IntroS "Let's think about this..."
}


<< export << Table Of Contents >> fail >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/limits.html0000644000175000017500000000655212614413013017764 0ustar bernatbernat limits

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< length << Table Of Contents >> limits (allocate) >>


Keyword limits

Purpose
Set per-interval allocation limits for the following resource definitions. The limits can be overwritten in each resource definition and the global limits can be changed later.
Syntax limits [{ <attributes> }]
Arguments none
Context properties

Attributes dailymax, dailymin, maximum, minimum, monthlymax, monthlymin, weeklymax, weeklymin



<< length << Table Of Contents >> limits (allocate) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/journalentry.html0000644000175000017500000001057112614413013021213 0ustar bernatbernat journalentry

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< journalattributes << Table Of Contents >> journalmode >>


Keyword journalentry

Purpose

This attribute adds an entry to the journal of the project. A journal can be used to record events, decisions or news that happened at a particular moment during the project. Depending on the context, a journal entry may or may not be associated with a specific property or author.

A journal entry can consists of up to three parts. The headline is mandatory and should be only 5 to 10 words long. The introduction is optional and should be only one or two sentences long. All other details should be put into the third part.

Depending on the context, journal entries are listed with headlines only, as headlines plus introduction or in full.

Syntax journalentry <date> <headline> [{ <attributes> }]
Arguments date
See date for details.
headline [STRING]
The headline of the journal entry. It will be interpreted as Rich Text.
Context project, resource, supplement (resource), task, supplement (task)

Attributes alert, author, details, flags (journalentry), summary



<< journalattributes << Table Of Contents >> journalmode >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/minend.html0000644000175000017500000000601312614413013017725 0ustar bernatbernat minend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< milestone << Table Of Contents >> minimum >>


Keyword minend

Purpose
Specifies the minimum wanted end time of the task. The value is not used during scheduling, but is checked after all tasks have been scheduled. If the end of the task is earlier than the specified value, then an error is reported.
Syntax minend <date>
Arguments date
See date for details.
Context task, supplement (task)



<< milestone << Table Of Contents >> minimum >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/tagfile.html0000644000175000017500000001001012614413013020056 0ustar bernatbernat tagfile

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< supplement (task) << Table Of Contents >> task >>


Keyword tagfile

Purpose
The tagfile report generates a file that maps properties to source file locations. This can be used by editors to quickly jump to a certain task or resource definition. Currently only the ctags format is supported that is used by editors like http://www.vim.org|vim.
Syntax tagfile [<id>] <file name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
file name [STRING]
The name of the tagfile to generate. Use tags if you want vim and other tools to find it automatically.
Context properties

Attributes hideresource, hidetask, rollupresource, rolluptask



<< supplement (task) << Table Of Contents >> task >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/include.project.html0000644000175000017500000000677712614413013021564 0ustar bernatbernat include.project

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< include (macro) << Table Of Contents >> include (properties) >>


Keyword include (project)

Purpose

Includes the specified file name as if its contents would be written instead of the include property. When the included files contains other include statements or report definitions, the filenames are relative to file where they are defined in.

This version of the include directive may only be used inside the project header section. The included files must only contain content that may be present in a project header section.

Syntax include <filename>
Arguments filename [STRING]
Name of the file to include. This must have a .tji extension. The name may have an absolute or relative path. You need to use / characters to separate directories.
Context project



<< include (macro) << Table Of Contents >> include (properties) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/title.column.html0000644000175000017500000000553012614413013021073 0ustar bernatbernat title.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< title << Table Of Contents >> tooltip (column) >>


Keyword title (column)

Purpose
Specifies an alternative title for a report column.
Syntax title <text>
Arguments text [STRING]
The new column title.
Context columns



<< title << Table Of Contents >> tooltip (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/milestone.html0000644000175000017500000000606212614413013020456 0ustar bernatbernat milestone

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< maxstart << Table Of Contents >> minend >>


Keyword milestone

Purpose

Turns the task into a special task that has no duration. You may not specify a duration, length, effort or subtasks for a milestone task.

A task that only has a start or an end specification and no duration specification, inherited start or end dates, no dependencies or sub tasks, will be recognized as milestone automatically.

Syntax milestone
Arguments none
Context task, supplement (task)



<< maxstart << Table Of Contents >> minend >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/Reporting_Bugs.html0000644000175000017500000001026212614413013021405 0ustar bernatbernat Reporting_Bugs

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< TaskJuggler_2x_Migration << Table Of Contents >> Installation >>


1.7 Reporting Bugs and Feature Requests

All official releases of TaskJuggler are meant to be stable releases unless explicitly noted differently. But our test suite is still very small and some features are not tested at all. So it's very likely that your current version of TaskJuggler contains some bugs. If you find a bug, please follow this procedure:

  • Read this manual to make sure that it is actually a bug and not a feature.
  • Try to create a test project that is as small as possible but still reproduces the bug.
  • If TaskJuggler has crashed you will usually get some debug output. This may not make any sense to you but it is vital information to analyze the bug. Please include it completely in your bug report. Use the following command to store the messages into a file.
tj3 yourproject.tjp 2> error_message
  • Enter a detailed description of the problem into the issue tracker and don't forget to attach your test project and the error log. You can paste that into the input field.
  • Not attaching a test project will severely limit our abilities to help you. In 95% of the reported bugs we need a test case to find and fix the problem. Please make sure you provide meaningful descriptions and a small but complete test project. Not providing this information with the first bug report means we have to follow up with you or the bug report is being ignored.
  • Please open a new issue for each bug. Don't include multiple problems in one bug report. This usually leads to one bug being fixed and the rest of the problems to be ignored.
  • If you have a feature request, please open a new issue and set the "Feature" label.


<< TaskJuggler_2x_Migration << Table Of Contents >> Installation >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/enabled.html0000644000175000017500000000621312614413013020047 0ustar bernatbernat enabled

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< email << Table Of Contents >> end >>


Keyword enabled

This keyword should no longer be used. It will be removed in future versions of this software.

Use active instead.

Purpose

This attribute is deprecated. Please use active instead.

Enable the scenario for scheduling. This is the default for the top-level scenario.

Syntax enabled
Arguments none
Context scenario
See also active



<< email << Table Of Contents >> end >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/textreport.html0000644000175000017500000004454512614413013020707 0ustar bernatbernat textreport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< text (extend) << Table Of Contents >> timeformat >>


Keyword textreport

Purpose
This report consists of 5 RichText sections, a header, a center section with a left and right margin and a footer. The sections may contain the output of other defined reports.
Syntax textreport [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]

The name of the report. This will be the base name for generated output files. The suffix will depend on the specified formats. It will also be used in navigation bars.

By default, report definitions do not generate any files. With more complex projects, most report definitions will be used to describe elements of composed reports. If you want to generate a file from this report, you must specify the list of formats that you want to generate. The report name will then be used as a base name to create the file. The suffix will be appended based on the generated format.

Reports have a local name space. All IDs and file names must be unique within the reports that belong to the same enclosing report. To reference a report for inclusion into another report, you need to specify the full report ID. This is composed of the report ID, prefixed by a dot-separated list of all parent report IDs.

Context accountreport, export, properties, resourcereport, taskreport, textreport, tracereport

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
accountreport
accountroot x
auxdir (report) x x
balance
caption x
center x
columns x
currencyformat
end (report) x x
epilog x
export
flags (report) x
footer x
formats x
header x
headline x
height
hideaccount
hidejournalentry
hideresource
hidetask
journalattributes
journalmode
left x
loadunit
numberformat
opennodes
period (report)
prolog x
purge
rawhtmlhead
resourcereport
resourceroot x
right x
rollupaccount
rollupresource
rolluptask
scenarios x
selfcontained x
sortaccounts
sortjournalentries
sortresources
sorttasks
start (report) x x
taskreport
taskroot x
textreport
timeformat
timezone (report) x x
title x
tracereport
width x

project "Test" 2011-12-11 +1m

task "Foo"

textreport frame "textreport" {
  taskreport r1 ""
  taskreport r2 ""

  formats html
  header -8<-
  This is the header
  ----
  ->8-
  left "<[report id='frame.r1']>"
  center "Center"
  right "<[report id='frame.r2']>"
  footer -8<-
  ----
  This is the footer
  ->8-
}


<< text (extend) << Table Of Contents >> timeformat >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/reportprefix.html0000644000175000017500000000566312614413013021216 0ustar bernatbernat reportprefix

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< replace << Table Of Contents >> resource >>


Keyword reportprefix

Purpose
This attribute can be used to insert the reports of the included file as sub-report of the report specified by ID. The parent report must already be defined.
Syntax reportprefix <report ID>
Arguments report ID
The absolute ID of an already defined report.
Context include (properties)



<< replace << Table Of Contents >> resource >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/end.timesheet.html0000644000175000017500000000704412614413013021214 0ustar bernatbernat end.timesheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< end (report) << Table Of Contents >> endcredit >>


Keyword end (timesheet)

Purpose
The expected end date for the task. This can only be used for duration based task. For effort based task remaining has to be used.
Syntax end <date>
Arguments date
See date for details.
Context newtask, task (timesheet)

task t2 "Task 2" {
  task t3 "Task 3" {
    duration 10d
    allocate r2
  }
}

timesheet r2 2009-11-30 +1w {
  task t2.t3 {
    work 5d
    end 2009-12-10
    status red "I need more time" {
      summary "This takes longer than expected"
      details -8<-
        To finish on time, I need help. Get this r1 guy to help me out
        here.
        * I want to have fun too!
      ->8-
    }
  }
  status yellow "My wife got ill" {
    summary "I might have to work from home for a few days next week."
  }
}


<< end (report) << Table Of Contents >> endcredit >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/start.html0000644000175000017500000000707512614413013017621 0ustar bernatbernat start

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< sorttasks << Table Of Contents >> start (column) >>


Keyword start

Purpose

The start attribute provides a guideline to the scheduler when the task should start. It will never start earlier, but it may start later when allocated resources are not available immediately. When a start date is provided for a container task, it will be passed down to ASAP task that don't have a well defined start criteria.

Setting a start date will implicitely set the scheduling policy for this task to ASAP.

Syntax start <date>
Arguments date
See date for details.
Context task, supplement (task)
See also end, maxstart, minstart, period (task), scheduling



<< sorttasks << Table Of Contents >> start (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/alert.html0000644000175000017500000000676012614413013017573 0ustar bernatbernat alert

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< aggregate << Table Of Contents >> alertlevels >>


Keyword alert

Purpose
Specify the alert level for this entry. This attribute is inteded to be used for status reporting. When used for a journal entry that is associated with a property, the value can be reported in the alert column. When multiple entries have been specified for the property, the entry with the date closest to the report end date will be used. Container properties will inherit the highest alert level of all its sub properties unless it has an own journal entry dated closer to the report end than all of its sub properties.
Syntax alert <alert level>
Arguments alert level [ID]
By default supported values are green, yellow and red. The default value is green. You can define your own levels with alertlevels.
Context journalentry



<< aggregate << Table Of Contents >> alertlevels >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/accountprefix.html0000644000175000017500000000575012614413013021334 0ustar bernatbernat accountprefix

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< account (task) << Table Of Contents >> accountreport >>


Keyword accountprefix

Purpose
This attribute can be used to insert the accounts of the included file as sub-account of the account specified by ID. The parent account must already be defined.
Syntax accountprefix <account ID>
Arguments account ID
The absolute ID of an already defined account
Context include (properties)



<< account (task) << Table Of Contents >> accountreport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/overtime.booking.html0000644000175000017500000000646212614413013021744 0ustar bernatbernat overtime.booking

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< outputdir << Table Of Contents >> period (column) >>


Keyword overtime (booking)

Purpose
This attribute enables bookings during off-hours and leaves. It implicitly sets the sloppy attribute accordingly.
Syntax overtime <value>
Arguments value [INTEGER]
  • 0: You can only book available working time. (Default)
  • 1: You can book off-hours as well.
  • 2: You can book working time, off-hours and vacation time.
Context booking (resource), booking (task)



<< outputdir << Table Of Contents >> period (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/rate.resource.html0000644000175000017500000000546012614413013021241 0ustar bernatbernat rate.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< rate << Table Of Contents >> rawhtmlhead >>


Keyword rate (resource)

Purpose
The rate specifies the daily cost of the resource.
Syntax rate (<INTEGER> | <FLOAT>)
Arguments none
Context resource, supplement (resource)



<< rate << Table Of Contents >> rawhtmlhead >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timeformat1.html0000644000175000017500000000565612614413013020717 0ustar bernatbernat timeformat1

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timeformat << Table Of Contents >> timeformat2 >>


Keyword timeformat1

Purpose
Specify an alternative format for the upper header line of calendar or Gantt chart columns.
Syntax timeformat1 <format>
Arguments format [STRING]
See timeformat for details.
Context columns



<< timeformat << Table Of Contents >> timeformat2 >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/start.limit.html0000644000175000017500000000621312614413013020727 0ustar bernatbernat start.limit

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< start (column) << Table Of Contents >> start (report) >>


Keyword start (limit)

Purpose
The start date of the limit interval. It must be within the project time frame.
Syntax start <date>
Arguments date
See date for details.
Context dailymax, dailymin, maximum, minimum, monthlymax, monthlymin, weeklymax, weeklymin



<< start (column) << Table Of Contents >> start (report) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/export.html0000644000175000017500000002646412614413013020010 0ustar bernatbernat export

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< epilog << Table Of Contents >> extend >>


Keyword export

Purpose

The export report looks like a regular TaskJuggler file with the provided input data complemented by the results of the scheduling process. The content of the report can be controlled with the definitions attribute. In case the file contains the project header, a .tjp extension is added to the file name. Otherwise, a .tji extension is used.

The resourceattributes and taskattributes attributes provide even more control over the content of the file.

The export report can be used to share certain tasks or milestones with other projects or to save past resource allocations as immutable part for future scheduling runs. When an export report is included the project IDs of the included tasks must be declared first with the project id property.

Syntax export [<id>] <file name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
file name [STRING]
The name of the report file to generate. It must end with a .tjp or .tji extension, or use . to use the standard output channel.
Context accountreport, export, properties, resourcereport, taskreport, textreport, tracereport

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
accountreport
definitions
end (report) x x
export
formats (export)
hideresource
hidetask
loadunit
period (report)
purge
resourceattributes
resourcereport
rollupresource
rolluptask
scenarios (export)
start (report) x x
taskattributes
taskreport
taskroot (export)
textreport
timezone (export)
tracereport

project export  "Project" "1.0" 2007-01-01 - 2008-01-01 {
  timezone "America/Denver"
}

resource tux "Tux"
resource bob "Bob"

task t1 "Task 1" {
  start 2007-01-01
  effort 20d
  allocate tux
  allocate bob
  limits { dailymax 6h }
}
task t2 "Task 2" {
  start 2007-01-01
  end 2007-06-30
  allocate tux
  allocate bob
  limits { weeklymax 3d }
}

# Export the project as fully scheduled project.
export "FullProject" {
  definitions *
  taskattributes *
  hideresource 0
}

# Export only bookings for 1st week as resource supplements
export "Week1Bookings" {
  definitions -
  start 2007-01-01
  end 2007-01-08
  taskattributes booking
  hideresource 0
}

# Export the scheduled project as Microsoft Project XML format.
export "MS-Project" {
  formats mspxml
  loadunit quarters
}


<< epilog << Table Of Contents >> extend >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/interval2.html0000644000175000017500000001041612614413013020363 0ustar bernatbernat interval2

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< interval1 << Table Of Contents >> interval3 >>


Keyword interval2

Purpose

There are two ways to specify a date interval. The first is the most obvious. A date interval consists of a start and end DATE. Watch out for end dates without a time specification! Date specifications are 0 extended. An end date without a time is expanded to midnight that day. So the day of the end date is not included in the interval! The start and end dates must be separated by a hyphen character.

In the second form specifies the start date and an interval duration. The duration must be prefixed by a plus character.

Syntax <date> (- <date> | + <duration> (min | h | d | w | m | y))
Arguments date
See date for details.
duration
The duration of the interval. May not be 0 and must be a multiple of timingresolution.
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context Global scope



<< interval1 << Table Of Contents >> interval3 >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/statussheetreport.html0000644000175000017500000001411012614413013022260 0ustar bernatbernat statussheetreport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< statussheet << Table Of Contents >> strict (projection) >>


Keyword statussheetreport

Purpose
A status sheet report is a template for a status sheet. It collects all the status information of the top-level task that a resource is responsible for. This report is typically used by managers or team leads to review the time sheet status information and destill it down to a summary that can be forwarded to the next person in the reporting chain. The report will be for the specified trackingscenario.
Syntax statussheetreport [<id>] <file name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
file name [STRING]
The name of the status sheet report file to generate. It must end with a .tji extension, or use . to use the standard output channel.
Context properties

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
end (report) x x
hideresource
hidetask
period (report)
sortresources
sorttasks
start (report) x x



<< statussheet << Table Of Contents >> strict (projection) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/workinghours.project.html0000644000175000017500000001066012614413013022664 0ustar bernatbernat workinghours.project

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< work << Table Of Contents >> workinghours (resource) >>


Keyword workinghours (project)

Purpose

Set the default working hours for all subsequent resource definitions. The standard working hours are 9:00am - 12:00am, 1:00pm - 18:00pm, Monday to Friday. The working hours specification limits the availability of resources to certain time slots of week days.

These default working hours can be replaced with other working hours for individual resources.

Syntax workinghours <weekday> [- <end weekday>] [, <weekday> [- <end weekday>]...] (off | <TIME> - <TIME> [, <TIME> - <TIME>...])
Arguments weekday
Weekday (sun - sat)
end weekday
Weekday (sun - sat). It is included in the interval.
Context project
See also dailyworkinghours, workinghours (resource), workinghours (shift)

project prj "Example Project" "1.0" 2007-01-01 - 2007-03-09 {
  # The following attributes are all optional. They illustrate the
  # default values. These attributes are only needed if you want to
  # specify different values than those listed below.
  timingresolution 60min
  timezone "America/Denver"
  dailyworkinghours 8
  yearlyworkingdays 260.714
  timeformat "%Y-%m-%d %H:%M"
  shorttimeformat "%H:%M"
  currencyformat "(" ")" "," "." 0
  weekstartsmonday
  workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00
  workinghours sat, sun off
  scenario plan "Plan" {
  }
}

task t "Task" {
  start 2007-01-01
}


<< work << Table Of Contents >> workinghours (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/credits.html0000644000175000017500000001034012614413013020106 0ustar bernatbernat credits

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< copyright << Table Of Contents >> currency >>


Keyword credits

Purpose
Book the specified amounts to the account at the specified date. The desciptions are just used for documentary purposes.
Syntax credits <date> <description> <amount> [, <date> <description> <amount>...]
Arguments date
See date for details.
description [STRING]
Short description of the transaction
amount
Amount to be booked.
Context account

account project_cost "Project Costs"
account payments "Customer Payments"{
  credits 2007-01-01 "Customer down payment" 500.0,
          2007-01-14 "1st rate" 2000.0
}

balance project_cost payments

resource tux "Tux" {
  rate 300
}
resource konqui "Konqui" {
  rate 200
}

task items "Room decoration" {
  start 2007-01-06
  # The default account for all tasks
  chargeset project_cost

  task plan "Plan work and buy material" {
    # Upfront material cost
    charge 500.0 onstart
    length 2d
  }
   task remove "Remove old inventory" {
    allocate tux
    allocate konqui
    effort 1d
    depends !plan
  }
  task implement "Arrange new decoration" {
    effort 5d
    allocate tux, konqui
    depends !remove
  }
  task acceptance "Presentation and customer acceptance" {
    duration 5d
    depends !implement
    chargeset payments
    # Customer pays at end of acceptance
    charge 2000.0 onend
  }
}


<< copyright << Table Of Contents >> currency >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/taskroot.export.html0000644000175000017500000000757012614413013021652 0ustar bernatbernat taskroot.export

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< taskroot << Table Of Contents >> text (extend) >>


Keyword taskroot (export)

This keyword is currently in an experimental state. The implementation is probably still incomplete and use of this keyword may lead to wrong results. Do not use this keyword unless you were specifically directed by the developers to try it.

Purpose
Only tasks below the specified root-level tasks are exported. The exported tasks will have the ID of the root-level task stripped from their ID, so that the sub-tasks of the root-level task become top-level tasks in the report file.
Syntax taskroot (<ABSOLUTE_ID> | <ID>)
Arguments none
Context export

project "Taskroot Example" 2005-07-22 - 2005-08-26 {
  timezone "America/Denver"
}

task items "Project breakdown" {
  start 2005-07-22

  task plan "Plan work" {
    length 3d
  }

  task implementation "Implement work" {
    task phase1 "Phase 1" {
      length 5d
      depends !!plan
    }
    task phase2 "Phase 2" {
      length 3d
      depends !phase1
    }
    task phase3 "Phase 3" {
      length 4d
      depends !phase2
    }
  }

  task acceptance "Customer acceptance" {
    duration 5d
    depends !implementation
  }
}

taskreport tasks "My Tasks" {
  formats html
  taskroot items.implementation
}


<< taskroot << Table Of Contents >> text (extend) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/isresource.html0000644000175000017500000000536412614413013020646 0ustar bernatbernat isresource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isongoing << Table Of Contents >> isresponsibilityof >>


Keyword isresource

Purpose
The result is true if the property is a resource.
Syntax isresource ( )
Arguments none
Context functions



<< isongoing << Table Of Contents >> isresponsibilityof >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/numberformat.html0000644000175000017500000001011012614413013021145 0ustar bernatbernat numberformat

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< number (extend) << Table Of Contents >> onend >>


Keyword numberformat

Purpose
These values specify the default format used for all numerical real values.
Syntax numberformat <negativeprefix> <negativesuffix> <thousandsep> <fractionsep> <fractiondigits>
Arguments negativeprefix [STRING]
Prefix for negative numbers
negativesuffix [STRING]
Suffix for negative numbers
thousandsep [STRING]
Separator used for every 3rd digit
fractionsep [STRING]
Separator used to separate the fraction digits
fractiondigits [INTEGER]
Number of fraction digits to show
Context accountreport, nikureport, project, resourcereport, taskreport, textreport, tracereport



<< number (extend) << Table Of Contents >> onend >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/weeklymax.html0000644000175000017500000000771512614413013020473 0ustar bernatbernat weeklymax

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< warn << Table Of Contents >> weeklymin >>


Keyword weeklymax

Purpose
Set a maximum limit for each calendar week.
Syntax weeklymax <value> (min | h | d | w | m | y) [{ <attributes> }]
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context limits, limits (resource), limits (task)

Attributes end (limit), period (limit), resources (limit), start (limit)



<< warn << Table Of Contents >> weeklymin >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/dailymin.html0000644000175000017500000001163712614413013020271 0ustar bernatbernat dailymin

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< dailymax << Table Of Contents >> dailyworkinghours >>


Keyword dailymin

Purpose
Minimum required effort for any calendar day. This value cannot be guaranteed by the scheduler. It is only checked after the schedule is complete. In case the minium required amount has not been reached, a warning will be generated.
Syntax dailymin <value> (min | h | d | w | m | y) [{ <attributes> }]
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context limits, limits (resource), limits (task)

Attributes end (limit), period (limit), resources (limit), start (limit)

resource r1 "R1" {
  # Limit the usage of this resource to a maximum of 2 hours per day,
  # 6 hours per week and 2.5 days per month.
  limits { dailymax 2h weeklymax 6h monthlymax 2.5d }
}

resource r2 "R2"

task t2 "Task 2" {
  start 2007-03-30
  effort 10d
  allocate r2
  limits { dailymin 5h }
}
task t5 "Task 5" {
  start ${projectstart}
  duration 60d
  # allocation is subject to resource limits
  allocate r1
}
task t7 "Task 7" {
  start 2007-06-20
  duration 20d
  allocate r1, r2
  # limits can also be specified per resource
  limits {
    # Limit r1 to half days only
    dailymax 4h { resources r1 }
    # Limit r2 to 6 hours per day
    dailymax 6h { resources r2 }
  }
}


<< dailymax << Table Of Contents >> dailyworkinghours >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/include.properties.html0000644000175000017500000001006512614413013022273 0ustar bernatbernat include.properties

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< include (project) << Table Of Contents >> inherit (extend) >>


Keyword include (properties)

Purpose

Includes the specified file name as if its contents would be written instead of the include property. The only exception is the include statement itself. When the included files contains other include statements or report definitions, the filenames are relative to file where they are defined in. include commands can be used in the project header, at global scope or between property declarations of tasks, resources, and accounts.

For technical reasons you have to supply the optional pair of curly brackets if the include is followed immediately by a macro call that is defined within the included file.

Syntax include <filename> [{ <attributes> }]
Arguments filename [STRING]
Name of the file to include. This must have a .tji extension. The name may have an absolute or relative path. You need to use / characters to separate directories.
Context properties

Attributes accountprefix, reportprefix, resourceprefix, taskprefix



<< include (project) << Table Of Contents >> inherit (extend) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/Software.html0000644000175000017500000003273012614413013020252 0ustar bernatbernat Software

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< Rich_Text_Attributes << Table Of Contents >> Day_To_Day_Juggling >>


6 The TaskJuggler Software

After the installation of a software package the first questions that most users have is "How do I run it?". Many users expect to find an icon on their desktop or an entry in the start menu hierarchy. Don't bother looking for them, you won't find any for TaskJuggler. As we have mentioned before, TaskJuggler is a command line program. All the components are started from a shell by typing in the command name.

This chapter describes the most important TaskJuggler commands and how to use them. If you haven't used command line programs before, don't worry - you will quickly get used to it.

6.1 tj3

tj3 is the main program that you will probably use the most. It reads in your project files and schedules them. In case there are no errors, it will also generate all the reports that you have defined in your project files. It requires at least one parameter, the name of your main project file. In case your main project file is called tutorial.tjp you can process it by typing

tj3 tutorial.tjp

You will see an output similar to the one below.

 > tj3 tutorial.tjp
 TaskJuggler v3.0.0 - A Project Management Software
 
 Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 by Chris Schlaeger
 
 This program is free software; you can redistribute it and/or modify it under
 the terms of version 2 of the GNU General Public License as published by the
 Free Software Foundation.
 
 Reading file tutorial.tjp                                    [      Done      ]
 Preparing scenario Plan                                      [      Done      ]
 Scheduling scenario Plan                                     [      Done      ]
 Checking scenario Plan                                       [      Done      ]
 Preparing scenario Delayed                                   [      Done      ]
 Scheduling scenario Delayed                                  [      Done      ]
 Checking scenario Delayed                                    [      Done      ]
 Report Overview                                              [      Done      ]
 Report Status                                                [      Done      ]
 Report Development                                           [      Done      ]
 Report Deliveries                                            [      Done      ]
 Report ContactList                                           [      Done      ]
 Report ResourceGraph                                         [      Done      ]

tj3 supports a number of runtime options that control the behavior. Please use the --help option to get more details.

tj3 --help

6.2 tj3man

tj3man provides you with a quick access to the syntax reference. If you run it without further parameters it will print a list of all syntax keywords. In some cases, keywords can be used in different contexts with different meanings. These keywords are listed multiple times with the context identifier appended separated by a dot. E. g. the shift keyword can be used in the global context, in the resource, task and timesheet context. It will be listed as

shift
shift.resource
shift.task
shift.timesheet

To get more information about a specific keyword you need to provide it as parameter to tj3man.

tj3man shift.task

The same information that is available in your shell is also available in your web browser.

tj3man --html <keyword>

The latter defaults to using the Mozilla Firefox web browser . Please see

tj3man --help

how to use a different browser. If you omit the keyword, you will get this user manual (browser).

6.3 tj3d

tj3d is the TaskJuggler daemon. It is a program that runs in the background, disconnected from your shell to provide certain services. It can generate reports on demand and processes incoming time sheets or status reports.

Depending on the size of your project the scheduling time can take several minutes or more. Since all operations need to be done on the data of a scheduled project, it makes sense to have this data readily available. This is the job of the TaskJuggler server or daemon in Linux lingo. The program is called tj3d. When started, it automatically disconnects from the terminal and runs in the background. All interactions with the server are done via the TCP/IP protocol. For security reasons, only connections from the same machine (localhost) are accepted. To get access all clients must provide an authentication key. A TaskJuggler server can serve any number of projects. Once a project has been loaded successfully, clients can retrieve the data in form of reports. Projects are identified by their project ID. If a newly added project has the same ID such as an already loaded project, the new project will replace the old project once it was scheduled successfully. Before you start the server, you need to provide a configuration file with some basic settings.

All taskjuggler components can use the same TaskJuggler configuration file. The format is a simple plain text format that follows the YAML specification. The file should be called .taskjuglerrc or taskjuggler.rc. The settings are structured by sections. Section names always start with an underscore.

_global:
  authKey: topsecret
  _log:
    logLevel: 3
    outputLevel: 3

This file sets the authentication key for all TaskJuggler components. You must replace topsecret with your own random character sequence.

For the purpose of this documentation we assume you have a local user called taskjuggler and your project data in /home/taskjuggler/project/prj. Your TaskJuggler configuration should then be put into /home/taskjuggler/project/prj/.taskjugglerrc.

The log section controls the content of the log file. Since the daemon does not have a terminal attached to it, all messages are stored in a file called tj3d.log. For debugging purposes, you can use the -d option to prevent the daemon from disconnecting from the terminal. In this case the outputLevel configuration option controls the amount of details to be printed out.

  • 0: No output
  • 1: Only fatal errors
  • 2: Fatal and normal errors
  • 3: Like 2, but additionally with information messages
  • 4: Like 3, but additionally with debug messages

The configuration file will be searched in the current directory, the current user's home directory or /etc. You can also explicitly tell the server where to find the configuration file with the -c option. See

tj3d --help

for details.

So far, the daemon has not received any kind of security review. We strongly advise you to only use the daemon in a trusted environment with only trusted users!

6.4 tj3client

To control the TaskJuggler server, you need to use the TaskJuggler client. You can use the client to add or remove projects from the server, inquire the status of loaded projects. It can also be used to show the available reports for each project and to generate report or check time or status sheets.

The client must provide the correct authentication key to the server. You need to ensure that it can find the proper configuration file with the authentication key.

tj3client --help

will provide a full list of supported commands. To load a project simply type

tj3client add yourproject.tjp

In case there were no errors

tj3client status

should now list your project.

tj3client list-reports <project_id>

shows a list of available reports for the project with the provided ID. To generate a report, you can type

tj3client report <project_id> <report_id>

A server that is running can be terminated with the following command.

tj3client terminate

6.5 tj3webd

This is a web server to serve the HTML reports of a project to any web browser. The HTML reports are generated on the fly when accessed. tj3webd requires that tj3d is already running on the same machine.

By default, the web server is listening on port 8080. This can be changed in the _global section of the config file.

_global:
  authKey: topsecret
  webServerPort: 8080

To access the HTML reports point your web browser to http://localhost:8080/taskjuggler. This assumes that the server is running on your local machine. You will then see a list of all loaded projects. Click on the project name to get a list of all reports for this project.

WARNING: Please be aware that the web server when you have started it can be accessed by anybody on your local machine and by anybody that can reach your machine over the network! It will serve all reports of all projects that are hosted by the TaskJuggler daemon.



<< Rich_Text_Attributes << Table Of Contents >> Day_To_Day_Juggling >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/end.html0000644000175000017500000000700612614413013017224 0ustar bernatbernat end

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< enabled << Table Of Contents >> end (column) >>


Keyword end

Purpose

The end attribute provides a guideline to the scheduler when the task should end. It will never end later, but it may end earlier when allocated resources are not available that long. When an end date is provided for a container task, it will be passed down to ALAP task that don't have a well defined end criteria.

Setting an end date will implicitely set the scheduling policy for this task to ALAP.

Syntax end <date>
Arguments date
See date for details.
Context task, supplement (task)

project export  "Project" "1.0" 2007-01-01 - 2008-01-01 {
  timezone "America/Denver"
}

resource tux "Tux"
resource bob "Bob"

task t2 "Task 2" {
  start 2007-01-01
  end 2007-06-30
  allocate tux
  allocate bob
  limits { weeklymax 3d }
}


<< enabled << Table Of Contents >> end (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/columns.html0000644000175000017500000001023512614413013020134 0ustar bernatbernat columns

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< columnid << Table Of Contents >> complete >>


Keyword columns

Purpose
Specifies which columns shall be included in a report. Some columns show values that are constant over the course of the project. Other columns show calculated values that depend on the time period that was chosen for the report.
Syntax columns <columnid> [{ <attributes> }] [, <columnid> [[, ... ]]...]
Arguments columnid
See columnid for details.
Context accountreport, resourcereport, taskreport, textreport, tracereport

Attributes cellcolor (column), celltext (column), end (column), fontcolor (column), halign (column), listitem (column), listtype (column), period (column), scale (column), start (column), timeformat1, timeformat2, title (column), tooltip (column), width (column)



<< columnid << Table Of Contents >> complete >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/mandatory.html0000644000175000017500000000560612614413013020460 0ustar bernatbernat mandatory

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< managers << Table Of Contents >> maxend >>


Keyword mandatory

Purpose
Makes a resource allocation mandatory. This means, that for each time slot only then resources are allocated when all mandatory resources are available. So either all mandatory resources can be allocated for the time slot, or no resource will be allocated.
Syntax mandatory
Arguments none
Context allocate



<< managers << Table Of Contents >> maxend >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/currency.html0000644000175000017500000001023612614413013020307 0ustar bernatbernat currency

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< credits << Table Of Contents >> currencyformat >>


Keyword currency

Purpose
The default currency unit.
Syntax currency <symbol>
Arguments symbol [STRING]
Currency symbol
Context project

project simple "Simple Project" "1.0" 2007-01-01 - 2007-01-30 {
  timezone "America/Denver"
  currency "USD"
}

account project_cost "Project Costs"
account payments "Customer Payments"{
  credits 2007-01-01 "Customer down payment" 500.0,
          2007-01-14 "1st rate" 2000.0
}

balance project_cost payments

resource tux "Tux" {
  rate 300
}
resource konqui "Konqui" {
  rate 200
}

task items "Room decoration" {
  start 2007-01-06
  # The default account for all tasks
  chargeset project_cost

  task plan "Plan work and buy material" {
    # Upfront material cost
    charge 500.0 onstart
    length 2d
  }
   task remove "Remove old inventory" {
    allocate tux
    allocate konqui
    effort 1d
    depends !plan
  }
  task implement "Arrange new decoration" {
    effort 5d
    allocate tux, konqui
    depends !remove
  }
  task acceptance "Presentation and customer acceptance" {
    duration 5d
    depends !implement
    chargeset payments
    # Customer pays at end of acceptance
    charge 2000.0 onend
  }
}

accountreport "Account-1" {
  formats html
  timeformat "%d-%M-%y"
  columns index, name, weekly
}

accountreport "Account-2" {
  formats html
  timeformat "%d-%M-%y"
  columns index,
          name,
          weekly {
            celltext 1 "<-query attribute='turnover'->"
          }
}


<< credits << Table Of Contents >> currencyformat >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/number.extend.html0000644000175000017500000000703412614413013021235 0ustar bernatbernat number.extend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< now << Table Of Contents >> numberformat >>


Keyword number (extend)

Purpose
Extend the property with a new attribute of type number. Possible values for this attribute could be integer or floating point numbers.
Syntax number <id> <name> [{ <attributes> }]
Arguments id [ID]
The ID of the new attribute. It can be used like the built-in IDs.
name [STRING]
The name of the new attribute. It is used as header in report columns and the like.
Context extend

Attributes inherit (extend), scenariospecific (extend)



<< now << Table Of Contents >> numberformat >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/purge.html0000644000175000017500000001012612614413013017575 0ustar bernatbernat purge

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< properties << Table Of Contents >> rate >>


Keyword purge

Purpose

Many attributes inherit their values from the enclosing property or the global scope. In certain circumstances, this is not desirable, e. g. for list attributes. A list attribute is any attribute that takes a comma separated list of values as argument. allocate and flags.task are good examples of commonly used list attributes. By defining values for such a list attribute in a nested property, the new values will be appended to the list that was inherited from the enclosing property. The purge attribute resets any attribute to its default value. A subsequent definition for the attribute within the property will then add their values to an empty list. The value of the enclosing property is not affected by purge.

For scenario specific attributes, an optional scenario ID can be specified before the attribute ID. If it's missing, the default (first) scenario will be used.

Syntax purge <attribute> <ID>
Arguments attribute
Any name of a list attribute
Context accountreport, export, resource, supplement (resource), resourcereport, task, supplement (task), taskreport, textreport, tracereport



<< properties << Table Of Contents >> rate >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/macro.html0000644000175000017500000001145712614413013017564 0ustar bernatbernat macro

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< logicalflagexpression << Table Of Contents >> managers >>


Keyword macro

Purpose

Defines a text fragment that can later be inserted by using the specified ID. To insert the text fragment anywhere in the text you need to write ${ID}.The body is not optional. It must be enclosed in square brackets. Macros can be declared like this:

macro FOO [ This text ]

If later ${FOO} is found in the project file, it is expanded to This text.

Macros may have arguments. Arguments are accessed with special macros with numbers as names. The number specifies the index of the argument.

macro FOO [ This ${1} text ]

will expand to This stupid text if called as ${FOO "stupid"}. Macros may call other macros. All macro arguments must be enclosed by double quotes. In case the argument contains a double quote, it must be escaped by a slash (/).

User defined macro IDs must have at least one uppercase letter as all lowercase letter IDs are reserved for built-in macros.

To terminate the macro definition, the ] must be the last character in the line. If there are any other characters trailing it (even spaces or comments) the ] will not be considered the end of the macro definition.

In macro calls the macro names can be prefixed by a question mark. In this case the macro will expand to nothing if the macro is not defined. Otherwise the undefined macro would be flagged with an error message.

The macro call

${?foo}

will expand to nothing if foo is undefined.

Syntax macro <ID> <MACRO>
Arguments none
Context properties

project "Example Project" 2008-01-18 +2m {
  timezone "America/Denver"
}

macro allocateGroup [
  allocate tux1, tux2
]

resource tux1 "Tux1"
resource tux2 "Tux2"

task t1 "Task1" {
  start ${projectstart}  # built-in macro
}

task t2 "Task2" {
  depends !t1
  effort 20d
  ${allocateGroup}
}


<< logicalflagexpression << Table Of Contents >> managers >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/alertlevels.html0000644000175000017500000001170112614413013020775 0ustar bernatbernat alertlevels

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< alert << Table Of Contents >> allocate >>


Keyword alertlevels

This keyword has not yet been fully tested yet. You are welcome to try it, but it may lead to wrong results. The syntax may still change with future versions. The developers appreciate any feedback on this keyword.

Purpose
By default TaskJuggler supports the pre-defined alert levels: green, yellow and red. This attribute can be used to replace them with your own set of alert levels. You can define any number of levels, but you need to define at least two and they must be specified in ascending order from the least severity to highest severity. Additionally, you need to provide a 15x15 pixel image file with the name flag-X.png for each level where X matches the ID of the alert level. These files need to be in the icons directory to be found by the browser when showing HTML reports.
Syntax alertlevels <ID> <color name> <color> [, <ID> <color name> <color>...]
Arguments ID
A unique ID for the alert level
color name [STRING]
A unique name of the alert level color
color [STRING]
The RGB color values of the color. The following formats are supported: #RGB and #RRGGBB. Where R, G, B are hexadecimal values. See Wikipedia for more details.
Context project

project "Alert Levels" 2011-11-24 +2m {
  alertlevels green "Low" "#2AA46C",
              blue "Guarded" "#457CC4",
              yellow "Elevated" "#F1D821",
              orange "High" "#F99836",
              red "Severe" "#E43745"
}

task "Holiday Season" {
  journalentry 2011-11-24 "All safe unless you are a turkey" {
    alert green
  }
  journalentry 2011-11-27 "Strange lights appearing everywhere" {
    alert blue
  }
  journalentry 2011-12-01 "Alarm bells heared" {
    alert yellow
  }
  journalentry 2011-12-20 "Everybody has strange packages" {
    alert orange
  }
  journalentry 2011-12-25 "Guy with red coat entered through chimney" {
    alert red
  }
}


<< alert << Table Of Contents >> allocate >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/resources.limit.html0000644000175000017500000000743312614413013021611 0ustar bernatbernat resources.limit

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< resourceroot << Table Of Contents >> responsible >>


Keyword resources (limit)

Purpose
When limits are used in a task context, the limits can be restricted to a list of resources that are allocated to the task. In that case each listed resource will not be allocated more than the specified upper limit. Lower limits have no impact on the scheduler but do generate a warning when not met. All specified resources must be leaf resources.
Syntax resources <resource> [, <resource>...]
Arguments resource
The ID of a leaf resource
Context dailymax, dailymin, maximum, minimum, monthlymax, monthlymin, weeklymax, weeklymin

task t7 "Task 7" {
  start 2007-06-20
  duration 20d
  allocate r1, r2
  # limits can also be specified per resource
  limits {
    # Limit r1 to half days only
    dailymax 4h { resources r1 }
    # Limit r2 to 6 hours per day
    dailymax 6h { resources r2 }
  }
}


<< resourceroot << Table Of Contents >> responsible >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/projectid.html0000644000175000017500000000566712614413013020454 0ustar bernatbernat projectid

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< project << Table Of Contents >> projectid (task) >>


Keyword projectid

Purpose
This declares a new project id and activates it. All subsequent task definitions will inherit this ID. The tasks of a project can have different IDs. This is particularly helpful if the project is merged from several sub projects that each have their own ID.
Syntax projectid <ID>
Arguments none
Context properties



<< project << Table Of Contents >> projectid (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/taskprefix.html0000644000175000017500000000570312614413013020640 0ustar bernatbernat taskprefix

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< taskattributes << Table Of Contents >> taskreport >>


Keyword taskprefix

Purpose
This attribute can be used to insert the tasks of the included file as sub-task of the task specified by ID. The parent task must already be defined.
Syntax taskprefix <task ID>
Arguments task ID
The absolute ID of an already defined task.
Context include (properties)



<< taskattributes << Table Of Contents >> taskreport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/maxend.html0000644000175000017500000000601112614413013017725 0ustar bernatbernat maxend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< mandatory << Table Of Contents >> maximum >>


Keyword maxend

Purpose
Specifies the maximum wanted end time of the task. The value is not used during scheduling, but is checked after all tasks have been scheduled. If the end of the task is later than the specified value, then an error is reported.
Syntax maxend <date>
Arguments date
See date for details.
Context task, supplement (task)



<< mandatory << Table Of Contents >> maximum >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/resourceprefix.html0000644000175000017500000000600112614413013021515 0ustar bernatbernat resourceprefix

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< resourceattributes << Table Of Contents >> resourcereport >>


Keyword resourceprefix

Purpose
This attribute can be used to insert the resources of the included file as sub-resource of the resource specified by ID. The parent resource must already be defined.
Syntax resourceprefix <resource ID>
Arguments resource ID
The ID of an already defined resource
Context include (properties)



<< resourceattributes << Table Of Contents >> resourcereport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/projectids.html0000644000175000017500000000556112614413013020630 0ustar bernatbernat projectids

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< projectid (task) << Table Of Contents >> projection >>


Keyword projectids

Purpose
Declares a list of project IDs. When an include file that was generated from another project brings different project IDs, these need to be declared first.
Syntax projectids <ID> [, <ID>...]
Arguments none
Context properties



<< projectid (task) << Table Of Contents >> projection >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/ismilestone.html0000644000175000017500000000554412614413013021016 0ustar bernatbernat ismilestone

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isleaf << Table Of Contents >> isongoing >>


Keyword ismilestone

Purpose
The result is true if the property is a milestone in the provided scenario.
Syntax ismilestone ( <Scenario ID> )
Arguments Scenario ID [ID]
A scenario ID
Context functions



<< isleaf << Table Of Contents >> isongoing >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/flags.statussheet.html0000644000175000017500000000557412614413013022135 0ustar bernatbernat flags.statussheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< flags (resource) << Table Of Contents >> flags (task) >>


Keyword flags (statussheet)

Purpose
Status sheet entries can have flags attached to them. These can be used to include only entries in a report that have a certain flag.
Syntax flags <ID> [, <ID>...]
Arguments none
Context status (statussheet)



<< flags (resource) << Table Of Contents >> flags (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/left.html0000644000175000017500000000676412614413013017422 0ustar bernatbernat left

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< leaves << Table Of Contents >> length >>


Keyword left

Purpose
This attribute defines the left margin section of the textreport. The text will be interpreted as Rich Text. The margin will not span the header or footer sections.
Syntax left <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport

project "Test" 2011-12-11 +1m

task "Foo"

textreport frame "textreport" {
  taskreport r1 ""
  taskreport r2 ""

  formats html
  header -8<-
  This is the header
  ----
  ->8-
  left "<[report id='frame.r1']>"
  center "Center"
  right "<[report id='frame.r2']>"
  footer -8<-
  ----
  This is the footer
  ->8-
}


<< leaves << Table Of Contents >> length >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/flags.html0000644000175000017500000000545412614413013017557 0ustar bernatbernat flags

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< fail << Table Of Contents >> flags (account) >>


Keyword flags

Purpose
Declare one or more flag for later use. Flags can be used to mark tasks, resources or other properties to filter them in reports.
Syntax flags <ID> [, <ID>...]
Arguments none
Context properties



<< fail << Table Of Contents >> flags (account) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/isfeatureof.html0000644000175000017500000000615412614413013020775 0ustar bernatbernat isfeatureof

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isdutyof << Table Of Contents >> isleaf >>


Keyword isfeatureof

Purpose
If the provided task or any of its sub-tasks depend on this task or any of its sub-tasks, we call this task a feature of the provided task.
Syntax isfeatureof ( <Task ID> , <Scenario ID> )
Arguments Task ID [ID]
The ID of a defined task
Scenario ID [ID]
A scenario ID
Context functions



<< isdutyof << Table Of Contents >> isleaf >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/scenarios.export.html0000644000175000017500000000565512614413013021774 0ustar bernatbernat scenarios.export

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< scenarios << Table Of Contents >> scenariospecific (extend) >>


Keyword scenarios (export)

Purpose
List of scenarios that should be included in the report. By default, all scenarios will be included. This attribute can be used to limit the included scenarios to a defined list.
Syntax scenarios <ID> [, <ID>...]
Arguments none
Context export



<< scenarios << Table Of Contents >> scenariospecific (extend) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/include.macro.html0000644000175000017500000000701012614413013021174 0ustar bernatbernat include.macro

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< icalreport << Table Of Contents >> include (project) >>


Keyword include (macro)

Purpose

Includes the specified file name as if its contents would be written instead of the include property. The only exception is the include statement itself. When the included files contains other include statements or report definitions, the filenames are relative to file where they are defined in.

The included file may only contain macro definitions. This version of the include directive can only be used before the project header.

Syntax include <filename>
Arguments filename [STRING]
Name of the file to include. This must have a .tji extension. The name may have an absolute or relative path. You need to use / characters to separate directories.
Context Global scope



<< icalreport << Table Of Contents >> include (project) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/reference.extend.html0000644000175000017500000000711012614413013021676 0ustar bernatbernat reference.extend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< rawhtmlhead << Table Of Contents >> remaining >>


Keyword reference (extend)

Purpose
Extend the property with a new attribute of type reference. A reference is a URL and an optional text that will be shown instead of the URL if needed.
Syntax reference <id> <name> [{ <attributes> }]
Arguments id [ID]
The ID of the new attribute. It can be used like the built-in IDs.
name [STRING]
The name of the new attribute. It is used as header in report columns and the like.
Context extend

Attributes inherit (extend), scenariospecific (extend)



<< rawhtmlhead << Table Of Contents >> remaining >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/cellcolor.column.html0000644000175000017500000001242212614413013021726 0ustar bernatbernat cellcolor.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< caption << Table Of Contents >> celltext (column) >>


Keyword cellcolor (column)

Purpose
Specifies an alternative background color for the cells of this column. The logical expression specifies for which cells the color should be used. If multiple cellcolor patterns are provided for a column, the first matching one is used for each cell.
Syntax cellcolor (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none)) <color>
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
color [STRING]
The RGB color values of the color. The following formats are supported: #RGB and #RRGGBB. Where R, G, B are hexadecimal values. See Wikipedia for more details.
Context columns



<< caption << Table Of Contents >> celltext (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/columnid.html0000644000175000017500000005362012614413013020273 0ustar bernatbernat columnid

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< chargeset << Table Of Contents >> columns >>


Keyword columnid

Purpose
This is a comprehensive list of all pre-defined columns. In addition to the listed IDs all user defined attributes can be used as column IDs.
Syntax (activetasks | annualleave | annualleavebalance | alert | alertmessages | alertsummaries | alerttrend | balance | bsi | chart | closedtasks | competitorcount | competitors | complete | completed | criticalness | cost | daily | directreports | duration | duties | efficiency | effort | effortdone | effortleft | email | end | flags | followers | freetime | freework | fte | gauge | headcount | hierarchindex | hourly | id | index | inputs | journal | journal_sub | journalmessages | journalsummaries | line | managers | maxend | maxstart | minend | minstart | monthly | no | name | note | opentasks | pathcriticalness | precursors | priority | quarterly | rate | reports | resources | responsible | revenue | scenario | scheduling | seqno | sickleave | specialleave | start | status | targets | turnover | wbs | unpaidleave | weekly | yearly)
Arguments activetasks
The number of sub-tasks (including the current task) that are active in the reported time period. Active means that they are ongoing at the current time or now date.
annualleave
The number of annual leave units within the reported time period. The unit can be adjusted with loadunit.
annualleavebalance
The balance of the annual leave at the end of the reporting interval. The unit can be adjusted with loadunit.
alert
The alert level of the property that was reported with the date closest to the end date of the report. Container properties that don't have their own alert level reported with a date equal or newer than the alert levels of all their sub properties will get the highest alert level of their direct sub properties.
alertmessages
Deprecated. Please use journal instead
alertsummaries
Deprecated. Please use journal instead
alerttrend
Shows how the alert level at the end of the report period compares to the alert level at the begining of the report period. Possible values are Up, Down or Flat.
balance
The account balance at the beginning of the reported period. This is the balance before any transactions of the reported period have been credited.
bsi
The hierarchical or work breakdown structure index (i. e. 1.2.3)
chart
A Gantt chart. This column type requires all lines to have the same fixed height. This does not work well with rich text columns in some browsers. Some show a scrollbar for the compressed table cells, others don't. It is recommended, that you don't use rich text columns in conjuction with the chart column.
closedtasks
The number of sub-tasks (including the current task) that have been closed during the reported time period. Closed means that they have and end date before the current time or now date.
competitorcount
The number of tasks that have successfully competed for the same resources and have potentially delayed the completion of this task.
competitors
A list of tasks that have successfully competed for the same resources and have potentially delayed the completion of this task.
complete
The completion degree of a task. Unless a completion degree is manually provided, this is a computed value relative the now date of the project. A task that has ended before the now date is always 100% complete. A task that starts at or after the now date is always 0%. For effort based task the computation degree is the percentage of done effort of the overall effort. For other leaf task, the completion degree is the percentage of the already passed duration of the overall task duration. For container task, it's always the average of the direct sub tasks. If the sub tasks consist of a mixture of effort and non-effort tasks, the completion value is only of limited value.
completed
Deprecated alias for complete
criticalness
A measure for how much effort the resource is allocated for, orhow strained the allocated resources of a task are
cost
The cost of the task or resource. The use of this column requires that a cost account has been set for the report using the balance attribute.
daily
A group of columns with one column for each day
directreports

The resources that have this resource assigned as manager.

The list can be customized by the listitem attribute.

duration
The duration of a task
duties
List of tasks that the resource is allocated to
efficiency
Measure for how efficient a resource can perform tasks
effort
The allocated effort during the reporting period
effortdone
The already completed effort as of now
effortleft
The remaining allocated effort as of now
email
The email address of a resource
end
The end date of a task
flags
List of attached flags
followers

A list of tasks that depend on the current task. The list contains the names, the IDs, the date and the type of dependency. For the type the following symbols are used for <dep>.

  • ]->[: End-to-Start dependency
  • [->[: Start-to-Start dependency
  • ]->]: End-to-End dependency
  • [->]: Start-to-End dependency

The list can be customized by the listitem attribute. The dependency symbol can be generated via the dependency attribute inthe query, the target date via the date attribute.

freetime
The amount of unallocated work time of a resource during the reporting period.
freework
The amount of unallocated work capacity of a resource during the reporting period. This is the product of unallocated work time times the efficiency of the resource.
fte
The Full-Time-Equivalent of a resource or group. This is the ratio of the resource working time and the global working time. Working time is defined by working hours and leaves. The FTE value can vary over time and is calculated for the report interval or the user specified interval.
gauge
When complete values have been provided to capture the actual progress on tasks, the gauge column will list whether the task is ahead of, behind or on schedule.
headcount

For resources this is the headcount number of the resource or resource group. For a single resource this is the efficiency rounded to the next integer. For a group it is the sum of the sub resources headcount.

For tasks it's the number of different resources allocated to the task during the report interval. Resources are weighted with their rounded efficiencies.

hierarchindex
Deprecated alias for bsi
hourly
A group of columns with one column for each hour
id
The id of the item
index
The index of the item based on the nesting hierachy
inputs

A list of milestones that are a prerequiste for the current task. For container tasks it will also include the inputs of the child tasks. Inputs may not have any predecessors.

The list can be customized by the listitem attribute.

journal
The journal entries for the task or resource for the reported interval. The generated text can be customized with the journalmode, journalattributes, hidejournalentry and sortjournalentries. If used in queries without a property context, the journal for the complete project is generated.
journal_sub
Deprecated. Please use journal instead
journalmessages
Deprecated. Please use journal instead
journalsummaries
Deprecated. Please use journal instead
line
The line number in the report
managers

A list of managers that the resource reports to.

The list can be customized by the listitem attribute.

maxend
The latest allowed end of a task
maxstart
The lastest allowed start of a task
minend
The earliest allowed end of a task
minstart
The earliest allowed start of a task
monthly
A group of columns with one column for each month
no
The object line number in the report (Cannot be used for sorting!)
name
The name or description of the item
note
The note attached to a task
opentasks
The number of sub-tasks (including the current task) that have not yet been closed during the reported time period. Closed means that they have and end date before the current time or now date.
pathcriticalness
The criticalness of the task with respect to all the paths that it is a part of.
precursors

A list of tasks the current task depends on. The list contains the names, the IDs, the date and the type of dependency. For the type the following symbols are used

  • ]->[: End-to-Start dependency
  • [->[: Start-to-Start dependency
  • ]->]: End-to-End dependency
  • [->]: Start-to-End dependency

The list can be customized by the listitem attribute. The dependency symbol can be generated via the dependency attribute inthe query, the target date via the date attribute.

priority
The priority of a task
quarterly
A group of columns with one column for each quarter
rate
The daily cost of a resource.
reports

All resources that have this resource assigned as a direct or indirect manager.

The list can be customized by the listitem attribute.

resources

A list of resources that are assigned to the task in the report time frame.

The list can be customized by the listitem attribute.

responsible

The responsible people for this task.

The list can be customized by the listitem attribute.

revenue
The revenue of the task or resource. The use of this column requires that a revenue account has been set for the report using the balance attribute.
scenario
The name of the scenario
scheduling
The scheduling mode of the leaf tasks. ASAP tasks are scheduled start to end while ALAP tasks are scheduled end to start.
seqno
The index of the item based on the declaration order
sickleave
The number of sick leave units within the reported time period. The unit can be adjusted with loadunit.
specialleave
The number of special leave units within the reported time period. The unit can be adjusted with loadunit.
start
The start date of the task
status
The status of a task. It is determined based on the current date or the date specified by now.
targets

A list of milestones that depend on the current task. For container tasks it will also include the targets of the child tasks. Targets may not have any follower tasks.

The list can be customized by the listitem attribute.

turnover
The financial turnover of an account during the reporting interval.
wbs
Deprecated alias for bsi.
unpaidleave
The number of unpaid leave units within the reported time period. The unit can be adjusted with loadunit.
weekly
A group of columns with one column for each week
yearly
A group of columns with one column for each year
Context Global scope



<< chargeset << Table Of Contents >> columns >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/sortjournalentries.html0000644000175000017500000000774412614413013022443 0ustar bernatbernat sortjournalentries

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< sortaccounts << Table Of Contents >> sortresources >>


Keyword sortjournalentries

Purpose

Determines how the entries in a journal are sorted. Multiple criteria can be specified as a comma separated list. If one criteria is not sufficient to sort a group of journal entries, the next criteria will be used to sort the entries in this group.

The following values are supported:

  • date.down: Sort descending order by the date of the journal entry
  • date.up: Sort ascending order by the date of the journal entry
  • alert.down: Sort in descending order by the alert level of the journal entry
  • alert.up: Sort in ascending order by the alert level of the journal entry property.down: Sort in descending order by the task or resource the journal entry is associated with
  • property.up: Sort in ascending order by the task or resource the journal entry is associated with
Syntax sortjournalentries <ABSOLUTE_ID> [, <ABSOLUTE_ID>...]
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< sortaccounts << Table Of Contents >> sortresources >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/maxstart.html0000644000175000017500000000602312614413013020317 0ustar bernatbernat maxstart

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< maximum << Table Of Contents >> milestone >>


Keyword maxstart

Purpose
Specifies the maximum wanted start time of the task. The value is not used during scheduling, but is checked after all tasks have been scheduled. If the start of the task is later than the specified value, then an error is reported.
Syntax maxstart <date>
Arguments date
See date for details.
Context task, supplement (task)



<< maximum << Table Of Contents >> milestone >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/hasalert.html0000644000175000017500000000617612614413013020270 0ustar bernatbernat hasalert

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< halign (right) << Table Of Contents >> header >>


Keyword hasalert

Purpose
Will evaluate to true if the current property has a current alert message within the report time frame and with at least the provided alert level.
Syntax hasalert ( <Level> , <date> )
Arguments Level [INTEGER]
The minimum required alert level to be considered.
date
See date for details.
Context functions



<< halign (right) << Table Of Contents >> header >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/icalreport.html0000644000175000017500000001341712614413013020625 0ustar bernatbernat icalreport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< hidetask << Table Of Contents >> include (macro) >>


Keyword icalreport

Purpose
Generates an RFC5545 compliant iCalendar file. This file can be used to export task information to calendar applications or other tools that read iCalendar files.
Syntax icalreport <file name> <STRING> [{ <attributes> }]
Arguments file name
The name of the report file to generate without an extension. Use . to use the standard output channel.
Context properties

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
end (report) x x
hidejournalentry
hideresource
hidetask
period (report)
rollupresource
rolluptask
scenario (ical)
start (report) x x



<< hidetask << Table Of Contents >> include (macro) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/journalmode.html0000644000175000017500000001342212614413013020774 0ustar bernatbernat journalmode

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< journalentry << Table Of Contents >> leaveallowance >>


Keyword journalmode

Purpose
This attribute controls what journal entries are aggregated into the report.
Syntax journalmode (journal | journal_sub | status_dep | status_down | status_up | alerts_dep | alerts_down)
Arguments journal
This is the regular journal. It contains all journal entries that are dated in the query interval. If a property is given, only entries of this property are included. Without a property context, all the project entries are included unless hidden by other attributes like hidejournalentry.
journal_sub
This mode only yields entries if used in the context of a task. It contains all journal entries that are dated in the query interval for the task and all its sub tasks.
status_dep
In this mode only the last entries before the report end date for each property and all its sub-properties and their dependencies are included. If there are multiple entries at the exact same date, then all these entries are included.
status_down
In this mode only the last entries before the report end date for each property and all its sub-properties are included. If there are multiple entries at the exact same date, then all these entries are included.
status_up
In this mode only the last entries before the report end date for each property are included. If there are multiple entries at the exact same date, then all these entries are included. If any of the parent properties has a more recent entry that is still before the report end date, no entries will be included.
alerts_dep
In this mode only the last entries before the report end date for the context property and all its sub-properties and their dependencies is included. If there are multiple entries at the exact same date, then all these entries are included. In contrast to the status_down mode, only entries with an alert level above the default level, and only those with the highest overall alert level are included.
alerts_down
In this mode only the last entries before the report end date for the context property and all its sub-properties is included. If there are multiple entries at the exact same date, then all these entries are included. In contrast to the status_down mode, only entries with an alert level above the default level, and only those with the highest overall alert level are included.
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< journalentry << Table Of Contents >> leaveallowance >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/listtype.column.html0000644000175000017500000000651012614413013021626 0ustar bernatbernat listtype.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< listitem (column) << Table Of Contents >> loadunit >>


Keyword listtype (column)

Purpose
Specifies what type of list should be used. This attribute only affects columns that contain a list of items.
Syntax listtype (bullets | comma | numbered)
Arguments bullets
List items as bullet list
comma
List items as comma separated list
numbered
List items as numbered list
Context columns
See also listitem (column)



<< listitem (column) << Table Of Contents >> loadunit >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/hidetask.html0000644000175000017500000001264412614413013020256 0ustar bernatbernat hidetask

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< hideresource << Table Of Contents >> icalreport >>


Keyword hidetask

Purpose
Do not include tasks that match the specified logical expression. If the report is sorted in tree mode (default) then enclosing tasks are listed even if the expression matches the task.
Syntax hidetask (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context accountreport, export, icalreport, nikureport, resourcereport, statussheetreport, tagfile, taskreport, textreport, timesheetreport, tracereport
See also sorttasks



<< hideresource << Table Of Contents >> icalreport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/resourceroot.html0000644000175000017500000000734612614413013021220 0ustar bernatbernat resourceroot

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< resourcereport << Table Of Contents >> resources (limit) >>


Keyword resourceroot

Purpose
Only resources below the specified root-level resources are exported. The exported resources will have the ID of the root-level resource stripped from their ID, so that the sub-resourcess of the root-level resource become top-level resources in the report file.
Syntax resourceroot <resource>
Arguments resource [ID]
The ID of a defined resource
Context accountreport, resourcereport, taskreport, textreport, tracereport

project "Test" "1.0" 2010-11-10 +2m {
  timezone "America/Denver"
}

resource org "Org" {
  resource team1 "Team1" {
    resource r1 "R1"
    resource r2 "R2"
    resource r3 "R3"
  }
  resource r4 "R4"
}
resource r5 "R5"

task "T"

resourcereport "ResourceRoot" {
  formats html
  columns name, id
  # Only list the Team1 as if it would be a top-level resource.
  resourceroot team1
}


<< resourcereport << Table Of Contents >> resources (limit) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/celltext.column.html0000644000175000017500000001234112614413013021574 0ustar bernatbernat celltext.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< cellcolor (column) << Table Of Contents >> center >>


Keyword celltext (column)

Purpose
Specifies an alternative content that is used for the cells of the column. Usually such a text contains a query function. Otherwise all cells of the column will have the same fixed value. The logical expression specifies for which cells the text should be used. If multiple celltext patterns are provided for a column, the first matching one is taken for each cell.
Syntax celltext (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none)) <text>
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
text [STRING]
Alterntive cell text specified as Rich Text
Context columns



<< cellcolor (column) << Table Of Contents >> center >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/fontcolor.column.html0000644000175000017500000001241012614413013021752 0ustar bernatbernat fontcolor.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< flags (timesheet) << Table Of Contents >> footer >>


Keyword fontcolor (column)

Purpose
Specifies an alternative font color for the cells of this column. The logical expression specifies for which cells the color should be used. If multiple fontcolor patterns are provided for a column, the first matching one is used for each cell.
Syntax fontcolor (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none)) <color>
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
color [STRING]
The RGB color values of the color. The following formats are supported: #RGB and #RRGGBB. Where R, G, B are hexadecimal values. See Wikipedia for more details.
Context columns



<< flags (timesheet) << Table Of Contents >> footer >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/limits.task.html0000644000175000017500000001036212614413013020717 0ustar bernatbernat limits.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< limits (resource) << Table Of Contents >> listitem (column) >>


Keyword limits (task)

Purpose
Set per-interval allocation limits for the task. This setting affects all allocations for this task.
Syntax limits [{ <attributes> }]
Arguments none
Context task, supplement (task)

Attributes dailymax, dailymin, maximum, minimum, monthlymax, monthlymin, weeklymax, weeklymin

resource r1 "R1" {
  # Limit the usage of this resource to a maximum of 2 hours per day,
  # 6 hours per week and 2.5 days per month.
  limits { dailymax 2h weeklymax 6h monthlymax 2.5d }
}

resource r2 "R2"

task t3 "Task 3" {
  start 2007-03-30
  effort 10d
  allocate r2
  limits { weeklymax 2d }
}
task t5 "Task 5" {
  start ${projectstart}
  duration 60d
  # allocation is subject to resource limits
  allocate r1
}
task t6 "Task 6" {
  start ${projectstart}
  duration 60d
  allocate r2
  limits { dailymax 4h weeklymax 3d monthlymax 2w }
}
task t7 "Task 7" {
  start 2007-06-20
  duration 20d
  allocate r1, r2
  # limits can also be specified per resource
  limits {
    # Limit r1 to half days only
    dailymax 4h { resources r1 }
    # Limit r2 to 6 hours per day
    dailymax 6h { resources r2 }
  }
}


<< limits (resource) << Table Of Contents >> listitem (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/logicalflagexpression.html0000644000175000017500000001051512614413013023041 0ustar bernatbernat logicalflagexpression

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< logicalexpression << Table Of Contents >> macro >>


Keyword logicalflagexpression

Purpose

A logical flag expression is a combination of operands and mathematical operations. The final result of a logical expression is always true or false. Logical expressions are used the reduce the properties in a report to a certain subset or to select alternatives for the cell content of a table. When used with attributes like hidejournalentry the logical expression evaluates to true for a certain property, this property is hidden or rolled-up in the report.

Operands must be previously declared flags or another logical expression. When you combine logical operations to a more complex expression, the operators are evaluated from left to right. a | b & c is identical to (a | b) & c. It's highly recommended that you always use brackets to control the evaluation sequence. Currently, TaskJuggler does not support the concept of operator precedence or right-left associativity. This may change in the future.

Syntax <operand> [(| | &) <operand>...]
Arguments operand
An operand is a declared flag. An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.
|
The 'or' operator
&
The 'and' operator
Context Global scope
See also functions



<< logicalexpression << Table Of Contents >> macro >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/maximum.html0000644000175000017500000001000612614413013020125 0ustar bernatbernat maximum

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< maxend << Table Of Contents >> maxstart >>


Keyword maximum

Purpose
Set a maximum limit for the specified period. You must ensure that the overall effort can be achieved!
Syntax maximum <value> (min | h | d | w | m | y) [{ <attributes> }]
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context limits, limits (resource), limits (task)

Attributes end (limit), period (limit), resources (limit), start (limit)



<< maxend << Table Of Contents >> maxstart >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/resourcereport.html0000644000175000017500000004533512614413013021550 0ustar bernatbernat resourcereport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< resourceprefix << Table Of Contents >> resourceroot >>


Keyword resourcereport

Purpose

The report lists resources and their respective values in a table. The task that are the resources are allocated to can be listed as well. To reduce the list of included resources, you can use the hideresource, rollupresource or resourceroot attributes. The order of the task can be controlled with sortresources. If the first sorting criteria is tree sorting, the parent resources will always be included to form the tree. Tree sorting is the default. You need to change it if you do not want certain parent resources to be included in the report.

By default, all the tasks that the resources are allocated to are hidden, but they can be listed as well. Use the hidetask attribute to select which tasks should be included.

Syntax resourcereport [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]

The name of the report. This will be the base name for generated output files. The suffix will depend on the specified formats. It will also be used in navigation bars.

By default, report definitions do not generate any files. With more complex projects, most report definitions will be used to describe elements of composed reports. If you want to generate a file from this report, you must specify the list of formats that you want to generate. The report name will then be used as a base name to create the file. The suffix will be appended based on the generated format.

Reports have a local name space. All IDs and file names must be unique within the reports that belong to the same enclosing report. To reference a report for inclusion into another report, you need to specify the full report ID. This is composed of the report ID, prefixed by a dot-separated list of all parent report IDs.

Context accountreport, export, properties, resourcereport, taskreport, textreport, tracereport

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
accountreport
accountroot x
auxdir (report) x x
balance
caption x
center x
columns x
currencyformat
end (report) x x
epilog x
export
flags (report) x
footer x
formats x
header x
headline x
height
hideaccount
hidejournalentry
hideresource
hidetask
journalattributes
journalmode
left x
loadunit
numberformat
opennodes
period (report)
prolog x
purge
rawhtmlhead
resourcereport
resourceroot x
right x
rollupaccount
rollupresource
rolluptask
scenarios x
selfcontained x
sortaccounts
sortjournalentries
sortresources
sorttasks
start (report) x x
taskreport
taskroot x
textreport
timeformat
timezone (report) x x
title x
tracereport
width x



<< resourceprefix << Table Of Contents >> resourceroot >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/scale.column.html0000644000175000017500000000706712614413013021050 0ustar bernatbernat scale.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< rolluptask << Table Of Contents >> scenario >>


Keyword scale (column)

Purpose
Specifies the scale that should be used for a chart column. This value is ignored for all other columns.
Syntax scale (hour | day | week | month | quarter | year)
Arguments hour
Set chart resolution to 1 hour.
day
Set chart resolution to 1 day.
week
Set chart resolution to 1 week.
month
Set chart resolution to 1 month.
quarter
Set chart resolution to 1 quarter.
year
Set chart resolution to 1 year.
Context columns



<< rolluptask << Table Of Contents >> scenario >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timesheetreport.html0000644000175000017500000001373512614413013021707 0ustar bernatbernat timesheetreport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timesheet << Table Of Contents >> timezone >>


Keyword timesheetreport

Purpose
For projects that flow mostly according to plan, TaskJuggler already knows much of the information that should be contained in the time sheets. With this property, you can generate a report that contains drafts of the time sheets for one or more resources. The time sheet drafts will be for the specified report period and the specified trackingscenario.
Syntax timesheetreport [<id>] <file name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
file name [STRING]
The name of the time sheet report file to generate. It must end with a .tji extension, or use . to use the standard output channel.
Context properties

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
end (report) x x
hideresource
hidetask
period (report)
sortresources
sorttasks
start (report) x x



<< timesheet << Table Of Contents >> timezone >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shift.allocate.html0000644000175000017500000000771212614413013021362 0ustar bernatbernat shift.allocate

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shift << Table Of Contents >> shift (resource) >>


Keyword shift (allocate)

This keyword should no longer be used. It will be removed in future versions of this software.

Use shifts (allocate) instead.

Purpose
Limits the allocations of resources during the specified interval to the specified shift. Multiple shifts can be defined, but shift intervals may not overlap. Allocation shifts are an additional restriction to the task shifts and resource shifts or resource working hours. Allocations will only be made for time slots that are specified as duty time in all relevant shifts. The restriction to the shift is only active during the specified time interval. Outside of this interval, no restrictions apply.
Syntax shift <shift> [<interval2>]
Arguments shift [ID]
The ID of a defined shift
interval2
See interval2 for details.
Context allocate
See also shifts (allocate)



<< shift << Table Of Contents >> shift (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shorttimeformat.html0000644000175000017500000000566612614413013021717 0ustar bernatbernat shorttimeformat

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shifts (task) << Table Of Contents >> sloppy (booking) >>


Keyword shorttimeformat

Purpose
Specifies time format for time short specifications. This is normaljust the hour and minutes.
Syntax shorttimeformat <format>
Arguments format [STRING]
strftime like format string
Context project



<< shifts (task) << Table Of Contents >> sloppy (booking) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/end.report.html0000644000175000017500000000777712614413013020555 0ustar bernatbernat end.report

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< end (limit) << Table Of Contents >> end (timesheet) >>


Keyword end (report)

Purpose
Specifies the end date of the report. In task reports only tasks that start before this end date are listed.
Syntax end <date>
Arguments date
See date for details.
Context accountreport, export, icalreport, nikureport, resourcereport, statussheetreport, taskreport, textreport, timesheetreport, tracereport

project export  "Project" "1.0" 2007-01-01 - 2008-01-01 {
  timezone "America/Denver"
}

resource tux "Tux"
resource bob "Bob"

task t1 "Task 1" {
  start 2007-01-01
  effort 20d
  allocate tux
  allocate bob
  limits { dailymax 6h }
}
# Export the project as fully scheduled project.
export "FullProject" {
  definitions *
  taskattributes *
  hideresource 0
}

# Export only bookings for 1st week as resource supplements
export "Week1Bookings" {
  definitions -
  start 2007-01-01
  end 2007-01-08
  taskattributes booking
  hideresource 0
}

# Export the scheduled project as Microsoft Project XML format.
export "MS-Project" {
  formats mspxml
  loadunit quarters
}


<< end (limit) << Table Of Contents >> end (timesheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/inherit.extend.html0000644000175000017500000000604412614413013021407 0ustar bernatbernat inherit.extend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< include (properties) << Table Of Contents >> interval1 >>


Keyword inherit (extend)

Purpose
If the this attribute is used, the property extension will be inherited by child properties from their parent property.
Syntax inherit
Arguments none
Context date (extend), number (extend), reference (extend), richtext (extend), text (extend)



<< include (properties) << Table Of Contents >> interval1 >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/hidereport.html0000644000175000017500000001141512614413013020622 0ustar bernatbernat hidereport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< hidejournalentry << Table Of Contents >> hideresource >>


Keyword hidereport

Purpose
This attribute can be used to exclude the reports that match the specified logical expression from the navigation bar.
Syntax hidereport (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context navigator



<< hidejournalentry << Table Of Contents >> hideresource >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/supplement.html0000644000175000017500000001054012614413013020647 0ustar bernatbernat supplement

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< summary << Table Of Contents >> supplement (resource) >>


Keyword supplement

Purpose

The supplement keyword provides a mechanism to add more attributes to already defined accounts, tasks or resources. The additional attributes must obey the same rules as in regular task or resource definitions and must be enclosed by curly braces.

This construct is primarily meant for situations where the information about a task or resource is split over several files. E. g. the vacation dates for the resources may be in a separate file that was generated by some other tool.

Syntax supplement (account <account ID> [{ <attributes> }] | report <report ID> [{ <attributes> }] | resource <resource ID> [{ <attributes> }] | task <task ID> [{ <attributes> }])
Arguments account ID
The ID of an already defined account.
report ID
The absolute ID of an already defined report.
resource ID
The ID of an already defined resource.
task ID
The absolute ID of an already defined task.
Context properties

project "Test Project" 2000-01-01 - 2000-01-04 {
  timezone "America/Denver"
}

flags important

resource joe "Joe"

task top "Top Task" {
  task sub "Sub Task"
  supplement task sub {
    length 1d
  }
}

supplement resource joe {
  vacation 2000-02-10 - 2000-02-20
}

supplement task top {
  flags important
  supplement task sub {
    allocate joe
  }
}


<< summary << Table Of Contents >> supplement (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/allocate.html0000644000175000017500000001054412614413013020243 0ustar bernatbernat allocate

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< alertlevels << Table Of Contents >> alternative >>


Keyword allocate

Purpose

Specify which resources should be allocated to the task. The attributes provide numerous ways to control which resource is used and when exactly it will be assigned to the task. Shifts and limits can be used to restrict the allocation to certain time intervals or to limit them to a certain maximum per time period. The purge statement can be used to remove inherited allocations or flags.

For effort-based tasks the task duration is clipped to only extend from the begining of the first allocation to the end of the last allocation. This is done to optimize for an overall minimum project duration as dependent tasks can potentially use the unallocated, clipped slots.

Syntax allocate <resource> [{ <attributes> }] [, <resource> [[, ... ]]...]
Arguments resource [ID]
The ID of a defined resource
Context task, supplement (task)

Attributes alternative, mandatory, persistent, select, shifts (allocate)

resource r1 "Resource 1"
resource r2 "Resource 2"

task t1 "Task 1" {
  start 2003-06-05
  # All sub-tasks inherit this allocation of r1
  allocate r1
  task t2 "Task 2" {
    effort 10d
  }
  task t3 "Task 3" {
    effort 20d
    # This task has r1 and r2 allocated
    allocate r2
  }
}


<< alertlevels << Table Of Contents >> alternative >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/navbar.html0000644000175000017500000003507612614413013017737 0ustar bernatbernat TaskJuggler Syntax Reference Navigator Table Of Contents

A

account
account (task)
accountprefix
accountreport
accountroot
active
adopt (task)
aggregate
alert
alertlevels
allocate
alternative
author
auxdir
auxdir (report)

B

balance
booking (resource)
booking (task)

C

caption
cellcolor (column)
celltext (column)
center
charge
chargeset
columnid
columns
complete
copyright
credits
currency
currencyformat

D

dailymax
dailymin
dailyworkinghours
date
date (extend)
definitions
depends
details
disabled
duration

E

efficiency
effort
email
enabled
end
end (column)
end (limit)
end (report)
end (timesheet)
endcredit
epilog
export
extend

F

fail
flags
flags (account)
flags (journalentry)
flags (report)
flags (resource)
flags (statussheet)
flags (task)
flags (timesheet)
fontcolor (column)
footer
formats
formats (export)
functions

G

gapduration
gaplength

H

halign (center)
halign (column)
halign (left)
halign (right)
hasalert
header
headline
height
hideaccount
hidejournalentry
hidereport
hideresource
hidetask

I

icalreport
include (macro)
include (project)
include (properties)
inherit (extend)
interval1
interval2
interval3
interval4
isactive
ischildof
isdependencyof
isdutyof
isfeatureof
isleaf
ismilestone
isongoing
isresource
isresponsibilityof
istask
isvalid

J

journalattributes
journalentry
journalmode

L

leaveallowance
leaves
left
length
limits
limits (allocate)
limits (resource)
limits (task)
listitem (column)
listtype (column)
loadunit
logicalexpression
logicalflagexpression

M

macro
managers
mandatory
maxend
maximum
maxstart
milestone
minend
minimum
minstart
monthlymax
monthlymin

N

navigator
newtask
nikureport
note (task)
now
number (extend)
numberformat

O

onend
onstart
opennodes
outputdir
overtime (booking)

P

period (column)
period (limit)
period (report)
period (task)
persistent
precedes
priority
priority (timesheet)
project
projectid
projectid (task)
projectids
projection
prolog
properties
purge

R

rate
rate (resource)
rawhtmlhead
reference (extend)
remaining
replace
reportprefix
resource
resourceattributes
resourceprefix
resourcereport
resourceroot
resources (limit)
responsible
richtext (extend)
right
rollupaccount
rollupresource
rolluptask

S

scale (column)
scenario
scenario (ical)
scenarios
scenarios (export)
scenariospecific (extend)
scheduled
scheduling
select
selfcontained
shift
shift (allocate)
shift (resource)
shift (task)
shift (timesheet)
shifts (allocate)
shifts (resource)
shifts (task)
shorttimeformat
sloppy (booking)
sloppy (projection)
sortaccounts
sortjournalentries
sortresources
sorttasks
start
start (column)
start (limit)
start (report)
startcredit
status (statussheet)
status (timesheet)
statussheet
statussheetreport
strict (projection)
summary
supplement
supplement (resource)
supplement (task)

T

tagfile
task
task (statussheet)
task (timesheet)
taskattributes
taskprefix
taskreport
taskroot
taskroot (export)
text (extend)
textreport
timeformat
timeformat1
timeformat2
timeoff (nikureport)
timesheet
timesheetreport
timezone
timezone (export)
timezone (report)
timezone (shift)
timingresolution
title
title (column)
tooltip (column)
tracereport
trackingscenario
treelevel

V

vacation
vacation (resource)
vacation (shift)

W

warn
weeklymax
weeklymin
weekstartsmonday
weekstartssunday
width
width (column)
work
workinghours (project)
workinghours (resource)
workinghours (shift)

Y

yearlyworkingdays
taskjuggler-3.5.0/manual/html/gaplength.html0000644000175000017500000000751512614413013020434 0ustar bernatbernat gaplength

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< gapduration << Table Of Contents >> halign (center) >>


Keyword gaplength

Purpose
Specifies the minimum required gap between the end of a preceding task and the start of this task, or the start of a following task and the end of this task. This is working time, not calendar time. 7d means 7 working days, not one week. Whether a day is considered a working day or not depends on the defined working hours and global leaves.
Syntax gaplength <value> (min | h | d | w | m | y)
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context depends, precedes



<< gapduration << Table Of Contents >> halign (center) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/toc.html0000644000175000017500000022222512614413013017245 0ustar bernatbernat Index

The TaskJuggler User Manual

Project Management beyond Gantt Chart drawing
Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>
Generated on 2013-06-29

This manual covers TaskJuggler version 3.5.0.




1 Introduction
1.1 About TaskJuggler
1.2 License and Copyright
1.3 Features and Highlights
1.3.1 Basic Properties
1.3.2 Advanced Scheduling
1.3.3 Accounting
1.3.4 Reporting
1.3.5 Scaling and Enterprise Features
1.3.6 Web Publishing and Groupware Functions
1.4 TaskJuggler on the Web
1.5 TaskJuggler 2.x Migration
1.6 Using TaskJuggler 2.x and TaskJuggler 3.x in parallel
1.7 Reporting Bugs and Feature Requests
2 Installation
2.1 Requirements
2.1.1 Supported Operating Systems
2.1.2 Other required Software
2.2 Installation Steps for Users
2.2.1 The easy way
2.2.1.1 System Wide Installation
2.2.1.2 Installation into a local Directory
2.2.2 The manual way
2.3 Update from older TaskJuggler 3.x versions
2.4 Installing TaskJuggler from the Git Repository
2.5 Quickly switching between various TaskJuggler 3.x versions
2.6 Installing a newer Ruby version
2.7 Installing the Vim Support
2.8 How to Contribute
2.8.1 Why contribute?
2.8.2 Preparing a contribution
2.8.3 Creating a Patch
2.8.4 Contributing to the User Manual
2.8.5 Contributing to the Test Suite
2.8.6 Contributing to the Ruby code
2.8.7 Some final words to Contributors
3 Getting Started
3.1 Basics
3.2 Structure of a TJP File
4 The Tutorial: Your first Project
4.1 Starting the project
4.2 Global Attributes
4.3 Macros
4.4 Declaring Flags
4.5 Declaring Accounts
4.6 Declaring Resources
4.7 Specifying the Tasks
4.8 Specifying Milestones
4.9 Visualizing the Project
5 The TaskJuggler Syntax
5.1 Understanding the Syntax Reference
5.1.1 ABSOLUTE_ID
5.1.2 ID
5.1.3 INTEGER
5.1.4 STRING
5.2 Predefined Macros
5.3 Environment Variable Expansions
5.4 Rich Text Attributes
5.4.1 Block Markups
5.4.2 In-Line Markups
5.4.3 Block and Inline Generators
6 The TaskJuggler Software
6.1 tj3
6.2 tj3man
6.3 tj3d
6.4 tj3client
6.5 tj3webd
7 Day To Day Juggling
7.1 Working with multiple scenarios
7.2 Important and fall-back Tasks
7.3 Tracking the Project
7.4 Recording Progress
7.4.1 Using completion values
7.4.2 Using bookings
7.5 Tracking status and actuals
7.5.1 The reporting and tracking cycle
7.6 Implementing the status tracking system
7.6.1 Prerequesites
7.6.2 The Time Sheet Template Sender
7.6.3 The Time Sheet Receiver
7.6.4 Time Sheet Template Requests
7.6.5 Time Sheet Summaries
7.6.6 Updating the Project Plan
7.7 Recording actual Resource Usage
7.7.1 Status Sheets
7.7.2 The Status Sheet Template Sender
7.7.3 Requesting Status Sheet Templates
7.7.4 The Status Sheet Receiver
8 TaskJuggler Internals
8.1 How the Scheduler works
9 GNU Free Documentation License
A Syntax Reference
A.1 account
A.2 account (task)
A.3 accountprefix
A.4 accountreport
A.5 accountroot
A.6 active
A.7 adopt (task)
A.8 aggregate
A.9 alert
A.10 alertlevels
A.11 allocate
A.12 alternative
A.13 author
A.14 auxdir
A.15 auxdir (report)
A.16 balance
A.17 booking (resource)
A.18 booking (task)
A.19 caption
A.20 cellcolor (column)
A.21 celltext (column)
A.22 center
A.23 charge
A.24 chargeset
A.25 columnid
A.26 columns
A.27 complete
A.28 copyright
A.29 credits
A.30 currency
A.31 currencyformat
A.32 dailymax
A.33 dailymin
A.34 dailyworkinghours
A.35 date
A.36 date (extend)
A.37 definitions
A.38 depends
A.39 details
A.40 disabled
A.41 duration
A.42 efficiency
A.43 effort
A.44 email
A.45 enabled
A.46 end
A.47 end (column)
A.48 end (limit)
A.49 end (report)
A.50 end (timesheet)
A.51 endcredit
A.52 epilog
A.53 export
A.54 extend
A.55 fail
A.56 flags
A.57 flags (account)
A.58 flags (journalentry)
A.59 flags (report)
A.60 flags (resource)
A.61 flags (statussheet)
A.62 flags (task)
A.63 flags (timesheet)
A.64 fontcolor (column)
A.65 footer
A.66 formats
A.67 formats (export)
A.68 functions
A.69 gapduration
A.70 gaplength
A.71 halign (center)
A.72 halign (column)
A.73 halign (left)
A.74 halign (right)
A.75 hasalert
A.76 header
A.77 headline
A.78 height
A.79 hideaccount
A.80 hidejournalentry
A.81 hidereport
A.82 hideresource
A.83 hidetask
A.84 icalreport
A.85 include (macro)
A.86 include (project)
A.87 include (properties)
A.88 inherit (extend)
A.89 interval1
A.90 interval2
A.91 interval3
A.92 interval4
A.93 isactive
A.94 ischildof
A.95 isdependencyof
A.96 isdutyof
A.97 isfeatureof
A.98 isleaf
A.99 ismilestone
A.100 isongoing
A.101 isresource
A.102 isresponsibilityof
A.103 istask
A.104 isvalid
A.105 journalattributes
A.106 journalentry
A.107 journalmode
A.108 leaveallowance
A.109 leaves
A.110 left
A.111 length
A.112 limits
A.113 limits (allocate)
A.114 limits (resource)
A.115 limits (task)
A.116 listitem (column)
A.117 listtype (column)
A.118 loadunit
A.119 logicalexpression
A.120 logicalflagexpression
A.121 macro
A.122 managers
A.123 mandatory
A.124 maxend
A.125 maximum
A.126 maxstart
A.127 milestone
A.128 minend
A.129 minimum
A.130 minstart
A.131 monthlymax
A.132 monthlymin
A.133 navigator
A.134 newtask
A.135 nikureport
A.136 note (task)
A.137 now
A.138 number (extend)
A.139 numberformat
A.140 onend
A.141 onstart
A.142 opennodes
A.143 outputdir
A.144 overtime (booking)
A.145 period (column)
A.146 period (limit)
A.147 period (report)
A.148 period (task)
A.149 persistent
A.150 precedes
A.151 priority
A.152 priority (timesheet)
A.153 project
A.154 projectid
A.155 projectid (task)
A.156 projectids
A.157 projection
A.158 prolog
A.159 properties
A.160 purge
A.161 rate
A.162 rate (resource)
A.163 rawhtmlhead
A.164 reference (extend)
A.165 remaining
A.166 replace
A.167 reportprefix
A.168 resource
A.169 resourceattributes
A.170 resourceprefix
A.171 resourcereport
A.172 resourceroot
A.173 resources (limit)
A.174 responsible
A.175 richtext (extend)
A.176 right
A.177 rollupaccount
A.178 rollupresource
A.179 rolluptask
A.180 scale (column)
A.181 scenario
A.182 scenario (ical)
A.183 scenarios
A.184 scenarios (export)
A.185 scenariospecific (extend)
A.186 scheduled
A.187 scheduling
A.188 select
A.189 selfcontained
A.190 shift
A.191 shift (allocate)
A.192 shift (resource)
A.193 shift (task)
A.194 shift (timesheet)
A.195 shifts (allocate)
A.196 shifts (resource)
A.197 shifts (task)
A.198 shorttimeformat
A.199 sloppy (booking)
A.200 sloppy (projection)
A.201 sortaccounts
A.202 sortjournalentries
A.203 sortresources
A.204 sorttasks
A.205 start
A.206 start (column)
A.207 start (limit)
A.208 start (report)
A.209 startcredit
A.210 status (statussheet)
A.211 status (timesheet)
A.212 statussheet
A.213 statussheetreport
A.214 strict (projection)
A.215 summary
A.216 supplement
A.217 supplement (resource)
A.218 supplement (task)
A.219 tagfile
A.220 task
A.221 task (statussheet)
A.222 task (timesheet)
A.223 taskattributes
A.224 taskprefix
A.225 taskreport
A.226 taskroot
A.227 taskroot (export)
A.228 text (extend)
A.229 textreport
A.230 timeformat
A.231 timeformat1
A.232 timeformat2
A.233 timeoff (nikureport)
A.234 timesheet
A.235 timesheetreport
A.236 timezone
A.237 timezone (export)
A.238 timezone (report)
A.239 timezone (shift)
A.240 timingresolution
A.241 title
A.242 title (column)
A.243 tooltip (column)
A.244 tracereport
A.245 trackingscenario
A.246 treelevel
A.247 vacation
A.248 vacation (resource)
A.249 vacation (shift)
A.250 warn
A.251 weeklymax
A.252 weeklymin
A.253 weekstartsmonday
A.254 weekstartssunday
A.255 width
A.256 width (column)
A.257 work
A.258 workinghours (project)
A.259 workinghours (resource)
A.260 workinghours (shift)
A.261 yearlyworkingdays



Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/text.extend.html0000644000175000017500000000706512614413013020735 0ustar bernatbernat text.extend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< taskroot (export) << Table Of Contents >> textreport >>


Keyword text (extend)

Purpose
Extend the property with a new attribute of type text. A text is a character sequence enclosed in single or double quotes.
Syntax text <id> <name> [{ <attributes> }]
Arguments id [ID]
The ID of the new attribute. It can be used like the built-in IDs.
name [STRING]
The name of the new attribute. It is used as header in report columns and the like.
Context extend

Attributes inherit (extend), scenariospecific (extend)



<< taskroot (export) << Table Of Contents >> textreport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/depends.html0000644000175000017500000001127312614413013020101 0ustar bernatbernat depends

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< definitions << Table Of Contents >> details >>


Keyword depends

Purpose

Specifies that the task cannot start before the specified tasks have been finished.

By using the 'depends' attribute, the scheduling policy is automatically set to asap. If both depends and precedes are used, the last policy counts.

Syntax depends (<ABSOLUTE ID> | <ID> | <RELATIVE ID>) [{ <attributes> }] [, (<ABSOLUTE ID> | <ID> | <RELATIVE ID>) [[, ... ]]...]
Arguments ABSOLUTE ID [ABSOLUTE_ID]
A reference using the full qualified ID of a task. The IDs of all enclosing parent tasks must be prepended to the task ID and separated with a dot, e.g. proj.plan.doc.
ID
Just the ID of the task without and parent IDs.
RELATIVE ID
A relative task ID always starts with one or more exclamation marks and is followed by a task ID. Each exclamation mark lifts the scope where the ID is looked for to the enclosing task. The ID may contain some of the parent IDs separated by dots, e. g. !!plan.doc.
Context task, supplement (task)

Attributes gapduration, gaplength, onend, onstart

project "P" 2007-11-09 - 2007-12-24 {
  timezone "America/Denver"
}

task foo1 "foo1" {
  task foo2 "foo2" {
    start 2007-12-04
    milestone
  }
  task foo3 "foo3" {
    depends !foo2
    length 1d
  }
}
task bar "bar" {
  depends foo1.foo2
  length 2d
}

task bar1 "bar1" {
  depends foo1 { gapduration 2d }, bar { gaplength 1d }
  duration 2d
}


<< definitions << Table Of Contents >> details >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/workinghours.resource.html0000644000175000017500000000715512614413013023052 0ustar bernatbernat workinghours.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< workinghours (project) << Table Of Contents >> workinghours (shift) >>


Keyword workinghours (resource)

Purpose
Set the working hours for a specific resource. The working hours specification limits the availability of resources to certain time slots of week days.
Syntax workinghours <weekday> [- <end weekday>] [, <weekday> [- <end weekday>]...] (off | <TIME> - <TIME> [, <TIME> - <TIME>...])
Arguments weekday
Weekday (sun - sat)
end weekday
Weekday (sun - sat). It is included in the interval.
Context resource, supplement (resource)
See also workinghours (project), workinghours (shift)



<< workinghours (project) << Table Of Contents >> workinghours (shift) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/currencyformat.html0000644000175000017500000001060312614413013021516 0ustar bernatbernat currencyformat

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< currency << Table Of Contents >> dailymax >>


Keyword currencyformat

Purpose
These values specify the default format used for all currency values.
Syntax currencyformat <negativeprefix> <negativesuffix> <thousandsep> <fractionsep> <fractiondigits>
Arguments negativeprefix [STRING]
Prefix for negative numbers
negativesuffix [STRING]
Suffix for negative numbers
thousandsep [STRING]
Separator used for every 3rd digit
fractionsep [STRING]
Separator used to separate the fraction digits
fractiondigits [INTEGER]
Number of fraction digits to show
Context accountreport, project, resourcereport, taskreport, textreport, tracereport

project prj "Project" "1.0" 2007-01-01 - 2007-03-01 {
  timezone "Europe/Berlin"
  # German currency format: e. g.  -10.000,20 5.014,11
  numberformat "-" "" "." "," 2

  # US currency format: e. g. (10,000.20) 5,014.11
  currencyformat "(" ")" "," "." 2
}

task t "Task" {
  start ${projectstart}
  milestone
}


<< currency << Table Of Contents >> dailymax >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/project.html0000644000175000017500000001242012614413013020120 0ustar bernatbernat project

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< priority (timesheet) << Table Of Contents >> projectid >>


Keyword project

Purpose

The project property is mandatory and should be the first property in a project file. It is used to capture basic attributes such as the project id, name and the expected time frame.

Be aware that the dates for the project period default to UTC times. See interval2 for details.

Syntax project [<id>] <name> [<version>] <interval2> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]
The name of the project
version [STRING]
An optional version ID. This can be something simple as "4.2" or an ID tag of a revision control system. If not specified, it defaults to "1.0".
interval2
See interval2 for details.
Context Global scope

Attributes alertlevels, currency, currencyformat, dailyworkinghours, extend, include (project), journalentry, now, numberformat, outputdir, scenario, shorttimeformat, timeformat, timezone, timingresolution, trackingscenario, weekstartsmonday, weekstartssunday, workinghours (project), yearlyworkingdays



<< priority (timesheet) << Table Of Contents >> projectid >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/note.task.html0000644000175000017500000000545612614413013020373 0ustar bernatbernat note.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< nikureport << Table Of Contents >> now >>


Keyword note (task)

Purpose
Attach a note to the task. This is usually a more detailed specification of what the task is about.
Syntax note <STRING>
Arguments none
Context task, supplement (task)



<< nikureport << Table Of Contents >> now >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/accountreport.html0000644000175000017500000005103712614413013021351 0ustar bernatbernat accountreport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< accountprefix << Table Of Contents >> accountroot >>


Keyword accountreport

This keyword has not yet been fully tested yet. You are welcome to try it, but it may lead to wrong results. The syntax may still change with future versions. The developers appreciate any feedback on this keyword.

Purpose

The report lists accounts and their respective values in a table. The report can operate in two modes:

  1. Balance mode: If a balance has been set, the report will include the defined cost and revenue accounts as well as all their sub accounts. To reduce the list of included accounts, you can use the hideaccount, rollupaccount or accountroot attributes. The order of the task can be controlled with sortaccounts. If the first sorting criteria is tree sorting, the parent accounts will always be included to form the tree. Tree sorting is the default. You need to change it if you do not want certain parent accounts to be included in the report. Additionally, it will contain a line at the end that lists the balance (revenue - cost).
  2. Normal mode: All reports are listed in the order and completeness as defined by the other report attributes. No balance line will be included.
Syntax accountreport [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]

The name of the report. This will be the base name for generated output files. The suffix will depend on the specified formats. It will also be used in navigation bars.

By default, report definitions do not generate any files. With more complex projects, most report definitions will be used to describe elements of composed reports. If you want to generate a file from this report, you must specify the list of formats that you want to generate. The report name will then be used as a base name to create the file. The suffix will be appended based on the generated format.

Reports have a local name space. All IDs and file names must be unique within the reports that belong to the same enclosing report. To reference a report for inclusion into another report, you need to specify the full report ID. This is composed of the report ID, prefixed by a dot-separated list of all parent report IDs.

Context accountreport, export, properties, resourcereport, taskreport, textreport, tracereport

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
accountreport
accountroot x
auxdir (report) x x
balance
caption x
center x
columns x
currencyformat
end (report) x x
epilog x
export
flags (report) x
footer x
formats x
header x
headline x
height
hideaccount
hidejournalentry
hideresource
hidetask
journalattributes
journalmode
left x
loadunit
numberformat
opennodes
period (report)
prolog x
purge
rawhtmlhead
resourcereport
resourceroot x
right x
rollupaccount
rollupresource
rolluptask
scenarios x
selfcontained x
sortaccounts
sortjournalentries
sortresources
sorttasks
start (report) x x
taskreport
taskroot x
textreport
timeformat
timezone (report) x x
title x
tracereport
width x

project "AccountReport" 2011-11-09 +1y {
  currencyformat "(" ")" "," "." 0
}

account resourceCost "Resource Cost" {
  aggregate resources
  account teamA "Team A"
  account teamB "Team B"
}
account productCost "Product Cost" {
  aggregate tasks
}
account customerPayments "Customer Payments" {
  credits 2011-12-01 "First downpayment" 80000,
          2012-06-01 "Second downpayment" 200000
}

balance productCost customerPayments

resource teamA "Team A" {
  rate 420
  chargeset teamA
  resource "R1"
  resource "R2"
}
resource teamB "Team B" {
  rate 380
  chargeset teamB
  resource "R3"
  resource "R4"
}

task "Products" {
  chargeset productCost

  task p1 "Product 1" {
    effort 20m
    allocate teamA, teamB
  }
  task p2 "Product 2" {
    effort 18m
    allocate teamA
    priority 600
  }
  task p3 "Product 3" {
    effort 6m
    allocate teamB
    priority 600
  }
  task mf "Manufacturing" {
    depends !p1, !p2, !p3
    duration 2w
    charge 12000 onend
  }
  task "Final Payment" {
    depends !mf
    purge chargeset
    chargeset customerPayments
    charge 170000 onstart
  }
}

accountreport "TeamBudget" {
  formats html
  accountroot resourceCost
  balance -
  columns no, name, quarterly { celltext 1 "<-query attribute='turnover'->" }
}

accountreport "ProfiAndLoss" {
  formats html
  columns no, name, monthly
}


<< accountprefix << Table Of Contents >> accountroot >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/flags.account.html0000644000175000017500000000547112614413013021211 0ustar bernatbernat flags.account

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< flags << Table Of Contents >> flags (journalentry) >>


Keyword flags (account)

Purpose
Attach a set of flags. The flags can be used in logical expressions to filter properties from the reports.
Syntax flags <ID> [, <ID>...]
Arguments none
Context account



<< flags << Table Of Contents >> flags (journalentry) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/remaining.html0000644000175000017500000001116712614413013020432 0ustar bernatbernat remaining

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< reference (extend) << Table Of Contents >> replace >>


Keyword remaining

Purpose

The remaining effort for the task. This value is ignored if there are bookings for the resource that overlap with the time sheet period. If there are no bookings, the value is compared with the effort specification of the task. If there a mismatch between the accumulated effort specified with bookings, work and remaining on one side and the specified effort on the other, a warning is generated.

This attribute can only be used with tasks that are effort based. Duration based tasks need to have an end attribute.

Syntax remaining <value> (min | h | d | w | m | y)
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context newtask, task (timesheet)

task t1 "Task 1" {
  effort 5d
  allocate r1
}

timesheet r1 2009-11-30 +1w {
  task t1 {
    work 3d
    remaining 0d
    status green "All work done" {
      summary "I had good fun!"
      details -8<-
        This task went smoothly and I got three things done:
        * Have fun
        * Be on time
        * Get things done
      ->8-
    }
  }
}


<< reference (extend) << Table Of Contents >> replace >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/chargeset.html0000644000175000017500000000721312614413013020423 0ustar bernatbernat chargeset

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< charge << Table Of Contents >> columnid >>


Keyword chargeset

Purpose
A chargeset defines how the turnover associated with the property will be charged to one or more accounts. A property may have any number of charge sets, but each chargeset must deal with a different top-level account. A charge set consists of one or more accounts. Each account must be a leaf account. The account ID may be followed by a percentage value that determines the share for this account. The total percentage of all accounts must be exactly 100%. If some accounts don't have a percentage specification, the remainder to 100% is distributed evenly between them.
Syntax chargeset <account> <share> [, <account> <share>...]
Arguments account
The ID of a previously defined leaf account.
share
A percentage between 0 and 100%
Context resource, supplement (resource), task, supplement (task)



<< charge << Table Of Contents >> columnid >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/alternative.html0000644000175000017500000000654712614413013021005 0ustar bernatbernat alternative

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< allocate << Table Of Contents >> author >>


Keyword alternative

Purpose
Specify which resources should be allocated to the task. The optional attributes provide numerous ways to control which resource is used and when exactly it will be assigned to the task. Shifts and limits can be used to restrict the allocation to certain time intervals or to limit them to a certain maximum per time period.
Syntax alternative <resource> [, <resource>...]
Arguments resource [ID]
The ID of a defined resource
Context allocate

resource tuxus "Tuxus"
resource tuxia "Tuxia"

task t "Task" {
  start ${projectstart}
  effort 5d
  # Use tuxus or tuxia, whoever is available.
  allocate tuxus { alternative tuxia }
}


<< allocate << Table Of Contents >> author >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timezone.html0000644000175000017500000000736512614413013020320 0ustar bernatbernat timezone

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timesheetreport << Table Of Contents >> timezone (export) >>


Keyword timezone

Purpose

Sets the default time zone of the project. All dates and times that have no time zones specified will be assumed to be in this time zone. If no time zone is specified for the project, UTC is assumed.

The project start and end time are not affected by this setting. They are always considered to be UTC unless specified differently.

In case the specified time zone is not hour-aligned with UTC, the timingresolution will automatically be decreased accordingly. Do not change the timingresolution after you've set the time zone!

Changing the time zone will reset the working hours to the default times. It's recommended that you declare your working hours after the time zone.

Syntax timezone <zone>
Arguments zone
Time zone to use. E. g. 'Europe/Berlin' or 'America/Denver'. Don't use the 3 letter acronyms. See Wikipedia for possible values.
Context project



<< timesheetreport << Table Of Contents >> timezone (export) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/taskroot.html0000644000175000017500000000745112614413013020330 0ustar bernatbernat taskroot

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< taskreport << Table Of Contents >> taskroot (export) >>


Keyword taskroot

Purpose
Only tasks below the specified root-level tasks are exported. The exported tasks will have the ID of the root-level task stripped from their ID, so that the sub-tasks of the root-level task become top-level tasks in the report file.
Syntax taskroot (<ABSOLUTE_ID> | <ID>)
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport

project "Taskroot Example" 2005-07-22 - 2005-08-26 {
  timezone "America/Denver"
}

task items "Project breakdown" {
  start 2005-07-22

  task plan "Plan work" {
    length 3d
  }

  task implementation "Implement work" {
    task phase1 "Phase 1" {
      length 5d
      depends !!plan
    }
    task phase2 "Phase 2" {
      length 3d
      depends !phase1
    }
    task phase3 "Phase 3" {
      length 4d
      depends !phase2
    }
  }

  task acceptance "Customer acceptance" {
    duration 5d
    depends !implementation
  }
}

taskreport tasks "My Tasks" {
  formats html
  taskroot items.implementation
}


<< taskreport << Table Of Contents >> taskroot (export) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/isdependencyof.html0000644000175000017500000000666612614413013021470 0ustar bernatbernat isdependencyof

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< ischildof << Table Of Contents >> isdutyof >>


Keyword isdependencyof

Purpose
Will evaluate to true for tasks that depend on the specified task in the specified scenario and are no more than distance tasks away. If distance is 0, all dependencies are considered independent of their distance.
Syntax isdependencyof ( <Task ID> , <Scenario ID> , <Distance> )
Arguments Task ID [ID]
The ID of a defined task
Scenario ID [ID]
A scenario ID
Distance [INTEGER]
The maximum task distance to be considered
Context functions



<< ischildof << Table Of Contents >> isdutyof >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/managers.html0000644000175000017500000001034412614413013020252 0ustar bernatbernat managers

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< macro << Table Of Contents >> mandatory >>


Keyword managers

Purpose

Defines one or more resources to be the manager who is responsible for this resource. Managers must be leaf resources. This attribute does not impact the scheduling. It can only be used for documentation purposes.

You must only specify direct managers here. Do not list higher level managers here. If necessary, use the purge attribute to clear inherited managers. For most use cases, there should be only one manager. But TaskJuggler is not limited to just one manager. Dotted reporting lines can be captured as well as long as the managers are not reporting to each other.

Syntax managers <resource> [, <resource>...]
Arguments resource [ID]
The ID of a defined resource
Context resource, supplement (resource)
See also statussheet

project "test" 2010-04-03 +1w {
  timezone "America/Denver"
}

resource "The Company" {
  resource ceo "Big Boss"
  managers ceo

  resource "R&D Team" {
    resource vpe "VP Engineering"
    purge managers
    managers vpe

    resource "The Hacker"
    resource "Doc Writer"
  }
  resource "F&A Team" {
    resource coo "Chief Operating Officer"
    purge managers
    managers coo

    resource "HR Lady"
    resource "Accountant"
  }
}

task "T"

resourcereport "Managers" {
  formats html
  columns name, directreports, reports
}


<< macro << Table Of Contents >> mandatory >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/booking.task.html0000644000175000017500000001432512614413013021051 0ustar bernatbernat booking.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< booking (resource) << Table Of Contents >> caption >>


Keyword booking (task)

Purpose

The booking attribute can be used to report actually completed work. A task with bookings must be scheduled in asap mode. If the scenario is not the tracking scenario or derived from it, the scheduler will not allocate resources prior to the current date or the date specified with now when a task has at least one booking.

Bookings are only valid in the scenario they have been defined in. They will in general not be passed to any other scenario. If you have defined a tracking scenario, the bookings of this scenario will be passed to all the derived scenarios of the tracking scenario.

The sloppy attribute can be used when you want to skip non-working time or other allocations automatically. If it's not given, all bookings must only cover working time for the resource.

The booking attributes is designed to capture the exact amount of completed work. This attribute is not really intended to specify completed effort by hand. Usually, booking statements are generated by export reports. The sloppy and overtime attributes are only kludge for users who want to write them manually. Bookings can be used to report already completed work by specifying the exact time intervals a certain resource has worked on this task.

Bookings can be defined in the task or resource context. If you move tasks around very often, put your bookings in the task context.

Syntax booking <resource> <interval4> [, <interval4>...] [{ <attributes> }]
Arguments resource [ID]
The ID of a defined resource
interval4
See interval4 for details.
Context task, supplement (task)
See also booking (resource)

Attributes overtime (booking), sloppy (booking)

project project "Simple Project" "1.0" 2007-01-05 +1m {
  timezone "America/Denver"
  # The baseline date for the projection.
  now 2007-01-15
}

resource tux "Tux"

task test "Testing" {
  start 2007-01-05
  effort 10d
  allocate tux
}

supplement resource tux {
  # Book a whole day (8 hours). The 1 hour lunch break is skipped.
  booking test 2007-01-06-9:00 +9h { sloppy 1 }
  # Book 2 days in the afternoon, 4 hours each.
  booking test 2007-01-08-13:00 +4h,
               2007-01-09-13:00 +4h
  # This is a common mistake. With standard working hours, this will
  # yield a zero time booking! The interval is midnight to 8am. So
  # it's outside of the working hours and 'sloopy 2' surpresses the
  # warning.
  booking test 2007-01-11 +8h { sloppy 2 }
  # Use 'overtime' to book off-hour slots. This booking will book the
  # full 10 hours, ignoring the lunch break and adding an extra hour
  # in the morning.
  booking test 2007-01-11-8:00 +10h { overtime 1 }
}


<< booking (resource) << Table Of Contents >> caption >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/efficiency.html0000644000175000017500000001017712614413013020565 0ustar bernatbernat efficiency

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< duration << Table Of Contents >> effort >>


Keyword efficiency

Purpose

The efficiency of a resource can be used for two purposes. First you can use it as a crude way to model a team. A team of 5 people should have an efficiency of 5.0. Keep in mind that you cannot track the members of the team individually if you use this feature. They always act as a group.

The other use is to model performance variations between your resources. Again, this is a fairly crude mechanism and should be used with care. A resource that isn't every good at some task might be pretty good at another. This can't be taken into account as the resource efficiency can only set globally for all tasks.

All resources that do not contribute effort to the task, should have an efficiency of 0.0. A typical example would be a conference room. It's necessary for a meeting, but it does not contribute any work.

Syntax efficiency (<INTEGER> | <FLOAT>)
Arguments none
Context resource, supplement (resource)

project "Resource Efficiency Example" 2007-07-21 - 2007-07-22 {
  timezone "America/Denver"
}

# A team of 5 people. They can only be assigned en block. Either all
# or nobody works.
resource tuxies "Tuxies" { efficiency 5.0 }

# A hard-working guy
resource tux1 "Tux 1" { efficiency 1.2 }

# And a lazy one
resource tux2 "Tux 2" { efficiency 0.9 }

# And a thing that cannot do any work
resource confRoom "Conference Room" { efficiency 0 }

task t "An important date" {
  start 2007-07-21
}


<< duration << Table Of Contents >> effort >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/taskattributes.html0000644000175000017500000001114212614413013021523 0ustar bernatbernat taskattributes

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< task (timesheet) << Table Of Contents >> taskprefix >>


Keyword taskattributes

Purpose
Define a list of task attributes that should be included in the report.
Syntax taskattributes (* | - | (booking | complete | depends | flags | maxend | maxstart | minend | minstart | note | priority | responsible) [, (booking | complete | depends | flags | maxend | maxstart | minend | minstart | note | priority | responsible)...])
Arguments *
A shortcut for all items
-
No items
booking
Include bookings
complete
Include completion values
depends
Include dependencies
flags
Include flags
maxend
Include maximum end dates
maxstart
Include maximum start dates
minend
Include minimum end dates
minstart
Include minimum start dates
note
Include notes
priority
Include priorities
responsible
Include responsible resource
Context export



<< task (timesheet) << Table Of Contents >> taskprefix >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/epilog.html0000644000175000017500000000633212614413013017736 0ustar bernatbernat epilog

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< endcredit << Table Of Contents >> export >>


Keyword epilog

Purpose
Define a text section that is printed right after the actual report data. The text will be interpreted as Rich Text.
Syntax epilog <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport
See also footer, header, prolog



<< endcredit << Table Of Contents >> export >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/scenarios.html0000644000175000017500000000635612614413013020453 0ustar bernatbernat scenarios

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< scenario (ical) << Table Of Contents >> scenarios (export) >>


Keyword scenarios

Purpose
List of scenarios that should be included in the report. By default, only the top-level scenario will be included. You can use this attribute to include data from the defined set of scenarios. Not all reports support reporting data from multiple scenarios. They will only include data from the first one in the list.
Syntax scenarios <ID> [, <ID>...]
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< scenario (ical) << Table Of Contents >> scenarios (export) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shift.html0000644000175000017500000001363312614413013017576 0ustar bernatbernat shift

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< selfcontained << Table Of Contents >> shift (allocate) >>


Keyword shift

Purpose

A shift combines several workhours related settings in a reusable entity. Besides the weekly working hours it can also hold information such as leaves and a time zone. It lets you create a work time calendar that can be used to limit the working time for resources or tasks.

Shifts have a global name space. All IDs must be unique within the shifts of the project.

Syntax shift [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]
The name of the shift
Context properties, shift
See also shifts (resource), shifts (task)

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
leaves x x x
replace x x
shift
timezone (shift) x x x
vacation (shift) x
workinghours (shift) x x x



<< selfcontained << Table Of Contents >> shift (allocate) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/flags.task.html0000644000175000017500000000563212614413013020516 0ustar bernatbernat flags.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< flags (statussheet) << Table Of Contents >> flags (timesheet) >>


Keyword flags (task)

Purpose
Attach a set of flags. The flags can be used in logical expressions to filter properties from the reports.
Syntax flags <ID> [, <ID>...]
Arguments none
Context task, supplement (task)



<< flags (statussheet) << Table Of Contents >> flags (timesheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/effort.html0000644000175000017500000001257412614413013017751 0ustar bernatbernat effort

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< efficiency << Table Of Contents >> email >>


Keyword effort

Purpose

Specifies the effort needed to complete the task. An effort of 6d (6 resource-days) can be done with 2 full-time resources in 3 working days. The task will not finish before the allocated resources have contributed the specified effort. Hence the duration of the task will depend on the availability of the allocated resources. The specified effort value must be at least as large as the timingresolution.

WARNING: In almost all real world projects effort is not the product of time and resources. This is only true if the task can be partitioned without adding any overhead. For more information about this read The Mythical Man-Month by Frederick P. Brooks, Jr.

Tasks may not have subtasks if this attribute is used. Setting this attribute will reset the duration and length attributes. A task with an effort value cannot be a milestone.

Syntax effort <value> (min | h | d | w | m | y)
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context task, supplement (task)
See also duration, length

project "Duration Example" 2007-06-06 - 2007-06-26 {
  timezone "America/Denver"
}

resource tux "Tux"

task t "Enclosing" {
  start 2007-06-06
  task durationTask "Duration Task" {
    # This task is 10 calendar days long.
    duration 10d
  }

  task intervalTask "Interval Task" {
    # This task is similar to the durationTask. Instead of a start
    # date and a duration it has a fixed start and end date.
    end 2007-06-17
  }

  task lengthTask "Length Task" {
    # This task 10 working days long. So about 12 calendar days.
    length 10d
  }

  task effortTask "Effort Task" {
    # Tux will need 10 days to complete this task.
    effort 10d
    allocate tux
  }
}


<< efficiency << Table Of Contents >> email >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/loadunit.html0000644000175000017500000001123112614413013020270 0ustar bernatbernat loadunit

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< listtype (column) << Table Of Contents >> logicalexpression >>


Keyword loadunit

Purpose
Determines what unit should be used to display all load values in this report.
Syntax loadunit (days | hours | longauto | minutes | months | quarters | shortauto | weeks | years)
Arguments days
Display all load and duration values as days.
hours
Display all load and duration values as hours.
longauto
Automatically select the unit that produces the shortest and most readable value. The unit name will not be abbreviated. It will not use quarters since it is not common.
minutes
Display all load and duration values as minutes.
months
Display all load and duration values as months.
quarters
Display all load and duration values as quarters.
shortauto
Automatically select the unit that produces the shortest and most readable value. The unit name will be abbreviated. It will not use quarters since it is not common.
weeks
Display all load and duration values as weeks.
years
Display all load and duration values as years.
Context accountreport, export, resourcereport, taskreport, textreport, tracereport



<< listtype (column) << Table Of Contents >> logicalexpression >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/leaveallowance.html0000644000175000017500000001251312614413013021437 0ustar bernatbernat leaveallowance

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< journalmode << Table Of Contents >> leaves >>


Keyword leaveallowance

This keyword has not yet been fully tested yet. You are welcome to try it, but it may lead to wrong results. The syntax may still change with future versions. The developers appreciate any feedback on this keyword.

Purpose

Add or subtract leave allowances. Currently, only allowances for the annual leaves are supported. Allowances can be negative to deal with expired allowances. The leaveallowancebalance report column can be used to report the current annual leave balance.

Leaves outside of the project period are silently ignored and will not be considered in the leave balance calculation. Therefor, leave allowances are only allowed within the project period.

Syntax leaveallowances annual <date> [-] <value> (min | h | d | w | m | y) [, annual <date> [-] <value> (min | h | d | w | m | y)...]
Arguments date
See date for details.
value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context resource, supplement (resource)

project "Annual Leave" 2011-12-19 +1y {
  now 2012-07-01
}

leaves holiday "Christmas" 2011-12-24 +3d,
       holiday "New Year" 2011-12-31 +3d

shift s1 "Shift 1" {
    leaves annual 2011-12-19 +3w,
           special 2012-01-12 +1d
}

resource team "Team" {
  leaveallowances annual 2011-12-19 20d
  leaves holiday 2012-01-06

  resource r1 "R1" {
    leaves annual 2011-12-19 +3w,
           special 2012-01-12 +1d
  }
  resource r2 "R2" {
    leaveallowances annual 2012-06-01 -10d
    leaves sick 2012-01-04 +2d,
           unpaid 2012-01-10 +3d
  }
}
resource r3 "R3" {
  shifts s1 2011-12-19 +3w
  leaves sick 2012-01-04 +2d
}

task "foo"

resourcereport "." {
  formats html
  columns name, annualleave, annualleavebalance, sickleave, specialleave, unpaidleave
}


<< journalmode << Table Of Contents >> leaves >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/halign.center.html0000644000175000017500000000535312614413013021202 0ustar bernatbernat halign.center

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< gaplength << Table Of Contents >> halign (column) >>


Keyword halign (center)

Purpose
Center the cell content
Syntax center
Arguments none
Context Global scope



<< gaplength << Table Of Contents >> halign (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/sorttasks.html0000644000175000017500000000773312614413013020522 0ustar bernatbernat sorttasks

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< sortresources << Table Of Contents >> start >>


Keyword sorttasks

Purpose
Determines how the tasks are sorted in the report. Multiple criteria can be specified as comma separated list. If one criteria is not sufficient to sort a group of tasks, the next criteria will be used to sort the tasks within this group.
Syntax sorttasks (<tree> | <criteria>) [, <criteria>...]
Arguments tree [ID]
Use 'tree' as first criteria to keep the breakdown structure.
criteria [ABSOLUTE_ID]
The sorting criteria must consist of a property attribute ID. See columnid for a complete list of available attributes. The ID must be suffixed by '.up' or '.down' to determine the sorting direction. Optionally the ID may be prefixed with a scenario ID and a dot to determine the scenario that should be used for sorting. So, possible values are 'plan.start.up' or 'priority.down'.
Context accountreport, resourcereport, statussheetreport, taskreport, textreport, timesheetreport, tracereport



<< sortresources << Table Of Contents >> start >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/richtext.extend.html0000644000175000017500000000700212614413013021572 0ustar bernatbernat richtext.extend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< responsible << Table Of Contents >> right >>


Keyword richtext (extend)

Purpose
Extend the property with a new attribute of type Rich Text.
Syntax richtext <id> <name> [{ <attributes> }]
Arguments id [ID]
The ID of the new attribute. It can be used like the built-in IDs.
name [STRING]
The name of the new attribute. It is used as header in report columns and the like.
Context extend

Attributes inherit (extend), scenariospecific (extend)



<< responsible << Table Of Contents >> right >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/period.limit.html0000644000175000017500000000634312614413013021060 0ustar bernatbernat period.limit

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< period (column) << Table Of Contents >> period (report) >>


Keyword period (limit)

Purpose
This property is a shortcut for setting the start and end dates of the limit interval. Both dates must be within the project time frame.
Syntax period <interval1>
Arguments interval1
See interval1 for details.
Context dailymax, dailymin, maximum, minimum, monthlymax, monthlymin, weeklymax, weeklymin



<< period (column) << Table Of Contents >> period (report) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timezone.export.html0000644000175000017500000000537712614413013021641 0ustar bernatbernat timezone.export

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timezone << Table Of Contents >> timezone (report) >>


Keyword timezone (export)

Purpose
Set the time zone to be used for all dates in the report.
Syntax timezone <STRING>
Arguments none
Context export



<< timezone << Table Of Contents >> timezone (report) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shifts.resource.html0000644000175000017500000000664512614413013021614 0ustar bernatbernat shifts.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shifts (allocate) << Table Of Contents >> shifts (task) >>


Keyword shifts (resource)

Purpose
Limits the working time of a resource to a defined shift during the specified interval. Multiple shifts can be defined, but shift intervals may not overlap. In case a shift is defined for a certain interval, the shift working hours replace the standard resource working hours for this interval.
Syntax shifts <shift> [<interval2>] [, <shift> [<interval2>]...]
Arguments shift [ID]
The ID of a defined shift
interval2
See interval2 for details.
Context resource, supplement (resource)



<< shifts (allocate) << Table Of Contents >> shifts (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/nikureport.html0000644000175000017500000001757712614413013020676 0ustar bernatbernat nikureport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< newtask << Table Of Contents >> note (task) >>


Keyword nikureport

Purpose
This report generates an XML file to be imported into the enterprise resource management software Clarity(R) from Computer Associates(R). The files contains allocation curves for the specified resources. All tasks with identical user defined attributes ClarityPID and ClarityPNAME are bundled into a Clarity project. The resulting XML file can be imported into Clarity with the xog-in tool.
Syntax nikureport <file name> <STRING> { <attributes> }
Arguments file name
The name of the time sheet report file to generate. It must end with a .tji extension, or use . to use the standard output channel.
Context properties

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
end (report) x x
formats x
headline x
hideresource
hidetask
numberformat
period (report)
start (report) x x
timeoff (nikureport)
title x

project "Niku Test" 2010-02-01 +3m {
  timezone "America/Denver"
  extend task {
    text ClarityPID "Clarity PID"
    text ClarityPName "Clarity Project Name"
  }
  extend resource {
    text ClarityRID "Clarity Resource ID"
  }
}

# The ClarityPID and ClarityPName must be always kept in sync. The
# easiest way to achieve this, is by using such macros.
macro PID_p1 [ ClarityPID "p1" ClarityPName "Project 1" ]
macro PID_p2 [ ClarityPID "p2" ClarityPName "Project 2" ]
macro PID_p3 [ ClarityPID "p3" ClarityPName "Project 3" ]

macro Resource [
  resource ${1} "${1}" {
    ClarityRID "${1}"
  }
]

${Resource "r1"}
${Resource "r2"}
${Resource "r3"}

supplement resource r2 {
  vacation 2010-02-15 +1w
}

task "T1" {
  allocate r1
  effort 5w
  ${PID_p1}
}

task t2 "T2" {
  allocate r2
  effort 10d
  ${PID_p1}
}

task "T3" {
  depends !t2
  allocate r2
  effort 10d
  ${PID_p2}
}

task "T4" {
  allocate r3
  effort 3w
  ${PID_p2}
}

nikureport "niku" {
  formats niku, html, csv
  headline "This is a test report"
  period 2010-02-01-8:00 - %{2010-03-01 -6h}
  timeoff "vacations" "Vacation time"
  # Depending on your Clarity configuration, you may need to add this
  # CustomInformation section in the report. It's raw XML code and
  # will be embedded into each <Project> section of the resulting
  # report. This is just an example and it must be customized to work!
  title -8<-
   <CustomInformation>
    <ColumnValue name="foo_state">foo_active</ColumnValue>
    <ColumnValue name="partition_code">foo_eng</ColumnValue>
   </CustomInformation>
  ->8-
}


<< newtask << Table Of Contents >> note (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/treelevel.html0000644000175000017500000000546512614413013020454 0ustar bernatbernat treelevel

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< trackingscenario << Table Of Contents >> vacation >>


Keyword treelevel

Purpose
Returns the nesting level of a property in the property tree. Top level properties have a level of 1, their children 2 and so on.
Syntax treelevel ( )
Arguments none
Context functions



<< trackingscenario << Table Of Contents >> vacation >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/width.html0000644000175000017500000000652712614413013017604 0ustar bernatbernat width

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< weekstartssunday << Table Of Contents >> width (column) >>


Keyword width

Purpose
Set the width of the report in pixels. This attribute is only used for reports that cannot determine the width based on the content. Such report can be freely resized to fit in. The vast majority of reports can determine their width based on the provided content. These reports will simply ignore this setting.
Syntax width <INTEGER>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport
See also height



<< weekstartssunday << Table Of Contents >> width (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/limits.allocate.html0000644000175000017500000000413312614413013021540 0ustar bernatbernat limits.allocate

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< limits << Table Of Contents >> limits (resource) >>


Keyword limits (allocate)

This keyword is no longer supported.



<< limits << Table Of Contents >> limits (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/tracereport.html0000644000175000017500000005135012614413013021011 0ustar bernatbernat tracereport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< tooltip (column) << Table Of Contents >> trackingscenario >>


Keyword tracereport

Purpose

The trace report works noticeably different than all other TaskJuggler reports. It uses a CSV file to track the values of the selected attributes. Each time tj3 is run with the --add-trace option, a new set of values is appended to the CSV file. The first column of the CSV file holds the date when the snapshot was taken. This is either the current date or the now date if provided. There is no need to specify CSV as output format for the report. You can either use these tracked values directly by specifying other report formats or by importing the CSV file into another program.

The first column always contains the current date when that table row was added. All subsequent columns can be defined by the user with the columns attribute. This column set is then repeated for all properties that are not hidden by hideaccount, hideresource and hidetask. By default, all properties are excluded. You must provide at least one of the hide... attributes to select the properties you want to have included in the report. Please be aware that total number of columns is the product of attributes defined with columns times the number of included properties. Select you values carefully or you will end up with very large reports.

The column headers can be customized by using the title attribute. When you include multiple properties, these headers are not unique unless you include mini-queries to modify them based on the property they colum is represeting. You can use the queries <-id->, <-name->, <-scenario-> and <-attribute->. <-id-> is replaced with the ID of the property, <-name-> with the name and so on.

You can change the set of tracked values over time. Old values will be preserved and the corresponding columns will be the last ones in the CSV file.

When other formats are requested, the CSV file is read in and a report that shows the tracked values over time will be generated. The CSV file may contain all kinds of values that are being tracked. Report formats that don't support a mix of different values will just show the values of the second column.

The HTML version generates SVG graphs that are embedded in the HTML page. These graphs are only visble if the web browser supports HTML5. This is true for the latest generation of browsers, but older browsers may not support this format.

Syntax tracereport [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]

The name of the report. This will be the base name for generated output files. The suffix will depend on the specified formats. It will also be used in navigation bars.

By default, report definitions do not generate any files. With more complex projects, most report definitions will be used to describe elements of composed reports. If you want to generate a file from this report, you must specify the list of formats that you want to generate. The report name will then be used as a base name to create the file. The suffix will be appended based on the generated format.

Reports have a local name space. All IDs and file names must be unique within the reports that belong to the same enclosing report. To reference a report for inclusion into another report, you need to specify the full report ID. This is composed of the report ID, prefixed by a dot-separated list of all parent report IDs.

Context accountreport, export, properties, resourcereport, taskreport, textreport, tracereport

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
accountreport
accountroot x
auxdir (report) x x
balance
caption x
center x
columns x
currencyformat
end (report) x x
epilog x
export
flags (report) x
footer x
formats x
header x
headline x
height
hideaccount
hidejournalentry
hideresource
hidetask
journalattributes
journalmode
left x
loadunit
numberformat
opennodes
period (report)
prolog x
purge
rawhtmlhead
resourcereport
resourceroot x
right x
rollupaccount
rollupresource
rolluptask
scenarios x
selfcontained x
sortaccounts
sortjournalentries
sortresources
sorttasks
start (report) x x
taskreport
taskroot x
textreport
timeformat
timezone (report) x x
title x
tracereport
width x

project "Trace Reports" 2012-01-14 +2m

task "Foo"
task "Bar"
task "FooBar"

tracereport "TraceReport" {
  columns end { title "<-name->" }
  hidetask 0
}


<< tooltip (column) << Table Of Contents >> trackingscenario >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/persistent.html0000644000175000017500000000574712614413013020670 0ustar bernatbernat persistent

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< period (task) << Table Of Contents >> precedes >>


Keyword persistent

Purpose
Specifies that once a resource is picked from the list of alternatives this resource is used for the whole task. This is useful when several alternative resources have been specified. Normally the selected resource can change after each break. A break is an interval of at least one timeslot where no resources were available.
Syntax persistent
Arguments none
Context allocate



<< period (task) << Table Of Contents >> precedes >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/onend.html0000644000175000017500000000536512614413013017567 0ustar bernatbernat onend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< numberformat << Table Of Contents >> onstart >>


Keyword onend

Purpose
The target of the dependency is the end of the task.
Syntax onend
Arguments none
Context depends, precedes



<< numberformat << Table Of Contents >> onstart >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/alphabet.html0000644000175000017500000000255612614413013020243 0ustar bernatbernat TaskJuggler Syntax Reference Navigator

A B C D E F G H I J L M N O P R S T V W Y

taskjuggler-3.5.0/manual/html/isleaf.html0000644000175000017500000000533112614413013017720 0ustar bernatbernat isleaf

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isfeatureof << Table Of Contents >> ismilestone >>


Keyword isleaf

Purpose
The result is true if the property is not a container.
Syntax isleaf ( )
Arguments none
Context functions



<< isfeatureof << Table Of Contents >> ismilestone >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/workinghours.shift.html0000644000175000017500000000776012614413013022342 0ustar bernatbernat workinghours.shift

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< workinghours (resource) << Table Of Contents >> yearlyworkingdays >>


Keyword workinghours (shift)

Purpose

Set the working hours for the shift. The working hours specification limits the availability of resources or the activity on a task to certain time slots of week days.

The shift working hours will replace the default or resource working hours for the specified time frame when assigning the shift to a resource.

In case the shift is used for a task, resources are only assigned during the working hours of this shift and during the working hours of the allocated resource. Allocations only happen when both the task shift and the resource work hours allow work to happen.

Syntax workinghours <weekday> [- <end weekday>] [, <weekday> [- <end weekday>]...] (off | <TIME> - <TIME> [, <TIME> - <TIME>...])
Arguments weekday
Weekday (sun - sat)
end weekday
Weekday (sun - sat). It is included in the interval.
Context shift
See also workinghours (project), workinghours (resource)



<< workinghours (resource) << Table Of Contents >> yearlyworkingdays >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/dailyworkinghours.html0000644000175000017500000000742112614413013022243 0ustar bernatbernat dailyworkinghours

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< dailymin << Table Of Contents >> date >>


Keyword dailyworkinghours

Purpose
Set the average number of working hours per day. This is used as the base to convert working hours into working days. This affects for example the length task attribute. The default value is 8 hours and should work for most Western countries. The value you specify should match the settings you specified as your default working hours.
Syntax dailyworkinghours <hours>
Arguments hours
Average number of working hours per working day
Context project

project prj "Example Project" "1.0" 2007-01-01 - 2007-03-09 {
  # The following attributes are all optional. They illustrate the
  # default values. These attributes are only needed if you want to
  # specify different values than those listed below.
  timingresolution 60min
  timezone "America/Denver"
  dailyworkinghours 8
  yearlyworkingdays 260.714
  timeformat "%Y-%m-%d %H:%M"
  shorttimeformat "%H:%M"
  currencyformat "(" ")" "," "." 0
  weekstartsmonday
  workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00
  workinghours sat, sun off
  scenario plan "Plan" {
  }
}

task t "Task" {
  start 2007-01-01
}


<< dailymin << Table Of Contents >> date >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/isactive.html0000644000175000017500000000551112614413013020264 0ustar bernatbernat isactive

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< interval4 << Table Of Contents >> ischildof >>


Keyword isactive

Purpose
Will evaluate to true for tasks and resources if they have bookings in the scenario during the report time frame.
Syntax isactive ( <ID> )
Arguments ID
A scenario ID
Context functions



<< interval4 << Table Of Contents >> ischildof >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/precedes.html0000644000175000017500000001110112614413013020237 0ustar bernatbernat precedes

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< persistent << Table Of Contents >> priority >>


Keyword precedes

Purpose

Specifies that the tasks with the specified IDs cannot start before the task has been finished. If multiple IDs are specified, they must be separated by commas. IDs must be either global or relative. A relative ID starts with a number of '!'. Each '!' moves the scope to the parent task. Global IDs do not contain '!', but have IDs separated by dots.

By using the 'precedes' attribute, the scheduling policy is automatically set to alap. If both depends and precedes are used within a task, the last policy counts.

Syntax precedes (<ABSOLUTE ID> | <ID> | <RELATIVE ID>) [{ <attributes> }] [, (<ABSOLUTE ID> | <ID> | <RELATIVE ID>) [[, ... ]]...]
Arguments ABSOLUTE ID [ABSOLUTE_ID]
A reference using the full qualified ID of a task. The IDs of all enclosing parent tasks must be prepended to the task ID and separated with a dot, e.g. proj.plan.doc.
ID
Just the ID of the task without and parent IDs.
RELATIVE ID
A relative task ID always starts with one or more exclamation marks and is followed by a task ID. Each exclamation mark lifts the scope where the ID is looked for to the enclosing task. The ID may contain some of the parent IDs separated by dots, e. g. !!plan.doc.
Context task, supplement (task)

Attributes gapduration, gaplength, onend, onstart



<< persistent << Table Of Contents >> priority >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/Rich_Text_Attributes.html0000644000175000017500000003530212614413013022555 0ustar bernatbernat Rich_Text_Attributes

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< The_TaskJuggler_Syntax << Table Of Contents >> Software >>


5.4 Rich Text Attributes

TaskJuggler supports Rich Text data for some STRING attributes that are marked accordingly in the syntax reference. Rich Text means, that you can use certain markup symbols to structure the text into sections, include headlines, bullet lists and the like. The following sections describe the supported markup elements and how to use them.

The markup syntax is mostly compatible to the syntax used by the popular MediaWiki.

5.4.1 Block Markups

All block markups are delimited by an empty line. The markup must always start at the beginning of the first line of the block. Block markups cannot be nested.

The simplest form of a block is a paragraph. It's a block of text that is separated by empty lines from other blocks. There is no markup needed to start a text block.

Headlines can be inserted by using = characters to start a line. There are 3 level of headlines.

== Headline Level 1 ==
=== Headline Level 2 ===
==== Headline Level 3 ====

A line that starts with four dashes creates a horizontal line.

----

Items of a bullet list start with a star. The number of stars determines the bullet list level of the item. Three levels are supported. Bullet items may span multiple lines but cannot contain paragraphs.

* Bullet 1
** Bullet 2
*** Bullet 3

Enumerated lists are formed by using a # instead of *.

# Enumeration Level 1
## Enumeration Level 2
### Enumeration Level 3

Sections of lines that start with a space character are interpreted as pre-formatted text. The formatting will be preserved by using a fixed-width font and by not interpreting any markup characters within the text.

 Preformatted text start with
 a single space at the start of 
 each line.

5.4.2 In-Line Markups

In-line markups may occur within a text block. They don't have to start at the start of the line.

This is an ''italic'' word.
This is a '''bold''' word.
This is a ''''monospaced'''' word.
This is a '''''italic and bold''''' word.

The monospace format is not part of the original MediaWiki markup, but we found it useful to have for this manual.

Text can be colored when enclosed in fcol tags.

This is a <fcol:green>green</fcol> word.

The following colors are supported: black, maroon, green, olive, navy, purple, teal, silver, gray, red, lime, yellow, blue, fuchsia, aqua and white.

Alternatively, a hash sign followed by a 3 or 6 digit hexadecimal number can be used as well. The hexadecimal number specifies the values for the red, green and blue component of the color (i. e., #FFF for white).

The above listed in-line markups cannot be nested.

Links to external documents are possible as well. In the first form, the URL will appear in the readable text as well. In the second form, the text after the URL will be visible but the link will be available if the output format supports it.

[http://www.taskjuggler.org]
[http://www.taskjuggler.org The TaskJuggler Web Site]

For local references, the second form is available as well. In this form, .html is appended to the first word in the reference to create the URL.

[[item]]
[[item|An item]]

Images can be added with a similar syntax.

[[File:image.jpg]]
[[File:image.jpg|alt=An image]]

This first version will be replaced with the file image.jpg when the output format supports this. Otherwise a blank space will be inserted. The second version inserts the text An image if the output format does not support images. The following image types are supported and detected by their file name extensions: .jpg, .gif, .png and .svg.

The vertical positioning of the embedded file can be controlled with additional attributes.

[[File:image.svg|text-bottom]]

The following attributes are supported: top, middle, bottom, baseline, sub, super, text-top, text-bottom.

In some situations, it is desirable to not interpret certain markup sequences and reproduce the text verbatim. Such text must be enclosed in nowiki tags.

<nowiki> This is not '''bold''' text. </nowiki>

You can also insert raw HTML code by enclosing it in <html>...</html> tags. For all other output formats, this content will be ignored. There is also no error checking if the code is valid! Use this feature very carefully.

5.4.3 Block and Inline Generators

Block and inline generators are a very powerful extension that allow you to insert arbitrarily complex content. Block generators create a text block whereas inline generators generate an element that fits inside a text paragraph.

Block generators use the following syntax:

<[generator_name parameter1="value1" ... ]>

Inline generators have a very similar syntax:

<-generator_name parameter1="value1" ... ->

Each generator is identified by a name. See the following list for supported generators and their functionality. Generators can have one or more optional parameters. Some parameters are mandatory, other are optional. The value of a parameter must be enclosed in single or double quotes. Since your rich text content must already be enclosed by double or single quotes, make sure you don't use the same quoting marks for the parameter value. Alternatively you can put a backslash in front of the quote mark to escape it.


Block Generator navigator

Parameters:

The navigator generator inserts the referenced navigator.


Block Generator report

Paramters:

  • id : ID of a defined report

The report generator inserts the referenced report as a new block of this text. The referenced report inherits some context such as the report period and the property set from the referencing report.


Inline Generator reportlink

Paramters:

  • id : ID of a defined report
  • attributes: A set of attributes that override the original attributes of the referenced report. All report attributes are supported. Since the value of attributes already must be enclosed by single or double quotes, all single or double quotes contained in the string must be escaped with backslashes. This feature enables reports with content that is customized based on where they have been referenced from. It requires the reports to be dynamically generated and is only available when used with the tj3d web server. The tj3 application will ignore the attributes setting.
taskreport "All" {
  formats html
  columns name { 
    celltext 1 -8<-
      <-query attribute="name"-> <-reportlink id="taskRep"
                   attributes="hidetask plan.id != \"<-id->\""->
      ->8-
  }, start, end
}
taskreport taskRep "Task" {
  formats html
} 

The report link generator inserts a link to the referenced report.


Inline Generator query

Paramters:

  • family : Specifies whether a task or a resource should be queried.
  • property : The ID of the task or resource to be queried. If no property is specified and no property is provided by the scope, the query will return a global project attribute. When the query is used with a scope that already provides a property, a sequence of ! can be used to move the property to the parent property. That way you can access attributes of any of the parents.
  • scopeproperty : The ID of the scope property. If the property is a task this must be a resource ID and vice versa.
  • attribute : The ID of the attribute which value should be returned by the query. If a property ID is provided, this must be one of the names that can be used as columnid values. Without a property, global attributes of the project can be requested. The following attributes are supported: copyright, currency, end, name, now, projectid, start and version.
  • scenario : The ID of a scenario. This must be provided whenever the requested attribute is scenario specific.
  • start : The start date of the report period of the current report.
  • end : The end date of the report period of the current report.
  • loadunit : The loadunit that should be used in case the requested attribute is an effort or duration value.
  • timeformat : The timeformat used to format date attributes.
  • numberformat : The numberformat used to format arithmetic attributes.

The query generator inserts any requested value from the project, a task or a resource.

Queries are context aware. Depending on the context where the query is used, certain or all of the above parameters have already predefined values. When used in the header section of a report, the context does not provide a property or scope property. Start and end dates as well the formatting options are taken from the report context. But when used e. g. in celltext.column the cell provides, that property and the attribute and possibly even the scope property.



<< The_TaskJuggler_Syntax << Table Of Contents >> Software >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/title.html0000644000175000017500000000615412614413013017602 0ustar bernatbernat title

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timingresolution << Table Of Contents >> title (column) >>


Keyword title

Purpose
The title of the report will be used in external references to the report. It will not show up in the reports directly. It's used e. g. by navigator.
Syntax title <STRING>
Arguments none
Context accountreport, nikureport, resourcereport, taskreport, textreport, tracereport



<< timingresolution << Table Of Contents >> title (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/isvalid.html0000644000175000017500000000537512614413013020120 0ustar bernatbernat isvalid

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< istask << Table Of Contents >> journalattributes >>


Keyword isvalid

Purpose
Returns false if argument is not an assigned or properly computed value.
Syntax isvalid ( <ID> )
Arguments none
Context functions



<< istask << Table Of Contents >> journalattributes >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/scheduling.html0000644000175000017500000001107512614413013020604 0ustar bernatbernat scheduling

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< scheduled << Table Of Contents >> select >>


Keyword scheduling

Purpose

Specifies the scheduling policy for the task. A task can be scheduled from start to end (As Soon As Possible, asap) or from end to start (As Late As Possible, alap).

A task can be scheduled from start to end (ASAP mode) when it has a hard (start) or soft (depends) criteria for the start time. A task can be scheduled from end to start (ALAP mode) when it has a hard (end) or soft (precedes) criteria for the end time.

Some task attributes set the scheduling policy implicitly. This attribute can be used to explicitly set the scheduling policy of the task to a certain direction. To avoid it being overwritten again by an implicit attribute this attribute should always be the last attribute of the task.

A random mixture of ASAP and ALAP tasks can have unexpected side effects on the scheduling of the project. It increases significantly the scheduling complexity and results in much longer scheduling times. Especially in projects with many hundreds of tasks the scheduling time of a project with a mixture of ASAP and ALAP times can be 2 to 10 times longer. When the projects contains chains of ALAP and ASAP tasks the tasks further down the dependency chain will be served much later than other non-chained task even when they have a much higher priority. This can result in situations where high priority tasks do not get their resources even though the parallel competing tasks have a much lower priority.

ALAP tasks may not have bookings since the first booked slot determines the start date of the task and prevents it from being scheduled from end to start.

As a general rule, try to avoid ALAP tasks whenever possible. Have a close eye on tasks that have been switched implicitly to ALAP mode because the end attribute comes after the start attribute.

Syntax scheduling (alap | asap)
Arguments none
Context task, supplement (task)



<< scheduled << Table Of Contents >> select >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/ischildof.html0000644000175000017500000000553712614413013020431 0ustar bernatbernat ischildof

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isactive << Table Of Contents >> isdependencyof >>


Keyword ischildof

Purpose
Will evaluate to true for tasks and resources if current property is a child of the provided parent property.
Syntax ischildof ( <ID> )
Arguments ID
The ID of the parent
Context functions



<< isactive << Table Of Contents >> isdependencyof >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/prolog.html0000644000175000017500000000635712614413013017770 0ustar bernatbernat prolog

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< projection << Table Of Contents >> properties >>


Keyword prolog

Purpose
Define a text section that is printed right before the actual report data. The text will be interpreted as Rich Text.
Syntax prolog <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport
See also epilog, footer, header



<< projection << Table Of Contents >> properties >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/author.html0000644000175000017500000000567212614413013017767 0ustar bernatbernat author

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< alternative << Table Of Contents >> auxdir >>


Keyword author

Purpose
This attribute can be used to capture the authorship or source of the information.
Syntax author <resource>
Arguments resource [ID]
The ID of a defined resource
Context journalentry, status (statussheet)



<< alternative << Table Of Contents >> auxdir >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timezone.report.html0000644000175000017500000000632512614413013021625 0ustar bernatbernat timezone.report

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timezone (export) << Table Of Contents >> timezone (shift) >>


Keyword timezone (report)

Purpose
Sets the time zone used for all dates in the report. This setting is ignored if the report is embedded into another report. Embedded in this context means the report is part of another generated report. It does not mean that the report definition is a sub report of another report definition.
Syntax timezone <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< timezone (export) << Table Of Contents >> timezone (shift) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/accountroot.html0000644000175000017500000001076212614413013021021 0ustar bernatbernat accountroot

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< accountreport << Table Of Contents >> active >>


Keyword accountroot

Purpose
Only accounts below the specified root-level accounts are exported. The exported accounts will have the ID of the root-level account stripped from their ID, so that the sub-accounts of the root-level account become top-level accounts in the report file.
Syntax accountroot <ID>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport

project "AccountReport" 2011-11-09 +1y {
  currencyformat "(" ")" "," "." 0
}

account resourceCost "Resource Cost" {
  aggregate resources
  account teamA "Team A"
  account teamB "Team B"
}
account productCost "Product Cost" {
  aggregate tasks
}
account customerPayments "Customer Payments" {
  credits 2011-12-01 "First downpayment" 80000,
          2012-06-01 "Second downpayment" 200000
}

balance productCost customerPayments

resource teamA "Team A" {
  rate 420
  chargeset teamA
  resource "R1"
  resource "R2"
}
resource teamB "Team B" {
  rate 380
  chargeset teamB
  resource "R3"
  resource "R4"
}

task "Products" {
  chargeset productCost

  task p1 "Product 1" {
    effort 20m
    allocate teamA, teamB
  }
  task p2 "Product 2" {
    effort 18m
    allocate teamA
    priority 600
  }
  task p3 "Product 3" {
    effort 6m
    allocate teamB
    priority 600
  }
  task mf "Manufacturing" {
    depends !p1, !p2, !p3
    duration 2w
    charge 12000 onend
  }
  task "Final Payment" {
    depends !mf
    purge chargeset
    chargeset customerPayments
    charge 170000 onstart
  }
}

accountreport "TeamBudget" {
  formats html
  accountroot resourceCost
  balance -
  columns no, name, quarterly { celltext 1 "<-query attribute='turnover'->" }
}

accountreport "ProfiAndLoss" {
  formats html
  columns no, name, monthly
}


<< accountreport << Table Of Contents >> active >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/formats.export.html0000644000175000017500000000767112614413013021461 0ustar bernatbernat formats.export

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< formats << Table Of Contents >> functions >>


Keyword formats (export)

This keyword has not yet been fully tested yet. You are welcome to try it, but it may lead to wrong results. The syntax may still change with future versions. The developers appreciate any feedback on this keyword.

Purpose
This attribute defines for which output formats the export report should be generated. By default, the TJP format will be used.
Syntax formats (tjp | mspxml) [, (tjp | mspxml)...]
Arguments tjp
Export of the scheduled project in TJP syntax.
mspxml
Export of the scheduled project in Microsoft Project XML format. This will export the data of the fully scheduled project. The exported data include the tasks, resources and the assignments of resources to task. This is only a small subset of the data that TaskJuggler can manage. This export is intended to share resource assignment data with other teams using Microsoft Project. TaskJuggler manages assignments with a larger accuracy than the Microsft Project XML format can represent. This will inevitably lead to some rounding errors and different interpretation of the data. The numbers you will see in Project are not necessarily an exact match of the numbers you see in TaskJuggler.
Context export



<< formats << Table Of Contents >> functions >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/startcredit.html0000644000175000017500000000644412614413013021013 0ustar bernatbernat startcredit

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< start (report) << Table Of Contents >> status (statussheet) >>


Keyword startcredit

This keyword should no longer be used. It will be removed in future versions of this software.

Use charge instead.

Purpose
Specifies an amount that is credited to the account specified by the chargeset attributes at the moment the tasks starts.
Syntax startcredit (<INTEGER> | <FLOAT>)
Arguments none
Context task, supplement (task)
See also charge



<< start (report) << Table Of Contents >> status (statussheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/isresponsibilityof.html0000644000175000017500000000623412614413013022420 0ustar bernatbernat isresponsibilityof

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isresource << Table Of Contents >> istask >>


Keyword isresponsibilityof

Purpose
Will evaluate to true for tasks that have the specified resource assigned as responsible in the specified scenario.
Syntax isresponsibilityof ( <Resource ID> , <Scenario ID> )
Arguments Resource ID [ID]
The ID of a defined resource
Scenario ID [ID]
A scenario ID
Context functions



<< isresource << Table Of Contents >> istask >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/selfcontained.html0000644000175000017500000000577112614413013021303 0ustar bernatbernat selfcontained

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< select << Table Of Contents >> shift >>


Keyword selfcontained

Purpose
Try to generate selfcontained output files when the format supports this. E. g. for HTML reports, the style sheet will be included and no icons will be used.
Syntax selfcontained (yes | no)
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< select << Table Of Contents >> shift >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timeformat.html0000644000175000017500000002342312614413013020626 0ustar bernatbernat timeformat

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< textreport << Table Of Contents >> timeformat1 >>


Keyword timeformat

Purpose
Determines how time specifications in reports look like.
Syntax timeformat <format>
Arguments format [STRING]

Ordinary characters placed in the format string are copied without conversion. Conversion specifiers are introduced by a `%' character, and are replaced in s as follows:

  • %a The abbreviated weekday name according to the current locale.
  • %A The full weekday name according to the current locale.
  • %b The abbreviated month name according to the current locale.
  • %B The full month name according to the current locale.
  • %c The preferred date and time representation for the current locale.
  • %C The century number (year/100) as a 2-digit integer. (SU)
  • %d The day of the month as a decimal number (range 01 to 31).
  • %e Like %d, the day of the month as a decimal number, but a leading zero is replaced by a space. (SU)
  • %E Modifier: use alternative format, see below. (SU)
  • %F Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)
  • %G The ISO 8601 year with century as a decimal number. The 4-digit year corresponding to the ISO week number (see %V). This has the same format and value as %y, except that if the ISO week number belongs to the previous or next year, that year is used instead. (TZ)
  • %g Like %G, but without century, i.e., with a 2-digit year (00-99). (TZ)
  • %h Equivalent to %b. (SU)
  • %H The hour as a decimal number using a 24-hour clock (range 00 to 23).
  • %I The hour as a decimal number using a 12-hour clock (range 01 to 12).
  • %j The day of the year as a decimal number (range 001 to 366).
  • %k The hour (24-hour clock) as a decimal number (range 0 to 23); single digits are preceded by a blank. (See also %H.) (TZ)
  • %l The hour (12-hour clock) as a decimal number (range 1 to 12); single digits are preceded by a blank. (See also %I.) (TZ)
  • %m The month as a decimal number (range 01 to 12).
  • %M The minute as a decimal number (range 00 to 59).
  • %n A newline character. (SU)
  • %O Modifier: use alternative format, see below. (SU)
  • %p Either 'AM' or 'PM' according to the given time value, or the corresponding strings for the current locale. Noon is treated as `pm' and midnight as 'am'.
  • %P Like %p but in lowercase: 'am' or 'pm' or %a corresponding string for the current locale. (GNU)
  • %r The time in a.m. or p.m. notation. In the POSIX locale this is equivalent to %I:%M:%S %p. (SU)
  • %R The time in 24-hour notation (%H:%M). (SU) For a version including the seconds, see %T below.
  • %s The number of seconds since the Epoch, i.e., since 1970-01-01 00:00:00 UTC. (TZ)
  • %S The second as a decimal number (range 00 to 61).
  • %t A tab character. (SU)
  • %T The time in 24-hour notation (%H:%M:%S). (SU)
  • %u The day of the week as a decimal, range 1 to 7, Monday being 1. See also %w. (SU)
  • %U The week number of the current year as a decimal number, range 00 to 53, starting with the first Sunday as the first day of week 01. See also %V and %W.
  • %V The ISO 8601:1988 week number of the current year as a decimal number, range 01 to 53, where week 1 is the first week that has at least 4 days in the current year, and with Monday as the first day of the week. See also %U and %W. %(SU)
  • %w The day of the week as a decimal, range 0 to 6, Sunday being 0. See also %u.
  • %W The week number of the current %year as a decimal number, range 00 to 53, starting with the first Monday as the first day of week 01.
  • %x The preferred date representation for the current locale without the time.
  • %X The preferred time representation for the current locale without the date.
  • %y The year as a decimal number without a century (range 00 to 99).
  • %Y The year as a decimal number including the century.
  • %z The time zone as hour offset from GMT. Required to emit RFC822-conformant dates (using %a, %d %%b %Y %H:%M:%S %%z). (GNU)
  • %Z The time zone or name or abbreviation.
  • %+ The date and time in date(1) format. (TZ)
  • %% A literal % character.

Some conversion specifiers can be modified by preceding them by the E or O modifier to indicate that an alternative format should be used. If the alternative format or specification does not exist for the current locale, the behavior will be as if the unmodified conversion specification were used.

(SU) The Single Unix Specification mentions %Ec, %EC, %Ex, %%EX, %Ry, %EY, %Od, %Oe, %OH, %OI, %Om, %OM, %OS, %Ou, %OU, %OV, %Ow, %OW, %Oy, where the effect of the O modifier is to use alternative numeric symbols (say, Roman numerals), and that of the E modifier is to use a locale-dependent alternative representation.

This documentation of the timeformat attribute has been taken from the man page of the GNU strftime function.

Context accountreport, project, resourcereport, taskreport, textreport, tracereport



<< textreport << Table Of Contents >> timeformat1 >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/rollupaccount.html0000644000175000017500000001162612614413013021353 0ustar bernatbernat rollupaccount

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< right << Table Of Contents >> rollupresource >>


Keyword rollupaccount

Purpose
Do not show sub-accounts of accounts that match the specified logical expression.
Syntax rollupaccount (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< right << Table Of Contents >> rollupresource >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/copyright.html0000644000175000017500000000665312614413013020475 0ustar bernatbernat copyright

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< complete << Table Of Contents >> credits >>


Keyword copyright

Purpose
Set a copyright notice for the project file and its content. This copyright notice will be added to all reports that can support it.
Syntax copyright <STRING>
Arguments none
Context properties

copyright "Tux Inc."


resource r1 "Resource 1"

task plant "How to plant a tree" {
 start 2007-01-01
 # All sub-tasks inherit this allocation of r1
 allocate r1
 task plan "Choose the planting site" {
   effort 2d
 }
 task buy "Get a tree" {
   effort 1d
   depends !plan
 }
 task action "Plant the tree" {
   effort 0.5d
   depends !buy
 }
}

taskreport planttree "PlantTree.html" {
  formats html
  caption "This project shows how to plant a tree easily"
  headline "How to plant a tree"
  columns name, start, end, daily
  # Don't hide any resource, thus show them all.
  hideresource 0
}


<< complete << Table Of Contents >> credits >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/tooltip.column.html0000644000175000017500000001242212614413013021442 0ustar bernatbernat tooltip.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< title (column) << Table Of Contents >> tracereport >>


Keyword tooltip (column)

Purpose
Specifies an alternative content for the tooltip. This will replace the original content of the tooltip that would be available for columns with text that does not fit the column with. The logical expression specifies for which cells the text should be used. If multiple tooltip patterns are provided for a column, the first matching one is taken for each cell.
Syntax tooltip (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none)) <text>
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
text [STRING]
The content of the tooltip. The text is interpreted as Rich Text.
Context columns



<< title (column) << Table Of Contents >> tracereport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/length.html0000644000175000017500000001212412614413013017734 0ustar bernatbernat length

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< left << Table Of Contents >> limits >>


Keyword length

Purpose

Specifies the duration of this task as working time, not calendar time. 7d means 7 working days, or 7 times 8 hours (assuming default settings), not one week.

A task with a length specification may have resource allocations. Resources are allocated when they are available. There is no guarantee that the task will get any resources allocated. The availability of resources has no impact on the duration of the task. A time slot where none of the specified resources is available is still considered working time, if there is no global vacation and global working hours are defined accordingly.

For the length calculation, the global working hours and the global leaves matter unless the task has shifts assigned. In the latter case the working hours and leaves of the shift apply for the specified period to determine if a slot is working time or not. If a resource has additinal working hours defined, it's quite possible that a task with a length of 5d will have an allocated effort larger than 40 hours. Resource working hours only have an impact on whether an allocation is made or not for a particular time slot. They don't effect the resulting duration of the task.

Tasks may not have subtasks if this attribute is used. Setting this attribute will reset the duration, effort and milestone attributes.

Syntax length <value> (min | h | d | w | m | y)
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context task, supplement (task)
See also duration, effort



<< left << Table Of Contents >> limits >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/functions.html0000644000175000017500000001223412614413013020465 0ustar bernatbernat functions

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< formats (export) << Table Of Contents >> gapduration >>


Keyword functions

Purpose

The following functions are supported in logical expressions. These functions are evaluated in logical conditions such as hidetask or rollupresource. For the evaluation, implicit and explicit parameters are used.

All functions may operate on the current property and the scope property. The scope property is the enclosing property in reports with nested properties. Imagine e. g a task report with nested resources. When the function is called for a task line, the task is the property and we don't have a scope property. When the function is called for a resource line, the resource is the property and the enclosing task is the scope property.

These number of arguments that are passed in brackets to the function depends on the specific function. See the reference for details on each function.

All functions can be suffixed with an underscore character. In that case, the function is operating on the scope property as if it were the property. The original property is ignored in that case. In our task report example from above, calling a function with an appended dash would mean that a task line would be evaluated for the enclosing resource.

In the example below you can see how this can be used. To generate a task report that lists all assigned leaf resources for leaf task lines only we use the expression

hideresource ~(isleaf() & isleaf_())

The tilde in front of the bracketed expression means not that expression. In other words: show resources that are leaf resources and show them for leaf tasks only. The regular form isleaf() (without the appended underscore) operates on the resource. The isleaf_() variant operates on the enclosing task.

Context Global scope

Attributes hasalert, isactive, ischildof, isdependencyof, isdutyof, isfeatureof, isleaf, ismilestone, isongoing, isresource, isresponsibilityof, istask, isvalid, treelevel

taskreport "LogicalFunction" {
  formats html
  hideresource ~(isleaf() & isleaf_())
}


<< formats (export) << Table Of Contents >> gapduration >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/isongoing.html0000644000175000017500000000547512614413013020462 0ustar bernatbernat isongoing

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< ismilestone << Table Of Contents >> isresource >>


Keyword isongoing

Purpose
Will evaluate to true for tasks that overlap with the report period in given scenario.
Syntax isongoing ( <ID> )
Arguments ID
A scenario ID
Context functions



<< ismilestone << Table Of Contents >> isresource >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/select.html0000644000175000017500000000777612614413013017753 0ustar bernatbernat select

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< scheduling << Table Of Contents >> selfcontained >>


Keyword select

Purpose

The select functions controls which resource is picked from an allocation and it's alternatives. The selection is re-evaluated each time the resource used in the previous time slot becomes unavailable.

Even for non-persistent allocations a change in the resource selection only happens if the resource used in the previous (or next for ASAP tasks) time slot has become unavailable.

Syntax select (maxloaded | minloaded | minallocated | order | random)
Arguments maxloaded
Pick the available resource that has been used the most so far.
minloaded
Pick the available resource that has been used the least so far.
minallocated
Pick the resource that has the smallest allocation factor. The allocation factor is calculated from the various allocations of the resource across the tasks. This is the default setting.)
order
Pick the first available resource from the list.
random
Pick a random resource from the list.
Context allocate



<< scheduling << Table Of Contents >> selfcontained >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/priority.html0000644000175000017500000001113012614413013020330 0ustar bernatbernat priority

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< precedes << Table Of Contents >> priority (timesheet) >>


Keyword priority

Purpose

Specifies the priority of the task. A task with higher priority is more likely to get the requested resources. The default priority value of all tasks is 500. Don't confuse the priority of a tasks with the importance or urgency of a task. It only increases the chances that the tasks gets the requested resources. It does not mean that the task happens earlier, though that is usually the effect you will see. It also does not have any effect on tasks that don't have any resources assigned (e.g. milestones).

For milestones it will raise or lower the chances that task leading up the milestone will get their resources over task with equal priority that compete for the same resources.

This attribute is inherited by subtasks if specified prior to the definition of the subtask.

Syntax priority <value>
Arguments value [INTEGER]
Priority value (1 - 1000)
Context task, supplement (task)

project "Priority Demo" 2011-04-17-0:00--0700 +2m {
  timezone "America/Denver"
}

resource tux "Tux"

task jobs "Project breakdown" {
  start ${projectstart}

  task work "The regular work" {
    effort 20d
    priority 500
    allocate tux
    limits { weeklymax 25h }
  }

  task support "Customer Support" {
    # This is a high priority task. Due to the high priority tux is
    # spending the required daily maximum on it.
    end ${projectend}
    priority 800
    allocate tux
    limits { dailymax 2h }
  }

  task conference "Attend Conference" {
    period 2011-04-25 +2d
    allocate tux
    priority 1000
  }

  task maintenance "Maintenance work" {
    # This is a fallback task. Whenever tux is not doing something
    # else he is allocated to this task.
    end ${projectend}
    priority 300
    allocate tux
    limits { weeklymax 2d }
  }
}


<< precedes << Table Of Contents >> priority (timesheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/journalattributes.html0000644000175000017500000001132712614413013022240 0ustar bernatbernat journalattributes

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isvalid << Table Of Contents >> journalentry >>


Keyword journalattributes

Purpose
A list that determines which of the journal attributes should be included in the journal report.
Syntax journalattributes (* | - | (alert | author | date | details | flags | headline | property | propertyid | summary | timesheet) [, (alert | author | date | details | flags | headline | property | propertyid | summary | timesheet)...])
Arguments *
A shortcut for all items
-
No items
alert
Include the alert status
author
Include the author if known
date
Include the date
details
Include the details
flags
Include the flags
headline
Include the headline
property
Include the task or resource name
propertyid
Include the property ID. Requires 'property'.
summary
Include the summary
timesheet
Include the timesheet information. Requires 'property'.
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< isvalid << Table Of Contents >> journalentry >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/aggregate.html0000644000175000017500000001051512614413013020403 0ustar bernatbernat aggregate

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< adopt (task) << Table Of Contents >> alert >>


Keyword aggregate

Purpose
Specifies whether the account is used to track task or resource specific amounts. The default is to track tasks.
Syntax aggregate (resources | tasks)
Arguments resources
Aggregate resources
tasks
Aggregate tasks
Context account

project "AccountReport" 2011-11-09 +1y {
  currencyformat "(" ")" "," "." 0
}

account resourceCost "Resource Cost" {
  aggregate resources
  account teamA "Team A"
  account teamB "Team B"
}
account productCost "Product Cost" {
  aggregate tasks
}
account customerPayments "Customer Payments" {
  credits 2011-12-01 "First downpayment" 80000,
          2012-06-01 "Second downpayment" 200000
}

balance productCost customerPayments

resource teamA "Team A" {
  rate 420
  chargeset teamA
  resource "R1"
  resource "R2"
}
resource teamB "Team B" {
  rate 380
  chargeset teamB
  resource "R3"
  resource "R4"
}

task "Products" {
  chargeset productCost

  task p1 "Product 1" {
    effort 20m
    allocate teamA, teamB
  }
  task p2 "Product 2" {
    effort 18m
    allocate teamA
    priority 600
  }
  task p3 "Product 3" {
    effort 6m
    allocate teamB
    priority 600
  }
  task mf "Manufacturing" {
    depends !p1, !p2, !p3
    duration 2w
    charge 12000 onend
  }
  task "Final Payment" {
    depends !mf
    purge chargeset
    chargeset customerPayments
    charge 170000 onstart
  }
}

accountreport "TeamBudget" {
  formats html
  accountroot resourceCost
  balance -
  columns no, name, quarterly { celltext 1 "<-query attribute='turnover'->" }
}

accountreport "ProfiAndLoss" {
  formats html
  columns no, name, monthly
}


<< adopt (task) << Table Of Contents >> alert >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/taskreport.html0000644000175000017500000004642112614413013020660 0ustar bernatbernat taskreport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< taskprefix << Table Of Contents >> taskroot >>


Keyword taskreport

Purpose

The report lists tasks and their respective values in a table. To reduce the list of included tasks, you can use the hidetask, rolluptask or taskroot attributes. The order of the task can be controlled with sorttasks. If the first sorting criteria is tree sorting, the parent tasks will always be included to form the tree. Tree sorting is the default. You need to change it if you do not want certain parent tasks to be included in the report.

By default, all the resources that are allocated to each task are hidden, but they can be listed as well. Use the hideresource attribute to select which resources should be included.

Syntax taskreport [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]

The name of the report. This will be the base name for generated output files. The suffix will depend on the specified formats. It will also be used in navigation bars.

By default, report definitions do not generate any files. With more complex projects, most report definitions will be used to describe elements of composed reports. If you want to generate a file from this report, you must specify the list of formats that you want to generate. The report name will then be used as a base name to create the file. The suffix will be appended based on the generated format.

Reports have a local name space. All IDs and file names must be unique within the reports that belong to the same enclosing report. To reference a report for inclusion into another report, you need to specify the full report ID. This is composed of the report ID, prefixed by a dot-separated list of all parent report IDs.

Context accountreport, export, properties, resourcereport, taskreport, textreport, tracereport

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
accountreport
accountroot x
auxdir (report) x x
balance
caption x
center x
columns x
currencyformat
end (report) x x
epilog x
export
flags (report) x
footer x
formats x
header x
headline x
height
hideaccount
hidejournalentry
hideresource
hidetask
journalattributes
journalmode
left x
loadunit
numberformat
opennodes
period (report)
prolog x
purge
rawhtmlhead
resourcereport
resourceroot x
right x
rollupaccount
rollupresource
rolluptask
scenarios x
selfcontained x
sortaccounts
sortjournalentries
sortresources
sorttasks
start (report) x x
taskreport
taskroot x
textreport
timeformat
timezone (report) x x
title x
tracereport
width x

project "Simple Project" 2005-06-06 - 2005-06-26 {
  timezone "America/Denver"
}

copyright "Bucks Beavis Inc."

resource tux "Tux"

task items "Project breakdown" {
  start 2005-06-06

  task plan "Plan work" {
    length 3d
  }

  task implementation "Implement work" {
    effort 5d
    allocate tux
    depends !plan
  }

  task acceptance "Customer acceptance" {
    duration 5d
    depends !implementation
  }
}

taskreport breakdown "ProjectBreakdown.html" {
  formats html
  caption "This is the project breakdown"
  headline "Project Breakdown"
  columns name, start, end, daily
  # Don't hide any resource, meaning show them all.
  hideresource 0
}


<< taskprefix << Table Of Contents >> taskroot >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/listitem.column.html0000644000175000017500000000600412614413013021601 0ustar bernatbernat listitem.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< limits (task) << Table Of Contents >> listtype (column) >>


Keyword listitem (column)

Purpose
Specifies a RichText pattern that is used to generate the text for the list items. The pattern should contain at least one <-query attribute='XXX'-> element that will be replaced with the value of attribute XXX. For the replacement, the property of the query will be the list item.
Syntax listitem <STRING>
Arguments none
Context columns



<< limits (task) << Table Of Contents >> listtype (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/status.timesheet.html0000644000175000017500000001016412614413013021766 0ustar bernatbernat status.timesheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< status (statussheet) << Table Of Contents >> statussheet >>


Keyword status (timesheet)

Purpose
The status attribute can be used to describe the current status of the task or resource. The content of the status messages is added to the project journal. The status section is optional for tasks that have been worked on less than one day during the report interval.
Syntax status <alert level> <STRING> [{ <attributes> }]
Arguments alert level [ID]
By default supported values are green, yellow and red. The default value is green. You can define your own levels with alertlevels.
Context timesheet, newtask, task (timesheet)

Attributes details, flags (timesheet), summary

timesheet r1 2009-11-30 +1w {
  task t1 {
    work 3d
    remaining 0d
    status green "All work done" {
      summary "I had good fun!"
      details -8<-
        This task went smoothly and I got three things done:
        * Have fun
        * Be on time
        * Get things done
      ->8-
    }
  }
}


<< status (statussheet) << Table Of Contents >> statussheet >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/flags.timesheet.html0000644000175000017500000000557212614413013021546 0ustar bernatbernat flags.timesheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< flags (task) << Table Of Contents >> fontcolor (column) >>


Keyword flags (timesheet)

Purpose
Time sheet entries can have flags attached to them. These can be used to include only entries in a report that have a certain flag.
Syntax flags <ID> [, <ID>...]
Arguments none
Context status (timesheet)



<< flags (task) << Table Of Contents >> fontcolor (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/auxdir.html0000644000175000017500000000662112614413013017754 0ustar bernatbernat auxdir

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< author << Table Of Contents >> auxdir (report) >>


Keyword auxdir

This keyword has not yet been fully tested yet. You are welcome to try it, but it may lead to wrong results. The syntax may still change with future versions. The developers appreciate any feedback on this keyword.

Purpose
Specifies an alternative directory for the auxiliary report files such as CSS, JavaScript and icon files. This setting will affect all subsequent report definitions unless it gets overridden. If this attribute is not set, the directory and its contents will be generated automatically. If this attribute is provided, the user has to ensure that the directory exists and is filled with the proper data. The specified path can be absolute or relative to the generated report file.
Syntax auxdir <STRING>
Arguments none
Context properties



<< author << Table Of Contents >> auxdir (report) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shifts.task.html0000644000175000017500000000715512614413013020724 0ustar bernatbernat shifts.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shifts (resource) << Table Of Contents >> shorttimeformat >>


Keyword shifts (task)

Purpose
Limits the working time for this task during the during the specified interval to the working hours of the given shift. Multiple shifts can be defined, but shift intervals may not overlap. This is an additional working time restriction ontop of the working hours of the allocated resources. It does not replace the resource working hour restrictions. For a resource to be assigned to a time slot, both the respective task shift as well as the resource working hours must declare the time slot as duty slot.
Syntax shifts <shift> [<interval2>] [, <shift> [<interval2>]...]
Arguments shift [ID]
The ID of a defined shift
interval2
See interval2 for details.
Context task, supplement (task)



<< shifts (resource) << Table Of Contents >> shorttimeformat >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/vacation.shift.html0000644000175000017500000000612012614413013021372 0ustar bernatbernat vacation.shift

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< vacation (resource) << Table Of Contents >> warn >>


Keyword vacation (shift)

Purpose
Specify a vacation period associated with this shift.
Syntax vacation [<name>] [<interval3> [, <interval3>...]]
Arguments name [STRING]
An optional name or reason for the leave
interval3
See interval3 for details.
Context shift



<< vacation (resource) << Table Of Contents >> warn >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/halign.column.html0000644000175000017500000001163212614413013021214 0ustar bernatbernat halign.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< halign (center) << Table Of Contents >> halign (left) >>


Keyword halign (column)

Purpose
Specifies the horizontal alignment of the cell content. The logical expression specifies for which cells the alignment setting should be used. If multiple halign patterns are provided for a column, the first matching one is used for each cell.
Syntax halign (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none)) (center | left | right)
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context columns



<< halign (center) << Table Of Contents >> halign (left) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/sloppy.booking.html0000644000175000017500000000711612614413013021435 0ustar bernatbernat sloppy.booking

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shorttimeformat << Table Of Contents >> sloppy (projection) >>


Keyword sloppy (booking)

Purpose
Controls how strict TaskJuggler checks booking intervals for conflicts with working periods and leaves. This attribute only affects the check for conflicts. No assignments will be made unless the overtime attribute is set accordingly.
Syntax sloppy <sloppyness>
Arguments sloppyness [INTEGER]
  • 0: Period may not contain any off-duty hours, vacation or other task assignments. (default)
  • 1: Period may contain off-duty hours, but no vacation time or other task assignments.
  • 2: Period may contain off-duty hours and vacation time, but no other task assignments.
Context booking (resource), booking (task)



<< shorttimeformat << Table Of Contents >> sloppy (projection) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/sloppy.projection.html0000644000175000017500000000615712614413013022165 0ustar bernatbernat sloppy.projection

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< sloppy (booking) << Table Of Contents >> sortaccounts >>


Keyword sloppy (projection)

This keyword should no longer be used. It will be removed in future versions of this software.

Use trackingscenario instead.

Purpose
Syntax sloppy
Arguments none
Context Global scope
See also trackingscenario



<< sloppy (booking) << Table Of Contents >> sortaccounts >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/halign.left.html0000644000175000017500000000537112614413013020654 0ustar bernatbernat halign.left

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< halign (column) << Table Of Contents >> halign (right) >>


Keyword halign (left)

Purpose
Left align the cell content
Syntax left
Arguments none
Context Global scope



<< halign (column) << Table Of Contents >> halign (right) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/halign.right.html0000644000175000017500000000534112614413013021034 0ustar bernatbernat halign.right

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< halign (left) << Table Of Contents >> hasalert >>


Keyword halign (right)

Purpose
Right align the cell content
Syntax right
Arguments none
Context Global scope



<< halign (left) << Table Of Contents >> hasalert >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/minstart.html0000644000175000017500000000603112614413013020314 0ustar bernatbernat minstart

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< minimum << Table Of Contents >> monthlymax >>


Keyword minstart

Purpose
Specifies the minimum wanted start time of the task. The value is not used during scheduling, but is checked after all tasks have been scheduled. If the start of the task is earlier than the specified value, then an error is reported.
Syntax minstart <date>
Arguments date
See date for details.
Context task, supplement (task)



<< minimum << Table Of Contents >> monthlymax >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/summary.html0000644000175000017500000000654612614413013020163 0ustar bernatbernat summary

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< strict (projection) << Table Of Contents >> supplement >>


Keyword summary

Purpose
This is the introductory part of the journal or status entry. It should only summarize the full entry but should contain more details than the headline. The text including formatting characters must be 240 characters long or less.
Syntax summary <text>
Arguments text [STRING]
The text will be interpreted as Rich Text. Only a small subset of the markup is supported for this attribute. You can use word formatting, hyperlinks and paragraphs.
Context journalentry, status (statussheet), status (timesheet)



<< strict (projection) << Table Of Contents >> supplement >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/endcredit.html0000644000175000017500000001027112614413013020415 0ustar bernatbernat endcredit

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< end (timesheet) << Table Of Contents >> epilog >>


Keyword endcredit

This keyword should no longer be used. It will be removed in future versions of this software.

Use charge instead.

Purpose
Specifies an amount that is credited to the accounts specified by the chargeset attributes at the moment the tasks ends.
Syntax endcredit (<INTEGER> | <FLOAT>)
Arguments none
Context task, supplement (task)
See also charge

account project_cost "Project Costs"
account payments "Customer Payments"{
  credits 2007-01-01 "Customer down payment" 500.0,
          2007-01-14 "1st rate" 2000.0
}

balance project_cost payments

resource tux "Tux" {
  rate 300
}
resource konqui "Konqui" {
  rate 200
}

task items "Room decoration" {
  start 2007-01-06
  # The default account for all tasks
  chargeset project_cost

  task plan "Plan work and buy material" {
    # Upfront material cost
    charge 500.0 onstart
    length 2d
  }
   task remove "Remove old inventory" {
    allocate tux
    allocate konqui
    effort 1d
    depends !plan
  }
  task implement "Arrange new decoration" {
    effort 5d
    allocate tux, konqui
    depends !remove
  }
  task acceptance "Presentation and customer acceptance" {
    duration 5d
    depends !implement
    chargeset payments
    # Customer pays at end of acceptance
    charge 2000.0 onend
  }
}


<< end (timesheet) << Table Of Contents >> epilog >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/header.html0000644000175000017500000000713512614413013017711 0ustar bernatbernat header

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< hasalert << Table Of Contents >> headline >>


Keyword header

Purpose
Define a text section that is put at the top of the report. The text will be interpreted as Rich Text.
Syntax header <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport
See also epilog, footer, prolog

project "Test" 2011-12-11 +1m

task "Foo"

textreport frame "textreport" {
  taskreport r1 ""
  taskreport r2 ""

  formats html
  header -8<-
  This is the header
  ----
  ->8-
  left "<[report id='frame.r1']>"
  center "Center"
  right "<[report id='frame.r2']>"
  footer -8<-
  ----
  This is the footer
  ->8-
}


<< hasalert << Table Of Contents >> headline >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/sortresources.html0000644000175000017500000001002512614413013021373 0ustar bernatbernat sortresources

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< sortjournalentries << Table Of Contents >> sorttasks >>


Keyword sortresources

Purpose
Determines how the resources are sorted in the report. Multiple criteria can be specified as a comma separated list. If one criteria is not sufficient to sort a group of resources, the next criteria will be used to sort the resources in this group.
Syntax sortresources (<tree> | <criteria>) [, <criteria>...]
Arguments tree [ID]
Use 'tree' as first criteria to keep the breakdown structure.
criteria [ABSOLUTE_ID]
The sorting criteria must consist of a property attribute ID. See columnid for a complete list of available attributes. The ID must be suffixed by '.up' or '.down' to determine the sorting direction. Optionally the ID may be prefixed with a scenario ID and a dot to determine the scenario that should be used for sorting. So, possible values are 'plan.start.up' or 'priority.down'.
Context accountreport, resourcereport, statussheetreport, taskreport, textreport, timesheetreport, tracereport



<< sortjournalentries << Table Of Contents >> sorttasks >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/weekstartsmonday.html0000644000175000017500000000547712614413013022074 0ustar bernatbernat weekstartsmonday

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< weeklymin << Table Of Contents >> weekstartssunday >>


Keyword weekstartsmonday

Purpose
Specify that you want to base all week calculation on weeks starting on Monday. This is common in many European countries.
Syntax weekstartsmonday
Arguments none
Context project



<< weeklymin << Table Of Contents >> weekstartssunday >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/end.limit.html0000644000175000017500000000616312614413013020344 0ustar bernatbernat end.limit

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< end (column) << Table Of Contents >> end (report) >>


Keyword end (limit)

Purpose
The end date of the limit interval. It must be within the project time frame.
Syntax end <date>
Arguments date
See date for details.
Context dailymax, dailymin, maximum, minimum, monthlymax, monthlymin, weeklymax, weeklymin



<< end (column) << Table Of Contents >> end (report) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/balance.html0000644000175000017500000001154712614413013020050 0ustar bernatbernat balance

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< auxdir (report) << Table Of Contents >> booking (resource) >>


Keyword balance

Purpose

During report generation, TaskJuggler can consider some accounts to be revenue accounts, while other can be considered cost accounts. By using the balance attribute, two top-level accounts can be designated for a profit-loss-analysis. This analysis includes all sub accounts of these two top-level accounts.

To clear a previously set balance, just use a -.

Syntax balance (<cost account> <ID> | -)
Arguments cost account
The top-level account that is used for all cost related charges.
Context accountreport, properties, resourcereport, taskreport, textreport, tracereport

project "AccountReport" 2011-11-09 +1y {
  currencyformat "(" ")" "," "." 0
}

account resourceCost "Resource Cost" {
  aggregate resources
  account teamA "Team A"
  account teamB "Team B"
}
account productCost "Product Cost" {
  aggregate tasks
}
account customerPayments "Customer Payments" {
  credits 2011-12-01 "First downpayment" 80000,
          2012-06-01 "Second downpayment" 200000
}

balance productCost customerPayments

resource teamA "Team A" {
  rate 420
  chargeset teamA
  resource "R1"
  resource "R2"
}
resource teamB "Team B" {
  rate 380
  chargeset teamB
  resource "R3"
  resource "R4"
}

task "Products" {
  chargeset productCost

  task p1 "Product 1" {
    effort 20m
    allocate teamA, teamB
  }
  task p2 "Product 2" {
    effort 18m
    allocate teamA
    priority 600
  }
  task p3 "Product 3" {
    effort 6m
    allocate teamB
    priority 600
  }
  task mf "Manufacturing" {
    depends !p1, !p2, !p3
    duration 2w
    charge 12000 onend
  }
  task "Final Payment" {
    depends !mf
    purge chargeset
    chargeset customerPayments
    charge 170000 onstart
  }
}

accountreport "TeamBudget" {
  formats html
  accountroot resourceCost
  balance -
  columns no, name, quarterly { celltext 1 "<-query attribute='turnover'->" }
}

accountreport "ProfiAndLoss" {
  formats html
  columns no, name, monthly
}


<< auxdir (report) << Table Of Contents >> booking (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/definitions.html0000644000175000017500000000772412614413013021000 0ustar bernatbernat definitions

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< date (extend) << Table Of Contents >> depends >>


Keyword definitions

Purpose

This attributes controls what definitions will be contained in the report. If the list includes project, the generated file will have a .tjp extension. Otherwise it will have a .tji extension.

By default, the report contains everything and the generated files has a .tjp extension.

Syntax definitions (* | - | (flags | project | projecids | tasks | resources) [, (flags | project | projecids | tasks | resources)...])
Arguments *
A shortcut for all items
-
No items
flags
Include flag definitions
project
Include project header
projecids
Include project IDs
tasks
Include task definitions
resources
Include resource definitions
Context export



<< date (extend) << Table Of Contents >> depends >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/scenariospecific.extend.html0000644000175000017500000000606712614413013023263 0ustar bernatbernat scenariospecific.extend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< scenarios (export) << Table Of Contents >> scheduled >>


Keyword scenariospecific (extend)

Purpose
If this attribute is used, the property extension is scenario specific. A different value can be set for each scenario.
Syntax scenariospecific
Arguments none
Context date (extend), number (extend), reference (extend), richtext (extend), text (extend)



<< scenarios (export) << Table Of Contents >> scheduled >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/fdl.html0000644000175000017500000006017612614413013017232 0ustar bernatbernat fdl

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< TaskJuggler_Internals << Table Of Contents


9 GNU Free Documentation License

                 Version 1.3, 3 November 2008
 Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
     <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.
 
0. PREAMBLE
 
The purpose of this License is to make a manual, textbook, or other
functional and useful document "free" in the sense of freedom: to
assure everyone the effective freedom to copy and redistribute it,
with or without modifying it, either commercially or noncommercially.
Secondarily, this License preserves for the author and publisher a way
to get credit for their work, while not being considered responsible
for modifications made by others.
 
This License is a kind of "copyleft", which means that derivative
works of the document must themselves be free in the same sense.  It
complements the GNU General Public License, which is a copyleft
license designed for free software.
 
We have designed this License in order to use it for manuals for free
software, because free software needs free documentation: a free
program should come with manuals providing the same freedoms that the
software does.  But this License is not limited to software manuals;
it can be used for any textual work, regardless of subject matter or
whether it is published as a printed book.  We recommend this License
principally for works whose purpose is instruction or reference.
 
 
1. APPLICABILITY AND DEFINITIONS
 
This License applies to any manual or other work, in any medium, that
contains a notice placed by the copyright holder saying it can be
distributed under the terms of this License.  Such a notice grants a
world-wide, royalty-free license, unlimited in duration, to use that
work under the conditions stated herein.  The "Document", below,
refers to any such manual or work.  Any member of the public is a
licensee, and is addressed as "you".  You accept the license if you
copy, modify or distribute the work in a way requiring permission
under copyright law.
 
A "Modified Version" of the Document means any work containing the
Document or a portion of it, either copied verbatim, or with
modifications and/or translated into another language.
 
A "Secondary Section" is a named appendix or a front-matter section of
the Document that deals exclusively with the relationship of the
publishers or authors of the Document to the Document's overall
subject (or to related matters) and contains nothing that could fall
directly within that overall subject.  (Thus, if the Document is in
part a textbook of mathematics, a Secondary Section may not explain
any mathematics.)  The relationship could be a matter of historical
connection with the subject or with related matters, or of legal,
commercial, philosophical, ethical or political position regarding
them.
 
The "Invariant Sections" are certain Secondary Sections whose titles
are designated, as being those of Invariant Sections, in the notice
that says that the Document is released under this License.  If a
section does not fit the above definition of Secondary then it is not
allowed to be designated as Invariant.  The Document may contain zero
Invariant Sections.  If the Document does not identify any Invariant
Sections then there are none.
 
The "Cover Texts" are certain short passages of text that are listed,
as Front-Cover Texts or Back-Cover Texts, in the notice that says that
the Document is released under this License.  A Front-Cover Text may
be at most 5 words, and a Back-Cover Text may be at most 25 words.
 
A "Transparent" copy of the Document means a machine-readable copy,
represented in a format whose specification is available to the
general public, that is suitable for revising the document
straightforwardly with generic text editors or (for images composed of
pixels) generic paint programs or (for drawings) some widely available
drawing editor, and that is suitable for input to text formatters or
for automatic translation to a variety of formats suitable for input
to text formatters.  A copy made in an otherwise Transparent file
format whose markup, or absence of markup, has been arranged to thwart
or discourage subsequent modification by readers is not Transparent.
An image format is not Transparent if used for any substantial amount
of text.  A copy that is not "Transparent" is called "Opaque".
 
Examples of suitable formats for Transparent copies include plain
ASCII without markup, Texinfo input format, LaTeX input format, SGML
or XML using a publicly available DTD, and standard-conforming simple
HTML, PostScript or PDF designed for human modification.  Examples of
transparent image formats include PNG, XCF and JPG.  Opaque formats
include proprietary formats that can be read and edited only by
proprietary word processors, SGML or XML for which the DTD and/or
processing tools are not generally available, and the
machine-generated HTML, PostScript or PDF produced by some word
processors for output purposes only.
 
The "Title Page" means, for a printed book, the title page itself,
plus such following pages as are needed to hold, legibly, the material
this License requires to appear in the title page.  For works in
formats which do not have any title page as such, "Title Page" means
the text near the most prominent appearance of the work's title,
preceding the beginning of the body of the text.
 
The "publisher" means any person or entity that distributes copies of
the Document to the public.
 
A section "Entitled XYZ" means a named subunit of the Document whose
title either is precisely XYZ or contains XYZ in parentheses following
text that translates XYZ in another language.  (Here XYZ stands for a
specific section name mentioned below, such as "Acknowledgements",
"Dedications", "Endorsements", or "History".)  To "Preserve the Title"
of such a section when you modify the Document means that it remains a
section "Entitled XYZ" according to this definition.
 
The Document may include Warranty Disclaimers next to the notice which
states that this License applies to the Document.  These Warranty
Disclaimers are considered to be included by reference in this
License, but only as regards disclaiming warranties: any other
implication that these Warranty Disclaimers may have is void and has
no effect on the meaning of this License.
 
2. VERBATIM COPYING
 
You may copy and distribute the Document in any medium, either
commercially or noncommercially, provided that this License, the
copyright notices, and the license notice saying this License applies
to the Document are reproduced in all copies, and that you add no
other conditions whatsoever to those of this License.  You may not use
technical measures to obstruct or control the reading or further
copying of the copies you make or distribute.  However, you may accept
compensation in exchange for copies.  If you distribute a large enough
number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and
you may publicly display copies.
3. COPYING IN QUANTITY
If you publish printed copies (or copies in media that commonly have
printed covers) of the Document, numbering more than 100, and the
Document's license notice requires Cover Texts, you must enclose the
copies in covers that carry, clearly and legibly, all these Cover
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
the back cover.  Both covers must also clearly and legibly identify
you as the publisher of these copies.  The front cover must present
the full title with all words of the title equally prominent and
visible.  You may add other material on the covers in addition.
Copying with changes limited to the covers, as long as they preserve
the title of the Document and satisfy these conditions, can be treated
as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit
legibly, you should put the first ones listed (as many as fit
reasonably) on the actual cover, and continue the rest onto adjacent
pages.
If you publish or distribute Opaque copies of the Document numbering
more than 100, you must either include a machine-readable Transparent
copy along with each Opaque copy, or state in or with each Opaque copy
a computer-network location from which the general network-using
public has access to download using public-standard network protocols
a complete Transparent copy of the Document, free of added material.
If you use the latter option, you must take reasonably prudent steps,
when you begin distribution of Opaque copies in quantity, to ensure
that this Transparent copy will remain thus accessible at the stated
location until at least one year after the last time you distribute an
Opaque copy (directly or through your agents or retailers) of that
edition to the public.
It is requested, but not required, that you contact the authors of the
Document well before redistributing any large number of copies, to
give them a chance to provide you with an updated version of the
Document.
4. MODIFICATIONS
You may copy and distribute a Modified Version of the Document under
the conditions of sections 2 and 3 above, provided that you release
the Modified Version under precisely this License, with the Modified
Version filling the role of the Document, thus licensing distribution
and modification of the Modified Version to whoever possesses a copy
of it.  In addition, you must do these things in the Modified Version:
A. Use in the Title Page (and on the covers, if any) a title distinct
   from that of the Document, and from those of previous versions
   (which should, if there were any, be listed in the History section
   of the Document).  You may use the same title as a previous version
   if the original publisher of that version gives permission.
B. List on the Title Page, as authors, one or more persons or entities
   responsible for authorship of the modifications in the Modified
   Version, together with at least five of the principal authors of the
   Document (all of its principal authors, if it has fewer than five),
   unless they release you from this requirement.
C. State on the Title page the name of the publisher of the
   Modified Version, as the publisher.
D. Preserve all the copyright notices of the Document.
E. Add an appropriate copyright notice for your modifications
   adjacent to the other copyright notices.
F. Include, immediately after the copyright notices, a license notice
   giving the public permission to use the Modified Version under the
   terms of this License, in the form shown in the Addendum below.
G. Preserve in that license notice the full lists of Invariant Sections
   and required Cover Texts given in the Document's license notice.
H. Include an unaltered copy of this License.
I. Preserve the section Entitled "History", Preserve its Title, and add
   to it an item stating at least the title, year, new authors, and
   publisher of the Modified Version as given on the Title Page.  If
   there is no section Entitled "History" in the Document, create one
   stating the title, year, authors, and publisher of the Document as
   given on its Title Page, then add an item describing the Modified
   Version as stated in the previous sentence.
J. Preserve the network location, if any, given in the Document for
   public access to a Transparent copy of the Document, and likewise
   the network locations given in the Document for previous versions
   it was based on.  These may be placed in the "History" section.
   You may omit a network location for a work that was published at
   least four years before the Document itself, or if the original
   publisher of the version it refers to gives permission.
K. For any section Entitled "Acknowledgements" or "Dedications",
   Preserve the Title of the section, and preserve in the section all
   the substance and tone of each of the contributor acknowledgements
   and/or dedications given therein.
L. Preserve all the Invariant Sections of the Document,
   unaltered in their text and in their titles.  Section numbers
   or the equivalent are not considered part of the section titles.
M. Delete any section Entitled "Endorsements".  Such a section
   may not be included in the Modified Version.
N. Do not retitle any existing section to be Entitled "Endorsements"
   or to conflict in title with any Invariant Section.
O. Preserve any Warranty Disclaimers.
If the Modified Version includes new front-matter sections or
appendices that qualify as Secondary Sections and contain no material
copied from the Document, you may at your option designate some or all
of these sections as invariant.  To do this, add their titles to the
list of Invariant Sections in the Modified Version's license notice.
These titles must be distinct from any other section titles.
You may add a section Entitled "Endorsements", provided it contains
nothing but endorsements of your Modified Version by various
parties--for example, statements of peer review or that the text has
been approved by an organization as the authoritative definition of a
standard.
You may add a passage of up to five words as a Front-Cover Text, and a
passage of up to 25 words as a Back-Cover Text, to the end of the list
of Cover Texts in the Modified Version.  Only one passage of
Front-Cover Text and one of Back-Cover Text may be added by (or
through arrangements made by) any one entity.  If the Document already
includes a cover text for the same cover, previously added by you or
by arrangement made by the same entity you are acting on behalf of,
you may not add another; but you may replace the old one, on explicit
permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License
give permission to use their names for publicity for or to assert or
imply endorsement of any Modified Version.
5. COMBINING DOCUMENTS
You may combine the Document with other documents released under this
License, under the terms defined in section 4 above for modified
versions, provided that you include in the combination all of the
Invariant Sections of all of the original documents, unmodified, and
list them all as Invariant Sections of your combined work in its
license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and
multiple identical Invariant Sections may be replaced with a single
copy.  If there are multiple Invariant Sections with the same name but
different contents, make the title of each such section unique by
adding at the end of it, in parentheses, the name of the original
author or publisher of that section if known, or else a unique number.
Make the same adjustment to the section titles in the list of
Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled "History"
in the various original documents, forming one section Entitled
"History"; likewise combine any sections Entitled "Acknowledgements",
and any sections Entitled "Dedications".  You must delete all sections
Entitled "Endorsements".
6. COLLECTIONS OF DOCUMENTS
You may make a collection consisting of the Document and other
documents released under this License, and replace the individual
copies of this License in the various documents with a single copy
that is included in the collection, provided that you follow the rules
of this License for verbatim copying of each of the documents in all
other respects.
You may extract a single document from such a collection, and
distribute it individually under this License, provided you insert a
copy of this License into the extracted document, and follow this
License in all other respects regarding verbatim copying of that
document.
7. AGGREGATION WITH INDEPENDENT WORKS
A compilation of the Document or its derivatives with other separate
and independent documents or works, in or on a volume of a storage or
distribution medium, is called an "aggregate" if the copyright
resulting from the compilation is not used to limit the legal rights
of the compilation's users beyond what the individual works permit.
When the Document is included in an aggregate, this License does not
apply to the other works in the aggregate which are not themselves
derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these
copies of the Document, then if the Document is less than one half of
the entire aggregate, the Document's Cover Texts may be placed on
covers that bracket the Document within the aggregate, or the
electronic equivalent of covers if the Document is in electronic form.
Otherwise they must appear on printed covers that bracket the whole
aggregate.
8. TRANSLATION
Translation is considered a kind of modification, so you may
distribute translations of the Document under the terms of section 4.
Replacing Invariant Sections with translations requires special
permission from their copyright holders, but you may include
translations of some or all Invariant Sections in addition to the
original versions of these Invariant Sections.  You may include a
translation of this License, and all the license notices in the
Document, and any Warranty Disclaimers, provided that you also include
the original English version of this License and the original versions
of those notices and disclaimers.  In case of a disagreement between
the translation and the original version of this License or a notice
or disclaimer, the original version will prevail.
If a section in the Document is Entitled "Acknowledgements",
"Dedications", or "History", the requirement (section 4) to Preserve
its Title (section 1) will typically require changing the actual
title.
9. TERMINATION
You may not copy, modify, sublicense, or distribute the Document
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense, or distribute it is void, and
will automatically terminate your rights under this License.
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, receipt of a copy of some or all of the same material does
not give you any rights to use it.
10. FUTURE REVISIONS OF THIS LICENSE
The Free Software Foundation may publish new, revised versions of the
GNU Free Documentation 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.  See
http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number.
If the Document specifies that a particular numbered version of this
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that specified version or
of any later version that has been published (not as a draft) by the
Free Software Foundation.  If the Document does not specify a version
number of this License, you may choose any version ever published (not
as a draft) by the Free Software Foundation.  If the Document
specifies that a proxy can decide which future versions of this
License can be used, that proxy's public statement of acceptance of a
version permanently authorizes you to choose that version for the
Document.
11. RELICENSING
"Massive Multiauthor Collaboration Site" (or "MMC Site") means any
World Wide Web server that publishes copyrightable works and also
provides prominent facilities for anybody to edit those works.  A
public wiki that anybody can edit is an example of such a server.  A
"Massive Multiauthor Collaboration" (or "MMC") contained in the site
means any set of copyrightable works thus published on the MMC site.
"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 
license published by Creative Commons Corporation, a not-for-profit 
corporation with a principal place of business in San Francisco, 
California, as well as future copyleft versions of that license 
published by that same organization.
"Incorporate" means to publish or republish a Document, in whole or in 
part, as part of another Document.
An MMC is "eligible for relicensing" if it is licensed under this 
License, and if all works that were first published under this License 
somewhere other than this MMC, and subsequently incorporated in whole or 
in part into the MMC, (1) had no cover texts or invariant sections, and 
(2) were thus incorporated prior to November 1, 2008.
The operator of an MMC Site may republish an MMC contained in the site
under CC-BY-SA on the same site at any time before August 1, 2009,
provided the MMC is eligible for relicensing.
ADDENDUM: How to use this License for your documents
To use this License in a document you have written, include a copy of
the License in the document and put the following copyright and
license notices just after the title page:
    Copyright (c)  YEAR  YOUR NAME.
    Permission is granted to copy, distribute and/or modify this document
    under the terms of the GNU Free Documentation License, Version 1.3
    or any later version published by the Free Software Foundation;
    with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
    A copy of the license is included in the section entitled "GNU
    Free Documentation License".
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
replace the "with...Texts." line with this:
    with the Invariant Sections being LIST THEIR TITLES, with the
    Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
If you have Invariant Sections without Cover Texts, or some other
combination of the three, merge those two alternatives to suit the
situation.
If your document contains nontrivial examples of program code, we
recommend releasing these examples in parallel under your choice of
free software license, such as the GNU General Public License,
to permit their use in free software.


<< TaskJuggler_Internals << Table Of Contents


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/resourceattributes.html0000644000175000017500000000662412614413013022421 0ustar bernatbernat resourceattributes

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< resource << Table Of Contents >> resourceprefix >>


Keyword resourceattributes

Purpose
Define a list of resource attributes that should be included in the report.
Syntax resourceattributes (* | - | (booking | leaves | workinghours) [, (booking | leaves | workinghours)...])
Arguments *
A shortcut for all items
-
No items
booking
Include bookings
leaves
Include leaves
workinghours
Include working hours
Context export



<< resource << Table Of Contents >> resourceprefix >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/TaskJuggler_Internals.html0000644000175000017500000002377412614413013022731 0ustar bernatbernat TaskJuggler_Internals

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< Day_To_Day_Juggling << Table Of Contents >> fdl >>


8 TaskJuggler Internals

This chapter contains information that you don't need to know to use TaskJuggler. It describes internal algorithms that are provided for the curious.

8.1 How the Scheduler works

It's important to understand that the scheduler implementation is not an optimization algorithm. It does not search a solution space and evaluates various alternative results against each other. This has been tried, but for any real-world project, the solution space becomes unmanageable and scheduling runs took hours to complete.

Instead, we use a heuristic to decide when each task gets its resources assigned. This heuristic is certainly not perfect but has shown good results with fairly moderate computation costs. The following sections contain an overview of the scheduling algorithm. Users are also encouraged to read the actual source code. It can be found in Project.rb, TaskScenario.rb, ResourceScenario.rb and Allocation.rb. All these files can be found in the lib/taskjuggler directory. You can also browse the sources on github.

The scheduler needs to determine the start and end date for all tasks that don't have such dates yet. To deal with multiple concurrent time zones, all time related events are stored internally as UTC time.Additionally, it allocates resources to tasks. All events such as start or end of a task, or allocation of a resource can only happen aligned with the timing resolution. This determines the smallest possible allocation period that we call a time slot. The duration of the slot can be set by the user. Possible values are 5, 10, 15, 30 and 60 minutes.

TaskJuggler keeps a scoreboard for each time slot for each leaf resource. Each scoreboard entry specifies whether the resource is unassigned, assigned to a specific task or on leave. This explains why the project duration and number of allocated resources determines the memory usage of the scheduler.

For the scheduling of the project, the scheduler only looks at leave tasks that are not milestones. Container tasks and milestones are scheduled once all necessary information is available. During the scheduling process, leave tasks can have 3 different states.

  1. Not ready for scheduling: The task is missing a start or end date that depends on another task's date that hasn't been determined yet.
  2. Ready for scheduling: The task has at least a start or end date but one of them is still missing or resources have not yet been assigned for all time slots. The scheduling direction (ASAP or ALAP) determines whether the start or end date is needed. ASAP tasks are scheduled from start to end, so they require a start date. ALAP tasks are scheduled from end to start.
  3. Scheduling completed: The task has a start and end date and resources have been assigned for all time slots.

The goal of the scheduler is to transfer all tasks in the completed state. Until this goal has been reached, at least one tasks needs to be in the ready state. If that's not the case, the project schedule cannot be determined and an error is raised. In case there are more than one task in the ready state, we need to have a well defined priority of the tasks. This is necessary since those ready tasks may compete for the same resource in the same time slot.

The priority can be directly influenced by the user with the priority attribute. This user-defined priority always trumps the other internal criteria described below. In case two tasks have the same priority, an additional measure is used. This measure is called path criticalness. The path criticalness is calculated for each leaf task. The path criticalness is a measure for how important the task is to keep the overall project duration (start of first task to end of last task) to a minimum.

To determine the path criticalness, we first need to determine the resource criticalness first. This is a measure for how likely the tasks that have this resource in their allocation list will actually get the resource. A resource criticalness larger than 1.0 means that statistically, at least one tasks will not get enough of this resource. This is just a statistical measure based on the total requested allocations and the number of available work time.

Once we have determined the criticalness of all allocated resources, we can calculate the criticalness of each individual task. This really only matters for effort based tasks. These really need their allocations to be finished within a limited amount of time. For length and duration tasks, the allocations are by definition optional. The user can still influence the allocation to length and duration tasks by adjusting the priority appropriately. However, there is no guarantee that such tasks will ever get any resources assigned. The criticalness of an effort based task is defined as the average of the criticalness of the resources allocated to this task.

We also assign a criticalness to milestones. Based on their priority a criticalness between 0 and 2.0 is assigned. This is done to reflect the user perception that milestones are usually some important goal of the project.

The final step is now the computation of the path criticalness for each effort-based leaf task. For each possible chain of task (path) that is going through a task, the sum of the criticalness values of the tasks of the path is computed. The largest sum is the path criticalness of that task.

This heuristic will favor allocations to task with critical resources and long dependency chains. As a result, the critical paths of the project are tried to be kept short. The user can use the criticalness and pathcriticalness columns to review the respective values for he project's tasks and resources.

When the criticalness and pathcriticalness for all leaf resources and tasks has been determined, the leaf tasks are sorted by priority (hight to low), then by pathcricialness (high to low) and then by the index (low to high). In a loop that is terminated when all tasks have been scheduled or an error condition has been detected, the first task that is ready for scheduling is completely scheduled. This means that resources are allocated for all time slots and missing dates are being computed. The newly determined end (for ASAP tasks) or start (for ALAP tasks) date is then propagated to dependent tasks, milestones or parent tasks if needed. This can result in other tasks becoming ready for scheduling and the list is searched again for the first task that is ready for scheduling to be scheduled.

A task can only be scheduled when it is ready for scheduling. This means that at least the start date for the scheduling process must be known. For ASAP (As Soon As Possible) tasks, the scheduler start date is the start date of the task. For ALAP (As Late As Possible) tasks the scheduler start date is the end date of the task. ASAP task will be scheduled from start to end, ALAP tasks from end to start. This is important to understand as resource assignments for each time slot will be determined in this order.

Mixing ASAP and ALAP tasks in the same project is supported but should be used very carefully. It can lead to situations where a lower priority tasks that is earlier in the scheduling process ready for scheduling takes away resources from a higher prioritized task that competes for the same resources. This condition is known is priority inversion. If the scheduler detects such a situation, a warning is generated.



<< Day_To_Day_Juggling << Table Of Contents >> fdl >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timesheet.html0000644000175000017500000001542712614413013020453 0ustar bernatbernat timesheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timeoff (nikureport) << Table Of Contents >> timesheetreport >>


Keyword timesheet

Purpose

A time sheet record can be used to capture the current status of the tasks assigned to a specific resource and the achieved progress for a given period of time. The status is assumed to be for the end of this time period. There must be a separate time sheet record for each resource per period. Different resources can use different reporting periods and reports for the same resource may have different reporting periods as long as they don't overlap. For the time after the last time sheet, TaskJuggler will project the result based on the plan data. For periods without a time sheet record prior to the last record for this resource, TaskJuggler assumes that no work has been done. The work is booked for the scenario specified by trackingscenario.

The intended use for time sheets is to have all resources report a time sheet every day, week or month. All time sheets can be added to the project plan. The status information is always used to determin the current status of the project. The work, remaining and end attributes are ignored if there are also bookings for the resource in the time sheet period. The non-ignored attributes of the time sheets will be converted into booking statements internally. These bookings can then be exported into a file which can then be added to the project again. This way, you can use time sheets to incrementally record progress of your project. There is a possibility that time sheets conflict with other data in the plan. In case TaskJuggler cannot automatically resolve them, these conflicts have to be manually resolved by either changing the plan or the time sheet.

The status messages are interpreted as journal entries. The alert level will be evaluated and the current state of the project can be put into a dashboard using the alert and alertmessage columns.

Currently, the provided effort values and dates are not yet used to automatically update the plan data. This feature will be added in future versions.

Syntax timesheet <resource> <interval4> { <attributes> }
Arguments resource [ID]
The ID of a defined resource
interval4
See interval4 for details.
Context properties

Attributes newtask, shift (timesheet), status (timesheet), task (timesheet)

project "test" 2009-11-30 +2m {
  timezone "America/Denver"
  trackingscenario plan
  now ${projectstart}
}
timesheet r1 2009-11-30 +1w {
  task t1 {
    work 3d
    remaining 0d
    status green "All work done" {
      summary "I had good fun!"
      details -8<-
        This task went smoothly and I got three things done:
        * Have fun
        * Be on time
        * Get things done
      ->8-
    }
  }
  newtask t4 "Another fun job" {
    work 2d
    remaining 4d
    status yellow "Had a cool idea" {
      summary "Will have a schedule impact though."
    }
  }
}

timesheet r2 2009-11-30 +1w {
  task t2.t3 {
    work 5d
    end 2009-12-10
    status red "I need more time" {
      summary "This takes longer than expected"
      details -8<-
        To finish on time, I need help. Get this r1 guy to help me out
        here.
        * I want to have fun too!
      ->8-
    }
  }
  status yellow "My wife got ill" {
    summary "I might have to work from home for a few days next week."
  }
}


<< timeoff (nikureport) << Table Of Contents >> timesheetreport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/end.column.html0000644000175000017500000000603412614413013020520 0ustar bernatbernat end.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< end << Table Of Contents >> end (limit) >>


Keyword end (column)

Purpose
Normally, columns with calculated values take the specified report period into account when calculating their values. With this attribute, the user can specify an end date for the period that should be used when calculating the values of this column. It does not have an impact on column with time invariant values.
Syntax end <date>
Arguments date
See date for details.
Context columns



<< end << Table Of Contents >> end (limit) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/headline.html0000644000175000017500000000615312614413013020231 0ustar bernatbernat headline

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< header << Table Of Contents >> height >>


Keyword headline

Purpose
Specifies the headline for a report.
Syntax headline <text>
Arguments text [STRING]
The text used for the headline. It is interpreted as Rich Text.
Context accountreport, nikureport, resourcereport, taskreport, textreport, tracereport



<< header << Table Of Contents >> height >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/minimum.html0000644000175000017500000000777112614413013020142 0ustar bernatbernat minimum

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< minend << Table Of Contents >> minstart >>


Keyword minimum

Purpose
Set a minim limit for each calendar month. This will only result in a warning if not met.
Syntax minimum <value> (min | h | d | w | m | y) [{ <attributes> }]
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context limits, limits (resource), limits (task)

Attributes end (limit), period (limit), resources (limit), start (limit)



<< minend << Table Of Contents >> minstart >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/start.column.html0000644000175000017500000000606312614413013021111 0ustar bernatbernat start.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< start << Table Of Contents >> start (limit) >>


Keyword start (column)

Purpose
Normally, columns with calculated values take the specified report period into account when calculating their values. With this attribute, the user can specify a start date for the period that should be used when calculating the values of this column. It does not have an impact on column with time invariant values.
Syntax start <date>
Arguments date
See date for details.
Context columns



<< start << Table Of Contents >> start (limit) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/trackingscenario.html0000644000175000017500000000745612614413013022015 0ustar bernatbernat trackingscenario

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< tracereport << Table Of Contents >> treelevel >>


Keyword trackingscenario

Purpose

Specifies which scenario is used to capture what actually has happened with the project. All sub-scenarios of this scenario inherit the bookings of the tracking scenario and may not have any bookings of their own. The tracking scenario must also be specified to use time and status sheet reports.

The tracking scenario must be defined after all scenario have been defined.

The tracking scenario and all scenarios derived from it will be scheduled in projection mode. This means that the scheduler will only add bookings after the current date or the date specified by now. It is assumed that all allocations prior to this date have been provided as task bookings or resource bookings.

Syntax trackingscenario <scenario>
Arguments scenario [ID]
ID of a defined scenario
Context project

project "test" 2009-11-30 +2m {
  timezone "America/Denver"
  trackingscenario plan
  now ${projectstart}
}

resource r1 "R1"


<< tracereport << Table Of Contents >> treelevel >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/onstart.html0000644000175000017500000000535112614413013020151 0ustar bernatbernat onstart

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< onend << Table Of Contents >> opennodes >>


Keyword onstart

Purpose
The target of the dependency is the start of the task.
Syntax onstart
Arguments none
Context depends, precedes



<< onend << Table Of Contents >> opennodes >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/charge.html0000644000175000017500000000754012614413013017712 0ustar bernatbernat charge

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< center << Table Of Contents >> chargeset >>


Keyword charge

Purpose
Specify a one-time or per-period charge to a certain account. The charge can occur at the start of the task, at the end of it, or continuously over the duration of the task. The accounts to be charged are determined by the chargeset setting of the task.
Syntax charge <amount> (onstart | onend | perhour | perday | perweek)
Arguments amount
The amount to charge
onstart
Charge the amount on starting the task.
onend
Charge the amount on finishing the task.
perhour
Charge the amount for every hour the task lasts.
perday
Charge the amount for every day the task lasts.
perweek
Charge the amount for every week the task lasts.
Context task, supplement (task)



<< center << Table Of Contents >> chargeset >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/strict.projection.html0000644000175000017500000000614312614413013022142 0ustar bernatbernat strict.projection

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< statussheetreport << Table Of Contents >> summary >>


Keyword strict (projection)

This keyword should no longer be used. It will be removed in future versions of this software.

Use trackingscenario instead.

Purpose
Syntax strict
Arguments none
Context Global scope
See also trackingscenario



<< statussheetreport << Table Of Contents >> summary >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timezone.shift.html0000644000175000017500000000700712614413013021425 0ustar bernatbernat timezone.shift

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timezone (report) << Table Of Contents >> timingresolution >>


Keyword timezone (shift)

Purpose

Sets the time zone of the shift. The working hours of the shift are assumed to be within the specified time zone. The time zone does not effect the vaction interval. The latter is assumed to be within the project time zone.

TaskJuggler stores all dates internally as UTC. Since all events must align with the timing resolution for time zones you may have to change the timing resolution appropriately. The time zone difference compared to UTC must be a multiple of the used timing resolution.

Syntax timezone <zone>
Arguments zone
Time zone to use. E. g. 'Europe/Berlin' or 'America/Denver'. Don't use the 3 letter acronyms. See Wikipedia for possible values.
Context shift



<< timezone (report) << Table Of Contents >> timingresolution >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/Installation.html0000644000175000017500000005700712614413013021125 0ustar bernatbernat Installation

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< Reporting_Bugs << Table Of Contents >> How_To_Contribute >>


2 Installation

TaskJuggler 3.x is written in Ruby. It should run on any platform that Ruby is available on. It uses the standard Ruby mechanism for distribution, a package format called RubyGems.

2.1 Requirements

Ruby applications are platform independent. There is no need to compile anything. But TaskJuggler has a very small set of dependencies that you have to take care of first. Please make sure you have the minimum required version installed.

2.1.1 Supported Operating Systems

  • Linux: Linux is the primary development platform for TaskJuggler. Releases are tested on recent openSUSE versions.
  • Other Unix OSes: Should work as well, but releases are not tested on these OSes.
  • Windows: Windows7 and some older version of Windows should work. There is no maintainer for this platform, so all releases are not tested on this platform.
  • MacOSX: Will probably work as well. Releases are not tested on this OS. Older MacOS versions will likely not work.

If you are interested in becoming the maintainer for any of the currently unmaintained (and untested) OSes, please contact us via the developer mailing list.

2.1.2 Other required Software

  • Ruby: TaskJuggler 3.x is written in Ruby. You need a Ruby runtime environment to run it. This can be downloaded from here. Most Linux distributions usually have Ruby already included. So does MacOS X Leopard. For Windows, there is a one-click installer available. The recommended Ruby version to make full use of TaskJuggler is Ruby 2.0. Ruby 1.9.1 contains some bugs that prevent the multi-core support to work. For users that are not interested in multi-core support, the web server, the time sheet infrastructure and daemon Ruby 1.8.7 is still ok to use. On Windows you need at least Ruby 1.9.2. If you want to use non-ASCII characters, Ruby 1.9.2 or later is required as well.

You must have configured your system locale to be UTF-8 to work properly with non-ASCII characters.

See below for instructions on how to use the latest and greates Ruby version in parallel with your distribution Ruby.

  • RubyGems: If it did not come with your OS or the Ruby installation, see here how to get and install it. RubyGems is a cross-platform package manager. It will download and install all other required software packages automatically when you install TaskJuggler. These packages are called Ruby gems.

Other versions of Ruby (Rubinius, JRuby, etc.) may work but have not been tested.

2.2 Installation Steps for Users

2.2.1 The easy way

2.2.1.1 System Wide Installation

TaskJuggler is a commandline tool. It does not (yet) have a graphical user interface. To use it, you need to know how to open a command or terminal window. In this manual, we refer to it as your shell. The following paragraphs describe the commands you need to type into your shell.

On systems that already have Ruby and the gem package manager installed you can simply type the following command as root or admin user into your shell or command window:

 gem install taskjuggler

This will download and install the latest version from the RubyGems.org site.

2.2.1.2 Installation into a local Directory

If you don't want to install TaskJuggler for all users on the system, you can also install it into your home or data directory. This does not require root or admin permissions.

The following steps are describe the installation on a Linux system with the bash shell. You may have to use slightly different commands on a different operating system.

Create a new directory taskjuggler in your $HOME directory for the installation to go into.

 mkdir taskjuggler

Install the gem and all dependencies.

 gem install --install-dir taskjuggler taskjuggler-X.X.X.gem

If you must use a proxy to access the Internet, you need to set a shell environment variable so gem can find and use it. The setting below needs to be adapted to your local environment. Check with your admin or IT department if needed.

 export HTTP_PROXY=http://%USER%:%PASSWORD%@%SERVER%:%PORT%

Configure your PATH variable to find the taskjuggler programs.

 export PATH="${PATH}:${HOME}/taskjuggler/bin"

Configure gem to find the installed files.

 export GEM_HOME=${HOME}/taskjuggler

The last two settings should also be added to your .profile file to make them permanent.

That's it. You now should be run TaskJuggler.

 tj3 --version

2.2.2 The manual way

If the easy way doesn't work for you, you need to download and install the packages manually. Download TaskJuggler gem file from the RubyGems.org site.

A gem package is an operating system and architecture independent archive file for Ruby programs. You can install it on any system that has Ruby and RubyGems installed. Normally, you should be logged-in as root or administrator to run the following installation command. Replace the X.X.X with the actual version that you have downloaded.

gem install pkg/taskjuggler-X.X.X.gem 

It will install all components of the Gem in the appropriate place.

On user friendly Linux distributions, the start scripts will be installed in a standard directory like /usr/bin. On Debian based distributions, the start scripts end up in a place like /var/lib/gems/1.8/bin/ that is not listed in the PATH variable. You either have to create a symbolic link for each start script or add the directory to your PATH variable. If you use the standard bash shell, put the following line in your ${HOME}/.profile file.

PATH=${PATH}:/var/lib/gems/1.8/bin/

Windows and MacOS platforms may require similar steps.

2.3 Update from older TaskJuggler 3.x versions

Updates work just like the installation.

gem update taskjuggler

For downloaded or self-built packages use the following command:

gem update pkg/taskjuggler-X.X.X.gem

2.4 Installing TaskJuggler from the Git Repository

The following description is for developers and users that want to learn more about TaskJuggler or want to make improvements. TaskJuggler is Open Source software and you are encouraged to read and modify the source code.

Before you download the source code, make sure you have all the necessary dependencies installed. You should have Ruby 1.9.2 or later and you need to have the following gems installed

gem install rake mail rspec term-ansicolor rcov

rcov is optional, but you must have the other gems and their dependencies installed.

To get the source code, the recommended way it to check out the latest code from the developer repository. To do this, you need to have git installed.

Then checkout the source code with the following command

git clone git@github.com:taskjuggler/TaskJuggler.git

Make sure, you have removed all previously installed instances of TaskJuggler from your system before doing so. It is a common mistake to have an old version of the TaskJuggler installed and then use parts of the old and new version together.

If your Ruby installation does not come with the Rake build tool, you need to install it now.

If you are interested in a code coverage analysis, you need to also install the rcov code coverage analysis tool. This tool is not needed for most developers. You can safely ignore the warning during rake builds if you don't have it installed.

The following command will create a gem package from the source code.

cd taskjuggler3; rake gem

If you plan to modify the TaskJuggler files, creating and installing the gem file for every test run is not very comfortable. To run tj3 from source put the following code in your .profile file. This is for users of the bash shell. Adapt it accordingly if you use another shell.

# Make sure the shell finds the TaskJuggler programs
export PATH=${PATH}:${TASKJUGGLER_DIR}/bin

2.5 Quickly switching between various TaskJuggler 3.x versions

One of the benefits of using TaskJuggler from the Git repository is the ability to get the latest bug fixes. If a bug was reported, it is usually fixed fairly quickly, but it can take several weeks before the next official release happens. The following commands must all be executed from within the checked-out Git directory.

git pull

gets you the latest changes. We usually try to keep the head branch stable. Using it should not be much more risky than using a regular release. Nevertheless, problems can occur and a fixed version might take a few days.

git checkout -f XXXXXXXX

will switch your current working copy to the version with commit ID XXXXXXXX. Alternatively, you can also use tag names.

git checkout -f release-0.0.10

This will switch to the released version 0.0.10.

git tag

provides you with a list of all tags.

TaskJuggler 3.x is written in Ruby. There is no make or build process needed. Every code change is effective immediately. The tutorial, the manual and some other parts do require a build step.

rake release

will do it all and even create installable gem files again.

2.6 Installing a newer Ruby version

New Ruby versions are released usually about once or twice a year. Unfortunately, it takes some time before Linux distributions pick up the new release. Depending on your distribution, this can take anything from a few weeks to several years. Many distributions still have not yet made the switch to Ruby 2.0. The core part of TaskJuggler can be used with Ruby 1.8.9, but it is at least 3 times slower. Therefor it is recommended, that you install the latest stable release of Ruby to use TaskJuggler. This can easily and safely being done in parallel to your distribution Ruby. Both versions can be used in parallel without interfering each other.

This section only covers Linux. For other operating system, please search the web for instructions. If you want to contribute the description for another OS, please see How_To_Contribute.

First, you need to download the source code of the latest stable release from www.ruby-lang.org.

The source code is distributed as zipped tarfile. You can extract it like this. Change the file name to the actual version you have downloaded.

 tar -Zxvf ruby-X.X.X-*.tar.gz

This will create a directory with the same name as the archive, but without the .tar.gz extension.

Before you continue, make sure you have all the necessary packages installed to compile ruby. That would be everything you need to compile C programs. That includes gcc, make, zlib and libyaml. If something is missing, you will run into problems in the next 2 steps. It's sometimes not obvious which package to install to fix the issue.

Now change into this directory and configure the source code for your specific OS and compile it. We configure Ruby to append 19 to all executable names. This way, you can easily choose if you want to run the old or the new Ruby. ruby runs your distribution Ruby, ruby19 runs your new ruby.

 cd ruby-X.X.X-*
 ./configure --program-suffix=19
 make

If all goes well, you can install it now. This requires root permission, so you need to enter the root password. All executables will be installed into /usr/local/bin.

 sudo make install

The TaskJuggler front-end scripts always use the ruby interpreter that's the first in the PATH. You need to set a link in your local bin directory to point to your ruby19 executable as ruby.

 ln -s /usr/local/bin/ruby19 ${HOME}/bin/ruby

Make sure your ${HOME}/bin directory is the first directory in the PATH. This step varies a lot depending on the login shell. E. g. for bash put the following at the end in your .profile shell config file. Please make sure that /usr/local/bin is also in the PATH so that the ruby executables (all having a 19 suffix) will be found as well.

 export PATH=${HOME}/bin:${PATH}

Log out and back in again. Now

 which ruby

should show return the path to the link to your ${HOME}/bin/ruby. You now have the latest Ruby installed and are ready to use TaskJuggler.

As a final step, you need to install the mail and term-ansicolor gems.

 sudo gem19 install mail term-ansicolor

If you don't want to use TaskJuggler from the git repository, you can install the TaskJuggler gem as well.

 sudo gem19 install taskjuggler

2.7 Installing the Vim Support

TaskJuggler can be used with any text editor that supports UTF-8 text file editing. If you don't have a preference yet, we recommend to try the Vim text editor. It's a very powerful editor and it has been customized for better integration with TaskJuggler. This section describes, how to activate and use the Vim integration. Vim is provided by pretty much any Linux distribution and also works well on MacOX and Windows. See the web page for how to install it if you don't have it yet.

This section describes the integration on Linux. Please see the How_To_Contribute section if you want to contribute the description for another OS.

If you have never customized Vim, you need to create a few directories first.

 cd ${HOME}
 mkdir .vim
 mkdir .vim/syntax

Then copy the syntax file tjp.vim into the vim syntax directory. The following command works if you have installed TaskJuggler as a gem with the system provided Ruby. For other cases, you may have to modify it accordingly.

 cp `gem contents taskjuggler | fgrep tjp.vim` .vim/syntax

Now we have to make sure, Vim detects the file. Edit the .vim/filetype.vim file to contain the following section.

augroup filetypedetect
au BufNewFile,BufRead *.tjp,*.tji               setf tjp
augroup END

And edit the .vim/syntax.vim file to contain the following line.

au! Syntax tjp          so ~/.vim/syntax/tjp.vim

When you now open a .tjp or .tji file in Vim, you should have the following features available:

  • Syntax highlighting. TJP keywords should be colored in different colors.
  • Syntax folding. The optional parts of properties within the curly braces can be collapsed. For this to work, the opening brace needs to be on the same line as the property keyword. The closing brace must be the first non-blank character of the last line of the block. See the :help fold Vim help command for details how to open and close folds.
  • Tag navigation. If you include a tagfile report in your project, Vim will know all property IDs and can jump to them. If you have a task with the ID foo.bar, the command :ta foo.bar will put the cursor right where task foo.bar was declared.
  • ID completion. If you include a tagfile report in your project, Vim can tell you the full hierarchical ID of a property. Just move the cursor to the first line of the property definition and press Ctrl-].
  • Run tj3 from within vim. Just type :make your_project.tjp to start the scheduling process. In case of errors or warnings, you will be able to navigate the errors with :cn and :cp.
  • Move the cursor over any TaskJuggler syntax keyword and press shift-k to get the manual page for this keyword.


<< Reporting_Bugs << Table Of Contents >> How_To_Contribute >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/rollupresource.html0000644000175000017500000001271212614413013021543 0ustar bernatbernat rollupresource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< rollupaccount << Table Of Contents >> rolluptask >>


Keyword rollupresource

Purpose
Do not show sub-resources of resources that match the specified logical expression.
Syntax rollupresource (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context accountreport, export, icalreport, resourcereport, tagfile, taskreport, textreport, tracereport

project "Test" "1.0" 2010-11-10 +2m {
  timezone "America/Denver"
}

resource org "Org" {
  resource team1 "Team1" {
    resource r1 "R1"
    resource r2 "R2"
    resource r3 "R3"
  }
  resource r4 "R4"
}
resource r5 "R5"

task "T"

resourcereport "RollupResource" {
  formats html
  columns name, id
  # Don't list the Team1 resources.
  rollupresource plan.id = 'team1'
}


<< rollupaccount << Table Of Contents >> rolluptask >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/scenario.ical.html0000644000175000017500000000571112614413013021171 0ustar bernatbernat scenario.ical

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< scenario << Table Of Contents >> scenarios >>


Keyword scenario (ical)

Purpose
Id of the scenario that should be included in the report. By default, the top-level scenario will be included. This attribute can be used select another scenario.
Syntax scenario <scenario>
Arguments scenario [ID]
ID of a defined scenario
Context icalreport



<< scenario << Table Of Contents >> scenarios >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/logicalexpression.html0000644000175000017500000001210012614413013022177 0ustar bernatbernat logicalexpression

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< loadunit << Table Of Contents >> logicalflagexpression >>


Keyword logicalexpression

Purpose

A logical expression is a combination of operands and mathematical operations. The final result of a logical expression is always true or false. Logical expressions are used the reduce the properties in a report to a certain subset or to select alternatives for the cell content of a table. When used with attributes like hidetask or hideresource the logical expression evaluates to true for a certain property, this property is hidden or rolled-up in the report.

Operands can be previously declared flags, built-in functions, property attributes (specified as scenario.attribute) or another logical expression. When you combine logical operations to a more complex expression, the operators are evaluated from left to right. a | b & c is identical to (a | b) & c. It's highly recommended that you always use brackets to control the evaluation sequence. Currently, TaskJuggler does not support the concept of operator precedence or right-left associativity. This may change in the future.

An operand can also be just a number. 0 evaluates to false, all other numbers to true. The logical expression can also be the special constants @all or @none. The first always evaluates to true, the latter to false.

Date attributes needs special attention. Attributes like maxend can be undefined. To use such an attribute in a comparison, you need to test for the validity first. E. g. to compare the end date of the plan scenario with the maxend value use isvalid(plan.maxend) & (plan.end > plan.maxend). The & and | operators are lazy. If the result is already known after evaluation the first operand, the second operand will not be evaluated any more.

Syntax @ (all | none)
Arguments none
Context Global scope
See also functions

taskreport "LeaveTasks" {
  hidetask isleaf()
  sorttasks plan.id # not 'tree' to really hide parent tasks
}

taskreport "Overruns" {
  hidetask isvalid(plan.maxend) & (plan.end > plan.maxend)
}


<< loadunit << Table Of Contents >> logicalflagexpression >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/resource.html0000644000175000017500000002166712614413013020316 0ustar bernatbernat resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< reportprefix << Table Of Contents >> resourceattributes >>


Keyword resource

Purpose

Tasks that have an effort specification need to have at least one resource assigned to do the work. Use this property to define resources or groups of resources.

Resources have a global name space. All IDs must be unique within the resources of the project.

Syntax resource [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]
The name of the resource
Context properties, resource, supplement (resource)

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
booking (resource) x
chargeset x x
efficiency x x
email
fail x
flags (resource) x x
journalentry
leaveallowance x
leaves x x x
limits (resource) x x x
managers x x
purge
rate (resource) x x x
resource
shifts (resource) x x
supplement (resource)
vacation (resource) x
warn x
workinghours (resource) x x x



<< reportprefix << Table Of Contents >> resourceattributes >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/task.html0000644000175000017500000003115712614413013017424 0ustar bernatbernat task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< tagfile << Table Of Contents >> task (statussheet) >>


Keyword task

Purpose

Tasks are the central elements of a project plan. Use a task to specify the various steps and phases of the project. Depending on the attributes of that task, a task can be a container task, a milestone or a regular leaf task. The latter may have resources assigned. By specifying dependencies the user can force a certain sequence of tasks.

Tasks have a local name space. All IDs must be unique within the tasks that belong to the same enclosing task.

Syntax task [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]
The name of the task
Context properties, task, supplement (task)

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
adopt (task)
allocate x x
booking (task) x
charge x
chargeset x x
complete x
depends x x
duration x
effort x
end x
fail x
flags (task) x x
journalentry
length x
limits (task) x
maxend x x
maxstart x
milestone x
minend x
minstart x x
note (task)
period (task) x
precedes x x
priority x x x
projectid (task) x x x
purge
responsible x x
scheduled x x
scheduling x
shifts (task) x x
start x
supplement (task)
task
warn x



<< tagfile << Table Of Contents >> task (statussheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/start.report.html0000644000175000017500000000645112614413013021130 0ustar bernatbernat start.report

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< start (limit) << Table Of Contents >> startcredit >>


Keyword start (report)

Purpose
Specifies the start date of the report. In task reports only tasks that end after this end date are listed.
Syntax start <date>
Arguments date
See date for details.
Context accountreport, export, icalreport, nikureport, resourcereport, statussheetreport, taskreport, textreport, timesheetreport, tracereport



<< start (limit) << Table Of Contents >> startcredit >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/task.statussheet.html0000644000175000017500000000640312614413013021773 0ustar bernatbernat task.statussheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< task << Table Of Contents >> task (timesheet) >>


Keyword task (statussheet)

Purpose
Opens the task with the specified ID to add a status report. Child task can be opened inside this context by specifying their relative ID to this parent.
Syntax task (<ABSOLUTE_ID> | <ID>) [{ <attributes> }]
Arguments none
Context statussheet, task (statussheet)

Attributes status (statussheet), task (statussheet)



<< task << Table Of Contents >> task (timesheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/supplement.resource.html0000644000175000017500000002144412614413013022502 0ustar bernatbernat supplement.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< supplement << Table Of Contents >> supplement (task) >>


Keyword supplement (resource)

Purpose

The supplement keyword provides a mechanism to add more attributes to already defined resources. The additional attributes must obey the same rules as in regular resource definitions and must be enclosed by curly braces.

This construct is primarily meant for situations where the information about a resource is split over several files. E. g. the vacation dates for the resources may be in a separate file that was generated by some other tool.

Syntax supplement <resource> [{ <attributes> }]
Arguments resource [ID]
The ID of a defined resource
Context resource, supplement (resource)

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
booking (resource) x
chargeset x x
efficiency x x
email
fail x
flags (resource) x x
journalentry
leaveallowance x
leaves x x x
limits (resource) x x x
managers x x
purge
rate (resource) x x x
resource
shifts (resource) x x
supplement (resource)
vacation (resource) x
warn x
workinghours (resource) x x x

resource joe "Joe"
supplement resource joe {
  vacation 2000-02-10 - 2000-02-20
}


<< supplement << Table Of Contents >> supplement (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/hideresource.html0000644000175000017500000001266412614413013021145 0ustar bernatbernat hideresource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< hidereport << Table Of Contents >> hidetask >>


Keyword hideresource

Purpose
Do not include resources that match the specified logical expression. If the report is sorted in tree mode (default) then enclosing resources are listed even if the expression matches the resource.
Syntax hideresource (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context accountreport, export, icalreport, nikureport, resourcereport, statussheetreport, tagfile, taskreport, textreport, timesheetreport, tracereport
See also sortresources



<< hidereport << Table Of Contents >> hidetask >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/Tutorial.html0000644000175000017500000014021712614413013020263 0ustar bernatbernat Tutorial

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< Getting_Started << Table Of Contents >> The_TaskJuggler_Syntax >>


4 The Tutorial: Your first Project

We have mentioned already that TaskJuggler uses plain text files that capture the known parts of the project. As you will see now, the syntax of these files is easy to understand and very intuitive. This chapter will walk you step by step through your first project. You create the project plan for a made-up accounting software project. This project demonstrates most of the commonly used features of TaskJuggler. It also includes some of the more advanced concepts that you may or may not need for your projects. Don't get scared by them. You can use them once you are more familiar with TaskJuggler and your projects grow larger. The complete tutorial example comes with your TaskJuggler software installation. You can use the following command to find the base directory of the example projects.

 ruby19 -e "puts Gem::Specification.find_by_name('taskjuggler').gem_dir"

The file for the tutorial project is called examples/Tutorial/tutorial.tjp. You can use any plain text editor to view and modify it.

4.1 Starting the project

Every TaskJuggler project file must start with the project property. It tells TaskJuggler the name of your project and a start and end date. The start and end dates don't need to be exact, but must fit all tasks of the project. It is the time interval the TaskJuggler scheduler will use to fit the tasks in. So, make it large enough for all your tasks to fit in. But don't make it too large, because this will result in longer scheduling times and higher memory consumption.

project acso "Accounting Software"  2002-01-16 +4m {
}

All TaskJuggler properties have a unique ID ,a name, and a set of optional attributes. The name must always be specified. The ID can be omitted if you never have to reference the property from another context. If you omit the ID, TaskJuggler will automatically generate a unique ID. The optional attributes are always enclosed in curly braces. If no optional attributes are specified, the braces can be omitted as well. In this example we will introduce a number of the attributes that may or may not matter for your specific projects. If you don't see an immediate need for a specific attribute, feel free to ignore it for now. You can always come back to them later. A full list of the supported project attributes can be found in the attributes section of the project property documentation.

Attributes always start with a keyword that identifies them. The meaning and parameters of attributes depends on the property context that they are used in. A context is delimited by a set of curly braces that enclose optional attributes of a property. The area outside of any property is called the global scope. Usually, attributes have one or more arguments. These arguments can be dates, character strings, numbers or symbols. Strings must be enclosed in single or double quotes. The argument types and meaning is explained for each keyword in the syntax reference section of this manual.

TaskJuggler manages all events with an accuracy of up to 15 minutes. In many cases, you don't care about this level of accuracy. Nevertheless, it's good to have it when you need it. All dates can optionally be extended by a time. By default, TaskJuggler assumes that all times are UTC (world time) times. If you prefer a different time zone, you need to use the timezone attribute.

  timezone "Europe/Paris"

Be aware that the project start and end dates in the project header are specified before you specify the time zone. The project header dates are always assumed to be UTC unless you specify differently. See interval2 for details.

project acso "Accounting Software"  2002-01-16-0:00-+0100

The currency attribute specifies the unit of all currency values.

  currency "USD"

Because each culture has its own way of specifying dates and numbers, the format for these are configurable. Use the timeformat attribute to specify the default format for dates. This format is used for reports, it does not affect the way you specify dates in the project files. Here you always need to use the TaskJuggler date notation.

  timeformat "%Y-%m-%d"
  numberformat "-" "" "," "." 1
  currencyformat "(" ")" "," "." 0

We also can specify the way numbers or currency values are shown in the reports. Use the numberformat and currencyformat attributes for this.

The attribute now is used to set the current day for the scheduler to another value than to the moment your invoke TaskJuggler. If this attribute is not present, TaskJuggler will use the current moment of time to determine where you are with your tasks. To get a defined result for the reports in this example we've picked a specific date that fits our purpose here. In your projects, you would use now to generate status reports for the date you specify.

  now 2002-03-05-13:00

In this tutorial we would like to compare two scenarios of the project. The first scenario is the one that we have planned. The second scenario is how it really happened. The two scenarios have the same task structure, but the start and end dates and other attributes of the task that are scenario specific may vary. In this example we assume that the project got delayed and use a second scenario that we name "Delayed" to describe the actual project. The scenario property is used to specify the scenarios. The delayed scenario is nested into the plan scenario. This tells TaskJuggler to use all values from the plan scenario also for the second scenario unless the second scenario has it's own values. This is a very easy but also powerful way to analyze the impact of certain changes to the plan of record. We'll see further below, how to specify values for a scenario and how to compare the results.

  scenario plan "Plan" {
    scenario delayed "Delayed"
  }

To summarize the above, let's look at the complete header again. Don't get scared by the wealth of attributes here. They are all optional and mostly used to illustrate the flexibility of TaskJuggler.

project acso "Accounting Software"  2002-01-16 +4m {
  # Set the default time zone for the project. If not specified, UTC
  # is used.
  timezone "Europe/Paris"
  # Hide the clock time. Only show the date.
  timeformat "%Y-%m-%d"
  # Use US format for numbers
  numberformat "-" "" "," "." 1
  # Use US financial format for currency values. Don't show cents.
  currencyformat "(" ")" "," "." 0
  # Pick a day during the project that will be reported as 'today' in
  # the project reports. If not specified, the current day will be
  # used, but this will likely be outside of the project range, so it
  # can't be seen in the reports.
  now 2002-03-05-13:00
  # The currency for all money values is the Euro.
  currency "USD"

  # We want to compare the baseline scenario to one with a slightly
  # delayed start.
  scenario plan "Plan" {
    scenario delayed "Delayed"
  }
  extend resource {
    text Phone "Phone"
  }
}

4.2 Global Attributes

For this tutorial, we also like to do a simple profit and loss analysis of the project. We will track labor cost versus customer payments. To calculate the labor costs we have to specify the default daily costs of an employee. This can be changed for certain employees later, but it illustrates an important concept of TaskJuggler – inheritance of attributes. In order to reduce the size of the TaskJuggler project file to a readable minimum, properties inherit many attributes from their enclosing scopes. We'll see further below, what this actually means. Right after the project property we are at top-level scope, so this is the default for all following properties.

rate 390.0

The rate attribute can be used to specify the daily costs of resources. All subsequently declared resources will get this rate. But it can certainly be changed to a different rate at group or individual resource level.

You may also want to tell TaskJuggler about holidays that affect all resources. Global holidays are time periods where TaskJuggler does not do any resource assignments to tasks.

leaves holiday "Good Friday" 2002-03-29

Use the leaves attribute to define a global holiday. Global holidays may have a name and must have a date or date range. Other leaves for individual resources or groups of resources can be defines similarly.

4.3 Macros

Macros are another TaskJuggler feature to save you typing work and to keep project files small and maintainable. Macros are text patterns that can be defined once and inserted multiple times in the project file. A macro always has a name and the text pattern is enclosed by square brackets.

macro allocate_developers [
  allocate dev1
  allocate dev2
  allocate dev3
]

To use the macro you simply have to write ${allocate_developers} and TaskJuggler will replace the term ${allocate_developers} with the pattern. We will use this macro further below in the example and then explain the meaning of the pattern.

4.4 Declaring Flags

A TaskJuggler feature that you will probably make heavy use of is flags. Once declared you can attach them to any property. When you generate reports of the TaskJuggler results, you can use the flags to filter out unwanted properties and limit the report to exactly those details that you want to have included.

flags team

This is a flags declaration. All flags need to be declared before they can be used to avoid hard to find errors due to misspelled flag names. The flags should be declared before any property at global scope. We will see further down, how we can make use of these flags.

4.5 Declaring Accounts

The use of our resources will generate costs. For a profit and loss analysis, we need to balance the cost against the customer payments. In order not to get lost with all the various amounts, we declare 3 accounts to credit the amounts to. We create one account for the development costs, one for the documentation costs, and one for the customer payments.

account cost "Project Cost" {
  account dev "Development"
  account doc "Documentation"
}
account rev "Payments"

The account needs an ID and a name. IDs may only consist of the characters a to z, A to Z and the underscore. All but the first character may also be digits 0 to 9. The ID is necessary so that we can reference the property again later without having to write the potentially much longer name. The name may contain space characters and therefore has to be enclosed with single or double quotes.

Accounts can be grouped by nesting them. You can use this feature to create sets of accounts. Such sets can then be balanced against each other to create a profit and loss analysis. When you have specified accounts in your project, you must at least define one default balance.

balance cost rev

4.6 Declaring Resources

While the above introduced account property is only needed if you want to do a P&L analysis, resources are usually found in almost any project.

resource boss "Paul Henry Bullock" {
  email "phb@crappysoftware.com"
  Phone "x100"
  rate 480
}
resource dev "Developers" {
  managers boss
  resource dev1 "Paul Smith" {
    email "paul@crappysoftware.com"
    Phone "x362"
    rate 350.0
  }
  resource dev2 "Sébastien Bono" {
    email "SBono@crappysoftware.com"
    Phone "x234"
  }
  resource dev3 "Klaus Müller" {
    email "Klaus.Mueller@crappysoftware.com"
    Phone "x490"
    leaves annual 2002-02-01 - 2002-02-05
  }
  flags team
}
resource misc "The Others" {
  managers boss
  resource test "Peter Murphy" {
    email "murphy@crappysoftware.com"
    Phone "x666"
    limits { dailymax 6.4h }
    rate 310.0
  }
  resource doc "Dim Sung" {
    email "sung@crappysoftware.com"
    Phone "x482"
    rate 300.0
    leaves annual 2002-03-11 - 2002-03-16
  }

  flags team
}

This snippet of the example shows the use of the resource property. Just like accounts, resources should have an ID and must have a name. Resource IDs, like account IDs must also be unique within their property class. As you can see, resource properties can be nested: dev is a group or container resource, a team that consists of three other resources.

dev1, alias Paul Smith, costs more than the normal employee. So the declaration of dev1 overwrites the inherited default rate with a higher value.

The default value has been inherited from the enclosing scope, resource dev, which in turn has inherited it from the global scope. The declaration of the resource Klaus Müller uses another optional attribute. Attributes are only inherited from the parent property if the attribute was declared in the parent property before the child property declaration was started.

The syntax reference lists for each property whether an attribute is inherited from the parent or the attribute in the global scope.

With leaves you can specify certain time intervals where the resource is not available. Leaves are list attributes. They accumulate the declarations. If you want to get rid of inherited or previously assigned values, you can use the purge attribute to clear the list.

leaves requires a time interval. It is important to understand how TaskJuggler handles time intervals. Internally, TaskJuggler uses the number of seconds after January 1st, 1970 to store any date. So all dates are actually stored with an accuracy of 1 second in UTC time. 2002-02-01 specifies midnight February 1st, 2002. Following the TaskJuggler concept of requiring as little information as necessary and extending the rest with sensible defaults, TaskJuggler adds the time 0:00:00 if nothing else has been specified. So the vacation ends on midnight February 5th, 2002. Well, almost. Every time you specify a time interval, the end date is not included in the interval. So Klaus Müller's vacation ends exactly at 0:00:00 on February 5th, 2002. February 5 is not part of the leave!

Peter Murphy only works 6.4 hours a day. So we use the limits attribute to limit his daily working hours. We could also define exact working hours using the shift property, but we ignore this for now.

Note that we have attached the flag team after the declaration of the sub-resources to the team resources. This way, these flags don't get passed down to the sub-resources. If we would have declared the flags before the sub-resources, then they would have the flags attached as well.

4.7 Specifying the Tasks

Let's focus on the real work now. The project should solve a problem: the creation of an accounting software. Because the job is quite complicated, we break it down into several subtasks. We need to do a specification, develop the software, test the software, and write a manual. Using the task property, this would look as follows:

task AcSo "Accounting Software" {
  task spec "Specification" {
  }
  task software "Software Development" {
  }
  task test "Software testing" {
  }
  task manual "Manual" {
    journalentry 2002-02-28 "User manual completed" {
      author boss
      summary "The doc writers did a really great job to finish on time."
    }
  }
  task deliveries "Milestones" {
  }
}

Similar to resources, tasks are declared by using the task keyword followed by an ID and a name string. All TaskJuggler properties have their own namespaces. This means, that it is quite OK to have a resource and a task with the same ID. Tasks may have optional attributes which can be tasks again, so tasks can be nested. In contrast to all other TaskJuggler properties, task IDs inherit the ID of the enclosing task as a prefix to the ID. The full ID of the spec task is AcSo.spec. You need to use this absolute ID when you want to reference the task later on. This hierarchical name space for tasks was chosen to support large projects where multiple project managers may use the same ID in different sub tasks.

To track important milestones of the project, we also added a task called Milestones. This task, like most of the other tasks will get some subtasks later on.

We consider the specification task simple enough, so we don't have to break it into further subtasks. So let's add some more details to it.

  task spec "Specification" {
    effort 20d
    ${allocate_developers}
    depends !deliveries.start
  }

The effort to complete the task is specified with 20 man-days. Alternatively we could have used the length attribute or the duration attribute. length specifies the duration of the task in working days while duration specifies the duration in calendar days. Contrary to effort, these two don't have to have a specification of the involved resources. Since effort specifies the duration in man-days, we need to say who should be allocated to the task. The task won't finish before the resources could be allocated long enough to reach the specified effort. Tasks with length or duration criteria and allocated resources will last exactly as long as requested. Resources will be allocated only if available. It's possible that such a tasks ends up with no allocations at all if the resources are always assigned to other tasks for that period. Each task can only have one of the three duration criteria. Container tasks may never have a duration specification. They are automatically adjusted to fit all sub tasks.

Here we use the allocate_developers macro mentioned above. The expression ${allocate_developers} is simply expanded to

  allocate dev1
  allocate dev2
  allocate dev3

If you need to allocate the same bunch of people to several tasks, the macro saves you some typing. You could have written the allocate attributes directly instead of using the macro. Since the allocation of multiple resources to a task is a good place for macro usage, we found it a good idea to use it in this example as well.

For TaskJuggler to schedule a task, it needs to know either the start and end criteria of a task, or one of them and a duration specification. The start and end criteria can either be fixed dates or relative dates. Relative dates are specifications of the type task B starts after task A has finished. Or in other words, task B depends on task A. In this example the spec task depends on a subtasks of the deliveries task. We have not specified it yet, but it has the local ID start.

To specify the dependency between the two tasks, we use the depends attribute. This attribute must be followed by one or more task IDs. If more than one ID is specified, each ID has to be separated with a comma from the previous one. Task IDs can be either absolute IDs or relative IDs. An absolute ID of a task is the ID of this task prepended by the IDs of all enclosing tasks. The task IDs are separated by a dot from each other. The absolute ID of the specification task would be AcSo.spec.

Relative IDs always start with one or more exclamation marks. Each exclamation mark moves the scope to the next enclosing task. So !deliveries.start is expanded to AcSo.deliveries.start since AcSo is the enclosing task of deliveries. Relative task IDs are a little bit confusing at first, but have a real advantage over absolute IDs. Sooner or later you want to move tasks around in your project and then it's a lot less likely that you have to fix dependency specifications of relative IDs.

The software development task is still too complex to specify it directly. So we split it further into subtasks.

  task software "Software Development" {
    priority 1000
    task database "Database coupling" {
      journalentry 2002-02-03 "Problems with the SQL Libary" {
        author dev1
        alert yellow
        summary -8<-
          We ran into some compatibility problems with the SQL
          Library.
        ->8-
        details -8<-
          We have already contacted the vendor and are now waiting for
          their advise.
        ->8-
      }
    }
    task gui "Graphical User Interface" {
    }
    task backend "Back-End Functions" {
    }
  }

We use the priority attribute to mark the importance of the tasks. 500 is the default priority of top-level tasks. Setting the priority to 1000 marks the task as most important task, since the possible range is 1 (not important at all) to 1000 (ultimately important). priority is an attribute that is passed down to subtasks if specified before the subtasks' declaration. So all subtasks of software have a priority of 1000 as well, unless they have their own priority definition.

    task database "Database coupling" {
      effort 20d
      allocate dev1, dev2
      journalentry 2002-02-03 "Problems with the SQL Libary" {
        author dev1
        alert yellow
        summary -8<-
          We ran into some compatibility problems with the SQL
          Library.
        ->8-
        details -8<-
          We have already contacted the vendor and are now waiting for
          their advise.
        ->8-
      }
    }

The work on the database coupling should not start before the specification has been finished. So we again use the depends attribute to let TaskJuggler know about this. This time we use two exclamation marks for the relative ID. The first one puts us in the scope of the enclosing software task. The second one is to get into the AcSo scope that contains the spec tasks. For a change, we allocate resources directly without using a macro.

    task gui "Graphical User Interface" {
      effort 35d
      delayed:effort 40d
      depends !database, !backend
      allocate dev2, dev3
      # Resource dev2 should only work 6 hours per day on this task.
      limits {
        dailymax 6h {
          resources dev2
        }
      }
    }

One more interesting thing to note is the fact that we like the resource dev2 only to work 6 hours each day on this task, so we use the optional attribute limits.resource to specify this.

TaskJuggler can schedule your project for two different scenarios. We have called the first scenario plan scenario and the second delayed scenario. Many of the reports allow you to put the values of both scenarios side by side to each other, so you can compare the scenarios. All scenario-specific values that are not explicitly stated for the delayed scenario are taken from the plan scenario. So the user only has to specify the values that differ in the delayed scenario. The two scenarios must have the same task structure and the same dependencies. But the start and end dates of tasks as well as the duration may vary. In the example we have planned the work on the graphical user interface to be 35 man-days. It turned out that we actually needed 40 man-days. By prefixing the effort attribute with delayed:, the effort value for the delayed scenario can be specified.

    task backend "Back-End Functions" {
      effort 30d
      complete 95
      depends !database
      allocate dev1, dev2
    }

By default, TaskJuggler assumes that all tasks are on schedule. Sometimes you want to generate reports that show how much of a task actually has been completed. TaskJuggler uses the current date for this, unless you have specified another date using the now attribute. If a task is ahead of schedule or late, this can be specified using the complete attribute. This specifies how many percent of the task have been completed up to the current date. In our case the back-end implementation is slightly ahead of schedule as we will see from the report.

  task test "Software testing" {

    task alpha "Alpha Test" {
      effort 1w
      depends !!software
      allocate test, dev2
      note "Hopefully most bugs will be found and fixed here."
      journalentry 2002-03-01 "Contract with Peter not yet signed" {
        author boss
        alert red
        summary -8<-
          The paperwork is stuck with HR and I can't hunt it down.
        ->8-
        details -8<-
          If we don't get the contract closed within the next week,
          the start of the testing is at risk.
        ->8-
      }
    }

    task beta "Beta Test" {
      effort 4w
      depends !alpha
      allocate test, dev1
    }
  }

The software testing task has been split up into an alpha and a beta test task. The interesting thing here is, that efforts can not only be specified as man-days, but also man-weeks, man-hours, etc. By default, TaskJuggler assumes a man-day is 8 hours, man-week is 40 man-hours or 5 man-days. The conversion factor can be changed using the dailyworkinghours attribute.

Let's go back to the outermost task again. At the beginning of the example we stated that we want to credit all development work to one account with ID dev and all documentation work to the account doc. To achieve this, we use the attribute chargeset to credit all tasks to the dev account.

For the duration of the AcSo task we also have running costs for the lease on the building and the equipment. To compensate this, we charge a daily rate of USD 170 per day using the charge attribute.

task AcSo "Accounting Software" {
  chargeset dev
  charge 170 perday
  task spec "Specification" {

Since we specify the attribute for the top-level task before we declare any subtasks, this attribute will be inherited by all subtasks and their subtasks and so on. The only exception is the writing of the manual. We need to change the chargeset for this task again, as it is also a subtask of AcSo and we want to use a different account for it.

  task manual "Manual" {
    effort 10w
    depends !deliveries.start
    allocate doc, dev3
    purge chargeset
    chargeset doc
    journalentry 2002-02-28 "User manual completed" {
      author boss
      summary "The doc writers did a really great job to finish on time."
    }
  }

4.8 Specifying Milestones

All tasks that have been discussed so far, had a certain duration. We did not always specify the duration explicitly, but we expect them to last for a certain period of time. Sometimes you just want to capture a certain moment in your project plan. These moments are usually called milestones, since they have some level of importance for the progress of the project.

TaskJuggler has support for milestones as well. Milestones are leaf tasks that don't have a duration specification.

  task deliveries "Milestones" {
    purge chargeset
    chargeset rev

    task start "Project start" {
      start ${projectstart}
      delayed:start 2002-01-20
      charge 21000.0 onstart
    }

    task prev "Technology Preview" {
      depends !!software.backend
      charge 31000.0 onstart
      note "All '''major''' features should be usable."
    }

    task beta "Beta version" {
      depends !!test.alpha
      charge 13000.0 onstart
      note "Fully functional, may contain bugs."
    }

    task done "Ship Product to Customer" {
      # maxend 2002-04-17
      depends !!test.beta, !!manual
      charge 33000.0 onstart
      note "All priority 1 and 2 bugs must be fixed."
    }
  }
}

We have put all important milestones of the project as subtasks of the deliveries task. This way they show up nicely grouped in the reports. All milestones either have a dependency or a fixed start date. For the first milestone we have used the attribute start to set a fixed start date. All other tasks have direct or indirect dependencies on this task. Moving back the start date will slip the whole project. This has actually happened, so we use the delayed: prefix again to specify the start date for the delayed scenario.

Every milestone is linked to a customer payment. By using the charge attribute we can credit the specified amount to the account associated with this task. Since we have assigned the rev account to the enclosing task, all milestones will use this account as well. This time, we use the keyword onstart to indicate that this is not a continuous charge but a one-time charge that is credited at the begin of the task.

Did you notice the line in the task done that starts with a hash? This line is commented out. If TaskJuggler finds a hash, it ignores the rest of the line. This way you can include comments in your project. The maxend attribute specifies that the task should end no later than the specified date. This information is not used for scheduling, but only for checking the schedule afterwards. Since the task will end later than the specified date, commenting out the line would trigger a warning.

Now the project has been completely specified. Stopping here would result in a valid TaskJuggler file that could be processed and scheduled. But no reports would be generated to visualize the results.

4.9 Visualizing the Project

To see and share the project data you reports can be generated. You can generate any number of reports and you can select from a variety of report types and output formats. To have a report generated after the project scheduling has been completed, you need include a report definition into the project description. Report definitions are properties that are very similar to the task and resource properties that you are already familiar with. Just like these, report definitions can be nested to take advantage of the attribute inheritance mechanism. Every report definition starts with the type of the report. Each type of report has a particular focus. A taskreport lists the project data in the form of a task list. A resourcereport does the same in form of a resource list. For a more generic report, you can use the textreport.

A textreport does not directly present the data in form of a task or resource list. It just consists of text building blocks that are described by Rich Text. There can be a building block at the top and bottom, as well as three columns in the center. The column are called left, center and right.

For our first report, we'll just use the center column for now. Like every property, you need to specify a name. This name will be the base name of the generated report file. Depending on the output format, the proper suffix is appended. For this report, we only chose to generate a web page in HTML format. There is no default format defined for reports. If the formats attribute is not specified, no output file will be generated for the report specification.

This may seem odd at first glance since TaskJuggler syntax always tries to use the most compact and readable syntax for the common case. As you will see in a minute, reports may be composed of several report specifications. One report specification can include the output of another report specification as well. In this case, the included report does not need to generate it's own file. The output will be included within the output of another report specification. In case of such composed reports, the output format specification of the top-level format will be used for all included reports as well.

textreport frame "" {
  textreport index "Overview" {
    formats html
    center '<[report id="overview"]>'
  }
}

For the main report, we choose the file name Overview and the format html. So, the generated file will be called Overview.html.

As we've mentioned before, the sections of a textreport are defined in Rich Text format. Here we use a so called block generator to include the HTML output of another report definition. The report block generator allows us to compose reports by combining their output into a single report. You must provide the id parameter to specify which report definition you would like to use. In this case, it is a report definition with the ID overview. Note that generator parameters need to be enclosed in single or double quotes. We are essentially marking a string within a string. This can only work out, if we don't use the same parameter for both. Let's define this report first.

taskreport overview "" {
  columns bsi { title 'WBS' },
          name, start, end, effort, cost,
          revenue, chart { ${TaskTip} }
}

Instead of another textreport definition we are now using a taskreport. A task report contains a list of tasks in a table structure. By default, it contains all tasks of the project. As we will see later on, we can use filter expressions to limit the content to a well defined subset of tasks. The table contains a line for each task and comes by default with a few columns like the name of the task, and the start and end dates. For this project overview report, we like to have also the effort for each task, the duration, the effort, the cost and revenue numbers included. To top it off, we also include a column with a Gantt chart.

By including the cost and revenue column, we are able to do a simple profit and loss analysis on the project. This P&L is computed from the accounts that we have provided above. For this to work, we need to tell TaskJuggler which accounts are cost accounts and which are revenue accounts. We have already conveniently grouped the accounts and the balance attribute specifies which accounts are used for the P&L in this report.

  balance cost rev

The columns of the report can be customized. You can overwrite the default title or the cell content. See columns for a full list of available attributes. For the chart column, we'd like to have a tool tip that displays additional details when the mouse pointer is placed over a task bar. Since we use this tool tip in several reports, we have defined the TaskTip macro for it.

macro TaskTip [
  tooltip istask() -8<-
    '''Start: ''' <-query attribute='start'->
    '''End: ''' <-query attribute='end'->
    ----
    '''Resources:'''

    <-query attribute='resources'->
    ----
    '''Precursors: '''

    <-query attribute='precursors'->
    ----
    '''Followers: '''

    <-query attribute='followers'->
    ->8-
]

The tooltip attribute describes the content of the tool tip. The first parameter is a logical expression that determines when the tool tip is active. You can specify multiple tool tips. The first matching one is being displayed. The condition is evaluated for each report line. The istask() function only evaluates to true for task lines. See functions for a complete list of functions that can be used in logical expressions.

The content of the tool tip is a template that uses query generators to include task attributes such as the start and end date.

We have chosen to include the start and end date of each task in the report. By default, TaskJuggler lists dates as day, month and year. We like the format to be similar to the format that the project syntax uses, but also like to include the weekday. To change the date format, the timeformat attribute can be used.

The project will last a few weeks. The most convenient unit to list efforts in is man or resource days. The loadunit attribute tells TaskJuggler to list the load of each task or resource in man days. Since this will just be a number without a unit, it is advisable to include a small hint for the reader that these values are indeed man or resource days. The caption of the table is a convenient place to put this information by using the caption attribute.

  timeformat "%a %Y-%m-%d"
  loadunit days
  caption 'All effort values are in man days.'

The taskreport can contain more than just the table. It is not as flexible as the textreport, but still has support for a header and footer. Let's look at the header first. We not only like to put a headline here, but several paragraphs of text. The header attribute is a Rich Text attribute just like center. We could enclose it in single or double quotes again. But for Strings that span multiple lines and potentially include single or double quotes as well, scissor-marks or cut-here-marks are recommended. These marks look like a pair of scissors that cut along a dashed line. Use -8<- to begin a string and ->8- to terminate it. The opening cut mark must be immediately followed by a line break. The indentation of the following line defines the indentation that will be ignored for all lines of the string. The following lines must have at least the same indentation. The indentation that exceeds the indentation of the first line will be kept in the resulting string. With this feature, you can define multi-line Rich Text strings without disturbing the indentation structure of your project file.

  header -8<-
    === Project Overview ===

    The project is structured into 3 phases.

    # Specification
    # <-reportlink id='frame.development'->
    # Testing

    === Original Project Plan ===
  ->8-

Section headers are surrounded by ==. The number of equal signs, define the section level. You need to start with two equal characters for the first level. Text that is surrounded by blank lines will create a paragraph. Bullet lists can be made by starting a line with a # character. Remember that the indentation of cut-mark strings will be ignored. Your # character must not be the first character in the line as long it is only preceded by the exact same number of blanks as the first line of the cut-mark string.

If you want to reference other reports from this report, you can include the file name of this report by [[ and ]]. Don't include the extension of the file name, it will be automatically appended. The actual representation of the reference depends on the chosen output format. For HTML output, the reference is a click-able link to the referenced report file.

For the footer we can proceed accordingly. We just add a few more paragraphs of text the describe certain aspects of the project. By putting it all together, we end up with the following report definition.

taskreport overview "" {
  header -8<-
    === Project Overview ===

    The project is structured into 3 phases.

    # Specification
    # <-reportlink id='frame.development'->
    # Testing

    === Original Project Plan ===
  ->8-
  columns bsi { title 'WBS' },
          name, start, end, effort, cost,
          revenue, chart { ${TaskTip} }
  # For this report we like to have the abbreviated weekday in front
  # of the date. %a is the tag for this.
  timeformat "%a %Y-%m-%d"
  loadunit days
  hideresource @all
  balance cost rev
  caption 'All effort values are in man days.'

  footer -8<-
    === Staffing ===

    All project phases are properly staffed. See [[ResourceGraph]] for
    detailed resource allocations.

    === Current Status ===

    The project started off with a delay of 4 days. This slightly affected
    the original schedule. See [[Deliveries]] for the impact on the
    delivery dates.
  ->8-
}

The generated report can be found here. It servers as an entry page for the other reports. While it already contains some references, a navigator bar would be handy as well. Fortunately, there is a block generator called 'navigator' to take care of this. But before we can include the navigator in the report, we need to define it first.

navigator navbar {
  hidereport @none
}

hidereport is a filter attribute. The logical expression determines which reports will be included in the navigator bar. A logical expression of 0 means hide no reports, so all are included.

The best place to put a navigator bar in the report is right at the top. We use two horizontal lines to separate the navigator from the main headline and the rest of the report. ---- at the begin of the line create such a horizontal separation line.

textreport frame "" {
  header -8<-
    == Accounting Software Project ==
    <[navigator id="navbar"]>
  ->8-
  footer "----"
  textreport index "Overview" {
    formats html
    center '<[report id="overview"]>'
  }
}


<< Getting_Started << Table Of Contents >> The_TaskJuggler_Syntax >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/leaves.html0000644000175000017500000001340012614413013017730 0ustar bernatbernat leaves

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< leaveallowance << Table Of Contents >> left >>


Keyword leaves

Purpose

Describe a list of leave periods. A leave can be due to a public holiday, personal or sick leave. At global scope, the leaves determine which day is considered a working day. Subsequent resource definitions will inherit the leave list.

Leaves can be defined at global level, at resource level and at shift level and intervals may overlap. The leave types have different priorities. A higher priority leave type can overwrite a lower priority type. This means that resource level leaves can overwrite global leaves when they have a higher priority. A sub resource can overwrite a leave of a enclosing resource.

Leave periods outside of the project interval are silently ignored. For leave periods that are partially outside of the project period only the part inside the project period will be considered.

Syntax leaves (project | annual | special | sick | unpaid | holiday) [<name>] <interval3> [, (project | annual | special | sick | unpaid | holiday) [<name>] <interval3>...]
Arguments project
Assignment to another project (lowest priority)
annual
Personal leave based on annual allowance
special
Personal leave based on a special occasion
sick
Sick leave
unpaid
Unpaid leave
holiday
Public or bank holiday (highest priority)
name [STRING]
An optional name or reason for the leave
interval3
See interval3 for details.
Context properties, resource, supplement (resource), shift

project "Annual Leave" 2011-12-19 +1y {
  now 2012-07-01
}

leaves holiday "Christmas" 2011-12-24 +3d,
       holiday "New Year" 2011-12-31 +3d

shift s1 "Shift 1" {
    leaves annual 2011-12-19 +3w,
           special 2012-01-12 +1d
}

resource team "Team" {
  leaveallowances annual 2011-12-19 20d
  leaves holiday 2012-01-06

  resource r1 "R1" {
    leaves annual 2011-12-19 +3w,
           special 2012-01-12 +1d
  }
  resource r2 "R2" {
    leaveallowances annual 2012-06-01 -10d
    leaves sick 2012-01-04 +2d,
           unpaid 2012-01-10 +3d
  }
}
resource r3 "R3" {
  shifts s1 2011-12-19 +3w
  leaves sick 2012-01-04 +2d
}

task "foo"

resourcereport "." {
  formats html
  columns name, annualleave, annualleavebalance, sickleave, specialleave, unpaidleave
}


<< leaveallowance << Table Of Contents >> left >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/scheduled.html0000644000175000017500000000611712614413013020420 0ustar bernatbernat scheduled

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< scenariospecific (extend) << Table Of Contents >> scheduling >>


Keyword scheduled

Purpose
This is mostly for internal use. It specifies that the task should be ignored for scheduling in the scenario. This option only makes sense if you provide all resource bookings manually. Without booking statements, the task will be reported with 0 effort and no resources assigned.
Syntax scheduled
Arguments none
Context task, supplement (task)



<< scenariospecific (extend) << Table Of Contents >> scheduling >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/css/0000755000175000017500000000000012614413013016355 5ustar bernatbernattaskjuggler-3.5.0/manual/html/css/tjmanual.css0000644000175000017500000000257712614413013020715 0ustar bernatbernatpre { font-size:16px; font-family: Courier; padding-left:8px; padding-right:8px; padding-top:0px; padding-bottom:0px; } p { margin-top:8px; margin-bottom:8px; } code { font-size:16px; font-family: Courier; } .table { background-color:#ABABAB; width:100%; } .tag { background-color:#E0E0F0; font-size:16px; font-weight:bold; padding-left:8px; padding-right:8px; padding-top:5px; padding-bottom:5px; } .descr { background-color:#F0F0F0; font-size:16px; padding-left:8px; padding-right:8px; padding-top:5px; padding-bottom:5px; } .attrtable { background-color:#ABABAB; } .attrtag { background-color:#F0F0F0; font-family: Courier; padding-left:6px; padding-right:6px; padding-top:3px; padding-bottom:3px; } .attrdescr { background-color:#F0F0F0; padding-left:6px; padding-right:6px; padding-top:3px; padding-bottom:3px; width:100%; } .codeframe{ border-width:2px; border-color:#ABABAB; border-style:solid; background-color:#F0F0F0; margin-top:8px; margin-bottom:8px; } .code { padding-left:15px; padding-right:15px; padding-top:0px; padding-bottom:0px; } div[codesection] { border-width:2px; border-color:#ABABAB; border-style:solid; background-color:#F0F0F0; margin-top:8px; margin-bottom:8px; } pre[codesection] { padding-left:15px; padding-right:15px; padding-top:0px; padding-bottom:0px; } taskjuggler-3.5.0/manual/html/css/tjreport.css0000644000175000017500000002162312614413013020744 0ustar bernatbernat/* WARNING: This is an automatically generated file. DO NOT EDIT IT! * If you want to use your own style sheet, keep a master copy * somewhere else and copy it over this file if needed. This file will * be overwritten whenever the stylesheet that comes with TaskJuggler * has been modified. */ body { font-family:Bitstream Vera Sans, Tahoma, sans-serif; font-size:15px; } h1, h2, table, tr, td, div, span { } table { } /* Treat images in tables as block and not line elements. This will * eliminate surprising space at the bottom. */ td img {display: block;} p { font-size:15px; } td, div { padding:0px; margin:0px; } h1 { font-size:22px; } h2 { font-size:18px; } h3 { font-size:16px; } .tj_journal { font-size:11px; } i.tj_journal { font-size:9px; } h1.tj_journal { font-size:16px; margin-left:0px; } h2.tj_journal { font-size:14px; margin-left:10px; } h3.tj_journal { margin-top:5px; font-size:13px; margin-bottom:1px; margin-left:20px; } h4.tj_journal { font-size:12px; margin-left:30px; } p.tj_journal { margin-top:1px; margin-bottom:5px; margin-left:30px; } /* The basic elements of a text report page. */ .tj_text_page { width:100%; border-spacing:0px; } .tj_text_row { } .tj_column_left { vertical-align:top; } .tj_column_center { vertical-align:top; } .tj_column_right { vertical-align:top; } /* The top-level page layout */ .tj_page { margin: 35px 5% 25px 5%; } /* The container that holds report tables */ .tj_table_frame { margin-left:auto; margin-right:auto; text-align:center; background-color:#9a9a9a; margin-top:15px; margin-bottom:15px; border-spacing:1px; font-size:13px; } /* The headline box for report tables */ .tj_table_headline { font-size:16px; font-weight:bold; white-space:nowrap; padding:5px; margin:1px; text-align:center; color:#000000; background-color:#d4dde6; } .tj_table { background-color:#9a9a9a; margin:0px; border-spacing:1px; } /* The cells of the table header. */ .tj_table_header_cell { padding:1px 3px 1px 3px; white-space:nowrap; border-spacing:0px; color:#ffffff; overflow:hidden; } /* A regular table cell. It usually contains the cell icon, the text * label and a tooltip trigger. */ .tj_table_cell { font-size:13px; vertical-align:top; padding:1px 3px 1px 3px; margin:0px; width:100%; border-spacing:0px; position: relative; overflow:hidden; } /* The symbol is the icon to the left of the text label in a table * cell. */ .tj_table_cell_icon { vertical-align:top; text-align:right; padding:1px 3px 0px 0px; width:19px; } /* This is the text label of a cell. */ .tj_table_cell_label { font-size:12px; vertical-align:top; padding-top:1px; } /* The box around the icon to the right of the text label. This is * optional and triggers the tooltip with the full text of the cell in * case the cell is not large enough to show everything. */ .tj_table_cell_tooltip { font-size:13px; vertical-align:top; padding:2px 0px 0px 3px; } /* The container that holds the invisible tooltips. */ .tj_tooltip_box{ position:fixed; top:0px; left:0px; display:none; visibility:hidden; } /* The caption box for report tables */ .tj_table_caption { padding: 5px 13px 5px 13px; background-color:#ebf2ff; text-align:left; white-space:normal; margin:1px; font-size:13px } .tj_table_legend_frame { padding:5px; margin:1px; background-color:#d4dde6; } /* The legend of reports with calendar and Gantt charts */ .tj_table_legend { margin-left:auto; margin-right:auto; text-align:center; font-size:11px; color:#000000; border-spacing:1px; } /* A row of the table legend */ .tj_legend_row { height:19px; } /* Headlines used for the legend when both chart types are used in a * report */ .tj_legend_headline { font-size:12px; font-weight:bold; } /* A legend row has 3 items. An item contains a label and a symbol */ .tj_legend_item { } .tj_legend_symbol { position: relative; width:45px; height:19px; } .tj_legend_label { text-align: left; } .tj_legend_spacer { width:30px; } .tj_gantt_jag { position:absolute; border-style: solid; width: 0px; height: 0px; line-height: 0px; border-top: 5px solid black; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: none } .tj_diamond_top { position:absolute; border-style: solid; width: 0px; height: 0px; line-height: 0px; border-top: none; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 7px solid black; } .tj_diamond_bottom { position:absolute; border-style: solid; width: 0px; height: 0px; line-height: 0px; border-top: 7px solid black; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: none; } .tj_arrow_head { position:absolute; border-style: solid; width: 0px; height: 0px; line-height: 0px; border-top: 5px solid transparent; border-left: 5px solid black; border-right: none; border-bottom: 5px solid transparent; } .tabback { background-color:#9a9a9a; overflow:visible; } .tabfront { background-color:#d4dde6; } .tabhead { white-space:nowrap; background-color:#7a7a7a; color:#ffffff; text-align:center; } .tabhead_offduty { white-space:nowrap; background-color:#bdbdaa; color:#000000; } .tabfooter { white-space:nowrap; background-color:#9a9a9a; color:#ffffff; text-align:center; } .headercelldiv { padding-top:1px; padding-right:3px; padding-left:3px; padding-bottom:0px; white-space:nowrap; overflow:hidden; } .celldiv { padding:1px 3px 2px 3px; white-space:nowrap; overflow:hidden; position: relative; } .tabline { color:#000000 } .tabcell { white-space:nowrap; overflow:hidden; padding:0px; } .costaccountcell1 { background-color:#fff2eb; white-space:nowrap; padding:0px; } .costaccountcell2 { background-color:#ebdfd9; white-space:nowrap; padding:0px; } .revenueaccountcell1 { background-color:#cbffcc; white-space:nowrap; padding:0px; } .revenueaccountcell2 { background-color:#a6d0a6; white-space:nowrap; padding:0px; } .accountcell1 { background-color:#ebf2ff; white-space:nowrap; padding:0px; } .accountcell2 { background-color:#d9dfeb; white-space:nowrap; padding:0px; } .taskcell1 { background-color:#ebf2ff; white-space:nowrap; padding:0px; } .taskcell2 { background-color:#d9dfeb; white-space:nowrap; padding:0px; } .resourcecell1 { background-color:#fff2eb; white-space:nowrap; padding:0px; } .resourcecell2 { background-color:#ebdfd9; white-space:nowrap; padding:0px; } /* The *2 versions have a 20 points less HSV value of the *1 versions*/ .busy1 { background-color:#ff3b3b; } .busy2 { background-color:#eb3636; } .loaded1 { background-color:#ff9b9b; } .loaded2 { background-color:#eb8f8f; } .free1 { background-color:#a5ffb4; } .free2 { background-color:#98eba6; } .offduty1 { background-color:#bdbdaa; } .offduty2 { background-color:#a9a999; } .calconttask1 { background-color:#abbeae; } .calconttask2 { background-color:#99aa9c; } .caltask1 { background-color:#2050e5; } .caltask2 { background-color:#1c4ad1; } .todo1 { background-color:#beabab; } .todo2 { background-color:#aa9999; } .tabvline { background-color:#9a9a9a; position:absolute; } .tj_gantt_frame { position:absolute; /* Make sure this element is above all other elements */ z-index:100; } .containerbar { background-color:#09090a; position:absolute; } .taskbarframe { background-color:#09090a; position:absolute; } .taskbar { background-color:#2f57ea; position:absolute; } .progressbar { background-color:#36363f; position:absolute; } .milestone { background-color:#09090a; position:absolute; } .loadstackframe { background-color:#452a2a; position:absolute; } .free { background-color:#a5ffb5; position:absolute; } .busy { background-color:#ff9b9b; position:absolute; } .assigned { background-color:#ff3b3b; position:absolute; } .offduty { background-color:#bdbdaa; white-space:nowrap; position:absolute; } .depline { background-color:#000000; position:absolute; } .nowline { background-color:#EE0000; position:absolute; } .white { background-color:#FFFFFF; position:absolute; } .navbar_topruler { margin:7px 0px 0px 0px; } .navbar_midruler { margin:5px 0px 0px 0px; } .navbar_bottomruler { margin:5px 0px 7px 0px; } .navbar_current { background-color:#606060; font-size:13px; font-weight:bold; padding:5px; color:#FFFFFF; } .navbar_other { background-color:#FFFFFF; font-size:13px; font-weight:bold; padding:2px; } .navbar { font-size:20px; font-weight:bold; padding:0px; } .copyright { font-size:9px; color:#101010; text-align:center; margin-top:10px; } div[codesection] { border-width:2px; border-color:#ABABAB; border-style:solid; background-color:#F0F0F0; margin-top:8px; margin-bottom:8px; } pre[codesection] { padding-left:15px; padding-right:15px; padding-top:0px; padding-bottom:0px; } taskjuggler-3.5.0/manual/html/right.html0000644000175000017500000000707412614413013017600 0ustar bernatbernat right

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< richtext (extend) << Table Of Contents >> rollupaccount >>


Keyword right

Purpose
This attribute defines the right margin section of the textreport. The text will be interpreted as Rich Text. The margin will not span the header or footer sections.
Syntax right <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport

project "Test" 2011-12-11 +1m

task "Foo"

textreport frame "textreport" {
  taskreport r1 ""
  taskreport r2 ""

  formats html
  header -8<-
  This is the header
  ----
  ->8-
  left "<[report id='frame.r1']>"
  center "Center"
  right "<[report id='frame.r2']>"
  footer -8<-
  ----
  This is the footer
  ->8-
}


<< richtext (extend) << Table Of Contents >> rollupaccount >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/sortaccounts.html0000644000175000017500000000770712614413013021215 0ustar bernatbernat sortaccounts

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< sloppy (projection) << Table Of Contents >> sortjournalentries >>


Keyword sortaccounts

Purpose
Determines how the accounts are sorted in the report. Multiple criteria can be specified as a comma separated list. If one criteria is not sufficient to sort a group of accounts, the next criteria will be used to sort the accounts in this group.
Syntax sortaccounts (<tree> | <criteria>) [, <criteria>...]
Arguments tree [ID]
Use 'tree' as first criteria to keep the breakdown structure.
criteria [ABSOLUTE_ID]
The sorting criteria must consist of a property attribute ID. See columnid for a complete list of available attributes. The ID must be suffixed by '.up' or '.down' to determine the sorting direction. Optionally the ID may be prefixed with a scenario ID and a dot to determine the scenario that should be used for sorting. So, possible values are 'plan.start.up' or 'priority.down'.
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< sloppy (projection) << Table Of Contents >> sortjournalentries >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/email.html0000644000175000017500000000536412614413013017552 0ustar bernatbernat email

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< effort << Table Of Contents >> enabled >>


Keyword email

Purpose
The email address of the resource.
Syntax email <STRING>
Arguments none
Context resource, supplement (resource)



<< effort << Table Of Contents >> enabled >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/details.html0000644000175000017500000000635512614413013020111 0ustar bernatbernat details

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< depends << Table Of Contents >> disabled >>


Keyword details

Purpose
This is a continuation of the summary of the journal or status entry. It can be several paragraphs long.
Syntax details <text>
Arguments text [STRING]
The text will be interpreted as Rich Text. Only a subset of the markup is supported for this attribute. You can use word formatting, paragraphs, hyperlinks, lists, section and subsection headers.
Context journalentry, status (statussheet), status (timesheet)



<< depends << Table Of Contents >> disabled >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/work.html0000644000175000017500000001071412614413013017440 0ustar bernatbernat work

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< width (column) << Table Of Contents >> workinghours (project) >>


Keyword work

Purpose

The amount of time that the resource has spend with the task during the reported period. This value is ignored when there are bookings for the resource overlapping with the time sheet period. If there are no bookings, TaskJuggler will try to convert the work specification into bookings internally before the actual scheduling is started.

Every task listed in the time sheet needs to have a work attribute. The total accumulated work time that is reported must match exactly the total working hours for the resource for that period.

If a resource has no vacation during the week that is reported and it has a regular 40 hour work week, exactly 40 hours total or 5 working days have to be reported.

Syntax work <value> (% | min | h | d)
Arguments value
A floating point or integer number
%
percentage of reported period
min
minutes
h
hours
d
days
Context newtask, task (timesheet)

timesheet r1 2009-11-30 +1w {
  task t1 {
    work 3d
    remaining 0d
    status green "All work done" {
      summary "I had good fun!"
      details -8<-
        This task went smoothly and I got three things done:
        * Have fun
        * Be on time
        * Get things done
      ->8-
    }
  }
}


<< width (column) << Table Of Contents >> workinghours (project) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/flags.resource.html0000644000175000017500000000564612614413013021410 0ustar bernatbernat flags.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< flags (report) << Table Of Contents >> flags (statussheet) >>


Keyword flags (resource)

Purpose
Attach a set of flags. The flags can be used in logical expressions to filter properties from the reports.
Syntax flags <ID> [, <ID>...]
Arguments none
Context resource, supplement (resource)



<< flags (report) << Table Of Contents >> flags (statussheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/center.html0000644000175000017500000000703212614413013017735 0ustar bernatbernat center

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< celltext (column) << Table Of Contents >> charge >>


Keyword center

Purpose
This attribute defines the center section of the textreport. The text will be interpreted as Rich Text.
Syntax center <text>
Arguments text [STRING]
The text
Context accountreport, resourcereport, taskreport, textreport, tracereport

project "Test" 2011-12-11 +1m

task "Foo"

textreport frame "textreport" {
  taskreport r1 ""
  taskreport r2 ""

  formats html
  header -8<-
  This is the header
  ----
  ->8-
  left "<[report id='frame.r1']>"
  center "Center"
  right "<[report id='frame.r2']>"
  footer -8<-
  ----
  This is the footer
  ->8-
}


<< celltext (column) << Table Of Contents >> charge >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timeoff.nikureport.html0000644000175000017500000000612112614413013022305 0ustar bernatbernat timeoff.nikureport

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timeformat2 << Table Of Contents >> timesheet >>


Keyword timeoff (nikureport)

Purpose
Set the Clarity project ID and name that the vacation time will be reported to.
Syntax timeoff <ID> <Name>
Arguments ID [STRING]
The Clarity project ID
Name [STRING]
The Clarity project name
Context nikureport



<< timeformat2 << Table Of Contents >> timesheet >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/scenario.html0000644000175000017500000001224212614413013020257 0ustar bernatbernat scenario

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< scale (column) << Table Of Contents >> scenario (ical) >>


Keyword scenario

Purpose

Defines a new project scenario. By default, the project has only one scenario called plan. To do plan vs. actual comparisons or to do a what-if-analysis, you can define a set of scenarios. There can only be one top-level scenario. Additional scenarios are either derived from this top-level scenario or other scenarios.

Each nested scenario is a variation of the enclosing scenario. All scenarios share the same set of properties (task, resources, etc.) but the attributes that are listed as scenario specific may differ between the various scenarios. A nested scenario uses all attributes from the enclosing scenario unless the user has specified a different value for this attribute.

By default, the scheduler assigns resources to task beginning with the project start date. If the scenario is switched to projection mode, no assignments will be made prior to the current date or the date specified by now. In this case, TaskJuggler assumes, that all assignements prior to the current date have been provided by booking.task statements.

Syntax scenario <id> <name> [{ <attributes> }]
Arguments id [ID]
The ID of the scenario
name [STRING]
The name of the scenario
Context project, scenario

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
active x
scenario



<< scale (column) << Table Of Contents >> scenario (ical) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/interval3.html0000644000175000017500000001055112614413013020364 0ustar bernatbernat interval3

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< interval2 << Table Of Contents >> interval4 >>


Keyword interval3

Purpose

There are three ways to specify a date interval. The first is the most obvious. A date interval consists of a start and end DATE. Watch out for end dates without a time specification! Date specifications are 0 extended. An end date without a time is expanded to midnight that day. So the day of the end date is not included in the interval! The start and end dates must be separated by a hyphen character.

In the second form, the end date is omitted. A 24 hour interval is assumed.

The third form specifies the start date and an interval duration. The duration must be prefixed by a plus character.

Syntax <date> [(- <date> | + <duration> (min | h | d | w | m | y))]
Arguments date
See date for details.
duration
The duration of the interval. May not be 0 and must be a multiple of timingresolution.
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context Global scope



<< interval2 << Table Of Contents >> interval4 >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/monthlymax.html0000644000175000017500000000774512614413013020670 0ustar bernatbernat monthlymax

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< minstart << Table Of Contents >> monthlymin >>


Keyword monthlymax

Purpose
Set a maximum limit for each calendar month.
Syntax monthlymax <value> (min | h | d | w | m | y) [{ <attributes> }]
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context limits, limits (resource), limits (task)

Attributes end (limit), period (limit), resources (limit), start (limit)



<< minstart << Table Of Contents >> monthlymin >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/period.column.html0000644000175000017500000000572412614413013021241 0ustar bernatbernat period.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< overtime (booking) << Table Of Contents >> period (limit) >>


Keyword period (column)

Purpose
This property is a shortcut for setting the start and end property at the same time.
Syntax period <interval2>
Arguments interval2
See interval2 for details.
Context columns



<< overtime (booking) << Table Of Contents >> period (limit) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shifts.allocate.html0000644000175000017500000000724312614413013021544 0ustar bernatbernat shifts.allocate

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shift (timesheet) << Table Of Contents >> shifts (resource) >>


Keyword shifts (allocate)

Purpose
Limits the allocations of resources during the specified interval to the specified shift. Multiple shifts can be defined, but shift intervals may not overlap. Allocation shifts are an additional restriction to the task shifts and resource shifts or resource working hours. Allocations will only be made for time slots that are specified as duty time in all relevant shifts. The restriction to the shift is only active during the specified time interval. Outside of this interval, no restrictions apply.
Syntax shifts <shift> [<interval2>] [, <shift> [<interval2>]...]
Arguments shift [ID]
The ID of a defined shift
interval2
See interval2 for details.
Context allocate



<< shift (timesheet) << Table Of Contents >> shifts (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/supplement.task.html0000644000175000017500000003055612614413013021621 0ustar bernatbernat supplement.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< supplement (resource) << Table Of Contents >> tagfile >>


Keyword supplement (task)

Purpose

The supplement keyword provides a mechanism to add more attributes to already defined tasks. The additional attributes must obey the same rules as in regular task definitions and must be enclosed by curly braces.

This construct is primarily meant for situations where the information about a task is split over several files. E. g. the vacation dates for the resources may be in a separate file that was generated by some other tool.

Syntax supplement task <task ID> [{ <attributes> }]
Arguments task ID
The absolute ID of an already defined task.
Context task, supplement (task)

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
adopt (task)
allocate x x
booking (task) x
charge x
chargeset x x
complete x
depends x x
duration x
effort x
end x
fail x
flags (task) x x
journalentry
length x
limits (task) x
maxend x x
maxstart x
milestone x
minend x
minstart x x
note (task)
period (task) x
precedes x x
priority x x x
projectid (task) x x x
purge
responsible x x
scheduled x x
scheduling x
shifts (task) x x
start x
supplement (task)
task
warn x

task top "Top Task" {
  task sub "Sub Task"
  supplement task sub {
    length 1d
  }
}
supplement task top {
  flags important
  supplement task sub {
    allocate joe
  }
}


<< supplement (resource) << Table Of Contents >> tagfile >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/formats.html0000644000175000017500000000774212614413013020140 0ustar bernatbernat formats

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< footer << Table Of Contents >> formats (export) >>


Keyword formats

Purpose

This attribute defines for which output formats the report should be generated. By default, this list is empty. Unless a formats attribute was added to a report definition, no output will be generated for this report.

As reports are composable, a report may include other report definitions. A format definition is only needed for the outermost report that includes the others.

Syntax formats (csv | html | niku) [, (csv | html | niku)...]
Arguments csv
The report lists the resources and their respective values as colon-separated-value (CSV) format. Due to the very simple nature of the CSV format, only a small subset of features will be supported for CSV output. Including tasks or listing multiple scenarios will result in very difficult to read reports.
html
Generate a web page (HTML file)
niku
Generate a XOG XML file to be used with Clarity.
Context accountreport, nikureport, resourcereport, taskreport, textreport, tracereport



<< footer << Table Of Contents >> formats (export) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timeformat2.html0000644000175000017500000000572212614413013020712 0ustar bernatbernat timeformat2

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timeformat1 << Table Of Contents >> timeoff (nikureport) >>


Keyword timeformat2

Purpose
Specify an alternative format for the lower header line of calendar or Gantt chart columns.
Syntax timeformat2 <format>
Arguments format [STRING]
See timeformat for details.
Context columns



<< timeformat1 << Table Of Contents >> timeoff (nikureport) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/TaskJuggler_2x_Migration.html0000644000175000017500000002021412614413013023316 0ustar bernatbernat TaskJuggler_2x_Migration

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< Intro << Table Of Contents >> Reporting_Bugs >>


1.5 TaskJuggler 2.x Migration

This section will cover changes between TaskJuggler 2.x and 3.x.

  • The syntax for macros has changed slightly. The terminating ] must be the last character before the line break. No spaces or comments are allowed here. Parameters of macro calls must always be enclosed by double quotes. In contrast to regular strings, single quotes are not allowed here. The parameter may not span multiple lines.
  • The projection attribute has been removed. The is now provided by trackingscenario.
  • The default working hours have been changed to 9:00 - 17:00.
  • IDs for properties such as tasks, resources and reports are now optional. If you don't need to reference a property, you can omit the ID. TaskJuggler will automatically assign an ID then.
  • Top-level accounts no longer need a cost or revenue attribute. Any two top level accounts can now be balanced against each other using the balance attribute in the report.
  • The shift attribute for tasks and resources has been renamed to shifts to allow support for multiple shifts.
  • The global limits attribute has been removed. Since both tasks and resources have a limits attribute, a global attribute was inconsistent as only resources inherited this attribute. Use a parent resource to emulate the old behaviour.
  • Shifts and limits for allocations have been deprecated. The concept was bogus and not compatible with bookings. The functionality is now provided by shifts and limits on the task level. Limits for a task can be selectively applied to certain resources.
  • The startbuffer and endbuffer attributes have been deprecated. They have not been widely used and had no impact on scheduling.
  • The project attribute allowredifinitions has been dropped. It was an ugly workaround for a rare corner case. Using supplement is the clean way to do this.
  • Camel case names for function names in logical expressions have been deprecated. Function names need to be all lower case now. Some functions have been removed as all attributes can now be accessed by scenario.attribute_id notation.
  • The format for report has been changed considerably. The old format was not very flexible and had some design flaws. TaskJuggler 3.x now supports report nesting and composition. A report definition can be used to generated multiple output formats. The name of a report must now be specified without the file name extension. It will be automatically added depending on the output format.
  • The sorting modes have been extended to include the scenario. Also, the sorting direction is no longer mangled with the attribute name. What used to be startup is now plan.start.up. See sorttasks or sortresources for details.
  • The attribute properties for export reports is no longer supported. The naming was inconsistent with TaskJuggler lingo and did not handle tasks and resources separately. It has been replaced with taskattributes and resourceattributes.
  • The barlabels attribute for reports is no longer needed. HTML reports have always empty Gantt-chart bars and the calendar reports always have values.
  • Support for reading and writing XML files is no longer available. The content was redundant with the TJP file format and it was not widely used. Keeping it in sync was too much of an effort to be worth it. There is nothing in the TJ3 design that would prevent this feature from being added again, but there are no plans for this right now.

1.6 Using TaskJuggler 2.x and TaskJuggler 3.x in parallel

While TaskJuggler 3.x has many new features over TaskJuggler 2.x like the much improved HTML reports, many 2.x users will miss the graphical user interface.

To ease the migration, you can continue to use the TaskJuggler 2.x front-end while using TaskJuggler 3.x for report generation. This is possible because TaskJuggler 3.x can read-in the TaskJuggler 2.x export files. Export files are fully scheduled projects that include start and end dates for all tasks and bookings for resource allocations.

To export all tasks and resources into a TJP file that can be read by TaskJuggler 3.x include the following export report definition in your TaskJuggler 2.x project plan. The necessary patches to support this only made it into TaskJuggler 2.x after the 2.4.3 release. So be sure to use a recent version from the Git repository to try this.

export "FullProject.tjp" {
  taskattributes all
  resourceattributes all
  hideresource 0
}

The resulting FullProject.tjp file is a valid self-contained project file that can be read with TaskJuggler 2.x or TaskJuggler 3.x. The file does not contain any report definitions. To generate reports with TaskJuggler 3.x you need to create an additional file that contains the TaskJugler 3.x report definitions.

Let's assume the file is called tj3reports.tji. Start TaskJuggler 3.x with the following command:

tj3 FullProject.tjp tj3reports.tji

Now you have generated TaskJuggler 3.x reports from you TaskJuggler 2.x project.



<< Intro << Table Of Contents >> Reporting_Bugs >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/vacation.html0000644000175000017500000000640612614413013020265 0ustar bernatbernat vacation

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< treelevel << Table Of Contents >> vacation (resource) >>


Keyword vacation

Purpose
Specify a global vacation period for all subsequently defined resources. A vacation can also be used to block out the time before a resource joined or after it left. For employees changing their work schedule from full-time to part-time, or vice versa, please refer to the 'Shift' property.
Syntax vacation <name> <interval3> [, <interval3>...]
Arguments name
Name or purpose of the vacation
interval3
See interval3 for details.
Context properties



<< treelevel << Table Of Contents >> vacation (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/warn.html0000644000175000017500000001203612614413013017424 0ustar bernatbernat warn

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< vacation (shift) << Table Of Contents >> weeklymax >>


Keyword warn

Purpose
The warn attribute adds a logical expression to the property. The condition described by the logical expression is checked after the scheduling and an warning is generated if the condition evaluates to true. This attribute is primarily intended for testing purposes.
Syntax warn (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context resource, supplement (resource), task, supplement (task)



<< vacation (shift) << Table Of Contents >> weeklymax >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/complete.html0000644000175000017500000000714712614413013020274 0ustar bernatbernat complete

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< columns << Table Of Contents >> copyright >>


Keyword complete

Purpose

Specifies what percentage of the task is already completed. This can be useful for simple progress tracking like in a TODO list. The provided completion degree is used for the complete and gauge columns in reports. Reports with calendar elements may show the completed part of the task in a different color.

The completion percentage has no impact on the scheduler. It's meant for documentation purposes only.

Syntax complete <percent>
Arguments percent
The percent value. It must be between 0 and 100.
Context task, supplement (task)

task "Build house" {
  start 2007-01-06
  duration 30d
  # This task should have been completed on Jan 15, but only
  # 20% of the task have been completed so far.
  complete 20
}

taskreport "Complete" {
  formats html
  columns name, end { title "Due date" }, complete, gauge, chart
}


<< columns << Table Of Contents >> copyright >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/yearlyworkingdays.html0000644000175000017500000000641712614413013022252 0ustar bernatbernat yearlyworkingdays

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< workinghours (shift) << Table Of Contents


Keyword yearlyworkingdays

Purpose

Specifies the number of average working days per year. This should correlate to the specified workinghours and vacation. It affects the conversion of working hours, working days, working weeks, working months and working years into each other.

When public holidays and leaves are disregarded, this value should be equal to the number of working days per week times 52.1428 (the average number of weeks per year). E. g. for a culture with 5 working days it is 260.714 (the default), for 6 working days it is 312.8568 and for 7 working days it is 365.

Syntax yearlyworkingdays <days>
Arguments days
Number of average working days for a year
Context project



<< workinghours (shift) << Table Of Contents


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/index.html0000644000175000017500000000157412614413013017571 0ustar bernatbernat The TaskJuggler User Manual taskjuggler-3.5.0/manual/html/status.statussheet.html0000644000175000017500000000713512614413013022357 0ustar bernatbernat status.statussheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< startcredit << Table Of Contents >> status (timesheet) >>


Keyword status (statussheet)

Purpose
The status attribute can be used to describe the current status of the task or resource. The content of the status messages is added to the project journal.
Syntax status <alert level> <STRING> [{ <attributes> }]
Arguments alert level [ID]
By default supported values are green, yellow and red. The default value is green. You can define your own levels with alertlevels.
Context task (statussheet)

Attributes author, details, flags (statussheet), summary



<< startcredit << Table Of Contents >> status (timesheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/hidejournalentry.html0000644000175000017500000000623612614413013022050 0ustar bernatbernat hidejournalentry

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< hideaccount << Table Of Contents >> hidereport >>


Keyword hidejournalentry

Purpose
Do not include journal entries that match the specified logical expression.
Syntax hidejournalentry <logicalflagexpression>
Arguments logicalflagexpression
See logicalflagexpression for details.
Context accountreport, icalreport, resourcereport, taskreport, textreport, tracereport



<< hideaccount << Table Of Contents >> hidereport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/now.html0000644000175000017500000000564012614413013017263 0ustar bernatbernat now

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< note (task) << Table Of Contents >> number (extend) >>


Keyword now

Purpose
Specify the date that TaskJuggler uses for calculation as current date. If no value is specified, the current value of the system clock is used.
Syntax now <date>
Arguments date
Alternative date to be used as current date for all computations
Context project



<< note (task) << Table Of Contents >> number (extend) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/newtask.html0000644000175000017500000000763012614413013020135 0ustar bernatbernat newtask

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< navigator << Table Of Contents >> nikureport >>


Keyword newtask

Purpose
The keyword can be used to request a new task to the project. If the task ID requires further parent task that don't exist yet, these tasks will be requested as well. If the task exists already, an error will be generated. The newly requested task can be used immediately to report progress and status against it. These tasks will not automatically be added to the project plan. The project manager has to manually create them after reviewing the request during the time sheet reviews.
Syntax newtask <task> <STRING> { <attributes> }
Arguments task
ID of the new task
Context timesheet

Attributes end (timesheet), priority (timesheet), remaining, status (timesheet), work

timesheet r1 2009-11-30 +1w {
  newtask t4 "Another fun job" {
    work 2d
    remaining 4d
    status yellow "Had a cool idea" {
      summary "Will have a schedule impact though."
    }
  }
}


<< navigator << Table Of Contents >> nikureport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/monthlymin.html0000644000175000017500000001024712614413013020655 0ustar bernatbernat monthlymin

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< monthlymax << Table Of Contents >> navigator >>


Keyword monthlymin

Purpose
Minimum required effort for any calendar month. This value cannot be guaranteed by the scheduler. It is only checked after the schedule is complete. In case the minium required amount has not been reached, a warning will be generated.
Syntax monthlymin <value> (min | h | d | w | m | y) [{ <attributes> }]
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context limits, limits (resource), limits (task)

Attributes end (limit), period (limit), resources (limit), start (limit)



<< monthlymax << Table Of Contents >> navigator >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/date.extend.html0000644000175000017500000000670512614413013020666 0ustar bernatbernat date.extend

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< date << Table Of Contents >> definitions >>


Keyword date (extend)

Purpose
Extend the property with a new attribute of type date.
Syntax date <id> <name> [{ <attributes> }]
Arguments id [ID]
The ID of the new attribute. It can be used like the built-in IDs.
name [STRING]
The name of the new attribute. It is used as header in report columns and the like.
Context extend

Attributes inherit (extend), scenariospecific (extend)



<< date << Table Of Contents >> definitions >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/flags.journalentry.html0000644000175000017500000000555712614413013022316 0ustar bernatbernat flags.journalentry

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< flags (account) << Table Of Contents >> flags (report) >>


Keyword flags (journalentry)

Purpose
Journal entries can have flags attached to them. These can be used to include only entries in a report that have a certain flag.
Syntax flags <ID> [, <ID>...]
Arguments none
Context journalentry



<< flags (account) << Table Of Contents >> flags (report) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/weeklymin.html0000644000175000017500000001027312614413013020462 0ustar bernatbernat weeklymin

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< weeklymax << Table Of Contents >> weekstartsmonday >>


Keyword weeklymin

Purpose
Minimum required effort for any calendar week. This value cannot be guaranteed by the scheduler. It is only checked after the schedule is complete. In case the minium required amount has not been reached, a warning will be generated.
Syntax weeklymin <value> (min | h | d | w | m | y) [{ <attributes> }]
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context limits, limits (resource), limits (task)

Attributes end (limit), period (limit), resources (limit), start (limit)



<< weeklymax << Table Of Contents >> weekstartsmonday >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/limits.resource.html0000644000175000017500000000712512614413013021607 0ustar bernatbernat limits.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< limits (allocate) << Table Of Contents >> limits (task) >>


Keyword limits (resource)

Purpose
Set per-interval usage limits for the resource.
Syntax limits [{ <attributes> }]
Arguments none
Context resource, supplement (resource)

Attributes dailymax, dailymin, maximum, minimum, monthlymax, monthlymin, weeklymax, weeklymin

resource r1 "R1" {
  # Limit the usage of this resource to a maximum of 2 hours per day,
  # 6 hours per week and 2.5 days per month.
  limits { dailymax 2h weeklymax 6h monthlymax 2.5d }
}


<< limits (allocate) << Table Of Contents >> limits (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/replace.html0000644000175000017500000000576312614413013020101 0ustar bernatbernat replace

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< remaining << Table Of Contents >> reportprefix >>


Keyword replace

Purpose

This replace mode is only effective for shifts that are assigned to resources directly. When replace mode is activated the leave definitions of the shift will replace all the leave definitions of the resource for the given period.

The mode is not effective for shifts that are assigned to tasks or allocations.

Syntax replace
Arguments none
Context shift



<< remaining << Table Of Contents >> reportprefix >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/interval1.html0000644000175000017500000001057212614413013020365 0ustar bernatbernat interval1

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< inherit (extend) << Table Of Contents >> interval2 >>


Keyword interval1

Purpose

There are two ways to specify a date interval. The start and end date must lie within the specified project period.

The first is the most obvious. A date interval consists of a start and end DATE. Watch out for end dates without a time specification! Date specifications are 0 extended. An end date without a time is expanded to midnight that day. So the day of the end date is not included in the interval! The start and end dates must be separated by a hyphen character.

In the second form specifies the start date and an interval duration. The duration must be prefixed by a plus character.

Syntax <date> (- <date> | + <duration> (min | h | d | w | m | y))
Arguments date
See date for details.
duration
The duration of the interval. May not be 0 and must be a multiple of timingresolution.
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context Global scope



<< inherit (extend) << Table Of Contents >> interval2 >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/height.html0000644000175000017500000000646312614413013017734 0ustar bernatbernat height

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< headline << Table Of Contents >> hideaccount >>


Keyword height

Purpose
Set the height of the report in pixels. This attribute is only used for reports that cannot determine the height based on the content. Such report can be freely resized to fit in. The vast majority of reports can determine their height based on the provided content. These reports will simply ignore this setting.
Syntax height <INTEGER>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport
See also width



<< headline << Table Of Contents >> hideaccount >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/dailymax.html0000644000175000017500000001165712614413013020275 0ustar bernatbernat dailymax

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< currencyformat << Table Of Contents >> dailymin >>


Keyword dailymax

Purpose
Set a maximum limit for each calendar day.
Syntax dailymax <value> (min | h | d | w | m | y) [{ <attributes> }]
Arguments value
A floating point or integer number
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context limits, limits (resource), limits (task)

Attributes end (limit), period (limit), resources (limit), start (limit)

resource r1 "R1" {
  # Limit the usage of this resource to a maximum of 2 hours per day,
  # 6 hours per week and 2.5 days per month.
  limits { dailymax 2h weeklymax 6h monthlymax 2.5d }
}

resource r2 "R2"

task t1 "Task 1" {
  start 2007-03-30
  effort 10d
  allocate r2
  limits { dailymax 3h }
}
task t2 "Task 2" {
  start 2007-03-30
  effort 10d
  allocate r2
  limits { dailymin 5h }
}
task t5 "Task 5" {
  start ${projectstart}
  duration 60d
  # allocation is subject to resource limits
  allocate r1
}
task t6 "Task 6" {
  start ${projectstart}
  duration 60d
  allocate r2
  limits { dailymax 4h weeklymax 3d monthlymax 2w }
}
task t7 "Task 7" {
  start 2007-06-20
  duration 20d
  allocate r1, r2
  # limits can also be specified per resource
  limits {
    # Limit r1 to half days only
    dailymax 4h { resources r1 }
    # Limit r2 to 6 hours per day
    dailymax 6h { resources r2 }
  }
}


<< currencyformat << Table Of Contents >> dailymin >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/projectid.task.html0000644000175000017500000000606312614413013021404 0ustar bernatbernat projectid.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< projectid << Table Of Contents >> projectids >>


Keyword projectid (task)

Purpose
In larger projects it may be desireable to work with different project IDs for parts of the project. This attribute assignes a new project ID to this task an all subsequently defined sub tasks. The project ID needs to be declared first using projectid or projectids.
Syntax projectid <ID>
Arguments none
Context task, supplement (task)



<< projectid << Table Of Contents >> projectids >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/task.timesheet.html0000644000175000017500000000726312614413013021413 0ustar bernatbernat task.timesheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< task (statussheet) << Table Of Contents >> taskattributes >>


Keyword task (timesheet)

Purpose
Specifies an existing task that progress and status should be reported against.
Syntax task <task> { <attributes> }
Arguments task
ID of an already existing task
Context timesheet

Attributes end (timesheet), priority (timesheet), remaining, status (timesheet), work

timesheet r1 2009-11-30 +1w {
  task t1 {
    work 3d
    remaining 0d
    status green "All work done" {
      summary "I had good fun!"
      details -8<-
        This task went smoothly and I got three things done:
        * Have fun
        * Be on time
        * Get things done
      ->8-
    }
  }
}


<< task (statussheet) << Table Of Contents >> taskattributes >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/projection.html0000644000175000017500000000661312614413013020635 0ustar bernatbernat projection

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< projectids << Table Of Contents >> prolog >>


Keyword projection

This keyword should no longer be used. It will be removed in future versions of this software.

Use booking (task) instead.

Purpose

This keyword has been deprecated! Don't use it anymore!

Projection mode is now automatically enabled as soon as a scenario has bookings.

Syntax projection [{ <attributes> }]
Arguments none
Context scenario
See also booking (task)

Attributes



<< projectids << Table Of Contents >> prolog >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/isdutyof.html0000644000175000017500000000617212614413013020327 0ustar bernatbernat isdutyof

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isdependencyof << Table Of Contents >> isfeatureof >>


Keyword isdutyof

Purpose
Will evaluate to true for tasks that have the specified resource assigned to it in the specified scenario.
Syntax isdutyof ( <Resource ID> , <Scenario ID> )
Arguments Resource ID [ID]
The ID of a defined resource
Scenario ID [ID]
A scenario ID
Context functions



<< isdependencyof << Table Of Contents >> isfeatureof >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/vacation.resource.html0000644000175000017500000000656312614413013022117 0ustar bernatbernat vacation.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< vacation << Table Of Contents >> vacation (shift) >>


Keyword vacation (resource)

Purpose
Specify a vacation period for the resource. It can also be used to block out the time before a resource joined or after it left. For employees changing their work schedule from full-time to part-time, or vice versa, please refer to the 'Shift' property.
Syntax vacation [<name>] <interval3> [, <interval3>...]
Arguments name [STRING]
An optional name or reason for the leave
interval3
See interval3 for details.
Context resource, supplement (resource)



<< vacation << Table Of Contents >> vacation (shift) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/The_TaskJuggler_Syntax.html0000644000175000017500000002141612614413013023047 0ustar bernatbernat The_TaskJuggler_Syntax

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< Tutorial << Table Of Contents >> Rich_Text_Attributes >>


5 The TaskJuggler Syntax

5.1 Understanding the Syntax Reference

This manual provides a comprehensive reference of the TaskJuggler syntax. It is automatically generated from the same data that the parser uses to read your project files. This ensures that the software and the manual always match.

The syntax reference is organized based on the keywords of the TaskJuggler syntax. There is an entry for every keyword. In some cases, a keyword can appear in different contexts with potentially different meanings. In this case, the context is provided in brackets after the keyword for at least one of them. That way, every keyword is uniquely referenced.

The syntax for each keyword is described in the syntax section of the table that is provided for each keyword. The syntax always starts with the keyword. Keywords are then followed by arguments. In some cases, an argument can be automatically generated. In such cases, the argument is enclosed in square brackets.

keyword [<id>]

Arguments can be variable or picked from a predefined list of options. Variable arguments are listed with their names enclosed in angle brackets.

keyword <name>

List of predefined options are enclosed in brackets, the options separated by a vertical bar.

keyword ( foo | bar | foobar )

Some keywords take one or more arguments. These are known as list attributes. The arguments are comma separated. The tree dots in the syntax description mean that the sequence before the dots can be repeated if needed. Inheritable list attributes will append the new list values to the inherited list. Use can use the purge attribute to clear the list before assigning new values to the list attribute.

keyword arg1 [, arg2 ... ]

Variable arguments are further described in the Arguments section of the keyword syntax table. The name is listed immediately followed by the type of the variable argument. The supported types and their meaning is described in the following sections.

5.1.1 ABSOLUTE_ID

An absolute identifier is composed of identifiers that are concatenated by dots, e. g. foo.bar. It is used to reference a TaskJuggler property that lives in a hierarchical name space. Accounts, Tasks, Reports are examples for such hierarchical name spaces. To reference the sub-task bar of task foo the absolute ID foo.bar is used.

5.1.2 ID

An identifier is composed of the letters a to z, A to Z, the underscore and the digits 0 to 9. There are no limits for the number of characters, but it may not begin with a digit.

5.1.3 INTEGER

An integer is any natural number, e. g. 0, 1, 2 and so on.

5.1.4 STRING

Strings are character sequences that are enclosed by special character marks. There are three different marks supported. For short strings that fit on one lines, you can either use single or double quotes.

'This is a single quoted string.'
"This is a double quoted string."

Single quoted strings may contain double quotes and vice versa. Alternatively, you can prefix the quote mark with a backslash to use it within a string.

'It\'s a string with a quote included.'

If you want to use a backslash right before the included quote you need to escape the backslash with another backslash. Backslashes that aren't followed by a quote mark don't need to be escaped.

'A backslash \\\' followed by a quote.'

For multi-line strings or strings with many included quotes cut mark strings are recommended. A cut-mark-string starts with -8<- (scissor on a dotted line) and ends with ->8-(scissors looking the other way). The start mark must be immediately followed by a line break. The indentation of the first line after the opening scissors must be repeated for all following lines of the string and is not included in the resulting strings. The terminating cut mark must only be preceded by white spaces in that line. When considering the indentation, tabs are not identical with some number of spaces. Each indented line must be prefixed by the exact same combination of tabs and spaces.

-8<-
  This is a
  multi-line
  string.
  ->8-

5.2 Predefined Macros

TaskJuggler supports a few predefined macros. These are available after the project header. They values correspond to the values provided in the project header.

  • projectstart The start date of the project.
  • projectend The end date of the project.
  • now The current date. If the user does not provide a date with the now keyword, the moment of the processing of the file will be inserted. Keep in mind that this will change every time you process your project. The current setting of timeformat has no impact on the expansion of the macro.
  • today Identical to now but formatted according to the timeformat setting of the current context.

5.3 Environment Variable Expansions

By using the $(VAR) syntax, you can insert the value of the environment variable name VAR. The name of the variable must consists only of uppercase ASCII letters, underscore or decimal digits.



<< Tutorial << Table Of Contents >> Rich_Text_Attributes >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/gapduration.html0000644000175000017500000000743012614413013020774 0ustar bernatbernat gapduration

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< functions << Table Of Contents >> gaplength >>


Keyword gapduration

Purpose
Specifies the minimum required gap between the end of a preceding task and the start of this task, or the start of a following task and the end of this task. This is calendar time, not working time. 7d means one week.
Syntax gapduration <duration> (min | h | d | w | m | y)
Arguments duration
The duration of the interval. May not be 0 and must be a multiple of timingresolution.
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context depends, precedes



<< functions << Table Of Contents >> gaplength >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/navigator.html0000644000175000017500000000701512614413013020450 0ustar bernatbernat navigator

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< monthlymin << Table Of Contents >> newtask >>


Keyword navigator

Purpose
Defines a navigator object with the specified ID. This object can be used in reports to include a navigation bar with references to other reports.
Syntax navigator <ID> [{ <attributes> }]
Arguments none
Context properties

Attributes hidereport

project "Navigator Example" 2011-12-12 +1m

task foo "Foo" {
  task "Foo 1"
  task "Foo 2"
}
task bar "Bar" {
  task "Bar 1"
  task "Bar 2"
}


navigator navbar

textreport frame "" {
  header -8<-
    == My Reports ==
    <[navigator id="navbar"]>
  ->8-
  footer "----"

  taskreport "Foo Reports" {
    formats html
    taskroot foo
  }
  taskreport "Bar Reports" {
    formats html
    taskroot bar
  }
}


<< monthlymin << Table Of Contents >> newtask >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/booking.resource.html0000644000175000017500000001432112614413013021732 0ustar bernatbernat booking.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< balance << Table Of Contents >> booking (task) >>


Keyword booking (resource)

Purpose

The booking attribute can be used to report actually completed work. A task with bookings must be scheduled in asap mode. If the scenario is not the tracking scenario or derived from it, the scheduler will not allocate resources prior to the current date or the date specified with now when a task has at least one booking.

Bookings are only valid in the scenario they have been defined in. They will in general not be passed to any other scenario. If you have defined a tracking scenario, the bookings of this scenario will be passed to all the derived scenarios of the tracking scenario.

The sloppy attribute can be used when you want to skip non-working time or other allocations automatically. If it's not given, all bookings must only cover working time for the resource.

The booking attributes is designed to capture the exact amount of completed work. This attribute is not really intended to specify completed effort by hand. Usually, booking statements are generated by export reports. The sloppy and overtime attributes are only kludge for users who want to write them manually. Bookings can be used to report already completed work by specifying the exact time intervals a certain resource has worked on this task.

Bookings can be defined in the task or resource context. If you move tasks around very often, put your bookings in the task context.

Syntax booking <id> <interval4> [, <interval4>...] [{ <attributes> }]
Arguments id
Absolute ID of a defined task
interval4
See interval4 for details.
Context resource, supplement (resource)
See also booking (task), scheduling

Attributes overtime (booking), sloppy (booking)

project project "Simple Project" "1.0" 2007-01-05 +1m {
  timezone "America/Denver"
  # The baseline date for the projection.
  now 2007-01-15
}

resource tux "Tux"

task test "Testing" {
  start 2007-01-05
  effort 10d
  allocate tux
}

supplement resource tux {
  # Book a whole day (8 hours). The 1 hour lunch break is skipped.
  booking test 2007-01-06-9:00 +9h { sloppy 1 }
  # Book 2 days in the afternoon, 4 hours each.
  booking test 2007-01-08-13:00 +4h,
               2007-01-09-13:00 +4h
  # This is a common mistake. With standard working hours, this will
  # yield a zero time booking! The interval is midnight to 8am. So
  # it's outside of the working hours and 'sloopy 2' surpresses the
  # warning.
  booking test 2007-01-11 +8h { sloppy 2 }
  # Use 'overtime' to book off-hour slots. This booking will book the
  # full 10 hours, ignoring the lunch break and adding an extra hour
  # in the morning.
  booking test 2007-01-11-8:00 +10h { overtime 1 }
}


<< balance << Table Of Contents >> booking (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/caption.html0000644000175000017500000000743312614413013020117 0ustar bernatbernat caption

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< booking (task) << Table Of Contents >> cellcolor (column) >>


Keyword caption

Purpose
The caption will be embedded in the footer of the table or data segment. The text will be interpreted as Rich Text.
Syntax caption <text>
Arguments text [STRING]
The caption text.
Context accountreport, resourcereport, taskreport, textreport, tracereport

resource r1 "Resource 1"

task plant "How to plant a tree" {
 start 2007-01-01
 # All sub-tasks inherit this allocation of r1
 allocate r1
 task plan "Choose the planting site" {
   effort 2d
 }
 task buy "Get a tree" {
   effort 1d
   depends !plan
 }
 task action "Plant the tree" {
   effort 0.5d
   depends !buy
 }
}

taskreport planttree "PlantTree.html" {
  formats html
  caption "This project shows how to plant a tree easily"
  headline "How to plant a tree"
  columns name, start, end, daily
  # Don't hide any resource, thus show them all.
  hideresource 0
}


<< booking (task) << Table Of Contents >> cellcolor (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/period.task.html0000644000175000017500000000600212614413013020674 0ustar bernatbernat period.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< period (report) << Table Of Contents >> persistent >>


Keyword period (task)

Purpose
This property is a shortcut for setting the start and end property at the same time. In contrast to using these, it does not change the scheduling direction.
Syntax period <interval1>
Arguments interval1
See interval1 for details.
Context task, supplement (task)



<< period (report) << Table Of Contents >> persistent >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/rate.html0000644000175000017500000000543612614413013017416 0ustar bernatbernat rate

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< purge << Table Of Contents >> rate (resource) >>


Keyword rate

Purpose
Set the default rate for all subsequently defined resources. The rate describes the daily cost of a resource.
Syntax rate (<INTEGER> | <FLOAT>)
Arguments none
Context properties



<< purge << Table Of Contents >> rate (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/flags.report.html0000644000175000017500000000605412614413013021066 0ustar bernatbernat flags.report

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< flags (journalentry) << Table Of Contents >> flags (resource) >>


Keyword flags (report)

Purpose
Attach a set of flags. The flags can be used in logical expressions to filter properties from the reports.
Syntax flags <ID> [, <ID>...]
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< flags (journalentry) << Table Of Contents >> flags (resource) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/timingresolution.html0000644000175000017500000000673212614413013022076 0ustar bernatbernat timingresolution

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< timezone (shift) << Table Of Contents >> title >>


Keyword timingresolution

Purpose

Sets the minimum timing resolution. The smaller the value, the longer the scheduling process lasts and the more memory the application needs. The default and maximum value is 1 hour. The smallest value is 5 min. This value is a pretty fundamental setting of TaskJuggler. It has a severe impact on memory usage and scheduling performance. You should set this value to the minimum required resolution. Make sure that all values that you specify are aligned with the resolution.

Changing the timing resolution will reset the working hours to the default times. It's recommended that this is the very first option in the project header section.

Do not use this option after you've set the time zone!

Syntax timingresolution <INTEGER> min
Arguments none
Context project



<< timezone (shift) << Table Of Contents >> title >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/outputdir.html0000644000175000017500000000600512614413013020513 0ustar bernatbernat outputdir

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< opennodes << Table Of Contents >> overtime (booking) >>


Keyword outputdir

Purpose
Specifies the directory into which the reports should be generated. This will not affect reports whos name start with a slash. This setting can be overwritten by the command line option.
Syntax outputdir <directory>
Arguments directory [STRING]
Path to an existing directory
Context project



<< opennodes << Table Of Contents >> overtime (booking) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/istask.html0000644000175000017500000000533412614413013017756 0ustar bernatbernat istask

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< isresponsibilityof << Table Of Contents >> isvalid >>


Keyword istask

Purpose
The result is true if the property is a task.
Syntax istask ( )
Arguments none
Context functions



<< isresponsibilityof << Table Of Contents >> isvalid >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/Day_To_Day_Juggling.html0000644000175000017500000013327612614413013022271 0ustar bernatbernat Day_To_Day_Juggling

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< Software << Table Of Contents >> TaskJuggler_Internals >>


7 Day To Day Juggling

7.1 Working with multiple scenarios

To analyze the impact that a small variation can have on a project, TaskJuggler supports an unlimited amount of scenarios. Each additional scenario is a slight derivation of the it's parent. The task tree structure needs to be the same for all scenarios, but most attributes can vary from one scenario to another. Several report types support comparative listing of multiple scenarios.

By default, TaskJuggler knows about one scenario, called plan. The name of this scenario can be changed just like you can add more scenarios in the project section of your project files.

project "Example" 2007-05-29 - 2007-07-01 {
  timezone "America/Denver"
  scenario plan "Planned Scenario" {
    scenario actual "Actual Scenario"
    scenario test "Test Scenario" {
      active no
    }
  }
}

This header section defines 3 different scenarios. plan is the top-level scenario. It has two derived scenarios, actual and test. These two scenarios are identical to the plan scenario except for those attributes that are changed for these scenarios. Normally, all scenarios are scheduled on each tj3 run. To temporarily disable the scheduling of a scenario, you can set the active attribute to no.

task t "Task" {
  start 2007-05-29
  actual:start 2007-06-03
  test:start 2007-06-07
}

If you prefix an attribute with the scenario ID followed immediately by a colon, you can specify a value for a particular attribute. Keep in mind that setting an attribute also sets the same value for all derived scenarios of this scenario as well! If you would specify actual:start first and then plan:start, the latter would overwrite the first value again since actual is a derived scenario of plan.

The syntax reference lists for each attribute whether it is scenario specific or not.

7.2 Important and fall-back Tasks

By default, the scheduler tries to guess the right priority of tasks. The higher the priority, the more likely it will get the requested resources. To override this mechanism, the priority attribute can be used.

task jobs "Project breakdown" {
  start ${projectstart}

  task work "The regular work" {
    effort 20d
    priority 500
    allocate tux
    limits { weeklymax 25h }
  }

  task support "Customer Support" {
    # This is a high priority task. Due to the high priority tux is
    # spending the required daily maximum on it.
    end ${projectend}
    priority 800
    allocate tux
    limits { dailymax 2h }
  }

  task conference "Attend Conference" {
    period 2011-04-25 +2d
    allocate tux
    priority 1000
  }

  task maintenance "Maintenance work" {
    # This is a fallback task. Whenever tux is not doing something
    # else he is allocated to this task.
    end ${projectend}
    priority 300
    allocate tux
    limits { weeklymax 2d }
  }
}

In the above example, the regular project work needs to be frequently interrupted by the Customer Support task. It's only 2 hours a day, but it's pretty important that this is done. Since the task has a higher priority than the regular work, the scheduler will try to ensure that a maximum of 2 hours per day is spent on support. There is no guarantee, that the task will always get the resource for 2 hours each day, but it's pretty likely in this setup.

There is only one task that is more important, the Attend Conference task. It has fixed dates and we want to make sure Tux can attend. So we use priority 1000, the highest possible priority. There should be only one such task. If not, you need to ensure that the top priority tasks don't compete for the same resource in the same time frame.

In contrast to the support and conference task, the Maintenance work task is a fall-back task. It has a lower priority than the regular work. Tux only gets assigned to it when there is no other work. We have limited the regular work to 25 hours per week. Since we spend up to 10 hours per week on support, there should be a remainder of 5 hours per week for the maintenance task. Again, no guarantees given.

If you want to ensure that a certain minimum or maximum effort is spent on a task, you can use the warn attribute. This will not affect the decisions of the scheduler, but at least it will trigger a warning if your criteria are not met.

7.3 Tracking the Project

Once the initial plan has been made and the project has started, TaskJuggler can be turned from a planning tool into a tracking tool. You don't have to change a lot to do this. After all, as the initial plan is almost always just a first guess, you need to continue planning your project as new details become evident. As the work progresses, you continuously review the state of the project and update the plan accordingly. A weekly review and update cycle seems to be pretty common for most projects. Usually the plan for the past week and the reality are mostly aligned. The future parts of the project often are more affected by necessary changes.

While it is generally accepted to invest some amount of time in project planning, it is very common that once the project has been started, project managers tend to avoid a proper tracking of the project. Our bet is that the vast majority of project plans are only made to get management or investor approval. After the approval phase, many project managers only work with their project plan again when the project is getting out of control and they are desperate for any help they can get.

Of course, there are projects that are done using strict project management techniques that require detailed status tracking. Both extremes probably have their fans and TaskJuggler offers good support for both extremes as well as various techniques in between.

7.4 Recording Progress

As mentioned previously, your initial project plan is only a first estimate of how the project will progress. During the course of the project you will have to make changes to the plan as new information needs to be taken into account and you probably want to track the progress of the project in a formalized form. TaskJuggler will support you during this phase of the project as well, but it needs your help. You have to provide the additional information in the project file. In return you get current status reports and an updated project plan based on the current status of the project.

7.4.1 Using completion values

The most simple form of capturing the current status of the project is to use the complete attribute.

task impl "Implementation" {
  depends !spec
  effort 4w
  allocate dev1, dev2
  complete 50
}

This tells TaskJuggler that 50% of the task's effort has been completed by the current date. Tasks that have no completion specification will be assumed to be on track and TaskJuggler calculates the expected completion degree based on the current date. Completion specifications only need to be supplied for tasks that are either ahead of schedule or behind schedule. Please be aware that the completion degree does not affect the scheduling and resource allocation. It is only for reporting purposes. It also does not tell TaskJuggler which resource actually worked on the tasks, nor does it update the total or remaining effort.

7.4.2 Using bookings

When TaskJuggler schedules your plan, it can tell you who should work when on what. Now, that's the plan. But reality might be different. To tell TaskJuggler what really happened, you can use booking statements. When the past is exactly described by providing booking statements, you can enable projection mode.

Entering all the bookings for each resource and task may sound like a daunting task at first. If you do it manually, it certainly is. Fortunately, TaskJuggler can generate them for you by using either the --freeze option of tj3 or by generating a manual export report. Before we discuss this in more detail, we need to make sure that the plan is up-to-date.

7.5 Tracking status and actuals

Creating a good project plan is one thing. Executing it is a whole new story. Usually, the first plan is never fully correct and the only way to make sure that you are making progress according to plan is to regularly get status updates from all the project contributors.

These status updates should be provided by all project contributors on a regular basis, usually once a week. The gathered information should tell project managers who really worked how much on what tasks and how much work the contributors believe is really left now. There are two categories of tasks in a project that need to be treated slightly differently.

A task can either be effort based or duration based. In the former case, the contributors must tell how much effort is left. For duration based task, this doesn't make much sense. For these task, the expected end date should be reported.

In addition to those numbers, managers in the reporting chain usually want to have a textual status that describes what happened and what kind of issues were encountered. Usually, these textual status reports are combined with alert levels like green, yellow and red. Green means everything is progressing according to plan, yellow means there is some schedule risk and red means the project is in serious trouble. Usually first line managers like to get all the details while people further up in the reporting chain only like to see summaries with varying level of details.

All of this creates additional overhead but is usually inevitable to ensure that you complete the project within the given time and budget. As a comprehensive project management solution, TaskJuggler provides full support for all those tracking and reporting steps. It comes with a powerful email and web based communication system that simplifies the tracking process for individual contributors as well as managers.

As a side note we would like to mention that the recording of the work time of employees is regulated by labor law in certain countries. You might also require approval from a Worker's Council before you can deploy any time recording tools. Please consult with your corporate counsel or legal expert for all geographic regions of your teams before you deploy a time tracking solution.

We also would like to point out that introducing status reporting and time sheets is usually a big change for every staff. Don't underestimate the psychological impact and the training requirements. We also recommend to test the described process with a small group of employees first to get familiar with the process and to adapt it to your needs. Don't rush a deployment! You usually only have one chance to roll-out such a new process.

7.5.1 The reporting and tracking cycle

In this description, we assume that you are using a weekly reporting cycle. TaskJuggler does support arbitrary cycles, but we highly recommend the described weekly cycle.

  1. Time sheets: Every project contributor needs to fill out a time sheet once a week. To simplify this task as much as possible, a template will be send out by email. The template already lists all tasks that were planned for this week to work on with the respective effort values and end dates. It also provides sections for textual status reports. The contributor needs to review and complete the time sheet and has to send it back via email. TaskJuggler validates the submission and returns an email with either an error message or a nicely formatted version of the time sheet.
  2. All time sheets must be submitted by a certain deadline, e. g. midnight on Sunday. TaskJuggler will then compile a summary report and sent it out to a list of interested parties. It will also detect missing time sheets and will send out a reminder to those contributors that have not submitted their report.
  3. On Monday the project managers need to review the time sheets and update the plan accordingly. TaskJuggler can compile a list changes compared to the plan. This makes it easy to update the plan according to the actual progress that was made. The closer the actuals match the plan the less work this is. The project managers now generate bookings for the last week and add them to the database with previous bookings. Doing so, will prevent changes to the plan to affect the past. Only the future will be modified.
  4. Once the plan has been updated, managers will receive their status sheet templates per email. Each manager will get the information for the tasks that they are responsible for. To consolidate the information for the next manager in the reporting chain they can moderate the reports in three ways. Consolidated manager reports are called dashboard reports.
    1. A status report for a task can be removed from the dashboard.
    2. A status report for a task can be corrected or updated.
    3. All reports for sub tasks of a task can be summarized by creating a new status for that task. This will remove all reports for sub tasks of that particular tasks from the dashboard.
  5. Managers than need to send back the edited status report via email. Like with time sheets, TaskJuggler will check them and return either an error message or a plain text version of the dashboard report of the manager.

In addition to the plain text versions of the time sheet summaries and the dashboards, TaskJuggler provides support for publishing them as HTML pages from a web server.

7.6 Implementing the status tracking system

7.6.1 Prerequesites

The .tjp and .tji files of your project plan should be managed by a revision control system. TaskJuggler does not require a particular software, but for this manual we illustrate the implementation with Subversion. It should be obvious how to do this with other software though.

All communication of time sheets and status sheets is done via email. TaskJuggler has built-in support for sending emails. To receive emails and to feed them to the correct program, TaskJuggler needs support from a mail transfer agent (MTA) and a mail processor. In this documentation we describe the setup with postfix as MTA and procmail as mail processor. These are standard parts of any Linux distribution and should be easy to setup. It's certainly possible to use other MTAs and mail processors, but this is not the scope of this manual.

Finally, you need a web server to publish your reports. This can really be any web server. The generated reports are static HTML pages that can simply be put into a directory that the web server is serving.

For the email based communication you need to provide email addresses for all project contributors. This is done in the project plan in the resource definition by using the email attribute.

resource joe "Joe Avergage" {
  email "joe@your_company.com
}

In this manual, we assume you have a dedicated Linux machine with a local user called taskjuggler. Your project files (*.tjp and *.tji) is under Subversion control and the taskjuggler user has a checked-out version in /home/taskjuggler/projects/prj. You can use another user name, another source code management system and even another operating system like Windows or MacOS. This is all possible, but not the scope of this manual.

To use the tracking system, you need to setup the taskjuggler server to serve your project.

7.6.2 The Time Sheet Template Sender

Each project contributor needs to fill out a time sheet each week. To simplify the process each contributor will receive a template that already contains a lot of the information they need to provide.

To send out the time sheets, the command tj3ts_sender must be used. It will call tj3client with appropriate parameters. To use it, you need to have a properly configured daemon running and the appropriate project loaded. Then you need to add the configuration data for tj3ts_sender to your TaskJuggler configuration file. The time sheet related settings have their own top-level section:

_global:
  emailDeliveryMethod: smtp
  smtpServer: smtp.your_company.com
  authKey: topsecret
  scmCommand: "svn add %f ; svn commit -m '%m' %f"
  projectId: prj
_timesheets:
  senderEmail: 'TaskJuggler <timesheets@taskjuggler.your_company.com>'
  _sender:
    hideResource: '~isleaf()'
  _summary:
    sheetRecipients:
      - team@your_company.com
    digestRecipients:
      - managers@your_company.com

The emailDeliveryMethod defines how emails should be sent. Use smtp to directly send the emails to an SMTP server. The smtpServer defines which host will handle your emails. Replace the host name with your local SMTP server. Alternatively, you can use the method sendmail on UNIX-like systems to pass the email to the sendmail tool. In this case, the smtpServer line can be omitted. The 'scmCommand' setting contains the command to add and commit new and old files to the source code management system. The command in this example works for Subversion.

The TaskJuggler server may serve multiple projects. With the projectId option you have to specify which project you would like to work with. senderEmail is the email address the time sheet infrastructure will use. Outgoing emails will have this address as sender so that replies will come back to this email address. We'll cover later how these are processed.

The hideResource option works similarly to the hideresource attribute in the report definitions of the project plan. It allows you to restrict the sending of time sheet templates to a subset of your defined resources. In this example, we only want to send templates to individual resources and not the teams you might have defined.

By default the time sheets will cover the week from Monday morning 0:00 to Sunday night 24:00. When called without the -e option, tj3ts_sender will send out templates for the current week.

To call the tj3ts_sender command you either need to be in the /home/taskjuggler/projects/prj directory or use the -c command line option to point it to the configuration file to use. In the latter case you also need to call it with the -d option to change the output directory to your project directory.

To test the command without sending out actual emails you can use the --dryrun option on the command line. To do its job, tj3ts_sender needs to generate a number of files and directories. A copy of the generated templates will be stored in TimeSheetTemplates/<date>/ under <resource_id>-date.tji. <date> is replaced with the end date of the reporting interval and <resoruce_id> is the ID of the resource.

If you re-run the command existing templates will not be regenerated nor will they be sent out again. You can use the -f command line option to force them to be generated and sent out again.

The tj3ts_sender command will also add the reporting interval to a file called TimeSheetTemplates/acceptable_invervals. We'll cover this file later on when we deal with the time sheet receiver.

7.6.3 The Time Sheet Receiver

To receive the filled-out time sheets and to process them automatically you need to create a special user. TaskJuggler requires a number of email addresses to be setup to receive emails. We recommend to use the following setup. Create a special user called taskjuggler on a dedicated Linux machine. Then create the following email aliases for this user.

timesheets
timesheet-request
statussheets
statussheet-request

Your MTA must be configured to use procmail for email delivery. See the manual of your MTA for details on how to configure aliases and for using procmail for delivery. If you have a resident MTA expert you should ask him or her for support.

The next step is to configure procmail to forward the incoming emails to the appropriate TaskJuggler components. Create a file called .procmailrc in the home directory of the taskjuggler user and put in the following content:

For debugging and testing purposes, all incoming emails are archived in a directory called Mail. If there is no such directory in the taskjuggler home directory, you need to create it now.

PATH=$HOME/bin:/usr/bin:/bin:/usr/local/bin            
MAILDIR=$HOME/Mail/                                    
DEFAULT=$HOME/Mail/all                                 
LOGFILE=$MAILDIR/procmail.log                          
SHELL=/bin/sh                                          
PROJECTDIR=/home/taskjuggler/projects/prj       
LANG=en_US.UTF-8                                       
LC_ALL=en_US.UTF-8                                     
# Archive all incoming emails in a file called all 
:0 c
all 
:0
* ^Subject:.*Out of Office.*
/dev/null                   
:0
* ^To:.*timesheets@taskjuggler\.your_company\.com
{                                       
  :0 c:                                 
  timesheets                            
  :0 w: tj3ts_receiver.lock
  | tj3ts_receiver --silent -c $PROJECTDIR/.taskjugglerrc -d $PROJECTDIR
  :0
  failed_sheets
}
:0
* ^To:.*timesheet-request@taskjuggler\.your_company\.com
{
  ID=`formail -xSubject:`
  :0 c:
  timesheet-request
  :0 w: tj3ts_sender.lock
  | tj3ts_sender -r $ID -f --silent -c $PROJECTDIR/.taskjugglerrc -d $PROJECTDIR
}
:0
* ^To:.*statussheets@taskjuggler\.your_company\.com
{
  :0 c:
  statussheets
  :0 w: tj3ss_receiver.lock
  | tj3ss_receiver --silent -c $PROJECTDIR/.taskjugglerrc -d $PROJECTDIR
  :0
  failed_sheets
}
:0
* ^To:.*statussheet-request@taskjuggler\.your_company\.com
{
  ID=`formail -xSubject:`
  :0 c:
  statussheet-request
  :0 w: tj3ss_sender.lock
  | tj3ss_sender -r $ID -f --silent -c $PROJECTDIR/.taskjugglerrc -d $PROJECTDIR
}
# Forward a copy to project admins
:0 c
! taskjuggler-admin@your_company.com
# Since we have archived a copy we can discard all mails here.
:0
/dev/null

This procmail configuration will cause incoming emails that are addressed to timesheets@taskjuggler.your_company.com to be forwarded to the tj3ts_receiver program. Of course you need to replace your_comany.com with whatever domain you are using.

The received emails are then checked for syntactical and logical errors. If such are found, an email is sent back with an appropriate error message. The time sheet contains the resource ID of the reporting resource. As soon as this has been detected, all email communication will be sent to the email address in the project plan. Only when the resource ID could not be identified, the sender of the email will get the answer. This was implemented as a security measure so other users cannot easily retrieve project related information from other users.

Correct time sheets are archived in the TimeSheets/<date>/ directory where <date> is the end date of the reporting period. If the directory does not exist yet, it will be created. The file will be called <resource_id>-<date>.tji. If a SCM command was specified, the file will be automatically put under revision control. Subsequent submission of the same time sheet will simply overwrite the earlier submissions. The file name will also be added to a file called all.tji which consists of include statements of all time sheet files in the directory. There also is an automatically maintained file all.tji in the TimeSheets directory that includes all the <date>/all.tji files. To add all the submitted time sheets to your project plan, simply include the top-level all.tji.

tj3ts_receiver will only accept time sheets for the time periods listed in TimeSheetTemplates/acceptable_intervals. tj3ts_sender will automatically enable the current period when it sends out the templates. If you want to stop receiving time sheet updates for a certain period, simply remove the period from the acceptable_intervals file.

7.6.4 Time Sheet Template Requests

Normally, the time sheets are sent out once a week automatically. In case a project contributor leaves earlier for vacation or has lost the template, they can request the template for the current week again.

By sending an email to timesheet-request@taskjuggler.your_company.com and putting their resource ID in the subject of the email, they will receive an email with the time sheet template. The email will be sent to the email address in the project plan, not the sender of the request email.

7.6.5 Time Sheet Summaries

All time sheets should be successfully submitted by Sunday 24:00. After this deadline, your can send out a summary of all submitted time sheets. This summary will also contain a list of those project contributors that have not submitted their time sheet. These individuals will also get a reminder to submit their time sheets immediately.

To send out the summary report, the program tj3ts_summary is used. Before you can use it, you need to add a few settings to the TaskJuggler configuration file.

_global:
  emailDeliveryMethod: smtp
  smtpServer: smtp.your_company.com
  authKey: topsecret
  projectId: prj
_timesheets:
  senderEmail: 'TaskJuggler <timesheets@taskjuggler.your_company.com>'
  _summary:
    sheetRecipients:
      - team@your_company.com
    digestRecipients:
      - managers@your_company.com

sheetRecipients is a list of email addresses that should receive a copy of the submitted time sheet. Each email address must be put on a separate, properly indented line that starts with a dash followed by a space. The emails will have the email of the original time sheet author as sender address.

7.6.6 Updating the Project Plan

The time sheets contain two kind of information that are intended for two sets of audiences. Project managers will be interested primarily in the scheduling related information but surely like to look at the task status as well.

Managers responsible for certain parts of the project will be primarily interested in the status reports for the ongoing tasks. We'll cover the processing of the status information in the next sections.

This section deals with the processing of the scheduling related information. Project contributors can specify several deviations of the current project plan.

  • Task may need more effort or time than was originally planned for.
  • They may have not worked the planned amounts on the tasks.
  • They may have started to work on new tasks that are not even in the project plan. The is usually a sign of project discipline and should be avoided. But in reality this will happen and TaskJuggler is able to handle it.

TaskJuggler can print a summary of all the deltas between the plan and the actual reports in the time sheets.

tj3 --warn-ts-deltas YourProject.tjp TimeSheets/all.tji

In this example call YourProject.tjp is your main project file and all submitted time sheets are included by TimeSheets/all.tji. This file and all subsequent include files are automatically generated and updated by tj3ts_receiver.

Project managers should use the printed output of this command to update the project plan accordingly. The specified deltas of existing tasks must be updated in the main project plan. For new tasks in the time sheets, the task has to be created in the project plan. Then the newtask statement in the time sheet needs to be converted into a normal task report.

 newtask some.task.id "My new task" {
    ...
 }

Needs to be converted into

 task some.task.id {
   ...
 }

The task ID in the status sheet must match the newly created task in the project plan.

To check that all deltas were properly processed, re-run the check command.

tj3 --warn-ts-deltas YourProject.tjp TimeSheets/all.tji

You may also want to remove the interval from the TimeSheetTemplates/acceptable_intervals file to prevent further submissions of time sheets for this time period.

7.7 Recording actual Resource Usage

To ensure that future changes won't change the past of the project, we need to freeze the history of the project. History in this context means which resource worked on what task from when to when. Since TaskJuggler cannot know what level of detail you want to include in the reports, this information has to be recorded with the highest possible accuracy. This means that we have to capture the exact start and end dates for every period that a resource worked on a task.

Unless you use some external time tracking system to capture this information and export it to TaskJuggler, you probably want TaskJuggler to generate this data for you based on the plan information.

Before you can freeze that past part of your project, you need to tell TaskJuggler which scenario should be used for tracking the actual progress. See the trackingscenario documentation for more details on this. Before you freeze your project for the first time, you should make sure that the current date is still before the project start. If that is not the case, use the now attribute to set the current date to the project start:

now ${projectstart}

Once you have frozen the project for the first time, you should remove the now attribute again. It will be automatically updated.

To freeze your project up to a certain date, you can use the following command:

 tj3 --freeze yourproject.tjp --freezedate YYYY-MM-DD

This will generate two files, yourproject-header.tji and yourproject-bookings.tji. The header files contains the date of the freeze as a now attribute. You must include this file at the end of your project header section.

The bookings file contains the resource assignment data. It usually contains many booking entries that look similar to this:

 supplement task t {
   booking r 2010-02-19-09:00-+0000 + 3.0h,
           2010-02-19-13:00-+0000 + 5.0h,
           2010-02-22-09:00-+0000 + 3.0h,
           2010-02-22-13:00-+0000 + 5.0h,
 }

The booking file must be included at the end of your main project file.

In case there are still some discrepancies between the booking data and the actual assignments of the resources, you can edit the booking file to correct the data.

The next time you run tj3 with your project, all assignments prior to the date in the project header file will be taken only from the bookings file. All assignments after this date will be determined by the scheduler according to your provided constraints.

When you run tj3 --freeze again, it will update the header and booking files. Since you have included your booking file, any modifications you have made, will be preserved. That is, the actual data will be preserved, not the formatting since the file will be completely re-generated again.

7.7.1 Status Sheets

For larger projects with many contributors the flood of time sheets can become hard to manage. Higher level managers are usually not interested in all the details as long as the project executes according to plan. To keep the managers on each level informed with the proper amount and content TaskJuggler provides the concept of status sheets.

To use status sheets, the reporting chains must be reflected in the task hierarchy of the project. The responsible attribute must be used to assign tasks to managers. Leaf tasks or whole sub trees must be assigned to the lowest level of management. The responsibility for one or more level of parent tasks must be assigned to the next level of managers and so on.

When all time sheets have been submitted, the reports for all tasks are sent to the responsible managers for these tasks. The information is generated by the tj3ss_sender program and is called a status report template. Each manager will get one template that includes the status reports for the tasks they are responsible for.

It's not the managers task to prepare the report for the next level of management. To do this, the manager has 3 options:

  • Forward the status report of a task directly to the next level. The original authorship can be keep or removed. The content can also be edited if needed.
  • Similar reports for a task or a whole task sub-tree can be combined into just one report. To achieve this a new task report must be created for the parent tasks of these lower-level tasks. This will replace all reports for sub tasks with this newly created report.
  • A task report can simply be removed from the status report.

The status sheet template is designed to perform all three actions in a simple manner. The original reports are commented out. To remove a report, it needs to be uncommented and the headline must be set to an empty string. To change a report, the text must be edited after the comment marks have been removed. To create a summary report for a group of tasks, a new report for the common parent task must be created.

7.7.2 The Status Sheet Template Sender

To send out the time sheets, the command tj3ts_sender must be used. It will use the tj3client program to do retrieve the necessary data from the TaskJuggler server.

Before the program can be used, a new section must be added to the TaskJuggelr configuration file.

_statussheets:
  projectId: prj
  _sender:
    senderEmail: 'TaskJuggler <statussheets@taskjuggler.amd.com>'
    hideResource: '~(isleaf()  & manager)'

If you are using status sheets for only one level of management you can hardcode that like in the example above. For multiple level of management you need to specify which group of managers should the report templates be generated for and pass that information on the command line. Use the --hideresource option to specify a logicalexpression to filter away the resources you don't want templates to be generated for. The easiest way to achieve this is by using unique flags for each management level. In the example above we assume you have assigned the flag manager to each first-level manager.

For the override mechanism to work, the manager reports must always have a newer date than the original report. So, the end date of the first-level manager status sheets must be after the time sheet interval. The second-level mangers must use a later date than the first-level managers and so on.

By default tj3ss_sender will use the next Wednesday as end date. If you need a different date, you must use the -e option to specify that date.

Let's say you have two levels of managers that use status sheets. The time sheets are due midnight on Sunday. The project managers can work in the deltas and new tasks on Monday. After that you generate the reports for the first level managers with and end date of Wednesday. This implies a submission deadline of midnight on Tuesday. The second level manager templates will be sent out right after this deadline with an end date of Thursday. That would be the deadline for the second-level managers. The final report can than be generated by TaskJuggler automatically right after that deadline.

7.7.3 Requesting Status Sheet Templates

Usually the status sheets templates should be send out automatically. But sometimes a manager needs them earlier or needs an updated version due to a late incoming downstream report.

The above provided procmail configuration supports the generation of status sheets templates on request by email.

By sending an email to statussheet-request@taskjuggler.your_company.com and putting their resource ID in the subject of the email, managers will receive an email with the status sheet template. The email will be sent to the email address in the project plan, not the sender of the request email.

The setup described here only works for first-level managers. By adding more email addresses, this can easily be extended for more levels of management. You just need to make sure that tj3ss_sender is called with the proper parameters to change the resource selection and end date.

7.7.4 The Status Sheet Receiver

Similarly to the time sheets a the completed status sheets must be send back by email. We already described how the necessary email aliases should be configured. For status sheets the address statussheets@taskjuggler.your_company.com can be used.

The incoming emails will then be forwarded to the tj3ss_receiver program that will process them. To use it, you first need to add the following settings to the statussheets section of your TaskJuggler configuration file:

_statussheets:
  projectId: prj
  _receiver:
    senderEmail: 'TaskJuggler <statussheets@taskjuggler.amd.com>'

This will set the sender email of outgoing emails. Every incoming status sheet will be checked and either an error message will be returned or a consolidated status report for all tasks that the resource is responsible for. This report can either be directly forwarded to the next level manager or interested groups, or an HTML report can be generated and shared. This is especially useful in case the next level management is not getting status sheet templates.

Usually status reports only contain task reports for the current reporting period. But if there were tasks with an elevated status, these will be carried forward until they were removed by providing an empty headline or replaced with a new report for the same task or a parent task.



<< Software << Table Of Contents >> TaskJuggler_Internals >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/interval4.html0000644000175000017500000001071412614413013020366 0ustar bernatbernat interval4

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< interval3 << Table Of Contents >> isactive >>


Keyword interval4

Purpose

There are three ways to specify a date interval. The first is the most obvious. A date interval consists of a start and end DATE. Watch out for end dates without a time specification! Date specifications are 0 extended. An end date without a time is expanded to midnight that day. So the day of the end date is not included in the interval! The start and end dates must be separated by a hyphen character.

In the second form, the end date is omitted. A 24 hour interval is assumed.

The third form specifies the start date and an interval duration. The duration must be prefixed by a plus character.

The start and end date of the interval must be within the specified project time frame.

Syntax <date> [(- <date> | + <duration> (min | h | d | w | m | y))]
Arguments date
See date for details.
duration
The duration of the interval. May not be 0 and must be a multiple of timingresolution.
min
minutes
h
hours
d
days
w
weeks
m
months
y
years
Context Global scope



<< interval3 << Table Of Contents >> isactive >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/priority.timesheet.html0000644000175000017500000000620612614413013022326 0ustar bernatbernat priority.timesheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< priority << Table Of Contents >> project >>


Keyword priority (timesheet)

Purpose
The priority is a value between 1 and 1000. It is used to determine the sequence of task when converting work to bookings. Tasks that need to finish earlier in the period should have a high priority, tasks that end later in the period should have a low priority. For tasks that don't get finished in the reported period the priority should be set to 1.
Syntax priority <INTEGER>
Arguments none
Context newtask, task (timesheet)



<< priority << Table Of Contents >> project >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shift.resource.html0000644000175000017500000000705312614413013021423 0ustar bernatbernat shift.resource

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shift (allocate) << Table Of Contents >> shift (task) >>


Keyword shift (resource)

This keyword should no longer be used. It will be removed in future versions of this software.

Use shift (resource) instead.

Purpose
This keyword has been deprecated. Please use shifts (resource) instead.
Syntax shift <shift> [<interval2>] [, <shift> [<interval2>]...]
Arguments shift [ID]
The ID of a defined shift
interval2
See interval2 for details.
Context resource, supplement (resource)
See also



<< shift (allocate) << Table Of Contents >> shift (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/adopt.task.html0000644000175000017500000000735012614413013020530 0ustar bernatbernat adopt.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< active << Table Of Contents >> aggregate >>


Keyword adopt (task)

This keyword is currently in an experimental state. The implementation is probably still incomplete and use of this keyword may lead to wrong results. Do not use this keyword unless you were specifically directed by the developers to try it.

Purpose

Add a previously defined task and its sub-tasks to this task. This can be used to create virtual projects that contain task (sub-)trees that are originally defined in another task context. Adopted tasks don't inherit anything from their step parents. However, the adopting task is scheduled to fit all adopted sub-tasks.

A top-level task and all its sub-tasks must never contain the same task more than once. All reports must use appropriate filters by setting taskroot, hidetask or rolluptask to ensure that no tasks are contained more than once in the report.

Syntax adopt (<ABSOLUTE_ID> | <ID>) [, (<ABSOLUTE_ID> | <ID>)...]
Arguments none
Context task, supplement (task)



<< active << Table Of Contents >> aggregate >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/account.html0000644000175000017500000001425412614413013020115 0ustar bernatbernat account

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


Table Of Contents >> account (task) >>


Keyword account

Purpose

Declares an account. Accounts can be used to calculate costs of tasks or the whole project. Account declaration may be nested, but only leaf accounts may be used to track turnover. When the cost of a task is split over multiple accounts they all must have the same top-level group account. Top-level accounts can be used for profit/loss calculations. The sub-account structure of a top-level account should be organized accordingly.

Accounts have a global name space. All IDs must be unique within the accounts of the project.

Syntax account [<id>] <name> [{ <attributes> }]
Arguments id [ID]
An optional ID. If you ever want to reference this property, you must specify your own unique ID. If no ID is specified one will be automatically generated. These IDs may become visible in reports, but may change at any time. You may never rely on automatically generated IDs.
name [STRING]
A name or short description of the account
Context account, properties

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
account
aggregate x x
credits x
flags (account) x x

account project_cost "Project Costs"
account payments "Customer Payments"{
  credits 2007-01-01 "Customer down payment" 500.0,
          2007-01-14 "1st rate" 2000.0
}

balance project_cost payments

resource tux "Tux" {
  rate 300
}
resource konqui "Konqui" {
  rate 200
}

task items "Room decoration" {
  start 2007-01-06
  # The default account for all tasks
  chargeset project_cost

  task plan "Plan work and buy material" {
    # Upfront material cost
    charge 500.0 onstart
    length 2d
  }
   task remove "Remove old inventory" {
    allocate tux
    allocate konqui
    effort 1d
    depends !plan
  }
  task implement "Arrange new decoration" {
    effort 5d
    allocate tux, konqui
    depends !remove
  }
  task acceptance "Presentation and customer acceptance" {
    duration 5d
    depends !implement
    chargeset payments
    # Customer pays at end of acceptance
    charge 2000.0 onend
  }
}


Table Of Contents >> account (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/opennodes.html0000644000175000017500000000602012614413013020443 0ustar bernatbernat opennodes

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< onstart << Table Of Contents >> outputdir >>


Keyword opennodes

Purpose
For internal use only!
Syntax opennodes ((<ID> | <ABSOLUTE_ID>) [: (<ID> | <ABSOLUTE_ID>)] [, (<ID> | <ABSOLUTE_ID>) [: (<ID> | <ABSOLUTE_ID>)]...] | -)
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< onstart << Table Of Contents >> outputdir >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shift.task.html0000644000175000017500000000710512614413013020534 0ustar bernatbernat shift.task

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shift (resource) << Table Of Contents >> shift (timesheet) >>


Keyword shift (task)

This keyword should no longer be used. It will be removed in future versions of this software.

Use shifts (task) instead.

Purpose
This keyword has been deprecated. Please use shifts (task) instead.
Syntax shift <shift> [<interval2>] [, <shift> [<interval2>]...]
Arguments shift [ID]
The ID of a defined shift
interval2
See interval2 for details.
Context task, supplement (task)
See also shifts (task)



<< shift (resource) << Table Of Contents >> shift (timesheet) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/fail.html0000644000175000017500000001174512614413013017376 0ustar bernatbernat fail

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< extend << Table Of Contents >> flags >>


Keyword fail

Purpose
The fail attribute adds a logical expression to the property. The condition described by the logical expression is checked after the scheduling and an error is raised if the condition evaluates to true. This attribute is primarily intended for testing purposes.
Syntax fail (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context resource, supplement (resource), task, supplement (task)



<< extend << Table Of Contents >> flags >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/Intro.html0000644000175000017500000002271512614413013017555 0ustar bernatbernat Intro

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


Table Of Contents >> TaskJuggler_2x_Migration >>


1 Introduction

1.1 About TaskJuggler

TaskJuggler is a modern and powerful project management tool. Its new approach to project planning and tracking is far superior to the commonly used Gantt chart editing tools. It has already been successfully used in many projects and scales to projects with hundreds of resources and thousands of tasks.

TaskJuggler is an Open Source tool for serious project managers. It covers the complete spectrum of project management tasks from the first idea to the completion of the project without enforcing certain work flows or methodologies. It assists you during project scoping, resource assignment, cost and revenue planning, risk and communication management, status tracking and reporting.

TaskJuggler provides an optimizing scheduler that computes your project time lines and resource assignments based on the project outline and the constrains that you have provided. The built-in resource balancer and constrains checker offload you from having to worry about irrelevant details and ring the alarm if the project gets out of hand. The flexible "as many details as necessary"-approach allows you to still plan your project as you go, making it also ideal for new management strategies such as Extreme Programming and Agile Project Management.

If you are about to build a skyscraper or just want to put together your colleague's shift plan for the next month, TaskJuggler is the right tool for you. If you just want to draw nice looking Gantt charts to impress your boss or your investors, TaskJuggler might not be right for you. It can certainly produce nice looking Gantt charts and other reports, but it takes some effort to master its power. For those that are willing to invest a few hours to get started with the software it will become a companion you don't want to miss anymore.

TaskJuggler is a commandline tool that you use from a shell. This means that to enter your project data you will use one of the most versatile and powerful tools there is: your favorite text editor. To get a first impression, you can look at this project file. The project description is fairly intuitive, but very powerful as well. The Tutorial will explain this file line by line. Please look at the resulting reports that visualize the project.

This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. You accept the terms of this license by distributing or using this software.

This manual is Copyright (c) 2006, 2007, 2008, 2009, 2010 Chris Schlaeger.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".

The HTML reports use icons from the KDE Icon Team. The icons are licensed under the GNU Lesser General Public License.

The HTML reports use Java Script code from Walter Zorn. The code is licensed under the GNU Lesser General Public License.

TaskJuggler does require other software components to operate. These components include the Ruby runtime system, operating system libraries and other components installed as Ruby gems. We have used great care to ensure that all dependencies are compatible with the TaskJuggler license and are being used as required by those licenses. But use cases may vary and you should check those licenses yourself to ensure that you use those components in accordance with their licenses.

1.3 Features and Highlights

1.3.1 Basic Properties

  • Manages tasks, resources and accounts of your project
  • Powerful to-do list management
  • Detailed reference manual
  • Simple installation
  • Runs on all Linux, Unix, Windows, MacOS and several other operating systems
  • Full integration with Vim text editor

1.3.2 Advanced Scheduling

  • Automatic resource leveling and tasks conflict resolution
  • Unlimited number of scenarios (baselines) of the same project for what-if analysis
  • Flexible working hours and leave management
  • Support for shift working
  • Multiple time zone support

1.3.3 Accounting

  • Tasks may have initial costs, finishing costs
  • Resources may have usage based costs
  • Task and/or resource base cost models
  • Support for profit/loss analysis

1.3.4 Reporting

  • Comprehensive and flexible reports so you can find the information you need when you need it
  • Powerful filtering functions to provide the right amount of detail to the right audience
  • Time and status sheet reporting infrastructure
  • Project tracking and status reporting with dashboard support

1.3.5 Scaling and Enterprise Features

  • Projects can be combined to larger projects
  • Support for central resource allocation database
  • Manages roles and complex reporting lines
  • Powerful project description language with macro support
  • Scales well on multi-core or multi-CPU systems
  • Support for project management teams and revision control systems
  • Data export to Microsoft Project and Computer Associates Clarity

1.3.6 Web Publishing and Groupware Functions

  • HTML reports for web publishing
  • CSV data export for exchange with popular office software
  • iCalendar export for data exchange with calendar and productivity applications
  • Built-in web server for dynamic and interactive reports
  • Server based time sheet system for status and actual work reporting

1.4 TaskJuggler on the Web

The official TaskJuggler web site can be found at http://www.taskjuggler.org.

Since the developers are mostly busy project managers themselves, we have created a forum for users to help each other.



Table Of Contents >> TaskJuggler_2x_Migration >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/disabled.html0000644000175000017500000000703012614413013020222 0ustar bernatbernat disabled

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< details << Table Of Contents >> duration >>


Keyword disabled

This keyword should no longer be used. It will be removed in future versions of this software.

Use active instead.

Purpose

This attribute is deprecated. Please use active instead.

Disable the scenario for scheduling. The default for the top-level scenario is to be enabled.

Syntax disabled
Arguments none
Context scenario
See also active

project "Example" 2007-05-29 - 2007-07-01 {
  timezone "America/Denver"
  scenario plan "Planned Scenario" {
    scenario actual "Actual Scenario"
    scenario test "Test Scenario" {
      active no
    }
  }
}

task t "Task" {
  start 2007-05-29
  actual:start 2007-06-03
  test:start 2007-06-07
}


<< details << Table Of Contents >> duration >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/hideaccount.html0000644000175000017500000001226612614413013020750 0ustar bernatbernat hideaccount

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< height << Table Of Contents >> hidejournalentry >>


Keyword hideaccount

Purpose
Do not include accounts that match the specified logical expression. If the report is sorted in tree mode (default) then enclosing accounts are listed even if the expression matches the account.
Syntax hideaccount (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context accountreport, resourcereport, taskreport, textreport, tracereport
See also sortaccounts



<< height << Table Of Contents >> hidejournalentry >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/width.column.html0000644000175000017500000000610312614413013021066 0ustar bernatbernat width.column

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< width << Table Of Contents >> work >>


Keyword width (column)

Purpose
Specifies the maximum width of the column in screen pixels. If the content of the column does not fit into this width, it will be cut off. In some cases a scrollbar is added or a tooltip window with the complete content is shown when the mouse is moved over the column. The latter is only supported in interactive output formats. The resulting column width may be smaller if the column has a fixed width (e. g. the chart column).
Syntax width (<INTEGER> | <FLOAT>)
Arguments none
Context columns



<< width << Table Of Contents >> work >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/Getting_Started.html0000644000175000017500000001243712614413013021551 0ustar bernatbernat Getting_Started

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< How_To_Contribute << Table Of Contents >> Tutorial >>


3 Getting Started

3.1 Basics

TaskJuggler uses one or more text files to describe a project. The main project should be placed in a file with the .tjp extension. This main project may include other files. Such included files must have file names with a .tji extension.

The graphical user interface from the 2.x version has not been ported to TaskJuggler 3.x yet. So all work with TaskJuggler needs to be done in your favorite text editor and in a command shell.

The commandline version of TaskJuggler works like a compiler. You provide the source files, it computes the contents and creates the output files. Let's say you have a project file called AcSo.tjp. It contains the tasks of your project and their dependencies. To schedule the project and create report files you have to ask TaskJuggler to process it.

tj3 AcSo.tjp

TaskJuggler will try to schedule all tasks with the specified conditions and generate the reports that were requested with the taskreport, resourcereport or other report properties in the input file. The report files will be generated in the current directory or relative to it.

If you specify file names in a project file, you need to use the / as directory separator. This way, projects are portable across all operating systems. Do not use the \ or : that are used on some operating systems.

3.2 Structure of a TJP File

Each TaskJuggler project consists of one or more text files. There is always a main project file that may include other files. The main file name should have a .tjp suffix, the included files must have a .tji suffix.

Every project must start with a project header. The project header must be in the main project file. All other elements may be put into include files. The project header must then be followed by any number of project properties such as accounts, resources, tasks and reports. Each project must have at least one task defined and should have at least one report. Properties don't have to be listed in a particular order, but may have interdependencies that require such an order. If you want to assign a resource to work on a task, this resource needs to be defined first. It is therefor recommended to define them in the following sequence.



<< How_To_Contribute << Table Of Contents >> Tutorial >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/active.html0000644000175000017500000000553512614413013017736 0ustar bernatbernat active

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< accountroot << Table Of Contents >> adopt (task) >>


Keyword active

Purpose
Enable the scenario to be scheduled or not. By default, all scenarios will be scheduled. If a scenario is marked as inactive, it not be scheduled and will be ignored in the reports.
Syntax active (yes | no)
Arguments none
Context scenario



<< accountroot << Table Of Contents >> adopt (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/shift.timesheet.html0000644000175000017500000000632212614413013021561 0ustar bernatbernat shift.timesheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< shift (task) << Table Of Contents >> shifts (allocate) >>


Keyword shift (timesheet)

Purpose

Specifies an alternative shift for the time sheet period. This shift will override any existing working hour definitions for the resource. It will not override already declared leaves though.

The primary use of this feature is to let the resources report different total work time for the report period.

Syntax shift <shift>
Arguments shift [ID]
The ID of a defined shift
Context timesheet



<< shift (task) << Table Of Contents >> shifts (allocate) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/period.report.html0000644000175000017500000000646112614413013021256 0ustar bernatbernat period.report

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< period (limit) << Table Of Contents >> period (task) >>


Keyword period (report)

Purpose
This property is a shortcut for setting the start and end property at the same time.
Syntax period <interval2>
Arguments interval2
See interval2 for details.
Context accountreport, export, icalreport, nikureport, resourcereport, statussheetreport, taskreport, textreport, timesheetreport, tracereport



<< period (limit) << Table Of Contents >> period (task) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/statussheet.html0000644000175000017500000001115412614413013021031 0ustar bernatbernat statussheet

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< status (timesheet) << Table Of Contents >> statussheetreport >>


Keyword statussheet

Purpose
A status sheet can be used to capture the status of various tasks outside of the regular task tree definition. It is intended for use by managers that don't directly work with the full project plan, but need to report the current status of each task or task-tree that they are responsible for.
Syntax statussheet <reporter> <interval4> [{ <attributes> }]
Arguments reporter
The ID of a defined resource. This identifies the status reporter. Unless the status entries provide a different author, the sheet author will be used as status entry author.
interval4
See interval4 for details.
Context properties

Attributes task (statussheet)

project "test" 2009-11-30 +2m {
  timezone "America/Denver"
  trackingscenario plan
  now ${projectstart}
}

resource r1 "R1"
resource r2 "R2"
resource r3 "R3"

task t1 "Task 1" {
  effort 5d
  allocate r1
}
task t2 "Task 2" {
  task t3 "Task 3" {
    effort 10d
    allocate r2
  }
}

statussheet r3 2009-12-04 {
  task t1 {
    status green "All work done" {
      author r1
      summary "I had good fun!"
      details -8<-
        This task went smoothly and I got three things done:
        * Have fun
        * Be on time
        * Get things done
      ->8-
    }
  }
  task t2 {
    task t3 {
      status red "I need more time" {
        author r2
        summary "This takes longer than expected"
        details -8<-
        To finish on time, I need help. Get this r1 guy to help me out
        here.
        * I want to have fun too!
        ->8-
      }
    }
  }
}


<< status (timesheet) << Table Of Contents >> statussheetreport >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/rolluptask.html0000644000175000017500000001202712614413013020655 0ustar bernatbernat rolluptask

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< rollupresource << Table Of Contents >> scale (column) >>


Keyword rolluptask

Purpose
Do not show sub-tasks of tasks that match the specified logical expression.
Syntax rolluptask (<operand> [(| | & | > | < | = | >= | <= | !=) <operand>...] | @ (all | none))
Arguments operand

An operand can consist of a date, a text string, a function, a property attribute or a numerical value. It can also be the name of a declared flag. Use the scenario_id.attribute notation to use an attribute of the currently evaluated property. The scenario ID always has to be specified, also for non-scenario specific attributes. This is necessary to distinguish them from flags. See columnid for a list of available attributes. The use of list attributes is not recommended. User defined attributes are available as well.

An operand can be a negated operand by prefixing a ~ charater or it can be another logical expression enclosed in braces.

|
The 'or' operator
&
The 'and' operator
>
The 'greater than' operator
<
The 'smaller than' operator
=
The 'equal' operator
>=
The 'greater-or-equal' operator
<=
The 'smaller-or-equal' operator
!=
The 'not-equal' operator
Context accountreport, export, icalreport, resourcereport, tagfile, taskreport, textreport, tracereport



<< rollupresource << Table Of Contents >> scale (column) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/rawhtmlhead.html0000644000175000017500000000577312614413013020767 0ustar bernatbernat rawhtmlhead

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< rate (resource) << Table Of Contents >> reference (extend) >>


Keyword rawhtmlhead

Purpose
Define a HTML fragment that will be inserted at the end of the HTML head section.
Syntax rawhtmlhead <STRING>
Arguments none
Context accountreport, resourcereport, taskreport, textreport, tracereport



<< rate (resource) << Table Of Contents >> reference (extend) >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/manual/html/properties.html0000644000175000017500000002522512614413013020655 0ustar bernatbernat properties

The TaskJuggler User Manual

Project Management beyond Gantt Chart Drawing


<< prolog << Table Of Contents >> purge >>


Keyword properties

Purpose
The project properties. Every project must consists of at least one task. The other properties are optional. To save the scheduled data at least one output generating property should be used.
Context Global scope

Attributes Name Scen. spec. Inh. fm. Global Inh. fm. Parent
account
accountreport
auxdir
balance
copyright
export
flags
icalreport
include (properties)
leaves x x x
limits
macro
navigator
nikureport
projectid
projectids
rate
resource
resourcereport
shift
statussheet
statussheetreport
supplement
tagfile
task
taskreport
textreport
timesheet
timesheetreport
tracereport
vacation



<< prolog << Table Of Contents >> purge >>


Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 by Chris Schlaeger <chris@linux.com>.TaskJuggler is a trademark of Chris Schlaeger.
taskjuggler-3.5.0/data/0000755000175000017500000000000012614413013014255 5ustar bernatbernattaskjuggler-3.5.0/data/scripts/0000755000175000017500000000000012614413013015744 5ustar bernatbernattaskjuggler-3.5.0/data/scripts/wz_tooltip.js0000644000175000017500000010734212614413013020523 0ustar bernatbernat/* This notice must be untouched at all times. Copyright (c) 2002-2008 Walter Zorn. All rights reserved. wz_tooltip.js v. 5.31 The latest version is available at http://www.walterzorn.com or http://www.devira.com or http://www.walterzorn.de Created 1.12.2002 by Walter Zorn (Web: http://www.walterzorn.com ) Last modified: 7.11.2008 Easy-to-use cross-browser tooltips. Just include the script at the beginning of the section, and invoke Tip('Tooltip text') to show and UnTip() to hide the tooltip, from the desired HTML eventhandlers. Example: My home page No container DIV required. By default, width and height of tooltips are automatically adapted to content. Is even capable of dynamically converting arbitrary HTML elements to tooltips by calling TagToTip('ID_of_HTML_element_to_be_converted') instead of Tip(), which means you can put important, search-engine-relevant stuff into tooltips. Appearance & behaviour of tooltips can be individually configured via commands passed to Tip() or TagToTip(). Tab Width: 4 LICENSE: LGPL This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For more details on the GNU Lesser General Public License, see http://www.gnu.org/copyleft/lesser.html */ var config = new Object(); //=================== GLOBAL TOOLTIP CONFIGURATION =========================// var tt_Debug = false // false or true - recommended: false once you release your page to the public var tt_Enabled = true // Allows to (temporarily) suppress tooltips, e.g. by providing the user with a button that sets this global variable to false var TagsToTip = true // false or true - if true, HTML elements to be converted to tooltips via TagToTip() are automatically hidden; // if false, you should hide those HTML elements yourself // For each of the following config variables there exists a command, which is // just the variablename in uppercase, to be passed to Tip() or TagToTip() to // configure tooltips individually. Individual commands override global // configuration. Order of commands is arbitrary. // Example: onmouseover="Tip('Tooltip text', LEFT, true, BGCOLOR, '#FF9900', FADEIN, 400)" config. Above = false // false or true - tooltip above mousepointer config. BgColor = '#C3C8DA' // Background colour (HTML colour value, in quotes) config. BgImg = '' // Path to background image, none if empty string '' config. BorderColor = '#999999' config. BorderStyle = 'solid' // Any permitted CSS value, but I recommend 'solid', 'dotted' or 'dashed' config. BorderWidth = 1 config. CenterMouse = true // false or true - center the tip horizontally below (or above) the mousepointer config. ClickClose = false // false or true - close tooltip if the user clicks somewhere config. ClickSticky = true // false or true - make tooltip sticky if user left-clicks on the hovered element while the tooltip is active config. CloseBtn = true // false or true - closebutton in titlebar config. CloseBtnColors = ['#505050', '#FFFFFF', '#A0A0A0', '#000000'] // [Background, text, hovered background, hovered text] - use empty strings '' to inherit title colours config. CloseBtnText = ' X ' // Close button text (may also be an image tag) config. CopyContent = true // When converting a HTML element to a tooltip, copy only the element's content, rather than converting the element by its own config. Delay = 750 // Time span in ms until tooltip shows up config. Duration = 0 // Time span in ms after which the tooltip disappears; 0 for infinite duration, < 0 for delay in ms _after_ the onmouseout until the tooltip disappears config. Exclusive = false // false or true - no other tooltip can appear until the current one has actively been closed config. FadeIn = 400 // Fade-in duration in ms, e.g. 400; 0 for no animation config. FadeOut = 400 config. FadeInterval = 30 // Duration of each fade step in ms (recommended: 30) - shorter is smoother but causes more CPU-load config. Fix = null // Fixated position, two modes. Mode 1: x- an y-coordinates in brackets, e.g. [210, 480]. Mode 2: Show tooltip at a position related to an HTML element: [ID of HTML element, x-offset, y-offset from HTML element], e.g. ['SomeID', 10, 30]. Value null (default) for no fixated positioning. config. FollowMouse = false // false or true - tooltip follows the mouse config. FontColor = '#000044' config. FontFace = 'Verdana,Geneva,sans-serif' config. FontSize = '8pt' // E.g. '9pt' or '12px' - unit is mandatory config. FontWeight = 'normal' // 'normal' or 'bold'; config. Height = 0 // Tooltip height; 0 for automatic adaption to tooltip content, < 0 (e.g. -100) for a maximum for automatic adaption config. JumpHorz = false // false or true - jump horizontally to other side of mouse if tooltip would extend past clientarea boundary config. JumpVert = true // false or true - jump vertically " config. Left = false // false or true - tooltip on the left of the mouse config. OffsetX = 14 // Horizontal offset of left-top corner from mousepointer config. OffsetY = 4 // Vertical offset config. Opacity = 100 // Integer between 0 and 100 - opacity of tooltip in percent config. Padding = 10 // Spacing between border and content config. Shadow = true // false or true config. ShadowColor = '#A0A0A0' config. ShadowWidth = 4 config. Sticky = false // false or true - fixate tip, ie. don't follow the mouse and don't hide on mouseout config. TextAlign = 'left' // 'left', 'right' or 'justify' config. Title = '' // Default title text applied to all tips (no default title: empty string '') config. TitleAlign = 'center' // 'left' or 'right' - text alignment inside the title bar config. TitleBgColor = '#7A7A7A' // If empty string '', BorderColor will be used config. TitleFontColor = '#FFFFFF' // Color of title text - if '', BgColor (of tooltip body) will be used config. TitleFontFace = '' // If '' use FontFace (boldified) config. TitleFontSize = '' // If '' use FontSize config. TitlePadding = 2 config. Width = -600 // Tooltip width; 0 for automatic adaption to tooltip content; < -1 (e.g. -240) for a maximum width for that automatic adaption; // -1: tooltip width confined to the width required for the titlebar //======= END OF TOOLTIP CONFIG, DO NOT CHANGE ANYTHING BELOW ==============// //===================== PUBLIC =============================================// function Tip() { tt_Tip(arguments, null); } function TagToTip() { var t2t = tt_GetElt(arguments[0]); if(t2t) tt_Tip(arguments, t2t); } function UnTip() { tt_OpReHref(); if(tt_aV[DURATION] < 0 && (tt_iState & 0x2)) tt_tDurt.Timer("tt_HideInit()", -tt_aV[DURATION], true); else if(!(tt_aV[STICKY] && (tt_iState & 0x2))) tt_HideInit(); } //================== PUBLIC PLUGIN API =====================================// // Extension eventhandlers currently supported: // OnLoadConfig, OnCreateContentString, OnSubDivsCreated, OnShow, OnMoveBefore, // OnMoveAfter, OnHideInit, OnHide, OnKill var tt_aElt = new Array(10), // Container DIV, outer title & body DIVs, inner title & body TDs, closebutton SPAN, shadow DIVs, and IFRAME to cover windowed elements in IE tt_aV = new Array(), // Caches and enumerates config data for currently active tooltip tt_sContent, // Inner tooltip text or HTML tt_t2t, tt_t2tDad, // Tag converted to tip, and its DOM parent element tt_musX, tt_musY, tt_over, tt_x, tt_y, tt_w, tt_h; // Position, width and height of currently displayed tooltip function tt_Extension() { tt_ExtCmdEnum(); tt_aExt[tt_aExt.length] = this; return this; } function tt_SetTipPos(x, y) { var css = tt_aElt[0].style; tt_x = x; tt_y = y; css.left = x + "px"; css.top = y + "px"; if(tt_ie56) { var ifrm = tt_aElt[tt_aElt.length - 1]; if(ifrm) { ifrm.style.left = css.left; ifrm.style.top = css.top; } } } function tt_HideInit() { if(tt_iState) { tt_ExtCallFncs(0, "HideInit"); tt_iState &= ~(0x4 | 0x8); if(tt_flagOpa && tt_aV[FADEOUT]) { tt_tFade.EndTimer(); if(tt_opa) { var n = Math.round(tt_aV[FADEOUT] / (tt_aV[FADEINTERVAL] * (tt_aV[OPACITY] / tt_opa))); tt_Fade(tt_opa, tt_opa, 0, n); return; } } tt_tHide.Timer("tt_Hide();", 1, false); } } function tt_Hide() { if(tt_db && tt_iState) { tt_OpReHref(); if(tt_iState & 0x2) { tt_aElt[0].style.visibility = "hidden"; tt_ExtCallFncs(0, "Hide"); } tt_tShow.EndTimer(); tt_tHide.EndTimer(); tt_tDurt.EndTimer(); tt_tFade.EndTimer(); if(!tt_op && !tt_ie) { tt_tWaitMov.EndTimer(); tt_bWait = false; } if(tt_aV[CLICKCLOSE] || tt_aV[CLICKSTICKY]) tt_RemEvtFnc(document, "mouseup", tt_OnLClick); tt_ExtCallFncs(0, "Kill"); // In case of a TagToTip tip, hide converted DOM node and // re-insert it into DOM if(tt_t2t && !tt_aV[COPYCONTENT]) tt_UnEl2Tip(); tt_iState = 0; tt_over = null; tt_ResetMainDiv(); if(tt_aElt[tt_aElt.length - 1]) tt_aElt[tt_aElt.length - 1].style.display = "none"; } } function tt_GetElt(id) { return(document.getElementById ? document.getElementById(id) : document.all ? document.all[id] : null); } function tt_GetDivW(el) { return(el ? (el.offsetWidth || el.style.pixelWidth || 0) : 0); } function tt_GetDivH(el) { return(el ? (el.offsetHeight || el.style.pixelHeight || 0) : 0); } function tt_GetScrollX() { return(window.pageXOffset || (tt_db ? (tt_db.scrollLeft || 0) : 0)); } function tt_GetScrollY() { return(window.pageYOffset || (tt_db ? (tt_db.scrollTop || 0) : 0)); } function tt_GetClientW() { return tt_GetWndCliSiz("Width"); } function tt_GetClientH() { return tt_GetWndCliSiz("Height"); } function tt_GetEvtX(e) { return (e ? ((typeof(e.pageX) != tt_u) ? e.pageX : (e.clientX + tt_GetScrollX())) : 0); } function tt_GetEvtY(e) { return (e ? ((typeof(e.pageY) != tt_u) ? e.pageY : (e.clientY + tt_GetScrollY())) : 0); } function tt_AddEvtFnc(el, sEvt, PFnc) { if(el) { if(el.addEventListener) el.addEventListener(sEvt, PFnc, false); else el.attachEvent("on" + sEvt, PFnc); } } function tt_RemEvtFnc(el, sEvt, PFnc) { if(el) { if(el.removeEventListener) el.removeEventListener(sEvt, PFnc, false); else el.detachEvent("on" + sEvt, PFnc); } } function tt_GetDad(el) { return(el.parentNode || el.parentElement || el.offsetParent); } function tt_MovDomNode(el, dadFrom, dadTo) { if(dadFrom) dadFrom.removeChild(el); if(dadTo) dadTo.appendChild(el); } //====================== PRIVATE ===========================================// var tt_aExt = new Array(), // Array of extension objects tt_db, tt_op, tt_ie, tt_ie56, tt_bBoxOld, // Browser flags tt_body, tt_ovr_, // HTML element the mouse is currently over tt_flagOpa, // Opacity support: 1=IE, 2=Khtml, 3=KHTML, 4=Moz, 5=W3C tt_maxPosX, tt_maxPosY, tt_iState = 0, // Tooltip active |= 1, shown |= 2, move with mouse |= 4, exclusive |= 8 tt_opa, // Currently applied opacity tt_bJmpVert, tt_bJmpHorz,// Tip temporarily on other side of mouse tt_elDeHref, // The tag from which we've removed the href attribute // Timer tt_tShow = new Number(0), tt_tHide = new Number(0), tt_tDurt = new Number(0), tt_tFade = new Number(0), tt_tWaitMov = new Number(0), tt_bWait = false, tt_u = "undefined"; function tt_Init() { tt_MkCmdEnum(); // Send old browsers instantly to hell if(!tt_Browser() || !tt_MkMainDiv()) return; tt_IsW3cBox(); tt_OpaSupport(); tt_AddEvtFnc(document, "mousemove", tt_Move); // In Debug mode we search for TagToTip() calls in order to notify // the user if they've forgotten to set the TagsToTip config flag if(TagsToTip || tt_Debug) tt_SetOnloadFnc(); // Ensure the tip be hidden when the page unloads tt_AddEvtFnc(window, "unload", tt_Hide); } // Creates command names by translating config variable names to upper case function tt_MkCmdEnum() { var n = 0; for(var i in config) eval("window." + i.toString().toUpperCase() + " = " + n++); tt_aV.length = n; } function tt_Browser() { var n, nv, n6, w3c; n = navigator.userAgent.toLowerCase(), nv = navigator.appVersion; tt_op = (document.defaultView && typeof(eval("w" + "indow" + "." + "o" + "p" + "er" + "a")) != tt_u); tt_ie = n.indexOf("msie") != -1 && document.all && !tt_op; if(tt_ie) { var ieOld = (!document.compatMode || document.compatMode == "BackCompat"); tt_db = !ieOld ? document.documentElement : (document.body || null); if(tt_db) tt_ie56 = parseFloat(nv.substring(nv.indexOf("MSIE") + 5)) >= 5.5 && typeof document.body.style.maxHeight == tt_u; } else { tt_db = document.documentElement || document.body || (document.getElementsByTagName ? document.getElementsByTagName("body")[0] : null); if(!tt_op) { n6 = document.defaultView && typeof document.defaultView.getComputedStyle != tt_u; w3c = !n6 && document.getElementById; } } tt_body = (document.getElementsByTagName ? document.getElementsByTagName("body")[0] : (document.body || null)); if(tt_ie || n6 || tt_op || w3c) { if(tt_body && tt_db) { if(document.attachEvent || document.addEventListener) return true; } else tt_Err("wz_tooltip.js must be included INSIDE the body section," + " immediately after the opening tag.", false); } tt_db = null; return false; } function tt_MkMainDiv() { // Create the tooltip DIV if(tt_body.insertAdjacentHTML) tt_body.insertAdjacentHTML("afterBegin", tt_MkMainDivHtm()); else if(typeof tt_body.innerHTML != tt_u && document.createElement && tt_body.appendChild) tt_body.appendChild(tt_MkMainDivDom()); if(window.tt_GetMainDivRefs /* FireFox Alzheimer */ && tt_GetMainDivRefs()) return true; tt_db = null; return false; } function tt_MkMainDivHtm() { return( '
' + (tt_ie56 ? ('') : '') ); } function tt_MkMainDivDom() { var el = document.createElement("div"); if(el) el.id = "WzTtDiV"; return el; } function tt_GetMainDivRefs() { tt_aElt[0] = tt_GetElt("WzTtDiV"); if(tt_ie56 && tt_aElt[0]) { tt_aElt[tt_aElt.length - 1] = tt_GetElt("WzTtIfRm"); if(!tt_aElt[tt_aElt.length - 1]) tt_aElt[0] = null; } if(tt_aElt[0]) { var css = tt_aElt[0].style; css.visibility = "hidden"; css.position = "absolute"; css.overflow = "hidden"; return true; } return false; } function tt_ResetMainDiv() { tt_SetTipPos(0, 0); tt_aElt[0].innerHTML = ""; tt_aElt[0].style.width = "0px"; tt_h = 0; } function tt_IsW3cBox() { var css = tt_aElt[0].style; css.padding = "10px"; css.width = "40px"; tt_bBoxOld = (tt_GetDivW(tt_aElt[0]) == 40); css.padding = "0px"; tt_ResetMainDiv(); } function tt_OpaSupport() { var css = tt_body.style; tt_flagOpa = (typeof(css.KhtmlOpacity) != tt_u) ? 2 : (typeof(css.KHTMLOpacity) != tt_u) ? 3 : (typeof(css.MozOpacity) != tt_u) ? 4 : (typeof(css.opacity) != tt_u) ? 5 : (typeof(css.filter) != tt_u) ? 1 : 0; } // Ported from http://dean.edwards.name/weblog/2006/06/again/ // (Dean Edwards et al.) function tt_SetOnloadFnc() { tt_AddEvtFnc(document, "DOMContentLoaded", tt_HideSrcTags); tt_AddEvtFnc(window, "load", tt_HideSrcTags); if(tt_body.attachEvent) tt_body.attachEvent("onreadystatechange", function() { if(tt_body.readyState == "complete") tt_HideSrcTags(); } ); if(/WebKit|KHTML/i.test(navigator.userAgent)) { var t = setInterval(function() { if(/loaded|complete/.test(document.readyState)) { clearInterval(t); tt_HideSrcTags(); } }, 10); } } function tt_HideSrcTags() { if(!window.tt_HideSrcTags || window.tt_HideSrcTags.done) return; window.tt_HideSrcTags.done = true; if(!tt_HideSrcTagsRecurs(tt_body)) tt_Err("There are HTML elements to be converted to tooltips.\nIf you" + " want these HTML elements to be automatically hidden, you" + " must edit wz_tooltip.js, and set TagsToTip in the global" + " tooltip configuration to true.", true); } function tt_HideSrcTagsRecurs(dad) { var ovr, asT2t; // Walk the DOM tree for tags that have an onmouseover or onclick attribute // containing a TagToTip('...') call. // (.childNodes first since .children is bugous in Safari) var a = dad.childNodes || dad.children || null; for(var i = a ? a.length : 0; i;) {--i; if(!tt_HideSrcTagsRecurs(a[i])) return false; ovr = a[i].getAttribute ? (a[i].getAttribute("onmouseover") || a[i].getAttribute("onclick")) : (typeof a[i].onmouseover == "function") ? (a[i].onmouseover || a[i].onclick) : null; if(ovr) { asT2t = ovr.toString().match(/TagToTip\s*\(\s*'[^'.]+'\s*[\),]/); if(asT2t && asT2t.length) { if(!tt_HideSrcTag(asT2t[0])) return false; } } } return true; } function tt_HideSrcTag(sT2t) { var id, el; // The ID passed to the found TagToTip() call identifies an HTML element // to be converted to a tooltip, so hide that element id = sT2t.replace(/.+'([^'.]+)'.+/, "$1"); el = tt_GetElt(id); if(el) { if(tt_Debug && !TagsToTip) return false; else el.style.display = "none"; } else tt_Err("Invalid ID\n'" + id + "'\npassed to TagToTip()." + " There exists no HTML element with that ID.", true); return true; } function tt_Tip(arg, t2t) { if(!tt_db || (tt_iState & 0x8)) return; if(tt_iState) tt_Hide(); if(!tt_Enabled) return; tt_t2t = t2t; if(!tt_ReadCmds(arg)) return; tt_iState = 0x1 | 0x4; tt_AdaptConfig1(); tt_MkTipContent(arg); tt_MkTipSubDivs(); tt_FormatTip(); tt_bJmpVert = false; tt_bJmpHorz = false; tt_maxPosX = tt_GetClientW() + tt_GetScrollX() - tt_w - 1; tt_maxPosY = tt_GetClientH() + tt_GetScrollY() - tt_h - 1; tt_AdaptConfig2(); // Ensure the tip be shown and positioned before the first onmousemove tt_OverInit(); tt_ShowInit(); tt_Move(); } function tt_ReadCmds(a) { var i; // First load the global config values, to initialize also values // for which no command is passed i = 0; for(var j in config) tt_aV[i++] = config[j]; // Then replace each cached config value for which a command is // passed (ensure the # of command args plus value args be even) if(a.length & 1) { for(i = a.length - 1; i > 0; i -= 2) tt_aV[a[i - 1]] = a[i]; return true; } tt_Err("Incorrect call of Tip() or TagToTip().\n" + "Each command must be followed by a value.", true); return false; } function tt_AdaptConfig1() { tt_ExtCallFncs(0, "LoadConfig"); // Inherit unspecified title formattings from body if(!tt_aV[TITLEBGCOLOR].length) tt_aV[TITLEBGCOLOR] = tt_aV[BORDERCOLOR]; if(!tt_aV[TITLEFONTCOLOR].length) tt_aV[TITLEFONTCOLOR] = tt_aV[BGCOLOR]; if(!tt_aV[TITLEFONTFACE].length) tt_aV[TITLEFONTFACE] = tt_aV[FONTFACE]; if(!tt_aV[TITLEFONTSIZE].length) tt_aV[TITLEFONTSIZE] = tt_aV[FONTSIZE]; if(tt_aV[CLOSEBTN]) { // Use title colours for non-specified closebutton colours if(!tt_aV[CLOSEBTNCOLORS]) tt_aV[CLOSEBTNCOLORS] = new Array("", "", "", ""); for(var i = 4; i;) {--i; if(!tt_aV[CLOSEBTNCOLORS][i].length) tt_aV[CLOSEBTNCOLORS][i] = (i & 1) ? tt_aV[TITLEFONTCOLOR] : tt_aV[TITLEBGCOLOR]; } // Enforce titlebar be shown if(!tt_aV[TITLE].length) tt_aV[TITLE] = " "; } // Circumvents broken display of images and fade-in flicker in Geckos < 1.8 if(tt_aV[OPACITY] == 100 && typeof tt_aElt[0].style.MozOpacity != tt_u && !Array.every) tt_aV[OPACITY] = 99; // Smartly shorten the delay for fade-in tooltips if(tt_aV[FADEIN] && tt_flagOpa && tt_aV[DELAY] > 100) tt_aV[DELAY] = Math.max(tt_aV[DELAY] - tt_aV[FADEIN], 100); } function tt_AdaptConfig2() { if(tt_aV[CENTERMOUSE]) { tt_aV[OFFSETX] -= ((tt_w - (tt_aV[SHADOW] ? tt_aV[SHADOWWIDTH] : 0)) >> 1); tt_aV[JUMPHORZ] = false; } } // Expose content globally so extensions can modify it function tt_MkTipContent(a) { if(tt_t2t) { if(tt_aV[COPYCONTENT]) tt_sContent = tt_t2t.innerHTML; else tt_sContent = ""; } else tt_sContent = a[0]; tt_ExtCallFncs(0, "CreateContentString"); } function tt_MkTipSubDivs() { var sCss = 'position:relative;margin:0px;padding:0px;border-width:0px;left:0px;top:0px;line-height:normal;width:auto;', sTbTrTd = ' cellspacing="0" cellpadding="0" border="0" style="' + sCss + '">' + '' + tt_aV[TITLE] + '' + (tt_aV[CLOSEBTN] ? ('') : '') + '
' + '' + tt_aV[CLOSEBTNTEXT] + '
') : '') + '
' + '' + tt_sContent + '
' + (tt_aV[SHADOW] ? ('
' + '
') : '') ); tt_GetSubDivRefs(); // Convert DOM node to tip if(tt_t2t && !tt_aV[COPYCONTENT]) tt_El2Tip(); tt_ExtCallFncs(0, "SubDivsCreated"); } function tt_GetSubDivRefs() { var aId = new Array("WzTiTl", "WzTiTlTb", "WzTiTlI", "WzClOsE", "WzBoDy", "WzBoDyI", "WzTtShDwB", "WzTtShDwR"); for(var i = aId.length; i; --i) tt_aElt[i] = tt_GetElt(aId[i - 1]); } function tt_FormatTip() { var css, w, h, pad = tt_aV[PADDING], padT, wBrd = tt_aV[BORDERWIDTH], iOffY, iOffSh, iAdd = (pad + wBrd) << 1; //--------- Title DIV ---------- if(tt_aV[TITLE].length) { padT = tt_aV[TITLEPADDING]; css = tt_aElt[1].style; css.background = tt_aV[TITLEBGCOLOR]; css.paddingTop = css.paddingBottom = padT + "px"; css.paddingLeft = css.paddingRight = (padT + 2) + "px"; css = tt_aElt[3].style; css.color = tt_aV[TITLEFONTCOLOR]; if(tt_aV[WIDTH] == -1) css.whiteSpace = "nowrap"; css.fontFamily = tt_aV[TITLEFONTFACE]; css.fontSize = tt_aV[TITLEFONTSIZE]; css.fontWeight = "bold"; css.textAlign = tt_aV[TITLEALIGN]; // Close button DIV if(tt_aElt[4]) { css = tt_aElt[4].style; css.background = tt_aV[CLOSEBTNCOLORS][0]; css.color = tt_aV[CLOSEBTNCOLORS][1]; css.fontFamily = tt_aV[TITLEFONTFACE]; css.fontSize = tt_aV[TITLEFONTSIZE]; css.fontWeight = "bold"; } if(tt_aV[WIDTH] > 0) tt_w = tt_aV[WIDTH]; else { tt_w = tt_GetDivW(tt_aElt[3]) + tt_GetDivW(tt_aElt[4]); // Some spacing between title DIV and closebutton if(tt_aElt[4]) tt_w += pad; // Restrict auto width to max width if(tt_aV[WIDTH] < -1 && tt_w > -tt_aV[WIDTH]) tt_w = -tt_aV[WIDTH]; } // Ensure the top border of the body DIV be covered by the title DIV iOffY = -wBrd; } else { tt_w = 0; iOffY = 0; } //-------- Body DIV ------------ css = tt_aElt[5].style; css.top = iOffY + "px"; if(wBrd) { css.borderColor = tt_aV[BORDERCOLOR]; css.borderStyle = tt_aV[BORDERSTYLE]; css.borderWidth = wBrd + "px"; } if(tt_aV[BGCOLOR].length) css.background = tt_aV[BGCOLOR]; if(tt_aV[BGIMG].length) css.backgroundImage = "url(" + tt_aV[BGIMG] + ")"; css.padding = pad + "px"; css.textAlign = tt_aV[TEXTALIGN]; if(tt_aV[HEIGHT]) { css.overflow = "auto"; if(tt_aV[HEIGHT] > 0) css.height = (tt_aV[HEIGHT] + iAdd) + "px"; else tt_h = iAdd - tt_aV[HEIGHT]; } // TD inside body DIV css = tt_aElt[6].style; css.color = tt_aV[FONTCOLOR]; css.fontFamily = tt_aV[FONTFACE]; css.fontSize = tt_aV[FONTSIZE]; css.fontWeight = tt_aV[FONTWEIGHT]; css.textAlign = tt_aV[TEXTALIGN]; if(tt_aV[WIDTH] > 0) w = tt_aV[WIDTH]; // Width like title (if existent) else if(tt_aV[WIDTH] == -1 && tt_w) w = tt_w; else { // Measure width of the body's inner TD, as some browsers would expand // the container and outer body DIV to 100% w = tt_GetDivW(tt_aElt[6]); // Restrict auto width to max width if(tt_aV[WIDTH] < -1 && w > -tt_aV[WIDTH]) w = -tt_aV[WIDTH]; } if(w > tt_w) tt_w = w; tt_w += iAdd; //--------- Shadow DIVs ------------ if(tt_aV[SHADOW]) { tt_w += tt_aV[SHADOWWIDTH]; iOffSh = Math.floor((tt_aV[SHADOWWIDTH] * 4) / 3); // Bottom shadow css = tt_aElt[7].style; css.top = iOffY + "px"; css.left = iOffSh + "px"; css.width = (tt_w - iOffSh - tt_aV[SHADOWWIDTH]) + "px"; css.height = tt_aV[SHADOWWIDTH] + "px"; css.background = tt_aV[SHADOWCOLOR]; // Right shadow css = tt_aElt[8].style; css.top = iOffSh + "px"; css.left = (tt_w - tt_aV[SHADOWWIDTH]) + "px"; css.width = tt_aV[SHADOWWIDTH] + "px"; css.background = tt_aV[SHADOWCOLOR]; } else iOffSh = 0; //-------- Container DIV ------- tt_SetTipOpa(tt_aV[FADEIN] ? 0 : tt_aV[OPACITY]); tt_FixSize(iOffY, iOffSh); } // Fixate the size so it can't dynamically change while the tooltip is moving. function tt_FixSize(iOffY, iOffSh) { var wIn, wOut, h, add, pad = tt_aV[PADDING], wBrd = tt_aV[BORDERWIDTH], i; tt_aElt[0].style.width = tt_w + "px"; tt_aElt[0].style.pixelWidth = tt_w; wOut = tt_w - ((tt_aV[SHADOW]) ? tt_aV[SHADOWWIDTH] : 0); // Body wIn = wOut; if(!tt_bBoxOld) wIn -= (pad + wBrd) << 1; tt_aElt[5].style.width = wIn + "px"; // Title if(tt_aElt[1]) { wIn = wOut - ((tt_aV[TITLEPADDING] + 2) << 1); if(!tt_bBoxOld) wOut = wIn; tt_aElt[1].style.width = wOut + "px"; tt_aElt[2].style.width = wIn + "px"; } // Max height specified if(tt_h) { h = tt_GetDivH(tt_aElt[5]); if(h > tt_h) { if(!tt_bBoxOld) tt_h -= (pad + wBrd) << 1; tt_aElt[5].style.height = tt_h + "px"; } } tt_h = tt_GetDivH(tt_aElt[0]) + iOffY; // Right shadow if(tt_aElt[8]) tt_aElt[8].style.height = (tt_h - iOffSh) + "px"; i = tt_aElt.length - 1; if(tt_aElt[i]) { tt_aElt[i].style.width = tt_w + "px"; tt_aElt[i].style.height = tt_h + "px"; } } function tt_DeAlt(el) { var aKid; if(el) { if(el.alt) el.alt = ""; if(el.title) el.title = ""; aKid = el.childNodes || el.children || null; if(aKid) { for(var i = aKid.length; i;) tt_DeAlt(aKid[--i]); } } } // This hack removes the native tooltips over links in Opera function tt_OpDeHref(el) { if(!tt_op) return; if(tt_elDeHref) tt_OpReHref(); while(el) { if(el.hasAttribute && el.hasAttribute("href")) { el.t_href = el.getAttribute("href"); el.t_stats = window.status; el.removeAttribute("href"); el.style.cursor = "hand"; tt_AddEvtFnc(el, "mousedown", tt_OpReHref); window.status = el.t_href; tt_elDeHref = el; break; } el = tt_GetDad(el); } } function tt_OpReHref() { if(tt_elDeHref) { tt_elDeHref.setAttribute("href", tt_elDeHref.t_href); tt_RemEvtFnc(tt_elDeHref, "mousedown", tt_OpReHref); window.status = tt_elDeHref.t_stats; tt_elDeHref = null; } } function tt_El2Tip() { var css = tt_t2t.style; // Store previous positioning tt_t2t.t_cp = css.position; tt_t2t.t_cl = css.left; tt_t2t.t_ct = css.top; tt_t2t.t_cd = css.display; // Store the tag's parent element so we can restore that DOM branch // when the tooltip is being hidden tt_t2tDad = tt_GetDad(tt_t2t); tt_MovDomNode(tt_t2t, tt_t2tDad, tt_aElt[6]); css.display = "block"; css.position = "static"; css.left = css.top = css.marginLeft = css.marginTop = "0px"; } function tt_UnEl2Tip() { // Restore positioning and display var css = tt_t2t.style; css.display = tt_t2t.t_cd; tt_MovDomNode(tt_t2t, tt_GetDad(tt_t2t), tt_t2tDad); css.position = tt_t2t.t_cp; css.left = tt_t2t.t_cl; css.top = tt_t2t.t_ct; tt_t2tDad = null; } function tt_OverInit() { if(window.event) tt_over = window.event.target || window.event.srcElement; else tt_over = tt_ovr_; tt_DeAlt(tt_over); tt_OpDeHref(tt_over); } function tt_ShowInit() { tt_tShow.Timer("tt_Show()", tt_aV[DELAY], true); if(tt_aV[CLICKCLOSE] || tt_aV[CLICKSTICKY]) tt_AddEvtFnc(document, "mouseup", tt_OnLClick); } function tt_Show() { var css = tt_aElt[0].style; // Override the z-index of the topmost wz_dragdrop.js D&D item css.zIndex = Math.max((window.dd && dd.z) ? (dd.z + 2) : 0, 1010); if(tt_aV[STICKY] || !tt_aV[FOLLOWMOUSE]) tt_iState &= ~0x4; if(tt_aV[EXCLUSIVE]) tt_iState |= 0x8; if(tt_aV[DURATION] > 0) tt_tDurt.Timer("tt_HideInit()", tt_aV[DURATION], true); tt_ExtCallFncs(0, "Show") css.visibility = "visible"; tt_iState |= 0x2; if(tt_aV[FADEIN]) tt_Fade(0, 0, tt_aV[OPACITY], Math.round(tt_aV[FADEIN] / tt_aV[FADEINTERVAL])); tt_ShowIfrm(); } function tt_ShowIfrm() { if(tt_ie56) { var ifrm = tt_aElt[tt_aElt.length - 1]; if(ifrm) { var css = ifrm.style; css.zIndex = tt_aElt[0].style.zIndex - 1; css.display = "block"; } } } function tt_Move(e) { if(e) tt_ovr_ = e.target || e.srcElement; e = e || window.event; if(e) { tt_musX = tt_GetEvtX(e); tt_musY = tt_GetEvtY(e); } if(tt_iState & 0x4) { // Prevent jam of mousemove events if(!tt_op && !tt_ie) { if(tt_bWait) return; tt_bWait = true; tt_tWaitMov.Timer("tt_bWait = false;", 1, true); } if(tt_aV[FIX]) { tt_iState &= ~0x4; tt_PosFix(); } else if(!tt_ExtCallFncs(e, "MoveBefore")) tt_SetTipPos(tt_Pos(0), tt_Pos(1)); tt_ExtCallFncs([tt_musX, tt_musY], "MoveAfter") } } function tt_Pos(iDim) { var iX, bJmpMod, cmdAlt, cmdOff, cx, iMax, iScrl, iMus, bJmp; // Map values according to dimension to calculate if(iDim) { bJmpMod = tt_aV[JUMPVERT]; cmdAlt = ABOVE; cmdOff = OFFSETY; cx = tt_h; iMax = tt_maxPosY; iScrl = tt_GetScrollY(); iMus = tt_musY; bJmp = tt_bJmpVert; } else { bJmpMod = tt_aV[JUMPHORZ]; cmdAlt = LEFT; cmdOff = OFFSETX; cx = tt_w; iMax = tt_maxPosX; iScrl = tt_GetScrollX(); iMus = tt_musX; bJmp = tt_bJmpHorz; } if(bJmpMod) { if(tt_aV[cmdAlt] && (!bJmp || tt_CalcPosAlt(iDim) >= iScrl + 16)) iX = tt_PosAlt(iDim); else if(!tt_aV[cmdAlt] && bJmp && tt_CalcPosDef(iDim) > iMax - 16) iX = tt_PosAlt(iDim); else iX = tt_PosDef(iDim); } else { iX = iMus; if(tt_aV[cmdAlt]) iX -= cx + tt_aV[cmdOff] - (tt_aV[SHADOW] ? tt_aV[SHADOWWIDTH] : 0); else iX += tt_aV[cmdOff]; } // Prevent tip from extending past clientarea boundary if(iX > iMax) iX = bJmpMod ? tt_PosAlt(iDim) : iMax; // In case of insufficient space on both sides, ensure the left/upper part // of the tip be visible if(iX < iScrl) iX = bJmpMod ? tt_PosDef(iDim) : iScrl; return iX; } function tt_PosDef(iDim) { if(iDim) tt_bJmpVert = tt_aV[ABOVE]; else tt_bJmpHorz = tt_aV[LEFT]; return tt_CalcPosDef(iDim); } function tt_PosAlt(iDim) { if(iDim) tt_bJmpVert = !tt_aV[ABOVE]; else tt_bJmpHorz = !tt_aV[LEFT]; return tt_CalcPosAlt(iDim); } function tt_CalcPosDef(iDim) { return iDim ? (tt_musY + tt_aV[OFFSETY]) : (tt_musX + tt_aV[OFFSETX]); } function tt_CalcPosAlt(iDim) { var cmdOff = iDim ? OFFSETY : OFFSETX; var dx = tt_aV[cmdOff] - (tt_aV[SHADOW] ? tt_aV[SHADOWWIDTH] : 0); if(tt_aV[cmdOff] > 0 && dx <= 0) dx = 1; return((iDim ? (tt_musY - tt_h) : (tt_musX - tt_w)) - dx); } function tt_PosFix() { var iX, iY; if(typeof(tt_aV[FIX][0]) == "number") { iX = tt_aV[FIX][0]; iY = tt_aV[FIX][1]; } else { if(typeof(tt_aV[FIX][0]) == "string") el = tt_GetElt(tt_aV[FIX][0]); // First slot in array is direct reference to HTML element else el = tt_aV[FIX][0]; iX = tt_aV[FIX][1]; iY = tt_aV[FIX][2]; // By default, vert pos is related to bottom edge of HTML element if(!tt_aV[ABOVE] && el) iY += tt_GetDivH(el); for(; el; el = el.offsetParent) { iX += el.offsetLeft || 0; iY += el.offsetTop || 0; } } // For a fixed tip positioned above the mouse, use the bottom edge as anchor // (recommended by Christophe Rebeschini, 31.1.2008) if(tt_aV[ABOVE]) iY -= tt_h; tt_SetTipPos(iX, iY); } function tt_Fade(a, now, z, n) { if(n) { now += Math.round((z - now) / n); if((z > a) ? (now >= z) : (now <= z)) now = z; else tt_tFade.Timer( "tt_Fade(" + a + "," + now + "," + z + "," + (n - 1) + ")", tt_aV[FADEINTERVAL], true ); } now ? tt_SetTipOpa(now) : tt_Hide(); } function tt_SetTipOpa(opa) { // To circumvent the opacity nesting flaws of IE, we set the opacity // for each sub-DIV separately, rather than for the container DIV. tt_SetOpa(tt_aElt[5], opa); if(tt_aElt[1]) tt_SetOpa(tt_aElt[1], opa); if(tt_aV[SHADOW]) { opa = Math.round(opa * 0.8); tt_SetOpa(tt_aElt[7], opa); tt_SetOpa(tt_aElt[8], opa); } } function tt_OnCloseBtnOver(iOver) { var css = tt_aElt[4].style; iOver <<= 1; css.background = tt_aV[CLOSEBTNCOLORS][iOver]; css.color = tt_aV[CLOSEBTNCOLORS][iOver + 1]; } function tt_OnLClick(e) { // Ignore right-clicks e = e || window.event; if(!((e.button && e.button & 2) || (e.which && e.which == 3))) { if(tt_aV[CLICKSTICKY] && (tt_iState & 0x4)) { tt_aV[STICKY] = true; tt_iState &= ~0x4; } else if(tt_aV[CLICKCLOSE]) tt_HideInit(); } } function tt_Int(x) { var y; return(isNaN(y = parseInt(x)) ? 0 : y); } Number.prototype.Timer = function(s, iT, bUrge) { if(!this.value || bUrge) this.value = window.setTimeout(s, iT); } Number.prototype.EndTimer = function() { if(this.value) { window.clearTimeout(this.value); this.value = 0; } } function tt_GetWndCliSiz(s) { var db, y = window["inner" + s], sC = "client" + s, sN = "number"; if(typeof y == sN) { var y2; return( // Gecko or Opera with scrollbar // ... quirks mode ((db = document.body) && typeof(y2 = db[sC]) == sN && y2 && y2 <= y) ? y2 // ... strict mode : ((db = document.documentElement) && typeof(y2 = db[sC]) == sN && y2 && y2 <= y) ? y2 // No scrollbar, or clientarea size == 0, or other browser (KHTML etc.) : y ); } // IE return( // document.documentElement.client+s functional, returns > 0 ((db = document.documentElement) && (y = db[sC])) ? y // ... not functional, in which case document.body.client+s // is the clientarea size, fortunately : document.body[sC] ); } function tt_SetOpa(el, opa) { var css = el.style; tt_opa = opa; if(tt_flagOpa == 1) { if(opa < 100) { // Hacks for bugs of IE: // 1.) Once a CSS filter has been applied, fonts are no longer // anti-aliased, so we store the previous 'non-filter' to be // able to restore it if(typeof(el.filtNo) == tt_u) el.filtNo = css.filter; // 2.) A DIV cannot be made visible in a single step if an // opacity < 100 has been applied while the DIV was hidden var bVis = css.visibility != "hidden"; // 3.) In IE6, applying an opacity < 100 has no effect if the // element has no layout (position, size, zoom, ...) css.zoom = "100%"; if(!bVis) css.visibility = "visible"; css.filter = "alpha(opacity=" + opa + ")"; if(!bVis) css.visibility = "hidden"; } else if(typeof(el.filtNo) != tt_u) // Restore 'non-filter' css.filter = el.filtNo; } else { opa /= 100.0; switch(tt_flagOpa) { case 2: css.KhtmlOpacity = opa; break; case 3: css.KHTMLOpacity = opa; break; case 4: css.MozOpacity = opa; break; case 5: css.opacity = opa; break; } } } function tt_Err(sErr, bIfDebug) { if(tt_Debug || !bIfDebug) alert("Tooltip Script Error Message:\n\n" + sErr); } //============ EXTENSION (PLUGIN) MANAGER ===============// function tt_ExtCmdEnum() { var s; // Add new command(s) to the commands enum for(var i in config) { s = "window." + i.toString().toUpperCase(); if(eval("typeof(" + s + ") == tt_u")) { eval(s + " = " + tt_aV.length); tt_aV[tt_aV.length] = null; } } } function tt_ExtCallFncs(arg, sFnc) { var b = false; for(var i = tt_aExt.length; i;) {--i; var fnc = tt_aExt[i]["On" + sFnc]; // Call the method the extension has defined for this event if(fnc && fnc(arg)) b = true; } return b; } tt_Init(); taskjuggler-3.5.0/data/tjp.vim0000644000175000017500000013134712614413013015600 0ustar bernatbernat" Vim syntax file " Language: TaskJuggler " Maintainer: TaskJuggler Developers " Last Change: 2013-06-29 14:04:24 +0200 " This file was automatically generated by VimSyntax.rb if exists("b:current_syntax") finish endif setlocal softtabstop=2 setlocal cindent shiftwidth=2 setlocal tabstop=2 setlocal expandtab setlocal cinoptions=g0,t0,+0,(0,c0,C1,n-2 setlocal cinwords=account,accountreport,allocate,booking,columns,dailymax,dailymin,date,depends,export,extend,icalreport,include,journalentry,limits,maximum,minimum,monthlymax,monthlymin,navigator,newtask,nikureport,number,precedes,project,reference,resource,resourcereport,richtext,scenario,shift,status,statussheet,statussheetreport,supplement,tagfile,task,taskreport,text,textreport,timesheet,timesheetreport,tracereport,weeklymax,weeklymin setlocal cinkeys=0{,0},!^F,o,O setlocal cindent syn keyword tjp_macro macro contained syn keyword tjp_project project contained syn keyword tjp_supplement supplement contained syn keyword tjp_account account contained hi def link tjp_account Function syn keyword tjp_accountreport accountreport contained hi def link tjp_accountreport Function syn keyword tjp_export export contained hi def link tjp_export Function syn keyword tjp_nikureport nikureport contained hi def link tjp_nikureport Function syn keyword tjp_resource resource contained hi def link tjp_resource Function syn keyword tjp_resourcereport resourcereport contained hi def link tjp_resourcereport Function syn keyword tjp_scenario scenario contained hi def link tjp_scenario Function syn keyword tjp_shift shift contained hi def link tjp_shift Function syn keyword tjp_statussheetreport statussheetreport contained hi def link tjp_statussheetreport Function syn keyword tjp_task_statussheet task contained hi def link tjp_task_statussheet Function syn keyword tjp_task task contained hi def link tjp_task Function syn keyword tjp_taskreport taskreport contained hi def link tjp_taskreport Function syn keyword tjp_textreport textreport contained hi def link tjp_textreport Function syn keyword tjp_timesheetreport timesheetreport contained hi def link tjp_timesheetreport Function syn keyword tjp_tracereport tracereport contained hi def link tjp_tracereport Function syn keyword tjp_aggregate aggregate contained hi def link tjp_aggregate Type syn keyword tjp_credits credits contained hi def link tjp_credits Type syn keyword tjp_flags_account flags contained hi def link tjp_flags_account Type syn keyword tjp_allocate allocate contained hi def link tjp_allocate Type syn keyword tjp_alternative alternative contained hi def link tjp_alternative Type syn keyword tjp_select select contained hi def link tjp_select Type syn keyword tjp_persistent persistent contained hi def link tjp_persistent Type syn keyword tjp_mandatory mandatory contained hi def link tjp_mandatory Type syn keyword tjp_shifts_allocate shifts contained hi def link tjp_shifts_allocate Type syn keyword tjp_author author contained hi def link tjp_author Type syn keyword tjp_balance balance hi def link tjp_balance Type syn keyword tjp_overtime_booking overtime contained hi def link tjp_overtime_booking Type syn keyword tjp_sloppy_booking sloppy contained hi def link tjp_sloppy_booking Type syn keyword tjp_chargeset chargeset contained hi def link tjp_chargeset Type syn keyword tjp_columnid_activetasks activetasks hi def link tjp_columnid_activetasks Type syn keyword tjp_columnid_annualleave annualleave hi def link tjp_columnid_annualleave Type syn keyword tjp_columnid_annualleavebalance annualleavebalance hi def link tjp_columnid_annualleavebalance Type syn keyword tjp_columnid_alert alert hi def link tjp_columnid_alert Type syn keyword tjp_columnid_alertmessages alertmessages hi def link tjp_columnid_alertmessages Type syn keyword tjp_columnid_alertsummaries alertsummaries hi def link tjp_columnid_alertsummaries Type syn keyword tjp_columnid_alerttrend alerttrend hi def link tjp_columnid_alerttrend Type syn keyword tjp_columnid_balance balance hi def link tjp_columnid_balance Type syn keyword tjp_columnid_bsi bsi hi def link tjp_columnid_bsi Type syn keyword tjp_columnid_chart chart hi def link tjp_columnid_chart Type syn keyword tjp_columnid_closedtasks closedtasks hi def link tjp_columnid_closedtasks Type syn keyword tjp_columnid_competitorcount competitorcount hi def link tjp_columnid_competitorcount Type syn keyword tjp_columnid_competitors competitors hi def link tjp_columnid_competitors Type syn keyword tjp_columnid_complete complete hi def link tjp_columnid_complete Type syn keyword tjp_columnid_completed completed hi def link tjp_columnid_completed Type syn keyword tjp_columnid_criticalness criticalness hi def link tjp_columnid_criticalness Type syn keyword tjp_columnid_cost cost hi def link tjp_columnid_cost Type syn keyword tjp_columnid_daily daily hi def link tjp_columnid_daily Type syn keyword tjp_columnid_directreports directreports hi def link tjp_columnid_directreports Type syn keyword tjp_columnid_duration duration hi def link tjp_columnid_duration Type syn keyword tjp_columnid_duties duties hi def link tjp_columnid_duties Type syn keyword tjp_columnid_efficiency efficiency hi def link tjp_columnid_efficiency Type syn keyword tjp_columnid_effort effort hi def link tjp_columnid_effort Type syn keyword tjp_columnid_effortdone effortdone hi def link tjp_columnid_effortdone Type syn keyword tjp_columnid_effortleft effortleft hi def link tjp_columnid_effortleft Type syn keyword tjp_columnid_email email hi def link tjp_columnid_email Type syn keyword tjp_columnid_end end hi def link tjp_columnid_end Type syn keyword tjp_columnid_flags flags hi def link tjp_columnid_flags Type syn keyword tjp_columnid_followers followers hi def link tjp_columnid_followers Type syn keyword tjp_columnid_freetime freetime hi def link tjp_columnid_freetime Type syn keyword tjp_columnid_freework freework hi def link tjp_columnid_freework Type syn keyword tjp_columnid_fte fte hi def link tjp_columnid_fte Type syn keyword tjp_columnid_gauge gauge hi def link tjp_columnid_gauge Type syn keyword tjp_columnid_headcount headcount hi def link tjp_columnid_headcount Type syn keyword tjp_columnid_hierarchindex hierarchindex hi def link tjp_columnid_hierarchindex Type syn keyword tjp_columnid_hourly hourly hi def link tjp_columnid_hourly Type syn keyword tjp_columnid_id id hi def link tjp_columnid_id Type syn keyword tjp_columnid_index index hi def link tjp_columnid_index Type syn keyword tjp_columnid_inputs inputs hi def link tjp_columnid_inputs Type syn keyword tjp_columnid_journal journal hi def link tjp_columnid_journal Type syn keyword tjp_columnid_journal_sub journal_sub hi def link tjp_columnid_journal_sub Type syn keyword tjp_columnid_journalmessages journalmessages hi def link tjp_columnid_journalmessages Type syn keyword tjp_columnid_journalsummaries journalsummaries hi def link tjp_columnid_journalsummaries Type syn keyword tjp_columnid_line line hi def link tjp_columnid_line Type syn keyword tjp_columnid_managers managers hi def link tjp_columnid_managers Type syn keyword tjp_columnid_maxend maxend hi def link tjp_columnid_maxend Type syn keyword tjp_columnid_maxstart maxstart hi def link tjp_columnid_maxstart Type syn keyword tjp_columnid_minend minend hi def link tjp_columnid_minend Type syn keyword tjp_columnid_minstart minstart hi def link tjp_columnid_minstart Type syn keyword tjp_columnid_monthly monthly hi def link tjp_columnid_monthly Type syn keyword tjp_columnid_no no hi def link tjp_columnid_no Type syn keyword tjp_columnid_name name hi def link tjp_columnid_name Type syn keyword tjp_columnid_note note hi def link tjp_columnid_note Type syn keyword tjp_columnid_opentasks opentasks hi def link tjp_columnid_opentasks Type syn keyword tjp_columnid_pathcriticalness pathcriticalness hi def link tjp_columnid_pathcriticalness Type syn keyword tjp_columnid_precursors precursors hi def link tjp_columnid_precursors Type syn keyword tjp_columnid_priority priority hi def link tjp_columnid_priority Type syn keyword tjp_columnid_quarterly quarterly hi def link tjp_columnid_quarterly Type syn keyword tjp_columnid_rate rate hi def link tjp_columnid_rate Type syn keyword tjp_columnid_reports reports hi def link tjp_columnid_reports Type syn keyword tjp_columnid_resources resources hi def link tjp_columnid_resources Type syn keyword tjp_columnid_responsible responsible hi def link tjp_columnid_responsible Type syn keyword tjp_columnid_revenue revenue hi def link tjp_columnid_revenue Type syn keyword tjp_columnid_scenario scenario hi def link tjp_columnid_scenario Type syn keyword tjp_columnid_scheduling scheduling hi def link tjp_columnid_scheduling Type syn keyword tjp_columnid_seqno seqno hi def link tjp_columnid_seqno Type syn keyword tjp_columnid_sickleave sickleave hi def link tjp_columnid_sickleave Type syn keyword tjp_columnid_specialleave specialleave hi def link tjp_columnid_specialleave Type syn keyword tjp_columnid_start start hi def link tjp_columnid_start Type syn keyword tjp_columnid_status status hi def link tjp_columnid_status Type syn keyword tjp_columnid_targets targets hi def link tjp_columnid_targets Type syn keyword tjp_columnid_turnover turnover hi def link tjp_columnid_turnover Type syn keyword tjp_columnid_wbs wbs hi def link tjp_columnid_wbs Type syn keyword tjp_columnid_unpaidleave unpaidleave hi def link tjp_columnid_unpaidleave Type syn keyword tjp_columnid_weekly weekly hi def link tjp_columnid_weekly Type syn keyword tjp_columnid_yearly yearly hi def link tjp_columnid_yearly Type syn keyword tjp_celltext_column celltext contained hi def link tjp_celltext_column Type syn keyword tjp_cellcolor_column cellcolor contained hi def link tjp_cellcolor_column Type syn keyword tjp_end_column end contained hi def link tjp_end_column Type syn keyword tjp_fontcolor_column fontcolor contained hi def link tjp_fontcolor_column Type syn keyword tjp_halign_column halign contained hi def link tjp_halign_column Type syn keyword tjp_listitem_column listitem contained hi def link tjp_listitem_column Type syn keyword tjp_listtype_column listtype contained hi def link tjp_listtype_column Type syn keyword tjp_period_column period contained hi def link tjp_period_column Type syn keyword tjp_scale_column scale contained hi def link tjp_scale_column Type syn keyword tjp_start_column start contained hi def link tjp_start_column Type syn keyword tjp_timeformat1 timeformat1 contained hi def link tjp_timeformat1 Type syn keyword tjp_timeformat2 timeformat2 contained hi def link tjp_timeformat2 Type syn keyword tjp_title_column title contained hi def link tjp_title_column Type syn keyword tjp_tooltip_column tooltip contained hi def link tjp_tooltip_column Type syn keyword tjp_width_column width contained hi def link tjp_width_column Type syn keyword tjp_currencyformat currencyformat contained hi def link tjp_currencyformat Type syn keyword tjp_details details contained hi def link tjp_details Type syn keyword tjp_definitions definitions contained hi def link tjp_definitions Type syn keyword tjp_formats_export formats contained hi def link tjp_formats_export Type syn keyword tjp_resourceattributes resourceattributes contained hi def link tjp_resourceattributes Type syn keyword tjp_scenarios_export scenarios contained hi def link tjp_scenarios_export Type syn keyword tjp_taskattributes taskattributes contained hi def link tjp_taskattributes Type syn keyword tjp_taskroot_export taskroot contained hi def link tjp_taskroot_export Type syn keyword tjp_timezone_export timezone contained hi def link tjp_timezone_export Type syn keyword tjp_date_extend date contained hi def link tjp_date_extend Type syn keyword tjp_number_extend number contained hi def link tjp_number_extend Type syn keyword tjp_reference_extend reference contained hi def link tjp_reference_extend Type syn keyword tjp_richtext_extend richtext contained hi def link tjp_richtext_extend Type syn keyword tjp_text_extend text contained hi def link tjp_text_extend Type syn keyword tjp_inherit_extend inherit contained hi def link tjp_inherit_extend Type syn keyword tjp_scenariospecific_extend scenariospecific contained hi def link tjp_scenariospecific_extend Type syn keyword tjp_fail fail contained hi def link tjp_fail Type syn keyword tjp_formats formats contained hi def link tjp_formats Type syn keyword tjp_hasalert hasalert contained hi def link tjp_hasalert Type syn keyword tjp_isactive isactive contained hi def link tjp_isactive Type syn keyword tjp_ischildof ischildof contained hi def link tjp_ischildof Type syn keyword tjp_isdependencyof isdependencyof contained hi def link tjp_isdependencyof Type syn keyword tjp_isdutyof isdutyof contained hi def link tjp_isdutyof Type syn keyword tjp_isfeatureof isfeatureof contained hi def link tjp_isfeatureof Type syn keyword tjp_isleaf isleaf contained hi def link tjp_isleaf Type syn keyword tjp_ismilestone ismilestone contained hi def link tjp_ismilestone Type syn keyword tjp_isongoing isongoing contained hi def link tjp_isongoing Type syn keyword tjp_isresource isresource contained hi def link tjp_isresource Type syn keyword tjp_isresponsibilityof isresponsibilityof contained hi def link tjp_isresponsibilityof Type syn keyword tjp_istask istask contained hi def link tjp_istask Type syn keyword tjp_isvalid isvalid contained hi def link tjp_isvalid Type syn keyword tjp_treelevel treelevel contained hi def link tjp_treelevel Type syn keyword tjp_halign_center center hi def link tjp_halign_center Type syn keyword tjp_halign_left left hi def link tjp_halign_left Type syn keyword tjp_halign_right right hi def link tjp_halign_right Type syn keyword tjp_headline headline contained hi def link tjp_headline Type syn keyword tjp_hideaccount hideaccount contained hi def link tjp_hideaccount Type syn keyword tjp_hidejournalentry hidejournalentry contained hi def link tjp_hidejournalentry Type syn keyword tjp_hideresource hideresource contained hi def link tjp_hideresource Type syn keyword tjp_hidetask hidetask contained hi def link tjp_hidetask Type syn keyword tjp_icalreport icalreport contained hi def link tjp_icalreport Type syn keyword tjp_scenario_ical scenario contained hi def link tjp_scenario_ical Type syn keyword tjp_accountprefix accountprefix contained hi def link tjp_accountprefix Type syn keyword tjp_reportprefix reportprefix contained hi def link tjp_reportprefix Type syn keyword tjp_resourceprefix resourceprefix contained hi def link tjp_resourceprefix Type syn keyword tjp_taskprefix taskprefix contained hi def link tjp_taskprefix Type syn keyword tjp_journalattributes journalattributes contained hi def link tjp_journalattributes Type syn keyword tjp_journalentry journalentry contained hi def link tjp_journalentry Type syn keyword tjp_alert alert contained hi def link tjp_alert Type syn keyword tjp_flags_journalentry flags contained hi def link tjp_flags_journalentry Type syn keyword tjp_leaveallowance leaveallowances contained hi def link tjp_leaveallowance Type syn keyword tjp_leaves leaves hi def link tjp_leaves Type syn keyword tjp_end_limit end contained hi def link tjp_end_limit Type syn keyword tjp_period_limit period contained hi def link tjp_period_limit Type syn keyword tjp_resources_limit resources contained hi def link tjp_resources_limit Type syn keyword tjp_start_limit start contained hi def link tjp_start_limit Type syn keyword tjp_dailymax dailymax contained hi def link tjp_dailymax Type syn keyword tjp_dailymin dailymin contained hi def link tjp_dailymin Type syn keyword tjp_maximum maximum contained hi def link tjp_maximum Type syn keyword tjp_minimum minimum contained hi def link tjp_minimum Type syn keyword tjp_monthlymax monthlymax contained hi def link tjp_monthlymax Type syn keyword tjp_monthlymin monthlymin contained hi def link tjp_monthlymin Type syn keyword tjp_weeklymax weeklymax contained hi def link tjp_weeklymax Type syn keyword tjp_weeklymin weeklymin contained hi def link tjp_weeklymin Type syn keyword tjp_loadunit loadunit contained hi def link tjp_loadunit Type syn keyword tjp_logicalexpression @ hi def link tjp_logicalexpression Type syn keyword tjp_navigator navigator contained hi def link tjp_navigator Type syn keyword tjp_hidereport hidereport contained hi def link tjp_hidereport Type syn keyword tjp_timeoff_nikureport timeoff contained hi def link tjp_timeoff_nikureport Type syn keyword tjp_numberformat numberformat contained hi def link tjp_numberformat Type syn keyword tjp_alertlevels alertlevels contained hi def link tjp_alertlevels Type syn keyword tjp_currency currency contained hi def link tjp_currency Type syn keyword tjp_dailyworkinghours dailyworkinghours contained hi def link tjp_dailyworkinghours Type syn keyword tjp_extend extend contained hi def link tjp_extend Type syn keyword tjp_now now contained hi def link tjp_now Type syn keyword tjp_outputdir outputdir contained hi def link tjp_outputdir Type syn keyword tjp_shorttimeformat shorttimeformat contained hi def link tjp_shorttimeformat Type syn keyword tjp_timingresolution timingresolution contained hi def link tjp_timingresolution Type syn keyword tjp_trackingscenario trackingscenario contained hi def link tjp_trackingscenario Type syn keyword tjp_weekstartsmonday weekstartsmonday contained hi def link tjp_weekstartsmonday Type syn keyword tjp_weekstartssunday weekstartssunday contained hi def link tjp_weekstartssunday Type syn keyword tjp_yearlyworkingdays yearlyworkingdays contained hi def link tjp_yearlyworkingdays Type syn keyword tjp_auxdir auxdir hi def link tjp_auxdir Type syn keyword tjp_copyright copyright hi def link tjp_copyright Type syn keyword tjp_flags flags hi def link tjp_flags Type syn keyword tjp_limits limits contained hi def link tjp_limits Type syn keyword tjp_projectid projectid hi def link tjp_projectid Type syn keyword tjp_projectids projectids hi def link tjp_projectids Type syn keyword tjp_rate rate hi def link tjp_rate Type syn keyword tjp_vacation vacation hi def link tjp_vacation Type syn keyword tjp_purge purge contained hi def link tjp_purge Type syn keyword tjp_accountroot accountroot contained hi def link tjp_accountroot Type syn keyword tjp_auxdir_report auxdir contained hi def link tjp_auxdir_report Type syn keyword tjp_caption caption contained hi def link tjp_caption Type syn keyword tjp_center center contained hi def link tjp_center Type syn keyword tjp_columns columns contained hi def link tjp_columns Type syn keyword tjp_epilog epilog contained hi def link tjp_epilog Type syn keyword tjp_flags_report flags contained hi def link tjp_flags_report Type syn keyword tjp_footer footer contained hi def link tjp_footer Type syn keyword tjp_header header contained hi def link tjp_header Type syn keyword tjp_height height contained hi def link tjp_height Type syn keyword tjp_journalmode journalmode contained hi def link tjp_journalmode Type syn keyword tjp_left left contained hi def link tjp_left Type syn keyword tjp_opennodes opennodes contained hi def link tjp_opennodes Type syn keyword tjp_prolog prolog contained hi def link tjp_prolog Type syn keyword tjp_rawhtmlhead rawhtmlhead contained hi def link tjp_rawhtmlhead Type syn keyword tjp_right right contained hi def link tjp_right Type syn keyword tjp_scenarios scenarios contained hi def link tjp_scenarios Type syn keyword tjp_selfcontained selfcontained contained hi def link tjp_selfcontained Type syn keyword tjp_resourceroot resourceroot contained hi def link tjp_resourceroot Type syn keyword tjp_taskroot taskroot contained hi def link tjp_taskroot Type syn keyword tjp_timezone_report timezone contained hi def link tjp_timezone_report Type syn keyword tjp_width width contained hi def link tjp_width Type syn keyword tjp_end_report end contained hi def link tjp_end_report Type syn keyword tjp_period_report period contained hi def link tjp_period_report Type syn keyword tjp_start_report start contained hi def link tjp_start_report Type syn keyword tjp_title title contained hi def link tjp_title Type syn keyword tjp_email email contained hi def link tjp_email Type syn keyword tjp_efficiency efficiency contained hi def link tjp_efficiency Type syn keyword tjp_flags_resource flags contained hi def link tjp_flags_resource Type syn keyword tjp_booking_resource booking contained hi def link tjp_booking_resource Type syn keyword tjp_limits_resource limits contained hi def link tjp_limits_resource Type syn keyword tjp_managers managers contained hi def link tjp_managers Type syn keyword tjp_rate_resource rate contained hi def link tjp_rate_resource Type syn keyword tjp_shifts_resource shifts contained hi def link tjp_shifts_resource Type syn keyword tjp_vacation_resource vacation contained hi def link tjp_vacation_resource Type syn keyword tjp_rollupaccount rollupaccount contained hi def link tjp_rollupaccount Type syn keyword tjp_rollupresource rollupresource contained hi def link tjp_rollupresource Type syn keyword tjp_rolluptask rolluptask contained hi def link tjp_rolluptask Type syn keyword tjp_active active contained hi def link tjp_active Type syn keyword tjp_replace replace contained hi def link tjp_replace Type syn keyword tjp_timezone_shift timezone contained hi def link tjp_timezone_shift Type syn keyword tjp_vacation_shift vacation contained hi def link tjp_vacation_shift Type syn keyword tjp_sortjournalentries sortjournalentries contained hi def link tjp_sortjournalentries Type syn keyword tjp_sortaccounts sortaccounts contained hi def link tjp_sortaccounts Type syn keyword tjp_sortresources sortresources contained hi def link tjp_sortresources Type syn keyword tjp_sorttasks sorttasks contained hi def link tjp_sorttasks Type syn keyword tjp_flags_statussheet flags contained hi def link tjp_flags_statussheet Type syn keyword tjp_status_statussheet status contained hi def link tjp_status_statussheet Type syn keyword tjp_statussheet statussheet contained hi def link tjp_statussheet Type syn keyword tjp_summary summary contained hi def link tjp_summary Type syn keyword tjp_tagfile tagfile contained hi def link tjp_tagfile Type syn keyword tjp_adopt_task adopt contained hi def link tjp_adopt_task Type syn keyword tjp_note_task note contained hi def link tjp_note_task Type syn keyword tjp_gapduration gapduration contained hi def link tjp_gapduration Type syn keyword tjp_gaplength gaplength contained hi def link tjp_gaplength Type syn keyword tjp_onend onend contained hi def link tjp_onend Type syn keyword tjp_onstart onstart contained hi def link tjp_onstart Type syn keyword tjp_period_task period contained hi def link tjp_period_task Type syn keyword tjp_booking_task booking contained hi def link tjp_booking_task Type syn keyword tjp_charge charge contained hi def link tjp_charge Type syn keyword tjp_complete complete contained hi def link tjp_complete Type syn keyword tjp_depends depends contained hi def link tjp_depends Type syn keyword tjp_duration duration contained hi def link tjp_duration Type syn keyword tjp_effort effort contained hi def link tjp_effort Type syn keyword tjp_end end contained hi def link tjp_end Type syn keyword tjp_flags_task flags contained hi def link tjp_flags_task Type syn keyword tjp_length length contained hi def link tjp_length Type syn keyword tjp_limits_task limits contained hi def link tjp_limits_task Type syn keyword tjp_maxend maxend contained hi def link tjp_maxend Type syn keyword tjp_maxstart maxstart contained hi def link tjp_maxstart Type syn keyword tjp_milestone milestone contained hi def link tjp_milestone Type syn keyword tjp_minend minend contained hi def link tjp_minend Type syn keyword tjp_minstart minstart contained hi def link tjp_minstart Type syn keyword tjp_precedes precedes contained hi def link tjp_precedes Type syn keyword tjp_priority priority contained hi def link tjp_priority Type syn keyword tjp_projectid_task projectid contained hi def link tjp_projectid_task Type syn keyword tjp_responsible responsible contained hi def link tjp_responsible Type syn keyword tjp_scheduled scheduled contained hi def link tjp_scheduled Type syn keyword tjp_scheduling scheduling contained hi def link tjp_scheduling Type syn keyword tjp_shifts_task shifts contained hi def link tjp_shifts_task Type syn keyword tjp_start start contained hi def link tjp_start Type syn keyword tjp_timeformat timeformat contained hi def link tjp_timeformat Type syn keyword tjp_timesheet timesheet contained hi def link tjp_timesheet Type syn keyword tjp_newtask newtask contained hi def link tjp_newtask Type syn keyword tjp_shift_timesheet shift contained hi def link tjp_shift_timesheet Type syn keyword tjp_task_timesheet task contained hi def link tjp_task_timesheet Type syn keyword tjp_timezone timezone contained hi def link tjp_timezone Type syn keyword tjp_flags_timesheet flags contained hi def link tjp_flags_timesheet Type syn keyword tjp_status_timesheet status contained hi def link tjp_status_timesheet Type syn keyword tjp_end_timesheet end contained hi def link tjp_end_timesheet Type syn keyword tjp_priority_timesheet priority contained hi def link tjp_priority_timesheet Type syn keyword tjp_remaining remaining contained hi def link tjp_remaining Type syn keyword tjp_work work contained hi def link tjp_work Type syn keyword tjp_warn warn contained hi def link tjp_warn Type syn keyword tjp_workinghours_project workinghours contained hi def link tjp_workinghours_project Type syn keyword tjp_workinghours_resource workinghours contained hi def link tjp_workinghours_resource Type syn keyword tjp_workinghours_shift workinghours contained hi def link tjp_workinghours_shift Type syn match tjparg contained /\${.*}/ syn match tjpcomment /#.*$/ syn match tjpcomment "//.*$" syn match tjpinclude /include.*$/ syn match tjpnumber /\s[-+]\?\d\+\(\.\d\+\)\?\([hdwmy]\|min\)\?/ syn match tjpdate /\s\d\{4}-\d\{1,2}-\d\{1,2}\(-\d\{1,2}:\d\{1,2}\(:\d\{1,2}\)\?\(-[-+]\?\d\{4}\)\?\)\?/ syn match tjptime /\s\d\{1,2}:\d\d\(:\d\d\)\?/ syn cluster tjpcommon contains=tjpcomment,tjpdate,tjptime,tjpstring,tjpnumber syn region tjpblk_account start=/^\s*account\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_account,tjp_account,tjpblk_account,tjp_aggregate,tjp_credits,tjp_flags_account syn region tjpblk_accountreport start=/^\s*accountreport\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_accountreport,tjp_accountroot,tjp_auxdir_report,tjp_balance,tjp_caption,tjp_center,tjp_columns,tjpblk_columns,tjp_currencyformat,tjp_end_report,tjp_epilog,tjp_flags_report,tjp_footer,tjp_formats,tjp_header,tjp_headline,tjp_hidejournalentry,tjp_hideaccount,tjp_hideresource,tjp_hidetask,tjp_height,tjp_journalattributes,tjp_journalmode,tjp_left,tjp_loadunit,tjp_numberformat,tjp_opennodes,tjp_period_report,tjp_prolog,tjp_purge,tjp_rawhtmlhead,tjp_accountreport,tjpblk_accountreport,tjp_export,tjpblk_export,tjp_resourcereport,tjpblk_resourcereport,tjp_taskreport,tjpblk_taskreport,tjp_textreport,tjpblk_textreport,tjp_tracereport,tjpblk_tracereport,tjp_right,tjp_rollupaccount,tjp_rollupresource,tjp_rolluptask,tjp_scenarios,tjp_selfcontained,tjp_sortaccounts,tjp_sortjournalentries,tjp_sortresources,tjp_sorttasks,tjp_start_report,tjp_resourceroot,tjp_taskroot,tjp_timeformat,tjp_timezone_report,tjp_title,tjp_width syn region tjpblk_allocate start=/^\s*allocate\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_allocate,tjp_alternative,tjp_select,tjp_persistent,tjp_mandatory,tjp_shifts_allocate contained syn region tjpblk_export start=/^\s*export\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_export,tjp_definitions,tjp_formats_export,tjp_hideresource,tjp_hidetask,tjp_loadunit,tjp_purge,tjp_end_report,tjp_period_report,tjp_accountreport,tjpblk_accountreport,tjp_export,tjpblk_export,tjp_resourcereport,tjpblk_resourcereport,tjp_taskreport,tjpblk_taskreport,tjp_textreport,tjpblk_textreport,tjp_tracereport,tjpblk_tracereport,tjp_start_report,tjp_resourceattributes,tjp_rollupresource,tjp_rolluptask,tjp_scenarios_export,tjp_taskattributes,tjp_taskroot_export,tjp_timezone_export syn region tjpblk_date_extend start=/^\s*date\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_date_extend,tjp_inherit_extend,tjp_scenariospecific_extend contained syn region tjpblk_number_extend start=/^\s*number\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_number_extend,tjp_inherit_extend,tjp_scenariospecific_extend contained syn region tjpblk_reference_extend start=/^\s*reference\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_reference_extend,tjp_inherit_extend,tjp_scenariospecific_extend contained syn region tjpblk_richtext_extend start=/^\s*richtext\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_richtext_extend,tjp_inherit_extend,tjp_scenariospecific_extend contained syn region tjpblk_text_extend start=/^\s*text\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_text_extend,tjp_inherit_extend,tjp_scenariospecific_extend contained syn region tjpblk_icalreport start=/^\s*icalreport\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_icalreport,tjp_hideresource,tjp_hidejournalentry,tjp_hidetask,tjp_end_report,tjp_period_report,tjp_start_report,tjp_rollupresource,tjp_rolluptask,tjp_scenario_ical syn region tjpblk_journalentry start=/^\s*journalentry\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_journalentry,tjp_alert,tjp_author,tjp_flags_journalentry,tjp_summary,tjp_details contained syn region tjpblk_dailymax start=/^\s*dailymax\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_dailymax,tjp_end_limit,tjp_period_limit,tjp_resources_limit,tjp_start_limit contained syn region tjpblk_dailymin start=/^\s*dailymin\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_dailymin,tjp_end_limit,tjp_period_limit,tjp_resources_limit,tjp_start_limit contained syn region tjpblk_maximum start=/^\s*maximum\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_maximum,tjp_end_limit,tjp_period_limit,tjp_resources_limit,tjp_start_limit contained syn region tjpblk_minimum start=/^\s*minimum\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_minimum,tjp_end_limit,tjp_period_limit,tjp_resources_limit,tjp_start_limit contained syn region tjpblk_monthlymax start=/^\s*monthlymax\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_monthlymax,tjp_end_limit,tjp_period_limit,tjp_resources_limit,tjp_start_limit contained syn region tjpblk_monthlymin start=/^\s*monthlymin\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_monthlymin,tjp_end_limit,tjp_period_limit,tjp_resources_limit,tjp_start_limit contained syn region tjpblk_weeklymax start=/^\s*weeklymax\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_weeklymax,tjp_end_limit,tjp_period_limit,tjp_resources_limit,tjp_start_limit contained syn region tjpblk_weeklymin start=/^\s*weeklymin\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_weeklymin,tjp_end_limit,tjp_period_limit,tjp_resources_limit,tjp_start_limit contained syn region tjpblk_navigator start=/^\s*navigator\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_navigator,tjp_hidereport syn region tjpblk_nikureport start=/^\s*nikureport\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_nikureport,tjp_formats,tjp_headline,tjp_hideresource,tjp_hidetask,tjp_numberformat,tjp_end_report,tjp_period_report,tjp_start_report,tjp_title,tjp_timeoff_nikureport syn region tjpblk_extend start=/^\s*extend\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_extend,tjp_date_extend,tjpblk_date_extend,tjp_number_extend,tjpblk_number_extend,tjp_reference_extend,tjpblk_reference_extend,tjp_richtext_extend,tjpblk_richtext_extend,tjp_text_extend,tjpblk_text_extend contained syn region tjpblk_project start=/^\s*project\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_project,tjp_alertlevels,tjp_currencyformat,tjp_currency,tjp_dailyworkinghours,tjp_extend,tjpblk_extend,tjp_include_project,tjp_journalentry,tjpblk_journalentry,tjp_now,tjp_numberformat,tjp_outputdir,tjp_scenario,tjpblk_scenario,tjp_shorttimeformat,tjp_timeformat,tjp_timezone,tjp_timingresolution,tjp_trackingscenario,tjp_weekstartsmonday,tjp_weekstartssunday,tjp_workinghours_project,tjp_yearlyworkingdays syn region tjpblk_limits start=/^\s*limits\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_limits,tjp_dailymax,tjpblk_dailymax,tjp_dailymin,tjpblk_dailymin,tjp_maximum,tjpblk_maximum,tjp_minimum,tjpblk_minimum,tjp_monthlymax,tjpblk_monthlymax,tjp_monthlymin,tjpblk_monthlymin,tjp_weeklymax,tjpblk_weeklymax,tjp_weeklymin,tjpblk_weeklymin syn region tjpblk_include_properties start=/^\s*include\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_include_properties,tjp_accountprefix,tjp_reportprefix,tjp_resourceprefix,tjp_taskprefix syn region tjpblk_columns start=/^\s*columns\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_columns,tjp_celltext_column,tjp_cellcolor_column,tjp_end_column,tjp_fontcolor_column,tjp_halign_column,tjp_listitem_column,tjp_listtype_column,tjp_period_column,tjp_scale_column,tjp_start_column,tjp_timeformat1,tjp_timeformat2,tjp_title_column,tjp_tooltip_column,tjp_width_column contained syn region tjpblk_resource start=/^\s*resource\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_resource,tjp_email,tjp_journalentry,tjpblk_journalentry,tjp_purge,tjp_resource,tjpblk_resource,tjp_chargeset,tjp_efficiency,tjp_flags_resource,tjp_booking_resource,tjpblk_booking_resource,tjp_fail,tjp_leaveallowance,tjp_leaves,tjp_limits_resource,tjpblk_limits_resource,tjp_managers,tjp_rate_resource,tjp_shifts_resource,tjp_vacation_resource,tjp_warn,tjp_workinghours_resource,tjp_supplement_resource,tjpblk_supplement_resource syn region tjpblk_supplement_resource start=/^\s*supplement resource\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_supplement_resource,tjp_email,tjp_journalentry,tjpblk_journalentry,tjp_purge,tjp_resource,tjpblk_resource,tjp_chargeset,tjp_efficiency,tjp_flags_resource,tjp_booking_resource,tjpblk_booking_resource,tjp_fail,tjp_leaveallowance,tjp_leaves,tjp_limits_resource,tjpblk_limits_resource,tjp_managers,tjp_rate_resource,tjp_shifts_resource,tjp_vacation_resource,tjp_warn,tjp_workinghours_resource,tjp_supplement_resource,tjpblk_supplement_resource,tjp_supplement contained syn region tjpblk_resourcereport start=/^\s*resourcereport\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_resourcereport,tjp_accountroot,tjp_auxdir_report,tjp_balance,tjp_caption,tjp_center,tjp_columns,tjpblk_columns,tjp_currencyformat,tjp_end_report,tjp_epilog,tjp_flags_report,tjp_footer,tjp_formats,tjp_header,tjp_headline,tjp_hidejournalentry,tjp_hideaccount,tjp_hideresource,tjp_hidetask,tjp_height,tjp_journalattributes,tjp_journalmode,tjp_left,tjp_loadunit,tjp_numberformat,tjp_opennodes,tjp_period_report,tjp_prolog,tjp_purge,tjp_rawhtmlhead,tjp_accountreport,tjpblk_accountreport,tjp_export,tjpblk_export,tjp_resourcereport,tjpblk_resourcereport,tjp_taskreport,tjpblk_taskreport,tjp_textreport,tjpblk_textreport,tjp_tracereport,tjpblk_tracereport,tjp_right,tjp_rollupaccount,tjp_rollupresource,tjp_rolluptask,tjp_scenarios,tjp_selfcontained,tjp_sortaccounts,tjp_sortjournalentries,tjp_sortresources,tjp_sorttasks,tjp_start_report,tjp_resourceroot,tjp_taskroot,tjp_timeformat,tjp_timezone_report,tjp_title,tjp_width syn region tjpblk_booking_resource start=/^\s*booking\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_booking_resource,tjp_overtime_booking,tjp_sloppy_booking contained syn region tjpblk_limits_resource start=/^\s*limits\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_limits_resource,tjp_dailymax,tjpblk_dailymax,tjp_dailymin,tjpblk_dailymin,tjp_maximum,tjpblk_maximum,tjp_minimum,tjpblk_minimum,tjp_monthlymax,tjpblk_monthlymax,tjp_monthlymin,tjpblk_monthlymin,tjp_weeklymax,tjpblk_weeklymax,tjp_weeklymin,tjpblk_weeklymin contained syn region tjpblk_scenario start=/^\s*scenario\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_scenario,tjp_active,tjp_scenario,tjpblk_scenario contained syn region tjpblk_shift start=/^\s*shift\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_shift,tjp_shift,tjpblk_shift,tjp_leaves,tjp_replace,tjp_timezone_shift,tjp_vacation_shift,tjp_workinghours_shift syn region tjpblk_status_statussheet start=/^\s*status\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_status_statussheet,tjp_author,tjp_details,tjp_flags_statussheet,tjp_summary contained syn region tjpblk_statussheet start=/^\s*statussheet\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_statussheet,tjp_task_statussheet,tjpblk_task_statussheet syn region tjpblk_statussheetreport start=/^\s*statussheetreport\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_statussheetreport,tjp_hideresource,tjp_hidetask,tjp_end_report,tjp_period_report,tjp_start_report,tjp_sortresources,tjp_sorttasks syn region tjpblk_task_statussheet start=/^\s*task\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_task_statussheet,tjp_status_statussheet,tjpblk_status_statussheet,tjp_task_statussheet,tjpblk_task_statussheet contained syn region tjpblk_tagfile start=/^\s*tagfile\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_tagfile,tjp_hideresource,tjp_hidetask,tjp_rollupresource,tjp_rolluptask syn region tjpblk_task start=/^\s*task\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_task,tjp_adopt_task,tjp_journalentry,tjpblk_journalentry,tjp_note_task,tjp_purge,tjp_supplement_task,tjpblk_supplement_task,tjp_task,tjpblk_task,tjp_allocate,tjpblk_allocate,tjp_booking_task,tjpblk_booking_task,tjp_charge,tjp_chargeset,tjp_complete,tjp_depends,tjpblk_depends,tjp_duration,tjp_effort,tjp_end,tjp_flags_task,tjp_fail,tjp_length,tjp_limits_task,tjpblk_limits_task,tjp_maxend,tjp_maxstart,tjp_milestone,tjp_minend,tjp_minstart,tjp_period_task,tjp_precedes,tjpblk_precedes,tjp_priority,tjp_projectid_task,tjp_responsible,tjp_scheduled,tjp_scheduling,tjp_shifts_task,tjp_start,tjp_warn syn region tjpblk_supplement_task start=/^\s*supplement task\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_supplement_task,tjp_adopt_task,tjp_journalentry,tjpblk_journalentry,tjp_note_task,tjp_purge,tjp_supplement_task,tjpblk_supplement_task,tjp_task,tjpblk_task,tjp_allocate,tjpblk_allocate,tjp_booking_task,tjpblk_booking_task,tjp_charge,tjp_chargeset,tjp_complete,tjp_depends,tjpblk_depends,tjp_duration,tjp_effort,tjp_end,tjp_flags_task,tjp_fail,tjp_length,tjp_limits_task,tjpblk_limits_task,tjp_maxend,tjp_maxstart,tjp_milestone,tjp_minend,tjp_minstart,tjp_period_task,tjp_precedes,tjpblk_precedes,tjp_priority,tjp_projectid_task,tjp_responsible,tjp_scheduled,tjp_scheduling,tjp_shifts_task,tjp_start,tjp_warn,tjp_supplement contained syn region tjpblk_taskreport start=/^\s*taskreport\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_taskreport,tjp_accountroot,tjp_auxdir_report,tjp_balance,tjp_caption,tjp_center,tjp_columns,tjpblk_columns,tjp_currencyformat,tjp_end_report,tjp_epilog,tjp_flags_report,tjp_footer,tjp_formats,tjp_header,tjp_headline,tjp_hidejournalentry,tjp_hideaccount,tjp_hideresource,tjp_hidetask,tjp_height,tjp_journalattributes,tjp_journalmode,tjp_left,tjp_loadunit,tjp_numberformat,tjp_opennodes,tjp_period_report,tjp_prolog,tjp_purge,tjp_rawhtmlhead,tjp_accountreport,tjpblk_accountreport,tjp_export,tjpblk_export,tjp_resourcereport,tjpblk_resourcereport,tjp_taskreport,tjpblk_taskreport,tjp_textreport,tjpblk_textreport,tjp_tracereport,tjpblk_tracereport,tjp_right,tjp_rollupaccount,tjp_rollupresource,tjp_rolluptask,tjp_scenarios,tjp_selfcontained,tjp_sortaccounts,tjp_sortjournalentries,tjp_sortresources,tjp_sorttasks,tjp_start_report,tjp_resourceroot,tjp_taskroot,tjp_timeformat,tjp_timezone_report,tjp_title,tjp_width syn region tjpblk_booking_task start=/^\s*booking\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_booking_task,tjp_overtime_booking,tjp_sloppy_booking contained syn region tjpblk_depends start=/^\s*depends\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_depends,tjp_gapduration,tjp_gaplength,tjp_onend,tjp_onstart contained syn region tjpblk_limits_task start=/^\s*limits\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_limits_task,tjp_dailymax,tjpblk_dailymax,tjp_dailymin,tjpblk_dailymin,tjp_maximum,tjpblk_maximum,tjp_minimum,tjpblk_minimum,tjp_monthlymax,tjpblk_monthlymax,tjp_monthlymin,tjpblk_monthlymin,tjp_weeklymax,tjpblk_weeklymax,tjp_weeklymin,tjpblk_weeklymin contained syn region tjpblk_precedes start=/^\s*precedes\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_precedes,tjp_gapduration,tjp_gaplength,tjp_onend,tjp_onstart contained syn region tjpblk_textreport start=/^\s*textreport\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_textreport,tjp_accountroot,tjp_auxdir_report,tjp_balance,tjp_caption,tjp_center,tjp_columns,tjpblk_columns,tjp_currencyformat,tjp_end_report,tjp_epilog,tjp_flags_report,tjp_footer,tjp_formats,tjp_header,tjp_headline,tjp_hidejournalentry,tjp_hideaccount,tjp_hideresource,tjp_hidetask,tjp_height,tjp_journalattributes,tjp_journalmode,tjp_left,tjp_loadunit,tjp_numberformat,tjp_opennodes,tjp_period_report,tjp_prolog,tjp_purge,tjp_rawhtmlhead,tjp_accountreport,tjpblk_accountreport,tjp_export,tjpblk_export,tjp_resourcereport,tjpblk_resourcereport,tjp_taskreport,tjpblk_taskreport,tjp_textreport,tjpblk_textreport,tjp_tracereport,tjpblk_tracereport,tjp_right,tjp_rollupaccount,tjp_rollupresource,tjp_rolluptask,tjp_scenarios,tjp_selfcontained,tjp_sortaccounts,tjp_sortjournalentries,tjp_sortresources,tjp_sorttasks,tjp_start_report,tjp_resourceroot,tjp_taskroot,tjp_timeformat,tjp_timezone_report,tjp_title,tjp_width syn region tjpblk_timesheet start=/^\s*timesheet\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_timesheet,tjp_newtask,tjpblk_newtask,tjp_shift_timesheet,tjp_status_timesheet,tjpblk_status_timesheet,tjp_task_timesheet,tjpblk_task_timesheet syn region tjpblk_newtask start=/^\s*newtask\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_newtask,tjp_end_timesheet,tjp_priority_timesheet,tjp_remaining,tjp_status_timesheet,tjpblk_status_timesheet,tjp_work contained syn region tjpblk_task_timesheet start=/^\s*task\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_task_timesheet,tjp_end_timesheet,tjp_priority_timesheet,tjp_remaining,tjp_status_timesheet,tjpblk_status_timesheet,tjp_work contained syn region tjpblk_timesheetreport start=/^\s*timesheetreport\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_timesheetreport,tjp_hideresource,tjp_hidetask,tjp_end_report,tjp_period_report,tjp_start_report,tjp_sortresources,tjp_sorttasks syn region tjpblk_tracereport start=/^\s*tracereport\s.*{\s*$/ end=/^\s*}\s*$/ transparent fold contains=@tjpcommon,tjp_tracereport,tjp_accountroot,tjp_auxdir_report,tjp_balance,tjp_caption,tjp_center,tjp_columns,tjpblk_columns,tjp_currencyformat,tjp_end_report,tjp_epilog,tjp_flags_report,tjp_footer,tjp_formats,tjp_header,tjp_headline,tjp_hidejournalentry,tjp_hideaccount,tjp_hideresource,tjp_hidetask,tjp_height,tjp_journalattributes,tjp_journalmode,tjp_left,tjp_loadunit,tjp_numberformat,tjp_opennodes,tjp_period_report,tjp_prolog,tjp_purge,tjp_rawhtmlhead,tjp_accountreport,tjpblk_accountreport,tjp_export,tjpblk_export,tjp_resourcereport,tjpblk_resourcereport,tjp_taskreport,tjpblk_taskreport,tjp_textreport,tjpblk_textreport,tjp_tracereport,tjpblk_tracereport,tjp_right,tjp_rollupaccount,tjp_rollupresource,tjp_rolluptask,tjp_scenarios,tjp_selfcontained,tjp_sortaccounts,tjp_sortjournalentries,tjp_sortresources,tjp_sorttasks,tjp_start_report,tjp_resourceroot,tjp_taskroot,tjp_timeformat,tjp_timezone_report,tjp_title,tjp_width syn region tjpblk_status_timesheet start=/^\s*status\s.*{\s*$/ end=/^\s*}\s*$/ transparent contains=@tjpcommon,tjp_status_timesheet,tjp_details,tjp_flags_timesheet,tjp_summary contained syn region tjpblk_macro start=/macro\s\+\h\w*\s*\[/ end=/\]$/ transparent fold contains=ALL syn region tjpstring start=/"/ skip=/\\"/ end=/"/ syn region tjpstring start=/'/ skip=/\\'/ end=/'/ syn region tjpstring start=/\s-8<-$/ end=/^\s*->8-/ fold syn region tjpmlcomment start=+/\*+ end=+\*/+ syn sync fromstart set foldmethod=syntax hi def link tjp_macro PreProc hi def link tjp_supplement Function hi def link tjp_project Function hi def link tjpproperty Function hi def link tjpattribute Type hi def link tjparg Special hi def link tjpstring String hi def link tjpcomment Comment hi def link tjpmlcomment Comment hi def link tjpinclude Include hi def link tjpdate Constant hi def link tjptime Constant hi def link tjpnumber Number let b:current_syntax = "tjp" " Support running tj3 from within vim. Just type ':make your_project.tjp' to " activate it. set makeprg=tj3\ --silent " Support browsing the man page by typing Shift-k while having the cursor over " any syntax keyword set keywordprg=tj3man " Remap Ctrl-] to show full ID of property defined in the current " line. This requires a current ctags file (generated by 'tagfile' " report') to be present in the directory where vim was started. map :call ShowFullID() function! ShowFullID() let linenumber = line(".") let filename = bufname("%") execute "!grep '".filename."\t".linenumber.";' tags|cut -f 1" endfunction augroup TaskJugglerSource " Remove all trailing white spaces from line ends when saving files " Note: This overwrites the s mark. autocmd BufWritePre *.tj[ip] mark s | %s/\s\+$//e | normal `s augroup END taskjuggler-3.5.0/data/css/0000755000175000017500000000000012614413013015045 5ustar bernatbernattaskjuggler-3.5.0/data/css/tjmanual.css0000644000175000017500000000257712614413013017405 0ustar bernatbernatpre { font-size:16px; font-family: Courier; padding-left:8px; padding-right:8px; padding-top:0px; padding-bottom:0px; } p { margin-top:8px; margin-bottom:8px; } code { font-size:16px; font-family: Courier; } .table { background-color:#ABABAB; width:100%; } .tag { background-color:#E0E0F0; font-size:16px; font-weight:bold; padding-left:8px; padding-right:8px; padding-top:5px; padding-bottom:5px; } .descr { background-color:#F0F0F0; font-size:16px; padding-left:8px; padding-right:8px; padding-top:5px; padding-bottom:5px; } .attrtable { background-color:#ABABAB; } .attrtag { background-color:#F0F0F0; font-family: Courier; padding-left:6px; padding-right:6px; padding-top:3px; padding-bottom:3px; } .attrdescr { background-color:#F0F0F0; padding-left:6px; padding-right:6px; padding-top:3px; padding-bottom:3px; width:100%; } .codeframe{ border-width:2px; border-color:#ABABAB; border-style:solid; background-color:#F0F0F0; margin-top:8px; margin-bottom:8px; } .code { padding-left:15px; padding-right:15px; padding-top:0px; padding-bottom:0px; } div[codesection] { border-width:2px; border-color:#ABABAB; border-style:solid; background-color:#F0F0F0; margin-top:8px; margin-bottom:8px; } pre[codesection] { padding-left:15px; padding-right:15px; padding-top:0px; padding-bottom:0px; } taskjuggler-3.5.0/data/css/tjreport.css0000644000175000017500000002162312614413013017434 0ustar bernatbernat/* WARNING: This is an automatically generated file. DO NOT EDIT IT! * If you want to use your own style sheet, keep a master copy * somewhere else and copy it over this file if needed. This file will * be overwritten whenever the stylesheet that comes with TaskJuggler * has been modified. */ body { font-family:Bitstream Vera Sans, Tahoma, sans-serif; font-size:15px; } h1, h2, table, tr, td, div, span { } table { } /* Treat images in tables as block and not line elements. This will * eliminate surprising space at the bottom. */ td img {display: block;} p { font-size:15px; } td, div { padding:0px; margin:0px; } h1 { font-size:22px; } h2 { font-size:18px; } h3 { font-size:16px; } .tj_journal { font-size:11px; } i.tj_journal { font-size:9px; } h1.tj_journal { font-size:16px; margin-left:0px; } h2.tj_journal { font-size:14px; margin-left:10px; } h3.tj_journal { margin-top:5px; font-size:13px; margin-bottom:1px; margin-left:20px; } h4.tj_journal { font-size:12px; margin-left:30px; } p.tj_journal { margin-top:1px; margin-bottom:5px; margin-left:30px; } /* The basic elements of a text report page. */ .tj_text_page { width:100%; border-spacing:0px; } .tj_text_row { } .tj_column_left { vertical-align:top; } .tj_column_center { vertical-align:top; } .tj_column_right { vertical-align:top; } /* The top-level page layout */ .tj_page { margin: 35px 5% 25px 5%; } /* The container that holds report tables */ .tj_table_frame { margin-left:auto; margin-right:auto; text-align:center; background-color:#9a9a9a; margin-top:15px; margin-bottom:15px; border-spacing:1px; font-size:13px; } /* The headline box for report tables */ .tj_table_headline { font-size:16px; font-weight:bold; white-space:nowrap; padding:5px; margin:1px; text-align:center; color:#000000; background-color:#d4dde6; } .tj_table { background-color:#9a9a9a; margin:0px; border-spacing:1px; } /* The cells of the table header. */ .tj_table_header_cell { padding:1px 3px 1px 3px; white-space:nowrap; border-spacing:0px; color:#ffffff; overflow:hidden; } /* A regular table cell. It usually contains the cell icon, the text * label and a tooltip trigger. */ .tj_table_cell { font-size:13px; vertical-align:top; padding:1px 3px 1px 3px; margin:0px; width:100%; border-spacing:0px; position: relative; overflow:hidden; } /* The symbol is the icon to the left of the text label in a table * cell. */ .tj_table_cell_icon { vertical-align:top; text-align:right; padding:1px 3px 0px 0px; width:19px; } /* This is the text label of a cell. */ .tj_table_cell_label { font-size:12px; vertical-align:top; padding-top:1px; } /* The box around the icon to the right of the text label. This is * optional and triggers the tooltip with the full text of the cell in * case the cell is not large enough to show everything. */ .tj_table_cell_tooltip { font-size:13px; vertical-align:top; padding:2px 0px 0px 3px; } /* The container that holds the invisible tooltips. */ .tj_tooltip_box{ position:fixed; top:0px; left:0px; display:none; visibility:hidden; } /* The caption box for report tables */ .tj_table_caption { padding: 5px 13px 5px 13px; background-color:#ebf2ff; text-align:left; white-space:normal; margin:1px; font-size:13px } .tj_table_legend_frame { padding:5px; margin:1px; background-color:#d4dde6; } /* The legend of reports with calendar and Gantt charts */ .tj_table_legend { margin-left:auto; margin-right:auto; text-align:center; font-size:11px; color:#000000; border-spacing:1px; } /* A row of the table legend */ .tj_legend_row { height:19px; } /* Headlines used for the legend when both chart types are used in a * report */ .tj_legend_headline { font-size:12px; font-weight:bold; } /* A legend row has 3 items. An item contains a label and a symbol */ .tj_legend_item { } .tj_legend_symbol { position: relative; width:45px; height:19px; } .tj_legend_label { text-align: left; } .tj_legend_spacer { width:30px; } .tj_gantt_jag { position:absolute; border-style: solid; width: 0px; height: 0px; line-height: 0px; border-top: 5px solid black; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: none } .tj_diamond_top { position:absolute; border-style: solid; width: 0px; height: 0px; line-height: 0px; border-top: none; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 7px solid black; } .tj_diamond_bottom { position:absolute; border-style: solid; width: 0px; height: 0px; line-height: 0px; border-top: 7px solid black; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: none; } .tj_arrow_head { position:absolute; border-style: solid; width: 0px; height: 0px; line-height: 0px; border-top: 5px solid transparent; border-left: 5px solid black; border-right: none; border-bottom: 5px solid transparent; } .tabback { background-color:#9a9a9a; overflow:visible; } .tabfront { background-color:#d4dde6; } .tabhead { white-space:nowrap; background-color:#7a7a7a; color:#ffffff; text-align:center; } .tabhead_offduty { white-space:nowrap; background-color:#bdbdaa; color:#000000; } .tabfooter { white-space:nowrap; background-color:#9a9a9a; color:#ffffff; text-align:center; } .headercelldiv { padding-top:1px; padding-right:3px; padding-left:3px; padding-bottom:0px; white-space:nowrap; overflow:hidden; } .celldiv { padding:1px 3px 2px 3px; white-space:nowrap; overflow:hidden; position: relative; } .tabline { color:#000000 } .tabcell { white-space:nowrap; overflow:hidden; padding:0px; } .costaccountcell1 { background-color:#fff2eb; white-space:nowrap; padding:0px; } .costaccountcell2 { background-color:#ebdfd9; white-space:nowrap; padding:0px; } .revenueaccountcell1 { background-color:#cbffcc; white-space:nowrap; padding:0px; } .revenueaccountcell2 { background-color:#a6d0a6; white-space:nowrap; padding:0px; } .accountcell1 { background-color:#ebf2ff; white-space:nowrap; padding:0px; } .accountcell2 { background-color:#d9dfeb; white-space:nowrap; padding:0px; } .taskcell1 { background-color:#ebf2ff; white-space:nowrap; padding:0px; } .taskcell2 { background-color:#d9dfeb; white-space:nowrap; padding:0px; } .resourcecell1 { background-color:#fff2eb; white-space:nowrap; padding:0px; } .resourcecell2 { background-color:#ebdfd9; white-space:nowrap; padding:0px; } /* The *2 versions have a 20 points less HSV value of the *1 versions*/ .busy1 { background-color:#ff3b3b; } .busy2 { background-color:#eb3636; } .loaded1 { background-color:#ff9b9b; } .loaded2 { background-color:#eb8f8f; } .free1 { background-color:#a5ffb4; } .free2 { background-color:#98eba6; } .offduty1 { background-color:#bdbdaa; } .offduty2 { background-color:#a9a999; } .calconttask1 { background-color:#abbeae; } .calconttask2 { background-color:#99aa9c; } .caltask1 { background-color:#2050e5; } .caltask2 { background-color:#1c4ad1; } .todo1 { background-color:#beabab; } .todo2 { background-color:#aa9999; } .tabvline { background-color:#9a9a9a; position:absolute; } .tj_gantt_frame { position:absolute; /* Make sure this element is above all other elements */ z-index:100; } .containerbar { background-color:#09090a; position:absolute; } .taskbarframe { background-color:#09090a; position:absolute; } .taskbar { background-color:#2f57ea; position:absolute; } .progressbar { background-color:#36363f; position:absolute; } .milestone { background-color:#09090a; position:absolute; } .loadstackframe { background-color:#452a2a; position:absolute; } .free { background-color:#a5ffb5; position:absolute; } .busy { background-color:#ff9b9b; position:absolute; } .assigned { background-color:#ff3b3b; position:absolute; } .offduty { background-color:#bdbdaa; white-space:nowrap; position:absolute; } .depline { background-color:#000000; position:absolute; } .nowline { background-color:#EE0000; position:absolute; } .white { background-color:#FFFFFF; position:absolute; } .navbar_topruler { margin:7px 0px 0px 0px; } .navbar_midruler { margin:5px 0px 0px 0px; } .navbar_bottomruler { margin:5px 0px 7px 0px; } .navbar_current { background-color:#606060; font-size:13px; font-weight:bold; padding:5px; color:#FFFFFF; } .navbar_other { background-color:#FFFFFF; font-size:13px; font-weight:bold; padding:2px; } .navbar { font-size:20px; font-weight:bold; padding:0px; } .copyright { font-size:9px; color:#101010; text-align:center; margin-top:10px; } div[codesection] { border-width:2px; border-color:#ABABAB; border-style:solid; background-color:#F0F0F0; margin-top:8px; margin-bottom:8px; } pre[codesection] { padding-left:15px; padding-right:15px; padding-top:0px; padding-bottom:0px; } taskjuggler-3.5.0/data/icons/0000755000175000017500000000000012614413013015370 5ustar bernatbernattaskjuggler-3.5.0/data/icons/trend-down.png0000644000175000017500000000136412614413013020163 0ustar bernatbernatPNG  IHDR;֕JsRGBbKGD pHYs^tIME 7VDtIDAT(ύ=LSQϽVPj+1$ƁM\\4#1qq0 ~ :"1U ! M *5@j$}}:hRxƓ'CP3zд|J閾ʬTi, f( L+hdv*B(#7LBN/Av0E^@;ryұy#۱횢fɛd` s˩O $jz{jY"-A7L˜9C1mY4PzUږ5,1s{dOͲ}0 *QBe 6i0sr4bx:wklw\DsUCU58$b. _fgsSω$Pg`Fѯ=b1fC`:lPNp[!Xͦ7pȹ}Už!S#ɅGx ]-uCr2ndݘ-@(dZ5KǪ7n9e$4148S?+=b &t$BQ0>\E)JolȾKW]>ZOv7=xYuT.dQ5tM៣MIENDB`taskjuggler-3.5.0/data/icons/taskgroup.png0000644000175000017500000000075012614413013020117 0ustar bernatbernatPNG  IHDR醟sRGB pHYs  tIME 67ftEXtCommentCreated with GIMPWUIDAT8˭JQ= ^/ ,}Bl7lE,D,, +kVEh3(**|p\爙}reg%]t y7: \VdfTu&} Vm_Jq֛|Oٵ=0#2,; P_Li79HEkIENDB`taskjuggler-3.5.0/data/icons/trend-flat.png0000644000175000017500000000133712614413013020142 0ustar bernatbernatPNG  IHDR;֕JsRGBbKGD pHYs^tIME 6H2_IDAT(ϕMHTQsϹW&jifL!CB #Irf 7A G]#bbCEd!)*Mo9(Ȟ~/M}&T5t{>0w]rGXuk3*{t7Z.mő3 f-Uepg&(ͽYxA2o+g7y?t`QZtL `h>2!wGZF>Nfl\&ˠf؜B-8NKVvxeBXP !>!-e4p|3?K|=u+ljz`8$qUTUR eAI(BX -'a2Wf2-yS pGDeHB `r$F U#ɗ5[Z#or B"XT!T`6$ [׃ޱ"r E K\<$%> ŧ v=V Ug7lrMum'=muHrtQ# K;*j B ν.mr%n+,[h_Lz"wF ;O'IENDB`taskjuggler-3.5.0/data/icons/flag-yellow.png0000644000175000017500000000110112614413013020311 0ustar bernatbernatPNG  IHDR;֕JsRGBbKGD pHYs7\7\ǤtIME  [.9IDAT(ϝMkA3;ɺtcb%x/~ ?7/**^z[xGAAJb)-l`6ٙCКKayghW\ƜJ{54@(~yਙW( kT|toeC5?~ V7t?u. X垦5-cVJVOJgGx5%5- Q<1_R *T~\|$y2 !9D,h!ˆt:}vJ.V(ZEeaE&& =Nk=go,׷6N |mߺ9|5RUDU@`'.24z}uln3+cs'wxZs|pe(]pxťS@^$[>P.< (OzIENDB`taskjuggler-3.5.0/data/icons/flag-red.png0000644000175000017500000000110212614413013017551 0ustar bernatbernatPNG  IHDR;֕JsRGBbKGD pHYs7\7\ǤtIME  )IDAT(ϝMkSA{ܐmըE)]WܸFDRHbTJ臘6)6Mn" $fSY <Jҋ[wg8/k5k7<'owxC  ~Y= r vU:{q2KFak-9jTpҜB7 SDR 0fgB =O }ErIENDB`taskjuggler-3.5.0/data/icons/resource.png0000644000175000017500000000132212614413013017723 0ustar bernatbernatPNG  IHDR;֕JsRGB pHYs  tIME ȏ`dIDAT(uKTQǿ͛y31&ǩ&C[ƅR`AfEmEPAXgFDaY 5lqF̼wo KFcŹ|?/vEU@MdgWS__?v\ŭ^sg_DkN>Zf8r`'CKuqGJ, Qe9iy>gpԚ9G2UHBSpE F.99'9p/yAi #vI8mg(z f m ?bo}xrb 3bfb+ cadl55]rr[o ˧ 3 f@A"GUEV^+/.nkn:"D0" 7ŭܷӇ{=2d,!bݞ\K?0=0U}܊ A\ `:(G.tFwx0uGI HD)s$l0 DžAz> q YHpEsϮ%pm%W :\,}1jˉwq&5TF7z{]8ސ00fg7BF'\ (gIENDB`taskjuggler-3.5.0/data/icons/details.png0000644000175000017500000000135012614413013017522 0ustar bernatbernatPNG  IHDR;֕JsRGBbKGD pHYs  tIME  :^HhIDAT(u_ha?Mha9"XM֔դą 7Db4 MZcsFNg"{?yy_Խgө`qeõ4 @X0=1M}l[ɸw<|eO%%b@.+}? /0(B%*eDXxI,9T^aj&,ɻV0/ӳ)a SrZgI/iMIENDB`taskjuggler-3.5.0/data/icons/task.png0000644000175000017500000000075612614413013017050 0ustar bernatbernatPNG  IHDR醟sRGB pHYs  tIME 4&IetEXtCommentCreated with GIMPW[IDAT8˭;/DQ3{z#⑬R/J@Ӑ(D Bh"bٽEONr$13mV?߽ώa DQ3+s\k|%ᢒ%3c=È"O̲D3HROC jJ>V)#8ev#")f:@$* ǎ!I]*5S|T:PAra φLd˅ 7gc/o9QOAٟm_WmM zk{6! 2KZc/y&^cIENDB`taskjuggler-3.5.0/data/icons/flag-green.png0000644000175000017500000000110712614413013020104 0ustar bernatbernatPNG  IHDR;֕JsRGBbKGD pHYs7\7\ǤtIME  ֶĉIDAT(ϝѻnQw7Bb8 J !x:x )4<5BTI J@ !"YBB6!pgPH7QF;)f_?]pH p0:F[oug =O,=~MRy욮nVNN-Bl(&( mj_KO.tu>R2aK`cK,QPF$͔3KmHǴǝb /B;yc#Hs/ԑGǥ?7S czo/3p[Vxa~i8Y+}[7rۿvI/8eR y=!dJ.ߝ=܉n {88R*b {﵈%L]3a:lhu j@24?wIENDB`taskjuggler-3.5.0/data/icons/resourcegroup.png0000644000175000017500000000154212614413013021004 0ustar bernatbernatPNG  IHDR;֕JsRGB pHYs  tIME 4$MIDAT(]]h[e䜓&'icf4 WuuBEA MŁ/AB'"N/J2vXZs:ɺ,ےiNNr~>/p~^O=6>O}0сswOo:ʝ}UY:Y宅e*[f>3 [/{ߓָ w~)ޔ>P[zg/m/^2|V9PM)&CD: A@j@^|P3[H)w祕Xm/"LTo'g;wmkj.e'KNbEI4v{JmfruMGu]nS݁؃hQL~U;Dn # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 $:.unshift File.dirname(__FILE__) require 'test/unit' require 'fileutils' require 'MessageChecker' require 'taskjuggler/Tj3Config' require 'taskjuggler/TaskJuggler' class TestReportGenerator < Test::Unit::TestCase include MessageChecker def setup @tmpDir = 'tmp-test_ReportGenerator' Dir.delete(@tmpDir) if File.directory?(@tmpDir) Dir.mkdir(@tmpDir) AppConfig.appName = 'taskjuggler3' ENV['TASKJUGGLER_DATA_PATH'] = './:../' end def teardown FileUtils::rm_rf(@tmpDir) end def test_ReportGeneratorErrors path = File.dirname(__FILE__) + '/' Dir.glob(path + 'TestSuite/ReportGenerator/Errors/*.tjp').each do |f| ENV['TZ'] = 'Europe/Berlin' (mh = TaskJuggler::MessageHandlerInstance.instance).reset mh.outputLevel = :none mh.trapSetup = true begin tj = TaskJuggler.new assert(tj.parse([ f ]), "Parser failed for #{f}") assert(tj.schedule, "Scheduler failed for #{f}") tj.warnTsDeltas = true tj.generateReports(@tmpDir) rescue TaskJuggler::TjRuntimeError end checkMessages(tj, f) end end def test_ReportGeneratorCorrect path = File.dirname(__FILE__) + '/' Dir.glob(path + 'TestSuite/ReportGenerator/Correct/*.tjp').each do |f| ENV['TZ'] = 'Europe/Berlin' (mh = TaskJuggler::MessageHandlerInstance.instance).reset mh.outputLevel = :none tj = TaskJuggler.new assert(tj.parse([ f ]), "Parser failed for #{f}") assert(tj.schedule, "Scheduler failed for #{f}") assert(tj.generateReports(@tmpDir), "Report generator failed for #{f}") assert(mh.messages.empty?, "Unexpected error in #{f}") checkReports(f) end end private def checkReports(tjpFile) baseName = File.basename(tjpFile)[0..-5] dirName = File.dirname(tjpFile) counter = 0 Dir.glob(dirName + "/refs/#{baseName}-[0-9]*").each do |ref| reportName = File.basename(ref) assert(FileUtils.compare_file(ref, "#{@tmpDir}/#{reportName}"), "Comparison of report #{reportName} of test case #{tjpFile} failed") counter += 1 end assert(counter > 0, "Project #{tjpFile} has no reference report") end end taskjuggler-3.5.0/test/test_SimpleQueryExpander.rb0000644000175000017500000000246112614413013021660 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_SimpleQueryExpander.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/SimpleQueryExpander' require 'taskjuggler/MessageHandler' class TestSimpleQueryExpander < Test::Unit::TestCase class Scenario def id 'scId' end end class Project def initialize end def scenario(foo) Scenario.new end end class Query def initialize end def process end def project Project.new end def scenarioIdx 0 end def attributeId=(value) end def ok true end def to_s 'XXX' end end def setup end def teardown end def test_expand exp = TaskJuggler::SimpleQueryExpander.new('foo <-bar-> foo', Query.new, nil) assert_equal('foo XXX foo', exp.expand) end end taskjuggler-3.5.0/test/all.rb0000644000175000017500000000074612614413013015427 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = all.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # Dir.glob(File.dirname(__FILE__) + '/test_*.rb').each { |f| require f } taskjuggler-3.5.0/test/test_Export-Reports.rb0000644000175000017500000000774612614413013020642 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_Export-Reports.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 $:.unshift File.dirname(__FILE__) require 'stringio' require 'test/unit' require 'MessageChecker' require 'taskjuggler/TaskJuggler' require 'taskjuggler/AlgorithmDiff' class TaskJuggler class TestExportReport < Test::Unit::TestCase include MessageChecker # This function captures the $stdout output of the passed block to a String # and returns it. def captureStdout oldStdOut = $stdout $stdout = (out = StringIO.new) begin yield ensure $stdout = oldStdOut end out.string end # This functions redirects all output of the passed block to a new file with # the name fileName. def stdoutToFile(fileName) oldStdOut = $stdout $stdout = File.open(fileName, 'w') begin yield $stdout.close ensure $stdout = oldStdOut end end # Compare the output Export (passed as String in _out_) with the content of # the Export reference files _refFile_. def compareExports(out, refFile, testCase) ref = File.new(refFile, 'r').read diff = ref.extend(DiffableString).diff(out).to_s if diff != '' File.new('failed.tjp', 'w').write(out) end assert_equal('', diff, "output for #{testCase} does not match " + "#{refFile}:\n#{diff}") end def checkExportReport(projectFile, repFile, refFile) tj = TaskJuggler.new assert(tj.parse([ projectFile, repFile ]), "Parser failed for #{projectFile}") # Schedule the project. assert(tj.schedule, "Scheduler failed for #{projectFile}") tj.project.reports.each do |report| next unless report.get('formats').include?(:tjp) if File.file?(refFile) # If there is a reference Export file for this test case, compare the # output against it. out = captureStdout do assert(tj.generateReport(report.fullId, false), "Report generation failed for #{projectFile}") end compareExports(out, refFile, projectFile) else # If not, we generate the reference file. stdoutToFile(refFile) do assert(tj.generateReport(report.fullId, false), "Reference file generation failed for #{projectFile}") end end end assert(MessageHandlerInstance.instance.messages.empty?, "Unexpected error in #{projectFile}") end def test_Export_Reports path = File.dirname(__FILE__) ENV['TEST1'] = 't_e_s_t_1' ENV['TEST2'] = '"A test String"' ENV['TEST3'] = '3' testDir = path + '/TestSuite/Syntax/Correct/' Dir.glob(testDir + '*.tjp').each do |f| # We ignore some test cases that cannot work in this setup. next if %w( Freeze.tjp Export.tjp ).include?(f[testDir.length..-1]) # Take the project, schedule it, check it against the reference and # export it. Then check the export against the reference file. refFile = refFileName(f) repFile = reportDefFileName(f) checkExportReport(f, repFile, refFile) checkExportReport(refFile, repFile, refFile) end end private def refFileName(originalFile) baseDir = File.dirname(originalFile) baseName = File.basename(originalFile, '.tjp') baseDir + "/../../Export-Reports/refs/#{baseName}.tjp" end def reportDefFileName(originalFile) File.dirname(originalFile) + "/../../Export-Reports/export.tji" end end end taskjuggler-3.5.0/test/test_Project.rb0000644000175000017500000000260012614413013017313 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_Project.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/Project' class TaskJuggler class TestProject < Test::Unit::TestCase def setup end def teardown end def test_helloWorld p = TaskJuggler::Project.new('hello', 'Hello World', '1.0') p['start'] = TjTime.new('2008-07-24') p['end'] = TjTime.new('2008-08-31') assert_equal(p['projectid'], 'hello') assert_equal(p['name'], 'Hello World') assert_equal(p['version'], '1.0') assert_equal(p.scenarioCount, 1) assert_equal(p.scenarioIdx('plan'), 0) assert_equal(p.scenario(0), p.scenario('plan')) p['rate'] = 100.0 assert_equal(p['rate'], 100.0) t = Task.new(p, 'foo', 'Foo', nil) t['start', 0] = TjTime.new('2008-07-25-9:00') t['duration', 0] = 10 assert_equal(p.task('foo'), t) p.schedule assert_equal(TjTime.new('2008-07-25-19:00'), t['end', 0]) p.generateReports(1) end end end taskjuggler-3.5.0/test/test_Journal.rb0000644000175000017500000001363612614413013017332 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_Journal.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/Project' class TaskJuggler class TestJournal < Test::Unit::TestCase class PTNMockup attr_reader :index def initialize(index) @index = index end def ptn self end end def setup @p = TaskJuggler::Project.new('hello', 'Hello World', '1.0') @p['start'] = tm('2009-11-01') @p['end'] = tm('2009-12-31') @j = @p['journal'] end def teardown end def test_add # First some simple add tests. @j.addEntry(e = JournalEntry.new(@j, tm('2009-11-29'), "E1", PTNMockup.new(1))) assert_equal(1, @j.entries.count) # Make sure we don't add the same entry twice. @j.addEntry(e) assert_equal(1, @j.entries.count) @j.addEntry(JournalEntry.new(@j, tm('2009-11-30'), "E2", PTNMockup.new(2))) @j.addEntry(JournalEntry.new(@j, tm('2009-12-01'), "E3", PTNMockup.new(3))) assert_equal(3, @j.entries.count) end def test_sort # Add a bunch of entries and see if the sorting by date works properly. @j.addEntry(JournalEntry.new(@j, tm('2009-12-10'), "E4", PTNMockup.new(4))) @j.addEntry(JournalEntry.new(@j, tm('2009-12-03'), "E2", PTNMockup.new(2))) @j.addEntry(JournalEntry.new(@j, tm('2009-12-06'), "E3", PTNMockup.new(3))) @j.addEntry(JournalEntry.new(@j, tm('2009-11-29'), "E0", PTNMockup.new(0))) @j.addEntry(JournalEntry.new(@j, tm('2009-12-01'), "E1", PTNMockup.new(1))) @j.addEntry(JournalEntry.new(@j, tm('2009-12-24'), "E5", PTNMockup.new(5))) pList = [] @j.entries.each { |e| pList << e.property } pList.each do |i| assert_equal(pList.index(i), i.index) end end def test_sortSameDate # Add a bunch of entries and see if the sorting by date works properly. @j.addEntry(e = JournalEntry.new(@j, tm('2009-12-10'), "A2", PTNMockup.new(0))) e.alertLevel = 2 @j.addEntry(e = JournalEntry.new(@j, tm('2009-12-10'), "A0", PTNMockup.new(0))) e.alertLevel = 0 @j.addEntry(e = JournalEntry.new(@j, tm('2009-12-10'), "A1", PTNMockup.new(0))) e.alertLevel = 1 @j.addEntry(e = JournalEntry.new(@j, tm('2009-12-10'), "A3", PTNMockup.new(0))) e.alertLevel = 3 i = 0 @j.entries.each do |entry| assert_equal(i, entry.alertLevel) i += 1 end end def test_currentEntries createTaskTree q = Query.new q.scenarioIdx = 0 # Set a 0 alert for a task a1 = addAlert('2009-11-29', 0, t = task('p1.m1.l1')) ce = @j.currentEntriesR(tm('2009-12-05'), t, 0, nil, q) assert_equal(1, ce.count) assert_equal(a1, ce[0]) # Add a later alert for the same task a2 = addAlert('2009-11-30', 0, t = task('p1.m1.l1')) ce = @j.currentEntriesR(tm('2009-12-05'), t, 0, nil, q) assert_equal(1, ce.count) assert_equal(a2, ce[0]) # Add another alert to the sister task and check parent a3 = addAlert('2009-11-30', 0, t = task('p1.m1.l2')) ce = @j.currentEntriesR(tm('2009-12-05'), task('p1.m1'), 0, nil, q) assert_equal(2, ce.count) assert_equal(a2, ce[0]) assert_equal(a3, ce[1]) # Check root task ce = @j.currentEntriesR(tm('2009-12-05'), task('p1'), 0, nil, q) assert_equal(2, ce.count) assert_equal(a2, ce[0]) assert_equal(a3, ce[1]) # Add old override alert to p1.m1 addAlert('2009-11-29', 0, t = task('p1.m1')) ce = @j.currentEntriesR(tm('2009-12-05'), task('p1'), 0, nil, q) assert_equal(2, ce.count) assert_equal(a2, ce[0]) assert_equal(a3, ce[1]) # Add new override alert to p1.m1 a4 = addAlert('2009-12-01', 0, t = task('p1.m1')) ce = @j.currentEntriesR(tm('2009-12-05'), task('p1'), 0, nil, q) assert_equal(1, ce.count) assert_equal(a4, ce[0]) end def test_alertSimple createTaskTree q = Query.new q.scenarioIdx = 0 # Set a 0 alert for a task addAlert('2009-11-29', 0, t = task('p1.m1.l1')) assert_equal(0, @j.alertLevel(tm('2009-12-01'), t, q)) # Now add a later 1 alert addAlert('2009-12-01', 1, t) assert_equal(1, @j.alertLevel(tm('2009-12-02'), t, q)) # Set a 2 alert for p1.m1.l2 addAlert('2009-11-29', 2, task('p1.m1.l2')) assert_equal(2, @j.alertLevel(tm('2009-12-01'), task('p1'), q)) # Overide p1.m1 with 0 alert addAlert('2009-12-01', 0, task('p1.m1')) assert_equal(0, @j.alertLevel(tm('2009-12-01'), task('p1'), q)) end private def tm(s) TjTime.new(s) end def addAlert(date, level, property) raise "No property" unless property @j.addEntry(e = JournalEntry.new(@j, tm(date), "Set #{property.fullId} " + "to level #{level}i at #{date}", property)) e.alertLevel = level e end def createTaskTree p1 = newTask(nil, 'p1') m1 = newTask(p1, 'm1') newTask(p1, 'm2') newTask(m1, 'l1') newTask(m1, 'l2') newTask(nil, 'p2') end def newTask(parent, id) Task.new(@p, id, 'Task #{id}', parent) end def task(id) @p.task(id) || ( raise "Unknown task id #{id}" ) end end end taskjuggler-3.5.0/test/test_Limits.rb0000644000175000017500000001064312614413013017154 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_Limits.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/Limits' require 'taskjuggler/Project' require 'taskjuggler/TjTime' class TaskJuggler class TestLimits < Test::Unit::TestCase def setup @p = Project.new('p1', 'p 1', '1.0') @p['start'] = TjTime.new('2009-01-21') @p['end'] = TjTime.new('2009-03-01') end def teardown @p = nil end def test_new l1 = Limits.new l1.setProject(@p) l2 = Limits.new(l1) assert_equal(l1.project, l2.project, "Copy constructor failed") end def test_setLimit l = Limits.new l.setProject(@p) l.setLimit('dailymax', 4) assert_equal(l.limits.length, 1, 'setLimits() failed') l.setLimit('dailymax', 6) assert_equal(l.limits.length, 1, 'setLimits() replace failed') l.setLimit('weeklymax', 20) assert_equal(l.limits.length, 2, 'setLimits() failed') end def test_inc l = Limits.new l.setProject(@p) l.setLimit('weeklymax', 2, ScoreboardInterval.new(@p['start'], @p['scheduleGranularity'], TjTime.new('2009-02-10'), TjTime.new('2009-02-15'))) # Outside of limit interval, should be ignored l.inc(-1) l.inc(100000) assert(l.ok?) # Inside the calendar week interval l.inc(dateToIdx('2009-02-09-10:00')) assert(l.ok?) # The inc will exceed the weekly limit l.inc(dateToIdx('2009-02-09-11:00')) assert(!l.ok?) end def test_ok l = Limits.new l.setProject(@p) l.setLimit('dailymax', 4) assert_equal(l.limits.length, 1, 'setLimits() failed') l.inc(dateToIdx('2009-02-01-10:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-11:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-12:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-13:00')) assert(!l.ok?) assert(l.ok?(dateToIdx('2009-01-31'))) assert(!l.ok?(dateToIdx('2009-02-01'))) assert(l.ok?(dateToIdx('2009-02-01'), false)) end def test_with_resource_1 l = Limits.new l.setProject(@p) l.setLimit('dailymax', 4) r = Resource.new(@p, 'r', 'R', nil) l.setLimit('dailymax', 5, nil, r) l.inc(dateToIdx('2009-02-01-10:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-11:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-12:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-13:00')) assert(!l.ok?) assert(!l.ok?(nil, true, r)) end def test_with_resource_2 l = Limits.new l.setProject(@p) l.setLimit('dailymax', 5) r = Resource.new(@p, 'r', 'R', nil) l.setLimit('dailymax', 1, nil, r) l.inc(dateToIdx('2009-02-01-10:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-11:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-12:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-13:00')) assert(l.ok?) assert(l.ok?(nil, true, r)) l.inc(dateToIdx('2009-02-01-14:00'), r) assert(!l.ok?) assert(!l.ok?(nil, true, r)) end def test_with_resource_3 l = Limits.new l.setProject(@p) l.setLimit('dailymax', 5) r = Resource.new(@p, 'r', 'R', nil) l.setLimit('dailymax', 3, nil, r) l.inc(dateToIdx('2009-02-01-10:00'), r) assert(l.ok?) l.inc(dateToIdx('2009-02-01-11:00')) assert(l.ok?) l.inc(dateToIdx('2009-02-01-12:00'), r) assert(l.ok?) l.inc(dateToIdx('2009-02-01-13:00'), r) assert(l.ok?) assert(!l.ok?(nil, true, r)) end def test_with_resource_4 l = Limits.new l.setProject(@p) l.setLimit('dailymax', 2) r = Resource.new(@p, 'r', 'R', nil) l.setLimit('dailymax', 3, nil, r) l.inc(dateToIdx('2009-02-01-10:00'), r) assert(l.ok?) l.inc(dateToIdx('2009-02-01-11:00')) assert(!l.ok?) l.inc(dateToIdx('2009-02-01-12:00'), r) assert(!l.ok?) assert(!l.ok?(nil, true, r)) l.inc(dateToIdx('2009-02-01-13:00'), r) assert(!l.ok?) assert(!l.ok?(nil, true, r)) end private def dateToIdx(date) @p.dateToIdx(TjTime.new(date)) end end end taskjuggler-3.5.0/test/test_Scheduler.rb0000644000175000017500000000334212614413013017627 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_Scheduler.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 $:.unshift File.dirname(__FILE__) require 'test/unit' require 'MessageChecker' require 'taskjuggler/TaskJuggler' class TestScheduler < Test::Unit::TestCase include MessageChecker def test_SchedulerErrors path = File.dirname(__FILE__) + '/' Dir.glob(path + 'TestSuite/Scheduler/Errors/*.tjp').each do |f| ENV['TZ'] = 'Europe/Berlin' (mh = TaskJuggler::MessageHandlerInstance.instance).reset mh.outputLevel = :none mh.trapSetup = true begin tj = TaskJuggler.new assert(tj.parse([ f ]), "Parser failed for #{f}") tj.warnTsDeltas = true tj.schedule rescue TaskJuggler::TjRuntimeError end checkMessages(tj, f) end end def test_SchedulerCorrect path = File.dirname(__FILE__) + '/' Dir.glob(path + 'TestSuite/Scheduler/Correct/*.tjp').each do |f| ENV['TZ'] = 'Europe/Berlin' (mh = TaskJuggler::MessageHandlerInstance.instance).reset mh.outputLevel = :none mh.trapSetup = true begin tj = TaskJuggler.new assert(tj.parse([ f ]), "Parser failed for #{f}") assert(tj.schedule, "Scheduler failed for #{f}") rescue TaskJuggler::TjRuntimeError end checkMessages(tj, f) end end end taskjuggler-3.5.0/test/test_LogicalExpression.rb0000644000175000017500000000602012614413013021337 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_LogicalExpression.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/LogicalExpression' class TaskJuggler class TestLogicalExpression < Test::Unit::TestCase def setup end def teardown end def test_unaryOperations parameter = [ [ true, '~', false ], [ false, '~', true ] ] parameter.each do |op, operator, result| exp = LogicalExpression.new(unaryOp(op, operator)) assert_equal(result, exp.eval(nil), "Operation #{operator} #{op} -> #{result} failed") end end def test_binaryOperations parameter = [ [ 2, '<', 3, true ], [ 3, '<', 2, false ], [ 2, '<', 2, false ], [ 4, '>', 5, false ], [ 5, '>', 4, true ], [ 5, '>', 5, false ], [ 'a', '>', 'b', false ], [ 'b', '>', 'a', true ], [ 2, '<=', 3, true ], [ 3, '<=', 2, false ], [ 2, '<=', 2, true], [ 4, '>=', 5, false ], [ 5, '>=', 4, true ], [ 5, '>=', 5, true], [ 'A', '>=', 'B', false ], [ 'A', '>=', 'A', true ], [ 'B', '>=', 'B', true ], [ 6, '=', 5, false ], [ 6, '=', 6, true], [ 'c', '=', 'c', true ], [ 'x', '=', 'y', false ], [ '', '=', '', true ], [ '', '=', 'a', false ], [ true, '&', true, true ], [ true, '&', false, false ], [ false, '&', true, false ], [ false, '&', false, false ], [ 1, '&', 1, true ], [ 1, '&', 0, false ], [ 0, '&', 1, false ], [ 0, '&', 0, false ], [ true, '|', true, true ], [ true, '|', false, true ], [ false, '|', true, true ], [ false, '|', false, false ] ] parameter.each do |op1, operator, op2, result| exp = LogicalExpression.new(binaryOp(op1, operator, op2)) assert_equal(result, exp.eval(nil), "Operation #{op1} #{operator} #{op2} -> #{result} failed") end end def test_operationTrees op1 = binaryOp(2, '<', 4) op2 = binaryOp(3, '>', 6) exp = LogicalExpression.new(binaryOp(op1, '|', op2)) assert_equal(true, exp.eval(nil), "Operation #{exp} -> true failed") end def test_exceptions begin exp = LogicalExpression.new(binaryOp(false, '<', true)) assert_raise TjException do exp.eval(nil) end rescue TjException end end private def binaryOp(op1, operator, op2) LogicalOperation.new(LogicalOperation.new(op1), operator, LogicalOperation.new(op2)) end def unaryOp(op, operator) LogicalOperation.new(LogicalOperation.new(op), operator) end end end taskjuggler-3.5.0/test/test_ShiftAssignments.rb0000644000175000017500000000555212614413013021207 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_ShiftAssignments.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/Project' class TestPropertySet < Test::Unit::TestCase def setup TaskJuggler::ShiftAssignments.sbClear TaskJuggler::MessageHandlerInstance.instance.reset @p = TaskJuggler::Project.new('p', 'Project', '1.0') @p['start'] = TaskJuggler::TjTime.new('2008-07-29') @p['end'] = TaskJuggler::TjTime.new('2008-08-31') @s1 = TaskJuggler::Shift.new(@p, 's1', "Shift2", nil).scenario(0) @s2 = TaskJuggler::Shift.new(@p, 's2', "Shift1", nil).scenario(0) end def teardown @p = @s1 = @s2 = nil TaskJuggler::ShiftAssignments.sbClear end def test_finalizer sas1 = TaskJuggler::ShiftAssignments.new sas1.project = @p assert_equal(0, TaskJuggler::ShiftAssignments.scoreboards.length) sas1.addAssignment(TaskJuggler::ShiftAssignment.new(@s1, TaskJuggler::TimeInterval.new(TaskJuggler::TjTime.new('2008-08-01'), TaskJuggler::TjTime.new('2008-08-05')))) assert_equal(1, TaskJuggler::ShiftAssignments.scoreboards.length) # Call finalizer directly to check for runtime errors that would otherwise # go unnoticed. TaskJuggler::ShiftAssignments.deleteScoreboard(sas1.object_id) assert_equal(0, TaskJuggler::ShiftAssignments.scoreboards.length) end def test_SBsharing sas1 = TaskJuggler::ShiftAssignments.new sas1.project = @p assert_equal(0, TaskJuggler::ShiftAssignments.scoreboards.length) sas1.addAssignment(TaskJuggler::ShiftAssignment.new(@s1, TaskJuggler::TimeInterval.new(TaskJuggler::TjTime.new('2008-08-01'), TaskJuggler::TjTime.new('2008-08-05')))) sas2 = TaskJuggler::ShiftAssignments.new sas2.project = @p sas2.addAssignment(TaskJuggler::ShiftAssignment.new(@s1, TaskJuggler::TimeInterval.new(TaskJuggler::TjTime.new('2008-08-01'), TaskJuggler::TjTime.new('2008-08-05')))) assert_equal(1, TaskJuggler::ShiftAssignments.scoreboards.length) sas3 = TaskJuggler::ShiftAssignments.new sas3.project = @p sas3.addAssignment(TaskJuggler::ShiftAssignment.new(@s2, TaskJuggler::TimeInterval.new(TaskJuggler::TjTime.new('2008-08-01'), TaskJuggler::TjTime.new('2008-08-05')))) assert_equal(2, TaskJuggler::ShiftAssignments.scoreboards.length) sas1 = sas2 = sas3 = nil end end taskjuggler-3.5.0/test/TestSuite/0000755000175000017500000000000012614413013016254 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/ReportGenerator/0000755000175000017500000000000012614413013021376 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/0000755000175000017500000000000012614413013022777 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/Journal.tjp0000644000175000017500000000205212614413013025127 0ustar bernatbernatproject "Journal" 2010-06-07 +1m flags foo, bar resource a1 "A1" resource a2 "A2" resource a3 "A3" task "T" { duration 3w journalentry 2010-06-07-8:00 "Entry 1" { flags foo, bar author a1 summary "Summary 1" details "Deails 1" } journalentry 2010-06-14-8:00 "Entry 2" { flags foo author a2 summary "Summary 1" details "Deails 1" } journalentry 2010-06-21-8:00 "Entry 3" { flags bar author a3 summary "Summary 1" details "Deails 1" } } textreport "Journal-0" { formats html journalmode journal journalattributes * sortjournalentries date.up center "<-query attribute='journal'->" } taskreport "Journal-1" { formats csv journalmode alerts_down # Only "Entry 2" should be included columns name, journal { period 2010-06-14 +1w } } taskreport "Journal-2" { formats csv # Only "Entry 1" should be included columns name, journal hidejournalentry ~(foo & bar) } textreport "Journal-3" { formats html hidejournalentry foo left -8<- <-query attribute='journal'-> ->8- } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/LogicalFunctions2.tjp0000644000175000017500000000042212614413013027041 0ustar bernatbernatproject "LogFunc2" 2010-06-07 +1m resource r1 "R1" resource r2 "R2" { vacation 2010-06-14 +7d } task "Task" { effort 25d allocate r1, r2 } resourcereport "LogicalFunctions2-1" { formats csv period 2010-06-14 +7d hideresource ~isactive(plan) columns name } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/JournalMode.tjp0000644000175000017500000000450712614413013025743 0ustar bernatbernatproject "Test" 2011-08-01 +1m { now 2011-08-15 trackingscenario plan } flags foo resource r1 "R1" resource r2 "R2" resource r3 "R3" resource r4 "R4" task t1 "T1" { task t2 "T2" { effort 1w allocate r1 } task t3 "T3" { effort 1w allocate r2 journalentry 2011-08-08 "r1 had to help out" { author r2 } } } # Timesheets 1st week timesheet r1 2011-08-01 +1w { task t1.t2 { work 3d remaining 6d status green "All good" { summary "No problems found" details "Work is progressing well" } } task t1.t3 { work 1d remaining 2d status yellow "Helped out" { summary "r2 asked for d2" } } newtask t4 "T4" { work 1d remaining 3d status red "Big Problem" { flags foo summary "Had to help out" details "Unplanned distraction" } } } timesheet r3 2011-08-01 +1w { status yellow "Not feeling good" { summary "Will see the doctor again" } newtask sick "Out sick" { work 5d remaining 2d status green "Hope to feel better soon" } } # Timesheet 2nd week timesheet r1 2011-08-01 +1w { task t1.t2 { work 5d remaining 1d status green "All ok" } task t1.t3 { work 0d remaining 2d status red "Fire burning" { details "It's really hot" } } } statussheet r4 2011-08-09 { task t1 { status green "Don't panik!" } } macro columns [ columns name, journal { title "journal" celltext 1 "<-query attribute='journal' journalmode='journal'->" }, journal { title "journal_sub" celltext 1 "<-query attribute='journal' journalmode='journal_sub'->" }, journal { title "status_up" celltext 1 "<-query attribute='journal' journalmode='status_up'->" }, journal { title "status_down" celltext 1 "<-query attribute='journal' journalmode='status_down'->" }, journal { title "alerts_down" celltext 1 "<-query attribute='journal' journalmode='alerts_down'->" } ] resourcereport "JournalMode-1" { formats html, csv journalmode journal journalattributes * ${columns} } taskreport "JournalMode-2" { formats html, csv journalmode journal journalattributes * ${columns} } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/Alerts.tjp0000644000175000017500000000365712614413013024763 0ustar bernatbernatproject "Alert Tests" 2010-08-03 +6m task "T1" { task "T1.1" { task "T1.1.1" { journalentry 2010-08-03 "T1.1.1 Not in report" journalentry 2010-08-05 "T1.1.1" journalentry 2010-08-15 "T1.1.1 Not in report" } journalentry 2010-08-05 "T1.1" } } task "T2" { task "T2.1" { task "T2.1.1" { journalentry 2010-08-03 "T2.1.1 Red Not in report" { alert red } journalentry 2010-08-05 "T2.1.1" journalentry 2010-08-15 "T2.1.1 Green Not in report" { alert green } } journalentry 2010-08-05 "T2.1 Yellow" { alert yellow } } } task "T3" { task "T3.1" { task "T3.1.1" { journalentry 2010-08-03 "T3.1.1 Not in report" journalentry 2010-08-06 "T3.1.1 Red" { alert red } journalentry 2010-08-15 "T3.1.1 Green Not in report" { alert green } } journalentry 2010-08-06 "T3.1 Yellow" { alert yellow } } } taskreport "Alerts-1" { formats html, csv columns name, alert, journal { celltext 1 "<-query attribute='journal' journalmode='journal_sub'->" title "Journal_sub (full period)" period ${projectstart} - ${projectend} }, journal { title "journal (full period)" period ${projectstart} - ${projectend} }, journal { title "journal" }, journal { title "journal_sub" celltext 1 "<-query attribute='journal' journalmode='journal_sub'->" }, journal { title "alerts_down" celltext 1 "<-query attribute='journal' journalmode='alerts_down'->" }, journal { title "status_down" celltext 1 "<-query attribute='journal' journalmode='status_down'->" }, journal { title "status_up" celltext 1 "<-query attribute='journal' journalmode='status_up'->" } period 2010-08-05 - 2010-08-15 } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/LogicalFunctions4.tjp0000644000175000017500000000032612614413013027046 0ustar bernatbernatproject "LogFunc4" 2011-06-02 +1m task "Task 1" { duration 1w } task "Milestone" task "Task 2" { duration 1d } taskreport "LogicalFunctions4-1" { formats csv hidetask ~ismilestone(plan) columns name } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/FTE.tjp0000644000175000017500000000030612614413013024133 0ustar bernatbernatproject "FTE" 2011-10-29 +1y resource "Team" { resource "A" { workinghours mon - fri 8:00 - 12:00 } resource "B" } task "T" resourcereport "FTE-1" { formats csv columns name, fte } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/LogicalFunctions3.tjp0000644000175000017500000000046312614413013027047 0ustar bernatbernatproject "LogFunc3" 2011-05-27 +1m task t1 "Task 1" { task t2 "Task 2" { task t3 "Task 3" { task t6 "Task 6" } task t7 "Task 7" } task t4 "Task 4" } task t5 "Task 5" taskreport "LogicalFunctions3-1" { formats csv sorttasks id.up hidetask ~(ischildof(t1.t2)) columns name } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/LogicalFunctions1.tjp0000644000175000017500000000073312614413013027045 0ustar bernatbernatproject "Logical Functions" 2009-11-25 +2m task "Task 1" task "Task 2" task "Task 3" task "Task 4" task "Task 5" task "" taskreport "LogicalFunctions1-1" { formats csv columns index, name, id { celltext plan.index < 3 "Index < 3" celltext 1 "Index >= 3" }, id { celltext plan.name = "Task 3" "This is task 3" celltext 1 "" }, id { celltext plan.name = "" "Task with no name" celltext 1 "" } } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/0000755000175000017500000000000012614413013023736 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/DependencyList-1.csv0000644000175000017500000000014612614413013027524 0ustar bernatbernat"Name";"Followers" "A";"B ]->[ (2011-05-14)), C ]->[ (2011-05-21))" "B";"C ]->[ (2011-05-21))" "C";"" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions1-1.csv0000644000175000017500000000035112614413013030134 0ustar bernatbernat"Index";"Name";"Id";"Id";"Id" 1;"Task 1";"Index < 3";"";"" 2;"Task 2";"Index < 3";"";"" 3;"Task 3";"Index >= 3";"This is task 3";"" 4;"Task 4";"Index >= 3";"";"" 5;"Task 5";"Index >= 3";"";"" 6;"";"Index >= 3";"";"Task with no name" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions3-1.csv0000644000175000017500000000004212614413013030133 0ustar bernatbernat"Name" "Task 3" "Task 6" "Task 7" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions2-1.csv0000644000175000017500000000001412614413013030131 0ustar bernatbernat"Name" "R1" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/FTE-1.csv0000644000175000017500000000005412614413013025226 0ustar bernatbernat"Name";"FTE" "Team";1.5 " A";0.5 " B";1.0 taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/JournalMode-2.csv0000644000175000017500000000541512614413013027036 0ustar bernatbernat"Name";"journal";"journal_sub";"status_up";"status_down";"alerts_down" "T1";"[Green]T1 (ID: t1) Don't panik! Reported on 2011-08-10 by R4";"[Green]T2 (ID: t1.t2) Work: 60% (0%) Remaining: 6.0d (5.0d) All good Reported on 2011-08-08 by R1 No problems found Work is progressing well [Green]T2 (ID: t1.t2) Work: 100% (0%) Remaining: 1.0d (5.0d) All ok Reported on 2011-08-08 by R1 [Green]T3 (ID: t1.t3) r1 had to help out Reported on 2011-08-08 by R2 [Green]T1 (ID: t1) Don't panik! Reported on 2011-08-10 by R4 [Yellow]T3 (ID: t1.t3) Work: 20% (0%) Remaining: 2.0d (0.0d) Helped out Reported on 2011-08-08 by R1 r2 asked for d2 [Red]T3 (ID: t1.t3) Work: 0% Remaining: 2.0d (0.0d) Fire burning Reported on 2011-08-08 by R1 It's really hot";"[Green]T1 (ID: t1) Don't panik! Reported on 2011-08-10 by R4";"[Green]T1 (ID: t1) Don't panik! Reported on 2011-08-10 by R4";"[Red]T3 (ID: t1.t3) Work: 0% Remaining: 2.0d (0.0d) Fire burning Reported on 2011-08-08 by R1 It's really hot" " T2";"[Green]T2 (ID: t1.t2) Work: 100% (0%) Remaining: 1.0d (5.0d) All ok Reported on 2011-08-08 by R1 [Green]T2 (ID: t1.t2) Work: 60% (0%) Remaining: 6.0d (5.0d) All good Reported on 2011-08-08 by R1 No problems found Work is progressing well";"[Green]T2 (ID: t1.t2) Work: 100% (0%) Remaining: 1.0d (5.0d) All ok Reported on 2011-08-08 by R1 [Green]T2 (ID: t1.t2) Work: 60% (0%) Remaining: 6.0d (5.0d) All good Reported on 2011-08-08 by R1 No problems found Work is progressing well";"";"[Green]T2 (ID: t1.t2) Work: 100% (0%) Remaining: 1.0d (5.0d) All ok Reported on 2011-08-08 by R1 [Green]T2 (ID: t1.t2) Work: 60% (0%) Remaining: 6.0d (5.0d) All good Reported on 2011-08-08 by R1 No problems found Work is progressing well";"" " T3";"[Green]T3 (ID: t1.t3) r1 had to help out Reported on 2011-08-08 by R2 [Yellow]T3 (ID: t1.t3) Work: 20% (0%) Remaining: 2.0d (0.0d) Helped out Reported on 2011-08-08 by R1 r2 asked for d2 [Red]T3 (ID: t1.t3) Work: 0% Remaining: 2.0d (0.0d) Fire burning Reported on 2011-08-08 by R1 It's really hot";"[Green]T3 (ID: t1.t3) r1 had to help out Reported on 2011-08-08 by R2 [Yellow]T3 (ID: t1.t3) Work: 20% (0%) Remaining: 2.0d (0.0d) Helped out Reported on 2011-08-08 by R1 r2 asked for d2 [Red]T3 (ID: t1.t3) Work: 0% Remaining: 2.0d (0.0d) Fire burning Reported on 2011-08-08 by R1 It's really hot";"";"[Green]T3 (ID: t1.t3) r1 had to help out Reported on 2011-08-08 by R2 [Yellow]T3 (ID: t1.t3) Work: 20% (0%) Remaining: 2.0d (0.0d) Helped out Reported on 2011-08-08 by R1 r2 asked for d2 [Red]T3 (ID: t1.t3) Work: 0% Remaining: 2.0d (0.0d) Fire burning Reported on 2011-08-08 by R1 It's really hot";"[Red]T3 (ID: t1.t3) Work: 0% Remaining: 2.0d (0.0d) Fire burning Reported on 2011-08-08 by R1 It's really hot" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/Journal-1.csv0000644000175000017500000000003012614413013026214 0ustar bernatbernat"Name";"Journal" "T";"" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/Macros-1.csv0000644000175000017500000000023212614413013026032 0ustar bernatbernat"Id";"Name";"Note" "t1";"t1";"This is a note" "t2";"t2";"This is a note" "t3";"t3";"This is a note" "t4";"t4";"This is a note" "t5";"t5";"This is a note" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions4-1.csv0000644000175000017500000000002312614413013030133 0ustar bernatbernat"Name" "Milestone" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/Journal-2.csv0000644000175000017500000000017312614413013026225 0ustar bernatbernat"Name";"Journal" "T";"[Green]T (ID: _Task_1) Entry 1 Reported on 2010-06-07 by A1 Flags: foo, bar Summary 1 Deails 1" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/Alerts-1.csv0000644000175000017500000001622212614413013026046 0ustar bernatbernat"Name";"Alert";"Journal_sub (full period)";"journal (full period)";"journal";"journal_sub";"alerts_down";"status_down";"status_up" "T1";"Green";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-03 [Green]T1.1 (ID: _Task_1._Task_2) T1.1 Reported on 2010-08-05 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Reported on 2010-08-05 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-15";"";"";"[Green]T1.1 (ID: _Task_1._Task_2) T1.1 Reported on 2010-08-05 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Reported on 2010-08-05";"";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-15";"" " T1.1";" Green";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-03 [Green]T1.1 (ID: _Task_1._Task_2) T1.1 Reported on 2010-08-05 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Reported on 2010-08-05 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-15";"[Green]T1.1 (ID: _Task_1._Task_2) T1.1 Reported on 2010-08-05";"[Green]T1.1 (ID: _Task_1._Task_2) T1.1 Reported on 2010-08-05";"[Green]T1.1 (ID: _Task_1._Task_2) T1.1 Reported on 2010-08-05 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Reported on 2010-08-05";"";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-15";"[Green]T1.1 (ID: _Task_1._Task_2) T1.1 Reported on 2010-08-05" " T1.1.1";" Green";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-03 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Reported on 2010-08-05 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-15";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-03 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Reported on 2010-08-05 [Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-15";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Reported on 2010-08-05";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Reported on 2010-08-05";"";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-15";"[Green]T1.1.1 (ID: _Task_1._Task_2._Task_3) T1.1.1 Not in report Reported on 2010-08-15" "T2";"Yellow";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Reported on 2010-08-05 [Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Green Not in report Reported on 2010-08-15 [Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05 [Red]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Red Not in report Reported on 2010-08-03";"";"";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Reported on 2010-08-05 [Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05";"[Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05";"[Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05";"" " T2.1";" Yellow";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Reported on 2010-08-05 [Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Green Not in report Reported on 2010-08-15 [Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05 [Red]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Red Not in report Reported on 2010-08-03";"[Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05";"[Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Reported on 2010-08-05 [Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05";"[Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05";"[Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05";"[Yellow]T2.1 (ID: _Task_4._Task_5) T2.1 Yellow Reported on 2010-08-05" " T2.1.1";" Green";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Reported on 2010-08-05 [Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Green Not in report Reported on 2010-08-15 [Red]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Red Not in report Reported on 2010-08-03";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Reported on 2010-08-05 [Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Green Not in report Reported on 2010-08-15 [Red]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Red Not in report Reported on 2010-08-03";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Reported on 2010-08-05";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Reported on 2010-08-05";"";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Green Not in report Reported on 2010-08-15";"[Green]T2.1.1 (ID: _Task_4._Task_5._Task_6) T2.1.1 Green Not in report Reported on 2010-08-15" "T3";"Yellow";"[Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Not in report Reported on 2010-08-03 [Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Green Not in report Reported on 2010-08-15 [Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06 [Red]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Red Reported on 2010-08-06";"";"";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06 [Red]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Red Reported on 2010-08-06";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06";"" " T3.1";" Yellow";"[Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Not in report Reported on 2010-08-03 [Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Green Not in report Reported on 2010-08-15 [Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06 [Red]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Red Reported on 2010-08-06";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06 [Red]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Red Reported on 2010-08-06";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06";"[Yellow]T3.1 (ID: _Task_7._Task_8) T3.1 Yellow Reported on 2010-08-06" " T3.1.1";" Green";"[Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Not in report Reported on 2010-08-03 [Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Green Not in report Reported on 2010-08-15 [Red]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Red Reported on 2010-08-06";"[Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Not in report Reported on 2010-08-03 [Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Green Not in report Reported on 2010-08-15 [Red]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Red Reported on 2010-08-06";"[Red]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Red Reported on 2010-08-06";"[Red]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Red Reported on 2010-08-06";"";"[Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Green Not in report Reported on 2010-08-15";"[Green]T3.1.1 (ID: _Task_7._Task_8._Task_9) T3.1.1 Green Not in report Reported on 2010-08-15" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/refs/JournalMode-1.csv0000644000175000017500000000216612614413013027035 0ustar bernatbernat"Name";"journal";"journal_sub";"status_up";"status_down";"alerts_down" "R1";"[Green]T2 (ID: t1.t2) Work: 100% (0%) Remaining: 1.0d (5.0d) All ok Reported on 2011-08-08 by R1 [Green]T2 (ID: t1.t2) Work: 60% (0%) Remaining: 6.0d (5.0d) All good Reported on 2011-08-08 by R1 No problems found Work is progressing well [Yellow]T3 (ID: t1.t3) Work: 20% (0%) Remaining: 2.0d (0.0d) Helped out Reported on 2011-08-08 by R1 r2 asked for d2 [Red][New Task] T4 (ID: t4) Work: 20.0% Remaining: 3.0d Big Problem Reported on 2011-08-08 by R1 Flags: foo Had to help out Unplanned distraction [Red]T3 (ID: t1.t3) Work: 0% Remaining: 2.0d (0.0d) Fire burning Reported on 2011-08-08 by R1 It's really hot";"";"";"";"" "R2";"[Green]T3 (ID: t1.t3) r1 had to help out Reported on 2011-08-08 by R2";"";"";"";"" "R3";"[Green][New Task] Out sick (ID: sick) Work: 100.0% Remaining: 2.0d Hope to feel better soon Reported on 2011-08-08 by R3 [Yellow]Personal Notes Not feeling good Reported on 2011-08-08 by R3 Will see the doctor again";"";"";"";"" "R4";"[Green]T1 (ID: t1) Don't panik! Reported on 2011-08-10 by R4";"";"";"";"" taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Correct/Macros.tjp0000644000175000017500000000110612614413013024740 0ustar bernatbernatproject "Macros" 2010-07-21 +1m { } macro m1 ["t1"] macro n1 [note "This is a note"] task t1 ${m1} { ${n1} } macro m2 ["t] macro m3 [2"] macro n2 [note] task t2 ${m2}${m3} { ${n2} -8<- This is a note ->8- } macro m4 [t] macro m5 [3] macro n3 [is a] task t3 "${m4}${m5}" { note "This ${n3} note" } macro m6 [4] macro n4 [is a] task t4 "t${m6}" { note -8<- This ${n4} note ->8- } macro m7 [t] macro n5 [This is a note ->8-] task t5 "${m7}5" { note -8<- ${n5} } taskreport "Macros-1" { formats csv columns id, name, note } taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Errors/0000755000175000017500000000000012614413013022652 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/ReportGenerator/Errors/no_report_defined.tjp0000644000175000017500000000016412614413013027057 0ustar bernatbernatproject "test" 2010-05-23 +1w task "foo" # MARK: warning 0 no_report_defined # MARK: warning 0 all_formats_empty taskjuggler-3.5.0/test/TestSuite/ReportGenerator/Errors/rtp_report_recursion.tjp0000644000175000017500000000053312614413013027663 0ustar bernatbernatproject "test" 2010-05-23 +1w task "foo" textreport "rtp_report_recursion" { formats html center "<[report id='r1']>" } textreport r1 "R1" { center "<[report id='r2']>" } textreport r2 "R2" { center "<[report id='r3']>" } # MARK: error 19 rtp_report_recursion textreport r3 "R3" { left "Hello, world!" center "<[report id='r1']>" } taskjuggler-3.5.0/test/TestSuite/Scheduler/0000755000175000017500000000000012614413013020172 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/0000755000175000017500000000000012614413013021573 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Optimize-3.tjp0000644000175000017500000000105112614413013024247 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01-0:00 +1m { timezone "UTC" } include "checks.tji" resource tux1 "Tux1" resource tux2 "Tux2" task t1 "Task1" { start 2000-01-04 ${FixEnd "2000-01-10-17:00"} effort 5d allocate tux1 } task t2 "Task2" { start 2000-01-10 ${FixEnd "2000-01-17-17:00"} effort 5d allocate tux1 } task t3 "Task3" { start 2000-01-02 ${FixEnd "2000-01-06-17:00"} effort 4d allocate tux1 { alternative tux2 persistent } } taskreport optimize3 "Optimize-3" { formats html columns name, start, end, daily hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/WeakDeps.tjp0000644000175000017500000000107412614413013024017 0ustar bernatbernatproject "Weak Deps Test" 2011-01-06 +2m resource r1 "R1" resource r2 "R2" task t1 "Task 1" { task st1 "SubTask 1" { effort 1d allocate r1 } task st2 "SubTask 2" { effort 1d allocate r2 depends !st1 { onstart } } } task t2 "Task 2" { task st1 "SubTask 1" { effort 1d allocate r1 } task st2 "SubTask 2" { depends !st1 { onstart } } } task t3 "Task 3" { task st1 "SubTask 1" { effort 1d scheduling alap allocate r1 } task st2 "SubTask 2" { effort 1d allocate r2 precedes !st1 { onend } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/TimeSheet2.tjp0000644000175000017500000000261212614413013024264 0ustar bernatbernatproject "test" 2010-02-21 +2m { trackingscenario plan now ${projectstart} } vacation 2010-02-24 resource r1 "R1" resource r2 "R2" resource r3 "R3" { efficiency 0.0 } task t1 "Task 1" { effort 5d allocate r1 } task t2 "Task 2" { task t3 "Task 3" { effort 10d allocate r2 } } timesheet r1 2010-02-21 +1w { task t1 { work 80% remaining 1.0d status green "Lots of work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } status yellow "About me" { summary "I'm not feeling good." } } timesheet r1 2010-02-28 +1w { newtask t4 "Something great" { work 100% remaining 10d status green "I had a great idea!" } } timesheet r2 2010-02-21 +1w { task t1 { work 20% remaining 1d status green "I helped r1" } task t2.t3 { work 60% remaining 8d status red "I need more time" { summary "This takes longer than expected" details -8<- To finish on time, I need help. Get this r1 guy to help me out here. * I want to have fun too! ->8- } } } timesheet r3 2010-02-21 +1w { task t1 { work 0% remaining 1.0d status green "I see nothing but roses." } } resourcereport "TimeSheet2" { formats html columns name, journal } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Shift.tjp0000644000175000017500000000354212614413013023373 0ustar bernatbernatproject test "Test" "1.0" 2007-08-22 +2m { workinghours mon-fri 9:00 - 12:00, 13:00 - 18:00 } include "checks.tji" shift odd_days "Odd Days" { workinghours mon, wed, fri 10:00 - 16:00 workinghours tue, thu, sat, sun off vacation 2007-09-03 } shift even_days "Even Days" { workinghours mon, wed, fri off workinghours tue, thu, sat, sun 10:00 - 16:00 vacation 2007-09-04 shift even_no_we "Even Days, no weekend" { workinghours sat, sun off } } shift morning "Morning" { workinghours mon - sun 8:00 - 12:00 } shift thu_vac "Vacation on Thursday" { vacation 2007-09-06 replace } resource team "Team" { vacation 2007-08-22 +3d shifts morning 2007-08-23 +5d resource mdf "MDF Worker" { shifts odd_days 2007-09-01 +2w } resource ttss "TTSS Worker" { shifts even_days 2007-09-01 +2w } resource tt "TT Worker" { shifts even_no_we 2007-09-01 +2w } resource wed_vac "Vacation on Wednesday" { vacation 2007-09-05 shifts thu_vac 2007-09-01 +7d } resource work1 "Worker 1" } resource default "Default Worker" task prj "Project" { start 2007-08-22 task mdf "MDF Task" { effort 4w allocate mdf ${FixEnd "2007-10-01-16:00"} } task ttss "TTSS Task" { effort 5w allocate ttss ${FixEnd "2007-10-05-11:00"} } task tt "TT Task" { effort 4w allocate tt ${FixEnd "2007-10-03-11:00"} } task default "Default Task" { effort 7w allocate default ${FixEnd "2007-10-09-18:00"} } task vac_test "Vacation on Thursday" { start 2007-09-01 effort 4d allocate wed_vac ${FixEnd "2007-09-07-18:00"} } task work1 "Task with shift morning" { start ${projectstart} allocate work1 effort 3w shifts morning 2007-09-01 +4w ${FixEnd "2007-10-02-18:00"} } } taskreport shift "Shift" { formats html columns name, start, end, daily } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Container-2.tjp0000644000175000017500000000116512614413013024376 0ustar bernatbernatproject prj "My Project" "1.0" 2009-01-01-0:00-+0100 - 2009-01-31-0:00-+0100 { timezone 'Europe/Amsterdam' } include "checks.tji" task C0 "C0" { task T0 "T0" { duration 6d ${FixTask "2009-01-01" "2009-01-07"} } ${FixTask "2009-01-01" "2009-01-07"} } task T1 "T1" { depends C0 duration 1d ${FixTask "2009-01-07" "2009-01-08"} } task M1 "M1" { milestone start 2009-01-10 depends T1, C0 ${FixMS "2009-01-10"} } task M2 "M2" { milestone depends C0 ${FixMS "2009-01-07"} } taskreport tr "Container-2" { formats html columns name,start,end,chart { scale day } sorttasks tree, seqno.up } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/InheritStartEnd.tjp0000644000175000017500000000450112614413013025361 0ustar bernatbernatproject "Test" 2009-02-04 - 2009-06-01 { timezone "Europe/Amsterdam" } include "checks.tji" resource r1 "Resource 1" resource r2 "Resource 2" resource r3 "Resource 3" resource r4 "Resource 4" task ms1 "Milestone 1" { ${FixStart "${projectstart}"} milestone } task ms2 "Milestone 2" { milestone scheduling alap ${FixEnd "${projectend}"} } task p1 "Parent Tasks" { task only_s_e "Only start or end dates" { task t1 "Task 1" { ${FixMS "${projectstart}"} } task t2 "Task 2" { start 2009-02-06 # This is an implicit milestone ${FixTask "2009-02-06" "2009-02-06"} } task t3 "Task 3" { end 2009-03-01 # This is an implicit milestone ${FixMS "2009-03-01"} } } task ms "Milestones" { task t1 "Task 1" { milestone ${FixMS "${projectstart}"} } task t2 "Task 2" { milestone scheduling alap ${FixMS "${projectend}"} } task t3 "Task 3" { start 2009-02-06 milestone ${FixMS "2009-02-06"} } task t4 "Task 4" { end 2009-03-01 milestone ${FixMS "2009-03-01"} } task t5 "Task 5" { start 2009-02-06 depends ms1 ${FixMS "2009-02-06"} } task t6 "Task 6" { end 2009-03-01 precedes ms2 ${FixMS "2009-03-01"} } } task w_effort "With effort" { task t1 "Task 1" { allocate r1 effort 20d scheduling asap ${FixTask "2009-02-04-09:00" "2009-03-03-17:00"} } task t2 "Task 2" { start 2009-02-06 effort 20d scheduling asap allocate r2 ${FixTask "2009-02-06-09:00" "2009-03-05-17:00"} } task t3 "Task 3" { effort 20d allocate r3 scheduling alap ${FixTask "2009-05-04-09:00" "2009-05-29-17:00"} } task t4 "Task 4" { end 2009-05-01 scheduling alap effort 20d allocate r4 ${FixTask "2009-04-03-09:00" "2009-04-30-17:00"} } } } task p2 "Parent Tasks 2" { task t1 "Task1" { ${FixMS "${projectstart}"} } task t2 "Task2" { depends !t1 ${FixMS "${projectstart}"} } task t3 "Task3" { scheduling alap ${FixMS "${projectend}"} } task t4 "Task4" { precedes !t3 ${FixMS "${projectend}"} } } taskreport inherit "InheritStartEnd" { formats html columns name, id, start, end, chart } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Container.tjp0000644000175000017500000000130312614413013024231 0ustar bernatbernatproject prj "Project" "1.0" 2009-01-01 +1m include "checks.tji" task S "start" { milestone start 2009-01-01 } task E "end" { milestone end 2009-01-30 } task C0 "C0" { depends S precedes E ${FixTask "2009-01-10-0:00" "2009-01-20-0:00"} task C1 "C1" { ${FixEnd "2009-01-16-17:00"} task T1 "T1" { ${FixTask "2009-01-10-0:00" "2009-01-16-17:00"} start 2009-1-10 length 1w } } task C2 "C2" { ${FixStart "2009-01-13-9:00"} scheduling alap task T2 "T2" { ${FixTask "2009-01-13-9:00" "2009-01-20-0:00"} end 2009-1-20 length 1w } } } taskreport t "Container" { formats html columns name, start, end, chart { scale day } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Shift2.tjp0000644000175000017500000000156012614413013023453 0ustar bernatbernatproject "Shift2" 2010-02-06 +1w include "checks.tji" shift s2 "s2" { replace workinghours mon - fri 8:00 - 16:00 } shift s3 "s3" { vacation 2010-02-09 workinghours mon - fri 8:00 - 16:00 } shift s4 "s4" { replace vacation 2010-02-09 workinghours mon - fri 8:00 - 16:00 } vacation 2010-02-10 resource r1 "r1" task "T1" { effort 32h allocate r1 ${FixEnd "2010-02-12-17:00"} } resource r2 "r2" { shifts s2 2010-02-08 +1w } task "T2" { effort 40h allocate r2 ${FixEnd "2010-02-12-16:00"} } resource r3 "r3" { shifts s3 2010-02-08 +1w } task "T3" { effort 24h allocate r3 ${FixEnd "2010-02-12-16:00"} } resource r4 "r4" { shifts s4 2010-02-08 +1w } task "T4" { effort 32h allocate r4 ${FixEnd "2010-02-12-16:00"} } taskreport "Shift2" { formats html columns name, start, end, daily sorttasks index.up loadunit hours } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Precedes.tjp0000644000175000017500000000140012614413013024037 0ustar bernatbernatproject test "Test" "1.0" 2007-04-02 - 2007-07-01 include "checks.tji" task t1 "T1" { end ${projectend} duration 1w ${FixStart "2007-06-24"} } task t2 "T2" { precedes t1 duration 1w ${FixTask "2007-06-17" "2007-06-24"} } task t3 "T3" { precedes t2 { gapduration 1w } duration 1w ${FixTask "2007-06-03" "2007-06-10"} } task t4 "T4" { precedes t3 { onstart gaplength 1w } duration 1w ${FixTask "2007-05-21-9:00" "2007-05-28-9:00"} } task t5 "T5" { precedes t4 { onend } duration 1w ${FixTask "2007-05-21-9:00" "2007-05-28-9:00"} } task t6 "T6" { precedes t4 { onend gaplength 1w } duration 1w ${FixTask "2007-05-14-9:00" "2007-05-21-9:00"} } taskreport preceds "Precedes" { formats html columns name, start, end, daily } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/InheritedAttributes.tjp0000644000175000017500000000263512614413013026302 0ustar bernatbernatproject "Inherited Attributes" 2011-01-04 +1m { scenario s1 "S1" { scenario s2 "S2" { scenario s3 "S3" } } } # Task/Scenario 1 2 3 # T1 3! 2! 2 # T2 3 2 2 # T3 3 2 2 task "T1" { priority 3 s2:priority 2 fail s1.priority != 3 fail s2.priority != 2 fail s3.priority != 2 task "T2" { fail s1.priority != 3 fail s2.priority != 2 fail s3.priority != 2 task "T3" { fail s1.priority != 3 fail s2.priority != 2 fail s3.priority != 2 } } } # Task/Scenario 1 2 3 # T1 3! 2! 2 # T2 3 2 2 # T3 3 1 1 task "T1" { priority 3 s2:priority 2 fail s1.priority != 3 fail s2.priority != 2 fail s3.priority != 2 task "T2" { fail s1.priority != 3 fail s2.priority != 2 fail s3.priority != 2 task "T3" { s2:priority 1 fail s1.priority != 3 fail s2.priority != 1 fail s3.priority != 1 } } } # Task/Scenario 1 2 3 # T1 3! 2! 2 # T2 3 2 1 # T3 3 2 1 task "T1" { priority 3 s2:priority 2 fail s1.priority != 3 fail s2.priority != 2 fail s3.priority != 2 task "T2" { s3:priority 1 fail s1.priority != 3 fail s2.priority != 2 fail s3.priority != 1 task "T3" { s2:priority 1 fail s1.priority != 3 fail s2.priority != 1 fail s3.priority != 1 } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/purge.tjp0000644000175000017500000000073612614413013023442 0ustar bernatbernatproject "Test" 2012-05-31 +1m { scenario s1 "S1" { scenario s2 "S2" } } include "checks.tji" resource r1 "R1" { workinghours mon - fri 8:00 - 12:00 } resource r2 "R2" task "T1" { allocate r1 task "T2" { purge s2:allocate s2:allocate r2 effort 2w ${FixEndSc "2012-06-27-12:00" "s1"} ${FixEndSc "2012-06-13-17:00" "s2"} } } taskreport "purge" { formats html columns name, scenario, end, weekly hideresource @none scenarios s1, s2 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Leaves.tjp0000644000175000017500000000060612614413013023533 0ustar bernatbernatproject "Leaves" 2012-03-12 +6m { scenario s1 "S1" { scenario s2 "S2" } } vacation "Goof-out day" 2012-03-14 resource r1 "R1" { vacation 2012-03-19 +1w } task "T1" { effort 20d allocate r1 fail s1.end != 2012-04-16-17:00 fail s2.end != 2012-04-16-17:00 } taskreport "Leaves" { formats html columns no, name, start, end, daily scenarios s1, s2 hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/MultipleMandatories.tjp0000644000175000017500000000142512614413013026276 0ustar bernatbernatproject test "Test" "1.0" 2007-11-22 +6d resource computenodes "Compute Nodes" { resource node01 "node01" resource node02 "node02" resource node03 "node03" resource node04 "node04" resource node05 "node05" } macro allocNodes [ allocate node01 { mandatory alternative node02, node03, node04, node05 } ] task ProductionPlan "Production Plan" { task A "A" { start ${projectstart} effort 4d ${allocNodes} ${allocNodes} ${allocNodes} ${allocNodes} } task B1 "B1" { depends !A effort 4d ${allocNodes} ${allocNodes} ${allocNodes} ${allocNodes} } task B2 "B2" { depends !A effort 4d ${allocNodes} ${allocNodes} ${allocNodes} ${allocNodes} minend 2007-11-26-17:00 maxend 2007-11-26-17:00 } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/AutomaticMilestones.tjp0000644000175000017500000000212112614413013026277 0ustar bernatbernatproject test "Test" "1.0" 2009-02-12 +4m include "checks.tji" task msStart "Start Milestone" { start ${projectstart} milestone } task msEnd "End Milestone" { end ${projectend} milestone } task ms1 "Milestone 1" { start ${projectstart} ${FixStart "${projectstart}"} ${FixEnd "${projectstart}"} } task ms2 "Milestone 2" { start ${projectstart} depends msStart ${FixStart "${projectstart}"} ${FixEnd "${projectstart}"} } task ms3 "Milestone 3" { depends !msStart ${FixStart "${projectstart}"} ${FixEnd "${projectstart}"} } task ms4 "Milestone 4" { end ${projectend} ${FixStart "${projectend}"} ${FixEnd "${projectend}"} } task ms5 "Milestone 5" { end ${projectend} precedes msEnd ${FixStart "${projectend}"} ${FixEnd "${projectend}"} } task ms6 "Milestone 6" { precedes msEnd ${FixStart "${projectend}"} ${FixEnd "${projectend}"} } task t1 "Task 1" { period ${projectstart} - ${projectend} ${FixStart "${projectstart}"} ${FixEnd "${projectend}"} } taskreport ms "AutomaticMilestones" { formats html columns name, id, start, end, chart } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Manager.tjp0000644000175000017500000000016212614413013023663 0ustar bernatbernatproject "test" 2010-04-03 +1w resource boss "Big Boss" resource joe "Joe Average" { managers boss } task "T" taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/DateAndDep.tjp0000644000175000017500000000060512614413013024244 0ustar bernatbernatproject "Date and dependency" 2013-06-22 +2m include "checks.tji" resource r "R" task "Forward" { task t1 "T1" { start 2013-06-24 effort 2d allocate r } task t2 "T2" { depends !t1 { onstart } start 2013-06-27 effort 2d allocate r ${FixStart "2013-06-27-9:00"} } } taskreport "DateAndDep" { formats html columns no, name, start, end, chart } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Booking2.tjp0000644000175000017500000000174512614413013023773 0ustar bernatbernatproject "Booking2" 2010-02-08 +1m { now 2010-02-15 trackingscenario plan } include "checks.tji" resource r1 "R1" task "T1" { effort 40h allocate r1 ${FixStart "2010-02-15-9:00"} ${FixEnd "2010-02-19-17:00"} } resource r2 "R2" task "T2" { effort 40h allocate r2 booking r2 2010-02-08-9:00 +5d { sloppy 1 } ${FixStart "2010-02-08-9:00"} ${FixEnd "2010-02-12-17:00"} } resource r3 "r3" { vacation 2010-02-09 } task "T3" { effort 32h allocate r3 booking r3 2010-02-08 +2d { sloppy 2 } ${FixStart "2010-02-08-9:00"} ${FixEnd "2010-02-17-17:00"} } resource r4 "R4" task "T4" { effort 56h allocate r4 booking r4 2010-02-08 +2d { overtime 1 } ${FixStart "2010-02-08"} ${FixEnd "2010-02-15-17:00"} } resource r5 "r5" { vacation 2010-02-09 } task "T5" { effort 56h allocate r5 booking r5 2010-02-08 +2d { overtime 2 } ${FixEnd "2010-02-15-17:00"} } taskreport "Booking2" { formats html hideresource 0 columns no, name, effort, end, hourly } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Limits.tjp0000644000175000017500000000331212614413013023552 0ustar bernatbernatproject test "Test" "1.0" 2007-08-29 +6m { workinghours mon-fri 9:00 - 12:00, 13:00 - 18:00 } include "checks.tji" resource a "A" resource b "B" resource c "C" resource d "D" resource e "E" resource f "F" resource g "G" resource h "H" resource i "I" resource j "J" resource k "K" task tasks "Tasks" { task t1 "40h with 4h daily limit" { effort 40h allocate a limits { dailymax 4h } ${FixEnd "2007-09-11-14:00"} } task t2 "30d with 3d weekly limit" { effort 30d allocate b limits { weeklymax 3d } ${FixEnd "2007-10-31-18:00"} } task t3 "45d with 15d monthly limit" { effort 45d allocate c limits { monthlymax 15d } ${FixEnd "2007-11-15-18:00"} } task t4 "7h/day 4d/week 15d/months" { effort 50d allocate d limits { dailymax 7h weeklymax 4d monthlymax 15d } ${FixEnd "2007-12-04-15:00"} } } task nested "Nested Tasks (20h/week limit)" { limits { weeklymax 20h } task a "Task A" { allocate e effort 30d ${FixEnd "2008-02-06-14:00"} } task b "Task B (7h/day limit)" { effort 30d allocate f priority 600 limits { dailymax 7h } ${FixEnd "2007-11-14-16:00"} } } task interval "Interval Limit" { effort 20d allocate g limits { weeklymax 8h { period 2007-09-05 +8d }} ${FixEnd "2007-10-01-18:00"} } task resource "Resource Limit" { effort 10d allocate h limits { weeklymax 16h { resources h }} ${FixEnd "2007-09-25-18:00"} } task resources "Multiple Resources" { effort 20d allocate i, j, k limits { dailymax 2h { resources i} dailymax 6h { resources j} } ${FixEnd "2007-09-11-18:00"} } taskreport limits "Limits" { formats html columns no, name, start, end, effort, daily } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Optimize-5.tjp0000644000175000017500000000171412614413013024257 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01-0:00 +2m { timezone "UTC" } include "checks.tji" resource tux1 "Tux1" resource tux2 "Tux2" resource tux3 "Tux3" task t0 "Task0" { start 2000-01-01 ${FixEnd "2000-01-07-17:00"} effort 5d allocate tux1 { alternative tux3 } } task t1 "Task1" { start 2000-01-01 ${FixEnd "2000-01-07-17:00"} effort 5d allocate tux1 } task t2 "Task2" { start 2000-01-01 ${FixEnd "2000-01-07-17:00"} effort 5d allocate tux2 } task t3 "Task3" { depends !t1, !t2 ${FixEnd "2000-01-11-17:00"} effort 4d allocate tux1, tux2 } task t4 "Task4" { depends !t3 ${FixEnd "2000-01-18-17:00"} effort 5d allocate tux1 } task t5 "Task5" { depends !t3 ${FixEnd "2000-01-25-17:00"} effort 10d allocate tux2 } task t6 "Task6" { start 2000-01-01 effort 5d allocate tux1 ${FixEnd "2000-01-25-17:00"} } taskreport optimize5 "Optimize-5" { formats html columns name, start, end, criticalness, pathcriticalness, daily hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Optimize-4.tjp0000644000175000017500000000114112614413013024250 0ustar bernatbernatproject test "Test" "Test" 2000-01-01-0:00 +1m { timezone "UTC" } include "checks.tji" # This example does not get optimized properly yet. resource tux1 "Tux1" resource tux2 "Tux2" task t1 "Task1" { start 2000-01-01 ${FixEnd "2000-01-07-17:00"} effort 5d allocate tux1 } task t2 "Task2" { start 2000-01-01 ${FixEnd "2000-01-24-17:00"} effort 6d allocate tux1 } task t3 "Task3" { depends !t1 ${FixEnd "2000-01-14-17:00"} effort 10d allocate tux1, tux2 } taskreport optimize4 "Optimize-4" { formats html columns name, start, end, criticalness, pathcriticalness, daily hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Depends.tjp0000644000175000017500000000135012614413013023673 0ustar bernatbernatproject test "Test" "1.0" 2007-04-02 +3m include "checks.tji" task t1 "T1" { start ${projectstart} duration 1w ${FixEnd "2007-04-09"} } task t2 "T2" { depends t1 duration 1w ${FixTask "2007-04-09" "2007-04-16"} } task t3 "T3" { depends t2 { gapduration 1w } duration 1w ${FixTask "2007-04-23" "2007-04-30"} } task t4 "T4" { depends t3 { onend gaplength 1w } duration 1w ${FixTask "2007-05-04-17:00" "2007-05-11-17:00"} } task t5 "T5" { depends t4 { onstart } duration 1w ${FixTask "2007-05-04-17:00" "2007-05-11-17:00"} } task t6 "T6" { depends t4 { onstart gaplength 1w } duration 1w ${FixTask "2007-05-11-17:00" "2007-05-18-17:00"} } taskreport depends "Depends" { formats html columns name, start, end, daily } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/checks.tji0000644000175000017500000000064712614413013023552 0ustar bernatbernatmacro FixStart [ minstart ${1} maxstart ${1} ] macro FixEnd [ minend ${1} maxend ${1} ] macro FixMS [ ${FixStart "${1}"} ${FixEnd "${1}"}] macro FixTask [ ${FixStart "${1}"} ${FixEnd "${2}"}] macro FixStartSc [ fail ${2}.start != ${1} ] macro FixEndSc [ fail ${2}.end != ${1} ] macro FixMSSc [ ${FixStartSc "${1}" "${2}"} ${FixEndSc "${1}" "${2}"}] macro FixTaskSc [ ${FixStartSc "${1}" "${3}"} ${FixEndSc "${2}" "${3}"}] taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Optimize-2.tjp0000644000175000017500000000114212614413013024247 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01-0:00 +1m { timezone "UTC" } include "checks.tji" resource tux1 "Tux1" resource tux2 "Tux2" task t1 "Task1" { start 2000-01-01 ${FixEnd "2000-01-14-17:00"} effort 10d allocate tux2 { alternative tux1 persistent } } task t2 "Task2" { start 2000-01-01 ${FixEnd "2000-01-06-17:00"} effort 4d allocate tux2 { persistent } } task t3 "Task3" { depends !t2 ${FixEnd "2000-01-20-17:00"} effort 10d allocate tux2 { persistent } } taskreport optimize2 "Optimize-2" { formats html timeformat "%Y-%m-%d %H:%M" columns name, start, end, daily hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Allocate.tjp0000644000175000017500000000447312614413013024046 0ustar bernatbernatproject test "Test" "1.0" 2007-08-31 +3w { workinghours mon-fri 9:00 - 12:00, 13:00 - 18:00 } macro Fixend [ minend ${1} maxend ${1} ] shift mowefr "Mo, We, Fr shift" { workinghours mon, wed, fri 9:00 - 12:00, 13:00 - 18:00 workinghours tue, thu, sat off } shift tuthsa "Tu, Th, Sa shift" { workinghours tue, thu, sat 9:00 - 12:00, 13:00 - 18:00 workinghours mon, wed, fri off } resource tue_off "Tuesday off" { workinghours mon, wed - fri 10:00 - 18:00 workinghours tue, sat, sun off } resource all_days "All days" resource all_days2 "All days2" resource only_wed "Only Wednesday" { workinghours sun - sat off resource ow1 "Monday off" { workinghours sun, mon, sat off workinghours tue - fri 10:00 - 18:00 } resource ow2 "Tuesday off" { workinghours sun, tue, sat off workinghours mon, wed - fri 10:00 - 18:00 } resource ow3 "Thu Fri off" { workinghours sun, thu - sat off workinghours mon - wed 10:00 - 18:00 } } resource all_week_group "All week group" { workinghours mon - fri 10:00 - 18:00 workinghours sat, sun off resource aw1 "All week 1" resource aw2 "All week 2" resource aw3 "All week 3" resource aw4 "All week 4" } task mandatory "Mandatory Tests" { task t1 "Task1" { start ${projectstart} effort 13d allocate tue_off { mandatory }, all_days ${Fixend "2007-09-12-18:00"} } task t2 "Task2" { start ${projectstart} effort 14d allocate only_wed { mandatory }, all_week_group ${Fixend "2007-09-12-18:00"} } } resource r1 "Resource 1" resource r2 "Resource 2" resource r3 "Resource 3" resource r4 "Resource 4" task inheritedAllocs "Inherited Allocations" { allocate r1 task t1 "Task 1" { effort 3d ${Fixend "2007-09-04-18:00"} } task t2 "Task 2" { allocate r2 task t2_1 "Task 2.1" { effort 2d ${Fixend "2007-09-03-18:00"} } task t2_2 "Task 2.2" { effort 4d depends !!t1 ${Fixend "2007-09-06-18:00"} } } task t3 "Task 3" { effort 2d depends !t2 ${Fixend "2007-09-10-18:00"} } } task allocShift "Allocation Shift" { effort 10d allocate r3 { shifts mowefr 2007-09-01 +1w }, r4 { shifts tuthsa 2007-09-01 +1w } ${Fixend "2007-09-11-14:00"} } taskreport allocate "Allocate" { formats html columns no, name, end, daily hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Duration.tjp0000644000175000017500000000154412614413013024103 0ustar bernatbernatproject test "Test" "1.0" 2007-03-25 +6m resource r1 "R1" resource r2 "R2" resource r3 "R3" resource r4 "R4" task duration "Duration Tasks" { start 2007-03-27 # MARK: warning 11 allocate_no_assigned task t1 "Duration 5h" { duration 5h allocate r1 minend 2007-03-27-5:00 maxend 2007-03-27-5:00 fail plan.effort != 0 } task t2 "Duration 5d" { duration 5d allocate r2 minend 2007-04-01 maxend 2007-04-01 fail plan.effort != 4 } task t3 "Duration 5w" { duration 5w allocate r3 minend 2007-05-01 maxend 2007-05-01 fail plan.effort != 25 } task t4 "Duration 5m" { duration 5m allocate r4 minend 2007-08-26-2:00 maxend 2007-08-26-2:00 fail plan.effort != 109 } } taskreport duration "Duration" { formats html loadunit days columns name, start, effort, end, daily } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Optimize-1.tjp0000644000175000017500000000075212614413013024254 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01-0:00 +1m { timezone "UTC" } include "checks.tji" resource tux1 "Tux1" resource tux2 "Tux2" task t1 "Task1" { start 2000-01-01 ${FixEnd "2000-01-04-17:00"} effort 2d allocate tux1 { alternative tux2 persistent } } task t2 "Task2" { start 2000-01-01 ${FixEnd "2000-01-06-17:00"} effort 4d allocate tux1 } taskreport optimize1 "Optimize-1" { formats html columns name, start, end, criticalness, pathcriticalness, daily hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/PersistentResources.tjp0000644000175000017500000000071412614413013026347 0ustar bernatbernatproject "Test" 2012-08-10 +3m include "checks.tji" resource a "A" resource b "B" macro Alloc [ allocate a { alternative b persistent } ] task "T1" { start 2012-08-13 effort 5d ${Alloc} ${FixEnd "2012-08-17-17:00"} } task "T2" { start 2012-08-14 effort 6d ${Alloc} ${FixEnd "2012-08-21-17:00"} } taskreport "PersistentResources" { formats html columns name, criticalness, pathcriticalness, chart { scale day } hideresource @none } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/hammock.tjp0000644000175000017500000000065312614413013023735 0ustar bernatbernatproject "Hammock tasks" 2013-06-22 +2m include 'checks.tji' task m1 "Milestone 1" { start 2013-07-01 } task m2 "Milestone 2" { start 2013-08-01 } task p1 "phase 1" { precedes m2 depends m1 task f "Foo" { ${FixStart "2013-07-01"} ${FixEnd "2013-08-01"} } task b "Bar" { ${FixStart "2013-07-01"} ${FixEnd "2013-08-01"} } } taskreport "test" { formats html columns name, start, end, chart } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Mandatory.tjp0000644000175000017500000000113112614413013024244 0ustar bernatbernatproject "Mandatory" 2009-10-31 +2m include "checks.tji" resource a "A" { workinghours mon, wed, fri off } resource b "B" resource c "C" { workinghours tue, thu off } resource d "D" { workinghours wed off } resource e "E" task "T1" { effort 16d allocate a { mandatory}, b ${FixTask "2009-11-03-9:00" "2009-11-26-17:00"} } task "T2" { effort 24d allocate c { mandatory}, d { mandatory}, e ${FixTask "2009-11-02-9:00" "2009-11-27-17:00"} } taskreport "Mandatory" { formats html timeformat "%Y-%m-%d %H:%M" columns no, name, start, end, chart { scale day } hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Scenarios.tjp0000644000175000017500000000051212614413013024236 0ustar bernatbernatproject "Multiple Scenarios" "1.0" 2011-02-27 +2m { scenario s1 "S1" { scenario s2 "S2" } } include "checks.tji" resource r1 "R1" resource r2 "R2" task t1 "T1" { allocate r1 allocate r2 effort 2w ${FixTaskSc "2011-02-28-9:00" "2011-03-04-17:00" "s1"} ${FixTaskSc "2011-02-28-9:00" "2011-03-04-17:00" "s2"} } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/PersistentResources-2.tjp0000644000175000017500000000104512614413013026504 0ustar bernatbernatproject "Test" 2012-08-10 +3m include "checks.tji" resource a "A" { leaves project "" 2012-08-14 - ${projectend} } resource b "B" macro Alloc [ allocate a { alternative b persistent } ] # MARK: warning 15 broken_persistence task "T1" { start 2012-08-13 effort 5d ${Alloc} ${FixEnd "2012-08-27-17:00"} } task "T2" { start 2012-08-14 effort 6d ${Alloc} ${FixEnd "2012-08-21-17:00"} } taskreport "PersistentResources" { formats html columns name, criticalness, pathcriticalness, chart { scale day } hideresource @none } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Booking.tjp0000644000175000017500000000611512614413013023705 0ustar bernatbernatproject test "Test" "1.0" 2007-04-22 +5w { now 2007-04-30 scenario plan "Plan" workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 } include "checks.tji" resource tux1 "Tux 1" resource tux2 "Tux 2" { vacation 2007-04-24 +2d } resource tux3 "Tux 3" { vacation 2007-04-24 +2d } resource tux4 "Tux 4" { vacation 2007-04-24 +2d } task t1 "Task 1" { effort 7d allocate tux1 # We got 2d from bookings. See supplement below! booking tux1 2007-04-24 +10h, 2007-04-25 +10h { sloppy 1 } booking tux1 2007-04-27 +10h { overtime 2 } ${FixStart "2007-04-24-9:00"} ${FixEnd "2007-05-04-18:00"} } task t2 "Task 2" { effort 10d allocate tux2 booking tux2 2007-04-23 +5d { sloppy 2 } ${FixEnd "2007-05-08-18:00"} } task t3 "Task 3" { effort 11d allocate tux3 booking tux3 2007-04-23 +5d { sloppy 2 overtime 1 } ${FixEnd "2007-05-01-18:00"} } task t4 "Task 3" { effort 16d allocate tux4 booking tux4 2007-04-23 +5d { sloppy 2 overtime 2 } ${FixEnd "2007-04-30-18:00"} } supplement resource tux1 { booking t1 2007-04-26-4:00 +10h { sloppy 1 } } resource r10 "R10" resource r11 "R11" resource r12 "R12" resource r13 "R13" resource r14 "R14" resource r15 "R15" resource r16 "R16" resource r17 "R17" resource r18 "R18" resource r19 "R19" resource r20 "R20" task t10 "Task 10" { booking r10 2007-04-23-9:00 +1h end 2007-04-24 scheduling asap ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-24"} } task t11 "Task 11" { start 2007-04-22-12:00 booking r11 2007-04-23-9:00 +1h end 2007-04-24 scheduling asap ${FixStart "2007-04-22-12:00"} ${FixEnd "2007-04-24"} } task t12 "Task 12" { effort 1d allocate r12 booking r12 2007-04-23-9:00 +1h ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-30-17:00"} } task t13 "Task 13" { effort 4h allocate r13 booking r13 2007-04-23-9:00 +5h { sloppy 2 } ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-23-14:00"} } task t14 "Task 14" { effort 4h allocate r14 booking r14 2007-04-23-9:00 +5h { sloppy 2 } ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-23-14:00"} } task t15 "Task 15" { length 4h allocate r15 booking r15 2007-04-23-9:00 +5h { sloppy 2 } ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-23-14:00"} } task t16 "Task 16" { length 5h allocate r16 booking r16 2007-04-23-9:00 +5h { sloppy 2 } ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-23-15:00"} } task t17 "Task 17" { length 6h allocate r17 booking r17 2007-04-23-9:00 +5h { sloppy 2 } ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-23-16:00"} } task t18 "Task 18" { duration 5h allocate r18 booking r18 2007-04-23-9:00 +5h { sloppy 2 } ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-23-14:00"} } task t19 "Task 19" { duration 5h allocate r19 booking r19 2007-04-23-9:00 +5h { sloppy 2 } ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-23-14:00"} } task t20 "Task 20" { duration 6h allocate r20 booking r20 2007-04-23-9:00 +5h { sloppy 2 } ${FixStart "2007-04-23-9:00"} ${FixEnd "2007-04-23-15:00"} } taskreport booking "Booking" { formats html columns no, name, effort, hourly hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/PriorityInversion.tjp0000644000175000017500000000075012614413013026032 0ustar bernatbernatproject pi "Priority Inversion" 2011-02-01 +1m resource r1 "R1" resource r2 "R2" task t1 "T1" { start 2011-02-08 effort 1w allocate r1 priority 400 } # MARK: warning 15 priority_inversion # MARK: info 23 priority_inversion_info task "T2" { precedes t1 { onend } effort 1w allocate r2 priority 600 } task "T3" { start 2011-02-08 effort 1w allocate r2 priority 500 } taskreport "PriorityInversion" { formats html columns no, name, daily hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Correct/Booking3.tjp0000644000175000017500000000101512614413013023762 0ustar bernatbernatproject "Test" 2011-05-15 +1w { scenario s1 "S1" { scenario s2 "S2" } trackingscenario s2 now 2011-05-17 workinghours mon-fri 9:00 - 12:00, 13:00 - 18:00 } include 'checks.tji' resource r1 "R1" resource r2 "R2" task t1 "T1" { effort 2d allocate r1 booking r1 2011-05-16-9:00 +3h, 2011-05-16-13:00 +5h ${FixEndSc "2011-05-17-18:00" "s1"} ${FixEndSc "2011-05-18-18:00" "s2"} } taskreport "Booking3" { formats html columns no, name, scenario, start, end, hourly scenarios s1, s2 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/0000755000175000017500000000000012614413013021446 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_alert1_more_details.tjp0000644000175000017500000000044412614413013026614 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: error 14 ts_alert1_more_details task t1 { work 2d remaining 0d status yellow "Some headline here!" { } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/resource_fail_check.tjp0000644000175000017500000000022712614413013026145 0ustar bernatbernatproject "test" 2010-04-02 +1m # MARK: error 4 resource_fail_check resource r "R" { fail plan.effort != 6 } task "T" { allocate r effort 5d } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_no_rem_or_end.tjp0000644000175000017500000000072512614413013025504 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { duration 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: error 14 ts_no_rem_or_end newtask t2 "A new task" { work 5d status green "All work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_alert2_more_details.tjp0000644000175000017500000000050112614413013026607 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: error 14 ts_alert2_more_details task t1 { work 2d remaining 0d status red "Some headline here!" { summary "I had good fun!" } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_work_too_high.tjp0000644000175000017500000000072412614413013025540 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } # MARK: error 13 ts_work_too_high timesheet r1 2009-11-30 +1w { task t1 { work 8d remaining 4d status green "All work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_underspecified_1.tjp0000644000175000017500000000022112614413013026413 0ustar bernatbernatproject test "Test" "1.0" 2009-02-13 +1m resource r "R" # MARK: error 6 task_underspecified task t "T" { booking r 2009-02-16 { sloppy 2 } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/onstart_wrong_direction.tjp0000644000175000017500000000043212614413013027132 0ustar bernatbernatproject "Test" 2011-01-08 +1m resource r1 "R1" resource r2 "R2" task "Task 1" { task t2 "Task 2" { effort 1w allocate r1 } # MARK: error 12 onstart_wrong_direction task "Task 3" { depends !t2 { onstart } effort 1w allocate r2 scheduling alap } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/account_no_leaf.tjp0000644000175000017500000000027012614413013025303 0ustar bernatbernatproject test "Test" "1.0" 2007-10-31 +1m account foo "Foo" # MARK: error 6 account_no_leaf task t "T" { effort 1d chargeset foo } supplement account foo { account bar "Bar" } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_underspecified_3.tjp0000644000175000017500000000021412614413013026417 0ustar bernatbernatproject test "Test" "1.0" 2009-02-13 +1m resource r "R" # MARK: error 6 task_underspecified task t "T" { scheduling alap allocate r } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_no_work.tjp0000644000175000017500000000070212614413013024350 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: error 14 ts_no_work task t1 { remaining 0d status green "All work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_work_too_low.tjp0000644000175000017500000000072312614413013025421 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } # MARK: error 13 ts_work_too_low timesheet r1 2009-11-30 +1w { task t1 { work 3d remaining 4d status green "All work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/container_duration.tjp0000644000175000017500000000023112614413013026050 0ustar bernatbernatproject test "Test" "1.0" 2007-10-31 +1m resource tux "Tux" # MARK: error 6 container_duration task t "T" { effort 1d allocate tux task s "S" } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_pred_before_1.tjp0000644000175000017500000000025612614413013025706 0ustar bernatbernatproject "Test" 2010-07-12 +1m task t1 "T1" { duration 1w } # MARK: error 7 task_pred_before task t2 "T2" { depends !t1 { gaplength 3d } duration 6d end 2010-07-26 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/overbooked_duration.tjp0000644000175000017500000000027612614413013026236 0ustar bernatbernatproject "Test" 2011-06-24 +1m resource r "R" # MARK: warning 6 overbooked_duration task "T" { allocate r duration 1d booking r 2011-06-24-9:00 +8h, 2011-06-27-9:00 +1h } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_depend_parent.tjp0000644000175000017500000000023212614413013026014 0ustar bernatbernatproject test "Test" "1.0" 2007-04-01 +2m task t1 "T1" { start ${projectstart} task t2 "T2" { depends t1 # MARK: error 5 task_depend_parent } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/resource_warn_check.tjp0000644000175000017500000000023112614413013026174 0ustar bernatbernatproject "test" 2010-04-02 +1m # MARK: warning 4 resource_warn_check resource r "R" { warn plan.effort != 6 } task "T" { allocate r effort 5d } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_overspecified_3.tjp0000644000175000017500000000030212614413013026253 0ustar bernatbernatproject test "Test" "1.0" 2008-02-03 +1m task m "M" { end ${projectstart} } # MARK: error 8 task_overspecified task t "T" { precedes !m length 2d start 2008-02-03 scheduling alap } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_succ_after_2.tjp0000644000175000017500000000031012614413013025540 0ustar bernatbernatproject "Test" 2010-07-12 +1m task t1 "T1" { duration 1w end 2010-07-26 } # MARK: error 8 task_succ_after task t2 "T2" { precedes !t1 { gapduration 3d } duration 6d start ${projectstart} } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/booking_on_vacation.tjp0000644000175000017500000000026512614413013026200 0ustar bernatbernatproject test "Test" "1.0" 2007-04-26 +2m resource tux "Tux" { vacation 2007-04-27 +2d } # MARK: error 8 booking_on_vacation task foo "Foo" { booking tux 2007-04-27-10:00 +1h } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/minend.tjp0000644000175000017500000000017112614413013023436 0ustar bernatbernatproject test "Test" "1.0" 2007-03-28 +2m task t "Task" { end 2007-03-29 # MARK: warning 3 minend minend 2007-03-30 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_14.tjp0000644000175000017500000000061412614413013025304 0ustar bernatbernatproject prj "Loop Detector Test" "$Id" 2005-10-25 - 2005-12-01 task a "Task A" { start 2005-10-25 task b1 "Task B1" { precedes m2 task c1 "Task C1" { depends m2 length 1d } } } task m2 "Milestone2" { start 2005-11-20 milestone } # MARK: warning 9 loop_detected # MARK: info 9 loop_at_end # MARK: info 16 loop_at_start # MARK: info 16 loop_at_end # MARK: error 9 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/maxend.tjp0000644000175000017500000000017112614413013023440 0ustar bernatbernatproject test "Test" "1.0" 2007-03-28 +2m task t "Task" { end 2007-03-29 # MARK: warning 3 maxend maxend 2007-03-28 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/no_tasks.tjp0000644000175000017500000000012412614413013024003 0ustar bernatbernatproject test "Test" "1.0" 2007-03-28 +2m resource r "R" # MARK: error 0 no_tasks taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_depend_self.tjp0000644000175000017500000000023112614413013025453 0ustar bernatbernatproject test "Test" "1.0" 2007-04-01 +2m task t1 "T1" { start ${projectstart} task t2 "T2" { # MARK: error 5 task_depend_self depends t1.t2 } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_10.tjp0000644000175000017500000000110112614413013025270 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-08 task a "Task A" { start 2000-01-01 task b1 "Task B1" { task c1 "Task C1" { length 1d depends !!b2 } task c2 "Task C2" { length 1d depends !c1 } } task b2 "Task B2" { task c1 "Task C1" { length 1d } task c2 "Task C2" { depends !!b1.c2 length 1d } } } # MARK: warning 7 loop_detected # MARK: info 7 loop_at_end # MARK: info 11 loop_at_start # MARK: info 11 loop_at_end # MARK: info 21 loop_at_start # MARK: info 21 loop_at_end # MARK: info 17 loop_at_end # MARK: error 7 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_no_status_work.tjp0000644000175000017500000000035712614413013025761 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: error 14 ts_no_status_work task t1 { work 2d remaining 0d } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_pred_before_2.tjp0000644000175000017500000000026012614413013025702 0ustar bernatbernatproject "Test" 2010-07-12 +1m task t1 "T1" { duration 1w } # MARK: error 7 task_pred_before task t2 "T2" { depends !t1 { gapduration 3d } duration 6d end 2010-07-26 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/milestone_booking.tjp0000644000175000017500000000026712614413013025701 0ustar bernatbernatproject test "Test" "1.0" 2007-10-31 +1m resource foo "Foo" # MARK: error 6 milestone_booking task t "T" { start ${projectstart} booking foo 2007-11-01-10:00 +1h milestone } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/allocate_no_assigned.tjp0000644000175000017500000000026512614413013026325 0ustar bernatbernatproject "Test" 2011-07-06 +1m resource r "R" task "T1" { effort 5d allocate r priority 600 } # MARK: warning 11 allocate_no_assigned task "T2" { length 5d allocate r } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/onend_wrong_direction.tjp0000644000175000017500000000045312614413013026546 0ustar bernatbernatproject "Test" 2011-01-08 +1m resource r1 "R1" resource r2 "R2" task "Task 1" { task t2 "Task 2" { effort 1w allocate r1 scheduling alap } # MARK: error 13 onend_wrong_direction task "Task 3" { precedes !t2 { onend } effort 1w allocate r2 scheduling asap } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_overspecified_2.tjp0000644000175000017500000000030112614413013026251 0ustar bernatbernatproject test "Test" "1.0" 2008-02-03 +1m task m "M" { start ${projectstart} } # MARK: error 8 task_overspecified task t "T" { depends !m length 2d end 2008-02-06 scheduling asap } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/container_booking.tjp0000644000175000017500000000032312614413013025655 0ustar bernatbernatproject test "Test" "1.0" 2007-10-31 +1m resource foo "Foo" # MARK: error 6 container_booking task t "T" { start ${projectstart} booking foo 2007-11-01-10:00 +1h } supplement task t { task bar "Bar" } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_2.tjp0000644000175000017500000000060212614413013025216 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-04 task t1 "Task1" { length 1d depends !t3 } task t2 "Task2" { length 1d depends !t1 } task t3 "Task3" { length 1d depends !t2 } # MARK: warning 3 loop_detected # MARK: info 3 loop_at_end # MARK: info 8 loop_at_start # MARK: info 8 loop_at_end # MARK: info 13 loop_at_start # MARK: info 13 loop_at_end # MARK: error 3 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_pred_before.tjp0000644000175000017500000000023512614413013025463 0ustar bernatbernatproject "Test" 2010-07-12 +1m task t1 "T1" { duration 1w } # MARK: error 7 task_pred_before task t2 "T2" { depends !t1 duration 8d end 2010-07-26 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_3.tjp0000644000175000017500000000044012614413013025217 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-04 task t1 "Task1" { length 1d precedes !t2 } task t2 "Task2" { length 1d precedes !t1 } # MARK: warning 3 loop_detected # MARK: info 3 loop_at_start # MARK: info 8 loop_at_end # MARK: info 8 loop_at_start # MARK: error 3 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_depend_child.tjp0000644000175000017500000000023312614413013025607 0ustar bernatbernatproject test "Test" "1.0" 2007-04-01 +2m task t1 "T1" { # MARK: error 3 task_depend_child depends t1.t2 task t2 "T2" { start ${projectstart} } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/overbooked_length.tjp0000644000175000017500000000031712614413013025666 0ustar bernatbernatproject "Test" 2011-06-24 +1m { now 2011-07-01 } resource r "R" # MARK: warning 8 overbooked_length task "T" { allocate r length 1d booking r 2011-06-24-9:00 +8h, 2011-06-27-9:00 +1h } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_fail_check.tjp0000644000175000017500000000016212614413013025256 0ustar bernatbernatproject "test" 2010-04-02 +1m # MARK: error 4 task_fail_check task "T" { length 2d fail plan.length != 3 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/minstart.tjp0000644000175000017500000000017712614413013024033 0ustar bernatbernatproject test "Test" "1.0" 2007-03-28 +2m task t "Task" { start 2007-03-29 # MARK: warning 3 minstart minstart 2007-03-30 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_depend_unknown.tjp0000644000175000017500000000023312614413013026223 0ustar bernatbernatproject test "Test" "1.0" 2007-04-01 +2m task t1 "T1" { start ${projectstart} task t2 "T2" { depends foo # MARK: error 5 task_depend_unknown } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/impossible_end_dep.tjp0000644000175000017500000000034112614413013026007 0ustar bernatbernatproject prj "Project" "1.0" 2009-10-04 +6m task T1 "T1" { end 2009-12-31 duration 2w } # MARK: error 8 impossible_end_dep task T2 "T2" { precedes !T1 start 2009-12-24 } task T3 "T3" { precedes !T2 duration 1w } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/manager_is_self.tjp0000644000175000017500000000022612614413013025303 0ustar bernatbernatproject "test" 2010-04-03 +1w # MARK: error 4 manager_is_self resource joe "Joe Average" { managers joe } task "T" { effort 1d allocate joe } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_overspecified_1.tjp0000644000175000017500000000021512614413013026254 0ustar bernatbernatproject test "Test" "1.0" 2008-02-03 +1m # MARK: error 4 task_overspecified task t "T" { start 2008-02-04 length 2d end 2008-02-06 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/impossible_start_dep.tjp0000644000175000017500000000034112614413013026376 0ustar bernatbernatproject prj "Project" "1.0" 2009-08-16 +6m task T1 "T1" { start 2009-09-23 duration 2w } # MARK: error 8 impossible_start_dep task T2 "T2" { depends !T1 end 2009-09-30 } task T3 "T3" { depends !T2 duration 1w } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/manager_is_group.tjp0000644000175000017500000000032712614413013025510 0ustar bernatbernatproject "test" 2010-04-03 +1w resource group "Group" { resource "Stan" resource "Oli" } # MARK: error 8 manager_is_group resource joe "Joe Average" { managers group } task "T" { effort 1d allocate joe } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/container_milestone.tjp0000644000175000017500000000026012614413013026224 0ustar bernatbernatproject test "Test" "1.0" 2013-01-22 +1m resource tux "Tux" # MARK: error 6 container_milestone task t "T" { milestone task s "S" { effort 1d allocate tux } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_no_remaining.tjp0000644000175000017500000000070212614413013025337 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: error 14 ts_no_remaining task t1 { work 0d status green "All work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/maxstart.tjp0000644000175000017500000000017712614413013024035 0ustar bernatbernatproject test "Test" "1.0" 2007-03-28 +2m task t "Task" { start 2007-03-29 # MARK: warning 3 maxstart maxstart 2007-03-28 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_11.tjp0000644000175000017500000000066112614413013025303 0ustar bernatbernatproject prj "Loop Detector Test" "$Id" 2000-01-01 - 2000-01-10 task t1 "Task1" { task t2 "Task2" { start 2000-01-01 length 1d } task t3 "Task3" { length 1d depends !t2, !t4 } task t4 "Task4" { length 1d depends !t3 } task t5 "Task5" { length 1d depends !t4 } } # MARK: warning 8 loop_detected # MARK: info 8 loop_at_end # MARK: info 12 loop_at_start # MARK: info 12 loop_at_end # MARK: error 8 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_6.tjp0000644000175000017500000000107212614413013025224 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-04 task t1 "Task1" { end 2000-01-04 task s1 "Sub1" { length 1d precedes !!t3.s1 } } task t2 "Task2" { precedes !t1 task s1 "Sub1" { length 1d } } task t3 "Task3" { task s1 "Sub1" { length 1d precedes !!t2 } } # MARK: warning 6 loop_detected # MARK: info 6 loop_at_start # MARK: info 3 loop_at_start # MARK: info 12 loop_at_end # MARK: info 14 loop_at_end # MARK: info 14 loop_at_start # MARK: info 12 loop_at_start # MARK: info 20 loop_at_end # MARK: info 20 loop_at_start # MARK: error 6 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/effort_no_allocations.tjp0000644000175000017500000000020312614413013026531 0ustar bernatbernatproject test "Test" "1.0" 2008-02-02 +1m # MARK: error 4 effort_no_allocations task t "T" { start ${projectstart} effort 2d } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_succ_after.tjp0000644000175000017500000000026512614413013025330 0ustar bernatbernatproject "Test" 2010-07-12 +1m task t1 "T1" { duration 1w end 2010-07-26 } # MARK: error 8 task_succ_after task t2 "T2" { precedes !t1 duration 8d start ${projectstart} } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/booking_no_duty.tjp0000644000175000017500000000022412614413013025354 0ustar bernatbernatproject test "Test" "1.0" 2007-04-26 +2m resource tux "Tux" # MARK: error 8 booking_no_duty task foo "Foo" { booking tux 2007-04-26-16:00 +3h } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_res_new_task.tjp0000644000175000017500000000052012614413013025354 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan now ${projectstart} } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: warning 15 ts_res_new_task newtask t1 "T1" { work 5d remaining 0d status yellow "Big problem" { summary "Big problem" } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_1.tjp0000644000175000017500000000043512614413013025221 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-04 task t1 "Task1" { length 1d depends !t2 } task t2 "Task2" { length 1d depends !t1 } # MARK: warning 3 loop_detected # MARK: info 3 loop_at_end # MARK: info 8 loop_at_start # MARK: info 8 loop_at_end # MARK: error 3 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/milestone_start_end.tjp0000644000175000017500000000021512614413013026225 0ustar bernatbernatproject test "Test" "1.0" 2008-02-02 +1m # MARK: error 4 milestone_start_end task t "T" { start 2008-02-10 end 2008-02-11 milestone } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_9.tjp0000644000175000017500000000051312614413013025226 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-10 task t1 "Task1" { task t2 "Task2" { start 2000-01-01 length 1d depends !t3 } task t3 "Task3" { length 1d depends !t2 } } # MARK: warning 4 loop_detected # MARK: info 4 loop_at_end # MARK: info 9 loop_at_start # MARK: info 9 loop_at_end # MARK: error 4 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_depend_multi.tjp0000644000175000017500000000024612614413013025662 0ustar bernatbernatproject test "Test" "1.0" 2007-04-01 +2m task t1 "T1" { start ${projectstart} duration 1d } task t2 "T2" { # MARK: error 8 task_depend_multi depends t1, t1 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/overbooked_effort.tjp0000644000175000017500000000027212614413013025672 0ustar bernatbernatproject "Test" 2011-06-24 +1m resource r "R" # MARK: warning 6 overbooked_effort task "T" { allocate r effort 1d booking r 2011-06-24-9:00 +8h, 2011-06-27-9:00 +1h } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_no_expected_end.tjp0000644000175000017500000000070712614413013026022 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { duration 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: error 14 ts_no_expected_end task t1 { work 0d status green "All work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/sched_runaway.tjp0000644000175000017500000000053312614413013025022 0ustar bernatbernatproject "Runaway" 2013-06-23 +3w resource r "R" # MARK: warning 11 runaway # MARK: info 11 runaway_tasks # MARK: info 17 runaway_competitor # MARK: warning 11 unscheduled_tasks # MARK: warning 11 unscheduled_task # MARK: error 11 sched_runaway task "T low" { effort 15d priority 1 allocate r } task "T high" { effort 10d allocate r } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/ts_no_headline1.tjp0000644000175000017500000000070512614413013025223 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2009-11-30 +1w { # MARK: error 14 ts_no_headline task t1 { work 2d remaining 0d status green "" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_5.tjp0000644000175000017500000000110212614413013025215 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-08 task a "Task A" { start 2000-01-01 task b1 "Task B1" { task c1 "Task C1" { length 1d depends !!b2 } task c2 "Task C2" { length 1d depends !c1 } } task b2 "Task B2" { task c1 "Task C1" { length 1d } task c2 "Task C2" { depends !!b1.c2 length 1d } } } # MARK: warning 7 loop_detected # MARK: info 7 loop_at_end # MARK: info 11 loop_at_start # MARK: info 11 loop_at_end # MARK: info 21 loop_at_start # MARK: info 21 loop_at_end # MARK: info 17 loop_at_end # MARK: error 7 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_13.tjp0000644000175000017500000000060112614413013025277 0ustar bernatbernatproject test "Test" "1.0" 2005-10-25 - 2005-12-01 task a "Task A" { start 2005-10-25 task b1 "Task B1" { depends m2 task c1 "Task C1" { precedes m2 length 1d } } } task m2 "Milestone2" { start 2005-11-20 milestone } # MARK: warning 9 loop_detected # MARK: info 9 loop_at_start # MARK: info 16 loop_at_end # MARK: info 16 loop_at_start # MARK: error 9 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/booking_conflict.tjp0000644000175000017500000000027012614413013025475 0ustar bernatbernatproject test "Test" "1.0" 2007-04-24 +2m resource tux "Tux" # MARK: error 9 booking_conflict task foo "Foo" { booking tux 2007-04-25-10:00 +2h booking tux 2007-04-25-11:00 +1h } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_4.tjp0000644000175000017500000000106512614413013025224 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-04 task t1 "Task1" { start 2000-01-01 task s1 "Sub1" { length 1d depends !!t3.s1 } } task t2 "Task2" { depends !t1 task s1 "Sub1" { length 1d } } task t3 "Task3" { task s1 "Sub1" { length 1d depends !!t2 } } # MARK: warning 6 loop_detected # MARK: info 6 loop_at_end # MARK: info 3 loop_at_end # MARK: info 12 loop_at_start # MARK: info 14 loop_at_start # MARK: info 14 loop_at_end # MARK: info 12 loop_at_end # MARK: info 20 loop_at_start # MARK: info 20 loop_at_end # MARK: error 6 loop_end taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_succ_after_1.tjp0000644000175000017500000000030612614413013025544 0ustar bernatbernatproject "Test" 2010-07-12 +1m task t1 "T1" { duration 1w end 2010-07-26 } # MARK: error 8 task_succ_after task t2 "T2" { precedes !t1 { gaplength 3d } duration 6d start ${projectstart} } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/task_warn_check.tjp0000644000175000017500000000016412614413013025314 0ustar bernatbernatproject "test" 2010-04-02 +1m # MARK: warning 4 task_warn_check task "T" { length 2d warn plan.length != 3 } taskjuggler-3.5.0/test/TestSuite/Scheduler/Errors/loop_detected_12.tjp0000644000175000017500000000051512614413013025302 0ustar bernatbernatproject test "Test" "1.0" 2000-01-01 - 2000-01-10 task t1 "Task1" { task t2 "Task2" { end 2000-01-04 length 1d precedes !t3 } task t3 "Task3" { length 1d precedes !t2 } } # MARK: warning 4 loop_detected # MARK: info 4 loop_at_start # MARK: info 9 loop_at_end # MARK: info 9 loop_at_start # MARK: error 4 loop_end taskjuggler-3.5.0/test/TestSuite/Syntax/0000755000175000017500000000000012614413013017542 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Syntax/Correct/0000755000175000017500000000000012614413013021143 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Syntax/Correct/ProjectIDs.tjp0000644000175000017500000000055512614413013023675 0ustar bernatbernatproject "ProjectIDs example" 2006-08-22 +1m { timezone "America/Denver" } task t1 "Task 1" { start 2006-08-22 # This task has project ID "mainID" } projectid prj1 projectids prj2 task t2 "Task 2" { start 2006-08-22 # This task has now project ID "prj1" } task t3 "Task 3" { start 2006-08-22 projectid prj2 # This task has now project ID "prj2" } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/TimeFrame.tjp0000644000175000017500000000044412614413013023535 0ustar bernatbernatproject "Simple Project" 2000-01-01-12:00 - 2000-01-04-18:00 { timezone "America/Denver" } resource tux "Tux" task t1 "Task1" { start 2000-01-01-12:00 length 1d } task t2 "Task2" { start 2000-01-02 duration 1d } task t3 "Task3" { start 2000-01-02 effort 1d allocate tux } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Complete.tjp0000644000175000017500000000063312614413013023434 0ustar bernatbernatproject simple "Simple Project" "1.0" 2007-01-01 +2m { timezone "America/Denver" now 2007-01-15 } # *** EXAMPLE: 1 + task "Build house" { start 2007-01-06 duration 30d # This task should have been completed on Jan 15, but only # 20% of the task have been completed so far. complete 20 } taskreport "Complete" { formats html columns name, end { title "Due date" }, complete, gauge, chart } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/EnvVar.tjp0000644000175000017500000000025312614413013023063 0ustar bernatbernatproject "Test" 2011-05-05 +6m task t "A $(TEST1) task" { note $(TEST2) duration 12$(TEST3)d } taskreport "EnvVar" { columns name, duration, note formats html } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/AlertLevels.tjp0000644000175000017500000000124312614413013024104 0ustar bernatbernatproject "Alert Levels" 2011-11-24 +2m { alertlevels green "Low" "#2AA46C", blue "Guarded" "#457CC4", yellow "Elevated" "#F1D821", orange "High" "#F99836", red "Severe" "#E43745" } task "Holiday Season" { journalentry 2011-11-24 "All safe unless you are a turkey" { alert green } journalentry 2011-11-27 "Strange lights appearing everywhere" { alert blue } journalentry 2011-12-01 "Alarm bells heared" { alert yellow } journalentry 2011-12-20 "Everybody has strange packages" { alert orange } journalentry 2011-12-25 "Guy with red coat entered through chimney" { alert red } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/textreport.tjp0000644000175000017500000000047412614413013024107 0ustar bernatbernatproject "Test" 2011-12-11 +1m task "Foo" textreport frame "textreport" { taskreport r1 "" taskreport r2 "" formats html header -8<- This is the header ---- ->8- left "<[report id='frame.r1']>" center "Center" right "<[report id='frame.r2']>" footer -8<- ---- This is the footer ->8- } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Journal.tjp0000644000175000017500000000101012614413013023264 0ustar bernatbernatproject "Project with Journal" 2009-05-04 +1m { timezone "America/Denver" journalentry 2009-05-05 "The project started." journalentry 2009-05-10 "We made some progress." { summary "All jobs have been assigned. The crew is working away." details "Let's hope, we can keep the good spirit." } } resource tux "Tux" { journalentry 2009-05-07 "This guy is a bummer." } task t1 "Task1" { journalentry 2009-05-08 "Probably will be done sooner." journalentry 2009-05-12 "Maybe not." start 2009-05-05 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Limits-1.tjp0000644000175000017500000000333412614413013023264 0ustar bernatbernatproject limits "Limits" "1.0" 2007-03-01 +1y { timezone "Europe/Amsterdam" } # Default limit that affects all subsequently defined resources limits { weeklymax 4d } # *** EXAMPLE: 1 + # *** EXAMPLE: 2 + # *** EXAMPLE: 3 + # *** EXAMPLE: 4 + # *** EXAMPLE: 6 + resource r1 "R1" { # Limit the usage of this resource to a maximum of 2 hours per day, # 6 hours per week and 2.5 days per month. limits { dailymax 2h weeklymax 6h monthlymax 2.5d } } # *** EXAMPLE: 6 - resource r2 "R2" # *** EXAMPLE: 2 - # *** EXAMPLE: 3 - # *** EXAMPLE: 4 - task t1 "Task 1" { start 2007-03-30 effort 10d allocate r2 limits { dailymax 3h } } # *** EXAMPLE: 4 + task t2 "Task 2" { start 2007-03-30 effort 10d allocate r2 limits { dailymin 5h } } # *** EXAMPLE: 1 - # *** EXAMPLE: 2 + # *** EXAMPLE: 4 - task t3 "Task 3" { start 2007-03-30 effort 10d allocate r2 limits { weeklymax 2d } } # *** EXAMPLE: 2 - # *** EXAMPLE: 3 + task t4 "Task 4" { start 2007-03-30 effort 10d allocate r2 limits { monthlymax 1w } } # *** EXAMPLE: 1 + # *** EXAMPLE: 2 + # *** EXAMPLE: 4 + task t5 "Task 5" { start ${projectstart} duration 60d # allocation is subject to resource limits allocate r1 } # *** EXAMPLE: 4 - task t6 "Task 6" { start ${projectstart} duration 60d allocate r2 limits { dailymax 4h weeklymax 3d monthlymax 2w } } # *** EXAMPLE: 3 - # *** EXAMPLE: 5 + # *** EXAMPLE: 4 + task t7 "Task 7" { start 2007-06-20 duration 20d allocate r1, r2 # limits can also be specified per resource limits { # Limit r1 to half days only dailymax 4h { resources r1 } # Limit r2 to 6 hours per day dailymax 6h { resources r2 } } } # *** EXAMPLE: 1 - # *** EXAMPLE: 2 - # *** EXAMPLE: 4 - # *** EXAMPLE: 5 - taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/ResourcePrefix.tjp0000644000175000017500000000057612614413013024637 0ustar bernatbernatproject "Resource Prefix Example" 2009-09-13 +1m { timezone "America/Denver" } resource team "Team" { resource foo "Foo" } include "ResourcePrefix.tji" { resourceprefix team } supplement resource bar { workinghours mon-fri 8:00-15:00 workinghours sat, sun off } task t "Task" { effort 10d allocate foo, bar } resourcereport rp "ResourcePrefix" { formats html } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Shift.tjp0000644000175000017500000000323712614413013022744 0ustar bernatbernatproject "Example" 2000-01-01 - 2000-03-31 { timezone "America/Denver" } shift s1 "Shift1" { # Special working hours Monday to Wednesday. Use program defaults # for other days. workinghours mon 10:00 - 12:00, 13:00 - 15:00 workinghours tue 9:00 - 14:00 workinghours wed off shift s2 "Shift2" { # Like s1 but with different times on Monday workinghours mon 10:00 - 17:00 } } resource r1 "Resource1" { shifts s1 2000-01-01 - 2000-01-10, s2 2000-01-11 - 2000-01-20 } shift s3 "Part-time schedule 1" { # The resource works on mondays, wednesdays and fridays. # The days that the resource doesn't work must be mentioned # explicitely otherwise the defaults values are used (usually # full-time employment). workinghours mon,fri 9:00 - 12:00, 13:00 - 18:00 workinghours wed 9:00 - 12:00 workinghours tue, thu off } shift s4 "Part-time schedule 2" { # The resource changes his schedule to work on tuesday and # thursdays. The days that the resource doesn't work must be # mentioned explicitely, otherwise the default values are used # (usually full-time employment). workinghours tue, thu 9:00 - 12:00, 13:00 - 18:00 workinghours mon, wed, fri off } shift s5 "All-day, all-week shift" { workinghours mon-sun 0:00 - 24:00 } # Now determine when these schedules are applicable resource r2 "Resource2" { # r2 works three days a week from January to June shifts s3 2005-01-01 - 2005-01-15, # r2 switches to two days a week s4 2005-01-15 - 2006-01-01 } task t1 "Task1" { start 2000-01-01 length 200h # During the specified interval only work at the shift s2 working # hours. shifts s2 2000-01-09 - 2000-01-17 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Leave.tjp0000644000175000017500000000140312614413013022714 0ustar bernatbernatproject "Annual Leave" 2011-12-19 +1y { now 2012-07-01 } leaves holiday "Christmas" 2011-12-24 +3d, holiday "New Year" 2011-12-31 +3d shift s1 "Shift 1" { leaves annual 2011-12-19 +3w, special 2012-01-12 +1d } resource team "Team" { leaveallowances annual 2011-12-19 20d leaves holiday 2012-01-06 resource r1 "R1" { leaves annual 2011-12-19 +3w, special 2012-01-12 +1d } resource r2 "R2" { leaveallowances annual 2012-06-01 -10d leaves sick 2012-01-04 +2d, unpaid 2012-01-10 +3d } } resource r3 "R3" { shifts s1 2011-12-19 +3w leaves sick 2012-01-04 +2d } task "foo" resourcereport "." { formats html columns name, annualleave, annualleavebalance, sickleave, specialleave, unpaidleave } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Depends1.tjp0000644000175000017500000000053712614413013023332 0ustar bernatbernatproject "P" 2007-11-09 - 2007-12-24 { timezone "America/Denver" } task foo1 "foo1" { task foo2 "foo2" { start 2007-12-04 milestone } task foo3 "foo3" { depends !foo2 length 1d } } task bar "bar" { depends foo1.foo2 length 2d } task bar1 "bar1" { depends foo1 { gapduration 2d }, bar { gaplength 1d } duration 2d } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/manual2example.rb0000644000175000017500000000133212614413013024402 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = manual2example.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009 by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # def removeTags(fileName, destDir) oFile = File.open("../../../../examples/#{destDir}/#{fileName}", 'w') File.open(fileName, 'r') do |iFile| while line = iFile.gets oFile.puts line unless line =~ /^# \*\*\* EXAMPLE:/ end end oFile.close end removeTags('tutorial.tjp', 'Tutorial') removeTags('template.tjp', 'ProjectTemplate') taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Currencyformat.tjp0000644000175000017500000000047012614413013024666 0ustar bernatbernatproject prj "Project" "1.0" 2007-01-01 - 2007-03-01 { timezone "Europe/Berlin" # German currency format: e. g. -10.000,20 5.014,11 numberformat "-" "" "." "," 2 # US currency format: e. g. (10,000.20) 5,014.11 currencyformat "(" ")" "," "." 2 } task t "Task" { start ${projectstart} milestone } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Numberformat.tjp0000644000175000017500000000046312614413013024326 0ustar bernatbernatproject prj "Project" "1.0" 2000-01-01 - 2000-03-01 { timezone "Europe/Berlin" # German number format: e. g. -10000,20 5014,11 numberformat "-" "" "" "," 2 # US currency format: e. g. (10,000.20) 5,014.11 currencyformat "(" ")" "," "." 2 } task t "Task" { start ${projectstart} milestone } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/AutoMacros.tjp0000644000175000017500000000035412614413013023741 0ustar bernatbernatproject prj "Auto Macro Test" "1.0" 2006-09-22-0:00 +1m { timezone "UTC" now 2006-10-15 } task items "Project breakdown" { start ${projectstart} } taskreport tasks "My Tasks" { formats html start ${now} end ${projectend} } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/AccountReport.tjp0000644000175000017500000000250112614413013024450 0ustar bernatbernatproject "AccountReport" 2011-11-09 +1y { currencyformat "(" ")" "," "." 0 } account resourceCost "Resource Cost" { aggregate resources account teamA "Team A" account teamB "Team B" } account productCost "Product Cost" { aggregate tasks } account customerPayments "Customer Payments" { credits 2011-12-01 "First downpayment" 80000, 2012-06-01 "Second downpayment" 200000 } balance productCost customerPayments resource teamA "Team A" { rate 420 chargeset teamA resource "R1" resource "R2" } resource teamB "Team B" { rate 380 chargeset teamB resource "R3" resource "R4" } task "Products" { chargeset productCost task p1 "Product 1" { effort 20m allocate teamA, teamB } task p2 "Product 2" { effort 18m allocate teamA priority 600 } task p3 "Product 3" { effort 6m allocate teamB priority 600 } task mf "Manufacturing" { depends !p1, !p2, !p3 duration 2w charge 12000 onend } task "Final Payment" { depends !mf purge chargeset chargeset customerPayments charge 170000 onstart } } accountreport "TeamBudget" { formats html accountroot resourceCost balance - columns no, name, quarterly { celltext 1 "<-query attribute='turnover'->" } } accountreport "ProfiAndLoss" { formats html columns no, name, monthly } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/String.tjp0000644000175000017500000000036212614413013023131 0ustar bernatbernatproject "String Tests" 2005-06-06 - 2005-06-26 { timezone "America/Denver" } resource tux "Tux \"The Penguing\" Tuxus" task items "Project Plan\\\\Breakdown" { start 2005-06-06 effort 2d allocate tux } taskreport tasks "My Tasks" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/MinMax.tjp0000644000175000017500000000063312614413013023055 0ustar bernatbernatproject "Min Max Example" 2005-06-06 - 2005-06-26 { timezone "America/Denver" } task items "Project breakdown" { start 2005-06-07 task plan "Plan work" { note "Some more information about this task." # Set acceptable interval for task start minstart 2005-06-06 maxstart 2005-06-08 length 3d # Set acceptable interval for task end minend 2005-06-09 maxend 2005-06-11 } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Freeze.tjp0000644000175000017500000000110512614413013023077 0ustar bernatbernatproject "Project" 2000-01-01-0:00 - 2000-03-01-0:00 { timezone "Europe/Berlin" now 2000-01-08 trackingscenario plan } resource r "Resource" task t "Task" { start ${projectstart} effort 10d allocate r } # Include the data from previous scheduling run. # We assume that the exported data has been frozen. # By importing it, we make sure they don't get changed any more. include "CompletedWork.tji" # Export only bookings for 1st week as resource supplements export "CompletedWork.tji" { start 2000-01-01 end 2000-01-08 taskattributes booking hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/ResourcePrefix.tji0000644000175000017500000000002412614413013024614 0ustar bernatbernatresource bar "Bar" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/TraceReport.tjp0000644000175000017500000000023312614413013024112 0ustar bernatbernatproject "Trace Reports" 2012-01-14 +2m task "Foo" task "Bar" task "FooBar" tracereport "TraceReport" { columns end { title "<-name->" } hidetask 0 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Macro-3.tjp0000644000175000017500000000052112614413013023061 0ustar bernatbernatmacro major [6] macro minor [0] macro maint [0] macro content [foo] macro content_title [Project] macro start_date [2009-12-01] macro end_date [2012-09-30] project ${content}${major}${minor}${maint} "${content_title}" "${major}" 2009-12-01 - 2012-09-30 { timezone "America/Denver" } task "foo" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Simple.tjp0000644000175000017500000000064112614413013023114 0ustar bernatbernatproject "Simple Project" 2005-06-06 - 2005-06-26 { timezone "America/Denver" } resource tux "Tux" task items "Project breakdown" { start 2005-06-06 task plan "Plan work" { length 3d } task implementation "Implement work" { effort 5d allocate tux depends !plan } task acceptance "Customer acceptance" { duration 5d depends !implementation } } taskreport tasks "My Tasks" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Macro-1.tjp0000644000175000017500000000044612614413013023065 0ustar bernatbernatproject "Example Project" 2008-01-18 +2m { timezone "America/Denver" } macro allocateGroup [ allocate tux1, tux2 ] resource tux1 "Tux1" resource tux2 "Tux2" task t1 "Task1" { start ${projectstart} # built-in macro } task t2 "Task2" { depends !t1 effort 20d ${allocateGroup} } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Select.tjp0000644000175000017500000000126212614413013023102 0ustar bernatbernatproject "Project" 2000-01-01 - 2000-03-01 { timezone "America/Denver" } resource tuxus "Tuxus" resource tuxia "Tuxia" task t1 "Task 1" { start 2000-01-01 effort 5d # First try to allocate Tuxus. When he is not available try Tuxia. allocate tuxus { alternative tuxia select order } } task t2 "Task 2" { start 2000-01-01 effort 5d # Use tuxux or tuxia, whoever is available and try to balance # the allocated load. allocate tuxus { alternative tuxia select minloaded} } task t3 "Task 3" { start 2000-01-01 effort 5d # For slave drivers: Always pick the resource that has been loaded # the most already. allocate tuxus { alternative tuxia select maxloaded} } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Include.tjp0000644000175000017500000000020212614413013023237 0ustar bernatbernatproject "Include Test" 2010-02-26 +1w { timezone "America/Denver" } include "include/dir3/all.tji" include "include/file1.tji" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/template.tjp0000644000175000017500000002204612614413013023501 0ustar bernatbernat/* * This file contains a project skeletton. It is part of the * TaskJuggler project management tool. You can use this as a basis to * start you own project file. */ project your_project_id "Your Project Title" 2011-11-11-0:00--0500 +4m { # Set the default time zone for the project. If not specified, UTC # is used. timezone "America/New_York" # Hide the clock time. Only show the date. timeformat "%Y-%m-%d" # Use US format for numbers numberformat "-" "" "," "." 1 # Use US financial format for currency values. Don't show cents. currencyformat "(" ")" "," "." 0 # Pick a day during the project that will be reported as 'today' in # the project reports. If not specified, the current day will be # used, but this will likely be outside of the project range, so it # can't be seen in the reports. now 2011-12-24 # The currency for all money values is the Euro. currency "USD" # You can define multiple scenarios here if you need them. #scenario plan "Plan" { # scenario actual "Actual" #} # You can define your own attributes for tasks and resources. This # is handy to capture additonal information about the project that # is not directly impacting the project schedule but you like to # keep in one place. #extend task { # reference spec "Link to Wiki page" #} #extend resource { # text Phone "Phone" #} } copyright "Claim your rights here" # If you have any text block that you need multiple times to describe # your project, you should define a macro for it. Macros can even have # variable segments that you can set upon calling the macro. # # macro Task [ # task "A ${1} task" { # } # ] # # Can be called as # ${Task "big"} # to generate # task "A big task" { # } # You can attach flags to accounts, resources and tasks. These can be # used to filter out subsets of them during reporting. flags important, hidden # If you want to do budget planning for you project, you need to # define some accounts. account cost "Project Cost" { account dev "Development" account doc "Documentation" } account rev "Customer Payments" # The Profit&Loss analysis should be rev - cost accounts. balance cost rev # Define you public holidays here. vacation "New Year's Day" 2012-01-02 vacation "Birthday of Martin Luther King, Jr." 2012-01-16 vacation "Washington's Birthday" 2012-02-20 vacation "Memorial Day" 2012-05-28 vacation "Independence Day" 2012-07-04 vacation "Labor Day" 2012-09-03 vacation "Columbus Day" 2012-10-08 vacation "Veterans Day" 2012-11-12 vacation "Thanksgiving Day" 2012-11-22 vacation "Christmas Day" 2012-12-25 # The daily default rate of all resources. This can be overridden for each # resource. We specify this, so that we can do a good calculation of # the costs of the project. rate 400.0 # This is a set of example resources. resource r1 "Resource 1" resource t1 "Team 1" { managers r1 resource r2 "Resource 2" resource r3 "Resource 3" } # This is a resource that does not do any work. resource s1 "System 1" { efficiency 0.0 rate 600.0 } task project "Project" { task wp1 "Workpackage 1" { task t1 "Task 1" task t2 "Task 2" } task wp2 "Work package 2" { depends !wp1 task t1 "Task 1" task t2 "Task 2" } task deliveries "Deliveries" { task "Item 1" { depends !!wp1 } task "Item 2" { depends !!wp2 } } } # Now the project has been specified completely. Stopping here would # result in a valid TaskJuggler file that could be processed and # scheduled. But no reports would be generated to visualize the # results. navigator navbar { hidereport 0 } macro TaskTip [ tooltip istask() -8<- '''Start: ''' <-query attribute='start'-> '''End: ''' <-query attribute='end'-> ---- '''Resources:''' <-query attribute='resources'-> ---- '''Precursors: ''' <-query attribute='precursors'-> ---- '''Followers: ''' <-query attribute='followers'-> ->8- ] textreport frame "" { header -8<- == TaskJuggler Project Template == <[navigator id="navbar"]> ->8- footer "----" textreport index "Overview" { formats html center '<[report id="overview"]>' } textreport "Status" { formats html center -8<- <[report id="status.dashboard"]> ---- <[report id="status.completed"]> ---- <[report id="status.ongoing"]> ---- <[report id="status.future"]> ->8- } textreport wps "Work packages" { textreport wp1 "Work package 1" { formats html center '<[report id="wp1"]>' } textreport wp2 "Work package 2" { formats html center '<[report id="wp2"]>' } } textreport "Deliveries" { formats html center '<[report id="deliveries"]>' } textreport "ContactList" { formats html title "Contact List" center '<[report id="contactList"]>' } textreport "ResourceGraph" { formats html title "Resource Graph" center '<[report id="resourceGraph"]>' } } # A traditional Gantt chart with a project overview. taskreport overview "" { header -8<- === Project Overview === The project is structured into 2 work packages. # Specification # <-reportlink id='frame.wps.wp1'-> # <-reportlink id='frame.wps.wp2'-> # Testing === Original Project Plan === ->8- columns bsi { title 'WBS' }, name, start, end, effort, cost, revenue, chart { ${TaskTip} } # For this report we like to have the abbreviated weekday in front # of the date. %a is the tag for this. timeformat "%a %Y-%m-%d" loadunit days hideresource 1 balance cost rev caption 'All effort values are in man days.' footer -8<- === Staffing === All project phases are properly staffed. See [[ResourceGraph]] for detailed resource allocations. === Current Status === Some blurb about the current situation. ->8- } # Macro to set the background color of a cell according to the alert # level of the task. macro AlertColor [ cellcolor plan.alert = 0 "#00D000" # green cellcolor plan.alert = 1 "#D0D000" # yellow cellcolor plan.alert = 2 "#D00000" # red ] taskreport status "" { columns bsi { width 50 title 'WBS' }, name { width 150 }, start { width 100 }, end { width 100 }, effort { width 100 }, alert { tooltip plan.journal != '' "<-query attribute='journal'->" width 150 }, status { width 150 } taskreport dashboard "" { headline "Project Dashboard (<-query attribute='now'->)" columns name { title "Task" ${AlertColor} width 200}, resources { width 200 ${AlertColor} listtype bullets listitem "<-query attribute='name'->" start ${projectstart} end ${projectend} }, alerttrend { title "Trend" ${AlertColor} width 50 }, journal { width 350 ${AlertColor} } journalmode status_up journalattributes headline, author, date, summary, details hidetask ~hasalert(0) sorttasks alert.down, plan.end.up period %{${now} - 1w} +1w } taskreport completed "" { headline "Already completed tasks" hidetask ~(plan.end <= ${now}) } taskreport ongoing "" { headline "Ongoing tasks" hidetask ~((plan.start <= ${now}) & (plan.end > ${now})) } taskreport future "" { headline "Future tasks" hidetask ~(plan.start > ${now}) } } # A list of tasks showing the resources assigned to each task. taskreport wp1 "" { headline "Work package 1 - Resource Allocation Report" columns bsi { title 'WBS' }, name, start, end, effort { title "Work" }, duration, chart { ${TaskTip} scale day width 500 } timeformat "%Y-%m-%d" hideresource ~(isleaf() & isleaf_()) sortresources name.up taskroot project.wp1 } # A list of tasks showing the resources assigned to each task. taskreport wp2 "" { headline "Work package 2 - Resource Allocation Report" columns bsi { title 'WBS' }, name, start, end, effort { title "Work" }, duration, chart { ${TaskTip} scale day width 500 } timeformat "%Y-%m-%d" hideresource ~(isleaf() & isleaf_()) sortresources name.up taskroot project.wp2 } # A list of all tasks with the percentage completed for each task taskreport deliveries "" { headline "Project Deliverables" columns bsi { title 'WBS' }, name, start, end, note { width 150 }, complete, chart { ${TaskTip} } taskroot project.deliveries hideresource 1 } # A list of all employees with their contact details. resourcereport contactList "" { headline "Contact list and duty plan" columns name, email { celltext 1 "[mailto:<-email-> <-email->]" }, managers { title "Manager" }, chart { scale day } hideresource ~isleaf() sortresources name.up hidetask 1 } # A graph showing resource allocation. It identifies whether each # resource is under- or over-allocated for. resourcereport resourceGraph "" { headline "Resource Allocation Graph" columns no, name, effort, rate, weekly { ${TaskTip} } loadunit shortauto # We only like to show leaf tasks for leaf resources. hidetask ~(isleaf() & isleaf_()) sorttasks plan.start.up } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Scenario.tjp0000644000175000017500000000060712614413013023430 0ustar bernatbernat# *** EXAMPLE: header + project "Example" 2007-05-29 - 2007-07-01 { timezone "America/Denver" scenario plan "Planned Scenario" { scenario actual "Actual Scenario" scenario test "Test Scenario" { active no } } } # *** EXAMPLE: header - # *** EXAMPLE: task + task t "Task" { start 2007-05-29 actual:start 2007-06-03 test:start 2007-06-07 } # *** EXAMPLE: task - taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/TaskPrefix.tji0000644000175000017500000000005412614413013023732 0ustar bernatbernattask sub_task1 "Sub task 1" { effort 1d } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/LogicalFunction.tjp0000644000175000017500000000047512614413013024750 0ustar bernatbernatproject "Logical Function Demo" 2009-11-21 +2w { timezone "America/Denver" } resource "Team" { resource joe "Joe" } task "Parent" { task "Sub" { effort 1w allocate joe } } # *** EXAMPLE: 1 + taskreport "LogicalFunction" { formats html hideresource ~(isleaf() & isleaf_()) } # *** EXAMPLE: 1 - taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Macro-4.tjp0000644000175000017500000000006512614413013023065 0ustar bernatbernatproject "Test" 2012-08-17 +1m task t ${?undef} "T" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Period.tjp0000644000175000017500000000043312614413013023104 0ustar bernatbernatproject prj "Period Project" "1.0" 2006-09-24 +3m { timezone "America/Denver" now 2006-10-02 } task items "Project breakdown" { start ${projectstart} task plan "Plan work" { period 2006-10-01 +2w } } taskreport tasks "My Tasks" { formats html period ${now} +1w } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/HtmlTaskReport.tjp0000644000175000017500000000122412614413013024604 0ustar bernatbernatproject "Simple Project" 2005-06-06 - 2005-06-26 { timezone "America/Denver" } copyright "Bucks Beavis Inc." resource tux "Tux" task items "Project breakdown" { start 2005-06-06 task plan "Plan work" { length 3d } task implementation "Implement work" { effort 5d allocate tux depends !plan } task acceptance "Customer acceptance" { duration 5d depends !implementation } } taskreport breakdown "ProjectBreakdown.html" { formats html caption "This is the project breakdown" headline "Project Breakdown" columns name, start, end, daily # Don't hide any resource, meaning show them all. hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Milestone.tjp0000644000175000017500000000052412614413013023622 0ustar bernatbernatproject prj "Milestone demo" "1.0" 2005-07-15 - 2005-08-01 { timezone "America/Denver" } task project_start "Project Start" { start 2005-07-15 milestone } task deadline "Important Deadline" { start 2005-07-20 # A task with only a start or end date and no duration specification # is automatically assumed to be a milestone. } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Durations.tjp0000644000175000017500000000124712614413013023636 0ustar bernatbernatproject "Duration Example" 2007-06-06 - 2007-06-26 { timezone "America/Denver" } resource tux "Tux" task t "Enclosing" { start 2007-06-06 task durationTask "Duration Task" { # This task is 10 calendar days long. duration 10d } task intervalTask "Interval Task" { # This task is similar to the durationTask. Instead of a start # date and a duration it has a fixed start and end date. end 2007-06-17 } task lengthTask "Length Task" { # This task 10 working days long. So about 12 calendar days. length 10d } task effortTask "Effort Task" { # Tux will need 10 days to complete this task. effort 10d allocate tux } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/0000755000175000017500000000000012614413013022566 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir3/0000755000175000017500000000000012614413013023427 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir3/all.tji0000644000175000017500000000006112614413013024704 0ustar bernatbernatinclude "file1.tji" { } include "file2.tji" { } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir3/file2.tji0000644000175000017500000000001712614413013025136 0ustar bernatbernatresource "RF2" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir3/file1.tji0000644000175000017500000000002312614413013025132 0ustar bernatbernatresource "RF1" { } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir2/0000755000175000017500000000000012614413013023426 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir2/file3.tji0000644000175000017500000000001412614413013025133 0ustar bernatbernattask "Foo" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir1/0000755000175000017500000000000012614413013023425 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir1/file5.tji0000644000175000017500000000001712614413013025137 0ustar bernatbernatresource "R2" taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/dir1/file2.tji0000644000175000017500000000010612614413013025133 0ustar bernatbernatresource "R" include "../dir2/file3.tji" { } include "file5.tji" { } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/include/file1.tji0000644000175000017500000000003612614413013024275 0ustar bernatbernatinclude "dir1/file2.tji" { } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/StatusSheet.tjp0000644000175000017500000000156212614413013024142 0ustar bernatbernatproject "test" 2009-11-30 +2m { timezone "America/Denver" trackingscenario plan now ${projectstart} } resource r1 "R1" resource r2 "R2" resource r3 "R3" task t1 "Task 1" { effort 5d allocate r1 } task t2 "Task 2" { task t3 "Task 3" { effort 10d allocate r2 } } statussheet r3 2009-12-04 { task t1 { status green "All work done" { author r1 summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } task t2 { task t3 { status red "I need more time" { author r2 summary "This takes longer than expected" details -8<- To finish on time, I need help. Get this r1 guy to help me out here. * I want to have fun too! ->8- } } } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/TaskRoot.tjp0000644000175000017500000000115012614413013023425 0ustar bernatbernatproject "Taskroot Example" 2005-07-22 - 2005-08-26 { timezone "America/Denver" } task items "Project breakdown" { start 2005-07-22 task plan "Plan work" { length 3d } task implementation "Implement work" { task phase1 "Phase 1" { length 5d depends !!plan } task phase2 "Phase 2" { length 3d depends !phase1 } task phase3 "Phase 3" { length 4d depends !phase2 } } task acceptance "Customer acceptance" { duration 5d depends !implementation } } taskreport tasks "My Tasks" { formats html taskroot items.implementation } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Macro-2.tjp0000644000175000017500000000030712614413013023062 0ustar bernatbernatproject "Test" 2010-04-28 +1w { timezone "America/Denver" } macro task [ task "${0}" ] macro tname [ Prepare ] macro meal [ Crème brûlée ] task "${meal} ${tname}" ${task "${tname} ${meal} "} taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Manager.tjp0000644000175000017500000000102712614413013023234 0ustar bernatbernatproject "test" 2010-04-03 +1w { timezone "America/Denver" } resource "The Company" { resource ceo "Big Boss" managers ceo resource "R&D Team" { resource vpe "VP Engineering" purge managers managers vpe resource "The Hacker" resource "Doc Writer" } resource "F&A Team" { resource coo "Chief Operating Officer" purge managers managers coo resource "HR Lady" resource "Accountant" } } task "T" resourcereport "Managers" { formats html columns name, directreports, reports } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/ResourceRoot.tjp0000644000175000017500000000060312614413013024314 0ustar bernatbernatproject "Test" "1.0" 2010-11-10 +2m { timezone "America/Denver" } resource org "Org" { resource team1 "Team1" { resource r1 "R1" resource r2 "R2" resource r3 "R3" } resource r4 "R4" } resource r5 "R5" task "T" resourcereport "ResourceRoot" { formats html columns name, id # Only list the Team1 as if it would be a top-level resource. resourceroot team1 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/TimeSheet1.tjp0000644000175000017500000000313612614413013023635 0ustar bernatbernat# *** EXAMPLE: 2 + # *** EXAMPLE: 1 + project "test" 2009-11-30 +2m { timezone "America/Denver" trackingscenario plan now ${projectstart} } # *** EXAMPLE: 1 - resource r1 "R1" # *** EXAMPLE: 2 - resource r2 "R2" # *** EXAMPLE: 6 + task t1 "Task 1" { effort 5d allocate r1 } # *** EXAMPLE: 6 - # *** EXAMPLE: 5 + task t2 "Task 2" { task t3 "Task 3" { duration 10d allocate r2 } } # *** EXAMPLE: 6 + # *** EXAMPLE: 5 - # *** EXAMPLE: 1 + # *** EXAMPLE: 3 + # *** EXAMPLE: 4 + timesheet r1 2009-11-30 +1w { # *** EXAMPLE: 3 - task t1 { work 3d remaining 0d status green "All work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } # *** EXAMPLE: 6 - # *** EXAMPLE: 4 - # *** EXAMPLE: 3 + newtask t4 "Another fun job" { work 2d remaining 4d status yellow "Had a cool idea" { summary "Will have a schedule impact though." } } # *** EXAMPLE: 4 + # *** EXAMPLE: 6 + } # *** EXAMPLE: 6 - # *** EXAMPLE: 4 - # *** EXAMPLE: 3 - # *** EXAMPLE: 5 + timesheet r2 2009-11-30 +1w { task t2.t3 { work 5d end 2009-12-10 status red "I need more time" { summary "This takes longer than expected" details -8<- To finish on time, I need help. Get this r1 guy to help me out here. * I want to have fun too! ->8- } } status yellow "My wife got ill" { summary "I might have to work from home for a few days next week." } } # *** EXAMPLE: 5 - # *** EXAMPLE: 1 - taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/LoadUnits.tjp0000644000175000017500000000075712614413013023575 0ustar bernatbernatproject simple "Simple Project" "$Id" 2000-01-01 - 2001-02-01 { timezone "America/Denver" yearlyworkingdays 252 } resource tux1 "Tux1" resource tux2 "Tux2" resource tux3 "Tux3" task t1 "Task1" { start 2000-01-01 effort 20d allocate tux1 } task t2 "Task2" { start 2000-01-01 length 1y allocate tux2 } task t3 "Task3" { start 2000-01-01 effort 2d allocate tux3 } taskreport loadunits "LoadUnits" { formats html columns no, name, effort, monthly loadunit months } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Flags.tjp0000644000175000017500000000121612614413013022716 0ustar bernatbernatproject prj "Flags Example" "1.0" 2005-07-21 - 2005-08-26 { timezone "America/Denver" } # Declare the flag to mark important tasks flags important task items "Project breakdown" { start 2005-07-22 task plan "Plan work" { length 3d flags important } task implementation "Implement work" { length 5d depends !plan } task acceptance "Customer acceptance" { duration 5d depends !implementation flags important } } taskreport tasks "My Tasks" { formats html # Show only the important tasks hidetask ~important # Turn treemode off so parent tasks are not automatically included. sorttasks name.up } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/RollupResource.tjp0000644000175000017500000000056712614413013024657 0ustar bernatbernatproject "Test" "1.0" 2010-11-10 +2m { timezone "America/Denver" } resource org "Org" { resource team1 "Team1" { resource r1 "R1" resource r2 "R2" resource r3 "R3" } resource r4 "R4" } resource r5 "R5" task "T" resourcereport "RollupResource" { formats html columns name, id # Don't list the Team1 resources. rollupresource plan.id = 'team1' } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Alternative.tjp0000644000175000017500000000043112614413013024136 0ustar bernatbernatproject "Project" 2000-01-01 - 2000-03-01 { timezone "America/Denver" } # *** EXAMPLE: 1 + resource tuxus "Tuxus" resource tuxia "Tuxia" task t "Task" { start ${projectstart} effort 5d # Use tuxus or tuxia, whoever is available. allocate tuxus { alternative tuxia } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Priority.tjp0000644000175000017500000000162012614413013023502 0ustar bernatbernatproject "Priority Demo" 2011-04-17-0:00--0700 +2m { timezone "America/Denver" } resource tux "Tux" # *** EXAMPLE: project + task jobs "Project breakdown" { start ${projectstart} task work "The regular work" { effort 20d priority 500 allocate tux limits { weeklymax 25h } } task support "Customer Support" { # This is a high priority task. Due to the high priority tux is # spending the required daily maximum on it. end ${projectend} priority 800 allocate tux limits { dailymax 2h } } task conference "Attend Conference" { period 2011-04-25 +2d allocate tux priority 1000 } task maintenance "Maintenance work" { # This is a fallback task. Whenever tux is not doing something # else he is allocated to this task. end ${projectend} priority 300 allocate tux limits { weeklymax 2d } } } # *** EXAMPLE: project - taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/CustomAttributes.tjp0000644000175000017500000000303712614413013025206 0ustar bernatbernatproject "Extend Test" 2013-04-24 +1m { extend task { date DueDate "Due Date" number Count "Count" reference URL "URL" richtext Claim "Claim" text Intro "Intro" date DueDateS "Due Date" { scenariospecific inherit } number CountS "Count" { scenariospecific } reference URLS "URL" { scenariospecific } richtext ClaimS "Claim" { scenariospecific inherit } text IntroS "Intro" { scenariospecific } } extend resource { date Birthday "Birthday" number Count "Count" reference URL "URL" richtext Claim "Claim" text Intro "Intro" date BirthdayS "Birthday" { scenariospecific inherit } number CountS "Count" { scenariospecific } reference URLS "URL" { scenariospecific } richtext ClaimS "Claim" { scenariospecific inherit } text IntroS "Intro" { scenariospecific } } scenario one "One" { scenario two "Two" } } resource "R" { Birthday 2000-05-01 Count 42 URL "http://www.taskjuggler.org" Claim "A '''big''' statement." Intro "Let's think about this..." two:BirthdayS 2000-05-01 two:CountS 42 two:URLS "http://www.taskjuggler.org" { label "TJ Web" } two:ClaimS "A '''big''' statement." two:IntroS "Let's think about this..." } task "T" { DueDate 2013-05-01 Count 42 URL "http://www.taskjuggler.org" Claim "A '''big''' statement." Intro "Let's think about this..." two:DueDateS 2013-05-01 two:CountS 42 two:URLS "http://www.taskjuggler.org" { label "TJ Web" } two:ClaimS "A '''big''' statement." two:IntroS "Let's think about this..." } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/TaskPrefix.tjp0000644000175000017500000000076112614413013023746 0ustar bernatbernatproject project "Include task prefix test" "1.0" 2010-12-01 +1m { timezone "Europe/Amsterdam" now 2010-12-01 } resource tux "Tux" task parent_task "Parent task" { start ${projectstart} allocate tux } include "TaskPrefix.tji" { taskprefix parent_task } task other_task "Other task" { start ${projectstart} effort 1d allocate tux } supplement resource tux { booking parent_task.sub_task1 2010-12-01-9:00 +9h { sloppy 2 } booking other_task 2010-12-02-9:00 +9h { sloppy 2 } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Comments.tjp0000644000175000017500000000106412614413013023450 0ustar bernatbernat/* This is a multi-line comment * that spans multiple rows. */ project simple "Simple Project" "1.0" 2005-06-06 - 2005-06-26 { timezone "America/Denver" } // A resource definition resource tux "Tux" task items "Project breakdown" { start 2005-06-06 task plan "Plan work" { length 3d } task implementation "Implement work" { effort 5d // Some effort allocate tux # A resource depends !plan } task acceptance "Customer acceptance" { duration 5d depends !implementation } } taskreport tasks "My Tasks" # End of project taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Account.tjp0000644000175000017500000000251312614413013023257 0ustar bernatbernatproject simple "Simple Project" "1.0" 2007-01-01 - 2007-01-30 { timezone "America/Denver" currency "USD" } # *** EXAMPLE: 1 + account project_cost "Project Costs" account payments "Customer Payments"{ credits 2007-01-01 "Customer down payment" 500.0, 2007-01-14 "1st rate" 2000.0 } balance project_cost payments resource tux "Tux" { rate 300 } resource konqui "Konqui" { rate 200 } task items "Room decoration" { start 2007-01-06 # The default account for all tasks chargeset project_cost task plan "Plan work and buy material" { # Upfront material cost charge 500.0 onstart length 2d } task remove "Remove old inventory" { allocate tux allocate konqui effort 1d depends !plan } task implement "Arrange new decoration" { effort 5d allocate tux, konqui depends !remove } task acceptance "Presentation and customer acceptance" { duration 5d depends !implement chargeset payments # Customer pays at end of acceptance charge 2000.0 onend } } # *** EXAMPLE: 1 - accountreport "Account-1" { formats html timeformat "%d-%M-%y" columns index, name, weekly } accountreport "Account-2" { formats html timeformat "%d-%M-%y" columns index, name, weekly { celltext 1 "<-query attribute='turnover'->" } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Gap.tjp0000644000175000017500000000047012614413013022372 0ustar bernatbernatproject prj "Example Project" "1.0" 2005-05-29 - 2005-07-01 { timezone "America/Denver" } task t1 "Task 1" { start 2005-05-29 } task t2 "Task 2" { # starts 5 calendar days after t1 depends !t1 { gapduration 5d } } task t3 "Task 3" { # starts 5 working days after t1 depends !t1 { gaplength 5d } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Query.tjp0000644000175000017500000000053712614413013022774 0ustar bernatbernatproject "Query Demo" 2009-11-22 +1m { timezone "America/Denver" now 2009-12-06 } resource joe "Joe" task "Job" { effort 2w allocate joe } taskreport "QueryDemo" { formats html header "Project data as of <-query attribute='now'->" columns name { celltext 1 "<-query-> : <-query attribute='id'->" }, effortdone, effortleft } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/AdoptedTasks.tjp0000644000175000017500000000026012614413013024246 0ustar bernatbernatproject "Adopted Tasks" 2011-03-05 +1m task t1 "T1" task t2 "T2" task t3 "T3" { adopt t1, t2 } taskreport "AdoptedTasks" { formats html columns no, bsi, index, name } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Efficiency.tjp0000644000175000017500000000075512614413013023735 0ustar bernatbernatproject "Resource Efficiency Example" 2007-07-21 - 2007-07-22 { timezone "America/Denver" } # A team of 5 people. They can only be assigned en block. Either all # or nobody works. resource tuxies "Tuxies" { efficiency 5.0 } # A hard-working guy resource tux1 "Tux 1" { efficiency 1.2 } # And a lazy one resource tux2 "Tux 2" { efficiency 0.9 } # And a thing that cannot do any work resource confRoom "Conference Room" { efficiency 0 } task t "An important date" { start 2007-07-21 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/LogicalExpression.tjp0000644000175000017500000000107512614413013025317 0ustar bernatbernatproject "LogExp" 2009-10-19 +2m { timezone "America/Denver" } resource r "R" task "T" { allocate r effort 1d } macro rep [ taskreport "LogExp${1}" { hidetask 1 ${2} 0 } ] ${rep "1" ">"} ${rep "2" "<"} ${rep "3" "<="} ${rep "4" ">="} ${rep "5" "!="} ${rep "6" "="} taskreport "LogExp7" { hidetask @none hideresource @all } # *** EXAMPLE: 1 + taskreport "LeaveTasks" { hidetask isleaf() sorttasks plan.id # not 'tree' to really hide parent tasks } taskreport "Overruns" { hidetask isvalid(plan.maxend) & (plan.end > plan.maxend) } # *** EXAMPLE: 1 - taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Responsible.tjp0000644000175000017500000000044612614413013024153 0ustar bernatbernatproject "Responsible Demo" 2005-07-15 - 2005-08-01 { timezone "America/Denver" } resource tux "Tux" resource ubertux "Uber Tux" task someJob "Some Job" { start 2005-07-15 effort 1w allocate tux responsible ubertux } taskreport joblist "Job List" { columns effort, responsible } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Persistent.tjp0000644000175000017500000000042212614413013024020 0ustar bernatbernatproject "Project" 2003-06-05 - 2003-07-05 { timezone "America/Denver" } resource r1 "Resource 1" resource r2 "Resource 2" task t1 "Task 1" { start 2003-06-05 effort 5d # Pick one of them and use it for the entire task allocate r1 { alternative r2 persistent } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Vacation.tjp0000644000175000017500000000143712614413013023433 0ustar bernatbernatproject "Vacation Examples" 2005-07-22 - 2006-01-01 { timezone "America/Denver" } # Labor Day vacation "Labor Day" 2005-09-05 # 2 days Christmas break (27th not included!) vacation "Christmas" 2005-12-25 - 2005-12-27 resource team "A team" { # 2 days of team vacation vacation 2005-10-07 +2d resource tux2 "Tux2" resource tux3 "Tux3" { # And one extra day vacation 2005-08-10 } } # The vacation property is also usefull when new employees start # working in the course of a project or if someone quits. resource tuxia "Tuxia" { # Tuxia is a new employee as of August 1st 2005 vacation 1971-01-01 - 2005-08-01 } resource tuxus "Tuxus" { # Tuxus quits his job on September 1st 2005 vacation 2005-09-01 - 2030-01-01 } task t "An important date" { start 2005-07-22 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Reports.tjp0000644000175000017500000000165012614413013023322 0ustar bernatbernatproject "Test Project" 2000-01-01 - 2000-03-01 { timezone "America/Denver" } flags flag1, flag2, flag3, flag4 rate 100.0 resource r1 "FooResource 1" resource r2 "FooResource 2" resource r3 "FooResource 3" resource r4 "FooResource 4" account a1 "FooAccount 1" { account a3 "FooAccount 3" } account a2 "FooAccount 2" { account a4 "FooAccount 4" } task t1 "FooTask1" { chargeset a4 task t1_1 "FooTask1_1" { flags flag2 start 2000-01-01 effort 20d allocate r1 allocate r2 } flags flag3 } task t2 "FooTask2" { flags flag1 start 2000-01-01 duration 1d chargeset a4 charge 10000.0 onstart } task t3 "FooTask3" { flags flag4 milestone start 2000-01-01 } taskreport task "Report_task" { formats html balance a1 a2 columns bsi, name { title "Task Name" }, daily, effort sorttasks tree, plan.start.up, name.up } resourcereport resource "Report_resource.html" { formats html } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/CompletedWork.tji0000644000175000017500000000074312614413013024436 0ustar bernatbernatsupplement task t { plan:booking r 2000-01-03-09:00-+0100 + 3.0h, 2000-01-03-13:00-+0100 + 5.0h, 2000-01-04-09:00-+0100 + 3.0h, 2000-01-04-13:00-+0100 + 5.0h, 2000-01-05-09:00-+0100 + 3.0h, 2000-01-05-13:00-+0100 + 5.0h, 2000-01-06-09:00-+0100 + 3.0h, 2000-01-06-13:00-+0100 + 5.0h, 2000-01-07-09:00-+0100 + 3.0h, 2000-01-07-13:00-+0100 + 5.0h } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Caption.tjp0000644000175000017500000000132612614413013023261 0ustar bernatbernatproject simple "Simple Project" "1.0" 2007-01-01 - 2007-02-01 { timezone "America/Denver" } # *** EXAMPLE: 2 + copyright "Tux Inc." # *** EXAMPLE: 1 + resource r1 "Resource 1" task plant "How to plant a tree" { start 2007-01-01 # All sub-tasks inherit this allocation of r1 allocate r1 task plan "Choose the planting site" { effort 2d } task buy "Get a tree" { effort 1d depends !plan } task action "Plant the tree" { effort 0.5d depends !buy } } taskreport planttree "PlantTree.html" { formats html caption "This project shows how to plant a tree easily" headline "How to plant a tree" columns name, start, end, daily # Don't hide any resource, thus show them all. hideresource 0 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/navigator.tjp0000644000175000017500000000063212614413013023655 0ustar bernatbernatproject "Navigator Example" 2011-12-12 +1m task foo "Foo" { task "Foo 1" task "Foo 2" } task bar "Bar" { task "Bar 1" task "Bar 2" } navigator navbar textreport frame "" { header -8<- == My Reports == <[navigator id="navbar"]> ->8- footer "----" taskreport "Foo Reports" { formats html taskroot foo } taskreport "Bar Reports" { formats html taskroot bar } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Mandatory.tjp0000644000175000017500000000065512614413013023626 0ustar bernatbernatproject prj "Project" "1.0" 2000-01-01 - 2000-03-01 { timingresolution 15min timezone "America/Denver" } resource tuxus "Tuxus" resource truck "Truck" { # Truck does not do any work! efficiency 0.0 } task t "Ship stones to customers" { start 2000-01-01 effort 5d # We need the truck to deliver the stones, so only allocate # tuxus when the truck is available. allocate tuxus allocate truck { mandatory } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Resource.tjp0000644000175000017500000000057412614413013023457 0ustar bernatbernatproject "Resource Examples" 2005-06-06 - 2005-06-26 { timezone "America/Denver" } # A simple resource resource tux1 "Tux1" # A team resource team "A team" { # A 2 days of team vacation vacation 2005-06-07 - 2006-05-09 resource tux2 "Tux2" resource tux3 "Tux3" { # And one extra day vacation 2005-06-10 } } task t "An important date" { start 2005-06-10 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/tutorial.tjp0000644000175000017500000004475012614413013023537 0ustar bernatbernat/* * This file contains an example project. It is part of the * TaskJuggler project management tool. It uses a made up software * development project to demonstrate some of the basic features of * TaskJuggler. Please see the TaskJuggler manual for a more detailed * description of the various syntax elements. */ # *** EXAMPLE: header1 + # *** EXAMPLE: header2 + project acso "Accounting Software" 2002-01-16 +4m { # *** EXAMPLE: header1 - # Set the default time zone for the project. If not specified, UTC # is used. # *** EXAMPLE: timezone + timezone "Europe/Paris" # *** EXAMPLE: timezone - # Hide the clock time. Only show the date. # *** EXAMPLE: formats + timeformat "%Y-%m-%d" # *** EXAMPLE: formats - # Use US format for numbers # *** EXAMPLE: formats + numberformat "-" "" "," "." 1 # *** EXAMPLE: formats - # Use US financial format for currency values. Don't show cents. # *** EXAMPLE: formats + currencyformat "(" ")" "," "." 0 # *** EXAMPLE: formats - # Pick a day during the project that will be reported as 'today' in # the project reports. If not specified, the current day will be # used, but this will likely be outside of the project range, so it # can't be seen in the reports. # *** EXAMPLE: now + now 2002-03-05-13:00 # *** EXAMPLE: now - # The currency for all money values is the Euro. # *** EXAMPLE: currency + currency "USD" # *** EXAMPLE: currency - # We want to compare the baseline scenario to one with a slightly # delayed start. # *** EXAMPLE: scenario + scenario plan "Plan" { scenario delayed "Delayed" } # *** EXAMPLE: scenario - # *** EXAMPLE: extend + extend resource { text Phone "Phone" } # *** EXAMPLE: extend - # *** EXAMPLE: header1 + } # *** EXAMPLE: header1 - # *** EXAMPLE: header2 - # This is not a real copyright for this file. It's just used as an example. # *** EXAMPLE: copyright + copyright "© 2002 Crappy Software, Inc." # *** EXAMPLE: copyright - # The daily default rate of all resources. This can be overridden for each # resource. We specify this, so that we can do a good calculation of # the costs of the project. # *** EXAMPLE: rate + rate 390.0 # *** EXAMPLE: rate - # Register Good Friday as a global holiday for all resources. # *** EXAMPLE: vacation + leaves holiday "Good Friday" 2002-03-29 # *** EXAMPLE: vacation - # *** EXAMPLE: flags + flags team # *** EXAMPLE: flags - # This is one way to form teams # *** EXAMPLE: macro + macro allocate_developers [ # *** EXAMPLE: expandedmacro + allocate dev1 allocate dev2 allocate dev3 # *** EXAMPLE: expandedmacro - ] # *** EXAMPLE: macro - # In order to do a simple profit and loss analysis of the project we # specify accounts. One for the development costs, one for the # documentation costs, and one account to credit the customer payments # to. # *** EXAMPLE: accounts + account cost "Project Cost" { account dev "Development" account doc "Documentation" } account rev "Payments" # *** EXAMPLE: accounts - # The Profit&Loss analysis should be rev - cost accounts. # *** EXAMPLE: balance + balance cost rev # *** EXAMPLE: balance - # *** EXAMPLE: resources + resource boss "Paul Henry Bullock" { email "phb@crappysoftware.com" Phone "x100" rate 480 } resource dev "Developers" { managers boss resource dev1 "Paul Smith" { email "paul@crappysoftware.com" Phone "x362" rate 350.0 } resource dev2 "Sébastien Bono" { email "SBono@crappysoftware.com" Phone "x234" } resource dev3 "Klaus Müller" { email "Klaus.Mueller@crappysoftware.com" Phone "x490" leaves annual 2002-02-01 - 2002-02-05 } flags team } resource misc "The Others" { managers boss resource test "Peter Murphy" { email "murphy@crappysoftware.com" Phone "x666" limits { dailymax 6.4h } rate 310.0 } resource doc "Dim Sung" { email "sung@crappysoftware.com" Phone "x482" rate 300.0 leaves annual 2002-03-11 - 2002-03-16 } flags team } # *** EXAMPLE: resources - # Now we specify the work packages. The whole project is described as # a task that contains subtasks. These subtasks are then broken down # into smaller tasks and so on. The innermost tasks describe the real # work and have resources allocated to them. Many attributes of tasks # are inherited from the enclosing task. This saves you a lot of typing. # *** EXAMPLE: task1 + # *** EXAMPLE: charge + task AcSo "Accounting Software" { # *** EXAMPLE: task1 - # *** EXAMPLE: charge - # All work-related costs will be booked to this account unless the # subtasks specify something different. # *** EXAMPLE: charge + chargeset dev # *** EXAMPLE: charge - # For the duration of the project we have running cost that are not # included in the labor cost. # *** EXAMPLE: charge + charge 170 perday # *** EXAMPLE: charge - responsible boss # *** EXAMPLE: task1 + # *** EXAMPLE: spec + # *** EXAMPLE: charge + task spec "Specification" { # *** EXAMPLE: charge - # *** EXAMPLE: task1 - # *** EXAMPLE: spec - # The effort to finish this task is 20 man-days. # *** EXAMPLE: spec + effort 20d # *** EXAMPLE: spec - # Now we use the macro declared above to allocate the resources # for this task. Because they can work in parallel, they may finish this # task earlier than in 20 working-days. # *** EXAMPLE: spec + ${allocate_developers} # *** EXAMPLE: spec - # Each task without subtasks must have a start or an end # criterion and a duration. For this task we use a reference to a # milestone defined further below as the start criterion. So this task # can not start before the specified milestone has been reached. # References to other tasks may be relative. Each exclamation mark (!) # means 'in the scope of the enclosing task'. To descent into a task, the # fullstop (.) together with the id of the tasks have to be specified. # *** EXAMPLE: spec + depends !deliveries.start # *** EXAMPLE: task1 + } # *** EXAMPLE: task1 - # *** EXAMPLE: spec - # *** EXAMPLE: task1 + # *** EXAMPLE: software + task software "Software Development" { # *** EXAMPLE: task1 - # *** EXAMPLE: software - # The software is the most critical task of the project. So we set # the priority of this task (and all its subtasks) to 1000, the top # priority. The higher the priority, the more likely the task will # get the requested resources. # *** EXAMPLE: software + priority 1000 # *** EXAMPLE: software - # All subtasks depend on the specification task. depends !spec responsible dev1 # *** EXAMPLE: software + # *** EXAMPLE: database + task database "Database coupling" { # *** EXAMPLE: software - effort 20d allocate dev1, dev2 # *** EXAMPLE: software + journalentry 2002-02-03 "Problems with the SQL Libary" { author dev1 alert yellow summary -8<- We ran into some compatibility problems with the SQL Library. ->8- details -8<- We have already contacted the vendor and are now waiting for their advise. ->8- } } # *** EXAMPLE: database - # *** EXAMPLE: software - # *** EXAMPLE: software + # *** EXAMPLE: gui + task gui "Graphical User Interface" { # *** EXAMPLE: software - effort 35d # *** EXAMPLE: gui - # This task has taken 5 man-days more than originally planned. # We record this as well, so that we can generate reports that # compare the delayed schedule of the project to the original plan. # *** EXAMPLE: gui + delayed:effort 40d depends !database, !backend allocate dev2, dev3 # Resource dev2 should only work 6 hours per day on this task. limits { dailymax 6h { resources dev2 } } # *** EXAMPLE: software + } # *** EXAMPLE: gui - # *** EXAMPLE: software - # *** EXAMPLE: software + # *** EXAMPLE: backend + task backend "Back-End Functions" { # *** EXAMPLE: software - effort 30d # *** EXAMPLE: backend - # This task is behind schedule, because it should have been # finished already. To document this, we specify that the task # is 95% completed. If nothing is specified, TaskJuggler assumes # that the task is on schedule and computes the completion rate # according to the current day and the plan data. # *** EXAMPLE: backend + complete 95 depends !database allocate dev1, dev2 # *** EXAMPLE: software + } # *** EXAMPLE: backend - # *** EXAMPLE: task1 + } # *** EXAMPLE: task1 - # *** EXAMPLE: software - # *** EXAMPLE: task1 + # *** EXAMPLE: test + task test "Software testing" { # *** EXAMPLE: task1 - task alpha "Alpha Test" { # *** EXAMPLE: test - # Efforts can not only be specified as man-days, but also as # man-weeks, man-hours, etc. By default, TaskJuggler assumes # that a man-week is 5 man-days or 40 man-hours. These values # can be changed, of course. # *** EXAMPLE: test + effort 1w # *** EXAMPLE: test - # This task depends on a task in the scope of the enclosing # task's enclosing task. So we need two exclamation marks (!!) # to get there. # *** EXAMPLE: test + depends !!software allocate test, dev2 note "Hopefully most bugs will be found and fixed here." journalentry 2002-03-01 "Contract with Peter not yet signed" { author boss alert red summary -8<- The paperwork is stuck with HR and I can't hunt it down. ->8- details -8<- If we don't get the contract closed within the next week, the start of the testing is at risk. ->8- } } task beta "Beta Test" { effort 4w depends !alpha allocate test, dev1 } # *** EXAMPLE: task1 + } # *** EXAMPLE: test - # *** EXAMPLE: task1 - # *** EXAMPLE: task1 + # *** EXAMPLE: manual + task manual "Manual" { # *** EXAMPLE: task1 - effort 10w depends !deliveries.start allocate doc, dev3 purge chargeset chargeset doc # *** EXAMPLE: task1 + journalentry 2002-02-28 "User manual completed" { author boss summary "The doc writers did a really great job to finish on time." } } # *** EXAMPLE: manual - # *** EXAMPLE: task1 - # *** EXAMPLE: task1 + # *** EXAMPLE: deliveries + task deliveries "Milestones" { # *** EXAMPLE: deliveries - # *** EXAMPLE: task1 - # Some milestones have customer payments associated with them. We # credit these payments to the 'rev' account. # *** EXAMPLE: deliveries + purge chargeset chargeset rev task start "Project start" { # *** EXAMPLE: deliveries - # A task that has no duration is a milestone. It only needs a # start or end criterion. All other tasks depend on this task. # Here we use the built-in macro ${projectstart} to align the # start of the task with the above specified project time frame. # *** EXAMPLE: deliveries + start ${projectstart} # *** EXAMPLE: deliveries - # For some reason the actual start of the project got delayed. # We record this, so that we can compare the planned run to the # delayed run of the project. # *** EXAMPLE: deliveries + delayed:start 2002-01-20 # *** EXAMPLE: deliveries - # At the beginning of this task we receive a payment from the # customer. This is credited to the account associated with this # task when the task starts. # *** EXAMPLE: deliveries + charge 21000.0 onstart } task prev "Technology Preview" { depends !!software.backend charge 31000.0 onstart note "All '''major''' features should be usable." } task beta "Beta version" { depends !!test.alpha charge 13000.0 onstart note "Fully functional, may contain bugs." } task done "Ship Product to Customer" { # *** EXAMPLE: deliveries - # The next line can be uncommented to trigger a warning about # the project being late. For all tasks, limits for the start and # end values can be specified. Those limits are checked after the # project has been scheduled. For all violated limits a warning # is issued. # *** EXAMPLE: deliveries + # maxend 2002-04-17 depends !!test.beta, !!manual charge 33000.0 onstart note "All priority 1 and 2 bugs must be fixed." } # *** EXAMPLE: task1 + } } # *** EXAMPLE: deliveries - # *** EXAMPLE: task1 - # Now the project has been specified completely. Stopping here would # result in a valid TaskJuggler file that could be processed and # scheduled. But no reports would be generated to visualize the # results. # *** EXAMPLE: navigator + navigator navbar { hidereport @none } # *** EXAMPLE: navigator - # *** EXAMPLE: tasktip + macro TaskTip [ tooltip istask() -8<- '''Start: ''' <-query attribute='start'-> '''End: ''' <-query attribute='end'-> ---- '''Resources:''' <-query attribute='resources'-> ---- '''Precursors: ''' <-query attribute='precursors'-> ---- '''Followers: ''' <-query attribute='followers'-> ->8- ] # *** EXAMPLE: tasktip - # *** EXAMPLE: overview_report1 + # *** EXAMPLE: overview_report2 + textreport frame "" { # *** EXAMPLE: overview_report1 - header -8<- == Accounting Software Project == <[navigator id="navbar"]> ->8- footer "----" # *** EXAMPLE: overview_report1 + textreport index "Overview" { formats html center '<[report id="overview"]>' } # *** EXAMPLE: overview_report1 - # *** EXAMPLE: overview_report2 - textreport "Status" { formats html center -8<- <[report id="status.dashboard"]> ---- <[report id="status.completed"]> ---- <[report id="status.ongoing"]> ---- <[report id="status.future"]> ->8- } textreport development "Development" { formats html center '<[report id="development"]>' } textreport "Deliveries" { formats html center '<[report id="deliveries"]>' } textreport "ContactList" { formats html title "Contact List" center '<[report id="contactList"]>' } textreport "ResourceGraph" { formats html title "Resource Graph" center '<[report id="resourceGraph"]>' } # *** EXAMPLE: overview_report1 + # *** EXAMPLE: overview_report2 + } # *** EXAMPLE: overview_report1 - # *** EXAMPLE: overview_report2 - # A traditional Gantt chart with a project overview. # *** EXAMPLE: overview + # *** EXAMPLE: overview1 + taskreport overview "" { # *** EXAMPLE: overview1 - # *** EXAMPLE: overview4 + header -8<- === Project Overview === The project is structured into 3 phases. # Specification # <-reportlink id='frame.development'-> # Testing === Original Project Plan === ->8- # *** EXAMPLE: overview4 - # *** EXAMPLE: overview1 + columns bsi { title 'WBS' }, name, start, end, effort, cost, revenue, chart { ${TaskTip} } # *** EXAMPLE: overview1 - # For this report we like to have the abbreviated weekday in front # of the date. %a is the tag for this. # *** EXAMPLE: overview3 + timeformat "%a %Y-%m-%d" loadunit days # *** EXAMPLE: overview3 - hideresource @all # *** EXAMPLE: overview2 + balance cost rev # *** EXAMPLE: overview2 - # *** EXAMPLE: overview3 + caption 'All effort values are in man days.' # *** EXAMPLE: overview3 - footer -8<- === Staffing === All project phases are properly staffed. See [[ResourceGraph]] for detailed resource allocations. === Current Status === The project started off with a delay of 4 days. This slightly affected the original schedule. See [[Deliveries]] for the impact on the delivery dates. ->8- # *** EXAMPLE: overview1 + } # *** EXAMPLE: overview1 - # *** EXAMPLE: overview - # Macro to set the background color of a cell according to the alert # level of the task. macro AlertColor [ cellcolor plan.alert = 0 "#00D000" # green cellcolor plan.alert = 1 "#D0D000" # yellow cellcolor plan.alert = 2 "#D00000" # red ] taskreport status "" { columns bsi { width 50 title 'WBS' }, name { width 150 }, start { width 100 }, end { width 100 }, effort { width 100 }, alert { tooltip plan.journal != '' "<-query attribute='journal'->" width 150 }, status { width 150 } scenarios delayed taskreport dashboard "" { headline "Project Dashboard (<-query attribute='now'->)" columns name { title "Task" ${AlertColor} width 200}, resources { width 200 ${AlertColor} listtype bullets listitem "<-query attribute='name'->" start ${projectstart} end ${projectend} }, alerttrend { title "Trend" ${AlertColor} width 50 }, journal { width 350 ${AlertColor} } journalmode status_up journalattributes headline, author, date, summary, details hidetask ~hasalert(0) sorttasks alert.down, delayed.end.up period %{${now} - 1w} +1w } taskreport completed "" { headline "Already completed tasks" hidetask ~(delayed.end <= ${now}) } taskreport ongoing "" { headline "Ongoing tasks" hidetask ~((delayed.start <= ${now}) & (delayed.end > ${now})) } taskreport future "" { headline "Future tasks" hidetask ~(delayed.start > ${now}) } } # A list of tasks showing the resources assigned to each task. taskreport development "" { scenarios delayed headline "Development - Resource Allocation Report" columns bsi { title 'WBS' }, name, start, end, effort { title "Work" }, duration, chart { ${TaskTip} scale day width 500 } timeformat "%Y-%m-%d" hideresource ~(isleaf() & isleaf_()) sortresources name.up } # A list of all tasks with the percentage completed for each task taskreport deliveries "" { headline "Project Deliverables" columns bsi { title 'WBS' }, name, start, end, note { width 150 }, complete, chart { ${TaskTip} } taskroot AcSo.deliveries hideresource @all scenarios plan, delayed } # A list of all employees with their contact details. resourcereport contactList "" { scenarios delayed headline "Contact list and duty plan" columns name, email { celltext 1 "[mailto:<-email-> <-email->]" }, Phone, managers { title "Manager" }, chart { scale day } hideresource ~isleaf() sortresources name.up hidetask @all } # A graph showing resource allocation. It identifies whether each # resource is under- or over-allocated for. resourcereport resourceGraph "" { scenarios delayed headline "Resource Allocation Graph" columns no, name, effort, rate, weekly { ${TaskTip} } loadunit shortauto # We only like to show leaf tasks for leaf resources. hidetask ~(isleaf() & isleaf_()) sorttasks plan.start.up } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/AutoID.tjp0000644000175000017500000000036512614413013023013 0ustar bernatbernatproject "Simple Project" 2009-10-04 +1m { timezone "America/Denver" } account "Foo" { account "Bar" } shift "Foo" { shift "bar" } resource "Foo" { resource "Bar" } task "Foo" { task "Bar" } taskreport "Foo" { taskreport "Bar" } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Project.tjp0000644000175000017500000000114712614413013023273 0ustar bernatbernatproject prj "Example Project" "1.0" 2007-01-01 - 2007-03-09 { # The following attributes are all optional. They illustrate the # default values. These attributes are only needed if you want to # specify different values than those listed below. timingresolution 60min timezone "America/Denver" dailyworkinghours 8 yearlyworkingdays 260.714 timeformat "%Y-%m-%d %H:%M" shorttimeformat "%H:%M" currencyformat "(" ")" "," "." 0 weekstartsmonday workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 workinghours sat, sun off scenario plan "Plan" { } } task t "Task" { start 2007-01-01 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Scheduling.tjp0000644000175000017500000000101512614413013023744 0ustar bernatbernatproject "Scheduling Example" 2005-07-23 - 2005-09-01 { timezone "America/Denver" } task items "Project breakdown" { task t1 "Task 1" { start 2005-07-25 end 2005-08-01 # Implicite ALAP task } task t2 "Task 2" { end 2005-08-01 start 2005-07-25 # Implicite ASAP task } task t3 "Task 3" { start 2005-07-25 end 2005-08-01 scheduling asap # Explicite ASAP task } task t4 "Task 4" { end 2005-08-01 start 2005-07-25 scheduling alap # Explicite ALAP task } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Celltext.tjp0000644000175000017500000000137312614413013023452 0ustar bernatbernatproject celltext "celltext" "1.0" 2007-01-01 - 2007-03-01 { timezone "America/Denver" } # *** EXAMPLE: 1 + resource tux "Tux" task t "Task" { task s "SubTask" { start 2007-01-01 effort 5d allocate tux } } # *** EXAMPLE: 1 - # Just a very basic report with some standard columns taskreport simple "SimpleReport" { formats html columns bsi, name, start, end, weekly } # Report with custom colum title taskreport custom "CustomTitle" { formats html columns bsi, name { title "Work Item" }, effort } # *** EXAMPLE: 1 + # Report with index and task name combined in one single column taskreport combined "CombinedColumn" { formats html columns name { celltext 1 "<-query attribute='bsi'-> <-query->"}, start, end, weekly } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Timezone.tjp0000644000175000017500000000023512614413013023454 0ustar bernatbernatproject tz "Timezone" "1.0" 2005-06-06-00:00-+0000 - 2005-06-07-0:00-+0000 { timezone "Europe/Athens" } task item "Project" { start 2005-06-06-12:00 } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Export.tjp0000644000175000017500000000162512614413013023147 0ustar bernatbernat# *** EXAMPLE: 1 + # *** EXAMPLE: 2 + project export "Project" "1.0" 2007-01-01 - 2008-01-01 { timezone "America/Denver" } resource tux "Tux" resource bob "Bob" # *** EXAMPLE: 1 - task t1 "Task 1" { start 2007-01-01 effort 20d allocate tux allocate bob limits { dailymax 6h } } # *** EXAMPLE: 1 + # *** EXAMPLE: 2 - task t2 "Task 2" { start 2007-01-01 end 2007-06-30 allocate tux allocate bob limits { weeklymax 3d } } # *** EXAMPLE: 1 - # *** EXAMPLE: 2 + # Export the project as fully scheduled project. export "FullProject" { definitions * taskattributes * hideresource 0 } # Export only bookings for 1st week as resource supplements export "Week1Bookings" { definitions - start 2007-01-01 end 2007-01-08 taskattributes booking hideresource 0 } # Export the scheduled project as Microsoft Project XML format. export "MS-Project" { formats mspxml loadunit quarters } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Precedes1.tjp0000644000175000017500000000040512614413013023474 0ustar bernatbernatproject "P" 2003-11-09 - 2003-12-24 { timezone "America/Denver" } task foo1 "foo1" { task foo2 "foo2" { start 2003-12-04 milestone } task foo3 "foo3" { precedes !foo2 length 1d } } task bar "bar" { precedes foo1.foo2 length 2d } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Allocate-1.tjp0000644000175000017500000000074112614413013023546 0ustar bernatbernatproject allocate "allocate" "1.0" 2003-06-05 - 2003-07-05 { timezone "America/Denver" } # *** EXAMPLE: 1 + resource r1 "Resource 1" resource r2 "Resource 2" task t1 "Task 1" { start 2003-06-05 # All sub-tasks inherit this allocation of r1 allocate r1 task t2 "Task 2" { effort 10d } task t3 "Task 3" { effort 20d # This task has r1 and r2 allocated allocate r2 } # *** EXAMPLE: 1 - task m1 "Milestone 1" { milestone } # *** EXAMPLE: 1 + } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Supplement.tjp0000644000175000017500000000104512614413013024016 0ustar bernatbernatproject "Test Project" 2000-01-01 - 2000-01-04 { timezone "America/Denver" } flags important # *** EXAMPLE: resource + resource joe "Joe" # *** EXAMPLE: resource - # *** EXAMPLE: task + task top "Top Task" { task sub "Sub Task" supplement task sub { length 1d } } # *** EXAMPLE: task - # *** EXAMPLE: resource + supplement resource joe { vacation 2000-02-10 - 2000-02-20 } # *** EXAMPLE: resource - # *** EXAMPLE: task + supplement task top { flags important supplement task sub { allocate joe } } # *** EXAMPLE: task - taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Booking.tjp0000644000175000017500000000171412614413013023255 0ustar bernatbernatproject project "Simple Project" "1.0" 2007-01-05 +1m { timezone "America/Denver" # The baseline date for the projection. now 2007-01-15 } resource tux "Tux" task test "Testing" { start 2007-01-05 effort 10d allocate tux } supplement resource tux { # Book a whole day (8 hours). The 1 hour lunch break is skipped. booking test 2007-01-06-9:00 +9h { sloppy 1 } # Book 2 days in the afternoon, 4 hours each. booking test 2007-01-08-13:00 +4h, 2007-01-09-13:00 +4h # This is a common mistake. With standard working hours, this will # yield a zero time booking! The interval is midnight to 8am. So # it's outside of the working hours and 'sloopy 2' surpresses the # warning. booking test 2007-01-11 +8h { sloppy 2 } # Use 'overtime' to book off-hour slots. This booking will book the # full 10 hours, ignoring the lunch break and adding an extra hour # in the morning. booking test 2007-01-11-8:00 +10h { overtime 1 } } taskjuggler-3.5.0/test/TestSuite/Syntax/Correct/Niku.tjp0000644000175000017500000000303612614413013022572 0ustar bernatbernatproject "Niku Test" 2010-02-01 +3m { timezone "America/Denver" extend task { text ClarityPID "Clarity PID" text ClarityPName "Clarity Project Name" } extend resource { text ClarityRID "Clarity Resource ID" } } # The ClarityPID and ClarityPName must be always kept in sync. The # easiest way to achieve this, is by using such macros. macro PID_p1 [ ClarityPID "p1" ClarityPName "Project 1" ] macro PID_p2 [ ClarityPID "p2" ClarityPName "Project 2" ] macro PID_p3 [ ClarityPID "p3" ClarityPName "Project 3" ] macro Resource [ resource ${1} "${1}" { ClarityRID "${1}" } ] ${Resource "r1"} ${Resource "r2"} ${Resource "r3"} supplement resource r2 { vacation 2010-02-15 +1w } task "T1" { allocate r1 effort 5w ${PID_p1} } task t2 "T2" { allocate r2 effort 10d ${PID_p1} } task "T3" { depends !t2 allocate r2 effort 10d ${PID_p2} } task "T4" { allocate r3 effort 3w ${PID_p2} } nikureport "niku" { formats niku, html, csv headline "This is a test report" period 2010-02-01-8:00 - %{2010-03-01 -6h} timeoff "vacations" "Vacation time" # Depending on your Clarity configuration, you may need to add this # CustomInformation section in the report. It's raw XML code and # will be embedded into each section of the resulting # report. This is just an example and it must be customized to work! title -8<- foo_active foo_eng ->8- } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/0000755000175000017500000000000012614413013021016 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Syntax/Errors/booking_milestone.tjp0000644000175000017500000000031512614413013025243 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m resource tux "Tux" task t "T" { start 2007-08-19 milestone } supplement resource tux { # MARK: error 12 booking_milestone booking t 2007-08-25-10:00 +2h } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/sorting_bsi.tjp0000644000175000017500000000025212614413013024056 0ustar bernatbernatproject test "Test" "1.0" 2007-11-07 +2m task t "T" { start ${projectstart} } taskreport report "report.html" { # MARK: error 9 sorting_bsi sorttasks plan.bsi.up } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_summary_too_long.tjp0000644000175000017500000000142612614413013025643 0ustar bernatbernatproject "test" 2010-02-21 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2010-02-21 +1w { task t1 { work 100% remaining 0d # MARK: error 19 ts_summary_too_long status green "headline" { summary -8<- 012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789 012345678901234567890123456789012345678901234567890123456789 ->8- } } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/no_token_match2.tjp0000644000175000017500000000013312614413013024604 0ustar bernatbernatproject test "Test" "1.0" 2009-08-25 +2m # MARK: error 5 no_token_match task t -8<- footaskjuggler-3.5.0/test/TestSuite/Syntax/Errors/resource_id_expected.tjp0000644000175000017500000000022512614413013025720 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 # MARK: error 6 resource_id_expected booking foo 2007-08-25-10:00 +2h } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/booking_no_leaf.tjp0000644000175000017500000000031412614413013024646 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m resource tux "Tux" task t "T" { start 2007-08-19 task s "S" } supplement resource tux { # MARK: error 12 booking_no_leaf booking t 2007-08-25-10:00 +2h } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/start_before_end2.tjp0000644000175000017500000000017712614413013025131 0ustar bernatbernatproject test "Test" "1.0" 2009-02-22 +3m task foo "Foo" { # MARK: error 5 start_before_end period 2009-02-24 - 2008-02-23 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/no_token_match3.tjp0000644000175000017500000000013712614413013024611 0ustar bernatbernatproject test "Test" "1.0" 2009-08-25 +2m # MARK: error 6 no_token_match task t -8<- foo -taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/empty.tjp0000644000175000017500000000003712614413013022673 0ustar bernatbernat# MARK: error 2 unexpctd_token taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/unknown_env_var.tjp0000644000175000017500000000014612614413013024755 0ustar bernatbernatproject "Test" "1.0" 2011-05-05 +2m task "T" { # MARK: error 5 unknown_env_var note $(UNSET_VAR) } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/no_reduce.tjp0000644000175000017500000000014012614413013023473 0ustar bernatbernatproject test "Test" "1.0" 2010-11-06 +1m task t "T" { # MARK: error 5 no_reduce effort 1dd } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/resource_exists.tjp0000644000175000017500000000021212614413013024756 0ustar bernatbernatproject test "Test" "1.0" 2009-02-11 +1m resource r "First resource r" # MARK: error 6 resource_exists resource r "Second resource r" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/start_before_end1.tjp0000644000175000017500000000020712614413013025122 0ustar bernatbernatproject test "Test" "1.0" 2009-02-22 +3m # MARK: error 4 start_before_end vacation "Day off" 2009-02-24 - 2008-02-23 task foo "Foo" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/unsupported_token.tjp0000644000175000017500000000030712614413013025325 0ustar bernatbernatproject "Test" 2009-11-28 +2m task t "T" { journalentry 2009-11-28 "Just testing" { # MARK: error 8 unsupported_token summary -8<- This is all good. * This is bad. ->8- } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/unknown_projectid.tjp0000644000175000017500000000017712614413013025304 0ustar bernatbernatproject test "Test" "1.0" 2007-11-15 +2m task t "T" { # MARK: error 5 unknown_projectid projectid foo start 2007-11-15 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/leaf_resource_id_expected.tjp0000644000175000017500000000032512614413013026710 0ustar bernatbernatproject test "Test" "1.0" 2009-01-22 +2m resource team "T" { resource foo "Foo" } task t "T" { start ${projectstart} # MARK: error 10 leaf_resource_id_expected limits { dailymax 4h { resources team }} } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/booking_group.tjp0000644000175000017500000000033712614413013024404 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m resource tuxies "Tuxies" { resource tux "Tux" } task t "T" { start 2007-08-19 } supplement resource tuxies { # MARK: error 13 booking_group booking t 2007-08-25-10:00 +2h } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/shift_id_expected.tjp0000644000175000017500000000017512614413013025212 0ustar bernatbernatproject test "Test" "1.0" 2007-08-20 +2m resource r "R" { # MARK: error 5 shift_id_expected shifts foo 2007-08-20 +2d } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/sorting_crit_exptd2.tjp0000644000175000017500000000024612614413013025533 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 } taskreport report "report.html" { # MARK: error 9 sorting_crit_exptd2 sorttasks foo } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/task_exists.tjp0000644000175000017500000000016612614413013024101 0ustar bernatbernatproject test "Test" "1.0" 2009-02-11 +1m task t "First task t" # MARK: error 6 task_exists task t "Second task t" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/no_token_match4.tjp0000644000175000017500000000014012614413013024604 0ustar bernatbernatproject test "Test" "1.0" 2009-08-25 +2m # MARK: error 6 no_token_match task t -8<- foo ->taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/sort_unknown_scen.tjp0000644000175000017500000000025512614413013025315 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 } taskreport report "report.html" { # MARK: error 9 sort_unknown_scen sorttasks foo.start.up } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/alert_name_redef.tjp0000644000175000017500000000030612614413013025010 0ustar bernatbernatproject "Test" 2011-12-08 +1m { # MARK: error 3 alert_name_redef alertlevels foo "Foo" "#000", bar "Bar" "#111", foo2 "Foo" "#222", bar1 "Bar1" "#333" } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_no_tracking_scenario.tjp0000644000175000017500000000034712614413013026430 0ustar bernatbernatproject "Test" 2011-05-15 +1w resource r "R" task t "T" # MARK: error 7 ts_no_tracking_scenario timesheet r 2011-05-15 +1w { task t { work 80% priority 1001 end 2011-05-18 status green "Lots of work done" } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/overtime_range.tjp0000644000175000017500000000032712614413013024545 0ustar bernatbernatproject test "Test" "1.0" 2007-04-22 +2m resource tux "Tux" task foo "Foo" { start ${projectstart} } supplement resource tux { # MARK: error 11 overtime_range booking foo ${projectstart} +1h { overtime 3 } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/resourceroot_leaf.tjp0000644000175000017500000000022012614413013025251 0ustar bernatbernatproject test "Test" "1.0" 2011-03-27 +2m resource r "R" task t "T" resourcereport "R" { # MARK: error 9 resourceroot_leaf resourceroot r } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/interval_end_in_range.tjp0000644000175000017500000000017112614413013026050 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { # MARK: error 5 interval_end_in_range period 2007-09-14 +2m } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/include_before_project.tjp0000644000175000017500000000007112614413013026226 0ustar bernatbernat# MARK: error 2 include_before_project include "foo.tji" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/adopt_duplicate_child-3.tjp0000644000175000017500000000022212614413013026175 0ustar bernatbernatproject "Adopted Tasks" 2011-03-05 +1m task t1 "T1" { task t2 "T2" } # MARK: error 7 adopt_duplicate_child task t3 "T3" { adopt t1, t1.t2 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/task_complete.tjp0000644000175000017500000000017212614413013024367 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 # MARK: error 6 task_complete complete 101 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/unknown_task.tjp0000644000175000017500000000023512614413013024256 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 } taskreport report "report.html" { # MARK: error 9 unknown_task taskroot foo } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/no_token_match1.tjp0000644000175000017500000000013212614413013024602 0ustar bernatbernatproject test "Test" "1.0" 2009-08-25 +2m # MARK: error 5 no_token_match task t -8<- taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/not_scheduled.tjp0000644000175000017500000000030412614413013024352 0ustar bernatbernatproject test "Test" "1.0" 2010-04-28 +1w resource r "R" task "Foo" { task bar "Bar" { start ${projectstart} effort 1d allocate r # MARK: error 11 not_scheduled scheduled } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_default_summary.tjp0000644000175000017500000000050512614413013025444 0ustar bernatbernatproject "test" 2010-02-21 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2010-02-21 +1w { task t1 { work 100% remaining 0d status green "Green" { # MARK: error 19 ts_default_summary summary -8<- A summary text ->8- } } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/unknown_scenario_id.tjp0000644000175000017500000000020612614413013025571 0ustar bernatbernatproject test "Test" "1.0" 2007-03-25 +2m resource r "R" { # MARK: error 5 unknown_scenario_id foo:workinghours mon 8:00 - 10:00 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/multiple_durations.tjp0000644000175000017500000000021412614413013025455 0ustar bernatbernatproject test "Test" "1.0" 2008-02-02 +1m task t "T" { start ${projectstart} milestone # MARK: error 7 multiple_durations length 2d } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/sorting_crit_exptd1.tjp0000644000175000017500000000033412614413013025530 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m { timezone "Europe/Amsterdam" } task t "T" { start ${projectstart} } taskreport report "report.html" { # MARK: error 11 sorting_crit_exptd1 sorttasks plan.start.foo.bar } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/bad_time_zone.tjp0000644000175000017500000000016012614413013024331 0ustar bernatbernatproject test "Test" "1.0" 2007-08-26 +1w { # MARK: error 3 bad_time_zone timezone "foo/bar" } task foo "Foo" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/operand_unkn_flag.tjp0000644000175000017500000000022712614413013025212 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 } export "report.tji" { # MARK: error 9 operand_unkn_flag hidetask foo } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/junk_after_cut.tjp0000644000175000017500000000016212614413013024537 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m # MARK: error 4 junk_after_cut task t -8<- ->8- { start 2007-08-19 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/adopt_duplicate_child-2.tjp0000644000175000017500000000022412614413013026176 0ustar bernatbernatproject "Adopted Tasks" 2011-03-05 +1m task t1 "T1" { task t2 "T2" # MARK: error 6 adopt_duplicate_child task t3 "T3" { adopt t1.t2 } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/cost_acct_no_top.tjp0000644000175000017500000000056312614413013025061 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m account group1 "Group1" { account g1 "G1" account g2 "G2" account g3 "G3" } account group2 "Group2" { account g4 "G1" account g5 "G2" account g6 "G3" } task t "T" { start ${projectstart} chargeset g1, g2 } taskreport tasks "Tasks.html" { formats html # MARK: error 23 cost_acct_no_top balance g1 group2 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/operand_attribute.tjp0000644000175000017500000000023612614413013025251 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 } export "report.tji" { # MARK: error 9 operand_attribute hidetask t.foo.bar } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/too_few_alert_levels.tjp0000644000175000017500000000014712614413013025742 0ustar bernatbernatproject "Test" 2011-12-08 +1m { # MARK: error 3 too_few_alert_levels alertlevels foo "Foo" "#000" } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_no_headline2.tjp0000644000175000017500000000073012614413013024572 0ustar bernatbernatproject "test" 2009-11-30 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2009-11-30 +1w { task t1 { work 2d remaining 0d # MARK: error 17 ts_no_headline status green "Your headline here!" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/alert_level_redef.tjp0000644000175000017500000000030712614413013025200 0ustar bernatbernatproject "Test" 2011-12-08 +1m { # MARK: error 3 alert_level_redef alertlevels foo "Foo" "#000", bar "Bar" "#111", foo "Foo2" "#222", bar1 "Bar1" "#333" } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/container_attribute.tjp0000644000175000017500000000027312614413013025604 0ustar bernatbernatproject test "Test" "1.0" 2008-02-03 +2m task m "M" { start ${projectstart} } task t "T" { start ${projectstart} task s "S" # MARK: error 11 container_attribute depends !m } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_default_details.tjp0000644000175000017500000000051012614413013025370 0ustar bernatbernatproject "test" 2010-02-21 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2010-02-21 +1w { task t1 { work 100% remaining 0d status green "Green" { # MARK: error 19 ts_default_details details -8<- Some more details ->8- } } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_bad_priority.tjp0000644000175000017500000000045012614413013024731 0ustar bernatbernatproject "test" 2010-02-19 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { duration 5d allocate r1 } timesheet r1 2010-02-21 +1w { task t1 { work 80% # MARK: error 16 ts_bad_priority priority 1001 end 2010-02-28 status green "Lots of work done" } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/task_priority.tjp0000644000175000017500000000017612614413013024444 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 # MARK: error 6 task_priority priority 1001 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_duplicate_task.tjp0000644000175000017500000000054712614413013025245 0ustar bernatbernatproject "test" 2010-02-21 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } # MARK: error 13 ts_duplicate_task timesheet r1 2010-02-21 +1w { task t1 { work 80% remaining 0d status green "Lots of work done" } task t1 { work 80% remaining 0d status green "Lots of work done" } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/interval_start_in_range.tjp0000644000175000017500000000017312614413013026441 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { # MARK: error 5 interval_start_in_range period 2007-08-18 +2d } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ss_no_tracking_scenario.tjp0000644000175000017500000000035112614413013026422 0ustar bernatbernatproject "Test" 2011-05-15 +1w resource r "R" task t "T" # MARK: error 7 ss_no_tracking_scenario statussheet r 2011-05-15 +1w { task t { work 80% priority 1001 end 2011-05-18 status green "Lots of work done" } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/taskroot_leaf.tjp0000644000175000017500000000016412614413013024373 0ustar bernatbernatproject test "Test" "1.0" 2011-03-27 +2m task t "T" taskreport "R" { # MARK: error 7 taskroot_leaf taskroot t } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/invalid_file_name.tjp0000644000175000017500000000016412614413013025163 0ustar bernatbernatproject "test" 2011-10-29 +1m task "T" # MARK: error 6 invalid_file_name taskreport "foo\bar" { formats html } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/task_without_chargeset.tjp0000644000175000017500000000024512614413013026310 0ustar bernatbernatproject test "Test" "1.0" 2007-11-18 +2m account bar "Bar" task foo "Foo" { start ${projectstart} # MARK: error 8 task_without_chargeset charge 1000 onstart } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_end_too_early.tjp0000644000175000017500000000042712614413013025071 0ustar bernatbernatproject "test" 2010-02-19 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { duration 5d allocate r1 } timesheet r1 2010-02-21 +1w { task t1 { work 80% # MARK: error 16 ts_end_too_early end 2010-02-20 status green "Lots of work done" } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/macro_stack_overflow.tjp0000644000175000017500000000132412614413013025746 0ustar bernatbernatproject "Macro Stack Overflow" 2009-11-01 +2m macro foo [ task "bar" { ${foo} } ] # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: info 3 macro_stack # MARK: error 28 macro_stack_overflow ${foo} taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/undecl_flag.tjp0000644000175000017500000000014112614413013023774 0ustar bernatbernatproject test "Test" "1.0" 2007-03-25 +2m task t "T" { # MARK: error 5 undecl_flag flags foo } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/report_exists.tjp0000644000175000017500000000025312614413013024447 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m task t "T" { start ${projectstart} } taskreport report "report1" # MARK: error 9 report_exists taskreport report "report2" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/bad_include.tjp0000644000175000017500000000035112614413013023765 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m # Unfortunately, include file errors are reported at the first token # after the include statement. # MARK: error 6 bad_include include "foo.tji" task bar "Bar" { start ${projectstart} } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/no_token_match5.tjp0000644000175000017500000000014112614413013024606 0ustar bernatbernatproject test "Test" "1.0" 2009-08-25 +2m # MARK: error 6 no_token_match task t -8<- foo ->8taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/ts_headline_too_long.tjp0000644000175000017500000000060412614413013025714 0ustar bernatbernatproject "test" 2010-02-21 +2m { trackingscenario plan } resource r1 "R1" task t1 "Task 1" { effort 5d allocate r1 } timesheet r1 2010-02-21 +1w { task t1 { work 100% remaining 0d # MARK: error 18 ts_headline_too_long status green "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891" } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/adopt_duplicate_child-1.tjp0000644000175000017500000000022212614413013026173 0ustar bernatbernatproject "Adopted Tasks" 2011-03-05 +1m task t1 "T1" { task t2 "T2" } # MARK: error 7 adopt_duplicate_child task t3 "T3" { adopt t1.t2, t1 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/unknown_scenario_idx.tjp0000644000175000017500000000025612614413013025766 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 } taskreport report "report.html" { # MARK: error 9 unknown_scenario_idx scenarios plan, foo } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/adopt_self.tjp0000644000175000017500000000014012614413013023650 0ustar bernatbernatproject "Adopted Tasks" 2011-11-17 +1m # MARK: error 4 adopt_self task t1 "T1" { adopt t1 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/purge_unknown_id.tjp0000644000175000017500000000020012614413013025102 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m task t "T" { start ${projectstart} # MARK: error 6 purge_unknown_id purge foo } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/rev_acct_no_top.tjp0000644000175000017500000000054312614413013024703 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m account group1 "Group1" { account g1 "G1" account g2 "G2" account g3 "G3" } account group2 "Group2" { account g4 "G1" account g5 "G2" account g6 "G3" } task t "T" { start ${projectstart} chargeset g1, g2 } taskreport tasks "Tasks.html" { # MARK: error 22 rev_acct_no_top balance group1 g5 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/extend_id_cap.tjp0000644000175000017500000000015112614413013024320 0ustar bernatbernatproject test "Test" "1.0" 2007-03-25 +2m { extend task { # MARK: error 4 extend_id_cap text foo } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/chargeset.tjp0000644000175000017500000000034312614413013023502 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m account group1 "Group1" { account g1 "G1" account g2 "G2" account g3 "G3" } task t "T" { start ${projectstart} # MARK: error 12 chargeset chargeset g1 30%, g2 30%, g3 30% } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/no_own_resource_booking.tjp0000644000175000017500000000037212614413013026455 0ustar bernatbernatproject "Test" 2011-05-15 +1w { scenario s1 "S1" { scenario s2 "S2" } trackingscenario s1 } resource r "R" task t "T" { duration 2d } supplement resource r { # MARK: error 15 no_own_resource_booking s2:booking t 2011-05-16-9:00 +2h } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/runaway_token.tjp0000644000175000017500000000016712614413013024427 0ustar bernatbernatproject "Test" 2011-06-06 +1m task "Foo" { # MARK: error 6 runaway_token note -8<- This note never ends... taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/time_interval.tjp0000644000175000017500000000024512614413013024400 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m resource tux "Tux" { # MARK: error 5 time_interval workinghours mon 10:00 - 9:00 } task t "T" { start 2007-08-19 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/include_recursion.tjp0000644000175000017500000000034412614413013025252 0ustar bernatbernatproject "Test" 2009-10-06 +2m # The following error really happens in the included file, but the # testing scripts look for the MARK in this file. # MARK: error 1 include_recursion include "include_recursion.tji" task "Foo" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/scenario_exists.tjp0000644000175000017500000000025112614413013024735 0ustar bernatbernatproject test "Test" "1.0" 2009-02-11 +1m { scenario plan "Plan" { scenario s "First scenario s" # MARK: error 6 scenario_exists scenario s "Second scenario s" } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/no_own_task_booking.tjp0000644000175000017500000000036212614413013025567 0ustar bernatbernatproject "Test" 2011-05-15 +1w { scenario s1 "S1" { scenario s2 "S2" } trackingscenario s1 } resource r "R" task t "T" { duration 2d } supplement task t { # MARK: error 15 no_own_task_booking s2:booking r 2011-05-16-9:00 +2h } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/report_end.tjp0000644000175000017500000000024312614413013023675 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m task t "T" { start ${projectstart} } taskreport report "report.html" { # MARK: error 9 report_end end 2007-10-01 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/scenario_after_tracking.tjp0000644000175000017500000000020412614413013026377 0ustar bernatbernatproject "Test" 2011-06-27 +1m { trackingscenario plan # MARK: error 4 scenario_after_tracking scenario foo "Bar" } task "Foo" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/bad_timing_res.tjp0000644000175000017500000000014712614413013024505 0ustar bernatbernatproject test "Test" "1.0" 2007-08-26 +1w { # MARK: error 3 bad_timing_res timingresolution 21 min } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/effort_zero.tjp0000644000175000017500000000016512614413013024063 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 # MARK: error 6 effort_zero effort 0d } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/sloppy_range.tjp0000644000175000017500000000032312614413013024235 0ustar bernatbernatproject test "Test" "1.0" 2007-04-22 +2m resource tux "Tux" task foo "Foo" { start ${projectstart} } supplement resource tux { # MARK: error 11 sloppy_range booking foo ${projectstart} +1h { sloppy 4 } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/report_start.tjp0000644000175000017500000000024712614413013024270 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m task t "T" { start ${projectstart} } taskreport report "report.html" { # MARK: error 9 report_start start 2008-02-01 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/navigator_exists.tjp0000644000175000017500000000024012614413013025122 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m task t "T" { start ${projectstart} } navigator foo navigator bar # MARK: error 10 navigator_exists navigator foo taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/shift_assignment_overlap.tjp0000644000175000017500000000037712614413013026641 0ustar bernatbernatproject test "Test" "1.0" 2007-08-20 +2m shift a "A" { workinghours mon - fri off } shift b "B" { workinghours mon - fri 10:00 - 18:00 } resource r "R" { shifts a 2007-08-20 +2w # MARK: error 13 shift_assignment_overlap shifts b 2007-09-01 +2w } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/too_many_bangs.tjp0000644000175000017500000000022012614413013024526 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 task s "S" { # MARK: error 7 too_many_bangs depends !!!foo } } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/cost_rev_same.tjp0000644000175000017500000000054512614413013024372 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m account group1 "Group1" { account g1 "G1" account g2 "G2" account g3 "G3" } account group2 "Group2" { account g4 "G1" account g5 "G2" account g6 "G3" } task t "T" { start ${projectstart} chargeset g1, g2 } taskreport tasks "Tasks.html" { # MARK: error 22 cost_rev_same balance group1 group1 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/include_recursion.tji0000644000175000017500000000004012614413013025234 0ustar bernatbernatinclude "include_recursion.tji" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/too_large_timing_res.tjp0000644000175000017500000000023312614413013025726 0ustar bernatbernatproject test "Test" "1.0" 2007-08-26 +1w { timezone "Pacific/Marquesas" # MARK: error 4 too_large_timing_res timingresolution 60 min } task foo "Foo" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/shift_exists.tjp0000644000175000017500000000017312614413013024252 0ustar bernatbernatproject test "Test" "1.0" 2009-02-11 +1m shift s "First shift s" # MARK: error 6 shift_exists shift s "Second shift s" taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/misaligned_date.tjp0000644000175000017500000000021512614413013024644 0ustar bernatbernatproject test "Test" "1.0" 2007-08-26 +1w { timingresolution 20 min } # MARK: error 6 misaligned_date vacation "Day off" 2007-08-27-18:15 taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/operand_unkn_scen.tjp0000644000175000017500000000030012614413013025221 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m { timezone "Europe/Amsterdam" } task t "T" { start ${projectstart} } export "report.tji" { # MARK: error 11 operand_unkn_scen hidetask foo.t } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/date_in_range.tjp0000644000175000017500000000015412614413013024314 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { # MARK: error 5 date_in_range start 2007-08-18 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/chargeset_master.tjp0000644000175000017500000000035112614413013025054 0ustar bernatbernatproject test "Test" "1.0" 2007-11-16 +2m account group1 "Group1" { account g1 "G1" account g2 "G2" account g3 "G3" } task t "T" { start ${projectstart} chargeset g1, g2 # MARK: error 13 chargeset_master chargeset g3 } taskjuggler-3.5.0/test/TestSuite/Syntax/Errors/sort_direction.tjp0000644000175000017500000000025412614413013024565 0ustar bernatbernatproject test "Test" "1.0" 2007-08-19 +2m task t "T" { start 2007-08-19 } taskreport report "report.html" { # MARK: error 9 sort_direction sorttasks plan.start.foo } taskjuggler-3.5.0/test/TestSuite/Export-Reports/0000755000175000017500000000000012614413013021171 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Export-Reports/export.tji0000644000175000017500000000013212614413013023216 0ustar bernatbernatexport "." { timezone "UTC" definitions * taskattributes * resourceattributes * } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/0000755000175000017500000000000012614413013022130 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/ProjectIDs.tjp0000644000175000017500000000112312614413013024652 0ustar bernatbernatproject prj1 "ProjectIDs example" "1.0" 2006-08-22-00:00-+0000 - 2006-09-21-10:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj, prj1, prj2 task t1 "Task 1" { start 2006-08-22-06:00-+0000 milestone scheduled } task t2 "Task 2" { start 2006-08-22-06:00-+0000 milestone scheduled } task t3 "Task 3" { start 2006-08-22-06:00-+0000 milestone scheduled } supplement task t1 { priority 500 projectid prj } supplement task t2 { priority 500 projectid prj1 } supplement task t3 { priority 500 projectid prj2 } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/TimeFrame.tjp0000644000175000017500000000205412614413013024521 0ustar bernatbernatproject prj "Simple Project" "1.0" 2000-01-01-12:00-+0000 - 2000-01-04-18:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux "Tux" task t1 "Task1" { start 2000-01-01-19:00-+0000 end 2000-01-04-00:00-+0000 scheduling asap scheduled } task t2 "Task2" { start 2000-01-02-07:00-+0000 end 2000-01-03-07:00-+0000 scheduling asap scheduled } task t3 "Task3" { start 2000-01-03-16:00-+0000 end 2000-01-04-00:00-+0000 scheduling asap scheduled } supplement task t1 { priority 500 projectid prj } supplement task t2 { priority 500 projectid prj } supplement task t3 { booking tux 2000-01-03-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Complete.tjp0000644000175000017500000000060512614413013024420 0ustar bernatbernatproject simple "Simple Project" "1.0" 2007-01-01-00:00-+0000 - 2007-03-02-20:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids simple task _Task_1 "Build house" { start 2007-01-06-07:00-+0000 end 2007-02-05-07:00-+0000 scheduling asap scheduled } supplement task _Task_1 { complete 20 priority 500 projectid simple } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/EnvVar.tjp0000644000175000017500000000055112614413013024051 0ustar bernatbernatproject prj "Test" "1.0" 2011-05-05-00:00-+0000 - 2011-11-03-12:00-+0000 { timezone "UTC" scenario plan "Plan Scenario" { active yes } } projectids prj task t "A t_e_s_t_1 task" { start 2011-05-05-00:00-+0000 end 2011-09-05-00:00-+0000 scheduling asap scheduled } supplement task t { note "A test String" priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/AlertLevels.tjp0000644000175000017500000000100512614413013025065 0ustar bernatbernatproject prj "Alert Levels" "1.0" 2011-11-24-00:00-+0000 - 2012-01-23-20:00-+0000 { timezone "UTC" alertlevels green 'Low' '#2AA46C', blue 'Guarded' '#457CC4', yellow 'Elevated' '#F1D821', orange 'High' '#F99836', red 'Severe' '#E43745' scenario plan "Plan Scenario" { active yes } } projectids prj task _Task_1 "Holiday Season" { start 2011-11-24-00:00-+0000 milestone scheduled } supplement task _Task_1 { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/textreport.tjp0000644000175000017500000000045612614413013025074 0ustar bernatbernatproject prj "Test" "1.0" 2011-12-11-00:00-+0000 - 2012-01-10-10:00-+0000 { timezone "UTC" scenario plan "Plan Scenario" { active yes } } projectids prj task _Task_1 "Foo" { start 2011-12-11-00:00-+0000 milestone scheduled } supplement task _Task_1 { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Journal.tjp0000644000175000017500000000121412614413013024257 0ustar bernatbernatproject prj "Project with Journal" "1.0" 2009-05-04-00:00-+0000 - 2009-06-03-10:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux "Tux" task t1 "Task1" { start 2009-05-05-06:00-+0000 milestone scheduled } supplement task t1 { priority 500 projectid prj } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Limits-1.tjp0000644000175000017500000001701112614413013024246 0ustar bernatbernatproject limits "Limits" "1.0" 2007-03-01-00:00-+0000 - 2008-02-29-00:00-+0000 { timezone "Europe/Amsterdam" scenario plan "Plan Scenario" { active yes } } projectids limits resource r1 "R1" resource r2 "R2" task t1 "Task 1" { start 2007-03-30-07:00-+0000 end 2007-05-07-09:00-+0000 scheduling asap scheduled } task t2 "Task 2" { start 2007-03-30-10:00-+0000 end 2007-05-01-12:00-+0000 scheduling asap scheduled } task t3 "Task 3" { start 2007-05-01-12:00-+0000 end 2007-06-04-13:00-+0000 scheduling asap scheduled } task t4 "Task 4" { start 2007-05-09-09:00-+0000 end 2007-06-07-13:00-+0000 scheduling asap scheduled } task t5 "Task 5" { start 2007-03-01-00:00-+0000 end 2007-04-30-00:00-+0000 scheduling asap scheduled } task t6 "Task 6" { start 2007-03-01-00:00-+0000 end 2007-04-30-00:00-+0000 scheduling asap scheduled } task t7 "Task 7" { start 2007-06-19-22:00-+0000 end 2007-07-09-22:00-+0000 scheduling asap scheduled } supplement task t1 { booking r2 2007-03-30-07:00-+0000 + 3.0h, 2007-04-02-07:00-+0000 + 3.0h, 2007-04-03-07:00-+0000 + 3.0h, 2007-04-04-07:00-+0000 + 3.0h, 2007-04-05-07:00-+0000 + 3.0h, 2007-04-06-07:00-+0000 + 3.0h, 2007-04-09-07:00-+0000 + 3.0h, 2007-04-10-07:00-+0000 + 3.0h, 2007-04-11-07:00-+0000 + 3.0h, 2007-04-12-07:00-+0000 + 3.0h, 2007-04-13-07:00-+0000 + 3.0h, 2007-04-16-07:00-+0000 + 3.0h, 2007-04-17-07:00-+0000 + 3.0h, 2007-04-18-07:00-+0000 + 3.0h, 2007-04-19-07:00-+0000 + 3.0h, 2007-04-20-07:00-+0000 + 3.0h, 2007-04-23-07:00-+0000 + 3.0h, 2007-04-24-07:00-+0000 + 3.0h, 2007-04-25-07:00-+0000 + 3.0h, 2007-04-26-07:00-+0000 + 3.0h, 2007-04-27-07:00-+0000 + 3.0h, 2007-04-30-07:00-+0000 + 3.0h, 2007-05-01-07:00-+0000 + 3.0h, 2007-05-02-07:00-+0000 + 3.0h, 2007-05-03-07:00-+0000 + 3.0h, 2007-05-04-07:00-+0000 + 3.0h, 2007-05-07-07:00-+0000 + 2.0h { overtime 2 } priority 500 projectid limits } supplement task t2 { booking r2 2007-03-30-10:00-+0000 + 5.0h, 2007-04-02-10:00-+0000 + 5.0h, 2007-04-03-10:00-+0000 + 5.0h, 2007-04-04-10:00-+0000 + 5.0h, 2007-04-05-10:00-+0000 + 2.0h, 2007-04-09-10:00-+0000 + 5.0h, 2007-04-10-10:00-+0000 + 5.0h, 2007-04-11-10:00-+0000 + 5.0h, 2007-04-12-10:00-+0000 + 2.0h, 2007-04-16-10:00-+0000 + 5.0h, 2007-04-17-10:00-+0000 + 5.0h, 2007-04-18-10:00-+0000 + 5.0h, 2007-04-19-10:00-+0000 + 2.0h, 2007-04-23-10:00-+0000 + 5.0h, 2007-04-24-10:00-+0000 + 5.0h, 2007-04-25-10:00-+0000 + 5.0h, 2007-04-26-10:00-+0000 + 2.0h, 2007-04-30-10:00-+0000 + 5.0h, 2007-05-01-10:00-+0000 + 2.0h { overtime 2 } priority 500 projectid limits } supplement task t3 { booking r2 2007-05-01-12:00-+0000 + 3.0h, 2007-05-02-10:00-+0000 + 5.0h, 2007-05-03-10:00-+0000 + 2.0h, 2007-05-07-09:00-+0000 + 6.0h, 2007-05-08-07:00-+0000 + 8.0h, 2007-05-09-07:00-+0000 + 2.0h, 2007-05-14-07:00-+0000 + 8.0h, 2007-05-15-07:00-+0000 + 8.0h, 2007-05-21-07:00-+0000 + 8.0h, 2007-05-22-07:00-+0000 + 8.0h, 2007-05-28-07:00-+0000 + 8.0h, 2007-05-29-07:00-+0000 + 8.0h, 2007-06-04-07:00-+0000 + 6.0h { overtime 2 } priority 500 projectid limits } supplement task t4 { booking r2 2007-05-09-09:00-+0000 + 6.0h, 2007-05-10-07:00-+0000 + 8.0h, 2007-05-16-07:00-+0000 + 8.0h, 2007-05-17-07:00-+0000 + 8.0h, 2007-05-23-07:00-+0000 + 8.0h, 2007-05-24-07:00-+0000 + 2.0h, 2007-05-30-07:00-+0000 + 8.0h, 2007-05-31-07:00-+0000 + 8.0h, 2007-06-04-13:00-+0000 + 2.0h, 2007-06-05-07:00-+0000 + 8.0h, 2007-06-06-07:00-+0000 + 8.0h, 2007-06-07-07:00-+0000 + 6.0h { overtime 2 } priority 500 projectid limits } supplement task t5 { booking r1 2007-03-01-08:00-+0000 + 2.0h, 2007-03-02-08:00-+0000 + 2.0h, 2007-03-05-08:00-+0000 + 2.0h, 2007-03-06-08:00-+0000 + 2.0h, 2007-03-07-08:00-+0000 + 2.0h, 2007-03-12-08:00-+0000 + 2.0h, 2007-03-13-08:00-+0000 + 2.0h, 2007-03-14-08:00-+0000 + 2.0h, 2007-03-19-08:00-+0000 + 2.0h, 2007-03-20-08:00-+0000 + 2.0h, 2007-04-02-07:00-+0000 + 2.0h, 2007-04-03-07:00-+0000 + 2.0h, 2007-04-04-07:00-+0000 + 2.0h, 2007-04-09-07:00-+0000 + 2.0h, 2007-04-10-07:00-+0000 + 2.0h, 2007-04-11-07:00-+0000 + 2.0h, 2007-04-16-07:00-+0000 + 2.0h, 2007-04-17-07:00-+0000 + 2.0h, 2007-04-18-07:00-+0000 + 2.0h, 2007-04-23-07:00-+0000 + 2.0h { overtime 2 } priority 500 projectid limits } supplement task t6 { booking r2 2007-03-01-08:00-+0000 + 4.0h, 2007-03-02-08:00-+0000 + 4.0h, 2007-03-05-08:00-+0000 + 4.0h, 2007-03-06-08:00-+0000 + 4.0h, 2007-03-07-08:00-+0000 + 4.0h, 2007-03-08-08:00-+0000 + 4.0h, 2007-03-09-08:00-+0000 + 4.0h, 2007-03-12-08:00-+0000 + 4.0h, 2007-03-13-08:00-+0000 + 4.0h, 2007-03-14-08:00-+0000 + 4.0h, 2007-03-15-08:00-+0000 + 4.0h, 2007-03-16-08:00-+0000 + 4.0h, 2007-03-19-08:00-+0000 + 4.0h, 2007-03-20-08:00-+0000 + 4.0h, 2007-03-21-08:00-+0000 + 4.0h, 2007-03-22-08:00-+0000 + 4.0h, 2007-03-23-08:00-+0000 + 4.0h, 2007-03-26-07:00-+0000 + 4.0h, 2007-03-27-07:00-+0000 + 4.0h, 2007-03-28-07:00-+0000 + 4.0h { overtime 2 } priority 500 projectid limits } supplement task t7 { booking r1 2007-06-20-07:00-+0000 + 2.0h, 2007-06-21-07:00-+0000 + 2.0h, 2007-06-22-07:00-+0000 + 2.0h, 2007-06-25-07:00-+0000 + 2.0h, 2007-06-26-07:00-+0000 + 2.0h, 2007-06-27-07:00-+0000 + 2.0h, 2007-07-02-07:00-+0000 + 2.0h, 2007-07-03-07:00-+0000 + 2.0h, 2007-07-04-07:00-+0000 + 2.0h, 2007-07-09-07:00-+0000 + 2.0h { overtime 2 } booking r2 2007-06-20-07:00-+0000 + 6.0h, 2007-06-21-07:00-+0000 + 6.0h, 2007-06-22-07:00-+0000 + 6.0h, 2007-06-25-07:00-+0000 + 6.0h, 2007-06-26-07:00-+0000 + 6.0h, 2007-06-27-07:00-+0000 + 6.0h, 2007-06-28-07:00-+0000 + 6.0h, 2007-06-29-07:00-+0000 + 6.0h, 2007-07-02-07:00-+0000 + 6.0h, 2007-07-03-07:00-+0000 + 6.0h, 2007-07-04-07:00-+0000 + 6.0h, 2007-07-05-07:00-+0000 + 6.0h, 2007-07-06-07:00-+0000 + 6.0h, 2007-07-09-07:00-+0000 + 6.0h { overtime 2 } priority 500 projectid limits } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/ResourcePrefix.tjp0000644000175000017500000000354012614413013025616 0ustar bernatbernatproject prj "Resource Prefix Example" "1.0" 2009-09-13-00:00-+0000 - 2009-10-13-10:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource team "Team" { resource foo "Foo" resource bar "Bar" } task t "Task" { start 2009-09-14-14:00-+0000 end 2009-09-21-17:00-+0000 scheduling asap scheduled } supplement task t { booking bar 2009-09-14-14:00-+0000 + 7.0h, 2009-09-15-14:00-+0000 + 7.0h, 2009-09-16-14:00-+0000 + 7.0h, 2009-09-17-14:00-+0000 + 7.0h, 2009-09-18-14:00-+0000 + 7.0h, 2009-09-21-14:00-+0000 + 3.0h { overtime 2 } booking foo 2009-09-14-15:00-+0000 + 8.0h, 2009-09-15-15:00-+0000 + 8.0h, 2009-09-16-15:00-+0000 + 8.0h, 2009-09-17-15:00-+0000 + 8.0h, 2009-09-18-15:00-+0000 + 8.0h, 2009-09-21-15:00-+0000 + 2.0h { overtime 2 } priority 500 projectid prj } supplement resource team { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource foo { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource bar { workinghours sun off workinghours mon 8:00 - 15:00 workinghours tue 8:00 - 15:00 workinghours wed 8:00 - 15:00 workinghours thu 8:00 - 15:00 workinghours fri 8:00 - 15:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Shift.tjp0000644000175000017500000000550012614413013023724 0ustar bernatbernatproject prj "Example" "1.0" 2000-01-01-00:00-+0000 - 2000-03-31-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj shift s1 "Shift1" { workinghours sun off workinghours mon 10:00 - 12:00, 13:00 - 15:00 workinghours tue 9:00 - 14:00 workinghours wed off workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off shift s2 "Shift2" { workinghours sun off workinghours mon 10:00 - 17:00 workinghours tue 9:00 - 14:00 workinghours wed off workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } } shift s3 "Part-time schedule 1" { workinghours sun off workinghours mon 9:00 - 12:00, 13:00 - 18:00 workinghours tue off workinghours wed 9:00 - 12:00 workinghours thu off workinghours fri 9:00 - 12:00, 13:00 - 18:00 workinghours sat off } shift s4 "Part-time schedule 2" { workinghours sun off workinghours mon off workinghours tue 9:00 - 12:00, 13:00 - 18:00 workinghours wed off workinghours thu 9:00 - 12:00, 13:00 - 18:00 workinghours fri off workinghours sat off } shift s5 "All-day, all-week shift" { workinghours sun 0:00 - 24:00 workinghours mon 0:00 - 24:00 workinghours tue 0:00 - 24:00 workinghours wed 0:00 - 24:00 workinghours thu 0:00 - 24:00 workinghours fri 0:00 - 24:00 workinghours sat 0:00 - 24:00 } resource r1 "Resource1" resource r2 "Resource2" task t1 "Task1" { start 2000-01-01-07:00-+0000 end 2000-02-08-20:00-+0000 scheduling asap scheduled } supplement task t1 { priority 500 projectid prj } supplement resource r1 { shifts s1 2000-01-01-07:00-+0000 - 2000-01-10-07:00-+0000, s2 2000-01-11-07:00-+0000 - 2000-01-20-07:00-+0000 workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { shifts s3 2005-01-01-07:00-+0000 - 2005-01-15-07:00-+0000, s4 2005-01-15-07:00-+0000 - 2006-01-01-07:00-+0000 workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Depends1.tjp0000644000175000017500000000171612614413013024317 0ustar bernatbernatproject prj "P" "1.0" 2007-11-09-00:00-+0000 - 2007-12-24-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task foo1 "foo1" { task foo2 "foo2" { start 2007-12-04-07:00-+0000 milestone scheduled } task foo3 "foo3" { depends foo1.foo2 start 2007-12-04-07:00-+0000 end 2007-12-05-00:00-+0000 scheduling asap scheduled } } task bar "bar" { depends foo1.foo2 start 2007-12-04-07:00-+0000 end 2007-12-06-00:00-+0000 scheduling asap scheduled } task bar1 "bar1" { depends foo1, bar start 2007-12-07-00:00-+0000 end 2007-12-09-00:00-+0000 scheduling asap scheduled } supplement task foo1 { priority 500 projectid prj } supplement task foo1.foo2 { priority 500 projectid prj } supplement task foo1.foo3 { priority 500 projectid prj } supplement task bar { priority 500 projectid prj } supplement task bar1 { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Currencyformat.tjp0000644000175000017500000000046012614413013025652 0ustar bernatbernatproject prj "Project" "1.0" 2007-01-01-00:00-+0000 - 2007-03-01-00:00-+0000 { timezone "Europe/Berlin" scenario plan "Plan Scenario" { active yes } } projectids prj task t "Task" { start 2007-01-01-00:00-+0000 milestone scheduled } supplement task t { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Numberformat.tjp0000644000175000017500000000046012614413013025310 0ustar bernatbernatproject prj "Project" "1.0" 2000-01-01-00:00-+0000 - 2000-03-01-00:00-+0000 { timezone "Europe/Berlin" scenario plan "Plan Scenario" { active yes } } projectids prj task t "Task" { start 2000-01-01-00:00-+0000 milestone scheduled } supplement task t { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/AutoMacros.tjp0000644000175000017500000000050312614413013024722 0ustar bernatbernatproject prj "Auto Macro Test" "1.0" 2006-09-22-00:00-+0000 - 2006-10-22-10:00-+0000 { timezone "UTC" scenario plan "Plan Scenario" { active yes } } projectids prj task items "Project breakdown" { start 2006-09-22-00:00-+0000 milestone scheduled } supplement task items { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/AccountReport.tjp0000644000175000017500000012362012614413013025443 0ustar bernatbernatproject prj "AccountReport" "1.0" 2011-11-09-00:00-+0000 - 2012-11-08-00:00-+0000 { timezone "UTC" scenario plan "Plan Scenario" { active yes } } projectids prj resource teamA "Team A" { resource _Resource_2 "R1" resource _Resource_3 "R2" } resource teamB "Team B" { resource _Resource_5 "R3" resource _Resource_6 "R4" } task _Task_1 "Products" { task p1 "Product 1" { start 2012-02-08-10:00-+0000 end 2012-10-08-17:00-+0000 scheduling asap scheduled } task p2 "Product 2" { start 2011-11-09-09:00-+0000 end 2012-08-08-14:00-+0000 scheduling asap scheduled } task p3 "Product 3" { start 2011-11-09-09:00-+0000 end 2012-02-08-11:00-+0000 scheduling asap scheduled } task mf "Manufacturing" { depends _Task_1.p1, _Task_1.p2, _Task_1.p3 start 2012-10-08-17:00-+0000 end 2012-10-22-17:00-+0000 scheduling asap scheduled } task _Task_6 "Final Payment" { depends _Task_1.mf start 2012-10-22-17:00-+0000 milestone scheduled } } supplement task _Task_1 { priority 500 projectid prj } supplement task _Task_1.p1 { booking _Resource_2 2012-08-08-14:00-+0000 + 3.0h, 2012-08-09-09:00-+0000 + 8.0h, 2012-08-10-09:00-+0000 + 8.0h, 2012-08-13-09:00-+0000 + 8.0h, 2012-08-14-09:00-+0000 + 8.0h, 2012-08-15-09:00-+0000 + 8.0h, 2012-08-16-09:00-+0000 + 8.0h, 2012-08-17-09:00-+0000 + 8.0h, 2012-08-20-09:00-+0000 + 8.0h, 2012-08-21-09:00-+0000 + 8.0h, 2012-08-22-09:00-+0000 + 8.0h, 2012-08-23-09:00-+0000 + 8.0h, 2012-08-24-09:00-+0000 + 8.0h, 2012-08-27-09:00-+0000 + 8.0h, 2012-08-28-09:00-+0000 + 8.0h, 2012-08-29-09:00-+0000 + 8.0h, 2012-08-30-09:00-+0000 + 8.0h, 2012-08-31-09:00-+0000 + 8.0h, 2012-09-03-09:00-+0000 + 8.0h, 2012-09-04-09:00-+0000 + 8.0h, 2012-09-05-09:00-+0000 + 8.0h, 2012-09-06-09:00-+0000 + 8.0h, 2012-09-07-09:00-+0000 + 8.0h, 2012-09-10-09:00-+0000 + 8.0h, 2012-09-11-09:00-+0000 + 8.0h, 2012-09-12-09:00-+0000 + 8.0h, 2012-09-13-09:00-+0000 + 8.0h, 2012-09-14-09:00-+0000 + 8.0h, 2012-09-17-09:00-+0000 + 8.0h, 2012-09-18-09:00-+0000 + 8.0h, 2012-09-19-09:00-+0000 + 8.0h, 2012-09-20-09:00-+0000 + 8.0h, 2012-09-21-09:00-+0000 + 8.0h, 2012-09-24-09:00-+0000 + 8.0h, 2012-09-25-09:00-+0000 + 8.0h, 2012-09-26-09:00-+0000 + 8.0h, 2012-09-27-09:00-+0000 + 8.0h, 2012-09-28-09:00-+0000 + 8.0h, 2012-10-01-09:00-+0000 + 8.0h, 2012-10-02-09:00-+0000 + 8.0h, 2012-10-03-09:00-+0000 + 8.0h, 2012-10-04-09:00-+0000 + 8.0h, 2012-10-05-09:00-+0000 + 8.0h, 2012-10-08-09:00-+0000 + 8.0h { overtime 2 } booking _Resource_3 2012-08-08-13:00-+0000 + 4.0h, 2012-08-09-09:00-+0000 + 8.0h, 2012-08-10-09:00-+0000 + 8.0h, 2012-08-13-09:00-+0000 + 8.0h, 2012-08-14-09:00-+0000 + 8.0h, 2012-08-15-09:00-+0000 + 8.0h, 2012-08-16-09:00-+0000 + 8.0h, 2012-08-17-09:00-+0000 + 8.0h, 2012-08-20-09:00-+0000 + 8.0h, 2012-08-21-09:00-+0000 + 8.0h, 2012-08-22-09:00-+0000 + 8.0h, 2012-08-23-09:00-+0000 + 8.0h, 2012-08-24-09:00-+0000 + 8.0h, 2012-08-27-09:00-+0000 + 8.0h, 2012-08-28-09:00-+0000 + 8.0h, 2012-08-29-09:00-+0000 + 8.0h, 2012-08-30-09:00-+0000 + 8.0h, 2012-08-31-09:00-+0000 + 8.0h, 2012-09-03-09:00-+0000 + 8.0h, 2012-09-04-09:00-+0000 + 8.0h, 2012-09-05-09:00-+0000 + 8.0h, 2012-09-06-09:00-+0000 + 8.0h, 2012-09-07-09:00-+0000 + 8.0h, 2012-09-10-09:00-+0000 + 8.0h, 2012-09-11-09:00-+0000 + 8.0h, 2012-09-12-09:00-+0000 + 8.0h, 2012-09-13-09:00-+0000 + 8.0h, 2012-09-14-09:00-+0000 + 8.0h, 2012-09-17-09:00-+0000 + 8.0h, 2012-09-18-09:00-+0000 + 8.0h, 2012-09-19-09:00-+0000 + 8.0h, 2012-09-20-09:00-+0000 + 8.0h, 2012-09-21-09:00-+0000 + 8.0h, 2012-09-24-09:00-+0000 + 8.0h, 2012-09-25-09:00-+0000 + 8.0h, 2012-09-26-09:00-+0000 + 8.0h, 2012-09-27-09:00-+0000 + 8.0h, 2012-09-28-09:00-+0000 + 8.0h, 2012-10-01-09:00-+0000 + 8.0h, 2012-10-02-09:00-+0000 + 8.0h, 2012-10-03-09:00-+0000 + 8.0h, 2012-10-04-09:00-+0000 + 8.0h, 2012-10-05-09:00-+0000 + 8.0h, 2012-10-08-09:00-+0000 + 8.0h { overtime 2 } booking _Resource_5 2012-02-08-11:00-+0000 + 6.0h, 2012-02-09-09:00-+0000 + 8.0h, 2012-02-10-09:00-+0000 + 8.0h, 2012-02-13-09:00-+0000 + 8.0h, 2012-02-14-09:00-+0000 + 8.0h, 2012-02-15-09:00-+0000 + 8.0h, 2012-02-16-09:00-+0000 + 8.0h, 2012-02-17-09:00-+0000 + 8.0h, 2012-02-20-09:00-+0000 + 8.0h, 2012-02-21-09:00-+0000 + 8.0h, 2012-02-22-09:00-+0000 + 8.0h, 2012-02-23-09:00-+0000 + 8.0h, 2012-02-24-09:00-+0000 + 8.0h, 2012-02-27-09:00-+0000 + 8.0h, 2012-02-28-09:00-+0000 + 8.0h, 2012-02-29-09:00-+0000 + 8.0h, 2012-03-01-09:00-+0000 + 8.0h, 2012-03-02-09:00-+0000 + 8.0h, 2012-03-05-09:00-+0000 + 8.0h, 2012-03-06-09:00-+0000 + 8.0h, 2012-03-07-09:00-+0000 + 8.0h, 2012-03-08-09:00-+0000 + 8.0h, 2012-03-09-09:00-+0000 + 8.0h, 2012-03-12-09:00-+0000 + 8.0h, 2012-03-13-09:00-+0000 + 8.0h, 2012-03-14-09:00-+0000 + 8.0h, 2012-03-15-09:00-+0000 + 8.0h, 2012-03-16-09:00-+0000 + 8.0h, 2012-03-19-09:00-+0000 + 8.0h, 2012-03-20-09:00-+0000 + 8.0h, 2012-03-21-09:00-+0000 + 8.0h, 2012-03-22-09:00-+0000 + 8.0h, 2012-03-23-09:00-+0000 + 8.0h, 2012-03-26-09:00-+0000 + 8.0h, 2012-03-27-09:00-+0000 + 8.0h, 2012-03-28-09:00-+0000 + 8.0h, 2012-03-29-09:00-+0000 + 8.0h, 2012-03-30-09:00-+0000 + 8.0h, 2012-04-02-09:00-+0000 + 8.0h, 2012-04-03-09:00-+0000 + 8.0h, 2012-04-04-09:00-+0000 + 8.0h, 2012-04-05-09:00-+0000 + 8.0h, 2012-04-06-09:00-+0000 + 8.0h, 2012-04-09-09:00-+0000 + 8.0h, 2012-04-10-09:00-+0000 + 8.0h, 2012-04-11-09:00-+0000 + 8.0h, 2012-04-12-09:00-+0000 + 8.0h, 2012-04-13-09:00-+0000 + 8.0h, 2012-04-16-09:00-+0000 + 8.0h, 2012-04-17-09:00-+0000 + 8.0h, 2012-04-18-09:00-+0000 + 8.0h, 2012-04-19-09:00-+0000 + 8.0h, 2012-04-20-09:00-+0000 + 8.0h, 2012-04-23-09:00-+0000 + 8.0h, 2012-04-24-09:00-+0000 + 8.0h, 2012-04-25-09:00-+0000 + 8.0h, 2012-04-26-09:00-+0000 + 8.0h, 2012-04-27-09:00-+0000 + 8.0h, 2012-04-30-09:00-+0000 + 8.0h, 2012-05-01-09:00-+0000 + 8.0h, 2012-05-02-09:00-+0000 + 8.0h, 2012-05-03-09:00-+0000 + 8.0h, 2012-05-04-09:00-+0000 + 8.0h, 2012-05-07-09:00-+0000 + 8.0h, 2012-05-08-09:00-+0000 + 8.0h, 2012-05-09-09:00-+0000 + 8.0h, 2012-05-10-09:00-+0000 + 8.0h, 2012-05-11-09:00-+0000 + 8.0h, 2012-05-14-09:00-+0000 + 8.0h, 2012-05-15-09:00-+0000 + 8.0h, 2012-05-16-09:00-+0000 + 8.0h, 2012-05-17-09:00-+0000 + 8.0h, 2012-05-18-09:00-+0000 + 8.0h, 2012-05-21-09:00-+0000 + 8.0h, 2012-05-22-09:00-+0000 + 8.0h, 2012-05-23-09:00-+0000 + 8.0h, 2012-05-24-09:00-+0000 + 8.0h, 2012-05-25-09:00-+0000 + 8.0h, 2012-05-28-09:00-+0000 + 8.0h, 2012-05-29-09:00-+0000 + 8.0h, 2012-05-30-09:00-+0000 + 8.0h, 2012-05-31-09:00-+0000 + 8.0h, 2012-06-01-09:00-+0000 + 8.0h, 2012-06-04-09:00-+0000 + 8.0h, 2012-06-05-09:00-+0000 + 8.0h, 2012-06-06-09:00-+0000 + 8.0h, 2012-06-07-09:00-+0000 + 8.0h, 2012-06-08-09:00-+0000 + 8.0h, 2012-06-11-09:00-+0000 + 8.0h, 2012-06-12-09:00-+0000 + 8.0h, 2012-06-13-09:00-+0000 + 8.0h, 2012-06-14-09:00-+0000 + 8.0h, 2012-06-15-09:00-+0000 + 8.0h, 2012-06-18-09:00-+0000 + 8.0h, 2012-06-19-09:00-+0000 + 8.0h, 2012-06-20-09:00-+0000 + 8.0h, 2012-06-21-09:00-+0000 + 8.0h, 2012-06-22-09:00-+0000 + 8.0h, 2012-06-25-09:00-+0000 + 8.0h, 2012-06-26-09:00-+0000 + 8.0h, 2012-06-27-09:00-+0000 + 8.0h, 2012-06-28-09:00-+0000 + 8.0h, 2012-06-29-09:00-+0000 + 8.0h, 2012-07-02-09:00-+0000 + 8.0h, 2012-07-03-09:00-+0000 + 8.0h, 2012-07-04-09:00-+0000 + 8.0h, 2012-07-05-09:00-+0000 + 8.0h, 2012-07-06-09:00-+0000 + 8.0h, 2012-07-09-09:00-+0000 + 8.0h, 2012-07-10-09:00-+0000 + 8.0h, 2012-07-11-09:00-+0000 + 8.0h, 2012-07-12-09:00-+0000 + 8.0h, 2012-07-13-09:00-+0000 + 8.0h, 2012-07-16-09:00-+0000 + 8.0h, 2012-07-17-09:00-+0000 + 8.0h, 2012-07-18-09:00-+0000 + 8.0h, 2012-07-19-09:00-+0000 + 8.0h, 2012-07-20-09:00-+0000 + 8.0h, 2012-07-23-09:00-+0000 + 8.0h, 2012-07-24-09:00-+0000 + 8.0h, 2012-07-25-09:00-+0000 + 8.0h, 2012-07-26-09:00-+0000 + 8.0h, 2012-07-27-09:00-+0000 + 8.0h, 2012-07-30-09:00-+0000 + 8.0h, 2012-07-31-09:00-+0000 + 8.0h, 2012-08-01-09:00-+0000 + 8.0h, 2012-08-02-09:00-+0000 + 8.0h, 2012-08-03-09:00-+0000 + 8.0h, 2012-08-06-09:00-+0000 + 8.0h, 2012-08-07-09:00-+0000 + 8.0h, 2012-08-08-09:00-+0000 + 8.0h, 2012-08-09-09:00-+0000 + 8.0h, 2012-08-10-09:00-+0000 + 8.0h, 2012-08-13-09:00-+0000 + 8.0h, 2012-08-14-09:00-+0000 + 8.0h, 2012-08-15-09:00-+0000 + 8.0h, 2012-08-16-09:00-+0000 + 8.0h, 2012-08-17-09:00-+0000 + 8.0h, 2012-08-20-09:00-+0000 + 8.0h, 2012-08-21-09:00-+0000 + 8.0h, 2012-08-22-09:00-+0000 + 8.0h, 2012-08-23-09:00-+0000 + 8.0h, 2012-08-24-09:00-+0000 + 8.0h, 2012-08-27-09:00-+0000 + 8.0h, 2012-08-28-09:00-+0000 + 8.0h, 2012-08-29-09:00-+0000 + 8.0h, 2012-08-30-09:00-+0000 + 8.0h, 2012-08-31-09:00-+0000 + 8.0h, 2012-09-03-09:00-+0000 + 8.0h, 2012-09-04-09:00-+0000 + 8.0h, 2012-09-05-09:00-+0000 + 8.0h, 2012-09-06-09:00-+0000 + 8.0h, 2012-09-07-09:00-+0000 + 8.0h, 2012-09-10-09:00-+0000 + 8.0h, 2012-09-11-09:00-+0000 + 8.0h, 2012-09-12-09:00-+0000 + 8.0h, 2012-09-13-09:00-+0000 + 8.0h, 2012-09-14-09:00-+0000 + 8.0h, 2012-09-17-09:00-+0000 + 8.0h, 2012-09-18-09:00-+0000 + 8.0h, 2012-09-19-09:00-+0000 + 8.0h, 2012-09-20-09:00-+0000 + 8.0h, 2012-09-21-09:00-+0000 + 8.0h, 2012-09-24-09:00-+0000 + 8.0h, 2012-09-25-09:00-+0000 + 8.0h, 2012-09-26-09:00-+0000 + 8.0h, 2012-09-27-09:00-+0000 + 8.0h, 2012-09-28-09:00-+0000 + 8.0h, 2012-10-01-09:00-+0000 + 8.0h, 2012-10-02-09:00-+0000 + 8.0h, 2012-10-03-09:00-+0000 + 8.0h, 2012-10-04-09:00-+0000 + 8.0h, 2012-10-05-09:00-+0000 + 8.0h, 2012-10-08-09:00-+0000 + 8.0h { overtime 2 } booking _Resource_6 2012-02-08-10:00-+0000 + 7.0h, 2012-02-09-09:00-+0000 + 8.0h, 2012-02-10-09:00-+0000 + 8.0h, 2012-02-13-09:00-+0000 + 8.0h, 2012-02-14-09:00-+0000 + 8.0h, 2012-02-15-09:00-+0000 + 8.0h, 2012-02-16-09:00-+0000 + 8.0h, 2012-02-17-09:00-+0000 + 8.0h, 2012-02-20-09:00-+0000 + 8.0h, 2012-02-21-09:00-+0000 + 8.0h, 2012-02-22-09:00-+0000 + 8.0h, 2012-02-23-09:00-+0000 + 8.0h, 2012-02-24-09:00-+0000 + 8.0h, 2012-02-27-09:00-+0000 + 8.0h, 2012-02-28-09:00-+0000 + 8.0h, 2012-02-29-09:00-+0000 + 8.0h, 2012-03-01-09:00-+0000 + 8.0h, 2012-03-02-09:00-+0000 + 8.0h, 2012-03-05-09:00-+0000 + 8.0h, 2012-03-06-09:00-+0000 + 8.0h, 2012-03-07-09:00-+0000 + 8.0h, 2012-03-08-09:00-+0000 + 8.0h, 2012-03-09-09:00-+0000 + 8.0h, 2012-03-12-09:00-+0000 + 8.0h, 2012-03-13-09:00-+0000 + 8.0h, 2012-03-14-09:00-+0000 + 8.0h, 2012-03-15-09:00-+0000 + 8.0h, 2012-03-16-09:00-+0000 + 8.0h, 2012-03-19-09:00-+0000 + 8.0h, 2012-03-20-09:00-+0000 + 8.0h, 2012-03-21-09:00-+0000 + 8.0h, 2012-03-22-09:00-+0000 + 8.0h, 2012-03-23-09:00-+0000 + 8.0h, 2012-03-26-09:00-+0000 + 8.0h, 2012-03-27-09:00-+0000 + 8.0h, 2012-03-28-09:00-+0000 + 8.0h, 2012-03-29-09:00-+0000 + 8.0h, 2012-03-30-09:00-+0000 + 8.0h, 2012-04-02-09:00-+0000 + 8.0h, 2012-04-03-09:00-+0000 + 8.0h, 2012-04-04-09:00-+0000 + 8.0h, 2012-04-05-09:00-+0000 + 8.0h, 2012-04-06-09:00-+0000 + 8.0h, 2012-04-09-09:00-+0000 + 8.0h, 2012-04-10-09:00-+0000 + 8.0h, 2012-04-11-09:00-+0000 + 8.0h, 2012-04-12-09:00-+0000 + 8.0h, 2012-04-13-09:00-+0000 + 8.0h, 2012-04-16-09:00-+0000 + 8.0h, 2012-04-17-09:00-+0000 + 8.0h, 2012-04-18-09:00-+0000 + 8.0h, 2012-04-19-09:00-+0000 + 8.0h, 2012-04-20-09:00-+0000 + 8.0h, 2012-04-23-09:00-+0000 + 8.0h, 2012-04-24-09:00-+0000 + 8.0h, 2012-04-25-09:00-+0000 + 8.0h, 2012-04-26-09:00-+0000 + 8.0h, 2012-04-27-09:00-+0000 + 8.0h, 2012-04-30-09:00-+0000 + 8.0h, 2012-05-01-09:00-+0000 + 8.0h, 2012-05-02-09:00-+0000 + 8.0h, 2012-05-03-09:00-+0000 + 8.0h, 2012-05-04-09:00-+0000 + 8.0h, 2012-05-07-09:00-+0000 + 8.0h, 2012-05-08-09:00-+0000 + 8.0h, 2012-05-09-09:00-+0000 + 8.0h, 2012-05-10-09:00-+0000 + 8.0h, 2012-05-11-09:00-+0000 + 8.0h, 2012-05-14-09:00-+0000 + 8.0h, 2012-05-15-09:00-+0000 + 8.0h, 2012-05-16-09:00-+0000 + 8.0h, 2012-05-17-09:00-+0000 + 8.0h, 2012-05-18-09:00-+0000 + 8.0h, 2012-05-21-09:00-+0000 + 8.0h, 2012-05-22-09:00-+0000 + 8.0h, 2012-05-23-09:00-+0000 + 8.0h, 2012-05-24-09:00-+0000 + 8.0h, 2012-05-25-09:00-+0000 + 8.0h, 2012-05-28-09:00-+0000 + 8.0h, 2012-05-29-09:00-+0000 + 8.0h, 2012-05-30-09:00-+0000 + 8.0h, 2012-05-31-09:00-+0000 + 8.0h, 2012-06-01-09:00-+0000 + 8.0h, 2012-06-04-09:00-+0000 + 8.0h, 2012-06-05-09:00-+0000 + 8.0h, 2012-06-06-09:00-+0000 + 8.0h, 2012-06-07-09:00-+0000 + 8.0h, 2012-06-08-09:00-+0000 + 8.0h, 2012-06-11-09:00-+0000 + 8.0h, 2012-06-12-09:00-+0000 + 8.0h, 2012-06-13-09:00-+0000 + 8.0h, 2012-06-14-09:00-+0000 + 8.0h, 2012-06-15-09:00-+0000 + 8.0h, 2012-06-18-09:00-+0000 + 8.0h, 2012-06-19-09:00-+0000 + 8.0h, 2012-06-20-09:00-+0000 + 8.0h, 2012-06-21-09:00-+0000 + 8.0h, 2012-06-22-09:00-+0000 + 8.0h, 2012-06-25-09:00-+0000 + 8.0h, 2012-06-26-09:00-+0000 + 8.0h, 2012-06-27-09:00-+0000 + 8.0h, 2012-06-28-09:00-+0000 + 8.0h, 2012-06-29-09:00-+0000 + 8.0h, 2012-07-02-09:00-+0000 + 8.0h, 2012-07-03-09:00-+0000 + 8.0h, 2012-07-04-09:00-+0000 + 8.0h, 2012-07-05-09:00-+0000 + 8.0h, 2012-07-06-09:00-+0000 + 8.0h, 2012-07-09-09:00-+0000 + 8.0h, 2012-07-10-09:00-+0000 + 8.0h, 2012-07-11-09:00-+0000 + 8.0h, 2012-07-12-09:00-+0000 + 8.0h, 2012-07-13-09:00-+0000 + 8.0h, 2012-07-16-09:00-+0000 + 8.0h, 2012-07-17-09:00-+0000 + 8.0h, 2012-07-18-09:00-+0000 + 8.0h, 2012-07-19-09:00-+0000 + 8.0h, 2012-07-20-09:00-+0000 + 8.0h, 2012-07-23-09:00-+0000 + 8.0h, 2012-07-24-09:00-+0000 + 8.0h, 2012-07-25-09:00-+0000 + 8.0h, 2012-07-26-09:00-+0000 + 8.0h, 2012-07-27-09:00-+0000 + 8.0h, 2012-07-30-09:00-+0000 + 8.0h, 2012-07-31-09:00-+0000 + 8.0h, 2012-08-01-09:00-+0000 + 8.0h, 2012-08-02-09:00-+0000 + 8.0h, 2012-08-03-09:00-+0000 + 8.0h, 2012-08-06-09:00-+0000 + 8.0h, 2012-08-07-09:00-+0000 + 8.0h, 2012-08-08-09:00-+0000 + 8.0h, 2012-08-09-09:00-+0000 + 8.0h, 2012-08-10-09:00-+0000 + 8.0h, 2012-08-13-09:00-+0000 + 8.0h, 2012-08-14-09:00-+0000 + 8.0h, 2012-08-15-09:00-+0000 + 8.0h, 2012-08-16-09:00-+0000 + 8.0h, 2012-08-17-09:00-+0000 + 8.0h, 2012-08-20-09:00-+0000 + 8.0h, 2012-08-21-09:00-+0000 + 8.0h, 2012-08-22-09:00-+0000 + 8.0h, 2012-08-23-09:00-+0000 + 8.0h, 2012-08-24-09:00-+0000 + 8.0h, 2012-08-27-09:00-+0000 + 8.0h, 2012-08-28-09:00-+0000 + 8.0h, 2012-08-29-09:00-+0000 + 8.0h, 2012-08-30-09:00-+0000 + 8.0h, 2012-08-31-09:00-+0000 + 8.0h, 2012-09-03-09:00-+0000 + 8.0h, 2012-09-04-09:00-+0000 + 8.0h, 2012-09-05-09:00-+0000 + 8.0h, 2012-09-06-09:00-+0000 + 8.0h, 2012-09-07-09:00-+0000 + 8.0h, 2012-09-10-09:00-+0000 + 8.0h, 2012-09-11-09:00-+0000 + 8.0h, 2012-09-12-09:00-+0000 + 8.0h, 2012-09-13-09:00-+0000 + 8.0h, 2012-09-14-09:00-+0000 + 8.0h, 2012-09-17-09:00-+0000 + 8.0h, 2012-09-18-09:00-+0000 + 8.0h, 2012-09-19-09:00-+0000 + 8.0h, 2012-09-20-09:00-+0000 + 8.0h, 2012-09-21-09:00-+0000 + 8.0h, 2012-09-24-09:00-+0000 + 8.0h, 2012-09-25-09:00-+0000 + 8.0h, 2012-09-26-09:00-+0000 + 8.0h, 2012-09-27-09:00-+0000 + 8.0h, 2012-09-28-09:00-+0000 + 8.0h, 2012-10-01-09:00-+0000 + 8.0h, 2012-10-02-09:00-+0000 + 8.0h, 2012-10-03-09:00-+0000 + 8.0h, 2012-10-04-09:00-+0000 + 8.0h, 2012-10-05-09:00-+0000 + 8.0h, 2012-10-08-09:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task _Task_1.p2 { booking _Resource_2 2011-11-09-09:00-+0000 + 8.0h, 2011-11-10-09:00-+0000 + 8.0h, 2011-11-11-09:00-+0000 + 8.0h, 2011-11-14-09:00-+0000 + 8.0h, 2011-11-15-09:00-+0000 + 8.0h, 2011-11-16-09:00-+0000 + 8.0h, 2011-11-17-09:00-+0000 + 8.0h, 2011-11-18-09:00-+0000 + 8.0h, 2011-11-21-09:00-+0000 + 8.0h, 2011-11-22-09:00-+0000 + 8.0h, 2011-11-23-09:00-+0000 + 8.0h, 2011-11-24-09:00-+0000 + 8.0h, 2011-11-25-09:00-+0000 + 8.0h, 2011-11-28-09:00-+0000 + 8.0h, 2011-11-29-09:00-+0000 + 8.0h, 2011-11-30-09:00-+0000 + 8.0h, 2011-12-01-09:00-+0000 + 8.0h, 2011-12-02-09:00-+0000 + 8.0h, 2011-12-05-09:00-+0000 + 8.0h, 2011-12-06-09:00-+0000 + 8.0h, 2011-12-07-09:00-+0000 + 8.0h, 2011-12-08-09:00-+0000 + 8.0h, 2011-12-09-09:00-+0000 + 8.0h, 2011-12-12-09:00-+0000 + 8.0h, 2011-12-13-09:00-+0000 + 8.0h, 2011-12-14-09:00-+0000 + 8.0h, 2011-12-15-09:00-+0000 + 8.0h, 2011-12-16-09:00-+0000 + 8.0h, 2011-12-19-09:00-+0000 + 8.0h, 2011-12-20-09:00-+0000 + 8.0h, 2011-12-21-09:00-+0000 + 8.0h, 2011-12-22-09:00-+0000 + 8.0h, 2011-12-23-09:00-+0000 + 8.0h, 2011-12-26-09:00-+0000 + 8.0h, 2011-12-27-09:00-+0000 + 8.0h, 2011-12-28-09:00-+0000 + 8.0h, 2011-12-29-09:00-+0000 + 8.0h, 2011-12-30-09:00-+0000 + 8.0h, 2012-01-02-09:00-+0000 + 8.0h, 2012-01-03-09:00-+0000 + 8.0h, 2012-01-04-09:00-+0000 + 8.0h, 2012-01-05-09:00-+0000 + 8.0h, 2012-01-06-09:00-+0000 + 8.0h, 2012-01-09-09:00-+0000 + 8.0h, 2012-01-10-09:00-+0000 + 8.0h, 2012-01-11-09:00-+0000 + 8.0h, 2012-01-12-09:00-+0000 + 8.0h, 2012-01-13-09:00-+0000 + 8.0h, 2012-01-16-09:00-+0000 + 8.0h, 2012-01-17-09:00-+0000 + 8.0h, 2012-01-18-09:00-+0000 + 8.0h, 2012-01-19-09:00-+0000 + 8.0h, 2012-01-20-09:00-+0000 + 8.0h, 2012-01-23-09:00-+0000 + 8.0h, 2012-01-24-09:00-+0000 + 8.0h, 2012-01-25-09:00-+0000 + 8.0h, 2012-01-26-09:00-+0000 + 8.0h, 2012-01-27-09:00-+0000 + 8.0h, 2012-01-30-09:00-+0000 + 8.0h, 2012-01-31-09:00-+0000 + 8.0h, 2012-02-01-09:00-+0000 + 8.0h, 2012-02-02-09:00-+0000 + 8.0h, 2012-02-03-09:00-+0000 + 8.0h, 2012-02-06-09:00-+0000 + 8.0h, 2012-02-07-09:00-+0000 + 8.0h, 2012-02-08-09:00-+0000 + 8.0h, 2012-02-09-09:00-+0000 + 8.0h, 2012-02-10-09:00-+0000 + 8.0h, 2012-02-13-09:00-+0000 + 8.0h, 2012-02-14-09:00-+0000 + 8.0h, 2012-02-15-09:00-+0000 + 8.0h, 2012-02-16-09:00-+0000 + 8.0h, 2012-02-17-09:00-+0000 + 8.0h, 2012-02-20-09:00-+0000 + 8.0h, 2012-02-21-09:00-+0000 + 8.0h, 2012-02-22-09:00-+0000 + 8.0h, 2012-02-23-09:00-+0000 + 8.0h, 2012-02-24-09:00-+0000 + 8.0h, 2012-02-27-09:00-+0000 + 8.0h, 2012-02-28-09:00-+0000 + 8.0h, 2012-02-29-09:00-+0000 + 8.0h, 2012-03-01-09:00-+0000 + 8.0h, 2012-03-02-09:00-+0000 + 8.0h, 2012-03-05-09:00-+0000 + 8.0h, 2012-03-06-09:00-+0000 + 8.0h, 2012-03-07-09:00-+0000 + 8.0h, 2012-03-08-09:00-+0000 + 8.0h, 2012-03-09-09:00-+0000 + 8.0h, 2012-03-12-09:00-+0000 + 8.0h, 2012-03-13-09:00-+0000 + 8.0h, 2012-03-14-09:00-+0000 + 8.0h, 2012-03-15-09:00-+0000 + 8.0h, 2012-03-16-09:00-+0000 + 8.0h, 2012-03-19-09:00-+0000 + 8.0h, 2012-03-20-09:00-+0000 + 8.0h, 2012-03-21-09:00-+0000 + 8.0h, 2012-03-22-09:00-+0000 + 8.0h, 2012-03-23-09:00-+0000 + 8.0h, 2012-03-26-09:00-+0000 + 8.0h, 2012-03-27-09:00-+0000 + 8.0h, 2012-03-28-09:00-+0000 + 8.0h, 2012-03-29-09:00-+0000 + 8.0h, 2012-03-30-09:00-+0000 + 8.0h, 2012-04-02-09:00-+0000 + 8.0h, 2012-04-03-09:00-+0000 + 8.0h, 2012-04-04-09:00-+0000 + 8.0h, 2012-04-05-09:00-+0000 + 8.0h, 2012-04-06-09:00-+0000 + 8.0h, 2012-04-09-09:00-+0000 + 8.0h, 2012-04-10-09:00-+0000 + 8.0h, 2012-04-11-09:00-+0000 + 8.0h, 2012-04-12-09:00-+0000 + 8.0h, 2012-04-13-09:00-+0000 + 8.0h, 2012-04-16-09:00-+0000 + 8.0h, 2012-04-17-09:00-+0000 + 8.0h, 2012-04-18-09:00-+0000 + 8.0h, 2012-04-19-09:00-+0000 + 8.0h, 2012-04-20-09:00-+0000 + 8.0h, 2012-04-23-09:00-+0000 + 8.0h, 2012-04-24-09:00-+0000 + 8.0h, 2012-04-25-09:00-+0000 + 8.0h, 2012-04-26-09:00-+0000 + 8.0h, 2012-04-27-09:00-+0000 + 8.0h, 2012-04-30-09:00-+0000 + 8.0h, 2012-05-01-09:00-+0000 + 8.0h, 2012-05-02-09:00-+0000 + 8.0h, 2012-05-03-09:00-+0000 + 8.0h, 2012-05-04-09:00-+0000 + 8.0h, 2012-05-07-09:00-+0000 + 8.0h, 2012-05-08-09:00-+0000 + 8.0h, 2012-05-09-09:00-+0000 + 8.0h, 2012-05-10-09:00-+0000 + 8.0h, 2012-05-11-09:00-+0000 + 8.0h, 2012-05-14-09:00-+0000 + 8.0h, 2012-05-15-09:00-+0000 + 8.0h, 2012-05-16-09:00-+0000 + 8.0h, 2012-05-17-09:00-+0000 + 8.0h, 2012-05-18-09:00-+0000 + 8.0h, 2012-05-21-09:00-+0000 + 8.0h, 2012-05-22-09:00-+0000 + 8.0h, 2012-05-23-09:00-+0000 + 8.0h, 2012-05-24-09:00-+0000 + 8.0h, 2012-05-25-09:00-+0000 + 8.0h, 2012-05-28-09:00-+0000 + 8.0h, 2012-05-29-09:00-+0000 + 8.0h, 2012-05-30-09:00-+0000 + 8.0h, 2012-05-31-09:00-+0000 + 8.0h, 2012-06-01-09:00-+0000 + 8.0h, 2012-06-04-09:00-+0000 + 8.0h, 2012-06-05-09:00-+0000 + 8.0h, 2012-06-06-09:00-+0000 + 8.0h, 2012-06-07-09:00-+0000 + 8.0h, 2012-06-08-09:00-+0000 + 8.0h, 2012-06-11-09:00-+0000 + 8.0h, 2012-06-12-09:00-+0000 + 8.0h, 2012-06-13-09:00-+0000 + 8.0h, 2012-06-14-09:00-+0000 + 8.0h, 2012-06-15-09:00-+0000 + 8.0h, 2012-06-18-09:00-+0000 + 8.0h, 2012-06-19-09:00-+0000 + 8.0h, 2012-06-20-09:00-+0000 + 8.0h, 2012-06-21-09:00-+0000 + 8.0h, 2012-06-22-09:00-+0000 + 8.0h, 2012-06-25-09:00-+0000 + 8.0h, 2012-06-26-09:00-+0000 + 8.0h, 2012-06-27-09:00-+0000 + 8.0h, 2012-06-28-09:00-+0000 + 8.0h, 2012-06-29-09:00-+0000 + 8.0h, 2012-07-02-09:00-+0000 + 8.0h, 2012-07-03-09:00-+0000 + 8.0h, 2012-07-04-09:00-+0000 + 8.0h, 2012-07-05-09:00-+0000 + 8.0h, 2012-07-06-09:00-+0000 + 8.0h, 2012-07-09-09:00-+0000 + 8.0h, 2012-07-10-09:00-+0000 + 8.0h, 2012-07-11-09:00-+0000 + 8.0h, 2012-07-12-09:00-+0000 + 8.0h, 2012-07-13-09:00-+0000 + 8.0h, 2012-07-16-09:00-+0000 + 8.0h, 2012-07-17-09:00-+0000 + 8.0h, 2012-07-18-09:00-+0000 + 8.0h, 2012-07-19-09:00-+0000 + 8.0h, 2012-07-20-09:00-+0000 + 8.0h, 2012-07-23-09:00-+0000 + 8.0h, 2012-07-24-09:00-+0000 + 8.0h, 2012-07-25-09:00-+0000 + 8.0h, 2012-07-26-09:00-+0000 + 8.0h, 2012-07-27-09:00-+0000 + 8.0h, 2012-07-30-09:00-+0000 + 8.0h, 2012-07-31-09:00-+0000 + 8.0h, 2012-08-01-09:00-+0000 + 8.0h, 2012-08-02-09:00-+0000 + 8.0h, 2012-08-03-09:00-+0000 + 8.0h, 2012-08-06-09:00-+0000 + 8.0h, 2012-08-07-09:00-+0000 + 8.0h, 2012-08-08-09:00-+0000 + 5.0h { overtime 2 } booking _Resource_3 2011-11-09-09:00-+0000 + 8.0h, 2011-11-10-09:00-+0000 + 8.0h, 2011-11-11-09:00-+0000 + 8.0h, 2011-11-14-09:00-+0000 + 8.0h, 2011-11-15-09:00-+0000 + 8.0h, 2011-11-16-09:00-+0000 + 8.0h, 2011-11-17-09:00-+0000 + 8.0h, 2011-11-18-09:00-+0000 + 8.0h, 2011-11-21-09:00-+0000 + 8.0h, 2011-11-22-09:00-+0000 + 8.0h, 2011-11-23-09:00-+0000 + 8.0h, 2011-11-24-09:00-+0000 + 8.0h, 2011-11-25-09:00-+0000 + 8.0h, 2011-11-28-09:00-+0000 + 8.0h, 2011-11-29-09:00-+0000 + 8.0h, 2011-11-30-09:00-+0000 + 8.0h, 2011-12-01-09:00-+0000 + 8.0h, 2011-12-02-09:00-+0000 + 8.0h, 2011-12-05-09:00-+0000 + 8.0h, 2011-12-06-09:00-+0000 + 8.0h, 2011-12-07-09:00-+0000 + 8.0h, 2011-12-08-09:00-+0000 + 8.0h, 2011-12-09-09:00-+0000 + 8.0h, 2011-12-12-09:00-+0000 + 8.0h, 2011-12-13-09:00-+0000 + 8.0h, 2011-12-14-09:00-+0000 + 8.0h, 2011-12-15-09:00-+0000 + 8.0h, 2011-12-16-09:00-+0000 + 8.0h, 2011-12-19-09:00-+0000 + 8.0h, 2011-12-20-09:00-+0000 + 8.0h, 2011-12-21-09:00-+0000 + 8.0h, 2011-12-22-09:00-+0000 + 8.0h, 2011-12-23-09:00-+0000 + 8.0h, 2011-12-26-09:00-+0000 + 8.0h, 2011-12-27-09:00-+0000 + 8.0h, 2011-12-28-09:00-+0000 + 8.0h, 2011-12-29-09:00-+0000 + 8.0h, 2011-12-30-09:00-+0000 + 8.0h, 2012-01-02-09:00-+0000 + 8.0h, 2012-01-03-09:00-+0000 + 8.0h, 2012-01-04-09:00-+0000 + 8.0h, 2012-01-05-09:00-+0000 + 8.0h, 2012-01-06-09:00-+0000 + 8.0h, 2012-01-09-09:00-+0000 + 8.0h, 2012-01-10-09:00-+0000 + 8.0h, 2012-01-11-09:00-+0000 + 8.0h, 2012-01-12-09:00-+0000 + 8.0h, 2012-01-13-09:00-+0000 + 8.0h, 2012-01-16-09:00-+0000 + 8.0h, 2012-01-17-09:00-+0000 + 8.0h, 2012-01-18-09:00-+0000 + 8.0h, 2012-01-19-09:00-+0000 + 8.0h, 2012-01-20-09:00-+0000 + 8.0h, 2012-01-23-09:00-+0000 + 8.0h, 2012-01-24-09:00-+0000 + 8.0h, 2012-01-25-09:00-+0000 + 8.0h, 2012-01-26-09:00-+0000 + 8.0h, 2012-01-27-09:00-+0000 + 8.0h, 2012-01-30-09:00-+0000 + 8.0h, 2012-01-31-09:00-+0000 + 8.0h, 2012-02-01-09:00-+0000 + 8.0h, 2012-02-02-09:00-+0000 + 8.0h, 2012-02-03-09:00-+0000 + 8.0h, 2012-02-06-09:00-+0000 + 8.0h, 2012-02-07-09:00-+0000 + 8.0h, 2012-02-08-09:00-+0000 + 8.0h, 2012-02-09-09:00-+0000 + 8.0h, 2012-02-10-09:00-+0000 + 8.0h, 2012-02-13-09:00-+0000 + 8.0h, 2012-02-14-09:00-+0000 + 8.0h, 2012-02-15-09:00-+0000 + 8.0h, 2012-02-16-09:00-+0000 + 8.0h, 2012-02-17-09:00-+0000 + 8.0h, 2012-02-20-09:00-+0000 + 8.0h, 2012-02-21-09:00-+0000 + 8.0h, 2012-02-22-09:00-+0000 + 8.0h, 2012-02-23-09:00-+0000 + 8.0h, 2012-02-24-09:00-+0000 + 8.0h, 2012-02-27-09:00-+0000 + 8.0h, 2012-02-28-09:00-+0000 + 8.0h, 2012-02-29-09:00-+0000 + 8.0h, 2012-03-01-09:00-+0000 + 8.0h, 2012-03-02-09:00-+0000 + 8.0h, 2012-03-05-09:00-+0000 + 8.0h, 2012-03-06-09:00-+0000 + 8.0h, 2012-03-07-09:00-+0000 + 8.0h, 2012-03-08-09:00-+0000 + 8.0h, 2012-03-09-09:00-+0000 + 8.0h, 2012-03-12-09:00-+0000 + 8.0h, 2012-03-13-09:00-+0000 + 8.0h, 2012-03-14-09:00-+0000 + 8.0h, 2012-03-15-09:00-+0000 + 8.0h, 2012-03-16-09:00-+0000 + 8.0h, 2012-03-19-09:00-+0000 + 8.0h, 2012-03-20-09:00-+0000 + 8.0h, 2012-03-21-09:00-+0000 + 8.0h, 2012-03-22-09:00-+0000 + 8.0h, 2012-03-23-09:00-+0000 + 8.0h, 2012-03-26-09:00-+0000 + 8.0h, 2012-03-27-09:00-+0000 + 8.0h, 2012-03-28-09:00-+0000 + 8.0h, 2012-03-29-09:00-+0000 + 8.0h, 2012-03-30-09:00-+0000 + 8.0h, 2012-04-02-09:00-+0000 + 8.0h, 2012-04-03-09:00-+0000 + 8.0h, 2012-04-04-09:00-+0000 + 8.0h, 2012-04-05-09:00-+0000 + 8.0h, 2012-04-06-09:00-+0000 + 8.0h, 2012-04-09-09:00-+0000 + 8.0h, 2012-04-10-09:00-+0000 + 8.0h, 2012-04-11-09:00-+0000 + 8.0h, 2012-04-12-09:00-+0000 + 8.0h, 2012-04-13-09:00-+0000 + 8.0h, 2012-04-16-09:00-+0000 + 8.0h, 2012-04-17-09:00-+0000 + 8.0h, 2012-04-18-09:00-+0000 + 8.0h, 2012-04-19-09:00-+0000 + 8.0h, 2012-04-20-09:00-+0000 + 8.0h, 2012-04-23-09:00-+0000 + 8.0h, 2012-04-24-09:00-+0000 + 8.0h, 2012-04-25-09:00-+0000 + 8.0h, 2012-04-26-09:00-+0000 + 8.0h, 2012-04-27-09:00-+0000 + 8.0h, 2012-04-30-09:00-+0000 + 8.0h, 2012-05-01-09:00-+0000 + 8.0h, 2012-05-02-09:00-+0000 + 8.0h, 2012-05-03-09:00-+0000 + 8.0h, 2012-05-04-09:00-+0000 + 8.0h, 2012-05-07-09:00-+0000 + 8.0h, 2012-05-08-09:00-+0000 + 8.0h, 2012-05-09-09:00-+0000 + 8.0h, 2012-05-10-09:00-+0000 + 8.0h, 2012-05-11-09:00-+0000 + 8.0h, 2012-05-14-09:00-+0000 + 8.0h, 2012-05-15-09:00-+0000 + 8.0h, 2012-05-16-09:00-+0000 + 8.0h, 2012-05-17-09:00-+0000 + 8.0h, 2012-05-18-09:00-+0000 + 8.0h, 2012-05-21-09:00-+0000 + 8.0h, 2012-05-22-09:00-+0000 + 8.0h, 2012-05-23-09:00-+0000 + 8.0h, 2012-05-24-09:00-+0000 + 8.0h, 2012-05-25-09:00-+0000 + 8.0h, 2012-05-28-09:00-+0000 + 8.0h, 2012-05-29-09:00-+0000 + 8.0h, 2012-05-30-09:00-+0000 + 8.0h, 2012-05-31-09:00-+0000 + 8.0h, 2012-06-01-09:00-+0000 + 8.0h, 2012-06-04-09:00-+0000 + 8.0h, 2012-06-05-09:00-+0000 + 8.0h, 2012-06-06-09:00-+0000 + 8.0h, 2012-06-07-09:00-+0000 + 8.0h, 2012-06-08-09:00-+0000 + 8.0h, 2012-06-11-09:00-+0000 + 8.0h, 2012-06-12-09:00-+0000 + 8.0h, 2012-06-13-09:00-+0000 + 8.0h, 2012-06-14-09:00-+0000 + 8.0h, 2012-06-15-09:00-+0000 + 8.0h, 2012-06-18-09:00-+0000 + 8.0h, 2012-06-19-09:00-+0000 + 8.0h, 2012-06-20-09:00-+0000 + 8.0h, 2012-06-21-09:00-+0000 + 8.0h, 2012-06-22-09:00-+0000 + 8.0h, 2012-06-25-09:00-+0000 + 8.0h, 2012-06-26-09:00-+0000 + 8.0h, 2012-06-27-09:00-+0000 + 8.0h, 2012-06-28-09:00-+0000 + 8.0h, 2012-06-29-09:00-+0000 + 8.0h, 2012-07-02-09:00-+0000 + 8.0h, 2012-07-03-09:00-+0000 + 8.0h, 2012-07-04-09:00-+0000 + 8.0h, 2012-07-05-09:00-+0000 + 8.0h, 2012-07-06-09:00-+0000 + 8.0h, 2012-07-09-09:00-+0000 + 8.0h, 2012-07-10-09:00-+0000 + 8.0h, 2012-07-11-09:00-+0000 + 8.0h, 2012-07-12-09:00-+0000 + 8.0h, 2012-07-13-09:00-+0000 + 8.0h, 2012-07-16-09:00-+0000 + 8.0h, 2012-07-17-09:00-+0000 + 8.0h, 2012-07-18-09:00-+0000 + 8.0h, 2012-07-19-09:00-+0000 + 8.0h, 2012-07-20-09:00-+0000 + 8.0h, 2012-07-23-09:00-+0000 + 8.0h, 2012-07-24-09:00-+0000 + 8.0h, 2012-07-25-09:00-+0000 + 8.0h, 2012-07-26-09:00-+0000 + 8.0h, 2012-07-27-09:00-+0000 + 8.0h, 2012-07-30-09:00-+0000 + 8.0h, 2012-07-31-09:00-+0000 + 8.0h, 2012-08-01-09:00-+0000 + 8.0h, 2012-08-02-09:00-+0000 + 8.0h, 2012-08-03-09:00-+0000 + 8.0h, 2012-08-06-09:00-+0000 + 8.0h, 2012-08-07-09:00-+0000 + 8.0h, 2012-08-08-09:00-+0000 + 4.0h { overtime 2 } priority 600 projectid prj } supplement task _Task_1.p3 { booking _Resource_5 2011-11-09-09:00-+0000 + 8.0h, 2011-11-10-09:00-+0000 + 8.0h, 2011-11-11-09:00-+0000 + 8.0h, 2011-11-14-09:00-+0000 + 8.0h, 2011-11-15-09:00-+0000 + 8.0h, 2011-11-16-09:00-+0000 + 8.0h, 2011-11-17-09:00-+0000 + 8.0h, 2011-11-18-09:00-+0000 + 8.0h, 2011-11-21-09:00-+0000 + 8.0h, 2011-11-22-09:00-+0000 + 8.0h, 2011-11-23-09:00-+0000 + 8.0h, 2011-11-24-09:00-+0000 + 8.0h, 2011-11-25-09:00-+0000 + 8.0h, 2011-11-28-09:00-+0000 + 8.0h, 2011-11-29-09:00-+0000 + 8.0h, 2011-11-30-09:00-+0000 + 8.0h, 2011-12-01-09:00-+0000 + 8.0h, 2011-12-02-09:00-+0000 + 8.0h, 2011-12-05-09:00-+0000 + 8.0h, 2011-12-06-09:00-+0000 + 8.0h, 2011-12-07-09:00-+0000 + 8.0h, 2011-12-08-09:00-+0000 + 8.0h, 2011-12-09-09:00-+0000 + 8.0h, 2011-12-12-09:00-+0000 + 8.0h, 2011-12-13-09:00-+0000 + 8.0h, 2011-12-14-09:00-+0000 + 8.0h, 2011-12-15-09:00-+0000 + 8.0h, 2011-12-16-09:00-+0000 + 8.0h, 2011-12-19-09:00-+0000 + 8.0h, 2011-12-20-09:00-+0000 + 8.0h, 2011-12-21-09:00-+0000 + 8.0h, 2011-12-22-09:00-+0000 + 8.0h, 2011-12-23-09:00-+0000 + 8.0h, 2011-12-26-09:00-+0000 + 8.0h, 2011-12-27-09:00-+0000 + 8.0h, 2011-12-28-09:00-+0000 + 8.0h, 2011-12-29-09:00-+0000 + 8.0h, 2011-12-30-09:00-+0000 + 8.0h, 2012-01-02-09:00-+0000 + 8.0h, 2012-01-03-09:00-+0000 + 8.0h, 2012-01-04-09:00-+0000 + 8.0h, 2012-01-05-09:00-+0000 + 8.0h, 2012-01-06-09:00-+0000 + 8.0h, 2012-01-09-09:00-+0000 + 8.0h, 2012-01-10-09:00-+0000 + 8.0h, 2012-01-11-09:00-+0000 + 8.0h, 2012-01-12-09:00-+0000 + 8.0h, 2012-01-13-09:00-+0000 + 8.0h, 2012-01-16-09:00-+0000 + 8.0h, 2012-01-17-09:00-+0000 + 8.0h, 2012-01-18-09:00-+0000 + 8.0h, 2012-01-19-09:00-+0000 + 8.0h, 2012-01-20-09:00-+0000 + 8.0h, 2012-01-23-09:00-+0000 + 8.0h, 2012-01-24-09:00-+0000 + 8.0h, 2012-01-25-09:00-+0000 + 8.0h, 2012-01-26-09:00-+0000 + 8.0h, 2012-01-27-09:00-+0000 + 8.0h, 2012-01-30-09:00-+0000 + 8.0h, 2012-01-31-09:00-+0000 + 8.0h, 2012-02-01-09:00-+0000 + 8.0h, 2012-02-02-09:00-+0000 + 8.0h, 2012-02-03-09:00-+0000 + 8.0h, 2012-02-06-09:00-+0000 + 8.0h, 2012-02-07-09:00-+0000 + 8.0h, 2012-02-08-09:00-+0000 + 2.0h { overtime 2 } booking _Resource_6 2011-11-09-09:00-+0000 + 8.0h, 2011-11-10-09:00-+0000 + 8.0h, 2011-11-11-09:00-+0000 + 8.0h, 2011-11-14-09:00-+0000 + 8.0h, 2011-11-15-09:00-+0000 + 8.0h, 2011-11-16-09:00-+0000 + 8.0h, 2011-11-17-09:00-+0000 + 8.0h, 2011-11-18-09:00-+0000 + 8.0h, 2011-11-21-09:00-+0000 + 8.0h, 2011-11-22-09:00-+0000 + 8.0h, 2011-11-23-09:00-+0000 + 8.0h, 2011-11-24-09:00-+0000 + 8.0h, 2011-11-25-09:00-+0000 + 8.0h, 2011-11-28-09:00-+0000 + 8.0h, 2011-11-29-09:00-+0000 + 8.0h, 2011-11-30-09:00-+0000 + 8.0h, 2011-12-01-09:00-+0000 + 8.0h, 2011-12-02-09:00-+0000 + 8.0h, 2011-12-05-09:00-+0000 + 8.0h, 2011-12-06-09:00-+0000 + 8.0h, 2011-12-07-09:00-+0000 + 8.0h, 2011-12-08-09:00-+0000 + 8.0h, 2011-12-09-09:00-+0000 + 8.0h, 2011-12-12-09:00-+0000 + 8.0h, 2011-12-13-09:00-+0000 + 8.0h, 2011-12-14-09:00-+0000 + 8.0h, 2011-12-15-09:00-+0000 + 8.0h, 2011-12-16-09:00-+0000 + 8.0h, 2011-12-19-09:00-+0000 + 8.0h, 2011-12-20-09:00-+0000 + 8.0h, 2011-12-21-09:00-+0000 + 8.0h, 2011-12-22-09:00-+0000 + 8.0h, 2011-12-23-09:00-+0000 + 8.0h, 2011-12-26-09:00-+0000 + 8.0h, 2011-12-27-09:00-+0000 + 8.0h, 2011-12-28-09:00-+0000 + 8.0h, 2011-12-29-09:00-+0000 + 8.0h, 2011-12-30-09:00-+0000 + 8.0h, 2012-01-02-09:00-+0000 + 8.0h, 2012-01-03-09:00-+0000 + 8.0h, 2012-01-04-09:00-+0000 + 8.0h, 2012-01-05-09:00-+0000 + 8.0h, 2012-01-06-09:00-+0000 + 8.0h, 2012-01-09-09:00-+0000 + 8.0h, 2012-01-10-09:00-+0000 + 8.0h, 2012-01-11-09:00-+0000 + 8.0h, 2012-01-12-09:00-+0000 + 8.0h, 2012-01-13-09:00-+0000 + 8.0h, 2012-01-16-09:00-+0000 + 8.0h, 2012-01-17-09:00-+0000 + 8.0h, 2012-01-18-09:00-+0000 + 8.0h, 2012-01-19-09:00-+0000 + 8.0h, 2012-01-20-09:00-+0000 + 8.0h, 2012-01-23-09:00-+0000 + 8.0h, 2012-01-24-09:00-+0000 + 8.0h, 2012-01-25-09:00-+0000 + 8.0h, 2012-01-26-09:00-+0000 + 8.0h, 2012-01-27-09:00-+0000 + 8.0h, 2012-01-30-09:00-+0000 + 8.0h, 2012-01-31-09:00-+0000 + 8.0h, 2012-02-01-09:00-+0000 + 8.0h, 2012-02-02-09:00-+0000 + 8.0h, 2012-02-03-09:00-+0000 + 8.0h, 2012-02-06-09:00-+0000 + 8.0h, 2012-02-07-09:00-+0000 + 8.0h, 2012-02-08-09:00-+0000 + 1.0h { overtime 2 } priority 600 projectid prj } supplement task _Task_1.mf { priority 500 projectid prj } supplement task _Task_1._Task_6 { priority 500 projectid prj } supplement resource teamA { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource teamB { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_5 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_6 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/String.tjp0000644000175000017500000000147412614413013024123 0ustar bernatbernatproject prj "String Tests" "1.0" 2005-06-06-00:00-+0000 - 2005-06-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux "Tux \"The Penguing\" Tuxus" task items "Project Plan\\\\Breakdown" { start 2005-06-06-15:00-+0000 end 2005-06-07-23:00-+0000 scheduling asap scheduled } supplement task items { booking tux 2005-06-06-15:00-+0000 + 8.0h, 2005-06-07-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/MinMax.tjp0000644000175000017500000000121112614413013024033 0ustar bernatbernatproject prj "Min Max Example" "1.0" 2005-06-06-00:00-+0000 - 2005-06-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task items "Project breakdown" { task plan "Plan work" { start 2005-06-07-06:00-+0000 end 2005-06-09-23:00-+0000 scheduling asap scheduled } } supplement task items { priority 500 projectid prj } supplement task items.plan { maxend 2005-06-11-06:00-+0000 maxstart 2005-06-08-06:00-+0000 minend 2005-06-09-06:00-+0000 minstart 2005-06-06-06:00-+0000 note "Some more information about this task." priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Macro-3.tjp0000644000175000017500000000050312614413013024046 0ustar bernatbernatproject foo600 "Project" "6" 2009-12-01-00:00-+0000 - 2012-09-30-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids foo600 task _Task_1 "foo" { start 2009-12-01-00:00-+0000 milestone scheduled } supplement task _Task_1 { priority 500 projectid foo600 } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Simple.tjp0000644000175000017500000000274112614413013024104 0ustar bernatbernatproject prj "Simple Project" "1.0" 2005-06-06-00:00-+0000 - 2005-06-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux "Tux" task items "Project breakdown" { task plan "Plan work" { start 2005-06-06-06:00-+0000 end 2005-06-08-23:00-+0000 scheduling asap scheduled } task implementation "Implement work" { depends items.plan start 2005-06-09-15:00-+0000 end 2005-06-15-23:00-+0000 scheduling asap scheduled } task acceptance "Customer acceptance" { depends items.implementation start 2005-06-15-23:00-+0000 end 2005-06-20-23:00-+0000 scheduling asap scheduled } } supplement task items { priority 500 projectid prj } supplement task items.plan { priority 500 projectid prj } supplement task items.implementation { booking tux 2005-06-09-15:00-+0000 + 8.0h, 2005-06-10-15:00-+0000 + 8.0h, 2005-06-13-15:00-+0000 + 8.0h, 2005-06-14-15:00-+0000 + 8.0h, 2005-06-15-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task items.acceptance { priority 500 projectid prj } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Macro-1.tjp0000644000175000017500000000374312614413013024055 0ustar bernatbernatproject prj "Example Project" "1.0" 2008-01-18-00:00-+0000 - 2008-03-18-20:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux1 "Tux1" resource tux2 "Tux2" task t1 "Task1" { start 2008-01-18-00:00-+0000 milestone scheduled } task t2 "Task2" { depends t1 start 2008-01-18-16:00-+0000 end 2008-02-01-00:00-+0000 scheduling asap scheduled } supplement task t1 { priority 500 projectid prj } supplement task t2 { booking tux1 2008-01-18-16:00-+0000 + 8.0h, 2008-01-21-16:00-+0000 + 8.0h, 2008-01-22-16:00-+0000 + 8.0h, 2008-01-23-16:00-+0000 + 8.0h, 2008-01-24-16:00-+0000 + 8.0h, 2008-01-25-16:00-+0000 + 8.0h, 2008-01-28-16:00-+0000 + 8.0h, 2008-01-29-16:00-+0000 + 8.0h, 2008-01-30-16:00-+0000 + 8.0h, 2008-01-31-16:00-+0000 + 8.0h { overtime 2 } booking tux2 2008-01-18-16:00-+0000 + 8.0h, 2008-01-21-16:00-+0000 + 8.0h, 2008-01-22-16:00-+0000 + 8.0h, 2008-01-23-16:00-+0000 + 8.0h, 2008-01-24-16:00-+0000 + 8.0h, 2008-01-25-16:00-+0000 + 8.0h, 2008-01-28-16:00-+0000 + 8.0h, 2008-01-29-16:00-+0000 + 8.0h, 2008-01-30-16:00-+0000 + 8.0h, 2008-01-31-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource tux1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Select.tjp0000644000175000017500000000374712614413013024101 0ustar bernatbernatproject prj "Project" "1.0" 2000-01-01-00:00-+0000 - 2000-03-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tuxus "Tuxus" resource tuxia "Tuxia" task t1 "Task 1" { start 2000-01-03-16:00-+0000 end 2000-01-08-00:00-+0000 scheduling asap scheduled } task t2 "Task 2" { start 2000-01-03-16:00-+0000 end 2000-01-08-00:00-+0000 scheduling asap scheduled } task t3 "Task 3" { start 2000-01-10-16:00-+0000 end 2000-01-15-00:00-+0000 scheduling asap scheduled } supplement task t1 { booking tuxus 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task t2 { booking tuxia 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task t3 { booking tuxus 2000-01-10-16:00-+0000 + 8.0h, 2000-01-11-16:00-+0000 + 8.0h, 2000-01-12-16:00-+0000 + 8.0h, 2000-01-13-16:00-+0000 + 8.0h, 2000-01-14-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource tuxus { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tuxia { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Include.tjp0000644000175000017500000000325212614413013024234 0ustar bernatbernatproject prj "Include Test" "1.0" 2010-02-26-00:00-+0000 - 2010-03-05-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource _Resource_1 "RF1" resource _Resource_2 "RF2" resource _Resource_3 "R" resource _Resource_4 "R2" task _Task_1 "Foo" { start 2010-02-26-00:00-+0000 milestone scheduled } supplement task _Task_1 { priority 500 projectid prj } supplement resource _Resource_1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_4 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/template.tjp0000644000175000017500000000664012614413013024470 0ustar bernatbernatproject your_project_id "Your Project Title" "1.0" 2011-11-11-05:00-+0000 - 2012-03-11-21:00-+0000 { timezone "America/New_York" scenario plan "Plan Scenario" { active yes } } projectids your_project_id resource r1 "Resource 1" resource t1 "Team 1" { resource r2 "Resource 2" resource r3 "Resource 3" } resource s1 "System 1" task project "Project" { task wp1 "Workpackage 1" { task t1 "Task 1" { start 2011-11-11-05:00-+0000 milestone scheduled } task t2 "Task 2" { start 2011-11-11-05:00-+0000 milestone scheduled } } task wp2 "Work package 2" { depends project.wp1 task t1 "Task 1" { start 2011-11-11-05:00-+0000 milestone scheduled } task t2 "Task 2" { start 2011-11-11-05:00-+0000 milestone scheduled } } task deliveries "Deliveries" { task _Task_9 "Item 1" { depends project.wp1 start 2011-11-11-05:00-+0000 milestone scheduled } task _Task_10 "Item 2" { depends project.wp2 start 2011-11-11-05:00-+0000 milestone scheduled } } } supplement task project { priority 500 projectid your_project_id } supplement task project.wp1 { priority 500 projectid your_project_id } supplement task project.wp1.t1 { priority 500 projectid your_project_id } supplement task project.wp1.t2 { priority 500 projectid your_project_id } supplement task project.wp2 { priority 500 projectid your_project_id } supplement task project.wp2.t1 { priority 500 projectid your_project_id } supplement task project.wp2.t2 { priority 500 projectid your_project_id } supplement task project.deliveries { priority 500 projectid your_project_id } supplement task project.deliveries._Task_9 { priority 500 projectid your_project_id } supplement task project.deliveries._Task_10 { priority 500 projectid your_project_id } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource t1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource s1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Scenario.tjp0000644000175000017500000000074612614413013024421 0ustar bernatbernatproject prj "Example" "1.0" 2007-05-29-00:00-+0000 - 2007-07-01-00:00-+0000 { timezone "America/Denver" scenario plan "Planned Scenario" { scenario actual "Actual Scenario" { active yes } scenario test "Test Scenario" { active no } active yes } } projectids prj task t "Task" { start 2007-05-29-06:00-+0000 milestone scheduled actual:start 2007-06-03-06:00-+0000 actual:milestone } supplement task t { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/LogicalFunction.tjp0000644000175000017500000000254212614413013025732 0ustar bernatbernatproject prj "Logical Function Demo" "1.0" 2009-11-21-00:00-+0000 - 2009-12-05-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource _Resource_1 "Team" { resource joe "Joe" } task _Task_1 "Parent" { task _Task_2 "Sub" { start 2009-11-23-16:00-+0000 end 2009-11-28-00:00-+0000 scheduling asap scheduled } } supplement task _Task_1 { priority 500 projectid prj } supplement task _Task_1._Task_2 { booking joe 2009-11-23-16:00-+0000 + 8.0h, 2009-11-24-16:00-+0000 + 8.0h, 2009-11-25-16:00-+0000 + 8.0h, 2009-11-26-16:00-+0000 + 8.0h, 2009-11-27-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource _Resource_1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource joe { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Period.tjp0000644000175000017500000000072412614413013024074 0ustar bernatbernatproject prj "Period Project" "1.0" 2006-09-24-00:00-+0000 - 2006-12-24-06:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task items "Project breakdown" { task plan "Plan work" { start 2006-10-01-06:00-+0000 end 2006-10-15-06:00-+0000 scheduling asap scheduled } } supplement task items { priority 500 projectid prj } supplement task items.plan { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/HtmlTaskReport.tjp0000644000175000017500000000274112614413013025576 0ustar bernatbernatproject prj "Simple Project" "1.0" 2005-06-06-00:00-+0000 - 2005-06-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux "Tux" task items "Project breakdown" { task plan "Plan work" { start 2005-06-06-06:00-+0000 end 2005-06-08-23:00-+0000 scheduling asap scheduled } task implementation "Implement work" { depends items.plan start 2005-06-09-15:00-+0000 end 2005-06-15-23:00-+0000 scheduling asap scheduled } task acceptance "Customer acceptance" { depends items.implementation start 2005-06-15-23:00-+0000 end 2005-06-20-23:00-+0000 scheduling asap scheduled } } supplement task items { priority 500 projectid prj } supplement task items.plan { priority 500 projectid prj } supplement task items.implementation { booking tux 2005-06-09-15:00-+0000 + 8.0h, 2005-06-10-15:00-+0000 + 8.0h, 2005-06-13-15:00-+0000 + 8.0h, 2005-06-14-15:00-+0000 + 8.0h, 2005-06-15-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task items.acceptance { priority 500 projectid prj } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Milestone.tjp0000644000175000017500000000076312614413013024614 0ustar bernatbernatproject prj "Milestone demo" "1.0" 2005-07-15-00:00-+0000 - 2005-08-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task project_start "Project Start" { start 2005-07-15-06:00-+0000 milestone scheduled } task deadline "Important Deadline" { start 2005-07-20-06:00-+0000 milestone scheduled } supplement task project_start { priority 500 projectid prj } supplement task deadline { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Durations.tjp0000644000175000017500000000340612614413013024622 0ustar bernatbernatproject prj "Duration Example" "1.0" 2007-06-06-00:00-+0000 - 2007-06-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux "Tux" task t "Enclosing" { task durationTask "Duration Task" { start 2007-06-06-06:00-+0000 end 2007-06-16-06:00-+0000 scheduling asap scheduled } task intervalTask "Interval Task" { start 2007-06-17-06:00-+0000 milestone scheduled } task lengthTask "Length Task" { start 2007-06-06-06:00-+0000 end 2007-06-19-23:00-+0000 scheduling asap scheduled } task effortTask "Effort Task" { start 2007-06-06-15:00-+0000 end 2007-06-19-23:00-+0000 scheduling asap scheduled } } supplement task t { priority 500 projectid prj } supplement task t.durationTask { priority 500 projectid prj } supplement task t.intervalTask { priority 500 projectid prj } supplement task t.lengthTask { priority 500 projectid prj } supplement task t.effortTask { booking tux 2007-06-06-15:00-+0000 + 8.0h, 2007-06-07-15:00-+0000 + 8.0h, 2007-06-08-15:00-+0000 + 8.0h, 2007-06-11-15:00-+0000 + 8.0h, 2007-06-12-15:00-+0000 + 8.0h, 2007-06-13-15:00-+0000 + 8.0h, 2007-06-14-15:00-+0000 + 8.0h, 2007-06-15-15:00-+0000 + 8.0h, 2007-06-18-15:00-+0000 + 8.0h, 2007-06-19-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/StatusSheet.tjp0000644000175000017500000000426512614413013025132 0ustar bernatbernatproject prj "test" "1.0" 2009-11-30-00:00-+0000 - 2010-01-29-20:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource r1 "R1" resource r2 "R2" resource r3 "R3" task t1 "Task 1" { start 2009-11-30-16:00-+0000 end 2009-12-05-00:00-+0000 scheduling asap scheduled } task t2 "Task 2" { task t3 "Task 3" { start 2009-11-30-16:00-+0000 end 2009-12-12-00:00-+0000 scheduling asap scheduled } } supplement task t1 { booking r1 2009-11-30-16:00-+0000 + 8.0h, 2009-12-01-16:00-+0000 + 8.0h, 2009-12-02-16:00-+0000 + 8.0h, 2009-12-03-16:00-+0000 + 8.0h, 2009-12-04-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task t2 { priority 500 projectid prj } supplement task t2.t3 { booking r2 2009-11-30-16:00-+0000 + 8.0h, 2009-12-01-16:00-+0000 + 8.0h, 2009-12-02-16:00-+0000 + 8.0h, 2009-12-03-16:00-+0000 + 8.0h, 2009-12-04-16:00-+0000 + 8.0h, 2009-12-07-16:00-+0000 + 8.0h, 2009-12-08-16:00-+0000 + 8.0h, 2009-12-09-16:00-+0000 + 8.0h, 2009-12-10-16:00-+0000 + 8.0h, 2009-12-11-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/TaskRoot.tjp0000644000175000017500000000307612614413013024423 0ustar bernatbernatproject prj "Taskroot Example" "1.0" 2005-07-22-00:00-+0000 - 2005-08-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task items "Project breakdown" { task plan "Plan work" { start 2005-07-22-06:00-+0000 end 2005-07-26-23:00-+0000 scheduling asap scheduled } task implementation "Implement work" { task phase1 "Phase 1" { depends items.plan start 2005-07-26-23:00-+0000 end 2005-08-02-23:00-+0000 scheduling asap scheduled } task phase2 "Phase 2" { depends items.implementation.phase1 start 2005-08-02-23:00-+0000 end 2005-08-05-23:00-+0000 scheduling asap scheduled } task phase3 "Phase 3" { depends items.implementation.phase2 start 2005-08-05-23:00-+0000 end 2005-08-11-23:00-+0000 scheduling asap scheduled } } task acceptance "Customer acceptance" { depends items.implementation start 2005-08-11-23:00-+0000 end 2005-08-16-23:00-+0000 scheduling asap scheduled } } supplement task items { priority 500 projectid prj } supplement task items.plan { priority 500 projectid prj } supplement task items.implementation { priority 500 projectid prj } supplement task items.implementation.phase1 { priority 500 projectid prj } supplement task items.implementation.phase2 { priority 500 projectid prj } supplement task items.implementation.phase3 { priority 500 projectid prj } supplement task items.acceptance { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Macro-2.tjp0000644000175000017500000000073312614413013024052 0ustar bernatbernatproject prj "Test" "1.0" 2010-04-28-00:00-+0000 - 2010-05-05-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task _Task_1 " Crème brûlée Prepare " { start 2010-04-28-00:00-+0000 milestone scheduled } task _Task_2 "task" { start 2010-04-28-00:00-+0000 milestone scheduled } supplement task _Task_1 { priority 500 projectid prj } supplement task _Task_2 { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Manager.tjp0000644000175000017500000000742412614413013024230 0ustar bernatbernatproject prj "test" "1.0" 2010-04-03-00:00-+0000 - 2010-04-10-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource _Resource_1 "The Company" { resource ceo "Big Boss" resource _Resource_3 "R&D Team" { resource vpe "VP Engineering" resource _Resource_5 "The Hacker" resource _Resource_6 "Doc Writer" } resource _Resource_7 "F&A Team" { resource coo "Chief Operating Officer" resource _Resource_9 "HR Lady" resource _Resource_10 "Accountant" } } task _Task_1 "T" { start 2010-04-03-00:00-+0000 milestone scheduled } supplement task _Task_1 { priority 500 projectid prj } supplement resource _Resource_1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource ceo { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource vpe { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_5 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_6 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_7 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource coo { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_9 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_10 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/ResourceRoot.tjp0000644000175000017500000000512512614413013025305 0ustar bernatbernatproject prj "Test" "1.0" 2010-11-10-00:00-+0000 - 2011-01-09-20:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource org "Org" { resource team1 "Team1" { resource r1 "R1" resource r2 "R2" resource r3 "R3" } resource r4 "R4" } resource r5 "R5" task _Task_1 "T" { start 2010-11-10-00:00-+0000 milestone scheduled } supplement task _Task_1 { priority 500 projectid prj } supplement resource org { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource team1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r4 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r5 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/TimeSheet1.tjp0000644000175000017500000000343312614413013024622 0ustar bernatbernatproject prj "test" "1.0" 2009-11-30-00:00-+0000 - 2010-01-29-20:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource r1 "R1" resource r2 "R2" task t1 "Task 1" { start 2009-11-30-16:00-+0000 end 2009-12-05-00:00-+0000 scheduling asap scheduled } task t2 "Task 2" { task t3 "Task 3" { start 2009-11-30-00:00-+0000 end 2009-12-10-00:00-+0000 scheduling asap scheduled } } supplement task t1 { booking r1 2009-11-30-16:00-+0000 + 8.0h, 2009-12-01-16:00-+0000 + 8.0h, 2009-12-02-16:00-+0000 + 8.0h, 2009-12-03-16:00-+0000 + 8.0h, 2009-12-04-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task t2 { priority 500 projectid prj } supplement task t2.t3 { booking r2 2009-11-30-16:00-+0000 + 8.0h, 2009-12-01-16:00-+0000 + 8.0h, 2009-12-02-16:00-+0000 + 8.0h, 2009-12-03-16:00-+0000 + 8.0h, 2009-12-04-16:00-+0000 + 8.0h, 2009-12-07-16:00-+0000 + 8.0h, 2009-12-08-16:00-+0000 + 8.0h, 2009-12-09-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/LoadUnits.tjp0000644000175000017500000003127212614413013024556 0ustar bernatbernatproject simple "Simple Project" "$Id" 2000-01-01-00:00-+0000 - 2001-02-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids simple resource tux1 "Tux1" resource tux2 "Tux2" resource tux3 "Tux3" task t1 "Task1" { start 2000-01-03-16:00-+0000 end 2000-01-29-00:00-+0000 scheduling asap scheduled } task t2 "Task2" { start 2000-01-01-07:00-+0000 end 2000-12-20-00:00-+0000 scheduling asap scheduled } task t3 "Task3" { start 2000-01-03-16:00-+0000 end 2000-01-05-00:00-+0000 scheduling asap scheduled } supplement task t1 { booking tux1 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h, 2000-01-10-16:00-+0000 + 8.0h, 2000-01-11-16:00-+0000 + 8.0h, 2000-01-12-16:00-+0000 + 8.0h, 2000-01-13-16:00-+0000 + 8.0h, 2000-01-14-16:00-+0000 + 8.0h, 2000-01-17-16:00-+0000 + 8.0h, 2000-01-18-16:00-+0000 + 8.0h, 2000-01-19-16:00-+0000 + 8.0h, 2000-01-20-16:00-+0000 + 8.0h, 2000-01-21-16:00-+0000 + 8.0h, 2000-01-24-16:00-+0000 + 8.0h, 2000-01-25-16:00-+0000 + 8.0h, 2000-01-26-16:00-+0000 + 8.0h, 2000-01-27-16:00-+0000 + 8.0h, 2000-01-28-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid simple } supplement task t2 { booking tux2 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h, 2000-01-10-16:00-+0000 + 8.0h, 2000-01-11-16:00-+0000 + 8.0h, 2000-01-12-16:00-+0000 + 8.0h, 2000-01-13-16:00-+0000 + 8.0h, 2000-01-14-16:00-+0000 + 8.0h, 2000-01-17-16:00-+0000 + 8.0h, 2000-01-18-16:00-+0000 + 8.0h, 2000-01-19-16:00-+0000 + 8.0h, 2000-01-20-16:00-+0000 + 8.0h, 2000-01-21-16:00-+0000 + 8.0h, 2000-01-24-16:00-+0000 + 8.0h, 2000-01-25-16:00-+0000 + 8.0h, 2000-01-26-16:00-+0000 + 8.0h, 2000-01-27-16:00-+0000 + 8.0h, 2000-01-28-16:00-+0000 + 8.0h, 2000-01-31-16:00-+0000 + 8.0h, 2000-02-01-16:00-+0000 + 8.0h, 2000-02-02-16:00-+0000 + 8.0h, 2000-02-03-16:00-+0000 + 8.0h, 2000-02-04-16:00-+0000 + 8.0h, 2000-02-07-16:00-+0000 + 8.0h, 2000-02-08-16:00-+0000 + 8.0h, 2000-02-09-16:00-+0000 + 8.0h, 2000-02-10-16:00-+0000 + 8.0h, 2000-02-11-16:00-+0000 + 8.0h, 2000-02-14-16:00-+0000 + 8.0h, 2000-02-15-16:00-+0000 + 8.0h, 2000-02-16-16:00-+0000 + 8.0h, 2000-02-17-16:00-+0000 + 8.0h, 2000-02-18-16:00-+0000 + 8.0h, 2000-02-21-16:00-+0000 + 8.0h, 2000-02-22-16:00-+0000 + 8.0h, 2000-02-23-16:00-+0000 + 8.0h, 2000-02-24-16:00-+0000 + 8.0h, 2000-02-25-16:00-+0000 + 8.0h, 2000-02-28-16:00-+0000 + 8.0h, 2000-02-29-16:00-+0000 + 8.0h, 2000-03-01-16:00-+0000 + 8.0h, 2000-03-02-16:00-+0000 + 8.0h, 2000-03-03-16:00-+0000 + 8.0h, 2000-03-06-16:00-+0000 + 8.0h, 2000-03-07-16:00-+0000 + 8.0h, 2000-03-08-16:00-+0000 + 8.0h, 2000-03-09-16:00-+0000 + 8.0h, 2000-03-10-16:00-+0000 + 8.0h, 2000-03-13-16:00-+0000 + 8.0h, 2000-03-14-16:00-+0000 + 8.0h, 2000-03-15-16:00-+0000 + 8.0h, 2000-03-16-16:00-+0000 + 8.0h, 2000-03-17-16:00-+0000 + 8.0h, 2000-03-20-16:00-+0000 + 8.0h, 2000-03-21-16:00-+0000 + 8.0h, 2000-03-22-16:00-+0000 + 8.0h, 2000-03-23-16:00-+0000 + 8.0h, 2000-03-24-16:00-+0000 + 8.0h, 2000-03-27-16:00-+0000 + 8.0h, 2000-03-28-16:00-+0000 + 8.0h, 2000-03-29-16:00-+0000 + 8.0h, 2000-03-30-16:00-+0000 + 8.0h, 2000-03-31-16:00-+0000 + 8.0h, 2000-04-03-15:00-+0000 + 8.0h, 2000-04-04-15:00-+0000 + 8.0h, 2000-04-05-15:00-+0000 + 8.0h, 2000-04-06-15:00-+0000 + 8.0h, 2000-04-07-15:00-+0000 + 8.0h, 2000-04-10-15:00-+0000 + 8.0h, 2000-04-11-15:00-+0000 + 8.0h, 2000-04-12-15:00-+0000 + 8.0h, 2000-04-13-15:00-+0000 + 8.0h, 2000-04-14-15:00-+0000 + 8.0h, 2000-04-17-15:00-+0000 + 8.0h, 2000-04-18-15:00-+0000 + 8.0h, 2000-04-19-15:00-+0000 + 8.0h, 2000-04-20-15:00-+0000 + 8.0h, 2000-04-21-15:00-+0000 + 8.0h, 2000-04-24-15:00-+0000 + 8.0h, 2000-04-25-15:00-+0000 + 8.0h, 2000-04-26-15:00-+0000 + 8.0h, 2000-04-27-15:00-+0000 + 8.0h, 2000-04-28-15:00-+0000 + 8.0h, 2000-05-01-15:00-+0000 + 8.0h, 2000-05-02-15:00-+0000 + 8.0h, 2000-05-03-15:00-+0000 + 8.0h, 2000-05-04-15:00-+0000 + 8.0h, 2000-05-05-15:00-+0000 + 8.0h, 2000-05-08-15:00-+0000 + 8.0h, 2000-05-09-15:00-+0000 + 8.0h, 2000-05-10-15:00-+0000 + 8.0h, 2000-05-11-15:00-+0000 + 8.0h, 2000-05-12-15:00-+0000 + 8.0h, 2000-05-15-15:00-+0000 + 8.0h, 2000-05-16-15:00-+0000 + 8.0h, 2000-05-17-15:00-+0000 + 8.0h, 2000-05-18-15:00-+0000 + 8.0h, 2000-05-19-15:00-+0000 + 8.0h, 2000-05-22-15:00-+0000 + 8.0h, 2000-05-23-15:00-+0000 + 8.0h, 2000-05-24-15:00-+0000 + 8.0h, 2000-05-25-15:00-+0000 + 8.0h, 2000-05-26-15:00-+0000 + 8.0h, 2000-05-29-15:00-+0000 + 8.0h, 2000-05-30-15:00-+0000 + 8.0h, 2000-05-31-15:00-+0000 + 8.0h, 2000-06-01-15:00-+0000 + 8.0h, 2000-06-02-15:00-+0000 + 8.0h, 2000-06-05-15:00-+0000 + 8.0h, 2000-06-06-15:00-+0000 + 8.0h, 2000-06-07-15:00-+0000 + 8.0h, 2000-06-08-15:00-+0000 + 8.0h, 2000-06-09-15:00-+0000 + 8.0h, 2000-06-12-15:00-+0000 + 8.0h, 2000-06-13-15:00-+0000 + 8.0h, 2000-06-14-15:00-+0000 + 8.0h, 2000-06-15-15:00-+0000 + 8.0h, 2000-06-16-15:00-+0000 + 8.0h, 2000-06-19-15:00-+0000 + 8.0h, 2000-06-20-15:00-+0000 + 8.0h, 2000-06-21-15:00-+0000 + 8.0h, 2000-06-22-15:00-+0000 + 8.0h, 2000-06-23-15:00-+0000 + 8.0h, 2000-06-26-15:00-+0000 + 8.0h, 2000-06-27-15:00-+0000 + 8.0h, 2000-06-28-15:00-+0000 + 8.0h, 2000-06-29-15:00-+0000 + 8.0h, 2000-06-30-15:00-+0000 + 8.0h, 2000-07-03-15:00-+0000 + 8.0h, 2000-07-04-15:00-+0000 + 8.0h, 2000-07-05-15:00-+0000 + 8.0h, 2000-07-06-15:00-+0000 + 8.0h, 2000-07-07-15:00-+0000 + 8.0h, 2000-07-10-15:00-+0000 + 8.0h, 2000-07-11-15:00-+0000 + 8.0h, 2000-07-12-15:00-+0000 + 8.0h, 2000-07-13-15:00-+0000 + 8.0h, 2000-07-14-15:00-+0000 + 8.0h, 2000-07-17-15:00-+0000 + 8.0h, 2000-07-18-15:00-+0000 + 8.0h, 2000-07-19-15:00-+0000 + 8.0h, 2000-07-20-15:00-+0000 + 8.0h, 2000-07-21-15:00-+0000 + 8.0h, 2000-07-24-15:00-+0000 + 8.0h, 2000-07-25-15:00-+0000 + 8.0h, 2000-07-26-15:00-+0000 + 8.0h, 2000-07-27-15:00-+0000 + 8.0h, 2000-07-28-15:00-+0000 + 8.0h, 2000-07-31-15:00-+0000 + 8.0h, 2000-08-01-15:00-+0000 + 8.0h, 2000-08-02-15:00-+0000 + 8.0h, 2000-08-03-15:00-+0000 + 8.0h, 2000-08-04-15:00-+0000 + 8.0h, 2000-08-07-15:00-+0000 + 8.0h, 2000-08-08-15:00-+0000 + 8.0h, 2000-08-09-15:00-+0000 + 8.0h, 2000-08-10-15:00-+0000 + 8.0h, 2000-08-11-15:00-+0000 + 8.0h, 2000-08-14-15:00-+0000 + 8.0h, 2000-08-15-15:00-+0000 + 8.0h, 2000-08-16-15:00-+0000 + 8.0h, 2000-08-17-15:00-+0000 + 8.0h, 2000-08-18-15:00-+0000 + 8.0h, 2000-08-21-15:00-+0000 + 8.0h, 2000-08-22-15:00-+0000 + 8.0h, 2000-08-23-15:00-+0000 + 8.0h, 2000-08-24-15:00-+0000 + 8.0h, 2000-08-25-15:00-+0000 + 8.0h, 2000-08-28-15:00-+0000 + 8.0h, 2000-08-29-15:00-+0000 + 8.0h, 2000-08-30-15:00-+0000 + 8.0h, 2000-08-31-15:00-+0000 + 8.0h, 2000-09-01-15:00-+0000 + 8.0h, 2000-09-04-15:00-+0000 + 8.0h, 2000-09-05-15:00-+0000 + 8.0h, 2000-09-06-15:00-+0000 + 8.0h, 2000-09-07-15:00-+0000 + 8.0h, 2000-09-08-15:00-+0000 + 8.0h, 2000-09-11-15:00-+0000 + 8.0h, 2000-09-12-15:00-+0000 + 8.0h, 2000-09-13-15:00-+0000 + 8.0h, 2000-09-14-15:00-+0000 + 8.0h, 2000-09-15-15:00-+0000 + 8.0h, 2000-09-18-15:00-+0000 + 8.0h, 2000-09-19-15:00-+0000 + 8.0h, 2000-09-20-15:00-+0000 + 8.0h, 2000-09-21-15:00-+0000 + 8.0h, 2000-09-22-15:00-+0000 + 8.0h, 2000-09-25-15:00-+0000 + 8.0h, 2000-09-26-15:00-+0000 + 8.0h, 2000-09-27-15:00-+0000 + 8.0h, 2000-09-28-15:00-+0000 + 8.0h, 2000-09-29-15:00-+0000 + 8.0h, 2000-10-02-15:00-+0000 + 8.0h, 2000-10-03-15:00-+0000 + 8.0h, 2000-10-04-15:00-+0000 + 8.0h, 2000-10-05-15:00-+0000 + 8.0h, 2000-10-06-15:00-+0000 + 8.0h, 2000-10-09-15:00-+0000 + 8.0h, 2000-10-10-15:00-+0000 + 8.0h, 2000-10-11-15:00-+0000 + 8.0h, 2000-10-12-15:00-+0000 + 8.0h, 2000-10-13-15:00-+0000 + 8.0h, 2000-10-16-15:00-+0000 + 8.0h, 2000-10-17-15:00-+0000 + 8.0h, 2000-10-18-15:00-+0000 + 8.0h, 2000-10-19-15:00-+0000 + 8.0h, 2000-10-20-15:00-+0000 + 8.0h, 2000-10-23-15:00-+0000 + 8.0h, 2000-10-24-15:00-+0000 + 8.0h, 2000-10-25-15:00-+0000 + 8.0h, 2000-10-26-15:00-+0000 + 8.0h, 2000-10-27-15:00-+0000 + 8.0h, 2000-10-30-16:00-+0000 + 8.0h, 2000-10-31-16:00-+0000 + 8.0h, 2000-11-01-16:00-+0000 + 8.0h, 2000-11-02-16:00-+0000 + 8.0h, 2000-11-03-16:00-+0000 + 8.0h, 2000-11-06-16:00-+0000 + 8.0h, 2000-11-07-16:00-+0000 + 8.0h, 2000-11-08-16:00-+0000 + 8.0h, 2000-11-09-16:00-+0000 + 8.0h, 2000-11-10-16:00-+0000 + 8.0h, 2000-11-13-16:00-+0000 + 8.0h, 2000-11-14-16:00-+0000 + 8.0h, 2000-11-15-16:00-+0000 + 8.0h, 2000-11-16-16:00-+0000 + 8.0h, 2000-11-17-16:00-+0000 + 8.0h, 2000-11-20-16:00-+0000 + 8.0h, 2000-11-21-16:00-+0000 + 8.0h, 2000-11-22-16:00-+0000 + 8.0h, 2000-11-23-16:00-+0000 + 8.0h, 2000-11-24-16:00-+0000 + 8.0h, 2000-11-27-16:00-+0000 + 8.0h, 2000-11-28-16:00-+0000 + 8.0h, 2000-11-29-16:00-+0000 + 8.0h, 2000-11-30-16:00-+0000 + 8.0h, 2000-12-01-16:00-+0000 + 8.0h, 2000-12-04-16:00-+0000 + 8.0h, 2000-12-05-16:00-+0000 + 8.0h, 2000-12-06-16:00-+0000 + 8.0h, 2000-12-07-16:00-+0000 + 8.0h, 2000-12-08-16:00-+0000 + 8.0h, 2000-12-11-16:00-+0000 + 8.0h, 2000-12-12-16:00-+0000 + 8.0h, 2000-12-13-16:00-+0000 + 8.0h, 2000-12-14-16:00-+0000 + 8.0h, 2000-12-15-16:00-+0000 + 8.0h, 2000-12-18-16:00-+0000 + 8.0h, 2000-12-19-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid simple } supplement task t3 { booking tux3 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid simple } supplement resource tux1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Flags.tjp0000644000175000017500000000175312614413013023711 0ustar bernatbernatproject prj "Flags Example" "1.0" 2005-07-21-00:00-+0000 - 2005-08-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } flags important projectids prj task items "Project breakdown" { task plan "Plan work" { start 2005-07-22-06:00-+0000 end 2005-07-26-23:00-+0000 scheduling asap scheduled } task implementation "Implement work" { depends items.plan start 2005-07-26-23:00-+0000 end 2005-08-02-23:00-+0000 scheduling asap scheduled } task acceptance "Customer acceptance" { depends items.implementation start 2005-08-02-23:00-+0000 end 2005-08-07-23:00-+0000 scheduling asap scheduled } } supplement task items { priority 500 projectid prj } supplement task items.plan { flags important priority 500 projectid prj } supplement task items.implementation { priority 500 projectid prj } supplement task items.acceptance { flags important priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/RollupResource.tjp0000644000175000017500000000512512614413013025637 0ustar bernatbernatproject prj "Test" "1.0" 2010-11-10-00:00-+0000 - 2011-01-09-20:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource org "Org" { resource team1 "Team1" { resource r1 "R1" resource r2 "R2" resource r3 "R3" } resource r4 "R4" } resource r5 "R5" task _Task_1 "T" { start 2010-11-10-00:00-+0000 milestone scheduled } supplement task _Task_1 { priority 500 projectid prj } supplement resource org { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource team1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r4 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r5 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Alternative.tjp0000644000175000017500000000232712614413013025131 0ustar bernatbernatproject prj "Project" "1.0" 2000-01-01-00:00-+0000 - 2000-03-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tuxus "Tuxus" resource tuxia "Tuxia" task t "Task" { start 2000-01-03-16:00-+0000 end 2000-01-08-00:00-+0000 scheduling asap scheduled } supplement task t { booking tuxus 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource tuxus { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tuxia { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Priority.tjp0000644000175000017500000001203112614413013024465 0ustar bernatbernatproject prj "Priority Demo" "1.0" 2011-04-17-07:00-+0000 - 2011-06-17-03:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux "Tux" task jobs "Project breakdown" { task work "The regular work" { start 2011-04-18-15:00-+0000 end 2011-06-01-20:00-+0000 scheduling asap scheduled } task support "Customer Support" { start 2011-04-17-07:00-+0000 end 2011-06-17-03:00-+0000 scheduling alap scheduled } task conference "Attend Conference" { start 2011-04-25-06:00-+0000 end 2011-04-27-06:00-+0000 scheduling asap scheduled } task maintenance "Maintenance work" { start 2011-04-17-07:00-+0000 end 2011-06-17-03:00-+0000 scheduling alap scheduled } } supplement task jobs { priority 500 projectid prj } supplement task jobs.work { booking tux 2011-04-18-15:00-+0000 + 6.0h, 2011-04-19-15:00-+0000 + 6.0h, 2011-04-20-15:00-+0000 + 6.0h, 2011-04-21-15:00-+0000 + 6.0h, 2011-04-22-15:00-+0000 + 1.0h, 2011-04-27-15:00-+0000 + 6.0h, 2011-04-28-15:00-+0000 + 6.0h, 2011-04-29-15:00-+0000 + 6.0h, 2011-05-02-15:00-+0000 + 6.0h, 2011-05-03-15:00-+0000 + 6.0h, 2011-05-04-15:00-+0000 + 6.0h, 2011-05-05-15:00-+0000 + 6.0h, 2011-05-06-15:00-+0000 + 1.0h, 2011-05-09-15:00-+0000 + 6.0h, 2011-05-10-15:00-+0000 + 6.0h, 2011-05-11-15:00-+0000 + 6.0h, 2011-05-12-15:00-+0000 + 6.0h, 2011-05-13-15:00-+0000 + 1.0h, 2011-05-16-15:00-+0000 + 6.0h, 2011-05-17-15:00-+0000 + 6.0h, 2011-05-18-15:00-+0000 + 6.0h, 2011-05-19-15:00-+0000 + 6.0h, 2011-05-20-15:00-+0000 + 1.0h, 2011-05-23-15:00-+0000 + 6.0h, 2011-05-24-15:00-+0000 + 6.0h, 2011-05-25-15:00-+0000 + 6.0h, 2011-05-26-15:00-+0000 + 6.0h, 2011-05-27-15:00-+0000 + 1.0h, 2011-05-30-15:00-+0000 + 6.0h, 2011-05-31-15:00-+0000 + 6.0h, 2011-06-01-15:00-+0000 + 5.0h { overtime 2 } priority 500 projectid prj } supplement task jobs.support { booking tux 2011-04-18-21:00-+0000 + 2.0h, 2011-04-19-21:00-+0000 + 2.0h, 2011-04-20-21:00-+0000 + 2.0h, 2011-04-21-21:00-+0000 + 2.0h, 2011-04-22-21:00-+0000 + 2.0h, 2011-04-27-21:00-+0000 + 2.0h, 2011-04-28-21:00-+0000 + 2.0h, 2011-04-29-21:00-+0000 + 2.0h, 2011-05-02-21:00-+0000 + 2.0h, 2011-05-03-21:00-+0000 + 2.0h, 2011-05-04-21:00-+0000 + 2.0h, 2011-05-05-21:00-+0000 + 2.0h, 2011-05-06-21:00-+0000 + 2.0h, 2011-05-09-21:00-+0000 + 2.0h, 2011-05-10-21:00-+0000 + 2.0h, 2011-05-11-21:00-+0000 + 2.0h, 2011-05-12-21:00-+0000 + 2.0h, 2011-05-13-21:00-+0000 + 2.0h, 2011-05-16-21:00-+0000 + 2.0h, 2011-05-17-21:00-+0000 + 2.0h, 2011-05-18-21:00-+0000 + 2.0h, 2011-05-19-21:00-+0000 + 2.0h, 2011-05-20-21:00-+0000 + 2.0h, 2011-05-23-21:00-+0000 + 2.0h, 2011-05-24-21:00-+0000 + 2.0h, 2011-05-25-21:00-+0000 + 2.0h, 2011-05-26-21:00-+0000 + 2.0h, 2011-05-27-21:00-+0000 + 2.0h, 2011-05-30-21:00-+0000 + 2.0h, 2011-05-31-21:00-+0000 + 2.0h, 2011-06-01-21:00-+0000 + 2.0h, 2011-06-02-21:00-+0000 + 2.0h, 2011-06-03-21:00-+0000 + 2.0h, 2011-06-06-21:00-+0000 + 2.0h, 2011-06-07-21:00-+0000 + 2.0h, 2011-06-08-21:00-+0000 + 2.0h, 2011-06-09-21:00-+0000 + 2.0h, 2011-06-10-21:00-+0000 + 2.0h, 2011-06-13-21:00-+0000 + 2.0h, 2011-06-14-21:00-+0000 + 2.0h, 2011-06-15-21:00-+0000 + 2.0h, 2011-06-16-15:00-+0000 + 8.0h { overtime 2 } priority 800 projectid prj } supplement task jobs.conference { booking tux 2011-04-25-15:00-+0000 + 8.0h, 2011-04-26-15:00-+0000 + 8.0h { overtime 2 } priority 1000 projectid prj } supplement task jobs.maintenance { booking tux 2011-04-22-16:00-+0000 + 5.0h, 2011-05-06-16:00-+0000 + 5.0h, 2011-05-13-16:00-+0000 + 5.0h, 2011-05-20-16:00-+0000 + 5.0h, 2011-05-27-16:00-+0000 + 5.0h, 2011-06-01-20:00-+0000 + 1.0h, 2011-06-02-15:00-+0000 + 6.0h, 2011-06-03-15:00-+0000 + 6.0h, 2011-06-08-17:00-+0000 + 4.0h, 2011-06-09-15:00-+0000 + 6.0h, 2011-06-10-15:00-+0000 + 6.0h, 2011-06-13-15:00-+0000 + 6.0h, 2011-06-14-15:00-+0000 + 6.0h, 2011-06-15-15:00-+0000 + 6.0h { overtime 2 } priority 300 projectid prj } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/CustomAttributes.tjp0000644000175000017500000000433512614413013026175 0ustar bernatbernatproject prj "Extend Test" "1.0" 2013-04-24-00:00-+0000 - 2013-05-24-10:00-+0000 { timezone "UTC" extend resource{ date Birthday "Birthday" date BirthdayS "Birthday" { scenariospecific inherit } richtext Claim "Claim" richtext ClaimS "Claim" { scenariospecific inherit } number Count "Count" number CountS "Count" { scenariospecific } text Intro "Intro" text IntroS "Intro" { scenariospecific } reference URL "URL" reference URLS "URL" { scenariospecific } } extend task{ richtext Claim "Claim" richtext ClaimS "Claim" { scenariospecific inherit } number Count "Count" number CountS "Count" { scenariospecific } date DueDate "Due Date" date DueDateS "Due Date" { scenariospecific inherit } text Intro "Intro" text IntroS "Intro" { scenariospecific } reference URL "URL" reference URLS "URL" { scenariospecific } } scenario one "One" { scenario two "Two" { active yes } active yes } } projectids prj resource _Resource_1 "R" task _Task_1 "T" { start 2013-04-24-00:00-+0000 milestone scheduled two:milestone } supplement task _Task_1 { Claim "A '''big''' statement." two:ClaimS "A '''big''' statement." Count 42 two:CountS 42 DueDate 2013-05-01-00:00-+0000 two:DueDateS 2013-05-01-00:00-+0000 Intro "Let's think about this..." two:IntroS "Let's think about this..." URL "http://www.taskjuggler.org" { label "http://www.taskjuggler.org" } two:URLS "http://www.taskjuggler.org" { label "TJ Web" } priority 500 projectid prj } supplement resource _Resource_1 { Birthday 2000-05-01-00:00-+0000 two:BirthdayS 2000-05-01-00:00-+0000 Claim "A '''big''' statement." two:ClaimS "A '''big''' statement." Count 42 two:CountS 42 Intro "Let's think about this..." two:IntroS "Let's think about this..." URL "http://www.taskjuggler.org" { label "http://www.taskjuggler.org" } two:URLS "http://www.taskjuggler.org" { label "TJ Web" } workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/TaskPrefix.tjp0000644000175000017500000000220512614413013024726 0ustar bernatbernatproject project "Include task prefix test" "1.0" 2010-12-01-00:00-+0000 - 2010-12-31-10:00-+0000 { timezone "Europe/Amsterdam" scenario plan "Plan Scenario" { active yes } } projectids project resource tux "Tux" task parent_task "Parent task" { task sub_task1 "Sub task 1" { start 2010-12-01-08:00-+0000 end 2010-12-01-16:00-+0000 scheduling asap scheduled } } task other_task "Other task" { start 2010-12-02-08:00-+0000 end 2010-12-02-16:00-+0000 scheduling asap scheduled } supplement task parent_task { priority 500 projectid project } supplement task parent_task.sub_task1 { booking tux 2010-12-01-08:00-+0000 + 8.0h { overtime 2 } priority 500 projectid project } supplement task other_task { booking tux 2010-12-02-08:00-+0000 + 8.0h { overtime 2 } priority 500 projectid project } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Comments.tjp0000644000175000017500000000276312614413013024444 0ustar bernatbernatproject simple "Simple Project" "1.0" 2005-06-06-00:00-+0000 - 2005-06-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids simple resource tux "Tux" task items "Project breakdown" { task plan "Plan work" { start 2005-06-06-06:00-+0000 end 2005-06-08-23:00-+0000 scheduling asap scheduled } task implementation "Implement work" { depends items.plan start 2005-06-09-15:00-+0000 end 2005-06-15-23:00-+0000 scheduling asap scheduled } task acceptance "Customer acceptance" { depends items.implementation start 2005-06-15-23:00-+0000 end 2005-06-20-23:00-+0000 scheduling asap scheduled } } supplement task items { priority 500 projectid simple } supplement task items.plan { priority 500 projectid simple } supplement task items.implementation { booking tux 2005-06-09-15:00-+0000 + 8.0h, 2005-06-10-15:00-+0000 + 8.0h, 2005-06-13-15:00-+0000 + 8.0h, 2005-06-14-15:00-+0000 + 8.0h, 2005-06-15-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid simple } supplement task items.acceptance { priority 500 projectid simple } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Account.tjp0000644000175000017500000000440012614413013024241 0ustar bernatbernatproject simple "Simple Project" "1.0" 2007-01-01-00:00-+0000 - 2007-01-30-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids simple resource tux "Tux" resource konqui "Konqui" task items "Room decoration" { task plan "Plan work and buy material" { start 2007-01-06-07:00-+0000 end 2007-01-10-00:00-+0000 scheduling asap scheduled } task remove "Remove old inventory" { depends items.plan start 2007-01-10-16:00-+0000 end 2007-01-10-20:00-+0000 scheduling asap scheduled } task implement "Arrange new decoration" { depends items.remove start 2007-01-10-20:00-+0000 end 2007-01-13-00:00-+0000 scheduling asap scheduled } task acceptance "Presentation and customer acceptance" { depends items.implement start 2007-01-13-00:00-+0000 end 2007-01-18-00:00-+0000 scheduling asap scheduled } } supplement task items { priority 500 projectid simple } supplement task items.plan { priority 500 projectid simple } supplement task items.remove { booking konqui 2007-01-10-16:00-+0000 + 4.0h { overtime 2 } booking tux 2007-01-10-16:00-+0000 + 4.0h { overtime 2 } priority 500 projectid simple } supplement task items.implement { booking konqui 2007-01-10-20:00-+0000 + 4.0h, 2007-01-11-16:00-+0000 + 8.0h, 2007-01-12-16:00-+0000 + 8.0h { overtime 2 } booking tux 2007-01-10-20:00-+0000 + 4.0h, 2007-01-11-16:00-+0000 + 8.0h, 2007-01-12-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid simple } supplement task items.acceptance { priority 500 projectid simple } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource konqui { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Gap.tjp0000644000175000017500000000113312614413013023354 0ustar bernatbernatproject prj "Example Project" "1.0" 2005-05-29-00:00-+0000 - 2005-07-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task t1 "Task 1" { start 2005-05-29-06:00-+0000 milestone scheduled } task t2 "Task 2" { depends t1 start 2005-06-03-06:00-+0000 milestone scheduled } task t3 "Task 3" { depends t1 start 2005-06-03-23:00-+0000 milestone scheduled } supplement task t1 { priority 500 projectid prj } supplement task t2 { priority 500 projectid prj } supplement task t3 { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Query.tjp0000644000175000017500000000213112614413013023751 0ustar bernatbernatproject prj "Query Demo" "1.0" 2009-11-22-00:00-+0000 - 2009-12-22-10:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource joe "Joe" task _Task_1 "Job" { start 2009-11-23-16:00-+0000 end 2009-12-05-00:00-+0000 scheduling asap scheduled } supplement task _Task_1 { booking joe 2009-11-23-16:00-+0000 + 8.0h, 2009-11-24-16:00-+0000 + 8.0h, 2009-11-25-16:00-+0000 + 8.0h, 2009-11-26-16:00-+0000 + 8.0h, 2009-11-27-16:00-+0000 + 8.0h, 2009-11-30-16:00-+0000 + 8.0h, 2009-12-01-16:00-+0000 + 8.0h, 2009-12-02-16:00-+0000 + 8.0h, 2009-12-03-16:00-+0000 + 8.0h, 2009-12-04-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource joe { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/AdoptedTasks.tjp0000644000175000017500000000113212614413013025232 0ustar bernatbernatproject prj "Adopted Tasks" "1.0" 2011-03-05-00:00-+0000 - 2011-04-04-10:00-+0000 { timezone "UTC" scenario plan "Plan Scenario" { active yes } } projectids prj task t1 "T1" { start 2011-03-05-00:00-+0000 milestone scheduled } task t2 "T2" { start 2011-03-05-00:00-+0000 milestone scheduled } task t3 "T3" { start 2011-03-05-00:00-+0000 end 2011-03-05-00:00-+0000 scheduling asap scheduled } supplement task t1 { priority 500 projectid prj } supplement task t2 { priority 500 projectid prj } supplement task t3 { adopt t1, t2 priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Efficiency.tjp0000644000175000017500000000324512614413013024717 0ustar bernatbernatproject prj "Resource Efficiency Example" "1.0" 2007-07-21-00:00-+0000 - 2007-07-22-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tuxies "Tuxies" resource tux1 "Tux 1" resource tux2 "Tux 2" resource confRoom "Conference Room" task t "An important date" { start 2007-07-21-06:00-+0000 milestone scheduled } supplement task t { priority 500 projectid prj } supplement resource tuxies { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource confRoom { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/LogicalExpression.tjp0000644000175000017500000000133212614413013026300 0ustar bernatbernatproject prj "LogExp" "1.0" 2009-10-19-00:00-+0000 - 2009-12-18-20:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource r "R" task _Task_1 "T" { start 2009-10-19-15:00-+0000 end 2009-10-19-23:00-+0000 scheduling asap scheduled } supplement task _Task_1 { booking r 2009-10-19-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource r { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Responsible.tjp0000644000175000017500000000240512614413013025135 0ustar bernatbernatproject prj "Responsible Demo" "1.0" 2005-07-15-00:00-+0000 - 2005-08-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux "Tux" resource ubertux "Uber Tux" task someJob "Some Job" { start 2005-07-15-15:00-+0000 end 2005-07-21-23:00-+0000 scheduling asap scheduled } supplement task someJob { booking tux 2005-07-15-15:00-+0000 + 8.0h, 2005-07-18-15:00-+0000 + 8.0h, 2005-07-19-15:00-+0000 + 8.0h, 2005-07-20-15:00-+0000 + 8.0h, 2005-07-21-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj responsible ubertux } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource ubertux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Persistent.tjp0000644000175000017500000000232612614413013025012 0ustar bernatbernatproject prj "Project" "1.0" 2003-06-05-00:00-+0000 - 2003-07-05-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource r1 "Resource 1" resource r2 "Resource 2" task t1 "Task 1" { start 2003-06-05-15:00-+0000 end 2003-06-11-23:00-+0000 scheduling asap scheduled } supplement task t1 { booking r1 2003-06-05-15:00-+0000 + 8.0h, 2003-06-06-15:00-+0000 + 8.0h, 2003-06-09-15:00-+0000 + 8.0h, 2003-06-10-15:00-+0000 + 8.0h, 2003-06-11-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Vacation.tjp0000644000175000017500000000373612614413013024424 0ustar bernatbernatproject prj "Vacation Examples" "1.0" 2005-07-22-00:00-+0000 - 2006-01-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource team "A team" { resource tux2 "Tux2" resource tux3 "Tux3" } resource tuxia "Tuxia" resource tuxus "Tuxus" task t "An important date" { start 2005-07-22-06:00-+0000 milestone scheduled } supplement task t { priority 500 projectid prj } supplement resource team { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tuxia { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tuxus { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Reports.tjp0000644000175000017500000000575012614413013024314 0ustar bernatbernatproject prj "Test Project" "1.0" 2000-01-01-00:00-+0000 - 2000-03-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } flags flag3, flag2, flag1, flag4 projectids prj resource r1 "FooResource 1" resource r2 "FooResource 2" resource r3 "FooResource 3" resource r4 "FooResource 4" task t1 "FooTask1" { task t1_1 "FooTask1_1" { start 2000-01-03-16:00-+0000 end 2000-01-15-00:00-+0000 scheduling asap scheduled } } task t2 "FooTask2" { start 2000-01-01-07:00-+0000 end 2000-01-02-07:00-+0000 scheduling asap scheduled } task t3 "FooTask3" { start 2000-01-01-07:00-+0000 milestone scheduled } supplement task t1 { flags flag3 priority 500 projectid prj } supplement task t1.t1_1 { booking r1 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h, 2000-01-10-16:00-+0000 + 8.0h, 2000-01-11-16:00-+0000 + 8.0h, 2000-01-12-16:00-+0000 + 8.0h, 2000-01-13-16:00-+0000 + 8.0h, 2000-01-14-16:00-+0000 + 8.0h { overtime 2 } booking r2 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h, 2000-01-10-16:00-+0000 + 8.0h, 2000-01-11-16:00-+0000 + 8.0h, 2000-01-12-16:00-+0000 + 8.0h, 2000-01-13-16:00-+0000 + 8.0h, 2000-01-14-16:00-+0000 + 8.0h { overtime 2 } flags flag2 priority 500 projectid prj } supplement task t2 { flags flag1 priority 500 projectid prj } supplement task t3 { flags flag4 priority 500 projectid prj } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r4 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Caption.tjp0000644000175000017500000000271712614413013024253 0ustar bernatbernatproject simple "Simple Project" "1.0" 2007-01-01-00:00-+0000 - 2007-02-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids simple resource r1 "Resource 1" task plant "How to plant a tree" { task plan "Choose the planting site" { start 2007-01-01-16:00-+0000 end 2007-01-03-00:00-+0000 scheduling asap scheduled } task buy "Get a tree" { depends plant.plan start 2007-01-03-16:00-+0000 end 2007-01-04-00:00-+0000 scheduling asap scheduled } task action "Plant the tree" { depends plant.buy start 2007-01-04-16:00-+0000 end 2007-01-04-20:00-+0000 scheduling asap scheduled } } supplement task plant { priority 500 projectid simple } supplement task plant.plan { booking r1 2007-01-01-16:00-+0000 + 8.0h, 2007-01-02-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid simple } supplement task plant.buy { booking r1 2007-01-03-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid simple } supplement task plant.action { booking r1 2007-01-04-16:00-+0000 + 4.0h { overtime 2 } priority 500 projectid simple } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/navigator.tjp0000644000175000017500000000165212614413013024645 0ustar bernatbernatproject prj "Navigator Example" "1.0" 2011-12-12-00:00-+0000 - 2012-01-11-10:00-+0000 { timezone "UTC" scenario plan "Plan Scenario" { active yes } } projectids prj task foo "Foo" { task _Task_2 "Foo 1" { start 2011-12-12-00:00-+0000 milestone scheduled } task _Task_3 "Foo 2" { start 2011-12-12-00:00-+0000 milestone scheduled } } task bar "Bar" { task _Task_5 "Bar 1" { start 2011-12-12-00:00-+0000 milestone scheduled } task _Task_6 "Bar 2" { start 2011-12-12-00:00-+0000 milestone scheduled } } supplement task foo { priority 500 projectid prj } supplement task foo._Task_2 { priority 500 projectid prj } supplement task foo._Task_3 { priority 500 projectid prj } supplement task bar { priority 500 projectid prj } supplement task bar._Task_5 { priority 500 projectid prj } supplement task bar._Task_6 { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Mandatory.tjp0000644000175000017500000000274512614413013024615 0ustar bernatbernatproject prj "Project" "1.0" 2000-01-01-00:00-+0000 - 2000-03-01-00:00-+0000 { timingresolution 15min timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tuxus "Tuxus" resource truck "Truck" task t "Ship stones to customers" { start 2000-01-03-16:00-+0000 end 2000-01-08-00:00-+0000 scheduling asap scheduled } supplement task t { booking truck 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h { overtime 2 } booking tuxus 2000-01-03-16:00-+0000 + 8.0h, 2000-01-04-16:00-+0000 + 8.0h, 2000-01-05-16:00-+0000 + 8.0h, 2000-01-06-16:00-+0000 + 8.0h, 2000-01-07-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource tuxus { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource truck { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Resource.tjp0000644000175000017500000000321212614413013024434 0ustar bernatbernatproject prj "Resource Examples" "1.0" 2005-06-06-00:00-+0000 - 2005-06-26-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj resource tux1 "Tux1" resource team "A team" { resource tux2 "Tux2" resource tux3 "Tux3" } task t "An important date" { start 2005-06-10-06:00-+0000 milestone scheduled } supplement task t { priority 500 projectid prj } supplement resource tux1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource team { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource tux3 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/tutorial.tjp0000644000175000017500000006405612614413013024525 0ustar bernatbernatproject acso "Accounting Software" "1.0" 2002-01-16-00:00-+0000 - 2002-05-17-16:00-+0000 { timezone "Europe/Paris" extend resource{ text Phone "Phone" } scenario plan "Plan" { scenario delayed "Delayed" { active yes } active yes } } flags team projectids acso resource boss "Paul Henry Bullock" resource dev "Developers" { resource dev1 "Paul Smith" resource dev2 "Sébastien Bono" resource dev3 "Klaus Müller" } resource misc "The Others" { resource test "Peter Murphy" resource doc "Dim Sung" } task AcSo "Accounting Software" { task spec "Specification" { depends AcSo.deliveries.start start 2002-01-16-08:00-+0000 end 2002-01-24-14:00-+0000 scheduling asap scheduled delayed:start 2002-01-21-08:00-+0000 delayed:end 2002-01-29-14:00-+0000 delayed:scheduling asap } task software "Software Development" { depends AcSo.spec task database "Database coupling" { start 2002-01-24-14:00-+0000 end 2002-02-07-14:00-+0000 scheduling asap scheduled delayed:start 2002-01-29-14:00-+0000 delayed:end 2002-02-12-14:00-+0000 delayed:scheduling asap } task gui "Graphical User Interface" { depends AcSo.software.database, AcSo.software.backend start 2002-02-28-14:00-+0000 end 2002-03-28-13:00-+0000 scheduling asap scheduled delayed:start 2002-03-05-14:00-+0000 delayed:end 2002-04-08-11:00-+0000 delayed:scheduling asap } task backend "Back-End Functions" { depends AcSo.software.database start 2002-02-07-14:00-+0000 end 2002-02-28-14:00-+0000 scheduling asap scheduled delayed:start 2002-02-12-14:00-+0000 delayed:end 2002-03-05-14:00-+0000 delayed:scheduling asap } } task test "Software testing" { task alpha "Alpha Test" { depends AcSo.software start 2002-03-28-13:00-+0000 end 2002-04-03-10:00-+0000 scheduling asap scheduled delayed:start 2002-04-08-11:00-+0000 delayed:end 2002-04-11-09:00-+0000 delayed:scheduling asap } task beta "Beta Test" { depends AcSo.test.alpha start 2002-04-03-10:00-+0000 end 2002-04-18-13:00-+0000 scheduling asap scheduled delayed:start 2002-04-11-09:00-+0000 delayed:end 2002-04-26-12:00-+0000 delayed:scheduling asap } } task manual "Manual" { depends AcSo.deliveries.start start 2002-01-16-08:00-+0000 end 2002-02-26-11:00-+0000 scheduling asap scheduled delayed:start 2002-01-21-08:00-+0000 delayed:end 2002-03-01-11:00-+0000 delayed:scheduling asap } task deliveries "Milestones" { task start "Project start" { start 2002-01-16-00:00-+0000 milestone scheduled delayed:start 2002-01-19-23:00-+0000 delayed:milestone } task prev "Technology Preview" { depends AcSo.software.backend start 2002-02-28-14:00-+0000 milestone scheduled delayed:start 2002-03-05-14:00-+0000 delayed:milestone } task beta "Beta version" { depends AcSo.test.alpha start 2002-04-03-10:00-+0000 milestone scheduled delayed:start 2002-04-11-09:00-+0000 delayed:milestone } task done "Ship Product to Customer" { depends AcSo.test.beta, AcSo.manual start 2002-04-18-13:00-+0000 milestone scheduled delayed:start 2002-04-26-12:00-+0000 delayed:milestone } } } supplement task AcSo { priority 500 projectid acso responsible boss } supplement task AcSo.spec { booking dev1 2002-01-16-08:00-+0000 + 8.0h, 2002-01-17-08:00-+0000 + 8.0h, 2002-01-18-08:00-+0000 + 8.0h, 2002-01-21-08:00-+0000 + 8.0h, 2002-01-22-08:00-+0000 + 8.0h, 2002-01-23-08:00-+0000 + 8.0h, 2002-01-24-08:00-+0000 + 6.0h { overtime 2 } booking dev2 2002-01-16-08:00-+0000 + 8.0h, 2002-01-17-08:00-+0000 + 8.0h, 2002-01-18-08:00-+0000 + 8.0h, 2002-01-21-08:00-+0000 + 8.0h, 2002-01-22-08:00-+0000 + 8.0h, 2002-01-23-08:00-+0000 + 8.0h, 2002-01-24-08:00-+0000 + 5.0h { overtime 2 } booking dev3 2002-01-16-08:00-+0000 + 8.0h, 2002-01-17-08:00-+0000 + 8.0h, 2002-01-18-08:00-+0000 + 8.0h, 2002-01-21-08:00-+0000 + 8.0h, 2002-01-22-08:00-+0000 + 8.0h, 2002-01-23-08:00-+0000 + 8.0h, 2002-01-24-08:00-+0000 + 5.0h { overtime 2 } delayed:booking dev1 2002-01-21-08:00-+0000 + 8.0h, 2002-01-22-08:00-+0000 + 8.0h, 2002-01-23-08:00-+0000 + 8.0h, 2002-01-24-08:00-+0000 + 8.0h, 2002-01-25-08:00-+0000 + 8.0h, 2002-01-28-08:00-+0000 + 8.0h, 2002-01-29-08:00-+0000 + 6.0h { overtime 2 } delayed:booking dev2 2002-01-21-08:00-+0000 + 8.0h, 2002-01-22-08:00-+0000 + 8.0h, 2002-01-23-08:00-+0000 + 8.0h, 2002-01-24-08:00-+0000 + 8.0h, 2002-01-25-08:00-+0000 + 8.0h, 2002-01-28-08:00-+0000 + 8.0h, 2002-01-29-08:00-+0000 + 5.0h { overtime 2 } delayed:booking dev3 2002-01-21-08:00-+0000 + 8.0h, 2002-01-22-08:00-+0000 + 8.0h, 2002-01-23-08:00-+0000 + 8.0h, 2002-01-24-08:00-+0000 + 8.0h, 2002-01-25-08:00-+0000 + 8.0h, 2002-01-28-08:00-+0000 + 8.0h, 2002-01-29-08:00-+0000 + 5.0h { overtime 2 } priority 500 projectid acso responsible boss } supplement task AcSo.software { priority 1000 projectid acso responsible boss, dev1 } supplement task AcSo.software.database { booking dev1 2002-01-24-14:00-+0000 + 2.0h, 2002-01-25-08:00-+0000 + 8.0h, 2002-01-28-08:00-+0000 + 8.0h, 2002-01-29-08:00-+0000 + 8.0h, 2002-01-30-08:00-+0000 + 8.0h, 2002-01-31-08:00-+0000 + 8.0h, 2002-02-01-08:00-+0000 + 8.0h, 2002-02-04-08:00-+0000 + 8.0h, 2002-02-05-08:00-+0000 + 8.0h, 2002-02-06-08:00-+0000 + 8.0h, 2002-02-07-08:00-+0000 + 6.0h { overtime 2 } booking dev2 2002-01-24-14:00-+0000 + 2.0h, 2002-01-25-08:00-+0000 + 8.0h, 2002-01-28-08:00-+0000 + 8.0h, 2002-01-29-08:00-+0000 + 8.0h, 2002-01-30-08:00-+0000 + 8.0h, 2002-01-31-08:00-+0000 + 8.0h, 2002-02-01-08:00-+0000 + 8.0h, 2002-02-04-08:00-+0000 + 8.0h, 2002-02-05-08:00-+0000 + 8.0h, 2002-02-06-08:00-+0000 + 8.0h, 2002-02-07-08:00-+0000 + 6.0h { overtime 2 } delayed:booking dev1 2002-01-29-14:00-+0000 + 2.0h, 2002-01-30-08:00-+0000 + 8.0h, 2002-01-31-08:00-+0000 + 8.0h, 2002-02-01-08:00-+0000 + 8.0h, 2002-02-04-08:00-+0000 + 8.0h, 2002-02-05-08:00-+0000 + 8.0h, 2002-02-06-08:00-+0000 + 8.0h, 2002-02-07-08:00-+0000 + 8.0h, 2002-02-08-08:00-+0000 + 8.0h, 2002-02-11-08:00-+0000 + 8.0h, 2002-02-12-08:00-+0000 + 6.0h { overtime 2 } delayed:booking dev2 2002-01-29-14:00-+0000 + 2.0h, 2002-01-30-08:00-+0000 + 8.0h, 2002-01-31-08:00-+0000 + 8.0h, 2002-02-01-08:00-+0000 + 8.0h, 2002-02-04-08:00-+0000 + 8.0h, 2002-02-05-08:00-+0000 + 8.0h, 2002-02-06-08:00-+0000 + 8.0h, 2002-02-07-08:00-+0000 + 8.0h, 2002-02-08-08:00-+0000 + 8.0h, 2002-02-11-08:00-+0000 + 8.0h, 2002-02-12-08:00-+0000 + 6.0h { overtime 2 } priority 1000 projectid acso responsible boss, dev1 } supplement task AcSo.software.gui { booking dev2 2002-02-28-14:00-+0000 + 2.0h, 2002-03-01-08:00-+0000 + 6.0h, 2002-03-04-08:00-+0000 + 6.0h, 2002-03-05-08:00-+0000 + 6.0h, 2002-03-06-08:00-+0000 + 6.0h, 2002-03-07-08:00-+0000 + 6.0h, 2002-03-08-08:00-+0000 + 6.0h, 2002-03-11-08:00-+0000 + 6.0h, 2002-03-12-08:00-+0000 + 6.0h, 2002-03-13-08:00-+0000 + 6.0h, 2002-03-14-08:00-+0000 + 6.0h, 2002-03-15-08:00-+0000 + 6.0h, 2002-03-18-08:00-+0000 + 6.0h, 2002-03-19-08:00-+0000 + 6.0h, 2002-03-20-08:00-+0000 + 6.0h, 2002-03-21-08:00-+0000 + 6.0h, 2002-03-22-08:00-+0000 + 6.0h, 2002-03-25-08:00-+0000 + 6.0h, 2002-03-26-08:00-+0000 + 6.0h, 2002-03-27-08:00-+0000 + 6.0h, 2002-03-28-08:00-+0000 + 5.0h { overtime 2 } booking dev3 2002-02-28-14:00-+0000 + 2.0h, 2002-03-01-08:00-+0000 + 8.0h, 2002-03-04-08:00-+0000 + 8.0h, 2002-03-05-08:00-+0000 + 8.0h, 2002-03-06-08:00-+0000 + 8.0h, 2002-03-07-08:00-+0000 + 8.0h, 2002-03-08-08:00-+0000 + 8.0h, 2002-03-11-08:00-+0000 + 8.0h, 2002-03-12-08:00-+0000 + 8.0h, 2002-03-13-08:00-+0000 + 8.0h, 2002-03-14-08:00-+0000 + 8.0h, 2002-03-15-08:00-+0000 + 8.0h, 2002-03-18-08:00-+0000 + 8.0h, 2002-03-19-08:00-+0000 + 8.0h, 2002-03-20-08:00-+0000 + 8.0h, 2002-03-21-08:00-+0000 + 8.0h, 2002-03-22-08:00-+0000 + 8.0h, 2002-03-25-08:00-+0000 + 8.0h, 2002-03-26-08:00-+0000 + 8.0h, 2002-03-27-08:00-+0000 + 8.0h, 2002-03-28-08:00-+0000 + 5.0h { overtime 2 } delayed:booking dev2 2002-03-05-14:00-+0000 + 2.0h, 2002-03-06-08:00-+0000 + 6.0h, 2002-03-07-08:00-+0000 + 6.0h, 2002-03-08-08:00-+0000 + 6.0h, 2002-03-11-08:00-+0000 + 6.0h, 2002-03-12-08:00-+0000 + 6.0h, 2002-03-13-08:00-+0000 + 6.0h, 2002-03-14-08:00-+0000 + 6.0h, 2002-03-15-08:00-+0000 + 6.0h, 2002-03-18-08:00-+0000 + 6.0h, 2002-03-19-08:00-+0000 + 6.0h, 2002-03-20-08:00-+0000 + 6.0h, 2002-03-21-08:00-+0000 + 6.0h, 2002-03-22-08:00-+0000 + 6.0h, 2002-03-25-08:00-+0000 + 6.0h, 2002-03-26-08:00-+0000 + 6.0h, 2002-03-27-08:00-+0000 + 6.0h, 2002-03-28-08:00-+0000 + 6.0h, 2002-04-01-07:00-+0000 + 6.0h, 2002-04-02-07:00-+0000 + 6.0h, 2002-04-03-07:00-+0000 + 6.0h, 2002-04-04-07:00-+0000 + 6.0h, 2002-04-05-07:00-+0000 + 6.0h, 2002-04-08-07:00-+0000 + 4.0h { overtime 2 } delayed:booking dev3 2002-03-05-14:00-+0000 + 2.0h, 2002-03-06-08:00-+0000 + 8.0h, 2002-03-07-08:00-+0000 + 8.0h, 2002-03-08-08:00-+0000 + 8.0h, 2002-03-11-08:00-+0000 + 8.0h, 2002-03-12-08:00-+0000 + 8.0h, 2002-03-13-08:00-+0000 + 8.0h, 2002-03-14-08:00-+0000 + 8.0h, 2002-03-15-08:00-+0000 + 8.0h, 2002-03-18-08:00-+0000 + 8.0h, 2002-03-19-08:00-+0000 + 8.0h, 2002-03-20-08:00-+0000 + 8.0h, 2002-03-21-08:00-+0000 + 8.0h, 2002-03-22-08:00-+0000 + 8.0h, 2002-03-25-08:00-+0000 + 8.0h, 2002-03-26-08:00-+0000 + 8.0h, 2002-03-27-08:00-+0000 + 8.0h, 2002-03-28-08:00-+0000 + 8.0h, 2002-04-01-07:00-+0000 + 8.0h, 2002-04-02-07:00-+0000 + 8.0h, 2002-04-03-07:00-+0000 + 8.0h, 2002-04-04-07:00-+0000 + 8.0h, 2002-04-05-07:00-+0000 + 8.0h, 2002-04-08-07:00-+0000 + 4.0h { overtime 2 } priority 1000 projectid acso responsible boss, dev1 } supplement task AcSo.software.backend { booking dev1 2002-02-07-14:00-+0000 + 2.0h, 2002-02-08-08:00-+0000 + 8.0h, 2002-02-11-08:00-+0000 + 8.0h, 2002-02-12-08:00-+0000 + 8.0h, 2002-02-13-08:00-+0000 + 8.0h, 2002-02-14-08:00-+0000 + 8.0h, 2002-02-15-08:00-+0000 + 8.0h, 2002-02-18-08:00-+0000 + 8.0h, 2002-02-19-08:00-+0000 + 8.0h, 2002-02-20-08:00-+0000 + 8.0h, 2002-02-21-08:00-+0000 + 8.0h, 2002-02-22-08:00-+0000 + 8.0h, 2002-02-25-08:00-+0000 + 8.0h, 2002-02-26-08:00-+0000 + 8.0h, 2002-02-27-08:00-+0000 + 8.0h, 2002-02-28-08:00-+0000 + 6.0h { overtime 2 } booking dev2 2002-02-07-14:00-+0000 + 2.0h, 2002-02-08-08:00-+0000 + 8.0h, 2002-02-11-08:00-+0000 + 8.0h, 2002-02-12-08:00-+0000 + 8.0h, 2002-02-13-08:00-+0000 + 8.0h, 2002-02-14-08:00-+0000 + 8.0h, 2002-02-15-08:00-+0000 + 8.0h, 2002-02-18-08:00-+0000 + 8.0h, 2002-02-19-08:00-+0000 + 8.0h, 2002-02-20-08:00-+0000 + 8.0h, 2002-02-21-08:00-+0000 + 8.0h, 2002-02-22-08:00-+0000 + 8.0h, 2002-02-25-08:00-+0000 + 8.0h, 2002-02-26-08:00-+0000 + 8.0h, 2002-02-27-08:00-+0000 + 8.0h, 2002-02-28-08:00-+0000 + 6.0h { overtime 2 } delayed:booking dev1 2002-02-12-14:00-+0000 + 2.0h, 2002-02-13-08:00-+0000 + 8.0h, 2002-02-14-08:00-+0000 + 8.0h, 2002-02-15-08:00-+0000 + 8.0h, 2002-02-18-08:00-+0000 + 8.0h, 2002-02-19-08:00-+0000 + 8.0h, 2002-02-20-08:00-+0000 + 8.0h, 2002-02-21-08:00-+0000 + 8.0h, 2002-02-22-08:00-+0000 + 8.0h, 2002-02-25-08:00-+0000 + 8.0h, 2002-02-26-08:00-+0000 + 8.0h, 2002-02-27-08:00-+0000 + 8.0h, 2002-02-28-08:00-+0000 + 8.0h, 2002-03-01-08:00-+0000 + 8.0h, 2002-03-04-08:00-+0000 + 8.0h, 2002-03-05-08:00-+0000 + 6.0h { overtime 2 } delayed:booking dev2 2002-02-12-14:00-+0000 + 2.0h, 2002-02-13-08:00-+0000 + 8.0h, 2002-02-14-08:00-+0000 + 8.0h, 2002-02-15-08:00-+0000 + 8.0h, 2002-02-18-08:00-+0000 + 8.0h, 2002-02-19-08:00-+0000 + 8.0h, 2002-02-20-08:00-+0000 + 8.0h, 2002-02-21-08:00-+0000 + 8.0h, 2002-02-22-08:00-+0000 + 8.0h, 2002-02-25-08:00-+0000 + 8.0h, 2002-02-26-08:00-+0000 + 8.0h, 2002-02-27-08:00-+0000 + 8.0h, 2002-02-28-08:00-+0000 + 8.0h, 2002-03-01-08:00-+0000 + 8.0h, 2002-03-04-08:00-+0000 + 8.0h, 2002-03-05-08:00-+0000 + 6.0h { overtime 2 } complete 95 priority 1000 projectid acso responsible boss, dev1 } supplement task AcSo.test { priority 500 projectid acso responsible boss } supplement task AcSo.test.alpha { booking dev2 2002-03-28-13:00-+0000 + 3.0h, 2002-04-01-07:00-+0000 + 8.0h, 2002-04-02-07:00-+0000 + 8.0h, 2002-04-03-07:00-+0000 + 3.0h { overtime 2 } booking test 2002-03-28-13:00-+0000 + 3.0h, 2002-04-01-07:00-+0000 + 6.0h, 2002-04-02-07:00-+0000 + 6.0h, 2002-04-03-07:00-+0000 + 3.0h { overtime 2 } delayed:booking dev2 2002-04-08-11:00-+0000 + 4.0h, 2002-04-09-07:00-+0000 + 8.0h, 2002-04-10-07:00-+0000 + 8.0h, 2002-04-11-07:00-+0000 + 2.0h { overtime 2 } delayed:booking test 2002-04-08-11:00-+0000 + 4.0h, 2002-04-09-07:00-+0000 + 6.0h, 2002-04-10-07:00-+0000 + 6.0h, 2002-04-11-07:00-+0000 + 2.0h { overtime 2 } note "Hopefully most bugs will be found and fixed here." priority 500 projectid acso responsible boss } supplement task AcSo.test.beta { booking dev1 2002-04-03-10:00-+0000 + 5.0h, 2002-04-04-07:00-+0000 + 8.0h, 2002-04-05-07:00-+0000 + 8.0h, 2002-04-08-07:00-+0000 + 8.0h, 2002-04-09-07:00-+0000 + 8.0h, 2002-04-10-07:00-+0000 + 8.0h, 2002-04-11-07:00-+0000 + 8.0h, 2002-04-12-07:00-+0000 + 8.0h, 2002-04-15-07:00-+0000 + 8.0h, 2002-04-16-07:00-+0000 + 8.0h, 2002-04-17-07:00-+0000 + 8.0h, 2002-04-18-07:00-+0000 + 6.0h { overtime 2 } booking test 2002-04-03-10:00-+0000 + 3.0h, 2002-04-04-07:00-+0000 + 6.0h, 2002-04-05-07:00-+0000 + 6.0h, 2002-04-08-07:00-+0000 + 6.0h, 2002-04-09-07:00-+0000 + 6.0h, 2002-04-10-07:00-+0000 + 6.0h, 2002-04-11-07:00-+0000 + 6.0h, 2002-04-12-07:00-+0000 + 6.0h, 2002-04-15-07:00-+0000 + 6.0h, 2002-04-16-07:00-+0000 + 6.0h, 2002-04-17-07:00-+0000 + 6.0h, 2002-04-18-07:00-+0000 + 6.0h { overtime 2 } delayed:booking dev1 2002-04-11-09:00-+0000 + 6.0h, 2002-04-12-07:00-+0000 + 8.0h, 2002-04-15-07:00-+0000 + 8.0h, 2002-04-16-07:00-+0000 + 8.0h, 2002-04-17-07:00-+0000 + 8.0h, 2002-04-18-07:00-+0000 + 8.0h, 2002-04-19-07:00-+0000 + 8.0h, 2002-04-22-07:00-+0000 + 8.0h, 2002-04-23-07:00-+0000 + 8.0h, 2002-04-24-07:00-+0000 + 8.0h, 2002-04-25-07:00-+0000 + 8.0h, 2002-04-26-07:00-+0000 + 5.0h { overtime 2 } delayed:booking test 2002-04-11-09:00-+0000 + 4.0h, 2002-04-12-07:00-+0000 + 6.0h, 2002-04-15-07:00-+0000 + 6.0h, 2002-04-16-07:00-+0000 + 6.0h, 2002-04-17-07:00-+0000 + 6.0h, 2002-04-18-07:00-+0000 + 6.0h, 2002-04-19-07:00-+0000 + 6.0h, 2002-04-22-07:00-+0000 + 6.0h, 2002-04-23-07:00-+0000 + 6.0h, 2002-04-24-07:00-+0000 + 6.0h, 2002-04-25-07:00-+0000 + 6.0h, 2002-04-26-07:00-+0000 + 5.0h { overtime 2 } priority 500 projectid acso responsible boss } supplement task AcSo.manual { booking dev3 2002-01-24-13:00-+0000 + 3.0h, 2002-01-25-08:00-+0000 + 8.0h, 2002-01-28-08:00-+0000 + 8.0h, 2002-01-29-08:00-+0000 + 8.0h, 2002-01-30-08:00-+0000 + 8.0h, 2002-01-31-08:00-+0000 + 8.0h, 2002-02-05-08:00-+0000 + 8.0h, 2002-02-06-08:00-+0000 + 8.0h, 2002-02-07-08:00-+0000 + 8.0h, 2002-02-08-08:00-+0000 + 8.0h, 2002-02-11-08:00-+0000 + 8.0h, 2002-02-12-08:00-+0000 + 8.0h, 2002-02-13-08:00-+0000 + 8.0h, 2002-02-14-08:00-+0000 + 8.0h, 2002-02-15-08:00-+0000 + 8.0h, 2002-02-18-08:00-+0000 + 8.0h, 2002-02-19-08:00-+0000 + 8.0h, 2002-02-20-08:00-+0000 + 8.0h, 2002-02-21-08:00-+0000 + 8.0h, 2002-02-22-08:00-+0000 + 8.0h, 2002-02-25-08:00-+0000 + 8.0h, 2002-02-26-08:00-+0000 + 2.0h { overtime 2 } booking doc 2002-01-16-08:00-+0000 + 8.0h, 2002-01-17-08:00-+0000 + 8.0h, 2002-01-18-08:00-+0000 + 8.0h, 2002-01-21-08:00-+0000 + 8.0h, 2002-01-22-08:00-+0000 + 8.0h, 2002-01-23-08:00-+0000 + 8.0h, 2002-01-24-08:00-+0000 + 8.0h, 2002-01-25-08:00-+0000 + 8.0h, 2002-01-28-08:00-+0000 + 8.0h, 2002-01-29-08:00-+0000 + 8.0h, 2002-01-30-08:00-+0000 + 8.0h, 2002-01-31-08:00-+0000 + 8.0h, 2002-02-01-08:00-+0000 + 8.0h, 2002-02-04-08:00-+0000 + 8.0h, 2002-02-05-08:00-+0000 + 8.0h, 2002-02-06-08:00-+0000 + 8.0h, 2002-02-07-08:00-+0000 + 8.0h, 2002-02-08-08:00-+0000 + 8.0h, 2002-02-11-08:00-+0000 + 8.0h, 2002-02-12-08:00-+0000 + 8.0h, 2002-02-13-08:00-+0000 + 8.0h, 2002-02-14-08:00-+0000 + 8.0h, 2002-02-15-08:00-+0000 + 8.0h, 2002-02-18-08:00-+0000 + 8.0h, 2002-02-19-08:00-+0000 + 8.0h, 2002-02-20-08:00-+0000 + 8.0h, 2002-02-21-08:00-+0000 + 8.0h, 2002-02-22-08:00-+0000 + 8.0h, 2002-02-25-08:00-+0000 + 8.0h, 2002-02-26-08:00-+0000 + 3.0h { overtime 2 } delayed:booking dev3 2002-01-29-13:00-+0000 + 3.0h, 2002-01-30-08:00-+0000 + 8.0h, 2002-01-31-08:00-+0000 + 8.0h, 2002-02-05-08:00-+0000 + 8.0h, 2002-02-06-08:00-+0000 + 8.0h, 2002-02-07-08:00-+0000 + 8.0h, 2002-02-08-08:00-+0000 + 8.0h, 2002-02-11-08:00-+0000 + 8.0h, 2002-02-12-08:00-+0000 + 8.0h, 2002-02-13-08:00-+0000 + 8.0h, 2002-02-14-08:00-+0000 + 8.0h, 2002-02-15-08:00-+0000 + 8.0h, 2002-02-18-08:00-+0000 + 8.0h, 2002-02-19-08:00-+0000 + 8.0h, 2002-02-20-08:00-+0000 + 8.0h, 2002-02-21-08:00-+0000 + 8.0h, 2002-02-22-08:00-+0000 + 8.0h, 2002-02-25-08:00-+0000 + 8.0h, 2002-02-26-08:00-+0000 + 8.0h, 2002-02-27-08:00-+0000 + 8.0h, 2002-02-28-08:00-+0000 + 8.0h, 2002-03-01-08:00-+0000 + 2.0h { overtime 2 } delayed:booking doc 2002-01-21-08:00-+0000 + 8.0h, 2002-01-22-08:00-+0000 + 8.0h, 2002-01-23-08:00-+0000 + 8.0h, 2002-01-24-08:00-+0000 + 8.0h, 2002-01-25-08:00-+0000 + 8.0h, 2002-01-28-08:00-+0000 + 8.0h, 2002-01-29-08:00-+0000 + 8.0h, 2002-01-30-08:00-+0000 + 8.0h, 2002-01-31-08:00-+0000 + 8.0h, 2002-02-01-08:00-+0000 + 8.0h, 2002-02-04-08:00-+0000 + 8.0h, 2002-02-05-08:00-+0000 + 8.0h, 2002-02-06-08:00-+0000 + 8.0h, 2002-02-07-08:00-+0000 + 8.0h, 2002-02-08-08:00-+0000 + 8.0h, 2002-02-11-08:00-+0000 + 8.0h, 2002-02-12-08:00-+0000 + 8.0h, 2002-02-13-08:00-+0000 + 8.0h, 2002-02-14-08:00-+0000 + 8.0h, 2002-02-15-08:00-+0000 + 8.0h, 2002-02-18-08:00-+0000 + 8.0h, 2002-02-19-08:00-+0000 + 8.0h, 2002-02-20-08:00-+0000 + 8.0h, 2002-02-21-08:00-+0000 + 8.0h, 2002-02-22-08:00-+0000 + 8.0h, 2002-02-25-08:00-+0000 + 8.0h, 2002-02-26-08:00-+0000 + 8.0h, 2002-02-27-08:00-+0000 + 8.0h, 2002-02-28-08:00-+0000 + 8.0h, 2002-03-01-08:00-+0000 + 3.0h { overtime 2 } priority 500 projectid acso responsible boss } supplement task AcSo.deliveries { priority 500 projectid acso responsible boss } supplement task AcSo.deliveries.start { priority 500 projectid acso responsible boss } supplement task AcSo.deliveries.prev { note "All '''major''' features should be usable." priority 500 projectid acso responsible boss } supplement task AcSo.deliveries.beta { note "Fully functional, may contain bugs." priority 500 projectid acso responsible boss } supplement task AcSo.deliveries.done { note "All priority 1 and 2 bugs must be fixed." priority 500 projectid acso responsible boss } supplement resource boss { Phone "x100" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource dev { flags team workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource dev1 { Phone "x362" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource dev2 { Phone "x234" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource dev3 { Phone "x490" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource misc { flags team workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource test { Phone "x666" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource doc { Phone "x482" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/AutoID.tjp0000644000175000017500000000333712614413013024002 0ustar bernatbernatproject prj "Simple Project" "1.0" 2009-10-04-00:00-+0000 - 2009-11-03-10:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj shift _Shift_1 "Foo" { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off shift _Shift_2 "bar" { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } } resource _Resource_1 "Foo" { resource _Resource_2 "Bar" } task _Task_1 "Foo" { task _Task_2 "Bar" { start 2009-10-04-00:00-+0000 milestone scheduled } } supplement task _Task_1 { priority 500 projectid prj } supplement task _Task_1._Task_2 { priority 500 projectid prj } supplement resource _Resource_1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource _Resource_2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Project.tjp0000644000175000017500000000046012614413013024255 0ustar bernatbernatproject prj "Example Project" "1.0" 2007-01-01-00:00-+0000 - 2007-03-09-00:00-+0000 { timezone "America/Denver" scenario plan "Plan" { active yes } } projectids prj task t "Task" { start 2007-01-01-07:00-+0000 milestone scheduled } supplement task t { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Scheduling.tjp0000644000175000017500000000176612614413013024746 0ustar bernatbernatproject prj "Scheduling Example" "1.0" 2005-07-23-00:00-+0000 - 2005-09-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task items "Project breakdown" { task t1 "Task 1" { start 2005-07-25-06:00-+0000 end 2005-08-01-06:00-+0000 scheduling alap scheduled } task t2 "Task 2" { start 2005-07-25-06:00-+0000 end 2005-08-01-06:00-+0000 scheduling asap scheduled } task t3 "Task 3" { start 2005-07-25-06:00-+0000 end 2005-08-01-06:00-+0000 scheduling asap scheduled } task t4 "Task 4" { start 2005-07-25-06:00-+0000 end 2005-08-01-06:00-+0000 scheduling alap scheduled } } supplement task items { priority 500 projectid prj } supplement task items.t1 { priority 500 projectid prj } supplement task items.t2 { priority 500 projectid prj } supplement task items.t3 { priority 500 projectid prj } supplement task items.t4 { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Celltext.tjp0000644000175000017500000000175312614413013024441 0ustar bernatbernatproject celltext "celltext" "1.0" 2007-01-01-00:00-+0000 - 2007-03-01-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids celltext resource tux "Tux" task t "Task" { task s "SubTask" { start 2007-01-01-16:00-+0000 end 2007-01-06-00:00-+0000 scheduling asap scheduled } } supplement task t { priority 500 projectid celltext } supplement task t.s { booking tux 2007-01-01-16:00-+0000 + 8.0h, 2007-01-02-16:00-+0000 + 8.0h, 2007-01-03-16:00-+0000 + 8.0h, 2007-01-04-16:00-+0000 + 8.0h, 2007-01-05-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid celltext } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Timezone.tjp0000644000175000017500000000046712614413013024450 0ustar bernatbernatproject tz "Timezone" "1.0" 2005-06-06-00:00-+0000 - 2005-06-07-00:00-+0000 { timezone "Europe/Athens" scenario plan "Plan Scenario" { active yes } } projectids tz task item "Project" { start 2005-06-06-09:00-+0000 milestone scheduled } supplement task item { priority 500 projectid tz } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Precedes1.tjp0000644000175000017500000000142512614413013024464 0ustar bernatbernatproject prj "P" "1.0" 2003-11-09-00:00-+0000 - 2003-12-24-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids prj task foo1 "foo1" { task foo2 "foo2" { start 2003-12-04-07:00-+0000 milestone scheduled } task foo3 "foo3" { precedes foo1.foo2 start 2003-12-03-16:00-+0000 end 2003-12-04-07:00-+0000 scheduling alap scheduled } } task bar "bar" { precedes foo1.foo2 start 2003-12-02-16:00-+0000 end 2003-12-04-07:00-+0000 scheduling alap scheduled } supplement task foo1 { priority 500 projectid prj } supplement task foo1.foo2 { priority 500 projectid prj } supplement task foo1.foo3 { priority 500 projectid prj } supplement task bar { priority 500 projectid prj } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Allocate-1.tjp0000644000175000017500000000527212614413013024537 0ustar bernatbernatproject allocate "allocate" "1.0" 2003-06-05-00:00-+0000 - 2003-07-05-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids allocate resource r1 "Resource 1" resource r2 "Resource 2" task t1 "Task 1" { task t2 "Task 2" { start 2003-06-19-15:00-+0000 end 2003-07-02-23:00-+0000 scheduling asap scheduled } task t3 "Task 3" { start 2003-06-05-15:00-+0000 end 2003-06-18-23:00-+0000 scheduling asap scheduled } task m1 "Milestone 1" { start 2003-06-05-06:00-+0000 milestone scheduled } } supplement task t1 { priority 500 projectid allocate } supplement task t1.t2 { booking r1 2003-06-19-15:00-+0000 + 8.0h, 2003-06-20-15:00-+0000 + 8.0h, 2003-06-23-15:00-+0000 + 8.0h, 2003-06-24-15:00-+0000 + 8.0h, 2003-06-25-15:00-+0000 + 8.0h, 2003-06-26-15:00-+0000 + 8.0h, 2003-06-27-15:00-+0000 + 8.0h, 2003-06-30-15:00-+0000 + 8.0h, 2003-07-01-15:00-+0000 + 8.0h, 2003-07-02-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid allocate } supplement task t1.t3 { booking r1 2003-06-05-15:00-+0000 + 8.0h, 2003-06-06-15:00-+0000 + 8.0h, 2003-06-09-15:00-+0000 + 8.0h, 2003-06-10-15:00-+0000 + 8.0h, 2003-06-11-15:00-+0000 + 8.0h, 2003-06-12-15:00-+0000 + 8.0h, 2003-06-13-15:00-+0000 + 8.0h, 2003-06-16-15:00-+0000 + 8.0h, 2003-06-17-15:00-+0000 + 8.0h, 2003-06-18-15:00-+0000 + 8.0h { overtime 2 } booking r2 2003-06-05-15:00-+0000 + 8.0h, 2003-06-06-15:00-+0000 + 8.0h, 2003-06-09-15:00-+0000 + 8.0h, 2003-06-10-15:00-+0000 + 8.0h, 2003-06-11-15:00-+0000 + 8.0h, 2003-06-12-15:00-+0000 + 8.0h, 2003-06-13-15:00-+0000 + 8.0h, 2003-06-16-15:00-+0000 + 8.0h, 2003-06-17-15:00-+0000 + 8.0h, 2003-06-18-15:00-+0000 + 8.0h { overtime 2 } priority 500 projectid allocate } supplement task t1.m1 { priority 500 projectid allocate } supplement resource r1 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Supplement.tjp0000644000175000017500000000155112614413013025005 0ustar bernatbernatproject prj "Test Project" "1.0" 2000-01-01-00:00-+0000 - 2000-01-04-00:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } flags important projectids prj resource joe "Joe" task top "Top Task" { task sub "Sub Task" { start 2000-01-01-00:00-+0000 end 2000-01-04-00:00-+0000 scheduling asap scheduled } } supplement task top { flags important priority 500 projectid prj } supplement task top.sub { booking joe 2000-01-03-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource joe { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Booking.tjp0000644000175000017500000000222112614413013024234 0ustar bernatbernatproject project "Simple Project" "1.0" 2007-01-05-00:00-+0000 - 2007-02-04-10:00-+0000 { timezone "America/Denver" scenario plan "Plan Scenario" { active yes } } projectids project resource tux "Tux" task test "Testing" { start 2007-01-08-20:00-+0000 end 2007-01-24-22:00-+0000 scheduling asap scheduled } supplement task test { booking tux 2007-01-08-20:00-+0000 + 4.0h, 2007-01-09-20:00-+0000 + 4.0h, 2007-01-11-15:00-+0000 + 10.0h, 2007-01-15-16:00-+0000 + 8.0h, 2007-01-16-16:00-+0000 + 8.0h, 2007-01-17-16:00-+0000 + 8.0h, 2007-01-18-16:00-+0000 + 8.0h, 2007-01-19-16:00-+0000 + 8.0h, 2007-01-22-16:00-+0000 + 8.0h, 2007-01-23-16:00-+0000 + 8.0h, 2007-01-24-16:00-+0000 + 6.0h { overtime 2 } priority 500 projectid project } supplement resource tux { workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/Export-Reports/refs/Niku.tjp0000644000175000017500000001125112614413013023555 0ustar bernatbernatproject prj "Niku Test" "1.0" 2010-02-01-00:00-+0000 - 2010-05-03-06:00-+0000 { timezone "America/Denver" extend resource{ text ClarityRID "Clarity Resource ID" } extend task{ text ClarityPID "Clarity PID" text ClarityPName "Clarity Project Name" } scenario plan "Plan Scenario" { active yes } } projectids prj resource r1 "r1" resource r2 "r2" resource r3 "r3" task _Task_1 "T1" { start 2010-02-01-16:00-+0000 end 2010-03-06-00:00-+0000 scheduling asap scheduled } task t2 "T2" { start 2010-02-01-16:00-+0000 end 2010-02-13-00:00-+0000 scheduling asap scheduled } task _Task_3 "T3" { depends t2 start 2010-02-22-16:00-+0000 end 2010-03-06-00:00-+0000 scheduling asap scheduled } task _Task_4 "T4" { start 2010-02-01-16:00-+0000 end 2010-02-20-00:00-+0000 scheduling asap scheduled } supplement task _Task_1 { ClarityPID "p1" ClarityPName "Project 1" booking r1 2010-02-01-16:00-+0000 + 8.0h, 2010-02-02-16:00-+0000 + 8.0h, 2010-02-03-16:00-+0000 + 8.0h, 2010-02-04-16:00-+0000 + 8.0h, 2010-02-05-16:00-+0000 + 8.0h, 2010-02-08-16:00-+0000 + 8.0h, 2010-02-09-16:00-+0000 + 8.0h, 2010-02-10-16:00-+0000 + 8.0h, 2010-02-11-16:00-+0000 + 8.0h, 2010-02-12-16:00-+0000 + 8.0h, 2010-02-15-16:00-+0000 + 8.0h, 2010-02-16-16:00-+0000 + 8.0h, 2010-02-17-16:00-+0000 + 8.0h, 2010-02-18-16:00-+0000 + 8.0h, 2010-02-19-16:00-+0000 + 8.0h, 2010-02-22-16:00-+0000 + 8.0h, 2010-02-23-16:00-+0000 + 8.0h, 2010-02-24-16:00-+0000 + 8.0h, 2010-02-25-16:00-+0000 + 8.0h, 2010-02-26-16:00-+0000 + 8.0h, 2010-03-01-16:00-+0000 + 8.0h, 2010-03-02-16:00-+0000 + 8.0h, 2010-03-03-16:00-+0000 + 8.0h, 2010-03-04-16:00-+0000 + 8.0h, 2010-03-05-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task t2 { ClarityPID "p1" ClarityPName "Project 1" booking r2 2010-02-01-16:00-+0000 + 8.0h, 2010-02-02-16:00-+0000 + 8.0h, 2010-02-03-16:00-+0000 + 8.0h, 2010-02-04-16:00-+0000 + 8.0h, 2010-02-05-16:00-+0000 + 8.0h, 2010-02-08-16:00-+0000 + 8.0h, 2010-02-09-16:00-+0000 + 8.0h, 2010-02-10-16:00-+0000 + 8.0h, 2010-02-11-16:00-+0000 + 8.0h, 2010-02-12-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task _Task_3 { ClarityPID "p2" ClarityPName "Project 2" booking r2 2010-02-22-16:00-+0000 + 8.0h, 2010-02-23-16:00-+0000 + 8.0h, 2010-02-24-16:00-+0000 + 8.0h, 2010-02-25-16:00-+0000 + 8.0h, 2010-02-26-16:00-+0000 + 8.0h, 2010-03-01-16:00-+0000 + 8.0h, 2010-03-02-16:00-+0000 + 8.0h, 2010-03-03-16:00-+0000 + 8.0h, 2010-03-04-16:00-+0000 + 8.0h, 2010-03-05-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement task _Task_4 { ClarityPID "p2" ClarityPName "Project 2" booking r3 2010-02-01-16:00-+0000 + 8.0h, 2010-02-02-16:00-+0000 + 8.0h, 2010-02-03-16:00-+0000 + 8.0h, 2010-02-04-16:00-+0000 + 8.0h, 2010-02-05-16:00-+0000 + 8.0h, 2010-02-08-16:00-+0000 + 8.0h, 2010-02-09-16:00-+0000 + 8.0h, 2010-02-10-16:00-+0000 + 8.0h, 2010-02-11-16:00-+0000 + 8.0h, 2010-02-12-16:00-+0000 + 8.0h, 2010-02-15-16:00-+0000 + 8.0h, 2010-02-16-16:00-+0000 + 8.0h, 2010-02-17-16:00-+0000 + 8.0h, 2010-02-18-16:00-+0000 + 8.0h, 2010-02-19-16:00-+0000 + 8.0h { overtime 2 } priority 500 projectid prj } supplement resource r1 { ClarityRID "r1" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r2 { ClarityRID "r2" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } supplement resource r3 { ClarityRID "r3" workinghours sun off workinghours mon 9:00 - 17:00 workinghours tue 9:00 - 17:00 workinghours wed 9:00 - 17:00 workinghours thu 9:00 - 17:00 workinghours fri 9:00 - 17:00 workinghours sat off } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/0000755000175000017500000000000012614413013020343 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/CSV-Reports/targets.tjp0000644000175000017500000000034212614413013022532 0ustar bernatbernatproject "targets" "1.0" 2007-12-16 +3m { timezone 'UTC' } task t1 "T1" task t2 "T2" { depends !t1 } task t3 "T3" { depends !t1 } task t4 "T4" { depends !t3 } taskreport '.' { formats csv columns name, targets } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/sortBy_plan.start.down.tjp0000644000175000017500000000044112614413013025457 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m { workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 } include "project-1.tji" taskreport csv "." { formats csv timezone "Europe/Amsterdam" columns start, bsi, id timeformat "%Y-%m-%d-%H:%M:%S-%z" sorttasks plan.start.down, id.up } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/sortBy_duration.down.tjp0000644000175000017500000000043712614413013025223 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m { workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 } include "project-1.tji" taskreport csv "." { formats csv timezone "Europe/Amsterdam" columns duration, id timeformat "%Y-%m-%d-%H:%M:%S" sorttasks plan.duration.down, id.up } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/Leave.tjp0000644000175000017500000000140212614413013022113 0ustar bernatbernatproject "Annual Leave" 2011-12-19 +1y { now 2012-07-01 } leaves holiday "Christmas" 2011-12-24 +3d, holiday "New Year" 2011-12-31 +3d shift s1 "Shift 1" { leaves annual 2011-12-19 +3w, special 2012-01-12 +1d } resource team "Team" { leaveallowances annual 2011-12-19 20d leaves holiday 2012-01-06 resource r1 "R1" { leaves annual 2011-12-19 +3w, special 2012-01-12 +1d } resource r2 "R2" { leaveallowances annual 2012-06-01 -10d leaves sick 2012-01-04 +2d, unpaid 2012-01-10 +3d } } resource r3 "R3" { shifts s1 2011-12-19 +3w leaves sick 2012-01-04 +2d } task "foo" resourcereport "." { formats csv columns name, annualleave, annualleavebalance, sickleave, specialleave, unpaidleave } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/celltext.tjp0000644000175000017500000000032012614413013022701 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m include "project-1.tji" taskreport csv "." { formats csv timezone "Europe/Amsterdam" columns bsi, name { celltext 1 "<-query-> (<-query attribute='id'->)" } } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/weekly.tjp0000644000175000017500000000045612614413013022367 0ustar bernatbernatproject "Weekly" "1.0" 2007-12-16 +3m { timezone 'UTC' workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 } include "project-1.tji" resourcereport '.' { formats csv timezone "Europe/Amsterdam" columns name, weekly, duration, weekly, effort period 2007-12-31-0:00-+0100 + 3w hidetask 0 } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/sortByTree.tjp0000644000175000017500000000036212614413013023165 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m { workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 } include "project-1.tji" taskreport cvs "." { formats csv timezone "Europe/Amsterdam" columns bsi, name, id, duration sorttasks tree } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/resourcereport.tjp0000644000175000017500000000034012614413013024142 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m include "project-1.tji" resourcereport csv "." { formats csv timezone "Europe/Amsterdam" columns bsi, name, effort, weekly timeformat "%Y-%m-%d-%H:%M:%S" loadunit days } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/alert.tjp0000644000175000017500000000016112614413013022167 0ustar bernatbernatproject "Alert" 2011-03-07 +2m include "project-1.tji" taskreport '.' { formats csv columns name, alert } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/niku.tjp0000644000175000017500000000276212614413013022037 0ustar bernatbernatproject "Niku Test" 2010-02-01 +3m { extend task { text ClarityPID "Clarity PID" text ClarityPName "Clarity Project Name" } extend resource { text ClarityRID "Clarity Resource ID" } } # The ClarityPID and ClarityPName must be always kept in sync. The # easiest way to achieve this, is by using such macros. macro PID_p1 [ ClarityPID "p1" ClarityPName "Project 1" ] macro PID_p2 [ ClarityPID "p2" ClarityPName "Project 2" ] macro PID_p3 [ ClarityPID "p3" ClarityPName "Project 3" ] macro Resource [ resource ${1} "${1}" { ClarityRID "${1}" } ] ${Resource "r1"} ${Resource "r2"} supplement resource r2 { vacation 2010-02-15 +1w } ${Resource "r3"} task "T1" { allocate r1 effort 5w ${PID_p1} } task t2 "T2" { allocate r2 effort 10d ${PID_p1} } task "T3" { depends !t2 allocate r2 effort 10d ${PID_p2} } task "T4" { allocate r3 effort 3w ${PID_p2} } nikureport "." { formats csv headline "This is a test report" period 2010-02-01-8:00 - %{2010-03-01 -6h} timeoff "vacations" "Vacation time" # Depending on your Clarity configuration, you may need to add this # CustomInformation section in the report. It's raw XML code and # will be embedded into each section of the resulting # report. This is just an example and it must be customized to work! title -8<- foo_active foo_eng ->8- } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/taskreport.tjp0000644000175000017500000000045612614413013023265 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m { workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 } include "project-1.tji" taskreport csv "." { formats csv timezone 'Europe/Amsterdam' columns bsi, name, start, end, duration, effort, weekly timeformat "%Y-%m-%d-%H:%M:%S-%z" loadunit days } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/headcount.tjp0000644000175000017500000000074512614413013023042 0ustar bernatbernatproject "Headcount Test" 2011-12-12 +2m resource r1 "R1" { efficiency 0.7 } resource "T1" { resource r2 "R2" { efficiency 0.0 } resource r3 "R3" { efficiency 2.0 vacation 2012-01-01 +10d } } task "Project" { task "Foo" { effort 40d allocate r1, r2, r3 } task "Bar" { effort 40d allocate r3, r1 } } taskreport "." { formats csv columns name, efficiency, headcount, weekly { celltext 1 "<-query attribute='headcount'->" } } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/taskreport_with_resources.tjp0000644000175000017500000000047712614413013026415 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m { workinghours mon - fri 9:00 - 12:00, 13:00 - 18:00 } include "project-1.tji" taskreport csv "." { formats csv timezone "Europe/Amsterdam" columns bsi, name, start, end, duration, effort, weekly timeformat "%Y-%m-%d-%H:%M:%S-%z" loadunit days hideresource 0 } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/efficiency.tjp0000644000175000017500000000041612614413013023167 0ustar bernatbernatproject "Efficiency Test" 2011-12-12 +2m resource "R1" { efficiency 0.7 } resource "T1" { resource "R2" { efficiency 0.0 } resource "R3" { efficiency 2.0 } } task "Foo" resourcereport "." { formats csv columns name, efficiency, fte, headcount } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/project-1.tji0000644000175000017500000000172612614413013022665 0ustar bernatbernatresource jill "Jill" resource joe "Joe" resource jack "Jack" task plan "Plan Work" { start ${projectstart} task plan_a "Plan A" { effort 2w allocate joe } task plan_b "Plan B" { effort 1.5w allocate jill } task plan_c "Plan C" { depends !plan_a allocate jill effort 1w } } task execute "Execute Work" { depends !plan task ex1 "Step 1" { allocate jill effort 4w } task ex2 "Step 2" { task ex2_1 "Step 2.1" { allocate joe effort 2d } task ex2_2 "Step 2.2" { depends !!ex1 effort 2w allocate jack, jill journalentry %{${projectstart} +2d} "Red" { alert red } } } task ms1 "Milestone 1" { depends !ex2 } task ex3 "Step 3" { depends execute.ex2.ex2_1 effort 12d allocate jack } task ex4 "Step 4" { depends !ex1 effort 2d allocate joe, jack } } task check "Check Work" { depends !execute allocate jill effort 1w } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/sortBy_effort.up.tjp0000644000175000017500000000034412614413013024335 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m include "project-1.tji" taskreport csv "." { formats csv timezone "Europe/Amsterdam" columns effort, bsi, id timeformat "%Y-%m-%d-%H:%M:%S" sorttasks plan.effort.up, id.up } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/0000755000175000017500000000000012614413013021302 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/sortBy_plan.start.down.csv0000644000175000017500000000114612614413013026417 0ustar bernatbernat"Start";"BSI";"Id" "2008-02-11-10:00:00-+0100";3;"check" "2008-02-08-19:00:00-+0100";2.3;"execute.ms1" "2008-02-04-10:00:00-+0100";"2.2.2";"execute.ex2.ex2_2" "2008-02-04-10:00:00-+0100";2.5;"execute.ex4" "2008-01-09-10:00:00-+0100";2.4;"execute.ex3" "2008-01-07-10:00:00-+0100";2;"execute" "2008-01-07-10:00:00-+0100";2.1;"execute.ex1" "2008-01-07-10:00:00-+0100";2.2;"execute.ex2" "2008-01-07-10:00:00-+0100";"2.2.1";"execute.ex2.ex2_1" "2007-12-31-10:00:00-+0100";1.3;"plan.plan_c" "2007-12-17-10:00:00-+0100";1.1;"plan.plan_a" "2007-12-17-10:00:00-+0100";1.2;"plan.plan_b" "2007-12-16-01:00:00-+0100";1;"plan" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/alert.csv0000644000175000017500000000045412614413013023131 0ustar bernatbernat"Name";"Alert" "Plan Work";"Green" " Plan A";" Green" " Plan B";" Green" " Plan C";" Green" "Execute Work";"Red" " Step 1";" Green" " Step 2";" Red" " Step 2.1";" Green" " Step 2.2";" Red" " Step 3";" Green" " Step 4";" Green" " Milestone 1";" Green" "Check Work";"Green" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/resourcereport.csv0000644000175000017500000000074312614413013025106 0ustar bernatbernat"BSI";"Name";"Effort";"2007-12-17-00:00:00";"2007-12-24-00:00:00";"2007-12-31-00:00:00";"2008-01-07-00:00:00";"2008-01-14-00:00:00";"2008-01-21-00:00:00";"2008-01-28-00:00:00";"2008-02-04-00:00:00";"2008-02-11-00:00:00";"2008-02-18-00:00:00";"2008-02-25-00:00:00";"2008-03-03-00:00:00";"2008-03-10-00:00:00" 3;"Jack";17.0;"";"";"";3.0;5.0;4.0;"";5.0;"";"";"";"";"" 1;"Jill";42.5;5.0;2.5;5.0;5.0;5.0;5.0;5.0;5.0;5.0;"";"";"";"" 2;"Joe";14.0;5.0;5.0;"";2.0;"";"";"";2.0;"";"";"";"";"" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv0000644000175000017500000000270412614413013027345 0ustar bernatbernat"BSI";"Name";"Effort";"2007-12-10-00:00:00";"2007-12-17-00:00:00";"2007-12-24-00:00:00";"2007-12-31-00:00:00";"2008-01-07-00:00:00";"2008-01-14-00:00:00";"2008-01-21-00:00:00";"2008-01-28-00:00:00";"2008-02-04-00:00:00";"2008-02-11-00:00:00";"2008-02-18-00:00:00" 3;"Jack";17.0;"";"";"";"";3.0;5.0;4.0;"";5.0;"";"" 2;" Execute Work";17.0;"";"";"";"";3.0;5.0;4.0;"";5.0;"";"" 2.2;" Step 2";5.0;"";"";"";"";"";"";"";"";5.0;"";"" " 2.2.2";" Step 2.2";5.0;"";"";"";"";"";"";"";"";5.0;"";"" 2.4;" Step 3";12.0;"";"";"";"";3.0;5.0;4.0;"";"";"";"" 1;"Jill";42.5;"";5.0;2.5;5.0;5.0;5.0;5.0;5.0;5.0;5.0;"" 1;" Plan Work";12.5;"";5.0;2.5;5.0;"";"";"";"";"";"";"" 1.2;" Plan B";7.5;"";5.0;2.5;"";"";"";"";"";"";"";"" 1.3;" Plan C";5.0;"";"";"";5.0;"";"";"";"";"";"";"" 2;" Execute Work";25.0;"";"";"";"";5.0;5.0;5.0;5.0;5.0;"";"" 2.1;" Step 1";20.0;"";"";"";"";5.0;5.0;5.0;5.0;"";"";"" 2.2;" Step 2";5.0;"";"";"";"";"";"";"";"";5.0;"";"" " 2.2.2";" Step 2.2";5.0;"";"";"";"";"";"";"";"";5.0;"";"" 3;" Check Work";5.0;"";"";"";"";"";"";"";"";"";5.0;"" 2;"Joe";14.0;"";5.0;5.0;"";2.0;"";"";"";2.0;"";"" 1;" Plan Work";10.0;"";5.0;5.0;"";"";"";"";"";"";"";"" 1.1;" Plan A";10.0;"";5.0;5.0;"";"";"";"";"";"";"";"" 2;" Execute Work";4.0;"";"";"";"";2.0;"";"";"";2.0;"";"" 2.2;" Step 2";2.0;"";"";"";"";2.0;"";"";"";"";"";"" " 2.2.1";" Step 2.1";2.0;"";"";"";"";2.0;"";"";"";"";"";"" 2.5;" Step 4";2.0;"";"";"";"";"";"";"";"";2.0;"";"" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/sortByTree.csv0000644000175000017500000000074112614413013024123 0ustar bernatbernat"BSI";"Name";"Id";"Duration" 1;"Plan Work";"plan";19.8 1.1;" Plan A";"plan.plan_a";11.4 1.2;" Plan B";"plan.plan_b";9.2 1.3;" Plan C";"plan.plan_c";4.4 2;"Execute Work";"execute";32.4 2.1;" Step 1";"execute.ex1";25.4 2.2;" Step 2";"execute.ex2";32.4 "2.2.1";" Step 2.1";"execute.ex2.ex2_1";1.4 "2.2.2";" Step 2.2";"execute.ex2.ex2_2";4.4 2.3;" Milestone 1";"execute.ms1";0.0 2.4;" Step 3";"execute.ex3";15.4 2.5;" Step 4";"execute.ex4";1.4 3;"Check Work";"check";4.4 taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/weekly.csv0000644000175000017500000000115012614413013023314 0ustar bernatbernat"Name";"2007-12-31";"2008-01-07";"2008-01-14";"Duration";"2007-12-31";"2008-01-07";"2008-01-14";"Effort" "Jack";"";3.0;5.0;"";"";3.0;5.0;8.0 " Execute Work";"";3.0;5.0;32.4;"";3.0;5.0;8.0 " Step 3";"";3.0;5.0;15.4;"";3.0;5.0;8.0 "Jill";5.0;5.0;5.0;"";5.0;5.0;5.0;15.0 " Plan Work";5.0;"";"";19.8;5.0;"";"";5.0 " Plan C";5.0;"";"";4.4;5.0;"";"";5.0 " Execute Work";"";5.0;5.0;32.4;"";5.0;5.0;10.0 " Step 1";"";5.0;5.0;25.4;"";5.0;5.0;10.0 "Joe";"";2.0;"";"";"";2.0;"";2.0 " Execute Work";"";2.0;"";32.4;"";2.0;"";2.0 " Step 2";"";2.0;"";32.4;"";2.0;"";2.0 " Step 2.1";"";2.0;"";1.4;"";2.0;"";2.0 taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/targets.csv0000644000175000017500000000015112614413013023465 0ustar bernatbernat"Name";"Targets" "T1";"T2 (t2) 2007-12-16, T4 (t4) 2007-12-16" "T2";"" "T3";"T4 (t4) 2007-12-16" "T4";"" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/inputs.csv0000644000175000017500000000015612614413013023343 0ustar bernatbernat"Name";"Inputs" "T1";"" "T2";"T1 (t1) 2007-12-16" "T3";"" "T4";"T3 (t3) 2007-12-16" "T5";"T3 (t3) 2007-12-16" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/headcount.csv0000644000175000017500000000046012614413013023771 0ustar bernatbernat"Name";"Efficiency";"Headcount";"2011-12-05";"2011-12-12";"2011-12-19";"2011-12-26";"2012-01-02";"2012-01-09";"2012-01-16";"2012-01-23";"2012-01-30" "Project";"";3.0;0.0;3.0;3.0;3.0;1.0;3.0;3.0;3.0;0.0 " Foo";"";3.0;0.0;0.0;0.0;3.0;1.0;3.0;3.0;3.0;0.0 " Bar";"";3.0;0.0;3.0;3.0;3.0;0.0;0.0;0.0;0.0;0.0 taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/sortBy_effort.up.csv0000644000175000017500000000046612614413013025277 0ustar bernatbernat"Effort";"BSI";"Id" 0.0;2.3;"execute.ms1" 2.0;"2.2.1";"execute.ex2.ex2_1" 2.0;2.5;"execute.ex4" 5.0;3;"check" 5.0;1.3;"plan.plan_c" 7.5;1.2;"plan.plan_b" 10.0;"2.2.2";"execute.ex2.ex2_2" 10.0;1.1;"plan.plan_a" 12.0;2.2;"execute.ex2" 12.0;2.4;"execute.ex3" 20.0;2.1;"execute.ex1" 22.5;1;"plan" 46.0;2;"execute" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/sortBy_duration.down.csv0000644000175000017500000000037312614413013026157 0ustar bernatbernat"Duration";"Id" 32.4;"execute" 32.4;"execute.ex2" 25.4;"execute.ex1" 19.8;"plan" 15.4;"execute.ex3" 11.4;"plan.plan_a" 9.2;"plan.plan_b" 4.4;"check" 4.4;"execute.ex2.ex2_2" 4.4;"plan.plan_c" 1.4;"execute.ex2.ex2_1" 1.4;"execute.ex4" 0.0;"execute.ms1" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/taskreport.csv0000644000175000017500000000352612614413013024223 0ustar bernatbernat"BSI";"Name";"Start";"End";"Duration";"Effort";"2007-12-10-00:00:00-+0100";"2007-12-17-00:00:00-+0100";"2007-12-24-00:00:00-+0100";"2007-12-31-00:00:00-+0100";"2008-01-07-00:00:00-+0100";"2008-01-14-00:00:00-+0100";"2008-01-21-00:00:00-+0100";"2008-01-28-00:00:00-+0100";"2008-02-04-00:00:00-+0100";"2008-02-11-00:00:00-+0100";"2008-02-18-00:00:00-+0100" 1;"Plan Work";"2007-12-16-01:00:00-+0100";"2008-01-04-19:00:00-+0100";19.8;22.5;"";10.0;7.5;5.0;"";"";"";"";"";"";"" 1.1;" Plan A";"2007-12-17-10:00:00-+0100";"2007-12-28-19:00:00-+0100";11.4;10.0;"";5.0;5.0;"";"";"";"";"";"";"";"" 1.2;" Plan B";"2007-12-17-10:00:00-+0100";"2007-12-26-15:00:00-+0100";9.2;7.5;"";5.0;2.5;"";"";"";"";"";"";"";"" 1.3;" Plan C";"2007-12-31-10:00:00-+0100";"2008-01-04-19:00:00-+0100";4.4;5.0;"";"";"";5.0;"";"";"";"";"";"";"" 2;"Execute Work";"2008-01-07-10:00:00-+0100";"2008-02-08-19:00:00-+0100";32.4;46.0;"";"";"";"";10.0;10.0;9.0;5.0;12.0;"";"" 2.1;" Step 1";"2008-01-07-10:00:00-+0100";"2008-02-01-19:00:00-+0100";25.4;20.0;"";"";"";"";5.0;5.0;5.0;5.0;"";"";"" 2.2;" Step 2";"2008-01-07-10:00:00-+0100";"2008-02-08-19:00:00-+0100";32.4;12.0;"";"";"";"";2.0;"";"";"";10.0;"";"" "2.2.1";" Step 2.1";"2008-01-07-10:00:00-+0100";"2008-01-08-19:00:00-+0100";1.4;2.0;"";"";"";"";2.0;"";"";"";"";"";"" "2.2.2";" Step 2.2";"2008-02-04-10:00:00-+0100";"2008-02-08-19:00:00-+0100";4.4;10.0;"";"";"";"";"";"";"";"";10.0;"";"" 2.4;" Step 3";"2008-01-09-10:00:00-+0100";"2008-01-24-19:00:00-+0100";15.4;12.0;"";"";"";"";3.0;5.0;4.0;"";"";"";"" 2.5;" Step 4";"2008-02-04-10:00:00-+0100";"2008-02-05-19:00:00-+0100";1.4;2.0;"";"";"";"";"";"";"";"";2.0;"";"" 2.3;" Milestone 1";"2008-02-08-19:00:00-+0100";"2008-02-08-19:00:00-+0100";0.0;0.0;"";"";"";"";"";"";"";"";"";"";"" 3;"Check Work";"2008-02-11-10:00:00-+0100";"2008-02-15-19:00:00-+0100";4.4;5.0;"";"";"";"";"";"";"";"";"";5.0;"" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/Leave.csv0000644000175000017500000000031012614413013023045 0ustar bernatbernat"Name";"Annual Leave";"Annual Leave Balance";"Sick Leave";"Special Leave";"Unpaid Leave" "R3";11.0;-11.0;2.0;0.0;0.0 "Team";12.0;"";2.0;1.0;3.0 " R1";12.0;8.0;0.0;1.0;0.0 " R2";0.0;10.0;2.0;0.0;3.0 taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv0000644000175000017500000000563312614413013027351 0ustar bernatbernat"BSI";"Name";"Start";"End";"Duration";"Effort";"2007-12-10-00:00:00-+0100";"2007-12-17-00:00:00-+0100";"2007-12-24-00:00:00-+0100";"2007-12-31-00:00:00-+0100";"2008-01-07-00:00:00-+0100";"2008-01-14-00:00:00-+0100";"2008-01-21-00:00:00-+0100";"2008-01-28-00:00:00-+0100";"2008-02-04-00:00:00-+0100";"2008-02-11-00:00:00-+0100";"2008-02-18-00:00:00-+0100" 1;"Plan Work";"2007-12-16-01:00:00-+0100";"2008-01-04-19:00:00-+0100";19.8;22.5;"";10.0;7.5;5.0;"";"";"";"";"";"";"" 1;" Jill";"";"";"";12.5;"";5.0;2.5;5.0;"";"";"";"";"";"";"" 2;" Joe";"";"";"";10.0;"";5.0;5.0;"";"";"";"";"";"";"";"" 1.1;" Plan A";"2007-12-17-10:00:00-+0100";"2007-12-28-19:00:00-+0100";11.4;10.0;"";5.0;5.0;"";"";"";"";"";"";"";"" 2;" Joe";"";"";"";10.0;"";5.0;5.0;"";"";"";"";"";"";"";"" 1.2;" Plan B";"2007-12-17-10:00:00-+0100";"2007-12-26-15:00:00-+0100";9.2;7.5;"";5.0;2.5;"";"";"";"";"";"";"";"" 1;" Jill";"";"";"";7.5;"";5.0;2.5;"";"";"";"";"";"";"";"" 1.3;" Plan C";"2007-12-31-10:00:00-+0100";"2008-01-04-19:00:00-+0100";4.4;5.0;"";"";"";5.0;"";"";"";"";"";"";"" 1;" Jill";"";"";"";5.0;"";"";"";5.0;"";"";"";"";"";"";"" 2;"Execute Work";"2008-01-07-10:00:00-+0100";"2008-02-08-19:00:00-+0100";32.4;46.0;"";"";"";"";10.0;10.0;9.0;5.0;12.0;"";"" 3;" Jack";"";"";"";17.0;"";"";"";"";3.0;5.0;4.0;"";5.0;"";"" 1;" Jill";"";"";"";25.0;"";"";"";"";5.0;5.0;5.0;5.0;5.0;"";"" 2;" Joe";"";"";"";4.0;"";"";"";"";2.0;"";"";"";2.0;"";"" 2.1;" Step 1";"2008-01-07-10:00:00-+0100";"2008-02-01-19:00:00-+0100";25.4;20.0;"";"";"";"";5.0;5.0;5.0;5.0;"";"";"" 1;" Jill";"";"";"";20.0;"";"";"";"";5.0;5.0;5.0;5.0;"";"";"" 2.2;" Step 2";"2008-01-07-10:00:00-+0100";"2008-02-08-19:00:00-+0100";32.4;12.0;"";"";"";"";2.0;"";"";"";10.0;"";"" 3;" Jack";"";"";"";5.0;"";"";"";"";"";"";"";"";5.0;"";"" 1;" Jill";"";"";"";5.0;"";"";"";"";"";"";"";"";5.0;"";"" 2;" Joe";"";"";"";2.0;"";"";"";"";2.0;"";"";"";"";"";"" "2.2.1";" Step 2.1";"2008-01-07-10:00:00-+0100";"2008-01-08-19:00:00-+0100";1.4;2.0;"";"";"";"";2.0;"";"";"";"";"";"" 2;" Joe";"";"";"";2.0;"";"";"";"";2.0;"";"";"";"";"";"" "2.2.2";" Step 2.2";"2008-02-04-10:00:00-+0100";"2008-02-08-19:00:00-+0100";4.4;10.0;"";"";"";"";"";"";"";"";10.0;"";"" 3;" Jack";"";"";"";5.0;"";"";"";"";"";"";"";"";5.0;"";"" 1;" Jill";"";"";"";5.0;"";"";"";"";"";"";"";"";5.0;"";"" 2.4;" Step 3";"2008-01-09-10:00:00-+0100";"2008-01-24-19:00:00-+0100";15.4;12.0;"";"";"";"";3.0;5.0;4.0;"";"";"";"" 3;" Jack";"";"";"";12.0;"";"";"";"";3.0;5.0;4.0;"";"";"";"" 2.5;" Step 4";"2008-02-04-10:00:00-+0100";"2008-02-05-19:00:00-+0100";1.4;2.0;"";"";"";"";"";"";"";"";2.0;"";"" 2;" Joe";"";"";"";2.0;"";"";"";"";"";"";"";"";2.0;"";"" 2.3;" Milestone 1";"2008-02-08-19:00:00-+0100";"2008-02-08-19:00:00-+0100";0.0;0.0;"";"";"";"";"";"";"";"";"";"";"" 3;"Check Work";"2008-02-11-10:00:00-+0100";"2008-02-15-19:00:00-+0100";4.4;5.0;"";"";"";"";"";"";"";"";"";5.0;"" 1;" Jill";"";"";"";5.0;"";"";"";"";"";"";"";"";"";5.0;"" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/celltext.csv0000644000175000017500000000062712614413013023650 0ustar bernatbernat"BSI";"Name" 1;"Plan Work (plan)" 1.1;" Plan A (plan.plan_a)" 1.2;" Plan B (plan.plan_b)" 1.3;" Plan C (plan.plan_c)" 2;"Execute Work (execute)" 2.1;" Step 1 (execute.ex1)" 2.2;" Step 2 (execute.ex2)" "2.2.1";" Step 2.1 (execute.ex2.ex2_1)" "2.2.2";" Step 2.2 (execute.ex2.ex2_2)" 2.4;" Step 3 (execute.ex3)" 2.5;" Step 4 (execute.ex4)" 2.3;" Milestone 1 (execute.ms1)" 3;"Check Work (check)" taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/efficiency.csv0000644000175000017500000000015612614413013024125 0ustar bernatbernat"Name";"Efficiency";"FTE";"Headcount" "R1";0.7;0.7;1.0 "T1";1.0;2.0;2.0 " R2";0.0;0.0;0.0 " R3";2.0;2.0;2.0 taskjuggler-3.5.0/test/TestSuite/CSV-Reports/refs/niku.csv0000644000175000017500000000022212614413013022761 0ustar bernatbernat"";"Project 1";"Project 2";"Vacation time" "Resource";"p1";"p2";"vacations" "r1 (r1)";1.0;0.0;0.0 "r2 (r2)";0.5;0.25;0.25 "r3 (r3)";0.0;0.75;0.25 taskjuggler-3.5.0/test/TestSuite/CSV-Reports/inputs.tjp0000644000175000017500000000033112614413013022401 0ustar bernatbernatproject "inputs" "1.0" 2007-12-16 +3m task t1 "T1" task t2 "T2" { depends !t1 } task t3 "T3" task t4 "T4" { depends !t3 } task t5 "T5" { depends !t4 } taskreport '.' { formats csv columns name, inputs } taskjuggler-3.5.0/test/TestSuite/CSV-Reports/resourcereport_with_tasks.tjp0000644000175000017500000000035512614413013026410 0ustar bernatbernatproject test "Test" "1.0" 2007-12-16 +3m include "project-1.tji" resourcereport csv "." { formats csv timezone "Europe/Amsterdam" columns bsi, name, effort, weekly timeformat "%Y-%m-%d-%H:%M:%S" loadunit days hidetask 0 } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/0000755000175000017500000000000012614413013020454 5ustar bernatbernattaskjuggler-3.5.0/test/TestSuite/HTML-Reports/CellText.tjp0000644000175000017500000000323012614413013022715 0ustar bernatbernatproject "Cell Text Test" 2009-11-23 +2m resource moe "Moe" resource curly "Curly Joe" resource larry "Larry" resource shemp "Shemp" resource ted "Ted" resource joe "Joe" resource emil "Emil" task ted "With Ted" { task joke1 "Joke 1" { effort 3w allocate ted, moe, shemp } task joke2 "Joke 2" { depends !joke1 effort 4w allocate ted, moe, larry, shemp } task joke3 "Joke 3" { depends !joke2 effort 4w allocate ted, moe, larry, curly } } task "With Moe" { task joke4 "Joke 4" { depends ted.joke3 effort 3w allocate moe, larry, curly } task joke5 "Joke 5" { depends !joke4 effort 3w allocate moe, larry, shemp } task joke6 "Joke 6" { depends !joke5 effort 3w allocate moe, larry, joe } task joke7 "Joke 7" { depends !joke6 effort 3w allocate moe, larry, curly } task "Joke 8" { depends !joke7 effort 3w allocate moe, emil, curly } } resourcereport "CellText" { formats html columns name, id { celltext isleaf() & isresource() "Person" celltext ~isleaf() & isresource() "Group" celltext isleaf() & istask() "Show" celltext ~isleaf() & istask() "Tour" tooltip isleaf() & isresource() "<-query attribute='name'-> is a person" tooltip ~isleaf() & isresource() "<-query attribute='name'-> is a group" tooltip isleaf() & istask() "<-query attribute='name'-> is a show" tooltip ~isleaf() & istask() "<-query attribute='name'-> is a tour" }, weekly hidetask 0 } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/depArrows.tjp0000644000175000017500000000267212614413013023150 0ustar bernatbernatproject "Dependency Arrows" 2009-10-10 +2m macro endSuccTask [ task "${1}" { depends ${2} { gapduration ${3}d } } ] macro startSuccTask [ task "${1}" { depends bt { onstart gapduration ${2}d } } ] task at "atask" { duration 2d } ${endSuccTask "as3" "at" "2"} ${endSuccTask "as1" "at" "0.5"} ${endSuccTask "as2" "at" "1"} ${endSuccTask "au2" "at" "1"} ${endSuccTask "au1" "at" "2"} ${endSuccTask "au3" "at" "0.5"} task bt "btask" { start ${projectstart} duration 3d } ${startSuccTask "bs3" "2"} ${startSuccTask "bu2" "1"} ${startSuccTask "bu3" "0.5"} ${startSuccTask "bu1" "2"} ${startSuccTask "bs1" "0.5"} ${startSuccTask "bs2" "1"} task ct "ctask" { start ${projectstart} duration 3d } ${endSuccTask "cs1" "ct" "0.5"} ${endSuccTask "cu2" "ct" "4"} ${endSuccTask "cs2" "ct" "1"} ${endSuccTask "cu1" "ct" "3"} ${endSuccTask "cu3" "ct" "5"} ${endSuccTask "cs3" "ct" "2"} task dt "dtask" { start ${projectstart} duration 1d } ${endSuccTask "du1" "dt" "0.5"} ${endSuccTask "du2" "dt" "1"} ${endSuccTask "du3" "dt" "2"} ${endSuccTask "du4" "dt" "3"} ${endSuccTask "du5" "dt" "4"} ${endSuccTask "du6" "dt" "5"} task et "etask" { start ${projectstart} duration 1d } ${endSuccTask "es1" "et" "1"} ${endSuccTask "es2" "et" "1"} ${endSuccTask "es3" "et" "1"} ${endSuccTask "eu4" "et" "1"} ${endSuccTask "eu5" "et" "1"} ${endSuccTask "eu6" "et" "1"} taskreport "depArrows" { formats html columns name, chart { scale day } sorttasks name.up } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/Navigator.tjp0000644000175000017500000000102512614413013023123 0ustar bernatbernatproject navigator "1.0" 2011-10-10 +1m task "foo" navigator menu textreport "top" { header '<[navigator id="menu"]>' textreport "Navigator-1" { title "One" center "one" formats html } textreport "Navigator-2" { title "Two" center "two" formats html } textreport "Navigator-3" { title "Three" textreport "Navigator-4" { title "Four" center "four" formats html } textreport "Navigator-5" { title "Five" center "five" formats html } } } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/reference.tjp0000644000175000017500000000052412614413013023132 0ustar bernatbernatproject "Test" 2008-02-28 +1m { extend task { reference URL "URL" } } task a "A A A" { task b "B B B" { URL "http://www.taskjuggler.org" { label "foo" } } } task c "C C C" { URL "http://www.kde.org" } taskreport "reference" { formats html columns name { celltext plan.URL != '' "[<-URL-> <-name->]" }, URL, start } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/TimeSheet.tjp0000644000175000017500000000221112614413013023056 0ustar bernatbernatproject "test" 2009-11-30 +2m trackingscenario plan resource r1 "R1" resource r2 "R2" resource r3 "R3" task t1 "Task 1" { effort 5d allocate r1 } task t2 "Task 2" { task t3 "Task 3" { effort 10d allocate r2 } task t4 "Task 4" { effort 5d allocate r3 } } timesheet r1 2009-11-30 +1w { task t1 { work 5d remaining 0d status green "All work done" { summary "I had good fun!" details -8<- This task went smoothly and I got three things done: * Have fun * Be on time * Get things done ->8- } } } timesheet r2 2009-11-30 +1w { task t2.t3 { work 5d remaining 8d status red "I need more time" { summary "This takes longer than expected" details -8<- To finish on time, I need help. Get this r1 guy to help me out here. * I want to have fun too! ->8- } } } timesheet r3 2009-11-30 +1w { task t2.t4 { work 5d remaining 0d status green "All things fine" } } taskreport "TimeSheet" { formats html columns name, id, alert, alertmessage hidetask ~hasalert(0) sorttasks alert.down } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/Sorting.tjp0000644000175000017500000000174712614413013022631 0ustar bernatbernatproject "Task Sorting" 2009-11-10 +2m task "Root" { task "Group 1" { task "M1" { start 2009-11-11 } task "M2" { start 2009-11-20 } task "M3" { start 2009-11-14 } } task "Group 2" { task "Group 3" { task "M1" { start 2009-12-30 } task "M2" { start 2009-12-20 } task "M3" { start 2009-12-10 } } task "Group 4" { task "M1" { start 2009-11-20 } task "M2" { start 2009-12-10 } task "M3" { start 2009-11-30 } } task "Group 5" { task "M1" { start 2009-12-01 } } task "M1" { start 2009-11-20 } task "M2" { start 2009-11-30 } task "M3" { start 2009-11-11 } } } task "M1" { start 2009-12-05 } task "Group6" { task "M1" { start 2009-11-20 } } taskreport "TaskSorting" { formats html columns name, chart sorttasks tree, plan.start.up } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/IsOngoing.tjp0000644000175000017500000000107512614413013023072 0ustar bernatbernatproject "test" 2009-12-05 +1y task "Hide" { period 2010-01-01 - 2010-02-01 } task "Show" { period 2010-02-01 - 2010-03-01 } task "Show" { period 2010-02-01 - 2010-05-01 } task "Show" { period 2010-03-01 - 2010-04-01 } task "Show" { period 2010-04-01 - 2010-05-01 } task "Hide" { period 2010-05-01 - 2010-06-01 } task "Show" { start 2010-02-15 } task "Hide" { end 2010-04-18 } taskreport "IsOngoing" { formats html columns no, name, start, end, chart hidetask ~isongoing(planx) period 2010-02-15 - 2010-04-18 headline "5 Tasks should be shown" } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/Alerts-2.tjp0000644000175000017500000000106412614413013022565 0ustar bernatbernatproject "Transitive Alert Test" 2012-05-04 +2m task t1 "T1" { task t1 "T1" { task t1 "T1" { journalentry 2012-05-06 "Red" { alert red } } } task t2 "T2" { depends !t1 } } task t2 "T2" { task t2 "T2" { depends t1.t2 } } task t3 "T3" { adopt t2 } task "L2" { task t1 "T1" { depends t2.t2 } } task "L3" { task t1 "T1" { depends t1 } } task l4 "L4" { depends t1.t2 task t1 "T1" } task "L5" { adopt l4 } taskreport "Alerts-2" { formats html journalmode alerts_dep columns name, journal } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/UDAQuery.tjp0000644000175000017500000000104412614413013022631 0ustar bernatbernatproject "test" 2009-12-01 +1m { extend resource { reference LinkR "Link R" } extend task { reference LinkT "Link T" } } resource r1 "R1" { LinkR "http://www.taskjuggler.org" { label "TaskJuggler" } } task "T1" { effort 10d allocate r1 LinkT "http://www.taskjuggler.org" { label "TaskJuggler" } } taskreport "UDAQuery" { formats html columns no, name { celltext istask() "[<-LinkT-> <-name->]" celltext isresource() "[<-LinkR-> <-name->]" }, LinkR, LinkT hidetask 0 hideresource 0 } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/Query.tjp0000644000175000017500000000127012614413013022300 0ustar bernatbernatproject query "Query Test" 2009-11-08 +1m copyright "The Gang" resource foo "Foo" task bar "Bar" { effort 1w allocate foo } textreport "Query" { formats html center -8<- * Copyright: <-query attribute="copyright"-> * Currency: <-query attribute="currency"-> * End: <-query attribute="end"-> * Name: <-query attribute="name"-> * Now: <-query attribute="now"-> * Project ID: <-query attribute="projectid"-> * Start: <-query attribute="start"-> * Version: <-query attribute="version"-> The task <-query family="task" property="bar" attribute="name"-> has an effort of <-query family="task" property="bar" attribute="effort"->. ->8- } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/Alerts.tjp0000644000175000017500000000327212614413013022431 0ustar bernatbernatproject "Alerts" 2009-11-18 +2m task "Green" { journalentry 2009-11-20 "Green" } task "Yellow" { journalentry 2009-11-20 "Yellow" { alert yellow } } task "Red" { journalentry 2009-11-20 "Red" { alert red } } task "Inherit Yellow" { task "Green" { } task "Yellow" { journalentry 2009-11-22 "Yellow" { alert yellow } } } task "Inherit Red" { task "Inherit Yellow" { task "Green" { } task "Yellow" { journalentry 2009-11-23 "Yellow" { alert yellow } } } task "Inherit Green" { task "Green" { } } task "Inherit Red" { task "Green" { } task "Red" { journalentry 2009-11-20 "Yellow" { alert yellow } journalentry 2009-11-21 "Green" { alert green } journalentry 2009-11-22 "Red" { alert red } } } } task "Overwrite Yellow" { journalentry 2009-11-23 "Yellow" { alert yellow } task "Inherit Yellow" { task "Green" { } task "Yellow" { journalentry 2009-11-23 "Yellow" { alert yellow } } } task "Overwrite Red" { journalentry 2009-11-22 "Red" { alert red } task "Green" { } } task "Inherit Red" { task "Green" { } task "Red" { journalentry 2009-11-20 "Yellow" { alert yellow } journalentry 2009-11-21 "Green" { alert green } journalentry 2009-11-22 "Red" { alert red } } } } task "Overwrite Green" { task "Overwrite Green" { task "Red" { journalentry 2009-11-20 "Red" { alert red } } task "Green" { journalentry 2009-11-23 "Green" { alert green } } journalentry 2009-11-22 "Green" { alert green } } } taskreport "Alerts" { formats html columns name, alert sorttasks alert.down #hidetask plan.alert < 2 } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/Calendars.tjp0000644000175000017500000000035212614413013023067 0ustar bernatbernatproject "Test" 2012-09-05 +2y task "Foo" taskreport "Calendars" { formats html columns name, quarterly { timeformat1 "TF1: %Y" timeformat2 "TF2: %Q" }, chart { timeformat1 "TF1: %Y" timeformat2 "TF2: %Q" } } taskjuggler-3.5.0/test/TestSuite/HTML-Reports/ColumnPeriods.tjp0000644000175000017500000000170312614413013023757 0ustar bernatbernatproject 'test' 2009-12-05 +6m shift part30 "30 hours part time" { workinghours thu 8:00 - 14:00 workinghours fri off } shift part20 "20 hours part time" { workinghours mon, tue off workinghours wed 12:00 - 16:00 } shift part10 "10 hours part time" { workinghours mon - fri 10:00 - 12:00 } resource r1 "R1" resource r2 "R2" { shift part20 } resource r3 "R3" task "T1" { effort 20d allocate r1, r2 shift part10 } task "T2" { effort 30d priority 600 allocate r1, r2 } task "T3" { effort 40d allocate r1, r2, r3 shift part30 } taskreport "ColumnPeriods" { formats html columns name, effort { title "Nov" start 2009-12-01 end 2009-12-01 }, effort { title "Dec" period 2009-12-01 - 2010-01-01 }, effort { title "Jan" start 2010-01-01 end 2010-02-01 }, effort { title "Feb" period 2010-02-01 - 2010-03-01 }, effort { title "Mar" start 2010-03-01 end 2010-04-01 }, monthly } taskjuggler-3.5.0/test/test_Syntax.rb0000644000175000017500000000324312614413013017177 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_Syntax.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 $:.unshift File.dirname(__FILE__) require 'test/unit' require 'taskjuggler/TaskJuggler' require 'MessageChecker' class TestSyntax < Test::Unit::TestCase include MessageChecker def test_syntaxCorrect ENV['TEST1'] = 't_e_s_t_1' ENV['TEST2'] = '"A test String"' ENV['TEST3'] = '3' path = File.dirname(__FILE__) + '/' Dir.glob(path + 'TestSuite/Syntax/Correct/*.tjp').each do |f| ENV['TZ'] = 'Europe/Berlin' (mh = TaskJuggler::MessageHandlerInstance.instance).reset mh.outputLevel = :none mh.trapSetup = true tj = TaskJuggler.new assert(tj.parse([ f ]), "Parser failed for #{f}") assert(mh.messages.empty?, "Unexpected error in #{f}") end end def test_syntaxErrors path = File.dirname(__FILE__) + '/' Dir.glob(path + 'TestSuite/Syntax/Errors/*.tjp').each do |f| ENV['TZ'] = 'Europe/Berlin' (mh = TaskJuggler::MessageHandlerInstance.instance).reset mh.outputLevel = :none mh.trapSetup = true begin tj = TaskJuggler.new assert(!tj.parse([ f ]), "Parser succedded for #{f}") rescue TaskJuggler::TjRuntimeError end checkMessages(tj, f) end end end taskjuggler-3.5.0/test/test_deep_copy.rb0000644000175000017500000000274712614413013017670 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_deep_copy.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/deep_copy' class A def initialize @a = [ 10, 11, 12 ] end def mute @a[1] = 'z' end def muted @a[1] end end class B def initialize @a = 1 @b = 'abc' @c = [ 1, 2, 3 ] @d = [ [ 1, 2], [ 3, 4 ], A.new ] @e = { '0' => 49, '1' => 50, '2' => 51 } end def mute @b[1] = '-' @d[1][1] = 'x' @d[2].mute @e['1'] = 111 end def muted [ @b[1, 1], @d[1][1], @d[2].muted, @e['1'] ] end end class Test_deep_copy < Test::Unit::TestCase def test_clone a = B.new b = a.deep_clone a.mute out = a.muted refA = [ '-', 'x', 'z', 111 ] refA.length.times do |i| assert_equal(refA[i], out[i]) end out = b.muted refB = [ 'b', 4, 11, 50 ] refB.length.times do |i| assert_equal(refB[i], out[i]) end end def test_network a = [ 0, '1', 'abc' ] b = { 'a' => 0, 'b' => '123', 'c' => a } a << b a.deep_clone b.deep_clone end end taskjuggler-3.5.0/test/test_TextFormatter.rb0000644000175000017500000000544012614413013020522 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_TextFormatter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/TextFormatter' class TestTextFormatter < Test::Unit::TestCase def setup end def teardown end def test_format_empty ftr = TaskJuggler::TextFormatter.new(20, 2, 4) inp = '' ref = "\n" out = ftr.format(inp) assert_equal(ref, out) end def test_format_singleWord ftr = TaskJuggler::TextFormatter.new(20, 2, 4) inp = 'foo' ref = " foo\n" out = ftr.format(inp) assert_equal(ref, out) end def test_format_multipleWords ftr = TaskJuggler::TextFormatter.new(20, 2, 4) inp = "foo bar \n foobar" ref = " foo bar foobar\n" out = ftr.format(inp) assert_equal(ref, out) end def test_format_multipleLines ftr = TaskJuggler::TextFormatter.new(23, 2, 4) inp = < # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 $:.unshift File.dirname(__FILE__) require 'stringio' require 'test/unit' require 'MessageChecker' require 'taskjuggler/TaskJuggler' require 'taskjuggler/reports/CSVFile' require 'taskjuggler/AlgorithmDiff' class TestScheduler < Test::Unit::TestCase include MessageChecker # This function captures the $stdout output of the passed block to a String # and returns it. def captureStdout oldStdOut = $stdout $stdout = (out = StringIO.new) begin yield ensure $stdout = oldStdOut end out.string end # This functions redirects all output of the passed block to a new file with # the name fileName. def stdoutToFile(fileName) oldStdOut = $stdout $stdout = File.open(fileName, 'w') begin yield ensure $stdout = oldStdOut end end # Compare the output CSV (passed as String) with the content of the CSV # reference files _refFile_. def compareCSVs(outStr, refFile) refStr = File.new(refFile, 'r').read diff = refStr.extend(DiffableString).diff(outStr).to_s if diff != '' puts diff File.new('failed.csv', 'w').write(outStr) ref = TaskJuggler::CSVFile.new.parse(refStr) out = TaskJuggler::CSVFile.new.parse(outStr) assert(ref.length == out.length, "Line number mismatch (#{out.length} instead of #{ref.length}) " + "in #{refFile}") 0.upto(ref.length - 1) do |line| refLine = ref[line] outLine = out[line] assert(refLine.length == outLine.length, "Line #{line} size mismatch (#{outLine.length} instead of " + "#{refLine.length}) in #{refFile}") 0.upto(refLine.length - 1) do |cell| assert(refLine[cell] == outLine[cell], "Cell #{cell} of line #{line} mismatch: " + "'#{outLine[cell]}' instead of '#{refLine[cell]}' " + "in #{refFile}") end end end end def checkCSVReport(projectFile) baseDir = File.dirname(projectFile) baseName = File.basename(projectFile, '.tjp') # The reference files must have the same base name as the project file but # they need to be in the ./refs/ directory relative to the project file. refFile = baseDir + "/refs/#{baseName}.csv" (mh = TaskJuggler::MessageHandlerInstance.instance).reset tj = TaskJuggler.new assert(tj.parse([ projectFile ]), "Parser failed for #{projectFile}") assert(tj.schedule, "Scheduler failed for #{projectFile}") if File.file?(refFile) # If there is a reference CSV file for this test case, compare the # output against it. out = captureStdout do assert(tj.generateReports(baseDir), "Report generation failed for #{projectFile}") end compareCSVs(out, refFile) else # If not, we generate the reference file. puts "refFile: #{refFile}" stdoutToFile(refFile) do assert(tj.generateReports, "Reference file generation failed for #{projectFile}") end end assert(mh.messages.empty?, "Unexpected error in #{projectFile}") end def test_CSV_Reports path = File.dirname(__FILE__) testDir = path + '/TestSuite/CSV-Reports/' Dir.glob(testDir + '*.tjp').each do |f| TaskJuggler::TjTime.setTimeZone('Europe/Berlin') checkCSVReport(f) end end end taskjuggler-3.5.0/test/MessageChecker.rb0000644000175000017500000000402212614413013017517 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = MessageChecker.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/MessageHandler' module MessageChecker # Check that all messages that were generated during the TaskJuggler run # match the references specified in the test file. def checkMessages(tj, file) refMessages = collectMessages(file) TaskJuggler::MessageHandlerInstance.instance.messages.each do |message| assert(ref = refMessages.pop, "Unexpected #{message.type.to_s} #{message.id}: #{message}") assert_equal(ref[0], message.type.to_s, "Error in #{file}: Got #{message.type.to_s} instead of #{ref[0]}") assert_equal(ref[2], message.id, "Error in #{file}: Got #{message.id} instead of #{ref[2]}") if message.sourceFileInfo assert_equal(ref[1], message.sourceFileInfo.lineNo, "Error in #{file}: Got line #{message.sourceFileInfo.lineNo} " + "instead of #{ref[1]}") end end # Make sure that all reference messages have been generated. assert(refMessages.empty?, "Error in #{file}: missing #{refMessages.length} errors") end # All files that generate messages have comments in them that specify the # expected messages. The comments have the following form: # MARK: # We collect all these reference messages to compare them with the # generated messages after the test has been run. def collectMessages(file) refMessages = [] File.open(file) do |f| f.each_line do |line| if line =~ /^# MARK: ([a-z]+) ([0-9]+) ([a-z0-9_]*)/ refMessages << [ $1, $2.to_i, $3 ] end end end refMessages.reverse! end end taskjuggler-3.5.0/test/TjpGen.rb0000644000175000017500000000757412614413013016054 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TjpGen.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') require 'taskjuggler/TjTime' class TjpGen def initialize(fileName = 'test.tjp', seed = 12345, tasks = 100, resources = 15) @fileName = fileName srand(seed) @tasks = tasks @resources = resources @start = TjTime.local(2000, 1, 1) + rand(365 * 5) * (60 * 60 * 24) @resourceList = [] @taskList = [] @depTargets = [] end def generate File.open(@fileName, "w") do |f| f.puts "project test \"Test\" \"1.0\" #{@start} +270d" begin genResource(f, 0) end while @resourceList.length < @resources begin genTask(f, 0, '', []) end while @taskList.length < @tasks #genReports(f) end end private def genResource(f, level) id = "r#{@resourceList.length}" indent = ' ' * 2 * level f.puts "#{indent}resource #{id} \"Resource #{@resourceList.length}\" {" @resourceList << id if rand(10) < 2 genResource(f, level + 1) end f.puts "#{indent}}" end def genTask(f, level, parent, brothers) id = "t#{@taskList.length}" fullId = parent + id indent = ' ' * 2 * level f.puts "#{indent}task #{id} \"Task #{@taskList.length}\" {" @taskList << fullId if rand(10) < 1 f.puts "#{indent} priority #{(rand(9) + 1) * 100}" end if level == 0 f.puts "#{indent} start #{@start + (60 * 60 * rand(@taskList.length))}" end children = [] if (level <= (Math.log10(@tasks) * 2) && rand(10) < 6) 0.upto(rand(1 + level)) do |i| children << genTask(f, level + 1, fullId + '.', children) end end if children.empty? wof = rand(100) milestone = false if wof < 10 f.puts "#{indent} milestone" milestone = true elsif wof < 70 f.puts "#{indent} effort #{1 + rand(60)}h" genAllocate(f, indent) elsif wof < 80 f.puts "#{indent} length #{1 + rand(80)}h" genAllocate(f, indent) if rand(5) < 2 else f.puts "#{indent} duration #{1 + rand(200)}h" genAllocate(f, indent) if rand(5) < 1 end if @depTargets.empty? || rand(10) < 1 || level == 0 f.puts "#{indent} start #{@start + rand(20) * (60 * 60 * 24)}" else deps = [] if rand(5) < 1 depList = @depTargets else depList = brothers end while !depList.empty? && rand(100) < (milestone ? 60 : 30) dep = depList[rand(depList.length)] deps << dep unless deps.include?(dep) end f.puts "#{indent} depends #{deps.join(', ')}" unless deps.empty? end end if level > 0 && rand(10) < 3 @depTargets << fullId end f.puts "#{indent}}" fullId end def genAllocate(f, indent) res = [] res << @resourceList[rand(@resourceList.length)] while rand(10) < 2 r = @resourceList[rand(@resourceList.length)] res << r unless res.include?(r) end f.puts "#{indent} allocate #{res.join(', ')}" end def genReports(f) f.puts "taskreport \"Tasks\" {" f.puts " columns no, name, start, end, chart" f.puts "}" f.puts "resourcereport \"Resources\" {" f.puts " columns no, name, effort, utilization, chart" f.puts "}" end end fileName = ARGV[0] ? ARGV[0] : 'test.tjp' seed = ARGV[1] ? ARGV[1].to_i : 12345 tasks = ARGV[2] ? ARGV[2].to_i : 100 resources = 1 + (tasks / 21.0).to_i gtor = TjpGen.new(fileName, seed, tasks, resources) gtor.generate taskjuggler-3.5.0/test/test_ProjectFileScanner.rb0000644000175000017500000000757512614413013021445 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_ProjectFileScanner.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') require 'test/unit' require 'taskjuggler/ProjectFileScanner' require 'taskjuggler/MessageHandler' class TaskJuggler class TestProjectFileScanner < Test::Unit::TestCase def setup end def teardown end def test_basic text = <<'EOT' Hello world 1 2.0 # Comment 2008-12-14 // Another comment foo: a.b.c - $ [A Macro] 15:23 "A string" 'It\'s a string' "A mult\"i line string" /* a comment */ EOT ref = [ [:ID, 'Hello', 1], [:ID, 'world', 1], [:INTEGER, 1, 1], [:FLOAT, 2.0, 2], [:DATE, TjTime.new('2008-12-14'), 3], [:ID_WITH_COLON, 'foo', 5], [:ABSOLUTE_ID, 'a.b.c', 6], [:LITERAL, '-', 6], [:LITERAL, '$', 6], [:MACRO, 'A Macro', 6], [:TIME, time(15, 23), 7], [:STRING, 'A string', 7], [:STRING, "It's a string", 8], [:STRING, "A\nmult\"i line\nstring", 9], [:eof, '', 14 ] ] check(text, ref) end def test_macro text = <<'EOT' This ${adj} software ${m1 "arg1"} EOT macros = [ [ 'adj', 'great' ], [ 'm1', 'macro with ${1} argument' ] ] ref = [ [:ID, 'This', 1], [:ID, 'great', 1], [:ID, 'software', 1], [:ID, 'macro', 2], [:ID, 'with', 2], [:ID, 'arg1', 2], [:ID, 'argument', 2], [:eof, '', 3] ] check(text, ref, macros) end def test_time text = <<'EOT' 0:00 00:00 1:00 11:59 12:01 24:00 EOT ref = [ [:TIME, time(0, 0), 1], [:TIME, time(0, 0), 2], [:TIME, time(1, 0), 3], [:TIME, time(11, 59), 4], [:TIME, time(12, 1), 5], [:TIME, time(24, 0), 6], [:eof, '', 7] ] check(text, ref) end def test_date text = <<'EOT' 1970-01-01 2035-12-31-23:59:59 2010-08-11-23:10 EOT ref = [ [:DATE, TjTime.new('1970-01-01'), 1], [:DATE, TjTime.new('2035-12-31-23:59:59'), 2], [:DATE, TjTime.new('2010-08-11-23:10'), 3], [:eof, '', 4] ] check(text, ref) end def test_macroDef text = <<'EOT' [ foo ] [ bar ] [ foo ] [ bar ] [] [ ] EOT ref = [ [ :MACRO, ' foo ', 1 ], [ :MACRO, "\n bar ", 2 ], [ :MACRO, " foo\n ", 4 ], [ :MACRO, "\n bar\n ", 6 ], [ :MACRO, '', 9 ], [ :MACRO, "\n ", 10 ], [ :eof, '', 13 ] ] check(text, ref) end def test_macroCall text = '${foo}' macros = [ [ 'foo', 'hello' ] ] ref = [ [ :ID, 'hello', 1 ] ] check(text, ref, macros) end private def time(h, m) (h * 60 + m) * 60 end def check(text, ref, macros = []) s = TaskJuggler::ProjectFileScanner.new(text) s.open(true) macros.each do |macro| s.addMacro(TaskJuggler::TextParser::Macro.new(macro[0], macro[1], nil)) end ref.each do |type, val, line| token = s.nextToken assert_equal([ type, val ], token[0..1], "1: Bad token #{token[1]} instead of #{val}") assert_equal(line, token[2].lineNo, "1: Bad line number #{token[2].lineNo} instead of #{line} for #{val}") s.returnToken(token) token = s.nextToken assert_equal([ type, val ], token[0..1], "2: Bad token #{token[1]} instead of #{val}") assert_equal(line, token[2].lineNo, "2: Bad line number #{token[2].lineNo} instead of #{line} for #{val}") end s.close end end end taskjuggler-3.5.0/test/test_RichText.rb0000644000175000017500000005456312614413013017456 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_RichText.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/RichText' require 'taskjuggler/RichText/FunctionHandler' require 'taskjuggler/MessageHandler' class RTFDummy < TaskJuggler::RichTextFunctionHandler def initialize() super('dummy') @blockFunction = true end # Return a XMLElement tree that represents the blockfunc in HTML code. def to_html(args) TaskJuggler::XMLElement.new('blockfunc:dummy', args, true) end end class TestRichText < Test::Unit::TestCase def setup end def teardown end def test_empty inp = '' tagged = "
\n" str = "\n" html = "
\n" assert_outputs(inp, tagged, str, html) assert_equal(true, newRichText(inp).empty?, 'Empty string') assert_equal(true, newRichText("\n").empty?, '\n') assert_equal(true, newRichText("\n \n").empty?, '\n \n') assert_equal(false, newRichText("foo").empty?, 'foo') end def test_one_word inp = "foo" tagged = "
[foo]
\n" str = "foo\n" html= "
foo
\n" assert_outputs(inp, tagged, str, html) end def test_two_words inp = "foo bar" tagged = "
[foo] [bar]
\n" str = "foo bar\n" html = "
foo bar
\n" assert_outputs(inp, tagged, str, html) end def test_paragraph inp = <<'EOT' A paragraph may span multiple lines of text. Single line breaks are ignored. Only 2 successive newlines end the paragraph. I hope this example is clear now. EOT tagged = <<'EOT'

[A] [paragraph] [may] [span] [multiple] [lines] [of] [text.] [Single] [line] [breaks] [are] [ignored.]

[Only] [2] [successive] [newlines] [end] [the] [paragraph.]

[I] [hope] [this] [example] [is] [clear] [now.]

EOT str = <<'EOT' A paragraph may span multiple lines of text. Single line breaks are ignored. Only 2 successive newlines end the paragraph. I hope this example is clear now. EOT html = <<'EOT'

A paragraph may span multiple lines of text. Single line breaks are ignored.

Only 2 successive newlines end the paragraph.

I hope this example is clear now.

EOT assert_outputs(inp, tagged, str, html) end def test_hline inp = <<'EOT' ---- Line above and below ---- == A heading == ---- ---- ---- Another bit of text. ---- EOT tagged = <<'EOT'

----

[Line] [above] [and] [below]


----

1 [A] [heading]


----
----
----

[Another] [bit] [of] [text.]


----
EOT str = <<'EOT' ------------------------------------------------------------ Line above and below ------------------------------------------------------------ 1) A heading ------------------------------------------------------------ ------------------------------------------------------------ ------------------------------------------------------------ Another bit of text. ------------------------------------------------------------ EOT html = <<'EOT'

Line above and below


1 A heading




Another bit of text.


EOT assert_outputs(inp, tagged, str, html, 60) end def test_italic inp = "This is a text with ''italic words '' in it." tagged = <<'EOT'
[This] [is] [a] [text] [with] [italic] [words] [in] [it.]
EOT str = <<'EOT' This is a text with italic words in it. EOT html = <<'EOT'
This is a text with italic words in it.
EOT assert_outputs(inp, tagged, str, html) end def test_bold inp = "This is a text with ''' bold words''' in it." tagged = <<'EOT'
[This] [is] [a] [text] [with] [bold] [words] [in] [it.]
EOT str = <<'EOT' This is a text with bold words in it. EOT html = <<'EOT'
This is a text with bold words in it.
EOT assert_outputs(inp, tagged, str, html) end def test_code inp = "This is a text with ''''monospaced words'''' in it." tagged = <<'EOT'
[This] [is] [a] [text] [with] [monospaced] [words] [in] [it.]
EOT str = <<'EOT' This is a text with monospaced words in it. EOT html = <<'EOT'
This is a text with monospaced words in it.
EOT assert_outputs(inp, tagged, str, html) end def test_boldAndItalic inp = <<'EOT' This is a text with some '''bold words''', some ''italic'' words and some '''''bold and italic''''' words in it. EOT tagged = <<'EOT'
[This] [is] [a] [text] [with] [some] [bold] [words][,] [some] [italic] [words] [and] [some] [bold] [and] [italic] [words] [in] [it.]
EOT str = <<'EOT' This is a text with some bold words, some italic words and some bold and italic words in it. EOT html = <<'EOT'
This is a text with some bold words, some italic words and some bold and italic words in it.
EOT assert_outputs(inp, tagged, str, html) end def test_ref inp = <<'EOT' This is a reference [[item]]. For more info see [[manual|the user manual]]. EOT tagged = <<'EOT'
[This] [is] [a] [reference] [item][.] [For] [more] [info] [see] [the user manual][.]
EOT str = <<'EOT' This is a reference item. For more info see the user manual. EOT html = <<'EOT'
This is a reference item. For more info see the user manual.
EOT assert_outputs(inp, tagged, str, html) end def test_img inp = <<'EOT' This is an [[File:image.jpg]]. For more info see [[File:icon.png|alt=this image]]. EOT tagged = <<'EOT'
[This] [is] [an] [.] [For] [more] [info] [see] [.]
EOT str = <<'EOT' This is an . For more info see this image. EOT html = <<'EOT'
This is an . For more info see .
EOT assert_outputs(inp, tagged, str, html) end def test_href inp = <<'EOT' This is a reference [http://www.taskjuggler.org]. For more info see [http://www.taskjuggler.org the TaskJuggler site]. EOT tagged = <<'EOT'
[This] [is] [a] [reference] [http://www.taskjuggler.org][.] [For] [more] [info] [see] [the] [TaskJuggler] [site][.]
EOT str = <<'EOT' This is a reference http://www.taskjuggler.org. For more info see the TaskJuggler site. EOT html = <<'EOT'
This is a reference http://www.taskjuggler.org. For more info see the TaskJuggler site.
EOT assert_outputs(inp, tagged, str, html) end def test_hrefWithWrappedLines inp = <<'EOT' A [http://www.taskjuggler.org multi line] reference. EOT tagged = <<'EOT'
[A] [multi] [line] [reference.]
EOT str = <<'EOT' A multi line reference. EOT html = <<'EOT'
A multi line reference.
EOT assert_outputs(inp, tagged, str, html) end def test_headline inp = <<'EOT' = This is not a headline == This is level 1 == === This is level 2 === ==== This is level 3 ==== ===== This is level 4 ===== EOT tagged = <<'EOT'

[=] [This] [is] [not] [a] [headline]

1 [This] [is] [level] [1]

1.1 [This] [is] [level] [2]

1.1.1 [This] [is] [level] [3]

1.1.1.1 [This] [is] [level] [4]

EOT str = <<'EOT' = This is not a headline 1) This is level 1 1.1) This is level 2 1.1.1) This is level 3 1.1.1.1) This is level 4 EOT html = <<'EOT'

= This is not a headline

1 This is level 1

1.1 This is level 2

1.1.1 This is level 3

1.1.1.1 This is level 4

EOT assert_outputs(inp, tagged, str, html) end def test_bullet inp = <<'EOT' * This is a bullet item ** This is a level 2 bullet item *** This is a level 3 bullet item **** This is a level 4 bullet item EOT tagged = <<'EOT'
  • * [This] [is] [a] [bullet] [item]
    • * [This] [is] [a] [level] [2] [bullet] [item]
      • * [This] [is] [a] [level] [3] [bullet] [item]
        • * [This] [is] [a] [level] [4] [bullet] [item]
EOT str = <<'EOT' * This is a bullet item * This is a level 2 bullet item * This is a level 3 bullet item * This is a level 4 bullet item EOT html = <<'EOT'
  • This is a bullet item
    • This is a level 2 bullet item
      • This is a level 3 bullet item
        • This is a level 4 bullet item
EOT assert_outputs(inp, tagged, str, html) end def test_number inp = <<'EOT' # This is item 1 # This is item 2 # This is item 3 Normal text. # This is item 1 ## This is item 1.1 ## This is item 1.2 ## This is item 1.3 # This is item 2 ## This is item 2.1 ## This is item 2.2 ### This is item 2.2.1 ### This is item 2.2.2 #### This is item 2.2.2.1 # This is item 3 ## This is item 3.1 ### This is item 3.1.1 # This is item 4 ### This is item 4.0.1 Normal text. # This is item 1 EOT tagged = <<'EOT'
  1. 1 [This] [is] [item] [1]
  2. 2 [This] [is] [item] [2]
  3. 3 [This] [is] [item] [3]

[Normal] [text.]

  1. 1 [This] [is] [item] [1]
    1. 1.1 [This] [is] [item] [1.1]
    2. 1.2 [This] [is] [item] [1.2]
    3. 1.3 [This] [is] [item] [1.3]
  2. 2 [This] [is] [item] [2]
    1. 2.1 [This] [is] [item] [2.1]
    2. 2.2 [This] [is] [item] [2.2]
      1. 2.2.1 [This] [is] [item] [2.2.1]
      2. 2.2.2 [This] [is] [item] [2.2.2]
        1. 2.2.2.1 [This] [is] [item] [2.2.2.1]
  3. 3 [This] [is] [item] [3]
    1. 3.1 [This] [is] [item] [3.1]
      1. 3.1.1 [This] [is] [item] [3.1.1]
  4. 4 [This] [is] [item] [4]
      1. 4.0.1 [This] [is] [item] [4.0.1]

[Normal] [text.]

  1. 1 [This] [is] [item] [1]
EOT str = <<'EOT' 1. This is item 1 2. This is item 2 3. This is item 3 Normal text. 1. This is item 1 1.1 This is item 1.1 1.2 This is item 1.2 1.3 This is item 1.3 2. This is item 2 2.1 This is item 2.1 2.2 This is item 2.2 2.2.1 This is item 2.2.1 2.2.2 This is item 2.2.2 2.2.2.1 This is item 2.2.2.1 3. This is item 3 3.1 This is item 3.1 3.1.1 This is item 3.1.1 4. This is item 4 4.0.1 This is item 4.0.1 Normal text. 1. This is item 1 EOT html = <<'EOT'
  1. This is item 1
  2. This is item 2
  3. This is item 3

Normal text.

  1. This is item 1
    1. This is item 1.1
    2. This is item 1.2
    3. This is item 1.3
  2. This is item 2
    1. This is item 2.1
    2. This is item 2.2
      1. This is item 2.2.1
      2. This is item 2.2.2
        1. This is item 2.2.2.1
  3. This is item 3
    1. This is item 3.1
      1. This is item 3.1.1
  4. This is item 4
      1. This is item 4.0.1

Normal text.

  1. This is item 1
EOT assert_outputs(inp, tagged, str, html) end def test_pre inp = <<'EOT' #include main() { printf("Hello, world!\n") } Some normal text. * A bullet item Some code More text. EOT tagged = <<'EOT'
#include 
main() {
  printf("Hello, world!\n")
}

[Some] [normal] [text.]

  • * [A] [bullet] [item]
Some code

[More] [text.]

EOT str = <<'EOT' #include main() { printf("Hello, world!\n") } Some normal text. * A bullet item Some code More text. EOT html = <<'EOT'
#include <stdin.h>
main() {
  printf("Hello, world!\n")
}

Some normal text.

  • A bullet item
Some code

More text.

EOT assert_outputs(inp, tagged, str, html) end def test_mix inp = <<'EOT' == This the first section == === This is the section 1.1 === Not sure what to put here. Maybe just some silly text. * A bullet ** Another bullet # A number item * A bullet ## Number 0.1, I guess == Section 2 == * Starts with bullets * ... Some more text. And we're done. EOT tagged = <<'EOT'

1 [This] [the] [first] [section]

1.1 [This] [is] [the] [section] [1.1]

[Not] [sure] [what] [to] [put] [here.] [Maybe] [just] [some] [silly] [text.]

  • * [A] [bullet]
    • * [Another] [bullet]
  1. 1 [A] [number] [item]
  • * [A] [bullet]
    1. 0.1 [Number] [0.1,] [I] [guess]

2 [Section] [2]

  • * [Starts] [with] [bullets]
  • * [...]

[Some] [more] [text.] [And] [we]['re] [done.]

EOT str = <<'EOT' 1) This the first section 1.1) This is the section 1.1 Not sure what to put here. Maybe just some silly text. * A bullet * Another bullet 1. A number item * A bullet 0.1 Number 0.1, I guess 2) Section 2 * Starts with bullets * ... Some more text. And we're done. EOT html = <<'EOT'

1 This the first section

1.1 This is the section 1.1

Not sure what to put here. Maybe just some silly text.

  • A bullet
    • Another bullet
  1. A number item
  • A bullet
    1. Number 0.1, I guess

2 Section 2

  • Starts with bullets
  • ...

Some more text. And we're done.

EOT assert_outputs(inp, tagged, str, html) end def test_htmlblob inp = <<'EOT' A raw foo bar blob. EOT tagged = <<'EOT'
[A] raw foo bar [blob.]
EOT str = <<'EOT' A blob. EOT html = <<'EOT'
A raw foo bar blob.
EOT assert_outputs(inp, tagged, str, html) end def test_nowiki inp = <<'EOT' == This the first section == === This is the section 1.1 === Not sure ''what'' to put here. Maybe just some silly text. * A bullet ** Another bullet # A number item * A bullet ## Number 0.1, I guess == Section 2 == * Starts with bullets * ... Some more text. And we're done. EOT tagged = <<'EOT'

1 [This] [the] [first] [section]

1.1 [This] [is] [the] [section] [1.1]

[Not] [sure] [''what''] [to] [put] [here.] [Maybe] [just] [some] [silly] [text.]

  • * [A] [bullet]
    • * [Another] [bullet]
  1. 1 [A] [number] [item]
  • * [A] [bullet]

[##] [Number] [0.1,] [I] [guess]

[==] [Section] [2] [==]

  • * [Starts] [with] [bullets]
  • * [...]

[Some] [more] [text.] [And] [we]['re] [done.]

EOT str = <<'EOT' 1) This the first section 1.1) This is the section 1.1 Not sure ''what'' to put here. Maybe just some silly text. * A bullet * Another bullet 1. A number item * A bullet ## Number 0.1, I guess == Section 2 == * Starts with bullets * ... Some more text. And we're done. EOT html = <<'EOT'

1 This the first section

1.1 This is the section 1.1

Not sure ''what'' to put here. Maybe just some silly text.

  • A bullet
    • Another bullet
  1. A number item
  • A bullet

## Number 0.1, I guess

== Section 2 ==

  • Starts with bullets
  • ...

Some more text. And we're done.

EOT assert_outputs(inp, tagged, str, html) end def test_hline_and_link inp = <
----

[bar]

EOT str = <bar

\n\n" assert_outputs(inp, tagged, str, html, 60) end def test_blockFunction inp = <<'EOT' <[dummy id="foo" arg1="bar"]> === Header === <[dummy arg1="A \"good\" day"]> some text <[dummy]> EOT tagged = <<'EOT'

0.1 [Header]

[some] [text]

EOT str = <

some text

EOT assert_outputs(inp, tagged, str, html, 60) end def test_stringLineWrapping inp = <blue word.\n" assert_outputs(inp, tagged, str, html) end def newRichText(text) mh = TaskJuggler::MessageHandlerInstance.instance mh.outputLevel = :none rText = TaskJuggler::RichText.new(text, [ RTFDummy.new ]) assert(rti = rText.generateIntermediateFormat, mh.to_s) rti.linkTarget = '_blank' rti end def assert_outputs(inp, tagged, str, html, width = 80) # Check tagged output. assert_tagged(inp, tagged) # Check ASCII output. assert_str(inp, str, width) # Check HTML output. assert_html(inp, html, width) end def assert_tagged(inp, ref) out = newRichText(inp).to_tagged + "\n" match(ref, out) end def assert_str(inp, ref, width) rt = newRichText(inp) rt.lineWidth = width out = rt.to_s + "\n" match(ref, out) end def assert_html(inp, ref, width) rt = newRichText(inp) rt.lineWidth = width out = rt.to_html.to_s + "\n" match(ref, out) end def match(ref, out) if ref != out common = '' refDiff = '' outDiff = '' diffI = nil len = ref.length < out.length ? ref.length : out.length len.times do |i| if ref[i] == out[i] common << ref[i] else diffI = i break end end refDiff = ref[diffI,20] + '...' if diffI && ref.length > diffI outDiff = out[diffI,20] + '...' if diffI && out.length > diffI end assert_equal(ref, out, "=== Maching part: #{'=' * 40}\n" + "#{common}\n" + "=== ref diff #{'=' * 44}\n" + "#{refDiff}\n" + "=== out diff #{'=' * 44}\n" + "#{outDiff}\n") end end taskjuggler-3.5.0/test/test_UTF8String.rb0000644000175000017500000000321712614413013017627 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_UTF8String.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') require 'test/unit' require 'taskjuggler/UTF8String' class TestUTF8String < Test::Unit::TestCase def setup end def teardown end def test_each_utf8_char patterns = [ [ '1', [ '1' ] ], [ 'abc', [ 'a', 'b', 'c' ] ], [ 'àcA绋féà', [ 'à', 'c', 'A', '绋', 'f', 'é', 'à' ] ] ] patterns.each do |inp, out| i = 0 inp.each_utf8_char do |c| assert_equal(out[i], c) i += 1 end end end def test_concat patterns = [ [ '', 'a', 'a' ], [ 'a', 'b', 'ab' ], [ 'abc', 'à', 'abcà' ], [ 'abá', 'b', 'abáb' ] ] patterns.each do |left, right, combined| left << right assert_equal(combined, left) end end def test_length patterns = [ [ '', 0 ], [ 'a', 1 ], [ 'ábc', 3 ], [ 'abç', 3 ], [ 'àcA绋féà', 7] ] patterns.each do |str, len| assert_equal(len, str.length_utf8) end end def test_reverse patterns = [ [ '', '' ], [ 'a', 'a' ], [ 'ábc', 'cbá' ], [ 'abç', 'çba' ] ] patterns.each do |str, rts| assert_equal(rts, str.reverse) end end end taskjuggler-3.5.0/test/test_AlgorithmDiff.rb0000644000175000017500000000750312614413013020433 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_AlgorithmDiff.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/AlgorithmDiff' class AlgorithmDiff < Test::Unit::TestCase class TestData < Struct.new(:name, :a, :b, :a_b, :b_a) end def test_editScript_and_patch data = [ TestData.new("identical inputs", [ 1, 2, 3 ], [ 1, 2, 3 ], [ ], [ ] ), TestData.new("delete 1 element in the middle", [ 1, 2, 3 ], [ 1, 3 ], [ '2d1' ], [ '2i2' ] ), TestData.new("delete 2 elements in the middle", [ 1, 2, 3, 4 ], [ 1, 4 ], [ '2d2' ], [ '2i2,3' ] ), TestData.new("delete 2 elements at 2 different locations", [ 1, 2, 3, 4, 5 ], [ 1, 3, 5 ], [ '2d1', '4d1' ], [ '2i2', '4i4' ] ), TestData.new("delete 2 and insert 1 elements at 2 different locations", [ 1, 2, 3, 5, 6, 7 ], [ 1, 3, 4, 5, 7 ], [ '2d1', '3i4', '5d1' ], [ '2i2', '3d1', '5i6' ] ), TestData.new("delete at start", [ 1, 2 ], [ 2 ], [ '1d1' ], [ '1i1' ] ), TestData.new("delete at end", [ 1, 2 ], [ 1 ], [ '2d1' ], [ '2i2' ] ), TestData.new("delete all", [ 1 ], [ ], [ '1d1' ], [ '1i1' ] ), TestData.new("replace 1 in the middle", [ 1, 0, 3 ], [ 1, 2, 3 ], [ '2d1', '2i2' ], [ '2d1', '2i0' ] ), TestData.new("replace 2 in the middle", [ 1, 0, 0, 4 ], [ 1, 2, 3, 4 ], [ '2d2', '2i2,3' ], [ '2d2', '2i0,0' ] ), TestData.new("many similar values, some changes", [ 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1 ], [ 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1 ], [ '3i1,1', '7d1', '11d1', '12i0' ], [ '3d2', '7i1', '12d1', '11i1' ] ) ] data.each do |set| set.a.extend(Diffable) set.b.extend(Diffable) res = (diff = set.a.diff(set.b)).editScript assert_equal(set.a_b, res, "A->B edit script #{set.name} failed") assert_equal(set.b, set.a.patch(diff), "A->B patch #{set.name} failed") res = (diff = set.b.diff(set.a)).editScript assert_equal(set.b_a, res, "B->A edit script #{set.name} failed") assert_equal(set.a, set.b.patch(diff), "B->A patch #{set.name} failed") end end def test_StringDiff data = [ TestData.new("Some insertions, some changes, some deletions", "0\n1\n2\n4\n5\n6\n7\n", "0\n2\nA\nB\n6\n5\n7\n \n", "2d1\n< 1\n4,5c3,4\n< 4\n< 5\n---\n> A\n> B\n6a6\n> 5\n7a8\n> \n", "1a2\n> 1\n3,5c4\n< A\n< B\n< 6\n---\n> 4\n6a6\n> 6\n8d7\n< \n" ) ] data.each do |set| set.a.extend(DiffableString) set.b.extend(DiffableString) res = (diff = set.a.diff(set.b)).to_s assert_equal(set.a_b, res, "A->B text diff #{set.name} failed") assert_equal(set.b, set.a.patch(diff), "A->B text patch #{set.name} failed") res = (diff = set.b.diff(set.a)).to_s assert_equal(set.b_a, res, "B->A text diff #{set.name} failed") assert_equal(set.a, set.b.patch(diff), "B->A text patch #{set.name} failed") end end end taskjuggler-3.5.0/test/test_CSVFile.rb0000644000175000017500000000333312614413013017144 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_CSVFile.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/reports/CSVFile' class TestCSVFile < Test::Unit::TestCase def test_to_s csv = TaskJuggler::CSVFile.new([ [ "foo", "bar" ], [ "rab", "oof" ] ]) ref = < # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/BatchProcessor' class TestProject < Test::Unit::TestCase def setup @t = Thread.new do # Timout for the watchdog timer. Even slow hardware should finish each # test in under 30s. sleep(120) assert(false, 'Test timed out') end end def teardown @t.kill end def test_simple doRun(1, 1) { sleep 0.1 } doRun(1, 2) { sleep 0.1 } doRun(1, 7) { sleep 0.1 } doRun(2, 1) { sleep 0.1 } doRun(2, 2) { sleep 0.1 } doRun(2, 33) { sleep 0.1 } doRun(3, 1) { sleep 0.1 } doRun(3, 3) { sleep 0.1 } doRun(3, 67) { sleep 0.1 } end # This test case triggers a Ruby 1.9.x mutex bug def test_fileIO doRun(3, 80) do fname = "test#{$$}.txt" f = File.new(fname, 'w') 0.upto(10000) { |i| f.puts "#{i} Hello, world!" } f.close File.delete(fname) end end def doRun(maxCPUs, jobs, &block) bp = TaskJuggler::BatchProcessor.new(maxCPUs) jobs.times do |i| bp.queue("job #{i}") { runJob(i, &block) } end @cnt = 0 lock = Monitor.new bp.wait do |j| puts "Signal error" if j.retVal.signaled? postprocess(j) lock.synchronize { @cnt += 1 } end assert_equal(jobs, @cnt, "Not all threads terminated propertly (#{@cnt})") end def runJob(n, &block) puts "Job #{n} started" yield $stderr.puts "Error #{n}" if n % 2 == 0 puts "Job #{n} finished" n end def postprocess(job) assert_equal(job.retVal.exitstatus, job.jobId, 'JobID mismatch') assert_equal(job.retVal.pid, job.pid, 'PID mismatch') assert_equal("job #{job.jobId}", job.tag) text = <<"EOT" Job #{job.jobId} started Job #{job.jobId} finished EOT assert_equal(text, job.stdout, "STDOUT mismatch #{job.stdout}") if job.jobId % 2 == 0 text = "Error #{job.jobId}\n" else text = '' end assert_equal(text, job.stderr, "STDERR mismatch #{job.stderr}") end end taskjuggler-3.5.0/test/test_PropertySet.rb0000644000175000017500000000350512614413013020212 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_PropertySet.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/Project' class TaskJuggler class TestPropertySet < Test::Unit::TestCase def setup end def teardown end def test_index p = TaskJuggler::Project.new('p', 'Project', '1.0') p['start'] = TjTime.new('2008-07-29') p['end'] = TjTime.new('2008-08-31') # This set of Arrays describes the tree structure that we want to test. # Each Array element is an tuple of breakdown structure idex and parent node. nodes = [ [ '1', nil ], [ '1.1', '1' ], [ '1.1.1', '1.1' ], [ '1.1.2', '1.1' ], [ '1.2', '1' ], [ '1.1.3', '1.1'], [ '2', nil ], [ '2.1', '2' ] ] # Now we create the nodes according to the above list. i = 0 nodes.each do |id, parent| # For the node id we use the expected bsi result. Task.new(p, id, "Node #{id}", parent ? p.task(parent) : nil) Resource.new(p, id, "Node #{id}", parent ? p.resource(parent) : nil) i += 1 end p.tasks.index p.resources.index p.tasks.each do |t| assert_equal(t.fullId, t.get('bsi')) end p.tasks.removeProperty('1.1') p.tasks.index assert_equal('1.1', p.task('1.2').get('bsi')) p.resources.each do |r| assert_equal(r.fullId, r.get('bsi')) end end end end taskjuggler-3.5.0/test/test_WorkingHours.rb0000644000175000017500000001207612614413013020356 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_WorkingHours.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/TjTime' require 'taskjuggler/WorkingHours' class TaskJuggler class TestLimits < Test::Unit::TestCase def test_equal wh1 = WorkingHours.new(3600, TjTime.new('2010-01-01'), TjTime.new('2010-12-31')) wh2 = WorkingHours.new(3600, TjTime.new('2010-01-01'), TjTime.new('2010-12-31')) assert(wh1 == wh2, "working hours must be equal") wh2.setWorkingHours(3, [[ 10 * 60 * 60, 11 * 60 * 60 ]]) assert(wh1 != wh2, "working hours must not be equal") 0.upto(6) do |d| wh1.setWorkingHours(d, []) wh2.setWorkingHours(d, []) end assert(wh1 == wh2, "working hours must be equal") end def test_onShift wh = WorkingHours.new(3600, TjTime.new('2010-01-01'), TjTime.new('2010-12-31')) timeZones = [ [ 'Europe/Berlin', '+0100' ], [ 'America/Los_Angeles', '-0800' ] ] # 2010-02-08 is a Monday workTimes = %w( 2010-02-08-9:04 2010-02-08-10:00 2010-02-08-11:00 2010-02-08-12:00 2010-02-08-13:00 2010-02-08-14:00 2010-02-08-15:00 2010-02-08-16:00 2010-02-09-9:00 2010-02-09-10:00 2010-02-09-11:00 2010-02-09-12:00 2010-02-09-13:00 2010-02-09-14:00 2010-02-09-15:00 2010-02-09-16:00 2010-02-10-9:00 2010-02-10-10:00 2010-02-10-11:00 2010-02-10-12:00 2010-02-10-13:00 2010-02-10-14:00 2010-02-10-15:00 2010-02-10-16:00 2010-02-11-9:00 2010-02-11-10:00 2010-02-11-11:00 2010-02-11-12:00 2010-02-11-13:00 2010-02-11-14:00 2010-02-11-15:00 2010-02-11-16:00 2010-02-12-9:00 2010-02-12-10:00 2010-02-12-11:00 2010-02-12-12:00 2010-02-12-13:00 2010-02-12-14:00 2010-02-12-15:00 2010-02-12-16:00 ) timeZones.each do |name, offset| wh.timezone = name workTimes.each do |wt| assert(wh.onShift?(TjTime.new(wt + ":00-#{offset}")), "Work time #{wt} (TZ #{name}) failed") end end offTimes = %w( 2010-02-06-9:00 2010-02-06-10:00 2010-02-06-11:00 2010-02-06-12:00 2010-02-06-13:00 2010-02-06-14:00 2010-02-06-15:00 2010-02-06-16:00 2010-02-07-9:00 2010-02-07-10:00 2010-02-07-11:00 2010-02-07-12:00 2010-02-07-13:00 2010-02-07-14:00 2010-02-07-15:00 2010-02-07-16:00 2010-02-08-0:00 2010-02-08-8:00 2010-02-08-19:00 2010-02-08-23:00 2010-02-09-0:00 2010-02-09-8:00 2010-02-09-19:00 2010-02-09-23:00 2010-02-10-0:00 2010-02-10-8:00 2010-02-10-19:00 2010-02-10-23:00 2010-02-10-0:00 2010-02-10-8:00 2010-02-10-19:00 2010-02-10-23:00 2010-02-11-0:00 2010-02-11-8:00 2010-02-11-19:00 2010-02-11-23:00 2010-02-12-0:00 2010-02-12-8:00 2010-02-12-19:00 2010-02-12-23:00 ) timeZones.each do |name, offset| wh.timezone = name offTimes.each do |wt| assert(!wh.onShift?(TjTime.new(wt + ":00-#{offset}")), "Off time #{wt} (TZ #{name}) failed") end end end def test_timeOff # Testing with default working hours. wh = WorkingHours.new(3600, TjTime.new('2010-01-01'), TjTime.new('2010-12-31')) # These intervals must have at least one working time slot in them. workTimes = [ # 2010-09-20 was a Monday [ '2010-09-20-9:00', '2010-09-20-12:00' ], [ '2010-09-20-8:00', '2010-09-20-10:00' ], [ '2010-09-20-11:00', '2010-09-20-13:00' ], [ '2010-09-20-11:00', '2010-09-20-14:00' ], [ '2010-09-20-16:00', '2010-09-20-18:00' ], [ '2010-09-20-16:00', '2010-09-20-19:00' ], [ '2010-09-20-16:00', '2010-09-21-9:00' ], [ '2010-09-20-17:00', '2010-09-21-10:00' ] ] workTimes.each do |iv| assert(!wh.timeOff?(TimeInterval.new(TjTime.new(iv[0]), TjTime.new(iv[1]))), "Work time interval #{iv[0]} - #{iv[1]} failed") end # These intervals must have no working time slot in them. offTimes = [ # 2010-09-17 was a Friday [ '2010-09-17-18:00', '2010-09-19-9:00' ], [ '2010-09-20-18:00', '2010-09-21-9:00' ] ] offTimes.each do |iv| assert(wh.timeOff?(TimeInterval.new(TjTime.new(iv[0]), TjTime.new(iv[1]))), "Off time interval #{iv[0]} - #{iv[1]} failed") end end end end taskjuggler-3.5.0/test/test_Query.rb0000644000175000017500000000532712614413013017023 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_Query.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/Project' require 'taskjuggler/Query' class TaskJuggler class TestQuery < Test::Unit::TestCase def setup @p = TaskJuggler::Project.new('id', 'name', 'ver') @p['start'] = TjTime.new('2010-09-25') @p['end'] = TjTime.new('2010-09-25') end def teardown end def test_scaleDuration q = Query.new('project' => @p, 'numberFormat' => @p['numberFormat']) units = [ :minutes, :hours, :days, :weeks, :months, :shortauto ] vals = [ # Inp mins hours days weeks months shortauto [ 0.0, '0.0', '0.0', '0.0', '0.0', '0.0', '0.0d'], [ 1.0, '1440.0', '24.0', '1.0', '0.1', '0.0', '1.0d'], [ 2.0, '2880.0', '48.0', '2.0', '0.3', '0.1', '2.0d'], [ 3.0, '4320.0', '72.0', '3.0', '0.4', '0.1', '3.0d'], [ 4.0, '5760.0', '96.0', '4.0', '0.6', '0.1', '4.0d'], [ 7.0, '10080.0', '168.0', '7.0', '1.0', '0.2', '7.0d'], [ 14.0, '20160.0', '336.0', '14.0', '2.0', '0.5', '2.0w'], [ 28.0, '40320.0', '672.0', '28.0', '4.0', '0.9', '4.0w'] ] vals.each do |inp, *out| 0.upto(5) do |i| q.loadUnit = units[i] assert_equal(out[i], q.scaleDuration(inp), "Input: #{inp}, Unit #{units[i]}") end end end def test_scaleLoad q = Query.new('project' => @p, 'numberFormat' => @p['numberFormat']) units = [ :minutes, :hours, :days, :weeks, :months, :shortauto ] vals = [ # Inp mins hours days weeks months shortauto [ 0.0, '0.0', '0.0', '0.0', '0.0', '0.0', '0.0d'], [ 0.25, '120.0', '2.0', '0.3', '0.1', '0.0', '2.0h'], [ 0.1, '48.0', '0.8', '0.1', '0.0', '0.0', '0.1d'], [ 0.5, '240.0', '4.0', '0.5', '0.1', '0.0', '0.5d'], [ 1.0, '480.0', '8.0', '1.0', '0.2', '0.0', '1.0d'], [ 1.5, '720.0', '12.0', '1.5', '0.3', '0.1', '1.5d'], [ 2.0, '960.0', '16.0', '2.0', '0.4', '0.1', '2.0d'], [ 5.0, '2400.0', '40.0', '5.0', '1.0', '0.2', '5.0d'], [ 10.0, '4800.0', '80.0', '10.0', '2.0', '0.5', '2.0w'] ] vals.each do |inp, *out| 0.upto(5) do |i| q.loadUnit = units[i] assert_equal(out[i], q.scaleLoad(inp), "Input: #{inp}, Unit #{units[i]}") end end end end end taskjuggler-3.5.0/test/test_MacroTable.rb0000644000175000017500000000322112614413013017716 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_MacroTable.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' class TaskJuggler require 'taskjuggler/TextParser/MacroTable' class TestMacroTable < Test::Unit::TestCase def setup @mt = TaskJuggler::TextParser::MacroTable.new @t = Thread.new do sleep(1) assert('Test timed out') end end def teardown @mt.clear @t.kill end def test_addAndClear @mt.add(TaskJuggler::TextParser::Macro.new('macro1', 'This is macro 1', nil)) @mt.add(TaskJuggler::TextParser::Macro.new('macro2', 'This is macro 2', nil)) @mt.clear end def test_resolve @mt.add(TaskJuggler::TextParser::Macro.new('macro1', 'This is macro 1', nil)) @mt.add(TaskJuggler::TextParser::Macro.new( 'macro2', 'This is macro 2 with ${1} and ${2}', nil)) assert_equal('This is macro 1', @mt.resolve(%w( macro1 ), nil)[1]) assert_equal('This is macro 2 with arg1 and arg2', @mt.resolve(%w( macro2 arg1 arg2), nil)[1]) assert_equal('This is macro 2 with arg1 and arg2', @mt.resolve(%w( macro2 arg1 arg2 arg3), nil)[1]) assert_equal('This is macro 2 with arg1 and ${2}', @mt.resolve(%w( macro2 arg1), nil)[1]) end end end taskjuggler-3.5.0/test/test_TjTime.rb0000644000175000017500000000736312614413013017114 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_TjTime.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') require 'test/unit' require 'taskjuggler/TjTime' class TestTjTime < Test::Unit::TestCase def setup @endTime = TaskJuggler::TjTime.new("2030-01-01") @startTimes = [ TaskJuggler::TjTime.new([ 1972, 3, 15, 19, 27 ]), TaskJuggler::TjTime.new([ 1972, 2, 12, 10 ]), TaskJuggler::TjTime.new([ 1984, 11, 1, 12 ]), TaskJuggler::TjTime.new([ 1992, 1, 1 ]), ] end def teardown end def test_sameTimeNextDay @startTimes.each do |st| t1 = t2 = st t1_a = old_t2_a = t1.to_a begin t2 = t2.sameTimeNextDay t2_a = t2.to_a assert_equal(t1_a[0, 3], t2_a[0, 3]) assert(t2_a[3] == old_t2_a[3] + 1 || t2_a[3] == 1, t2_a.join(', ')) assert(t2_a[7] == old_t2_a[7] + 1 || t2_a[7] == 1, t2_a.join(', ')) old_t2_a = t2_a end while t2 < @endTime end end def test_sameTimeNextWeek @startTimes.each do |st| t1 = t2 = st t1_a = old_t2_a = t1.to_a begin t2 = t2.sameTimeNextWeek t2_a = t2.to_a # Check that hour, minutes and seconds are the same. assert_equal(t1_a[0, 3], t2_a[0, 3]) # Check that weekday is the same assert(t2_a[6] == old_t2_a[6], "old_t2: #{old_t2_a.join(', ')}\nt2: #{t2_a.join(', ')}") # Check that day of year has increased by 7 or has wrapped at end of # the year. assert(t2_a[7] == old_t2_a[7] + 7 || t2_a[7] <= 7, "old_t2: #{old_t2_a.join(', ')}\nt2: #{t2_a.join(', ')}") old_t2_a = t2_a end while t2 < @endTime end end def test_sameTimeNextMonth @startTimes.each do |st| t1 = t2 = st t1_a = old_t2_a = t1.to_a begin t2 = t2.sameTimeNextMonth t2_a = t2.to_a assert_equal(t1_a[0, 3], t2_a[0, 3]) assert(t2_a[3] == t2_a[3] || t2_a[3] > 28, "old_t2: #{old_t2_a.join(', ')}\nt2: #{t2_a.join(', ')}") assert(t2_a[4] == old_t2_a[4] + 1 || t2_a[4] == 1, "old_t2: #{old_t2_a.join(', ')}\nt2: #{t2_a.join(', ')}") old_t2_a = t2_a end while t2 < @endTime end end def test_sameTimeNextQuarter @startTimes.each do |st| t1 = t2 = st t1_a = old_t2_a = t1.to_a begin t2 = t2.sameTimeNextQuarter t2_a = t2.to_a assert_equal(t1_a[0, 3], t2_a[0, 3], "old_t2: #{old_t2_a.join(', ')}\nt2: #{t2_a.join(', ')}") assert((t2_a[4] == old_t2_a[4] + 3 && t2_a[5] == old_t2_a[5]) || (t2_a[4] == old_t2_a[4] - 9 && t2_a[5] == old_t2_a[5] + 1), "old_t2: #{old_t2_a.join(', ')}\nt2: #{t2_a.join(', ')}") old_t2_a = t2_a end while t2 < @endTime end end def test_nextDayOfWeek probes = [ [ '2010-03-17', 0, '2010-03-21' ], [ '2010-03-17', 1, '2010-03-22' ], [ '2010-03-17', 2, '2010-03-23' ], [ '2010-03-17', 3, '2010-03-24' ], [ '2010-03-17', 4, '2010-03-18' ], [ '2010-03-17', 5, '2010-03-19' ], [ '2010-03-17', 6, '2010-03-20' ], ] probes.each do |p| assert_equal(TaskJuggler::TjTime.new(p[2]), TaskJuggler::TjTime.new(p[0]).nextDayOfWeek(p[1])) end end end taskjuggler-3.5.0/test/test_TjpExample.rb0000644000175000017500000000607612614413013017771 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_TjpExample.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') require 'test/unit' require 'taskjuggler/TjpExample' class TestScheduler < Test::Unit::TestCase def setup end def teardown end def test_1 text = <<'EOT' # *** EXAMPLE: 1 + This line is in. # *** EXAMPLE: 1 - This line is out. EOT ex = TaskJuggler::TjpExample.new ex.parse(text) out = ex.to_s ref = <<'EOT' This line is in. This line is out. EOT assert_equal(ref, out) out = ex.to_s('1') ref = <<'EOT' This line is in. EOT assert_equal(ref, out) end def test_2 text = <<'EOT' This line is in no snip. # *** EXAMPLE: 1 + This line is in snip 1. # *** EXAMPLE: 2 + This line is in snip 1 and 2. This line is as well in 1 and 2. # *** EXAMPLE: 1 - This line is in snip 2. # *** EXAMPLE: 3 + This line is in snip 2 and 3. # *** EXAMPLE: 2 - This line is in snip 3. EOT ex = TaskJuggler::TjpExample.new ex.parse(text) out = ex.to_s ref = <<'EOT' This line is in no snip. This line is in snip 1. This line is in snip 1 and 2. This line is as well in 1 and 2. This line is in snip 2. This line is in snip 2 and 3. This line is in snip 3. EOT assert_equal(ref, out) out = ex.to_s('1') ref = <<'EOT' This line is in snip 1. This line is in snip 1 and 2. This line is as well in 1 and 2. EOT assert_equal(ref, out) out = ex.to_s('2') ref = <<'EOT' This line is in snip 1 and 2. This line is as well in 1 and 2. This line is in snip 2. This line is in snip 2 and 3. EOT assert_equal(ref, out) out = ex.to_s('3') ref = <<'EOT' This line is in snip 2 and 3. This line is in snip 3. EOT assert_equal(ref, out) end def test_3 text = <<'EOT' # *** EXAMPLE: 1 + This line is in. # *** EXAMPLE: 1 - This line is out. # *** EXAMPLE: 1 + This line is in as well. EOT ex = TaskJuggler::TjpExample.new ex.parse(text) out = ex.to_s ref = <<'EOT' This line is in. This line is out. This line is in as well. EOT assert_equal(ref, out) out = ex.to_s('1') ref = <<'EOT' This line is in. This line is in as well. EOT assert_equal(ref, out) end def test_error_1 text = <<'EOT' # *** EXAMPLE: 1 - This line is in. EOT ex = TaskJuggler::TjpExample.new assert_raise(RuntimeError) { ex.parse(text) } end def test_error_2 text = <<'EOT' # *** EXAMPLE: 1 + This line is in. # *** EXAMPLE: 1 + This line is in. EOT ex = TaskJuggler::TjpExample.new assert_raise(RuntimeError) { ex.parse(text) } end def test_error_3 text = <<'EOT' # *** EXAMPLE: foo ! This line is in. EOT ex = TaskJuggler::TjpExample.new assert_raise(RuntimeError) { ex.parse(text) } end end taskjuggler-3.5.0/test/test_URLParameter.rb0000644000175000017500000000136312614413013020215 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_URLParameter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/URLParameter' class TaskJuggler class TestURLParameter < Test::Unit::TestCase def test_simple s = "Hello, world!\n" assert_equal(s, URLParameter.decode(URLParameter.encode(s))) end end end taskjuggler-3.5.0/test/test_CollisionDetector.rb0000644000175000017500000000475212614413013021344 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_CollisionDetector.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/reports/GanttRouter' class TaskJuggler class TestCollisionDetector < Test::Unit::TestCase def test_collisions # To test the collion?() method we use a fix block area and then try # various lines that either collide or not. # # 2,2 # +--+ # | | # +--+ # 4,4 # # We use the same set of lines for horizontal and vertical tests. The # first value is the x or y coordinate of the line, the tuple is the start # and end coordinate in the other dimension. The third value is the # expected result. lines = [ [ [ 1, [0, 1] ], false ], [ [ 2, [0, 1] ], false ], [ [ 3, [0, 1] ], false ], [ [ 4, [0, 1] ], false ], [ [ 5, [0, 1] ], false ], [ [ 1, [0, 2] ], false ], [ [ 2, [0, 2] ], true ], [ [ 3, [0, 2] ], true ], [ [ 4, [0, 2] ], true ], [ [ 5, [0, 2] ], false ], [ [ 1, [0, 4] ], false ], [ [ 2, [0, 4] ], true ], [ [ 3, [0, 4] ], true ], [ [ 4, [0, 4] ], true ], [ [ 5, [0, 4] ], false ], [ [ 1, [0, 5] ], false ], [ [ 2, [0, 5] ], true ], [ [ 3, [0, 5] ], true ], [ [ 4, [0, 5] ], true ], [ [ 5, [0, 6] ], false ], [ [ 1, [4, 6] ], false ], [ [ 2, [4, 6] ], true ], [ [ 3, [4, 6] ], true ], [ [ 4, [4, 6] ], true ], [ [ 5, [4, 6] ], false ], [ [ 1, [5, 6] ], false ], [ [ 2, [5, 6] ], false], [ [ 3, [5, 6] ], false ], [ [ 4, [5, 6] ], false ], [ [ 5, [5, 6] ], false ] ] # Try horizontal lines first. cd = CollisionDetector.new(10, 10) cd.addBlockedZone(2, 2, 3, 3, true, true) lines.each do |line| assert_equal(line[1], cd.collision?(*(line[0] + [ true ])), "Horizontal #{line[0]} is not #{line[1]}") end lines.each do |line| assert_equal(line[1], cd.collision?(*(line[0] + [ false ])), "Vertical #{line[0]} is not #{line[1]}") end end end end taskjuggler-3.5.0/test/test_RealFormat.rb0000644000175000017500000000467412614413013017756 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = test_RealFormat.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 require 'test/unit' require 'taskjuggler/RealFormat' class TaskJuggler class TestRealFormat < Test::Unit::TestCase def setup end def teardown end def test_frac values = [ # Input 0 1 2 3 fraction digits [ 0.01, '0', '0.0', '0.01', '0.010' ], [ 0.04, '0', '0.0', '0.04', '0.040' ], [ 0.05, '0', '0.1', '0.05', '0.050' ], [ 0.09, '0', '0.1', '0.09', '0.090' ], [ 0.099, '0', '0.1', '0.10', '0.099' ], [ 0.0999, '0', '0.1', '0.10', '0.100' ], [ 0.1, '0', '0.1', '0.10', '0.100' ], [ 0.4, '0', '0.4', '0.40', '0.400' ], [ 0.5, '1', '0.5', '0.50', '0.500' ], [ 0.9, '1', '0.9', '0.90', '0.900' ], [ 0.99, '1', '1.0', '0.99', '0.990' ], [ 0.999, '1', '1.0', '1.00', '0.999' ], [ 0.9999, '1', '1.0', '1.00', '1.000' ], [ 1.0, '1', '1.0', '1.00', '1.000' ], [ 4.0, '4', '4.0', '4.00', '4.000' ], [ 5.0, '5', '5.0', '5.00', '5.000' ], [ 9.0, '9', '9.0', '9.00', '9.000' ], [ 9.9, '10', '9.9', '9.90', '9.900' ], [ 9.999, '10', '10.0', '10.00', '9.999' ], [ 9.9999, '10', '10.0', '10.00', '10.000' ] ] values.each do |inp, *out| 0.upto(3) do |i| f = RealFormat.new(['(', ')', ',', '.', i]) assert_equal(out[i], res = f.format(inp), "Value: #{inp} Digits: #{i} Result: #{res}") end end end def test_negative f = RealFormat.new(['(', ')', ',', '.', 3]) assert_equal(f.format(-Math::PI), '(3.142)') f = RealFormat.new(['-', '', ',', '.', 3]) assert_equal(f.format(-Math::PI), '-3.142') end def test_thousand f = RealFormat.new(['(', ')', ',', '.', 3]) assert_equal(f.format(1234567.8901234), '1,234,567.890') f = RealFormat.new(['(', ')', ',', '.', 3]) assert_equal(f.format(123456.78901234), '123,456.789') f = RealFormat.new(['(', ')', ',', '.', 0]) assert_equal(f.format(-1234.5678901234), '(1,235)') end end end taskjuggler-3.5.0/test/ReferenceGenerator.rb0000644000175000017500000000502412614413013020416 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ReferenceGenerator.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This script can be used to (re-)generate all reference reports in the # TaskJuggler test suite. These reports will be put in the /refs directories # in the TestSuite sub-directories. Usually, reference reports are generated # by hand and then manually checked for correctness before they are added to # the test suite. But sometimes changes in the syntax will require all # reference files to be regenerated. # Reference reports must use the following naming scheme: # -[0-9]+.(csv|html) $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') if __FILE__ == $0 $:.unshift File.dirname(__FILE__) require 'fileutils' require 'taskjuggler/Tj3Config' require 'taskjuggler/TaskJuggler' class TaskJuggler class ReferenceGenerator def initialize AppConfig.appName = 'taskjuggler3' ENV['TASKJUGGLER_DATA_PATH'] = './:../' ENV['TZ'] = 'Europe/Berlin' end def generate processDirectory('ReportGenerator/Correct') end private def processProject(tjpFile, outputDir) deleteOldReports(tjpFile[0..-5]) puts "Generating references for #{tjpFile}" tj = TaskJuggler.new tj.parse([ tjpFile ]) || error("Parser failed for ${tjpFile}") tj.schedule || error("Scheduler failed for #{tjpFile}") tj.generateReports(outputDir) || error("Report generator failed for #{tjpFile}") unless tj.messageHandler.messages.empty? error("Unexpected error in #{tjpFile}") end end def processDirectory(dir) puts "Generating references in #{dir}" path = File.dirname(__FILE__) + '/' projectDir = path + "TestSuite/#{dir}/" outputDir = path + "TestSuite/#{dir}/refs/" Dir.glob(projectDir + '*.tjp').each do |f| processProject(f, outputDir) end end def deleteOldReports(basename) %w( .csv .html ).each do |ext| Dir.glob(basename + "-[0-9]*" + ext).each do |f| puts "Removing old report #{f}" File.delete(f) end end end def error(text) $stderr.puts text exit 1 end end ReferenceGenerator.new.generate end taskjuggler-3.5.0/Rakefile0000644000175000017500000000072512614413013015015 0ustar bernatbernat$:.unshift File.join(File.dirname(__FILE__)) # Add the lib directory to the search path if it isn't included already lib = File.expand_path('../lib', __FILE__) $:.unshift lib unless $:.include?(lib) require 'rake/clean' Dir.glob( 'tasks/*.rake').each do |fn| begin load fn; rescue LoadError puts "#{fn.split('/')[1]} tasks unavailable: #{$!}" end end task :default => [ :test ] desc 'Run all unit and spec tests' task :test => [ :unittest, :spec ] taskjuggler-3.5.0/tasks/0000755000175000017500000000000012614413013014471 5ustar bernatbernattaskjuggler-3.5.0/tasks/changelog.rake0000644000175000017500000000770212614413013017272 0ustar bernatbernatrequire 'time' desc 'Generate the CHANGELOG file' task :changelog do class Entry attr_reader :type def initialize(ref, author, time, message) @ref = ref @author = author @time = time @message = message if (m = /New: (.*)/.match(@message)) @type = :feature @message = m[1] elsif (m = /Fix: (.*)/.match(@message)) @type = :bugfix @message = m[1] else @type = :other end end def to_s " * #{@message}\n" end end class Release attr_reader :date, :version, :tag def initialize(tag, predecessor) @tag = tag # We only support release tags in the form X.X.X @version = /\d+\.\d+\.\d+/.match(tag) # Construct a Git range. interval = predecessor ? "#{predecessor.tag}..#{@tag}" : @tag # Get the date of the release date = Time.parse(/Date: (.*)/.match(`git show #{tag}`)[1]).utc @date = date.strftime("%Y-%m-%d") @entries = [] # Use -z option for git-log to get 0 bytes as separators. `git log -z #{interval}`.split("\0").each do |commit| # We ignore merges. next if commit =~ /^Merge: \d*/ ref, author, time, _, message = commit.split("\n", 5) ref = ref[/commit ([0-9a-f]+)/, 1] author = author[/Author: (.*)/, 1].strip time = Time.parse(time[/Date: (.*)/, 1]).utc # Eleminate git-svn-id: lines message.gsub!(/git-svn-id: .*\n/, '') # Eliminate Signed-off-by: lines message.gsub!(/Signed-off-by: .*\n/, '') message.strip! @entries << Entry.new(ref, author, time, message) end end def empty? @entries.empty? end def to_s s = '' if hasFeatures? || hasFixes? if hasFeatures? s << "== New Features\n\n" @entries.each do |entry| s << entry.to_s if entry.type == :feature end s << "\n" end if hasFixes? s << "== Bug Fixes\n\n" @entries.each do |entry| s << entry.to_s if entry.type == :bugfix end s << "\n" end else @entries.each do |entry| s << entry.to_s end end s end private def hasFeatures? @entries.each do |entry| return true if entry.type == :feature end false end def hasFixes? @entries.each do |entry| return true if entry.type == :bugfix end false end end class ChangeLog def initialize @releases = [] predecessor = nil getReleaseVersions.each do |version| @releases << (predecessor = Release.new(version, predecessor)) end end def to_s s = '' @releases.reverse.each do |release| next if release.empty? # We use RDOC markup syntax to generate a title if release.version s << "= Release #{release.version} (#{release.date})\n\n" else s << "= Next Release (Some Day)\n\n" end s << release.to_s + "\n" end s end private # 'git tag' is not sorted numerically. This function implements a # numerical comparison for tag versions of the format 'release-X.X.X'. X # can be a multi-digit number. def compareTags(a, b) def versionToComparable(v) /\d+\.\d+\.\d+/.match(v)[0].split('.').map{ |l| sprintf("%03d", l.to_i)}. join('.') end versionToComparable(a) <=> versionToComparable(b) end def getReleaseVersions # Get list of release tags from Git repository releaseVersions = `git tag`.split("\n").map { |r| r.chomp }. delete_if { |r| ! (/release-\d+\.\d+\.\d+/ =~ r) }. sort{ |a, b| compareTags(a, b) } releaseVersions << 'HEAD' end end File.open('CHANGELOG', 'w+') do |changelog| changelog.puts ChangeLog.new.to_s end end taskjuggler-3.5.0/tasks/manual.rake0000644000175000017500000000061712614413013016616 0ustar bernatbernat# TASK MANUAL require 'taskjuggler/apps/Tj3Man' desc 'Generate User Manual' task :manual do htmldir = 'manual/html' rm_rf htmldir if File.exists? htmldir mkdir_p htmldir # Make sure we can run 'rake manual' from all subdirs. ENV['TASKJUGGLER_DATA_PATH'] = Array.new(4) { |i| Dir.getwd + '/..' * i }.join(':') TaskJuggler::Tj3Man.new.main([ '-d', htmldir, '-m', '--silent' ]) end taskjuggler-3.5.0/tasks/spec.rake0000644000175000017500000000025312614413013016267 0ustar bernatbernatrequire 'rake' require 'rspec/core/rake_task' desc 'Run all RSpec tests in the spec directory' RSpec::Core::RakeTask.new(:spec) do |t| t.pattern = 'spec/*_spec.rb' end taskjuggler-3.5.0/tasks/vim.rake0000644000175000017500000000024412614413013016130 0ustar bernatbernat# TASK VIM SYNTAX require 'taskjuggler/VimSyntax' desc 'Generate vim.tjp Vim syntax file' task :vim do TaskJuggler::VimSyntax.new.generate('data/tjp.vim') end taskjuggler-3.5.0/tasks/kate.rake0000644000175000017500000000026212614413013016261 0ustar bernatbernat# TASK Kate SYNTAX require 'taskjuggler/KateSyntax' desc 'Generate kate-tjp.xml Kate syntax file' task :kate do TaskJuggler::KateSyntax.new.generate('data/kate-tjp.xml') end taskjuggler-3.5.0/tasks/gem.rake0000644000175000017500000000355112614413013016111 0ustar bernatbernat# GEM TASK require 'find' require 'rubygems' require 'rubygems/package' # Unfortunately Rake::GemPackageTest cannot deal with files that are generated # by Rake targets. So we have to write our own packaging task. desc 'Build the gem package' task :gem => [:clobber] do Rake::Task[:vim].invoke Rake::Task[:manual].invoke Rake::Task[:changelog].invoke Rake::Task[:permissions].invoke load 'taskjuggler.gemspec'; # Build the gem file according to the loaded spec. if RUBY_VERSION >= "2.0.0" Gem::Package.build(GEM_SPEC) else Gem::Builder.new(GEM_SPEC).build end pkgBase = "#{GEM_SPEC.name}-#{GEM_SPEC.version}" # Create a pkg directory if it doesn't exist already. FileUtils.mkdir_p('pkg') # Move the gem file into the pkg directory. verbose(true) { FileUtils.mv("#{pkgBase}.gem", "pkg/#{pkgBase}.gem")} # Create a tar file with all files that are in the gem. FileUtils.rm_f("pkg/#{pkgBase}.tar") FileUtils.rm_f("pkg/#{pkgBase}.tar.gz") verbose(false) {GEM_SPEC.files.each { |f| `tar rf pkg/#{pkgBase}.tar "#{f}"` } } # And gzip the file. `gzip pkg/#{pkgBase}.tar` end desc 'Make sure all files and directories are readable' task :permissions do # Find the bin and test directories relative to this file. baseDir = File.expand_path('..', File.dirname(__FILE__)) execs = Dir.glob("#{baseDir}/bin/*") + Dir.glob("#{baseDir}/test/**/genrefs") Find.find(baseDir) do |f| # Ignore the whoke pkg directory as it may contain links to the other # directories. next if Regexp.new("#{baseDir}/pkg/*").match(f) FileUtils.chmod_R((FileTest.directory?(f) || execs.include?(f) ? 0755 : 0644), f) end end desc 'Run all tests and build scripts and create the gem package' task :release do Rake::Task[:test].invoke Rake::Task[:spec].invoke Rake::Task[:rdoc].invoke Rake::Task[:gem].invoke end taskjuggler-3.5.0/tasks/rdoc.rake0000644000175000017500000000051112614413013016261 0ustar bernatbernatif RUBY_VERSION < '1.9.3' require 'rake/rdoctask' else require 'rdoc/task' end # RDOC TASK Rake::RDocTask.new(:rdoc) do |t| t.rdoc_files = %w( README.rdoc COPYING CHANGELOG ) + `git ls-files -- lib`.split("\n") t.title = "TaskJuggler API documentation" t.main = 'README.rdoc' t.rdoc_dir = 'doc' end taskjuggler-3.5.0/tasks/test.rake0000644000175000017500000000044012614413013016312 0ustar bernatbernat$:.unshift File.join(File.dirname(__FILE__), '..', 'test') require 'rake/testtask' # TEST TASK desc 'Run all unit tests in the test directory' Rake::TestTask.new(:unittest) do |t| t.libs << 'test' t.test_files = FileList['test/test_*.rb'] t.verbose = false t.warning = true end taskjuggler-3.5.0/spec/0000755000175000017500000000000012614413013014276 5ustar bernatbernattaskjuggler-3.5.0/spec/Tj3Daemon_spec.rb0000644000175000017500000000233412614413013017423 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3Daemon_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rubygems' require 'support/DaemonControl' class TaskJuggler describe Tj3Daemon do include DaemonControl before(:each) do cleanup startDaemon end after(:each) do stopDaemon cleanup end it 'should be startable and stopable' do res = stdIoWrapper do Tj3Client.new.main(%w( --unsafe --silent status )) end res.returnValue.should == 0 res.stdErr.should == '' res.stdOut.should match /No projects registered/ end it 'should be able to load a project' do prj = 'project foo "Foo" 2011-03-14 +1d task "Foo"' res = stdIoWrapper(prj) do Tj3Client.new.main(%w( --unsafe add . )) end res.returnValue.should == 0 res.stdErr.should match /Project\(s\) \. added/ end end end taskjuggler-3.5.0/spec/support/0000755000175000017500000000000012614413013016012 5ustar bernatbernattaskjuggler-3.5.0/spec/support/spec_helper.rb0000644000175000017500000000105212614413013020626 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = spec_helper.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # RSpec.configure do |c| c.filter_run_excluding :ruby => lambda {|version| !(RUBY_VERSION.to_s =~ /^#{version.to_s}/) } end taskjuggler-3.5.0/spec/support/DaemonControl.rb0000644000175000017500000000442712614413013021112 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = DaemonControl.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/StdIoWrapper' require 'taskjuggler/apps/Tj3Daemon' require 'taskjuggler/apps/Tj3Client' require 'fileutils' class TaskJuggler module DaemonControl include StdIoWrapper include FileUtils def startDaemon(config = '') (f = File.new('taskjuggler.rc', 'w')).write(<<"EOT" _global: authKey: 'secret_key' port: 0 _log: outputLevel: 3 logLevel: 3 #{config} EOT ) f.close if (pid = fork).nil? at_exit { exit! } $stdout.reopen('stdout.log', 'w') $stderr.reopen('stderr.log', 'w') res = stdIoWrapper do Tj3Daemon.new.main(%w( --silent )) end raise "Failed to start tj3d: #{res.stdErr}" if res.returnValue != 0 exit! else # Wait for the daemon to get online. i = 0 while !File.exists?('.tj3d.uri') && i < 10 sleep 0.5 i += 1 end raise 'Daemon did not start properly' if i == 10 end 0 end def stopDaemon res = stdIoWrapper do Tj3Client.new.main(%w( --silent --unsafe terminate )) end raise "tj3d termination failed: #{res.stdErr}" if res.returnValue != 0 i = 0 while File.exists?('.tj3d.uri') && i < 10 sleep 0.5 i += 1 end raise "Daemon did not terminate properly" if i == 10 # Cleanup file system again. %w( taskjuggler.rc stdout.log stderr.log ).each do |file| File.delete(file) end end def cleanup rm_rf %w( TimeSheetTemplates TimeSheets timesheets.log StatusSheetTemplates StatusSheets statussheets.log tj3d.log tj3client.log tj3.log tj3ss_sender.log tj3ss_receiver.log tj3ss_summary.log tj3ts_sender.log tj3ts_receiver.log tj3ts_summary.log ) end end end taskjuggler-3.5.0/spec/TraceReport_spec.rb0000644000175000017500000000440012614413013020065 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TraceReport_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TaskJuggler' require 'taskjuggler/StdIoWrapper' require 'taskjuggler/apps/Tj3' class TaskJuggler describe TraceReport do include StdIoWrapper before(:all) do tf = 'tracereport' @tf = tf + '.csv' @prj = <<"EOT" project "Test" 2012-03-01 +2m { now 2012-03-11 } resource r "R" task t1 "T1" { effort 10d allocate r } task t2 "T2" { depends t1 duration 5d } task t3 "T3" { depends t2 } tracereport '#{tf}' { columns complete } EOT end after(:all) do File.delete(@tf) end it 'should generate a trace report' do File.delete(@tf) if File.exists?(@tf) tj3(@prj) ref = <<'EOT' "Date";"t1:plan.complete";"t2:plan.complete";"t3:plan.complete" "2012-03-11";70.0;0.0;0.0 EOT checkCSV(@tf, ref) end it 'should replace the existing line' do before = File.read(@tf) tj3(@prj) after = File.read(@tf) before.should == after end it 'should add a new line for another day' do prj = @prj.gsub(/now 2012-03-11/, 'now 2012-03-18') tj3(prj) ref = <<'EOT' "Date";"t1:plan.complete";"t2:plan.complete";"t3:plan.complete" "2012-03-11";70.0;0.0;0.0 "2012-03-18";100.0;65.83333333333333;0.0 EOT checkCSV(@tf, ref) end it 'should add to a file without data columns' do File.write(@tf, <<'EOT' "Date" "2012-03-11" EOT ) tj3(@prj) ref = <<'EOT' "Date";"t1:plan.complete";"t2:plan.complete";"t3:plan.complete" "2012-03-11";70.0;0.0;0.0 EOT checkCSV(@tf, ref) end private def tj3(prj) res = stdIoWrapper(prj) do Tj3.new.main(%w( --silent --add-trace . )) end res.stdOut.should == '' res.stdErr.should == '' res.returnValue.should == 0 end def checkCSV(file, ref) File.read(file).should == ref end end end taskjuggler-3.5.0/spec/Color_spec.rb0000644000175000017500000000352012614413013016713 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Color_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Painter/Color' class TaskJuggler class Painter describe Color do it 'should Convert RGB to HSV' do Color.new(0, 0, 0).to_hsv.should == [ 0, 0, 0 ] Color.new(255, 0, 0).to_hsv.should == [ 0, 255, 255 ] Color.new(255, 0, 4).to_hsv.should == [ 359, 255, 255 ] Color.new(255, 255, 255).to_hsv.should == [ 0, 0, 255 ] Color.new(60, 125, 116).to_hsv.should == [ 171, 132, 125 ] end it 'should convert HSV to RGB' do Color.new(0, 0, 0, :hsv).to_rgb.should == [ 0, 0, 0 ] Color.new(0, 0, 255, :hsv).to_rgb.should == [ 255, 255, 255 ] Color.new(150, 0, 255, :hsv).to_rgb.should == [ 255, 255, 255 ] Color.new(93, 156, 121, :hsv).to_rgb.should == [ 80, 121, 46 ] Color.new(275, 87, 94, :hsv).to_rgb.should == [ 80, 61, 94 ] Color.new(335, 47, 223, :hsv).to_rgb.should == [ 223, 181, 199 ] end it 'should Convert to HSV and back' do 0.step(255, 8) do |r| 0.step(255, 8) do |g| 0.step(255, 8) do |b| rgbRef = [r, g, b] hsv = Color.new(r, g, b).to_hsv rgb = Color.new(*hsv, :hsv).to_rgb 3.times do |i| # Due to rounding errors, we tolerate a difference of up to 5. (rgb[i] - rgbRef[i]).abs.should <= 5 end end end end end end end end taskjuggler-3.5.0/spec/ProjectBroker_spec.rb0000644000175000017500000000657312614413013020423 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ProjectBroker_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/daemon/ProjectBroker' require 'support/spec_helper' class TaskJuggler def TaskJuggler::runBroker(pb, key) pb.authKey = key pb.daemonize = false pb.logStdIO = false pb.port = 0 # Don't generate any debug or info messages mh = MessageHandlerInstance.instance mh.outputLevel = 1 mh.logLevel = 1 t = Thread.new { pb.start } yield pb.stop t.join end describe ProjectBroker, :ruby => 1.9 do it "can be started and stopped" do @pb = ProjectBroker.new @authKey = 'secret' TaskJuggler::runBroker(@pb, @authKey) do true end end end describe ProjectBrokerIface, :ruby => 1.9 do before do @pb = ProjectBroker.new @pbi = ProjectBrokerIface.new(@pb) @authKey = 'secret' end describe "apiVersion" do it "should fail with bad authentication key" do TaskJuggler::runBroker(@pb, @authKey) do @pbi.apiVersion('bad key', 1).should == 0 end end it "should pass with correct authentication key" do TaskJuggler::runBroker(@pb, @authKey) do @pbi.apiVersion(@authKey, 1).should == 1 end end it "should fail with wrong API version", :ruby => 1.9 do TaskJuggler::runBroker(@pb, @authKey) do @pbi.apiVersion(@authKey, 0).should == -1 end end end describe "command" do it "should fail with bad authentication key" do TaskJuggler::runBroker(@pb, @authKey) do @pbi.command('bad key', :status, []).should be_false end end it "should support 'status'" do TaskJuggler::runBroker(@pb, @authKey) do @pbi.command(@authKey, :status, []).should match \ /.*No projects registered.*/ end end it "should support 'terminate'" do TaskJuggler::runBroker(@pb, @authKey) do @pbi.command(@authKey, :stop, []).should be_nil end end it "should support 'add' and 'remove'" do TaskJuggler::runBroker(@pb, @authKey) do stdIn = StringIO.new("project foo 'foo' 2011-01-04 +1w task 'foo'") stdOut = StringIO.new stdErr = StringIO.new args = [ Dir.getwd, [ '.' ], stdOut, stdErr, stdIn, true ] @pbi.command(@authKey, :addProject, args).should be_true stdErr.string.should be_empty # Can't remove non-existing project bar @pbi.command(@authKey, :removeProject, 'bar').should be_false @pbi.command(@authKey, :removeProject, 'foo').should be_true # Can't remove foo twice @pbi.command(@authKey, :removeProject, 'foo').should be_false end end end describe "updateState" do it "should fail with bad authentication key" do TaskJuggler::runBroker(@pb, @authKey) do @pbi.updateState('bad key', 'foo', 'foo', :status, true).should \ be_false end end end end end taskjuggler-3.5.0/spec/StatusSheets_spec.rb0000644000175000017500000001573412614413013020306 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = StatusSheets_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rubygems' require 'support/DaemonControl' require 'taskjuggler/apps/Tj3SsSender' require 'taskjuggler/apps/Tj3SsReceiver' class TaskJuggler class StatusSheetTest end describe StatusSheetTest do include DaemonControl before(:all) do # Make sure we run in the same directory as the spec file. @pwd = pwd cd(File.dirname(__FILE__)) ENV['TASKJUGGLER_DATA_PATH'] = "../" cleanup startDaemon(<<'EOT' smtpServer: example.com _statussheets: projectId: sstest senderEmail: foo@example.com _sender: hideResource: '~(isleaf() & manager)' EOT ) prj = <<'EOT' project sstest "Time Sheet Test" 2011-03-14 +2m { trackingscenario plan now ${projectstart} } flags manager, important, late resource "Team" { resource boss "Boss" { email "boss@example.com" flags manager } resource r1 "R1" { email "r1@example.com" } resource r2 "R2" { email "r2@example.com" } } task t1 "T1" { effort 2.5d allocate r1 responsible boss } task t2 "T2" { depends !t1 effort 2.5d allocate r1 responsible boss } task t3 "T3" { effort 10d allocate r2 responsible boss } timesheet r1 2011-03-14-00:00-+0000 - 2011-03-21-00:00-+0000 { task t1 { work 30.0% remaining 2.0d status red "More work" { flags important, late details -8<- This is more work than expected. ->8- } } task t2 { work 50.0% remaining 0.0d status green "All work done!" } newtask t4 "A new job" { work 20% remaining 1.0d status green "May be a good idea" { summary -8<- I thought this might be useful work. ->8- } } } timesheet r2 2011-03-14-00:00-+0000 - 2011-03-21-00:00-+0000 { # Task: T3 task t3 { work 100.0% remaining 5.0d status green "What a job!" } status yellow "I'm not feeling good!" { flags important summary -8<- We all live on a yellow submarine! ->8- } } EOT res = stdIoWrapper(prj) do Tj3Client.new.main(%w( --unsafe add . )) end unless res.stdErr.include?("Info: Project(s) . added") raise "Project not loaded: #{res.stdErr}" end raise "Can't load project: #{res.stdErr}" unless res.returnValue == 0 res = stdIoWrapper do Tj3SsSender.new.main(%w( --dryrun --silent -e 2011-03-23 )) end unless res.returnValue == 0 raise " Status sheet template generation failed: #{res.stdErr}" end @sss_mails = collectMails(res.stdOut) @sheet = <<'EOT' # --------8<--------8<-------- statussheet boss 2011-03-16-00:00-+0000 - 2011-03-23-00:00-+0000 { # Task: T1 task t1 { status green "No More work" { # Date: 2011-03-21-00:00-+0000 # Work: 30% (50%) Remaining: 2.0d (0.0d) author r1 flags late details -8<- This is job is a breeze. ->8- } } # Task: T2 task t2 { # status green "All work done!" { # # Date: 2011-03-21-00:00-+0000 # # Work: 50% Remaining: 0.0d # author r1 # } } # Task: T3 task t3 { status green "What a nice job!" { # Date: 2011-03-21-00:00-+0000 # Work: 100% Remaining: 5.0d (0.0d) author r2 } } } # -------->8-------->8-------- EOT mailBody = @sheet.unix2dos.to_base64 mail = Mail.new do subject "Status sheet" content_type [ 'text', 'plain', { 'charset' => 'UTF-8' } ] content_transfer_encoding 'base64' body mailBody end mail.to = 'taskjuggler@example.com' mail.from 'boss@example.com' res = stdIoWrapper(mail.to_s) do Tj3SsReceiver.new.main(%w( --dryrun --silent . )) end unless res.returnValue == 0 raise " Status sheet reception failed: #{res.stdErr}" end @ssr_mails = collectMails(res.stdOut) end after(:all) do stopDaemon cleanup cd(@pwd) end it 'is just a dummy' do end describe StatusSheetSender do it 'should have generated 1 mail' do @sss_mails.length.should == 1 end it 'should have email sender foo@example.com' do @sss_mails.each do |mail| mail.from[0].should == 'foo@example.com' end end it 'should have proper email receivers' do @sss_mails[0].to[0].should == 'boss@example.com' end it 'should generate properly dated headers' do countLines(@sss_mails[0].parts[0].decoded, 'statussheet boss 2011-03-16-00:00-+0000 - ' + '2011-03-23-00:00-+0000').should == 1 end it 'should have matching status sheets in body and attachment' do @sss_mails.each do |mail| bodySheet = extractStatusSheet(mail.parts[0].decoded) attachedSheet = extractStatusSheet(mail.part[1].decoded) bodySheet.should == attachedSheet end end end describe StatusSheetReceiver do it 'should have generated 1 mails' do @ssr_mails.length.should == 1 end it 'should have email sender foo@example.com' do @ssr_mails.each do |mail| mail.from[0].should == 'foo@example.com' end end it 'should have email receivers boss@example.com' do @ssr_mails[0].to[0].should == 'boss@example.com' end it 'should have stored status sheet' do @sheet.should == File.read('StatusSheets/2011-03-23/boss_2011-03-23.tji') end end private def countLines(text, pattern) c = 0 if pattern.is_a?(Regexp) text.each_line do |line| c += 1 if line =~ pattern end else text.each_line do |line| c += 1 if line.include?(pattern) end end c end def extractStatusSheet(lines) sheet = nil lines.each_line do |line| if line =~ /^# --------8<--------8<--------/ sheet = "" elsif line =~ /^# -------->8-------->8--------/ raise 'Found end marker, but no start marker' unless sheet return sheet elsif sheet sheet += line end end raise "No end marker found" end def collectMails(lines) mails = [] mailLines = nil lines.each_line do |line| if line =~ /^-- Email Start ---/ mailLines = "" elsif line =~ /^-- Email End ---/ raise 'Found end marker, but no start marker' unless mailLines mails << Mail.read_from_string(mailLines) mailLines = nil elsif mailLines mailLines += line end end mails end end end taskjuggler-3.5.0/spec/Tj3_spec.rb0000644000175000017500000000151212614413013016274 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rubygems' require 'taskjuggler/StdIoWrapper' require 'taskjuggler/apps/Tj3' class TaskJuggler describe Tj3 do include StdIoWrapper it 'should schedule a project' do prj = 'project "Foo" 2011-03-14 +1d task "Foo"' res = stdIoWrapper(prj) do Tj3.new.main(%w( --silent --no-reports . )) end res.stdErr.should == '' res.returnValue.should == 0 end end end taskjuggler-3.5.0/spec/ICalendar_spec.rb0000644000175000017500000000200612614413013017455 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ICalendar_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rubygems' require 'taskjuggler/ICalendar' require 'support/spec_helper.rb' class TaskJuggler describe ICalendar do describe ICalendar::Component do it 'should quote properly' do c = ICalendar::Component.new(nil, '', nil, nil) [ [ '', '' ], [ 'foo', 'foo' ], [ '"', '\"' ], [ ';', '\;' ], [ ',', '\,' ], [ "\n", '\n' ], [ "foo\nbar", 'foo\nbar' ], [ 'a"b"c', 'a\"b\"c' ] ].each do |i, o| c.send('quoted', i).should == o end end end end end taskjuggler-3.5.0/spec/TernarySearchTree_spec.rb0000644000175000017500000000662712614413013021242 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TernarySearchTree_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rubygems' require 'taskjuggler/TernarySearchTree' class TaskJuggler describe TernarySearchTree do before do @tst = TernarySearchTree.new end it 'should not contain anything yet' do @tst.length.should be_equal 0 @tst[''].should be_nil end it 'should accept single element on creation' do @tst = TernarySearchTree.new('foo') @tst.length.should == 1 end it 'should accept an Array on creation' do @tst = TernarySearchTree.new(%w( foo bar )) @tst.length.should == 2 end it 'should not accept an empty String' do lambda { @tst.insert('') }.should raise_error end it 'should not accept nil' do lambda { @tst.insert(nil) }.should raise_error end it 'should store inserted values' do v = %w( foo bar foobar barfoo fo ba foo1 bar1 zzz ) @tst.insertList(v) @tst.length.should be_equal v.length rv = @tst.to_a.sort rv.should == v.sort end it 'should find exact matches' do v = %w( foo bar foobar barfoo fo ba foo1 bar1 zzz ) v.each { |val| @tst << val } v.each do |val| @tst[val].should == val end end it 'should not find non-existing elements' do %w( foo bar foobar barfoo fo ba foo1 bar1 zzz ).each { |v| @tst << v } @tst['foos'].should be_nil @tst['bax'].should be_nil @tst[''].should be_nil end it 'should find partial matches' do %w( foo bar foobar barfoo ba foo1 bar1 zzz ).each { |v| @tst << v } @tst['foo', true].sort.should == %w( foo foobar foo1 ).sort @tst['fo', true].sort.should == %w( foo foobar foo1 ).sort @tst['b', true].sort.should == %w( bar barfoo ba bar1 ).sort @tst['zzz', true].should == [ 'zzz' ] end it 'should not find non-existing elements' do %w( foo bar foobar barfoo fo ba foo1 bar1 zzz ).each { |v| @tst << v } @tst['foos', true].should be_nil @tst['', true].should be_nil end it 'should store duplicate entries only once' do v = %w( foo bar foobar bar foo fo ba foo1 ba foobar bar1 zzz ) @tst.insertList(v) @tst.length.should == v.uniq.length end it 'maxDepth should work' do v = %w( a b c d e f) v.each { |val| @tst << val } @tst.maxDepth.should == v.length end it 'should be able to balance a tree' do %w( aa ab ac ba bb bc ca cb cc ).each { |v| @tst << v } tst = @tst.balanced @tst.balance! @tst.to_a.should == tst.to_a # The tree is not perfectly balanced. @tst.maxDepth.should == 5 end #it 'should store integer lists' do # @tst.insert([ 0, 1, 2 ]) # @tst.length.should == 1 # @tst.to_a.should be == [ 0, 1, 2 ] #end #it 'should work with integer lists as well' do # v = [ [ 0, 1, 2], [ 0, 3, 2], [ 1, 3 ], [ 0, 2, 1], [ 1, 0, 3] ] # @tst.insertList(v) # @tst.length.should be_equal v.length # rv = @tst.to_a.sort # rv.should == v.sort #end end end taskjuggler-3.5.0/spec/IntervalList_spec.rb0000644000175000017500000000752712614413013020270 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = IntervalList_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rubygems' require 'taskjuggler/IntervalList' class TaskJuggler describe IntervalList do before(:all) do @t0 = TjTime.new('2011-01-01').freeze @t1 = TjTime.new('2011-01-02').freeze @t2 = TjTime.new('2011-01-03').freeze @t3 = TjTime.new('2011-01-04').freeze @t4 = TjTime.new('2011-01-05').freeze @t5 = TjTime.new('2011-01-06').freeze @t6 = TjTime.new('2011-01-07').freeze @i0_1 = TimeInterval.new(@t0, @t1).freeze @i0_2 = TimeInterval.new(@t0, @t2).freeze @i0_3 = TimeInterval.new(@t0, @t3).freeze @i1_2 = TimeInterval.new(@t1, @t2).freeze @i1_3 = TimeInterval.new(@t1, @t3).freeze @i2_3 = TimeInterval.new(@t2, @t3).freeze @i3_4 = TimeInterval.new(@t3, @t4).freeze @i3_6 = TimeInterval.new(@t3, @t6).freeze @i4_5 = TimeInterval.new(@t4, @t5).freeze @i4_6 = TimeInterval.new(@t4, @t6).freeze @i5_6 = TimeInterval.new(@t5, @t6).freeze end describe "#<<" do before do @il = IntervalList.new end it 'should add a new interval' do @il << @i0_1 @il.should == [ @i0_1 ] end it 'Intervals should be added in ascending order' do @il << @i1_2 lambda { @il << @i0_1 }.should raise_error end it 'should merge adjecent intervals on add' do @il << @i0_1 @il << @i1_2 @il.should == [ @i0_2 ] end it 'should not merge non-adjecent intervals on add' do @il << @i0_1 @il << @i2_3 @il.should == [ @i0_1, @i2_3 ] end it 'operator concatenation should work' do @il << @i_01 << @i2_3 << @i4_5 @il.length.should == 3 end end describe '#&' do it 'without overlap should be empty' do il1 = IntervalList.new([ @i0_1 ]) il2 = IntervalList.new([ @i1_2 ]) (il1 & il2).should be_empty (il2 & il1).should be_empty end it 'with empty list should be empty' do il1 = IntervalList.new([ @i0_1 ]) il2 = IntervalList.new([ ]) (il1 & il2).should be_empty (il2 & il1).should be_empty end it 'with self should be self' do il = IntervalList.new([ @i0_1 ]) (il & il).should == il end it 'with partial overlap should be overlap' do il1 = IntervalList.new([ @i0_2 ]) il2 = IntervalList.new([ @i1_3 ]) il3 = IntervalList.new([ @i1_2 ]) (il1 & il2).should == il3 (il2 & il1).should == il3 end it 'with center inclusion should be inclusion' do il1 = IntervalList.new([ @i0_3, @i3_6 ]) il2 = IntervalList.new([ @i1_2, @i4_5 ]) (il1 & il2).should == il2 (il2 & il1).should == il2 end it 'with left inclusion should be inclusion' do il1 = IntervalList.new([ @i1_3, @i4_6 ]) il2 = IntervalList.new([ @i1_2, @i4_5 ]) (il1 & il2).should == il2 (il2 & il1).should == il2 end it 'with right inclusion should be inclusion' do il1 = IntervalList.new([ @i1_3, @i4_6 ]) il2 = IntervalList.new([ @i2_3, @i5_6 ]) (il1 & il2).should == il2 (il2 & il1).should == il2 end it 'with adjecent intervals should be empty' do il1 = IntervalList.new([ @i0_1, @i2_3 ]) il2 = IntervalList.new([ @i1_2, @i3_4 ]) (il1 & il2).should be_empty (il2 & il1).should be_empty end end end end taskjuggler-3.5.0/spec/TimeSheets_spec.rb0000644000175000017500000002143112614413013017710 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TimeSheets_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rubygems' require 'support/DaemonControl' require 'taskjuggler/apps/Tj3TsSender' require 'taskjuggler/apps/Tj3TsReceiver' require 'taskjuggler/apps/Tj3TsSummary' class TaskJuggler describe TimeSheets do include DaemonControl before(:all) do # Make sure we run in the same directory as the spec file. @pwd = pwd cd(File.dirname(__FILE__)) ENV['TASKJUGGLER_DATA_PATH'] = "../" cleanup startDaemon(<<'EOT' emailDeliveryMethod: smtp smtpServer: foobar.com _timesheets: projectId: tstest senderEmail: foo@example.com _sender: hideResource: '~isleaf()' _summary: sheetRecipients: - archive@example.com digestRecipients: - archive@example.com - crew@example.com EOT ) prj = <<'EOT' project tstest "Time Sheet Test" 2011-03-14 +2m { trackingscenario plan now ${projectstart} } flags important, late resource "Team" { resource r1 "R1" { email "r1@example.com" } resource r2 "R2" { email "r2@example.com" } } task t1 "T1" { effort 2.5d allocate r1 } task t2 "T2" { depends !t1 effort 2.5d allocate r1 } task t3 "T3" { effort 10d allocate r2 } EOT res = stdIoWrapper(prj) do Tj3Client.new.main(%w( --unsafe add . )) end unless res.stdErr.include?("Info: Project(s) . added") raise "Project not loaded: #{res.stdErr}" end raise "Can't load project" unless res.returnValue == 0 res = stdIoWrapper do Tj3TsSender.new.main(%w( --dryrun --silent -e 2011-03-21 )) end if res.stdErr != '' raise "Tj3TsSender failed: #{res.stdErr}" end @tss_mails = collectMails(res.stdOut) raise "Timesheet generation failed" unless res.returnValue == 0 @sheet1 = <<'EOT' # --------8<--------8<-------- timesheet r1 2011-03-14-00:00-+0000 - 2011-03-21-00:00-+0000 { task t1 { work 30.0% remaining 2.0d status red "More work" { flags important, late details -8<- This is more work than expected. ->8- } } task t2 { work 50.0% remaining 0.0d status green "All work done!" } newtask t4 "A new job" { work 20% remaining 1.0d status green "May be a good idea" { summary -8<- I thought this might be useful work. ->8- } } } # -------->8-------->8-------- EOT @sheet2 = <<'EOT' # --------8<--------8<-------- timesheet r2 2011-03-14-00:00-+0000 - 2011-03-21-00:00-+0000 { # Task: T3 task t3 { work 100.0% remaining 5.0d status green "What a job!" } status yellow "I'm not feeling good!" { summary -8<- We all live on a yellow submarine! ->8- } } # -------->8-------->8-------- EOT @tsr_mails = [] [ @sheet1, @sheet2 ].each do |sheet| mail = Mail.new do subject "Timesheet" content_type [ 'text', 'plain', { 'charset' => 'UTF-8' } ] content_transfer_encoding 'base64' body sheet.to_base64 end mail.to = 'taskjuggler@example.com' mail.from 'r@example.com' res = stdIoWrapper(mail.to_s) do Tj3TsReceiver.new.main(%w( --dryrun --silent )) end @tsr_mails += collectMails(res.stdOut) unless res.returnValue == 0 raise "Timesheet processing failed: #{res.stdErr}" end end res = stdIoWrapper(prj) do Tj3Client.new.main(%w( --unsafe --silent add . TimeSheets/2011-03-21/all.tji )) end unless res.returnValue == 0 raise "Project reloading failed: #{res.stdErr}" end res = stdIoWrapper do Tj3TsSummary.new.main(%w( --dryrun --silent -e 2011-03-21 )) end @sum_mails = collectMails(res.stdOut) unless res.returnValue == 0 raise "Summary generation failed: #{res.stdErr}" end end after(:all) do stopDaemon cleanup cd(@pwd) end describe TimeSheetSender do it 'should have generated 2 mails' do @tss_mails.length.should == 2 end it 'should have email sender foo@example.com' do @tss_mails.each do |mail| mail.from[0].should == 'foo@example.com' end end it 'should have proper email receivers' do @tss_mails[0].to[0].should == 'r1@example.com' @tss_mails[1].to[0].should == 'r2@example.com' end it 'should generate properly dated headers' do countLines(@tss_mails[0].parts[0].decoded, 'timesheet r1 2011-03-14-00:00-+0000 - ' + '2011-03-21-00:00-+0000').should == 1 countLines(@tss_mails[1].parts[0].decoded, 'timesheet r2 2011-03-14-00:00-+0000 - ' + '2011-03-21-00:00-+0000').should == 1 end it 'should have matching timesheets in body and attachment' do @tss_mails.each do |mail| bodySheet = extractTimeSheet(mail.parts[0].decoded) attachedSheet = extractTimeSheet(mail.part[1].decoded) bodySheet.should == attachedSheet end end end describe TimeSheetReceiver do it 'should have generated 2 mails' do @tsr_mails.length.should == 2 end it 'should have email sender foo@example.com' do @tsr_mails.each do |mail| mail.from[0].should == 'foo@example.com' end end it 'should have proper email receivers' do @tsr_mails[0].to[0].should == 'r1@example.com' @tsr_mails[1].to[0].should == 'r2@example.com' end it 'should have stored timesheets' do @sheet1.should == File.read('TimeSheets/2011-03-21/r1_2011-03-21.tji') @sheet2.should == File.read('TimeSheets/2011-03-21/r2_2011-03-21.tji') end it 'should report an error on bad keyword' do sheet = <<'EOT' # --------8<--------8<-------- timesheet r2 2011-03-14-00:00-+0000 - 2011-03-21-00:00-+0000 { task t3 { wirk 100.0% remaining 5.0d status green "All green!" } } # -------->8-------->8-------- EOT mail = Mail.new do subject "Timesheet" content_type [ 'text', 'plain', { 'charset' => 'UTF-8' } ] content_transfer_encoding 'base64' body sheet.unix2dos.to_base64 end mail.to = 'taskjuggler@example.com' mail.from 'r@example.com' res = stdIoWrapper(mail.to_s) do Tj3TsReceiver.new.main(%w( --dryrun --silent )) end countLines(res.stdErr, /\.\:5\: Error\: Unexpected token 'wirk' found\./).should == 1 res.returnValue.should == 1 end end describe TimeSheetSummary do it 'should have generated 4 mails' do @sum_mails.length.should == 4 end it 'should have proper email receivers' do @sum_mails[0].to[0].should == 'archive@example.com' @sum_mails[1].to[0].should == 'archive@example.com' @sum_mails[2].to[0].should == 'archive@example.com' @sum_mails[3].to[0].should == 'crew@example.com' end it 'should have proper email senders' do @sum_mails[0].from[0].should == 'r1@example.com' @sum_mails[1].from[0].should == 'r2@example.com' @sum_mails[2].from[0].should == 'foo@example.com' @sum_mails[3].from[0].should == 'foo@example.com' end end private def countLines(text, pattern) c = 0 if pattern.is_a?(Regexp) text.each_line do |line| c += 1 if line =~ pattern end else text.each_line do |line| c += 1 if line.include?(pattern) end end c end def extractTimeSheet(lines) sheet = nil lines.each_line do |line| if line =~ /^# --------8<--------8<--------/ sheet = "" elsif line =~ /^# -------->8-------->8--------/ raise 'Found end marker, but no start marker' unless sheet return sheet elsif sheet sheet += line end end raise "No end marker found" end def collectMails(lines) mails = [] mailLines = nil lines.each_line do |line| if line =~ /^-- Email Start ---/ mailLines = "" elsif line =~ /^-- Email End ---/ raise 'Found end marker, but no start marker' unless mailLines mails << Mail.read_from_string(mailLines) mailLines = nil elsif mailLines mailLines += line end end mails end end end taskjuggler-3.5.0/spec/TableColumnSorter_spec.rb0000644000175000017500000000434112614413013021243 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TableColumnSorter_spec.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rubygems' require 'taskjuggler/TableColumnSorter' class TaskJuggler describe TableColumnSorter do before do @table = [ %w( One Two Three ), [ 1, 2, 3 ] ] @sorter = TableColumnSorter.new(@table) end it "should not change for same header" do t = @sorter.sort(%w( One Two Three )) t.should == @table @sorter.discontinuedColumns.should == 0 end it "should not change for all remove" do t = @sorter.sort(%w( )) t.should == @table @sorter.discontinuedColumns.should == 3 end it "should move Two to back" do t = @sorter.sort(%w( One Three )) t.should == [ %w( One Three Two ), [ 1, 3, 2 ] ] @sorter.discontinuedColumns.should == 1 end it "should not change when last columns is missing" do t = @sorter.sort(%w( One Two )) t.should == @table @sorter.discontinuedColumns.should == 1 end it "should insert Four in front" do t = @sorter.sort(%w( Four One Two Three )) t.should == [ %w( Four One Two Three ), [ nil, 1, 2, 3 ] ] @sorter.discontinuedColumns.should == 0 end it "should insert Four and Five at end" do t = @sorter.sort(%w( One Two Three Four Five )) t.should == [ %w( One Two Three Four Five ), [ 1, 2, 3, nil, nil ] ] @sorter.discontinuedColumns.should == 0 end it "should insert Four at end and move Three to back" do t = @sorter.sort(%w( One Two Four )) t.should == [ %w( One Two Four Three ), [ 1, 2, nil, 3 ] ] @sorter.discontinuedColumns.should == 1 end it "should keep first columns and insert new directly after" do t = @sorter.sort(%w( One Four Five )) t.should == [ %w( One Four Five Two Three), [ 1, nil, nil, 2, 3 ] ] @sorter.discontinuedColumns.should == 2 end end end taskjuggler-3.5.0/taskjuggler.gemspec0000644000175000017500000000526412614413013017242 0ustar bernatbernat# -*- coding: utf-8 -*- # # = taskjuggler.gemspec -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This gemspec file will be used to package the taskjuggler gem. Before you # use it, the manual and other generated files must have been created! lib = File.expand_path('../lib', __FILE__) $:.unshift lib unless $:.include?(lib) # Get software version number from Tj3Config class. begin $: << 'lib' require 'taskjuggler/Tj3Config' PROJECT_VERSION = AppConfig.version PROJECT_NAME = AppConfig.softwareName rescue LoadError raise "Error: Cannot determine software settings: #{$!}" end GEM_SPEC = Gem::Specification.new { |s| s.name = 'taskjuggler' s.version = PROJECT_VERSION s.homepage = 'http://www.taskjuggler.org' s.author = 'Chris Schlaeger' s.email = 'chris@linux.com' s.rubyforge_project = 'taskjuggler' s.summary = 'A Project Management Software' s.description = <<'EOT' TaskJuggler is a modern and powerful, Free and Open Source Software project management tool. Its new approach to project planing and tracking is more flexible and superior to the commonly used Gantt chart editing tools. TaskJuggler is project management software for serious project managers. It covers the complete spectrum of project management tasks from the first idea to the completion of the project. It assists you during project scoping, resource assignment, cost and revenue planing, risk and communication management. EOT s.require_path = 'lib' s.files = (`git ls-files -- lib`).split("\n") + (`git ls-files -- data`).split("\n") + (`git ls-files -- manual`).split("\n") + (`git ls-files -- examples`).split("\n") + (`git ls-files -- tasks`).split("\n") + %w( .gemtest taskjuggler.gemspec Rakefile ) + # Generated files, not contained in Git repository. %w( data/tjp.vim ) + Dir.glob('manual/html/**/*') s.bindir = 'bin' s.executables = (`git ls-files -- bin`).split("\n"). map { |fn| File.basename(fn) } s.test_files = (`git ls-files -- test`).split("\n") + (`git ls-files -- spec`).split("\n") s.has_rdoc = true s.extra_rdoc_files = %w( README.rdoc COPYING CHANGELOG ) s.add_dependency('mail', '>= 2.4.3') s.add_dependency('term-ansicolor', '>= 1.0.7') s.add_development_dependency('rspec', '>= 2.5.0') s.platform = Gem::Platform::RUBY s.required_ruby_version = '>= 1.8.7' } taskjuggler-3.5.0/metadata.yml0000644000175000017500000017754012614413013015665 0ustar bernatbernat--- !ruby/object:Gem::Specification name: taskjuggler version: !ruby/object:Gem::Version version: 3.5.0 prerelease: platform: ruby authors: - Chris Schlaeger autorequire: bindir: bin cert_chain: [] date: 2013-06-29 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: mail requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 2.4.3 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 2.4.3 - !ruby/object:Gem::Dependency name: term-ansicolor requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 1.0.7 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 1.0.7 - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 2.5.0 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 2.5.0 description: ! 'TaskJuggler is a modern and powerful, Free and Open Source Software project management tool. Its new approach to project planing and tracking is more flexible and superior to the commonly used Gantt chart editing tools. TaskJuggler is project management software for serious project managers. It covers the complete spectrum of project management tasks from the first idea to the completion of the project. It assists you during project scoping, resource assignment, cost and revenue planing, risk and communication management. ' email: chris@linux.com executables: - tj3 - tj3client - tj3d - tj3man - tj3ss_receiver - tj3ss_sender - tj3ts_receiver - tj3ts_sender - tj3ts_summary - tj3webd extensions: [] extra_rdoc_files: - README.rdoc - COPYING - CHANGELOG files: - lib/header.tmpl - lib/taskjuggler/Account.rb - lib/taskjuggler/AccountCredit.rb - lib/taskjuggler/AccountScenario.rb - lib/taskjuggler/AlertLevelDefinitions.rb - lib/taskjuggler/AlgorithmDiff.rb - lib/taskjuggler/Allocation.rb - lib/taskjuggler/AppConfig.rb - lib/taskjuggler/AttributeBase.rb - lib/taskjuggler/AttributeDefinition.rb - lib/taskjuggler/Attributes.rb - lib/taskjuggler/BatchProcessor.rb - lib/taskjuggler/Booking.rb - lib/taskjuggler/Charge.rb - lib/taskjuggler/ChargeSet.rb - lib/taskjuggler/DataCache.rb - lib/taskjuggler/FileList.rb - lib/taskjuggler/HTMLDocument.rb - lib/taskjuggler/HTMLElements.rb - lib/taskjuggler/ICalendar.rb - lib/taskjuggler/Interval.rb - lib/taskjuggler/IntervalList.rb - lib/taskjuggler/Journal.rb - lib/taskjuggler/KateSyntax.rb - lib/taskjuggler/KeywordArray.rb - lib/taskjuggler/KeywordDocumentation.rb - lib/taskjuggler/LeaveList.rb - lib/taskjuggler/Limits.rb - lib/taskjuggler/Log.rb - lib/taskjuggler/LogicalExpression.rb - lib/taskjuggler/LogicalFunction.rb - lib/taskjuggler/LogicalOperation.rb - lib/taskjuggler/MessageHandler.rb - lib/taskjuggler/PTNProxy.rb - lib/taskjuggler/Painter.rb - lib/taskjuggler/Painter/BasicShapes.rb - lib/taskjuggler/Painter/Color.rb - lib/taskjuggler/Painter/Element.rb - lib/taskjuggler/Painter/FontData.rb - lib/taskjuggler/Painter/FontMetrics.rb - lib/taskjuggler/Painter/FontMetricsData.rb - lib/taskjuggler/Painter/Group.rb - lib/taskjuggler/Painter/Points.rb - lib/taskjuggler/Painter/Primitives.rb - lib/taskjuggler/Painter/SVGSupport.rb - lib/taskjuggler/Painter/Text.rb - lib/taskjuggler/Project.rb - lib/taskjuggler/ProjectFileParser.rb - lib/taskjuggler/ProjectFileScanner.rb - lib/taskjuggler/PropertyList.rb - lib/taskjuggler/PropertySet.rb - lib/taskjuggler/PropertyTreeNode.rb - lib/taskjuggler/Query.rb - lib/taskjuggler/RealFormat.rb - lib/taskjuggler/Resource.rb - lib/taskjuggler/ResourceScenario.rb - lib/taskjuggler/RichText.rb - lib/taskjuggler/RichText/Document.rb - lib/taskjuggler/RichText/Element.rb - lib/taskjuggler/RichText/FunctionExample.rb - lib/taskjuggler/RichText/FunctionHandler.rb - lib/taskjuggler/RichText/Parser.rb - lib/taskjuggler/RichText/RTFHandlers.rb - lib/taskjuggler/RichText/RTFNavigator.rb - lib/taskjuggler/RichText/RTFQuery.rb - lib/taskjuggler/RichText/RTFReport.rb - lib/taskjuggler/RichText/RTFReportLink.rb - lib/taskjuggler/RichText/RTFWithQuerySupport.rb - lib/taskjuggler/RichText/Scanner.rb - lib/taskjuggler/RichText/Snip.rb - lib/taskjuggler/RichText/SyntaxRules.rb - lib/taskjuggler/RichText/TOCEntry.rb - lib/taskjuggler/RichText/TableOfContents.rb - lib/taskjuggler/RuntimeConfig.rb - lib/taskjuggler/Scenario.rb - lib/taskjuggler/ScenarioData.rb - lib/taskjuggler/Scoreboard.rb - lib/taskjuggler/SheetHandlerBase.rb - lib/taskjuggler/SheetReceiver.rb - lib/taskjuggler/SheetSender.rb - lib/taskjuggler/Shift.rb - lib/taskjuggler/ShiftAssignments.rb - lib/taskjuggler/ShiftScenario.rb - lib/taskjuggler/SimpleQueryExpander.rb - lib/taskjuggler/StatusSheetReceiver.rb - lib/taskjuggler/StatusSheetSender.rb - lib/taskjuggler/StdIoWrapper.rb - lib/taskjuggler/SyntaxReference.rb - lib/taskjuggler/TableColumnDefinition.rb - lib/taskjuggler/TableColumnSorter.rb - lib/taskjuggler/Task.rb - lib/taskjuggler/TaskDependency.rb - lib/taskjuggler/TaskJuggler.rb - lib/taskjuggler/TaskScenario.rb - lib/taskjuggler/TernarySearchTree.rb - lib/taskjuggler/TextFormatter.rb - lib/taskjuggler/TextParser.rb - lib/taskjuggler/TextParser/MacroTable.rb - lib/taskjuggler/TextParser/Pattern.rb - lib/taskjuggler/TextParser/Rule.rb - lib/taskjuggler/TextParser/Scanner.rb - lib/taskjuggler/TextParser/SourceFileInfo.rb - lib/taskjuggler/TextParser/StackElement.rb - lib/taskjuggler/TextParser/State.rb - lib/taskjuggler/TextParser/TokenDoc.rb - lib/taskjuggler/TimeSheetReceiver.rb - lib/taskjuggler/TimeSheetSender.rb - lib/taskjuggler/TimeSheetSummary.rb - lib/taskjuggler/TimeSheets.rb - lib/taskjuggler/Tj3AppBase.rb - lib/taskjuggler/Tj3Config.rb - lib/taskjuggler/Tj3SheetAppBase.rb - lib/taskjuggler/TjException.rb - lib/taskjuggler/TjTime.rb - lib/taskjuggler/TjpExample.rb - lib/taskjuggler/TjpSyntaxRules.rb - lib/taskjuggler/URLParameter.rb - lib/taskjuggler/UTF8String.rb - lib/taskjuggler/UserManual.rb - lib/taskjuggler/VimSyntax.rb - lib/taskjuggler/WorkingHours.rb - lib/taskjuggler/XMLDocument.rb - lib/taskjuggler/XMLElement.rb - lib/taskjuggler/apps/Tj3.rb - lib/taskjuggler/apps/Tj3Client.rb - lib/taskjuggler/apps/Tj3Daemon.rb - lib/taskjuggler/apps/Tj3Man.rb - lib/taskjuggler/apps/Tj3SsReceiver.rb - lib/taskjuggler/apps/Tj3SsSender.rb - lib/taskjuggler/apps/Tj3TsReceiver.rb - lib/taskjuggler/apps/Tj3TsSender.rb - lib/taskjuggler/apps/Tj3TsSummary.rb - lib/taskjuggler/apps/Tj3WebD.rb - lib/taskjuggler/daemon/Daemon.rb - lib/taskjuggler/daemon/DaemonConnector.rb - lib/taskjuggler/daemon/ProcessIntercom.rb - lib/taskjuggler/daemon/ProjectBroker.rb - lib/taskjuggler/daemon/ProjectServer.rb - lib/taskjuggler/daemon/ReportServer.rb - lib/taskjuggler/daemon/ReportServlet.rb - lib/taskjuggler/daemon/WebServer.rb - lib/taskjuggler/daemon/WelcomePage.rb - lib/taskjuggler/deep_copy.rb - lib/taskjuggler/reports/AccountListRE.rb - lib/taskjuggler/reports/CSVFile.rb - lib/taskjuggler/reports/ChartPlotter.rb - lib/taskjuggler/reports/CollisionDetector.rb - lib/taskjuggler/reports/ColumnTable.rb - lib/taskjuggler/reports/ExportRE.rb - lib/taskjuggler/reports/GanttChart.rb - lib/taskjuggler/reports/GanttContainer.rb - lib/taskjuggler/reports/GanttHeader.rb - lib/taskjuggler/reports/GanttHeaderScaleItem.rb - lib/taskjuggler/reports/GanttLine.rb - lib/taskjuggler/reports/GanttLoadStack.rb - lib/taskjuggler/reports/GanttMilestone.rb - lib/taskjuggler/reports/GanttRouter.rb - lib/taskjuggler/reports/GanttTaskBar.rb - lib/taskjuggler/reports/HTMLGraphics.rb - lib/taskjuggler/reports/ICalReport.rb - lib/taskjuggler/reports/MspXmlRE.rb - lib/taskjuggler/reports/Navigator.rb - lib/taskjuggler/reports/NikuReport.rb - lib/taskjuggler/reports/Report.rb - lib/taskjuggler/reports/ReportBase.rb - lib/taskjuggler/reports/ReportContext.rb - lib/taskjuggler/reports/ReportTable.rb - lib/taskjuggler/reports/ReportTableCell.rb - lib/taskjuggler/reports/ReportTableColumn.rb - lib/taskjuggler/reports/ReportTableLegend.rb - lib/taskjuggler/reports/ReportTableLine.rb - lib/taskjuggler/reports/ResourceListRE.rb - lib/taskjuggler/reports/StatusSheetReport.rb - lib/taskjuggler/reports/TableReport.rb - lib/taskjuggler/reports/TableReportColumn.rb - lib/taskjuggler/reports/TagFile.rb - lib/taskjuggler/reports/TaskListRE.rb - lib/taskjuggler/reports/TextReport.rb - lib/taskjuggler/reports/TimeSheetReport.rb - lib/taskjuggler/reports/TjpExportRE.rb - lib/taskjuggler/reports/TraceReport.rb - lib/tj3.rb - lib/tj3client.rb - lib/tj3d.rb - lib/tj3man.rb - lib/tj3ss_receiver.rb - lib/tj3ss_sender.rb - lib/tj3ts_receiver.rb - lib/tj3ts_sender.rb - lib/tj3ts_summary.rb - lib/tj3webd.rb - lib/updateheader.sh - data/css/tjmanual.css - data/css/tjreport.css - data/icons/details.png - data/icons/flag-green.png - data/icons/flag-red.png - data/icons/flag-yellow.png - data/icons/resource.png - data/icons/resourcegroup.png - data/icons/task.png - data/icons/taskgroup.png - data/icons/trend-down.png - data/icons/trend-flat.png - data/icons/trend-up.png - data/scripts/wz_tooltip.js - manual/Day_To_Day_Juggling - manual/Getting_Started - manual/How_To_Contribute - manual/Installation - manual/Intro - manual/Reporting_Bugs - manual/Rich_Text_Attributes - manual/Software - manual/TaskJuggler_2x_Migration - manual/TaskJuggler_Internals - manual/The_TaskJuggler_Syntax - manual/Tutorial - manual/fdl - examples/Fedora-20/f-20.tjp - examples/Fedora-20/icons/fedoralogo.png - examples/Fedora-20/reports.tji - examples/ProjectTemplate/template.tjp - examples/Scrum/Product Burndown.csv - examples/Scrum/Sprint 1 Burndown.csv - examples/Scrum/Sprint 2 Burndown.csv - examples/Scrum/Sprint 3 Burndown.csv - examples/Scrum/scrum.tjp - examples/ToDo-List/todolist.tjp - examples/Tutorial/tutorial.tjp - tasks/changelog.rake - tasks/gem.rake - tasks/kate.rake - tasks/manual.rake - tasks/rdoc.rake - tasks/spec.rake - tasks/test.rake - tasks/vim.rake - .gemtest - taskjuggler.gemspec - Rakefile - data/tjp.vim - manual/html/minend.html - manual/html/text.extend.html - manual/html/aggregate.html - manual/html/accountreport.html - manual/html/length.html - manual/html/tracereport.html - manual/html/purge.html - manual/html/mandatory.html - manual/html/precedes.html - manual/html/limits.resource.html - manual/html/endcredit.html - manual/html/flags.journalentry.html - manual/html/loadunit.html - manual/html/rawhtmlhead.html - manual/html/booking.task.html - manual/html/taskreport.html - manual/html/startcredit.html - manual/html/now.html - manual/html/currencyformat.html - manual/html/inherit.extend.html - manual/html/accountroot.html - manual/html/responsible.html - manual/html/task.statussheet.html - manual/html/supplement.resource.html - manual/html/shifts.task.html - manual/html/duration.html - manual/html/taskattributes.html - manual/html/remaining.html - manual/html/shift.html - manual/html/auxdir.html - manual/html/effort.html - manual/html/vacation.shift.html - manual/html/alert.html - manual/html/isdependencyof.html - manual/html/scenario.ical.html - manual/html/credits.html - manual/html/hidereport.html - manual/html/vacation.resource.html - manual/html/interval3.html - manual/html/Reporting_Bugs.html - manual/html/weeklymin.html - manual/html/dailymax.html - manual/html/functions.html - manual/html/hideresource.html - manual/html/selfcontained.html - manual/html/journalattributes.html - manual/html/textreport.html - manual/html/complete.html - manual/html/allocate.html - manual/html/scheduled.html - manual/html/istask.html - manual/html/opennodes.html - manual/html/flags.resource.html - manual/html/headline.html - manual/html/start.column.html - manual/html/outputdir.html - manual/html/Getting_Started.html - manual/html/chargeset.html - manual/html/isongoing.html - manual/html/sorttasks.html - manual/html/flags.statussheet.html - manual/html/yearlyworkingdays.html - manual/html/flags.account.html - manual/html/period.column.html - manual/html/navbar.html - manual/html/isactive.html - manual/html/sloppy.booking.html - manual/html/end.column.html - manual/html/balance.html - manual/html/sloppy.projection.html - manual/html/columns.html - manual/html/charge.html - manual/html/monthlymin.html - manual/html/status.statussheet.html - manual/html/period.task.html - manual/html/alternative.html - manual/html/scenario.html - manual/html/columnid.html - manual/html/email.html - manual/html/index.html - manual/html/end.limit.html - manual/html/interval1.html - manual/html/alphabet.html - manual/html/limits.allocate.html - manual/html/replace.html - manual/html/isresponsibilityof.html - manual/html/status.timesheet.html - manual/html/shift.resource.html - manual/html/scenariospecific.extend.html - manual/html/width.column.html - manual/html/left.html - manual/html/projectid.task.html - manual/html/flags.html - manual/html/task.timesheet.html - manual/html/date.extend.html - manual/html/reference.extend.html - manual/html/sortjournalentries.html - manual/html/monthlymax.html - manual/html/reportprefix.html - manual/html/auxdir.report.html - manual/html/treelevel.html - manual/html/sortresources.html - manual/html/leaveallowance.html - manual/html/timeformat.html - manual/html/start.html - manual/html/interval2.html - manual/html/shorttimeformat.html - manual/html/flags.timesheet.html - manual/html/epilog.html - manual/html/accountprefix.html - manual/html/number.extend.html - manual/html/resourceroot.html - manual/html/limits.task.html - manual/html/limits.html - manual/html/resourceprefix.html - manual/html/scenarios.export.html - manual/html/priority.timesheet.html - manual/html/milestone.html - manual/html/width.html - manual/html/TaskJuggler_2x_Migration.html - manual/html/extend.html - manual/html/note.task.html - manual/html/nikureport.html - manual/html/weekstartssunday.html - manual/html/shift.allocate.html - manual/html/shift.timesheet.html - manual/html/resource.html - manual/html/copyright.html - manual/html/start.report.html - manual/html/resources.limit.html - manual/html/hasalert.html - manual/html/account.task.html - manual/html/alertlevels.html - manual/html/taskroot.html - manual/html/managers.html - manual/html/resourcereport.html - manual/html/persistent.html - manual/html/vacation.html - manual/html/leaves.html - manual/html/summary.html - manual/html/shifts.allocate.html - manual/html/richtext.extend.html - manual/html/header.html - manual/html/priority.html - manual/html/newtask.html - manual/html/overtime.booking.html - manual/html/rate.resource.html - manual/html/footer.html - manual/html/resourceattributes.html - manual/html/logicalexpression.html - manual/html/timingresolution.html - manual/html/isvalid.html - manual/html/projectid.html - manual/html/rollupaccount.html - manual/html/Intro.html - manual/html/workinghours.project.html - manual/html/maximum.html - manual/html/fdl.html - manual/html/fontcolor.column.html - manual/html/ischildof.html - manual/html/active.html - manual/html/halign.left.html - manual/html/listtype.column.html - manual/html/export.html - manual/html/fail.html - manual/html/isleaf.html - manual/html/task.html - manual/html/gaplength.html - manual/html/onstart.html - manual/html/flags.report.html - manual/html/definitions.html - manual/html/timesheet.html - manual/html/period.report.html - manual/html/workinghours.shift.html - manual/html/isdutyof.html - manual/html/warn.html - manual/html/isresource.html - manual/html/timezone.export.html - manual/html/enabled.html - manual/html/onend.html - manual/html/end.report.html - manual/html/timeformat1.html - manual/html/trackingscenario.html - manual/html/properties.html - manual/html/title.html - manual/html/ismilestone.html - manual/html/workinghours.resource.html - manual/html/rate.html - manual/html/prolog.html - manual/html/account.html - manual/html/Software.html - manual/html/weeklymax.html - manual/html/timeoff.nikureport.html - manual/html/disabled.html - manual/html/end.html - manual/html/caption.html - manual/html/projectids.html - manual/html/tagfile.html - manual/html/height.html - manual/html/Installation.html - manual/html/include.project.html - manual/html/flags.task.html - manual/html/The_TaskJuggler_Syntax.html - manual/html/strict.projection.html - manual/html/shifts.resource.html - manual/html/maxstart.html - manual/html/Day_To_Day_Juggling.html - manual/html/TaskJuggler_Internals.html - manual/html/efficiency.html - manual/html/scheduling.html - manual/html/rolluptask.html - manual/html/journalmode.html - manual/html/timezone.report.html - manual/html/center.html - manual/html/supplement.html - manual/html/timezone.shift.html - manual/html/hidejournalentry.html - manual/html/How_To_Contribute.html - manual/html/Tutorial.html - manual/html/scenarios.html - manual/html/booking.resource.html - manual/html/shift.task.html - manual/html/details.html - manual/html/work.html - manual/html/timesheetreport.html - manual/html/rollupresource.html - manual/html/interval4.html - manual/html/formats.html - manual/html/timeformat2.html - manual/html/halign.center.html - manual/html/start.limit.html - manual/html/taskroot.export.html - manual/html/logicalflagexpression.html - manual/html/sortaccounts.html - manual/html/minimum.html - manual/html/listitem.column.html - manual/html/macro.html - manual/html/date.html - manual/html/halign.column.html - manual/html/supplement.task.html - manual/html/include.properties.html - manual/html/statussheetreport.html - manual/html/scale.column.html - manual/html/minstart.html - manual/html/taskprefix.html - manual/html/currency.html - manual/html/author.html - manual/html/celltext.column.html - manual/html/include.macro.html - manual/html/maxend.html - manual/html/tooltip.column.html - manual/html/dailymin.html - manual/html/cellcolor.column.html - manual/html/timezone.html - manual/html/adopt.task.html - manual/html/journalentry.html - manual/html/halign.right.html - manual/html/icalreport.html - manual/html/dailyworkinghours.html - manual/html/hideaccount.html - manual/html/depends.html - manual/html/toc.html - manual/html/navigator.html - manual/html/formats.export.html - manual/html/statussheet.html - manual/html/css/tjmanual.css - manual/html/css/tjreport.css - manual/html/numberformat.html - manual/html/end.timesheet.html - manual/html/select.html - manual/html/isfeatureof.html - manual/html/weekstartsmonday.html - manual/html/period.limit.html - manual/html/hidetask.html - manual/html/project.html - manual/html/gapduration.html - manual/html/projection.html - manual/html/title.column.html - manual/html/right.html - manual/html/Rich_Text_Attributes.html - README.rdoc - COPYING - CHANGELOG - test/MessageChecker.rb - test/ReferenceGenerator.rb - test/TestSuite/CSV-Reports/Leave.tjp - test/TestSuite/CSV-Reports/alert.tjp - test/TestSuite/CSV-Reports/celltext.tjp - test/TestSuite/CSV-Reports/efficiency.tjp - test/TestSuite/CSV-Reports/headcount.tjp - test/TestSuite/CSV-Reports/inputs.tjp - test/TestSuite/CSV-Reports/niku.tjp - test/TestSuite/CSV-Reports/project-1.tji - test/TestSuite/CSV-Reports/refs/Leave.csv - test/TestSuite/CSV-Reports/refs/alert.csv - test/TestSuite/CSV-Reports/refs/celltext.csv - test/TestSuite/CSV-Reports/refs/efficiency.csv - test/TestSuite/CSV-Reports/refs/headcount.csv - test/TestSuite/CSV-Reports/refs/inputs.csv - test/TestSuite/CSV-Reports/refs/niku.csv - test/TestSuite/CSV-Reports/refs/resourcereport.csv - test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv - test/TestSuite/CSV-Reports/refs/sortByTree.csv - test/TestSuite/CSV-Reports/refs/sortBy_duration.down.csv - test/TestSuite/CSV-Reports/refs/sortBy_effort.up.csv - test/TestSuite/CSV-Reports/refs/sortBy_plan.start.down.csv - test/TestSuite/CSV-Reports/refs/targets.csv - test/TestSuite/CSV-Reports/refs/taskreport.csv - test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv - test/TestSuite/CSV-Reports/refs/weekly.csv - test/TestSuite/CSV-Reports/resourcereport.tjp - test/TestSuite/CSV-Reports/resourcereport_with_tasks.tjp - test/TestSuite/CSV-Reports/sortByTree.tjp - test/TestSuite/CSV-Reports/sortBy_duration.down.tjp - test/TestSuite/CSV-Reports/sortBy_effort.up.tjp - test/TestSuite/CSV-Reports/sortBy_plan.start.down.tjp - test/TestSuite/CSV-Reports/targets.tjp - test/TestSuite/CSV-Reports/taskreport.tjp - test/TestSuite/CSV-Reports/taskreport_with_resources.tjp - test/TestSuite/CSV-Reports/weekly.tjp - test/TestSuite/Export-Reports/export.tji - test/TestSuite/Export-Reports/refs/Account.tjp - test/TestSuite/Export-Reports/refs/AccountReport.tjp - test/TestSuite/Export-Reports/refs/AdoptedTasks.tjp - test/TestSuite/Export-Reports/refs/AlertLevels.tjp - test/TestSuite/Export-Reports/refs/Allocate-1.tjp - test/TestSuite/Export-Reports/refs/Alternative.tjp - test/TestSuite/Export-Reports/refs/AutoID.tjp - test/TestSuite/Export-Reports/refs/AutoMacros.tjp - test/TestSuite/Export-Reports/refs/Booking.tjp - test/TestSuite/Export-Reports/refs/Caption.tjp - test/TestSuite/Export-Reports/refs/Celltext.tjp - test/TestSuite/Export-Reports/refs/Comments.tjp - test/TestSuite/Export-Reports/refs/Complete.tjp - test/TestSuite/Export-Reports/refs/Currencyformat.tjp - test/TestSuite/Export-Reports/refs/CustomAttributes.tjp - test/TestSuite/Export-Reports/refs/Depends1.tjp - test/TestSuite/Export-Reports/refs/Durations.tjp - test/TestSuite/Export-Reports/refs/Efficiency.tjp - test/TestSuite/Export-Reports/refs/EnvVar.tjp - test/TestSuite/Export-Reports/refs/Flags.tjp - test/TestSuite/Export-Reports/refs/Gap.tjp - test/TestSuite/Export-Reports/refs/HtmlTaskReport.tjp - test/TestSuite/Export-Reports/refs/Include.tjp - test/TestSuite/Export-Reports/refs/Journal.tjp - test/TestSuite/Export-Reports/refs/Limits-1.tjp - test/TestSuite/Export-Reports/refs/LoadUnits.tjp - test/TestSuite/Export-Reports/refs/LogicalExpression.tjp - test/TestSuite/Export-Reports/refs/LogicalFunction.tjp - test/TestSuite/Export-Reports/refs/Macro-1.tjp - test/TestSuite/Export-Reports/refs/Macro-2.tjp - test/TestSuite/Export-Reports/refs/Macro-3.tjp - test/TestSuite/Export-Reports/refs/Manager.tjp - test/TestSuite/Export-Reports/refs/Mandatory.tjp - test/TestSuite/Export-Reports/refs/Milestone.tjp - test/TestSuite/Export-Reports/refs/MinMax.tjp - test/TestSuite/Export-Reports/refs/Niku.tjp - test/TestSuite/Export-Reports/refs/Numberformat.tjp - test/TestSuite/Export-Reports/refs/Period.tjp - test/TestSuite/Export-Reports/refs/Persistent.tjp - test/TestSuite/Export-Reports/refs/Precedes1.tjp - test/TestSuite/Export-Reports/refs/Priority.tjp - test/TestSuite/Export-Reports/refs/Project.tjp - test/TestSuite/Export-Reports/refs/ProjectIDs.tjp - test/TestSuite/Export-Reports/refs/Query.tjp - test/TestSuite/Export-Reports/refs/Reports.tjp - test/TestSuite/Export-Reports/refs/Resource.tjp - test/TestSuite/Export-Reports/refs/ResourcePrefix.tjp - test/TestSuite/Export-Reports/refs/ResourceRoot.tjp - test/TestSuite/Export-Reports/refs/Responsible.tjp - test/TestSuite/Export-Reports/refs/RollupResource.tjp - test/TestSuite/Export-Reports/refs/Scenario.tjp - test/TestSuite/Export-Reports/refs/Scheduling.tjp - test/TestSuite/Export-Reports/refs/Select.tjp - test/TestSuite/Export-Reports/refs/Shift.tjp - test/TestSuite/Export-Reports/refs/Simple.tjp - test/TestSuite/Export-Reports/refs/StatusSheet.tjp - test/TestSuite/Export-Reports/refs/String.tjp - test/TestSuite/Export-Reports/refs/Supplement.tjp - test/TestSuite/Export-Reports/refs/TaskPrefix.tjp - test/TestSuite/Export-Reports/refs/TaskRoot.tjp - test/TestSuite/Export-Reports/refs/TimeFrame.tjp - test/TestSuite/Export-Reports/refs/TimeSheet1.tjp - test/TestSuite/Export-Reports/refs/Timezone.tjp - test/TestSuite/Export-Reports/refs/Vacation.tjp - test/TestSuite/Export-Reports/refs/navigator.tjp - test/TestSuite/Export-Reports/refs/template.tjp - test/TestSuite/Export-Reports/refs/textreport.tjp - test/TestSuite/Export-Reports/refs/tutorial.tjp - test/TestSuite/HTML-Reports/Alerts-2.tjp - test/TestSuite/HTML-Reports/Alerts.tjp - test/TestSuite/HTML-Reports/Calendars.tjp - test/TestSuite/HTML-Reports/CellText.tjp - test/TestSuite/HTML-Reports/ColumnPeriods.tjp - test/TestSuite/HTML-Reports/IsOngoing.tjp - test/TestSuite/HTML-Reports/Navigator.tjp - test/TestSuite/HTML-Reports/Query.tjp - test/TestSuite/HTML-Reports/Sorting.tjp - test/TestSuite/HTML-Reports/TimeSheet.tjp - test/TestSuite/HTML-Reports/UDAQuery.tjp - test/TestSuite/HTML-Reports/depArrows.tjp - test/TestSuite/HTML-Reports/reference.tjp - test/TestSuite/ReportGenerator/Correct/Alerts.tjp - test/TestSuite/ReportGenerator/Correct/FTE.tjp - test/TestSuite/ReportGenerator/Correct/Journal.tjp - test/TestSuite/ReportGenerator/Correct/JournalMode.tjp - test/TestSuite/ReportGenerator/Correct/LogicalFunctions1.tjp - test/TestSuite/ReportGenerator/Correct/LogicalFunctions2.tjp - test/TestSuite/ReportGenerator/Correct/LogicalFunctions3.tjp - test/TestSuite/ReportGenerator/Correct/LogicalFunctions4.tjp - test/TestSuite/ReportGenerator/Correct/Macros.tjp - test/TestSuite/ReportGenerator/Correct/refs/Alerts-1.csv - test/TestSuite/ReportGenerator/Correct/refs/DependencyList-1.csv - test/TestSuite/ReportGenerator/Correct/refs/FTE-1.csv - test/TestSuite/ReportGenerator/Correct/refs/Journal-1.csv - test/TestSuite/ReportGenerator/Correct/refs/Journal-2.csv - test/TestSuite/ReportGenerator/Correct/refs/JournalMode-1.csv - test/TestSuite/ReportGenerator/Correct/refs/JournalMode-2.csv - test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions1-1.csv - test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions2-1.csv - test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions3-1.csv - test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions4-1.csv - test/TestSuite/ReportGenerator/Correct/refs/Macros-1.csv - test/TestSuite/ReportGenerator/Errors/no_report_defined.tjp - test/TestSuite/ReportGenerator/Errors/rtp_report_recursion.tjp - test/TestSuite/Scheduler/Correct/Allocate.tjp - test/TestSuite/Scheduler/Correct/AutomaticMilestones.tjp - test/TestSuite/Scheduler/Correct/Booking.tjp - test/TestSuite/Scheduler/Correct/Booking2.tjp - test/TestSuite/Scheduler/Correct/Booking3.tjp - test/TestSuite/Scheduler/Correct/Container-2.tjp - test/TestSuite/Scheduler/Correct/Container.tjp - test/TestSuite/Scheduler/Correct/DateAndDep.tjp - test/TestSuite/Scheduler/Correct/Depends.tjp - test/TestSuite/Scheduler/Correct/Duration.tjp - test/TestSuite/Scheduler/Correct/InheritStartEnd.tjp - test/TestSuite/Scheduler/Correct/InheritedAttributes.tjp - test/TestSuite/Scheduler/Correct/Leaves.tjp - test/TestSuite/Scheduler/Correct/Limits.tjp - test/TestSuite/Scheduler/Correct/Manager.tjp - test/TestSuite/Scheduler/Correct/Mandatory.tjp - test/TestSuite/Scheduler/Correct/MultipleMandatories.tjp - test/TestSuite/Scheduler/Correct/Optimize-1.tjp - test/TestSuite/Scheduler/Correct/Optimize-2.tjp - test/TestSuite/Scheduler/Correct/Optimize-3.tjp - test/TestSuite/Scheduler/Correct/Optimize-4.tjp - test/TestSuite/Scheduler/Correct/Optimize-5.tjp - test/TestSuite/Scheduler/Correct/PersistentResources-2.tjp - test/TestSuite/Scheduler/Correct/PersistentResources.tjp - test/TestSuite/Scheduler/Correct/Precedes.tjp - test/TestSuite/Scheduler/Correct/PriorityInversion.tjp - test/TestSuite/Scheduler/Correct/Scenarios.tjp - test/TestSuite/Scheduler/Correct/Shift.tjp - test/TestSuite/Scheduler/Correct/Shift2.tjp - test/TestSuite/Scheduler/Correct/TimeSheet2.tjp - test/TestSuite/Scheduler/Correct/WeakDeps.tjp - test/TestSuite/Scheduler/Correct/checks.tji - test/TestSuite/Scheduler/Correct/hammock.tjp - test/TestSuite/Scheduler/Correct/purge.tjp - test/TestSuite/Scheduler/Errors/account_no_leaf.tjp - test/TestSuite/Scheduler/Errors/allocate_no_assigned.tjp - test/TestSuite/Scheduler/Errors/booking_conflict.tjp - test/TestSuite/Scheduler/Errors/booking_no_duty.tjp - test/TestSuite/Scheduler/Errors/booking_on_vacation.tjp - test/TestSuite/Scheduler/Errors/container_booking.tjp - test/TestSuite/Scheduler/Errors/container_duration.tjp - test/TestSuite/Scheduler/Errors/container_milestone.tjp - test/TestSuite/Scheduler/Errors/effort_no_allocations.tjp - test/TestSuite/Scheduler/Errors/impossible_end_dep.tjp - test/TestSuite/Scheduler/Errors/impossible_start_dep.tjp - test/TestSuite/Scheduler/Errors/loop_detected_1.tjp - test/TestSuite/Scheduler/Errors/loop_detected_10.tjp - test/TestSuite/Scheduler/Errors/loop_detected_11.tjp - test/TestSuite/Scheduler/Errors/loop_detected_12.tjp - test/TestSuite/Scheduler/Errors/loop_detected_13.tjp - test/TestSuite/Scheduler/Errors/loop_detected_14.tjp - test/TestSuite/Scheduler/Errors/loop_detected_2.tjp - test/TestSuite/Scheduler/Errors/loop_detected_3.tjp - test/TestSuite/Scheduler/Errors/loop_detected_4.tjp - test/TestSuite/Scheduler/Errors/loop_detected_5.tjp - test/TestSuite/Scheduler/Errors/loop_detected_6.tjp - test/TestSuite/Scheduler/Errors/loop_detected_9.tjp - test/TestSuite/Scheduler/Errors/manager_is_group.tjp - test/TestSuite/Scheduler/Errors/manager_is_self.tjp - test/TestSuite/Scheduler/Errors/maxend.tjp - test/TestSuite/Scheduler/Errors/maxstart.tjp - test/TestSuite/Scheduler/Errors/milestone_booking.tjp - test/TestSuite/Scheduler/Errors/milestone_start_end.tjp - test/TestSuite/Scheduler/Errors/minend.tjp - test/TestSuite/Scheduler/Errors/minstart.tjp - test/TestSuite/Scheduler/Errors/no_tasks.tjp - test/TestSuite/Scheduler/Errors/onend_wrong_direction.tjp - test/TestSuite/Scheduler/Errors/onstart_wrong_direction.tjp - test/TestSuite/Scheduler/Errors/overbooked_duration.tjp - test/TestSuite/Scheduler/Errors/overbooked_effort.tjp - test/TestSuite/Scheduler/Errors/overbooked_length.tjp - test/TestSuite/Scheduler/Errors/resource_fail_check.tjp - test/TestSuite/Scheduler/Errors/resource_warn_check.tjp - test/TestSuite/Scheduler/Errors/sched_runaway.tjp - test/TestSuite/Scheduler/Errors/task_depend_child.tjp - test/TestSuite/Scheduler/Errors/task_depend_multi.tjp - test/TestSuite/Scheduler/Errors/task_depend_parent.tjp - test/TestSuite/Scheduler/Errors/task_depend_self.tjp - test/TestSuite/Scheduler/Errors/task_depend_unknown.tjp - test/TestSuite/Scheduler/Errors/task_fail_check.tjp - test/TestSuite/Scheduler/Errors/task_overspecified_1.tjp - test/TestSuite/Scheduler/Errors/task_overspecified_2.tjp - test/TestSuite/Scheduler/Errors/task_overspecified_3.tjp - test/TestSuite/Scheduler/Errors/task_pred_before.tjp - test/TestSuite/Scheduler/Errors/task_pred_before_1.tjp - test/TestSuite/Scheduler/Errors/task_pred_before_2.tjp - test/TestSuite/Scheduler/Errors/task_succ_after.tjp - test/TestSuite/Scheduler/Errors/task_succ_after_1.tjp - test/TestSuite/Scheduler/Errors/task_succ_after_2.tjp - test/TestSuite/Scheduler/Errors/task_underspecified_1.tjp - test/TestSuite/Scheduler/Errors/task_underspecified_3.tjp - test/TestSuite/Scheduler/Errors/task_warn_check.tjp - test/TestSuite/Scheduler/Errors/ts_alert1_more_details.tjp - test/TestSuite/Scheduler/Errors/ts_alert2_more_details.tjp - test/TestSuite/Scheduler/Errors/ts_no_expected_end.tjp - test/TestSuite/Scheduler/Errors/ts_no_headline1.tjp - test/TestSuite/Scheduler/Errors/ts_no_rem_or_end.tjp - test/TestSuite/Scheduler/Errors/ts_no_remaining.tjp - test/TestSuite/Scheduler/Errors/ts_no_status_work.tjp - test/TestSuite/Scheduler/Errors/ts_no_work.tjp - test/TestSuite/Scheduler/Errors/ts_res_new_task.tjp - test/TestSuite/Scheduler/Errors/ts_work_too_high.tjp - test/TestSuite/Scheduler/Errors/ts_work_too_low.tjp - test/TestSuite/Syntax/Correct/Account.tjp - test/TestSuite/Syntax/Correct/AccountReport.tjp - test/TestSuite/Syntax/Correct/AdoptedTasks.tjp - test/TestSuite/Syntax/Correct/AlertLevels.tjp - test/TestSuite/Syntax/Correct/Allocate-1.tjp - test/TestSuite/Syntax/Correct/Alternative.tjp - test/TestSuite/Syntax/Correct/AutoID.tjp - test/TestSuite/Syntax/Correct/AutoMacros.tjp - test/TestSuite/Syntax/Correct/Booking.tjp - test/TestSuite/Syntax/Correct/Caption.tjp - test/TestSuite/Syntax/Correct/Celltext.tjp - test/TestSuite/Syntax/Correct/Comments.tjp - test/TestSuite/Syntax/Correct/Complete.tjp - test/TestSuite/Syntax/Correct/CompletedWork.tji - test/TestSuite/Syntax/Correct/Currencyformat.tjp - test/TestSuite/Syntax/Correct/CustomAttributes.tjp - test/TestSuite/Syntax/Correct/Depends1.tjp - test/TestSuite/Syntax/Correct/Durations.tjp - test/TestSuite/Syntax/Correct/Efficiency.tjp - test/TestSuite/Syntax/Correct/EnvVar.tjp - test/TestSuite/Syntax/Correct/Export.tjp - test/TestSuite/Syntax/Correct/Flags.tjp - test/TestSuite/Syntax/Correct/Freeze.tjp - test/TestSuite/Syntax/Correct/Gap.tjp - test/TestSuite/Syntax/Correct/HtmlTaskReport.tjp - test/TestSuite/Syntax/Correct/Include.tjp - test/TestSuite/Syntax/Correct/Journal.tjp - test/TestSuite/Syntax/Correct/Leave.tjp - test/TestSuite/Syntax/Correct/Limits-1.tjp - test/TestSuite/Syntax/Correct/LoadUnits.tjp - test/TestSuite/Syntax/Correct/LogicalExpression.tjp - test/TestSuite/Syntax/Correct/LogicalFunction.tjp - test/TestSuite/Syntax/Correct/Macro-1.tjp - test/TestSuite/Syntax/Correct/Macro-2.tjp - test/TestSuite/Syntax/Correct/Macro-3.tjp - test/TestSuite/Syntax/Correct/Macro-4.tjp - test/TestSuite/Syntax/Correct/Manager.tjp - test/TestSuite/Syntax/Correct/Mandatory.tjp - test/TestSuite/Syntax/Correct/Milestone.tjp - test/TestSuite/Syntax/Correct/MinMax.tjp - test/TestSuite/Syntax/Correct/Niku.tjp - test/TestSuite/Syntax/Correct/Numberformat.tjp - test/TestSuite/Syntax/Correct/Period.tjp - test/TestSuite/Syntax/Correct/Persistent.tjp - test/TestSuite/Syntax/Correct/Precedes1.tjp - test/TestSuite/Syntax/Correct/Priority.tjp - test/TestSuite/Syntax/Correct/Project.tjp - test/TestSuite/Syntax/Correct/ProjectIDs.tjp - test/TestSuite/Syntax/Correct/Query.tjp - test/TestSuite/Syntax/Correct/Reports.tjp - test/TestSuite/Syntax/Correct/Resource.tjp - test/TestSuite/Syntax/Correct/ResourcePrefix.tji - test/TestSuite/Syntax/Correct/ResourcePrefix.tjp - test/TestSuite/Syntax/Correct/ResourceRoot.tjp - test/TestSuite/Syntax/Correct/Responsible.tjp - test/TestSuite/Syntax/Correct/RollupResource.tjp - test/TestSuite/Syntax/Correct/Scenario.tjp - test/TestSuite/Syntax/Correct/Scheduling.tjp - test/TestSuite/Syntax/Correct/Select.tjp - test/TestSuite/Syntax/Correct/Shift.tjp - test/TestSuite/Syntax/Correct/Simple.tjp - test/TestSuite/Syntax/Correct/StatusSheet.tjp - test/TestSuite/Syntax/Correct/String.tjp - test/TestSuite/Syntax/Correct/Supplement.tjp - test/TestSuite/Syntax/Correct/TaskPrefix.tji - test/TestSuite/Syntax/Correct/TaskPrefix.tjp - test/TestSuite/Syntax/Correct/TaskRoot.tjp - test/TestSuite/Syntax/Correct/TimeFrame.tjp - test/TestSuite/Syntax/Correct/TimeSheet1.tjp - test/TestSuite/Syntax/Correct/Timezone.tjp - test/TestSuite/Syntax/Correct/TraceReport.tjp - test/TestSuite/Syntax/Correct/Vacation.tjp - test/TestSuite/Syntax/Correct/include/dir1/file2.tji - test/TestSuite/Syntax/Correct/include/dir1/file5.tji - test/TestSuite/Syntax/Correct/include/dir2/file3.tji - test/TestSuite/Syntax/Correct/include/dir3/all.tji - test/TestSuite/Syntax/Correct/include/dir3/file1.tji - test/TestSuite/Syntax/Correct/include/dir3/file2.tji - test/TestSuite/Syntax/Correct/include/file1.tji - test/TestSuite/Syntax/Correct/manual2example.rb - test/TestSuite/Syntax/Correct/navigator.tjp - test/TestSuite/Syntax/Correct/template.tjp - test/TestSuite/Syntax/Correct/textreport.tjp - test/TestSuite/Syntax/Correct/tutorial.tjp - test/TestSuite/Syntax/Errors/adopt_duplicate_child-1.tjp - test/TestSuite/Syntax/Errors/adopt_duplicate_child-2.tjp - test/TestSuite/Syntax/Errors/adopt_duplicate_child-3.tjp - test/TestSuite/Syntax/Errors/adopt_self.tjp - test/TestSuite/Syntax/Errors/alert_level_redef.tjp - test/TestSuite/Syntax/Errors/alert_name_redef.tjp - test/TestSuite/Syntax/Errors/bad_include.tjp - test/TestSuite/Syntax/Errors/bad_time_zone.tjp - test/TestSuite/Syntax/Errors/bad_timing_res.tjp - test/TestSuite/Syntax/Errors/booking_group.tjp - test/TestSuite/Syntax/Errors/booking_milestone.tjp - test/TestSuite/Syntax/Errors/booking_no_leaf.tjp - test/TestSuite/Syntax/Errors/chargeset.tjp - test/TestSuite/Syntax/Errors/chargeset_master.tjp - test/TestSuite/Syntax/Errors/container_attribute.tjp - test/TestSuite/Syntax/Errors/cost_acct_no_top.tjp - test/TestSuite/Syntax/Errors/cost_rev_same.tjp - test/TestSuite/Syntax/Errors/date_in_range.tjp - test/TestSuite/Syntax/Errors/effort_zero.tjp - test/TestSuite/Syntax/Errors/empty.tjp - test/TestSuite/Syntax/Errors/extend_id_cap.tjp - test/TestSuite/Syntax/Errors/include_before_project.tjp - test/TestSuite/Syntax/Errors/include_recursion.tji - test/TestSuite/Syntax/Errors/include_recursion.tjp - test/TestSuite/Syntax/Errors/interval_end_in_range.tjp - test/TestSuite/Syntax/Errors/interval_start_in_range.tjp - test/TestSuite/Syntax/Errors/invalid_file_name.tjp - test/TestSuite/Syntax/Errors/junk_after_cut.tjp - test/TestSuite/Syntax/Errors/leaf_resource_id_expected.tjp - test/TestSuite/Syntax/Errors/macro_stack_overflow.tjp - test/TestSuite/Syntax/Errors/misaligned_date.tjp - test/TestSuite/Syntax/Errors/multiple_durations.tjp - test/TestSuite/Syntax/Errors/navigator_exists.tjp - test/TestSuite/Syntax/Errors/no_own_resource_booking.tjp - test/TestSuite/Syntax/Errors/no_own_task_booking.tjp - test/TestSuite/Syntax/Errors/no_reduce.tjp - test/TestSuite/Syntax/Errors/no_token_match1.tjp - test/TestSuite/Syntax/Errors/no_token_match2.tjp - test/TestSuite/Syntax/Errors/no_token_match3.tjp - test/TestSuite/Syntax/Errors/no_token_match4.tjp - test/TestSuite/Syntax/Errors/no_token_match5.tjp - test/TestSuite/Syntax/Errors/not_scheduled.tjp - test/TestSuite/Syntax/Errors/operand_attribute.tjp - test/TestSuite/Syntax/Errors/operand_unkn_flag.tjp - test/TestSuite/Syntax/Errors/operand_unkn_scen.tjp - test/TestSuite/Syntax/Errors/overtime_range.tjp - test/TestSuite/Syntax/Errors/purge_unknown_id.tjp - test/TestSuite/Syntax/Errors/report_end.tjp - test/TestSuite/Syntax/Errors/report_exists.tjp - test/TestSuite/Syntax/Errors/report_start.tjp - test/TestSuite/Syntax/Errors/resource_exists.tjp - test/TestSuite/Syntax/Errors/resource_id_expected.tjp - test/TestSuite/Syntax/Errors/resourceroot_leaf.tjp - test/TestSuite/Syntax/Errors/rev_acct_no_top.tjp - test/TestSuite/Syntax/Errors/runaway_token.tjp - test/TestSuite/Syntax/Errors/scenario_after_tracking.tjp - test/TestSuite/Syntax/Errors/scenario_exists.tjp - test/TestSuite/Syntax/Errors/shift_assignment_overlap.tjp - test/TestSuite/Syntax/Errors/shift_exists.tjp - test/TestSuite/Syntax/Errors/shift_id_expected.tjp - test/TestSuite/Syntax/Errors/sloppy_range.tjp - test/TestSuite/Syntax/Errors/sort_direction.tjp - test/TestSuite/Syntax/Errors/sort_unknown_scen.tjp - test/TestSuite/Syntax/Errors/sorting_bsi.tjp - test/TestSuite/Syntax/Errors/sorting_crit_exptd1.tjp - test/TestSuite/Syntax/Errors/sorting_crit_exptd2.tjp - test/TestSuite/Syntax/Errors/ss_no_tracking_scenario.tjp - test/TestSuite/Syntax/Errors/start_before_end1.tjp - test/TestSuite/Syntax/Errors/start_before_end2.tjp - test/TestSuite/Syntax/Errors/task_complete.tjp - test/TestSuite/Syntax/Errors/task_exists.tjp - test/TestSuite/Syntax/Errors/task_priority.tjp - test/TestSuite/Syntax/Errors/task_without_chargeset.tjp - test/TestSuite/Syntax/Errors/taskroot_leaf.tjp - test/TestSuite/Syntax/Errors/time_interval.tjp - test/TestSuite/Syntax/Errors/too_few_alert_levels.tjp - test/TestSuite/Syntax/Errors/too_large_timing_res.tjp - test/TestSuite/Syntax/Errors/too_many_bangs.tjp - test/TestSuite/Syntax/Errors/ts_bad_priority.tjp - test/TestSuite/Syntax/Errors/ts_default_details.tjp - test/TestSuite/Syntax/Errors/ts_default_summary.tjp - test/TestSuite/Syntax/Errors/ts_duplicate_task.tjp - test/TestSuite/Syntax/Errors/ts_end_too_early.tjp - test/TestSuite/Syntax/Errors/ts_headline_too_long.tjp - test/TestSuite/Syntax/Errors/ts_no_headline2.tjp - test/TestSuite/Syntax/Errors/ts_no_tracking_scenario.tjp - test/TestSuite/Syntax/Errors/ts_summary_too_long.tjp - test/TestSuite/Syntax/Errors/undecl_flag.tjp - test/TestSuite/Syntax/Errors/unknown_env_var.tjp - test/TestSuite/Syntax/Errors/unknown_projectid.tjp - test/TestSuite/Syntax/Errors/unknown_scenario_id.tjp - test/TestSuite/Syntax/Errors/unknown_scenario_idx.tjp - test/TestSuite/Syntax/Errors/unknown_task.tjp - test/TestSuite/Syntax/Errors/unsupported_token.tjp - test/TjpGen.rb - test/all.rb - test/test_AlgorithmDiff.rb - test/test_BatchProcessor.rb - test/test_CSV-Reports.rb - test/test_CSVFile.rb - test/test_CollisionDetector.rb - test/test_Export-Reports.rb - test/test_Journal.rb - test/test_Limits.rb - test/test_LogicalExpression.rb - test/test_MacroTable.rb - test/test_Project.rb - test/test_ProjectFileScanner.rb - test/test_PropertySet.rb - test/test_Query.rb - test/test_RealFormat.rb - test/test_ReportGenerator.rb - test/test_RichText.rb - test/test_Scheduler.rb - test/test_ShiftAssignments.rb - test/test_SimpleQueryExpander.rb - test/test_Syntax.rb - test/test_TextFormatter.rb - test/test_TjTime.rb - test/test_TjpExample.rb - test/test_URLParameter.rb - test/test_UTF8String.rb - test/test_WorkingHours.rb - test/test_deep_copy.rb - spec/Color_spec.rb - spec/ICalendar_spec.rb - spec/IntervalList_spec.rb - spec/ProjectBroker_spec.rb - spec/StatusSheets_spec.rb - spec/TableColumnSorter_spec.rb - spec/TernarySearchTree_spec.rb - spec/TimeSheets_spec.rb - spec/Tj3Daemon_spec.rb - spec/Tj3_spec.rb - spec/TraceReport_spec.rb - spec/support/DaemonControl.rb - spec/support/spec_helper.rb - bin/tj3 - bin/tj3client - bin/tj3d - bin/tj3man - bin/tj3ss_receiver - bin/tj3ss_sender - bin/tj3ts_receiver - bin/tj3ts_sender - bin/tj3ts_summary - bin/tj3webd homepage: http://www.taskjuggler.org licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: 1.8.7 required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: taskjuggler rubygems_version: 1.8.23 signing_key: specification_version: 3 summary: A Project Management Software test_files: - test/MessageChecker.rb - test/ReferenceGenerator.rb - test/TestSuite/CSV-Reports/Leave.tjp - test/TestSuite/CSV-Reports/alert.tjp - test/TestSuite/CSV-Reports/celltext.tjp - test/TestSuite/CSV-Reports/efficiency.tjp - test/TestSuite/CSV-Reports/headcount.tjp - test/TestSuite/CSV-Reports/inputs.tjp - test/TestSuite/CSV-Reports/niku.tjp - test/TestSuite/CSV-Reports/project-1.tji - test/TestSuite/CSV-Reports/refs/Leave.csv - test/TestSuite/CSV-Reports/refs/alert.csv - test/TestSuite/CSV-Reports/refs/celltext.csv - test/TestSuite/CSV-Reports/refs/efficiency.csv - test/TestSuite/CSV-Reports/refs/headcount.csv - test/TestSuite/CSV-Reports/refs/inputs.csv - test/TestSuite/CSV-Reports/refs/niku.csv - test/TestSuite/CSV-Reports/refs/resourcereport.csv - test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv - test/TestSuite/CSV-Reports/refs/sortByTree.csv - test/TestSuite/CSV-Reports/refs/sortBy_duration.down.csv - test/TestSuite/CSV-Reports/refs/sortBy_effort.up.csv - test/TestSuite/CSV-Reports/refs/sortBy_plan.start.down.csv - test/TestSuite/CSV-Reports/refs/targets.csv - test/TestSuite/CSV-Reports/refs/taskreport.csv - test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv - test/TestSuite/CSV-Reports/refs/weekly.csv - test/TestSuite/CSV-Reports/resourcereport.tjp - test/TestSuite/CSV-Reports/resourcereport_with_tasks.tjp - test/TestSuite/CSV-Reports/sortByTree.tjp - test/TestSuite/CSV-Reports/sortBy_duration.down.tjp - test/TestSuite/CSV-Reports/sortBy_effort.up.tjp - test/TestSuite/CSV-Reports/sortBy_plan.start.down.tjp - test/TestSuite/CSV-Reports/targets.tjp - test/TestSuite/CSV-Reports/taskreport.tjp - test/TestSuite/CSV-Reports/taskreport_with_resources.tjp - test/TestSuite/CSV-Reports/weekly.tjp - test/TestSuite/Export-Reports/export.tji - test/TestSuite/Export-Reports/refs/Account.tjp - test/TestSuite/Export-Reports/refs/AccountReport.tjp - test/TestSuite/Export-Reports/refs/AdoptedTasks.tjp - test/TestSuite/Export-Reports/refs/AlertLevels.tjp - test/TestSuite/Export-Reports/refs/Allocate-1.tjp - test/TestSuite/Export-Reports/refs/Alternative.tjp - test/TestSuite/Export-Reports/refs/AutoID.tjp - test/TestSuite/Export-Reports/refs/AutoMacros.tjp - test/TestSuite/Export-Reports/refs/Booking.tjp - test/TestSuite/Export-Reports/refs/Caption.tjp - test/TestSuite/Export-Reports/refs/Celltext.tjp - test/TestSuite/Export-Reports/refs/Comments.tjp - test/TestSuite/Export-Reports/refs/Complete.tjp - test/TestSuite/Export-Reports/refs/Currencyformat.tjp - test/TestSuite/Export-Reports/refs/CustomAttributes.tjp - test/TestSuite/Export-Reports/refs/Depends1.tjp - test/TestSuite/Export-Reports/refs/Durations.tjp - test/TestSuite/Export-Reports/refs/Efficiency.tjp - test/TestSuite/Export-Reports/refs/EnvVar.tjp - test/TestSuite/Export-Reports/refs/Flags.tjp - test/TestSuite/Export-Reports/refs/Gap.tjp - test/TestSuite/Export-Reports/refs/HtmlTaskReport.tjp - test/TestSuite/Export-Reports/refs/Include.tjp - test/TestSuite/Export-Reports/refs/Journal.tjp - test/TestSuite/Export-Reports/refs/Limits-1.tjp - test/TestSuite/Export-Reports/refs/LoadUnits.tjp - test/TestSuite/Export-Reports/refs/LogicalExpression.tjp - test/TestSuite/Export-Reports/refs/LogicalFunction.tjp - test/TestSuite/Export-Reports/refs/Macro-1.tjp - test/TestSuite/Export-Reports/refs/Macro-2.tjp - test/TestSuite/Export-Reports/refs/Macro-3.tjp - test/TestSuite/Export-Reports/refs/Manager.tjp - test/TestSuite/Export-Reports/refs/Mandatory.tjp - test/TestSuite/Export-Reports/refs/Milestone.tjp - test/TestSuite/Export-Reports/refs/MinMax.tjp - test/TestSuite/Export-Reports/refs/Niku.tjp - test/TestSuite/Export-Reports/refs/Numberformat.tjp - test/TestSuite/Export-Reports/refs/Period.tjp - test/TestSuite/Export-Reports/refs/Persistent.tjp - test/TestSuite/Export-Reports/refs/Precedes1.tjp - test/TestSuite/Export-Reports/refs/Priority.tjp - test/TestSuite/Export-Reports/refs/Project.tjp - test/TestSuite/Export-Reports/refs/ProjectIDs.tjp - test/TestSuite/Export-Reports/refs/Query.tjp - test/TestSuite/Export-Reports/refs/Reports.tjp - test/TestSuite/Export-Reports/refs/Resource.tjp - test/TestSuite/Export-Reports/refs/ResourcePrefix.tjp - test/TestSuite/Export-Reports/refs/ResourceRoot.tjp - test/TestSuite/Export-Reports/refs/Responsible.tjp - test/TestSuite/Export-Reports/refs/RollupResource.tjp - test/TestSuite/Export-Reports/refs/Scenario.tjp - test/TestSuite/Export-Reports/refs/Scheduling.tjp - test/TestSuite/Export-Reports/refs/Select.tjp - test/TestSuite/Export-Reports/refs/Shift.tjp - test/TestSuite/Export-Reports/refs/Simple.tjp - test/TestSuite/Export-Reports/refs/StatusSheet.tjp - test/TestSuite/Export-Reports/refs/String.tjp - test/TestSuite/Export-Reports/refs/Supplement.tjp - test/TestSuite/Export-Reports/refs/TaskPrefix.tjp - test/TestSuite/Export-Reports/refs/TaskRoot.tjp - test/TestSuite/Export-Reports/refs/TimeFrame.tjp - test/TestSuite/Export-Reports/refs/TimeSheet1.tjp - test/TestSuite/Export-Reports/refs/Timezone.tjp - test/TestSuite/Export-Reports/refs/Vacation.tjp - test/TestSuite/Export-Reports/refs/navigator.tjp - test/TestSuite/Export-Reports/refs/template.tjp - test/TestSuite/Export-Reports/refs/textreport.tjp - test/TestSuite/Export-Reports/refs/tutorial.tjp - test/TestSuite/HTML-Reports/Alerts-2.tjp - test/TestSuite/HTML-Reports/Alerts.tjp - test/TestSuite/HTML-Reports/Calendars.tjp - test/TestSuite/HTML-Reports/CellText.tjp - test/TestSuite/HTML-Reports/ColumnPeriods.tjp - test/TestSuite/HTML-Reports/IsOngoing.tjp - test/TestSuite/HTML-Reports/Navigator.tjp - test/TestSuite/HTML-Reports/Query.tjp - test/TestSuite/HTML-Reports/Sorting.tjp - test/TestSuite/HTML-Reports/TimeSheet.tjp - test/TestSuite/HTML-Reports/UDAQuery.tjp - test/TestSuite/HTML-Reports/depArrows.tjp - test/TestSuite/HTML-Reports/reference.tjp - test/TestSuite/ReportGenerator/Correct/Alerts.tjp - test/TestSuite/ReportGenerator/Correct/FTE.tjp - test/TestSuite/ReportGenerator/Correct/Journal.tjp - test/TestSuite/ReportGenerator/Correct/JournalMode.tjp - test/TestSuite/ReportGenerator/Correct/LogicalFunctions1.tjp - test/TestSuite/ReportGenerator/Correct/LogicalFunctions2.tjp - test/TestSuite/ReportGenerator/Correct/LogicalFunctions3.tjp - test/TestSuite/ReportGenerator/Correct/LogicalFunctions4.tjp - test/TestSuite/ReportGenerator/Correct/Macros.tjp - test/TestSuite/ReportGenerator/Correct/refs/Alerts-1.csv - test/TestSuite/ReportGenerator/Correct/refs/DependencyList-1.csv - test/TestSuite/ReportGenerator/Correct/refs/FTE-1.csv - test/TestSuite/ReportGenerator/Correct/refs/Journal-1.csv - test/TestSuite/ReportGenerator/Correct/refs/Journal-2.csv - test/TestSuite/ReportGenerator/Correct/refs/JournalMode-1.csv - test/TestSuite/ReportGenerator/Correct/refs/JournalMode-2.csv - test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions1-1.csv - test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions2-1.csv - test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions3-1.csv - test/TestSuite/ReportGenerator/Correct/refs/LogicalFunctions4-1.csv - test/TestSuite/ReportGenerator/Correct/refs/Macros-1.csv - test/TestSuite/ReportGenerator/Errors/no_report_defined.tjp - test/TestSuite/ReportGenerator/Errors/rtp_report_recursion.tjp - test/TestSuite/Scheduler/Correct/Allocate.tjp - test/TestSuite/Scheduler/Correct/AutomaticMilestones.tjp - test/TestSuite/Scheduler/Correct/Booking.tjp - test/TestSuite/Scheduler/Correct/Booking2.tjp - test/TestSuite/Scheduler/Correct/Booking3.tjp - test/TestSuite/Scheduler/Correct/Container-2.tjp - test/TestSuite/Scheduler/Correct/Container.tjp - test/TestSuite/Scheduler/Correct/DateAndDep.tjp - test/TestSuite/Scheduler/Correct/Depends.tjp - test/TestSuite/Scheduler/Correct/Duration.tjp - test/TestSuite/Scheduler/Correct/InheritStartEnd.tjp - test/TestSuite/Scheduler/Correct/InheritedAttributes.tjp - test/TestSuite/Scheduler/Correct/Leaves.tjp - test/TestSuite/Scheduler/Correct/Limits.tjp - test/TestSuite/Scheduler/Correct/Manager.tjp - test/TestSuite/Scheduler/Correct/Mandatory.tjp - test/TestSuite/Scheduler/Correct/MultipleMandatories.tjp - test/TestSuite/Scheduler/Correct/Optimize-1.tjp - test/TestSuite/Scheduler/Correct/Optimize-2.tjp - test/TestSuite/Scheduler/Correct/Optimize-3.tjp - test/TestSuite/Scheduler/Correct/Optimize-4.tjp - test/TestSuite/Scheduler/Correct/Optimize-5.tjp - test/TestSuite/Scheduler/Correct/PersistentResources-2.tjp - test/TestSuite/Scheduler/Correct/PersistentResources.tjp - test/TestSuite/Scheduler/Correct/Precedes.tjp - test/TestSuite/Scheduler/Correct/PriorityInversion.tjp - test/TestSuite/Scheduler/Correct/Scenarios.tjp - test/TestSuite/Scheduler/Correct/Shift.tjp - test/TestSuite/Scheduler/Correct/Shift2.tjp - test/TestSuite/Scheduler/Correct/TimeSheet2.tjp - test/TestSuite/Scheduler/Correct/WeakDeps.tjp - test/TestSuite/Scheduler/Correct/checks.tji - test/TestSuite/Scheduler/Correct/hammock.tjp - test/TestSuite/Scheduler/Correct/purge.tjp - test/TestSuite/Scheduler/Errors/account_no_leaf.tjp - test/TestSuite/Scheduler/Errors/allocate_no_assigned.tjp - test/TestSuite/Scheduler/Errors/booking_conflict.tjp - test/TestSuite/Scheduler/Errors/booking_no_duty.tjp - test/TestSuite/Scheduler/Errors/booking_on_vacation.tjp - test/TestSuite/Scheduler/Errors/container_booking.tjp - test/TestSuite/Scheduler/Errors/container_duration.tjp - test/TestSuite/Scheduler/Errors/container_milestone.tjp - test/TestSuite/Scheduler/Errors/effort_no_allocations.tjp - test/TestSuite/Scheduler/Errors/impossible_end_dep.tjp - test/TestSuite/Scheduler/Errors/impossible_start_dep.tjp - test/TestSuite/Scheduler/Errors/loop_detected_1.tjp - test/TestSuite/Scheduler/Errors/loop_detected_10.tjp - test/TestSuite/Scheduler/Errors/loop_detected_11.tjp - test/TestSuite/Scheduler/Errors/loop_detected_12.tjp - test/TestSuite/Scheduler/Errors/loop_detected_13.tjp - test/TestSuite/Scheduler/Errors/loop_detected_14.tjp - test/TestSuite/Scheduler/Errors/loop_detected_2.tjp - test/TestSuite/Scheduler/Errors/loop_detected_3.tjp - test/TestSuite/Scheduler/Errors/loop_detected_4.tjp - test/TestSuite/Scheduler/Errors/loop_detected_5.tjp - test/TestSuite/Scheduler/Errors/loop_detected_6.tjp - test/TestSuite/Scheduler/Errors/loop_detected_9.tjp - test/TestSuite/Scheduler/Errors/manager_is_group.tjp - test/TestSuite/Scheduler/Errors/manager_is_self.tjp - test/TestSuite/Scheduler/Errors/maxend.tjp - test/TestSuite/Scheduler/Errors/maxstart.tjp - test/TestSuite/Scheduler/Errors/milestone_booking.tjp - test/TestSuite/Scheduler/Errors/milestone_start_end.tjp - test/TestSuite/Scheduler/Errors/minend.tjp - test/TestSuite/Scheduler/Errors/minstart.tjp - test/TestSuite/Scheduler/Errors/no_tasks.tjp - test/TestSuite/Scheduler/Errors/onend_wrong_direction.tjp - test/TestSuite/Scheduler/Errors/onstart_wrong_direction.tjp - test/TestSuite/Scheduler/Errors/overbooked_duration.tjp - test/TestSuite/Scheduler/Errors/overbooked_effort.tjp - test/TestSuite/Scheduler/Errors/overbooked_length.tjp - test/TestSuite/Scheduler/Errors/resource_fail_check.tjp - test/TestSuite/Scheduler/Errors/resource_warn_check.tjp - test/TestSuite/Scheduler/Errors/sched_runaway.tjp - test/TestSuite/Scheduler/Errors/task_depend_child.tjp - test/TestSuite/Scheduler/Errors/task_depend_multi.tjp - test/TestSuite/Scheduler/Errors/task_depend_parent.tjp - test/TestSuite/Scheduler/Errors/task_depend_self.tjp - test/TestSuite/Scheduler/Errors/task_depend_unknown.tjp - test/TestSuite/Scheduler/Errors/task_fail_check.tjp - test/TestSuite/Scheduler/Errors/task_overspecified_1.tjp - test/TestSuite/Scheduler/Errors/task_overspecified_2.tjp - test/TestSuite/Scheduler/Errors/task_overspecified_3.tjp - test/TestSuite/Scheduler/Errors/task_pred_before.tjp - test/TestSuite/Scheduler/Errors/task_pred_before_1.tjp - test/TestSuite/Scheduler/Errors/task_pred_before_2.tjp - test/TestSuite/Scheduler/Errors/task_succ_after.tjp - test/TestSuite/Scheduler/Errors/task_succ_after_1.tjp - test/TestSuite/Scheduler/Errors/task_succ_after_2.tjp - test/TestSuite/Scheduler/Errors/task_underspecified_1.tjp - test/TestSuite/Scheduler/Errors/task_underspecified_3.tjp - test/TestSuite/Scheduler/Errors/task_warn_check.tjp - test/TestSuite/Scheduler/Errors/ts_alert1_more_details.tjp - test/TestSuite/Scheduler/Errors/ts_alert2_more_details.tjp - test/TestSuite/Scheduler/Errors/ts_no_expected_end.tjp - test/TestSuite/Scheduler/Errors/ts_no_headline1.tjp - test/TestSuite/Scheduler/Errors/ts_no_rem_or_end.tjp - test/TestSuite/Scheduler/Errors/ts_no_remaining.tjp - test/TestSuite/Scheduler/Errors/ts_no_status_work.tjp - test/TestSuite/Scheduler/Errors/ts_no_work.tjp - test/TestSuite/Scheduler/Errors/ts_res_new_task.tjp - test/TestSuite/Scheduler/Errors/ts_work_too_high.tjp - test/TestSuite/Scheduler/Errors/ts_work_too_low.tjp - test/TestSuite/Syntax/Correct/Account.tjp - test/TestSuite/Syntax/Correct/AccountReport.tjp - test/TestSuite/Syntax/Correct/AdoptedTasks.tjp - test/TestSuite/Syntax/Correct/AlertLevels.tjp - test/TestSuite/Syntax/Correct/Allocate-1.tjp - test/TestSuite/Syntax/Correct/Alternative.tjp - test/TestSuite/Syntax/Correct/AutoID.tjp - test/TestSuite/Syntax/Correct/AutoMacros.tjp - test/TestSuite/Syntax/Correct/Booking.tjp - test/TestSuite/Syntax/Correct/Caption.tjp - test/TestSuite/Syntax/Correct/Celltext.tjp - test/TestSuite/Syntax/Correct/Comments.tjp - test/TestSuite/Syntax/Correct/Complete.tjp - test/TestSuite/Syntax/Correct/CompletedWork.tji - test/TestSuite/Syntax/Correct/Currencyformat.tjp - test/TestSuite/Syntax/Correct/CustomAttributes.tjp - test/TestSuite/Syntax/Correct/Depends1.tjp - test/TestSuite/Syntax/Correct/Durations.tjp - test/TestSuite/Syntax/Correct/Efficiency.tjp - test/TestSuite/Syntax/Correct/EnvVar.tjp - test/TestSuite/Syntax/Correct/Export.tjp - test/TestSuite/Syntax/Correct/Flags.tjp - test/TestSuite/Syntax/Correct/Freeze.tjp - test/TestSuite/Syntax/Correct/Gap.tjp - test/TestSuite/Syntax/Correct/HtmlTaskReport.tjp - test/TestSuite/Syntax/Correct/Include.tjp - test/TestSuite/Syntax/Correct/Journal.tjp - test/TestSuite/Syntax/Correct/Leave.tjp - test/TestSuite/Syntax/Correct/Limits-1.tjp - test/TestSuite/Syntax/Correct/LoadUnits.tjp - test/TestSuite/Syntax/Correct/LogicalExpression.tjp - test/TestSuite/Syntax/Correct/LogicalFunction.tjp - test/TestSuite/Syntax/Correct/Macro-1.tjp - test/TestSuite/Syntax/Correct/Macro-2.tjp - test/TestSuite/Syntax/Correct/Macro-3.tjp - test/TestSuite/Syntax/Correct/Macro-4.tjp - test/TestSuite/Syntax/Correct/Manager.tjp - test/TestSuite/Syntax/Correct/Mandatory.tjp - test/TestSuite/Syntax/Correct/Milestone.tjp - test/TestSuite/Syntax/Correct/MinMax.tjp - test/TestSuite/Syntax/Correct/Niku.tjp - test/TestSuite/Syntax/Correct/Numberformat.tjp - test/TestSuite/Syntax/Correct/Period.tjp - test/TestSuite/Syntax/Correct/Persistent.tjp - test/TestSuite/Syntax/Correct/Precedes1.tjp - test/TestSuite/Syntax/Correct/Priority.tjp - test/TestSuite/Syntax/Correct/Project.tjp - test/TestSuite/Syntax/Correct/ProjectIDs.tjp - test/TestSuite/Syntax/Correct/Query.tjp - test/TestSuite/Syntax/Correct/Reports.tjp - test/TestSuite/Syntax/Correct/Resource.tjp - test/TestSuite/Syntax/Correct/ResourcePrefix.tji - test/TestSuite/Syntax/Correct/ResourcePrefix.tjp - test/TestSuite/Syntax/Correct/ResourceRoot.tjp - test/TestSuite/Syntax/Correct/Responsible.tjp - test/TestSuite/Syntax/Correct/RollupResource.tjp - test/TestSuite/Syntax/Correct/Scenario.tjp - test/TestSuite/Syntax/Correct/Scheduling.tjp - test/TestSuite/Syntax/Correct/Select.tjp - test/TestSuite/Syntax/Correct/Shift.tjp - test/TestSuite/Syntax/Correct/Simple.tjp - test/TestSuite/Syntax/Correct/StatusSheet.tjp - test/TestSuite/Syntax/Correct/String.tjp - test/TestSuite/Syntax/Correct/Supplement.tjp - test/TestSuite/Syntax/Correct/TaskPrefix.tji - test/TestSuite/Syntax/Correct/TaskPrefix.tjp - test/TestSuite/Syntax/Correct/TaskRoot.tjp - test/TestSuite/Syntax/Correct/TimeFrame.tjp - test/TestSuite/Syntax/Correct/TimeSheet1.tjp - test/TestSuite/Syntax/Correct/Timezone.tjp - test/TestSuite/Syntax/Correct/TraceReport.tjp - test/TestSuite/Syntax/Correct/Vacation.tjp - test/TestSuite/Syntax/Correct/include/dir1/file2.tji - test/TestSuite/Syntax/Correct/include/dir1/file5.tji - test/TestSuite/Syntax/Correct/include/dir2/file3.tji - test/TestSuite/Syntax/Correct/include/dir3/all.tji - test/TestSuite/Syntax/Correct/include/dir3/file1.tji - test/TestSuite/Syntax/Correct/include/dir3/file2.tji - test/TestSuite/Syntax/Correct/include/file1.tji - test/TestSuite/Syntax/Correct/manual2example.rb - test/TestSuite/Syntax/Correct/navigator.tjp - test/TestSuite/Syntax/Correct/template.tjp - test/TestSuite/Syntax/Correct/textreport.tjp - test/TestSuite/Syntax/Correct/tutorial.tjp - test/TestSuite/Syntax/Errors/adopt_duplicate_child-1.tjp - test/TestSuite/Syntax/Errors/adopt_duplicate_child-2.tjp - test/TestSuite/Syntax/Errors/adopt_duplicate_child-3.tjp - test/TestSuite/Syntax/Errors/adopt_self.tjp - test/TestSuite/Syntax/Errors/alert_level_redef.tjp - test/TestSuite/Syntax/Errors/alert_name_redef.tjp - test/TestSuite/Syntax/Errors/bad_include.tjp - test/TestSuite/Syntax/Errors/bad_time_zone.tjp - test/TestSuite/Syntax/Errors/bad_timing_res.tjp - test/TestSuite/Syntax/Errors/booking_group.tjp - test/TestSuite/Syntax/Errors/booking_milestone.tjp - test/TestSuite/Syntax/Errors/booking_no_leaf.tjp - test/TestSuite/Syntax/Errors/chargeset.tjp - test/TestSuite/Syntax/Errors/chargeset_master.tjp - test/TestSuite/Syntax/Errors/container_attribute.tjp - test/TestSuite/Syntax/Errors/cost_acct_no_top.tjp - test/TestSuite/Syntax/Errors/cost_rev_same.tjp - test/TestSuite/Syntax/Errors/date_in_range.tjp - test/TestSuite/Syntax/Errors/effort_zero.tjp - test/TestSuite/Syntax/Errors/empty.tjp - test/TestSuite/Syntax/Errors/extend_id_cap.tjp - test/TestSuite/Syntax/Errors/include_before_project.tjp - test/TestSuite/Syntax/Errors/include_recursion.tji - test/TestSuite/Syntax/Errors/include_recursion.tjp - test/TestSuite/Syntax/Errors/interval_end_in_range.tjp - test/TestSuite/Syntax/Errors/interval_start_in_range.tjp - test/TestSuite/Syntax/Errors/invalid_file_name.tjp - test/TestSuite/Syntax/Errors/junk_after_cut.tjp - test/TestSuite/Syntax/Errors/leaf_resource_id_expected.tjp - test/TestSuite/Syntax/Errors/macro_stack_overflow.tjp - test/TestSuite/Syntax/Errors/misaligned_date.tjp - test/TestSuite/Syntax/Errors/multiple_durations.tjp - test/TestSuite/Syntax/Errors/navigator_exists.tjp - test/TestSuite/Syntax/Errors/no_own_resource_booking.tjp - test/TestSuite/Syntax/Errors/no_own_task_booking.tjp - test/TestSuite/Syntax/Errors/no_reduce.tjp - test/TestSuite/Syntax/Errors/no_token_match1.tjp - test/TestSuite/Syntax/Errors/no_token_match2.tjp - test/TestSuite/Syntax/Errors/no_token_match3.tjp - test/TestSuite/Syntax/Errors/no_token_match4.tjp - test/TestSuite/Syntax/Errors/no_token_match5.tjp - test/TestSuite/Syntax/Errors/not_scheduled.tjp - test/TestSuite/Syntax/Errors/operand_attribute.tjp - test/TestSuite/Syntax/Errors/operand_unkn_flag.tjp - test/TestSuite/Syntax/Errors/operand_unkn_scen.tjp - test/TestSuite/Syntax/Errors/overtime_range.tjp - test/TestSuite/Syntax/Errors/purge_unknown_id.tjp - test/TestSuite/Syntax/Errors/report_end.tjp - test/TestSuite/Syntax/Errors/report_exists.tjp - test/TestSuite/Syntax/Errors/report_start.tjp - test/TestSuite/Syntax/Errors/resource_exists.tjp - test/TestSuite/Syntax/Errors/resource_id_expected.tjp - test/TestSuite/Syntax/Errors/resourceroot_leaf.tjp - test/TestSuite/Syntax/Errors/rev_acct_no_top.tjp - test/TestSuite/Syntax/Errors/runaway_token.tjp - test/TestSuite/Syntax/Errors/scenario_after_tracking.tjp - test/TestSuite/Syntax/Errors/scenario_exists.tjp - test/TestSuite/Syntax/Errors/shift_assignment_overlap.tjp - test/TestSuite/Syntax/Errors/shift_exists.tjp - test/TestSuite/Syntax/Errors/shift_id_expected.tjp - test/TestSuite/Syntax/Errors/sloppy_range.tjp - test/TestSuite/Syntax/Errors/sort_direction.tjp - test/TestSuite/Syntax/Errors/sort_unknown_scen.tjp - test/TestSuite/Syntax/Errors/sorting_bsi.tjp - test/TestSuite/Syntax/Errors/sorting_crit_exptd1.tjp - test/TestSuite/Syntax/Errors/sorting_crit_exptd2.tjp - test/TestSuite/Syntax/Errors/ss_no_tracking_scenario.tjp - test/TestSuite/Syntax/Errors/start_before_end1.tjp - test/TestSuite/Syntax/Errors/start_before_end2.tjp - test/TestSuite/Syntax/Errors/task_complete.tjp - test/TestSuite/Syntax/Errors/task_exists.tjp - test/TestSuite/Syntax/Errors/task_priority.tjp - test/TestSuite/Syntax/Errors/task_without_chargeset.tjp - test/TestSuite/Syntax/Errors/taskroot_leaf.tjp - test/TestSuite/Syntax/Errors/time_interval.tjp - test/TestSuite/Syntax/Errors/too_few_alert_levels.tjp - test/TestSuite/Syntax/Errors/too_large_timing_res.tjp - test/TestSuite/Syntax/Errors/too_many_bangs.tjp - test/TestSuite/Syntax/Errors/ts_bad_priority.tjp - test/TestSuite/Syntax/Errors/ts_default_details.tjp - test/TestSuite/Syntax/Errors/ts_default_summary.tjp - test/TestSuite/Syntax/Errors/ts_duplicate_task.tjp - test/TestSuite/Syntax/Errors/ts_end_too_early.tjp - test/TestSuite/Syntax/Errors/ts_headline_too_long.tjp - test/TestSuite/Syntax/Errors/ts_no_headline2.tjp - test/TestSuite/Syntax/Errors/ts_no_tracking_scenario.tjp - test/TestSuite/Syntax/Errors/ts_summary_too_long.tjp - test/TestSuite/Syntax/Errors/undecl_flag.tjp - test/TestSuite/Syntax/Errors/unknown_env_var.tjp - test/TestSuite/Syntax/Errors/unknown_projectid.tjp - test/TestSuite/Syntax/Errors/unknown_scenario_id.tjp - test/TestSuite/Syntax/Errors/unknown_scenario_idx.tjp - test/TestSuite/Syntax/Errors/unknown_task.tjp - test/TestSuite/Syntax/Errors/unsupported_token.tjp - test/TjpGen.rb - test/all.rb - test/test_AlgorithmDiff.rb - test/test_BatchProcessor.rb - test/test_CSV-Reports.rb - test/test_CSVFile.rb - test/test_CollisionDetector.rb - test/test_Export-Reports.rb - test/test_Journal.rb - test/test_Limits.rb - test/test_LogicalExpression.rb - test/test_MacroTable.rb - test/test_Project.rb - test/test_ProjectFileScanner.rb - test/test_PropertySet.rb - test/test_Query.rb - test/test_RealFormat.rb - test/test_ReportGenerator.rb - test/test_RichText.rb - test/test_Scheduler.rb - test/test_ShiftAssignments.rb - test/test_SimpleQueryExpander.rb - test/test_Syntax.rb - test/test_TextFormatter.rb - test/test_TjTime.rb - test/test_TjpExample.rb - test/test_URLParameter.rb - test/test_UTF8String.rb - test/test_WorkingHours.rb - test/test_deep_copy.rb - spec/Color_spec.rb - spec/ICalendar_spec.rb - spec/IntervalList_spec.rb - spec/ProjectBroker_spec.rb - spec/StatusSheets_spec.rb - spec/TableColumnSorter_spec.rb - spec/TernarySearchTree_spec.rb - spec/TimeSheets_spec.rb - spec/Tj3Daemon_spec.rb - spec/Tj3_spec.rb - spec/TraceReport_spec.rb - spec/support/DaemonControl.rb - spec/support/spec_helper.rb taskjuggler-3.5.0/lib/0000755000175000017500000000000012614413013014112 5ustar bernatbernattaskjuggler-3.5.0/lib/tj3ts_sender.rb0000644000175000017500000000077212614413013017054 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = tj3ts_sender.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/apps/Tj3TsSender' exit TaskJuggler::Tj3TsSender.new.main() taskjuggler-3.5.0/lib/tj3ss_sender.rb0000644000175000017500000000077212614413013017053 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = tj3ss_sender.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/apps/Tj3SsSender' exit TaskJuggler::Tj3SsSender.new.main() taskjuggler-3.5.0/lib/taskjuggler/0000755000175000017500000000000012614413013016434 5ustar bernatbernattaskjuggler-3.5.0/lib/taskjuggler/TernarySearchTree.rb0000644000175000017500000001363112614413013022357 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TernarySearchTree.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/UTF8String' class TaskJuggler # Classical ternary search tree implementation. It can store any list # objects who's elements are comparable. These are usually String or Array # objects. Common elements (by value and index) are only stored once which # makes it fairly efficient for large lists that have similar start # sequences. It also provides a fast find method. class TernarySearchTree # Create a new TernarySearchTree object. The optional _arg_ can be an # element to store in the new tree or a list of elements to store. def initialize(arg = nil) clear if arg.nil? return elsif arg.is_a?(Array) sortForBalancedTree(arg).each { |elem| insert(elem) } else insert(arg) if arg end end # Stores _str_ in the tree. _index_ is for internal use only. def insert(str, index = 0) if str.nil? || str.empty? raise ArgumentError, "Cannot insert nil or empty lists" end if index > (maxIdx = str.length - 1) || index < 0 raise ArgumentError, "index out of range [0..#{maxIdx}]" end @value = str[index] unless @value if str[index] < @value @smaller = TernarySearchTree.new unless @smaller @smaller.insert(str, index) elsif str[index] > @value @larger = TernarySearchTree.new unless @larger @larger.insert(str, index) else if index == maxIdx @last = true else @equal = TernarySearchTree.new unless @equal @equal.insert(str, index + 1) end end end alias << insert # Insert the elements of _list_ into the tree. def insertList(list) list.each { |val| insert(val) } end # if _str_ is stored in the tree it returns _str_. If _partialMatch_ is # true, it returns all items that start with _str_. _index_ is for # internal use only. If nothing is found it returns either nil or an empty # list. def find(str, partialMatch = false, index = 0) return nil if str.nil? || index > (maxIdx = str.length - 1) if str[index] < @value return @smaller.find(str, partialMatch, index) if @smaller elsif str[index] > @value return @larger.find(str, partialMatch, index) if @larger else if index == maxIdx # We've reached the end of the search pattern. if partialMatch # The strange looking ('' << val) is for Ruby 1.8 compatibility. return collect { |v| str[0..-2] + ('' << v) } else return str if @last end end return @equal.find(str, partialMatch, index + 1) if @equal end nil end alias [] find # Returns the number of elements in the tree. def length result = 0 result += @smaller.length if @smaller result += 1 if @last result += @equal.length if @equal result += @larger.length if @larger result end # Return the maximum depth of the tree. def maxDepth(depth = 0) depth += 1 depths = [] depths << @smaller.maxDepth(depth) if @smaller depths << @equal.maxDepth(depth) if @equal depths << @larger.maxDepth(depth) if @larger depths << depth if @last depths.max end # Invokes _block_ for each element and returns the results as an Array. def collect(str = nil, &block) result = [] result += @smaller.collect(str, &block) if @smaller # The strange looking ('' << val) is for Ruby 1.8 compatibility. newStr = str.nil? ? ('' << @value) : str + ('' << @value) result << yield(newStr) if @last result += @equal.collect(newStr, &block) if @equal result += @larger.collect(str, &block) if @larger result end # Return an Array with all the elements stored in the tree. def to_a collect{ |x| x} end # Balance the tree for more effective data retrieval. def balance! list = sortForBalancedTree(to_a) clear list.each { |x| insert(x) } end # Return a balanced version of the tree. def balanced TernarySearchTree.new(to_a) end def inspect(prefix = ' ', indent = 0) puts "#{' ' * indent}#{prefix} #{@value} #{@last ? '!' : ''}" @smaller.inspect('<', indent + 2) if @smaller @equal.inspect('=', indent + 2) if @equal @larger.inspect('>', indent + 2) if @larger end private # Reset the node to an empty tree. def clear @smaller = @equal = @larger = @value = nil @last = false end # Split the list into the first element and the remaining ones. def split(str) # The list may not be nil or empty. This would be a bug. raise ArgumentError if str.nil? || str.empty? # The second element of the result may be nil. [ str[0], str[1..-1] ] end # Reorder the list elements so that we get a fully balanced tree when # inserting the elements from front to back. def sortForBalancedTree(list) lists = [ list.sort ] result = [] while !lists.empty? newLists = [] lists.each do |l| # Split the list in half and add the center element to the result # list. pivot = l.length / 2 result << l[pivot] # Add the two remaining sub lists to the newLists Array. newLists << l[0..pivot - 1] if pivot > 0 newLists << l[pivot + 1..-1] if pivot < l.length - 1 end lists = newLists end result end end end taskjuggler-3.5.0/lib/taskjuggler/SheetSender.rb0000644000175000017500000002134612614413013021200 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = SheetSender.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'mail' require 'yaml' require 'taskjuggler/StdIoWrapper' require 'taskjuggler/SheetHandlerBase' require 'taskjuggler/reports/CSVFile' require 'taskjuggler/apps/Tj3Client' class TaskJuggler # A base class for sheet senders. class SheetSender < SheetHandlerBase attr_accessor :force, :intervalDuration include StdIoWrapper def initialize(appName, type) super(appName) @sheetType = type # The following settings must be provided by the deriving class. # This is a LogicalExpression string that controls what resources should # not be getting a report sheet template. @hideResource = nil # This file contains the signature (date or interval) that the # SheetReceiver will accept as a valid signature. @signatureFile = nil # The base directory of the sheet templates. @templateDir = nil # When true, existing templates will be regenerated and send out again. # Otherwise the existing template will not be send out again. @force = false @signatureFilter = nil # The subject of the template email. @mailSubject = nil # The into text of the template email. @introText = nil # The end date of the reported interval. @date = Time.new.strftime('%Y-%m-%d') # Determines the length of the reported interval. @intervalDuration = '1w' # We need this to determine if we already sent out a report. @timeStamp = Time.new end # Send out report templates to a list of project resources. The resources # are selected by the @hideResource filter expression and can be further # limited with a list of resource IDs passed by _resourceList_. def sendTemplates(resourceList) setWorkingDir createDirectories resources = genResourceList(resourceList) genTemplates(resources) sendReportTemplates(resources) end private def createDirectories unless File.directory?(@templateDir) warning("Creating directory #{@templateDir}") Dir.mkdir(@templateDir) end @templateDir += "/#{@date}" unless File.directory?(@templateDir) Dir.mkdir(@templateDir) end end def genResourceList(resourceList) list = [] info('Retrieving resource list...') # Create a TJP report definition for a CSV report that contains the id, # name, email, effort and free work for each resource that is not hidden # by @hideResource. reportDef = <<"EOF" resourcereport rl_21497214 '.' { formats csv columns id, name, email, effort, freework, efficiency hideresource #{@hideResource} sortresources id.up loadunit days period %{#{@date} - 1w} +1w } EOF report = generateReport('rl_21497214', reportDef) # Parse the CSV report into an Array of Arrays csv = CSVFile.new.parse(report) # Get rid of the column title line csv.delete_at(0) # Process the CSV report line by line csv.each do |id, name, email, effort, free, efficiency| if email.nil? || email.empty? error("Resource '#{id}' must have a valid email address") end # Ignore resources that are on leave for the whole period. if effort == 0.0 && free == 0.0 && efficiency != 0.0 info("Resource '#{id}' was on leave the whole period") next end list << [ id, name, email, effort, free ] end # Save the resource list to a file. We'll need it in the receiver again. begin fileName = @templateDir + '/resources.yml' File.open(fileName, 'w') do |file| YAML.dump(list, file) end rescue error("Saving of #{fileName} failed: #{$!}") end unless resourceList.empty? # When the user specified resource list is empty, we generate templates # for all users that don't match the @hideResource filter. Otherwise we # only generate templates for those in the list and that are not hidden # by the filter. list.delete_if { |item| !resourceList.include?(item[0]) } end error('genResourceList: list is empty') if list.empty? info("#{list.length} resources found") list end def genTemplates(resources) firstTemplateFile = nil resources.each do |resInfo| res = resInfo[0] info("Generating template for #{res}...") reportId = "sheet_template_#{res}" templateFile = "#{@templateDir}/#{res}_#{@date}" # We use the first template file to get the sheet interval. firstTemplateFile = templateFile + '.tji' unless firstTemplateFile # Don't re-generate already existing templates unless we are in force # mode. We probably have sent them out earlier with a manual trigger. if !@force && File.exist?(templateFile + '.tji') info("Skipping already existing #{templateFile}.tji.") next end reportDef = <<"EOT" #{@sheetType}sheetreport #{reportId} \"#{templateFile}\" { hideresource ~(plan.id = \"#{res}\") period %{#{@date} - #{@intervalDuration}} +#{@intervalDuration} sorttasks id.up } EOT generateReport(reportId, reportDef) end unless firstTemplateFile error("No #{@sheetType} sheet templates found in #{@templateDir}") end enableSignatureForReporting(firstTemplateFile) end def sendReportTemplates(resources) resources.each do |id, name, email| attachment = "#{@templateDir}/#{id}_#{@date}.tji" unless File.exist?(attachment) error("sendReportTemplates: " + "#{@sheetType} sheet #{attachment} for #{name} not found") end # Don't send out old templates again. @timeStamp has a higher # resolution. We add 1s to avoid truncation errors. if (File.mtime(attachment) + 1) < @timeStamp info("Old template #{attachment} found. Not sending it out.") next end message = " Hello #{name}!\n\n#{@introText}" + File.read(attachment) sendEmail(email, sprintf(@mailSubject, @date), message, attachment) end end def enableSignatureForReporting(templateFile) signature = nil # That's a pretty bad hack to make reasonably certain that the tj3 server # process has put the complete file into the file system. i = 0 begin if File.exist?(templateFile) File.open(templateFile, 'r') do |file| while (line = file.gets) if matches = @signatureFilter.match(line) signature = matches[1] end end end end i += 1 # If the file doesn't exist yet or the cannot yet be read, wait for # 300ms. We try this 100 times. sleep(0.3) unless signature end while signature.nil? && i < 100 unless signature error("enableSignatureForReporting: Cannot find signature in file " + "#{templateFile}") end acceptedSignatures = [] if File.exist?(@signatureFile) File.open(@signatureFile, 'r') do |file| acceptedSignatures = file.readlines end acceptedSignatures.map! { |s| s.chomp } acceptedSignatures.delete_if { |s| s.chomp.empty? } else info("#{@signatureFile} does not exist yet.") end unless acceptedSignatures.include?(signature) # Add the new signature info("Adding #{signature} to #{@signatureFile}") acceptedSignatures << signature # And write back the adapted file. File.open(@signatureFile, 'w') do |file| acceptedSignatures.each do |iv| file.write("#{iv}\n") end end else info("Signature #{signature} is already listed in #{@signatureFile}") end end def generateReport(id, reportDef) out = '' err = '' res = nil begin command = [ '--unsafe', '--silent', 'report', @projectId, id, '=', '.' ] # Send the report definition to the tj3client process via stdin. res = stdIoWrapper(reportDef) do Tj3Client.new.main(command) end out = res.stdOut err = res.stdErr if res.returnValue != 0 error("generateReport: #{err}") end rescue error("generateReport: Report generation failed: #{$!}") end out end end end taskjuggler-3.5.0/lib/taskjuggler/TjException.rb0000644000175000017500000000115512614413013021217 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TjException.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class TjException < RuntimeError attr_reader :error, :fatal def initialize(error = true, fatal = false) @error = error @fatal = fatal end end end taskjuggler-3.5.0/lib/taskjuggler/LogicalExpression.rb0000644000175000017500000000415312614413013022416 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = LogicalExpression.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/LogicalOperation' require 'taskjuggler/Attributes' require 'taskjuggler/LogicalFunction' class TaskJuggler # A LogicalExpression is an object that describes tree of LogicalOperation # objects and the context that it should be evaluated in. class LogicalExpression attr_reader :query, :sourceFileInfo # Create a new LogicalExpression object. _op_ must be a LogicalOperation. # _sourceFileInfo_ is the file position where expression started. It may be # nil if not available. def initialize(op, sourceFileInfo = nil) @operation = op @sourceFileInfo = sourceFileInfo @query = nil end # This function triggers the evaluation of the expression. _property_ is the # PropertyTreeNode that should be used for the evaluation. _scopeProperty_ # is the PropertyTreeNode that describes the scope. It may be nil. def eval(query) @query = query res = @operation.eval(self) return res if res.is_a?(TrueClass) || res.is_a?(FalseClass) || res.is_a?(String) # In TJP syntax 'non 0' means false. return res != 0 end # Dump the LogicalExpression as a String. If _query_ is provided, it will # show the actual values, otherwise just the variable names. def to_s(query = nil) if @sourceFileInfo.nil? "#{@operation.to_s(query)}" else "#{@sourceFileInfo} #{@operation.to_s(query)}" end end # This is an internal function. It's called by the LogicalOperation methods # in case something went wrong during an evaluation. def error(text) # :nodoc: raise TjException.new, "#{to_s}\nLogical expression error: #{text}" end end end taskjuggler-3.5.0/lib/taskjuggler/Charge.rb0000644000175000017500000000402512614413013020153 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Charge.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TjTime' class TaskJuggler # This class describes a one-time or per time charge that can be associated # with a Task. The charge can take effect either on starting the task, # finishing it, or per time interval. class Charge # Create a new Charge object. _amount_ is either the one-time charge or the # per-day-rate. _task_ is the Task that owns this charge. _scenarioIdx_ is # the index of the scenario this Charge belongs to. def initialize(amount, mode, task, scenarioIdx) @amount = amount unless [ :onStart, :onEnd, :perDiem ].include?(mode) raise "Unsupported mode #{mode}" end @mode = mode @task = task @scenarioIdx = scenarioIdx end # Compute the total charge for the TimeInterval described by _period_. def turnover(period) case @mode when :onStart return period.contains?(@task['start', @scenarioIdx]) ? @amount : 0.0 when :onEnd return period.contains?(@task['end', @scenarioIdx]) ? @amount : 0.0 else iv = period.intersection(TimeInterval.new(@task['start', @scenarioIdx], @task['end', @scenarioIdx])) if iv return (iv.duration / (60 * 60 * 24)) * @amount else return 0.0 end end end # Dump object in human readable form. def to_s case @mode when :onStart mode = 'on start' when :onEnd mode = 'on end' when :perDiem mode = 'per day' else mode = 'unknown' end "#{@amount} #{mode}" end end end taskjuggler-3.5.0/lib/taskjuggler/TaskJuggler.rb0000644000175000017500000003133612614413013021211 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TaskJuggler.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'drb' # Only needed during profiling. #require 'ruby-prof' require 'taskjuggler/Project' require 'taskjuggler/MessageHandler' require 'taskjuggler/Log' # The TaskJuggler class models the object that provides access to the # fundamental features of the TaskJuggler software. It can read project # files, schedule them and generate the reports. class TaskJuggler include MessageHandler attr_reader :project attr_accessor :maxCpuCores, :warnTsDeltas, :generateTraces # Create a new TaskJuggler object. _console_ is a boolean that determines # whether or not messsages can be written to $stderr. def initialize @project = nil @parser = nil @maxCpuCores = 1 @warnTsDeltas = false @generateTraces = false TjTime.setTimeZone('UTC') end # Read in the files passed as file names in _files_, parse them and # construct a Project object. In case of success true is returned. # Otherwise false. def parse(files, keepParser = false) # Reset the MessageHandler to clear all errors. MessageHandlerInstance.instance.clear Log.enter('parser', 'Parsing files ...') master = true @project = nil #RubyProf.start @parser = ProjectFileParser.new files.each do |file| begin @parser.open(file, master) rescue TjException => msg if msg.message && !msg.message.empty? critical('parse', msg.message) end Log.exit('parser') return false end if master # The first file is considered the master file. if (@project = @parser.parse(:project)) == false Log.exit('parser') return false end master = false else # All other files. @parser.setGlobalMacros if @parser.parse(:propertiesFile) == false Log.exit('parser') return false end end @project.inputFiles << file @parser.close end #profile = RubyProf.stop #printer = RubyProf::GraphHtmlPrinter.new(profile) #File.open("profile.html", "w") do |file| # printer.print(file) #end #printer = RubyProf::CallTreePrinter.new(profile) #File.open("profile.clt", "w") do |file| # printer.print(file) #end # For the report server mode we may need to keep the parser. Otherwise, # destroy it. @parser = nil unless keepParser Log.exit('parser') MessageHandlerInstance.instance.errors == 0 end # Parse a file and add the content to the existing project. _fileName_ is # the name of the file. _rule_ is the TextParser::Rule to start with. def parseFile(fileName, rule) begin @parser.open(fileName, false) @project.inputFiles << fileName rescue TjException => msg if msg.message && !msg.message.empty? critical('parse_file', msg.message) end return nil end @parser.setGlobalMacros return nil if (res = @parser.parse(rule)) == false @parser.close res end # Schedule all scenarios in the project. Return true if no error was # detected, false otherwise. def schedule Log.enter('scheduler', 'Scheduling project ...') #puts @project.to_s @project.warnTsDeltas = @warnTsDeltas begin res = @project.schedule rescue TjException => msg if msg.message && !msg.message.empty? critical('scheduling_error', msg.message) end return false end @project.enableTraceReports(@generateTraces) Log.exit('scheduler') res end # Generate all specified reports. The project must have be scheduled before # this method can be called. It returns true if no error occured, false # otherwise. def generateReports(outputDir = nil) @project.checkReports if outputDir # Make sure the output directory path always ends with a '/' unless empty. outputDir += '/' unless outputDir.empty? || outputDir[-1] == '/' @project.outputDir = outputDir end Log.enter('reports', 'Generating reports ...') begin #RubyProf.start @project.generateReports(@maxCpuCores) #profile = RubyProf.stop #printer = RubyProf::GraphHtmlPrinter.new(profile) #File.open("profile.html", "w") do |file| # printer.print(file) #end #printer = RubyProf::CallTreePrinter.new(profile) #File.open("profile.clt", "w") do |file| # printer.print(file) #end rescue TjException => msg if msg.message && !msg.message.empty? critical('generate_reports', msg.message) end return false end Log.exit('reports') true end # Generate the report with the ID _reportId_. If _regExpMode_ is true, # _reportId_ is interpreted as a Regular Expression and all reports with # matching IDs are generated. _formats_ is a list of formats (e. g. :html, # :csv, etc.). _dynamicAtributes_ is a String that may contain attributes to # supplement the report definition. The String must be in TJP format and may # be nil if no additional attributes are provided. def generateReport(reportId, regExpMode, formats = nil, dynamicAttributes = nil) begin Log.enter('generateReport', 'Generating report #{reportId} ...') @project.generateReport(reportId, regExpMode, formats, dynamicAttributes) rescue TjException => msg if msg.message && !msg.message.empty? critical('generate_report', msg.message) end Log.exit('generateReport') return false end Log.exit('generateReport') true end # List the details of the report with _reportId_ or if _regExpMode_ the # reports that match the regular expression in _reportId_. def listReports(reportId, regExpMode) begin Log.enter('listReports', 'Generating report list for #{reportId} ...') @project.listReports(reportId, regExpMode) rescue TjException => msg if msg.message && !msg.message.empty? critical('list_reports', msg.message) end Log.exit('listReports') return false end Log.exit('listReports') true end # Generate an export report definition for bookings up to the _freezeDate_. def freeze(freezeDate, taskBookings) begin # Check the master file is really a file and not stdin. unless (masterFile = @project.inputFiles.masterFile) error('cannot_freeze_stdin', "The project freeze feature can only be used when the " + "master file is a real file, not standard input.") end # Derive the file names for the header and bookings file from the base # name of the master file. masterFileBase = Dir.pwd + '/' + File.basename(masterFile, '.tjp') headerFile = masterFileBase + '-header.tji' bookingsFileBase = masterFileBase + '-bookings' bookingsFile = bookingsFileBase + '.tji' if !File.exists?(bookingsFile) || !File.exists?(headerFile) info('incl_freeze_files', "Please make sure you include #{headerFile} at " + "the end of the project header and " + "#{bookingsFile} at the end of #{masterFile}.") end # Generate the project header include file with the new 'now' date. begin File.open(headerFile, 'w') do |f| f.puts("now #{freezeDate}") end rescue error('write_header_incl', "Cannote write header include file " + "#{headerFile}") end # Generate an export report for the bookings. report = Report.new(@project, '_bookings_', bookingsFileBase, nil) report.typeSpec = :export report.set('formats', [ :tjp ]) report.inheritAttributes # We export only the tracking scenario. unless (trackingScenarioIdx = @project['trackingScenarioIdx']) error('no_tracking_scen', 'No trackingscenario defined') end report.set('scenarios', [ trackingScenarioIdx ]) # Only generate bookings up to the freeze date. report.set('end', freezeDate) # Show all tasks, sorted by seqno-up. report.set('hideTask', LogicalExpression.new(LogicalOperation.new(0))) report.set('sortTasks', [ [ 'seqno', true, -1 ] ]) # Show all resources, sorted by seqno-up. report.set('hideResource', LogicalExpression.new(LogicalOperation.new(0))) report.set('sortResources', [ [ 'seqno', true, -1 ] ]) # Only generate bookings, no other attributes or definitions. report.set('definitions', []) # We group the bookings by task or by resource depending on the user # request. if taskBookings report.set('taskAttributes', [ 'booking' ]) report.set('resourceAttributes', []) else report.set('taskAttributes', []) report.set('resourceAttributes', [ 'booking' ]) end rescue TjException return false end true end # Check the content of the file _fileName_ and interpret it as a time sheet. # If the sheet is syntaxtically correct and matches the loaded project, true # is returned. Otherwise false. def checkTimeSheet(fileName) begin Log.enter('checkTimeSheet', 'Parsing #{fileName} ...') # To use this feature, the user must have specified which scenario is # the tracking scenario. unless @project['trackingScenarioIdx'] raise TjException.new, 'No trackingscenario defined' end # Make sure we don't use data from old time sheets or Journal entries. @project.timeSheets.clear @project['journal'] = Journal.new return false unless (ts = parseFile(fileName, :timeSheetFile)) return false unless @project.checkTimeSheets queryAttrs = { 'project' => @project, 'property' => ts.resource, 'scopeProperty' => nil, 'scenarioIdx' => @project['trackingScenarioIdx'], 'start' => ts.interval.start, 'end' => ts.interval.end, 'journalMode' => :journal, 'journalAttributes' => %w( alert property propertyid headline flags timesheet summary details ), 'sortJournalEntries' => [ [ :seqno, 1 ] ], 'timeFormat' => '%Y-%m-%d', 'selfContained' => true } query = Query.new(queryAttrs) puts ts.resource.query_journal(query).richText.inputText rescue TjException => msg if msg.message && !msg.message.empty? critical('check_time_sheet', msg.message) end Log.exit('checkTimeSheet') return false end Log.exit('checkTimeSheet') true end # Check the content of the file _fileName_ and interpret it as a status # sheet. If the sheet is syntaxtically correct and matches the loaded # project, true is returned. Otherwise false. def checkStatusSheet(fileName) begin Log.enter('checkStatusSheet', 'Parsing #{fileName} ...') # To use this feature, the user must have specified which scenario is # the tracking scenario. unless @project['trackingScenarioIdx'] raise TjException.new, 'No trackingscenario defined' end return false unless (ss = parseFile(fileName, :statusSheetFile)) queryAttrs = { 'project' => @project, 'property' => ss[0], 'scopeProperty' => nil, 'scenarioIdx' => @project['trackingScenarioIdx'], 'timeFormat' => '%Y-%m-%d', 'start' => ss[1], 'end' => ss[2], 'timeFormat' => '%Y-%m-%d', 'selfContained' => true } query = Query.new(queryAttrs) puts ss[0].query_dashboard(query).richText.inputText rescue TjException => msg if msg.message && !msg.message.empty? critical('check_status_sheet', msg.message) end Log.exit('checkStatusSheet') return false end Log.exit('checkStatusSheet') true end # Return the ID of the project or nil if no project has been loaded yet. def projectId return nil if @project.nil? @project['projectid'] end # Return the name of the project or nil if no project has been loaded yet. def projectName return nil if @project.nil? @project['name'] end # Return the number of errors that had been reported during processing. def errors MessageHandlerInstance.instance.errors end end taskjuggler-3.5.0/lib/taskjuggler/AlertLevelDefinitions.rb0000644000175000017500000000571112614413013023220 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = AlertLevelDefinitions.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This class holds all information to describe a alert level as used by # TaskJuggler. A level has a unique ID, a unique name and a unique color. # Colors are stored as HTML compatible strings, e. g. "#RGB" where R, G, B # are a single or two-digit hex value. class AlertLevelDefinition < Struct.new(:id, :name, :color) def to_s "#{id} '#{name}' '#{color}'" end end # This class holds a list of AlertLevelDefinition objects. There are 3 # default levels. If they are changed, the :modified flag will indicate # this. class AlertLevelDefinitions def initialize # By default, we have a green, a yellow and a red level defined. @levels = [] add(AlertLevelDefinition.new('green', 'Green', '#008000')) add(AlertLevelDefinition.new('yellow', 'Yellow', '#BEA800')) add(AlertLevelDefinition.new('red', 'Red', '#C00000')) # Since those are the default values, we reset the modified flag. @modified = false end # Remove all AlertLevelDefinition objects from the list. def clear @levels = [] @modified = true end # Add a new AlertLevelDefinition. def add(level) raise ArgumentError unless level.is_a?(AlertLevelDefinition) if indexById(level.id) || indexByName(level.name) raise ArgumentError, "ID and name must be unique" end @levels << level @modified = true end # Return true if the alert levels are no longer the default ones, # otherwise return false. def modified? @modified end # Try to match _id_ to a defined alert level ID and return the # index of it. If no level is found, nil is returned. def indexById(id) @levels.index { |level| id == level.id } end # Try to match _name_ to a defined alert level ID and return the # index of it. If no level is found, nil is returned. def indexByName(name) @levels.index { |level| name == level.name } end # Try to match _color_ to a defined alert level ID and return the # index of it. If no level is found, nil is returned. def indexByColor(color) @levels.index { |level| color == level.color } end # Return the AlertLevelDefinition at _index_ or nil if _index_ is out of # range. def [](index) @levels[index] end # Pass map call to @levels. def map(&block) @levels.map(&block) end # Return the definition of the alert levels in TJP syntax. def to_tjp "alertlevels #{@levels.join(",\n")}" end end end taskjuggler-3.5.0/lib/taskjuggler/AttributeDefinition.rb0000644000175000017500000000437512614413013022746 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = AttributeDefinition.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # The AttributeDefinition describes the meta information of a PropertyTreeNode # attribute. It contains various information about the attribute. Based on # these bits of information, the PropertySet objects generate the attribute # lists for each PropertyTreeNode upon creation of the node. class AttributeDefinition attr_reader :id, :name, :objClass, :inheritedFromParent, :inheritedFromProject, :scenarioSpecific, :userDefined, :default # Create a new AttributeDefinition. _id_ is the ID of the attribute. It must # be unique within the PropertySet where it is used. _name_ is a more # descriptive text that will be used in report columns and the like. # _objClass_ is a reference to the class (not the object itself) of the # attribute. The possible classes all have names ending in Attribute. # _inheritedFromParent_ is a boolean flag that needs to be true if the # node can inherit the setting from the attribute of the parent node. # _inheritedFromProject_ is a boolen flag that needs to be true if the # node can inherit the setting from an attribute in the global scope. # _scenarioSpecific_ is a boolean flag that is set to true if the attribute # can have different values for each scenario. _default_ is the default # value that is set upon creation of the attribute. def initialize(id, name, objClass, inheritedFromParent, inheritedFromProject, scenarioSpecific, default, userDefined = false) @id = id @name = name @objClass = objClass @inheritedFromParent = inheritedFromParent @inheritedFromProject = inheritedFromProject @scenarioSpecific = scenarioSpecific @default = default @userDefined = userDefined # Prevent objects from being deep copied. freeze end end end taskjuggler-3.5.0/lib/taskjuggler/Limits.rb0000644000175000017500000002325512614413013020231 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Limits.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Scoreboard' class TaskJuggler # This class holds a set of limits. Each limit can be created individually and # must have unique name. The Limit objects are created when an upper or lower # limit is set. All upper or lower limits can be tested with a single function # call. class Limits # This class implements a mechanism that can be used to limit certain events # within a certain time period. It supports an upper and a lower limit. class Limit attr_accessor :resource attr_reader :name, :interval, :upper # To create a new Limit object, the Interval +interval+ and the # period duration (+period+ in seconds) must be specified. This creates # a counter for each period within the overall interval. +value+ is the # value of the limit. +upper+ specifies whether the limit is an upper or # lower limit. The limit can also be restricted to certain a Resource # specified by +resource+. def initialize(name, interval, period, value, upper, resource) @name = name @interval = interval @period = period @value = value @upper = upper @resource = resource # To avoid multiple resets of untouched scoreboards we keep this dirty # flag. It's set whenever a counter is increased. @dirty = true reset end # Returns a deep copy of the class instance. def copy Limit.new(@name, @interval, @period, @value, @upper, @resource) end # This function can be used to reset the counter for a specific period # specified by +index+ or to reset all counters. def reset(index = nil) return unless @dirty if index.nil? @scoreboard = Scoreboard.new(@interval.startDate, @interval.endDate, @period, 0) else return unless @interval.contains?(index) # The scoreboard may be just a subset of the @interval period. @scoreboard[idxToSbIdx(index)] = 0 end @dirty = false end # Increase the counter if the _index_ matches the @interval. The # relationship between @resource and _resource_ is described below. # @r \ _r_ nil y # nil inc inc # x - if x==y inc else - def inc(index, resource) if @interval.contains?(index) && (@resource.nil? || @resource == resource) # The condition is met, increment the counter for the interval. @dirty = true @scoreboard[idxToSbIdx(index)] += 1 end end # Decrease the counter if the _index_ matches the @interval. The # relationship between @resource and _resource_ is described below. # @r \ _r_ nil y # nil inc inc # x - if x==y inc else - def dec(index, resource) if @interval.contains?(index) && (@resource.nil? || @resource == resource) # The condition is met, decrement the counter for the interval. @dirty = true @scoreboard[idxToSbIdx(index)] -= 1 end end # Returns true if the counter for the time slot specified by +index+ or # all counters are within the limit. If +upper+ is true, only upper # limits are checked. If not, only lower limits are checked. The # dependency between _resource_ and @resource is described in the matrix # below: # @r \ _r_ nil y # nil test true # x true if x==y test else true def ok?(index, upper, resource) # if @upper does not match or the provided resource does not match, # we can ignore this limit. return true if @upper != upper || (@resource && @resource != resource) if index.nil? # No index given. We need to check all counters. @scoreboard.each do |i| return false if @upper ? i >= @value : i < @value end return true else # If the index is outside the interval we don't have to check # anything. Everything is ok. return true if !@interval.contains?(index) sbVal = @scoreboard[idxToSbIdx(index)] return @upper ? (sbVal < @value) : (sbVal >= @value) end end private # The project scoreboard and the Limit scoreboard differ from each # other. The Limit scoreboard may only be a subset of the project # scoreboard interval. And the Limit scoreboard has a larger slot # duration that depends on what kind of limit it is (daily, weekly, # etc.). Therefor, we have to use this method to translate project # scoreboard indexes to Limit scoreboard indexes. def idxToSbIdx(index) (index - @interval.start) * @interval.slotDuration / @period end end attr_reader :project, :limits # Create a new Limits object. If an argument is passed, it acts as a copy # contructor. def initialize(limits = nil) if limits.nil? # Normal initialization @limits = [] @project = nil else # Deep copy content from other instance. @limits = [] limits.limits.each do |name, limit| @limits << limit.copy end @project = limits.project end end # The objects need access to some project specific data like the project # period. def setProject(project) unless @limits.empty? raise "Cannot change project after limits have been set!" end @project = project end # Reset all counter for all limits. def reset @limits.each { |limit| limit.reset } end # Call this function to create or change a limit. The limit is uniquely # identified by the combination of +name+, +interval+ and +resource+. # +value+ is the new limit value (in time slots). In case the interval # is nil, the complete project time frame is used. def setLimit(name, value, interval = nil, resource = nil) iv = interval || ScoreboardInterval.new(@project['start'], @project['scheduleGranularity'], @project['start'], @project['end']) unless iv.is_a?(ScoreboardInterval) raise ArgumentError, "interval must be of class ScoreboardInterval" end # The known ivs are aligned to start at their respective start. iv.start = iv.startDate.midnight iv.end = iv.endDate.midnight case name when 'dailymax' period = 60 * 60 * 24 upper = true when 'dailymin' period = 60 * 60 * 24 upper = false when 'weeklymax' iv.start = iv.startDate.beginOfWeek( @project['weekStartsMonday']) iv.end = iv.endDate.beginOfWeek(@project['weekStartsMonday']) period = 60 * 60 * 24 * 7 upper = true when 'weeklymin' iv.start = iv.startDate.beginOfWeek( @project['weekStartsMonday']) iv.end = iv.endDate.beginOfWeek(@project['weekStartsMonday']) period = 60 * 60 * 24 * 7 upper = false when 'monthlymax' iv.start = iv.startDate.beginOfMonth iv.end = iv.endDate.beginOfMonth # We use 30 days ivs here. This will cause the iv to drift # away from calendar months. But it's better than using 30.4167 which # does not align with day boundaries. period = 60 * 60 * 24 * 30 upper = true when 'monthlymin' iv.start = iv.startDate.beginOfMonth iv.end = iv.endDate.beginOfMonth # We use 30 days ivs here. This will cause the iv to drift # away from calendar months. But it's better than using 30.4167 which # does not align with day boundaries. period = 60 * 60 * 24 * 30 upper = false when 'maximum' period = iv.duration upper = true when 'minimum' period = iv.duration upper = false else raise "Limit period undefined" end # If we have already a limit for the name + interval + resource # combination, we delete it first. @limits.delete_if do |l| l.name == name && l.interval.startDate == iv.startDate && l.interval.endDate == iv.endDate && l.resource == resource end @limits << Limit.new(name, iv, period, value, upper, resource) end # This function increases the counters for all limits for a specific # interval identified by _index_. def inc(index, resource = nil) @limits.each do |limit| limit.inc(index, resource) end end # This function decreases the counters for all limits for a specific # interval identified by _index_. def dec(index, resource = nil) @limits.each do |limit| limit.dec(index, resource) end end # Check all upper limits and return true if none is exceeded. If an # _index_ is specified only the counters for that specific period are # tested. Otherwise all periods are tested. If _resource_ is nil, only # non-resource-specific counters are checked, otherwise only the ones that # match the _resource_. def ok?(index = nil, upper = true, resource = nil) @limits.each do |limit| return false unless limit.ok?(index, upper, resource) end true end end end taskjuggler-3.5.0/lib/taskjuggler/XMLElement.rb0000644000175000017500000001413212614413013020734 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = XMLElement.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/UTF8String' class TaskJuggler # This class models an XML node that may contain other XML nodes. XML element # trees can be constructed with the class constructor and converted into XML. class XMLElement # Construct a new XML element and include it in an existing XMLElement tree. def initialize(name, attributes = {}, selfClosing = false, &block) if (name.nil? && attributes.length > 0) || (!name.nil? && !name.is_a?(String)) raise ArgumentError, "Name must be nil or a String " end @name = name attributes.each do |n, v| if n.nil? || v.nil? raise ArgumentError, "Attribute name (#{n}) or value (#{v}) may not be nil" end unless v.is_a?(String) raise ArgumentError, "Attribute value of #{n} must be a String" end end @attributes = attributes # This can be set to true if is legal for this element. @selfClosing = selfClosing @children = block ? yield(block) : [] # Allow blocks with single elements not to be Arrays. They will be # automatically converted into Arrays here. unless @children.is_a?(Array) @children = [ @children ] else @children.flatten! end # Convert all children that are text String objects into XMLText # objects. @children.collect! do |c| c.is_a?(String) ? XMLText.new(c) : c end # Make sure we have no nil objects in the list. @children.delete_if { |c| c.nil? } # Now all children must be XMLElement objects. @children.each do |c| unless c.is_a?(XMLElement) raise ArgumentError, "Element must be of type XMLElement, not #{c.class}: #{c.inspect}" end end end # Add a new child or a set of new childs to the element. def <<(arg) # If the argument is an array, we have to insert each element # individually. if arg.is_a?(XMLElement) @children << arg elsif arg.is_a?(String) @children << XMLText.new(arg) elsif arg.is_a?(Array) # Delete all nil entries arg.delete_if { |i| i.nil? } # Check that the rest are really all XMLElement objects. arg.each do |i| unless i.is_a?(XMLElement) raise ArgumentError, "Element must be of type XMLElement, not #{i.class}: #{i.inspect}" end end @children += arg elsif arg.nil? # Do nothing. Insertions of nil are simply ignored. else raise "Elements must be of type XMLElement not #{arg.class}" end self end # Add or change _attribute_ to _value_. def []=(attribute, value) raise ArgumentError, "Attribute value #{value} is not a String" unless value.is_a?(String) @attributes[attribute] = value end # Return the value of attribute _attribute_. def [](attribute) @attributes[attribute] end # Return the element and all sub elements as properly formatted XML. def to_s(indent = 0) out = '<' + @name @attributes.keys.sort.each do |attrName| out << " #{attrName}=\"#{escape(@attributes[attrName], true)}\"" end if @children.empty? && @selfClosing out << '/>' else out << '>' @children.each do |child| # We only insert newlines for multiple childs and after a tag has been # closed. if @children.size > 1 && !child.is_a?(XMLText) && out[-1] == ?> out << "\n" + indentation(indent + 1) end out << child.to_s(indent + 1) end out << "\n" + indentation(indent) if @children.size > 1 && out[-1] == ?> out << '' end end protected # Escape special characters in input String _str_. def escape(str, quotes = false) out = '' str.each_utf8_char do |c| case c when '&' out << '&' when '"' out << '\"' else out << c end end out end def indentation(indent) ' ' * indent end end # This is a specialized XMLElement to represent a simple text. class XMLText < XMLElement def initialize(text) super(nil, {}) raise 'Text may not be nil' unless text @text = text end def to_s(indent) out = '' @text.each_utf8_char do |c| case c when '<' out << '<' when '>' out << '>' when '&' out << '&' else out << c end end out end end # This is a convenience class that allows the creation of an XMLText nested # into an XMLElement. The _name_ and _attributes_ belong to the XMLElement, # the text to the XMLText. class XMLNamedText < XMLElement def initialize(text, name, attributes = {}) super(name, attributes) self << XMLText.new(text) end end # This is a specialized XMLElement to represent a comment. class XMLComment < XMLElement def initialize(text = '') super(nil, {}) @text = text end def to_s(indent) '\n#{' ' * indent}" end end # This is a specialized XMLElement to represent XML blobs. The content is not # interpreted and must be valid XML in the content it is added. class XMLBlob < XMLElement def initialize(blob = '') super(nil, {}) raise ArgumentError, "blob may not be nil" if blob.nil? @blob = blob end def to_s(indent) out = '' @blob.each_utf8_char do |c| out += (c == "\n" ? "\n" + ' ' * indent : c) end out end end end taskjuggler-3.5.0/lib/taskjuggler/MessageHandler.rb0000644000175000017500000002603612614413013021652 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = MessageHandler.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # if RUBY_VERSION < "1.9.0" require 'rubygems' end require 'singleton' require 'term/ansicolor' require 'taskjuggler/TextParser/SourceFileInfo' class TaskJuggler class TjRuntimeError < RuntimeError end # The Message object can store several classes of messages that the # application can send out. class Message include Term::ANSIColor attr_reader :type, :id, :message, :line attr_accessor :sourceFileInfo # Create a new Message object. The _type_ specifies what tpye of message # this is. The following types are supported: fatal, error, warning, info # and debug. _id_ is a String that must uniquely identify the source of # the Message. _message_ is a String with the actual message. # _sourceLineInfo_ is a SourceLineInfo object that can reference a # location in a specific file. _line_ is a String of that file. _data_ can # be any context sensitive data. _sceneario_ specifies the Scenario in # which the message originated. def initialize(type, id, message, sourceFileInfo, line, data, scenario) unless [ :fatal, :error, :warning, :info, :debug ]. include?(type) raise "Unknown message type: #{type}" end @type = type @id = id if message && !message.is_a?(String) raise "String object expected as message but got #{message.class}" end @message = message if sourceFileInfo && !sourceFileInfo.is_a?(TextParser::SourceFileInfo) raise "SourceFileInfo object expected but got #{sourceFileInfo.class}" end @sourceFileInfo = sourceFileInfo if line && !line.is_a?(String) raise "String object expected as line but got #{line.class}" end @line = line @data = data if scenario && !scenario.is_a?(Scenario) raise "Scenario object expected by got #{scenario.class}" end @scenario = scenario end # Convert the Message into a String that can be printed to the console. def to_s str = "" # The SourceFileInfo is printed as :line: if @sourceFileInfo str += "#{@sourceFileInfo.fileName}:#{sourceFileInfo.lineNo}: " end if @scenario tag = "#{@type.to_s.capitalize} in scenario #{@scenario.id}: " else tag = "#{@type.to_s.capitalize}: " end colors = { :fatal => red, :error => red, :warning => magenta, :info => blue, :debug => green } str += colors[@type] + tag + @message + reset str += "\n" + @line if @line str end # Convert the Message into a String that can be stored in a log file. def to_log str = "" # The SourceFileInfo is printed as :line: if @sourceFileInfo str += "#{@sourceFileInfo.fileName}:#{sourceFileInfo.lineNo}: " end str += "Scenario #{@scenario.id}: " if @scenario str += @message str end end # The MessageHandler can display and store application messages. Depending # on the type of the message, a TjExeption can be raised (:error), or the # program can be immedidately aborted (:fatal). Other types will just # continue the program flow. class MessageHandlerInstance include Singleton attr_reader :messages, :errors attr_accessor :logFile, :appName, :abortOnWarning LogLevels = { :none => 0, :fatal => 1, :error => 2, :critical => 2, :warning => 3, :info => 4, :debug => 5 } # Initialize the MessageHandler. def initialize reset end # Reset the MessageHandler to the initial state. All messages will be # purged and the error counter set to 0. def reset # This setting controls what type of messages will be written to the # console. @outputLevel = 4 # This setting controls what type of messages will be written to the log # file. @logLevel = 3 # The full file name of the log file. @logFile = nil # Toggle if scenario ids are included in the messages or not. @hideScenario = true # The name of the current application @appName = 'unknown' # Set to true if program should be exited on warnings. @abortOnWarning = false # A SourceFileInfo object that will be used to baseline the provided # source file infos of the messages. We use a Hash to keep per Thread # values. @baselineSFI = {} # Each tread can request to only throw a TjRuntimeError instead of # using exit(). This hash keeps a flag for each thread using the # object_id of the Thread object as key. @trapSetup = {} clear end def baselineSFI=(line) @baselineSFI[Thread.current.object_id] = line end def trapSetup=(enable) @trapSetup[Thread.current.object_id] = enable end # Clear the error log. def clear # A counter for messages of type error. @errors = 0 # A list of all generated messages. @messages = [] end # Set the console output level. def outputLevel=(level) @outputLevel = checkLevel(level) end # Set the log output level. def logLevel=(level) @logLevel = checkLevel(level) end def hideScenario=(yesNo) @hideScenario = yesNo end # Generate a fatal message that will abort the application. def fatal(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) addMessage(:fatal, id, message, sourceFileInfo, line, data, scenario) end # Generate an error message. def error(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) addMessage(:error, id, message, sourceFileInfo, line, data, scenario) end # Generate an critical message. def critical(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) addMessage(:critical, id, message, sourceFileInfo, line, data, scenario) end # Generate a warning. def warning(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) addMessage(:warning, id, message, sourceFileInfo, line, data, scenario) end # Generate an info message. def info(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) addMessage(:info, id, message, sourceFileInfo, line, data, scenario) end # Generate a debug message. def debug(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) addMessage(:debug, id, message, sourceFileInfo, line, data, scenario) end # Convert all messages into a single String. def to_s text = '' @messages.each { |msg| text += msg.to_s } text end private def checkLevel(level) if level.is_a?(Fixnum) if level < 0 || level > 5 raise ArgumentError, "Unsupported level #{level}" end else unless (level = LogLevels[level]) raise ArgumentError, "Unsupported level #{level}" end end level end def log(type, message) return unless @logFile timeStamp = Time.new.strftime("%Y-%m-%d %H:%M:%S") begin @logFile.untaint File.open(@logFile, 'a') do |f| f.write("#{timeStamp} #{type} #{@appName}[#{Process.pid}]: " + "#{message}\n") end rescue $stderr.puts "Cannot write to log file #{@logFile}: #{$!}" end end # Generate a message by specifying the _type_. def addMessage(type, id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) # If we have a SourceFileInfo and a baseline SFI, we correct the # sourceFileInfo accordingly. baselineSFI = @baselineSFI[Thread.current.object_id] if sourceFileInfo && baselineSFI sourceFileInfo = TextParser::SourceFileInfo.new( baselineSFI.fileName, sourceFileInfo.lineNo + baselineSFI.lineNo - 1, sourceFileInfo.columnNo) end # Treat criticals like errors but without generating another # exception. msg = Message.new(type == :critical ? :error : type, id, message, sourceFileInfo, line, data, @hideScenario ? nil : scenario) @messages << msg # Append the message to the log file if requested by the user. log(type, msg.to_log) if @logLevel >= LogLevels[type] # Print the message to $stderr if requested by the user. $stderr.puts msg.to_s if @outputLevel >= LogLevels[type] case type when :warning raise TjException.new, '' if @abortOnWarning when :critical # Increase the error counter. @errors += 1 when :error # Increase the error counter. @errors += 1 if @trapSetup[Thread.current.object_id] raise TjRuntimeError else exit 1 end when :fatal raise RuntimeError end end end module MessageHandler # Generate a fatal message that will abort the application. def fatal(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) MessageHandlerInstance.instance.fatal(id, message, sourceFileInfo, line, data, scenario) end # Generate an error message. def error(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) MessageHandlerInstance.instance.error(id, message, sourceFileInfo, line, data, scenario) end # Generate an critical message. def critical(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) MessageHandlerInstance.instance.critical(id, message, sourceFileInfo, line, data, scenario) end # Generate a warning. def warning(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) MessageHandlerInstance.instance.warning(id, message, sourceFileInfo, line, data, scenario) end # Generate an info message. def info(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) MessageHandlerInstance.instance.info(id, message, sourceFileInfo, line, data, scenario) end # Generate a debug message. def debug(id, message, sourceFileInfo = nil, line = nil, data = nil, scenario = nil) MessageHandlerInstance.instance.debug(id, message, sourceFileInfo, line, data, scenario) end end end taskjuggler-3.5.0/lib/taskjuggler/TaskDependency.rb0000644000175000017500000000226612614413013021670 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TaskDependency.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class TaskDependency attr_accessor :onEnd, :gapDuration, :gapLength attr_reader :taskId, :task def initialize(taskId, onEnd) @taskId = taskId @task = nil # Specifies whether the dependency is relative to the start or the # end of the dependent task. @onEnd = onEnd # The gap duration is stored in seconds of calendar time. @gapDuration = 0 # The gap length is stored in number of scheduling slots. @gapLength = 0 end def ==(dep) @taskId == dep.taskId && @task == dep.task && @onEnd == dep.onEnd && @gapDuration == dep.gapDuration && @gapLength == dep.gapLength end def resolve(project) @task = project.task(@taskId) end end end taskjuggler-3.5.0/lib/taskjuggler/Allocation.rb0000644000175000017500000001136212614413013021051 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Allocation.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Resource' require 'taskjuggler/Shift' class TaskJuggler # The Allocation is key object in TaskJuggler. It contains a description how # Resources are assigned to a Task. Each allocation holds a non-empty list of # candidate resources. For each time slot one candidate will be assigned if # any are available. A selectionMode controls the order in which the resources # are checked for availability. The first available one is selected. class Allocation attr_reader :selectionMode attr_accessor :atomic, :persistent, :mandatory, :shifts, :lockedResource # Create an Allocation object. The _candidates_ list must at least contain # one Resource reference. def initialize(candidates, selectionMode = 1, persistent = false, mandatory = false, atomic = false) @candidates = candidates # The selection mode determines how the candidate is selected from the # list of candidates. # 0 : 'order' : select by order of list # 1 : 'minallocated' : select candidate with lowest allocation # probability # 2 : 'minloaded' : select candidate with lowest allocated overall # load # 3 : 'maxloaded' : select candidate with highest allocated overall # load # 4 : 'random' : select a random candidate @selectionMode = selectionMode @atomic = atomic @persistent = persistent @mandatory = mandatory @shifts = nil @staticCandidates = nil end # Set the selection mode identified by name specified in _str_. For # efficiency reasons, we turn the name into a Fixnum value. def setSelectionMode(str) modes = %w( order minallocated minloaded maxloaded random ) @selectionMode = modes.index(str) raise "Unknown selection mode #{str}" if @selectionMode.nil? end # Append another candidate to the candidates list. def addCandidate(candidate) @candidates << candidate end # Returns true if we either have no shifts defined or the defined shifts # are active at date specified by global scoreboard index _sbIdx_. def onShift?(sbIdx) return @shifts.onShift?(sbIdx) if @shifts true end # Return the candidate list sorted according to the selectionMode. def candidates(scenarioIdx = nil) # In case we have selection criteria that results in a static list, we # can use the previously determined list. return @staticCandidates if @staticCandidates if scenarioIdx.nil? || @selectionMode == 0 # declaration order return @candidates end if @selectionMode == 4 # random # For a random sorting we put the candidates in a hash with a random # number as key. Then we sort the hash according to the random keys an # use the resuling sequence of the values. hash = {} @candidates.each { |c| hash[rand] = c } twinList = hash.sort { |x, y| x[0] <=> y[0] } list = [] twinList.each { |k, v| list << v } return list end list = @candidates.sort do |x, y| case @selectionMode when 1 # lowest alloc probability if @persistent # For persistent resources we use a more sophisticated heuristic # than just the criticalness of the resource. Instead, we # look at the already allocated slots of the resource. This will # reduce the probability to pick a persistent resource that was # already allocated for a higher priority or more critical task. if (cmp = x.bookedEffort(scenarioIdx) <=> y.bookedEffort(scenarioIdx)) == 0 x['criticalness', scenarioIdx] <=> y['criticalness', scenarioIdx] else cmp end else x['criticalness', scenarioIdx] <=> y['criticalness', scenarioIdx] end when 2 # lowest allocated load x.bookedEffort(scenarioIdx) <=> y.bookedEffort(scenarioIdx) when 3 # hightes allocated load y.bookedEffort(scenarioIdx) <=> x.bookedEffort(scenarioIdx) else raise "Unknown selection mode #{@selectionMode}" end end @staticCandidates = list if @selectionMode == 1 && !@persistent list end end end taskjuggler-3.5.0/lib/taskjuggler/ProjectFileScanner.rb0000644000175000017500000003250712614413013022510 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ProjectFileScanner.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TextParser/Scanner' class TaskJuggler # This class specializes the TextParser::Scanner class to detect the tokens # of the TJP syntax. class ProjectFileScanner < TextParser::Scanner def initialize(masterFile) tokenPatterns = [ # Any white spaces [ nil, /\s+/, :tjp, method('newPos') ], # Single line comments starting with # [ nil, /#.*\n?/, :tjp, method('newPos') ], # C++ style single line comments starting with // [ nil, /\/\/.*\n?/, :tjp, method('newPos') ], # C style single line comment /* .. */. [ nil, /\/\*.*\*\//, :tjp, method('newPos') ], # C style multi line comment: We need three patterns here. The first # one is for the start of the string. It switches the scanner mode to # the :cppComment mode. [ nil, /\/\*([^*]*[^\/]|.*)\n/, :tjp, method('startComment') ], # This is the string end pattern. It switches back to tjp mode. [ nil, /.*\*\//, :cppComment, method('endComment') ], # This pattern matches string lines that contain neither the start, # nor the end of the string. [ nil, /^.*\n/, :cppComment ], # Macro Call: This case is more complicated because we want to replace # macro calls inside of numbers, strings and identifiers. For this to # work, macro calls may have a prefix that looks like a number, a part # of a string or an identifier. This prefix is preserved and # re-injected into the scanner together with the expanded text. Macro # calls may span multiple lines. The ${ and the macro name must be in # the first line. Arguments that span multiple lines are not # supported. As above, we need rules for the start, the end and lines # with neither start nor end. Macro calls inside of strings need a # special start pattern that is active in the string modes. Both # patterns switch the scanner to macroCall mode. [ nil, /([-a-zA-Z_0-9>:.+]*|"(\\"|[^"])*?|'(\\'|[^'])*?)?\$\{\s*(\??[a-zA-Z_]\w*)(\s*"(\\"|[^"])*")*/, :tjp, method('startMacroCall') ], # This pattern is similar to the previous one, but is active inside of # multi-line strings. The corresponding rule for sizzors strings # can be found below. [ nil, /(\\"|[^"])*?\$\{\s*(\??[a-zA-Z_]\w*)(\s*"(\\"|[^"])*")*/, :dqString, method('startMacroCall') ], [ nil, /(\\'|[^'])*?\$\{\s*(\??[a-zA-Z_]\w*)(\s*"(\\"|[^"])*")*/, :sqString, method('startMacroCall') ], # This pattern matches the end of a macro call. It injects the prefix # and the expanded macro into the scanner again. The mode is restored # to the previous mode. [ nil, /(\s*"(\\"|[^"])*")*\s*\}/, :macroCall, method('endMacroCall') ], # This pattern collects macro call arguments in lines that contain # neither the start nor the end of the macro. [ nil, /.*\n/, :macroCall, method('midMacroCall') ], # Environment variable reference. This is similar to the macro call, # but the it can only extend within the starting line. [ nil, /([-a-zA-Z_0-9>:.+]*|"(\\"|[^"])*?|'(\\'|[^'])*?)?\$\([A-Z_][A-Z_0-9]*\)/, :tjp, method('environmentVariable') ], # An ID with a colon suffix: foo: [ :ID_WITH_COLON, /[a-zA-Z_]\w*:/, :tjp, method('chop') ], # An absolute ID: a.b.c [ :ABSOLUTE_ID, /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)+/ ], # A normal ID: bar [ :ID, /[a-zA-Z_]\w*/ ], # A date [ :DATE, /\d{4}-\d{1,2}-\d{1,2}(-\d{1,2}:\d{1,2}(:\d{1,2})?(-[-+]?\d{4})?)?/, :tjp, method('to_date') ], # A time of day [ :TIME, /\d{1,2}:\d{2}/, :tjp, method('to_time') ], # A floating point number (e. g. 3.143) [ :FLOAT, /\d*\.\d+/, :tjp, method('to_f') ], # An integer number [ :INTEGER, /\d+/, :tjp, method('to_i') ], # Multi line string enclosed with double quotes. The string may # contain double quotes prefixed by a backslash. The first rule # switches the scanner to dqString mode. [ 'nil', /"(\\"|[^"])*/, :tjp, method('startStringDQ') ], # Any line not containing the start or end. [ 'nil', /^(\\"|[^"])*\n/, :dqString, method('midStringDQ') ], # The end of the string. [ :STRING, /(\\"|[^"])*"/, :dqString, method('endStringDQ') ], # Multi line string enclosed with single quotes. [ 'nil', /'(\\'|[^'])*/, :tjp, method('startStringSQ') ], # Any line not containing the start or end. [ 'nil', /^(\\'|[^'])*\n/, :sqString, method('midStringSQ') ], # The end of the string. [ :STRING, /(\\'|[^'])*'/, :sqString, method('endStringSQ') ], # Scizzors marked string -8<- ... ->8-: The opening mark must be the # last thing in the line. The indentation of the first line after the # opening mark determines the indentation for all following lines. So, # we first switch the scanner to szrString1 mode. [ 'nil', /-8<-.*\n/, :tjp, method('startStringSZR') ], # Since the first line can be the last line (empty string case), we # need to detect the end in szrString1 and szrString mode. The # patterns switch the scanner back to tjp mode. [ :STRING, /\s*->8-/, :szrString1, method('endStringSZR') ], [ :STRING, /\s*->8-/, :szrString, method('endStringSZR') ], # This rule handles macros inside of sizzors strings. [ nil, /.*?\$\{\s*(\??[a-zA-Z_]\w*)(\s*"(\\"|[^"])*")*/, [ :szrString, :szrString1 ], method('startMacroCall') ], # Any line not containing the start or end. [ 'nil', /.*\n/, :szrString1, method('firstStringSZR') ], [ 'nil', /.*\n/, :szrString, method('midStringSZR') ], # Single line macro definition [ :MACRO, /\[.*\]\n/, :tjp, method('chop2nl') ], # Multi line macro definition: The pattern switches the scanner into # macroDef mode. [ nil, /\[.*\n/, :tjp, method('startMacroDef') ], # The end of the macro is marked by a ']' that is immediately followed # by a line break. It switches the scanner back to tjp mode. [ :MACRO, /.*\]\n/, :macroDef, method('endMacroDef') ], # Any line not containing the start or end. [ nil, /.*\n/, :macroDef, method('midMacroDef') ], # Some multi-char literals. [ :LITERAL, /<=?/ ], [ :LITERAL, />=?/ ], [ :LITERAL, /!=?/ ], # Everything else is returned as a single-char literal. [ :LITERAL, /./ ] ] super(masterFile, Log, tokenPatterns, :tjp) end private def to_i(type, match) [ type, match.to_i ] end def to_f(type, match) [ type, match.to_f ] end def to_time(type, match) h, m = match.split(':') h = h.to_i if h < 0 || h > 24 error('time_bad_hour', "Hour #{h} out of range (0 - 24)") end m = m.to_i if m < 0 || h > 59 error('time_bad_minute', "Minute #{m} out of range (0 - 59)") end if h == 24 && m != 0 error('time_bad_time', "Time #{match} cannot be larger then 24:00") end [ type, (h * 60 + m) * 60 ] end def to_date(type, match) begin [ type, TjTime.new(match) ] rescue TjException => msg error('time_error', msg.message) end end def newPos(type, match) @startOfToken = sourceFileInfo [ nil, '' ] end def chop(type, match) [ type, match[0..-2] ] end def chop2(type, match) # Remove first and last character. [ type, match[1..-2] ] end def chop2nl(type, match) # remove first and last \n (if it exists) and the last character. if match[-1] == ?\n [ type, match[1..-3] ] else [ type, match[1..-2] ] end end def startComment(type, match) self.mode = :cppComment [ nil, '' ] end def endComment(type, match) self.mode = :tjp [ nil, '' ] end def startStringDQ(type, match) self.mode = :dqString # Remove the opening " and remove the backslashes from escaped ". @string = match[1..-1].gsub(/\\"/, '"') [ nil, '' ] end def midStringDQ(type, match) # Remove the backslashes from escaped ". @string += match.gsub(/\\"/, '"') [ nil, '' ] end def endStringDQ(type, match) self.mode = :tjp # Remove the trailing " and remove the backslashes from escaped ". @string += match[0..-2].gsub(/\\"/, '"') [ :STRING, @string ] end def startStringSQ(type, match) self.mode = :sqString # Remove the opening ' and remove the backslashes from escaped '. @string = match[1..-1].gsub(/\\'/, "'") [ nil, '' ] end def midStringSQ(type, match) # Remove the backslashes from escaped '. @string += match.gsub(/\\'/, "'") [ nil, '' ] end def endStringSQ(type, match) self.mode = :tjp # Remove the trailing ' and remove the backslashes from escaped '. @string += match[0..-2].gsub(/\\'/, "'") [ :STRING, @string ] end def startStringSZR(type, match) # There should be a line break after the cut mark, but we allow some # spaces between the mark and the line break as well. if match.length != 5 && /-8<-\s*\n$/.match(match).nil? @lineDelta = 1 error('junk_after_cut', 'The cut mark -8<- must be immediately followed by a ' + 'line break.') end self.mode = :szrString1 @startOfToken = sourceFileInfo @string = '' [ nil, '' ] end def firstStringSZR(type, match) self.mode = :szrString # Split the leading indentation and the rest of the string. @indent, @string = */(\s*)(.*\n)/.match(match)[1, 2] [ nil, '' ] end def midStringSZR(type, match) # Ignore all the characters from the begining of match that are the same # in @indent. i = 0 while i < @indent.length && @indent[i] == match[i] i += 1 end @string += match[i..-1] [ nil, '' ] end def endStringSZR(type, match) self.mode = :tjp [ :STRING, @string ] end def environmentVariable(type, match) # Store any characters that precede the $( in prefix and remove it from # @macroCall. if (start = match.index('$(')) > 0 prefix = match[0..(start - 1)] envRef = match[start..-1] else prefix = '' envRef = match end # Remove '$(' and ')' varName = envRef[2..-2] if (value = ENV[varName]) @cf.injectText(prefix + value, envRef.length) else error('unknown_env_var', "Unknown environment variable '#{varName}'") end [ nil, '' ] end def startMacroDef(type, match) self.mode = :macroDef # Remove the opening '[' @macroDef = match[1..-1] [ nil, '' ] end def midMacroDef(type, match) @macroDef += match [ nil, '' ] end def endMacroDef(type, match) self.mode = :tjp # Remove "](\n|$)" if match[-1] == ?\n @macroDef += match[0..-3] else @macroDef += match[0..-2] end [ :MACRO, @macroDef ] end def startMacroCall(type, match) @macroCallPreviousMode = @scannerMode self.mode = :macroCall @macroCall = match [ nil, '' ] end def midMacroCall(type, match) @macroCall += match [ nil, '' ] end def endMacroCall(type, match) self.mode = @macroCallPreviousMode @macroCall += match # Store any characters that precede the ${ in prefix and remove it from # @macroCall. if (macroStart = @macroCall.index('${')) > 0 prefix = @macroCall[0..(macroStart - 1)] @macroCall = @macroCall[macroStart..-1] else prefix = '' end macroCallLength = @macroCall.length # Remove '${' and '}' and white spaces at begin and end argsStr = @macroCall[2..-2].sub(/^[ \t\n]*(.*?)[ \t\n]*$/, '\1') # Extract the macro name. if argsStr.index(' ').nil? expandMacro(prefix, [ argsStr ], macroCallLength) else macroName = argsStr[0, argsStr.index(' ')] # Remove the name part from argsStr argsStr = argsStr[macroName.length..-1] # Array to hold the arguments args = [] # We use another StringScanner to clean the double quotes. scanner = StringScanner.new(argsStr) while (scanner.scan(/\s*"/)) args << scanner.scan(/(\\"|[^"])*/).gsub(/\\"/, '"') scanner.scan(/"\s*/) end unless scanner.eos? error('junk_at_eom', "Junk found at end of macro: #{scanner.post_match}") end # Expand the macro and inject it into the scanner. expandMacro(prefix, [ macroName ] + args, macroCallLength) end [ nil, '' ] end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser/0000755000175000017500000000000012614413013020535 5ustar bernatbernattaskjuggler-3.5.0/lib/taskjuggler/TextParser/SourceFileInfo.rb0000644000175000017500000000207712614413013023744 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = SourceFileInfo.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class TextParser # Simple class that holds the info about a source file reference. class SourceFileInfo attr_reader :fileName, :lineNo, :columnNo # Create a new SourceFileInfo object. _file_ is the name of the file. # _line_ is the line in this file, _col_ is the column number in the # line. def initialize(file, line, col) @fileName = file @lineNo = line @columnNo = col end # Return the info in the common "filename:line:" format. def to_s # The column is not reported for now. "#{@fileName}:#{@lineNo}:" end end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser/Rule.rb0000644000175000017500000001475312614413013022003 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Rule.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TextParser/State' class TaskJuggler::TextParser # The TextParserRule holds the basic elment of the syntax description. Each # rule has a name and a set of patterns. The parser uses these rules to parse # the input files. The first token of a pattern must resolve to a terminal # token. The resolution can run transitively over a set of rules. The first # tokens of each pattern of a rule must resolve to a terminal symbol and all # terminals must be unique in the scope that they appear in. The parser uses # this first token to select the next pattern it uses for the syntactical # analysis. A rule can be marked as repeatable and/or optional. In this case # the syntax element described by the rule may occur 0 or multiple times in # the parsed file. class Rule attr_reader :name, :patterns, :optional, :repeatable, :keyword, :doc # Create a new syntax rule called +name+. def initialize(name) @name = name @patterns = [] @repeatable = false @optional = false @keyword = nil flushCache end def flushCache # A rule is considered to describe optional tokens in case the @optional # flag is set or all of the patterns reference optional rules again. # This variable caches the transitively determined optional value. @transitiveOptional = nil end # Add a new +pattern+ to the Rule. It should be of type # TextParser::Pattern. def addPattern(pattern) @patterns << pattern end def include?(token) @patterns.each { |p| return true if p[0][1] == token } false end # Mark the rule as an optional element of the syntax. def setOptional @optional = true end # Return true if the rule describes optional elements. The evaluation # recursively descends into the pattern if necessary and stores the result # to be reused for later calls. def optional?(rules) # If we have a cached result, use this. return @transitiveOptional if @transitiveOptional # If the rule is marked optional, then it is optional. if @optional return @transitiveOptional = true end # If all patterns describe optional content, then this rule is optional # as well. @transitiveOptional = true @patterns.each do |pat| return @transitiveOptional = false unless pat.optional?(rules) end end def generateStates(rules) # First, add an entry State for this rule. Entry states are never # reached by normal state transitions. They are only used as (re-)start # states. states = [ State.new(self) ] @patterns.each do |pattern| states += pattern.generateStates(self, rules) end states end # Return a Hash of all state transitions caused by the 1st token of each # pattern of this rule. def addTransitionsToState(states, rules, stateStack, sourceState, loopBack) @patterns.each do |pattern| pattern.addTransitionsToState(states, rules, stateStack.dup, sourceState, self, 0, loopBack) end end # Mark the syntax element described by this Rule as a repeatable element # that can occur once or more times in sequence. def setRepeatable @repeatable = true end # Add a description for the syntax elements of this Rule. +doc+ is a # RichText and +keyword+ is a unique name of this Rule. To avoid # ambiguouties, an optional scope can be appended, separated by a dot # (E.g. name.scope). def setDoc(keyword, doc) raise 'No pattern defined yet' if @patterns.empty? @patterns[-1].setDoc(keyword, doc) end # Add a description for a pattern element of the last added pattern. def setArg(idx, doc) raise 'No pattern defined yet' if @patterns.empty? @patterns[-1].setArg(idx, doc) end # Specify the index +idx+ of the last token to be used for the syntax # documentation. All subsequent tokens will be ignored. def setLastSyntaxToken(idx) raise 'No pattern defined yet' if @patterns.empty? raise 'Token index too large' if idx >= @patterns[-1].tokens.length @patterns[-1].setLastSyntaxToken(idx) end # Specify the support level of the current pattern. def setSupportLevel(level) raise 'No pattern defined yet' if @patterns.empty? @patterns[-1].setSupportLevel(level) end # Add a reference to another rule for documentation purposes. def setSeeAlso(also) raise 'No pattern defined yet' if @patterns.empty? @patterns[-1].setSeeAlso(also) end # Add a reference to a code example. +file+ is the name of the file. +tag+ # is a tag within the file that specifies a part of this file. def setExample(file, tag) @patterns[-1].setExample(file, tag) end # Return a reference the pattern of this Rule. def pattern(idx) @patterns[idx] end def to_syntax(stack, docs, rules, skip) str = '' str << '[' if @optional || @repeatable str << '(' if @patterns.length > 1 first = true pStr = '' @patterns.each do |pat| if first first = false else pStr << ' | ' end pStr << pat.to_syntax_r(stack, docs, rules, skip) end return '' if pStr == '' str << pStr str << '...' if @repeatable str << ')' if @patterns.length > 1 str << ']' if @optional || @repeatable str end def dump puts "Rule: #{name} #{@optional ? "[optional]" : ""} " + "#{@repeatable ? "[repeatable]" : ""}" @patterns.length.times do |i| puts " Pattern: \"#{@patterns[i]}\"" unless @transitions[i] puts "No transitions for this pattern!" next end @transitions[i].each do |key, rule| if key[0] == ?_ token = "\"" + key.slice(1, key.length - 1) + "\"" else token = key.slice(1, key.length - 1) end puts " #{token} -> #{rule.name}" end end puts end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser/Pattern.rb0000644000175000017500000003731512614413013022510 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Pattern.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TextParser/TokenDoc' require 'taskjuggler/TextParser/State' class TaskJuggler::TextParser # This class models the most crutial elements of a syntax description - the # pattern. A TextParserPattern primarily consists of a set of tokens. Tokens # are Strings where the first character determines the type of the token. # There are 4 known types. # # Terminal token: In the syntax declaration the terminal token is prefixed # by an underscore. Terminal tokens are terminal symbols of the syntax tree. # They just represent themselves. # # Variable token: The variable token describes values of a certain class such # as strings or numbers. In the syntax declaration the token is prefixed by # a dollar sign and the text of the token specifies the variable type. See # ProjectFileParser for a complete list of variable types. # # Reference token: The reference token specifies a reference to another parser # rule. In the syntax declaration the token is prefixed by a bang and the # text matches the name of the rule. See TextParserRule for details. # # End token: The . token marks the expected end of the input stream. # # In addition to the pure syntax tree information the pattern also holds # documentary information about the pattern. class Pattern attr_reader :keyword, :doc, :supportLevel, :seeAlso, :exampleFile, :exampleTag, :tokens, :function # Create a new Pattern object. _tokens_ must be an Array of String objects # that describe the Pattern. _function_ can be a reference to a method # that should be called when the Pattern was recognized by the parser. def initialize(tokens, function = nil) # A unique name for the pattern that is used in the documentation. @keyword = nil # Initialize pattern doc as empty. @doc = nil # A list of TokenDoc elements that describe the meaning of variable # tokens. The order of the tokens and entries in the Array must correlate. @args = [] # The syntax can evolve over time. The support level specifies which # level of support this pattern hast. Possible values are :experimental, # :beta, :supported, :deprecated, :removed @supportLevel = :supported # A list of references to other patterns that are related to this pattern. @seeAlso = [] # A reference to a file under test/TestSuite/Syntax/Correct and a tag # within that file. This identifies example TJP code to be included with # the reference manual. @exampleFile = nil @exampleTag = nil @tokens = [] tokens.each do |token| unless '!$_.'.include?(token[0]) raise "Fatal Error: All pattern tokens must start with a type " + "identifier [!$_.]: #{tokens.join(', ')}" end # For the syntax specification using a prefix character is more # convenient. But for further processing, we need to split the string # into two symbols. The prefix determines the token type, the rest is # the token name. There are 4 types of tokens: # :reference : a reference to another rule # :variable : a terminal symbol # :literal : a user defined string # :eof : marks the end of an input stream type = [ :reference, :variable, :literal, :eof ]['!$_.'.index(token[0])] # For literals we use a String to store the token content. For others, # a symbol is better suited. name = type == :literal ? token[1..-1] : (type == :eof ? '' : token[1..-1].intern) # We favor an Array to store the 2 elements over a Hash for # performance reasons. @tokens << [ type, name ] # Initialize pattern argument descriptions as empty. @args << nil end @function = function # In some cases we don't want to show all tokens in the syntax # documentation. This value specifies the index of the last shown token. @lastSyntaxToken = @tokens.length - 1 @transitions = [] end # Generate the state machine states for the pattern. _rule_ is the Rule # that the pattern belongs to. A list of generated State objects will be # returned. def generateStates(rule, rules) # The last token of a pattern must always trigger a reduce operation. # But the the last tokens of a pattern describe fully optional syntax, # the last non-optional token and all following optional tokens must # trigger a reduce operation. Here we find the index of the first token # that must trigger a reduce operation. firstReduceableToken = @tokens.length - 1 (@tokens.length - 2).downto(0).each do |i| if optionalToken(i + 1, rules) # If token i + 1 is optional, assume token i is the first one to # trigger a reduce. firstReduceableToken = i else # token i + 1 is not optional, we found the first token to trigger # the reduce. break end end states = [] @tokens.length.times do |i| states << (state = State.new(rule, self, i)) # Mark all states that are allowed to trigger a reduce operation. state.noReduce = false if i >= firstReduceableToken end states end # Add the transitions to the State objects of this pattern. _states_ is a # Hash with all State objects. _rules_ is a Hash with the Rule objects of # the syntax. _stateStack_ is an Array of State objects that have been # traversed before reaching this pattern. _sourceState_ is the State that # the transition originates from. _destRule_, this pattern and _destIndex_ # describe the State the transition is leading to. _loopBack_ is boolean # flag, set to true when the transition describes a loop back to the start # of the Rule. def addTransitionsToState(states, rules, stateStack, sourceState, destRule, destIndex, loopBack) # If we hit a token in the pattern that is optional, we need to consider # the next token of the pattern as well. loop do if destIndex >= @tokens.length if sourceState.rule == destRule if destRule.repeatable # The transition leads us back to the start of the Rule. This # will generate transitions to the first token of all patterns # of this Rule. destRule.addTransitionsToState(states, rules, [], sourceState, true) end end # We've reached the end of the pattern. No more transitions to # consider. return end # The token descriptor tells us where the transition(s) need to go to. tokenType, tokenName = @tokens[destIndex] case tokenType when :reference # The descriptor references another rule. unless (refRule = rules[tokenName]) raise "Unknown rule #{tokenName} referenced in rule #{refRule.name}" end # If we reference another rule from a pattern, we need to come back # to the pattern once we are done with the referenced rule. To be # able to come back, we collect a list of all the States that we # have passed during a reference resolution. This list forms a stack # that is popped during recude operations of the parser FSM. skippedState = states[[ destRule, self, destIndex ]] # Rules may reference themselves directly or indirectly. To avoid # endless recursions of this algorithm, we stop once we have # detected a recursion. We have already all necessary transitions # collected. The recursion will be unrolled in the parser FSM. unless stateStack.include?(skippedState) # Push the skipped state on the stateStack before recursing. stateStack.push(skippedState) refRule.addTransitionsToState(states, rules, stateStack, sourceState, loopBack) # Once we're done, remove the State from the stateStack again. stateStack.pop end # If the referenced rule is not optional, we have no further # transitions for this pattern at this destIndex. break unless refRule.optional?(rules) else unless (destState = states[[ destRule, self, destIndex ]]) raise "Destination state not found" end # We've found a transition to a terminal token. Add the transition # to the source State. sourceState.addTransition(@tokens[destIndex], destState, stateStack, loopBack) # Fixed tokens are never optional. There are no more transitions for # this pattern at this index. break end destIndex += 1 end end # Set the keyword and documentation text for the pattern. def setDoc(keyword, doc) @keyword = keyword @doc = doc end # Set the documentation text and for the idx-th variable. def setArg(idx, doc) @args[idx] = doc end # Restrict the syntax documentation to the first +idx+ tokens. def setLastSyntaxToken(idx) @lastSyntaxToken = idx end # Specify the support level of this pattern. def setSupportLevel(level) unless [ :experimental, :beta, :supported, :deprecated, :removed ].include?(level) raise "Fatal Error: Unknown support level #{level}" end @supportLevel = level end # Set the references to related patterns. def setSeeAlso(also) @seeAlso = also end # Set the file and tag for the TJP code example. def setExample(file, tag) @exampleFile = file @exampleTag = tag end # Conveniance function to access individual tokens by index. def [](i) @tokens[i] end # Iterator for tokens. def each @tokens.each { |type, name| yield(type, name) } end # Returns true of the pattern is empty. def empty? @tokens.empty? end # Returns the number of tokens in the pattern. def length @tokens.length end # Return true if all tokens of the pattern are optional. If a token # references a rule, this rule is followed for the check. def optional?(rules) @tokens.each do |type, name| if type == :literal || type == :variable return false elsif type == :reference if !rules[name].optional?(rules) return false end end end true end # Returns true if the i-th token is a terminal symbol. def terminalSymbol?(i) @tokens[i][0] == :variable || @tokens[i][0] == :literal end # Find recursively the first terminal token of this pattern. If an index is # specified start the search at this n-th pattern token instead of the # first. The return value is an Array of [ token, pattern ] tuple. def terminalTokens(rules, index = 0) type, name = @tokens[index] # Terminal token start with an underscore or dollar character. if type == :literal return [ [ name, self ] ] elsif type == :variable return [] elsif type == :reference # We have to continue the search at this rule. rule = rules[name] # The rule may only have a single pattern. If not, then this pattern # has no terminal token. tts = [] rule.patterns.each { |p| tts += p.terminalTokens(rules, 0) } return tts else raise "Unexpected token #{type} #{name}" end end # Returns a string that expresses the elements of the pattern in an EBNF # like fashion. The resolution of the pattern is done recursively. This is # just the wrapper function that sets up the stack. def to_syntax(argDocs, rules, skip = 0) to_syntax_r({}, argDocs, rules, skip) end # Generate a syntax description for this pattern. def to_syntax_r(stack, argDocs, rules, skip) # If we find ourself on the stack we hit a recursive pattern. This is used # in repetitions. if stack[self] return '[, ... ]' end # "Push" us on the stack. stack[self] = true str = '' first = true # Analyze the tokens of the pattern skipping the first 'skip' tokens. skip.upto(@lastSyntaxToken) do |i| type, name = @tokens[i] # If the first token is a _{ the pattern describes optional attributes. # They are represented by a standard idiom. if first first = false return '{ }' if name == '{' else # Separate the syntax elemens by a whitespace. str << ' ' end if @args[i] # The argument is documented in the syntax definition. We copy the # entry as we need to modify it. argDoc = @args[i].dup # A documented argument without a name is a terminal token. We use the # terminal symbol as name. if @args[i].name.nil? str << "#{name}" argDoc.name = name else str << "<#{@args[i].name}>" end addArgDoc(argDocs, argDoc) # Documented arguments don't have the type set yet. Use the token # value for that. if type == :variable argDoc.typeSpec = "<#{name}>" end else # Undocumented tokens are recursively expanded. case type when :literal # Literals are shown as such. str << name.to_s when :variable # Variables are enclosed by angle brackets. str << "<#{name}>" when :reference if rules[name].patterns.length == 1 && !rules[name].patterns[0].doc.nil? addArgDoc(argDocs, TokenDoc.new(rules[name].patterns[0].keyword, rules[name].patterns[0])) str << '<' + rules[name].patterns[0].keyword + '>' else # References are followed recursively. str << rules[name].to_syntax(stack, argDocs, rules, 0) end end end end # Remove us from the "stack" again. stack.delete(self) str end # Generate a text form of the pattern. This is similar to the syntax in # the original syntax description. def to_s str = "" @tokens.each do |type, name| case type when :reference str += "!#{name} " when :variable str += "$#{name } " when :literal str += "#{name} " when :eof str += ". " else raise "Unknown type #{type}" end end str end private def addArgDoc(argDocs, argDoc) raise 'Error' if argDoc.name.nil? argDocs.each do |ad| return if ad.name == argDoc.name end argDocs << argDoc end # Check if token with _index_ describes fully optional syntax elements. def optionalToken(index, rules) # If the token is a reference to another rule, we need to check if it's # optional. if @tokens[index][0] == :reference return rules[@tokens[index][1]].optional?(rules) end # All other token types are never optional. false end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser/State.rb0000644000175000017500000001402212614413013022141 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = State.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler::TextParser # A StateTransition maps a token type to the next state to be # processed. A token descriptor is either a Symbol that maps to a RegExp in # the TextScanner or an expected String. The transition may also have a # list of State objects that are being activated by the transition. class StateTransition attr_reader :tokenType, :state, :stateStack, :loopBack # Create a new StateTransition object. _descriptor_ is a [ token type, # token value ] touple. _state_ is the State objects this transition # originates at. _stateStack_ is the list of State objects that have been # activated by this transition. _loopBack_ is a boolean flag that # specifies whether the transition describes a loop back to the start of # the Rule or not. def initialize(descriptor, state, stateStack, loopBack) if !descriptor.respond_to?(:length) || descriptor.length != 2 raise "Bad parameter descriptor: #{descriptor} " + "of type #{descriptor.class}" end @tokenType = descriptor[0] == :eof ? :eof : descriptor[1] if !state.is_a?(State) raise "Bad parameter state: #{state} of type #{state.class}" end @state = state if !stateStack.is_a?(Array) raise "Bad parameter stateStack: #{stateStack} " + "of type #{stateStack.class}" end @stateStack = stateStack.dup @loopBack = loopBack end # Generate a human readable form of the TransitionState date. It's only # used for debugging. def to_s str = "#{@state.rule.name}, " + "#{@state.rule.patterns.index(@state.pattern)}, #{@state.index} " unless @stateStack.empty? str += "(" @stateStack.each do |s| str += "#{s.rule.name} " end str += ")" end str += '(loop)' if @loopBack str end end # This State objects describes a state of the TextParser FSM. A State # captures the position in the syntax description that the parser is # currently at. A position is defined by the Rule, the Pattern and the index # of the current token of that Pattern. An index of 0 means, we've just read # the 1st token of the pattern. States which have no Pattern describe the # start of rule. The parser has not yet identified the first token, so it # doesn't know the Pattern yet. # # The actual data of a State is the list of possible StateTransitions to # other states and a boolean flag that specifies if Reduce operations are # valid for this State or not. The transitions are hashed by the token that # would trigger this transition. class State attr_reader :rule, :pattern, :index, :transitions attr_accessor :noReduce def initialize(rule, pattern = nil, index = 0) @rule = rule @pattern = pattern @index = index # Starting states are always reduceable. Other states may or may not be # reduceable. For now, we assume they are not. @noReduce = !pattern.nil? @transitions = {} end # Complete the StateTransition list. We can only call this function after # all State objects for the syntax have been created. So we can't make # this part of the constructor. def addTransitions(states, rules) if @pattern # This is an normal state node. @pattern.addTransitionsToState(states, rules, [], self, @rule, @index + 1, false) else # This is a start node. @rule.addTransitionsToState(states, rules, [], self, false) end end # This method adds the actual StateTransition to this State. def addTransition(token, nextState, stateStack, loopBack) tr = StateTransition.new(token, nextState, stateStack, loopBack) if @transitions.include?(tr.tokenType) raise "Ambiguous transition for #{tr.tokenType} in \n#{self}\n" + "The following transition both match:\n" + " #{tr}\n #{@transitions[tr.tokenType]}" end @transitions[tr.tokenType] = tr end # Find the transition that matches _token_. def transition(token) if token[0] == :ID # The scanner cannot differentiate between IDs and literals that look # like IDs. So we look for literals first and then for IDs. @transitions[token[1]] || @transitions[:ID] elsif token[0] == :LITERAL @transitions[token[1]] else @transitions[token[0]] end end # Return a comma separated list of token strings that would trigger # transitions for this State. def expectedTokens tokens = [] @transitions.each_key do |t| tokens << "#{t.is_a?(String) ? "'#{t}'" : ":#{t}"}" end tokens end # Convert the State data into a human readable form. Used for debugging # only. def to_s(short = false) if short if @pattern str = "#{rule.name} " + "#{rule.patterns.index(@pattern)} #{@index}" else str = "#{rule.name} (Starting Node)" end else if @pattern str = "=== State: #{rule.name} " + "#{rule.patterns.index(@pattern)} #{@index}" + " #{@noReduce ? '' : '(R)'}" + " #{'=' * 40}\nPattern: #{@pattern}\n" else str = "=== State: #{rule.name} (Starting Node) #{'=' * 30}\n" end @transitions.each do |type, target| targetStr = target ? target.to_s : "" str += " #{type.is_a?(String) ? "'#{type}'" : ":#{type}"}" + " => #{targetStr}\n" end str += "#{'=' * 76}\n" end str end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser/Scanner.rb0000644000175000017500000004553612614413013022470 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Scanner.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'stringio' require 'strscan' require 'taskjuggler/UTF8String' require 'taskjuggler/TextParser/SourceFileInfo' require 'taskjuggler/TextParser/MacroTable' class TaskJuggler::TextParser # The Scanner class is an abstract text scanner with support for nested # include files and text macros. The tokenizer will operate on rules that # must be provided by a derived class. The scanner is modal. Each mode # operates only with the subset of token patterns that are assigned to the # current mode. The current line is tracked accurately and can be used for # error reporting. The scanner can operate on Strings or Files. class Scanner class MacroStackEntry attr_reader :macro, :args, :text, :endPos def initialize(macro, args, text, endPos) @macro = macro @args = args @text = text @endPos = endPos end end # This class is used to handle the low-level input operations. It knows # whether it deals with a text buffer or a file and abstracts this to the # Scanner. For each nested file the scanner puts an StreamHandle on the # stack while the file is scanned. With this stack the scanner can resume # the processing of the enclosing file once the included files has been # completely processed. class StreamHandle attr_reader :fileName, :macroStack def initialize(log, textScanner) @log = log @textScanner = textScanner @fileName = nil @stream = nil @line = nil @endPos = 1 @scanner = nil @wrapped = false @macroStack = [] @nextMacroEnd = nil end def error(id, message) @textScanner.error(id, message) end def close @stream = nil end # Inject the String _text_ into the input stream at the current cursor # position. def injectText(text, callLength) # Remove the macro call from the end of the already parsed input. preCall = @scanner.pre_match[0..-(callLength + 1)] # Store the end position of the inserted macro in bytes. @nextMacroEnd = preCall.bytesize + text.bytesize # Compose the new @line from the cleaned input, the injected text and # the remainer of the old @line. @line = preCall + text + @scanner.post_match # Start the StringScanner again at the first character of the injected # text. @scanner.string = @line @scanner.pos = preCall.bytesize end def injectMacro(macro, args, text, callLength) injectText(text, callLength) # Simple detection for recursive macro calls. return false if @macroStack.length > 20 @macroStack << MacroStackEntry.new(macro, args, text, @nextMacroEnd) true end def readyNextLine # We read the file line by line with gets(). If we don't have a line # yet or we've reached the end of a line, we get the next one. if @scanner.nil? || @scanner.eos? if (@line = @stream.gets) # Update activity meter about every 1024 lines. @log.activity if (@stream.lineno & 0x3FF) == 0 else # We've reached the end of the current file. @scanner = nil return false end @scanner = StringScanner.new(@line) @wrapped = @line[-1] == ?\n end true end def scan(re) @scanner.scan(re) end def cleanupMacroStack if @nextMacroEnd pos = @scanner.pos while @nextMacroEnd && @nextMacroEnd < pos @macroStack.pop @nextMacroEnd = @macroStack.empty? ? nil : @macroStack.last.endPos end end end def peek(n) @scanner ? @scanner.peek(n) : nil end def eof? @stream.eof? && @scanner.eos? end def dirname @fileName ? File.dirname(@fileName) : '' end # Return the number of the currently processed line. def lineNo # The IO object counts the lines for us by counting the gets() calls. currentLine = @stream && @scanner ? @stream.lineno : 1 # If we've just read the LF, we have to add 1. The LF can only be the # last character of the line. currentLine += 1 if @wrapped && @line && @scanner && @scanner.eos? currentLine end # Return the already processed part of the current line. def line return '' unless @line (@scanner.pre_match || '') + (@scanner.matched || '') end end # Specialized version of StreamHandle for operations on files. class FileStreamHandle < StreamHandle attr_reader :fileName def initialize(fileName, log, textScanner) super(log, textScanner) @fileName = fileName.dup.untaint data = (fileName == '.' ? $stdin : File.new(@fileName, 'r')).read begin @stream = StringIO.new(data.forceUTF8Encoding) rescue error('fileEncoding', $!.message) end @log.msg { "Parsing file #{@fileName} ..." } @log.startProgressMeter("Reading file #{fileName}") end def close @stream.close unless @stream == $stdin super end end # Specialized version of StreamHandle for operations on Strings. class BufferStreamHandle < StreamHandle def initialize(buffer, log, textScanner) super(log, textScanner) begin @stream = StringIO.new(buffer.forceUTF8Encoding) rescue error('bufferEncoding', $!.message) end #@log.msg { "Parsing buffer #{buffer[0, 20]} ..." } end end # Create a new instance of Scanner. _masterFile_ must be a String that # either contains the name of the file to start with or the text itself. # _messageHandler_ is a MessageHandler that is used for error messages. # _log_ is a Log to report progress and status. def initialize(masterFile, log, tokenPatterns, defaultMode) @masterFile = masterFile @messageHandler = TaskJuggler::MessageHandlerInstance.instance @log = log # This table contains all macros that may be expanded when found in the # text. @macroTable = MacroTable.new # The currently processed IO object. @cf = nil # This Array stores the currently processed nested files. It's an Array # of Arrays. The nested Array consists of 2 elements, the IO object and # the @tokenBuffer. @fileStack = [] # This flag is set if we have reached the end of a file. Since we will # only know when the next new token is requested that the file is really # done now, we have to use this flag. @finishLastFile = false # True if the scanner operates on a buffer. @fileNameIsBuffer = false # A SourceFileInfo of the start of the currently processed token. @startOfToken = nil # Line number correction for error messages. @lineDelta = 0 # Lists of regexps that describe the detectable tokens. The Arrays are # grouped by mode. @patternsByMode = { } # The currently active scanner mode. @scannerMode = nil # The mode that the scanner is in at the start and end of file @defaultMode = defaultMode # Points to the currently active pattern set as defined by the mode. @activePatterns = nil tokenPatterns.each do |pat| type = pat[0] regExp = pat[1] mode = pat[2] || :tjp postProc = pat[3] addPattern(type, regExp, mode, postProc) end self.mode = defaultMode end # Add a new pattern to the scanner. _type_ is either nil for tokens that # will be ignored, or some identifier that will be returned with each # token of this type. _regExp_ is the RegExp that describes the token. # _mode_ identifies the scanner mode where the pattern is active. If it's # only a single mode, _mode_ specifies the mode directly. For multiple # modes, it's an Array of modes. _postProc_ is a method reference. This # method is called after the token has been detected. The method gets the # type and the matching String and returns them again in an Array. def addPattern(type, regExp, mode, postProc = nil) if mode.is_a?(Array) mode.each do |m| # The pattern is active in multiple modes @patternsByMode[m] = [] unless @patternsByMode.include?(m) @patternsByMode[m] << [ type, regExp, postProc ] end else # The pattern is only active in one specific mode. @patternsByMode[mode] = [] unless @patternsByMode.include?(mode) @patternsByMode[mode] << [ type, regExp, postProc ] end end # Switch the parser to another mode. The scanner will then only detect # patterns of that _newMode_. def mode=(newMode) #puts "**** New mode: #{newMode}" @activePatterns = @patternsByMode[newMode] raise "Undefined mode #{newMode}" unless @activePatterns @scannerMode = newMode end # Start the processing. if _fileNameIsBuffer_ is true, we operate on a # String, else on a File. def open(fileNameIsBuffer = false) @fileNameIsBuffer = fileNameIsBuffer if fileNameIsBuffer @fileStack = [ [ @cf = BufferStreamHandle.new(@masterFile, @log, self), nil, nil ] ] else begin @fileStack = [ [ @cf = FileStreamHandle.new(@masterFile, @log, self), nil, nil ] ] rescue IOError, SystemCallError error('open_file', "Cannot open file #{@masterFile}: #{$!}") end end @masterPath = @cf.dirname + '/' @tokenBuffer = nil end # Finish processing and reset all data structures. def close unless @fileNameIsBuffer @log.startProgressMeter("Reading file #{@masterFile}") @log.stopProgressMeter end @fileStack = [] @cf = @tokenBuffer = nil end # Continue processing with a new file specified by _includeFileName_. When # this file is finished, we will continue in the old file after the # location where we started with the new file. The method returns the full # qualified name of the included file. def include(includeFileName, sfi, &block) if includeFileName[0] != '/' pathOfCallingFile = @fileStack.last[0].dirname path = pathOfCallingFile.empty? ? '' : pathOfCallingFile + '/' # If the included file is not an absolute name, we interpret the file # name relative to the including file. includeFileName = path + includeFileName end # Try to dectect recursive inclusions. This will not work if files are # accessed via filesystem links. @fileStack.each do |entry| if includeFileName == entry[0].fileName error('include_recursion', "Recursive inclusion of #{includeFileName} detected", sfi) end end # Save @tokenBuffer in the record of the parent file. @fileStack.last[1] = @tokenBuffer unless @fileStack.empty? @tokenBuffer = nil @finishLastFile = false # Open the new file and push the handle on the @fileStack. begin @fileStack << [ (@cf = FileStreamHandle.new(includeFileName, @log, self)), nil, block ] @log.msg { "Parsing file #{includeFileName}" } rescue StandardError error('bad_include', "Cannot open include file #{includeFileName}", sfi) end # Return the name of the included file. includeFileName end # Return SourceFileInfo for the current processing prosition. def sourceFileInfo @cf ? SourceFileInfo.new(fileName, @cf.lineNo - @lineDelta, 0) : SourceFileInfo.new(@masterFile, 0, 0) end # Return the name of the currently processed file. If we are working on a # text buffer, the text will be returned. def fileName @cf ? @cf.fileName : @masterFile end def lineNo # :nodoc: @cf ? @cf.lineNo : 0 end def columnNo # :nodoc: 0 end def line # :nodoc: @cf ? @cf.line : 0 end # Return the next token from the input stream. The result is an Array with # 3 entries: the token type, the token String and the SourceFileInfo where # the token started. def nextToken # If we have a pushed-back token, return that first. unless @tokenBuffer.nil? res = @tokenBuffer @tokenBuffer = nil return res end if @finishLastFile # The previously processed file has now really been processed to # completion. Close it and remove the corresponding entry from the # @fileStack. @finishLastFile = false #@log.msg { "Completed file #{@cf.fileName}" } # If we have a block to be executed on EOF, we call it now. onEof = @fileStack.last[2] onEof.call if onEof @cf.close if @cf @fileStack.pop if @fileStack.empty? # We are done with the top-level file now. @cf = @tokenBuffer = nil @finishLastFile = true return [ :endOfText, '', @startOfToken ] else # Continue parsing the file that included the current file. @cf, tokenBuffer = @fileStack.last @log.msg { "Parsing file #{@cf.fileName} ..." } # If we have a left over token from previously processing this file, # return it now. if tokenBuffer @finishLastFile = true if tokenBuffer[0] == :eof return tokenBuffer end end end scanToken end # Return a token to retrieve it with the next nextToken() call again. Only 1 # token can be returned before the next nextToken() call. def returnToken(token) #@log.msg { "-> Returning Token: [#{token[0]}][#{token[1]}]" } unless @tokenBuffer.nil? $stderr.puts @tokenBuffer raise "Fatal Error: Cannot return more than 1 token in a row" end @tokenBuffer = token end # Add a Macro to the macro translation table. def addMacro(macro) @macroTable.add(macro) end # Return true if the Macro _name_ has been added already. def macroDefined?(name) @macroTable.include?(name) end # Expand a macro and inject it into the input stream. _prefix_ is any # string that was found right before the macro call. We have to inject it # before the expanded macro. _args_ is an Array of Strings. The first is # the macro name, the rest are the parameters. _callLength_ is the number # of characters for the complete macro call "${...}". def expandMacro(prefix, args, callLength) # Get the expanded macro from the @macroTable. macro, text = @macroTable.resolve(args, sourceFileInfo) # If the expanded macro is empty, we can ignore it. return if text == '' unless macro && text error('undefined_macro', "Undefined macro '#{args[0]}' called") end unless @cf.injectMacro(macro, args, prefix + text, callLength) error('macro_stack_overflow', "Too many nested macro calls.") end end # Call this function to report any errors related to the parsed input. def error(id, text, sfi = nil, data = nil) message(:error, id, text, sfi, data) end def warning(id, text, sfi = nil, data = nil) message(:warning, id, text, sfi, data) end private def scanToken @startOfToken = sourceFileInfo begin match = nil loop do # First make sure that the line buffer has been filled and we have a # line to parse. unless @cf.readyNextLine if @scannerMode != @defaultMode # The stream resets the line number to 1. Since we still # know the start of the token, we setup @lineDelta so that # sourceFileInfo() returns the proper line number. @lineDelta = -(@startOfToken.lineNo - 1) error('runaway_token', "Unterminated token starting at line #{@startOfToken}") end # We've found the end of an input file. Return a special token # that describes the end of a file. @finishLastFile = true return [ :eof, '', @startOfToken ] end @activePatterns.each do |type, re, postProc| if (match = @cf.scan(re)) #raise "#{re} matches empty string" if match.empty? # If we have a post processing method, call it now. It may modify # the type or the found token String. type, match = postProc.call(type, match) if postProc break if type.nil? # Ignore certain tokens with nil type. @cf.cleanupMacroStack return [ type, match, @startOfToken ] end end if match.nil? # If we haven't found a match, we either hit EOF or a token we did # not expect. if @cf.eof? error('unexpected_eof', "Unexpected end of file found") else error('no_token_match', "Unexpected characters found: '#{@cf.peek(10)}...'") end else # Remove completely scanned expanded macros from stack. @cf.cleanupMacroStack end end rescue ArgumentError # This is triggered by StringScanner.scan, but we don't want to put # the block in the inner loops for performance reasons. error('scan_encoding_error', $!.message) end end def message(type, id, text, sfi, data) unless text.empty? line = @cf ? @cf.line : nil sfi ||= sourceFileInfo if @cf && !@cf.macroStack.empty? @messageHandler.info('macro_stack', 'Macro call history:', nil) @cf.macroStack.reverse_each do |entry| macro = entry.macro args = entry.args[1..-1] args.collect! { |a| '"' + a + '"' } @messageHandler.info('macro_stack', " ${#{macro.name}#{args.empty? ? '' : ' '}" + "#{args.join(' ')}}", macro.sourceFileInfo) end end case type when :error @messageHandler.error(id, text, sfi, line, data) when :warning @messageHandler.warning(id, text, sfi, line, data) else raise "Unknown message type #{type}" end end end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser/MacroTable.rb0000644000175000017500000000452512614413013023101 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = MacroTable.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler::TextParser class Macro attr_reader :name, :value, :sourceFileInfo def initialize(name, value, sourceFileInfo) @name = name @value = value @sourceFileInfo = sourceFileInfo end end # The MacroTable is used by the TextScanner to store defined macros and # resolve them on request later on. A macro is a text pattern that has a name. # The pattern may contain variable parts that are replaced by arguments passed # during the macro call. class MacroTable def initialize @macros = {} end # Add a new macro definition to the table or replace an existing one. def add(macro) @macros[macro.name] = macro end # Remove all definitions from the table. def clear @macros = [] end # Returns true only if a macro named _name_ is defined in the table. def include?(name) @macros.include?(name) end # Returns the definition of the macro specified by name as first entry of # _args_. The other entries of _args_ are parameters that are replacing the # ${n} tokens in the macro definition. In case the macro call has less # arguments than the macro definition uses, the ${n} tokens remain # unchanged. No error is generated. def resolve(args, sourceFileInfo) name = args[0] # If the first character of the macro name is a '?', the macro may be # undefined and is silently ignored. if name[0] == ?? # Remove the '?' from the name. name = name[1..-1] return [ nil, '' ] unless @macros[name] end return nil unless @macros[name] resolved = @macros[name].value.dup i = 0 args.each do |arg| resolved.gsub!(Regexp.new("(([^$]|^))\\$\\{#{i}\\}"), "\\1#{arg}") i += 1 end # Remove the escape character from all the escaped '${...}'. resolved.gsub!('$${', '${') [ @macros[name], resolved ] end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser/TokenDoc.rb0000644000175000017500000000211012614413013022562 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TokenDoc.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler::TextParser # Utility class to store a name and a textual description of the meaning of a # token used by the parser syntax tree. A specification of the variable type # and a reference to a specific pattern are optional. class TokenDoc attr_reader :text attr_accessor :name, :typeSpec, :pattern # Construct a ParserTokenDoc object. _name_ and _text_ are Strings that # hold the name and textual description of the parser token. def initialize(name, arg) @name = name if arg.is_a?(String) @text = arg else @pattern = arg end @typeSpec = nil @pattern = nil end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser/StackElement.rb0000644000175000017500000000570312614413013023446 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = StackElement.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler::TextParser # This class models the elements of the stack that the TextParser uses to keep # track of its state. It stores the current TextParserRule, the current # pattern position and the TextScanner position at the start of processing. It # also store the function that must be called to store the collected values. class StackElement attr_reader :val, :function, :sourceFileInfo, :firstSourceFileInfo attr_accessor :state # Create a new stack element. _rule_ is the TextParserRule that triggered # the creation of this element. _function_ is the function that will be # called at the end to store the collected data. _sourceFileInfo_ is a # SourceFileInfo reference that describes the TextScanner position when the # rule was entered. def initialize(function, state = nil) # This Array stores the collected values. @val = [] # Array to store the source file references for the collected values. @sourceFileInfo = [] # A shortcut to the first non-nil sourceFileInfo. @firstSourceFileInfo = nil # Counter used for StackElement::store() @position = 0 # The method that will process the collected values. @function = function @state = state end # Insert the value _val_ at the position _index_. It also stores the # _sourceFileInfo_ for this element. In case _multiValue_ is true, the # old value is not overwritten, but values are stored in an # TextParserResultArray object. def insert(index, val, sourceFileInfo, multiValue) if multiValue if @val[index] # We already have a value for this token position. unless @val[index].is_a?(TextParserResultArray) # This should never happen. raise "#{@val[index].class} must be an Array" end else @val[index] = TextParserResultArray.new end # Just append the value and apply the special Array merging. @val[index] << val else @val[index] = val end @sourceFileInfo[index] = sourceFileInfo # Store the first SFI for faster access. @firstSourceFileInfo = sourceFileInfo unless @firstSourceFileInfo val end # Store a collected value and move the position to the next pattern. def store(val, sourceFileInfo = nil) @val[@position] = val @sourceFileInfo[@position] = sourceFileInfo @position += 1 end def each @val.each { |x| yield x } end def length @val.length end end end taskjuggler-3.5.0/lib/taskjuggler/Task.rb0000644000175000017500000000747212614413013017675 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Task.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/PropertyTreeNode' require 'taskjuggler/TaskScenario' class TaskJuggler class Task < PropertyTreeNode def initialize(project, id, name, parent) super(project.tasks, id, name, parent) project.addTask(self) @data = Array.new(@project.scenarioCount, nil) @project.scenarioCount.times do |i| TaskScenario.new(self, i, @scenarioAttributes[i]) end end def readyForScheduling?(scenarioIdx) @data[scenarioIdx].readyForScheduling? end private # Create a blog-style list of all alert messages that match the Query. def journalText(query, longVersion, recursive) # The components of the message are either UTF-8 text or RichText. For # the RichText components, we use the originally provided markup since # we compose the result as RichText markup first. rText = '' if recursive list = @project['journal'].entriesByTaskR(self, query.start, query.end, query.hideJournalEntry) else list = @project['journal'].entriesByTask(self, query.start, query.end, query.hideJournalEntry) end list.setSorting([ [ :alert, -1 ], [ :date, -1 ], [ :seqno, 1 ] ]) list.sort! list.each do |entry| tsRecord = entry.timeSheetRecord if entry.property.is_a?(Task) levelRecord = @project['alertLevels'][entry.alertLevel] if query.selfContained alertName = "[#{levelRecord.name}]" else alertName = "[[File:icons/flag-#{levelRecord.id}.png|" + "alt=[#{levelRecord.name}]|text-bottom]]" end rText += "== #{alertName} #{entry.headline} ==\n" + "''Reported on #{entry.date.to_s(query.timeFormat)}'' " if entry.author rText += "''by #{entry.author.name}''" end rText += "\n\n" unless entry.flags.empty? rText += "'''Flags:''' #{entry.flags.join(', ')}\n\n" end if tsRecord rText += "'''Work:''' #{tsRecord.actualWorkPercent.to_i}% " if tsRecord.remaining rText += "'''Remaining:''' #{tsRecord.actualRemaining}d " else rText += "'''End:''' " + "#{tsRecord.actualEnd.to_s(query.timeFormat)} " end rText += "\n\n" end end unless entry.headline.empty? rText += "'''#{entry.headline}'''\n\n" end if entry.summary rText += entry.summary.richText.inputText + "\n\n" end if longVersion && entry.details rText += entry.details.richText.inputText + "\n\n" end end # Don't generate a RichText object for an empty String. return if rText.empty? # Now convert the RichText markup String into RichTextIntermediate # format. unless (rti = RichText.new(rText, RTFHandlers.create(@project)). generateIntermediateFormat) warning('task_journal_text', 'Syntax error in journal text') return nil end # No section numbers, please! rti.sectionNumbers = false # We use a special class to allow CSS formating. rti.cssClass = 'tj_journal' query.rti = rti end end end taskjuggler-3.5.0/lib/taskjuggler/HTMLElements.rb0000644000175000017500000000244712614413013021231 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = HTMLElements.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/XMLElement' class TaskJuggler module HTMLElements # A list of supported HTML tags. htmlTags = %w( a b body br code col colgroup div em frame frameset footer h1 h2 h3 head html hr meta p pre table td title tr ) # A list of HTML tags that are self-closing. closureTags = %w( area base basefont br hr input img link meta ) # For every HTML tag, we generate a class with the equivalent uppercase # name. This class is derived off of XMLElement. This makes creating HTML # code a lot simpler. Instead of # XMLElement.new('h1') # we now can write # H1.new htmlTags.each do |tag| class_eval <<"EOT" class #{tag.upcase} < XMLElement def initialize(attrs = {}, &block) super("#{tag}", attrs, #{closureTags.include?(tag)}) end end EOT end end end taskjuggler-3.5.0/lib/taskjuggler/TableColumnSorter.rb0000644000175000017500000000540312614413013022367 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TableColumnSorter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This class can rearrange the columns of a table according to a new order # determined by an Array of table headers. The table is an Array of table # lines. Each line is another Array. The first line of the table is an Array # of the headers of the columns. class TableColumnSorter attr_reader :discontinuedColumns # Register a new table for rearranging. def initialize(table) @oldTable = table @discontinuedColumns = nil end # Rearrange the registered table. The old table won't be modified. The # method returns a new table (Array of Arrays). _newHeaders_ is an Array # that represents the new column headers. The columns that are not in the # new header will be the last columns of the new table. def sort(newHeaders) # Maps old index to new index. columnIdxMap = {} newHeaderIndex = newHeaders.length oldHeaders = @oldTable[0] discontinuedHeaders = [] oldHeaders.length.times do |i| if (ni = newHeaders.index(oldHeaders[i])) # This old column is still in the new header columnIdxMap[i] = ni else # This old column is no longer contained in the new header. We # append it at the end. columnIdxMap[i] = newHeaderIndex discontinuedHeaders << oldHeaders[i] newHeaderIndex += 1 end end # We construct a new table from scratch. All values from the old table # are copied over. columns in the new table that were not contained in # the old table will be filled with nil. newTable = [] @oldTable.length.times do |lineIdx| oldLine = @oldTable[lineIdx] if lineIdx == 0 # Insert the new headers. The discontinued ones will be added below. newTable[0] = newHeaders else # Add a line of nils to the new table. newTable[lineIdx] = Array.new(newHeaderIndex, nil) end # Copy the old column to the new position. columnIdxMap.each do |oldColIdx, newColIdx| newTable[lineIdx][newColIdx] = oldLine[oldColIdx] end end # Now we need to add the new column headers that were not in the old # headers. #newTable[0] += discontinuedHeaders @discontinuedColumns = discontinuedHeaders.length newTable end end end taskjuggler-3.5.0/lib/taskjuggler/SheetReceiver.rb0000644000175000017500000003040712614413013021522 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = SheetReceiver.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'digest/md5' require 'mail' require 'yaml' require 'taskjuggler/apps/Tj3Client' require 'taskjuggler/StdIoWrapper' require 'taskjuggler/SheetHandlerBase' require 'taskjuggler/RichText' class TaskJuggler class SheetReceiver < SheetHandlerBase include StdIoWrapper def initialize(appName, type) super(appName) @sheetType = type # The following settings must be set by the deriving class. # Sheet type specific option for tj3client @tj3clientOption = nil # Base directory to store received sheets @sheetDir = nil # Base directory where to find the resource file. @templateDir = nil # Directory to store the failed emails. @failedMailsDir = nil # Directory to store the failed sheets @failedSheetsDir = nil # File that holds the acceptable signatures. @signatureFile = nil # The log file @logFile = nil # The subject of the confirmation email @emailSubject = nil # Regular expressions to identify a sheet. @sheetHeader = nil # Regular expression to extract the sheet signature (date). @signatureFilter = nil # The email address of the submitter of the sheet. @submitter = nil # The resource ID of the submitter. @resourceId = nil # The stdout content from tj3client @report = nil # The stderr content from tj3client @warnings = nil # The extracted sheet text. @sheet = nil # Will indicate whether the sheet was attached or in mail body @sheetWasAttached = true # The end date of the reporting period. @date = nil # The id of the incomming message. @messageId = nil end # Read the sheet from $stdin in email format. Extract the sheet from the # attachments or body and check it. If ok, send back a summary, otherwise # the error message. # The actual check is done by a tj3 server process that is accessed via # tj3client. def processEmail setWorkingDir createDirectories begin # Read the RFC 822 compliant mail from STDIN. rawMail = $stdin.read rawMail = rawMail.forceUTF8Encoding mail = Mail.new(rawMail) rescue # In certain cases, Mail will fail to create the Mail object. Since we # don't have the email sender yet, we have to try to extract it # ourself. fromLine = nil rawMail.each_line do |line| unless fromLine matches = line.match('^From: .*') if matches fromLine = matches[0] break end end end # Try to extract the mail sender the dirty way so we can at least send # a response to the submitter. @submitter = fromLine[6..-1] if fromLine && fromLine.is_a?(String) error("Incoming mail could not be processed: #{$!}") end # Who sent this email? @submitter = mail.from.respond_to?('[]') ? mail.from[0] : mail.from # Getting the message ID. @messageId = mail.message_id || 'unknown' @idDigest = Digest::MD5.hexdigest(@messageId) info("Processing #{@sheetType} mail from #{@submitter} " + "with ID #{@messageId} (#{@idDigest})") # Store the mail in the failedMailsDir in case something goes wrong. File.open("#{@failedMailsDir}/#{@idDigest}", 'w') do |f| f.write(mail) end # First we search the attachments and then the body. mail.attachments.each do |attachment| # We are looking for an attached file with a .tji extension. fileName = attachment.filename next unless fileName && fileName[-4..-1] == '.tji' # Further inspect the attachment. If we could process it, we are done. return true if processSheet(attachment.body.decoded) end # None of the attachements worked, so let's try the mail body. @sheetWasAttached = false return true if processSheet(mail.body.decoded) error(<<"EOT" No #{@sheetType} sheet found in email. Please make sure the header syntax is correct and contained in a single line that starts at the begining of the line. If you had the #{@sheetType} sheet attached, the file name must have a '.tji' extension to be found. EOT ) end private # Isolate the actual syntax from _sheet_ and process it. def processSheet(sheet) begin @sheet = sheet.forceUTF8Encoding rescue error($!.message) end # If the sheet contains special cut markers, we extract only the content # within those markers. @sheet = cutOut(@sheet) # A valid sheet must have the poper header line. if @sheetHeader.match(@sheet) checkSignature(@sheet) # Extract the resource ID and the end date from the sheet. matches = @sheetHeader.match(@sheet) @resourceId, @date = matches[1..2] # Email answers will only go the email address on file! @submitter = getResourceEmail(@resourceId) info("Found #{@sheetWasAttached ? 'attached ' : ''}sheet for " + "#{@resourceId} dated #{@date}") # Ok, found. Now check the full sheet. if checkSheet(@sheet) # Everything is fine. Store it away. fileSheet(@sheet) # Remove the mail from the failedMailsDir File.delete("#{@failedMailsDir}/#{@idDigest}") info("Accepted sheet for #{@resourceId} dated #{@date}") return true end end end def checkSheet(sheet) res = nil begin # Save a copy of the sheet for debugging purposes. File.open("#{@failedSheetsDir}/#{@resourceId}-#{@date}.tji", 'w') do |f| f.write(sheet) end command = [ '--unsafe', '--silent', *@tj3clientOption.split(' '), @projectId, '.' ] # Send the report to the tj3client process via stdin. res = stdIoWrapper(sheet) do Tj3Client.new.main(command) end # Without errors, the incoming report is pretty printed and returned # in RichText format. @report = res.stdOut @warnings = res.stdErr rescue fatal("Cannot check #{@sheetType} sheet: #{$!}") end if res.returnValue == 0 File.delete("#{@failedSheetsDir}/#{@resourceId}-#{@date}.tji") return true end # The exit status was not 0. The stderr output should not be empty and # will contain error and warning messages. error(@warnings) end def fileSheet(sheet) # Create the appropriate directory structure if it doesn't exist. dir = "#{@sheetDir}/#{@date}" fileName = "#{dir}/#{@resourceId}_#{@date}.tji" newDir = false begin unless File.directory?(dir) Dir.mkdir(dir) addToScm('Adding new directory', dir) newDir = true end File.open(fileName, 'w') { |f| f.write(sheet) } addToScm("Adding/updating #{fileName}", fileName) rescue fatal("Cannot store #{@sheetType} sheet #{fileName}: #{$!}") return end # Create or update the file that includes all *.tji in the directory. generateInclusionFile(dir) if newDir # Add the new directory to the parent all.tji file. allFile = "#{@sheetDir}/all.tji" File.open(allFile, 'a') do |f| f.write("\ninclude '#{@date}/all.tji' { }") end addToScm('Adding new directory to all.tji', allFile) end text = <<"EOT" == Report from #{getResourceName} for the period ending #{@date} == EOT # Add warnings if we had any. unless @warnings.empty? text += <<"EOT" ---- Your report does contain some issues that you may want to fix or address with your manager or project manager: #{@warnings} ---- EOT end # Append the pretty printed version of the submitted sheet. text += @report # Send out the email. sendRichTextEmail(@submitter, sprintf(@emailSubject, getResourceName, @date), text, nil, nil, @messageId) true end # Generate or update a file the contains 'include' statements for all the # .tji files in the provided directory. The generated file will be in this # directory as well. def generateInclusionFile(dir) pwd = Dir.pwd begin Dir.chdir(dir) File.open('all.tji', 'w') do |file| Dir.glob('*.tji').each do |tji| file.puts("include '#{tji}' { }") unless tji == 'all.tji' end end rescue error("Can't create inclusion file: #{$!}") ensure Dir.chdir(pwd) end # Report the change to the SCM handler. addToScm('Adding/updating summary include file.', "#{dir}/all.tji") end def checkSignature(sheet) if matches = @signatureFilter.match(sheet) interval = matches[1] else fatal("No #{@sheetType}sheet header found") end acceptedSignatures = [] if File.exist?(@signatureFile) File.open(@signatureFile, 'r') do |file| acceptedSignatures = file.readlines end acceptedSignatures.map! { |s| s.chomp } acceptedSignatures.delete_if { |s| s.chomp.empty? } else error("#{@signatureFile} does not exist yet.") end unless acceptedSignatures.include?(interval) error(<<"EOT" The reporting period #{interval} was not accepted! Either you have modified the sheet header, you are submitting the sheet too late or too early. EOT ) end end def createDirectories [ @sheetDir, @failedMailsDir, @failedSheetsDir ].each do |dir| unless File.directory?(dir) info("Creating directory #{dir}") Dir.mkdir(dir) end end end def error(message) $stderr.puts message if @outputLevel >= 1 log('ERROR', "#{message}") if @logLevel >= 1 # Append the submitted sheet for further tries. We may run into encoding # errors here. In this case we send the answer without the incoming time # sheet. begin message += "\n" + @sheet if @sheet && !@sheetWasAttached rescue end sendEmail(@submitter, "Your #{@sheetType} sheet submission failed!", message) raise TjRuntimeError end def fatal(message) log('FATAL', "#{message}") # Append the submitted sheet for further tries. message += "\n" + @sheet if @sheet sendEmail(@submitter, 'Temporary server error', <<"EOT" We are sorry! The #{@sheetType} sheet server detected a configuration problem and is temporarily out of service. The administrator has been notified and will try to rectify the situation as soon as possible. Please re-submit your #{@sheetType} sheet later! EOT ) raise TjRuntimeError end # Load tye resources.yml YAML file into the @resourceList variable. # The format is Array with one entry per resource. The entry is an Array # with 3 fields: ID, name and email. All fields are String objects. def getResourceList fatal('@date not set') unless @date fileName = "#{@templateDir}/#{@date}/resources.yml" begin @resourceList = YAML.load(File.read(fileName)) info("#{@resourceList.length} resources loaded") rescue error("Cannot read resource file #{fileName}: #{$!}") end @resourceList end def getResourceEmail(id = @resourceId) getResourceList unless @resourceList @resourceList.each do |resource| return resource[2] if resource[0] == id end error("Resource ID '#{id}' not found in list") end def getResourceName(id = @resourceId) getResourceList unless @resourceList @resourceList.each do |resource| return resource[1] if resource[0] == id end error("Resource ID '#{id}' not found in list") end end end taskjuggler-3.5.0/lib/taskjuggler/ShiftAssignments.rb0000644000175000017500000002321112614413013022251 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ShiftAssignments.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'monitor' require 'taskjuggler/Scoreboard' class TaskJuggler # A ShiftAssignment associate a specific defined shift with a time interval # where the shift should be active. class ShiftAssignment attr_reader :shiftScenario attr_accessor :interval def initialize(shiftScenario, interval) @shiftScenario = shiftScenario @interval = interval end def hashKey return "#{@shiftScenario.object_id}|#{@interval.start}|#{@interval.end}" end # Return a deep copy of self. def copy ShiftAssignment.new(@shiftScenario, TimeInterval.new(@interval)) end # Return true if the _iv_ interval overlaps with the assignment interval. def overlaps?(iv) @interval.overlaps?(iv) end # Returns true if the shift is active and requests to replace global # leave settings. def replace?(date) @interval.start <= date && date < @interval.end && @shiftScenario.replace? end # Check if date is withing the assignment period. def assigned?(date) @interval.start <= date && date < @interval.end end # Returns true if the shift has working hours defined for the _date_. def onShift?(date) @shiftScenario.onShift?(date) end # Returns true if the shift has a leave defined for the _date_. def onLeave?(date) @shiftScenario.onLeave?(date) end # Primarily used for debugging def to_s "#{@shiftScenario.property.id} #{interval}" end end # This class manages a list of ShiftAssignment elements. The intervals of the # assignments must not overlap. # # Since it is fairly costly to determine the onShift and onLeave values # for a given date we use a scoreboard to cache all computed values. # Changes to the assigment set invalidate the cache again. # # To optimize memory usage and computation time the Scoreboard objects for # similar ShiftAssignments are shared. # # Scoreboard may be nil or a bit vector encoded as a Fixnum # nil: Value has not been determined yet. # Bit 0: 0: No assignment # 1: Has assignement # Bit 1: 0: Work time (as defined by working hours) # 1: No work time (as defined by working hours) # Bit 2 - 5: 0: No holiday or leave time # 1: Public holiday (holiday) # 2: Annual leave # 3: Special leave # 4: Sick leave # 5: unpaid leave # 6: blocked for other projects # 7 - 15: Reserved # Bit 6 - 7: Reserved # Bit 8: 0: No global override # 1: Override global setting class ShiftAssignments < Monitor include ObjectSpace attr_accessor :project attr_reader :assignments # This class is sharing the Scoreboard instances for ShiftAssignments that # have identical assignment data. This class variable holds a Hash with # records for each unique Scoreboard. A record is an array of references # to the owning ShiftAssignments objects and a reference to the Scoreboard # object. @@scoreboards = {} def initialize(sa = nil) define_finalizer(self, self.class.method(:deleteScoreboard).to_proc) # An Array of ShiftAssignment objects. @assignments = [] # A String that uniquely identifies the content of this ShiftAssignment # object. @hashKey = nil if sa # A ShiftAssignments object was passed to the contructor. We create a # deep copy of it. @project = sa.project sa.assignments.each do |assignment| @assignments << assignment.copy end # Create a new ScoreBoard or share one with a ShiftAssignments object # that has the same set of shift assignments. @scoreboard = newScoreboard else @project = nil @scoreboard = nil end end # Add a new assignment to the list. In case there was no overlap the # function returns true. Otherwise false. def addAssignment(shiftAssignment) # Make sure we don't insert overlapping assignments. return false if overlaps?(shiftAssignment.interval) @assignments << shiftAssignment @scoreboard = newScoreboard true end # This function returns the entry in the scoreboard that corresponds to # _idx_. If the slot has not yet been determined, it's calculated first. def getSbSlot(idx) # Check if we have a value already for this slot. return @scoreboard[idx] unless @scoreboard[idx].nil? date = @scoreboard.idxToDate(idx) # If not, compute it. @assignments.each do |sa| next unless sa.assigned?(date) # Mark the slot as 'assigned'. Meaning, the rest of the bits are valid # for this time slot. @scoreboard[idx] = 1 # Set bit 1 if the shift is not active @scoreboard[idx] |= 1 << 1 unless sa.onShift?(date) # Set bits 2 - 5 to 1 if it's a leave slot. @scoreboard[idx] |= 1 << 3 if sa.onLeave?(date) # Set the 8th bit if the shift replaces global leaves. @scoreboard[idx] |= 1 << 8 if sa.replace?(date) return @scoreboard[idx] end # The slot is not covered by any assignment. @scoreboard[idx] = 0 end # Returns true if any of the defined shift periods overlaps with the date or # interval specified by _idx_. def assigned?(idx) (getSbSlot(idx) & 1) == 1 end # Returns true if any of the defined shift periods contains the date # specified by the scoreboard index _idx_ and the shift has working hours # defined for that date. def onShift?(idx) (getSbSlot(idx) & (1 << 1)) == 0 end # Returns true if any of the defined shift periods contains the date # specified by the scoreboard index _idx_ and the shift has a leave # defined or all off hours defined for that date. def timeOff?(idx) (getSbSlot(idx) & 0x3E) != 0 end # Returns true if any of the defined shift periods contains the date # specified by the scoreboard index _idx_ and if the shift has a leave # defined for the date. def onLeave?(idx) (getSbSlot(idx) & 0x3C) != 0 end # Return a list of intervals that lay within _iv_ and are at least # minDuration long and contain no working time. def collectTimeOffIntervals(iv, minDuration) @scoreboard.collectIntervals(iv, minDuration) do |val| (val & 0x3E) != 0 end end def ShiftAssignments.scoreboards @@scoreboards end def ShiftAssignments.sbClear @@scoreboards = {} end # This function is primarily used for debugging purposes. def to_s return '' if @assignments.empty? out = "shifts " first = true @assignments.each do |sa| if first first = false else out += ', ' end out += sa.to_s end out end def hashKey @hashKey if @hashKey @hashKey = "#{@project.object_id}|" @assignments.sort! { |a, b| a.interval.start <=> b.interval.start } @assignments.each { |a| @hashKey += a.hashKey + '||' } @hashKey end private # This function either returns a new Scoreboard or a reference to an # existing one in case we already have one for the same assigment patterns. def newScoreboard if (record = @@scoreboards[hashKey]) # If we already have a Scoreboard object for the hashKey of this # ShiftAssignments object, we can re-use this. We just need to # register the object as a user of it. record[0] << object_id # Return the re-used Scoreboard object. return record[1] end # We have not found a matching scoreboard, so we have to create a new one. newSb = Scoreboard.new(@project['start'], @project['end'], @project['scheduleGranularity']) # Create a new record for it and register the ShiftAssignments object as # first user. Add the record to the @@scoreboards list. @@scoreboards[hashKey] = [ [ object_id ], newSb ] # Append the new record to the list. return newSb end # This function is called whenever a ShiftAssignments object gets destroyed # by the GC. def ShiftAssignments.deleteScoreboard(objId) # Attention: Due to the way this class is called, there will be no # visible exceptions here. All runtime errors will go unnoticed! # # We'll search the @@scoreboards for an entry that holds a reference to # the deleted ShiftAssignments object. If it's the last in the record, # we delete the whole record. If not, we'll just remove the reference # form the record. @@scoreboards.each_value do |record| if record[0].include?(objId) # Remove the ShiftAssignments object as user of this Scoreboard # object. record[0].delete(objId) # We've found what we were looking for. break end end # Delete all entries which have empty reference lists. @@scoreboards.delete_if { |key, record| record[0].empty? } end # Returns true if the interval overlaps with any of the assignment periods. def overlaps?(iv) @assignments.each do |sa| return true if sa.overlaps?(iv) end false end end end taskjuggler-3.5.0/lib/taskjuggler/LogicalOperation.rb0000644000175000017500000002032512614413013022216 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = LogicalOperation.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TjTime' require 'taskjuggler/RichText' require 'taskjuggler/TjException' class TaskJuggler # A LogicalOperation is the basic building block for a LogicalExpression. A # logical operation has one or two operands and an operator. The operands can # be LogicalOperation objects, fixed values or references to project data. The # LogicalOperation can be evaluated in a certain context. This contexts # determines the actual values of the project data references. # The evaluation is done by calling LogicalOperation#eval. The result must be # of a type that responds to all the operators that are used in the eval # method. class LogicalOperation attr_reader :operand1 attr_accessor :operand2, :operator # Create a new LogicalOperation object. _opnd1_ is the mandatory operand. # The @operand2 and the @operator can be set later. def initialize(opnd1, operator = nil, opnd2 = nil) @operand1 = opnd1 @operand2 = opnd2 @operator = operator end # Evaluate the expression in a given context represented by _expr_ of type # LogicalExpression. The result must be of a type that responds to all the # operators of this function. def eval(expr) case @operator when nil if @operand1.respond_to?(:eval) # An operand can be a fixed value or another term. This could be a # LogicalOperation, LogicalFunction or anything else that provides # an appropriate eval() method. return @operand1.eval(expr) else return @operand1 end when '~' return !coerceBoolean(@operand1.eval(expr), expr) when '>', '>=', '=', '<', '<=', '!=' # Evaluate the operation for all 2 operand operations that can be # either interpreted as date, numbers or Strings. opnd1 = @operand1.eval(expr) opnd2 = @operand2.eval(expr) if opnd1.is_a?(TjTime) res= evalBinaryOperation(opnd1, operator, opnd2) do |o| coerceTime(o, expr) end return res elsif opnd1.is_a?(Fixnum) || opnd1.is_a?(Float) || opnd1.is_a?(Bignum) return evalBinaryOperation(opnd1, operator, opnd2) do |o| coerceNumber(o, expr) end elsif opnd1.is_a?(RichTextIntermediate) return evalBinaryOperation(opnd1.to_s, operator, opnd2) do |o| coerceString(o, expr) end elsif opnd1.is_a?(String) return evalBinaryOperation(opnd1, operator, opnd2) do |o| coerceString(o, expr) end else expr.error("First operand of a binary operation must be a date, " + "a number or a string: #{opnd1.class}") end when '&' return coerceBoolean(@operand1.eval(expr), expr) && coerceBoolean(@operand2.eval(expr), expr) when '|' return coerceBoolean(@operand1.eval(expr), expr) || coerceBoolean(@operand2.eval(expr), expr) else expr.error("Unknown operator #{@operator} in logical expression") end end # Convert the operation into a textual representation. def to_s(query) if @operator.nil? operand_to_s(@operand1, query) elsif @operand2.nil? @operator + operand_to_s(@operand1, query) else "(#{operand_to_s(@operand1, query)} #{@operator} " + "#{operand_to_s(@operand2, query)})" end end private def operand_to_s(operand, query) if operand.is_a?(LogicalOperation) operand.to_s(query) elsif operand.is_a?(String) "'#{operand}'" else operand.to_s end end # We need to do binary operator evaluation with various coerce functions. # This function does the evaluation of _opnd1_ and _opnd2_ with the # operation specified by _operator_. The operands are first coerced into # the proper format by calling the block. def evalBinaryOperation(opnd1, operator, opnd2) case operator when '>' return yield(opnd1) > yield(opnd2) when '>=' return yield(opnd1) >= yield(opnd2) when '=' return yield(opnd1) == yield(opnd2) when '<' return yield(opnd1) < yield(opnd2) when '<=' return yield(opnd1) <= yield(opnd2) when '!=' return yield(opnd1) != yield(opnd2) else raise "Operator error" end end # Force the _val_ into a boolean value. def coerceBoolean(val, expr) # First the obvious ones. return val if val.class == TrueClass || val.class == FalseClass # An empty String means false, else true. return !val.empty? if val.is_a?(String) # In TJP logic 'non 0' means false. return val != 0 if val.is_a?(Fixnum) || val.is_a?(Bignum) expr.error("Operand #{val} can't be evaluated to true or false.") end # Force the _val_ into a number. In case this fails, an exception is raised. def coerceNumber(val, expr) unless val.is_a?(Fixnum) || val.is_a?(Float) || val.is_a?(Bignum) expr.error("Operand #{val} of type #{val.class} must be a number.") end val end # Force the _val_ into a String. In case this fails, an exception is raised. def coerceString(val, expr) unless val.respond_to?('to_s') expr.error("Operand #{val} of type #{val.class} can't be converted " + "into a string") end val end # Force the _val_ into a String. In case this fails, an exception is raised. def coerceTime(val, expr) unless val.is_a?(TjTime) expr.error("Operand #{val} of type #{val.class} can't be converted " + "into a date") end val end end # This class handles operands that are property attributes. They are # addressed by attribute ID and scenario index. The expression provides the # property reference. class LogicalAttribute < LogicalOperation def initialize(attribute, scenario) @scenario = scenario super end # To evaluate a property attribute we use the Query mechanism to retrieve # the value. def eval(expr) query = expr.query.dup query.scenarioIdx = @scenario.sequenceNo - 1 query.attributeId = @operand1 query.process # The logical expressions are mostly about comparing values. So we use # the sortableResult of the Query. This creates some challenges for load # values, as the user is not accustomed to the internal representation # of those. # Convert nil results into empty Strings if necessary query.result || '' end # Dumps the LogicalOperation as String. If _query_ is nil, the variable # names are shown, otherwise their values. def to_s(query) if query query = query.dup query.scenarioIdx = @scenario.sequenceNo - 1 query.attributeId = @operand1 query.process query.to_s else "#{@scenario.fullId}.#{@operand1}" end end end # This class handles operands that represent flags. The operation evaluates # to true if the property provided by the expression has the flag assigned. class LogicalFlag < LogicalOperation def initialize(opnd) super end # Return true if the property has the flag assigned. def eval(expr) if expr.query.is_a?(Query) # This is used for Project or PTN related Queries expr.query.property['flags', 0].include?(@operand1) else # This is used for Journal objects. expr.query.flags.include?(@operand1) end end def to_s(query) if query if query.is_a?(Query) query.property['flags', 0].include(@operand1) ? 'true' : 'false' else query.flags.include(@operand1) ? 'true' : 'false' end else @operand1 end end end end taskjuggler-3.5.0/lib/taskjuggler/Tj3AppBase.rb0000644000175000017500000001400312614413013020653 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3AppBase.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'optparse' require 'term/ansicolor' require 'taskjuggler/Tj3Config' require 'taskjuggler/RuntimeConfig' require 'taskjuggler/TjTime' require 'taskjuggler/TextFormatter' require 'taskjuggler/MessageHandler' require 'taskjuggler/Log' class TaskJuggler class Tj3AppBase include MessageHandler def initialize # Indent and width of options. The deriving class may has to change # this. @optsSummaryWidth = 22 @optsSummaryIndent = 5 # Show some progress information by default @silent = false @configFile = nil @mandatoryArgs = '' @mininumRubyVersion = '1.9.2' # If stdout is not a tty, we don't use ANSI escape sequences to color # the terminal output. Additionally, we have the --no-color option to # force colors off in case this does not work properly. Term::ANSIColor.coloring = STDOUT.tty? # Make sure the MessageHandler is set to default values. MessageHandlerInstance.instance.reset end def processArguments(argv) @opts = OptionParser.new @opts.summary_width = @optsSummaryWidth @opts.summary_indent = ' ' * @optsSummaryIndent @opts.banner = "#{AppConfig.softwareName} v#{AppConfig.version} - " + "#{AppConfig.packageInfo}\n\n" + "Copyright (c) #{AppConfig.copyright.join(', ')}\n" + " by #{AppConfig.authors.join(', ')}\n\n" + "#{AppConfig.license}\n" + "For more info about #{AppConfig.softwareName} see " + "#{AppConfig.contact}\n\n" + "Usage: #{AppConfig.appName} [options] " + "#{@mandatoryArgs}\n\n" @opts.separator "" @opts.on('-c', '--config ', String, format('Use the specified YAML configuration file')) do |arg| @configFile = arg end @opts.on('--silent', format("Don't show program and progress information")) do @silent = true MessageHandlerInstance.instance.outputLevel = :warning TaskJuggler::Log.silent = true end @opts.on('--no-color', format(<<'EOT' Don't use ANSI contol sequences to color the terminal output. Colors should only be used when spooling to an ANSI terminal. In case the detection fails, you can this option to force colors to be off. EOT )) do Term::ANSIColor::coloring = false end @opts.on('--debug', format('Enable Ruby debug mode')) do $DEBUG = true end yield @opts.on_tail('-h', '--help', format('Show this message')) do puts @opts.to_s quit end @opts.on_tail('--version', format('Show version info')) do puts "#{AppConfig.softwareName} v#{AppConfig.version} - " + "#{AppConfig.packageInfo}" quit end begin files = @opts.parse(argv) rescue OptionParser::ParseError => msg puts @opts.to_s + "\n" error('tj3app_bad_cmd_options', msg.message) end files end def main(argv = ARGV) if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new(@mininumRubyVersion) error('tj3app_ruby_version', 'This program requires at least Ruby version ' + "#{@mininumRubyVersion}!") end # Install signal handler to exit gracefully on CTRL-C. intHandler = Kernel.trap('INT') do begin fatal('tj3app_user_abort', "Aborting on user request!") rescue RuntimeError exit 1 end end retVal = 0 begin args = processArguments(argv) # If DEBUG mode has been enabled, we restore the INT trap handler again # to get Ruby backtrackes. Kernel.trap('INT', intHandler) if $DEBUG unless @silent puts "#{AppConfig.softwareName} v#{AppConfig.version} - " + "#{AppConfig.packageInfo}\n\n" + "Copyright (c) #{AppConfig.copyright.join(', ')}\n" + " by #{AppConfig.authors.join(', ')}\n\n" + "#{AppConfig.license}\n" end @rc = RuntimeConfig.new(AppConfig.packageName, @configFile) begin MessageHandlerInstance.instance.trapSetup = true retVal = appMain(args) MessageHandlerInstance.instance.trapSetup = false rescue TjRuntimeError # We have hit a sitatuation that we can't recover from. A message # was severed via the MessageHandler to inform the user and we now # abort the program. return 1 end rescue Exception => e if e.is_a?(SystemExit) || e.is_a?(Interrupt) # Don't show backtrace on user interrupt unless we are in debug mode. $stderr.puts e.backtrace.join("\n") if $DEBUG 1 else fatal('crash_trap', "#{e}\n#{e.backtrace.join("\n")}\n\n" + "#{'*' * 79}\nYou have triggered a bug in " + "#{AppConfig.softwareName} version #{AppConfig.version}!\n" + "Please see the user manual on how to get this bug fixed!\n" + "http://www.taskjuggler.org/tj3/manual/Reporting_Bugs.html#" + "Reporting_Bugs_and_Feature_Requests\n" + "#{'*' * 79}\n") end end # Exit value in case everything was fine. retVal end private def quit exit 0 end def format(str, indent = nil) indent = @optsSummaryWidth + @optsSummaryIndent + 1 unless indent TextFormatter.new(79, indent).format(str)[indent..-1] end end end taskjuggler-3.5.0/lib/taskjuggler/AlgorithmDiff.rb0000644000175000017500000001601412614413013021502 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = AlgorithmDiff.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This class is an implementation of the classic UNIX diff functionality. It's # based on an original implementation by Lars Christensen, which based his # version on the Perl Algorithm::Diff implementation. This is largly a # from-scratch implementation that tries to have a less intrusive and more # user-friendly interface. But some code fragments are very similar to the # origninal and are copyright (C) 2001 Lars Christensen. class Diff # A Hunk stores all information about a contiguous change of the destination # list. It stores the inserted and deleted values as well as their positions # in the A and B list. class Hunk attr_reader :insertValues, :deleteValues attr_accessor :aIdx, :bIdx # Create a new Hunk. _aIdx_ is the index in the A list. _bIdx_ is the # index in the B list. def initialize(aIdx, bIdx) @aIdx = aIdx # A list of values to be deleted from the A list starting at aIdx. @deleteValues = [] @bIdx = bIdx # A list of values to be inserted into the B list at bIdx. @insertValues = [] end # Has the Hunk any values to insert? def insert? !@insertValues.empty? end # Has the Hunk any values to be deleted? def delete? !@deleteValues.empty? end def to_s str = '' showSeparator = false if insert? && delete? str << "#{aRange}c#{bRange}\n" showSeparator = true elsif insert? str << "#{aIdx}a#{bRange}\n" else str << "#{aRange}d#{bIdx}\n" end @deleteValues.each { |value| str << "< #{value}\n" } str << "---\n" if showSeparator @insertValues.each { |value| str << "> #{value}\n" } str end def inspect puts to_s end private def aRange range(@aIdx + 1, @aIdx + @deleteValues.length) end def bRange range(@bIdx + 1, @bIdx + @insertValues.length) end def range(startIdx, endIdx) if (startIdx == endIdx) "#{startIdx}" else "#{startIdx},#{endIdx}" end end end # Create a new Diff between the _a_ list and _b_ list. def initialize(a, b) @hunks = [] diff(a, b) end # Modify the _values_ list according to the stored diff information. def patch(values) res = values.dup @hunks.each do |hunk| if hunk.delete? res.slice!(hunk.bIdx, hunk.deleteValues.length) end if hunk.insert? res.insert(hunk.bIdx, *hunk.insertValues) end end res end def editScript script = [] @hunks.each do |hunk| if hunk.delete? script << "#{hunk.aIdx + 1}d#{hunk.deleteValues.length}" end if hunk.insert? script << "#{hunk.bIdx + 1}i#{hunk.insertValues.join(',')}" end end script end # Return the diff list as standard UNIX diff output. def to_s str = '' @hunks.each { |hunk| str << hunk.to_s } str end def inspect puts to_s end private def diff(a, b) indexTranslationTable = computeIndexTranslations(a, b) ai = bi = 0 tableLength = indexTranslationTable.length while ai < tableLength do # Check if value from index ai should be included in B. destIndex = indexTranslationTable[ai] if destIndex # Yes, it needs to go to position destIndex. All values from bi to # newIndex - 1 are new values in B, not in A. while bi < destIndex insertElement(ai, bi, b[bi]) bi += 1 end bi += 1 else # No, it's not in B. Put it onto the deletion list. deleteElement(ai, bi, a[ai]) end ai += 1 end # The remainder of the A list has to be deleted. while ai < a.length deleteElement(ai, bi, a[ai]) ai += 1 end # The remainder of the B list are new values. while bi < b.length insertElement(ai, bi, b[bi]) bi += 1 end end def computeIndexTranslations(a, b) aEndIdx = a.length - 1 bEndIdx = b.length - 1 startIdx = 0 indexTranslationTable = [] while (startIdx < aEndIdx && startIdx < bEndIdx && a[startIdx] == b[startIdx]) indexTranslationTable[startIdx] = startIdx startIdx += 1 end while (aEndIdx >= startIdx && bEndIdx >= startIdx && a[aEndIdx] == b[bEndIdx]) indexTranslationTable[aEndIdx] = bEndIdx aEndIdx -= 1 bEndIdx -= 1 end return indexTranslationTable if startIdx >= aEndIdx && startIdx >= bEndIdx links = [] thresholds = [] bHashesToIndicies = reverseHash(b, startIdx, bEndIdx) startIdx.upto(aEndIdx) do |ai| aValue = a[ai] next unless bHashesToIndicies.has_key? aValue k = nil bHashesToIndicies[aValue].each do |bi| if k && (thresholds[k] > bi) && (thresholds[k - 1] < bi) thresholds[k] = bi else k = replaceNextLarger(thresholds, bi, k) end links[k] = [ k == 0 ? nil : links[k - 1], ai, bi ] if k end end if !thresholds.empty? link = links[thresholds.length - 1] while link indexTranslationTable[link[1]] = link[2] link = link[0] end end return indexTranslationTable end def reverseHash(values, startIdx, endIdx) hash = {} startIdx.upto(endIdx) do |i| element = values[i] if hash.has_key?(element) hash[element].insert(0, i) else hash[element] = [ i ] end end hash end def replaceNextLarger(ary, value, high = nil) high ||= ary.length if ary.empty? || value > ary[-1] ary.push value return high end low = 0 while low < high index = (high + low) / 2 found = ary[index] return nil if value == found if value > found low = index + 1 else high = index end end ary[low] = value low end def deleteElement(aIdx, bIdx, value) if @hunks.empty? || @hunks.last.aIdx + @hunks.last.deleteValues.length != aIdx @hunks << (hunk = Hunk.new(aIdx, bIdx)) else hunk = @hunks.last end hunk.deleteValues << value end def insertElement(aIdx, bIdx, value) if @hunks.empty? || @hunks.last.bIdx + @hunks.last.insertValues.length != bIdx @hunks << (hunk = Hunk.new(aIdx, bIdx)) else hunk = @hunks.last end hunk.insertValues << value end end module Diffable def diff(b) Diff.new(self, b) end def patch(diff) diff.patch(self) end end module DiffableString def diff(b) split("\n").extend(Diffable).diff(b.split("\n")) end def patch(hunks) split("\n").extend(Diffable).patch(hunks).join("\n") + "\n" end end taskjuggler-3.5.0/lib/taskjuggler/Log.rb0000644000175000017500000001253212614413013017505 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Log.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'singleton' require 'monitor' require 'term/ansicolor' class TaskJuggler # The Log class implements a filter for segmented execution traces. The # trace messages are filtered based on their segment name and the nesting # level of the segments. The class is a Singleton, so there is only one # instance in the program. class Log < Monitor include Singleton @@level = 0 @@stack = [] @@segments = [] @@silent = true @@progress = 0 @@progressMeter = '' # Set the maximum nesting level that should be shown. Segments with a # nesting level greater than _l_ will be silently dropped. def Log.level=(l) @@level = l end # The trace output can be limited to a list of segments. Messages not in # these segments will be ignored. Messages from segments that are nested # into the shown segments will be shown for the next @@level nested # segments. def Log.segments=(s) @@segments = s end # if +s+ is true, progress information will not be shown. def Log.silent=(s) @@silent = s end # Return the @@silent value. def Log.silent @@silent end # This function is used to open a new segment. +segment+ is the name of # the segment and +message+ is a description of it. def Log.enter(segment, message) return if @@level == 0 @@stack << segment Log.msg { ">> [#{segment}] #{message}" } end # This function is used to close an open segment. To make this mechanism a # bit more robust, it will search the stack of open segments for a segment # with that name and will close all nested segments as well. def Log.exit(segment, message = nil) return if @@level == 0 Log.msg { "<< [#{segment}] #{message}" } if message if @@stack.include?(segment) loop do m = @@stack.pop break if m == segment end end end # Use this function to show a log message within the currently active # segment. The message is the result of the passed block. The block will # only be evaluated if the message will actually be shown. def Log.msg(&block) return if @@level == 0 offset = 0 unless @@segments.empty? showMessage = false @@stack.each do |segment| # If a segment list is used to filter the output, we look for the # first listed segments on the stack. This and all nested segments # will be shown. if @@segments.include?(segment) offset = @@stack.index(segment) showMessage = true break end end return unless showMessage end if @@stack.length - offset < @@level $stderr.puts ' ' * (@@stack.length - offset) + yield(block) end end # Print out a status message unless we are in silent mode. def Log.status(message) return if @@silent $stdout.puts message end # The progress meter can be a textual progress bar or some animated # character sequence that informs the user about ongoing activities. Call # this function to start the progress meter display or to change the info # +text+. The the meter is active the text cursor is always returned to # the start of the same line. Consequent output will overwrite the last # meter text. def Log.startProgressMeter(text) return if @@silent maxlen = 60 text = text.ljust(maxlen) text = text[0..maxlen - 1] if text.length_utf8 > maxlen @@progressMeter = text $stdout.print("#{@@progressMeter} ...\r") end # This sets the progress meter status to "done" and puts the cursor into # the next line again. def Log.stopProgressMeter return if @@silent $stdout.print("#{@@progressMeter} [ " + Term::ANSIColor.green("Done") + " ]\n") end # This function may only be called when Log#startProgressMeter has been # called before. It updates the progress indicator to the next symbol to # visualize ongoing activity. def Log.activity return if @@silent indicator = %w( - \\ | / ) @@progress = (@@progress.to_i + 1) % indicator.length $stdout.print("#{@@progressMeter} [#{indicator[@@progress]}]\r") end # This function may only be called when Log#startProgressMeter has been # called before. It updates the progress bar to the given +percent+ # completion value. The value should be between 0.0 and 1.0. def Log.progress(percent) return if @@silent percent = 0.0 if percent < 0.0 percent = 1.0 if percent > 1.0 @@progress = percent length = 16 full = (length * percent).to_i bar = '=' * full + ' ' * (length - full) label = (percent * 100.0).to_i.to_s + '%' bar[length / 2 - label.length / 2, label.length] = label $stdout.print("#{@@progressMeter} [" + Term::ANSIColor.green("#{bar}") + "]\r") end end end taskjuggler-3.5.0/lib/taskjuggler/UserManual.rb0000644000175000017500000001764412614413013021051 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = UserManual.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'fileutils' require 'taskjuggler/Tj3Config' require 'taskjuggler/RichText/Document' require 'taskjuggler/SyntaxReference' require 'taskjuggler/TjTime' require 'taskjuggler/RichText/FunctionExample' require 'taskjuggler/HTMLElements' class TaskJuggler # This class specializes the RichTextDocument class for the TaskJuggler user # manual. This manual is not only generated from a set of RichTextSnip files, # but also contains the SyntaxReference for the TJP syntax. class UserManual < RichTextDocument include HTMLElements # Create a UserManual object and gather the TJP syntax information. def initialize super # Don't confuse this with RichTextDocument#references @reference = SyntaxReference.new(self) registerFunctionHandler(RichTextFunctionExample.new) @linkTarget = '_top' end def generate(directory) # Directory where to find the manual RichText sources. Must be relative # to lib directory. srcDir = AppConfig.dataDirs('manual')[0] # Directory where to put the generated HTML files. Must be relative to # lib directory. destDir = directory + (directory[-1] == '/' ? '' : '/') # A list of all source files. The order is important. %w( Intro TaskJuggler_2x_Migration Reporting_Bugs Installation How_To_Contribute Getting_Started Tutorial The_TaskJuggler_Syntax Rich_Text_Attributes Software Day_To_Day_Juggling TaskJuggler_Internals fdl ).each do |file| snip = addSnip(File.join(srcDir, file)) snip.cssClass = 'manual' end # Generate the table of contents tableOfContents # Generate the HTML files. generateHTML(destDir) checkInternalReferences FileUtils.cp_r(AppConfig.dataDirs('data/css')[0], destDir) end # Generate the manual in HTML format. _directory_ specifies a directory # where the HTML files should be put. def generateHTML(directory) generateHTMLindex(directory) generateHTMLReference(directory) # The SyntaxReference only generates the reference list when the HTML is # generated. So we have to collect it after the HTML generation. @references.merge!(@reference.internalReferences) super end # Callback function used by the RichTextDocument and KeywordDocumentation # classes to generate the HTML style sheet for the manual pages. def generateStyleSheet XMLElement.new('link', 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => 'css/tjmanual.css') end # Callback function used by the RichTextDocument class to generate the cover # page for the manual. def generateHTMLCover [ DIV.new('align' => 'center', 'style' => 'margin-top:40px; margin-botton:40px') do [ H1.new { "The #{AppConfig.softwareName} User Manual" }, EM.new { 'Project Management beyond Gantt Chart drawing' }, BR.new, B.new do "Copyright (c) #{AppConfig.copyright.join(', ')} " + "by #{AppConfig.authors.join(', ')}" end, BR.new, "Generated on #{TjTime.new.strftime('%Y-%m-%d')}", BR.new, H3.new { "This manual covers #{AppConfig.softwareName} " + "version #{AppConfig.version}." } ] end, BR.new, HR.new, BR.new ] end # Callback function used by the RichTextDocument class to generate the # header for the manual pages. def generateHTMLHeader DIV.new('align' => 'center') do [ H3.new('align' => 'center') do "The #{AppConfig.softwareName} User Manual" end, EM.new('align' => 'center') do 'Project Management beyond Gantt Chart Drawing' end ] end end # Callback function used by the RichTextDocument class to generate the # footer for the manual pages. def generateHTMLFooter DIV.new('align' => 'center', 'style' => 'font-size:10px;') do [ "Copyright (c) #{AppConfig.copyright.join(', ')} by " + "#{AppConfig.authors.join(', ')}.", A.new('href' => AppConfig.contact) do 'TaskJuggler' end, ' is a trademark of Chris Schlaeger.' ] end end # Callback function used by the RichTextDocument and KeywordDocumentation # classes to generate the navigation bars for the manual pages. # _predLabel_: Text for the reference to the previous page. May be nil. # _predURL: URL to the previous page. # _succLabel_: Text for the reference to the next page. May be nil. # _succURL: URL to the next page. def generateHTMLNavigationBar(predLabel, predURL, succLabel, succURL) html = [ BR.new, HR.new ] if predLabel || succLabel # We use a tabel to get the desired layout. html += [ TABLE.new('style' => 'width:90%; margin-left:5%; ' + 'margin-right:5%') do TR.new do [ TD.new('style' => 'text-align:left; width:35%;') do if predLabel # Link to previous page. [ '<< ', A.new('href' => predURL) { predLabel }, ' <<' ] end end, # Link to table of contents TD.new('style' => 'text-align:center; width:30%;') do A.new('href' => 'toc.html') { 'Table Of Contents' } end, TD.new('style' => 'text-align:right; width:35%;') do if succLabel # Link to next page. [ '>> ', A.new('href' => succURL) { succLabel }, ' >>' ] end end ] end end, HR.new ] end html << BR.new html end # Generate the top-level file for the HTML user manual. def generateHTMLindex(directory) html = HTMLDocument.new(:frameset) html.generateHead("The #{AppConfig.softwareName} User Manual", { 'description' => 'A reference and user manual for the ' + 'TaskJuggler project management software.', 'keywords' => 'taskjuggler, manual, reference'}) html.html << FRAMESET.new('cols' => '15%, 85%') do [ FRAMESET.new('rows' => '140,*') do [ FRAME.new('src' => 'alphabet.html', 'name' => 'alphabet'), FRAME.new('src' => 'navbar.html', 'name' => 'navigator') ] end, FRAME.new('src' => 'toc.html', 'name' => 'display') ] end html.write(directory + 'index.html') end private # Create a table of contents that includes both the sections from the # RichText pages as well as the SyntaxReference. def tableOfContents super # Let's call the reference 'Appendix A' @reference.tableOfContents(@toc, 'A') @anchors += @reference.all end # Generate the HTML pages for the syntax reference and a navigation page # with links to all generated pages. def generateHTMLReference(directory) keywords = @reference.all @reference.generateHTMLnavbar(directory, keywords) keywords.each do |keyword| @reference.generateHTMLreference(directory, keyword) end end end end taskjuggler-3.5.0/lib/taskjuggler/TimeSheetReceiver.rb0000644000175000017500000000254412614413013022342 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TimeSheetReceiver.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/SheetReceiver' class TaskJuggler # This class specializes SheetReceiver to process time sheets. class TimeSheetReceiver < SheetReceiver def initialize(appName) super(appName, 'time') @tj3clientOption = 'check-ts' # File name and directory settings. @sheetDir = 'TimeSheets' @templateDir = 'TimeSheetTemplates' @failedMailsDir = "#{@sheetDir}/FailedMails" @failedSheetsDir = "#{@sheetDir}/FailedSheets" @signatureFile = "#{@templateDir}/acceptable_intervals" @logFile = 'timesheets.log' # Regular expression to identify time sheets. @sheetHeader = /^[ ]*timesheet\s([a-z][a-z0-9_]*)\s[0-9\-:+]*\s-\s([0-9]*-[0-9]*-[0-9]*)/ # Regular expression to extract the sheet signature (time period). @signatureFilter = /^[ ]*timesheet\s[a-z][a-z0-9_]*\s([0-9:\-+]*\s-\s[0-9:\-+]*)/ @emailSubject = "Report from %s for %s" end end end taskjuggler-3.5.0/lib/taskjuggler/AppConfig.rb0000644000175000017500000000700512614413013020631 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = AppConfig.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'rbconfig' # This class provides central management of configuration data to an # application. It stores the version number, the name of the application and # the suite it belongs to. It also holds copyright and license information. # These infos have to be set in the main module of the application right after # launch. Then, all other modules can retrieve them from the global instance # as needed. class AppConfig def initialize @@version = '0.0.0' @@packageName = 'unnamed' @@softwareName = 'unnamed' @@packageInfo = 'no info' @@appName = 'unnamed' @@authors = [] @@copyright = [] @@contact = 'not specified' @@license = 'no license' end def AppConfig.version=(version) @@version = version end def AppConfig.version @@version end def AppConfig.packageName=(name) @@packageName = name end def AppConfig.packageName @@packageName end def AppConfig.softwareName=(name) @@softwareName = name end def AppConfig.softwareName @@softwareName end def AppConfig.packageInfo=(info) @@packageInfo = info end def AppConfig.packageInfo @@packageInfo end def AppConfig.appName=(name) @@appName = name end def AppConfig.appName @@appName end def AppConfig.authors=(authors) @@authors = authors end def AppConfig.authors @@authors end def AppConfig.copyright=(copyright) @@copyright = copyright end def AppConfig.copyright @@copyright end def AppConfig.contact=(contact) @@contact = contact end def AppConfig.contact @@contact end def AppConfig.license=(license) @@license = license end def AppConfig.license @@license end def AppConfig.dataDirs(baseDir = 'data') dirs = dataSearchDirs(baseDir) # Remove non-existing directories from the list again dirs.delete_if do |dir| !File.exists?(dir.untaint) end dirs end def AppConfig.dataSearchDirs(baseDir = 'data') rubyLibDir = RbConfig::CONFIG['rubylibdir'] rubyBaseDir, versionDir = rubyLibDir.scan(/(.*\/)(.*)/)[0] dirs = [] if ENV['TASKJUGGLER_DATA_PATH'] ENV['TASKJUGGLER_DATA_PATH'].split(':').each do |path| dirs << path + "/#{baseDir}/" end end # Find the data dir relative to the source of this file. This should # always work. dirs << File.join(File.dirname(__FILE__), '..', '..', baseDir) # This hopefully works for all setups. Otherwise we have to add more # alternative pathes. # This one is for RPM based distros like Novell dirs << rubyBaseDir + "gems/" + versionDir + '/gems/' \ + @@packageName + '-' + @@version + "/#{baseDir}/" # This one is for Debian based distros dirs << rubyLibDir + '/gems/' \ + @@packageName + '-' + @@version + "/#{baseDir}/" dirs end def AppConfig.dataFiles(fileName) files = [] dirs = dataDirs dirs.each { |d| files << d + fileName if File.exist?(d + fileName) } files end def AppConfig.dataFile(fileName) dirs = dataDirs dirs.each { |d| return d + fileName if File.exist?(d + fileName) } nil end end taskjuggler-3.5.0/lib/taskjuggler/AccountCredit.rb0000644000175000017500000000121712614413013021511 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = AccountCredit.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class AccountCredit attr_reader :date, :description, :amount def initialize(date, description, amount) @date = date @description = description @amount = amount end end end taskjuggler-3.5.0/lib/taskjuggler/VimSyntax.rb0000644000175000017500000001642412614413013020732 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = VimSyntax.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/SyntaxReference' class TaskJuggler # This class is a generator for vim (http://www.vim.org) TaskJuggler syntax # highlighting files. class VimSyntax # Create a generator object. def initialize # Create a syntax reference for all current keywords. @reference = SyntaxReference.new(nil, true) @properties = [] @attributes = [] @optionBlocks = [] @reference.keywords.each_value do |kw| if kw.isProperty? @properties << kw else @attributes << kw end if !kw.optionalAttributes.empty? @optionBlocks << kw end end @file = nil end # Generate the vim syntax file into _file_. def generate(file) @file = File.open(file, 'w') header setLocal keywords matches regions highlights @file.close end private def header # Generate the header section. Mostly consists of comments and a check # if we have source the syntax file already. @file.write <<"EOT" " Vim syntax file " Language: TaskJuggler " Maintainer: TaskJuggler Developers " Last Change: #{Time.now} " This file was automatically generated by VimSyntax.rb if exists("b:current_syntax") finish endif EOT end def setLocal cinwords = [] @optionBlocks.each { |kw| cinwords += kw.names } cinwords.uniq!.sort! @file.write <<'EOT' setlocal softtabstop=2 setlocal cindent shiftwidth=2 setlocal tabstop=2 setlocal expandtab setlocal cinoptions=g0,t0,+0,(0,c0,C1,n-2 EOT @file.write "setlocal cinwords=#{cinwords.join(',')}\n" @file.write <<'EOT' setlocal cinkeys=0{,0},!^F,o,O setlocal cindent EOT end def regions @optionBlocks.each do |kw| single = kw.names.length == 1 kw.names.each do |name| normalizedName = "#{normalize(kw.keyword)}" + "#{single ? '' : "_#{name}"}" tag = name == 'supplement' ? kw.keyword.gsub(/\./, ' ') : name @file.write "syn region tjpblk_#{normalizedName}" + " start=/^\\s*#{tag}\\s.*{\\s*$/ end=/^\\s*}\\s*$/ transparent" # We allow properties and special attributes to be folded. foldable = %w( task.timesheet project ) @file.write " fold" if @properties.include?(kw) || foldable.include?(kw.keyword) # The header is part of the region. So we must make sure that common # parameters and the property/attribute name are contained as well. @file.write " contains=@tjpcommon,tjp_#{normalizedName}" kw.optionalAttributes.each do |oa| tag = normalize(oa.keyword) @file.write ",tjp_#{tag}" if !oa.optionalAttributes.empty? # Option blocks may be contained as block or non-block. @file.write ",tjpblk_#{tag}" end end if name == 'supplement' @file.write(',tjp_supplement') end if !kw.globalScope? # Every region but a property and 'project' is contained. @file.write " contained" end @file.puts end end @file.write <<'EOT' syn region tjpblk_macro start=/macro\s\+\h\w*\s*\[/ end=/\]$/ transparent fold contains=ALL syn region tjpstring start=/"/ skip=/\\"/ end=/"/ syn region tjpstring start=/'/ skip=/\\'/ end=/'/ syn region tjpstring start=/\s-8<-$/ end=/^\s*->8-/ fold syn region tjpmlcomment start=+/\*+ end=+\*/+ syn sync fromstart set foldmethod=syntax EOT end def keywords %w( macro project supplement ).each do |kw| @file.puts "syn keyword tjp_#{kw} #{kw} contained" end @file.puts # Property keywords @properties.each do |kw| single = kw.names.length == 1 kw.names.each do |name| # Ignore the 'supplement' entries. They are not real properties. next if name == 'supplement' @file.puts "syn keyword tjp_#{normalize(kw.keyword)}" + "#{single ? '' : "_#{name}"} #{name} contained" @file.puts "hi def link tjp_#{normalize(kw.keyword)}" + "#{single ? '' : "_#{name}"} Function" end end @file.puts # Attribute keywords @attributes.each do |kw| next if %w( resourcereport taskreport textreport ).include?(kw.keyword) single = kw.names.length == 1 kw.names.each do |name| break if [ '%', '(', '~', 'include', 'macro', 'project', 'supplement' ].include?(name) @file.puts "syn keyword tjp_#{normalize(kw.keyword)}" + "#{single ? '' : "_#{name}"} #{name}" + "#{kw.globalScope? && !@optionBlocks.include?(kw) ? '' : ' contained'}" @file.puts "hi def link tjp_#{normalize(kw.keyword)}" + "#{single ? '' : "_#{name}"} Type" end end @file.puts end def matches @file.write <<'EOT' syn match tjparg contained /\${.*}/ syn match tjpcomment /#.*$/ syn match tjpcomment "//.*$" syn match tjpinclude /include.*$/ syn match tjpnumber /\s[-+]\?\d\+\(\.\d\+\)\?\([hdwmy]\|min\)\?/ syn match tjpdate /\s\d\{4}-\d\{1,2}-\d\{1,2}\(-\d\{1,2}:\d\{1,2}\(:\d\{1,2}\)\?\(-[-+]\?\d\{4}\)\?\)\?/ syn match tjptime /\s\d\{1,2}:\d\d\(:\d\d\)\?/ syn cluster tjpcommon contains=tjpcomment,tjpdate,tjptime,tjpstring,tjpnumber EOT end def highlights @file.write <<'EOT' hi def link tjp_macro PreProc hi def link tjp_supplement Function hi def link tjp_project Function hi def link tjpproperty Function hi def link tjpattribute Type hi def link tjparg Special hi def link tjpstring String hi def link tjpcomment Comment hi def link tjpmlcomment Comment hi def link tjpinclude Include hi def link tjpdate Constant hi def link tjptime Constant hi def link tjpnumber Number let b:current_syntax = "tjp" " Support running tj3 from within vim. Just type ':make your_project.tjp' to " activate it. set makeprg=tj3\ --silent " Support browsing the man page by typing Shift-k while having the cursor over " any syntax keyword set keywordprg=tj3man " Remap Ctrl-] to show full ID of property defined in the current " line. This requires a current ctags file (generated by 'tagfile' " report') to be present in the directory where vim was started. map :call ShowFullID() function! ShowFullID() let linenumber = line(".") let filename = bufname("%") execute "!grep '".filename."\t".linenumber.";' tags|cut -f 1" endfunction augroup TaskJugglerSource " Remove all trailing white spaces from line ends when saving files " Note: This overwrites the s mark. autocmd BufWritePre *.tj[ip] mark s | %s/\s\+$//e | normal `s augroup END EOT end def normalize(str) str.gsub(/\./, '_') end end end taskjuggler-3.5.0/lib/taskjuggler/TjpExample.rb0000644000175000017500000000700012614413013021027 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TjpExample.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'stringio' class TaskJuggler # This class can extract snippets from an annotated TJP syntax file. The file # does not care about the TJP syntax but the annotation lines must start with # a '#' character at the begining of the line. The snippets must be enclosed # by a starting line and an ending line. Each snippet must have a unique tag # that can be used to retrieve the specific snip. # # The following line starts a snip called 'foo': # # *** EXAMPLE: foo + # # The following line ends a snip called 'foo': # # *** EXAMPLE: foo - # # The function TjpExample#to_s() can be used to get the content of the snip. # It takes the tag name as optional parameter. If no tag is specified, the # full example without the annotation lines is returned. class TjpExample # Create a new TjpExample object. def initialize @snippets = { } # Here we will store the complete example. @snippets['full text'] = [] @file = nil end # Use this function to process the file called _fileName_. def open(fileName) @file = File.open(fileName, 'r') process @file.close end # Use this function to process the String _text_. def parse(text) @file = StringIO.new(text) process end # This method returns the snip identified by _tag_. def to_s(tag = nil) tag = 'full text' unless tag return nil unless @snippets[tag] s = '' @snippets[tag].each { |l| s << l } s end private def process # This mark identifies the annotation lines. mark = '# *** EXAMPLE: ' # We need this to remember what snippets are currently active. snippetState = { } # Now process the file or String line by line. @file.each_line do |line| if line[0, mark.length] == mark # We've found an annotation line. Get the tag and indicator. dum, dum, dum, tag, indicator = line.split if indicator == '+' # Start a new snip if snippetState[tag] raise "Snippet #{tag} has already been started" end snippetState[tag] = true elsif indicator == '-' # Stop an existing snip unless snippetState[tag] raise "Snippet #{tag} has not yet been started" end snippetState[tag] = false else raise "Bad indicator #{indicator}. Must be '+' or '-': #{line}" end else # Process the regular lines and add them to all currently active # snippets. snippetState.each do |t, state| if state # Create a new snip buffer if it does not yet exist. @snippets[t] = [] unless @snippets[t] # Add the line. @snippets[t] << line end end # Add all lines to this buffer. @snippets['full text'] << line end end # Remove empty lines at end of all snips @snippets.each_value do |snip| snip.delete_at(-1) if snip[-1] == "\n" end end end end taskjuggler-3.5.0/lib/taskjuggler/TjTime.rb0000644000175000017500000003725112614413013020165 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TjTime.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'time' require 'date' class TaskJuggler # The TjTime class extends the original Ruby class Time with lots of # TaskJuggler specific additional functionality. This is mostly for handling # time zones. class TjTime attr_reader :time # The number of days per month. Leap years are taken care of separately. MON_MAX = [ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ] # Initialize @@tz with the current time zone if it is set. @@tz = ENV['TZ'] # call-seq: # TjTime() -> TjTime (now) # TjTime(tjtime) -> TjTime # TjTime(time, timezone) -> TjTime # TjTime(str) -> TjTime # TjTime(secs) -> TjTime # # The constructor is overloaded and accepts 4 kinds of arguments. If _t_ # is a Time object it's assumed to be in local time. If it's a string, it # is parsed as a date. Or else it is interpreted as seconds after Epoch. def initialize(t = nil) case t when nil @time = Time.now when Time @time = t when TjTime @time = t.time when String parse(t) when Array @time = Time.mktime(*t) else @time = Time.at(t) end end # Check if +zone+ is a valid time zone. def TjTime.checkTimeZone(zone) return true if zone == 'UTC' # Valid time zones must be of the form 'Region/City' return false unless zone.include?('/') # Save curent value of TZ tz = ENV['TZ'] ENV['TZ'] = zone newZone = Time.new.zone # If the time zone is valid, the OS can convert a zone like # 'America/Denver' into 'MST'. Unknown time zones are either not # converted or cause a fallback to UTC. # Since glibc 2.10 Time.new.zone only return the region for illegal # zones instead of the full zone string like it does on earlier # versions. region = zone[0..zone.index('/') - 1] res = (newZone != zone && newZone != region && newZone != 'UTC') # Restore TZ if it was set earlier. if tz ENV['TZ'] = tz else ENV.delete('TZ') end res end # Set a new active time zone. _zone_ must be a valid String known to the # underlying operating system. def TjTime.setTimeZone(zone) unless zone && TjTime.checkTimeZone(zone) raise "Illegal time zone #{zone}" end oldTimeZone = @@tz @@tz = zone ENV['TZ'] = zone oldTimeZone end # Return the name of the currently active time zone. def TjTime.timeZone @@tz end # Align the date to a time grid. The grid distance is determined by _clock_. def align(clock) TjTime.new((localtime.to_i / clock) * clock) end # Return the time object in UTC. def utc TjTime.new(@time.dup.gmtime) end # Returns the total number of seconds of the day. The time is assumed to be # in the time zone specified by _tz_. def secondsOfDay(tz = nil) lt = localtime (lt.to_i + lt.gmt_offset) % (60 * 60 * 24) end # Add _secs_ number of seconds to the time. def +(secs) TjTime.new(@time.to_i + secs) end # Substract _arg_ number of seconds or return the number of seconds between # _arg_ and this time. def -(arg) if arg.is_a?(TjTime) @time - arg.time else TjTime.new(@time.to_i - arg) end end # Convert the time to seconds since Epoch and return the module of _val_. def %(val) @time.to_i % val end # Return true if time is smaller than _t_. def <(t) return false unless t @time < t.time end # Return true if time is smaller or equal than _t_. def <=(t) return false unless t @time <= t.time end # Return true if time is larger than _t_. def >(t) return true unless t @time > t.time end # Return true if time is larger or equal than _t_. def >=(t) return true unless t @time >= t.time end # Return true if time and _t_ are identical. def ==(t) return false unless t @time == t.time end # Coparison operator for time with another time _t_. def <=>(t) return -1 unless t @time <=> t.time end # Iterator that executes the block until time has reached _endDate_ # increasing time by _step_ on each iteration. def upto(endDate, step = 1) t = @time while t < endDate.time yield(TjTime.new(t)) t += step end end # Normalize time to the beginning of the current hour. def beginOfHour sec, min, hour, day, month, year = localtime.to_a sec = min = 0 TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Normalize time to the beginning of the current day. def midnight sec, min, hour, day, month, year = localtime.to_a sec = min = hour = 0 TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Normalize time to the beginning of the current week. _startMonday_ # determines whether the week should start on Monday or Sunday. def beginOfWeek(startMonday) t = localtime.to_a # Set time to noon, 12:00:00 t[0, 3] = [ 0, 0, 12 ] weekday = t[6] t.slice!(6, 4) t.reverse! # Substract the number of days determined by the weekday t[6] and set time # to midnight of that day. (TjTime.new(Time.local(*t)) - (weekday - (startMonday ? 1 : 0)) * 60 * 60 * 24).midnight end # Normalize time to the beginning of the current month. def beginOfMonth sec, min, hour, day, month, year = localtime.to_a sec = min = hour = 0 day = 1 TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Normalize time to the beginning of the current quarter. def beginOfQuarter sec, min, hour, day, month, year = localtime.to_a sec = min = hour = 0 day = 1 month = ((month - 1) % 3 ) + 1 TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Normalize time to the beginning of the current year. def beginOfYear sec, min, hour, day, month, year = localtime.to_a sec = min = hour = 0 day = month = 1 TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Return a new time that is _hours_ later than time. def hoursLater(hours) TjTime.new(@time + hours * 3600) end # Return a new time that is 1 hour later than time. def sameTimeNextHour hoursLater(1) end # Return a new time that is 1 day later than time but at the same time of # day. def sameTimeNextDay sec, min, hour, day, month, year = localtime.to_a if (day += 1) > lastDayOfMonth(month, year) day = 1 if (month += 1) > 12 month = 1 year += 1 end end TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Return a new time that is 1 week later than time but at the same time of # day. def sameTimeNextWeek sec, min, hour, day, month, year = localtime.to_a if (day += 7) > lastDayOfMonth(month, year) day -= lastDayOfMonth(month, year) if (month += 1) > 12 month = 1 year += 1 end end TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Return a new time that is 1 month later than time but at the same time of # day. def sameTimeNextMonth sec, min, hour, day, month, year = localtime.to_a monMax = month == 2 && leapYear?(year) ? 29 : MON_MAX[month] if (month += 1) > 12 month = 1 year += 1 end day = monMax if day >= lastDayOfMonth(month, year) TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Return a new time that is 1 quarter later than time but at the same time of # day. def sameTimeNextQuarter sec, min, hour, day, month, year = localtime.to_a if (month += 3) > 12 month -= 12 year += 1 end TjTime.new([ year, month, day, hour, min, sec, 0 ]) end # Return a new time that is 1 year later than time but at the same time of # day. def sameTimeNextYear sec, min, hour, day, month, year = localtime.to_a year += 1 TjTime.new([ year, month, day, hour, min, sec, 0]) end # Return the start of the next _dow_ day of week after _date_. _dow_ must # be 0 for Sundays, 1 for Mondays and 6 for Saturdays. If _date_ is a # Tuesday and _dow_ is 5 (Friday) the date of next Friday 0:00 will be # returned. If _date_ is a Tuesday and _dow_ is 2 (Tuesday) the date of # the next Tuesday will be returned. def nextDayOfWeek(dow) raise "Day of week must be 0 - 6." unless dow >= 0 && dow <= 6 d = midnight.sameTimeNextDay currentDoW = d.strftime('%w').to_i 1.upto((dow + 7 - currentDoW) % 7) { |i| d = d.sameTimeNextDay } d end # Return the number of hours between this time and _date_. The result is # always rounded up. def hoursTo(date) t1, t2 = order(date) ((t2 - t1) / 3600).ceil end # Return the number of days between this time and _date_. The result is # always rounded up. def daysTo(date) countIntervals(date, :sameTimeNextDay) end # Return the number of weeks between this time and _date_. The result is # always rounded up. def weeksTo(date) countIntervals(date, :sameTimeNextWeek) end # Return the number of months between this time and _date_. The result is # always rounded up. def monthsTo(date) countIntervals(date, :sameTimeNextMonth) end # Return the number of quarters between this time and _date_. The result is # always rounded up. def quartersTo(date) countIntervals(date, :sameTimeNextQuarter) end # Return the number of years between this time and _date_. The result is # always rounded up. def yearsTo(date) countIntervals(date, :sameTimeNextYear) end # This function is just a wrapper around Time.strftime(). In case @time is # nil, it returns 'unkown'. def to_s(format = nil, tz = nil) return 'unknown' if @time.nil? t = tz == 'UTC' ? gmtime : localtime if format.nil? fmt = '%Y-%m-%d-%H:%M' + (@time.sec == 0 ? '' : ':%S') + '-%z' else # Handle TJ specific extensions to the strftime format. fmt = format.sub(/%Q/, "#{((t.mon - 1) / 3) + 1}") end # Always report values in local timezone t.strftime(fmt) end # Return the seconds since Epoch. def to_i localtime.to_i end def to_a localtime.to_a end def strftime(format) localtime.strftime(format) end # Return the day of the week. 0 for Sunday, 1 for Monday and so on. def wday localtime.wday end # Return the hours of the day (0..23) def hour localtime.hour end # Return the day of the month (1..n). def day localtime.day end # Return the month of the year (1..12) def month localtime.month end alias mon month # Return the year. def year localtime.year end private def parse(t) year, month, day, time, zone = t.split('-', 5) # Check the year if year year = year.to_i if year < 1970 || year > 2035 raise TjException.new, "Year #{year} out of range (1970 - 2035)" end else raise TjException.new, "Year not specified" end # Check the month if month month = month.to_i if month < 1 || month > 12 raise TjException.new, "Month #{month} out of range (1 - 12)" end else raise TjException.new, "Month not specified" end # Check the day if day day = day.to_i maxDay = [ 0, 31, Date.gregorian_leap?(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ] if month < 1 || month > maxDay[month] raise TjException.new, "Day #{day} out of range (1 - #{maxDay[month]})" end else raise TjException.new, "Day not specified" end # The time is optional. Will be expanded to 00:00:00. if time hour, minute, second = time.split(':') # Check hour if hour hour = hour.to_i if hour < 0 || hour > 23 raise TjException.new, "Hour #{hour} out of range (0 - 23)" end else raise TjException.new, "Hour not specified" end if minute minute = minute.to_i if minute < 0 || minute > 59 raise TjException.new, "Minute #{minute} out of range (0 - 59)" end else raise TjException.new, "Minute not specified" end # Check sencond. This value is optional and defaults to 0. if second second = second.to_i if second < 0 || second > 59 raise TjException.new, "Second #{second} out of range (0 - 59)" end else second = 0 end else hour = minute = second = 0 end # The zone is optional and defaults to the current time zone. if zone if zone[0] != ?- && zone[0] != ?+ raise TjException.new, "Time zone adjustment must be prefixed by " + "+ or -, not #{zone[0]}" end if zone.length != 5 raise TjException.new, "Time zone adjustment must use (+/-)HHMM format" end @time = Time.utc(year, month, day, hour, minute, second) sign = zone[0] == ?- ? 1 : -1 tzHour = zone[1..2].to_i if tzHour < 0 || tzHour > 12 raise TjException.new, "Time zone adjustment hour out of range " + "(0 - 12) but is #{tzHour}" end tzMinute = zone[3..4].to_i if tzMinute < 0 || tzMinute > 59 raise TjException.new, "Time zone adjustment minute out of range " + "(0 - 59) but is #{tzMinute}" end @time += sign * (tzHour * 3600 + tzMinute * 60) else @time = Time.mktime(year, month, day, hour, minute, second) end end def order(date) self < date ? [ self, date ] : [ date, self ] end def countIntervals(date, stepFunc) i = 0 t1, t2 = order(date) while t1 < t2 t1 = t1.send(stepFunc) i += 1 end i end def lastDayOfMonth(month, year) month == 2 && leapYear?(year) ? 29 : MON_MAX[month] end def leapYear?(year) case when year % 400 == 0 true when year % 100 == 0 false else year % 4 == 0 end end def gmtime if @time.utc? # @time is already in the right zone (UTC) @time else # To convert a Time object from local time to UTC. @time.dup.gmtime end end def localtime if @time.utc? if @@tz == 'UTC' # @time is already in the right zone (UTC) @time else @time.dup.localtime end elsif @@tz == 'UTC' # @time is not in UTC, so convert it to local time. @time.dup.gmtime else # To convert a Time object from one local time to another, we need to # conver to UTC first and then to the new local time. @time.dup.gmtime.localtime end end end end taskjuggler-3.5.0/lib/taskjuggler/ShiftScenario.rb0000644000175000017500000000211612614413013021522 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ShiftScenario.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/ScenarioData' class TaskJuggler # This class handles the scenario specific features of a Shift object. class ShiftScenario < ScenarioData def initialize(resource, scenarioIdx, attributes) super end # Returns true if the shift has working time defined for the _date_. def onShift?(date) a('workinghours').onShift?(date) end def replace? a('replace') end # Returns true if the shift has a vacation defined for the _date_. def onLeave?(date) a('leaves').each do |leave| if leave.interval.contains?(date) return true end end false end end end taskjuggler-3.5.0/lib/taskjuggler/HTMLDocument.rb0000644000175000017500000000554712614413013021237 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = HTMLDocument.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/XMLDocument' require 'taskjuggler/HTMLElements' class TaskJuggler # HTMLDocument objects are a specialized form of XMLDocument objects. All # mandatory elements of a proper HTML document will be added automatically # upon object creation. class HTMLDocument < XMLDocument include HTMLElements attr_reader :html # When creating a HTMLDocument the caller can specify the type of HTML that # will be used. The constructor then generates the proper XML declaration # for it. :strict, :transitional and :frameset are supported for _docType_. def initialize(docType = :html5, &block) super(&block) unless docType == :html5 @elements << XMLBlob.new('') case docType when :strict dtdRef = 'Strict' url = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd' when :transitional dtdRef = 'Transitional' url = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd' when :frameset dtdRef = 'Frameset' url = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd' else raise "Unsupported docType" end @elements << XMLBlob.new( '") else @elements << XMLBlob.new('') end @elements << XMLComment.new('This file has been generated by ' + "#{AppConfig.appName} v#{AppConfig.version}") attrs = { 'xml:lang' => 'en', 'lang' => 'en' } attrs['xmlns'] ='http://www.w3.org/1999/xhtml' unless docType == :html5 @elements << (@html = HTML.new(attrs)) end # Generate the 'head' section of an HTML page. def generateHead(title, metaTags = {}, blob = nil) @html << HEAD.new { e = [ TITLE.new { title }, META.new({ 'http-equiv' => 'Content-Type', 'content' => 'text/html; charset=utf-8' }), # Ugly hack to force IE into IE-9 mode. META.new({ 'http-equiv' => 'X-UA-Compatible', 'content' => 'IE=9' }) ] # Include optional meta tags. metaTags.each do |name, content| e << META.new({ 'name' => name, 'content' => content }) end # Add a raw HTML blob into the header if provided. e << XMLBlob.new(blob) if blob e } end end end taskjuggler-3.5.0/lib/taskjuggler/TextFormatter.rb0000644000175000017500000001275512614413013021603 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TextFormatter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/UTF8String' class TaskJuggler # This class provides a simple text block formatting function. Plain text # can be indented and limited to a given text width. class TextFormatter attr_accessor :indentation, :width, :firstLineIndent def initialize(width = 80, indentation = 0, firstLineIndent = nil) # The width of the text including the indent. @width = width # The indent for the first line of a paragraph @firstLineIndent = firstLineIndent || indentation # The indent for other lines. @indentation = indentation end # Add @indentation number of spaces at the beginning of each line. The # first line will be indented by @firstLineIndent. Lines that are longer # than @width will be clipped. def indent(str) out = '' # Indentation to be used for the currently processed line. It will be # set to nil if it was inserted already. indentBuf = ' ' * @firstLineIndent linePos = 0 # Process the input String from start to finish. str.each_utf8_char do |c| if c == "\n" # To prevent trailing white spaces we only insert a line break # instead of the indent buffer. if indentBuf out += "\n" end # The indent buffer for the next line. indentBuf = "\n" + ' ' * @indentation else # If we still have an indent buffer, we need to insert it first. if indentBuf out += indentBuf linePos = indentBuf.delete("\n").length indentBuf = nil end # Discard all characters that extend of the requested line width. if linePos < @width out << c linePos += 1 end end end # Always end with a line break out += "\n" unless out[-1] == "\n" out end # Format the String _str_ according to the class settings. def format(str) # The resulting String. @out = '' # The column of the last character of the current line. @linePos = 0 # A buffer for the currently processed word. @wordBuf = '' # True of we are at the beginning of a line. @beginOfLine = true # A buffer for the indentation to be used for the next line. @indentBuf = ' ' * @firstLineIndent # The status of the state machine. state = :beginOfParagraph # Process the input String from start to finish. str.each_utf8_char do |c| case state when :beginOfParagraph # We are currently a the beginning of a new paragraph. if c == ' ' || c == "\n" # ignore it else # A new word started. @wordBuf << c state = :inWord end when :inWord # We are in the middle of processing a word. if c == ' ' || c == "\n" # The word has ended. appendWord state = c == ' ' ? :betweenWords : :betweenWordsOrLines elsif c == "\r" # CR is used to start a new line but without starting a new # paragraph. appendWord @indentBuf = "\n" + ' ' * @firstLineIndent @beginOfLine = true state = :betweenWords else # Add the character to the word buffer. @wordBuf << c end when :betweenWords # We are in between words. if c == ' ' # ignore it elsif c == "\n" state = :betweenWordsOrLines else # A new word started. @wordBuf << c state = :inWord end when :betweenWordsOrLines if c == "\n" # The word break is really a paragraph break. @indentBuf = "\n\n" + ' ' * @firstLineIndent @beginOfLine = true state = :beginOfParagraph elsif c == ' ' state = :betweenWords else @wordBuf << c state = :inWord end else raise "Unknown state in state machine: #{state}" end end # Add any still pending word. appendWord # Always end with a line break @out += "\n" unless @out[-1] == "\n" @out end private def appendWord # Ignore empty words. wordLength = @wordBuf.length return unless wordLength > 0 # If the word does not fit into the current line anymore, we have to # start a new line. @beginOfLine = true if @linePos + 1 + wordLength > @width if @beginOfLine # Insert the content of the @indentBuf and reset @linePos and # @indentBuf. @out += @indentBuf @linePos = @indentBuf.delete("\n").length @indentBuf = "\n" + ' ' * @indentation else # Insert a space to separate the words. @out += ' ' @linePos += 1 end # Append the word and reset the @wordBuf. @out += @wordBuf @wordBuf = '' @linePos += wordLength @beginOfLine = false if @beginOfLine end end end taskjuggler-3.5.0/lib/taskjuggler/XMLDocument.rb0000644000175000017500000000325112614413013021121 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = XMLDocument.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/XMLElement' class TaskJuggler # This class provides a rather simple XML document generator. It provides # basic features to create a tree of XMLElements and to generate a XML String # or file. It's much less powerful than REXML but provides a more efficient # API to create XMLDocuments with lots of attributes. class XMLDocument # Create an empty XML document. def initialize(&block) @elements = block ? yield(block) : [] end # Add a top-level XMLElement. def <<(arg) if arg.is_a?(Array) @elements += arg.flatten elsif arg.nil? # do nothing elsif arg.is_a?(XMLElement) @elements << arg else raise ArgumentError, "Unsupported argument of type #{arg.class}: " + "#{arg.inspect}" end end # Produce the XMLDocument as String. def to_s str = '' @elements.each do |element| str << element.to_s(0) end str end # Write the XMLDocument to the specified file. def write(filename) f = filename == '.' ? $stdout : File.new(filename.untaint, 'w') @elements.each do |element| f.puts element.to_s(0) end f.close unless f == $stdout end end end taskjuggler-3.5.0/lib/taskjuggler/KeywordDocumentation.rb0000644000175000017500000006024112614413013023142 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = KeywordDocumentation.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'term/ansicolor' require 'taskjuggler/MessageHandler' require 'taskjuggler/HTMLDocument' require 'taskjuggler/RichText' require 'taskjuggler/TjpExample' require 'taskjuggler/TextFormatter' require 'taskjuggler/Project' class TaskJuggler # The textual TaskJuggler Project description consists of many keywords. The # parser has built-in support to document the meaning and usage of these # keywords. Most keywords are unique, but there can be exceptions. To # resolve ambiguoties the keywords can be prefixed by a scope. The scope is # usually a keyword that describes the context that the ambiguous keyword is # used in. This class stores the keyword, the corresponding # TextParser::Pattern and the context that the keyword is used in. It also # stores information such as the list of optional attributes (keywords used # in the context of the current keyword) and whether the keyword is scenario # specific or not. class KeywordDocumentation include HTMLElements include Term::ANSIColor include MessageHandler attr_reader :keyword, :names, :pattern, :references, :optionalAttributes attr_accessor :contexts, :scenarioSpecific, :inheritedFromProject, :inheritedFromParent, :predecessor, :successor # Construct a new KeywordDocumentation object. _rule_ is the # TextParser::Rule and _pattern_ is the corresponding TextParser::Pattern. # _syntax_ is an expanded syntax representation of the _pattern_. _args_ # is an Array of TextParser::TokenDoc that describe the arguments of the # _pattern_. _optAttrPatterns_ is an Array with references to # TextParser::Patterns that are optional attributes to this keyword. def initialize(rule, pattern, syntax, args, optAttrPatterns, manual) @rule = rule @pattern = pattern # The unique identifier. Usually the attribute or property name. To # disambiguate a . can be added. @keyword = pattern.keyword # Similar to @keyword, but without the scope. Since there could be # several, this is an Array of String objects. @names = [] @syntax = syntax @args = args @manual = manual # Hash that maps patterns of optional attributes to a boolean value. It # is true if the pattern is a scenario specific attribute. @optAttrPatterns = optAttrPatterns # The above hash is later converted into a list that points to the # keyword documentation of the optional attribute. @optionalAttributes = [] @scenarioSpecific = false @inheritedFromProject= false @inheritedFromParent = false @contexts = [] @seeAlso = [] # The following are references to the neighboring keyword in an # alphabetically sorted list. @predecessor = nil @successor = nil # Array to collect all references to other RichText objects. @references = [] end # Returns true of the KeywordDocumentation is documenting a TJP property # (task, resources, etc.). A TJP property can be nested. def isProperty? # I haven't found a good way to automatically detect all the various # report types as properties. They don't directly include themselves as # attributes. return true if %w( accountreport export nikureport resourcereport taskreport textreport timesheetreport statussheetreport).include?(keyword) @optionalAttributes.include?(self) end # Returns true of the keyword can be used outside of any other keyword # context. def globalScope? return true if @contexts.empty? @contexts.each do |context| return true if context.keyword == 'properties' end false end # Post process the class member to set cross references to other # KeywordDocumentation items. def crossReference(keywords, rules) # Get the attribute or property name of the Keyword. This is not unique # like @keyword since it's got no scope. @pattern.terminalTokens(rules).each do |tok| # Ignore patterns that don't have a real name. break if tok[0] == '{' @names << tok[0] end # Some arguments are references to other patterns. The current keyword # is added as context to such patterns. @args.each do |arg| if arg.pattern && checkReference(arg.pattern) kwd = keywords[arg.pattern.keyword] kwd.contexts << self unless kwd.contexts.include?(self) end end # Optional attributes are treated similarly. In addition we add them to # the @optionalAttributes list of this keyword. @optAttrPatterns.each do |pattern, scenarioSpecific| next unless checkReference(pattern) # Check if all the attributes are documented. We ignore undocumented # keywords that are deprecated or removed. if (kwd = keywords[pattern.keyword]).nil? unless [ :deprecated, :removed ].include?(pattern.supportLevel) token = pattern.terminalTokens(rules) $stderr.puts "Keyword #{keyword} has undocumented optional " + "attribute #{token[0]}" end else @optionalAttributes << kwd kwd.contexts << self unless kwd.contexts.include?(self) kwd.scenarioSpecific = true if scenarioSpecific end end # Resolve the seeAlso patterns to keyword references. @pattern.seeAlso.sort.each do |also| if keywords[also].nil? raise "See also reference #{also} of #{@pattern} is unknown" end @seeAlso << keywords[also] end end def computeInheritance(keywords, rules) property = nil @contexts.each do |kwd| if %w( task resource account shift scenario accountreport resourcereport taskreport textreport ). include?(kwd.keyword) property = kwd.keyword break end end if property project = Project.new('id', 'dummy', '1.0') propertySet = case property when 'task' project.tasks when 'resource' project.resources when 'account' project.accounts when 'shift' project.shifts when 'scenario' project.scenarios else project.reports end keyword = @keyword keyword = keyword.split('.')[0] if keyword.include?('.') @inheritedFromProject = propertySet.inheritedFromProject?(keyword) @inheritedFromParent = propertySet.inheritedFromParent?(keyword) end end # Return the keyword name in a more readable form. E.g. 'foo.bar' is # returned as 'foo (bar)'. 'foo' will remain 'foo'. def title kwTokens = @keyword.split('.') if kwTokens.size == 1 title = @keyword else title = "#{kwTokens[0]} (#{kwTokens[1]})" end title end # Return the complete documentation of this keyword as formatted text # string. def to_s tagW = 13 textW = 79 # Top line with multiple elements str = "#{blue('Keyword:')} #{bold(@keyword)}\n\n" if @pattern.supportLevel != :supported msg = supportLevelMessage if [ :deprecated, :removed ].include?(@pattern.supportLevel) && @seeAlso.length > 0 msg += "\n\nPlease use " alsoStr = '' @seeAlso.each do |also| unless alsoStr.empty? alsoStr += ', ' end alsoStr += also.keyword end msg += "#{alsoStr} instead!" end str += red("Warning: #{format(tagW, msg, textW)}\n") end # Don't show further details if the keyword has been removed. return str if @pattern.supportLevel == :removed str += blue('Purpose:') + " #{format(tagW, newRichText(@pattern.doc).to_s, textW)}\n" if @syntax != '[{ }]' str += blue('Syntax:') + " #{format(tagW, @syntax, textW)}\n" str += blue('Arguments:') + " " if @args.empty? str += format(tagW, "none\n", textW) else argStr = '' @args.each do |arg| argText = newRichText(arg.text || "See '#{arg.name}' for details.").to_s if arg.typeSpec.nil? || ("<#{arg.name}>") == arg.typeSpec indent = arg.name.length + 2 argStr += "#{arg.name}: " + "#{format(indent, argText, textW - tagW)}\n" else typeSpec = arg.typeSpec typeSpec[0] = '[' typeSpec[-1] = ']' indent = arg.name.length + typeSpec.size + 3 argStr += "#{arg.name} #{typeSpec}: " + "#{format(indent, argText, textW - tagW)}\n" end end str += indent(tagW, argStr) end str += "\n" end str += blue('Context:') + ' ' if @contexts.empty? str += format(tagW, 'Global scope', textW) else cxtStr = '' @contexts.each do |context| unless cxtStr.empty? cxtStr += ', ' end cxtStr += context.keyword end str += format(tagW, cxtStr, textW) end str += "\n#{blue('Attributes:')} " if @optionalAttributes.empty? str += "none\n\n" else attrStr = '' @optionalAttributes.sort! do |a, b| a.keyword <=> b.keyword end showLegend = false @optionalAttributes.each do |attr| unless attrStr.empty? attrStr += ', ' end attrStr += attr.keyword if attr.scenarioSpecific || attr.inheritedFromProject || attr.inheritedFromParent first = true showLegend = true tag = '[' if attr.scenarioSpecific tag += 'sc' first = false end if attr.inheritedFromProject tag += ':' unless first tag += 'ig' first = false end if attr.inheritedFromParent tag += ':' unless first tag += 'ip' end tag += ']' attrStr += cyan(tag) end end if showLegend attrStr += "\n\n#{cyan('[sc]')} : Attribute is scenario specific" + "\r#{cyan('[ig]')} : " + "Value can be inherited from global setting" + "\r#{cyan('[ip]')} : " + "Value can be inherited from parent property" end str += format(tagW, attrStr, textW) str += "\n" end unless @seeAlso.empty? str += blue('See also:') + " " alsoStr = '' @seeAlso.each do |also| unless alsoStr.empty? alsoStr += ', ' end alsoStr += also.keyword end str += format(tagW, alsoStr, textW) str += "\n" end # str += "Rule: #{@rule.name}\n" if @rule # str += "Pattern: #{@pattern.tokens.join(' ')}\n" if @pattern str end # Return a String that represents the keyword documentation in an XML # formatted form. def generateHTML(directory) html = HTMLDocument.new head = html.generateHead(keyword, { 'description' => 'The TaskJuggler Manual', 'keywords' => 'taskjuggler, project, management' }) head << @manual.generateStyleSheet html.html << BODY.new do [ @manual.generateHTMLHeader, generateHTMLNavigationBar, DIV.new('style' => 'margin-left:5%; margin-right:5%') do [ generateHTMLKeywordBox, generateHTMLSupportLevel, generateHTMLDescriptionBox, generateHTMLOptionalAttributesBox, generateHTMLExampleBox ] end, generateHTMLNavigationBar, @manual.generateHTMLFooter ] end if directory html.write(directory + "#{keyword}.html") else puts html.to_s end end private def checkReference(pattern) if pattern.keyword.nil? $stderr.puts "Pattern #{pattern} is undocumented but referenced by " + "#{@keyword}." false end true end def indent(width, str) TextFormatter.new(80, width).indent(str)[width..-1] end # Generate the navigation bar. def generateHTMLNavigationBar @manual.generateHTMLNavigationBar( @predecessor ? @predecessor.title : nil, @predecessor ? "#{@predecessor.keyword}.html" : nil, @successor ? @successor.title : nil, @successor ? "#{@successor.keyword}.html" : nil) end # Return a HTML object with a link to the manual page for the keyword. def keywordHTMLRef(parent, keyword) parent << XMLNamedText.new(keyword.title, 'a', 'href' => "#{keyword.keyword}.html") end # This function is primarily a wrapper around the RichText constructor. It # catches all RichTextScanner processing problems and converts the exception # data into an error message. def newRichText(text) rText = RichText.new(text, []) unless (rti = rText.generateIntermediateFormat) error('rich_text', "Error in RichText of rule #{@keyword}") end @references += rti.internalReferences rti end # Utility function to turn a list of keywords into a comma separated list # of HTML references to the files of these keywords. All embedded in a # table cell element. _list_ is the KeywordDocumentation list. _width_ is # the percentage width of the cell. def listHTMLAttributes(list, width) td = XMLElement.new('td', 'class' => 'descr', 'style' => "width:#{width}%") first = true list.each do |attr| if first first = false else td << XMLText.new(', ') end keywordHTMLRef(td, attr) end td end def format(indent, str, width) TextFormatter.new(width, indent).format(str)[indent..-1] end def generateHTMLSupportLevel if @pattern.supportLevel != :supported [ P.new do newRichText("#{supportLevelMessage}").to_html end, [ :deprecated, :removed ].include?(@pattern.supportLevel) ? P.new do useInsteadMessage end : nil ] else nil end end def generateHTMLKeywordBox # Box with keyword name. P.new do TABLE.new('align' => 'center', 'class' => 'table') do TR.new('align' => 'left') do [ TD.new({ 'class' => 'tag', 'style' => 'width:16%'}) { 'Keyword' }, TD.new({ 'class' => 'descr', 'style' => 'width:84%; font-weight:bold' }) { title } ] end end end end def generateHTMLDescriptionBox return nil if @pattern.supportLevel == :removed # Box with purpose, syntax, arguments and context. P.new do TABLE.new({ 'align' => 'center', 'class' => 'table' }) do [ COLGROUP.new do [ COL.new('width' => '16%'), COL.new('width' => '24%'), COL.new('width' => '60%') ] end, generateHTMLPurposeLine, generateHTMLSyntaxLine, generateHTMLArgumentsLine, generateHTMLContextLine, generateHTMLAlsoLine ] end end end def generateHTMLPurposeLine generateHTMLTableLine('Purpose', newRichText(@pattern.doc).to_html) end def generateHTMLSyntaxLine if @syntax != '[{ }]' generateHTMLTableLine('Syntax', CODE.new { @syntax }) end end def generateHTMLArgumentsLine return nil unless @syntax != '[{ }]' if @args.empty? generateHTMLTableLine('Arguments', 'none') else rows = [] first = true @args.each do |arg| if first col1 = 'Arguments' col1rows = @args.length first = false else col1 = col1rows = nil end if arg.typeSpec.nil? || ('<' + arg.name + '>') == arg.typeSpec col2 = "#{arg.name}" else typeSpec = arg.typeSpec typeName = typeSpec[1..-2] typeSpec[0] = '[' typeSpec[-1] = ']' col2 = [ "#{arg.name} [", A.new('href' => "The_TaskJuggler_Syntax.html" + "\##{typeName}") { typeName }, ']' ] end col3 = newRichText(arg.text || "See [[#{arg.name}]] for details.").to_html rows << generateHTMLTableLine(col1, col2, col3, col1rows) end rows end end def generateHTMLContextLine descr = [] @contexts.each do |c| next if [ :deprecated, :removed ].include?(c.pattern.supportLevel) descr << ', ' unless descr.empty? descr << A.new('href' => "#{c.keyword}.html") { c.title } end if descr.empty? descr = A.new('href' => 'Getting_Started.html#Structure_of_a_TJP_File') do 'Global scope' end end generateHTMLTableLine('Context', descr) end def generateHTMLAlsoLine unless @seeAlso.empty? descr = [] @seeAlso.each do |a| next if [ :deprecated, :removed ].include?(a.pattern.supportLevel) descr << ', ' unless descr.empty? descr << A.new('href' => "#{a.keyword}.html") { a.title } end generateHTMLTableLine('See also', descr) end end def generateHTMLTableLine(col1, col2, col3 = nil, col1rows = nil) return nil if @pattern.supportLevel == :removed TR.new('align' => 'left') do columns = [] attrs = { 'class' => 'tag' } attrs['rowspan'] = col1rows.to_s if col1rows columns << TD.new(attrs) { col1 } if col1 attrs = { 'class' => 'descr' } attrs['colspan'] = '2' unless col3 columns << TD.new(attrs) { col2 } columns << TD.new('class' => 'descr') { col3 } if col3 columns end end def generateHTMLOptionalAttributesBox return nil if @pattern.supportLevel == :removed # Box with attributes. unless @optionalAttributes.empty? @optionalAttributes.sort! do |a, b| a.keyword <=> b.keyword end showDetails = false @optionalAttributes.each do |attr| if attr.scenarioSpecific || attr.inheritedFromProject || attr.inheritedFromParent showDetails = true break end end P.new do TABLE.new('align' => 'center', 'class' => 'table') do if showDetails # Table of all attributes with checkmarks for being scenario # specific, inherited from parent and inherited from global # scope. rows = [] rows << COLGROUP.new do [ 16, 24, 20, 20, 20 ].map { |p| COL.new('width' => "#{p}%") } end rows << TR.new('align' => 'left') do [ TD.new('class' => 'tag', 'rowspan' => "#{@optionalAttributes.length + 1}") do 'Attributes' end, TD.new('class' => 'tag') { 'Name' }, TD.new('class' => 'tag') { 'Scen. spec.' }, TD.new('class' => 'tag') { 'Inh. fm. Global' }, TD.new('class' => 'tag') { 'Inh. fm. Parent' } ] end @optionalAttributes.each do |attr| if [ :deprecated, :removed ].include?(attr.pattern.supportLevel) next end rows << TR.new('align' => 'left') do [ TD.new('align' => 'left', 'class' => 'descr') do A.new('href' => "#{attr.keyword}.html") { attr.title } end, TD.new('align' => 'center', 'class' => 'descr') do 'x' if attr.scenarioSpecific end, TD.new('align' => 'center', 'class' => 'descr') do 'x' if attr.inheritedFromProject end, TD.new('align' => 'center', 'class' => 'descr') do 'x' if attr.inheritedFromParent end ] end end rows else # Comma separated list of all attributes. TR.new('align' => 'left') do [ TD.new('class' => 'tag', 'style' => 'width:16%') do 'Attributes' end, TD.new('class' => 'descr', 'style' => 'width:84%') do list = [] @optionalAttributes.each do |attr| if [ :deprecated, :removed ]. include?(attr.pattern.supportLevel) next end list << ', ' unless list.empty? list << A.new('href' => "#{attr.keyword}.html") do attr.title end end list end ] end end end end end end def generateHTMLExampleBox return nil if @pattern.supportLevel == :removed if @pattern.exampleFile exampleDir = File.join(AppConfig.dataDirs('test')[0], 'TestSuite', 'Syntax', 'Correct') example = TjpExample.new fileName = "#{exampleDir}/#{@pattern.exampleFile}.tjp" example.open(fileName) unless (text = example.to_s(@pattern.exampleTag)) raise "There is no tag '#{@pattern.exampleTag}' in file " + "#{fileName}." end DIV.new('class' => 'codeframe') do PRE.new('class' => 'code') { text } end end end def supportLevelMessage case @pattern.supportLevel when :experimental "This keyword is currently in an experimental state. " + "The implementation is probably still incomplete and " + "use of this keyword may lead to wrong results. Do not " + "use this keyword unless you were specifically directed " + "by the developers to try it." when :beta "This keyword has not yet been fully tested yet. You are " + "welcome to try it, but it may lead to wrong results. " + "The syntax may still change with future versions. " + "The developers appreciate any feedback on this keyword." when :deprecated "This keyword should no longer be used. It will be removed " + "in future versions of this software." when :removed "This keyword is no longer supported." end end def useInsteadMessage return nil if @seeAlso.empty? descr = [ 'Use ' ] @seeAlso.each do |a| descr << ', ' unless descr.length <= 1 descr << A.new('href' => "#{a.keyword}.html") { a.title } end descr << " instead." end end end taskjuggler-3.5.0/lib/taskjuggler/WorkingHours.rb0000644000175000017500000001727112614413013021432 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = WorkingHours.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Interval' require 'taskjuggler/Scoreboard' class TaskJuggler # Class to store the working hours for each day of the week. The working hours # are stored as Arrays of Fixnum intervals for each day of the week. A day off # is modelled as empty Array for that week day. The start end end times of # each working period are stored as seconds after midnight. class WorkingHours attr_reader :days, :startDate, :endDate, :slotDuration, :timezone, :scoreboard # Create a new WorkingHours object. The method accepts a reference to an # existing WorkingHours object in +wh+. When it's present, the new object # will be a deep copy of the given object. The Scoreboard object is _not_ # deep copied. It will be copied on write. def initialize(arg1 = nil, startDate = nil, endDate = nil, timeZone = nil) # One entry for every day of the week. Sunday === 0. @days = Array.new(7, []) @scoreboard = nil if arg1.is_a?(WorkingHours) # Create a copy of the passed WorkingHours object. wh = arg1 @timezone = wh.timezone 7.times do |day| hours = [] wh.days[day].each do |hrs| hours << hrs.dup end setWorkingHours(day, hours) end @startDate = wh.startDate @endDate = wh.endDate @slotDuration = wh.slotDuration # Make sure the copied scoreboard has been created, so we can share it # copy-on-write. wh.onShift?(0) @scoreboard = wh.scoreboard else slotDuration = arg1 if arg1.nil? || startDate.nil? || endDate.nil? raise "You must supply values for slotDuration, start and end dates" end @startDate = startDate @endDate = endDate @slotDuration = slotDuration # Create a new object with default working hours. @timezone = timeZone # Set the default working hours. Monday to Friday 9am - 5pm. # Saturday and Sunday are days off. 1.upto(5) do |day| @days[day] = [ [ 9 * 60 * 60, 17 * 60 * 60 ] ] end end end # Since we want to share the scoreboard among instances with identical # working hours, we need to prevent the scoreboard from being deep cloned. # Calling the constructor with self in a re-defined deep_clone method will # do just that. def deep_clone WorkingHours.new(self) end # Return true of the given WorkingHours object +wh+ is identical to this # object. def ==(wh) return false if wh.nil? || @timezone != wh.timezone || @startDate != wh.startDate || @endDate != wh.endDate || @slotDuration != wh.slotDuration 7.times do |d| return false if @days[d].length != wh.days[d].length # Check all working hour intervals @days[d].length.times do |i| return false if @days[d][i][0] != wh.days[d][i][0] || @days[d][i][1] != wh.days[d][i][1] end end true end # Set the working hours for a given week day. +dayOfWeek+ must be 0 for # Sunday, 1 for Monday and so on. +intervals+ must be an Array that # contains an Array with 2 Fixnums for each working period. Each value # specifies the time of day as minutes after midnight. The first value is # the start time of the interval, the second the end time. def setWorkingHours(dayOfWeek, intervals) # Changing the working hours requires the score board to be regenerated. @scoreboard = nil # Legal values range from 0 Sunday to 6 Saturday. if dayOfWeek < 0 || dayOfWeek > 6 raise "dayOfWeek out of range: #{dayOfWeek}" end intervals.each do |iv| if iv[0] < 0 || iv[0] > 24 * 60 * 60 || iv[1] < 0 || iv[1] > 24 * 60 * 60 raise "Time interval has illegal values: " + "#{time_to_s(iv[0])} - #{time_to_s(iv[1])}" end if iv[0] >= iv[1] raise "Interval end time must be larger than start time" end end @days[dayOfWeek] = intervals end # Set the time zone _zone_ for the working hours. This will reset the # @scoreboard. def timezone=(zone) @scoreboard = nil @timezone = zone end # Return the working hour intervals for a given day of the week. # +dayOfWeek+ must 0 for Sunday, 1 for Monday and so on. The result is an # Array that contains Arrays of 2 Fixnums. def getWorkingHours(dayOfWeek) @days[dayOfWeek] end # Return true if _arg_ is within the defined working hours. _arg_ can be a # TjTime object or a global scoreboard index. def onShift?(arg) initScoreboard unless @scoreboard if arg.is_a?(TjTime) @scoreboard.get(arg) else @scoreboard[arg] end end # Return true only if all slots in the _interval_ are offhour slots. def timeOff?(interval) initScoreboard unless @scoreboard startIdx = @scoreboard.dateToIdx(interval.start) endIdx = @scoreboard.dateToIdx(interval.end) startIdx.upto(endIdx - 1) do |i| return false if @scoreboard[i] end true end # Return the number of working hours per week. def weeklyWorkingHours seconds = 0 @days.each do |day| day.each do |from, to| seconds += (to - from) end end seconds / (60 * 60) end # Returns the time interval settings for each day in a human readable form. def to_s dayNames = %w( Sun Mon Tue Wed Thu Fri Sat ) str = '' 7.times do |day| str += "#{dayNames[day]}: " if @days[day].empty? str += "off" str += "\n" if day < 6 next end first = true @days[day].each do |iv| if first first = false else str += ', ' end str += "#{time_to_s(iv[0])} - #{time_to_s(iv[0])}" end str += "\n" if day < 6 end str end private def time_to_s(t) "#{t >= 24 * 60 * 60 ? '24:00' : "#{t / 3600}:#{t % 3600}"}" end def initScoreboard # The scoreboard is an Array of True/False values. It spans a certain # time period with one entry per time slot. @scoreboard = Scoreboard.new(@startDate, @endDate, @slotDuration, false) oldTimezone = nil # Active the appropriate time zone for the working hours. if @timezone oldTimezone = TjTime.setTimeZone(@timezone) end date = @startDate @scoreboard.collect! do |slot| # The weekday and seconds of the day needs to be calculated according # to the local timezone. weekday = date.wday secondsOfDay = date.secondsOfDay result = false @days[weekday].each do |iv| # Check the working hours of that day if they overlap with +date+. if iv[0] <= secondsOfDay && secondsOfDay < iv[1] # The time slot is a working slot. result = true break end end # Calculate date of next scoreboard slot date += @slotDuration result end # Restore old time zone setting. if @timezone && oldTimezone TjTime.setTimeZone(oldTimezone) end end end end taskjuggler-3.5.0/lib/taskjuggler/TableColumnDefinition.rb0000644000175000017500000000747612614413013023215 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TableColumnDefinition.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # A CellSettingPattern is used to store alternative settings for # ReportTableCell settings. These could be the cell text, the tooltip or a # color setting. The user can provide multiple options and the # LogicalExpression is used to select the pattern for a given cell. class CellSettingPattern attr_reader :setting, :logExpr def initialize(setting, logExpr) @setting = setting @logExpr = logExpr end end # The CellSettingPatternList holds a list of possible test pattern for a cell # or tooltip. The first entry who's LogicalExpression matches is used. class CellSettingPatternList def initialize @patterns = [] end # Add a new pattern to the list. def addPattern(pattern) @patterns << pattern end # Get the RichText that matches the _property_ and _scopeProperty_. def getPattern(query) @patterns.each do |pattern| if pattern.logExpr.eval(query) return pattern.setting end end nil end end # This class holds the definition of a column of a report. This is the user # specified data that is later used to generate the actual ReportTableColumn. # The column is uniquely identified by an ID. class TableColumnDefinition attr_reader :id, :cellText, :tooltip, :hAlign, :cellColor, :fontColor attr_accessor :title, :start, :end, :scale, :listItem, :listType, :width, :content, :column, :timeformat1, :timeformat2 def initialize(id, title) # The column ID. It must be unique within the report. @id = id # An alternative title for the column header. @title = title # An alternative start date for columns with time-variant values. @start = nil # An alternative end date for columns with time-variant values. @end = nil # For regular columns (non-calendar and non-chart) the user can override # the actual cell content. @cellText = CellSettingPatternList.new # The content attribute is only used for calendar columns. It specifies # what content should be displayed in the calendar columns. @content = 'load' # Horizontal alignment of the cell content. @hAlign = CellSettingPatternList.new # An alternative content for the tooltip message. It should be a # RichText object. @tooltip = CellSettingPatternList.new # An alternative background color for the cell. The color setting is # stored as "#RGB" or "#RRGGBB" String. @cellColor = CellSettingPatternList.new # An alternative font color for the cell. The format is equivalent to # the @cellColor setting. @fontColor = CellSettingPatternList.new # Specifies a RichText pattern to be used to generate the text of the # individual list items. @listItem = nil # Specifies whether list items are comma separated, bullet or numbered # list. @listType = nil # The scale attribute is only used for Gantt chart columns. It specifies # the minimum resolution of the chart. @scale = 'week' # The width of columns. @width = nil # Format of the upper calendar header line @timeformat1 = nil # Format of the lower calendar header line @timeformat2 = nil # Reference to the ReportTableColumn object that was created based on this # definition. @column = nil end end end taskjuggler-3.5.0/lib/taskjuggler/StdIoWrapper.rb0000644000175000017500000000333212614413013021345 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = StdIoWrapper.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This module provides just one method to run the passed block. It will # capture all content that will be send to $stdout and $stderr by the block. # I can also feed the String provided by _stdIn_ to $stdin of the block. module StdIoWrapper Results = Struct.new(:returnValue, :stdOut, :stdErr) def stdIoWrapper(stdIn = nil) # Save the old $stdout and $stderr and replace them with StringIO # objects to capture the output. oldStdOut = $stdout oldStdErr = $stderr $stdout = (out = StringIO.new) $stderr = (err = StringIO.new) # If the caller provided a String to feed into $stdin, we replace that # as well. if stdIn oldStdIn = $stdin $stdin = StringIO.new(stdIn) end begin # Call the block with the hooked up IOs. res = yield rescue RuntimeError # Blocks that are called this way usually return 0 on success and 1 on # errors. res = 1 ensure # Restore the stdio channels no matter what. $stdout = oldStdOut $stderr = oldStdErr $stdin = oldStdIn if stdIn end # Return the return value of the block and the $stdout and $stderr # captures. Results.new(res, out.string, err.string) end end end taskjuggler-3.5.0/lib/taskjuggler/ICalendar.rb0000644000175000017500000001434212614413013020607 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ICalendar.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Tj3Config' class TaskJuggler # This class implements a very basic RFC5545 compliant iCalendar file # generator. It currently only supports a very small subset of the tags that # are needed for TaskJuggler. class ICalendar # The maximum allowed length of a content line without line end character. LINELENGTH = 75 # Utility class to store name and email of a person. class Person < Struct.new(:name, :email) end # Base class for all ICalendar components. class Component attr_accessor :description, :relatedTo, :organizer attr_reader :uid def initialize(ical, uid, summary, startDate) @ical = ical @type = self.class.to_s.split('::').last.upcase @uid = uid + "-#{@type}" @summary = summary @startDate = startDate # Optional attributes @description = nil @relatedTo = nil @organizer = nil @attendees = [] end def setOrganizer(name, email) @organizer = Person.new(name, email) end def addAttendee(name, email) @attendees << Person.new(name, email) end def to_s str = <<"EOT" BEGIN:V#{@type} DTSTAMP:#{dateTime(TjTime.new.utc)} CREATED:#{dateTime(@ical.creationDate)} UID:#{@uid} LAST-MODIFIED:#{dateTime(@ical.lastModified)} SUMMARY:#{quoted(@summary)} DTSTART:#{dateTime(@startDate)} EOT str += "DESCRIPTION:#{quoted(@description)}\n" if @description str += "RELATED-TO:#{@relatedTo}\n" if @relatedTo if @organizer str += "ORGANIZER;CN=#{@organizer.name}:mailto:#{@organizer.email}\n" end @attendees.each do |attendee| str += "ATTENDEE;CN=#{attendee.name}:mailto:#{attendee.email}\n" end str += yield if block_given? str += "END:V#{@type}\n\n" end private def dateTime(date) @ical.dateTime(date) end def quoted(str) str.gsub(/([;,"\\])/, '\\\\\1').gsub(/\n/, '\n') end end # Stores the data of an VTODO component and can generate one. class Todo < Component attr_accessor :priority, :percentComplete # Create the Todo object with some mandatory data. _ical_ is a reference # to the parent ICalendar object. _uid_ is a unique pattern used to # generate the UID tag. _summary_ is a String for SUMMARY. _startDate_ # is used to generate DTSTART. _endDate_ is used to either generate # the COMPLETED or DUE tag. def initialize(ical, uid, summary, startDate, endDate) super(ical, uid, summary, startDate) # Mandatory attributes @ical.addTodo(self) @endDate = endDate # Priority value (0 - 9) @priority = 0 @percentComplete = -1 end # Generate the VTODO record as String. def to_s super do str = '' if @percentComplete < 100.0 str += "DUE:#{dateTime(@endDate)}\n" else str += "COMPLETED:#{dateTime(@endDate)}\n" end str += "PERCENT-COMPLETE:#{@percentComplete}\n" end end end # Stores the data of an VTODO component and can generate one. class Event < Component # Create the Event object with some mandatory data. _ical_ is a # reference to the parent ICalendar object. _uid_ is a unique pattern # used to generate the UID tag. _summary_ is a String for SUMMARY. # _startDate_ is used to generate DTSTART. _endDate_ is used to either # generate the COMPLETED or DUE tag. def initialize(ical, uid, summary, startDate, endDate) super(ical, uid, summary, startDate) @ical.addEvent(self) # Mandatory attributes @endDate = endDate # Optional attributes @priority = 1 end # Generate the VEVENT record as String. def to_s super do <<"EOT" PRIORITY:#{@priority} DTEND:#{dateTime(@endDate)} TRANSP:TRANSPARENT EOT end end end class Journal < Component def initialize(ical, uid, summary, startDate) super @ical.addJournal(self) end def to_s super end end attr_reader :uid attr_accessor :creationDate, :lastModified def initialize(uid) @uid = "#{AppConfig.packageName}-#{uid}" @creationDate = @lastModified = TjTime.new.utc @todos = [] @events = [] @journals = [] end # Add a new VTODO component. For internal use only! def addTodo(todo) @todos << todo end # Add a new VEVENT component. For internal use only! def addEvent(event) @events << event end # Add a new VJOURNAL component. For internal user only! def addJournal(journal) @journals << journal end def to_s str = <<"EOT" BEGIN:VCALENDAR PRODID:-//The #{AppConfig.softwareName} Project/NONSGML #{AppConfig.softwareName} #{AppConfig.version}//EN VERSION:2.0 EOT @todos.each { |todo| str += todo.to_s } @events.each { |event| str += event.to_s } @journals.each { |journal| str += journal.to_s } str << <<"EOT" END:VCALENDAR EOT foldLines(str) end def dateTime(date) date.to_s("%Y%m%dT%H%M%SZ", 'UTC') end private # Make sure that no line is longer than LINELENTH octets (excl. the # newline character) def foldLines(str) newStr = '' str.each_line do |line| bytes = 0 line.each_utf8_char do |c| # Make sure we support Ruby 1.8 and 1.9 String handling. cBytes = c.bytesize if bytes + cBytes > LINELENGTH && c != "\n" newStr += "\n " bytes = 0 else bytes += cBytes end newStr << c end end # Convert line ends to CR+LF newStr.gsub(/\n/, "\r\n") end end end taskjuggler-3.5.0/lib/taskjuggler/SimpleQueryExpander.rb0000644000175000017500000000413612614413013022733 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = SimpleQueryExpander.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'strscan' require 'taskjuggler/MessageHandler' class TaskJuggler # The SimpleQueryExpander class is used to replace embedded attribute # queries in a string with the value of the attribute. The embedded queries # must have the form <-name-> where name is the name of the attribute. The # Query class is used to determine the value of the attribute within the # context of the query. class SimpleQueryExpander include MessageHandler # _inputStr_ is the String with the embedded queries. _query_ is the Query # with that provides the evaluation context. _messageHandle_ is a # MessageHandler that will be used for error reporting. _sourceFileInfo_ # is a SourceFileInfo reference used for error reporting. def initialize(inputStr, query, sourceFileInfo) @inputStr = inputStr @query = query.dup @sourceFileInfo = sourceFileInfo end def expand # Create a copy of the input string since we will modify it. str = @inputStr.dup # The scenario name is not an attribute that can be queried. We need to # handle this separately if @query.scenarioIdx str.gsub!(/<-scenario->/, @query.project.scenario(@query.scenarioIdx).id) end # Replace all occurences of <-name->. str.gsub!(/<-[a-zA-Z][_a-zA-Z]*->/) do |match| attribute = match[2..-3] @query.attributeId = attribute @query.process if @query.ok @query.to_s else # The query failed. We report an error. error('sqe_expand_failed', "Unknown attribute #{attribute}", @sourceFileInfo) end end str end end end taskjuggler-3.5.0/lib/taskjuggler/Interval.rb0000644000175000017500000002133212614413013020546 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Interval.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TjTime' class TaskJuggler # This is the based class used to store several kinds of intervals in # derived classes. class Interval attr_reader :start, :end # Create a new Interval object. _s_ is the interval start, _e_ the # interval end (not included). def initialize(s, e) @start = s @end = e # The end must not be before the start. if @end < @start raise ArgumentError, "Invalid interval (#{s} - #{e})" end end # Return true if _arg_ is contained within the Interval. It can either # be a single TjTime or another Interval. def contains?(arg) if arg.is_a?(Interval) raise ArgumentError, "Class mismatch" if self.class != arg.class return @start <= arg.start && arg.end <= @end else raise ArgumentError, "Class mismatch" if @start.class != arg.class return @start <= arg && arg < @end end end # Check whether the Interval _arg_ overlaps with this Interval. def overlaps?(arg) if arg.is_a?(Interval) raise ArgumentError, "Class mismatch" if self.class != arg.class return (@start <= arg.start && arg.start < @end) || (arg.start <= @start && @start < arg.end) else raise ArgumentError, "Class mismatch" if @start.class != arg.class return @start <= arg && arg < @end end end # Return a new Interval that contains the overlap of self and the Interval # _iv_. In case there is no overlap, nil is returned. def intersection(iv) raise ArgumentError, "Class mismatch" if self.class != iv.class newStart = @start > iv.start ? @start : iv.start newEnd = @end < iv.end ? @end : iv.end newStart < newEnd ? self.class.new(newStart, newEnd) : nil end # Append or prepend the Interval _iv_ to self. If _iv_ does not directly # attach to self, just return self. def combine(iv) raise ArgumentError, "Class mismatch" if self.class != iv.class if iv.end == @start # Prepend iv Array.new self.class.new(iv.start, @end) elsif @end == iv.start # Append iv Array.new self.class.new(@start, iv.end) else self end end # Compare self with Interval _iv_. This function only works for # non-overlapping Interval objects. def <=>(iv) raise ArgumentError, "Class mismatch" if self.class != iv.class if @end < iv.start -1 elsif iv.end < @start 1 end 0 end # Return true if the Interval _iv_ describes an identical time period. def ==(iv) raise ArgumentError, "Class mismatch" if self.class != iv.class @start == iv.start && @end == iv.end end end # The TimeInterval class provides objects that model a time interval. The # start end end time are represented as seconds after Jan 1, 1970. The start # is part of the interval, the end is not. class TimeInterval < Interval attr_accessor :start, :end # Create a new TimeInterval. _args_ can be three different kind of arguments. # # a and b should be TjTime objects. # # TimeInterval.new(a, b) | -> Interval(a, b) # TimeInterval.new(a) | -> Interval(a, a) # TimeInterval.new(iv) | -> Interval(iv.start, iv.end) # def initialize(*args) if args.length == 1 if args[0].is_a?(TjTime) # Just one argument, a date super(args[0], args[0]) elsif args[0].is_a?(TimeInterval) # Just one argument, a TimeInterval super(args[0].start, args[0].end) else raise ArgumentError, "Illegal argument 1: #{args[0].class}" end elsif args.length == 2 # Two arguments, a start and end date unless args[0].is_a?(TjTime) raise ArgumentError, "Interval start must be a date, not a " + "#{args[0].class}" end unless args[1].is_a?(TjTime) raise ArgumentError, "Interval end must be a date, not a" + "#{args[1].class}" end super(args[0], args[1]) else raise ArgumentError, "Too many arguments: #{args.length}" end end # Return the duration of the TimeInterval. def duration @end - @start end # Turn the TimeInterval into a human readable form. def to_s @start.to_s + ' - ' + @end.to_s end end # This class describes an interval of a scoreboard. The start and end of the # interval are stored as indexes but can always be converted back to TjTime # objects if needed. class ScoreboardInterval < Interval attr_reader :sbStart, :slotDuration # Create a new ScoreboardInterval. _args_ can be three different kind of # arguments. # # sbStart must be a TjTime of the scoreboard start # slotDuration must be the duration of the scoreboard slots in seconds # a and b should be TjTime or Fixnum objects that describe the start and # end time or index of the interval. # # TimeInterval.new(iv) # TimeInterval.new(sbStart, slotDuration, a) # TimeInterval.new(sbStart, slotDuration, a, b) # def initialize(*args) case args.length when 1 # If there is only one argument, it must be a ScoreboardInterval. if args[0].is_a?(ScoreboardInterval) @sbStart = args[0].sbStart @slotDuration = args[0].slotDuration # Just one argument, a TimeInterval super(args[0].start, args[0].end) else raise ArgumentError, "Illegal argument 1: #{args[0].class}" end when 3 @sbStart = args[0] @slotDuration = args[1] # If the third argument is a date we convert it to a scoreboard index. args[2] = dateToIndex(args[2]) if args[2].is_a?(TjTime) if args[2].is_a?(Fixnum) || args[2].is_a?(Bignum) super(args[2], args[2]) else raise ArgumentError, "Illegal argument 3: #{args[0].class}" end when 4 @sbStart = args[0] @slotDuration = args[1] # If the third and forth arguments are a date we convert them to a # scoreboard index. args[2] = dateToIndex(args[2]) if args[2].is_a?(TjTime) args[3] = dateToIndex(args[3]) if args[3].is_a?(TjTime) if !(args[2].is_a?(Fixnum) || args[2].is_a?(Bignum)) raise ArgumentError, "Interval start must be an index or TjTime, " + "not a #{args[2].class}" end if !(args[3].is_a?(Fixnum) || args[3].is_a?(Bignum)) raise ArgumentError, "Interval end must be an index or TjTime, " + "not a #{args[3].class}" end super(args[2], args[3]) else raise ArgumentError, "Wrong number of arguments: #{args.length}" end unless @sbStart.is_a?(TjTime) raise ArgumentError, "sbStart must be a TjTime object, not a" + "#{@sbStart.class}" end unless @slotDuration.is_a?(Fixnum) raise ArgumentError, "slotDuration must be a Fixnum, not a " + "#{@slotDuration.class}" end end # Assign the start of the interval. +arg+ can be a Fixnum, Bignum or # TjTime object. def start=(arg) case arg when Fixnum when Bignum @start = arg when TjTime @start = dateToIndex(arg) else raise ArgumentError, "Unsupported class #{arg.class}" end end # Assign the start of the interval. +arg+ can be a Fixnum, Bignum or # TjTime object. def end=(arg) case arg when Fixnum when Bignum @end = arg when TjTime @end = dateToIndex(arg) else raise ArgumentError, "Unsupported class #{arg.class}" end end # Return the interval start as TjTime object. def startDate indexToDate(@start) end # Return the interval end as TjTime object. def endDate indexToDate(@end) end # Return the duration of the ScoreboardInterval. def duration indexToDate(@end) - indexToDate(@start) end # Turn the ScoreboardInterval into a human readable form. def to_s indexToDate(@start).to_s + ' - ' + indexToDate(@end).to_s end private def dateToIndex(date) (date - @sbStart).to_i / @slotDuration end def indexToDate(index) @sbStart + (index * @slotDuration) end end end taskjuggler-3.5.0/lib/taskjuggler/TimeSheetSummary.rb0000644000175000017500000001431012614413013022225 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TimeSheetSummary.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/SheetReceiver' class TaskJuggler # The TimeSheetSender class generates time sheet templates for the current # week and sends them out to the project contributors. For this to work, the # resources must provide the 'Email' custom attribute with their email # address. The actual project data is accessed via tj3client on a tj3 server # process. class TimeSheetSummary < SheetReceiver attr_accessor :date, :sheetRecipients, :digestRecipients def initialize super('tj3ts_summary', 'summary') # This is a LogicalExpression string that controls what resources should # not be considered in the summary. @hideResource = '0' # The base directory of the time sheet templates. @templateDir = 'TimeSheetTemplates' # The base directory of the submitted time sheets @sheetDir = 'TimeSheets' # The log file @logFile = 'timesheets.log' # A list of email addresses to send the individual sheets. The sender # will be the sheet submitter. @sheetRecipients = [] # A list of email addresses to send the summary to @digestRecipients = [] @resourceIntro = "== Weekly Report from %s ==\n" @resourceSheetSubject = "Weekly report %s" @summarySubject = "Weekly staff reports %s" @reminderSubject = "Your time sheet for the period ending %s is overdue!" @reminderText = <<'EOT' The deadline for your time sheet submission has passed but we haven't received it yet. Please submit your time sheet immediately so the content can still be included in the management reports. Please send a copy of your submission notification email to your manager. If possible, your manager will still try to include your report data in his/her report. Please be aware that post deadline submissions must be processed manually and create an additional load for your manager and/or project manager. Please try to submit in time in the future. Thanks for your cooperation! EOT @defaulterHeader = "The following %d person(s) have not yet submitted " + "their time sheets:\n\n" end def sendSummary(resourceIds) setWorkingDir summary = '' defaulterList = [] getResourceList.each do |resource| resourceId = resource[0] resourceName = resource[1] resourceEmail = resource[2] next if !resourceIds.empty? && !resourceIds.include?(resourceId) templateFile = "#{@templateDir}/#{@date}/#{resourceId}_#{@date}.tji" sheetFile = "#{@sheetDir}/#{@date}/#{resourceId}_#{@date}.tji" if File.exist?(templateFile) if File.exists?(sheetFile) # If there are no recipients specified, we don't need to compile # the summary. unless @digestRecipients.empty? && @sheetRecipients.empty? # Resource has submitted a time sheet sheet = getResourceJournal(sheetFile) summary += sprintf(@resourceIntro, resourceName) summary += sheet + "\n----\n" info("Adding report from #{resourceName} to summary") @sheetRecipients.each do |to| sendRichTextEmail(to, sprintf(@resourceSheetSubject, @date), sheet, nil, "#{resourceName} <#{resourceEmail}>") end end else defaulterList << resource # Resource did not submit a time sheet info("Report from #{resourceId} is missing") end end end unless defaulterList.empty? # Prepend the defaulter list to the summary. text = sprintf(@defaulterHeader, defaulterList.length) defaulterList.each do |resource| text += "* #{resource[1]}\n" end text += "\n----\n" summary = text + summary # Create a file with the IDs of the resources who's reports are # missing. missingFile = "#{@sheetDir}/#{@date}/missing-reports" begin File.open(missingFile, 'w') do |f| defaulterList.each { |resource| f.puts resource[0] } end rescue error("Cannot write file with missing reports (#missingFile): #{$!}") end end # Send out the summary text to the list of digest recipients. @digestRecipients.each do |to| sendRichTextEmail(to, sprintf(@summarySubject, @date), summary) end # If there is a reminder text defined, resend the template to those # individuals that have not yet submitted their report yet. if @reminderText && !@reminderText.empty? defaulterList.each do |resource| sendReminder(resource[0], resource[1], resource[2]) end end end private def sendReminder(id, name, email) attachment = "#{@templateDir}/#{@date}/#{id}_#{@date}.tji" unless File.exist?(attachment) error("sendReportTemplates: " + "#{@sheetType} sheet #{attachment} for #{name} not found") end message = "Hello #{name}!\n\n#{@reminderText}" + File.read(attachment) sendEmail(email, sprintf(@reminderSubject, @date), message, attachment) end def getResourceJournal(sheetFile) err = '' status = nil report = nil warnings = nil begin # Save a copy of the sheet for debugging purposes. command = [ '--unsafe', "--silent", "check-ts", @projectId, sheetFile ] res = stdIoWrapper do Tj3Client.new.main(command) end if res.returnValue != 0 error("summary sheets: #{err}") end report = res.stdOut warnings = res.stdErr rescue fatal("Cannot summarize sheet: #{$!}") end report end end end taskjuggler-3.5.0/lib/taskjuggler/ScenarioData.rb0000644000175000017500000000352412614413013021322 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ScenarioData.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TjException' require 'taskjuggler/MessageHandler' class TaskJuggler class ScenarioData attr_reader :property def initialize(property, idx, attributes) @property = property @project = property.project @scenarioIdx = idx @attributes = attributes @messageHandler = MessageHandlerInstance.instance # Register the scenario with the Task. @property.data[idx] = self end # We only use deep_clone for attributes, never for properties. Since # attributes may reference properties these references should remain # references. def deep_clone self end def a(attributeName) @attributes[attributeName].get end def error(id, text, sourceFileInfo = nil, property = nil) @messageHandler.error( id, text, sourceFileInfo || @property.sourceFileInfo, nil, property || @property, @project.scenario(@scenarioIdx)) end def warning(id, text, sourceFileInfo = nil, property = nil) @messageHandler.warning( id, text, sourceFileInfo || @property.sourceFileInfo, nil, property || @property, @project.scenario(@scenarioIdx)) end def info(id, text, sourceFileInfo = nil, property = nil) @messageHandler.info( id, text, sourceFileInfo || @property.sourceFileInfo, nil, property || @property, @project.scenario(@scenarioIdx)) end end end taskjuggler-3.5.0/lib/taskjuggler/AttributeBase.rb0000644000175000017500000001210312614413013021514 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = AttributeBase.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/deep_copy' class TaskJuggler # This class is the base for all property attribute types. Each property can # have multiple attributes of different type. For each type, there must be a # special Ruby class. Each of these classes must be derived from this class. # The class holds information like a reference to the property that owns the # attribute and the type of the attribute. # # The class can track wheter the attribute value was provided by the project # file, inherited from another property or computed during scheduling. # # Attributes that are of an inherited type will be copied from a parent # property or the global scope. class AttributeBase attr_reader :property, :type, :provided, :inherited # The mode is flag that controls how value assignments affect the flags. @@mode = 0 # Create a new AttributeBase object. _type_ specifies the specific type of # the object. _property_ is the PropertyTreeNode object this attribute # belongs to. def initialize(property, type, container) @type = type @property = property @container = container reset end # Reset the attribute value to the default value. def reset @inherited = false # Flag that marks whether the value of this attribute was provided by the # user (in contrast to being calculated). @provided = false # If type is an AttributeDefinition, create the initial value according # to the specified default for this type. Otherwise type is the initial # value. if @type.is_a?(AttributeDefinition) @container.instance_variable_set(('@' + type.id).intern, @type.default.deep_clone) else @container.instance_variable_set(('@' + type.id).intern, @type) end end # Call this function to inherit _value_ from the parent property. It is # very important that the values are deep copied as they may be modified # later on. def inherit(value) @inherited = true @container.instance_variable_set(('@' + type.id).intern, value.deep_clone) end # Return the current attribute setting mode. def AttributeBase.mode @@mode end # Change the @@mode. 0 means values are provided, 1 means values are # inherited, any other value means calculated. def AttributeBase.setMode(mode) @@mode = mode end # Return the ID of the attribute. def id type.id end # Return the name of the attribute. def name type.name end # Set the value of the attribute. Depending on the mode we are in, the flags # are updated accordingly. def set(value) case @@mode when 0 @provided = true when 1 @inherited = true end # Store the value in an instance variable in the PropertyTreeNode or # ScenarioData object referred to by @container. @container.instance_variable_set(('@' + type.id).intern, value) end # Return the attribute value. def get @container.instance_variable_get(('@' + type.id).intern) end # For legacy purposes we provide another name for get(). alias value get # Check whether the value is uninitialized or nil. def nil? if (v = get).is_a?(Array) v.empty? else v.nil? end end # We overwrite this for ListAttributeBase. def isList? false end # Return the value as String. def to_s(query = nil) get.to_s end def to_num v = get if v.is_a?(Fixnum) || v.is_a?(Bignum) || v.is_a?(Float) v else nil end end def to_sort v = get if v.is_a?(Fixnum) || v.is_a?(Bignum) || v.is_a?(Float) v elsif v.respond_to?('to_s') v.to_s else nil end end def to_rti(query) get.is_a?(RichTextIntermediate) ? !value : nil end # Return the value in TJP file syntax. def to_tjp @type.id + " " + get.to_s end private def quotedString(str) if str.include?("\n") "-8<-\n#{str}\n->8-" else "\"#{str.gsub("\"", '\"')}\"" end end end # The ListAttributeBase is a specialized form of AttributeBase for a list of # values instead of a single value. It will be used as a base class for all # attributes that hold lists. class ListAttributeBase < AttributeBase def initialize(property, type, container) super end def to_s get.join(', ') end # We overwrite this for ListAttributeBase. def isList? true end end class AttributeOverwrite < ArgumentError end end taskjuggler-3.5.0/lib/taskjuggler/KateSyntax.rb0000644000175000017500000001626612614413013021067 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = KateSyntax.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/SyntaxReference' class TaskJuggler # This class is a generator for Kate (http://kate-editor.org/) TaskJuggler syntax # highlighting files. class KateSyntax # Create a generator object. def initialize # Create a syntax reference for all current keywords. @reference = SyntaxReference.new(nil, true) @properties = [] @attributes = [] @reference.keywords.each_value do |kw| if kw.isProperty? @properties << kw else @attributes << kw end end @file = nil end # Generate the Kate syntax file into _file_. def generate(file) @file = File.open(file, 'w') header keywords contexts highlights footer @file.close end private def header # Generate the header section. Mostly consists of comments and # description attributes @file.write <<"EOT" EOT end def footer # Generate the footer section. Mostly consists of closing tags. @file.write <<"EOT" EOT end def contexts @file.write <<'EOT' EOT #syn match tjparg contained /\${.*}/ end def keywords @file.puts "" %w( macro project supplement include supplement ).each do |kw| @file.puts " #{kw} " end @file.puts "" # Property keywords @file.puts "" @properties.each do |kw| kw.names.each do |name| # Ignore the 'supplement' entries. They are not real properties. next if name == 'supplement' @file.puts " #{name} " end end @file.puts "" # Attribute keywords @file.puts "" @attributes.each do |kw| next if %w( resourcereport taskreport textreport ).include?(kw.keyword) single = kw.names.length == 1 kw.names.each do |name| break if [ '%', '(', '~', 'include', 'macro', 'project', 'supplement' ].include?(name) @file.puts " #{name} " end end @file.puts "" end def highlights @file.write <<'EOT' EOT end end end taskjuggler-3.5.0/lib/taskjuggler/TimeSheets.rb0000644000175000017500000003242012614413013021034 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TimeSheets.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This class holds the work related bits of a time sheet that are specific # to a single Task. This can be an existing Task or a new one identified by # it's ID String. For effort based task, it stores the remaining effort, for # other task the expected end date. For all tasks it stores the completed # work during the reporting time frame. class TimeSheetRecord include MessageHandler attr_reader :task, :work attr_accessor :sourceFileInfo, :remaining, :expectedEnd, :status, :priority, :name def initialize(timeSheet, task) # This is a reference to a Task object for existing tasks or an ID as # String for new tasks. @task = task # Add the new TimeSheetRecord to the TimeSheet it belongs to. (@timeSheet = timeSheet) << self # Work done will be measured in time slots. @work = nil # Remaining work will be measured in time slots. @remaining = nil @expectedEnd = nil # For new task, we also need to store the name. @name = nil # Reference to the JournalEntry object that holds the status for this # record. @status = nil @priority = 0 @sourceFileInfo = nil end # Store the number of worked time slots. If the value is a Fixnum, it can # be directly assigned. A Float is interpreted as percentage and must be # in the rage of 0.0 to 1.0. def work=(value) if value.is_a?(Fixnum) @work = value else # Must be percent value @work = @timeSheet.percentToSlots(value) end end # Perform all kinds of consistency checks. def check scIdx = @timeSheet.scenarioIdx taskId = @task.is_a?(Task) ? @task.fullId : @task # All TimeSheetRecords must have a 'work' attribute. if @work.nil? error('ts_no_work', "The time sheet record for task #{taskId} must " + "have a 'work' attribute to specify how much was done " + "for this task during the reported period.") end if @task.is_a?(Task) # This is already known tasks. if @task['effort', scIdx] > 0 unless @remaining error('ts_no_remaining', "The time sheet record for task #{taskId} must " + "have a 'remaining' attribute to specify how much " + "effort is left for this task.") end else unless @expectedEnd error('ts_no_expected_end', "The time sheet record for task #{taskId} must " + "have an 'end' attribute to specify the expected end " + "of this task.") end end else # This is for new tasks. if @remaining.nil? && @expectedEnd.nil? error('ts_no_rem_or_end', "New task #{taskId} requires either a 'remaining' or a " + "'end' attribute.") end end if @work >= @timeSheet.daysToSlots(1) && @status.nil? error('ts_no_status_work', "You must specify a status for task #{taskId}. It was worked " + "on for a day or more.") end if @status if @status.headline.empty? error('ts_no_headline', "You must provide a headline for the status of " + "task #{taskId}") end if @status.summary && @status.summary.richText.inputText == "A summary text\n" error('ts_default_summary', "You must change the default summary text of the status " + "for task #{taskId}.") end if @status.alertLevel > 0 && @status.summary.nil? && @status.details.nil? error('ts_alert1_more_details', "Task #{taskId} has an elevated alert level and must " + "have a summary or details section.") end if @status.alertLevel > 1 && @status.details.nil? error('ts_alert2_more_details', "Task #{taskId} has a high alert level and must have " + "a details section.") end end end def warnOnDelta(startIdx, endIdx) # Ignore personal entries. return unless @task resource = @timeSheet.resource if @task.is_a?(String) # A resource has requested a new Task to be created. warning('ts_res_new_task', "#{resource.name} is requesting a new task:\n" + " ID: #{@task}\n" + " Name: #{@name}\n" + " Work: #{@timeSheet.slotsToDays(@work)}d " + (@remaining ? "Remaining: #{@timeSheet.slotsToDays(@remaining)}d" : "End: #{@end.to_s}")) return end scenarioIdx = @timeSheet.scenarioIdx project = resource.project plannedWork = @task.getEffectiveWork(scenarioIdx, startIdx, endIdx, resource) # Convert the @work slots into a daily load. work = project.convertToDailyLoad(@work * project['scheduleGranularity']) if work != plannedWork warning('ts_res_work_delta', "#{resource.name} worked " + "#{work < plannedWork ? 'less' : 'more'} " + "on #{@task.fullId}\n" + "#{work}d instead of #{plannedWork}d") end if @task['effort', scenarioIdx] > 0 startIdx = endIdx endIdx = project.dateToIdx(@task['end', scenarioIdx]) remainingWork = @task.getEffectiveWork(scenarioIdx, startIdx, endIdx, resource) # Convert the @remaining slots into a daily load. remaining = project.convertToDailyLoad(@remaining * project['scheduleGranularity']) if remaining != remainingWork warning('ts_res_remain_delta', "#{resource.name} requests " + "#{remaining < remainingWork ? 'less' : 'more'} " + "remaining effort for task #{@task.fullId}\n" + "#{remaining}d instead of #{remainingWork}d") end else if @expectedEnd != @task['end', scenarioIdx] warning('ts_res_end_delta', "#{resource.name} requests " + "#{@expectedEnd < @task['end', scenarioIdx] ? 'earlier' : 'later'} end (#{@expectedEnd}) for task " + "#{@task.fullId}. Planned end is " + "#{@task['end', scenarioIdx]}.") end end end def taskId @task.is_a?(Task) ? @task.fullId : task end # The reported work in % (0.0 - 100.0) of the average working time. def actualWorkPercent (@work.to_f / @timeSheet.totalGrossWorkingSlots) * 100.0 end # The planned work in % (0.0 - 100.0) of the average working time. def planWorkPercent resource = @timeSheet.resource project = resource.project scenarioIdx = @timeSheet.scenarioIdx startIdx = project.dateToIdx(@timeSheet.interval.start) endIdx = project.dateToIdx(@timeSheet.interval.end) (@timeSheet.resource.getAllocatedSlots(scenarioIdx, startIdx, endIdx, @task).to_f / @timeSheet.totalGrossWorkingSlots) * 100.0 end # The reporting remaining effort in days. def actualRemaining project = @timeSheet.resource.project project.convertToDailyLoad(@remaining * project['scheduleGranularity']) end # The remaining effort according to the plan. def planRemaining resource = @timeSheet.resource project = resource.project scenarioIdx = @timeSheet.scenarioIdx startIdx = project.dateToIdx(project['now']) endIdx = project.dateToIdx(@task['end', scenarioIdx]) @task.getEffectiveWork(scenarioIdx, startIdx, endIdx, resource) end # The reported expected end of the task. def actualEnd @expectedEnd end # The planned end of the task. def planEnd @task['end', @timeSheet.scenarioIdx] end private end # The TimeSheet class stores the work related bits of a time sheet. For each # task it holds a TimeSheetRecord object. A time sheet is always bound to an # existing Resource. class TimeSheet attr_accessor :sourceFileInfo attr_reader :resource, :interval, :scenarioIdx def initialize(resource, interval, scenarioIdx) raise "Illegal resource" unless resource.is_a?(Resource) @resource = resource raise "Interval undefined" if interval.nil? @interval = interval raise "Sceneario index undefined" if scenarioIdx.nil? @scenarioIdx = scenarioIdx @sourceFileInfo = nil # This flag is set to true if at least one record was reported as # percentage. @percentageUsed = false # The TimeSheetRecord list. @records = [] @messageHandler = MessageHandlerInstance.instance end # Add a new TimeSheetRecord to the list. def<<(record) @records.each do |r| if r.task == record.task error('ts_duplicate_task', "Duplicate records for task #{r.taskId}") end end @records << record end # Perform all kinds of consitency checks. def check totalSlots = 0 @records.each do |record| record.check totalSlots += record.work end unless (scenarioIdx = @resource.project['trackingScenarioIdx']) error('ts_no_tracking_scenario', 'No trackingscenario has been defined.') end if @resource['efficiency', scenarioIdx] > 0.0 targetSlots = totalNetWorkingSlots # This is the acceptable rounding error when checking the total # reported work. delta = 1 if totalSlots < (targetSlots - delta) error('ts_work_too_low', "The total work to be reported for this time sheet " + "is #{workWithUnit(targetSlots)} but only " + "#{workWithUnit(totalSlots)} were reported.") end if totalSlots > (targetSlots + delta) error('ts_work_too_high', "The total work to be reported for this time sheet " + "is #{workWithUnit(targetSlots)} but " + "#{workWithUnit(totalSlots)} were reported.") end else if totalSlots > 0 error('ts_work_not_null', "The reported work for non-working resources must be 0.") end end end def warnOnDelta project = @resource.project startIdx = project.dateToIdx(@interval.start) endIdx = project.dateToIdx(@interval.end) @records.each do |record| record.warnOnDelta(startIdx, endIdx) end end # Compute the total number of potential working time slots during the # report period. This value is not resource specific. def totalGrossWorkingSlots project = @resource.project # Calculate the number of weeks in the report weeksToReport = (@interval.end - @interval.start) / (60 * 60 * 24 * 7) daysToSlots(project.weeklyWorkingDays * weeksToReport) end # Compute the total number of actual working time slots of the # Resource. This is the sum of allocated, free time slots. def totalNetWorkingSlots project = @resource.project startIdx = project.dateToIdx(@interval.start) endIdx = project.dateToIdx(@interval.end) @resource.getAllocatedSlots(@scenarioIdx, startIdx, endIdx, nil) + @resource.getFreeSlots(@scenarioIdx, startIdx, endIdx) end # Converts allocation percentage into time slots. def percentToSlots(value) @percentageUsed = true (totalGrossWorkingSlots * value).to_i end # Computes how many percent the _slots_ are of the total working slots in # the report time frame. def slotsToPercent(slots) slots.to_f / totalGrossWorkingSlots end def slotsToDays(slots) slots * @resource.project['scheduleGranularity'] / (60 * 60 * @resource.project.dailyWorkingHours) end def daysToSlots(days) ((days * 60 * 60 * @resource.project.dailyWorkingHours) / @resource.project['scheduleGranularity']).to_i end def error(id, text, sourceFileInfo = nil) @messageHandler.error(id, text, sourceFileInfo || @sourceFileInfo, nil, @resource) end def warning(id, text, sourceFileInfo = nil) @messageHandler.warning(id, text, sourceFileInfo, nil, @resource) end private def workWithUnit(slots) if @percentageUsed "#{(slotsToPercent(slots) * 100.0).to_i}%" else "#{slotsToDays(slots)} days" end end end # A class to hold all time sheets of a project. class TimeSheets < Array def initialize super end def check each { |s| s.check } end def warnOnDelta each { |s| s.warnOnDelta } end end end taskjuggler-3.5.0/lib/taskjuggler/ResourceScenario.rb0000644000175000017500000010156312614413013022242 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ResourceScenario.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/ScenarioData' class TaskJuggler class ResourceScenario < ScenarioData def initialize(resource, scenarioIdx, attributes) super # Scoreboard may be nil, a Task, or a bit vector encoded as a Fixnum # nil: Value has not been determined yet. # Task: A reference to a Task object # Bit 0: Reserved # Bit 1: 0: Work time (as defined by working hours) # 1: No work time (as defined by working hours) # Bit 2 - 5: 0: No holiday or leave time # 1: Public holiday (holiday) # 2: Annual leave # 3: Special leave # 4: Sick leave # 5: unpaid leave # 6: blocked for other projects # 7 - 15: Reserved # Bit 6 - 7: Reserved # Bit 8: 0: No global override # 1: Override global setting # The scoreboard is only created when needed to save memory for projects # which read-in the coporate employee database but only need a small # subset. @scoreboard = nil # The index of the earliest booked time slot. @firstBookedSlot = nil # Same but for each assigned resource. @firstBookedSlots = {} # The index of the last booked time Slot. @lastBookedSlot = nil # Same but for each assigned resource. @lastBookedSlots = {} # First available slot of the resource. @minslot = nil # Last available slot of the resource. @maxslot = nil # Attributed are only really created when they are accessed the first # time. So make sure some needed attributes really exist so we don't # have to check for existance each time we access them. %w( alloctdeffort chargeset criticalness directreports duties efficiency effort limits managers rate reports shifts leaves leaveallowances workinghours ).each do |attr| @property[attr, @scenarioIdx] end @dCache = DataCache.instance end # This method must be called at the beginning of each scheduling run. It # initializes variables used during the scheduling process. def prepareScheduling @effort = 0 initScoreboard if @property.leaf? setDirectReports end # The criticalness of a resource is a measure for the probabilty that all # allocations can be fullfilled. The smaller the value, the more likely # will the tasks get the resource. A value above 1.0 means that # statistically some tasks will not get their resources. A value between # 0 and 1 implies no guarantee, though. def calcCriticalness if @scoreboard.nil? # Resources that are not allocated are not critical at all. @criticalness = 0.0 else freeSlots = 0 @scoreboard.each do |slot| freeSlots += 1 if slot.nil? end @criticalness = freeSlots == 0 ? 1.0 : @alloctdeffort / freeSlots end end def setDirectReports # Only leaf resources have reporting lines. return unless @property.leaf? # The 'directreports' attribute is the reverse link for the 'managers' # attribute. In contrast to the 'managers' attribute, the # 'directreports' list has no duplicate entries. @managers.each do |manager| unless manager['directreports', @scenarioIdx].include?(@property) manager['directreports', @scenarioIdx] << @property end end end def setReports return unless @directreports.empty? @managers.each do |r| r.setReports_i(@scenarioIdx, [ @property ]) end end def preScheduleCheck @managers.each do |manager| unless manager.leaf? error('manager_is_group', "Resource #{@property.fullId} has group #{manager.fullId} " + "assigned as manager. Managers must be leaf resources.") end if manager == @property error('manager_is_self', "Resource #{@property.fullId} cannot manage itself.") end end end # This method does some housekeeping work after the scheduling is # completed. It's meant to be called for top-level resources and then # recursively descends into all child resources. def finishScheduling # Recursively descend into all child resources. @property.children.each do |resource| resource.finishScheduling(@scenarioIdx) end # Add the parent tasks of each task to the duties list. @duties.each do |task| task.ancestors(true).each do |pTask| @duties << pTask unless @duties.include?(pTask) end end # Add the assigned task to the parent(s) resource duties list. @property.parents.each do |pResource| @duties.each do |task| unless pResource['duties', @scenarioIdx].include?(task) pResource['duties', @scenarioIdx] << task end end end end # Returns true if the resource is available at the time specified by # _sbIdx_. def available?(sbIdx) return false unless @scoreboard[sbIdx].nil? limits = @limits return false if limits && !limits.ok?(sbIdx) true end # Return true if the resource is booked for a tasks at the time specified by # _sbIdx_. def booked?(sbIdx) @scoreboard[sbIdx].is_a?(Task) end # Return the Task that this resource is booked for at the time specified # by _sbIdx_. If not booked to a task, nil is returned. def bookedTask(sbIdx) return nil unless (sb = @scoreboard[sbIdx]).is_a?(Task) sb end # Book the slot indicated by the scoreboard index +sbIdx+ for Task +task+. # If +force+ is true, overwrite the existing booking for this slot. The # method returns true if the slot was available. def book(sbIdx, task, force = false) return false if !force && !available?(sbIdx) # Make sure the task is in the list of duties. unless @duties.include?(task) @duties << task end #puts "Booking resource #{@property.fullId} at " + # "#{@scoreboard.idxToDate(sbIdx)}/#{sbIdx} for task #{task.fullId}\n" @scoreboard[sbIdx] = task # Track the total allocated slots for this resource. @effort += @efficiency @limits.inc(sbIdx) if @limits task.incLimits(@scenarioIdx, sbIdx, @property) # Scoreboard iterations are fairly expensive but they are very frequent # operations in later processing. To limit the interations to the # relevant intervals, we store the interval for all bookings and for # each individual task. if @firstBookedSlot.nil? || @firstBookedSlot > sbIdx @firstBookedSlot = @firstBookedSlots[task] = sbIdx elsif @firstBookedSlots[task].nil? || @firstBookedSlots[task] > sbIdx @firstBookedSlots[task] = sbIdx end if @lastBookedSlot.nil? || @lastBookedSlot < sbIdx @lastBookedSlot = @lastBookedSlots[task] = sbIdx elsif @lastBookedSlots[task].nil? || @lastBookedSlots[task] < sbIdx @lastBookedSlots[task] = sbIdx end true end def bookBooking(sbIdx, booking) initScoreboard if @scoreboard.nil? unless @scoreboard[sbIdx].nil? if booked?(sbIdx) error('booking_conflict', "Resource #{@property.fullId} has multiple conflicting " + "bookings for #{@scoreboard.idxToDate(sbIdx)}. The " + "conflicting tasks are #{@scoreboard[sbIdx].fullId} and " + "#{booking.task.fullId}.", booking.sourceFileInfo) end val = @scoreboard[sbIdx] if ((val & 2) != 0 && booking.overtime < 1) # The booking is blocked due to the overtime attribute. Now let's # see if the user wants to be warned about it. if booking.sloppy < 1 error('booking_no_duty', "Resource #{@property.fullId} has no duty at " + "#{@scoreboard.idxToDate(sbIdx)}.", booking.sourceFileInfo) end return false end if ((val & 0x3C) != 0 && booking.overtime < 2) # The booking is blocked due to the overtime attribute. Now let's # see if the user wants to be warned about it. if booking.sloppy < 2 error('booking_on_vacation', "Resource #{@property.fullId} is on vacation at " + "#{@scoreboard.idxToDate(sbIdx)}.", booking.sourceFileInfo) end return false end end book(sbIdx, booking.task, true) end # @effort only trackes the already allocated effort for leaf resources. It's # too expensive to propagate this to the group resources on every booking. # If a value for a group effort is needed, it's computed here. def bookedEffort if @property.leaf? @effort else effort = 0 @property.kids.each do |r| effort += r.bookedEffort(@scenarioIdx) end effort end end # Compute the annual leave days within the period specified by the # _query_. The result is in days. def query_annualleave(query) query.sortable = query.numerical = val = getLeave(query.startIdx, query.endIdx, :annual) query.string = query.scaleLoad(val) end def query_annualleavebalance(query) if @property.leaf? leave = getLeave(query.startIdx, query.endIdx, :annual) allowanceSlots = @leaveallowances.balance(:annual, query.start, query.end) allowance = @project.slotsToDays(allowanceSlots) query.sortable = query.numerical = val = allowance - leave query.string = query.scaleLoad(val) end end # Compute the cost generated by this Resource for a given Account during a # given interval. If a Task is provided as scopeProperty only the turnover # directly assiciated with the Task is taken into account. def query_cost(query) if query.costAccount query.sortable = query.numerical = cost = turnover(query.startIdx, query.endIdx, query.costAccount, query.scopeProperty, true) query.string = query.currencyFormat.format(cost) else query.string = 'No \'balance\' defined!' end end # The effort allocated to the Resource in the specified interval. In case a # Task is given as scope property only the effort allocated to this Task is # taken into account. def query_effort(query) query.sortable = query.numerical = effort = getEffectiveWork(query.startIdx, query.endIdx, query.scopeProperty) query.string = query.scaleLoad(effort) end # The completed (as of 'now') effort allocated for the resource in the # specified interval. In case a Task is given as scope property only # the effort allocated for this Task is taken into account. def query_effortdone(query) # For this query, we always override the query period. query.sortable = query.numerical = effort = getEffectiveWork(@project.dateToIdx(@project['start'], false), @project.dateToIdx(@project['now']), query.scopeProperty) query.string = query.scaleLoad(effort) end # The remaining (as of 'now') effort allocated for the resource in the # specified interval. In case a Task is given as scope property only # the effort allocated for this Task is taken into account. def query_effortleft(query) # For this query, we always override the query period. query.sortable = query.numerical = effort = getEffectiveWork(@project.dateToIdx(@project['now']), @project.dateToIdx(@project['end'], false), query.scopeProperty) query.string = query.scaleLoad(effort) end # The unallocated work time of the Resource during the specified interval. def query_freetime(query) query.sortable = query.numerical = time = getEffectiveFreeTime(query.startIdx, query.endIdx) / (60 * 60 * 24) query.string = query.scaleDuration(time) end # The unallocated effort of the Resource during the specified interval. def query_freework(query) query.sortable = query.numerical = work = getEffectiveFreeWork(query.startIdx, query.endIdx) query.string = query.scaleLoad(work) end # The the Full-time equivalent for the resource or group. def query_fte(query) fte = 0.0 if @property.container? # Accumulate the FTEs of all sub-resources. @property.kids.each do |resource| resource.query_fte(@scenarioIdx, query) fte += query.to_num end else # TODO: Getting the globalWorkSlots is relatively expensive. We # probably don't need to compute this for every resource. globalWorkSlots = @project.getWorkSlots(query.startIdx, query.endIdx) workSlots = getWorkSlots(query.startIdx, query.endIdx) if globalWorkSlots > 0 fte = (workSlots.to_f / globalWorkSlots) * @efficiency end end query.sortable = query.numerical = fte query.string = query.numberFormat.format(fte) end # The headcount of the resource or group. def query_headcount(query) headcount = 0 if @property.container? @property.kids.each do |resource| resource.query_headcount(@scenarioIdx, query) headcount += query.to_num end else headcount += @efficiency.round end query.sortable = query.numerical = headcount query.string = query.numberFormat.format(headcount) end # Get the rate of the resource. def query_rate(query) query.sortable = query.numerical = r = rate query.string = query.currencyFormat.format(r) end # Compute the revenue generated by this Resource for a given Account during # a given interval. If a Task is provided as scopeProperty only the # revenue directly associated to this Task is taken into account. def query_revenue(query) if query.revenueAccount query.sortable = query.numerical = revenue = turnover(query.startIdx, query.endIdx, query.revenueAccount, query.scopeProperty) query.string = query.currencyFormat.format(revenue) else query.string = 'No \'balance\' defined!' end end # Compute the sick leave days within the period specified by the # _query_. The result is in days. def query_sickleave(query) query.sortable = query.numerical = val = getLeave(query.startIdx, query.endIdx, :sick) query.string = query.scaleLoad(val) end # Compute the special leave days within the period specified by the # _query_. The result is in days. def query_specialleave(query) query.sortable = query.numerical = val = getLeave(query.startIdx, query.endIdx, :special) query.string = query.scaleLoad(val) end # The work time of the Resource that was blocked by leaves during the # specified TimeInterval. The result is in working days (effort). def query_timeoffdays(query) query.sortable = query.numerical = time = getTimeOffDays(query.startIdx, query.endIdx) query.string = query.scaleLoad(time) end # Compute the unpaid leave days within the period specified by the # _query_. The result is in days. def query_unpaidleave(query) query.sortable = query.numerical = val = getLeave(query.startIdx, query.endIdx, :unpaid) query.string = query.scaleLoad(val) end # A generic tree iterator that recursively accumulates the result of the # block for each leaf object. def treeSum(startIdx, endIdx, *args, &block) cacheTag = "#{self.class}.#{caller[0][/`.*'/][1..-2]}" treeSumR(cacheTag, startIdx, endIdx, *args, &block) end # Recursing method for treeSum. def treeSumR(cacheTag, startIdx, endIdx, *args, &block) # Check if the value to be computed is already in the data cache. If so, # return it. Otherwise we have to compute it first and then store it in # the cache. We use the signature of the method that called treeSum() # and its arguments together with 'self' as index to the cache. @dCache.cached(self, cacheTag, startIdx, endIdx, *args) do if @property.container? sum = 0.0 # Iterate over all the kids and accumulate the result of the # recursively called method. @property.kids.each do |resource| sum += resource.treeSumR(@scenarioIdx, cacheTag, startIdx, endIdx, *args, &block) end sum else instance_eval(&block) end end end # Returns the number of leave days for the period described by _startIdx_ # and _endIdx_ for the given _type_ of leave. def getLeave(startIdx, endIdx, type) treeSum(startIdx, endIdx, type) do @project.convertToDailyLoad(@project['scheduleGranularity'] * getLeaveSlots(startIdx, endIdx, type)) end end # Returns the work of the resource (and its children) weighted by their # efficiency. If _task_ is provided, only the work for this task and all # its sub tasks are being counted. def getEffectiveWork(startIdx, endIdx, task = nil) # Make sure we have the real Task and not a proxy. task = task.ptn if task # There can't be any effective work if the start is after the end or the # todo list doesn't contain the specified task. return 0.0 if startIdx >= endIdx || (task && !@duties.include?(task)) # Temporary workaround until @duties is fixed again. # The unique key we use to address the result in the cache. @dCache.cached(self, :ResourceScenarioGetEffectiveWork, startIdx, endIdx, task) do work = 0.0 if @property.container? @property.kids.each do |resource| work += resource.getEffectiveWork(@scenarioIdx, startIdx, endIdx, task) end else unless @scoreboard.nil? work = @project.convertToDailyLoad( getAllocatedSlots(startIdx, endIdx, task) * @project['scheduleGranularity']) * @efficiency end end work end end # Returns the allocated accumulated time of this resource and its children. def getAllocatedTime(startIdx, endIdx, task = nil) treeSum(startIdx, endIdx, task) do return 0 if @scoreboard.nil? @project.convertToDailyLoad(@project['scheduleGranularity'] * getAllocatedSlots(startIdx, endIdx, task)) end end # Return the unallocated work time (in seconds) of the resource and its # children. def getEffectiveFreeTime(startIdx, endIdx) treeSum(startIdx, endIdx) do getFreeSlots(startIdx, endIdx) * @project['scheduleGranularity'] end end # Return the unallocated work of the resource and its children weighted by # their efficiency. def getEffectiveFreeWork(startIdx, endIdx) treeSum(startIdx, endIdx) do @project.convertToDailyLoad(getFreeSlots(startIdx, endIdx) * @project['scheduleGranularity']) * @efficiency end end # Return the number of working days that are blocked by leaves. def getTimeOffDays(startIdx, endIdx) treeSum(startIdx, endIdx) do @project.convertToDailyLoad(getTimeOffSlots(startIdx, endIdx) * @project['scheduleGranularity']) * @efficiency end end def turnover(startIdx, endIdx, account, task = nil, includeKids = false) amount = 0.0 if @property.container? && includeKids @property.kids.each do |child| amount += child.turnover(@scenarioIdx, startIdx, endIdx, account, task) end else if task # If we have a known task, we only include the amount that is # specific to this resource, this task and the chargeset of the # task. amount += task.turnover(@scenarioIdx, startIdx, endIdx, account, @property) elsif !@chargeset.empty? # If no tasks was provided, we include the amount of this resource, # weighted by the chargeset of this resource. totalResourceCost = cost(startIdx, endIdx) @chargeset.each do |set| set.each do |accnt, share| if share > 0.0 && (accnt == account || accnt.isChildOf?(account)) amount += totalResourceCost * share end end end end end amount end # Returns the cost for using this resource during the specified # TimeInterval _period_. If a Task _task_ is provided, only the work on # this particular task is considered. def cost(startIdx, endIdx, task = nil) getAllocatedTime(startIdx, endIdx, task) * @rate end # Returns true if the resource or any of its children is allocated during # the period specified with the TimeInterval _iv_. If task is not nil # only allocations to this tasks are respected. def allocated?(iv, task = nil) return false if task && !@duties.include?(task) startIdx = @project.dateToIdx(iv.start) endIdx = @project.dateToIdx(iv.end) startIdx, endIdx = fitIndicies(startIdx, endIdx, task) return false if startIdx >= endIdx return allocatedSub(startIdx, endIdx, task) end # Iterate over the scoreboard and turn its content into a set of Bookings. # _iv_ can be a TimeInterval to limit the bookings within the provided # period. if _hashByTask_ is true, the result is a Hash of Arrays with # bookings hashed by Task. Otherwise it's just a plain Array with # Bookings. def getBookings(iv = nil, hashByTask = true) bookings = hashByTask ? {} : [] return bookings if @property.container? || @scoreboard.nil? || @firstBookedSlot.nil? || @lastBookedSlot.nil? # To speedup the collection we start with the first booked slot and end # with the last booked slot. startIdx = @firstBookedSlot endIdx = @lastBookedSlot + 1 # If the user provided a TimeInterval, we only return bookings within # this TimeInterval. if iv ivStartIdx = @project.dateToIdx(iv.start) ivEndIdx = @project.dateToIdx(iv.end) startIdx = ivStartIdx if ivStartIdx > startIdx endIdx = ivEndIdx if ivEndIdx < endIdx end lastTask = nil bookingStart = nil startIdx.upto(endIdx) do |idx| task = @scoreboard[idx] # Now we watch for task changes. if task != lastTask || (task.is_a?(Task) && (lastTask.nil? || idx == endIdx)) if lastTask # We've found the end of a task booking series. # If we don't have a Booking for the task yet, we create one. if hashByTask if bookings[lastTask].nil? bookings[lastTask] = Booking.new(@property, lastTask, []) end # Append the new interval to the Booking. bookings[lastTask].intervals << TimeInterval.new(@scoreboard.idxToDate(bookingStart), @scoreboard.idxToDate(idx)) else if bookings.empty? || bookings.last.task != lastTask bookings << Booking.new(@property, lastTask, []) end # Append the new interval to the Booking. bookings.last.intervals << TimeInterval.new(@scoreboard.idxToDate(bookingStart), @scoreboard.idxToDate(idx)) end end # Get ready for the next task booking interval if task.is_a?(Task) lastTask = task bookingStart = idx else lastTask = bookingStart = nil end end end bookings end # Return a list of scoreboard intervals that are at least _minDuration_ long # and contain only off-duty and leave slots. The result is an Array of # [ start, end ] TjTime values. def collectTimeOffIntervals(iv, minDuration) # Time-off intervals are only useful for leaf resources. Group resources # would just default to the global working hours. return [] unless @property.leaf? initScoreboard if @scoreboard.nil? @scoreboard.collectIntervals(iv, minDuration) do |val| val.is_a?(Fixnum) && (val & 0x3E) != 0 end end # Count the booked slots between the start and end index. If _task_ is not # nil count only those slots that are assigned to this particular task or # any of its sub tasks. def getAllocatedSlots(startIdx, endIdx, task = nil) # If there is no scoreboard, we don't have any allocations. return 0 unless @scoreboard startIdx, endIdx = fitIndicies(startIdx, endIdx, task) return 0 if startIdx >= endIdx bookedSlots = 0 taskList = task ? task.all : [] @scoreboard.each(startIdx, endIdx) do |slot| if slot.is_a?(Task) && (task.nil? || taskList.include?(slot)) bookedSlots += 1 end end bookedSlots end # Count the number of slots betweend the _startIdx_ and _endIdx_ that can # be used for work def getWorkSlots(startIdx, endIdx) countSlots(startIdx, endIdx) do |val| # We count free slots and assigned slots. val.nil? || val.is_a?(Task) end end # Count the number of slots that are work time slots but marked as annual # leave. def getLeaveSlots(startIdx, endIdx, type) countSlots(startIdx, endIdx) do |val| val.is_a?(Fixnum) && (val & 0x3E) == (Leave::Types[type] << 2) end end # Count the free slots between the start and end index. def getFreeSlots(startIdx, endIdx) countSlots(startIdx, endIdx) do |val| val.nil? end end # Count the regular work time slots between the start and end index that # have been blocked by leaves. def getTimeOffSlots(startIdx, endIdx) countSlots(startIdx, endIdx) do |val| # Bit 1 needs to be unset and the leave bits must not be 0. val.is_a?(Fixnum) && (val & 0x2) == 0 && (val & 0x3C) != 0 end end # Get the first available slot of the resource. def getMinSlot initScoreboard unless @minslot @minslot end # Get the last available slot of the resource. def getMaxSlot initScoreboard unless @maxslot @maxslot end private def initScoreboard # Create scoreboard and mark all slots as non-working-time. @scoreboard = Scoreboard.new(@project['start'], @project['end'], @project['scheduleGranularity'], 2) # Change all work time slots to nil (available) again. @project.scoreboardSize.times do |i| @scoreboard[i] = nil if onShift?(i) end # Mark all global leave slots as such @project['leaves'].each do |leave| startIdx = @scoreboard.dateToIdx(leave.interval.start) endIdx = @scoreboard.dateToIdx(leave.interval.end) startIdx.upto(endIdx - 1) do |i| sb = @scoreboard[i] # We preseve the work-time bit (#1). @scoreboard[i] = (sb.nil? ? 0 : 2) | (leave.typeIdx << 2) end end # Mark all resource specific leave slots as such @leaves.each do |leave| startIdx = @scoreboard.dateToIdx(leave.interval.start) endIdx = @scoreboard.dateToIdx(leave.interval.end) startIdx.upto(endIdx - 1) do |i| if (sb = @scoreboard[i]) # The slot is already marked as non-working slot. We override the # leave type if the new type is larger than the old one. leaveIdx = (sb & 0x3C) >> 2 if leave.typeIdx > leaveIdx # The work-time bit (#1) is preserved. @scoreboard[i] = (sb & 0x2) | (leave.typeIdx << 2) end else # This marks a working time slot as a leave slot. Since bit 1 is # not set, we still know that this could be a working slot. @scoreboard[i] = leave.typeIdx << 2 end end end unless @shifts.nil? # Mark the leaves from all the shifts the resource is assigned to. @project.scoreboardSize.times do |i| v = @shifts.getSbSlot(i) # Make sure a shift is actually assigned. next unless v if (v & (1 << 8)) != 0 # Check if the leave replacement bit (#8) is set. In that case we # copy the whole interval over to the resource scoreboard # overriding any global leaves. @scoreboard[i] = (v & 0x3E == 0) ? nil : (v & 0x3D) elsif ((sb = @scoreboard[i]).nil? || ((sb & 0x3C) < (v & 0x3C))) && (v & 0x3C) != 0 # In merge mode, we only add the shift leaves with higher type # index or unassigned slots. @scoreboard[i] = v & 0x3E end end end # Set minimum and maximum availability idx = 0 while idx < @scoreboard.size if available?(idx) @minslot = idx break end idx += 1 end idx = @scoreboard.size - 1 while idx >= 0 if available?(idx) @maxslot = idx break end idx -= 1 end end def countSlots(startIdx, endIdx) return 0 if startIdx >= endIdx initScoreboard unless @scoreboard slots = 0 startIdx.upto(endIdx - 1) do |idx| slots += 1 if yield(@scoreboard[idx]) end slots end # Limit the _startIdx_ and _endIdx_ to the actually assigned interval. # If _task_ is provided, fit it for the bookings of this particular task. def fitIndicies(startIdx, endIdx, task = nil) if task startIdx = @firstBookedSlots[task] if @firstBookedSlots[task] && startIdx < @firstBookedSlots[task] endIdx = @lastBookedSlots[task] + 1 if @lastBookedSlots[task] && endIdx > @lastBookedSlots[task] + 1 else startIdx = @firstBookedSlot if @firstBookedSlot && startIdx < @firstBookedSlot endIdx = @lastBookedSlot + 1 if @lastBookedSlot && endIdx > @lastBookedSlot + 1 end [ startIdx, endIdx ] end def setReports_i(reports) if reports.include?(@property) # A manager must never show up in the list of his/her own reports. error('manager_loop', "Management loop detected. #{@property.fullId} has self " + "in list of reports") end @reports += reports # Resources can end up multiple times in the list if they have multiple # reporting chains. We only need them once in the list. @reports.uniq! @managers.each do |r| r.setReports_i(@scenarioIdx, @reports) end end def onShift?(sbIdx) if @shifts && @shifts.assigned?(sbIdx) return @shifts.onShift?(sbIdx) else @workinghours.onShift?(sbIdx) end end # Returns true if the resource or any of its children is allocated during # the period specified with _startIdx_ and _endIdx_. If task is not nil # only allocations to this tasks are respected. def allocatedSub(startIdx, endIdx, task) if @property.container? @property.kids.each do |resource| return true if resource.allocatedSub(@scenarioIdx, startIdx, endIdx, task) end else return false unless @scoreboard && @duties.include?(task) startIdx, endIdx = fitIndicies(startIdx, endIdx, task) return false if startIdx >= endIdx startIdx.upto(endIdx - 1) do |idx| return true if @scoreboard[idx] == task end end false end # Return the daily cost of a resource or resource group. def rate if @property.container? dailyRate = 0.0 @property.kids.each do |resource| dailyRate += resource.rate(@scenarioIdx) end dailyRate else @rate end end end end taskjuggler-3.5.0/lib/taskjuggler/apps/0000755000175000017500000000000012614413013017377 5ustar bernatbernattaskjuggler-3.5.0/lib/taskjuggler/apps/Tj3WebD.rb0000644000175000017500000000613712614413013021135 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3WebD.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'drb' require 'taskjuggler/Tj3AppBase' require 'taskjuggler/MessageHandler' require 'taskjuggler/daemon/WebServer' # Name of the application AppConfig.appName = 'tj3webd' class TaskJuggler class Tj3WebD < Tj3AppBase def initialize super @mhi = MessageHandlerInstance.instance @mhi.logFile = File.join(Dir.getwd, "/#{AppConfig.appName}.log") @mhi.appName = AppConfig.appName # By default show only warnings and more serious messages. @mhi.outputLevel = :warning @daemonize = true @uriFile = File.join(Dir.getwd, '.tj3d.uri') @port = nil @webServerPort = nil @pidFile = nil end def processArguments(argv) super do @opts.banner += <<'EOT' The TaskJuggler web server can be used to serve the HTTP reports of TaskJuggler projects to be viewed by any HTML5 compliant web browser. It uses the TaskJuggler daemon (tj3d) for data hosting and report generation. EOT @opts.on('-d', '--dont-daemonize', format("Don't put program into daemon mode. Keep it " + 'connected to the terminal and show debug output.')) do @daemonize = false end @opts.on('-p', '--port ', Integer, format('Use the specified TCP/IP port to connect to the ' + 'TaskJuggler daemon (Default: 8474).')) do |arg| @port = arg end @opts.on('--pidfile ', String, format('Write the process ID of the daemon to the ' + 'specified file.')) do |arg| @pidFile = arg end @opts.on('--urifile', String, format('If the port is 0, use this file to read the URI ' + 'of the TaskJuggler daemon.')) do |arg| @uriFile = arg end @opts.on('--webserver-port ', Integer, format('Use the specified TCP/IP port to serve web browser ' + 'requests (Default: 8080).')) do |arg| @webServerPort = arg end end end def appMain(files) @rc.configure(self, 'global') @rc.configure(@mhi, 'global.log') webServer = WebServer.new @rc.configure(webServer, 'global') @rc.configure(webServer, 'webd') # Set some config variables if corresponding data was provided via the # command line. webServer.port = @port if @port webServer.uriFile = @uriFile.untaint webServer.webServerPort = @webServerPort if @webServerPort webServer.daemonize = @daemonize webServer.pidFile = @pidFile debug('', "pidFile 1: #{@pidFile}") webServer.start 0 end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3TsReceiver.rb0000644000175000017500000000300012614413013022351 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3TsReceiver.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This script is used to send out the time sheet templates to the employees. # It should be run from a cron job once a week. require 'taskjuggler/Tj3SheetAppBase' require 'taskjuggler/TimeSheetReceiver' # Name of the application suite AppConfig.appName = 'tj3ts_receiver' class TaskJuggler class Tj3TsReceiver < Tj3SheetAppBase def initialize super end def processArguments(argv) super do @opts.banner += <<'EOT' This program can be used to receive filled-out time sheets via email. It reads the emails from STDIN and extracts the time sheet from the attached files. The time sheet is checked for correctness. Good time sheets are filed away. The sender will be informed by email that the time sheets was accepted or rejected. EOT end end def appMain(argv) ts = TimeSheetReceiver.new('tj3ts_receiver') @rc.configure(ts, 'global') @rc.configure(ts, 'timesheets') @rc.configure(ts, 'timesheets.receiver') ts.workingDir = @workingDir if @workingDir ts.dryRun = @dryRun ts.processEmail 0 end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3SsSender.rb0000644000175000017500000000537012614413013022040 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3SsSender.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This script is used to send out the time sheet templates to the employees. # It should be run from a cron job once a week. require 'taskjuggler/Tj3SheetAppBase' require 'taskjuggler/StatusSheetSender' # Name of the application AppConfig.appName = 'tj3ss_sender' class TaskJuggler class Tj3SsSender < Tj3SheetAppBase def initialize super @optsSummaryWidth = 25 @force = false @intervalDuration = nil @hideResource = nil # The default report period end is next Wednesday 0:00. @date = TjTime.new.nextDayOfWeek(3).to_s('%Y-%m-%d') @resourceList = [] end def processArguments(argv) super do @opts.banner += <<'EOT' This program can be used to out status sheets templates via email. It will generate status sheet templates for managers of the project. The project data will be accesses via tj3client from a running TaskJuggler server process. EOT @opts.on('-r', '--resource ', String, format('Only generate template for given resource')) do |arg| @resourceList << arg end @opts.on('-f', '--force', format('Send out a new template even if one exists ' + 'already')) do |arg| @force = true end @opts.on('--hideresource ', String, format('Filter expression to limit the resource list')) do |arg| @hideResource = arg end @opts.on('-i', '--interval ', String, format('The duration of the interval. This is a number ' + 'directly followed by a unit. 1w means one week ' + '(the default), 5d means 5 days and 72h means 72 ' + 'hours.')) do |arg| @intervalDuration = arg end optsEndDate end end def appMain(argv) ts = StatusSheetSender.new('tj3ss_sender') @rc.configure(ts, 'global') @rc.configure(ts, 'statussheets') @rc.configure(ts, 'statussheets.sender') ts.workingDir = @workingDir if @workingDir ts.dryRun = @dryRun ts.force = @force ts.intervalDuration = @intervalDuration if @intervalDuration ts.date = @date if @date ts.hideResource = @hideResource if @hideResource ts.sendTemplates(@resourceList) 0 end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3Daemon.rb0000644000175000017500000001403112614413013021507 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3Daemon.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'drb' require 'taskjuggler/Tj3AppBase' require 'taskjuggler/MessageHandler' require 'taskjuggler/daemon/ProjectBroker' # Name of the application AppConfig.appName = 'tj3d' class TaskJuggler class Tj3Daemon < Tj3AppBase def initialize super @mandatoryArgs = '[ [ ...] ...]' @mhi = MessageHandlerInstance.instance @mhi.logFile = File.join(Dir.getwd, "/#{AppConfig.appName}.log") @mhi.appName = AppConfig.appName # By default show only warnings and more serious messages. @mhi.outputLevel = :warning @daemonize = true @uriFile = File.join(Dir.getwd, '.tj3d.uri') @port = nil @webServer = false @webServerPort = 8080 @webdPidFile = File.join(Dir.getwd, ".tj3webd-#{$$}.pid").untaint end def processArguments(argv) super do @opts.banner += <<'EOT' The TaskJuggler daemon can be used to quickly generate reports for a number of scheduled projects that are resident in memory. Once the daemon has been started tj3client can be used to control it. EOT @opts.on('-d', '--dont-daemonize', format("Don't put program into daemon mode. Keep it " + 'connected to the terminal and show debug output.')) do @daemonize = false end @opts.on('-p', '--port ', Integer, format('Use the specified TCP/IP port to serve tj3client ' + 'requests (Default: 8474).')) do |arg| @port = arg end @opts.on('--urifile', String, format('If the port is 0, use this file to store the URI ' + 'of the server.')) do |arg| @uriFile = arg end @opts.on('-w', '--webserver', format('Start a web server that serves the reports of ' + 'the loaded projects.')) do @webServer = true end @opts.on('--webserver-port ', Integer, format('Use the specified TCP/IP port to serve web browser ' + 'requests (Default: 8080).')) do |arg| @webServerPort = arg end end end def appMain(files) broker = ProjectBroker.new @rc.configure(self, 'global') @rc.configure(@mhi, 'global.log') @rc.configure(broker, 'global') @rc.configure(broker, 'daemon') # Set some config variables if corresponding data was provided via the # command line. broker.port = @port if @port broker.uriFile = @uriFile.untaint broker.projectFiles = sortInputFiles(files) unless files.empty? broker.daemonize = @daemonize # Create log files for standard IO for each child process if the daemon # is not disconnected from the terminal. broker.logStdIO = !@daemonize if @webServer webdCommand = "tj3webd --webserver-port #{@webServerPort} " + "--pidfile #{@webdPidFile}" # Also start the web server as a separate process. We keep the PID, so # we can terminate that process again when we exit the daemon. begin `#{webdCommand}` rescue error('tj3webd_start_failed', "Could not start tj3webd: #{$!}") end info('web_server_started', "Web server started as '#{webdCommand}'") end broker.start if @webServer pid = nil begin # Read the PID of the web server from the PID file. File.open(@webdPidFile, 'r') do |f| pid = f.read.to_i end rescue warning('cannot_read_webd_pidfile', "Cannot read tj3webd PID file (#{@webdPidFile}): #{$!}") end # If we have started the web server, we are also trying to terminate # that process again. begin Process.kill("TERM", pid) rescue warning('tj3webd_term_failed', "Could not terminate web server: #{$!}") end info('web_server_terminated', "Web server with PID #{pid} terminated") end 0 end private # Sort the provided input files into groups of projects. Each *.tjp file # starts a new project. A *.tjp file may be followed by any number of # *.tji files. The result is an Array of projects. Each consists of an # Array like this: [ , (, ...) ]. def sortInputFiles(files) projects = [] project = nil files.each do |file| if file[-4..-1] == '.tjp' # The project master file determines the working directory. If it's # an absolute file name, that directory will become the working # directory. If it's a relative file name, the current working # directory will be kept. if file[0] == '/' # Absolute file name workingDir = File.dirname(file) fileName = File.basename(file) else # Relative file name workingDir = Dir.getwd fileName = file end project = [ workingDir, fileName ] projects << project elsif file[-4..-1] == '.tji' # .tji files are optional. But if they are specified, they must # always follow the master file in the list. if project.nil? error('tj3d_tji_before_tjp', "You must specify a '.tjp' file before the '.tji' files") end project << file else error('tj3d_no_file_Ext', "Project files must have a '.tjp' or '.tji' extension") end end projects end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3TsSummary.rb0000644000175000017500000000507512614413013022260 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3TsSummary.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This script is used to send out the time sheet templates to the employees. # It should be run from a cron job once a week. require 'taskjuggler/Tj3SheetAppBase' require 'taskjuggler/TimeSheetSummary' # Name of the application AppConfig.appName = 'tj3ts_summary' class TaskJuggler class Tj3TsSummary < Tj3SheetAppBase def initialize super # The default report period end is next Monday 0:00. @date = TjTime.new.nextDayOfWeek(1).to_s('%Y-%m-%d') @resourceList = [] @sheetRecipients = [] @digestRecipients = [] end def processArguments(argv) super do @opts.banner += <<'EOT' This program can be used to send out individual copies and a summary of all accepted time sheets a list of email addresses. The directory structures for templates and submitted time sheets must be present. The project data will be accesses via tj3client from a running TaskJuggler server process. EOT @opts.on('-r', '--resource ', String, format('Only generate summary for given resource')) do |arg| @resourceList << arg end @opts.on('-t', '--to ', String, format('Send all individual reports and a summary report ' + 'to this email address')) do |arg| @sheetRecipients << arg @digestRecipients << arg end @opts.on('--sheet ', String, format('Send all reports to this email address')) do |arg| @sheetRecipients << arg end @opts.on('--digest ', String, format('Send a summary report to this email address')) do |arg| @digestRecipients << arg end optsEndDate end end def appMain(argv) ts = TimeSheetSummary.new @rc.configure(ts, 'global') @rc.configure(ts, 'timesheets') @rc.configure(ts, 'timesheets.summary') ts.workingDir = @workingDir if @workingDir ts.dryRun = @dryRun ts.date = @date if @date ts.sheetRecipients += @sheetRecipients ts.digestRecipients += @digestRecipients ts.sendSummary(@resourceList) 0 end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3Client.rb0000644000175000017500000003457512614413013021541 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3Client.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'drb' require 'drb/acl' require 'taskjuggler/Tj3AppBase' require 'taskjuggler/daemon/DaemonConnector' # Name of the application AppConfig.appName = 'tj3client' class TaskJuggler # The Tj3Client class provides the primary interface to the TaskJuggler # daemon. It exposes a rich commandline interface that supports key # operations like add/removing a project, generating a report or checking a # time or status sheet. All connections are made via DRb and tj3client # requires a properly configured tj3d to work. class Tj3Client < Tj3AppBase include DaemonConnectorMixin def initialize super # For security reasons, this will probably not change. All DRb # operations are limited to localhost only. The client and the sever # must have access to the identical file system. @host = '127.0.0.1' # The default port. 'T' and 'J' in ASCII decimal @port = 8474 # The file with the server URI in case port is 0. @uriFile = File.join(Dir.getwd, '.tj3d.uri') # This must must be changed for the communication to work. @authKey = nil # Determines whether report IDs are fix IDs or regular expressions that # match a set of reports. @regExpMode = false # Prevents usage of protective sandbox if set to true. @unsafeMode = false # List of requested output formats for reports. @formats = nil @mandatoryArgs = ' [arg1 arg2 ...]' # This list describes the supported command line commands and their # parameter. # :label : The command name # :args : A list of parameters. If the first character is a '+' the # parameter must be provided 1 or more times. If the first character is # a '*' the parameter must be provided 0 or more times. Repeatable and # optional paramters must follow the mandatory ones. # :descr : A short description of the command used for the help text. @commands = [ { :label => 'status', :args => [], :descr => 'Display the status of the available projects' }, { :label => 'terminate', :args => [], :descr => 'Terminate the TaskJuggler daemon' }, { :label => 'add', :args => [ 'tjp file', '*tji file'], :descr => 'Add a new project or update and existing one' }, { :label => 'update', :args => [], :descr => 'Reload all projects that have modified files and '+ 'are not being reloaded already' }, { :label => 'remove', :args => [ '+project ID' ], :descr => 'Remove the project with the specified ID from the ' + 'daemon' }, { :label => 'report', :args => [ 'project ID', '+report ID', '!=', '*tji file'], :descr => 'Generate the report with the provided ID for ' + 'the project with the given ID'}, { :label => 'list-reports', :args => [ 'project ID', '!report ID' ], :descr => 'List all available reports of the project or those ' + 'that match the provided report ID' }, { :label => 'check-ts', :args => [ 'project ID', 'time sheet' ], :descr => 'Check the provided time sheet for correctness ' + 'against the project with the given ID'}, { :label => 'check-ss', :args => [ 'project ID', 'status sheet' ], :descr => 'Check the provided status sheet for correctness ' + 'against the project with the given ID'} ] end def processArguments(argv) super do @opts.banner += <<'EOT' The TaskJuggler client is used to send commands and data to the TaskJuggler daemon. The communication is done via TCP/IP. The following commands are supported: EOT # Convert the command list into a help text. @commands.each do |cmd| tail = '' args = cmd[:args].dup args.map! do |c| if c[0] == '*' "[<#{c[1..-1]}> ...]" elsif c[0] == '+' "<#{c[1..-1]}> [<#{c[1..-1]}> ...]" elsif c[0] == '!' tail += ']' "[#{c[1..-1]} " else "<#{c}>" end end args = args.join(' ') @opts.banner += " #{cmd[:label] + ' ' + args + tail}" + "\n\n#{' ' * 10 + format(cmd[:descr], 10)}\n" end @opts.on('-p', '--port ', Integer, format('Use the specified TCP/IP port')) do |arg| @port = arg end @opts.on('--urifile ', String, format('If the port is 0, use this file to get the URI ' + 'of the server.')) do |arg| @uriFile = arg end @opts.on('-r', '--regexp', format('The report IDs are not fixed but regular ' + 'expressions that match a set of reports')) do |arg| @regExpMode = true end @opts.on('--unsafe', format('Run the program without sandbox protection. This ' + 'is not recommended for normal operation! It may ' + 'only be used for debugging or testing ' + 'purposes.')) do |arg| @unsafeMode = true end @opts.on('--format [FORMAT]', [ :csv, :html, :mspxml, :niku, :tjp ], format('Request the report to be generated in the specified' + 'format. Use multiple options to request multiple ' + 'formats. Supported formats are csv, html, niku and ' + 'tjp. By default, the formats specified in the ' + 'report definition are used.')) do |arg| @formats = [] unless @formats @formats << arg end end end def appMain(args) # Run a first check of the non-optional command line arguments. checkCommand(args) # Read some configuration variables. Except for the authKey, they are # all optional. @rc.configure(self, 'global') @broker = connectDaemon retVal = executeCommand(args[0], args[1..-1]) disconnectDaemon @broker = nil retVal end private def checkCommand(args) if args.empty? errorMessage = 'You must specify a command!' else errorMessage = "Unknown command #{args[0]}" @commands.each do |cmd| # The first value of args is the command name. if cmd[:label] == args[0] # Find out how many arguments we need to have and if that's a # lower limit or a fixed value. minArgs = 0 varArgs = false cmd[:args].each do |arg| # Arguments starting with '+' must have 1 or more showings. # Arguments starting with '*' may show up 0 or more times. minArgs += 1 unless '!*'.include?(arg[0]) varArgs = true if '!*+'.include?(arg[0]) end return true if args.length - 1 >= minArgs errorMessage = "Command #{args[0]} must have " + "#{varArgs ? 'at least ' : ''}#{minArgs} " + 'arguments' end end end error('tjc_cmd_error', errorMessage) end def executeCommand(command, args) case command when 'status' $stdout.puts callDaemon(:status, []) when 'terminate' callDaemon(:stop, []) info('tjc_daemon_term', 'Daemon terminated') when 'add' res = callDaemon(:addProject, [ Dir.getwd, args, $stdout, $stderr, $stdin, @silent ]) if res info('tjc_proj_added', "Project(s) #{args.join(', ')} added") return 0 else warning('tjc_proj_adding_failed', "Projects(s) #{args.join(', ')} could not be added") return 1 end when 'remove' args.each do |arg| unless callDaemon(:removeProject, arg) error('tjc_prj_not_found', "Project '#{arg}' not found in list") end end info('tjc_prj_removed', 'Project removed') when 'update' callDaemon(:update, []) info('tjc_reload_req', 'Reload requested') when 'report' # The first value of args is the project ID. The following values # could be either report IDs or TJI file # names ('.' or '*.tji'). projectId = args.shift # Ask the ProjectServer to launch a new ReportServer process and # provide a DRbObject reference to it. connectToReportServer(projectId) reportIds, tjiFiles = splitIdsAndFiles(args) if reportIds.empty? disconnectReportServer error('tjc_no_rep_id', 'You must provide at least one report ID') end # Send the provided .tji files to the ReportServer. failed = !addFiles(tjiFiles) # Ask the ReportServer to generate the reports with the provided IDs. unless failed reportIds.each do |reportId| begin unless @reportServer.generateReport(@rs_authKey, reportId, @regExpMode, @formats, nil) failed = true break end rescue error('tjc_gen_rep_failed', "Could not generate report #{reportId}: #{$!}") end end end # Terminate the ReportServer disconnectReportServer return failed ? 1 : 0 when 'list-reports' # The first value of args is the project ID. The following values # could be either report IDs or TJI file # names ('.' or '*.tji'). projectId = args.shift # Ask the ProjectServer to launch a new ReportServer process and # provide a DRbObject reference to it. connectToReportServer(projectId) reportIds, tjiFiles = splitIdsAndFiles(args) if reportIds.empty? # If the user did not provide a report ID we generate a full list. reportIds = [ '.*' ] @regExpMode = true end # Send the provided .tji files to the ReportServer. failed = !addFiles(tjiFiles) # Ask the ReportServer to generate the reports with the provided IDs. unless failed reportIds.each do |reportId| begin unless @reportServer.listReports(@rs_authKey, reportId, @regExpMode) failed = true break end rescue error('tjc_report_list_failed', "Getting report list failed: #{$!}") end end end # Terminate the ReportServer disconnectReportServer return failed ? 1 : 0 when 'check-ts' connectToReportServer(args[0]) begin res = @reportServer.checkTimeSheet(@rs_authKey, args[1]) rescue error('tjc_tschck_failed', "Time sheet check failed: #{$!}") end disconnectReportServer return res ? 0 : 1 when 'check-ss' connectToReportServer(args[0]) begin res = @reportServer.checkStatusSheet(@rs_authKey, args[1]) rescue error('tjc_sschck_failed', "Status sheet check failed: #{$!}") end disconnectReportServer return res ? 0 : 1 else raise "Unknown command #{command}" end 0 end def connectToReportServer(projectId) @ps_uri, @ps_authKey = callDaemon(:getProject, projectId) if @ps_uri.nil? error('tjc_prj_id_not_loaded', "No project with ID #{projectId} loaded") end begin @projectServer = DRbObject.new(nil, @ps_uri) @rs_uri, @rs_authKey = @projectServer.getReportServer(@ps_authKey) @reportServer = DRbObject.new(nil, @rs_uri) rescue error('tjc_no_rep_srv', "Cannot get report server: #{$!}") end begin @reportServer.connect(@rs_authKey, $stdout, $stderr, $stdin, @silent) rescue error('tjc_no_io_connect', "Can't connect IO: #{$!}") end end def disconnectReportServer begin @reportServer.disconnect(@rs_authKey) rescue error('tjc_no_io_disconnect', "Can't disconnect IO: #{$!}") end begin @reportServer.terminate(@rs_authKey) rescue error('tjc_srv_term_failed', "Report server termination failed: #{$!}") end @reportServer = nil @rs_uri = nil @rs_authKey = nil @projectServer = nil @ps_uri = nil @ps_authKey = nil end # Call the TaskJuggler daemon (ProjectBroker) and execute the provided # command with the provided arguments. def callDaemon(command, args) begin return @broker.command(@authKey, command, args) rescue error('tjc_call_srv_failed', "Call to TaskJuggler server on host '#{@host}' " + "port #{@port} failed: #{$!}") end end # Sort the remaining arguments into a report ID and a TJI file list. # If .tji files are present, they must be separated from the report ID # list by a '='. def splitIdsAndFiles(args) reportIds = [] tjiFiles = [] addToReports = true args.each do |arg| if arg == '=' # Switch to tji file list. addToReports = false elsif addToReports reportIds << arg else tjiFiles << arg end end [ reportIds, tjiFiles ] end # Transfer the _tjiFiles_ to the _reportServer_. def addFiles(tjiFiles) tjiFiles.each do |file| begin unless @reportServer.addFile(@rs_authKey, file) return false end rescue error('tjc_canont_add_file', "Cannot add file #{file} to ReportServer") end end true end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3TsSender.rb0000644000175000017500000000420212614413013022032 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3TsSender.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This script is used to send out the time sheet templates to the employees. # It should be run from a cron job once a week. require 'taskjuggler/Tj3SheetAppBase' require 'taskjuggler/TimeSheetSender' # Name of the application suite AppConfig.appName = 'tj3ts_sender' class TaskJuggler class Tj3TsSender < Tj3SheetAppBase def initialize super @optsSummaryWidth = 22 @force = false @intervalDuration = nil # The default report period end is next Monday 0:00. @date = TjTime.new.nextDayOfWeek(1).to_s('%Y-%m-%d') @resourceList = [] end def processArguments(argv) super do @opts.banner += <<'EOT' This program can be used to send out time sheets templates via email. It will generate time sheet templates for all resources of the project. The project data will be accesses via tj3client from a running TaskJuggler server process. EOT @opts.on('-r', '--resource ', String, format('Only generate template for given resource')) do |arg| @resourceList << arg end @opts.on('-f', '--force', format('Send out a new template even if one exists ' + 'already')) do |arg| @force = true end optsEndDate end end def appMain(argv) ts = TimeSheetSender.new('tj3ts_sender') @rc.configure(ts, 'global') @rc.configure(ts, 'timesheets') @rc.configure(ts, 'timesheets.sender') ts.workingDir = @workingDir if @workingDir ts.dryRun = @dryRun ts.force = @force ts.intervalDuration = @intervalDuration if @intervalDuration ts.date = @date if @date ts.sendTemplates(@resourceList) 0 end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3SsReceiver.rb0000644000175000017500000000262512614413013022364 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3SsReceiver.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Tj3SheetAppBase' require 'taskjuggler/StatusSheetReceiver' # Name of the application AppConfig.appName = 'tj3ss_receiver' class TaskJuggler class Tj3SsReceiver < Tj3SheetAppBase def initialize super end def processArguments(argv) super do @opts.banner += <<'EOT' This program can be used to receive filled-out status sheets via email. It reads the emails from STDIN and extracts the status sheet from the attached files. The status sheet is checked for correctness. Good status sheets are filed away. The sender be informed by email that the status sheets was accepted or rejected. EOT end end def appMain(argv) ts = TaskJuggler::StatusSheetReceiver.new('tj3ss_receiver') @rc.configure(ts, 'global') @rc.configure(ts, 'statussheets') @rc.configure(ts, 'statussheets.receiver') ts.workingDir = @workingDir if @workingDir ts.dryRun = @dryRun ts.processEmail 0 end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3.rb0000644000175000017500000002011312614413013020361 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Tj3AppBase' require 'taskjuggler/TaskJuggler' # Name of the application suite AppConfig.appName = 'tj3' class TaskJuggler class Tj3 < Tj3AppBase def initialize super # tj3 just requires Ruby 1.8.7. All other apps need 1.9.2. @mininumRubyVersion = '1.8.7' # By default, we're only using 1 CPU core. @maxCpuCores = 1 # Don't generate warnings for differences between time sheet data and # the plan. @warnTsDeltas = false # Don't stop after reading all files. @checkSyntax = false # Don't generate reports when previous errors have been found. @forceReports = false # List of requested report IDs. @reportIDs = [] # List of requested report IDs (as regular expressions). @reportRegExpIDs = [] # Don't generate trace reports by default @generateTraces = false # Should a booking file be generated? @freeze = false # The cut-off date for the freeze @freezeDate = TjTime.new.align(3600) # Should bookings be grouped by Task or by Resource (default). @freezeByTask = false # Generate a list of all defined reports if true. @listReports = false # Regular expression to select the report IDs to be listed. @listReportPattern = '*' # Don't generate any reports. @noReports = false # Treat warnings like errors or not. @abortOnWarning = false # The directory where generated reports should be put in. @outputDir = nil # The file names of the time sheet files to check. @timeSheets = [] # The file names of the status sheet files to check. @statusSheets = [] # Show some progress information by default TaskJuggler::Log.silent = false end def processArguments(argv) super do @opts.banner += <<'EOT' This is the main application. It reads in your project files, schedules the project and generates the reports. EOT @opts.on('--debuglevel N', Integer, format("Verbosity of debug output")) do |arg| TaskJuggler::Log.level = arg end @opts.on('--debugmodules x,y,z', Array, format('Restrict debug output to a list of modules')) do |arg| TaskJuggler::Log.segments = arg end @opts.on('--freeze', format('Generate or update the booking file for ' + 'the project. The file will have the same ' + 'base name as the project file but has a ' + '-bookings.tji extension.')) do @freeze = true end @opts.on('--freezedate ', String, format('Use a different date than the current moment' + 'as cut-off date for the booking file')) do |arg| begin @freezeDate = TjTime.new(arg).align(3600) rescue TjException => msg error('tj3_ivld_freeze_date', "Invalid freeze date: #{msg.message}") end end @opts.on('--freezebytask', format('Group the bookings in the booking file generated ' + 'during a freeze by task instead of by resource.')) do @freezeByTask = true end @opts.on('--check-time-sheet ', String, format("Check the given time sheet")) do |arg| @timeSheets << arg end @opts.on('--check-status-sheet ', String, format("Check the given status sheet")) do |arg| @statusSheets << arg end @opts.on('--warn-ts-deltas', format('Turn on warnings for requested changes in time ' + 'sheets')) do @warnTsDeltas = true end @opts.on('--check-syntax', format('Only parse the input files and check the syntax.')) do @checkSyntax = true end @opts.on('--no-reports', format('Just schedule the project, but don\'t generate any ' + 'reports.')) do @noReports = true end @opts.on('--list-reports ', String, format('List id, formats and file name of all the defined ' + 'reports that have IDs that match the specified ' + 'regular expression.')) do |arg| @listReports = true @listReportPattern = arg end @opts.on('--report ', String, format('Only generate the report with the specified ID. ' + 'This option can be used multiple times.')) do |arg| @reportIDs << arg end @opts.on('--reports ', String, format('Only generate the reports that have IDs that match ' + 'the specified regular expression. This option can ' + 'be used multiple times.')) do |arg| @reportRegExpIDs << arg end @opts.on('-f', '--force-reports', format('Generate reports despite scheduling errors')) do @forceReports = true end @opts.on('--add-trace', format('Append a current data set to all trace reports.')) do @generateTraces = true end @opts.on('--abort-on-warnings', format('Abort program on warnings like we do on errors.')) do @abortOnWarning = true end @opts.on('-o', '--output-dir ', String, format('Directory the reports should go into')) do |arg| @outputDir = arg + (arg[-1] == ?/ ? '' : '/') end @opts.on('-c N', Integer, format('Maximum number of CPU cores to use')) do |arg| @maxCpuCores = arg end end end def appMain(files) if files.empty? error('tj3_tjp_file_missing', 'You must provide at least one .tjp file') end if @outputDir && !File.directory?(@outputDir) error('tj3_outdir_missing', "Output directory '#{@outputDir}' does not exist or is not " + "a directory!") end tj = TaskJuggler.new tj.maxCpuCores = @maxCpuCores tj.warnTsDeltas = @warnTsDeltas tj.generateTraces = @generateTraces MessageHandlerInstance.instance.abortOnWarning = @abortOnWarning keepParser = !@timeSheets.empty? || !@statusSheets.empty? return 1 unless tj.parse(files, keepParser) return 0 if @checkSyntax if !tj.schedule return 1 unless @forceReports end # The checks of time and status sheets is probably only used for # debugging. Normally, this function is provided by tj3client. @timeSheets.each do |ts| return 1 if !tj.checkTimeSheet(ts) || tj.errors > 0 end @statusSheets.each do |ss| return 1 if !tj.checkStatusSheet(ss) || tj.errors > 0 end # Check for freeze mode and generate the booking file if requested. if @freeze return 1 unless tj.freeze(@freezeDate, @freezeByTask) && tj.errors == 0 end # List all the reports that match the requested expression. tj.listReports(@listReportPattern, true) if @listReports return 0 if @noReports if @reportIDs.empty? && @reportRegExpIDs.empty? return 1 if !tj.generateReports(@outputDir) || tj.errors > 0 else @reportIDs.each do |id| return 1 if !tj.generateReport(id, false) end @reportRegExpIDs.each do |id| return 1 if !tj.generateReport(id, true) end end 0 end end end taskjuggler-3.5.0/lib/taskjuggler/apps/Tj3Man.rb0000644000175000017500000000731312614413013021024 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3Man.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Tj3AppBase' require 'taskjuggler/TernarySearchTree' require 'taskjuggler/SyntaxReference' require 'taskjuggler/UserManual' AppConfig.appName = 'tj3man' class TaskJuggler class Tj3Man < Tj3AppBase def initialize super @man = SyntaxReference.new @keywords = TernarySearchTree.new(@man.all) @manual = false @showHtml = false @browser = ENV['BROWSER'] || 'firefox' @directory = './' @mininumRubyVersion = '1.8.7' end def processArguments(argv) super do @opts.banner += <<'EOT' This program can be used to generate the user manual in HTML format or to get a textual help for individual keywords. EOT @opts.on('-d', '--dir ', String, format('directory to put the manual')) do |dir| @directory = dir end @opts.on('--html', format('Show the user manual in your local web browser. ' + 'By default, Firefox is used or the brower specified ' + 'with the $BROWSER environment variable.')) do @showHtml = true end @opts.on('--browser ', String, format('Specify the command to start your web browser. ' + 'The default is \'firefox\'.')) do |browser| @browser = browser end @opts.on('-m', '--manual', format('Generate the user manual into the current directory ' + 'or the directory specified with the -d option.')) do @manual = true end end end def appMain(requestedKeywords) if @manual UserManual.new.generate(@directory) elsif requestedKeywords.empty? showManual else requestedKeywords.each do |keyword| if (kws = @keywords[keyword, true]).nil? error('tj3man_no_matches', "No matches found for '#{keyword}'") elsif kws.length == 1 || kws.include?(keyword) showManual(keyword) else warning('tj3man_multi_match', "Multiple matches found for '#{keyword}':\n" + "#{kws.join(', ')}") end end end 0 end private def showManual(keyword = nil) if @showHtml # If the user requested HTML format, we start the browser. startBrowser(keyword) else if keyword # Print the documentation for the keyword. puts @man.to_s(keyword) else # Print a list of all documented keywords. puts @man.all.join("\n") end end end # Start the web browser with either the entry page or the page for the # specified keyword. def startBrowser(keyword = nil) # Find the manual relative to this file. manualDir = File.join(File.dirname(__FILE__), '..', '..', '..', 'manual', 'html') file = "#{manualDir}/#{keyword || 'index'}.html" # Make sure the file exists. unless File.exists?(file) $stderr.puts "Cannot open manual file #{file}" exit 1 end # Start the browser. begin `#{@browser} file:#{file}` rescue $stderr.puts "Cannot open browser: #{$!}" exit 1 end end end end taskjuggler-3.5.0/lib/taskjuggler/reports/0000755000175000017500000000000012614413013020132 5ustar bernatbernattaskjuggler-3.5.0/lib/taskjuggler/reports/NikuReport.rb0000644000175000017500000004252712614413013022573 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = NikuReport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/AppConfig' require 'taskjuggler/reports/ReportBase' class TaskJuggler class NikuProject attr_reader :name, :id, :tasks, :resources def initialize(id, name) @id = id @name = name @tasks = [] @resources = {} end end class NikuResource attr_reader :id attr_accessor :sum def initialize(id) @id = id @sum = 0.0 end end # The Niku report can be used to export resource allocation data for certain # task groups in the Niku XOG format. This file can be read by the Clarity # enterprise resource management software from Computer Associates. # Since I don't think this is a use case for many users, the implementation # is somewhat of a hack. The report relies on 3 custom attributes that the # user has to define in the project. # Resources must be tagged with a ClarityRID and Tasks must have a # ClarityPID and a ClarityPName. # This file format works for our Clarity installation. I have no idea if it # is even portable to other Clarity installations. class NikuReport < ReportBase def initialize(report) super(report) # A Hash to store NikuProject objects by id @projects = {} # A Hash to map ClarityRID to Resource @resources = {} # Unallocated and vacation time during the report period for all # resources hashed by ClarityId. Values are in days. @resourcesFreeWork = {} # Resources total effort during the report period hashed by ClarityId @resourcesTotalEffort = {} @scenarioIdx = nil end def generateIntermediateFormat super @scenarioIdx = a('scenarios')[0] computeResourceTotals collectProjects computeProjectAllocations end def to_html tableFrame = generateHtmlTableFrame tableFrame << (tr = XMLElement.new('tr')) tr << (td = XMLElement.new('td')) td << (table = XMLElement.new('table', 'class' => 'tj_table', 'cellspacing' => '1')) # Table Header with two rows. First the project name, then the ID. table << (thead = XMLElement.new('thead')) thead << (tr = XMLElement.new('tr', 'class' => 'tabline')) # First line tr << htmlTabCell('Project', true, 'right') @projects.keys.sort.each do |projectId| # Don't include projects without allocations. next if projectTotal(projectId) <= 0.0 name = @projects[projectId].name # To avoid exploding tables for long project names, we only show the # last 15 characters for those. We expect the last characters to be # more significant in those names than the first. name = '...' + name[-15..-1] if name.length > 15 tr << htmlTabCell(name, true, 'center') end tr << htmlTabCell('', true) # Second line thead << (tr = XMLElement.new('tr', 'class' => 'tabline')) tr << htmlTabCell('Resource', true, 'left') @projects.keys.sort.each do |projectId| # Don't include projects without allocations. next if projectTotal(projectId) <= 0.0 tr << htmlTabCell(projectId, true, 'center') end tr << htmlTabCell('Total', true, 'center') # The actual content. One line per resource. table << (tbody = XMLElement.new('tbody')) numberFormat = a('numberFormat') @resourcesTotalEffort.keys.sort.each do |resourceId| tbody << (tr = XMLElement.new('tr', 'class' => 'tabline')) tr << htmlTabCell("#{@resources[resourceId].name} (#{resourceId})", true, 'left') @projects.keys.sort.each do |projectId| next if projectTotal(projectId) <= 0.0 value = sum(projectId, resourceId) valStr = numberFormat.format(value) valStr = '' if valStr.to_f == 0.0 tr << htmlTabCell(valStr) end tr << htmlTabCell(numberFormat.format(resourceTotal(resourceId)), true) end # Project totals tbody << (tr = XMLElement.new('tr', 'class' => 'tabline')) tr << htmlTabCell('Total', 'true', 'left') @projects.keys.sort.each do |projectId| next if (pTotal = projectTotal(projectId)) <= 0.0 tr << htmlTabCell(numberFormat.format(pTotal), true, 'right') end tr << htmlTabCell(numberFormat.format(total()), true, 'right') tableFrame end def to_niku xml = XMLDocument.new xml << XMLComment.new(<<"EOT" Generated by #{AppConfig.softwareName} v#{AppConfig.version} on #{TjTime.new} For more information about #{AppConfig.softwareName} see #{AppConfig.contact}. Project: #{@project['name']} Date: #{@project['now']} EOT ) xml << (nikuDataBus = XMLElement.new('NikuDataBus', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => '../xsd/nikuxog_project.xsd')) nikuDataBus << XMLElement.new('Header', 'action' => 'write', 'externalSource' => 'NIKU', 'objectType' => 'project', 'version' => '7.5.0') nikuDataBus << (projects = XMLElement.new('Projects')) timeFormat = '%Y-%m-%dT%H:%M:%S' numberFormat = a('numberFormat') @projects.keys.sort.each do |projectId| prj = @projects[projectId] projects << (project = XMLElement.new('Project', 'name' => prj.name, 'projectID' => prj.id)) project << (resources = XMLElement.new('Resources')) # We iterate over all resources to ensure that all have an entry in # the Clarity database for all projects. This is done to work around a # limitation of Clarity with respect to filling time sheets with # assigned projects. @resources.keys.sort.each do |clarityRID| resources << (resource = XMLElement.new('Resource', 'resourceID' => clarityRID, 'defaultAllocation' => '0')) resource << (allocCurve = XMLElement.new('AllocCurve')) sum = sum(prj.id, clarityRID) allocCurve << (XMLElement.new('Segment', 'start' => a('start').to_s(timeFormat), 'finish' => (a('end') - 1).to_s(timeFormat), 'sum' => numberFormat.format(sum).to_s)) end # The custom information section usually contains Clarity installation # specific parts. They are identical for each project section, so we # mis-use the title attribute to insert them as an XML blob. project << XMLBlob.new(a('title')) unless a('title').empty? end xml.to_s end def to_csv table = [] # Header line with project names table << (row = []) # First column is the resource name and ID. row << "" projectIds = @projects.keys.sort projectIds.each do |projectId| row << @projects[projectId].name end # Header line with project IDs table << (row = []) row << "Resource" projectIds.each do |projectId| row << projectId end @resourcesTotalEffort.keys.sort.each do |resourceId| # Add one line per resource. table << (row = []) row << "#{@resources[resourceId].name} (#{resourceId})" projectIds.each do |projectId| row << sum(projectId, resourceId) end end table end private def sum(projectId, resourceId) project = @projects[projectId] return 0.0 unless project resource = project.resources[resourceId] return 0.0 unless resource && @resourcesTotalEffort[resourceId] resource.sum / @resourcesTotalEffort[resourceId] end def resourceTotal(resourceId) total = 0.0 @projects.each_key do |projectId| total += sum(projectId, resourceId) end total end def projectTotal(projectId) total = 0.0 @resources.each_key do |resourceId| total += sum(projectId, resourceId) end total end def total total = 0.0 @projects.each_key do |projectId| @resources.each_key do |resourceId| total += sum(projectId, resourceId) end end total end def htmlTabCell(text, headerCell = false, align = 'right') td = XMLElement.new('td', 'class' => headerCell ? 'tabhead' : 'taskcell1') td << XMLNamedText.new(text, 'div', 'class' => headerCell ? 'headercelldiv' : 'celldiv', 'style' => "text-align:#{align}") td end # The report must contain percent values for the allocation of the # resources. A value of 1.0 means 100%. The resource is fully allocated # for the whole report period. To compute the percentage later on, we # first have to compute the maximum possible allocation. def computeResourceTotals # Prepare the resource list. resourceList = PropertyList.new(@project.resources) resourceList.setSorting(@report.get('sortResources')) resourceList = filterResourceList(resourceList, nil, @report.get('hideResource'), @report.get('rollupResource'), @report.get('openNodes')) # Prepare a template for the Query we will use to get all the data. queryAttrs = { 'project' => @project, 'scopeProperty' => nil, 'scenarioIdx' => @scenarioIdx, 'loadUnit' => a('loadUnit'), 'numberFormat' => a('numberFormat'), 'timeFormat' => a('timeFormat'), 'currencyFormat' => a('currencyFormat'), 'start' => a('start'), 'end' => a('end'), 'journalMode' => a('journalMode'), 'journalAttributes' => a('journalAttributes'), 'sortJournalEntries' => a('sortJournalEntries'), 'costAccount' => a('costaccount'), 'revenueAccount' => a('revenueaccount') } query = Query.new(queryAttrs) # Calculate the number of working days in the report interval. workingDays = @project.workingDays(TimeInterval.new(a('start'), a('end'))) resourceList.each do |resource| # We only care about leaf resources that have the custom attribute # 'ClarityRID' set. next if !resource.leaf? || (resourceId = resource.get('ClarityRID')).nil? || resourceId.empty? query.property = resource # First get the allocated effort. query.attributeId = 'effort' query.process # Effort in resource days total = query.to_num # A fully allocated resource should always have a total of 1.0 per # working day. If the total is larger, we assume unpaid overtime. If # it's less, the resource was either not fully allocated or had less # working hours or was on vacation. if total >= workingDays @resourcesFreeWork[resourceId] = 0.0 else @resourcesFreeWork[resourceId] = workingDays - total total = workingDays end @resources[resourceId] = resource # This is the maximum possible work of this resource in the report # period. @resourcesTotalEffort[resourceId] = total end # Make sure that we have at least one Resource with a ClarityRID. if @resourcesTotalEffort.empty? raise TjException.new, 'No resources with the custom attribute ClarityRID were found!' end end # Search the Task list for the various ClarityPIDs and create a new Task # list for each ClarityPID. def collectProjects # Prepare the task list. taskList = PropertyList.new(@project.tasks) taskList.setSorting(@report.get('sortTasks')) taskList = filterTaskList(taskList, nil, @report.get('hideTask'), @report.get('rollupTask'), @report.get('openNodes')) taskList.each do |task| # We only care about tasks that are leaf tasks and have resource # allocations. next unless task.leaf? || task['assignedresources', @scenarioIdx].empty? id = task.get('ClarityPID') # Ignore tasks without a ClarityPID attribute. next if id.nil? if id.empty? raise TjException.new, "ClarityPID of task #{task.fullId} may not be empty" end name = task.get('ClarityPName') if name.nil? raise TjException.new, "ClarityPName of task #{task.fullId} has not been set!" end if name.empty? raise TjException.new, "ClarityPName of task #{task.fullId} may not be empty!" end if (project = @projects[id]).nil? # We don't have a record for the Clarity project yet, so we create a # new NikuProject object. project = NikuProject.new(id, name) # And store it in the project list hashed by the ClarityPID. @projects[id] = project else # Due to a design flaw in the Niku file format, Clarity projects are # identified by a name and an ID. We have to check that those pairs # are always the same. if (fTask = project.tasks.first).get('ClarityPName') != name raise TjException.new, "Task #{task.fullId} and task #{fTask.fullId} " + "have same ClarityPID (#{id}) but different ClarityPName " + "(#{name}/#{fTask.get('ClarityPName')})" end end # Append the Task to the task list of the Clarity project. project.tasks << task end if @projects.empty? raise TjException.new, 'No tasks with the custom attributes ClarityPID and ClarityPName ' + 'were found!' end # If the user did specify a project ID and name to collect the vacation # time, we'll add this as a project as well. if (id = @report.get('timeOffId')) && (name = @report.get('timeOffName')) @projects[id] = project = NikuProject.new(id, name) @resources.each do |resourceId, resource| project.resources[resourceId] = r = NikuResource.new(resourceId) r.sum = @resourcesFreeWork[resourceId] end end end # Compute the total effort each Resource is allocated to the Task objects # that have the same ClarityPID. def computeProjectAllocations # Prepare a template for the Query we will use to get all the data. queryAttrs = { 'project' => @project, 'scenarioIdx' => @scenarioIdx, 'loadUnit' => a('loadUnit'), 'numberFormat' => a('numberFormat'), 'timeFormat' => a('timeFormat'), 'currencyFormat' => a('currencyFormat'), 'start' => a('start'), 'end' => a('end'), 'journalMode' => a('journalMode'), 'journalAttributes' => a('journalAttributes'), 'sortJournalEntries' => a('sortJournalEntries'), 'costAccount' => a('costaccount'), 'revenueAccount' => a('revenueaccount') } query = Query.new(queryAttrs) timeOffId = @report.get('timeOffId') @projects.each_value do |project| next if project.id == timeOffId project.tasks.each do |task| task['assignedresources', @scenarioIdx].each do |resource| # Only consider resources that are in the filtered resource list. next unless @resources[resource.get('ClarityRID')] query.property = task query.scopeProperty = resource query.attributeId = 'effort' query.process work = query.to_num # If the resource was not actually working on this task during the # report period, we don't create a record for it. next if work <= 0.0 resourceId = resource.get('ClarityRID') if (resourceRecord = project.resources[resourceId]).nil? # If we don't already have a NikuResource object for the # Resource, we create a new one. resourceRecord = NikuResource.new(resourceId) # Store the new NikuResource in the resource list of the # NikuProject record. project.resources[resourceId] = resourceRecord end resourceRecord.sum += query.to_num end end end end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttHeader.rb0000644000175000017500000001237712614413013022657 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttHeader.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/GanttHeaderScaleItem' class TaskJuggler # This class stores output format independent information to describe a # GanttChart header. A Gantt chart header consists of 2 lines. The top line # holds the large scale (e. g. the year or month and year) and the lower line # holds the small scale (e. g. week or day). class GanttHeader attr_reader :gridLines, :nowLineX, :cellStartDates attr_accessor :height # Create a GanttHeader object and generate the scales for the header. def initialize(columnDef, chart) @columnDef = columnDef @chart = chart @largeScale = [] @smallScale = [] # Positions where chart should be marked with vertical lines that match # the large scale. @gridLines = [] # X coordinate of the "now" line. nil if "now" is off-chart. @nowLineX = nil # The x coordinates and width of the cells created by the small scale. The # values are stored as [ x, w ]. @cellStartDates = [] # The height of the header in pixels. @height = 39 generate end # Convert the header into an HTML format. def to_html div = XMLElement.new('div', 'class' => 'tabback', 'style' => "margin:0px; padding:0px; " + "position:relative; " + "width:#{@chart.width.to_i}px; " + "height:#{@height.to_i}px; " + "font-size:#{(@height / 4).to_i}px; ") @largeScale.each { |s| div << s.to_html } @smallScale.each { |s| div << s.to_html } div end private # Call genHeaderScale with the right set of parameters (depending on the # selected scale) for the lower and upper header line. def generate # The 2 header lines are separated by a 1 pixel boundary. h = ((@height - 1) / 2).to_i case @chart.scale['name'] when 'hour' genHeaderScale(@largeScale, 0, h, :midnight, :sameTimeNextDay, @columnDef.timeformat1 || '%A %Y-%m-%d') genHeaderScale(@smallScale, h + 1, h, :beginOfHour, :sameTimeNextHour, @columnDef.timeformat2 || '%H') when 'day' genHeaderScale(@largeScale, 0, h, :beginOfMonth, :sameTimeNextMonth, @columnDef.timeformat1 || '%b %Y') genHeaderScale(@smallScale, h + 1, h, :midnight, :sameTimeNextDay, @columnDef.timeformat2 || '%d') when 'week' genHeaderScale(@largeScale, 0, h, :beginOfMonth, :sameTimeNextMonth, @columnDef.timeformat1 || '%b %Y') genHeaderScale(@smallScale, h + 1, h, :beginOfWeek, :sameTimeNextWeek, @columnDef.timeformat2 || '%d') when 'month' genHeaderScale(@largeScale, 0, h, :beginOfYear, :sameTimeNextYear, @columnDef.timeformat1 || '%Y') genHeaderScale(@smallScale, h + 1, h, :beginOfMonth, :sameTimeNextMonth, @columnDef.timeformat2 || '%b') when 'quarter' genHeaderScale(@largeScale, 0, h, :beginOfYear, :sameTimeNextYear, @columnDef.timeformat1 || '%Y') genHeaderScale(@smallScale, h + 1, h, :beginOfQuarter, :sameTimeNextQuarter, @columnDef.timeformat2 || 'Q%Q') when 'year' genHeaderScale(@smallScale, h + 1, h, :beginOfYear, :sameTimeNextYear, @columnDef.timeformat1 || '%Y') else raise "Unknown scale: #{@chart.scale['name']}" end nlx = @chart.dateToX(@chart.now) @nowLineX = nlx if nlx end # Generate the actual scale cells. def genHeaderScale(scale, y, h, beginOfFunc, sameTimeNextFunc, timeformat) # The beginOfWeek function needs a parameter, so we have to handle it as a # special case. if beginOfFunc == :beginOfWeek t = @chart.start.send(beginOfFunc, @chart.weekStartsMonday) else t = @chart.start.send(beginOfFunc) end # Now we iterate of the report period in steps defined by # sameTimeNextFunc. For each time slot we generate GanttHeaderScaleItem # object and append it to the scale. while t < @chart.end nextT = t.send(sameTimeNextFunc) # Determine the end of the cell. We keep 1 pixel for the boundary. w = (xR = @chart.dateToX(nextT).to_i - 1) - (x = @chart.dateToX(t).to_i) # We collect the positions of the large grid scale marks for later use # in the chart. if scale == @largeScale @gridLines << xR else @cellStartDates << t end scale << GanttHeaderScaleItem.new(t.to_s(timeformat), x, y, w, h) t = nextT end # Add the end date of the last cell when generating the small scale. @cellStartDates << t if scale == @smallScale end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ColumnTable.rb0000644000175000017500000000502012614413013022661 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ColumnTable.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportTable' class TaskJuggler # This class is essentially a wrapper around ReportTable that allows us to # embed a ReportTable object as a column of another ReportTable object. Both # ReportTables must have the same number of lines. class ColumnTable < ReportTable attr_writer :viewWidth # Create a new ColumnTable object. def initialize super # The user requested width of the column (chart) @viewWidth = nil # The header will have 2 lines. So, use a smaller font. This should match # the font size used for the GanttChart header. @headerFontSize = 10 # This is an embedded table. @embedded = true end def to_html height = 2 * @headerLineHeight + 1 @lines.each do |line| # Add line height plus 1 pixel padding height += line.height + 1 end # Since we don't know the resulting width of the column, we need to always # add an extra space for the scrollbar. td = XMLElement.new('td', 'rowspan' => "#{2 + @lines.length + 1}", 'style' => 'padding:0px; vertical-align:top;') # Now we generate a 'div' that will contain the nested table. It has a # height that fits all lines but has a maximum width. In case the embedded # table is larger, a scrollbar will appear. We assume that the scrollbar # has a height of SCROLLBARHEIGHT pixels or less. # If there is a user specified with, use it. Otherwise use the # calculated minimum with. width = @viewWidth ? @viewWidth : minWidth td << (scrollDiv = XMLElement.new('div', 'class' => 'tabback', 'style' => 'position:relative; overflow:auto; ' + "width:#{width}px; " + 'margin-top:-1px; margin-bottom:-1px; ' + "height:#{height + SCROLLBARHEIGHT + 2}px;")) scrollDiv << (contentDiv = XMLElement.new('div', 'style' => 'margin: 0px; padding: 0px; position: absolute; top: 0px;' + "left: 0px; width: #{@viewWidth}px; height: #{height}px; ")) contentDiv << super td end end end taskjuggler-3.5.0/lib/taskjuggler/reports/TjpExportRE.rb0000644000175000017500000004212312614413013022647 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TjpExportRE.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' class TaskJuggler # This specialization of ReportBase implements an export of the # project data in the TJP syntax format. class TjpExportRE < ReportBase # Create a new object and set some default values. def initialize(report) super(report) @supportedTaskAttrs = %w( booking complete depends flags maxend maxstart minend minstart note priority projectid responsible ) @supportedResourceAttrs = %w( booking flags shifts vacation workinghours ) end def generateIntermediateFormat super end # Return the project data in TJP syntax format. def to_tjp # Prepare the resource list. @resourceList = PropertyList.new(@project.resources) @resourceList.setSorting(a('sortResources')) @resourceList = filterResourceList(@resourceList, nil, a('hideResource'), a('rollupResource'), a('openNodes')) @resourceList.sort! # Prepare the task list. @taskList = PropertyList.new(@project.tasks) @taskList.setSorting(a('sortTasks')) @taskList = filterTaskList(@taskList, nil, a('hideTask'), a('rollupTask'), a('openNodes')) @taskList.sort! getBookings @file = '' generateProjectProperty if a('definitions').include?('project') generateFlagDeclaration if a('definitions').include?('flags') generateProjectIDs if a('definitions').include?('projectids') generateShiftList if a('definitions').include?('shifts') generateResourceList if a('definitions').include?('resources') generateTaskList if a('definitions').include?('tasks') generateTaskAttributes unless a('taskAttributes').empty? generateResourceAttributes unless a('resourceAttributes').empty? @file end private def generateProjectProperty @file << "project #{@project['projectid']} \"#{@project['name']}\" " + "\"#{@project['version']}\" #{@project['start']} - " + "#{@project['end']} {\n" # Add timingresolution attribute if it's not the default value. if @project['scheduleGranularity'] != 3600 generateAttributeText("timingresolution " + "#{@project['scheduleGranularity'] / 60}min", 2) end generateAttributeText("timezone \"#{@project['timezone']}\"", 2) if @project['alertLevels'].modified? generateAttributeText(@project['alertLevels'].to_tjp, 2) end generateCustomAttributeDeclarations('resource', @project.resources, a('resourceAttributes')) generateCustomAttributeDeclarations('task', @project.tasks, a('taskAttributes')) generateScenarioDefinition(@project.scenario(0), 2) @file << "}\n\n" end def generateScenarioDefinition(scenario, indent) @file << "#{' ' * indent}scenario #{scenario.id} " + "#{quotedString(scenario.name)} {\n" scenario.children.each do |sc| generateScenarioDefinition(sc, indent + 2) end @file << "#{' ' * (indent + 2)}active " + "#{scenario.get('active') ? 'yes' : 'no'}\n" @file << "#{' ' * indent}}\n" end def generateCustomAttributeDeclarations(tag, propertySet, attributes) # First we search the attribute definitions for any user defined # attributes and count them. customAttributes = 0 propertySet.eachAttributeDefinition do |ad| customAttributes += 1 if ad.userDefined end # Return if there are no user defined attributes. return if customAttributes == 0 # Generate definitions for each user defined attribute that is in the # taskAttributes list. @file << ' extend ' + tag + "{\n" propertySet.eachAttributeDefinition do |ad| next unless ad.userDefined && attributes.include?(ad.id) @file << " #{ad.objClass.tjpId} #{ad.id} " + "#{quotedString(ad.name)}" if ad.scenarioSpecific || ad.inheritedFromParent @file << " { " @file << "scenariospecific " if ad.scenarioSpecific @file << "inherit " if ad.inheritedFromParent @file << "}" end @file << "\n" end @file << " }\n" end def generateFlagDeclaration flags = [] properties = @resourceList + @taskList properties.each do |property| a('scenarios').each do |scenarioIdx| property['flags', scenarioIdx].each do |flag| flags << flag unless flags.include?(flag) end end end flags.sort unless flags.empty? @file << "flags #{flags.join(', ')}\n\n" end end def generateProjectIDs # Compile a list of all projectIDs from the tasks in the taskList. projectIDs = [] a('scenarios').each do |scenarioIdx| @taskList.each do |task| pid = task['projectid', scenarioIdx] projectIDs << pid unless pid.nil? || projectIDs.include?(pid) end end @file << "projectids #{projectIDs.join(', ')}\n\n" unless projectIDs.empty? end def generateShiftList @project.shifts.each do |shift| generateShift(shift, 0) unless shift.parent end end def generateShift(shift, indent) @file << ' ' * indent + "shift #{shift.id} " + "#{quotedString(shift.name)} {\n" a('scenarios').each do |scenarioIdx| generateAttribute(shift, 'workinghours', indent + 2, scenarioIdx) end # Call this method recursively for all children. shift.children.each do |subshift| generateShift(subshift, indent + 2) end @file << ' ' * indent + "}\n" end def generateResourceList # The resource definitions are generated recursively. So we only need to # start it for the top-level resources. @resourceList.each do |resource| if resource.parent.nil? generateResource(resource, 0) end end @file << "\n" end def generateResource(resource, indent) Log.activity if resource.sequenceNo % 100 == 0 @file << ' ' * indent + "resource #{resource.id} " + "#{quotedString(resource.name)}" @file << ' {' unless resource.children.empty? @file << "\n" # Call this function recursively for all children that are included in the # resource list as well. resource.children.each do |subresource| if @resourceList.include?(subresource) generateResource(subresource, indent + 2) end end @file << ' ' * indent + "}\n" unless resource.children.empty? end def generateTaskList # The task definitions are generated recursively. So we only need to start # it for the top-level tasks. @taskList.each do |task| if task.parent.nil? generateTask(task, 0) end end end # Generate a task definition. It only contains a very small set of # attributes that have to be passed on the the nested tasks at creation # time. All other attributes are declared in subsequent supplement # statements. def generateTask(task, indent) Log.activity if task.sequenceNo % 100 == 0 @file << ' ' * indent + "task #{task.subId} " + "#{quotedString(task.name)} {\n" if a('taskAttributes').include?('depends') a('scenarios').each do |scenarioIdx| generateTaskDependency(scenarioIdx, task, 'depends', indent + 2) generateTaskDependency(scenarioIdx, task, 'precedes', indent + 2) end end # Call this function recursively for all children that are included in the # task list as well. task.children.each do |subtask| if @taskList.include?(subtask) generateTask(subtask, indent + 2) end end # Determine whether this task has subtasks that are included in the # report or whether this is a leaf task for the report. isLeafTask = true task.children.each do |subtask| if @taskList.include?(subtask) isLeafTask = false break end end # For leaf tasks we put some attributes right here. if isLeafTask a('scenarios').each do |scenarioIdx| generateAttribute(task, 'start', indent + 2, scenarioIdx) if task['milestone', scenarioIdx] if task['scheduled', scenarioIdx] generateAttributeText('milestone', indent + 2, scenarioIdx) end else generateAttribute(task, 'end', indent + 2, scenarioIdx) generateAttributeText('scheduling ' + (task['forward', scenarioIdx] ? 'asap' : 'alap'), indent + 2, scenarioIdx) end if task['scheduled', scenarioIdx] && !inheritable?(task, 'scheduled', scenarioIdx) generateAttributeText('scheduled', indent + 2, scenarioIdx) end end end @file << ' ' * indent + "}\n" end # Generate 'depends' or 'precedes' attributes for a task. def generateTaskDependency(scenarioIdx, task, tag, indent) return unless a('taskAttributes').include?('depends') taskDeps = task[tag, scenarioIdx] unless taskDeps.empty? str = "#{tag} " first = true taskDeps.each do |dep| next if inheritable?(task, tag, scenarioIdx, dep) || (task.parent && task.parent[tag, scenarioIdx].include?(dep)) if first first = false else str << ', ' end str << dep.task.fullId end generateAttributeText(str, indent, scenarioIdx) unless first end end # Generate a list of resource supplement statements that include the rest of # the attributes. def generateResourceAttributes @resourceList.each do |resource| Log.activity if resource.sequenceNo % 100 == 0 @file << "supplement resource #{resource.fullId} {\n" @project.resources.eachAttributeDefinition do |attrDef| id = attrDef.id next if (!@supportedResourceAttrs.include?(id) && !attrDef.userDefined) || !a('resourceAttributes').include?(id) if attrDef.scenarioSpecific a('scenarios').each do |scenarioIdx| next if inheritable?(resource, id, scenarioIdx) generateAttribute(resource, id, 2, scenarioIdx) end else generateAttribute(resource, id, 2) end end # Since 'booking' is a task attribute, we need a special handling if # we want to list them in the resource context. if a('resourceAttributes').include?('booking') && a('resourceAttributes')[0] != '*' a('scenarios').each do |scenarioIdx| generateBookingsByResource(resource, 2, scenarioIdx) end end @file << "}\n" end end # Generate a list of task supplement statements that include the rest of the # attributes. def generateTaskAttributes @taskList.each do |task| Log.activity if task.sequenceNo % 100 == 0 @file << "supplement task #{task.fullId} {\n" # Declare adopted tasks. adoptees = "" task.adoptees.each do |adoptee| next unless @taskList.include?(adoptee) adoptees += ', ' unless adoptees.empty? adoptees += adoptee.fullId end generateAttributeText("adopt #{adoptees}", 2) unless adoptees.empty? @project.tasks.eachAttributeDefinition do |attrDef| id = attrDef.id next if (!@supportedTaskAttrs.include?(id) && !attrDef.userDefined) || !a('taskAttributes').include?(id) if attrDef.scenarioSpecific a('scenarios').each do |scenarioIdx| # Some attributes need special treatment. case id when 'depends' next # already taken care of when 'booking' generateBookingsByTask(task, 2, scenarioIdx) else generateAttribute(task, id, 2, scenarioIdx) end end else generateAttribute(task, id, 2) end end @file << "}\n" end end def generateAttribute(property, attrId, indent, scenarioIdx = nil) val = scenarioIdx ? property[attrId, scenarioIdx] : property.get(attrId) return if val.nil? || (val.is_a?(Array) && val.empty?) || (scenarioIdx && inheritable?(property, attrId, scenarioIdx)) generateAttributeText(property.getAttribute(attrId, scenarioIdx).to_tjp, indent, scenarioIdx) end def generateAttributeText(text, indent, scenarioIdx = nil) @file << ' ' * indent tag = '' if !scenarioIdx.nil? && scenarioIdx != 0 tag = "#{@project.scenario(scenarioIdx).id}:" @file << tag end @file << "#{indentBlock(text, indent + tag.length + 2)}\n" end # Get the booking data for all resources that should be included in the # report. def getBookings @bookings = {} if a('taskAttributes').include?('booking') || a('resourceAttributes').include?('booking') a('scenarios').each do |scenarioIdx| @bookings[scenarioIdx] = {} @resourceList.each do |resource| # Get the bookings for this resource hashed by task. bookings = resource.getBookings( scenarioIdx, TimeInterval.new(a('start'), a('end'))) next if bookings.nil? # Now convert/add them to a tripple-stage hash by scenarioIdx, task # and then resource. bookings.each do |task, booking| next unless @taskList.include?(task) if !@bookings[scenarioIdx].include?(task) @bookings[scenarioIdx][task] = {} end @bookings[scenarioIdx][task][resource] = booking end end end end end def generateBookingsByTask(task, indent, scenarioIdx) return unless @bookings[scenarioIdx].include?(task) # Convert Hash into an [ Resource, Booking ] Array sorted by Resource # ID. This guarantees a reproducible order. resourceBookings = @bookings[scenarioIdx][task].sort do |a, b| a[0].fullId <=> b[0].fullId end resourceBookings.each do |resourceId, booking| generateAttributeText('booking ' + booking.to_tjp(false), indent, scenarioIdx) end end def generateBookingsByResource(resource, indent, scenarioIdx) # Get the bookings for this resource hashed by task. bookings = resource.getBookings(scenarioIdx, TimeInterval.new(a('start'), a('end')), false) bookings.each do |booking| next unless @taskList.include?(booking.task) generateAttributeText('booking ' + booking.to_tjp(true), indent, scenarioIdx) end end # This utility function is used to indent multi-line attributes. All # attributes should be filtered through this function. Attributes that # contain line breaks will be indented properly. In addition to the # indentation specified by _indent_ all but the first line will be indented # after the first word of the first line. The text may not end with a line # break. def indentBlock(text, indent) out = '' firstSpace = 0 text.length.times do |i| if firstSpace == 0 && text[i] == ?\ # There must be a space after ? firstSpace = i end out << text[i] if text[i] == ?\n out += ' ' * (indent + firstSpace - 1) end end out end def quotedString(str) if str.include?("\n") "-8<-\n#{str}\n->8-" else escaped = str.gsub("\"", '\"') "\"#{escaped}\"" end end # Return true if the attribute value for _attrId_ can be inherited from # the parent scenario. def inheritable?(property, attrId, scenarioIdx, listItem = nil) parentScenario = @project.scenario(scenarioIdx).parent return false unless parentScenario parentScenarioIdx = @project.scenarioIdx(parentScenario) parentAttr = property[attrId, parentScenarioIdx] if parentAttr.is_a?(Array) && listItem return parentAttr.include?(listItem) else return property[attrId, scenarioIdx] == parentAttr end end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ReportContext.rb0000644000175000017500000000610212614413013023276 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ReportContext.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # The ReportContext objects provide some settings that are used during the # generation of a report. Reports can be nested, so multiple objects can # exist at a time. But there is only one current ReportContext that is # always accessable via Project.reportContexts.last(). class ReportContext attr_reader :dynamicReportId, :project, :report, :query attr_accessor :childReportCounter, :tasks, :resources, :attributeBackup def initialize(project, report) @project = project @report = report @childReportCounter = 0 @attributeBackup = nil queryAttrs = { 'project' => @project, 'loadUnit' => @report.get('loadUnit'), 'numberFormat' => @report.get('numberFormat'), 'timeFormat' => @report.get('timeFormat'), 'currencyFormat' => @report.get('currencyFormat'), 'start' => @report.get('start'), 'end' => @report.get('end'), 'hideJournalEntry' => @report.get('hideJournalEntry'), 'journalMode' => @report.get('journalMode'), 'journalAttributes' => @report.get('journalAttributes'), 'sortJournalEntries' => @report.get('sortJournalEntries'), 'costAccount' => @report.get('costaccount'), 'revenueAccount' => @report.get('revenueaccount') } @query = Query.new(queryAttrs) if (@parent = @project.reportContexts.last) # For interactive reports we need some ID that uniquely identifies the # report within the composed report. Since a project report can be # included multiple times in the same report, we need to generate # another ID for each instantiated report. We create this report by # using a counter for the number of child reports that each report # has. The unique ID is then the concatenated list of counters from # parent to leaf, separating each value by a '.'. @dynamicReportId = @parent.dynamicReportId + ".#{@parent.childReportCounter}" @parent.childReportCounter += 1 # If the new ReportContext is created from within an existing context, # this is used as parent context and the settings are copied as # default initial values. @tasks = @parent.tasks.dup @resources = @parent.resources.dup else # The ID of the root report is always "0". The first child will then # be "0.0", the seconds "0.1" and so on. @dynamicReportId = "0" # There is no existing ReportContext yet, so we create one based on # the settings of the report. @tasks = @project.tasks.dup @resources = @project.resources.dup end end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ReportTableColumn.rb0000644000175000017500000000557312614413013024072 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ReportTableColumn.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # The ReportTableColumn class models the output format independend column of a # ReportTable. It usually just contains the table header description. The # table header comprises of one or two lines per column. So each column header # consists of 2 cells. @cell1 is the top cell and must be present. @cell2 is # the optional bottom cell. If @cell2 is hidden, @cell1 takes all the vertical # space. # # For some columns, the table does not contain the usual grid lines but # another abstract object that responds to the usual generator methods such as # to_html(). In such a case, @cell1 references the embedded object via its # special variable. The embedded object then replaced the complete column # content. class ReportTableColumn attr_reader :definition, :cell1, :cell2 attr_accessor :scrollbar # Create a new column. _table_ is a reference to the ReportTable this column # belongs to. _definition_ is the TableColumnDefinition of the column from # the project definition. _title_ is the text that is used for the column # header. def initialize(table, definition, title) @table = table # Register this new column with the ReportTable. @table.addColumn(self) @definition = definition # Register this new column with the TableColumnDefinition. definition.column = self if definition # Create the 2 cells of the header. @cell1 = ReportTableCell.new(nil, nil, title, true) @cell1.padding = 5 @cell2 = ReportTableCell.new(nil, nil, '', true) # Header text is always bold. @cell1.bold = @cell2.bold = true # This variable is set to true if the column requires a scrollbar later # on. @scrollbar = false end # Return the mininum required width for the column. def minWidth width = @cell1.width width = @cell2.width if width.nil? || @cell2.width > width width end # Convert the abstract representation into HTML elements. def to_html(row) if row == 1 @cell1.to_html else @cell2.to_html end end # Put the abstract representation into an Array. _csv_ is an Array of Arrays # of Strings. We have an Array with Strings for every cell. The outer Array # holds the Arrays representing the lines. def to_csv(csv, startColumn) # For CSV reports we can only include the first header line. @cell1.to_csv(csv, startColumn, 0) end end end taskjuggler-3.5.0/lib/taskjuggler/reports/MspXmlRE.rb0000644000175000017500000003770412614413013022141 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = MspXmlRE.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' class TaskJuggler # This specialization of ReportBase implements an export of the # project data into Microsoft Project XML format. Due to limitations of MS # Project and this implementation, only a subset of core data is being # exported. The exported data is already a scheduled project with full # resource/task assignment data. class MspXmlRE < ReportBase # Create a new object and set some default values. def initialize(report) super(report) # This report type currently only supports a single scenario. Use the # first specified one. @scenarioIdx = a('scenarios').first # Hash to map calendar names to UIDs (numbers). @calendarUIDs = {} @timeformat = "%Y-%m-%dT%H:%M:%S" end def generateIntermediateFormat super end # Return the project data in Microsoft Project XML format. def to_mspxml @query = @project.reportContexts.last.query.dup # Prepare the resource list. @resourceList = PropertyList.new(@project.resources) @resourceList.setSorting(a('sortResources')) @resourceList = filterResourceList(@resourceList, nil, a('hideResource'), a('rollupResource'), a('openNodes')) @resourceList.sort! # Prepare the task list. @taskList = PropertyList.new(@project.tasks) @taskList.includeAdopted @taskList.setSorting(a('sortTasks')) @taskList = filterTaskList(@taskList, nil, a('hideTask'), a('rollupTask'), a('openNodes')) @taskList.sort! @taskList.checkForDuplicates(@report.sourceFileInfo) @file = XMLDocument.new @file << XMLBlob.new('') @file << XMLComment.new(<<"EOT" Generated by #{AppConfig.softwareName} v#{AppConfig.version} on #{TjTime.new} For more information about #{AppConfig.softwareName} see #{AppConfig.contact}. Project: #{@project['name']} Date: #{@project['now']} EOT ) @file << (project = XMLElement.new('Project', 'xmlns' => 'http://schemas.microsoft.com/project')) calendars = generateProjectAttributes(project) generateTasks(project) generateResources(project, calendars) generateAssignments(project) @file.to_s end private def generateProjectAttributes(p) p << XMLNamedText.new('14', 'SaveVersion') p << XMLNamedText.new(@report.name + '.xml', 'Name') p << XMLNamedText.new(TjTime.new.to_s(@timeformat), 'CreationDate') p << XMLNamedText.new('1', 'ScheduleFromStart') p << XMLNamedText.new(@project['start'].to_s(@timeformat), 'StartDate') p << XMLNamedText.new(@project['end'].to_s(@timeformat), 'FinishDate') p << XMLNamedText.new('09:00:00', 'DefaultStartTime') p << XMLNamedText.new('17:00:00', 'DefaultFinishTime') p << XMLNamedText.new('1', 'CalendarUID') p << XMLNamedText.new((@project.dailyWorkingHours * 60).to_i.to_s, 'MinutesPerDay') p << XMLNamedText.new((@project.weeklyWorkingDays * @project.dailyWorkingHours * 60).to_i.to_s, 'MinutesPerWeek') p << XMLNamedText.new((@project.yearlyWorkingDays / 12).to_s, 'DaysPerMonth') p << XMLNamedText.new(@project['now'].to_s(@timeformat), 'CurrentDate') p << XMLNamedText.new(@project['now'].to_s(@timeformat), 'StatusDate') loadUnitsMap = { :minutes => 1, :hours => 2, :days => 3, :weeks => 4, :months => 5, :quarters => 5, :years => 5, :shortAuto => 3, :longAuto => 3 } p << XMLNamedText.new(loadUnitsMap[a('loadUnit')].to_s, 'WorkFormat') p << XMLNamedText.new('1', 'NewTasksAreManual') p << XMLNamedText.new('0', 'SpreadPercentComplete') rate = (@project['rate'] / @project.dailyWorkingHours).to_s p << XMLNamedText.new(rate, 'StandardRate') p << XMLNamedText.new(rate, 'OvertimeRate') p << XMLNamedText.new(@project['currency'], 'CurrencySymbol') p << XMLNamedText.new(@project['currency'], 'CurrencyCode') #p << XMLNamedText.new('0', 'MicrosoftProjectServerURL') p << (calendars = XMLElement.new('Calendars')) generateCalendar(calendars, @project['workinghours'], 'Standard') calendars end def generateTasks(project) project << (tasks = XMLElement.new('Tasks')) @taskList.each do |task| generateTask(tasks, task) end end def generateResources(project, calendars) project << (resources = XMLElement.new('Resources')) @resourceList.each do |resource| generateResource(resources, resource, calendars) end end def generateAssignments(project) project << (assignments = XMLElement.new('Assignments')) i = 0 @taskList.each do |task| rollupTask = a('rollupTask') @query.property = task @query.scopeProperty = nil # We only generate assignments for leaf tasks and rolled-up container # tasks. next if (task.container? && !(rollupTask && rollupTask.eval(@query))) task.assignedResources(@scenarioIdx).each do |resource| generateAssignment(assignments, task, resource, i) i += 1 end end end def generateCalendar(calendars, workinghours, name) calendars << (cal = XMLElement.new('Calendar')) uid = @calendarUIDs.length.to_s @calendarUIDs[name] = uid cal << XMLNamedText.new(uid, 'UID') cal << XMLNamedText.new(name, 'Name') cal << XMLNamedText.new('1', 'IsBaseCalendar') cal << XMLNamedText.new('-1', 'BaseCalendarUID') cal << (weekdays = XMLElement.new('WeekDays')) d = 1 workinghours.days.each do |day| weekdays << (weekday = XMLElement.new('WeekDay')) weekday << XMLNamedText.new(d.to_s, 'DayType') d += 1 if day.empty? weekday << XMLNamedText.new('0', 'DayWorking') else weekday << XMLNamedText.new('1', 'DayWorking') weekday << (workingtimes = XMLElement.new('WorkingTimes')) day.each do |iv| workingtimes << (worktime = XMLElement.new('WorkingTime')) worktime << XMLNamedText.new(daytime_to_s(iv[0]), 'FromTime') worktime << XMLNamedText.new(daytime_to_s(iv[1]), 'ToTime') end end end end def generateTask(tasks, task) @query.property = task task.calcCompletion(@scenarioIdx) percentComplete = task['complete', @scenarioIdx] tasks << (t = XMLElement.new('Task')) t << XMLNamedText.new(task.get('index').to_s, 'UID') t << XMLNamedText.new(task.get('index').to_s, 'ID') t << XMLNamedText.new('1', 'Active') t << XMLNamedText.new('0', 'Type') t << XMLNamedText.new('0', 'IsNull') t << XMLNamedText.new(task.get('name'), 'Name') t << XMLNamedText.new(task.get('bsi'), 'WBS') t << XMLNamedText.new(task.get('bsi'), 'OutlineNumber') t << XMLNamedText.new((task.level - (a('taskroot') ? a('taskroot').level : 0)).to_s, 'OutlineLevel') t << XMLNamedText.new(task['priority', @scenarioIdx].to_s, 'Priority') t << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat), 'Start') t << XMLNamedText.new(task['end', @scenarioIdx].to_s(@timeformat), 'Finish') t << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat), 'ManualStart') t << XMLNamedText.new(task['end', @scenarioIdx].to_s(@timeformat), 'ManualFinish') t << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat), 'ActualStart') t << XMLNamedText.new(task['end', @scenarioIdx].to_s(@timeformat), 'ActualFinish') t << XMLNamedText.new('2', 'ConstraintType') t << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat), 'ConstraintDate') t << XMLNamedText.new('3', 'FixedCostAccrual') if (note = task.get('note')) t << XMLNamedText.new(note.to_s, 'Notes') end responsible = task['responsible', @scenarioIdx]. map { |r| r.name }.join(', ') t << XMLNamedText.new(responsible, 'Contact') unless responsible.empty? if task.container? rollupTask = a('rollupTask') t << XMLNamedText.new(rollupTask ? '1' : '0', 'Manual') t << XMLNamedText.new(rollupTask && rollupTask.eval(@query) ? '0' : '1', 'Summary') else t << XMLNamedText.new('1', 'Manual') t << XMLNamedText.new('0', 'Summary') t << XMLNamedText.new('0', 'Estimated') t << XMLNamedText.new('5', 'DurationFormat') if task['milestone', @scenarioIdx] t << XMLNamedText.new('1', 'Milestone') else t << XMLNamedText.new('0', 'Milestone') t << XMLNamedText.new(percentComplete.to_i.to_s, 'PercentComplete') t << XMLNamedText.new(percentComplete.to_i.to_s, 'PercentWorkComplete') end end task['startpreds', @scenarioIdx].each do |dt, onEnd| next unless @taskList.include?(dt) next if task.parent && task.parent['startpreds', @scenarioIdx].include?([ dt, onEnd ]) t << (pl = XMLElement.new('PredecessorLink')) pl << XMLNamedText.new(@taskList[dt].get('index').to_s, 'PredecessorUID') pl << XMLNamedText.new(onEnd ? '1' : '3', 'Type') end task['endpreds', @scenarioIdx].each do |dt, onEnd| next unless @taskList.include?(dt) next if task.parent && task.parent['endpreds', @scenarioIdx].include?([ dt, onEnd ]) t << (pl = XMLElement.new('PredecessorLink')) pl << XMLNamedText.new(@taskList[dt].get('index').to_s, 'PredecessorUID') pl << XMLNamedText.new(onEnd ? '0' : '2', 'Type') end end def generateResource(resources, resource, calendars) # MS Project can only deal with a flat resource list. We don't export # resource groups. return unless resource.leaf? resources << (r = XMLElement.new('Resource')) r << XMLNamedText.new(resource.get('index').to_s, 'UID') # All TJ resources are people or equipment. r << XMLNamedText.new('1', 'Type') r << XMLNamedText.new(resource.name, 'Name') r << XMLNamedText.new(resource.id, 'Initials') # MS Project seems to use hourly rates, TJ daily rates. rate = (resource['rate', @scenarioIdx] / @project.dailyWorkingHours).to_s r << XMLNamedText.new(rate, 'StandardRate') r << XMLNamedText.new(rate, 'OvertimeRate') r << XMLNamedText.new(resource['efficiency', @scenarioIdx].to_s, 'MaxUnits') if (email = resource.get('email')) r << XMLNamedText.new(email, 'EmailAddress') end r << XMLNamedText.new(resource.parent.name, 'Group') if resource.parent #if (code = resource.get('Code')) # r << XMLNamedText.new(code, 'Code') # r << XMLNamedText.new('1', 'IsEnterprise') #end #if (ntaccount = resource.get('NTAccount')) # r << XMLNamedText.new(ntaccount, 'NTAccount') #end # Generate a calendar for this resource and assign it. generateCalendar(calendars, resource['workinghours', @scenarioIdx], "Calendar #{resource.name}") r << XMLNamedText.new(@calendarUIDs["Calendar #{resource.name}"], 'CalendarUID') end def generateAssignment(assignments, task, resource, uid) assignments << (a = XMLElement.new('Assignment')) a << XMLNamedText.new(uid.to_s, 'UID') a << XMLNamedText.new(@taskList[task].get('index').to_s, 'TaskUID') a << XMLNamedText.new(resource.get('index').to_s, 'ResourceUID') a << XMLNamedText.new(resource['efficiency', @scenarioIdx].to_s, 'Units') a << XMLNamedText.new(task['start', @scenarioIdx].to_s(@timeformat), 'start') a << XMLNamedText.new(task['end', @scenarioIdx].to_s(@timeformat), 'finish') a << XMLNamedText.new('100.0', 'Cost') a << XMLNamedText.new('8', 'WorkContour') # The PercentWorkComplete value must be 0. Otherwise the completed # work of the assignments will be ignored. a << XMLNamedText.new('0', 'PercentWorkComplete') responsible = task['responsible', @scenarioIdx]. map { |r| r.name }.join(', ') a << XMLNamedText.new(responsible, 'AssnOwner') unless responsible.empty? # Setup the query for this task and resource. @query.property = resource @query.scopeProperty = task @query.attributeId = 'effort' @query.scenarioIdx = @scenarioIdx @query.start = task['start', @scenarioIdx] @query.end = task['end', @scenarioIdx] @query.process tStart = task['start', @scenarioIdx] # We provide assignement data on a day-by-day basis. We report the work # that happens each day from task start to task end. tStart = tStart.midnight tEnd = task['end', @scenarioIdx] t = tStart while t < tEnd tn = t.sameTimeNextDay # We need to make sure that the stored intervals are within the task # and report boundaries. tn and tnc are corrected versions of t and tn # that meet this criterium. tc = t < task['start', @scenarioIdx] ? task['start', @scenarioIdx] : t tc = tc < a('start') ? a('start') : tc tnc = tn > task['end', @scenarioIdx] ? task['end', @scenarioIdx] : tn tnc = tnc > a('end') ? a('end') : tnc @query.start = tc @query.end = tnc @query.process workSeconds = @query.to_num * @project.dailyWorkingHours * 3600 a << (td = XMLElement.new('TimephasedData')) td << XMLNamedText.new(uid.to_s, 'UID') # The Type must be 1 or MS Project will take forever to load the file. td << XMLNamedText.new('1', 'Type') td << XMLNamedText.new(tc.to_s(@timeformat), 'Start') td << XMLNamedText.new((tnc - 1).to_s(@timeformat), 'Finish') td << XMLNamedText.new('2', 'Unit') td << XMLNamedText.new(durationToMsp(workSeconds), 'Value') t = tn end end def findRolledUpParent(task) return nil unless (rollupTask = a('rollupTask')) hideTask = a('hideTask') while task @query.property = task # We don't want to include any tasks that are explicitely hidden via # 'hidetask'. return nil if hideTask && hideTask.eval(@query) return task if rollupTask.eval(@query) && @taskList.include?(task) task = task.parent end end def durationToMsp(duration) return '' if duration == 0 hours = (duration / (60 * 60)).to_i minutes = ((duration - (hours * 60 * 60)) / 60).to_i seconds = (duration % 60).to_i "PT#{hours}H#{minutes}M#{seconds}S" end def daytime_to_s(t) h = (t / (60 * 60)).to_i m = ((t - (h * 60 * 60)) / 60).to_i s = (t % 60).to_i sprintf('%02d:%02d:%02d', h, m, s) end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttRouter.rb0000644000175000017500000002154312614413013022742 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttRouter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/CollisionDetector' class TaskJuggler # The GanttRouter is used by the GanttChart to route the dependency lines from # the start to the end point. The chart is a rectangular area with a certain # width and height. The graphical elements of the Gantt chart can be # registered as don't-cross-zones. These zones block the either horizontal or # vertical lines (or both) from crossing the zone. Zones can be registered by # calling addZone(). The route() method returns routed path from # start to end point. class GanttRouter # Minimum distance between the starting point and the first turning point. MinStartGap = 5 # Minimum distance between the last turning point and the tip of the # arrow. MinEndGap = 10 # Create a GanttRouter object. _width_ and _height_ describe the size of the # rectangular area this router is operating on. def initialize(width, height) @width = width.to_i @height = height.to_i @detector = CollisionDetector.new(@width, @height) end def addZone(x, y, w, h, horiz, vert) @detector.addBlockedZone(x, y, w, h, horiz, vert) end def routeLines(fromToPoints) # We first convert the fromToPoints list into a more readable list of # Hash objects. routes = [] fromToPoints.each do |touple| routes << { :startX => touple[0], :startY => touple[1], :endX => touple[2], :endY => touple[3], :id => touple[4] } end # To make sure that we minimize the crossings of arrows that # originate from the same position, we sort the arrows by the # smallest angle between the vertical line through the task end # and the line between the start and end of the arrow. routes.each do |r| adjLeg = (r[:endX] - MinEndGap) - (r[:startX] + MinStartGap) oppLeg = (r[:startY] - r[:endY]).abs r[:distance] = Math.sqrt(adjLeg ** 2 + oppLeg ** 2) # We can now calculate the sinus values of the angle between the # vertical and a line through the coordinates. sinus = oppLeg.abs / r[:distance] r[:angle] = (adjLeg < 0 ? Math::PI / 2 + Math.asin(Math::PI/2 - sinus) : Math.asin(sinus)) / (Math::PI / (2 * 90)) end # We sort the arrows from small to a large angle. In case the angle is # identical, we use the length of the line as second criteria. routes.sort! { |r1, r2| (r1[:angle] / 5).to_i == (r2[:angle] / 5).to_i ? -(r1[:distance] <=> r2[:distance]) : -(r1[:angle] <=> r2[:angle]) } # Now that the routes are in proper order, we can actually lay the # routes. routePoints = [] routes.each do |r| routePoints << route(r[:startX], r[:startY], r[:endX], r[:endY]) end routePoints end # Find a non-blocked route from the _startPoint_ [ x, y ] to the # _endPoint_ [ x, y ]. The route always starts from the start point towards # the right side of the chart and reaches the end point from the left side # of the chart. All lines are always strictly horizontal or vertical. There # are no diagonal lines. The result is an Array of [ x, y ] points that # include the _startPoint_ as first and _endPoint_ as last element. def route(startX, startY, endX, endY) points = [ [ startX, startY ] ] startGap = MinStartGap endGap = MinEndGap if endX - startX > startGap + endGap + 2 # If the horizontal distance between start and end point is large enough # we can try a direct route. # # xSeg # |startGap| # startX/endX X--------1 # | # | # 2------X endX/endY # |endGap| # xSeg = placeLine([ startY + (startY < endY ? 1 : -1), endY ], false, startX + startGap, 1) if xSeg && xSeg < endX - endGap # The simple version works. Add the lines. addLineTo(points, xSeg, startY) # Point 1 addLineTo(points, xSeg, endY) # Point 2 addLineTo(points, endX, endY) return points end end # If the simple approach above fails, the try a more complex routing # strategy. # # x1 # |startGap| # startX/startY X--------1 yLS # | # 3---------------2 ySeg # | # 4------X endX/endY # |endGap| # x2 # Place horizontal segue. We don't know the width yet, so we have to # assume full width. That's acceptable for horizontal lines. deltaY = startY < endY ? 1 : -1 ySeg = placeLine([ 0, @width - 1 ], true, startY + 2 * deltaY, deltaY) raise "Routing failed" unless ySeg # Place 1st vertical x1 = placeLine([ startY + deltaY, ySeg ], false, startX + startGap, 1) raise "Routing failed" unless x1 # Place 2nd vertical x2 = placeLine([ ySeg + deltaY, endY ], false, endX - endGap, -1) raise "Routing failed" unless x2 # Now add the points 1 - 4 to the list and mark the zones around them. For # vertical lines, we only mark vertical zones and vice versa. addLineTo(points, x1, startY) # Point 1 if x1 != x2 addLineTo(points, x1, ySeg) # Point 2 addLineTo(points, x2, ySeg) # Point 3 end addLineTo(points, x2, endY) # Point 4 addLineTo(points, endX, endY) points end # This function is only intended for debugging purposes. It marks either the # vertical or horizontal zones in the chart. def to_html @detector.to_html end private # This function is at the heart of the routing algorithm. It tries to find a # place for the line described by _segment_ without overlapping with the # defined zones. _horizontal_ determines whether the line is running # horizontally or vertically. _start_ is the first coordinate that is looked # at. In case of collisions, _start_ is moved by _delta_ and the check is # repeated. The function returns the first collision free coordinate or the # outside edge of the routing area. def placeLine(segment, horizontal, start, delta) raise "delta may not be 0" if delta == 0 # Start must be an integer and lie within the routing area. pos = start.to_i pos = 0 if pos < 0 max = (horizontal ? @height: @width) - 1 pos = max if pos > max # Make sure that the segment coordinates are in ascending order. segment.sort! # TODO: Remove this check once the code becomes stable. #checkLines(lines) while @detector.collision?(pos, segment, horizontal) pos += delta # Check if we have exceded the chart area towards top/left. if delta < 0 if pos < 0 break end else # And towards right/bottom. break if pos >= (horizontal ? @height : @width) end end pos end # This function adds another waypoint to an existing line. In addition it # adds a zone that is 2 pixel wide on each side of the line and runs in the # direction of the line. This avoids too closely aligned parallel lines in # the chart. def addLineTo(points, x2, y2) raise "Point list may not be empty" if points.empty? x1, y1 = points[-1] points << [ x2, y2 ] if x1 == x2 # vertical line return if x1 < 0 || x1 >= @width x, y, w, h = justify(x1 - 2, y1, 5, y2 - y1 + 1) addZone(x, y, w, h, false, true) else # horizontal line return if y1 < 0 || x1 >= @height x, y, w, h = justify(x1, y1 - 2, x2 - x1 + 1, 5) addZone(x, y, w, h, true, false) end end # This function makes sure that the rectangle described by _x_, _y_, _w_ # and _h_ is properly justfified. If the width or height are negative, _x_ # and _y_ are adjusted to describe the same rectangle with all positive # coordinates. def justify(x, y, w, h) if w < 0 w = -w x = x - w + 1 end if h < 0 h = -h y = y - h + 1 end # Return the potentially adjusted rectangle coordinates. return x, y, w, h end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ReportTable.rb0000644000175000017500000001310512614413013022702 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ReportTable.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportTableColumn' require 'taskjuggler/reports/ReportTableLine' class TaskJuggler # This class models the intermediate format of all report tables. The # generators for all the table reports create the report in this intermediate # format. The to_* member functions can then output the table in the # appropriate format. class ReportTable # The height in pixels of a horizontal scrollbar on an HTML page. This # value should be large enough to work for all browsers. SCROLLBARHEIGHT = 20 attr_reader :maxIndent, :headerLineHeight, :headerFontSize attr_accessor :equiLines, :embedded, :selfcontained, :auxDir # Create a new ReportTable object. def initialize # The height if the header lines in screen pixels. @headerLineHeight = 19 # Size of the font used in the header @headerFontSize = 15 # Array of ReportTableColumn objects. @columns = [] # Array of ReportTableLine objects. @lines = [] @maxIndent = 0 # Whether or not all table lines must have same height. @equiLines = false # True if the table is embedded as a column of another ReportTable. @embedded = false # True if the report does not rely on the data of other files. @selfcontained = false # Path to the auxiliary data directory. @auxDir = '' end # This function should only be called by the ReportTableColumn constructor. def addColumn(col) @columns << col end # This function should only be called by the ReportTableLine constructor. def addLine(line) @lines << line end # Return the number of registered lines for this table. def lines @lines.length end # Return the minimum required width for the table. If we don't have a # mininum with, nil is returned. def minWidth width = 1 @columns.each do |column| cw = column.minWidth width += cw + 1 if cw end width end # Output the table as HTML. def to_html determineMaxIndents attr = { 'class' => 'tj_table', 'cellspacing' => '1' } attr['style'] = 'width:100%; ' if @embedded table = XMLElement.new('table', attr) table << (tbody = XMLElement.new('tbody')) # Generate the 1st table header line. allCellsHave2Rows = true lineHeight = @headerLineHeight @columns.each do |col| if col.cell1.rows != 2 && !col.cell1.special allCellsHave2Rows = false break; end end if allCellsHave2Rows @columns.each { |col| col.cell1.rows = 1 } lineHeight = @headerLineHeight * 2 + 1 end tbody << (tr = XMLElement.new('tr', 'class' => 'tabhead', 'style' => "height:#{lineHeight}px; " + "font-size:#{@headerFontSize}px;")) @columns.each { |col| tr << col.to_html(1) } unless allCellsHave2Rows # Generate the 2nd table header line. tbody << (tr = XMLElement.new('tr', 'class' => 'tabhead', 'style' => "height:#{@headerLineHeight}px; " + "font-size:#{@headerFontSize}px;")) @columns.each { |col| tr << col.to_html(2) } end # Generate the rest of the table. @lines.each { |line| tbody << line.to_html } # In case we have columns with scrollbars, we generate an extra line with # cells for all columns that don't have a scrollbar. The scrollbar must # have a height of SCROLLBARHEIGHT pixels or less. if hasScrollbar? tbody << (tr = XMLElement.new('tr', 'style' => "height:#{SCROLLBARHEIGHT}px")) @columns.each do |column| unless column.scrollbar tr << XMLElement.new('td') end end end table end # Convert the intermediate representation into an Array of Arrays. _csv_ is # the destination Array of Arrays. It may contain columns already. def to_csv(csv = [[ ]], startColumn = 0) # Generate the header line. columnIdx = startColumn @columns.each do |col| columnIdx += col.to_csv(csv, columnIdx) end if @embedded columnIdx - startColumn else # Content of embedded tables is inserted when generating the # respective Line. lineIdx = 1 @lines.each do |line| # Insert a new Array for each line. csv[lineIdx] = [] line.to_csv(csv, startColumn, lineIdx) lineIdx += 1 end csv end end private # Some columns need to be indented when the data is sorted in tree mode. # This function determines the largest needed indentation of all lines. The # result is stored in the _@maxIndent_ variable. def determineMaxIndents @maxIndent = 0 @lines.each do |line| @maxIndent = line.indentation if line.indentation > @maxIndent end end # Returns true if any of the columns has a scrollbar. def hasScrollbar? @columns.each { |col| return true if col.scrollbar } false end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ReportTableCell.rb0000644000175000017500000003254312614413013023511 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ReportTableCell.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This class models the output format independent version of a cell in a # TableReport. It belongs to a certain ReportTableLine and # ReportTableColumn. Normally a cell contains text on a colored background. # By help of the @special variable it can alternatively contain any object # the provides the necessary output methods such as to_html. class ReportTableCell attr_reader :line attr_accessor :data, :category, :hidden, :alignment, :padding, :text, :tooltip, :showTooltipHint, :iconTooltip, :cellColor, :indent, :icon, :fontSize, :fontColor, :bold, :width, :rows, :columns, :special # Create the ReportTableCell object and initialize the attributes to some # default values. _line_ is the ReportTableLine this cell belongs to. _text_ # is the text that should appear in the cell. _headerCell_ is a flag that # must be true only for table header cells. def initialize(line, query, text = nil, headerCell = false) @line = line @line.addCell(self) if line # Specifies whether this is a header cell or not. @headerCell = headerCell # A copy of a Query object that is needed to access project data via the # query function. @query = query ? query.dup : nil # The cell textual content. This may be a String or a # RichTextIntermediate object. self.text = text || '' # A custom text for the tooltip. @tooltip = nil # Determines if the tooltip is triggered by an special hinting icon or # the whole cell. @showTooltipHint = true # The original data of the cell content (optional, nil if not provided) @data = nil # Determines the background color of the cell. @category = nil # True of the cell is hidden (because other cells span multiple rows or # columns) @hidden = false # How to horizontally align the cell @alignment = :center # Horizontal padding between frame and cell content @padding = 3 # Whether or not to indent the cell. If not nil, it is a Fixnum # indicating the indentation level. @indent = nil # The basename of the icon file @icon = nil # A custom tooltip for the cell icon @iconTooltip = nil # Font size of the cell text in pixels @fontSize = nil # The background color of the cell. Overwrite the @category color. @cellColor = nil # The color of the cell text font. @fontColor = nil # True of a bold font is to be used for the cell text. @bold = false # The width of the column in pixels @width = nil # The number of rows the cell spans @rows = 1 # The number of columns the cell spans @columns = 1 # Ignore everything and use this reference to generate the output. @special = nil end # Return true if two cells are similar enough so that they can be merged in # the report to a single, wider cell. _c_ is the cell to compare this cell # with. def ==(c) @text == c.text && @tooltip == c.tooltip && @alignment == c.alignment && @padding == c.padding && @indent == c.indent && @cellColor == c.cellColor && @category == c.category end # Turn the abstract cell representation into an HTML element tree. def to_html return nil if @hidden return @special.to_html if @special # Determine cell attributes attribs = { } attribs['rowspan'] = "#{@rows}" if @rows > 1 attribs['colspan'] = "#{@columns}" if @columns > 1 attribs['class'] = @category ? @category : 'tabcell' style = '' style += "background-color: #{@cellColor}; " if @cellColor attribs['style'] = style unless style.empty? cell = XMLElement.new('td', attribs) cell << (table = XMLElement.new('table', 'class' => @category ? 'tj_table_cell' : 'tj_table_header_cell', 'cellspacing' => '0', 'style' => cellStyle)) table << (row = XMLElement.new('tr')) calculateIndentation # Insert a padding cell for the left side indentation. if @leftIndent && @leftIndent > 0 row << XMLElement.new('td', 'style' => "width:#{@leftIndent}px; ") end row << cellIcon(cell) labelDiv, tooltip = cellLabel row << labelDiv # Overwrite the tooltip if the user has specified a custom tooltip. tooltip = @tooltip if @tooltip if tooltip && !tooltip.empty? && !selfcontained if @showTooltipHint row << (td = XMLElement.new('td')) td << XMLElement.new('img', 'src' => "#{auxDir}icons/details.png", 'class' => 'tj_table_cell_tooltip') addHtmlTooltip(tooltip, td, cell) else addHtmlTooltip(tooltip, cell) end end # Insert a padding cell for the right side indentation. if @rightIndent && @rightIndent > 0 row << XMLElement.new('td', 'style' => "width:#{@rightIndent}px; ") end cell end # Add the text content of the cell to an Array of Arrays form of the table. def to_csv(csv, columnIdx, lineIdx) # We only support left indentation in CSV files as the spaces for right # indentation will be disregarded by most applications. indent = @indent && @alignment == :left ? ' ' * @indent : '' columns = 1 if @special # This is for nested tables. They will be inserted as whole columns # in the existing table. csv[lineIdx][columnIdx] = nil columns = @special.to_csv(csv, columnIdx) else cell = if @data && @data.is_a?(String) @data elsif @text if @text.respond_to?('functionHandler') @text.setQuery(@query) end str = @text.to_s # Remove any trailing line breaks. These don't really make much # sense in CSV files. while str[-1] == ?\n str.chomp! end str end # Try to convert numbers and other types to their native Ruby type if # they are supported by CSVFile. native = CSVFile.strToNative(cell) # Only for String objects, we add the indentation. csv[lineIdx][columnIdx] = (native.is_a?(String) ? indent + native : native) end return columns end private def selfcontained @line && @line.table.selfcontained end def auxDir @line ? @line.table.auxDir : nil end def calculateIndentation # In tree sorting mode, some cells have to be indented to reflect the # tree nesting structure. The indentation is achieved with padding cells # and needs to be applied to the proper side depending on the alignment. @leftIndent = @rightIndent = 0 if @indent && @alignment != :center if @alignment == :left @leftIndent = @indent * 8 elsif @alignment == :right @rightIndent = (@line.table.maxIndent - @indent) * 8 end end end # Determine cell style def cellStyle style = "text-align:#{@alignment.to_s}; " if @line && @line.table.equiLines style += "height:#{@line.height - 7}px; " end style end def cellIcon(cell) if @icon && !selfcontained td = XMLElement.new('td', 'class' => 'tj_table_cell_icon') td << XMLElement.new('img', 'src' => "#{auxDir}icons/#{@icon}.png", 'alt' => "Icon") addHtmlTooltip(@iconTooltip, td, cell) return td end nil end def cellLabel # If we have a RichText content and a width limit, we enable line # wrapping. # Overfl. Wrap. Height Width # Fixed Height: x - x - # Fixed Width: x x - x # Both: x - x x # None: - x - - fixedHeight = @line && @line.table.equiLines fixedWidth = !@width.nil? style = '' style += "overflow:hidden; " if fixedHeight || fixedWidth style += "white-space:#{fixedWidth && !fixedHeight ? 'normal' : 'nowrap'}; " if fixedHeight && !fixedWidth style += "height:#{@line.height - 3}px; " end style += 'font-weight:bold; ' if @bold style += "font-size: #{@fontSize}px; " if fontSize if @fontColor style += "color:#{@fontColor}; " end return nil, nil if @text.nil? || @text.empty? tooltip = nil # @text can be a String or a RichText (with or without embedded # queries). To find out if @text has multiple lines, we need to expand # it and convert it to a plain text again. textAsString = if @text.is_a?(RichTextIntermediate) # @text is a RichText. if @text.respond_to?('functionHandler') @text.setQuery(@query) end @text.to_s else @text end return nil, nil if textAsString.empty? if @width # We have 4 pixels padding on each side of the cell. labelWidth = @width - 8 labelWidth -= @leftIndent if @leftIndent labelWidth -= @rightIndent if @rightIndent if !selfcontained # The icons are 20 pixels width including padding. labelWidth -= 20 if @icon labelWidth -= 20 if tooltip || @tooltip end else labelWidth = nil end shortText, singleLine = shortVersion(textAsString, labelWidth) if (@line && @line.table.equiLines && (!singleLine || @width )) && !@headerCell # The cell is size-limited. We only put a shortened plain-text version # in the cell and provide the full content via a tooltip. # Header cells are never shortened. tooltip = @text if shortText != textAsString tl = XMLText.new(shortText) else tl = (@text.is_a?(RichTextIntermediate) ? @text.to_html : XMLText.new(@text)) end if labelWidth style += "min-width: #{labelWidth}px; max-width: #{labelWidth}px; " end td = XMLElement.new('td', 'class' => 'tj_table_cell_label', 'style' => style) td << tl return td, tooltip end # Convert a RichText String into a small one-line plain text # version that fits the column. def shortVersion(itext, width) text = itext.to_s singleLine = true modified = false if text.include?("\n") text = text[0, text.index("\n")] singleLine = false modified = true end if width widthWithoutIcon = width - 20 # Assuming an average character width of 7 pixels maxChars = widthWithoutIcon / 7 if text.length > maxChars if maxChars > 0 text = text[0, maxChars] else text = '' end modified = true end end # Add three dots to show that there is more info available. text += "..." if modified [ text, singleLine ] end def addHtmlTooltip(tooltip, trigger, hook = nil) return unless tooltip && !tooltip.empty? && !selfcontained hook = trigger if hook.nil? if tooltip.respond_to?('functionHandler') tooltip.setQuery(@query) end if @query @query.attributeId = 'name' @query.process title = @query.to_s else title = '' end trigger['onclick'] = "TagToTip('ID#{trigger.object_id}', " + "TITLE, '#{title.gsub(/'/, ''')}')" trigger['style'] = trigger['style'] ? trigger['style'] : 'cursor:help; ' hook << (ltDiv = XMLElement.new('div', 'class' => 'tj_tooltip_box', 'style' => 'cursor:help', 'id' => "ID#{trigger.object_id}")) ltDiv << (tooltip.respond_to?('to_html') ? tooltip.to_html : XMLText.new(tooltip)) end end # This class is used to model cells that are just placeholders for a line of # an embedded ReportTable. class PlaceHolderCell # Create a new placeholder cell. _line_ is the line that this cell belongs # to. _embeddedLine_ is the ReportTableLine that is embedded in this cell. def initialize(line, embeddedLine) @line = line @line.addCell(self) if line @embeddedLine = embeddedLine end # Add the current cell to the _csv_ CSV Arrays. _columnIdx_ is the start # column in the _csv_. _lineIdx_ is the index of the current line. The # return value is the number of added cells. def to_csv(csv, columnIdx, lineIdx) @embeddedLine.to_csv(csv, columnIdx, lineIdx) end def to_html nil end end end taskjuggler-3.5.0/lib/taskjuggler/reports/TaskListRE.rb0000644000175000017500000000436612614413013022455 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TaskListRE.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/TableReport' require 'taskjuggler/reports/ReportTable' require 'taskjuggler/TableColumnDefinition' require 'taskjuggler/LogicalExpression' class TaskJuggler # This specialization of TableReport implements a task listing. It # generates a list of tasks that can optionally have the allocated resources # nested underneath each task line. class TaskListRE < TableReport # Create a new object and set some default values. def initialize(report) super @table = ReportTable.new @table.selfcontained = report.get('selfcontained') @table.auxDir = report.get('auxdir') end # Generate the table in the intermediate format. def generateIntermediateFormat super # Prepare the task list. taskList = PropertyList.new(@project.tasks) taskList.includeAdopted taskList.setSorting(@report.get('sortTasks')) taskList.query = @report.project.reportContexts.last.query taskList = filterTaskList(taskList, nil, @report.get('hideTask'), @report.get('rollupTask'), @report.get('openNodes')) taskList.sort! taskList.checkForDuplicates(@report.sourceFileInfo) # Prepare the resource list. Don't filter it yet! It would break the # *_() LogicalFunctions. resourceList = PropertyList.new(@project.resources) resourceList.setSorting(@report.get('sortResources')) resourceList.query = @report.project.reportContexts.last.query resourceList.sort! # Generate the table header. @report.get('columns').each do |columnDescr| adjustColumnPeriod(columnDescr, taskList, @report.get('scenarios')) generateHeaderCell(columnDescr) end # Generate the list. generateTaskList(taskList, resourceList, nil) end end end taskjuggler-3.5.0/lib/taskjuggler/reports/TraceReport.rb0000644000175000017500000002042412614413013022713 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TraceReport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' require 'taskjuggler/reports/CSVFile' require 'taskjuggler/reports/ChartPlotter' require 'taskjuggler/TableColumnSorter' require 'taskjuggler/MessageHandler' class TaskJuggler # The trace report is used to periodically snapshot a specific list of # property attributes and add them to a CSV file. class TraceReport < ReportBase include MessageHandler # Create a new object and set some default values. def initialize(report) super @table = nil end # Generate the table in the intermediate format. def generateIntermediateFormat super queryAttrs = { 'project' => @project, 'scopeProperty' => nil, 'loadUnit' => a('loadUnit'), 'numberFormat' => a('numberFormat'), # We use a hardcoded %Y-%m-%d format for tracereports. 'timeFormat' => "%Y-%m-%d", 'currencyFormat' => a('currencyFormat'), 'start' => a('start'), 'end' => a('end'), 'hideJournalEntry' => a('hideJournalEntry'), 'journalMode' => a('journalMode'), 'journalAttributes' => a('journalAttributes'), 'sortJournalEntries' => a('sortJournalEntries'), 'costAccount' => a('costaccount'), 'revenueAccount' => a('revenueaccount') } query = Query.new(queryAttrs) # Prepare the account list. accountList = PropertyList.new(@project.accounts) accountList.setSorting(a('sortAccounts')) accountList.query = query accountList = filterAccountList(accountList, a('hideAccount'), a('rollupAccount'), a('openNodes')) accountList.sort! # Prepare the resource list. resourceList = PropertyList.new(@project.resources) resourceList.setSorting(a('sortResources')) resourceList.query = query resourceList = filterTaskList(resourceList, nil, a('hideResource'), a('rollupResource'), a('openNodes')) resourceList.sort! # Prepare the task list. taskList = PropertyList.new(@project.tasks) taskList.includeAdopted taskList.setSorting(a('sortTasks')) taskList.query = query taskList = filterTaskList(taskList, nil, a('hideTask'), a('rollupTask'), a('openNodes')) taskList.sort! @fileName = ((@report.name[0] == '/' ? '' : @project.outputDir) + @report.name + '.csv').untaint # Generate the table header. headers = [ 'Date' ] + generatePropertyListHeader(accountList, query) + generatePropertyListHeader(resourceList, query) + generatePropertyListHeader(taskList, query) discontinuedColumns = 0 if File.exists?(@fileName) begin @table = CSVFile.new(nil, nil).read(@fileName) rescue error('tr_cannot_read_csv', "Cannot read CSV file #{@fileName}: #{$!}") end if @table[0] != headers # Some columns have changed. We move all discontinued columns to the # last columns and rearrange the others according to the new # headers. New columns will be filled with nil in previous rows. sorter = TableColumnSorter.new(@table) @table = sorter.sort(headers) discontinuedColumns = sorter.discontinuedColumns end else @table = [ headers ] end # Convert empty strings into nil objects and dates in %Y-%m-%d format # into TjTime objects. @table.each do |line| line.length.times do |i| if line[i] == '' line[i] = nil elsif line[i].is_a?(String) && /\d{4}-\d{2}-\d{2}/ =~ line[i] line[i] = TjTime.new(line[i]) end end end query = @project.reportContexts.last.query.dup dateTag = @project['now'].midnight idx = @table.index { |line| line[0] == dateTag } discColumnValues = discontinuedColumns > 0 ? Array.new(discontinuedColumns, nil) : [] if idx # We already have an entry for the current date. All old values of # this line will be overwritten with the current values. The old # values in the discontinued columns will be kept. if discontinuedColumns > 0 discColumnValues = @table[idx][headers.length..-1] end @table[idx] = [] else # Append a new line of values to the table. @table << [] idx = -1 end # The first entry is always the current date. @table[idx] << dateTag # Now add the new values to the line generatePropertyListValues(idx, accountList, query) generatePropertyListValues(idx, resourceList, query) generatePropertyListValues(idx, taskList, query) # Fill the discontinued columns with old values or nil. @table[idx] += discColumnValues # Sort the table by ascending first column dates. We need to ensure that # the header remains the first line in the table. @table.sort! { |l1, l2| l1[0].is_a?(String) ? -1 : (l2[0].is_a?(String) ? 1 : l1[0] <=> l2[0]) } end def to_html html = [] html << rt_to_html('header') begin plotter = ChartPlotter.new(a('width'), a('height'), @table) plotter.generate html << plotter.to_svg rescue ChartPlotterError => exception warning('chartPlotterError', exception.message, @report.sourceFileInfo) end html << rt_to_html('footer') html end def to_csv # Convert all TjTime values into String with format %Y-%m-%d and nil # objects into empty Strings. @table.each do |line| line.length.times do |i| if line[i].nil? line[i] = '' elsif line[i].is_a?(TjTime) line[i] = line[i].to_s('%Y-%m-%d') end end end @table end private def generatePropertyListHeader(propertyList, query) headers = [] query = query.dup a('columns').each do |columnDescr| query.attributeId = columnDescr.id a('scenarios').each do |scenarioIdx| query.scenarioIdx = scenarioIdx propertyList.each do |property| query.property = property #adjustColumnPeriod(columnDescr, propertyList, a.get('scenarios')) header = SimpleQueryExpander.new(columnDescr.title, query, @report.sourceFileInfo).expand if headers.include?(header) error('trace_columns_not_uniq', "The column title '#{header}' is already used " + "by a previous column. Column titles must be " + "unique!") end headers << header end end end headers end def generatePropertyListValues(idx, propertyList, query) @report.get('columns').each do |columnDescr| query.attributeId = columnDescr.id a('scenarios').each do |scenarioIdx| query.scenarioIdx = scenarioIdx propertyList.each do |property| query.property = property query.process @table[idx] << query.result end end end end def columnTitle(property, scenarioIdx, columnDescr) title = columnDescr.title.dup # The title can be parameterized by including mini-queries for the ID # or the name of the property, the scenario id or the attribute ID. title.gsub!(/<-id->/, property.fullId) title.gsub!(/<-scenario->/, @project.scenario(scenarioIdx).id) title.gsub!(/<-name->/, property.name) title.gsub!(/<-attribute->/, columnDescr.id) title end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ReportBase.rb0000644000175000017500000001474412614413013022537 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ReportBase.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This is the abstract base class for all kinds of reports. The derived # classes must implement the generateIntermediateFormat function as well as # the to_* members. class ReportBase def initialize(report) @report = report @project = report.project end # Convenience function to access a report attribute def a(attribute) @report.get(attribute) end def generateIntermediateFormat query = @report.project.reportContexts.last.query %w( header left center right footer prolog headline caption epilog ).each do |name| next unless (text = a(name)) text.setQuery(query) end end # Take the complete account list and remove all accounts that are matching # the hide expression, the rollup Expression or are not a descendent of # accountroot. def filterAccountList(list_, hideExpr, rollupExpr, openNodes) list = PropertyList.new(list_) if (accountroot = a('accountroot')) # Remove all accounts that are not descendents of the accountroot. list.delete_if { |account| !account.isChildOf?(accountroot) } end standardFilterOps(list, hideExpr, rollupExpr, openNodes, nil, accountroot) end # Take the complete task list and remove all tasks that are matching the # hide expression, the rollup Expression or are not a descendent of # taskroot. In case resource is not nil, a task is only included if # the resource is allocated to it in any of the reported scenarios. def filterTaskList(list_, resource, hideExpr, rollupExpr, openNodes) list = PropertyList.new(list_) if (taskRoot = a('taskroot')) # Remove all tasks that are not descendents of the taskRoot. list.delete_if { |task| !task.isChildOf?(taskRoot) } end if resource # If we have a resource we need to check that the resource is allocated # to the tasks in any of the reported scenarios within the report time # frame. list.delete_if do |task| delete = true a('scenarios').each do |scenarioIdx| iv = TimeInterval.new(a('start'), a('end')) if task.hasResourceAllocated?(scenarioIdx, iv, resource) delete = false break; end end delete end end standardFilterOps(list, hideExpr, rollupExpr, openNodes, resource, taskRoot) end # Take the complete resource list and remove all resources that are matching # the hide expression, the rollup Expression or are not a descendent of # resourceroot. In case task is not nil, a resource is only included if # it is assigned to the task in any of the reported scenarios. def filterResourceList(list_, task, hideExpr, rollupExpr, openNodes) list = PropertyList.new(list_) if (resourceRoot = a('resourceroot')) # Remove all resources that are not descendents of the resourceRoot. list.delete_if { |resource| !resource.isChildOf?(resourceRoot) } end if task # If we have a task we need to check that the resources are assigned # to the task in any of the reported scenarios. iv = TimeInterval.new(a('start'), a('end')) list.delete_if do |resource| delete = true a('scenarios').each do |scenarioIdx| if task.hasResourceAllocated?(scenarioIdx, iv, resource) delete = false break; end end delete end end standardFilterOps(list, hideExpr, rollupExpr, openNodes, task, resourceRoot) end private def generateHtmlTableFrame table = XMLElement.new('table', 'class' => 'tj_table_frame', 'cellspacing' => '1') # Headline box if a('headline') table << generateHtmlTableRow do td = XMLElement.new('td') td << (div = XMLElement.new('div', 'class' => 'tj_table_headline')) div << a('headline').to_html td end end table end def generateHtmlTableRow XMLElement.new('tr') << yield end # Convert the RichText object _name_ into a HTML form. def rt_to_html(name) return unless a(name) a(name).sectionNumbers = false a(name).to_html end # This function implements the generic filtering functionality for all kinds # of lists. def standardFilterOps(list, hideExpr, rollupExpr, openNodes, scopeProperty, root) # Make a copy of the current Query. query = @project.reportContexts.last.query.dup query.scopeProperty = scopeProperty # Remove all properties that the user wants to have hidden. if hideExpr list.delete_if do |property| query.property = property hideExpr.eval(query) end end # Remove all children of properties that the user has rolled-up. if rollupExpr || openNodes list.delete_if do |property| parent = property.parent delete = false while (parent) query.property = parent # If openNodes is not nil, only the listed nodes will be unrolled. # If openNodes is nil, only the nodes that match rollupExpr will # not be unrolled. if (openNodes && !openNodes.include?([ parent, scopeProperty ])) || (!openNodes && rollupExpr.eval(query)) delete = true break end parent = parent.parent end delete end end # Re-add parents in tree mode if list.treeMode? parents = [] list.each do |property| parent = property while (parent = parent.parent) parents << parent unless list.include?(parent) || parents.include?(parent) break if parent == root end end list.append(parents) end list end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ReportTableLine.rb0000644000175000017500000000625212614413013023517 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ReportTableLine.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportTableCell' class TaskJuggler class ReportTableLine attr_reader :table, :property, :scopeLine attr_accessor :height, :indentation, :fontSize, :bold, :no, :lineNo, :subLineNo # Create a ReportTableCell object and initialize the variables with default # values. _table_ is a reference to the ReportTable object this line belongs # to. _property_ is a reference to the Task or Resource that is displayed in # this line. _scopeLine_ is the line that sets the scope for this line. The # value is nil if this is a primary line. def initialize(table, property, scopeLine) @table = table @property = property @scopeLine = scopeLine # Register the new line with the table it belongs to. @table.addLine(self) # The cells of this line. Should be references to ReportTableCell objects. @cells = [] # Heigh of the line in screen pixels @height = 21 # Indentation for hierachiecal columns in screen pixels. @indentation = 0 # The factor used to enlarge or shrink the font size for this line. @fontSize = 12 # Specifies whether the whole line should be in bold type or not. @bold = false # Counter that counts primary and nested lines separately. It restarts # with 0 for each new nested line set. Scenario lines don't count. @no = nil # Counter that counts the primary lines. Scenario lines don't count. @lineNo = nil # Counter that counts all lines. @subLineNo = nil end # Return the last non-hidden cell of the line. Start to look for the cell at # the first cell after _count_ cells. def last(count = 0) (1 + count).upto(@cells.length) do |i| return @cells[-i] unless @cells[-i].hidden end nil end # Add the new cell to the line. _cell_ must reference a ReportTableCell # object. def addCell(cell) @cells << cell end # Return the scope property or nil def scopeProperty @scopeLine ? @scopeLine.property : nil end # Return this line as a set of XMLElement that represent the line in HTML. def to_html style = "" style += "height:#{@height}px; " if @table.equiLines style += "font-size:#{@fontSize}px; " if @fontSize tr = XMLElement.new('tr', 'class' => 'tabline', 'style' => style) @cells.each { |cell| tr << cell.to_html } tr end # Convert the intermediate format into an Array of values. One entry for # every column cell of this line. def to_csv(csv, startColumn, lineIdx) columnIdx = startColumn @cells.each do |cell| columnIdx += cell.to_csv(csv, columnIdx, lineIdx) end columnIdx - startColumn end end end taskjuggler-3.5.0/lib/taskjuggler/reports/AccountListRE.rb0000644000175000017500000001045012614413013023136 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = AccountListRE.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/TableReport' require 'taskjuggler/reports/ReportTable' require 'taskjuggler/TableColumnDefinition' require 'taskjuggler/LogicalExpression' class TaskJuggler # This specialization of TableReport implements a task listing. It # generates a list of tasks that can optionally have the allocated resources # nested underneath each task line. class AccountListRE < TableReport # Create a new object and set some default values. def initialize(report) super @table = ReportTable.new @table.selfcontained = report.get('selfcontained') @table.auxDir = report.get('auxdir') end # Generate the table in the intermediate format. def generateIntermediateFormat super # Prepare the account list. accountList = PropertyList.new(@project.accounts) accountList.setSorting(@report.get('sortAccounts')) accountList.query = @report.project.reportContexts.last.query accountList = filterAccountList(accountList, @report.get('hideAccount'), @report.get('rollupAccount'), @report.get('openNodes')) accountList.sort! # Generate the table header. @report.get('columns').each do |columnDescr| adjustColumnPeriod(columnDescr) generateHeaderCell(columnDescr) end if (costAccount = @report.get('costaccount')) && (revenueAccount = @report.get('revenueaccount')) # We are in balance mode. First show the cost and then the revenue # accounts and then the total balance. costAccountList = PropertyList.new(@project.accounts) costAccountList.clear costAccountList.setSorting(@report.get('sortAccounts')) costAccountList.query = @report.project.reportContexts.last.query revenueAccountList = PropertyList.new(@project.accounts) revenueAccountList.clear revenueAccountList.setSorting(@report.get('sortAccounts')) revenueAccountList.query = @report.project.reportContexts.last.query # Split the account list into a cost and a revenue account list. accountList.each do |account| if account.isChildOf?(costAccount) || account == costAccount costAccountList << account elsif account.isChildOf?(revenueAccount) || account == revenueAccount revenueAccountList << account end end # Make sure that the top-level cost and revenue accounts are always # included in the lists. unless costAccountList.include?(costAccount) costAccountList << costAccount end unless revenueAccountList.include?(revenueAccount) revenueAccountList << revenueAccount end generateAccountList(costAccountList, 0, nil) generateAccountList(revenueAccountList, costAccountList.length, nil) # To generate a total line that reports revenue minus cost, we create # a temporary Account object that adopts the cost and revenue # accounts. totalAccount = Account.new(@report.project, '0', "Total", nil) totalAccount.adopt(costAccount) totalAccount.adopt(revenueAccount) totalAccountList = PropertyList.new(@project.accounts) totalAccountList.clear totalAccountList.setSorting(@report.get('sortAccounts')) totalAccountList.query = @report.project.reportContexts.last.query totalAccountList << totalAccount generateAccountList(totalAccountList, costAccountList.length + revenueAccountList.length, nil) @report.project.removeAccount(totalAccount) else # We are not in balance mode. Simply show a list of all reports that # aren't filtered out. generateAccountList(accountList, 0, nil) end end end end taskjuggler-3.5.0/lib/taskjuggler/reports/TagFile.rb0000644000175000017500000000717712614413013022006 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TagFile.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase.rb' require 'taskjuggler/Tj3Config' class TaskJuggler # This class specializes ReportBase to generate tag files used by editors # such as vim. class TagFile < ReportBase # The TagFileEntry class is used to store the intermediate representation # of the TagFile. class TagFileEntry attr_reader :tag, :file, :line, :kind # Create a new TagFileEntry object. _tag_ is the property ID. _file_ is # the source file name, _line_ the line number in this file. _kind_ # specifies the property type. The following types should be used: # r : Resource # t : Task # p : Report def initialize(tag, file, line, kind) @tag = tag @file = file @line = line @kind = kind end # Used to sort the tag file entries by tag. def <=>(e) @tag <=> e.tag end # Convert the entry into a ctags compatible line. def to_ctags "#{@tag}\t#{@file}\t#{@line};\"\t#{@kind}\n" end end def initialize(report) super end def generateIntermediateFormat super @tags = [] # Add the resources. @resourceList = PropertyList.new(@project.resources) @resourceList.setSorting(a('sortResources')) @resourceList = filterResourceList(@resourceList, nil, a('hideResource'), a('rollupResource'), a('openNodes')) @resourceList.each do |resource| next unless resource.sourceFileInfo @tags << TagFileEntry.new(resource.fullId, resource.sourceFileInfo.fileName, resource.sourceFileInfo.lineNo, 'r') end # Add the tasks. @taskList = PropertyList.new(@project.tasks) @taskList.setSorting(a('sortTasks')) @taskList = filterTaskList(@taskList, nil, a('hideTask'), a('rollupTask'), a('openNodes')) @taskList.each do |task| next unless task.sourceFileInfo @tags << TagFileEntry.new(task.fullId, task.sourceFileInfo.fileName, task.sourceFileInfo.lineNo, 't') end # Add the reports. @project.reports.each do |report| next unless report.sourceFileInfo @tags << TagFileEntry.new(report.fullId, report.sourceFileInfo.fileName, report.sourceFileInfo.lineNo, 'p') end end # Returns a String that contains the content of the ctags file. # See http://vimdoc.sourceforge.net/htmldoc/tagsrch.html for the spec. def to_ctags # The ctags header. Not used if this is really needed. s = <<"EOT" !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/ !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ !_TAG_PROGRAM_AUTHOR #{AppConfig.authors.join(';')} // !_TAG_PROGRAM_NAME #{AppConfig.softwareName} // !_TAG_PROGRAM_URL #{AppConfig.contact} /official site/ !_TAG_PROGRAM_VERSION #{AppConfig.version} // EOT # Turn the list of Tags into ctags lines. @tags.sort.each do |tag| s << tag.to_ctags end s end end end taskjuggler-3.5.0/lib/taskjuggler/reports/Navigator.rb0000644000175000017500000001671212614413013022420 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Navigator.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportContext' class TaskJuggler class NavigatorElement attr_reader :parent, :label attr_accessor :url, :elements, :current def initialize(parent, label = nil, url = nil) @parent = parent @label = label @url = url @elements = [] # True if the current report is included in this NavigatorElement or any # of its sub elements. @current = false end def to_html(html = nil) first = true topLevel = html.nil? # If we don't have a container yet, to put all the menus into, create one. html ||= XMLElement.new('div', 'class' => 'navbar_container') html << XMLElement.new('hr', 'class' => 'navbar_topruler') if topLevel # Create a container for this (sub-)menu. html << (div = XMLElement.new('div', 'class' => 'navbar')) @elements.each do |element| # Separate the menu entries by vertical bars. Prepend them for all but # the first entry. if first first = false else div << XMLText.new('|') end if element.current # The navbar entry is referencing this page. Highlight is as the # currently selected page. div << (span = XMLElement.new('span', 'class' => 'navbar_current')) span << XMLText.new(element.label) else # The navbar entry is refencing another page. Show the link to it. div << (span = XMLElement.new('span', 'class' => 'navbar_other')) span << (a = XMLElement.new('a', 'href' => element.url)) a << XMLText.new(element.label) end end # Now see if the current menu entry is actually just holding another sub # menu and generate that menue in another line after an HR. @elements.each do |element| if element.current && !element.elements.empty? html << XMLElement.new('hr', 'class' => 'navbar_midruler') unless first element.to_html(html) break end end html << XMLElement.new('hr', 'class' => 'navbar_bottomruler') if topLevel html end # Return a text version of the tree. Currently used for debugging only. def to_s(indent = 0) @elements.each do |element| puts "#{' ' * indent}#{element.current ? '<' : ''}" + "#{element.label}#{element.current ? '>' : ''}" + " -> #{element.url}" element.to_s(indent + 1) end end end # A Navigator is an automatically generated menu to navigate a list of # reports. The hierarchical structure of the reports will be reused to # group them. The actual structure of the Navigator depends on the output # format. class Navigator attr_reader :id attr_accessor :hideReport def initialize(id, project) @id = id @project = project @hideReport = LogicalExpression.new(LogicalOperation.new(0)) @elements = [] end # Generate an output format independant version of the navigator. This is # a tree of NavigatorElement objects. def generate(allReports, currentReports, reportDef, parentElement) element = nextParentElement = nextParentReport = nil currentReports.each do |report| hasURL = report.get('formats').include?(:html) # Only generate menu entries for container reports or leaf reports # have a HTML output format. next if (report.leaf? && !hasURL) || !allReports.include?(report) # What label should be used for the menu entry? It's either the name # of the report or the user specified title. label = report.get('title') || report.name url = findReportURL(report, allReports, reportDef) # Now we have all data so we can create the actual menu entry. parentElement.elements << (element = NavigatorElement.new(parentElement, label, url)) # Check if 'report' matches the 'reportDef' report or is a child of # it. if reportDef == report || reportDef.isChildOf?(report) nextParentReport = report nextParentElement = element element.current = true end end if nextParentReport && nextParentReport.container? generate(allReports, nextParentReport.kids, reportDef, nextParentElement) end end def to_html # The the Report object that contains this Navigator. reportDef ||= @project.reportContexts.last.report raise "Report context missing" unless reportDef # Compile a list of all reports that the user wants to include in the # menu. reports = filterReports return nil if reports.empty? # Make sure the report is actually in the filtered list. unless reports.include?(reportDef) @project.warning('nav_in_hidden_rep', "Navigator requested for a report that is not " + "included in the navigator list.", reportDef.sourceFileInfo) return nil end # Find the list of reports that become the top-level menu entries. topLevelReports = [ reportDef ] report = reportDef while report.parent report = report.parent topLevelReports = report.kids end generate(reports, topLevelReports, reportDef, content = NavigatorElement.new(nil)) content.to_html end private def filterReports list = PropertyList.new(@project.reports) list.setSorting([[ 'seqno', true, -1 ]]) list.sort! # Remove all reports that the user doesn't want to have include. query = @project.reportContexts.last.query.dup query.scopeProperty = nil query.scenarioIdx = query.scenario = nil list.delete_if do |property| query.property = property @hideReport.eval(query) end list end # Remove the URL or directory path from _url1_ that is identical to # _url2_. def normalizeURL(url1, url2) cut = 0 url1.length.times do |i| return url1[cut, url1.length - cut] if url1[i] != url2[i] cut = i + 1 if url1[i] == ?/ end url1 end # Find the URL to be used for the current Navigator menu entry. def findReportURL(report, allReports, reportDef) return nil unless allReports.include?(report) if report.get('formats').include?(:html) # The element references an HTML report. Point to it. if @project.reportContexts.last.report.interactive? url = "/taskjuggler?project=#{report.project['projectid']};" + "report=#{report.fullId}" else url = report.name + '.html' url = normalizeURL(url, reportDef.name) end return url else # The menu element is just a entry for another sub-menu. The the URL # from the first kid of the report that has a URL. report.kids.each do |r| if (url = findReportURL(r, allReports, reportDef)) return url end end end nil end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ResourceListRE.rb0000644000175000017500000000523312614413013023334 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ResourceListRE.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/TableReport' require 'taskjuggler/reports/ReportTable' require 'taskjuggler/TableColumnDefinition' require 'taskjuggler/LogicalExpression' class TaskJuggler # This specialization of TableReport implements a resource listing. It # generates a list of resources that can optionally have the assigned tasks # nested underneath each resource line. class ResourceListRE < TableReport # Create a new object and set some default values. def initialize(report) super @table = ReportTable.new @table.selfcontained = report.get('selfcontained') @table.auxDir = report.get('auxdir') end # Generate the table in the intermediate format. def generateIntermediateFormat super # Prepare the resource list. resourceList = PropertyList.new(@project.resources) resourceList.setSorting(@report.get('sortResources')) resourceList.query = @report.project.reportContexts.last.query resourceList = filterResourceList(resourceList, nil, @report.get('hideResource'), @report.get('rollupResource'), @report.get('openNodes')) resourceList.sort! # Prepare the task list. Don't filter it yet! It would break the # *_() LogicalFunctions. taskList = PropertyList.new(@project.tasks) taskList.setSorting(@report.get('sortTasks')) taskList.query = @report.project.reportContexts.last.query taskList.sort! assignedTaskList = [] resourceList.each do |resource| assignedTaskList += filterTaskList(taskList, resource, @report.get('hideTask'), @report.get('rollupTask'), @report.get('openNodes')) assignedTaskList.uniq! end # Generate the table header. @report.get('columns').each do |columnDescr| adjustColumnPeriod(columnDescr, assignedTaskList, @report.get('scenarios')) generateHeaderCell(columnDescr) end # Generate the list. generateResourceList(resourceList, taskList, nil) end end end taskjuggler-3.5.0/lib/taskjuggler/reports/TableReport.rb0000644000175000017500000013443012614413013022707 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TableReport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' require 'taskjuggler/reports/GanttChart' require 'taskjuggler/reports/ReportTableLegend' require 'taskjuggler/reports/ColumnTable' require 'taskjuggler/reports/TableReportColumn' require 'taskjuggler/Query' class TaskJuggler # This is base class for all types of tabular reports. All tabular reports # are converted to an abstract (output independent) intermediate form first, # before the are turned into the requested output format. class TableReport < ReportBase attr_reader :legend @@propertiesById = { # ID Header Indent Align Scen Spec. 'activetasks' => [ 'Active Tasks', true, :right, true ], 'annualleave' => [ 'Annual Leave', true, :right, true ], 'annualleavebalance'=> [ 'Annual Leave Balance', false, :right, true ], 'alert' => [ 'Alert', true, :left, false ], 'alertmessages' => [ 'Alert Messages', false, :left, false ], 'alertsummaries' => [ 'Alert Summaries', false, :left, false ], 'alerttrend' => [ 'Alert Trend', false, :left, false ], 'balance' => [ 'Balance', true, :right, true ], 'bsi' => [ 'BSI', false, :left, false ], 'closedtasks' => [ 'Closed Tasks', true, :right, true ], 'competitorcount' => [ 'Competitor count', true, :right, true ], 'competitors' => [ 'Competitors', true, :left, true ], 'complete' => [ 'Completion', false, :right, true ], 'cost' => [ 'Cost', true, :right, true ], 'duration' => [ 'Duration', true, :right, true ], 'effort' => [ 'Effort', true, :right, true ], 'effortdone' => [ 'Effort Done', true, :right, true ], 'effortleft' => [ 'Effort Left', true, :right, true ], 'freetime' => [ 'Free Time', true, :right, true ], 'freework' => [ 'Free Work', true, :right, true ], 'followers' => [ 'Followers', false, :left, true ], 'fte' => [ 'FTE', true, :right, true ], 'headcount' => [ 'Headcount', true, :right, true ], 'id' => [ 'Id', false, :left, false ], 'inputs' => [ 'Inputs', false, :left, true ], 'journal' => [ 'Journal', false, :left, false ], 'journal_sub' => [ 'Journal', false, :left, false ], 'journalmessages' => [ 'Journal Messages', false, :left, false ], 'journalsummaries' => [ 'Journal Summaries', false, :left, false ], 'line' => [ 'Line No.', false, :right, false ], 'name' => [ 'Name', true, :left, false ], 'no' => [ 'No.', false, :right, false ], 'opentasks' => [ 'Open Tasks', true, :right, true ], 'precursors' => [ 'Precursors', false, :left, true ], 'rate' => [ 'Rate', true, :right, true ], 'resources' => [ 'Resources', false, :left, true ], 'responsible' => [ 'Responsible', false, :left, true ], 'revenue' => [ 'Revenue', true, :right, true ], 'scenario' => [ 'Scenario', false, :left, true ], 'scheduling' => [ 'Scheduling Mode', true, :left, true ], 'sickleave' => [ 'Sick Leave', true, :right, true ], 'specialleave' => [ 'Special Leave', true, :right, true ], 'status' => [ 'Status', false, :left, true ], 'targets' => [ 'Targets', false, :left, true ], 'unpaidleave' => [ 'Unpaid Leave', true, :right, true ] } @@propertiesByType = { # Type Indent Align DateAttribute => [ false, :left ], FixnumAttribute => [ false, :right ], FloatAttribute => [ false, :right ], ResourceListAttribute => [ false, :left ], RichTextAttribute => [ false, :left ], StringAttribute => [ false, :left ] } # Generate a new TableReport object. def initialize(report) super @report.content = self # Reference to the intermediate representation. @table = nil # The table is generated row after row. We need to hold some computed # values that are specific to certain columns. For that we use a Hash of # ReportTableColumn objects. @columns = { } @legend = ReportTableLegend.new end def generateIntermediateFormat super end # Turn the TableReport into an equivalent HTML element tree. def to_html html = [] html << XMLComment.new("Dynamic Report ID: " + "#{@report.project.reportContexts.last. dynamicReportId}") html << rt_to_html('header') html << (tableFrame = generateHtmlTableFrame) # Now generate the actual table with the data. tableFrame << generateHtmlTableRow do td = XMLElement.new('td') td << @table.to_html td end # Embedd the caption as RichText into the table footer. if a('caption') tableFrame << generateHtmlTableRow do td = XMLElement.new('td') td << (div = XMLElement.new('div', 'class' => 'tj_table_caption')) a('caption').sectionNumbers = false div << a('caption').to_html td end end # The legend. tableFrame << generateHtmlTableRow do td = XMLElement.new('td') td << @legend.to_html td end html << rt_to_html('footer') html end # Convert the table into an Array of Arrays. It has one Array for each # line. The nested Arrays have one String for each column. def to_csv @table.to_csv end # Returns the default column title for the columns _id_. def TableReport::defaultColumnTitle(id) # Return an empty string for some special columns that don't have a fixed # title. specials = %w( chart hourly daily weekly monthly quarterly yearly) return '' if specials.include?(id) # Return the title for build-in hardwired columns. @@propertiesById.include?(id) ? @@propertiesById[id][0] : nil end # Return if the column values should be indented based on the _colId_ or the # _propertyType_. def indent(colId, propertyType) if @@propertiesById.has_key?(colId) return @@propertiesById[colId][1] elsif @@propertiesByType.has_key?(propertyType) return @@propertiesByType[propertyType][0] else false end end # Return the alignment of the column based on the _colId_ or the # _attributeType_. def alignment(colId, attributeType) if @@propertiesById.has_key?(colId) return @@propertiesById[colId][2] elsif @@propertiesByType.has_key?(attributeType) return @@propertiesByType[attributeType][1] else :center end end # This function returns true if the values for the _colId_ column need to be # calculated. def calculated?(colId) return @@propertiesById.has_key?(colId) end # This functions returns true if the values for the _col_id_ column are # scenario specific. def scenarioSpecific?(colId) if @@propertiesById.has_key?(colId) return @@propertiesById[colId][3] end return false end def supportedColumns @@propertiesById.keys end protected # In case the user has not specified the report period, we try to fit all # the _tasks_ in and add an extra 5% time at both ends for some specific # type of columns. _scenarios_ is a list of scenario indexes. _columnDef_ # is a reference to the TableColumnDefinition object describing the # current column. def adjustColumnPeriod(columnDef, tasks = [], scenarios = []) # If we have user specified dates for the report period or the column # period, we don't adjust the period. This flag is used to mark if we # have user-provided values. doNotAdjustStart = false doNotAdjustEnd = false # Determine the start date for the column. if columnDef.start # We have a user-specified, column specific start date. rStart = columnDef.start doNotAdjustStart = true else # Use the report start date. rStart = a('start') doNotAdjustStart = true if rStart != @project['start'] end if columnDef.end rEnd = columnDef.end doNotAdjustEnd = true else rEnd = a('end') doNotAdjustEnd = true if rEnd != @project['end'] end origStart = rStart origEnd = rEnd # Save the unadjusted dates to the columns Hash. @columns[columnDef] = TableReportColumn.new(rStart, rEnd) # If the task list is empty or the user has provided a custom start or # end date, we don't touch the report period. return if tasks.empty? || scenarios.empty? || (doNotAdjustStart && doNotAdjustEnd) # Find the start date of the earliest tasks included in the report and # the end date of the last included tasks. rStart = rEnd = nil scenarios.each do |scenarioIdx| tasks.each do |task| date = task['start', scenarioIdx] || @project['start'] rStart = date if rStart.nil? || date < rStart date = task['end', scenarioIdx] || @project['end'] rEnd = date if rEnd.nil? || date > rEnd end end # We want to add at least 5% on both ends. margin = 0 minWidth = rEnd - rStart + 1 case columnDef.id when 'chart' # In case we have a 'chart' column, we enforce certain minimum width # The following table contains an entry for each scale. The entry # consists of the triple 'seconds per unit', 'minimum width units' # and 'margin units'. The minimum with does not include the margins # since they are always added. mwMap = { 'hour' => [ 60 * 60, 18, 2 ], 'day' => [ 60 * 60 * 24, 18, 2 ], 'week' => [ 60 * 60 * 24 * 7, 6, 1 ], 'month' => [ 60 * 60 * 24 * 31, 10, 1 ], 'quarter' => [ 60 * 60 * 24 * 90, 6, 1 ], 'year' => [ 60 * 60 * 24 * 365, 4, 1 ] } entry = mwMap[columnDef.scale] raise "Unknown scale #{columnDef.scale}" unless entry margin = entry[0] * entry[2] # If the with determined by start and end dates of the task is below # the minimum width, we increase the width to the value provided by # the table. minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1] when 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly' # For the calendar columns we use a similar approach as we use for # the 'chart' column. mwMap = { 'hourly' => [ 60 * 60, 18, 2 ], 'daily' => [ 60 * 60 * 24, 18, 2 ], 'weekly' => [ 60 * 60 * 24 * 7, 6, 1 ], 'monthly' => [ 60 * 60 * 24 * 31, 10, 1 ], 'quarterly' => [ 60 * 60 * 24 * 90, 6, 1 ], 'yearly' => [ 60 * 60 * 24 * 365, 4, 1 ] } entry = mwMap[columnDef.id] raise "Unknown scale #{columnDef.id}" unless entry margin = entry[0] * entry[2] minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1] else doNotAdjustStart = doNotAdjustEnd = true end unless doNotAdjustStart && doNotAdjustEnd if minWidth > (rEnd - rStart + 1) margin = (minWidth - (rEnd - rStart + 1)) / 2 end rStart -= margin rEnd += margin # This could cause rStart to be larger than rEnd. rStart = origStart if doNotAdjustStart rEnd = origEnd if doNotAdjustEnd # Ensure that we have a valid interval. If not, go back to the # original interval dates. if rStart >= rEnd rStart = origStart rEnd = origEnd end # Save the adjusted dates to the columns Hash. @columns[columnDef] = TableReportColumn.new(rStart, rEnd) end end # Generates cells for the table header. _columnDef_ is the # TableColumnDefinition object that describes the column. Based on the id of # the column different actions need to be taken to generate the header text. def generateHeaderCell(columnDef) rStart = @columns[columnDef].start rEnd = @columns[columnDef].end case columnDef.id when 'chart' # For the 'chart' column we generate a GanttChart object. The sizes are # set so that the lines of the Gantt chart line up with the lines of the # table. gantt = GanttChart.new(a('now'), a('weekStartsMonday'), columnDef, self) gantt.generateByScale(rStart, rEnd, columnDef.scale) # The header consists of 2 lines separated by a 1 pixel boundary. gantt.header.height = @table.headerLineHeight * 2 + 1 # The maximum width of the chart. In case it needs more space, a # scrollbar is shown or the chart gets truncated depending on the output # format. gantt.viewWidth = columnDef.width ? columnDef.width : 450 column = ReportTableColumn.new(@table, columnDef, '') column.cell1.special = gantt column.cell2.hidden = true column.scrollbar = gantt.hasScrollbar? @table.equiLines = true when 'hourly' genCalChartHeader(columnDef, rStart.midnight, rEnd, :sameTimeNextHour, '%A %Y-%m-%d', '%H') when 'daily' genCalChartHeader(columnDef, rStart.midnight, rEnd, :sameTimeNextDay, '%b %Y', '%d') when 'weekly' genCalChartHeader(columnDef, rStart.beginOfWeek(a('weekStartsMonday')), rEnd, :sameTimeNextWeek, '%b %Y', '%d') when 'monthly' genCalChartHeader(columnDef, rStart.beginOfMonth, rEnd, :sameTimeNextMonth, '%Y', '%b') when 'quarterly' genCalChartHeader(columnDef, rStart.beginOfQuarter, rEnd, :sameTimeNextQuarter, '%Y', 'Q%Q') when 'yearly' genCalChartHeader(columnDef, rStart.beginOfYear, rEnd, :sameTimeNextYear, nil, '%Y') else # This is the most common case. It does not need any special treatment. # We just set the pre-defined or user-defined column title in the first # row of the header. The 2nd row is not visible. column = ReportTableColumn.new(@table, columnDef, columnDef.title) column.cell1.rows = 2 column.cell2.hidden = true column.cell1.width = columnDef.width if columnDef.width end end # Generate a ReportTableLine for each of the accounts in _accountList_. If # _scopeLine_ is defined, the generated account lines will be within the # scope this resource line. def generateAccountList(accountList, lineOffset, mode) # Get the current Query from the report context and create a copy. We # are going to modify it. accountList.query = query = @project.reportContexts.last.query.dup accountList.sort! # The primary line counter. Is not used for enclosed lines. no = lineOffset # The scope line counter. It's reset for each new scope. lineNo = lineOffset # Init the variable to get a larger scope line = nil accountList.each do |account| query.property = account no += 1 Log.activity if lineNo % 10 == 0 lineNo += 1 a('scenarios').each do |scenarioIdx| query.scenarioIdx = scenarioIdx # Generate line for each account. line = ReportTableLine.new(@table, account, nil) line.no = no line.lineNo = lineNo line.subLineNo = @table.lines setIndent(line, a('accountroot'), accountList.treeMode?) # Generate a cell for each column in this line. a('columns').each do |columnDef| next unless generateTableCell(line, columnDef, query) end end end lineNo end # Generate a ReportTableLine for each of the tasks in _taskList_. In case # _resourceList_ is not nil, it also generates the nested resource lines for # each resource that is assigned to the particular task. If _scopeLine_ # is defined, the generated task lines will be within the scope this # resource line. def generateTaskList(taskList, resourceList, scopeLine) # Get the current Query from the report context and create a copy. We # are going to modify it. taskList.query = query = @project.reportContexts.last.query.dup query.scopeProperty = scopeLine ? scopeLine.property : nil taskList.sort! # The primary line counter. Is not used for enclosed lines. no = 0 # The scope line counter. It's reset for each new scope. lineNo = scopeLine ? scopeLine.lineNo : 0 # Init the variable to get a larger scope line = nil taskList.each do |task| # Get the current Query from the report context and create a copy. We # are going to modify it. query.property = task query.scopeProperty = scopeLine ? scopeLine.property : nil no += 1 Log.activity if lineNo % 10 == 0 lineNo += 1 a('scenarios').each do |scenarioIdx| query.scenarioIdx = scenarioIdx # Generate line for each task. line = ReportTableLine.new(@table, task, scopeLine) line.no = no unless scopeLine line.lineNo = lineNo line.subLineNo = @table.lines setIndent(line, a('taskroot'), taskList.treeMode?) # Generate a cell for each column in this line. a('columns').each do |columnDef| next unless generateTableCell(line, columnDef, query) end end if resourceList # If we have a resourceList we generate nested lines for each of the # resources that are assigned to this task and pass the user-defined # filter. resourceList.setSorting(a('sortResources')) assignedResourceList = filterResourceList(resourceList, task, a('hideResource'), a('rollupResource'), a('openNodes')) assignedResourceList.sort! lineNo = generateResourceList(assignedResourceList, nil, line) end end lineNo end # Generate a ReportTableLine for each of the resources in _resourceList_. In # case _taskList_ is not nil, it also generates the nested task lines for # each task that the resource is assigned to. If _scopeLine_ is defined, the # generated resource lines will be within the scope this task line. def generateResourceList(resourceList, taskList, scopeLine) # Get the current Query from the report context and create a copy. We # are going to modify it. resourceList.query = query = @project.reportContexts.last.query.dup query.scopeProperty = scopeLine ? scopeLine.property : nil resourceList.sort! # The primary line counter. Is not used for enclosed lines. no = 0 # The scope line counter. It's reset for each new scope. lineNo = scopeLine ? scopeLine.lineNo : 0 # Init the variable to get a larger scope line = nil resourceList.each do |resource| # Get the current Query from the report context and create a copy. We # are going to modify it. query.property = resource query.scopeProperty = scopeLine ? scopeLine.property : nil no += 1 Log.activity if lineNo % 10 == 0 lineNo += 1 a('scenarios').each do |scenarioIdx| query.scenarioIdx = scenarioIdx # Generate line for each resource. line = ReportTableLine.new(@table, resource, scopeLine) line.no = no unless scopeLine line.lineNo = lineNo line.subLineNo = @table.lines setIndent(line, a('resourceroot'), resourceList.treeMode?) # Generate a cell for each column in this line. a('columns').each do |column| next unless generateTableCell(line, column, query) end end if taskList # If we have a taskList we generate nested lines for each of the # tasks that the resource is assigned to and pass the user-defined # filter. taskList.setSorting(a('sortTasks')) assignedTaskList = filterTaskList(taskList, resource, a('hideTask'), a('rollupTask'), a('openNodes')) assignedTaskList.sort! lineNo = generateTaskList(assignedTaskList, nil, line) end end lineNo end private # Generate the header data for calendar tables. They consists of columns for # each hour, day, week, etc. _columnDef_ is the definition of the columns. # _t_ is the start time for the calendar. _sameTimeNextFunc_ is a function # that is called to advance _t_ to the next table column interval. # _timeformat1_ and _timeformat2_ are strftime format Strings that are used # to generate the upper and lower title of the particular column. def genCalChartHeader(columnDef, t, rEnd, sameTimeNextFunc, timeformat1, timeformat2) tableColumn = ReportTableColumn.new(@table, columnDef, '') # Overwrite the built-in time formats if the user specified a different # one. timeformat1 = columnDef.timeformat1 if columnDef.timeformat1 timeformat2 = columnDef.timeformat2 if columnDef.timeformat2 # Calendar chars only work when all lines have same height. @table.equiLines = true # Embedded tables have unpredictable width. So we always need to make room # for a potential scrollbar. tableColumn.scrollbar = true # Create the table that is embedded in this column. tableColumn.cell1.special = table = ColumnTable.new table.equiLines = true table.selfcontained = a('selfcontained') tableColumn.cell2.hidden = true table.viewWidth = columnDef.width ? columnDef.width : 450 # Iterate over the report interval until we hit the end date. The # iteration is done with 2 nested loops. The outer loops generates the # intervals for the upper (larger) scale. The inner loop generates the # lower (smaller) scale. while t < rEnd cellsInInterval = 0 # Label for upper scale. The yearly calendar only has a lower scale. currentInterval = t.to_s(timeformat1) if timeformat1 firstColumn = nil # The innter loops terminates when the label for the upper scale has # changed to the next scale cell. while t < rEnd && (timeformat1.nil? || t.to_s(timeformat1) == currentInterval) # call TjTime::sameTimeNext... function to get the end of the column. nextT = t.send(sameTimeNextFunc) iv = TimeInterval.new(t, nextT) # Create the new column object. column = ReportTableColumn.new(table, nil, '') # Store the date of the column in the original form. column.cell1.data = t.to_s(a('timeFormat')) # The upper scale cells will be merged into one large cell that spans # all lower scale cells that belong to this upper cell. if firstColumn.nil? firstColumn = column column.cell1.text = currentInterval else column.cell1.hidden = true end column.cell2.text = t.to_s(timeformat2) # We assume an average of 7 pixel per character width = 8 + 7 * column.cell2.text.length # Ensure a minimum with of 28 to have good looking tables even with # small column headers (like day of months numbers). column.cell2.width = width <= 28 ? 28 : width # Off-duty cells will have a different color than working time cells. unless @project.hasWorkingTime(iv) column.cell2.category = 'tabhead_offduty' end cellsInInterval += 1 t = nextT end # The the first upper scale cell how many trailing hidden cells are # following. firstColumn.cell1.columns = cellsInInterval end end # Generate a cell of the table. _line_ is the ReportTableLine that this cell # should belong to. _property_ is the PropertyTreeNode that is reported in # this _line_. _columnDef_ is the TableColumnDefinition of the column this # cell should belong to. _scenarioIdx_ is the index of the scenario that is # reported in this _line_. # # There are 4 kinds of cells. The most simple one is the standard cell. It # literally reports the value of a property attribute. Calculated cells are # more flexible. They contain computed values. The values are computed at # cell generation time. The calendar columns consist of multiple sub # columns. In such a case many cells are generated with a single call of # this method. The last kind of cell is actually not a cell. It just # generates the chart objects that belong to the property in this line. def generateTableCell(line, columnDef, query) # Adjust the Query to use column specific settings. We create a copy of # the Query to avoid spoiling the original query with column specific # settings. query = query.dup query.attributeId = columnDef.id query.start = @columns[columnDef].start query.end = @columns[columnDef].end query.listType = columnDef.listType query.listItem = columnDef.listItem case columnDef.id when 'chart' # Generate a hidden cell. The real meat is in the actual chart object, # not in this cell. cell = ReportTableCell.new(line, query, '') cell.hidden = true cell.text = nil # The GanttChart can be reached via the special variable of the column # header. chart = columnDef.column.cell1.special GanttLine.new(chart, query, (line.subLineNo - 1) * (line.height + 1), line.height, a('selfcontained') ? nil : columnDef.tooltip) return true # The calendar cells can be all generated by the same function. But we # need to use different parameters. when 'hourly' start = query.start.midnight sameTimeNextFunc = :sameTimeNextHour when 'daily' start = query.start.midnight sameTimeNextFunc = :sameTimeNextDay when 'weekly' start = query.start.beginOfWeek(a('weekStartsMonday')) sameTimeNextFunc = :sameTimeNextWeek when 'monthly' start = query.start.beginOfMonth sameTimeNextFunc = :sameTimeNextMonth when 'quarterly' start = query.start.beginOfQuarter sameTimeNextFunc = :sameTimeNextQuarter when 'yearly' start = query.start.beginOfYear sameTimeNextFunc = :sameTimeNextYear else if calculated?(columnDef.id) return genCalculatedCell(query, line, columnDef) else return genStandardCell(query, line, columnDef) end end # The calendar cells don't live in this ReportTable but in an embedded # ReportTable that can be reached via the column header special variable. # For embedded column tables we need to create a new line. tcLine = ReportTableLine.new(columnDef.column.cell1.special, line.property, line.scopeLine) PlaceHolderCell.new(line, tcLine) # Depending on the property type we use different generator functions. if query.property.is_a?(Task) genCalChartTaskCell(query, tcLine, columnDef, start, sameTimeNextFunc) elsif query.property.is_a?(Resource) genCalChartResourceCell(query, tcLine, columnDef, start, sameTimeNextFunc) elsif query.property.is_a?(Account) genCalChartAccountCell(query, tcLine, columnDef, start, sameTimeNextFunc) else raise "Unknown property type #{query.property.class}" end true end # Generate a ReportTableCell filled the value of an attribute of the # property that line is for. It returns true if the cell exists, false for a # hidden cell. def genStandardCell(query, line, columnDef) # Find out, what type of PropertyTreeNode we are dealing with. property = line.property if property.is_a?(Task) propertyList = @project.tasks elsif property.is_a?(Resource) propertyList = @project.resources elsif property.is_a?(Account) propertyList = @project.accounts else raise "Unknown property type #{property.class}" end # Create a new cell cell = newCell(query, line) unless setScenarioSettings(cell, query.scenarioIdx, propertyList.scenarioSpecific?(columnDef.id)) return false end setStandardCellAttributes(query, cell, columnDef, propertyList.attributeType(columnDef.id), line) # If the user has requested a custom cell text, this will be used # instead of the queried one. if (cdText = columnDef.cellText.getPattern(query)) cell.text = cdText elsif query.process cell.text = (rti = query.to_rti) ? rti : query.to_s end setCustomCellAttributes(cell, columnDef, query) checkCellText(cell) true end # Generate a ReportTableCell filled with a calculted value from the property # or other sources of information. It returns true if the cell exists, false # for a hidden cell. _query_ is the Query to get the cell value. _line_ # is the ReportTableLine of the cell. _columnDef_ is the # TableColumnDefinition of the column. def genCalculatedCell(query, line, columnDef) # Create a new cell cell = newCell(query, line) unless setScenarioSettings(cell, query.scenarioIdx, scenarioSpecific?(columnDef.id)) return false end setStandardCellAttributes(query, cell, columnDef, nil, line) if query.process cell.text = (rti = query.to_rti) ? rti : query.to_s end # Some columns need some extra care. case columnDef.id when 'alert' id = @project['alertLevels'][query.to_sort].id cell.icon = "flag-#{id}" cell.fontColor = @project['alertLevels'][query.to_sort].color when 'alerttrend' icons = %w( up flat down ) cell.icon = "trend-#{icons[query.to_sort]}" when 'line' cell.text = line.lineNo.to_s when 'name' property = query.property cell.icon = if property.is_a?(Task) if property.container? 'taskgroup' else 'task' end elsif property.is_a?(Resource) if property.container? 'resourcegroup' else 'resource' end else nil end cell.iconTooltip = RichText.new("'''ID:''' #{property.fullId}"). generateIntermediateFormat when 'no' cell.text = line.no.to_s when 'bsi' cell.indent = 2 if line.scopeLine when 'scenario' cell.text = @project.scenario(query.scenarioIdx).name end # Replace the cell text if the user has requested a custom cell text. cdText = columnDef.cellText.getPattern(query) cell.text = cdText if cdText setCustomCellAttributes(cell, columnDef, query) checkCellText(cell) true end # Generate the cells for the account lines of a calendar column. These # lines do not directly belong to the @table object but to an embedded # ColumnTable object. Therefor a single @table column usually has many # cells on each single line. _scenarioIdx_ is the index of the scenario # that is reported in this line. _line_ is the @table line. _t_ is the # start date for the calendar. _sameTimeNextFunc_ is the function that # will move the date to the next cell. def genCalChartAccountCell(query, line, columnDef, t, sameTimeNextFunc) # We modify the start and end dates to match the cell boundaries. So # we need to make sure we don't modify the original Query but our own # copies. query = query.dup firstCell = nil endDate = query.end while t < endDate # call TjTime::sameTimeNext... function nextT = t.send(sameTimeNextFunc) query.attributeId = 'balance' query.start = t query.end = nextT query.process # Create a new cell cell = newCell(query, line) cell.text = query.to_s cdText = columnDef.cellText.getPattern(query) cell.text = cdText if cdText cell.showTooltipHint = false setAccountCellBgColor(query, line, cell) setCustomCellAttributes(cell, columnDef, query) tryCellMerging(cell, line, firstCell) t = nextT firstCell = cell unless firstCell end end # Generate the cells for the task lines of a calendar column. These lines do # not directly belong to the @table object but to an embedded ColumnTable # object. Therefor a single @table column usually has many cells on each # single line. _scenarioIdx_ is the index of the scenario that is reported # in this line. _line_ is the @table line. _t_ is the start date for the # calendar. _sameTimeNextFunc_ is the function that will move the date to # the next cell. def genCalChartTaskCell(query, line, columnDef, t, sameTimeNextFunc) task = line.property # Find out if we have an enclosing resource scope. if line.scopeLine && line.scopeLine.property.is_a?(Resource) resource = line.scopeLine.property else resource = nil end # Get the interval of the task. In case a date is invalid due to a # scheduling problem, we use the full project interval. taskStart = task['start', query.scenarioIdx] taskEnd = task['end', query.scenarioIdx] taskIv = TimeInterval.new(taskStart.nil? ? @project['start'] : taskStart, taskEnd.nil? ? @project['end'] : taskEnd) # We modify the start and end dates to match the cell boundaries. So # we need to make sure we don't modify the original Query but our own # copies. query = query.dup firstCell = nil endDate = query.end while t < endDate # call TjTime::sameTimeNext... function nextT = t.send(sameTimeNextFunc) cellIv = TimeInterval.new(t, nextT) case columnDef.content when 'empty' # Create a new cell cell = newCell(query, line) # We only generate cells will different background colors. when 'load' query.attributeId = 'effort' query.start = t query.end = nextT query.process # Create a new cell cell = newCell(query, line) # To increase readability show empty cells instead of 0.0 values. cell.text = query.to_s if query.to_num != 0.0 else raise "Unknown column content #{column.content}" end cdText = columnDef.cellText.getPattern(query) cell.text = cdText if cdText cell.showTooltipHint = false # Determine cell category (mostly the background color) if cellIv.overlaps?(taskIv) # The cell is either a container or leaf task cell.category = task.container? ? 'calconttask' : 'caltask' elsif !@project.isWorkingTime(cellIv) # The cell is a vacation cell. cell.category = 'offduty' else # The cell is just filled with the background color. cell.category = 'taskcell' end cell.category += line.property.get('index') % 2 == 1 ? '1' : '2' setCustomCellAttributes(cell, columnDef, query) tryCellMerging(cell, line, firstCell) t = nextT firstCell = cell unless firstCell end legend.addCalendarItem('Container Task', 'calconttask1') legend.addCalendarItem('Task', 'caltask1') legend.addCalendarItem('Off duty time', 'offduty') end # Generate the cells for the resource lines of a calendar column. These # lines do not directly belong to the @table object but to an embedded # ColumnTable object. Therefor a single @table column usually has many cells # on each single line. _scenarioIdx_ is the index of the scenario that is # reported in this line. _line_ is the @table line. _t_ is the start date # for the calendar. _sameTimeNextFunc_ is the function that will move the # date to the next cell. def genCalChartResourceCell(query, line, columnDef, t, sameTimeNextFunc) # Find out if we have an enclosing task scope. if line.scopeLine && line.scopeLine.property.is_a?(Task) task = line.scopeLine.property # Get the interval of the task. In case a date is invalid due to a # scheduling problem, we use the full project interval. taskStart = task['start', query.scenarioIdx] taskEnd = task['end', query.scenarioIdx] taskIv = TimeInterval.new(taskStart.nil? ? @project['start'] : taskStart, taskEnd.nil? ? @project['end'] : taskEnd) else task = nil end # We modify the start and end dates to match the cell boundaries. So # we need to make sure we don't modify the original Query but our own # copies. query = query.dup firstCell = nil endDate = query.end while t < endDate # Create a new cell cell = newCell(query, line) # call TjTime::sameTimeNext... function nextT = t.send(sameTimeNextFunc) cellIv = TimeInterval.new(t, nextT) # Get work load for all tasks. query.scopeProperty = nil query.attributeId = 'effort' query.startIdx = @project.dateToIdx(t) query.endIdx = @project.dateToIdx(nextT) query.process workLoad = query.to_num scaledWorkLoad = query.to_s if task # Get work load for the particular task. query.scopeProperty = task query.process workLoadTask = query.to_num scaledWorkLoad = query.to_s else workLoadTask = 0.0 end # Get unassigned work load. query.attributeId = 'freework' query.process freeLoad = query.to_num case columnDef.content when 'empty' # We only generate cells will different background colors. when 'load' # Report the workload of the resource in this time interval. # To increase readability, we don't show 0.0 values. wLoad = task ? workLoadTask : workLoad if wLoad > 0.0 cell.text = scaledWorkLoad end else raise "Unknown column content #{column.content}" end cdText = columnDef.cellText.getPattern(query) cell.text = cdText if cdText # Set the tooltip for the cell. We might delete it again. cell.tooltip = columnDef.tooltip.getPattern(query) || nil cell.showTooltipHint = false # Determine cell category (mostly the background color) cell.category = if task if cellIv.overlaps?(taskIv) if workLoadTask > 0.0 && freeLoad == 0.0 'busy' elsif workLoad == 0.0 && freeLoad == 0.0 cell.tooltip = nil 'offduty' else 'loaded' end else if freeLoad > 0.0 'free' elsif workLoad == 0.0 && freeLoad == 0.0 cell.tooltip = nil 'offduty' else cell.tooltip = nil 'resourcecell' end end else if workLoad > 0.0 && freeLoad == 0.0 'busy' elsif workLoad > 0.0 && freeLoad > 0.0 'loaded' elsif workLoad == 0.0 && freeLoad > 0.0 'free' else cell.tooltip = nil 'offduty' end end cell.category += line.property.get('index') % 2 == 1 ? '1' : '2' setCustomCellAttributes(cell, columnDef, query) tryCellMerging(cell, line, firstCell) t = nextT firstCell = cell unless firstCell end legend.addCalendarItem('Resource is fully loaded', 'busy1') legend.addCalendarItem('Resource is partially loaded', 'loaded1') legend.addCalendarItem('Resource is available', 'free') legend.addCalendarItem('Off duty time', 'offduty') end # This method takes care of often used cell attributes like indentation, # alignment and background color. def setStandardCellAttributes(query, cell, columnDef, attributeType, line) # Determine whether it should be indented if indent(columnDef.id, attributeType) cell.indent = line.indentation end # Determine the cell alignment cell.alignment = alignment(columnDef.id, attributeType) # Set background color if line.property.is_a?(Task) cell.category = 'taskcell' cell.category += line.property.get('index') % 2 == 1 ? '1' : '2' elsif line.property.is_a?(Resource) cell.category = 'resourcecell' cell.category += line.property.get('index') % 2 == 1 ? '1' : '2' elsif line.property.is_a?(Account) setAccountCellBgColor(query, line, cell) end # Set column width cell.width = columnDef.width if columnDef.width end def setCustomCellAttributes(cell, columnDef, query) # Replace the cell background color if the user has requested a custom # color. cellColor = columnDef.cellColor.getPattern(query) cell.cellColor = cellColor if cellColor # Replace the font color setting if the user has requested a custom # color. fontColor = columnDef.fontColor.getPattern(query) cell.fontColor = fontColor if fontColor # Replace the default cell alignment if the user has requested a custom # alignment. hAlign = columnDef.hAlign.getPattern(query) cell.alignment = hAlign if hAlign # Register the custom tooltip if the user has requested one. cdTooltip = columnDef.tooltip.getPattern(query) cell.tooltip = cdTooltip if cdTooltip end def setScenarioSettings(cell, scenarioIdx, scenarioSpecific) # Check if we are dealing with multiple scenarios. if a('scenarios').length > 1 # Check if the attribute is not scenario specific unless scenarioSpecific if scenarioIdx == a('scenarios').first # Use a somewhat bigger font. cell.fontSize = 15 else # And hide the cells for all but the first scenario. cell.hidden = true return false end cell.rows = a('scenarios').length end end true end # Create a new ReportTableCell object and initialize some common values. def newCell(query, line) property = line.property cell = ReportTableCell.new(line, query) # Cells for containers should be using bold font face. cell.bold = true if property.container? && line.bold cell end # Determine the indentation for this line. def setIndent(line, propertyRoot, treeMode) property = line.property scopeLine = line.scopeLine level = property.level - (propertyRoot ? propertyRoot.level : 0) # We indent at least as much as the scopeline + 1, if we have a scope. line.indentation = scopeLine.indentation + 1 if scopeLine # In tree mode we indent according to the level. if treeMode line.indentation += level line.bold = true end end def setAccountCellBgColor(query, line, cell) if query.costAccount && (query.property.isChildOf?(query.costAccount) || query.costAccount == query.property) prefix = 'cost' elsif query.revenueAccount && (query.property.isChildOf?(query.revenueAccount) || query.revenueAccount == query.property) prefix = 'revenue' else prefix = '' end cell.category = prefix + 'accountcell' + (line.property.get('index') % 2 == 1 ? '1' : '2') end # Make sure we have a valid cell text. If not, this is the result of an # error. This could happen after scheduling errors. def checkCellText(cell) unless cell.text cell.text = '' cell.fontColor = '#FF0000' end end # Try to merge equal cells without text to multi-column cells. def tryCellMerging(cell, line, firstCell) if cell.text == '' && firstCell && (c = line.last(1)) && c == cell cell.hidden = true c.columns += 1 end end end end taskjuggler-3.5.0/lib/taskjuggler/reports/TimeSheetReport.rb0000644000175000017500000002552712614413013023555 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TimeSheetReport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' class TaskJuggler # Utility class for the intermediate TimeSheetReport format. class TSResourceRecord attr_reader :resource, :tasks attr_accessor :vacationHours, :vacationPercent def initialize(resource) @resource = resource @vacationHours = 0.0 @vacationPercent = 0.0 @tasks = [] end end # Utility class for the intermediate TimeSheetReport format. class TSTaskRecord attr_reader :task, :workDays, :workPercent, :remaining, :endDate def initialize(task, workDays, workPercent, remaining = nil, endDate = nil) @task = task @workDays = workDays @workPercent = workPercent @remaining = remaining @endDate = endDate end end # This specialization of ReportBase implements a template generator for time # sheets. The time sheet is structured using the TJP file syntax. class TimeSheetReport < ReportBase # Create a new object and set some default values. def initialize(report) super(report) @current = [] @future = [] end # In the future we might want to generate other output than TJP synatx. So # we generate an abstract version of the time sheet first. This abstract # version has a TSResourceRecord for each resource and each of these # records holds a TSTaskRecord for each assigned task. def generateIntermediateFormat super @current = collectRecords(a('start'), a('end')) newEnd = a('end') + (a('end').to_i - a('start').to_i) newEnd = @project['end'] if newEnd > @project['end'] @future = collectRecords(a('end'), a('end') + (a('end') - a('start'))) end # Generate a time sheet in TJP syntax format. def to_tjp # This String will hold the result. @file = <<'EOT' # The status headline should be no more than 60 characters and may # not be empty! The status summary is optional and should be no # longer than one or two sentences of plain text. The details section # is also optional has no length limitation. You can use simple # markup in this section. It is recommended that you provide at # least a summary or a details section. # See http://www.taskjuggler.org/tj3/manual/timesheet.html for details. # # --------8<--------8<-------- EOT # Iterate over all the resources that we have TSResourceRecords for. @current.each do |rr| resource = rr.resource # Generate the time sheet header @file << "timesheet #{resource.fullId} " + "#{a('start')} - #{a('end')} {\n\n" @file << " # Vacation time: #{rr.vacationPercent}%\n\n" if rr.tasks.empty? # If there were no assignments, just write a comment. @file << " # There were no planned tasks assignments for " + "this period!\n\n" else rr.tasks.each do |tr| task = tr.task @file << " # Task: #{task.name}\n" @file << " task #{task.fullId} {\n" #@file << " work #{tr.workDays * # @project['dailyworkinghours']}h\n" @file << " work #{tr.workPercent}%\n" if tr.remaining @file << " remaining #{tr.remaining}d\n" else @file << " end #{tr.endDate}\n" end c = tr.workDays > 1.0 ? '' : '# ' @file << " #{c}status green \"Your headline here!\" {\n" + " # summary -8<-\n" + " # A summary text\n" + " # ->8-\n" + " # details -8<-\n" + " # Some more details\n" + " # ->8-\n" + " # flags ...\n" + " #{c}}\n" @file << " }\n\n" end end @file << <<'EOT' # If you had unplanned tasks, uncomment and fill out the # following lines: # newtask new.task.id "A task title" { # work X% # remaining Y.Yd # status green "Your headline here!" { # summary -8<- # A summary text # ->8- # details -8<- # Some more details # ->8- # flags ... # } # } # You can use the following section to report personal notes. # status green "Your headline here!" { # summary -8<- # A summary text # ->8- # details -8<- # Some more details # ->8- # } EOT future = @future[@future.index { |r| r.resource == resource }] if future && !future.tasks.empty? @file << <<'EOT' # # Your upcoming tasks for the next period # Please check them carefully and discuss any necessary # changes with your manager or project manager! # EOT future.tasks.each do |taskRecord| @file << " # #{taskRecord.task.name}: #{taskRecord.workPercent}%\n" end @file << "\n" else @file << "\n # You have no future assignments for this project!\n" end @file << "}\n# -------->8-------->8--------\n\n" end @file end private def collectRecords(from, to) # Prepare the resource list. resourceList = PropertyList.new(@project.resources) resourceList.setSorting(@report.get('sortResources')) resourceList = filterResourceList(resourceList, nil, @report.get('hideResource'), @report.get('rollupResource'), @report.get('openNodes')) # Prepare a template for the Query we will use to get all the data. scenarioIdx = a('scenarios')[0] queryAttrs = { 'project' => @project, 'scopeProperty' => nil, 'scenarioIdx' => scenarioIdx, 'loadUnit' => :days, 'numberFormat' => RealFormat.new([ '-', '', '', '.', 1]), 'timeFormat' => "%Y-%m-%d", 'currencyFormat' => a('currencyFormat'), 'start' => from, 'end' => to, 'hideJournalEntry' => a('hideJournalEntry'), 'journalMode' => a('journalMode'), 'journalAttributes' => a('journalAttributes'), 'sortJournalEntries' => a('sortJournalEntries'), 'costAccount' => a('costaccount'), 'revenueAccount' => a('revenueaccount') } resourceList.query = Query.new(queryAttrs) resourceList.sort! # Prepare the task list. taskList = PropertyList.new(@project.tasks) taskList.setSorting(@report.get('sortTasks')) taskList = filterTaskList(taskList, nil, @report.get('hideTask'), @report.get('rollupTask'), @report.get('openNodes')) records = [] resourceList.each do |resource| # Time sheets only make sense for leaf resources that actuall do work. next unless resource.leaf? # Create a new TSResourceRecord for the resource. records << (resourceRecord = TSResourceRecord.new(resource)) # Calculate the average working days per week (usually 5) weeklyWorkingDays = @project.weeklyWorkingDays # Calculate the number of weeks in the report weeksToReport = (to - from) / (60 * 60 * 24 * 7) # Get the vacation days for the resource for this period. queryAttrs['property'] = resource query = Query.new(queryAttrs) query.attributeId = 'timeoffdays' query.start = from query.end = to query.process resourceRecord.vacationHours = query.to_s resourceRecord.vacationPercent = (query.to_num / (weeksToReport * weeklyWorkingDays)) * 100.0 # Now we have to find all the task that the resource is allocated to # during the report period. assignedTaskList = filterTaskList(taskList, resource, a('hideTask'), a('rollupTask'), a('openNodes')) queryAttrs['scopeProperty'] = resource assignedTaskList.query = Query.new(queryAttrs) assignedTaskList.sort! assignedTaskList.each do |task| # Time sheet task records only make sense for leaf tasks. reportIv = TimeInterval.new(from, to) taskIv = TimeInterval.new(task['start', scenarioIdx], task['end', scenarioIdx]) next if !task.leaf? || !reportIv.overlaps?(taskIv) queryAttrs['property'] = task query = Query.new(queryAttrs) # Get the allocated effort for the task for this period. query.attributeId = 'effort' query.start = from query.end = to query.process # The Query.to_num of an effort always returns the value in days. workDays = query.to_num workPercent = (workDays / (weeksToReport * weeklyWorkingDays)) * 100.0 remaining = endDate = nil if task['effort', scenarioIdx] > 0 # The task is an effort based task. # Get the remaining effort for this task. query.start = to query.end = task['end', scenarioIdx] query.loadUnit = :days query.process remaining = query.to_s else # The task is a duration task. # Get the planned task end date. endDate = task['end', scenarioIdx] end # Put all data into a TSTaskRecord and push it into the resource # record. resourceRecord.tasks << TSTaskRecord.new(task, workDays, workPercent, remaining, endDate) end end records end # This utility function is used to indent multi-line attributes. All # attributes should be filtered through this function. Attributes that # contain line breaks will be indented properly. In addition to the # indentation specified by _indent_ all but the first line will be indented # after the first word of the first line. The text may not end with a line # break. def indentBlock(text, indent) out = '' firstSpace = 0 text.length.times do |i| if firstSpace == 0 && text[i] == ?\ # There must be a space after ? firstSpace = i end out << text[i] if text[i] == ?\n out += ' ' * (indent + firstSpace) end end out end end end taskjuggler-3.5.0/lib/taskjuggler/reports/TextReport.rb0000644000175000017500000000532612614413013022605 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TextReport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' class TaskJuggler # This is the most basic type of report. It only contains 5 RichText elements. # It's got a header and footer and a central text with margin elements left # and right. class TextReport < ReportBase attr_accessor :header, :left, :center, :right, :footer def initialize(report) super @lWidth = @cWidth = @rWidth = 0 @lPadding = @cPadding = @rPadding = 0 end def generateIntermediateFormat super # A width of 0 means, the columns flexible. if a('center') if a('left') && a('right') @lWidth = @rWidth = 20 @cWidth = 60 @lPadding = @cPadding = 2 elsif a('left') && !a('right') @lWidth = 25 @cWidth = 75 @lPadding = 2 elsif !a('left') && a('right') @cWidth = 75 @rWidth = 25 @cPadding = 2 else @cWidth = 100 end else if a('left') && a('right') @lWidth = @rWidth = 50 @lPadding = 2 elsif a('left') && !a('right') @lWidth = 100 elsif !a('left') && a('right') @rWidth = 100 end end end def to_html html = [] html << rt_to_html('header') if a('left') || a('center') || a('right') html << (table = XMLElement.new('table', 'class' => 'tj_text_page', 'cellspacing' => '0')) table << (row = XMLElement.new('tr', 'class' => 'tj_text_row')) %w( left center right).each do |i| width = instance_variable_get('@' + i[0].chr + 'Width') padding = instance_variable_get('@' + i[0].chr + 'Padding') if a(i) row << (col = XMLElement.new('td', 'class' => "tj_column_#{i}")) style = '' style += "width:#{width}%; " if width > 0 style += "padding-right:#{padding}%; " if padding > 0 col['style'] = style col << rt_to_html(i) end end end html << rt_to_html('footer') html end def to_csv @report.warning('text_report_no_csv', "textreport '#{@report.fullId}' cannot be converted " + "into CSV format") nil end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ChartPlotter.rb0000644000175000017500000003454612614413013023106 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ChartPlotter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Painter' class TaskJuggler class ChartPlotterError < RuntimeError end class ChartPlotter def initialize(width, height, data) # +------------------------------------------------ # | ^ # | topMargin | legendGap # | v <-> # | | -x- foo # |<-leftMargin->| -x- bar # | | <-legend # | | Width----> # | +------------ # | ^ <-rightMargin-> # | bottomMargin| # | v # +------------------------------------------------ # <-----------------canvasWidth--------------------> # The width of the canvas area @width = width # The height of the canvas area @height = height # The raw data to plot as loaded from the CSV file. @data = data # The margins between the graph plotting area and the canvas borders. @topMargin = 30 @bottomMargin = 30 @leftMargin = 70 @rightMargin = (@width * 0.382).to_i @legendGap = 20 @markerWidth = 20 @markerX = @width - @rightMargin + @legendGap @markerGap = 5 @labelX = @markerX + @markerWidth + @markerGap @labelHeight = 24 # The location of the 0/0 point of the graph plotter. @x0 = @leftMargin @y0 = @height - @bottomMargin @labels = [] @yData = [] @xData = nil @dataType = nil @xMinDate = nil @xMaxDate = nil @yMinDate = nil @yMaxDate = nil @yMinVal = nil @yMaxVal = nil end # Create the chart as Painter object. def generate analyzeData calcChartGeometry @painter = Painter.new(@width, @height) do |pa| drawGrid(pa) 0.upto(@yData.length - 1) do |ci| # Compute a unique and distinguishable color for each data set. We # primarily use the hue value of the HSV color space for this. It # has 6 main colors each 60 degrees apart from each other. After the # first 360 round, we shift the angle by 60 / round so we get a # different color set than in the previous round. Additionally, the # saturation is decreased with each data set. color = Painter::Color.new( (60 * (ci % 6) + (60 / (1 + ci / 6))) % 360, 255 - (ci / 8), 230, :hsv) drawDataGraph(pa, ci, color) drawLegendEntry(pa, ci, color) end end end def to_svg @painter.to_svg end private def analyzeData # Convert the @data from a line list into a column list. Each element of # the list is an Array for the other dimension. columns = [] ri = 0 @data.each do |row| ci = 0 row.each do |col| columns << [] if ri == 0 if ci >= columns.length error("Row #{ri} contains more elements than the header row") end columns[ci][ri] = col ci += 1 end ri += 1 end header = true line = 1 columns[0].each do |date| if header unless date == "Date" error("First column must have a 'Date' header instead of '#{date}'") end header = false else unless date.is_a?(TjTime) error("First colum (#{date}) of line #{line} must be all dates") end @xMinDate = date if @xMinDate.nil? || date < @xMinDate @xMaxDate = date if @xMaxDate.nil? || date > @xMaxDate end line += 1 end unless @xMinDate && @xMaxDate error("First column does not contain valid dates.") end # Add the xData values. @xData = columns[0][1..-1] # Now eleminate columns that contain invalid data. 1.upto(columns.length - 1) do |colIdx| col = columns[colIdx] badCol = false col[1..-1].each do |cell| if cell if cell.is_a?(TjTime) if @dataType && @dataType != :date error("Column #{colIdx} contains non-date (#{cell}). " + "The columns will be ignored.") badCol = true break else @dataType = :date end @yMinDate = cell if @yMinDate.nil? || cell < @yMinDate @yMaxDate = cell if @yMaxDate.nil? || cell > @yMaxDate elsif cell.is_a?(Fixnum) || cell.is_a?(Float) if @dataType && @dataType != :number error("Column #{colIdx} contains non-number (#{cell}). " + "The columns will be ignored.") badCol = true break else @dataType = :number end @yMinVal = cell if @yMinVal.nil? || cell < @yMinVal @yMaxVal = cell if @yMaxVal.nil? || cell > @yMaxVal else error("Column #{colIdx} contains invalid data (#{cell}). " + "The columns will be ignored.") badCol = true break end else # Ignore missing values next unless cell end end # Store the header of the column. It will be used as label. @labels << col[0] # Add the column values as another entry into the yData list. @yData << col[1..-1] unless badCol end if @dataType.nil? || @yData.empty? error("Columns don't contain any valid dates.") end end def calcChartGeometry # The size of the X-axis in pixels xAxisPixels = @width - (@rightMargin + @leftMargin) fm = Painter::FontMetrics.new # Width of the date label in pixels @dateLabelWidth = fm.width('LiberationSans', 10.0, '2000-01-01') # Height of the date label in pixels @labelHeight = fm.height('LiberationSans', 10.0) # Distance between 2 labels in pixels labelPadding = 10 # The number of labels that fit underneath the X-axis @noXLabels = (xAxisPixels / (@dateLabelWidth + labelPadding)).floor # The number of labels that fit along the Y-axis yAxisPixels = @height - (@topMargin + @bottomMargin) @noYLabels = (yAxisPixels / (@labelHeight + labelPadding)).floor @noYLabels = 10 if @noYLabels > 10 # Set min X date to midnight time. @xMinDate = @xMinDate.midnight # Ensure that we have at least a @noXLabels days long interval. minInterval = 60 * 60 * 24 * @noXLabels @xMaxDate = @xMinDate + minInterval if @xMaxDate - @xMinDate < minInterval case @dataType when :date # Set min Y date to midnight time. @yMinDate = @yMinDate.midnight # Ensure that we have at least a @noYLabels days long interval. minInterval = 60 * 60 * 24 * @noYLabels if @yMaxDate - @yMinDate < minInterval @yMaxDate = @yMinDate + minInterval end when :number # If all Y values are the same, we ensure that the Y-axis starts at 0 # to provide a sense of scale. @yMinVal = 0 if @yMinVal == @yMaxVal # Ensure that Y-axis has at least a range of @noYLabels if @yMaxVal - @yMinVal < @noYLabels @yMaxVal = @yMinVal + @noYLabels end else raise "Unsupported dataType: #{@dataType}" end end def xLabels(p) # The time difference between two labels. labelInterval = (@xMaxDate - @xMinDate) / @noXLabels # We want the first label to show left-aligned with the Y-axis. Calc the # date for the first label. date = @xMinDate + labelInterval / 2 p.group(:font_family => 'LiberationSans, Arial', :font_size => 10.0, :stroke => p.color(:black), :stroke_width => 1, :fill => p.color(:black)) do |gp| @noXLabels.times do |i| x = xDate2c(date) gp.text(x - @dateLabelWidth / 2, y2c(-5 - @labelHeight), date.to_s('%Y-%m-%d'), :stroke_width => 0) #gp.rect(x - @dateLabelWidth / 2, y2c(-5 - @labelHeight), # @dateLabelWidth, @labelHeight, :fill => gp.color(:white)) gp.line(x, y2c(0), x, y2c(-4)) date += labelInterval end end end def yLabels(p) case @dataType when :date return unless @yMinDate && @yMaxDate yInterval = @yMaxDate - @yMinDate # The time difference between two labels. labelInterval = yInterval / @noYLabels date = @yMinDate + labelInterval / 2 p.group(:font_family => 'LiberationSans, Arial', :font_size => 10.0, :stroke => p.color(:black), :stroke_width => 1, :fill => p.color(:black)) do |gp| @noYLabels.times do |i| y = yDate2c(date) gp.text(0, y + @labelHeight / 2 - 2, date.to_s('%Y-%m-%d'), :stroke_width => 0) gp.line(x2c(-4), y, @width - @rightMargin, y) date += labelInterval end end when :number return unless @yMinVal && @yMaxVal yInterval = (@yMaxVal - @yMinVal).to_f fm = Painter::FontMetrics.new # The value difference between two labels. labelInterval = yInterval / @noYLabels # We'd like to have the labels to only show number starting with # single most significant digit that read 1, 2 or 5. If necessary, we # increase the labelInterval to the next matching number and reduce # the number of y labels accordingly. factor = 10 ** Math.log10(labelInterval).floor msd = (labelInterval / factor).ceil if msd == 3 || msd == 4 msd = 5 elsif msd > 5 msd = 10 end labelInterval = msd * factor @noYLabels = ((@yMaxVal - @yMinVal) / labelInterval).floor val = @yMinVal + labelInterval p.group(:font_family => 'LiberationSans, Arial', :font_size => 10.0, :stroke => p.color(:black), :stroke_width => 1, :fill => p.color(:black)) do |gp| @noYLabels.times do |i| y = yNum2c(val) labelText = val.to_s labelWidth = fm.width('LiberationSans', 10.0, labelText) gp.text(@leftMargin - 7 - labelWidth, y + @labelHeight / 2 - 3, labelText, :stroke_width => 0) gp.line(x2c(-4), y, @width - @rightMargin, y) val += labelInterval end end else raise "Unsupported dataType #{@dataType}" end end # Convert a chart X coordinate to a canvas X coordinate. def x2c(x) @x0 + x end # Convert a chart Y coordinate to a canvas Y coordinate. def y2c(y) @y0 - y end # Convert a date to a chart X coordinate. def xDate2c(date) x2c(((date - @xMinDate) * (@width - (@leftMargin + @rightMargin))) / (@xMaxDate - @xMinDate)) end # Convert a Y data date to a chart Y coordinate. def yDate2c(date) y2c(((date - @yMinDate) * (@height - (@topMargin + @bottomMargin))) / (@yMaxDate - @yMinDate)) end # Convert a Y data value to a chart Y coordinate. def yNum2c(number) y2c(((number - @yMinVal) * (@height - (@topMargin + @bottomMargin))) / (@yMaxVal - @yMinVal)) end def drawGrid(painter) painter.group(:stroke => painter.color(:black), :font_size => 11) do |p| p.line(x2c(0), y2c(0), x2c(@width - (@leftMargin + @rightMargin)), y2c(0)) p.line(x2c(0), y2c(0), x2c(0), y2c(@height - (@topMargin + @bottomMargin))) yLabels(p) xLabels(p) end end def drawDataGraph(painter, ci, color) values = @yData[ci] painter.group(:stroke_width => 3, :stroke => color, :fill => color) do |p| lastX = lastY = nil # Plot markers for each x/y data pair of the set and connect the # dots with lines. If a y value is nil, the line will be # interrupted. values.length.times do |i| if values[i] xc = xDate2c(@xData[i]) if values[i].is_a?(TjTime) yc = yDate2c(values[i]) else yc = yNum2c(values[i]) end p.line(lastX, lastY, xc, yc) if lastY setMarker(p, ci, xc, yc) lastX = xc lastY = yc end end end end def drawLegendEntry(painter, ci, color) painter.group(:stroke_width => 3, :stroke => color, :fill => color, :font_size => 11) do |p| # Add the marker to the legend labelY = @topMargin + @labelHeight / 2 + ci * (@labelHeight + 4) markerY = labelY + (@labelHeight + 4) / 2 setMarker(p, ci, @markerX + @markerWidth / 2, markerY) p.line(@markerX, markerY, @markerX + @markerWidth, markerY) p.text(@labelX, labelY + @labelHeight, @labels[ci], :stroke => p.color(:black), :stroke_width => 0, :fill => p.color(:black)) end end def setMarker(p, type, x, y) r = 4 case (type / 5) % 5 when 0 # Diamond points = [ [ x - r, y ], [ x, y + r ], [ x + r, y ], [ x, y - r ], [ x - r, y ] ] p.polyline(points) when 1 # Square rr = (r / Math.sqrt(2.0)).to_i p.rect(x - rr, y - rr, 2 * rr, 2 * rr) when 2 # Triangle Down points = [ [ x - r, y - r ], [ x, y + r ], [ x + r, y - r ], [ x - r, y - r ] ] p.polyline(points) when 3 # Triangle Up points = [ [ x - r, y + r ], [ x, y - r ], [ x + r, y + r ], [ x - r, y + r ] ] p.polyline(points) else p.circle(x, y, r) end end def error(msg) raise ChartPlotterError, msg end end end taskjuggler-3.5.0/lib/taskjuggler/reports/StatusSheetReport.rb0000644000175000017500000002031312614413013024126 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = StatusSheetReport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' class TaskJuggler class ManagerStatusRecord attr_reader :resource, :responsibilities def initialize(resource) # The Resource record of the manager @resource = resource # A list of Task objects with their JournalEntry records. Stored as # Array of ManagerResponsibilities objects. @responsibilities = [] end def sort!(taskList) @responsibilities.sort! do |r1, r2| taskList.itemIndex(r1.task) <=> taskList.itemIndex(r2.task) end @responsibilities.each { |r| r.sort!(taskList) } end end class ManagerResponsibilities attr_reader :task, :journalEntries def initialize(task, journalEntries) @task = task @journalEntries = journalEntries.dup end def sort!(taskList) @journalEntries.sort! do |e1, e2| taskList.itemIndex(e1.property) <=> taskList.itemIndex(e2.property) end end end # This specialization of ReportBase implements a template generator for # status sheets. The status sheet is structured using the TJP file syntax. class StatusSheetReport < ReportBase # Create a new object and set some default values. def initialize(report) super(report) # A list of ManagerStatusRecord objects, one for each manager. @managers = [] end # In the future we might want to generate other output than TJP synatx. So # we generate an abstract version of the status sheet first. def generateIntermediateFormat super # Prepare the resource list. resourceList = PropertyList.new(@project.resources) resourceList.setSorting(@report.get('sortResources')) resourceList = filterResourceList(resourceList, nil, @report.get('hideResource'), @report.get('rollupResource'), @report.get('openNodes')) # Prepare a template for the Query we will use to get all the data. scenarioIdx = a('scenarios')[0] queryAttrs = { 'project' => @project, 'scopeProperty' => nil, 'scenarioIdx' => scenarioIdx, 'loadUnit' => :days, 'numberFormat' => RealFormat.new([ '-', '', '', '.', 1]), 'timeFormat' => "%Y-%m-%d", 'currencyFormat' => a('currencyFormat'), 'start' => a('start'), 'end' => a('end'), 'hideJournalEntry' => a('hideJournalEntry'), 'journalMode' => a('journalMode'), 'journalAttributes' => a('journalAttributes'), 'sortJournalEntries' => a('sortJournalEntries'), 'costAccount' => a('costaccount'), 'revenueAccount' => a('revenueaccount') } resourceList.query = Query.new(queryAttrs) resourceList.sort! # Prepare the task list. taskList = PropertyList.new(@project.tasks) taskList.setSorting(@report.get('sortTasks')) taskList = filterTaskList(taskList, nil, @report.get('hideTask'), @report.get('rollupTask'), @report.get('openNodes')) taskList.sort! resourceList.each do |resource| # Status sheets only make sense for leaf resources. next unless resource.leaf? # Collect a list of tasks that the Resource is responsible for and # don't have a parent task that the Resource is responsible for. topLevelTasks = [] taskList.each do |task| if task['responsible', scenarioIdx].include?(resource) && (task.parent.nil? || !task.parent['responsible', scenarioIdx].include?(resource)) topLevelTasks << task end end next if topLevelTasks.empty? # Store the list of top-level responsibilities. @managers << (manager = ManagerStatusRecord.new(resource)) topLevelTasks.each do |task| # Get a list of all the current Journal entries for this task and # all it's sub tasks. entries = @project['journal']. currentEntriesR(a('end'), task, 0, a('start') + 1, resourceList.query) next if entries.empty? manager.responsibilities << ManagerResponsibilities.new(task, entries) end # Sort the responsibilities list according to the original taskList. manager.sort!(taskList) end end # Generate a time sheet in TJP syntax format. def to_tjp # This String will hold the result. @file = '' # Iterate over all the ManagerStatusRecord objects. @managers.each do |manager| resource = manager.resource @file << "# --------8<--------8<--------\n" # Generate the time sheet header @file << "statussheet #{resource.fullId} " + "#{a('start')} - #{a('end')} {\n\n" if manager.responsibilities.empty? # If there were no assignments, just write a comment. @file << " # This resource is not responsible for any task.\n\n" else manager.responsibilities.each do |responsibility| task = responsibility.task @file << " # Task: #{task.name}\n" responsibility.journalEntries.each do |entry| task = entry.property @file << " task #{task.fullId} {\n" alertLevel = @project['alertLevels'][entry.alertLevel].id @file << " # status #{alertLevel} \"#{entry.headline}\" {\n" @file << " # # Date: #{entry.date}\n" if (tsRecord = entry.timeSheetRecord) @file << " # # " @file << "Work: #{tsRecord.actualWorkPercent.to_i}% " if tsRecord.actualWorkPercent != tsRecord.planWorkPercent @file << "(#{tsRecord.planWorkPercent.to_i}%) " end if tsRecord.remaining @file << " Remaining: #{tsRecord.actualRemaining}d " if tsRecord.actualRemaining != tsRecord.planRemaining @file << "(#{tsRecord.planRemaining}d) " end else @file << " End: " + "#{tsRecord.actualEnd.to_s(a('timeFormat'))} " if tsRecord.actualEnd != tsRecord.planEnd @file << "(#{tsRecord.planEnd.to_s(a('timeFormat'))}) " end end @file << "\n" end @file << " # author #{entry.author.fullId}\n" if entry.author unless entry.flags.empty? @file << " # flags #{entry.flags.join(', ')}\n" end if entry.summary @file << " # summary -8<-\n" + indentBlock(4, entry.summary.richText.inputText) + " # ->8-\n" end if entry.details @file << " # details -8<-\n" + indentBlock(4, entry.details.richText.inputText) + " # ->8-\n" end @file << " # }\n }\n\n" end end end @file << "}\n# -------->8-------->8--------\n\n" end @file end private def indentBlock(indent, text) indentation = ' ' * indent + '# ' buffer = indentation out = '' text.each_utf8_char do |c| unless buffer.empty? out += buffer buffer = '' end out << c buffer = indentation if c == "\n" end # Make sure we always have a trailing line break out += "\n" unless out[-1] == "\n" out end end end taskjuggler-3.5.0/lib/taskjuggler/reports/CSVFile.rb0000644000175000017500000001647112614413013021723 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = CSVFile.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/UTF8String' class TaskJuggler # This is a very lightweight version of the Ruby library class CSV. That class # changed significantly from 1.8 to 1.9 and is a compatibility nightmare. # Hence we use our own class. class CSVFile attr_reader :data # At construction time you need to specify the +data+ container. This is an # Array of Arrays that holds the table. Optionally, you can specify a # +separator+ and a +quote+ string for the CSV file. def initialize(data = nil, separator = ';', quote = '"') @data = data if !separator.nil? && '."'.include?(separator) raise "Illegal separator: #{separator}" end @separator = separator raise "Illegal quote: #{quote}" if quote == '.' @quote = quote end # Use this function to write the table into a CSV file +fileName+. '.' can # be used to write to $stdout. def write(fileName) if (fileName == '.') file = $stdout else file = File.open(fileName, 'w') end file.write(to_s) file.close unless fileName == '.' end # Read the data as Array of Arrays from a CSV formated file +fileName+. In # case '.' is used for the +fileName+ the data is read from $stdin. def read(fileName) if (fileName == '.') file = $stdin else file = File.open(fileName, 'r') end parse(file.read) file.close unless fileName == '.' @data end # Convert the CSV data into a CSV formatted String. def to_s raise "No seperator defined." if @separator.nil? s = '' @data.each do |line| first = true line.each do |field| # Don't output a separator before the first field of the line. if first first = false else s << @separator end s << marshal(field) end s << "\n" end s end # Read the data as Array of Arrays from a CSV formated String +str+. def parse(str) @data = [] state = :startOfRecord fields = field = quoted = nil # Make sure the input is terminated with a record end. str += "\n" unless str[-1] == ?\n # If the user hasn't defined a separator, we try to detect it. @separator = detectSeparator(str) unless @separator line = 1 str.each_utf8_char do |c| #puts "c: #{c} State: #{state}" case state when :startOfRecord # This will store the fields of a record fields = [] state = :startOfField redo when :startOfField field = '' quoted = false if c == @quote # We've found the start of a quoted field. state = :inQuotedField quoted = true elsif c == @separator || c == "\n" # We've found an empty field field = nil state = :fieldEnd redo else # We've found the first character of an unquoted field field << c state = :inUnquotedField end when :inQuotedField # We are processing the content of a quoted field if c == @quote # This could be then end of the field or a quoted quote. state = :quoteInQuotedField else # We've found a normal character of the quoted field field << c line += 1 if c == "\n" end when :quoteInQuotedField # We are processing a quoted quote or the end of a quoted field if c == @quote # We've found a quoted quote field << c state = :inQuotedField elsif c == @separator || c == "\n" state = :fieldEnd redo else raise "Line #{line}: Unexpected character #{c} in cell: #{field}" end when :inUnquotedField # We are processing an unquoted field if c == @separator || c == "\n" # We've found the end of a unquoted field state = :fieldEnd redo else # A normal character of an unquoted field field << c end when :fieldEnd # We've completed processing a field. Add the field to the list of # fields. Convert Fixnums and Floats in native types. fields << unMarshal(field, quoted) if c == "\n" # The field end is an end of a record as well. state = :recordEnd redo else # Get the next field. state = :startOfField end when :recordEnd # We've found the end of a record. Add fields to the @data # structure. @data << fields # Look for a new record. state = :startOfRecord line += 1 else raise "Unknown state #{state}" end end unless state == :startOfRecord if state == :inQuotedField raise "Line #{line}: Unterminated quoted cell: #{field}" else raise "Line #{line}: CSV error in state #{state}: #{field}" end end @data end # Utility function that tries to convert a String into a native type that # is supported by the CSVFile generator. If no native type is found, the # input String _str_ will be returned unmodified. nil is returned as nil. def CSVFile.strToNative(str) if str.nil? nil elsif /^[-+]?\d+$/ =~ str # field is a Fixnum str.to_i elsif /^[-+]?\d*\.?\d+([eE][-+]?\d+)?$/ =~ str # field is a Float str.to_f else # Everything else is kept as String str end end private # This function is used to properly quote @quote and @separation # characters contained in the +field+. def marshal(field) if field.nil? '' elsif field.is_a?(Fixnum) || field.is_a?(Float) || field.is_a?(Bignum) # Numbers don't have to be quoted. field.to_s else # Duplicate quote characters. f = field.gsub(/@quote/, "#{@quote * 2}") # Enclose the field in quote characters @quote + f.to_s + @quote end end # Convert the String _field_ into a native Ruby type. If field was # _quoted_, the result is always the String. def unMarshal(field, quoted) # Quoted Strings and nil are returned verbatim. if quoted || field.nil? field else # Unquoted fields are inspected for special types CSVFile.strToNative(field) end end def detectSeparator(str) # Pick the separator that was found the most. best = nil bestCount = 0 "\t;:".each_char do |c| if best.nil? || str.count(c) > bestCount best = c bestCount = str.count(c) end end return best end end end taskjuggler-3.5.0/lib/taskjuggler/reports/CollisionDetector.rb0000644000175000017500000001270212614413013024106 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = CollisionDetector.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/HTMLGraphics' class TaskJuggler class CollisionDetector include HTMLGraphics def initialize(width, height) @width = width @height = height # The zones are stored as Arrays of line segments. Horizontal blocks are # stored separately from vertical blocks. Blocked segments for a # particular x coordinate are stored in @vLines, for y coordinates in # @hLines. Each entry is an Array of [ start, end ] values that describe # the blocked segments of that particular line. Start and end point are # part of the segment. A listed segment will not be overwritten during # routing. @hLines = Array.new(@height) { |i| i = [] } @vLines = Array.new(@width) { |i| i = [] } end # This function registers an area as don't-cross-zone. The rectangular zone # is described by _x_, _y_, _w_ and _h_. If _horiz_ is true, the zone will # be blocked for horizontal lines, if _vert_ is true the zone will be # blocked for vertical lines. def addBlockedZone(x, y, w, h, horiz, vert) # Clip the input rectangle to fit within the handled area of this router. x = clip(x.to_i, @width - 1) y = clip(y.to_i, @height - 1) w = clip(w.to_i, @width - x) h = clip(h.to_i, @height - y) # We can ignore empty zones. return if w == 0 || h == 0 # Break the rectangle into line segments and add them to the appropriate # line Arrays. if horiz y.upto(y + h - 1) do |i| addSegment(@hLines[i], [ x, x + w - 1 ]) end end if vert x.upto(x + w - 1) do |i| addSegment(@vLines[i], [ y, y + h - 1 ]) end end end # Find out if there is a block at line _pos_ for the start/end coordinates # given by _segment_. If _horizontal_ is true, we are looking for a # horizontal block, otherwise a vertical. def collision?(pos, segment, horizontal) line = (horizontal ? @hLines : @vLines)[pos] # For complex charts, the segment lists can be rather long. We use a # binary search to be fairly efficient. l = 0 u = line.length - 1 while l <= u # Look at the element in the middle between l and u. p = l + ((u - l) / 2).to_i return true if overlaps?(line[p], segment) if segment[0] > line[p][1] # The potential target is above p. Adjust lower bound. l = p + 1 else # The potential target is below p. Adjust upper bound. u = p - 1 end end false end def to_html html = [] # Change this to determine what zones you want to see. if true # Show vertical blocks x = 0 @vLines.each do |line| line.each do |segment| html << lineToHTML(x, segment[0], x, segment[1], 'white') end x += 1 end else # Show horizontal blocks y = 0 @hLines.each do |line| line.each do |segment| html << lineToHTML(segment[0], y, segment[1], y, 'white') end y += 1 end end html end private # Simple utility function to limit _v_ between 0 and _max_. def clip(v, max) v = 0 if v < 0 v = max if v > max v end # This function adds a new segment to the line. In case the new segment # overlaps with or directly attaches to existing segments, these segments # are merged into a single segment. def addSegment(line, newSegment) # Search for overlaping or directly attaching segments in the list. i = 0 while (i < line.length) segment = line[i] if mergeable?(newSegment, segment) # Merge exiting segment into new one merge(newSegment, segment) # Remove the old one from the list and restart with the newly created # one at the same position. line.delete_at(i) next elsif segment[0] > newSegment[1] # Segments are stored in ascending order. If the next segment starts # with a larger value, we insert the new segment before the larger # one. line.insert(i, newSegment) return end i += 1 end # Append new segment line << newSegment end # Return true if the two segments described by _s1_ and _s2_ overlap each # other. A segment is a [ start, end ] Array. The two points are part of the # segment. def overlaps?(s1, s2) (s1[0] <= s2[0] && s2[0] <= s1[1]) || (s2[0] <= s1[0] && s1[0] <= s2[1]) end # Return true if the two segments described by _s1_ and _s2_ overlap each # other or are directly attached to each other. def mergeable?(s1, s2) overlaps?(s1, s2) || (s1[1] + 1 == s2[0]) || (s2[1] + 1 == s1[0]) end # Merge the two segments described by _dst_ and _src_ into _dst_. def merge(dst, seg) dst[0] = seg[0] if seg[0] < dst[0] dst[1] = seg[1] if seg[1] > dst[1] end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttHeaderScaleItem.rb0000644000175000017500000000214312614413013024434 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttHeaderScaleItem.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This class is a storate container for all data related to a scale step of a # GanttChart header. class GanttHeaderScaleItem attr_reader :label, :pos, :width def initialize(label, x, y, width, height) @label = label @x = x @y = y @width = width @height = height end def to_html div = XMLElement.new('div', 'class' => 'tabhead', 'style' => "font-weight:bold; position:absolute; " + "left:#{@x}px; top:#{@y}px; width:#{@width}px; height:#{@height}px; ") div << (div1 = XMLElement.new('div', 'style' => 'padding:3px; ')) div1 << XMLText.new("#{label}") div end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttLoadStack.rb0000644000175000017500000000707012614413013023326 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttLoadStack.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/HTMLGraphics' class TaskJuggler # The GanttLoadStack is a simple stack diagram that shows the relative shares # of the values. The stack is always normed to the line height. class GanttLoadStack include HTMLGraphics # Create a GanttLoadStack object based on the following information: _line_ # is a reference to the GanttLine. _x_ is the left edge in chart coordinates # and _w_ is the stack width. _values_ are the values to be displayed and # _categories_ determines the color for each of the values. def initialize(line, x, w, values, categories) @line = line @lineHeight = line.height @x = x @y = @line.y @w = w <= 0 ? 1 : w @drawFrame = false if values.length != categories.length raise "Values and categories must have the same number of entries!" end @categories = categories i = 0 @categories.each do |cat| if cat.nil? && values[i] > 0 @drawFrame = true break end i += 1 end # Convert the values to chart Y coordinates and store them in yLevels. sum = 0 values.each { |v| sum += v } # If the sum is 0, all yLevels values must be 0 as well. if sum == 0 @yLevels = nil @drawFrame = true else @yLevels = [] values.each do |v| # We leave 1 pixel to the top and bottom of the line and need 1 pixel # for the frame. @yLevels << (@lineHeight - 4) * v / sum end end end def addBlockedZones(router) # Horizontal block router.addZone(@x - 2, @y, @w + 4, @lineHeight, true, false) end # Convert the abstact representation of the GanttLoadStack into HTML # elements. def to_html # Draw nothing if all values are 0. return nil unless @yLevels html = [] # Draw a background rectable to create a frame. In case the frame is not # fully filled by the stack, we need to draw a real frame to keep the # background. if @drawFrame # Top frame line html << @line.lineToHTML(@x, 1, @x + @w - 1, 1, 'loadstackframe') # Bottom frame line html << @line.lineToHTML(@x, @lineHeight - 2, @x + @w - 1, @lineHeight - 2, 'loadstackframe') # Left frame line html << @line.lineToHTML(@x, 1, @x, @lineHeight - 2, 'loadstackframe') # Right frame line html << @line.lineToHTML(@x + @w - 1, 1, @x + @w - 1, @lineHeight - 2, 'loadstackframe') else html << @line.rectToHTML(@x, 1, @w, @lineHeight - 2, 'loadstackframe') end yPos = 2 # Than draw the slighly narrower bars as a pile ontop of it. (@yLevels.length - 1).downto(0) do |i| next if @yLevels[i] <= 0 if @categories[i] html << @line.rectToHTML(@x + 1, yPos.to_i, @w - 2, (yPos + @yLevels[i]).to_i - yPos.to_i, @categories[i]) end yPos += @yLevels[i] end html end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttContainer.rb0000644000175000017500000000604112614413013023400 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttContainer.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/HTMLGraphics' class TaskJuggler # The GanttContainer represents a container task (task with sub-tasks). class GanttContainer include HTMLGraphics # The size of the bars in pixels from center to top/bottom. @@size = 5 # Create a GanttContainer object based on the following information: _line_ # is a reference to the GanttLine. _xStart_ is the left edge of the task in # chart coordinates. _xEnd_ is the right edge. The container extends over # the edges due to the shape of the jags. def initialize(lineHeight, xStart, xEnd, y) @lineHeight = lineHeight @start = xStart @end = xEnd @y = y end # Return the point [ x, y ] where task start dependency lines should start # from. def startDepLineStart [ @start, @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task start dependency lines should end at. def startDepLineEnd [ @start - @@size, @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task end dependency lines should start # from. def endDepLineStart [ @end + @@size, @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task end dependency lines should end at. def endDepLineEnd [ @end, @y + @lineHeight / 2 ] end def addBlockedZones(router) # Horizontal block router.addZone(@start - @@size, @y + (@lineHeight / 2) - @@size - 2, @end - @start + 1 + 2 * @@size, 2 * @@size + 5, true, false) # Block for arrowhead. router.addZone(@start - @@size - 9, @y + (@lineHeight / 2) - 7, 10, 15, true, true) # Vertical block for end cap router.addZone(@start - @@size - 2, @y, 2 * @@size + 5, @lineHeight, false, true) router.addZone(@end - @@size - 2, @y, 2 * @@size + 5, @lineHeight, false, true) end # Convert the abstact representation of the GanttContainer into HTML # elements. def to_html xStart = @start.to_i yCenter = (@lineHeight / 2).to_i width = @end.to_i - @start.to_i + 1 html = [] # Invisible trigger frame for tooltips. html << rectToHTML(xStart - @@size, 0, width + 2 * @@size, @lineHeight, 'tj_gantt_frame') # The bar html << rectToHTML(xStart - @@size, yCenter - @@size, width + 2 * @@size, @@size, 'containerbar') # The left jag html << jagToHTML(xStart, yCenter) # The right jag html << jagToHTML(xStart + width, yCenter) html end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ICalReport.rb0000644000175000017500000001234612614413013022471 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ICalReport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' require 'taskjuggler/ICalendar' class TaskJuggler # This Report derivative generates iCalendar files. class ICalReport < ReportBase # Create a new ICalReport with _report_ as description. def initialize(report) super end # Generate an intermediate version of the report data. def generateIntermediateFormat super # Prepare the task list. @taskList = PropertyList.new(@project.tasks) @taskList.setSorting(a('sortTasks')) @taskList = filterTaskList(@taskList, nil, a('hideTask'), a('rollupTask'), a('openNodes')) @taskList.sort! # Prepare the resource list. This is not yet used. @resourceList = PropertyList.new(@project.resources) @resourceList.setSorting(a('sortResources')) @resourceList = filterResourceList(@resourceList, nil, a('hideResource'), a('rollupResource'), a('openNodes')) @resourceList.sort! @query = @report.project.reportContexts.last.query.dup @ical = ICalendar.new("#{AppConfig.packageName}-#{@project['projectid']}") # Use the project start date of the current date (whichever is earlier) # for the calendar creation date. @ical.creationDate = [ @report.project['start'], TjTime.new ].min # Use the project 'now' date a last modification date. @ical.lastModified = @report.project['now'] # We only care about the first requested scenario. scenarioIdx = a('scenarios').first uidMap = {} @taskList.each do |task| todo = ICalendar::Todo.new( @ical, "#{task['projectid', scenarioIdx]}-#{task.fullId}", task.name, task['start', scenarioIdx], task['end', scenarioIdx]) # Save the ical UID of this TODO. uidMap[task] = todo.uid @query.property = task @query.attributeId = 'complete' @query.scenarioIdx = scenarioIdx @query.process todo.percentComplete = @query.to_num.to_i # We must convert the TJ priority range (1 - 1000) to iCalendar range # (0 - 9). todo.priority = (task['priority', scenarioIdx] - 1) / 100 # If there is a parent task and it's known already, we set the # relation in the TODO component. if task.parent && uidMap[task.parent] todo.relatedTo = uidMap[task.parent] end # If we have a task note, use this for the DESCRIPTION property. if (note = task.get('note')) note = note.to_s todo.description = note end # Check if we have a responsible resource with an email address. Since # ICalendar only knows one organizer we ignore all but the first. organizer = nil if !(responsible = task['responsible', scenarioIdx]).empty? && @resourceList.include?(organizer = responsible[0]) && organizer.get('email') todo.setOrganizer(organizer.name, organizer.get('email')) end # Set the assigned resources as attendees. attendees = [] task['assignedresources', scenarioIdx].each do |resource| next unless @resourceList.include?(resource) && resource.get('email') attendees << resource todo.addAttendee(resource.name, resource.get('email')) end # Generate an additional VEVENT entry for all leaf tasks that aren't # milestones. if task.leaf? && !task['milestone', scenarioIdx] event = ICalendar::Event.new( @ical, "#{task['projectid', scenarioIdx]}-#{task.fullId}", task.name, task['start', scenarioIdx], task['end', scenarioIdx]) event.description = note if note if organizer event.setOrganizer(organizer.name, organizer.get('email')) end attendees.each do |attendee| event.addAttendee(attendee.name, attendee.get('email')) end end # Generate VJOURNAL entries for all the journal entries of this task. @report.project['journal']. entriesByTask(task, a('start'), a('end'), a('hideJournalEntry')).each do |entry| journal = ICalendar::Journal.new( @ical, "#{task['projectid', scenarioIdx]}-#{task.fullId}", entry.headline, entry.date) journal.relatedTo = uidMap[task] journal.description = entry.summary.to_s + entry.details.to_s # Set the author of the journal entry as organizer. if (author = entry.author) && @resourceList.include?(author) && author.get('email') journal.setOrganizer(author.name, author.get('email')) end end end end # Convert the intermediate format into a DOS formated String. def to_iCal @ical.to_s end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttChart.rb0000644000175000017500000003017312614413013022522 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttChart.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/GanttHeader' require 'taskjuggler/reports/GanttLine' require 'taskjuggler/reports/GanttRouter' require 'taskjuggler/reports/HTMLGraphics' class TaskJuggler # This class represents an abstract (output format independent) Gantt chart. # It provides generator functions that can transform the abstract form into # formats such as HTML or SVG. # The appearance of the chart depend on 3 variable: the report period, # the geometrical width and the scale. The report period is always provided by # the user. In addition the width _or_ the scale can be provided. The # non-provided value will then be calculated. So after the object has been # created, the user must call generateByWidth or generateByResolution. class GanttChart # The height in pixels of a horizontal scrollbar on an HTML page. This # value should be large enough to work for all browsers. SCROLLBARHEIGHT = 20 include HTMLGraphics attr_reader :start, :end, :now, :weekStartsMonday, :header, :width, :scale, :scales, :table attr_writer :viewWidth # Create the GanttChart object, but don't do much right now. We still need # more information about the chart before we can actually generate it. _now_ # is the date that should be used as current date. _weekStartsMonday_ is # true if the weeks should start on Mondays instead of Sundays. _table_ is a # reference to the TableReport that the chart is part of. def initialize(now, weekStartsMonday, columnDef, table = nil) # The start and end dates of the reported interval. @start = nil @end = nil @now = now @columnDef = columnDef @table = table # This defines the possible horizontal scales that the Gantt chart can # have. The scales differ in their resolution and the amount of detail # that is displayed. A scale is defined by its name. The _name_ must be # unique and can be used to select the scale. The _stepSize_ defines the # width of a scale step in pixels. The _stepsToFunc_ is a TjTime method # that determines the number of steps between 2 dates. _minTimeOff_ # defines the minimum required length of an time-off interval that is # displayed in this scale. @@scales = [ { 'name' => 'hour', 'stepSize' => 20, 'stepsToFunc' => :hoursTo, 'minTimeOff' => 5 * 60 }, { 'name' => 'day', 'stepSize' => 20, 'stepsToFunc' => :daysTo, 'minTimeOff' => 6 * 60 * 60 }, { 'name' => 'week', 'stepSize' => 20, 'stepsToFunc' => :weeksTo, 'minTimeOff' => 24 * 60 * 60 }, { 'name' => 'month', 'stepSize' => 35, 'stepsToFunc' => :monthsTo, 'minTimeOff' => 5 * 24 * 60 * 60 }, { 'name' => 'quarter', 'stepSize' => 28, 'stepsToFunc' => :quartersTo, 'minTimeOff' => -1 }, { 'name' => 'year', 'stepSize' => 20, 'stepsToFunc' => :yearsTo, 'minTimeOff' => -1 } ] # This points to one of the scales above and marks the current scale. @scale = nil # The height of the chart (without the header) @height = 0 # The width of the chart in pixels. @width = 0 # The width of the view that the chart is presented in. If it's nil, the # view will be adapted to the width of the chart. @viewWidth = nil # True of the week starts on a Monday. @weekStartsMonday = weekStartsMonday # Reference to the GanttHeader object that models the chart header. @header = nil # The GanttLine objects that model the lines of the chart. @lines = [] # The router for dependency lines. @router = nil # This dictionary stores primary task lines indexed by their task. To # handle multiple scenarios, the dictionary stored the lines in an Array. # This is used to generate dependency arrows. @tasks = {} # This is a list of the dependency lines. Each entry is an Array of [x, y] # coordinate pairs. @depArrows = [] # This is the list of arrow heads used for the dependency arrows. It # contains an Array of [ x, y ] coordinates that mark the tip of the # arrow. @arrowHeads = [] end # Add a primary tasks line to the dictonary. _task_ is a reference to the # Task object and _line_ is the corresponding primary ReportTableLine. def addTask(task, line) if @tasks.include?(task) # Append the line to the existing lines. @tasks[task] << line else # Add a new Array for this tasks and store the first line. @tasks[task] = [ line ] end end def generateByWidth(periodStart, periodEnd, width) @start = periodStart @end = periodEnd @width = width # TODO end # Generate the actual chart data based on the report interval specified by # _periodStart_ and _periodEnd_ as well as the name of the requested scale # to be used. This function (or generateByWidth) must be called before any # GanttLine objects are created for this chart. def generateByScale(periodStart, periodEnd, scaleName) @start = periodStart @end = periodEnd @scale = scaleByName(scaleName) @stepSize = @scale['stepSize'] steps = @start.send(@scale['stepsToFunc'], @end) @width = @stepSize * steps @header = GanttHeader.new(@columnDef, self) end # Convert the chart into an HTML representation. def to_html completeChart # The chart is rendered into a cell that extends over the full height of # the table. No other cells for this column will be generated. In case # there is a scrollbar, the table will have an extra line to hold the # scrollbar. td = XMLElement.new('td', 'rowspan' => "#{2 + @lines.length + (hasScrollbar? ? 1 : 0)}", 'style' => 'padding:0px; vertical-align:top;') # Now we generate two 'div's nested into each other. The first div is the # view. It may contain a scrollbar if the second div is wider than the # first one. In case we need a scrollbar The outer div is # SCROLLBARHEIGHT pixels heigher to hold the scrollbar. Unfortunately # this must be a hardcoded value even though the height of the scrollbar # varies from system to system. This value should be good enough for # most systems. td << (scrollDiv = XMLElement.new('div', 'class' => 'tabback', 'style' => 'position:relative; ' + "overflow:auto; " + "width:#{hasScrollbar? ? @viewWidth : @width}px; " + "height:#{@height + (hasScrollbar? ? SCROLLBARHEIGHT : 0)}px;")) scrollDiv << (div = XMLElement.new('div', 'style' => "margin:0px; padding:0px; " + "position:absolute; overflow:hidden; " + "top:0px; left:0px; " + "width:#{@width}px; " + "height:#{@height}px; " + "font-size:10px;")) # Add the header. div << @header.to_html # These are the lines of the chart. @lines.each do |line| div << line.to_html end # This is used for debugging and testing only. #div << @router.to_html # Render the dependency lines. @depArrows.each do |arrow| xx = yy = nil arrow.each do |x, y| if xx div << lineToHTML(xx, yy, x, y, 'depline') end xx = x yy = y end end # And the corresponsing arrow heads. @arrowHeads.each do |x, y| div << arrowHeadToHTML(x, y) end td end # This is a noop function. def to_csv(csv, startColumn) # Can't put a Gantt chart into a CSV file. 0 end # Utility function that convers a date to the corresponding X-position in # the Gantt chart. def dateToX(date) ((@width / (@end - @start)) * (date - @start)).to_i end # This is not a user callable function. It's only meant for use within the # library. def addLine(line) #:nodoc: if @scale.nil? raise "generateByScale or generateByWidth must be called first" end @lines << line end # Returns true if the chart includes a scrollbar. def hasScrollbar? @viewWidth && (@viewWidth < @width) end private # Find the scale with the name _name_ and return a reference to the scale. # If nothing is round an exception is raised. def scaleByName(name) @@scales.each do |scale| return scale if scale['name'] == name end raise "Unknown scale #{name}" end # Calculate the overall height of the chart and generate dependency arrows. def completeChart @lines.each do |line| @height = line.y + line.height if line.y + line.height > @height end # To layout the dependency lines, we use a GanttRouter. We only provide # the start and end coordinates of each line and it will do the layout # and routing. @router = GanttRouter.new(@width, @height) # We don't want horizontal lines to cross the task bars. So we block # these chart zones. Milestones should not be crossed in any direction. @lines.each do |line| line.addBlockedZones(@router) end # Also protect the current date line from other vertical lines. @router.addZone(@header.nowLineX - 1, 0, 3, @height - 1, false, true) # Generate the dependency arrows for all visible tasks. @tasks.each do |task, lines| generateDepLines(task, lines) end # Make sure we have exactly one arrow head for each line end point even # if the point is used by multiple lines. @depArrows.each do |line| endPoint = line.last @arrowHeads << endPoint unless @arrowHeads.include?(endPoint) end end # Generate an output format independent description of the dependency lines # for a specific _task_. _lines_ is a list of GanttLines that the tasks are # displayed on. Reports with multiple scenarios have multiple lines per # task. def generateDepLines(task, lines) # Since we need the line and the index we use an index iterator. lines.length.times do |lineIndex| line = lines[lineIndex] scenarioIdx = line.query.scenarioIdx # Generate the dependencies on the start of the task. generateTaskDepLines('startsuccs', task, scenarioIdx, lineIndex, *line.getTask.startDepLineStart) # Generate the dependencies on the end of the task. generateTaskDepLines('endsuccs', task, scenarioIdx, lineIndex, *line.getTask.endDepLineStart) end end # Generate the dependencies on the start or end of the task depending on # _kind_. Use 'startsuccs' for the start and 'endsuccs' for end. _startX_ # and _startY_ are the graphic coordinates for the begin of the arrow # line. _task_ references the Task in question and _scenarioIdx_ the # scenario. _lineIndex_ specifies the line number in the chart. def generateTaskDepLines(kind, task, scenarioIdx, lineIndex, startX, startY) # This is an Array that holds 4 values for # each entry: The x and y coordinates for start and end points. touples = [] task[kind, scenarioIdx].each do |t, onEnd| # Skip inherited dependencies and tasks that are not included in the # chart. if (t.parent && task.hasDependency?(scenarioIdx, kind, t.parent, onEnd)) || !@tasks.include?(t) next end endX, endY = @tasks[t][lineIndex].getTask.send( onEnd ? :endDepLineEnd : :startDepLineEnd) touples << [ startX, startY, endX, endY ] end @depArrows += @router.routeLines(touples) unless touples.empty?() end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttLine.rb0000644000175000017500000003325412614413013022353 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttLine.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/GanttTaskBar' require 'taskjuggler/reports/GanttMilestone' require 'taskjuggler/reports/GanttContainer' require 'taskjuggler/reports/GanttLoadStack' require 'taskjuggler/reports/HTMLGraphics' require 'taskjuggler/XMLDocument' class TaskJuggler # This class models the abstract (output independent) form of a line of a # Gantt chart. Each line represents a property. Depending on the type of # property and it's context (for nested properties) the content varies. Tasks # (not nested) are represented as task bars or milestones. When nested into a # resource they are represented as load stacks. class GanttLine include HTMLGraphics attr_reader :y, :height, :query # Create a GanttLine object and generate the abstract representation. def initialize(chart, query, y, height, tooltip) # A reference to the chart that the line belongs to. @chart = chart # Register the line with the chart. @chart.addLine(self) # The query is used to access the presented project data. @query = query # A CellSettingPatternList object to determine the tooltips for the # line's content. @tooltip = tooltip # The category determines the background color of the line. @category = nil # The y coordinate of the topmost pixel of this line. @y = y + chart.header.height + 1 # The height of the line in screen pixels. @height = height # The x coordinates of the time-off zones. It's an Array of [ startX, endX # ] touples. @timeOffZones = [] generate end # Convert the abstract representation of the GanttLine into HTML elements. def to_html # The whole line is put in a 'div' section. All coordinates relative to # the top-left corner of this div. Elements that extend over the # boundaries of this div are cut off. div = XMLElement.new('div', 'class' => @category, 'style' => "margin:0px; padding:0px; " + "position:absolute; " + "left:0px; top:#{@y}px; " + "width:#{@chart.width.to_i}px; " + "height:#{@height}px; " + "font-size:10px;") # Render time-off zones. @timeOffZones.each do |zone| div << rectToHTML(zone[0], 0, zone[1], @height, 'offduty') end # Render grid lines. The grid lines are determined by the large scale. @chart.header.gridLines.each do |line| div << rectToHTML(line, 0, 1, @height, 'tabvline') end # Now render the content as HTML elements. @content.each do |c| html = c.to_html if html && html[0] addHtmlTooltip(@tooltip, @query, html[0], div) div << html end end # Render the 'now' line if @chart.header.nowLineX div << rectToHTML(@chart.header.nowLineX, 0, 1, @height, 'nowline') end div end # This function only works for primary task lines. It returns the generated # intermediate object for that line. def getTask if @content.length == 1 @content[0] else nil end end # Register the areas that dependency lines should not cross. def addBlockedZones(router) @content.each do |c| c.addBlockedZones(router) end end private # Create the data objects that represent the abstract form of this # perticular Gantt chart line. def generate # This Array holds the GanttLineObjects. @content = [] generateTimeOffZones if @query.property.is_a?(Task) generateTask else generateResource end end # Generate abstract form of a task line. The task can be a primary line or # appear in the scope of a resource. def generateTask # Set the background color @category = "taskcell#{(@query.property.get('index') + 1) % 2 + 1}" project = @query.project property = @query.property scopeProperty = @query.scopeProperty taskStart = property['start', @query.scenarioIdx] || project['start'] taskEnd = property['end', @query.scenarioIdx] || project['end'] if scopeProperty # The task is nested into a resource. We show the work the resource is # doing for this task relative to the work the resource is doing for # all tasks. x = nil startDate = endDate = nil categories = [ 'busy', nil ] @chart.header.cellStartDates.each do |date| if x.nil? x = @chart.dateToX(endDate = date).to_i else xNew = @chart.dateToX(date).to_i w = xNew - x startDate = endDate endDate = date # If we have a scope limiting task, we only want to generate load # stacks that overlap with the task interval. next if endDate <= taskStart || taskEnd <= startDate if startDate < taskStart && endDate > taskStart # Make sure the left edge of the first stack aligns with the # start of the scope task. startDate = taskStart x = @chart.dateToX(startDate) w = xNew - x + 1 elsif startDate < taskEnd && endDate > taskEnd # Make sure the right edge of the last stack aligns with the end # of the scope task. endDate = taskEnd w = @chart.dateToX(endDate) - x end startIdx = project.dateToIdx(startDate) endIdx = project.dateToIdx(endDate) overallWork = scopeProperty.getEffectiveWork(@query.scenarioIdx, startIdx, endIdx) + scopeProperty.getEffectiveFreeWork(@query.scenarioIdx, startIdx, endIdx) workThisTask = property.getEffectiveWork(@query.scenarioIdx, startIdx, endIdx, scopeProperty) # If all values are 0 we make sure we show an empty frame. if overallWork == 0 && workThisTask == 0 values = [ 0, 1 ] else values = [ workThisTask, overallWork - workThisTask ] end @content << GanttLoadStack.new(self, x + 1, w - 2, values, categories) x = xNew end end if @chart.table @chart.table.legend.addGanttItem('Resource assigned to task(s)', 'busy') end else # The task is not nested into a resource. We show the classical Gantt # bars for the task. xStart = @chart.dateToX(taskStart) xEnd = @chart.dateToX(taskEnd) @chart.addTask(property, self) @content << if property['milestone', @query.scenarioIdx] GanttMilestone.new(@height, xStart, @y) elsif property.container? && ((rollupExpr = @query.project.reportContexts. last.report.get('rollupTask')).nil? || !rollupExpr.eval(@query)) GanttContainer.new(@height, xStart, xEnd, @y) else GanttTaskBar.new(@query, @height, xStart, xEnd, @y) end # Make sure the legend includes the Gantt symbols. @chart.table.legend.showGanttItems = true if @chart.table @chart.table.legend.addGanttItem('Off-duty period', 'offduty') end end # Generate abstract form of a resource line. The resource can be a primary # line or appear in the scope of a task. def generateResource # Set the alternating background color @category = "resourcecell#{(@query.property.get('index') + 1) % 2 + 1}" # The cellStartDate Array contains the end of the final cell as last # element. We need to use a shift mechanism to start and end # dates/positions properly. x = nil startDate = endDate = nil project = @query.project property = @query.property scopeProperty = @query.scopeProperty # For unnested resource lines we show the assigned work and the # available work. For resources in a task scope we show the work # allocated to this task, the work allocated to other tasks and the free # work. if scopeProperty categories = [ 'assigned', 'busy', 'free' ] taskStart = scopeProperty['start', @query.scenarioIdx] || project['start'] taskEnd = scopeProperty['end', @query.scenarioIdx] || project['end'] if @chart.table @chart.table.legend.addGanttItem('Resource assigned to this task', 'assigned') @chart.table.legend.addGanttItem('Resource assigned to task(s)', 'busy') @chart.table.legend.addGanttItem('Resource available', 'free') @chart.table.legend.addGanttItem('Off-duty period', 'offduty') end else categories = [ 'busy', 'free' ] if @chart.table @chart.table.legend.addGanttItem('Resource assigned to task(s)', 'busy') @chart.table.legend.addGanttItem('Resource available', 'free') @chart.table.legend.addGanttItem('Off-duty period', 'offduty') end end endDate = nil @chart.header.cellStartDates.each do |date| if endDate.nil? endDate = date next end startDate = endDate endDate = date if scopeProperty # If we have a scope limiting task, we only want to generate load # stacks that overlap with the task interval. next if endDate <= taskStart || taskEnd <= startDate if startDate < taskStart # Make sure the left edge of the first stack aligns with the # start of the scope task. startDate = taskStart end if endDate > taskEnd # Make sure the right edge of the last stack aligns with the end # of the scope task. endDate = taskEnd end startIdx = project.dateToIdx(startDate) endIdx = project.dateToIdx(endDate) taskWork = property.getEffectiveWork(@query.scenarioIdx, startIdx, endIdx, scopeProperty) overallWork = property.getEffectiveWork(@query.scenarioIdx, startIdx, endIdx) freeWork = property.getEffectiveFreeWork(@query.scenarioIdx, startIdx, endIdx) values = [ taskWork, overallWork - taskWork, freeWork ] else startIdx = project.dateToIdx(startDate) endIdx = project.dateToIdx(endDate) values = [] values << property.getEffectiveWork(@query.scenarioIdx, startIdx, endIdx) values << property.getEffectiveFreeWork(@query.scenarioIdx, startIdx, endIdx) end x = @chart.dateToX(startDate) w = @chart.dateToX(endDate) - x + 1 @content << GanttLoadStack.new(self, x + 1, w - 2, values, categories) end end # Generate the data structures that mark the time-off periods of a task or # resource int the chart. Depending on the resolution, the only periods with # a duration above the threshold are shown. def generateTimeOffZones iv = TimeInterval.new(@chart.start, @chart.end) # Don't show any zones if the threshold for this scale is 0 or smaller. return if (minTimeOff = @chart.scale['minTimeOff']) <= 0 # Get the time-off intervals. @timeOffZones = @query.property.collectTimeOffIntervals( @query.scenarioIdx, iv, minTimeOff) # Convert the start/end dates to X coordinates of the chart. When # finished, the zones in @timeOffZones are [ startX, endX ] touples. zones = [] @timeOffZones.each do |zone| zones << [ s = @chart.dateToX(zone.start), @chart.dateToX(zone.end) - s ] end @timeOffZones = zones end def addHtmlTooltip(tooltip, query, trigger, hook = nil) return unless tooltip tooltip = tooltip.getPattern(query) return unless tooltip && !tooltip.empty? if tooltip.respond_to?('functionHandler') tooltip.setQuery(query) end if query query.attributeId = 'name' query.process title = query.to_s else title = '' end trigger['onclick'] = "TagToTip('ID#{trigger.object_id}', " + "TITLE, '#{title.gsub(/'/, ''')}')" trigger['style'] += 'cursor:help; ' hook = trigger unless hook hook << (ltDiv = XMLElement.new('div', 'class' => 'tj_tooltip_box', 'id' => "ID#{trigger.object_id}")) ltDiv << (tooltip.respond_to?('to_html') ? tooltip.to_html : XMLText.new(tooltip)) end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ReportTableLegend.rb0000644000175000017500000001355112614413013024026 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ReportTableLegend.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # The ReportTableLegend models an output format independent legend for the # ReportTable. It lists the graphical symbols used in the table together with # a short textual description. class ReportTableLegend attr_accessor :showGanttItems # Create a new ReportTableLegend object. def initialize @showGanttItems = false @ganttItems = [] @calendarItems = [] end # Add another Gantt item to the legend. Make sure we don't have any # duplicates. def addGanttItem(text, color) @ganttItems << [ text, color ] unless @ganttItems.include?([ text, color ]) end # Add another chart item to the legend. Make sure we don't have any # duplicates. def addCalendarItem(text, color) unless @calendarItems.include?([ text, color ]) @calendarItems << [ text, color ] end end # Convert the abstract description into HTML elements. def to_html return nil if !@showGanttItems && @ganttItems.empty? && @calendarItems.empty? frame = XMLElement.new('div', 'class' => 'tj_table_legend_frame') frame << (legend = XMLElement.new('table', 'class' => 'tj_table_legend', 'cellspacing' => '1')) legend << headlineToHTML('Gantt Chart Symbols:') # Generate the Gantt chart symbols if @showGanttItems legend << (row = XMLElement.new('tr', 'class' => 'tj_legend_row')) row << ganttItemToHTML(GanttContainer.new(15, 10, 35, 0), 'Container Task', 40) row << ganttItemToHTML(GanttTaskBar.new(nil, 15, 5, 35, 0), 'Normal Task', 40) row << ganttItemToHTML(GanttMilestone.new(15, 10, 0), 'Milestone', 20) row << XMLElement.new('td', 'class' => 'tj_legend_spacer') end legend << itemsToHTML(@ganttItems) legend << headlineToHTML('Calendar Symbols:') legend << itemsToHTML(@calendarItems) frame end private # In case we have both the calendar and the Gantt chart in the report # element, we have to add description lines before the symbols. The two # charts use the same colors for different meanings. This function generates # the HTML version of the headlines. def headlineToHTML(text) unless @calendarItems.empty? || @ganttItems.empty? div = XMLElement.new('tr', 'class' => 'tj_legend_headline') div << XMLNamedText.new(text, 'td', 'colspan' => '10') return div end nil end # Turn the Gantt symbold descriptions into HTML elements. def ganttItemToHTML(itemRef, name, width) cells = [] # Empty cell for margin first. cells << (item = XMLElement.new('td', 'class' => 'tj_legend_spacer')) # The symbol cell cells << (item = XMLElement.new('td', 'class' => 'tj_legend_item')) item << (symbol = XMLElement.new('div', 'class' => 'tj_legend_symbol', 'style' => 'top:3px')) symbol << itemRef.to_html # The label cell cells << (item = XMLElement.new('td', 'class' => 'tj_legend_item')) item << (label = XMLElement.new('div', 'class' => 'tj_legend_label')) label << XMLText.new(name) cells end # Turn a single color item into HTML elements. def itemToHTML(itemRef) cells = [] # Empty cell for margin first. cells << XMLElement.new('td', 'class' => 'tj_legend_spacer') # The symbol cell cells << (item = XMLElement.new('td', 'class' => 'tj_legend_item')) item << (symbol = XMLElement.new('div', 'class' => 'tj_legend_symbol')) symbol << (box = XMLElement.new('div', 'style' => 'position:relative; ' + 'top:2px;' + 'width:20px; height:15px')) box << (div = XMLElement.new('div', 'class' => 'loadstackframe', 'style' => 'position:absolute; ' + 'left:5px; width:16px; height:15px;')) div << XMLElement.new('div', 'class' => "#{itemRef[1]}", 'style' => 'position:absolute; ' + 'left:1px; top:1px; ' + 'width:14px; height:13px;') # The label cell cells << (item = XMLElement.new('td', 'class' => 'tj_legend_item')) item << (label = XMLElement.new('div', 'class' => 'tj_legend_label')) label << XMLText.new(itemRef[0]) cells end # Turn the color items into HTML elements. def itemsToHTML(items) rows = [] row = nil gridCells = ((items.length / 3) + (items.length % 3 != 0 ? 1 : 0)) * 3 gridCells.times do |i| # We show no more than 3 items in a row. if i % 3 == 0 rows << (row = XMLElement.new('tr', 'class' => 'tj_legend_row')) end # If we run out of items before the line is filled, we just insert # empty cells to fill the line. if i < items.length row << itemToHTML(items[i]) else row << XMLElement.new('td', 'class' => 'tj_legend_item', 'colspan' => '3') end if (i + 1) % 3 == 0 # Append an empty cell at the end of each row. row << XMLElement.new('td', 'class' => 'tj_legend_spacer') end end rows end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttMilestone.rb0000644000175000017500000000461512614413013023422 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttMilestone.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/HTMLGraphics' class TaskJuggler # The GanttMilestone represents a milestone task. class GanttMilestone include HTMLGraphics # The size of the milestone symbol measured from the center to the tips. @@size = 6 # Create a GanttMilestone object based on the following information: _task_ # is a reference to the Task to be displayed. _lineHeight_ is the height of # the line this milestone is shown in. _x_ and _y_ are the coordinates of # the center of the milestone in the GanttChart. def initialize(lineHeight, x, y) @lineHeight = lineHeight @x = x @y = y end # Return the point [ x, y ] where task start dependency lines should start # from. def startDepLineStart [ @x + @@size, @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task start dependency lines should end at. def startDepLineEnd [ @x - @@size, @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task end dependency lines should start # from. def endDepLineStart [ @x + @@size , @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task end dependency lines should end at. def endDepLineEnd [ @x + @@size, @y + @lineHeight / 2 ] end def addBlockedZones(router) router.addZone(@x - @@size - 2, @y + (@lineHeight / 2) - @@size - 2, 2 * @@size + 5, 2 * @@size + 5, true, true) # Block for arrowhead. router.addZone(@x - @@size - 9, @y + (@lineHeight / 2) - 7, 10, 15, true, true) end # Convert the abstact representation of the GanttMilestone into HTML # elements. def to_html html = [] # Invisible trigger frame for tooltips. html << rectToHTML(@x - (@lineHeight / 2), 0, @lineHeight, @lineHeight, 'tj_gantt_frame') # Draw a diamond shape. html += diamondToHTML(@x, @lineHeight / 2) html end end end taskjuggler-3.5.0/lib/taskjuggler/reports/GanttTaskBar.rb0000644000175000017500000000635312614413013023013 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = GanttTaskBar.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/HTMLGraphics' class TaskJuggler # The GanttTaskBar represents a normal task that is part of a GanttChart. class GanttTaskBar include HTMLGraphics # The size of the bar in pixels from center to top/bottom. @@size = 6 # Create a GanttContainer object based on the following information: _line_ # is a reference to the GanttLine. _xStart_ is the left edge of the task in # chart coordinates. _xEnd_ is the right edge. def initialize(query, lineHeight, xStart, xEnd, y) @query = query @lineHeight = lineHeight @start = xStart @end = xEnd @y = y end # Return the point [ x, y ] where task start dependency lines should start # from. def startDepLineStart [ @start + 1, @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task start dependency lines should end at. def startDepLineEnd [ @start - 1, @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task end dependency lines should start # from. def endDepLineStart [ @end + 1, @y + @lineHeight / 2 ] end # Return the point [ x, y ] where task end dependency lines should end at. def endDepLineEnd [ @end - 1, @y + @lineHeight / 2 ] end def addBlockedZones(router) # Horizontal block for whole bar. router.addZone(@start, @y + (@lineHeight / 2) - @@size - 1, @end - @start + 1, 2 * @@size + 3, true, false) # Block for arrowhead. router.addZone(@start - 9, @y + (@lineHeight / 2) - 7, 10, 15, true, true) # Vertical block for end cap router.addZone(@start - 2, @y, 5, @lineHeight, false, true) router.addZone(@end - 2, @y, 5, @lineHeight, false, true) end # Convert the abstact representation of the GanttTaskBar into HTML # elements. def to_html xStart = @start.to_i yCenter = (@lineHeight / 2).to_i width = @end.to_i - @start.to_i + 1 html = [] # Invisible trigger frame for tooltips. html << rectToHTML(xStart, 0, width, @lineHeight, 'tj_gantt_frame') # First we draw the task frame. html << rectToHTML(xStart, yCenter - @@size, width, 2 * @@size, 'taskbarframe') # The we draw the filling. html << rectToHTML(xStart + 1, yCenter - @@size + 1, width - 2, 2 * @@size - 2, 'taskbar') # And then the progress bar. If query is null we assume 50% completion. if @query @query.attributeId = 'complete' @query.process res = @query.result completion = res ? res / 100.0 : 0.0 else completion = 0.5 end html << rectToHTML(xStart + 1, yCenter - @@size / 2, (width - 2) * completion, @@size, 'progressbar') end end end taskjuggler-3.5.0/lib/taskjuggler/reports/ExportRE.rb0000644000175000017500000000214312614413013022167 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ExportRE.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/reports/ReportBase' require 'taskjuggler/reports/TjpExportRE' require 'taskjuggler/reports/MspXmlRE' class TaskJuggler # This specialization of ReportBase implements an export of the # project data in the TJP syntax format. class ExportRE < ReportBase # Create a new object and set some default values. def initialize(report) super(report) end def generateIntermediateFormat super end # Return the project data in TJP syntax format. def to_tjp TjpExportRE.new(@report).to_tjp end # Return the project data in Microsoft Project XML format. def to_mspxml MspXmlRE.new(@report).to_mspxml end end end taskjuggler-3.5.0/lib/taskjuggler/reports/Report.rb0000644000175000017500000003777012614413013021750 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Report.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'fileutils' require 'taskjuggler/PropertyTreeNode' require 'taskjuggler/reports/AccountListRE' require 'taskjuggler/reports/TextReport' require 'taskjuggler/reports/TaskListRE' require 'taskjuggler/reports/ResourceListRE' require 'taskjuggler/reports/TraceReport' require 'taskjuggler/reports/TagFile' require 'taskjuggler/reports/ExportRE' require 'taskjuggler/reports/StatusSheetReport' require 'taskjuggler/reports/TimeSheetReport' require 'taskjuggler/reports/NikuReport' require 'taskjuggler/reports/ICalReport' require 'taskjuggler/reports/CSVFile' require 'taskjuggler/reports/Navigator' require 'taskjuggler/reports/ReportContext' require 'taskjuggler/HTMLDocument' class TaskJuggler # Just a dummy class to make the 'flags' attribute work. class ReportScenario < ScenarioData end # The Report class holds the fundamental description and functionality to # turn the scheduled project into a user readable form. A report may contain # other reports. class Report < PropertyTreeNode attr_accessor :typeSpec, :content # Create a new report object. def initialize(project, id, name, parent) super(project.reports, id, name, parent) @messageHandler = MessageHandlerInstance.instance checkFileName(name) project.addReport(self) # The type specifier must be set for every report. It tells whether this # is a task, resource, text or other report. @typeSpec = nil # Reports don't really have any scenario specific attributes. But the # flag handling code assumes they are. To use flags, we need them as # well. @data = Array.new(@project.scenarioCount, nil) @project.scenarioCount.times do |i| ReportScenario.new(self, i, @scenarioAttributes[i]) end end # The generate function is where the action happens in this class. The # report defined by all the class attributes and report elements is # generated according the the requested output format(s). # _requestedFormats_ can be a list of formats that should be generated (e. # g. :html, :csv, etc.). def generate(requestedFormats = nil) oldTimeZone = TjTime.setTimeZone(get('timezone')) generateIntermediateFormat # We either generate the requested formats or the list of formats that # was specified in the report definition. (requestedFormats || get('formats')).each do |format| if @name.empty? error('empty_report_file_name', "Report #{@id} has output formats requested, but the " + "file name is empty.", sourceFileInfo) end case format when :iCal generateICal when :html generateHTML copyAuxiliaryFiles when :csv generateCSV when :ctags generateCTags when :niku generateNiku when :tjp generateTJP when :mspxml generateMspXml else raise 'Unknown report output format #{format}.' end end TjTime.setTimeZone(oldTimeZone) 0 end # Generate an output format agnostic version that can later be turned into # the respective output formats. def generateIntermediateFormat if get('scenarios').empty? warning('all_scenarios_disabled', "The report #{fullId} has only disabled scenarios. The " + "report will possibly be empty.") end @content = nil case @typeSpec when :accountreport @content = AccountListRE.new(self) when :export @content = ExportRE.new(self) when :iCal @content = ICalReport.new(self) when :niku @content = NikuReport.new(self) when :resourcereport @content = ResourceListRE.new(self) when :tagfile @content = TagFile.new(self) when :textreport @content = TextReport.new(self) when :taskreport @content = TaskListRE.new(self) when :tracereport @content = TraceReport.new(self) when :statusSheet @content = StatusSheetReport.new(self) when :timeSheet @content = TimeSheetReport.new(self) else raise "Unknown report type" end # Most output format can be generated from a common intermediate # representation of the elements. We generate that IR first. @content.generateIntermediateFormat if @content end # Render the content of the report as HTML (without the framing). def to_html @content ? @content.to_html : nil end # Return true if the report should be rendered in the interactive version, # false if not. The top-level report defines the output format and the # interactive setting. def interactive? @project.reportContexts.first.report.get('interactive') end private # Convenience function to access a report attribute def a(attribute) get(attribute) end # Generate an HTML version of the report. def generateHTML return nil unless @content unless @content.respond_to?('to_html') warning('html_not_supported', "HTML format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end html = HTMLDocument.new head = html.generateHead(@project['name'] + " - #{get('title') || @name}", { 'description' => 'TaskJuggler Report', 'keywords' => 'taskjuggler, project, management' }, a('rawHtmlHead')) if a('selfcontained') auxSrcDir = AppConfig.dataDirs('data/css')[0] cssFileName = (auxSrcDir ? auxSrcDir + '/tjreport.css' : '') # Raise an error if we haven't found the data directory if auxSrcDir.nil? || !File.exists?(cssFileName) dataDirError(cssFileName, AppConfig.dataSearchDirs('data/css')) end cssFile = IO.read(cssFileName) if cssFile.empty? error('css_file_error', "Cannot read '#{cssFileName}'. Make sure the file is not " + "empty and you have read access permission.", sourceFileInfo) end head << XMLElement.new('meta', 'http-equiv' => 'Content-Style-Type', 'content' => 'text/css; charset=utf-8') head << (style = XMLElement.new('style', 'type' => 'text/css')) style << XMLBlob.new("\n" + cssFile) else head << XMLElement.new('link', 'rel' => 'stylesheet', 'type' => 'text/css', 'href' => "#{a('auxdir')}css/tjreport.css") end html.html << XMLComment.new("Dynamic Report ID: " + "#{@project.reportContexts.last.dynamicReportId}") html.html << (body = XMLElement.new('body')) unless a('selfcontained') body << XMLElement.new('script', 'type' => 'text/javascript', 'src' => "#{a('auxdir')}scripts/wz_tooltip.js") body << (noscript = XMLElement.new('noscript')) noscript << (nsdiv = XMLElement.new('div', 'style' => 'text-align:center; ' + 'color:#FF0000')) nsdiv << XMLText.new(<<'EOT' This page requires Javascript for full functionality. Please enable it in your browser settings! EOT ) end # Make sure we have some margins around the report. body << (frame = XMLElement.new('div', 'class' => 'tj_page')) frame << @content.to_html # The footer with some administrative information. frame << (div = XMLElement.new('div', 'class' => 'copyright')) div << XMLText.new(@project['copyright'] + " - ") if @project['copyright'] div << XMLText.new("Project: #{@project['name']} " + "Version: #{@project['version']} - " + "Created on #{TjTime.new.to_s("%Y-%m-%d %H:%M:%S")} " + "with ") div << XMLNamedText.new("#{AppConfig.softwareName}", 'a', 'href' => "#{AppConfig.contact}") div << XMLText.new(" v#{AppConfig.version}") fileName = if a('interactive') || @name == '.' # Interactive HTML reports are always sent to stdout. '.' else # Prepend the specified output directory unless the provided file # name is an absolute file name. absoluteFileName(@name) + '.html' end begin html.write(fileName) rescue IOError, SystemCallError error('write_html', "Cannot write to file #{fileName}.\n#{$!}", sourceFileInfo) end end # Generate a CSV version of the report. def generateCSV # The CSV format can only handle the first element of a report. return nil unless @content unless @content.respond_to?('to_csv') warning('csv_not_supported', "CSV format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end return nil unless (csv = @content.to_csv) # Use the CSVFile class to write the Array of Arrays to a colon # separated file. Write to $stdout if the filename was set to '.'. begin fileName = (@name == '.' ? '.' : absoluteFileName(@name) + '.csv') CSVFile.new(csv, ';').write(fileName) rescue IOError, SystemCallError error('write_csv', "Cannot write to file #{fileName}.\n#{$!}", sourceFileInfo) end end # Generate the report in TJP format. def generateTJP unless @content.respond_to?('to_tjp') warning('tjp_not_supported', "TJP format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end begin fileName = '.' if @name == '.' $stdout.write(@content.to_tjp) else fileName = absoluteFileName(@name) fileName += a('definitions').include?('project') ? '.tjp' : '.tji' File.open(fileName, 'w') { |f| f.write(@content.to_tjp) } end rescue IOError, SystemCallError error('write_tjp', "Cannot write to file #{fileName}.\n#{$!}", sourceFileInfo) end end # Generate the report in Microsoft Project XML format. def generateMspXml unless @content.respond_to?('to_mspxml') warning('mspxml_not_supported', "Microsoft Project XML format is not supported for " + "report #{@id} of type #{@typeSpec}.") return nil end begin fileName = '.' if @name == '.' $stdout.write(@content.to_mspxml) else fileName = absoluteFileName(@name) + '.xml' File.open(fileName, 'w') { |f| f.write(@content.to_mspxml) } end rescue IOError, SystemCallError error('write_mspxml', "Cannot write to file #{fileName}.\n#{$!}", sourceFileInfo) end end # Generate Niku report def generateNiku unless @content.respond_to?('to_niku') warning('niku_not_supported', "niku format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end begin f = @name == '.' ? $stdout : File.new(absoluteFileName(@name) + '.xml', 'w') f.puts "#{@content.to_niku}" rescue IOError, SystemCallError error('write_niku', "Cannot write to file #{@name}.\n#{$!}", sourceFileInfo) end end # Generate the report in iCal format. def generateICal unless @content.respond_to?('to_iCal') warning('ical_not_supported', "iCalendar format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end begin f = @name == '.' ? $stdout : File.new(absoluteFileName(@name) + '.ics', 'w') f.puts "#{@content.to_iCal}" rescue IOError, SystemCallError error('write_ical', "Cannot write to file #{@name}.\n#{$!}", sourceFileInfo) end end # Generate ctags file def generateCTags unless @content.respond_to?('to_ctags') warning('ctags_not_supported', "ctags format is not supported for report #{@id} of " + "type #{@typeSpec}.") return nil end begin f = @name == '.' ? $stdout : File.new(absoluteFileName(@name), 'w') f.puts "#{@content.to_ctags}" rescue IOError, SystemCallError error('write_ctags', "Cannot write to file #{@name}.\n#{$!}", sourceFileInfo) end end def copyAuxiliaryFiles # Don't copy files if output is stdout, the requested by the web server # or the user has specified a custom aux directory. return if @name == '.' || a('interactive') || !a('auxdir').empty? copyDirectory('css') copyDirectory('icons') copyDirectory('scripts') end def copyDirectory(dirName) # The directory needs to be in the same directory as the HTML report. auxDstDir = File.dirname(absoluteFileName(@name)) + '/' # Find the data directory that came with the TaskJuggler installation. auxSrcDir = AppConfig.dataDirs("data/#{dirName}")[0].untaint # Raise an error if we haven't found the data directory if auxSrcDir.nil? || !File.exists?(auxSrcDir) dataDirError(dirName, AppConfig.dataSearchDirs("data/#{dirName}")) end # Don't copy directory if all files are up-to-date. return if directoryUpToDate?(auxSrcDir, auxDstDir + dirName) begin # Recursively copy the directory and all content. FileUtils.cp_r(auxSrcDir, auxDstDir) rescue IOError, SystemCallError error('copy_dir', "Cannot copy directory #{auxSrcDir} to " + "#{auxDstDir}.\n#{$!}", sourceFileInfo) end end def directoryUpToDate?(auxSrcDir, auxDstDir) return false unless File.exists?(auxDstDir.untaint) Dir.entries(auxSrcDir).each do |file| next if file == '.' || file == '..' srcFile = (auxSrcDir + '/' + file).untaint dstFile = (auxDstDir + '/' + file).untaint return false if !File.exist?(dstFile) || File.mtime(srcFile) > File.mtime(dstFile) end true end def dataDirError(dirName, dirs) error('data_dir_error', <<"EOT", Cannot find the #{dirName} directory. This is usually the result of an improper TaskJuggler installation. If you know the directory, you can use the TASKJUGGLER_DATA_PATH environment variable to specify the location. The variable should be set to the path without the /data at the end. Multiple directories must be separated by colons. The following directories have been tried: #{dirs.join("\n")} EOT sourceFileInfo ) end def windowsOS? (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil end def checkFileName(name) if windowsOS? illegalChars = /[\x00\\\*\?\"<>\|]/ else illegalChars = /[\\?%*:|"<>]/ end if name =~ illegalChars error('invalid_file_name', 'File names may not contain any of the following characters: ' + '\?%*:|\"<>', sourceFileInfo) end end def absoluteFileName?(name) if windowsOS? name[0] =~ /a-zA-Z/ && name[1] == ?: else name[0] == ?/ end end def absoluteFileName(name) ((absoluteFileName?(name) ? '' : @project.outputDir) + name).untaint end end end taskjuggler-3.5.0/lib/taskjuggler/reports/HTMLGraphics.rb0000644000175000017500000000552512614413013022713 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = HTMLGraphics.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This module provides some functions to render simple graphical objects like # filled rectangles and lines as HTML elements. module HTMLGraphics # Render a line as HTML element. We use 'div's with a single pixel width or # height for this purpose. As a consequence of this, we can only generate # horizontal or vertical lines. Diagonal lines are not supported. _xs_ and # _ys_ are the start coordinates, _xe_ and _ye_ are the end coordinates. # _category_ determines the color. def lineToHTML(xs, ys, xe, ye, category) xs = xs.to_i ys = ys.to_i xe = xe.to_i ye = ye.to_i if ys == ye # Horizontal line xs, xe = xe, xs if xe < xs style = "left:#{xs}px; top:#{ys}px; " + "width:#{xe - xs + 1}px; height:1px;" elsif xs == xe # Vertical line ys, ye = ye, ys if ye < ys style = "left:#{xs}px; top:#{ys}px; " + "width:1px; height:#{ye - ys + 1}px;" else raise "Can't draw diagonal line #{xs}/#{ys} to #{xe}/#{ye}!" end XMLElement.new('div', 'class' => category, 'style' => style) end # Draw a filled rectable at position _x_ and _y_ with the dimension _w_ and # _h_ into another HTML element. The color is determined by the class # _category_. def rectToHTML(x, y, w, h, category) style = "left:#{x.to_i}px; top:#{y.to_i}px; " + "width:#{w.to_i}px; height:#{h.to_i}px;" XMLElement.new('div', 'class' => category, 'style' => style) end def jagToHTML(x, y) XMLElement.new('div', 'class' => 'tj_gantt_jag', 'style' => "left:#{x.to_i - 5}px; " + "top:#{y.to_i}px;") end def diamondToHTML(x, y) html = [] html << XMLElement.new('div', 'class' => 'tj_diamond_top', 'style' => "left:#{x.to_i - 6}px; " + "top:#{y.to_i - 7}px;") html << XMLElement.new('div', 'class' => 'tj_diamond_bottom', 'style' => "left:#{x.to_i - 6}px; " + "top:#{y.to_i}px;") html end def arrowHeadToHTML(x, y) XMLElement.new('div', 'class' => 'tj_arrow_head', 'style' => "left:#{x.to_i - 5}px; " + "top:#{y.to_i - 5}px;") end end end taskjuggler-3.5.0/lib/taskjuggler/reports/TableReportColumn.rb0000644000175000017500000000130112614413013024053 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TableReportColumn.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This class holds some computed data that is used to render the TableReport # Columns. class TableReportColumn attr_accessor :start, :end def initialize(startDate, endDate) @start = startDate @end = endDate end end end taskjuggler-3.5.0/lib/taskjuggler/StatusSheetSender.rb0000644000175000017500000001077412614413013022407 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = StatusSheetSender.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/SheetSender' class TaskJuggler # The StatusSheetSender class generates status sheet templates for the current # week and sends them out to the managers. For this to work, the resources # must provide the 'Email' custom attribute with their email address. The # actual project data is accessed via tj3client on a tj3 server process. class StatusSheetSender < SheetSender attr_accessor :date, :hideResource def initialize(appName) super(appName, 'status') # This is a LogicalExpression string that controls what resources should # not be getting a status sheet. @hideResource = '0' # The base directory of the status sheet templates. @templateDir = 'StatusSheetTemplates' # The base directory of the received time sheets. @timeSheetDir = 'TimeSheets' # This file contains the time intervals that the StatusSheetReceiver will # accept as a valid interval. @signatureFile = "#{@templateDir}/acceptable_intervals" # The log file @logFile = 'statussheets.log' @signatureFilter = /^[ ]*statussheet\s[a-z][a-z0-9_]*\s([0-9:\-+]*\s-\s[0-9:\-+]*)/ @introText = <<'EOT' Please find enclosed your weekly status report template. Please fill out the form and send it back to the sender of this email. You can either use the attached file or the body of the email. In case you send it in the body of the email, make sure it only contains the 'statussheet' syntax. It must be plain text, UTF-8 encoded and the status sheet header from 'statussheet' to the period end date must be in a single line that starts at the beginning of the line. EOT # tj3ts_summary generates a list of resources that have not submitted # their reports yet. If you want to generate the warning below, make # sure you run tj3ts_summary immediately before you sent the status sheet # templates. defaulters = defaulterList unless defaulters.empty? @introText += <<"EOT" =============================== W A R N I N G ============================== The following people have not submitted their report yet. The status reports for the work they have done is not included in this template! You can either manually add their status to the tasks or asked them to send their time sheet immediately and re-request this template. #{defaulters.join} EOT end @mailSubject = "Your weekly status report template for %s" end def defaulterList dirs = Dir.glob("#{@timeSheetDir}/????-??-??").sort tsDir = nil # The status sheet intervals and the time sheet intervals are not # identical. The status sheet interval can be smaller and is somewhat # later. But it always includes the end date of the corresponding time # sheet period. To get the file with the IDs of the resources that have # not submitted their report, we need to find the time sheet directory # that is within the status sheet period. repDate = Time.local(*@date.split('-')) dirs.each do |dir| dirDate = Time.local(*dir[-10..-1].split('-')) if dirDate < repDate tsDir = dir else break end end # Check if there is a time sheet directory. return [] unless tsDir missingFile = "#{tsDir}/missing-reports" # Check if it's got a missing-reports file. return [] if !File.exists?(missingFile) # The sheet could have been submitted after tj3ts_summary was run. We # ignore the entry if a time sheet file now exists. There is a race # condition here. The file may exist, but it may not yet be loaded for # the current project that is used to generate the status report. There # is a race condition here. The file may exist, but it may not yet be # loaded for the current project that is used to generate the status # report. list = File.readlines(missingFile) list.delete_if do |resource| tsDate = tsDir[-10..-1] File.exists?("#{tsDir}/#{resource.chomp}_#{tsDate}.tji") end # Return the content of the file. list end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/0000755000175000017500000000000012614413013020036 5ustar bernatbernattaskjuggler-3.5.0/lib/taskjuggler/Painter/Primitives.rb0000644000175000017500000000465512614413013022530 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Primitives.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Painter/Color' require 'taskjuggler/Painter/Points' class TaskJuggler class Painter # This module contains utility methods to create the canvas Elements with # minimal overhead. The element is added to it's current parent and # mandatory arguments are enforced. It also eliminates the need to call # 'new' methods of each Element. module Primitives unless defined?(StrokeAttrs) StrokeAttrs = [ :stroke, :stroke_opacity, :stroke_width ] FillAttrs = [ :fill, :fill_opacity ] FillAndStrokeAttrs = StrokeAttrs + FillAttrs TextAttrs = FillAndStrokeAttrs + [ :font_family, :font_size ] end def color(*args) Color.new(*args) end def points(arr) Points.new(arr) end def group(attrs = {}, &block) @elements << (g = Group.new(attrs, &block)) g end def circle(cx, cy, r, attrs = {}) attrs[:cx] = cx attrs[:cy] = cy attrs[:r] = r @elements << (c = Circle.new(attrs)) c end def ellipse(cx, cy, rx, ry, attrs = {}) attrs[:cx] = cx attrs[:cy] = cy attrs[:rx] = rx attrs[:ry] = ry @elements << (e = Ellipse.new(attrs)) e end def line(x1, y1, x2, y2, attrs = {}) attrs[:x1] = x1 attrs[:y1] = y1 attrs[:x2] = x2 attrs[:y2] = y2 @elements << (l = Line.new(attrs)) l end def polyline(points, attrs = {}) attrs[:points] = points.is_a?(Array) ? Points.new(points) : points @elements << (l = PolyLine.new(attrs)) l end def rect(x, y, width, height, attrs = {}) attrs[:x] = x attrs[:y] = y attrs[:width] = width attrs[:height] = height @elements << (r = Rect.new(attrs)) r end def text(x, y, str, attrs = {}) attrs[:x] = x attrs[:y] = y @elements << (t = Text.new(str, attrs)) t end end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/Text.rb0000644000175000017500000000136512614413013021314 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Text.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/XMLElement' require 'taskjuggler/Painter/Element' class TaskJuggler class Painter # A text element. class Text < Element # Create a text of _str_ at x, y coordinates. def initialize(str, attrs) super('text', [ :x, :y ] + TextAttrs, attrs) @text = str end end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/SVGSupport.rb0000644000175000017500000000153312614413013022421 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = SVGSupport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class Painter # Utility module to convert the attributes into SVG compatible syntax. module SVGSupport def valuesToSVG values = {} @values.each do |k, v| unit = k == :font_size ? 'pt' : '' # Convert the underscores to dashes and the symbols to Strings. values[k.to_s.gsub(/[_]/, '-')] = v.to_s + unit end values end end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/Color.rb0000644000175000017500000002152612614413013021447 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Color.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class Painter # A description of the color. The color is stored internally as an RGB # value. Common names are provided for many popular colors. class Color NamedColors = { :aliceblue => [ 240, 248, 255 ], :antiquewhite => [ 250, 235, 215 ], :aqua => [ 0, 255, 255 ], :aquamarine => [ 127, 255, 212 ], :azure => [ 240, 255, 255 ], :beige => [ 245, 245, 220 ], :bisque => [ 255, 228, 196 ], :black => [ 0, 0, 0 ], :blanchedalmond => [ 255, 235, 205 ], :blue => [ 0, 0, 255 ], :blueviolet => [ 138, 43, 226 ], :brown => [ 165, 42, 42 ], :burlywood => [ 222, 184, 135 ], :cadetblue => [ 95, 158, 160 ], :chartreuse => [ 127, 255, 0 ], :chocolate => [ 210, 105, 30 ], :coral => [ 255, 127, 80 ], :cornflowerblue => [ 100, 149, 237 ], :cornsilk => [ 255, 248, 220 ], :crimson => [ 220, 20, 60 ], :cyan => [ 0, 255, 255 ], :darkblue => [ 0, 0, 139 ], :darkcyan => [ 0, 139, 139 ], :darkgoldenrod => [ 184, 134, 11 ], :darkgray => [ 169, 169, 169 ], :darkgreen => [ 0, 100, 0 ], :darkgrey => [ 169, 169, 169 ], :darkkhaki => [ 189, 183, 107 ], :darkmagenta => [ 139, 0, 139 ], :darkolivegreen => [ 85, 107, 47 ], :darkorange => [ 255, 140, 0 ], :darkorchid => [ 153, 50, 204 ], :darkred => [ 139, 0, 0 ], :darksalmon => [ 233, 150, 122 ], :darkseagreen => [ 143, 188, 143 ], :darkslateblue => [ 72, 61, 139 ], :darkslategray => [ 47, 79, 79 ], :darkslategrey => [ 47, 79, 79 ], :darkturquoise => [ 0, 206, 209 ], :darkviolet => [ 148, 0, 211 ], :deeppink => [ 255, 20, 147 ], :deepskyblue => [ 0, 191, 255 ], :dimgray => [ 105, 105, 105 ], :dimgrey => [ 105, 105, 105 ], :dodgerblue => [ 30, 144, 255 ], :firebrick => [ 178, 34, 34 ], :floralwhite => [ 255, 250, 240 ], :forestgreen => [ 34, 139, 34 ], :fuchsia => [ 255, 0, 255 ], :gainsboro => [ 220, 220, 220 ], :ghostwhite => [ 248, 248, 255 ], :gold => [ 255, 215, 0 ], :goldenrod => [ 218, 165, 32 ], :gray => [ 128, 128, 128 ], :grey => [ 128, 128, 128 ], :green => [ 0, 128, 0 ], :greenyellow => [ 173, 255, 47 ], :honeydew => [ 240, 255, 240 ], :hotpink => [ 255, 105, 180 ], :indianred => [ 205, 92, 92 ], :indigo => [ 75, 0, 130 ], :ivory => [ 255, 255, 240 ], :khaki => [ 240, 230, 140 ], :lavender => [ 230, 230, 250 ], :lavenderblush => [ 255, 240, 245 ], :lawngreen => [ 124, 252, 0 ], :lemonchiffon => [ 255, 250, 205 ], :lightblue => [ 173, 216, 230 ], :lightcoral => [ 240, 128, 128 ], :lightcyan => [ 224, 255, 255 ], :lightgoldenrodyellow => [ 250, 250, 210 ], :lightgray => [ 211, 211, 211 ], :lightgreen => [ 144, 238, 144 ], :lightgrey => [ 211, 211, 211 ], :lightpink => [ 255, 182, 193 ], :lightsalmon => [ 255, 160, 122 ], :lightseagreen => [ 32, 178, 170 ], :lightskyblue => [ 135, 206, 250 ], :lightslategray => [ 119, 136, 153 ], :lightslategrey => [ 119, 136, 153 ], :lightsteelblue => [ 176, 196, 222 ], :lightyellow => [ 255, 255, 224 ], :lime => [ 0, 255, 0 ], :limegreen => [ 50, 205, 50 ], :linen => [ 250, 240, 230 ], :magenta => [ 255, 0, 255 ], :maroon => [ 128, 0, 0 ], :mediumaquamarine => [ 102, 205, 170 ], :mediumblue => [ 0, 0, 205 ], :mediumorchid => [ 186, 85, 211 ], :mediumpurple => [ 147, 112, 219 ], :mediumseagreen => [ 60, 179, 113 ], :mediumslateblue => [ 123, 104, 238 ], :mediumspringgreen => [ 0, 250, 154 ], :mediumturquoise => [ 72, 209, 204 ], :mediumvioletred => [ 199, 21, 133 ], :midnightblue => [ 25, 25, 112 ], :mintcream => [ 245, 255, 250 ], :mistyrose => [ 255, 228, 225 ], :moccasin => [ 255, 228, 181 ], :navajowhite => [ 255, 222, 173 ], :navy => [ 0, 0, 128 ], :oldlace => [ 253, 245, 230 ], :olive => [ 128, 128, 0 ], :olivedrab => [ 107, 142, 35 ], :orange => [ 255, 165, 0 ], :orangered => [ 255, 69, 0 ], :orchid => [ 218, 112, 214 ], :palegoldenrod => [ 238, 232, 170 ], :palegreen => [ 152, 251, 152 ], :paleturquoise => [ 175, 238, 238 ], :palevioletred => [ 219, 112, 147 ], :papayawhip => [ 255, 239, 213 ], :peachpuff => [ 255, 218, 185 ], :peru => [ 205, 133, 63 ], :pink => [ 255, 192, 203 ], :plum => [ 221, 160, 221 ], :powderblue => [ 176, 224, 230 ], :purple => [ 128, 0, 128 ], :red => [ 255, 0, 0 ], :rosybrown => [ 188, 143, 143 ], :royalblue => [ 65, 105, 225 ], :saddlebrown => [ 139, 69, 19 ], :salmon => [ 250, 128, 114 ], :sandybrown => [ 244, 164, 96 ], :seagreen => [ 46, 139, 87 ], :seashell => [ 255, 245, 238 ], :sienna => [ 160, 82, 45 ], :silver => [ 192, 192, 192 ], :skyblue => [ 135, 206, 235 ], :slateblue => [ 106, 90, 205 ], :slategray => [ 112, 128, 144 ], :slategrey => [ 112, 128, 144 ], :snow => [ 255, 250, 250 ], :springgreen => [ 0, 255, 127 ], :steelblue => [ 70, 130, 180 ], :tan => [ 210, 180, 140 ], :teal => [ 0, 128, 128 ], :thistle => [ 216, 191, 216 ], :tomato => [ 255, 99, 71 ], :turquoise => [ 64, 224, 208 ], :violet => [ 238, 130, 238 ], :wheat => [ 245, 222, 179 ], :white => [ 255, 255, 255 ], :whitesmoke => [ 245, 245, 245 ], :yellow => [ 255, 255, 0 ], :yellowgreen => [ 154, 205, 50 ] } # Create a new Color object. def initialize(*args) if args.length == 1 unless NamedColors.include?(args[0]) raise "Unknown color name #{args[0]}" end @r, @g, @b = NamedColors[args[0]] elsif args.length == 3 args.each do |v| unless v >= 0 && v < 256 raise ArgumentError, "RGB values (#{args.join(', ')}) must " + "be between 0 and 255." end end @r, @g, @b = args elsif args.length == 4 unless args[0] >= 0 && args[0] < 360 raise ArgumentError, "Hue value must be between 0 and 360" end unless args[1] >= 0 && args[1] <= 255 raise ArgumentError, "Saturation value (#{args[1]}) must be " + "between 0 and 255" end unless args[2] >= 0 && args[2] <= 255 raise ArgumentError, "Color value (#{args[2]}) must be " + "between 0 and 255" end @r, @g, @b = hsvToRgb(*args[0..2]) else raise ArgumentError end end def to_rgb [ @r, @g, @b ] end def to_hsv rgbToHsv(@r, @g, @b) end # Convert the RGB value into a String format that is used in HTML. def to_s format("#%02x%02x%02x", @r, @g, @b) end private def hsvToRgb(h, s, v) hi = (h / 60.0).floor % 6 f = (h / 60.0) - (h / 60.0).floor p = (v * (1.0 - s / 255.0)).to_i q = (v * (1.0 - (f * s / 255.0))).to_i t = (v * (1.0 - ((1.0 - f) * s / 255.0))).to_i [ [ v, t, p ], [ q, v, p ], [ p, v, t ], [ p, q, v ], [ t, p, v ], [ v, p, q ] ][hi] end def rgbToHsv(*rgb) max = rgb.max min = rgb.min chroma = (max - min).to_f v = max if (max != 0.0) s = chroma / max * 255.0 else s = 0.0 end r, g, b = rgb if (s == 0.0) h = 0.0 else if (r == max) h = (g - b) / chroma elsif (g == max) h = 2.0 + (b - r) / chroma elsif (b == max) h = 4.0 + (r - g) / chroma end h *= 60.0 h += 360.0 if h < 0.0 end [ h.to_i, s.to_i, v.to_i ] end end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/Element.rb0000644000175000017500000000254112614413013021756 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Line.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Painter/SVGSupport' require 'taskjuggler/Painter/Primitives' class TaskJuggler class Painter # The base class for all drawable elements. class Element include SVGSupport include Primitives # Create a new Element. _type_ specifies the type of the element. # _attrs_ is a list of the supported attributes. _values_ is a hash of # the provided attributes. def initialize(type, attrs, values) @type = type @attributes = attrs @values = {} @text = nil values.each do |k, v| unless @attributes.include?(k) raise ArgumentError, "Unsupported attribute #{k}" end @values[k] = v end end # Convert the Element into an XMLElement tree using SVG syntax. def to_svg el = XMLElement.new(@type, valuesToSVG) el << XMLText.new(@text) if @text el end end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/FontMetrics.rb0000644000175000017500000000763412614413013022632 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Painter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # Set this flag to true to generate FontData.rb. This will require the prawn # gem to be installed. For normal operation, this flag must be set to false. GeneratorMode = false if GeneratorMode # Only required to generate the font metrics data. require 'prawn' end require 'taskjuggler/Painter/FontMetricsData' unless GeneratorMode require 'taskjuggler/Painter/FontData' end class TaskJuggler class Painter # Class to compute or store the raw data for glyph size and kerning # infomation. Developers can use it to generate FontData.rb. This file # contains pre-computed font metrics data for some selected fonts. This # data can then be used to determine the width and height of a bounding # box of a given String. # # Developers can also use this file to generate FontData.rb using prawn as # a back-end. We currently do not want to have prawn as a runtime # dependency for TaskJuggler. class FontMetrics # Initialize the FontMetrics object. def initialize() @fonts = {} # We currently only support the LiberationSans font which is metric # compatible to Arial. @fonts['Arial'] = @fonts['LiberationSans'] = Font_LiberationSans_normal @fonts['Arial-Italic'] = @fonts['LiberationSans-Italic'] = Font_LiberationSans_italic @fonts['Arial-Bold'] = @fonts['LiberationSans-Bold'] = Font_LiberationSans_bold @fonts['Arial-BoldItalic'] = @fonts['LiberationSans-BoldItalic'] = Font_LiberationSans_bold_italic end # Return the height of the _font_ with _ptSize_ points in screen pixels. def height(font, ptSize) checkFontName(font) # Calculate resulting height scaled to the font size and convert to # screen pixels instead of points. (@fonts[font].height * (ptSize.to_f / @fonts[font].ptSize) * (4.0 / 3.0)).to_i end # Return the width of the string in screen pixels when using the font # _font_ with _ptSize_ points. def width(font, ptSize, str) checkFontName(font) w = 0 lastC = nil str.each_char do |c| cw = @fonts[font].glyphWidth(c) w += cw || @font[font].averageWidth if lastC delta = @fonts[font].kerningDelta[lastC + c] w += delta if delta end lastC = c end # Calculate resulting width scaled to the font size and convert to # screen pixels instead of points. (w * (ptSize.to_f / @fonts[font].ptSize) * (4.0 / 3.0)).to_i end private def checkFontName(font) unless @fonts.include?(font) raise ArgumentError, "Unknown font '#{font}'!" end end end end if GeneratorMode File.open('FontData.rb', 'w') do |f| f.puts <<'EOT' class TaskJuggler class Painter class FontMetrics EOT font = 'LiberationSans' f.puts Painter::FontMetricsData.new(font, :normal).to_ruby f.puts Painter::FontMetricsData.new(font, :italic).to_ruby f.puts Painter::FontMetricsData.new(font, :bold).to_ruby f.puts Painter::FontMetricsData.new(font, :bold_italic).to_ruby #font = 'Helvetica' #f.puts Painter::FontMetricsData.new(font, :normal).to_ruby #f.puts Painter::FontMetricsData.new(font, :italic).to_ruby #f.puts Painter::FontMetricsData.new(font, :bold).to_ruby #f.puts Painter::FontMetricsData.new(font, :bold_italic).to_ruby f.puts <<'EOT' end end end EOT end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/BasicShapes.rb0000644000175000017500000000330212614413013022546 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = BasicShapes.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/XMLElement' require 'taskjuggler/Painter/Element' class TaskJuggler class Painter # A circle element. class Circle < Element # Create a circle with center at cx, cy and radius r. def initialize(attrs) super('circle', [ :cx, :cy, :r ] + FillAndStrokeAttrs, attrs) end end # An ellipse element. class Ellipse < Element # Create an ellipse with center at cx, cy and radiuses rx and ry. def initialize(attrs) super('ellipse', [ :cx, :cy, :rx, :ry ] + FillAndStrokeAttrs, attrs) end end # A line element. class Line < Element # Create a line from x1, y1, to x2, y2. def initialize(attrs) super('line', [ :x1, :y1, :x2, :y2 ] + StrokeAttrs, attrs) end end # A Rectangle element. class Rect < Element # Create a rectangle at x, y with width and height. def initialize(attrs) super('rect', [ :x, :y, :width, :height, :rx, :ry ] + FillAndStrokeAttrs, attrs) end end # A Polygon line element. class PolyLine < Element # Create a polygon line with the provided Points. def initialize(attrs) super('polyline', [ :points ] + FillAndStrokeAttrs, attrs) end end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/FontData.rb0000644000175000017500000003360312614413013022070 0ustar bernatbernat class TaskJuggler class Painter class FontMetrics Font_LiberationSans_normal = Painter::FontMetricsData.new('LiberationSans', :normal, 24, 26.088, @charWidth = { ' ' => 6.648, '!' => 6.648, '"' => 8.496, '#' => 13.344, '$' => 13.344, '%' => 21.336, '&' => 15.984, '\'' => 4.560, '(' => 7.992, ')' => 7.992, '*' => 9.336, '+' => 13.992, ',' => 6.648, '-' => 7.992, '.' => 6.648, '/' => 6.648, '0' => 13.344, '1' => 13.344, '2' => 13.344, '3' => 13.344, '4' => 13.344, '5' => 13.344, '6' => 13.344, '7' => 13.344, '8' => 13.344, '9' => 13.344, ':' => 6.648, ';' => 6.648, '<' => 13.992, '=' => 13.992, '>' => 13.992, '?' => 13.344, '@' => 24.360, 'A' => 15.984, 'B' => 15.984, 'C' => 17.328, 'D' => 17.328, 'E' => 15.984, 'F' => 14.640, 'G' => 18.648, 'H' => 17.328, 'I' => 6.648, 'J' => 12.000, 'K' => 15.984, 'L' => 13.344, 'M' => 19.992, 'N' => 17.328, 'O' => 18.648, 'P' => 15.984, 'Q' => 18.648, 'R' => 17.328, 'S' => 15.984, 'T' => 14.640, 'U' => 17.328, 'V' => 15.984, 'W' => 22.632, 'X' => 15.984, 'Y' => 15.984, 'Z' => 14.640, '[' => 6.648, '\\' => 6.648, ']' => 6.648, '^' => 11.256, '_' => 13.344, '`' => 7.992, 'a' => 13.344, 'b' => 13.344, 'c' => 12.000, 'd' => 13.344, 'e' => 13.344, 'f' => 6.648, 'g' => 13.344, 'h' => 13.344, 'i' => 5.328, 'j' => 5.328, 'k' => 12.000, 'l' => 5.328, 'm' => 19.992, 'n' => 13.344, 'o' => 13.344, 'p' => 13.344, 'q' => 13.344, 'r' => 7.992, 's' => 12.000, 't' => 6.648, 'u' => 13.344, 'v' => 12.000, 'w' => 17.328, 'x' => 12.000, 'y' => 12.000, 'z' => 12.000, '{' => 7.992, '|' => 6.216, '}' => 7.992, '~' => 13.992, }, @kerningDelta = { ' A' => -1.324, ' T' => -0.434, ' Y' => -0.434, '11' => -1.781, 'A ' => -1.324, 'AT' => -1.781, 'AV' => -1.781, 'AW' => -0.891, 'AY' => -1.781, 'Av' => -0.434, 'Aw' => -0.434, 'Ay' => -0.434, 'F,' => -2.660, 'F.' => -2.660, 'FA' => -1.324, 'L ' => -0.891, 'LT' => -1.781, 'LV' => -1.781, 'LW' => -1.781, 'LY' => -1.781, 'Ly' => -0.891, 'P ' => -0.434, 'P,' => -3.094, 'P.' => -3.094, 'PA' => -1.781, 'RT' => -0.434, 'RV' => -0.434, 'RW' => -0.434, 'RY' => -0.434, 'T ' => -0.434, 'T,' => -2.660, 'T-' => -1.324, 'T.' => -2.660, 'T:' => -2.660, 'T;' => -2.660, 'TA' => -1.781, 'TO' => -0.434, 'Ta' => -2.660, 'Tc' => -2.660, 'Te' => -2.660, 'Ti' => -0.891, 'To' => -2.660, 'Tr' => -0.891, 'Ts' => -2.660, 'Tu' => -0.891, 'Tw' => -1.324, 'Ty' => -1.324, 'V,' => -2.203, 'V-' => -1.324, 'V.' => -2.203, 'V:' => -0.891, 'V;' => -0.891, 'VA' => -1.781, 'Va' => -1.781, 'Ve' => -1.324, 'Vi' => -0.434, 'Vo' => -1.324, 'Vr' => -0.891, 'Vu' => -0.891, 'Vy' => -0.891, 'W,' => -1.324, 'W-' => -0.434, 'W.' => -1.324, 'W:' => -0.434, 'W;' => -0.434, 'WA' => -0.891, 'Wa' => -0.891, 'We' => -0.434, 'Wo' => -0.434, 'Wr' => -0.434, 'Wu' => -0.434, 'Wy' => -0.211, 'Y ' => -0.434, 'Y,' => -3.094, 'Y-' => -2.203, 'Y.' => -3.094, 'Y:' => -1.324, 'Y;' => -1.559, 'YA' => -1.781, 'Ya' => -1.781, 'Ye' => -2.203, 'Yi' => -0.891, 'Yo' => -2.203, 'Yp' => -1.781, 'Yq' => -2.203, 'Yu' => -1.324, 'Yv' => -1.324, 'ff' => -0.434, 'r,' => -1.324, 'r.' => -1.324, 'v,' => -1.781, 'v.' => -1.781, 'w,' => -1.324, 'w.' => -1.324, 'y,' => -1.781, 'y.' => -1.781, } ) Font_LiberationSans_italic = Painter::FontMetricsData.new('LiberationSans', :italic, 24, 26.016, @charWidth = { ' ' => 6.648, '!' => 6.648, '"' => 8.496, '#' => 13.344, '$' => 13.344, '%' => 21.336, '&' => 15.984, '\'' => 4.560, '(' => 7.992, ')' => 7.992, '*' => 9.336, '+' => 13.992, ',' => 6.648, '-' => 7.992, '.' => 6.648, '/' => 6.648, '0' => 13.344, '1' => 13.344, '2' => 13.344, '3' => 13.344, '4' => 13.344, '5' => 13.344, '6' => 13.344, '7' => 13.344, '8' => 13.344, '9' => 13.344, ':' => 6.648, ';' => 6.648, '<' => 13.992, '=' => 13.992, '>' => 13.992, '?' => 13.344, '@' => 24.360, 'A' => 15.984, 'B' => 15.984, 'C' => 17.328, 'D' => 17.328, 'E' => 15.984, 'F' => 14.640, 'G' => 18.648, 'H' => 17.328, 'I' => 6.648, 'J' => 12.000, 'K' => 15.984, 'L' => 13.344, 'M' => 19.992, 'N' => 17.328, 'O' => 18.648, 'P' => 15.984, 'Q' => 18.648, 'R' => 17.328, 'S' => 15.984, 'T' => 14.640, 'U' => 17.328, 'V' => 15.984, 'W' => 22.632, 'X' => 15.984, 'Y' => 15.984, 'Z' => 14.640, '[' => 6.648, '\\' => 6.648, ']' => 6.648, '^' => 11.256, '_' => 13.344, '`' => 7.992, 'a' => 13.344, 'b' => 13.344, 'c' => 12.000, 'd' => 13.344, 'e' => 13.344, 'f' => 6.648, 'g' => 13.344, 'h' => 13.344, 'i' => 5.328, 'j' => 5.328, 'k' => 12.000, 'l' => 5.328, 'm' => 19.992, 'n' => 13.344, 'o' => 13.344, 'p' => 13.344, 'q' => 13.344, 'r' => 7.992, 's' => 12.000, 't' => 6.648, 'u' => 13.344, 'v' => 12.000, 'w' => 17.328, 'x' => 12.000, 'y' => 12.000, 'z' => 12.000, '{' => 7.992, '|' => 6.216, '}' => 7.992, '~' => 13.992, }, @kerningDelta = { ' A' => -0.891, ' Y' => -0.434, '11' => -1.781, 'A ' => -0.891, 'AT' => -1.781, 'AV' => -1.324, 'AW' => -0.434, 'AY' => -1.781, 'Av' => -0.434, 'Aw' => -0.434, 'Ay' => -0.211, 'F ' => -0.434, 'F,' => -3.094, 'F.' => -3.094, 'FA' => -1.781, 'L ' => -0.434, 'LT' => -1.781, 'LV' => -1.324, 'LW' => -0.891, 'LY' => -2.203, 'Ly' => -0.434, 'P ' => -0.891, 'P,' => -3.094, 'P.' => -3.094, 'PA' => -1.781, 'RT' => -0.434, 'RV' => -0.434, 'RW' => -0.434, 'RY' => -0.891, 'T,' => -2.203, 'T-' => -2.203, 'T.' => -2.203, 'T:' => -1.781, 'T;' => -1.781, 'TA' => -1.781, 'TO' => -0.434, 'Ta' => -2.203, 'Tc' => -2.203, 'Te' => -2.203, 'Ti' => -0.211, 'To' => -2.203, 'Tr' => -1.781, 'Ts' => -2.203, 'Tu' => -1.781, 'Tw' => -1.781, 'Ty' => -1.781, 'V,' => -1.781, 'V-' => -0.891, 'V.' => -1.781, 'V:' => -0.434, 'V;' => -0.434, 'VA' => -1.324, 'Va' => -0.891, 'Ve' => -0.891, 'Vi' => -0.434, 'Vo' => -0.891, 'Vr' => -0.434, 'Vu' => -0.434, 'Vy' => -0.434, 'W,' => -0.891, 'W-' => -0.434, 'W.' => -0.891, 'WA' => -0.434, 'Wa' => -0.434, 'We' => -0.434, 'Wi' => -0.211, 'Y ' => -0.434, 'Y,' => -2.203, 'Y-' => -1.781, 'Y.' => -2.203, 'Y:' => -0.891, 'Y;' => -0.891, 'YA' => -1.324, 'Ya' => -1.781, 'Ye' => -1.324, 'Yi' => -0.434, 'Yo' => -1.324, 'Yp' => -1.324, 'Yq' => -1.324, 'Yu' => -0.891, 'Yv' => -0.891, 'r,' => -1.324, 'r-' => -0.434, 'r.' => -0.891, 'v,' => -1.781, 'v.' => -1.781, 'w,' => -1.324, 'w.' => -1.324, 'y,' => -1.781, 'y.' => -1.781, } ) Font_LiberationSans_bold = Painter::FontMetricsData.new('LiberationSans', :bold, 24, 26.088, @charWidth = { ' ' => 6.648, '!' => 7.992, '"' => 11.376, '#' => 13.344, '$' => 13.344, '%' => 21.336, '&' => 17.328, '\'' => 5.688, '(' => 7.992, ')' => 7.992, '*' => 9.336, '+' => 13.992, ',' => 6.648, '-' => 7.992, '.' => 6.648, '/' => 6.648, '0' => 13.344, '1' => 13.344, '2' => 13.344, '3' => 13.344, '4' => 13.344, '5' => 13.344, '6' => 13.344, '7' => 13.344, '8' => 13.344, '9' => 13.344, ':' => 7.992, ';' => 7.992, '<' => 13.992, '=' => 13.992, '>' => 13.992, '?' => 14.640, '@' => 23.400, 'A' => 17.328, 'B' => 17.328, 'C' => 17.328, 'D' => 17.328, 'E' => 15.984, 'F' => 14.640, 'G' => 18.648, 'H' => 17.328, 'I' => 6.648, 'J' => 13.344, 'K' => 17.328, 'L' => 14.640, 'M' => 19.992, 'N' => 17.328, 'O' => 18.648, 'P' => 15.984, 'Q' => 18.648, 'R' => 17.328, 'S' => 15.984, 'T' => 14.640, 'U' => 17.328, 'V' => 15.984, 'W' => 22.632, 'X' => 15.984, 'Y' => 15.984, 'Z' => 14.640, '[' => 7.992, '\\' => 6.648, ']' => 7.992, '^' => 13.992, '_' => 13.344, '`' => 7.992, 'a' => 13.344, 'b' => 14.640, 'c' => 13.344, 'd' => 14.640, 'e' => 13.344, 'f' => 7.992, 'g' => 14.640, 'h' => 14.640, 'i' => 6.648, 'j' => 6.648, 'k' => 13.344, 'l' => 6.648, 'm' => 21.336, 'n' => 14.640, 'o' => 14.640, 'p' => 14.640, 'q' => 14.640, 'r' => 9.336, 's' => 13.344, 't' => 7.992, 'u' => 14.640, 'v' => 13.344, 'w' => 18.648, 'x' => 13.344, 'y' => 13.344, 'z' => 12.000, '{' => 9.336, '|' => 6.696, '}' => 9.336, '~' => 13.992, }, @kerningDelta = { ' A' => -0.891, ' Y' => -0.434, '11' => -1.324, 'A ' => -0.891, 'AT' => -1.781, 'AV' => -1.781, 'AW' => -1.324, 'AY' => -2.203, 'Av' => -0.891, 'Aw' => -0.434, 'Ay' => -0.891, 'F,' => -2.660, 'F.' => -2.660, 'FA' => -1.324, 'L ' => -0.434, 'LT' => -1.781, 'LV' => -1.781, 'LW' => -1.324, 'LY' => -2.203, 'Ly' => -0.891, 'P ' => -0.434, 'P,' => -3.094, 'P.' => -3.094, 'PA' => -1.781, 'RV' => -0.434, 'RW' => -0.434, 'RY' => -0.891, 'T,' => -2.660, 'T-' => -1.324, 'T.' => -2.660, 'T:' => -2.660, 'T;' => -2.660, 'TA' => -1.781, 'TO' => -0.434, 'Ta' => -1.781, 'Tc' => -1.781, 'Te' => -1.781, 'Ti' => -0.434, 'To' => -1.781, 'Tr' => -1.324, 'Ts' => -1.781, 'Tu' => -1.781, 'Tw' => -1.781, 'Ty' => -1.781, 'V,' => -2.203, 'V-' => -1.324, 'V.' => -2.203, 'V:' => -1.324, 'V;' => -1.324, 'VA' => -1.781, 'Va' => -1.324, 'Ve' => -1.324, 'Vi' => -0.434, 'Vo' => -1.781, 'Vr' => -1.324, 'Vu' => -0.891, 'Vy' => -0.891, 'W,' => -1.324, 'W-' => -0.480, 'W.' => -1.324, 'W:' => -0.434, 'W;' => -0.434, 'WA' => -1.324, 'Wa' => -0.891, 'We' => -0.434, 'Wi' => -0.211, 'Wo' => -0.434, 'Wr' => -0.434, 'Wu' => -0.434, 'Wy' => -0.434, 'Y ' => -0.434, 'Y,' => -2.660, 'Y-' => -1.324, 'Y.' => -2.660, 'Y:' => -1.781, 'Y;' => -1.781, 'YA' => -2.203, 'Ya' => -1.324, 'Ye' => -1.324, 'Yi' => -0.891, 'Yo' => -1.781, 'Yp' => -1.324, 'Yq' => -1.781, 'Yu' => -1.324, 'Yv' => -1.324, 'r,' => -1.324, 'r.' => -1.324, 'v,' => -1.781, 'v.' => -1.781, 'w,' => -0.891, 'w.' => -0.891, 'y,' => -1.781, 'y.' => -1.781, } ) Font_LiberationSans_bold_italic = Painter::FontMetricsData.new('LiberationSans', :bold_italic, 24, 26.088, @charWidth = { ' ' => 6.648, '!' => 7.992, '"' => 11.376, '#' => 13.344, '$' => 13.344, '%' => 21.336, '&' => 17.328, '\'' => 5.688, '(' => 7.992, ')' => 7.992, '*' => 9.336, '+' => 13.992, ',' => 6.648, '-' => 7.992, '.' => 6.648, '/' => 6.648, '0' => 13.344, '1' => 13.344, '2' => 13.344, '3' => 13.344, '4' => 13.344, '5' => 13.344, '6' => 13.344, '7' => 13.344, '8' => 13.344, '9' => 13.344, ':' => 7.992, ';' => 7.992, '<' => 13.992, '=' => 13.992, '>' => 13.992, '?' => 14.640, '@' => 23.400, 'A' => 17.328, 'B' => 17.328, 'C' => 17.328, 'D' => 17.328, 'E' => 15.984, 'F' => 14.640, 'G' => 18.648, 'H' => 17.328, 'I' => 6.648, 'J' => 13.344, 'K' => 17.328, 'L' => 14.640, 'M' => 19.992, 'N' => 17.328, 'O' => 18.648, 'P' => 15.984, 'Q' => 18.648, 'R' => 17.328, 'S' => 15.984, 'T' => 14.640, 'U' => 17.328, 'V' => 15.984, 'W' => 22.632, 'X' => 15.984, 'Y' => 15.984, 'Z' => 14.640, '[' => 7.992, '\\' => 6.648, ']' => 7.992, '^' => 13.992, '_' => 13.344, '`' => 7.992, 'a' => 13.344, 'b' => 14.640, 'c' => 13.344, 'd' => 14.640, 'e' => 13.344, 'f' => 7.992, 'g' => 14.640, 'h' => 14.640, 'i' => 6.648, 'j' => 6.648, 'k' => 13.344, 'l' => 6.648, 'm' => 21.336, 'n' => 14.640, 'o' => 14.640, 'p' => 14.640, 'q' => 14.640, 'r' => 9.336, 's' => 13.344, 't' => 7.992, 'u' => 14.640, 'v' => 13.344, 'w' => 18.648, 'x' => 13.344, 'y' => 13.344, 'z' => 12.000, '{' => 9.336, '|' => 6.696, '}' => 9.336, '~' => 13.992, }, @kerningDelta = { ' A' => -0.891, ' Y' => -0.434, '11' => -1.781, 'A ' => -0.891, 'AT' => -1.781, 'AV' => -1.781, 'AW' => -1.324, 'AY' => -1.781, 'F,' => -2.660, 'F.' => -2.660, 'FA' => -1.324, 'L ' => -0.434, 'LT' => -1.781, 'LV' => -1.324, 'LW' => -1.324, 'LY' => -1.781, 'P ' => -0.891, 'P,' => -3.094, 'P.' => -3.094, 'PA' => -1.781, 'RT' => -0.434, 'RW' => -0.434, 'RY' => -0.434, 'T,' => -1.781, 'T-' => -1.324, 'T.' => -1.781, 'T:' => -1.781, 'T;' => -1.781, 'TA' => -1.781, 'TO' => -0.434, 'Ta' => -0.891, 'Tc' => -0.891, 'Te' => -0.891, 'Ti' => -0.434, 'To' => -0.891, 'Tr' => -0.434, 'Ts' => -0.891, 'Tu' => -0.434, 'Tw' => -0.891, 'Ty' => -0.891, 'V,' => -2.203, 'V-' => -0.891, 'V.' => -2.203, 'V:' => -0.891, 'V;' => -0.891, 'VA' => -1.781, 'Va' => -0.891, 'Ve' => -0.891, 'Vi' => -0.891, 'Vo' => -0.891, 'Vr' => -0.434, 'Vu' => -0.434, 'Vy' => -0.434, 'W,' => -1.781, 'W-' => -0.891, 'W.' => -1.781, 'W:' => -0.891, 'W;' => -0.891, 'WA' => -1.324, 'Wa' => -0.434, 'We' => -0.434, 'Wi' => -0.211, 'Wo' => -0.434, 'Wr' => -0.434, 'Wu' => -0.434, 'Wy' => -0.434, 'Y ' => -0.434, 'Y,' => -2.203, 'Y-' => -1.781, 'Y.' => -2.203, 'Y:' => -1.324, 'Y;' => -1.324, 'YA' => -1.781, 'Ya' => -0.891, 'Ye' => -0.891, 'Yi' => -0.891, 'Yo' => -0.891, 'Yp' => -0.891, 'Yq' => -0.891, 'Yu' => -0.891, 'Yv' => -0.891, 'ff' => -0.434, 'r,' => -1.324, 'r.' => -1.324, 'v,' => -1.324, 'v.' => -1.324, 'w,' => -0.891, 'w.' => -0.891, 'y,' => -0.891, 'y.' => -0.891, } ) end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/Points.rb0000644000175000017500000000222712614413013021642 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Points.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class Painter # Utility class to describe a list of x, y coordinates. Each coordinate is # an Array with 2 elements. The whole list is another Array. class Points # Store the list after doing some error checking. def initialize(arr) arr.each do |point| unless point.is_a?(Array) && point.length == 2 raise ArgumentError, 'Points must be an Array with 2 coordinates' end end @points = arr end # Conver the list of coordinates into a String that is compatible with # SVG syntax. def to_s str = '' @points.each do |point| str += "#{point[0]},#{point[1]} " end str end end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/FontMetricsData.rb0000644000175000017500000001105112614413013023410 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Painter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class Painter # The FontMetricsData objects generate and store the font metrics data for # a particular font. The glyph set is currently restricted to US ASCII # characters. class FontMetricsData MIN_GLYPH_INDEX = 32 MAX_GLYPH_INDEX = 126 attr_reader :ptSize, :charWidth, :height, :kerningDelta # The constructor can be used in two different modes. If all font data # is supplied, the object just stores the supplied font data. If only # the font name is given, the class uses the prawn library to generate # the font metrics for the requested font. def initialize(fontName, type = :normal, ptSize = 24, height = nil, wData = nil, kData = nil) @fontName = fontName @type = type @height = height @ptSize = ptSize @averageWidth = 0.0 if wData && kData @charWidth = wData @kerningDelta = kData else generateMetrics end end # Return the width of the glyph _c_. This must be a single character # String. If the glyph is not known, nil is returned. def glyphWidth(c) return @charWidth[c] end # The average with of all glyphs. def averageWidth return (@averageWidth * (3.0 / 4.0)).to_i end # Generate the FontMetricsData initialization code for the particular # font. The output will be Ruby syntax. def to_ruby indent = ' ' * 6 s = "#{indent}Font_#{@fontName.gsub(/-/, '_')}_#{@type} = " + "Painter::FontMetricsData.new('#{@fontName}', :#{@type}, " + "#{@ptSize}, #{"%.3f" % @height},\n" s << "#{indent} @charWidth = {" i = 0 @charWidth.each do |c, w| s << (i % 4 == 0 ? "\n#{indent} " : ' ') i += 1 s << "'#{escapedChars(c)}' => #{"%0.3f" % w}," end s << "\n#{indent} },\n" s << "#{indent} @kerningDelta = {" i = 0 @kerningDelta.each do |cp, w| s << (i % 4 == 0 ? "\n#{indent} " : ' ') i += 1 s << "'#{cp}' => #{"%.3f" % w}," end s << "\n#{indent} }\n#{indent})\n" end private def escapedChars(c) c.gsub(/\\/, '\\\\\\\\').gsub(/'/, '\\\\\'') end def generateMetrics @pdf = Prawn::Document.new ttfDir = "/usr/share/fonts/truetype/" @pdf.font_families.update( "LiberationSans" => { :bold => "#{ttfDir}LiberationSans-Bold.ttf", :italic => "#{ttfDir}LiberationSans-Italic.ttf", :bold_italic => "#{ttfDir}LiberationSans-BoldItalic.ttf", :normal => "#{ttfDir}LiberationSans-Regular.ttf" } ) @pdf.font(@fontName, :size => @ptSize, :style => @type) # Determine the height of the font. @height = @pdf.height_of("jjggMMWW") @charWidth = {} @averageWidth = 0.0 MIN_GLYPH_INDEX.upto(MAX_GLYPH_INDEX) do |c| char = "" << c begin @charWidth[char] = (w = @pdf.width_of(char)) rescue # the glyph is not in this font. end @averageWidth += w end @averageWidth /= (MAX_GLYPH_INDEX - MIN_GLYPH_INDEX) @kerningDelta = {} MIN_GLYPH_INDEX.upto(MAX_GLYPH_INDEX) do |c1| char1 = "" << c1 next unless (cw1 = glyphWidth(char1)) MIN_GLYPH_INDEX.upto(MAX_GLYPH_INDEX) do |c2| char2 = "" << c2 next unless (cw2 = glyphWidth(char2)) chars = char1 + char2 # The kerneing delta is the difference between the computed width # of the combined characters and the sum of the individual # character widths. delta = @pdf.width_of(chars, :kerning => true) - (cw1 + cw2) # We ususally don't use Strings longer than 100 characters. So we # can ignore kerning deltas below a certain threshhold. if delta.abs > 0.001 @kerningDelta[chars] = delta end end end end end end end taskjuggler-3.5.0/lib/taskjuggler/Painter/Group.rb0000644000175000017500000000433512614413013021464 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Group.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Painter/Primitives' require 'taskjuggler/Painter/SVGSupport' class TaskJuggler class Painter # The Group can be used to group Elements together and define common # attributes in a single place. class Group include Primitives include SVGSupport def initialize(values, &block) @attributes = [ :fill, :font_family, :font_size, :stroke, :stroke_width ] values.each do |k, v| unless @attributes.include?(k) raise ArgumentError, "Unsupported attribute #{k}. " + "Use one of #{@attributes.join(', ')}." end end @values = values @elements = [] if block if block.arity == 1 # This is the traditional case where self is passed to the block. # All Primitives methods now must be prefixed with the block # variable to call them. yield self else # In order to have the primitives easily available in the block, # we use instance_eval to switch self to this object. But this # makes the methods of the original self no longer accessible. We # work around this by saving the original self and using # method_missing to delegate the method call to the original self. @originalSelf = eval('self', block.binding) instance_eval(&block) end end end # Delegator to @originalSelf. def method_missing(method, *args, &block) @originalSelf.send(method, *args, &block) end # Convert the Group into an XMLElement tree using SVG syntax. def to_svg XMLElement.new('g', valuesToSVG) do @elements.map { |el| el.to_svg } end end end end end taskjuggler-3.5.0/lib/taskjuggler/UTF8String.rb0000644000175000017500000001020512614413013020674 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = UTF8String.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # KCODE='u' require 'base64' # This is an extension and modification of the standard String class. We do a # lot of UTF-8 character processing in the parser. Ruby 1.8 does not have good # enough UTF-8 support and Ruby 1.9 only handles UTF-8 characters as Strings. # This is very inefficient compared to representing them as Fixnum objects. # Some of these hacks can be removed once we have switched to 1.9 support # only. class String if RUBY_VERSION < '1.9.0' # Iterate over the String calling the block for each UTF-8 character in # the String. This implementation looks more awkward but is noticeably # faster than the often propagated regexp based implementations. def each_utf8_char c = '' length = 0 each_byte do |b| c << b if length > 0 # subsequent unicode byte if (length -= 1) == 0 # end of unicode character reached yield c c = '' end elsif (b & 0xC0) == 0xC0 # first unicode byte length = -1 while (b & 0x80) != 0 length += 1 b = b << 1 end else # ASCII character yield c c = '' end end end alias old_double_left_angle << # Replacement for the existing << operator that also works for characters # above Fixnum 255 (UTF-8 characters). def << (obj) if obj.is_a?(String) || (obj < 256) # In this case we can use the built-in concat. concat(obj) else # UTF-8 characters have a maximum length of 4 byte and no byte is 0. mask = 0xFF000000 pos = 3 while pos >= 0 # Use the built-in concat operator for each byte. concat((obj & mask) >> (8 * pos)) if (obj & mask) != 0 # Move mask and position to the next byte. mask = mask >> 8 pos -= 1 end end end # Return the number of UTF8 characters in the String. We don't override # the built-in length() function here as we don't know who else uses it # for what purpose. def length_utf8 len = 0 each_utf8_char { |c| len += 1 } len end def ljust(len, pad = ' ') return self + pad * (len - length_utf8) if length_utf8 < len self end alias old_reverse reverse # UTF-8 aware version of reverse that replaces the built-in one. def reverse a = [] each_utf8_char { |c| a << c } a.reverse.join end else alias each_utf8_char each_char alias length_utf8 length end def to_quoted_printable [self].pack('M').gsub(/\n/, "\r\n") end def to_base64 Base64.encode64(self) end def unix2dos gsub(/\n/, "\r\n") end # Ensure the String is really UTF-8 encoded and newlines are only \n. If # that's not possible, an Encoding::UndefinedConversionError is raised. def forceUTF8Encoding if RUBY_VERSION < '1.9.0' # Ruby 1.8 really only support 7 bit ASCII well. Only do the line-end # clean-up. gsub(/\r\n/, "\n") else begin # Ensure that the text has LF line ends and is UTF-8 encoded. encode('UTF-8', :universal_newline => true) rescue # The encoding of the String is broken. Find the first broken line and # report it. lineCtr = 1 each_line do |line| begin line.encode('UTF-8') rescue line = line.encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => '') raise Encoding::UndefinedConversionError, "UTF-8 encoding error in line #{lineCtr}: #{line}" end lineCtr += 1 end end end end end taskjuggler-3.5.0/lib/taskjuggler/ChargeSet.rb0000644000175000017500000000763112614413013020635 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = ChargeSet.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TjException' class TaskJuggler # A charge set describes how a given amount is distributed over a set of # accounts. It stores the percentage share for each account. The accumulated # percentages must always be 100% for a valid charge set. For consistency # reasons, accounts must always be leaf accounts of the same top-level # account. Percentage values must range from 0.0 to 1.0. class ChargeSet attr_reader :master # Create a new ChargeSet object. def initialize @set = {} @master = nil end # Add a new account to the set. Accounts and share rates must meet a number # of requirements. This method does some error checking and raises a # TjException in case of problems. It cannot check everything. Accounts can # later be turned into group accounts or the total share sum may not be # 100%. This needs to be checked at a later stage. Accounts may have a share # of nil. This will be set in ChargeSet#complete later. def addAccount(account, share) unless account.leaf? raise TjException.new, "Account #{account.fullId} is a group account and cannot be used " + "in a chargeset." end if @set.include?(account) raise TjException.new, "Account #{account.fullId} is already a member of the charge set." end if @master.nil? @master = account.root elsif @master != account.root raise TjException.new, "All members of this charge set must belong to the " + "#{@master.fullId} account. #{account.fullId} belongs to " + "#{account.root.fullId}." end if account.container? raise TjException.new, "#{account.fullId} is a group account. Only leaf accounts are " + "allowed for a charge set." end if share && (share < 0.0 || share > 1.0) raise TjException.new, "Charge set shares must be between 0 and 100%" end @set[account] = share end def each @set.each do |account, share| yield account, share end end # Check for accounts that don't have a share yet and distribute the # remainder to 100% evenly accross them. def complete # Calculate the current total share. totalPercent = 0.0 undefined = 0 @set.each_value do |share| if share totalPercent += share else undefined += 1 end end # Must be less than 100%. if totalPercent > 1.0 raise TjException.new, "Total share of this set (#{totalPercent * 100}%) excedes 100%." end if undefined > 0 commonShare = (1.0 - totalPercent) / undefined if commonShare <= 0 raise TjException.new, "Total share is 100% but #{undefined} account(s) still exist." end @set.each do |account, share| if share.nil? @set[account] = commonShare end end elsif totalPercent != 1.0 raise TjException.new, "Total share of this set is #{totalPercent * 100} instead of 100%." end end # Return the share percentage for a given Account _account_. def share(account) @set[account] end # Return the set as comma separated list of account ID + share pairs. def to_s str = '(' @set.each do |account, share| str += ', ' unless str == '(' str += "#{account.fullId} #{share * 100}%" end str += ')' end end end taskjuggler-3.5.0/lib/taskjuggler/StatusSheetReceiver.rb0000644000175000017500000000301012614413013022714 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = StatusSheetReceiver.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/SheetReceiver' class TaskJuggler # This class specializes SheetReceiver to process status sheets. class StatusSheetReceiver < SheetReceiver def initialize(appName) super(appName, 'status') @tj3clientOption = 'check-ss' # File name and directory settings. @sheetDir = 'StatusSheets' @templateDir = 'StatusSheetTemplates' @failedMailsDir = "#{@sheetDir}/FailedMails" @failedSheetsDir = "#{@sheetDir}/FailedSheets" # This file contains the time intervals that the StatusSheetReceiver will # accept as a valid interval. @signatureFile = "#{@templateDir}/acceptable_intervals" # The log file @logFile = 'statussheets.log' # Regular expression to identify status sheets. @sheetHeader = /^[ ]*statussheet\s([a-z][a-z0-9_]*)\s[0-9\-:+]*\s-\s([0-9]*-[0-9]*-[0-9]*)/ # Regular expression to extract the sheet signature (time period). @signatureFilter = /^[ ]*statussheet\s[a-z][a-z0-9_]*\s([0-9:\-+]*\s-\s[0-9:\-+]*)/ @emailSubject = "Status report from %s for %s" end end end taskjuggler-3.5.0/lib/taskjuggler/KeywordArray.rb0000644000175000017500000000150312614413013021403 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = KeywordArray.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This class is a specialized version of Array. It stores a list of # keywords as String objects. The entry '*' is special. It means all # keywords of a particular set are included. '*' must be the first entry if # it is present. class KeywordArray < Array alias a_include? include? def include?(keyword) (self[0] == '*') || a_include?(keyword) end end end taskjuggler-3.5.0/lib/taskjuggler/BatchProcessor.rb0000644000175000017500000003022412614413013021703 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = BatchProcessor.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'thread' require 'monitor' class TaskJuggler # The JobInfo class is just a storage container for some batch job realted # pieces of information. It contains things like a job id, the process id, # the stdout data and the like. class JobInfo attr_reader :jobId, :block, :tag attr_accessor :pid, :retVal, :stdoutP, :stdoutC, :stdout, :stdoutEOT, :stderrP, :stderrC, :stderr, :stderrEOT def initialize(jobId, block, tag) # The job id. A unique number that is used by the BatchProcessor objects # to indentify jobs. @jobId = jobId # This the the block of code to be run as external process. @block = block # The tag can really be anything that the user of BatchProcessor needs # to uniquely identify the job. @tag = tag # The pipe to transfer stdout data from the child to the parent. @stdoutP, @stdoutC = nil # The stdout output of the child @stdout = '' # This flag is set to true when the EOT character has been received. @stdoutEOF = false # The pipe to transfer stderr data from the child to the parent. @stderrP, @stderrC = nil # The stderr output of the child @stderr = '' # This flag is set to true when the EOT character has been received. @stderrEOT = false end def openPipes @stdoutP, @stdoutC = IO.pipe @stderrP, @stderrC = IO.pipe end end # The BatchProcessor class can be used to run code blocks of the program as # a separate process. Mulitple pieces of code can be submitted to be # executed in parallel. The number of CPU cores to use is limited at object # creation time. The submitted jobs will be queued and scheduled to the # given number of CPUs. The usage model is simple. Create an BatchProcessor # object. Use BatchProcessor#queue to submit all the jobs and then use # BatchProcessor#wait to wait for completion and to process the results. class BatchProcessor # Create a BatchProcessor object. +maxCpuCores+ limits the number of # simultaneously spawned processes. def initialize(maxCpuCores) @maxCpuCores = maxCpuCores # Jobs submitted by calling queue() are put in the @toRunQueue. The # pusher Thread will pick them up and fork them off into another # process. @toRunQueue = Queue.new # A hash that maps the JobInfo objects of running jobs by their PID. @runningJobs = { } # A list of jobs that wait to complete their writing. @spoolingJobs = [ ] # The wait() method will then clean the @toDropQueue, executes the post # processing block and removes all JobInfo related objects. @toDropQueue = Queue.new # A semaphore to guard accesses to @runningJobs, @spoolingJobs and # following shared data structures. @lock = Monitor.new # We count the submitted and completed jobs. The @jobsIn counter also # doubles as a unique job ID. @jobsIn = @jobsOut = 0 # An Array that holds all the IO objects to receive data from. @pipes = [] # A hash that maps IO objects to JobInfo objects @pipeToJob = {} # This global flag is set to true to signal the threads to terminate. @terminate = false # Sleep time of the threads when no data is pending. This value must be # large enough to allow for a context switch between the sending # (forked-off) process and this process. If it's too large, throughput # will suffer. @timeout = 0.02 Thread.abort_on_exception = true end # Add a new job the job queue. +tag+ is some data that the caller can use # to identify the job upon completion. +block+ is a Ruby code block to be # executed in a separate process. def queue(tag = nil, &block) raise 'You cannot call queue() while wait() is running!' if @jobsOut > 0 # If this is the first queued job for this run, we have to start the # helper threads. if @jobsIn == 0 # The JobInfo objects in the @toRunQueue are processed by the pusher # thread. It forkes off processes to execute the code block associated # with the JobInfo. @pusher = Thread.new { pusher } # The popper thread waits for terminated childs and picks up the # results. @popper = Thread.new { popper } # The grabber thread collects $stdout and $stderr data from each child # process and stores them in the corresponding JobInfo. @grabber = Thread.new { grabber } end # Create a new JobInfo object for the job and push it to the @toRunQueue. job = JobInfo.new(@jobsIn, block, tag) # Increase job counter @lock.synchronize { @jobsIn += 1 } @toRunQueue.push(job) end # Wait for all jobs to complete. The code block will get the JobInfo # objects for each job to pick up the results. def wait # Don't wait if there are no jobs. return if @jobsIn == 0 # When we have received as many jobs in the @toDropQueue than we have # started then we're done. while !@lock.synchronize { @jobsIn == @jobsOut } if @toDropQueue.empty? sleep(@timeout) else # We have completed jobs. while !@toDropQueue.empty? # Pop a job from the @toDropQueue and call the block with it. job = @toDropQueue.pop # Remove the job related entries from the housekeeping tables. @lock.synchronize { @jobsOut += 1 } # Call the post-processing block that was passed to wait() with # the JobInfo object as argument. yield(job) end end end # Signal threads to stop @terminate = true # Wait for treads to finish @pusher.join @popper.join @grabber.join # Reset some variables so we can reuse the object for further job runs. @jobsIn = @jobsOut = 0 @terminate = false # Make sure all data structures are empty and clean. check end private # This function runs in a separate thread to pop JobInfo items from the # @toRunQueue and create child processes for them. def pusher # Run until the terminate flag is set. until @terminate if @toRunQueue.empty? || @lock.synchronize{ @runningJobs.length >= @maxCpuCores } # We have no jobs in the @toRunQueue or all CPU cores in use already. sleep(@timeout) else @lock.synchronize do # Get a new job from the @toRunQueue job = @toRunQueue.pop job.openPipes # Add the receiver end of the pipe to the @pipes Array. @pipes << job.stdoutP # Map the pipe end to this JobInfo object. @pipeToJob[job.stdoutP] = job # Same for $stderr. @pipes << job.stderrP @pipeToJob[job.stderrP] = job pid = fork do # This is the child process now. Connect $stdout and $stderr to # the pipes. $stdout.reopen(job.stdoutC) job.stdoutC.close $stderr.reopen(job.stderrC) job.stderrC.close # Call the Ruby code block retVal = job.block.call # Send EOT character to mark the end of the text. $stdout.putc 4 $stdout.close $stderr.putc 4 $stderr.close # Now exit the child process and return the return value of the # block as process return value. exit retVal end job.pid = pid # Save the process ID in the PID to JobInfo hash. @runningJobs[pid] = job end end end end # This function runs in a separate thread to wait for completed jobs. It # waits for the process completion and stores the result in the # corresponding JobInfo object. def popper until @terminate if @runningJobs.empty? # No pending jobs, wait a bit. sleep(@timeout) else # Wait for the next job to complete. pid, retVal = Process.wait2 job = nil @lock.synchronize do # Get the JobInfo object that corresponds to the process ID. The # blocks passed to queue() or wait() may fork child processes as # well. If we get their PID, we can just ignore them. next if (job = @runningJobs[pid]).nil? # Remove the job from the @runningJobs Hash. @runningJobs.delete(pid) # Save the return value. job.retVal = retVal.dup if retVal.signaled? cleanPipes(job) # Aborted jobs will probably not send an EOT. So we fastrack # them to the toDropQueue. @toDropQueue.push(job) else # Push the job into the @spoolingJobs list to wait for it to # finish writing IO. @spoolingJobs << job end end end end end # This function runs in a separate thread to pick up the $stdout and # $stderr outputs of the child processes. It stores them in the JobInfo # object that corresponds to each child process. def grabber until @terminate # Wait for output in any of the pipes or a timeout. To make sure that # we get all output, we remain in the loop until the select() call # times out. res = nil begin @lock.synchronize do if (res = select(@pipes, nil, @pipes, @timeout)) # We have output data from at least one child. Check which pipe # actually triggered the select. res[0].each do |pipe| # Find the corresponding JobInfo object. job = @pipeToJob[pipe] # Store the output. if pipe == job.stdoutP # Look for the EOT character to signal the end of the text. if (c = pipe.getc) == ?\004 job.stdoutEOT = true else job.stdout << c end else if (c = pipe.getc) == ?\004 job.stderrEOT = true else job.stderr << c end end end end end sleep(@timeout) unless res end while res # Search the @spoolingJobs list for jobs that have completed IO and # push them to the @toDropQueue. @lock.synchronize do @spoolingJobs.each do |job| # Both stdout and stderr need to have reached the end of text. if job.stdoutEOT && job.stderrEOT @spoolingJobs.delete(job) cleanPipes(job) @toDropQueue.push(job) # Since we deleted a list item during an iterator run, we # terminate the iterator. break end end end end end def cleanPipes(job) @pipes.delete(job.stdoutP) @pipeToJob.delete(job.stdoutP) @pipes.delete(job.stderrP) @pipeToJob.delete(job.stderrP) job.stdoutC.close job.stdoutP.close job.stderrC.close job.stderrP.close job.stdoutC = job.stderrC = nil job.stdoutP = job.stderrP = nil end def check raise "toRunQueue not empty!" unless @toRunQueue.empty? raise "runningJobs list not empty!" unless @runningJobs.empty? raise "spoolingJobs list not empty!" unless @spoolingJobs.empty? raise "toDropQueue not empty!" unless @toDropQueue.empty? raise "pipe list not empty!" unless @pipes.empty? raise "pipe map not empty!" unless @pipeToJob.empty? end end end taskjuggler-3.5.0/lib/taskjuggler/PropertyTreeNode.rb0000644000175000017500000006204412614413013022241 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = PropertyTreeNode.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/MessageHandler' class TaskJuggler # This class is the base object for all Project properties. A Project property # is a e. g. a Task, a Resource or other objects. Such properties can be # arranged in tree form by assigning child properties to an existing property. # The parent object needs to exist at object creation time. The # PropertyTreeNode class holds all data and methods that are common to the # different types of properties. Each property can have a set of predifined # attributes. The PropertySet class holds collections of the same # PropertyTreeNode objects and the defined attributes. # Each PropertySet has a predefined set of attributes, but the attribute set # can be extended by the user. E.g. a task has the predefined attribute # 'start' and 'end' date. The user can extend tasks with a user defined # attribute like an URL that contains more details about the task. class PropertyTreeNode include MessageHandler attr_reader :propertySet, :id, :subId, :parent, :project, :sequenceNo, :children, :adoptees attr_accessor :name, :sourceFileInfo attr_reader :data # Create a new PropertyTreeNode object. _propertySet_ is the PropertySet # that this PropertyTreeNode object belongs to. The PropertySet determines # the attributes that are common to all Nodes in the set. _id_ is a String # that is unique in the namespace of the set. _name_ is a user readable, # short description of the object. _parent_ is the PropertyTreeNode that # sits above this node in the object hierachy. A root object has a _parent_ # of nil. For sets with hierachical name spaces, parent can be nil and # specified by a hierachical _id_ (e. g. 'father.son'). def initialize(propertySet, id, name, parent) @propertySet = propertySet @project = propertySet.project @parent = parent # Scenario specific data @data = nil # Attributes are created on-demand. We need to be careful that a pure # check for existance does not create them unecessarily. @attributes = Hash.new do |hash, attributeId| unless (aType = attributeDefinition(attributeId)) raise ArgumentError, "Unknown attribute '#{attributeId}' requested for " + "#{self.class.to_s.sub(/TaskJuggler::/, '')} '#{fullId}'" end unless aType.scenarioSpecific hash[attributeId] = aType.objClass.new(@propertySet, aType, self) else raise ArgumentError, "Attribute '#{attributeId}' is scenario specific" end end @scenarioAttributes = Array.new(@project.scenarioCount) do |scenarioIdx| Hash.new do |hash, attributeId| unless (aType = attributeDefinition(attributeId)) raise ArgumentError, "Unknown attribute '#{attributeId}' requested for " + "#{self.class.to_s.sub(/TaskJuggler::/, '')} '#{fullId}'" end if aType.scenarioSpecific hash[attributeId] = aType.objClass.new(@propertySet, aType, @data[scenarioIdx]) else raise ArgumentError, "Attribute '#{attributeId}' is not scenario specific" end end end # If _id_ is still nil, we generate a unique id. unless id tag = self.class.to_s.gsub(/TaskJuggler::/, '') id = '_' + tag + '_' + (propertySet.items + 1).to_s id = parent.fullId + '.' + id if !@propertySet.flatNamespace && parent end if !@propertySet.flatNamespace && id.include?('.') parentId = id[0..(id.rindex('.') - 1)] # Set parent to the parent property if it's still nil. @parent = @propertySet[parentId] unless @parent if $DEBUG if !@parent || !@propertySet[@parent.fullId] raise "Fatal Error: parent must be member of same property set" end if parentId != @parent.fullId raise "Fatal Error: parent (#{@parent.fullId}) and parent ID " + "(#{@parentId}) don't match" end end @subId = id[(id.rindex('.') + 1).. -1] else @subId = id end # The attribute 'id' is either the short ID or the full hierarchical ID. set('id', fullId) # The name of the property. @name = name set('name', name) @level = -1 @sourceFileInfo = nil @sequenceNo = @propertySet.items + 1 set('seqno', @sequenceNo) # This is a list of the real sub nodes of this PropertyTreeNode. @children = [] # This is a list of the adopted sub nodes of this PropertyTreeNode. @adoptees = [] # In case we have a parent object, we register this object as child of # the parent. if (@parent) @parent.addChild(self) end # This is a list of the PropertyTreeNode objects that have adopted this # node. @stepParents = [] end # We only use deep_clone for attributes, never for properties. Since # attributes may reference properties these references should remain # references. def deep_clone self end # We often use PTNProxy objects to represent PropertyTreeNode objects. The # proxy usually does a good job acting like a PropertyTreeNode. But in # some situations, we want to make sure to operate on the PropertyTreeNode # and not the PTNProxy. Both classes provide a ptn() method that always # return the PropertyTreeNode. def ptn self end # Adopt _property_ as a step child. Also register the new relationship # with the child. def adopt(property) # A property cannot adopt itself. if self == property error('adopt_self', 'A property cannot adopt itself') end # A top level task must never contain the same leaf task more then once! allOfRoot = root.all property.allLeaves.each do |adoptee| if allOfRoot.include?(adoptee) error('adopt_duplicate_child', "The task '#{adoptee.fullId}' has already been adopted by " + "property '#{root.fullId}' or any of its sub-properties.") end end @adoptees << property property.getAdopted(self) end # Get adopted by _property_. Also register the new relationship with the # step parent. This method is for internal use only. Other classes should # alway use PropertyTreeNode::adopt(). def getAdopted(property) # :nodoc: return if @stepParents.include?(property) @stepParents << property end # Return a list of all children including adopted kids. def kids @children + @adoptees end # Return a list of all parents including step parents. def parents (@parent ? [ @parent ] : []) + @stepParents end # This method creates a shallow copy of all attributes and returns them as # an Array that can be used with restoreAttributes(). def backupAttributes [ @attributes.clone, @scenarioAttributes.clone ] end # Restore the attributes to a previously saved state. _backup_ is an Array # generated by backupAttributes(). def restoreAttributes(backup) @attributes, @scenarioAttributes = backup end # Remove any references in the stored data that references the _property_. def removeReferences(property) @children.delete(property) @adoptees.delete(property) @stepParents.delete(property) end # Return the index of the child _node_. def levelSeqNo(node) @children.index(node) + 1 end # Inherit values for the attributes from the parent node or the Project. def inheritAttributes # Inherit non-scenario-specific values @propertySet.eachAttributeDefinition do |attrDef| next if attrDef.scenarioSpecific || !attrDef.inheritedFromParent aId = attrDef.id if parent # Inherit values from parent property if parent.provided(aId) || parent.inherited(aId) @attributes[aId].inherit(parent.get(aId)) end else # Inherit selected values from project if top-level property if attrDef.inheritedFromProject if @project[aId] @attributes[aId].inherit(@project[aId]) end end end end # Inherit scenario-specific values @propertySet.eachAttributeDefinition do |attrDef| next if !attrDef.scenarioSpecific || !attrDef.inheritedFromParent @project.scenarioCount.times do |scenarioIdx| if parent # Inherit scenario specific values from parent property if parent.provided(attrDef.id, scenarioIdx) || parent.inherited(attrDef.id, scenarioIdx) @scenarioAttributes[scenarioIdx][attrDef.id].inherit( parent[attrDef.id, scenarioIdx]) end else # Inherit selected values from project if top-level property if attrDef.inheritedFromProject if @project[attrDef.id] && @scenarioAttributes[scenarioIdx][attrDef.id] @scenarioAttributes[scenarioIdx][attrDef.id].inherit( @project[attrDef.id]) end end end end end end # Returns a list of this node and all transient sub nodes. def all res = [ self ] kids.each do |c| res = res.concat(c.all) end res end # Return a list of all leaf nodes of this node. def allLeaves(withoutSelf = false) res = [] if leaf? res << self unless withoutSelf else kids.each do |c| res += c.allLeaves end end res end def logicalId fullId end # Return the full id of this node. For PropertySet objects with a flat # namespace, this is just the ID. Otherwise, the full ID is composed of all # IDs from the root node to this node, separating the IDs by a dot. def fullId res = @subId unless @propertySet.flatNamespace t = self until (t = t.parent).nil? res = t.subId + "." + res end end res end # Returns the level that this property is on. Top-level properties return # 0, their children 1 and so on. This value is cached internally, so it does # not have to be calculated each time the function is called. def level return @level if @level >= 0 t = self @level = 0 until (t = t.parent).nil? @level += 1 end @level end # Return the hierarchical index of this node. In project management lingo # this is called the Breakdown Structure Index (BSI). The result is an Array # with an index for each level from the root to this node. def getBSIndicies idcs = [] p = self begin parent = p.parent idcs.insert(0, parent ? parent.levelSeqNo(p) : @propertySet.levelSeqNo(p)) p = parent end while p idcs end # Return the 'index' attributes of this property, prefixed by the 'index' # attributes of all its parents. The result is an Array of Fixnums. def getIndicies idcs = [] p = self begin parent = p.parent idcs.insert(0, p.get('index')) p = parent end while p idcs end # Add _child_ node as child to this node. def addChild(child) if $DEBUG && child.propertySet != @propertySet raise "Child nodes must belong to the same property set as the parent" end @children.push(child) end # Find out if this property is a direct or indirect child of _ancestor_. def isChildOf?(ancestor) parent = self while parent = parent.parent return true if (parent == ancestor) end false end # Return true if the node is a leaf node (has no children). def leaf? @children.empty? && @adoptees.empty? end # Return true if the node has children. def container? !@children.empty? || !@adoptees.empty? end # Return a list with all parent nodes of this node. def ancestors(includeStepParents = false) nodes = [] if includeStepParents parents.each do |parent| nodes << parent nodes += parent.ancestors(true) end else n = self while n.parent nodes << (n = n.parent) end end nodes end # Return the top-level node for this node. def root n = self while n.parent n = n.parent end n end # Return the type of the attribute with ID _attributeId_. def attributeDefinition(attributeId) @propertySet.attributeDefinitions[attributeId] end # Return the value of the non-scenario-specific attribute with ID # _attributeId_. This method works for built-in attributes as well. # In case the attribute does not exist, an exception is raised. def get(attributeId) # Make sure the attribute gets created if it doesn't exist already. @attributes[attributeId] instance_variable_get(('@' + attributeId).intern) end # Return the value of the attribute with ID _attributeId_. This method # works for built-in attributes as well. In case this is a # scenario-specific attribute, the scenario index needs to be provided by # _scenarioIdx_, otherwise it must be nil. In case the attribute does not # exist, an exception is raised. def getAttribute(attributeId, scenarioIdx = nil) if scenarioIdx @scenarioAttributes[scenarioIdx][attributeId] else @attributes[attributeId] end end # Set the non-scenario-specific attribute with ID _attributeId_ to # _value_. No further checks are done. def force(attributeId, value) @attributes[attributeId].set(value) end # Set the non-scenario-specific attribute with ID _attributeId_ to _value_. # In case an already provided value is overwritten again, an exeception is # raised. def set(attributeId, value) attr = @attributes[attributeId] # Assignments to list attributes always append. We don't # consider this an overwrite. overwrite = attr.provided && !attr.isList? attr.set(value) # We only raise the overwrite error after the value has been set. if overwrite raise AttributeOverwrite, "Overwriting a previously provided value for attribute " + "#{attributeId}" end end # Set the scenario specific attribute with ID _attributeId_ for the # scenario with index _scenario_ to _value_. If _scenario_ is nil, the # attribute must not be scenario specific. In case the attribute does not # exist, an exception is raised. def []=(attributeId, scenario, value) overwrite = false if scenario if AttributeBase.mode == 0 # If we get values in 'provided' mode, we copy them immedidately to # all derived scenarios. @project.scenario(scenario).all.each do |sc| scenarioIdx = @project.scenarioIdx(sc) attr = @scenarioAttributes[scenarioIdx][attributeId] if attr.provided && !attr.isList? # Assignments to list attributes always append. We don't # consider this an overwrite. overwrite = true end if scenarioIdx == scenario attr.set(value) else attr.inherit(value) end end else attr = @scenarioAttributes[scenario][attributeId] overwrite = attr.provided && !attr.isList? attr.set(value) end else attr = @attributes[attributeId] overwrite = attr.provided && !attr.isList? attr.set(value) end # We only raise the overwrite error after all scenarios have been # set. For some attributes the overwrite is actually allowed. if overwrite raise AttributeOverwrite, "Overwriting a previously provided value for attribute " + "#{attributeId}" end end # Return the value of the attribute with ID _attributeId_. For # scenario-specific attributes, _scenario_ must indicate the index of the # Scenario. def [](attributeId, scenario) @scenarioAttributes[scenario][attributeId] @data[scenario].instance_variable_get(('@' + attributeId).intern) end # Returns true if the value of the attribute _attributeId_ (in scenario # _scenarioIdx_) has been provided by the user. def provided(attributeId, scenarioIdx = nil) if scenarioIdx unless @scenarioAttributes[scenarioIdx].has_key?(attributeId) return false end @scenarioAttributes[scenarioIdx][attributeId].provided else return false unless @attributes.has_key?(attributeId) @attributes[attributeId].provided end end # Returns true if the value of the attribute _attributeId_ (in scenario # _scenarioIdx_) has been inherited from a parent node or scenario. def inherited(attributeId, scenarioIdx = nil) if scenarioIdx unless @scenarioAttributes[scenarioIdx].has_key?(attributeId) return false end @scenarioAttributes[scenarioIdx][attributeId].inherited else return false unless @attributes.has_key?(attributeId) @attributes[attributeId].inherited end end def modified?(attributeId, scenarioIdx = nil) if scenarioIdx unless @scenarioAttributes[scenarioIdx].has_key?(attributeId) return false end @scenarioAttributes[scenarioIdx][attributeId].provided || @scenarioAttributes[scenarioIdx][attributeId].inherited else return false unless @attributes.has_key?(attributeId) @attributes[attributeId].provided || @attributes[attributeId].inherited end end def checkFailsAndWarnings if @attributes.has_key?('fail') || @attributes.has_key?('warn') propertyType = case self when Task 'task' when Resource 'resource' else 'unknown' end queryAttrs = { 'project' => @project, 'property' => self, 'scopeProperty' => nil, 'start' => @project['start'], 'end' => @project['end'], 'loadUnit' => :days, 'numberFormat' => @project['numberFormat'], 'timeFormat' => nil, 'currencyFormat' => @project['currencyFormat'] } query = Query.new(queryAttrs) if @attributes['fail'] @attributes['fail'].get.each do |expr| if expr.eval(query) error("#{propertyType}_fail_check", "User defined check failed for #{propertyType} " + "#{fullId} \n" + "Condition: #{expr.to_s}\n" + "Result: #{expr.to_s(query)}") end end end if @attributes['warn'] @attributes['warn'].get.each do |expr| if expr.eval(query) warning("#{propertyType}_warn_check", "User defined warning triggered for #{propertyType} " + "#{fullId} \n" + "Condition: #{expr.to_s}\n" + "Result: #{expr.to_s(query)}") end end end end end def query_journal(query) @project['journal'].to_rti(query) end def query_alert(query) journal = @project['journal'] query.sortable = query.numerical = alert = journal.alertLevel(query.end, self, query) alertLevel = @project['alertLevels'][alert] query.string = alertLevel.name rText = "#{alertLevel.name}" + "" unless (rti = RichText.new(rText, RTFHandlers.create(@project)). generateIntermediateFormat) warning('ptn_journal', "Syntax error in journal message") return nil end rti.blockMode = false query.rti = rti end def query_alertmessages(query) journalMessages(@project['journal'].alertEntries(query.end, self, 1, query.start, query), query, true) end def query_alertsummaries(query) journalMessages(@project['journal'].alertEntries(query.end, self, 1, query.start, query), query, false) end def query_journalmessages(query) journalMessages(@project['journal'].currentEntries(query.end, self, 0, query.start, query.hideJournalEntry), query, true) end def query_journalsummaries(query) journalMessages(@project['journal'].currentEntries(query.end, self, 0, query.start, query.hideJournalEntry), query, false) end def query_alerttrend(query) journal = @project['journal'] startAlert = journal.alertLevel(query.start, self, query) endAlert = journal.alertLevel(query.end, self, query) if startAlert < endAlert query.sortable = 0 query.string = 'Up' elsif startAlert > endAlert query.sortable = 2 query.string = 'Down' else query.sortable = 1 query.string = 'Flat' end end # Dump the class data in human readable form. Used for debugging only. def to_s # :nodoc: res = "#{self.class} #{fullId} \"#{@name}\"\n" + " Sequence No: #{@sequenceNo}\n" res += " Parent: #{@parent.fullId}\n" if @parent children = "" @children.each do |c| children += ', ' unless children.empty? children += c.fullId end res += ' Children: ' + children + "\n" unless children.empty? @attributes.sort.each do |key, attr| res += indent(" #{key}: ", attr.to_s) end unless @scenarioAttributes.empty? project.scenarioCount.times do |sc| break if @scenarioAttributes[sc].nil? headerShown = false @scenarioAttributes[sc].sort.each do |key, attr| unless headerShown res += " Scenario #{project.scenario(sc).get('id')} (#{sc})\n" headerShown = true end res += indent(" #{key}: ", attr.to_s) end end end res += '-' * 75 + "\n" end # Many PropertyTreeNode functions are scenario specific. These functions are # provided by the class *Scenario classes. In case we can't find a function # called for the base class we try to find it in corresponding *Scenario # class. def method_missing(func, scenarioIdx, *args, &block) @data[scenarioIdx].send(func, *args, &block) end private # Create a blog-style list of all alert messages that match the Query. def journalMessages(entries, query, longVersion) # The components of the message are either UTF-8 text or RichText. For # the RichText components, we use the originally provided markup since # we compose the result as RichText markup first. rText = '' entries.each do |entry| rText += "==== " + entry.headline + " ====\n" rText += "''Reported on #{entry.date.to_s(query.timeFormat)}'' " if entry.author rText += "''by #{entry.author.name}''" end rText += "\n\n" unless entry.flags.empty? rText += "''Flags:'' #{entry.flags.join(', ')}\n\n" end if entry.summary rText += entry.summary.richText.inputText + "\n\n" end if longVersion && entry.details rText += entry.details.richText.inputText + "\n\n" end end # Now convert the RichText markup String into RichTextIntermediate # format. unless (rti = RichText.new(rText, RTFHandlers.create(@project)). generateIntermediateFormat) warning('ptn_journal', "Syntax error in journal message") return nil end # No section numbers, please! rti.sectionNumbers = false # We use a special class to allow CSS formating. rti.cssClass = 'tj_journal' query.rti = rti end def indent(tag, str) tag + str.gsub(/\n/, "\n#{' ' * tag.length}") + "\n" end end end taskjuggler-3.5.0/lib/taskjuggler/Scoreboard.rb0000644000175000017500000001335112614413013021047 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Scoreboard.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/IntervalList' class TaskJuggler # Scoreboard objects are instrumental during the scheduling process. The # project time span is divided into discrete time slots by the scheduling # resolution. This class models the resulting time slots with an array that # spans from project start o project end. Each slot has an index start with 0 # at the project start. class Scoreboard attr_reader :startDate, :endDate, :resolution, :size # Create the scoreboard based on the the given _startDate_, _endDate_ and # timing _resolution_. The resolution must be specified in seconds. # Optionally you can provide an initial value for the scoreboard cells. def initialize(startDate, endDate, resolution, initVal = nil) @startDate = startDate @endDate = endDate @resolution = resolution @size = ((endDate - startDate) / resolution).ceil + 1 clear(initVal) end # Erase all values and set them to nil or a new initial value. def clear(initVal = nil) @sb = Array.new(@size, initVal) end # Converts a scroreboard index to the corresponding date. You can optionally # sanitize the _idx_ value by forcing it into the project range. def idxToDate(idx, forceIntoProject = false) if forceIntoProject return @startDate if kdx < 0 return @endDate if @size - 1 if idx >= @size elsif idx < 0 || idx >= @size raise "Index #{idx} is out of scoreboard range (#{size - 1})" end @startDate + idx * @resolution end # Converts a date to the corresponding scoreboard index. You can optionally # sanitize the _date_ by forcing it into the project time span. def dateToIdx(date, forceIntoProject = true) idx = ((date - @startDate) / @resolution).to_i if forceIntoProject return 0 if idx < 0 return @size - 1 if idx >= @size elsif (idx < 0 || idx >= @size) raise "Date #{date} is out of project time range " + "(#{@startDate} - #{@endDate})" end idx end # Iterate over all scoreboard entries. def each(startIdx = 0, endIdx = @size) if startIdx != 0 || endIdx != @size startIdx.upto(endIdx - 1) do |i| yield @sb[i] end else @sb.each do |entry| yield entry end end end # Iterate over all scoreboard entries by index. def each_index @sb.each_index do |index| yield index end end # Assign result of block to each element. def collect! @sb.collect! { |x| yield x } end # Get the value at index _idx_. def [](idx) @sb[idx] end # Set the _value_ at index _idx_. def []=(idx, value) @sb[idx] = value end # Get the value corresponding to _date_. def get(date) @sb[dateToIdx(date)] end # Set the _value_ corresponding to _date_. def set(date, value) @sb[dateToIdx(date)] = value end # Return a list of intervals that describe a contiguous part of the # scoreboard that contains only the values that yield true for the passed # block. The intervals must be within the interval described by _iv_ and # must be at least _minDuration_ long. The return value is an # IntervalList. def collectIntervals(iv, minDuration) # Determine the start and stop index for the scoreboard search. We save # the original values for later use as well. startIdx = sIdx = dateToIdx(iv.start) endIdx = eIdx = dateToIdx(iv.end) # Convert the minDuration into number of slots. minDuration /= @resolution minDuration = 1 if minDuration <= 0 # Expand the interval with the minDuration to both sides. This will # reduce the failure to detect intervals at the iv boundary. However, # this will not prevent undetected intervals at the project time frame # boundaries. startIdx -= minDuration startIdx = 0 if startIdx < 0 endIdx += minDuration endIdx = @size - 1 if endIdx > @size - 1 # This is collects the resulting intervals. intervals = IntervalList.new # The duration counter for the currently analyzed interval and the start # index. duration = start = 0 idx = startIdx loop do # Check whether the scoreboard slot matches any of the target values # and we have not yet reached the last slot. if yield(@sb[idx]) && idx < endIdx # If so, save the start position if this is the first slot and start # counting the matching slots. start = idx if start == 0 duration += 1 else # If we don't have a match or are at the end of the interval, check # if we've just finished a matching interval. if duration > 0 if duration >= minDuration # Make sure that all intervals are within the originally # requested Interval. start = sIdx if start < sIdx idx = eIdx if idx > eIdx intervals << TimeInterval.new(idxToDate(start), idxToDate(idx)) end duration = start = 0 end end break if (idx += 1) > endIdx end intervals end def inspect 0.upto(@sb.length - 1) do |i| puts "#{idxToDate(i)}: #{@sb[i]}" end end end end taskjuggler-3.5.0/lib/taskjuggler/Project.rb0000644000175000017500000014273212614413013020400 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Project.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TjException' require 'taskjuggler/MessageHandler' require 'taskjuggler/FileList' require 'taskjuggler/TjTime' require 'taskjuggler/AlertLevelDefinitions' require 'taskjuggler/AccountCredit' require 'taskjuggler/Booking' require 'taskjuggler/DataCache' require 'taskjuggler/LeaveList' require 'taskjuggler/PropertySet' require 'taskjuggler/Attributes' require 'taskjuggler/RealFormat' require 'taskjuggler/PropertyList' require 'taskjuggler/TaskDependency' require 'taskjuggler/Scenario' require 'taskjuggler/Shift' require 'taskjuggler/Account' require 'taskjuggler/Task' require 'taskjuggler/Resource' require 'taskjuggler/reports/Report' require 'taskjuggler/ShiftAssignments' require 'taskjuggler/WorkingHours' require 'taskjuggler/TimeSheets' require 'taskjuggler/ProjectFileParser' require 'taskjuggler/BatchProcessor' require 'taskjuggler/Journal' require 'taskjuggler/KeywordArray' class TaskJuggler # This class implements objects that hold all project properties. Project # generally consist of resources, tasks and a number of other optional # properties. Tasks, Resources, Accounts and Shifts are all build on the # same underlying storage class PropertyTreeNode. Properties of the same # kind are kept in PropertySet objects. There is only one PropertySet for # each type of property. Additionally, each property may belong to various # PropertyList objects. In contrast to PropertySet objects, PropertyList # object have well defined sorting order and no information about the # attributes of each type of property. The PropertySet holds the blueprints # for the data construction inside the PropertyTreeNode objects. It contains # the list of known Attributes. class Project include MessageHandler attr_reader :accounts, :shifts, :tasks, :resources, :scenarios, :timeSheets, :reports, :inputFiles attr_accessor :reportContexts, :outputDir, :warnTsDeltas # Create a project with the specified +id+, +name+ and +version+. # The constructor will set default values for all project attributes. def initialize(id, name, version) AttributeBase.setMode(0) @attributes = { # This nested Array defines the supported alert levels. The lowest # level comes first at index 0 and the level rises from there on. # Currently, these levels are hardcoded. Each level entry has 3 # members: the tjp syntax token, the user visible name and the # associated color as RGB byte array. 'alertLevels' => AlertLevelDefinitions.new, 'auxdir' => '', 'copyright' => nil, 'costaccount' => nil, 'currency' => "EUR", 'currencyFormat' => RealFormat.new([ '-', '', '', ',', 2 ]), 'dailyworkinghours' => 8.0, 'end' => nil, 'flags' => [], 'journal' => Journal.new, 'limits' => nil, 'leaves' => LeaveList.new, 'loadUnit' => :days, 'name' => name, 'navigators' => {}, 'now' => TjTime.new.align(3600), 'numberFormat' => RealFormat.new([ '-', '', '', '.', 1]), 'priority' => 500, 'projectid' => id || "prj", 'projectids' => [ id ], 'rate' => 0.0, 'revenueaccount' => nil, 'scheduleGranularity' => Project.maxScheduleGranularity, 'shortTimeFormat' => "%H:%M", 'start' => nil, 'timeFormat' => "%Y-%m-%d", 'timezone' => TjTime.timeZone, 'trackingScenarioIdx' => nil, 'version' => version || "1.0", 'weekStartsMonday' => true, 'workinghours' => nil, 'yearlyworkingdays' => 260.714 } # Before we can add any properties to this project, we need to define the # attributes that each of the property types will be using. In TaskJuggler # lingo, properties of a project are resources, tasks, accounts, shifts # and scenarios. Each of these properties can have lots of further # information attached to it. These bits of information are called # attributes. An attribute is defined by the AttributeDefinition class. # The PropertySet objects need to be fed with a list of such attribute # definitions to register the attributes with the properties. @scenarios = PropertySet.new(self, true) attrs = [ # ID Name Type # Inh. Inh.Prj Scen. Default [ 'active', 'Enabled', BooleanAttribute, true, false, false, true ], [ 'id', 'ID', StringAttribute, false, false, false, nil ], [ 'name', 'Name', StringAttribute, false, false, false, nil ], [ 'ownbookings', 'Own Bookings', BooleanAttribute, false, false, false, true ], [ 'projection', 'Projection Mode', BooleanAttribute, true, false, false, false ], [ 'seqno', 'No', FixnumAttribute, false, false, false, nil ], ] attrs.each { |a| @scenarios.addAttributeType(AttributeDefinition.new(*a)) } @shifts = PropertySet.new(self, true) attrs = [ # ID Name Type # Inh. Inh.Prj Scen. Default [ 'bsi', 'BSI', StringAttribute, false, false, false, "" ], [ 'id', 'ID', StringAttribute, false, false, false, nil ], [ 'index', 'Index', FixnumAttribute, false, false, false, -1 ], [ 'leaves', 'Leaves', LeaveListAttribute, true, true, true, LeaveList.new ], [ 'name', 'Name', StringAttribute, false, false, false, nil ], [ 'replace', 'Replace', BooleanAttribute, true, false, true, false ], [ 'seqno', 'No', FixnumAttribute, false, false, false, nil ], [ 'timezone', 'Time Zone', StringAttribute, true, true, true, TjTime.timeZone ], [ 'tree', 'Tree Index', StringAttribute, false, false, false, "" ], [ 'workinghours', 'Working Hours', WorkingHoursAttribute, true, true, true, nil ] ] attrs.each { |a| @shifts.addAttributeType(AttributeDefinition.new(*a)) } @accounts = PropertySet.new(self, true) attrs = [ # ID Name Type # Inh. Inh.Prj Scen. Default [ 'aggregate', 'Aggregate', SymbolAttribute, true, false, false, :tasks ], [ 'bsi', 'BSI', StringAttribute, false, false, false, "" ], [ 'credits', 'Credits', AccountCreditListAttribute, false, false, true, [] ], [ 'id', 'ID', StringAttribute, false, false, false, nil ], [ 'index', 'Index', FixnumAttribute, false, false, false, -1 ], [ 'flags', 'Flags', FlagListAttribute, true, false, true, [] ], [ 'name', 'Name', StringAttribute, false, false, false, nil ], [ 'seqno', 'No', FixnumAttribute, false, false, false, nil ], [ 'tree', 'Tree Index', StringAttribute, false, false, false, "" ] ] attrs.each { |a| @accounts.addAttributeType(AttributeDefinition.new(*a)) } @resources = PropertySet.new(self, true) attrs = [ # ID Name Type # Inh. Inh.Prj Scen. Default [ 'alloctdeffort', 'Alloctd. Effort', FloatAttribute, false, false, true, 0.0 ], [ 'bsi', 'BSI', StringAttribute, false, false, false, "" ], [ 'chargeset', 'Charge Sets', ChargeSetListAttribute, true, false, true, [] ], [ 'criticalness', 'Criticalness', FloatAttribute, false, false, true, 0.0 ], [ 'duties', 'Duties', TaskListAttribute, false, false, true, [] ], [ 'directreports', 'Direct Reports', ResourceListAttribute, false, false, true, [] ], [ 'efficiency','Efficiency', FloatAttribute, true, false, true, 1.0 ], [ 'effort', 'Total Effort', FixnumAttribute, false, false, true, 0 ], [ 'email', 'Email', StringAttribute, false, false, false, nil ], [ 'fail', 'Failure Conditions', LogicalExpressionListAttribute, false, false, false, [] ], [ 'flags', 'Flags', FlagListAttribute, true, false, true, [] ], [ 'index', 'Index', FixnumAttribute, false, false, false, -1 ], [ 'leaveallowances', 'Leave Allowances', LeaveAllowanceListAttribute, true, false, true, LeaveAllowanceList.new ], [ 'leaves', 'Leaves', LeaveListAttribute, true, true, true, LeaveList.new ], [ 'limits', 'Limits', LimitsAttribute, true, true, true, nil ], [ 'managers', 'Managers', ResourceListAttribute, true, false, true, [] ], [ 'rate', 'Rate', FloatAttribute, true, true, true, 0.0 ], [ 'reports', 'Reports', ResourceListAttribute, false, false, true, [] ], [ 'seqno', 'No', FixnumAttribute, false, false, false, nil ], [ 'shifts', 'Shifts', ShiftAssignmentsAttribute, true, false, true, nil ], [ 'tree', 'Tree Index', StringAttribute, false, false, false, "" ], [ 'warn', 'Warning Condition', LogicalExpressionListAttribute, false, false, false, [] ], [ 'workinghours', 'Working Hours', WorkingHoursAttribute, true, true, true, nil ] ] attrs.each { |a| @resources.addAttributeType(AttributeDefinition.new(*a)) } @tasks = PropertySet.new(self, false) attrs = [ # ID Name Type # Inh. Inh.Prj Scen. Default [ 'allocate', 'Allocations', AllocationAttribute, true, false, true, [] ], [ 'assignedresources', 'Assigned Resources', ResourceListAttribute, false, false, true, [] ], [ 'booking', 'Bookings', BookingListAttribute, false, false, true, [] ], [ 'bsi', 'BSI', StringAttribute, false, false, false, "" ], [ 'charge', 'Charges', ChargeListAttribute, false, false, true, [] ], [ 'chargeset', 'Charge Sets', ChargeSetListAttribute, true, false, true, [] ], [ 'complete', 'Completion', FloatAttribute, false, false, true, nil ], [ 'competitors', 'Competitors', TaskListAttribute, false, false, true, [] ], [ 'criticalness', 'Criticalness', FloatAttribute, false, false, true, 0.0 ], [ 'depends', 'Preceding tasks', DependencyListAttribute, true, false, true, [] ], [ 'duration', 'Duration', DurationAttribute, false, false, true, 0 ], [ 'effort', 'Effort', DurationAttribute, false, false, true, 0 ], [ 'end', 'End', DateAttribute, false, false, true, nil ], [ 'endpreds', 'End Preds.', TaskDepListAttribute, false, false, true, [] ], [ 'endsuccs', 'End Succs.', TaskDepListAttribute, false, false, true, [] ], [ 'fail', 'Failure Conditions', LogicalExpressionListAttribute, false, false, false, [] ], [ 'flags', 'Flags', FlagListAttribute, true, false, true, [] ], [ 'forward', 'Scheduling', BooleanAttribute, true, false, true, true ], [ 'gauge', 'Schedule gauge', StringAttribute, false, false, true, nil ], [ 'id', 'ID', StringAttribute, false, false, false, nil ], [ 'index', 'Index', FixnumAttribute, false, false, false, -1 ], [ 'length', 'Length', DurationAttribute, false, false, true, 0 ], [ 'limits', 'Limits', LimitsAttribute, false, false, true, nil ], [ 'maxend', 'Max. End', DateAttribute, true, false, true, nil ], [ 'maxstart', 'Max. Start', DateAttribute, false, false, true, nil ], [ 'milestone', 'Milestone', BooleanAttribute, false, false, true, false ], [ 'minend', 'Min. End', DateAttribute, false, false, true, nil ], [ 'minstart', 'Min. Start', DateAttribute, true, false, true, nil ], [ 'name', 'Name', StringAttribute, false, false, false, nil ], [ 'note', 'Note', RichTextAttribute, false, false, false, nil ], [ 'pathcriticalness', 'Path Criticalness', FloatAttribute, false, false, true, 0.0 ], [ 'precedes', 'Following tasks', DependencyListAttribute, true, false, true, [] ], [ 'priority', 'Priority', FixnumAttribute, true, true, true, 500 ], [ 'projectid', 'Project ID', SymbolAttribute, true, true, true, nil ], [ 'responsible', 'Responsible', ResourceListAttribute, true, false, true, [] ], [ 'scheduled', 'Scheduled', BooleanAttribute, true, false, true, false ], [ 'seqno', 'No', FixnumAttribute, false, false, false, nil ], [ 'shifts', 'Shifts', ShiftAssignmentsAttribute, true, false, true, nil ], [ 'start', 'Start', DateAttribute, false, false, true, nil ], [ 'startpreds', 'Start Preds.', TaskDepListAttribute, false, false, true, [] ], [ 'startsuccs', 'Start Succs.', TaskDepListAttribute, false, false, true, [] ], [ 'status', 'Task Status', StringAttribute, false, false, true, "" ], [ 'tree', 'Tree Index', StringAttribute, false, false, false, "" ], [ 'warn', 'Warning Condition', LogicalExpressionListAttribute, false, false, false, [] ] ] attrs.each { |a| @tasks.addAttributeType(AttributeDefinition.new(*a)) } @reports = PropertySet.new(self, false) attrs = [ # ID Name Type # Inh. Inh.Prj Scen. Default [ 'accountroot', 'Account Root', PropertyAttribute, true, false, false, nil ], [ 'auxdir', 'Auxiliary files directory', StringAttribute, true, true, false, '' ], [ 'bsi', 'BSI', StringAttribute, false, false, false, '' ], [ 'caption', 'Caption', RichTextAttribute, true, false, false, nil ], [ 'center', 'Center', RichTextAttribute, true, false, false, nil ], [ 'columns', 'Columns', ColumnListAttribute, true, false, false, [] ], [ 'costaccount', 'Cost Account', AccountAttribute, true, true, false, nil ], [ 'currencyFormat', 'Currency Format', RealFormatAttribute, true, true, false, nil ], [ 'definitions', 'Definitions', DefinitionListAttribute, true, false, false, KeywordArray.new([ '*' ]) ], [ 'end', 'End', DateAttribute, true, true, false, nil ], [ 'epilog', 'Epilog', RichTextAttribute, true, false, false, nil ], [ 'flags', 'Flags', FlagListAttribute, true, false, true, [] ], [ 'footer', 'Footer', RichTextAttribute, true, false, false, nil ], [ 'formats', 'Formats', FormatListAttribute, true, false, false, [] ], [ 'ganttBars', 'Gantt Bars', BooleanAttribute, true, false, false, true ], [ 'header', 'Header', RichTextAttribute, true, false, false, nil ], [ 'headline', 'Headline', RichTextAttribute, true, false, false, nil ], [ 'hideAccount', 'Hide Account', LogicalExpressionAttribute, true, false, false, nil ], [ 'hideJournalEntry', 'Hide JournalEntry', LogicalExpressionAttribute, true, false, false, nil ], [ 'hideResource', 'Hide Resource', LogicalExpressionAttribute, true, false, false, nil ], [ 'hideTask', 'Hide Task', LogicalExpressionAttribute, true, false, false, nil ], [ 'height', 'Height', FixnumAttribute, false, false, false, 480 ], [ 'id', 'ID', StringAttribute, false, false, false, nil ], [ 'index', 'Index', FixnumAttribute, false, false, false, -1 ], [ 'interactive', 'Interactive', BooleanAttribute, false, false, false, false ], [ 'journalAttributes', 'Journal Attributes', SymbolListAttribute, true, false, false, KeywordArray.new([ '*' ]) ], [ 'journalMode', 'Journal Mode', SymbolAttribute, true, false, false, :journal ], [ 'left', 'Left', RichTextAttribute, true, false, false, nil ], [ 'loadUnit', 'Load Unit', StringAttribute, true, true, false, nil ], [ 'name', 'Name', StringAttribute, false, false, false, nil ], [ 'now', 'Now', DateAttribute, true, true, false, nil ], [ 'numberFormat', 'Number Format', RealFormatAttribute, true, true, false, nil ], [ 'openNodes', 'Open Nodes', NodeListAttribute, false, false, false, nil ], [ 'prolog', 'Prolog', RichTextAttribute, true, false, false, nil ], [ 'rawHtmlHead', 'Raw HTML Header', StringAttribute, true, false, false, nil ], [ 'resourceAttributes', 'Resource Attributes', FormatListAttribute, true, false, false, KeywordArray.new([ '*' ]) ], [ 'resourceroot', 'resource Root', PropertyAttribute, true, false, false, nil ], [ 'revenueaccount', 'Revenue Account', AccountAttribute, true, true, false, nil ], [ 'right', 'Right', RichTextAttribute, true, false, false, nil ], [ 'rollupAccount', 'Rollup Account', LogicalExpressionAttribute, true, false, false, nil ], [ 'rollupResource', 'Rollup Resource', LogicalExpressionAttribute, true, false, false, nil ], [ 'rollupTask', 'Rollup Task', LogicalExpressionAttribute, true, false, false, nil ], [ 'scenarios', 'Scenarios', ScenarioListAttribute, true, false, false, [ 0 ] ], [ 'selfcontained', 'Selfcontained', BooleanAttribute, true, false, false, false ], [ 'seqno', 'No', FixnumAttribute, false, false, false, nil ], [ 'shortTimeFormat', 'Short Time Format', StringAttribute, true, true, false, nil ], [ 'sortAccounts', 'Sort Accounts', SortListAttribute, true, false, false, [[ 'seqno', true, -1 ]] ], [ 'sortJournalEntries', 'Sort Journal Entries', JournalSortListAttribute, true, false, false, [[ :alert, 1 ], [ :date, 1 ], [ :seqno, 1 ]] ], [ 'sortResources', 'Sort Resources', SortListAttribute, true, false, false, [[ 'seqno', true, -1 ]] ], [ 'sortTasks', 'Sort Tasks', SortListAttribute, true, false, false, [[ 'seqno', true, -1 ]] ], [ 'start', 'Start', DateAttribute, true, true, false, nil ], [ 'taskAttributes', 'Task Attributes', FormatListAttribute, true, false, false, KeywordArray.new([ '*' ]) ], [ 'taskroot', 'Task Root', PropertyAttribute, true, false, false, nil ], [ 'timeFormat', 'Time Format', StringAttribute, true, true, false, nil ], [ 'timeOffId', 'Time Off ID', StringAttribute, false, false, false, nil ], [ 'timeOffName', 'Time Off Name', StringAttribute, false, false, false, nil ], [ 'timezone', 'Time Zone', StringAttribute, true, true, false, TjTime.timeZone ], [ 'title', 'Title', StringAttribute, true, false, false, nil ], [ 'tree', 'Tree Index', StringAttribute, false, false, false, "" ], [ 'weekStartsMonday', 'Week Starts Monday', BooleanAttribute, true, true, false, false ], [ 'width', 'Width', FixnumAttribute, true, false, false, 640 ] ] attrs.each { |a| @reports.addAttributeType(AttributeDefinition.new(*a)) } Scenario.new(self, 'plan', 'Plan Scenario', nil) # A list of files that contained the project data. @inputFiles = FileList.new @timeSheets = TimeSheets.new # A scoreboard that reflects the global working hours and leaves. @scoreboard = nil # A scoreboard that reflects the global working hours but no leaves. @scoreboardNoLeaves = nil # The ReportContext provides additional settings to the report that can # complement or replace the report attributes. Reports can include other # reports. During report generation, only one context is active, but the # context of enclosing reports needs to be preserved. Therefor we use a # stack to implement this. @reportContexts = [] @outputDir = './' @warnTsDeltas = false end # Overload the deep_clone function so that references to the project don't # lead to deep copying of the whole project. def deep_clone self end # Query the value of a Project attribute. _name_ is the ID of the attribute. def [](name) if !@attributes.has_key?(name) raise "Unknown project attribute #{name}" end @attributes[name] end # Set the Project attribute with ID _name_ to _value_. def []=(name, value) if !@attributes.has_key?(name) raise "Unknown project attribute #{name}" end @attributes[name] = value # If the start, end or schedule granularity have been changed, we have # to reset the working hours. if %w(start end scheduleGranularity timezone timingresolution). include?(name) if @attributes['start'] && @attributes['end'] @attributes['workinghours'] = WorkingHours.new(@attributes['scheduleGranularity'], @attributes['start'], @attributes['end'], @attributes['timezone']) end end value end # Return the number of defined scenarios for the project. def scenarioCount @scenarios.items end # Return the average number of working hours per day. This defaults to 8 but # can be set to other values by the user. def dailyWorkingHours @attributes['dailyworkinghours'].to_f end def weeklyWorkingDays @attributes['workinghours'].weeklyWorkingHours / @attributes['dailyworkinghours'] end # Return the average number of working days per month. def monthlyWorkingDays @attributes['yearlyworkingdays'] / 12.0 end # Return the average number of working days per year. def yearlyWorkingDays @attributes['yearlyworkingdays'].to_f end # Convert timeSlots to working days. def slotsToDays(slots) slots * @attributes['scheduleGranularity'] / (60 * 60 * dailyWorkingHours) end # call-seq: # scenario(index) -> Scenario # scenario(id) -> Scenario # # Return the Scenario with the given _id_ or _index_. def scenario(arg) if arg.is_a?(Fixnum) @scenarios.each do |sc| return sc if sc.sequenceNo - 1 == arg end else return @scenarios[arg] end nil end # call-seq: # scenarioIdx(scenario) # scenarioIdx(id) # # Return the index of the given Scenario specified by _scenario_ or _id_. def scenarioIdx(sc) if sc.is_a?(Scenario) return sc.sequenceNo - 1 elsif @scenarios[sc].nil? return nil else return @scenarios[sc].sequenceNo - 1 end end # Return the Shift with the ID _id_ or return nil if it does not exist. def shift(id) @shifts[id] end # Return the Account with the ID _id_ or return nil if it does not exist. def account(id) @accounts[id] end # Return the Task with the ID _id_ or return nil if it does not exist. def task(id) @tasks[id] end # Return the Resource with the ID _id_ or return nil if it does not exist. def resource(id) @resources[id] end # Return the Report with the ID +id+ or return nil if it does not exist. def report(id) @reports[id] end # Return the Report with the name +name+ or return nil if it does not # exist. def reportByName(name) @reports.each do |report| return report if report.name == name end nil end # This function must be called after the Project data structures have been # filled with data. It schedules all scenario and stores the result in the # data structures again. def schedule initScoreboards [ @accounts, @shifts, @resources, @tasks ].each do |p| # Set all index counters to their proper values. p.index end if @tasks.empty? error('no_tasks', "No tasks defined") end @scenarios.each do |sc| # Skip disabled scenarios next unless sc.get('active') scIdx = scenarioIdx(sc) # All user provided values are set now. The next step is to # propagate inherited values. These values must be marked as # inherited by setting the mode to 1. As we always call # PropertyTreeNode#inherit this is just a safeguard. AttributeBase.setMode(1) prepareScenario(scIdx) # Now change to mode 2 so all values that are modified are marked # as computed. AttributeBase.setMode(2) # Schedule the scenario. scheduleScenario(scIdx) # Complete the data sets, and check the result. finishScenario(scIdx) end resources.each do |resource| resource.checkFailsAndWarnings end tasks.each do |task| task.checkFailsAndWarnings end @timeSheets.warnOnDelta if @warnTsDeltas true end # Add the CSV output format to all reports of type 'tracereport' if # _enable_ is true. Otherwise remove all CSV output formats. def enableTraceReports(enable) @reports.each do |report| next unless report.typeSpec == :tracereport if enable # Enable the CSV format for the tracereport unless report.get('formats').include?(:csv) report.get('formats') << :csv end else # Disabe CSV format for the tracereport report.get('formats').delete(:csv) end end end # Make sure that we have a least one report defined that has an output # format. def checkReports if @reports.empty? warning('no_report_defined', "This project has no reports defined. " + "No output data will be generated.") end unless @accounts.empty? @reports.each do |report| if (report.typeSpec != :accountreport) && (report.get('costaccount').nil? || report.get('revenueaccount').nil?) warning('report_without_balance', "The report #{report.fullId} has no 'balance' defined. " + "No cost or revenue computation will be possible.", report.sourceFileInfo) end end end @reports.each do |report| return unless report.get('formats').empty? end warning('all_formats_empty', "None of the reports has a 'formats' attribute. " + "No output data will be generated.") end # Call this function to generate the reports based on the scheduling result. # This function may only be called after Project#schedule has been called. def generateReports(maxCpuCores) @reports.index if maxCpuCores == 1 @reports.each do |report| # Skip reports that don't have any format specified or trace reports # when generateTraces is false. next if report.get('formats').empty? Log.startProgressMeter("Report #{report.name}") @reportContexts.push(ReportContext.new(self, report)) report.generate @reportContexts.pop Log.stopProgressMeter end else # Kickoff the generation of all reports by pushing the jobs into the # BatchProcessor queue. bp = BatchProcessor.new(maxCpuCores) @reports.each do |report| # Skip reports that don't have any format specified or trace reports # when generateTraces is false. next if report.get('formats').empty? || (report.typeSpec == :trace && !generateTraces) bp.queue(report) { @reportContexts.push(ReportContext.new(self, report)) res = report.generate @reportContexts.pop res } end # Now wait for all the jobs to finish. bp.wait do |report| Log.startProgressMeter("Report #{report.tag.name}") $stdout.print(report.stdout) $stderr.print(report.stderr) if report.retVal.signaled? error('rg_signal', "Signal raised") end unless report.retVal.success? error('rg_abort', "Process aborted") end Log.stopProgressMeter end end DataCache.instance.flush end def generateReport(reportId, regExpMode, formats = nil, dynamicAttributes = nil) reportList = regExpMode ? reportList = matchingReports(reportId) : [ reportId ] reportList.each do |id| unless (report = @reports[id]) error('unknown_report_id', "Request to generate unknown report #{id}") end if formats.nil? && report.get('formats').empty? error('formats_empty', "The report #{report.fullId} has no 'formats' attribute. " + "No output data will be generated.", report.sourceFileInfo) end Log.startProgressMeter("Report #{report.name}") @reportContexts.push(context = ReportContext.new(self, report)) # If we have dynamic attributes we need to backup the old attributes # first, then parse the dynamicAttributes String replacing the # original values. if dynamicAttributes unless dynamicAttributes.empty? context.attributeBackup = report.backupAttributes parser = ProjectFileParser.new parser.parseReportAttributes(report, dynamicAttributes) end report.set('interactive', true) end report.generate(formats) if dynamicAttributes && !dynamicAttributes.empty? report.restoreAttributes(context.attributeBackup) end @reportContexts.pop Log.stopProgressMeter end end def listReports(reportId, regExpMode) reportList = regExpMode ? reportList = matchingReports(reportId) : @reports[reportId] ? [ reportId ] : [] puts "No match for #{reportId}" if reportList.empty? reportList.each do |id| report = @reports[id] formats = report.get('formats') next if formats.empty? puts sprintf("%s\t%s\t%s", id, formats.join(', '), report.name) end end def checkTimeSheets @timeSheets.check end #################################################################### # The following functions are not intended to be called from outside # the TaskJuggler library. There is no guarantee that these # functions will be usable or present in future releases. #################################################################### def addScenario(scenario) # :nodoc: @scenarios.addProperty(scenario) end def addShift(shift) # :nodoc: @shifts.addProperty(shift) end def addAccount(account) # :nodoc: @accounts.addProperty(account) end def addTask(task) # :nodoc: @tasks.addProperty(task) end def addResource(resource) # :nodoc: @resources.addProperty(resource) end def addReport(report) # :nodoc: @reports.addProperty(report) end def removeAccount(account) # :nodoc: @accounts.removeProperty(account) end # call-seq: # isWorkingTime(slotIdx) -> true or false # isWorkingTime(slot) -> true or false # isWorkingTime(startTime, endTime) -> true or false # isWorkingTime(interval) -> true or false # # Return true if the slot or interval is withing globally defined working # time or false if not. If the argument is a TimeInterval, all slots of # the interval must be working time to return true as result. Global work # time means, no global leaves defined and the slot lies within a # defined global working time period. def isWorkingTime(*args) # Normalize argument(s) to TimeInterval if args.length == 1 if args[0].is_a?(Fixnum) || args[0].is_a?(Bignum) return @scoreboard[args[0]].nil? elsif args[0].is_a?(TjTime) return @scoreboard[dateToIdx(args[0])].nil? elsif args[0].is_a?(TimeInterval) startIdx = dateToIdx(args[0].start) endIdx = dateToIdx(args[0].end) else raise ArgumentError, "Unsupported argument type #{args[0].class}" end else startIdx = dateToIdx(args[0]) endIdx = dateToIdx(args[1]) end startIdx.upto(endIdx) do |idx| return false if @scoreboard[idx] end true end # call-seq: # hasWorkingTime(startTime, endTime) -> true or false # hasWorkingTime(interval) -> true or false # # Return true if the interval overlaps with a globally defined working # time or false if not. Global work time means, no global leaves defined # and the slot lies within a defined global working time period. def hasWorkingTime(*args) # Normalize argument(s) to TimeInterval if args.length == 1 if args[0].is_a?(TimeInterval) startIdx = dateToIdx(args[0].start) endIdx = dateToIdx(args[0].end) else raise ArgumentError, "Unsupported argument type #{args[0].class}" end else startIdx = dateToIdx(args[0]) endIdx = dateToIdx(args[1]) end startIdx.upto(endIdx) do |idx| return true if @scoreboard[idx] end false end # Convert working _seconds_ to working days. The result depends on the # setting of the global 'dailyworkinghours' attribute. def convertToDailyLoad(seconds) seconds / (@attributes['dailyworkinghours'] * 3600.0) end # Many internal data structures use Scoreboard objects to keep track of # scheduling data. These have one entry for every schedulable time slot in # the project time frame. This functions returns the number of entries in # the scoreboards. def scoreboardSize ((@attributes['end'] - @attributes['start']) / @attributes['scheduleGranularity']).to_i end # Convert a Scoreboard index to the equivalent date. _idx_ is the index and # it must be within the range of the Scoreboard objects. If not, an # exception is raised. def idxToDate(idx) if $DEBUG && (idx < 0 || idx > scoreboardSize) raise "Scoreboard index out of range" end @attributes['start'] + idx * @attributes['scheduleGranularity'] end # Convert a _date_ (TjTime) to the equivalent Scoreboard index. If # _forceIntoProject_ is true, the date will be pushed into the project time # frame. def dateToIdx(date, forceIntoProject = true) if (date < @attributes['start'] || date > @attributes['end']) # Date is out of range. if forceIntoProject return 0 if date < @attributes['start'] return scoreboardSize - 1 if date > @attributes['end'] else raise "Date #{date} is out of project time range " + "(#{@attributes['start']} - #{@attributes['end']})" end end # Calculate the corresponding index. ((date - @attributes['start']) / @attributes['scheduleGranularity']).to_i end def collectTimeOffIntervals(iv, minDuration) @scoreboard.collectIntervals(iv, minDuration) do |val| val.is_a?(Fixnum) && (val & 0x3E) != 0 end end # Return the number of working days (ignoring global leaves) during the # given _interval_. def workingDays(interval) startIdx = dateToIdx(interval.start) endIdx = dateToIdx(interval.end) slots = 0 startIdx.upto(endIdx) do |idx| slots += 1 unless @scoreboardNoLeaves[idx] end slotsToDays(slots) end # Return the number of global working slots during the given time interval # specified by _startIdx_ and _endIdx_. This method takes global leaves # into account. def getWorkSlots(startIdx, endIdx) slots = 0 startIdx.upto(endIdx) do |idx| slots += 1 unless @scoreboard[idx] end slots end # Return true if for the date specified by the global scoreboard index # _sbIdx_ there is any resource that is available. def anyResourceAvailable?(sbIdx) @resourceAvailability[sbIdx] end # TaskJuggler keeps all times in UTC. All time values must be multiples of # the used scheduling granularity. If the local time zone is not # hour-aligned to UTC, the maximum allowed schedule granularity is # reduced. def Project.maxScheduleGranularity refTime = Time.gm(2000, 1, 1, 0, 0, 0) case (min = refTime.getlocal.min) when 0 # We are hour-aligned to UTC; scheduleGranularity is 1 hour 60 * 60 when 30 # We are half-hour off from UTC; scheduleGranularity is 30 minutes 30 * 60 when 15, 45 # We are 15 or 45 minutes off from UTC; scheduleGranularity is 15 # minutes 15 * 60 else raise "Unknown Time zone alignment #{min}" end end # Return the name of the attribute _id_. Since we don't know whether we # are looking for a task, resource, etc. attribute, we prefer tasks over # resources here. def attributeName(id) # We have to see if the attribute id is a task or resource attribute and # return it's name. (name = @tasks.attributeName(id)).nil? && (name = @resources.attributeName(id)).nil? name end def journal(query) @attributes['journal'].to_rti(query) end # Print the attribute values. It's used for debugging only. def to_s #raise "STOP!" str = '' @attributes.each do |attribute, value| if value str += "#{attribute}: " + "#{value.is_a?(PropertyTreeNode) ? value.fullId : value}" end end str end protected def prepareScenario(scIdx) Log.enter('prepareScenario', "Finishing scenario #{scenario(scIdx).get('name')}") Log.startProgressMeter("Preparing scenario " + "#{scenario(scIdx).get('name')}") resources = PropertyList.new(@resources) tasks = PropertyList.new(@tasks) # Compile a list of leaf resources that are actually used in this # project. usedResources = [] tasks.each do |task| task.candidates(scIdx).each do |resource| usedResources << resource unless usedResources.include?(resource) end end total = usedResources.length i = 0 usedResources.each do |resource| resource.prepareScheduling(scIdx) resource.preScheduleCheck(scIdx) i += 1 Log.progress((i.to_f / total) * 0.8) end resources.each { |resource| resource.setDirectReports(scIdx) } resources.each { |resource| resource.setReports(scIdx) } computeResourceAvailabilities(scIdx, usedResources) Log.progress(0.81) tasks.each { |task| task.prepareScheduling(scIdx) } Log.progress(0.82) tasks.each { |task| task.Xref(scIdx) } tasks.each { |task| task.propagateInitialValues(scIdx) } Log.progress(0.83) tasks.each { |task| task.preScheduleCheck(scIdx) } Log.progress(0.84) # Check for dependency loops in the task graph. tasks.each { |task| task.resetLoopFlags(scIdx) } tasks.each do |task| task.checkForLoops(scIdx, [], false, true, true) if task.parent.nil? end Log.progress(0.85) tasks.each { |task| task.resetLoopFlags(scIdx) } tasks.each do |task| task.checkForLoops(scIdx, [], true, true, false) if task.parent.nil? end Log.progress(0.87) # Compute the criticalness of the tasks and their pathes. tasks.each { |task| task.countResourceAllocations(scIdx) } Log.progress(0.88) resources.each { |resource| resource.calcCriticalness(scIdx) } Log.progress(0.9) tasks.each { |task| task.calcCriticalness(scIdx) } Log.progress(0.95) tasks.each { |task| task.calcPathCriticalness(scIdx) } Log.progress(0.99) @timeSheets.check Log.progress(1.0) Log.stopProgressMeter # This is used for debugging only if false resources.each do |resource| puts "#{resource}" end tasks.each do |task| puts "#{task}" end end Log.exit('prepareScenario', "Preparing scenario #{scenario(scIdx).get('name')} completed") end def finishScenario(scIdx) Log.enter('finishScenario', "Finishing scenario #{scenario(scIdx).get('name')}") Log.startProgressMeter("Checking scenario #{scenario(scIdx).get('name')}") @tasks.each do |task| # Recursively traverse the top-level tasks to finish all tasks. task.finishScheduling(scIdx) unless task.parent end @resources.each do |resource| # Recursively traverse the top-level resources to finish them all. resource.finishScheduling(scIdx) unless resource.parent end i = 0 total = @tasks.items @tasks.each do |task| task.postScheduleCheck(scIdx) if task.parent.nil? i += 1 Log.progress(i.to_f / total) end Log.stopProgressMeter Log.exit('finishScenario', "Finishing scenario #{scenario(scIdx).get('name')} completed") end # Schedule all tasks for the given Scenario with index +scIdx+. def scheduleScenario(scIdx) tasks = PropertyList.new(@tasks) # Only care about leaf tasks that are not milestones and aren't # scheduled already (marked with the 'scheduled' attribute). tasks.delete_if { |task| !task.leaf? || task['milestone', scIdx] || task['scheduled', scIdx] } Log.enter('scheduleScenario', "#{tasks.length} leaf tasks") # The sorting of the work item list determines which tasks will get their # resources first. The first sorting criterium is the user specified task # priority. The second criterium is the scheduler determined priority # stored in the pathcriticalness attribute. That way, the user can always # override the scheduler determined priority. To always have a defined # order, the third criterium is the sequence number. tasks.setSorting([ [ 'priority', false, scIdx ], [ 'pathcriticalness', false, scIdx ], [ 'seqno', true, -1 ] ]) tasks.sort! totalTasks = tasks.length # Enter the main scheduling loop. This loop is only terminated when all # tasks have been scheduled or another thread has set the breakFlag to # true. Log.startProgressMeter("Scheduling scenario " + "#{scenario(scIdx).get('name')}") failedTasks = [] while !tasks.empty? # Only update the progress bar every 10 completed tasks. if tasks.length % 10 == 0 percentComplete = (totalTasks - tasks.length).to_f / totalTasks Log.progress(percentComplete) end # Now find the task with the highest priority that can be scheduled # and schedule it. taskToRemove = nil tasks.each do |task| # Task not ready? Ignore it. next unless task.readyForScheduling?(scIdx) unless task.schedule(scIdx) failedTasks << task end # The task has been completed or failed. But we can remove it from # the todo list. taskToRemove = task # The scheduling of this task may cause other higher priority tasks # to be ready now. So we terminate the inner loop and start at the # top of the list again. break end # There should always be a task to be removed. If not, the scheduler # has found a set of tasks that deadlock each other. if taskToRemove tasks.delete(taskToRemove) elsif failedTasks.empty? warning('deadlock', 'Some tasks reference each other but don\'t provide ' + 'enough information to start the scheduling. The ' + 'scheduler does not know where to start scheduling ' + 'these tasks. You need to provide more fixed dates ' + 'or dependencies on already scheduled tasks.') failedTasks = tasks break else # We have some tasks that cannot be scheduled. break end end # Check for failed tasks and report the first 10 of them as # warnings. unless failedTasks.empty? warning('unscheduled_tasks', "#{failedTasks.length} tasks could not be scheduled") i = 0 failedTasks.each do |t| warning('unscheduled_task', "Task #{t.fullId}: " + "#{t['start', scIdx] ? t['start', scIdx] : ''} -> " + "#{t['end', scIdx] ? t['end', scIdx] : ''}", t.sourceFileInfo) i += 1 break if i >= 10 end Log.stopProgressMeter return false end Log.stopProgressMeter Log.exit('scheduleScenario', "Scheduling of scenario #{scIdx} finished") true end private def initScoreboards # Create scoreboard and mark all slots as unavailable @scoreboard = Scoreboard.new(@attributes['start'], @attributes['end'], @attributes['scheduleGranularity'], 2) # And the same for another scoreboard. @scoreboardNoLeaves = Scoreboard.new(@attributes['start'], @attributes['end'], @attributes['scheduleGranularity'], 2) workinghours = @attributes['workinghours'] # Change all work time slots to nil (available) again. date = @scoreboard.idxToDate(0) delta = @attributes['scheduleGranularity'] scoreboardSize.times do |i| if workinghours.onShift?(date) @scoreboard[i] = nil @scoreboardNoLeaves[i] = nil end date += delta end # Mark all global leave slots as such @attributes['leaves'].each do |leave| startIdx = @scoreboard.dateToIdx(leave.interval.start) endIdx = @scoreboard.dateToIdx(leave.interval.end) startIdx.upto(endIdx - 1) do |i| # If the slot is nil or set to 4 then don't set the time-off bit. sb = @scoreboard[i] @scoreboard[i] = ((sb.nil? || sb == 4) ? 0 : 2) | (1 << 2) end end end def computeResourceAvailabilities(scIdx, usedResources) @resourceAvailability = Scoreboard.new(@attributes['start'], @attributes['end'], @attributes['scheduleGranularity']) @resourceAvailability.each_index do |idx| usedResources.each do |resource| if resource.available?(scIdx, idx) @resourceAvailability[idx] = true break end end end end def matchingReports(reportId) list = [] @reports.each do |report| id = report.fullId list << id if Regexp.new(reportId) =~ id end list end end end taskjuggler-3.5.0/lib/taskjuggler/DataCache.rb0000644000175000017500000001044712614413013020564 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = DataCache.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'time' require 'singleton' class TaskJuggler # These are the entries in the DataCache. They store a value and an access # counter. The counter can be read and written externally. class DataCacheEntry attr_reader :unhashedKey attr_accessor :hits # Create a new DataCacheEntry for the _value_. We also store the unhashed # key to be able to detect hash collisions. The access counter is set to 1 # to increase the chance that it is not flushed immedidate. def initialize(unhashedKey, value) @unhashedKey = unhashedKey @value = value @hits = 1 end # Return the value and increase the access counter by 1. def value if @hits <= 0 @hits = 1 else @hits += 1 end @value end end # This class provides a global data cache that can be used to store and # retrieve values indexed by a key. The cache is size limited. When maximum # capacity is reached, a certain percentage of the least requested values is # dropped from the cache. The primary purpose of this global cache is to # store values that are expensive to compute but may be need on several # occasions during the program execution. class DataCache include Singleton def initialize resize flush # Counter for the number of writes to the cache. @stores = 0 # Counter for the number of found values. @hits = 0 # Counter for the number of not found values. @misses = 0 # Counter for hash collisions @collisions = 0 end # For now, we use this randomly determined size. def resize(size = 100000) @highWaterMark = size # Flushing out the least used entries is fairly expensive. So we only # want to do this once in a while. The lowWaterMark determines how much # of the entries will survive the flush. @lowWaterMark = size * 0.9 end # Completely flush the cache. The statistic counters will remain intact, # but all data values are lost. def flush @entries = {} end if RUBY_VERSION < '1.9.0' # Ruby 1.8 has a buggy hash key generation algorithm that leads to many # hash collisions. We completely disable caching on 1.8. def cached(*args) yield end else # _args_ is a set of arguments that unambigously identify the data entry. # It's converted into a hash to store or recover a previously stored # entry. If we have a value for the key, return the value. Otherwise call # the block to compute the value, store it and return it. def cached(*args) key = args.hash if @entries.has_key?(key) e = @entries[key] if e.unhashedKey != args # Two different args produce the same hash key. This should be a # very rare event! @collisions += 1 yield else @hits += 1 e.value end else @misses += 1 store(yield, args, key) end end end def to_s <<"EOT" Entries: #{@entries.size} Stores: #{@stores} Collisions: #{@collisions} Hits: #{@hits} Misses: #{@misses} Hit Rate: #{@hits * 100.0 / (@hits + @misses)}% EOT end private # Store _value_ into the cache using _key_ to tag it. _key_ must be unique # and must be used to load the value from the cache again. You cannot # store nil values! def store(value, unhashedKey, key) @stores += 1 if @entries.size > @highWaterMark while @entries.size > @lowWaterMark # How many entries do we need to delete to get to the low watermark? toDelete = @entries.size - @lowWaterMark @entries.delete_if do |foo, e| # Hit counts age with every cleanup. (e.hits -= 1) < 0 && (toDelete -= 1) >= 0 end end end @entries[key] = DataCacheEntry.new(unhashedKey, value) value end end end taskjuggler-3.5.0/lib/taskjuggler/Shift.rb0000644000175000017500000000267212614413013020045 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Shift.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/PropertyTreeNode' require 'taskjuggler/ShiftScenario' class TaskJuggler # A shift is a definition of working hours for each day of the week. It may # also contain a list of intervals that define off-duty periods or leaves. class Shift < PropertyTreeNode def initialize(project, id, name, parent) super(project.shifts, id, name, parent) project.addShift(self) @data = Array.new(@project.scenarioCount, nil) @project.scenarioCount.times do |i| ShiftScenario.new(self, i, @scenarioAttributes[i]) end end # Many Shift functions are scenario specific. These functions are # provided by the class ShiftScenario. In case we can't find a # function called for the Shift class we try to find it in # ShiftScenario. def method_missing(func, scenarioIdx, *args) @data[scenarioIdx].method(func).call(*args) end # Return a reference to the _scenarioIdx_-th scenario. def scenario(scenarioIdx) return @data[scenarioIdx] end end end taskjuggler-3.5.0/lib/taskjuggler/AccountScenario.rb0000644000175000017500000000657212614413013022053 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = AccountScenario.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/ScenarioData' class TaskJuggler # This class handles the scenario specific features of a Account object. class AccountScenario < ScenarioData def initialize(account, scenarioIdx, attributes) super %w( credits ).each do |attr| @property[attr, @scenarioIdx] end end def query_balance(query) # The account balance is the turnover from project start (index 0) to # the start of the query period. It's the start because that's what the # label in the column header says. startIdx = 0 endIdx = @project.dateToIdx(query.start) query.sortable = query.numerical = amount = turnover(startIdx, endIdx) query.string = query.currencyFormat.format(amount) end def query_turnover(query) startIdx = @project.dateToIdx(query.start) endIdx = @project.dateToIdx(query.end) query.sortable = query.numerical = amount = turnover(startIdx, endIdx) query.string = query.currencyFormat.format(amount) end private # Compute the turnover for the period between _startIdx_ end _endIdx_. # TODO: This method is horribly inefficient! def turnover(startIdx, endIdx) amount = 0.0 # Accumulate the amounts that were directly credited to the account # during the given interval. unless @credits.empty? # For this, we need the real dates again. Conver the indices back to # dates. startDate = @project.idxToDate(startIdx) endDate = @project.idxToDate(endIdx) @credits.each do |credit| if startDate <= credit.date && credit.date < endDate amount += credit.amount end end end if @property.container? if @property.adoptees.empty? # Normal case. Accumulate turnover of child accounts. @property.children.each do |child| amount += child.turnover(@scenarioIdx, startIdx, endIdx) end else # Special case for meta account that is used to calculate a balance. # The first adoptee is the top-level cost account, the second the # top-level revenue account. amount += -@property.adoptees[0].turnover(@scenarioIdx, startIdx, endIdx) + @property.adoptees[1].turnover(@scenarioIdx, startIdx, endIdx) end else case @property.get('aggregate') when :tasks @project.tasks.each do |task| amount += task.turnover(@scenarioIdx, startIdx, endIdx, @property, nil, false) end when :resources @project.resources.each do |resource| next unless resource.leaf? amount += resource.turnover(@scenarioIdx, startIdx, endIdx, @property, nil, false) end else raise "Unknown aggregation type #{@property.get('aggregate')}" end end amount end end end taskjuggler-3.5.0/lib/taskjuggler/URLParameter.rb0000644000175000017500000000124212614413013021263 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = URLParameter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'zlib' class TaskJuggler class URLParameter def URLParameter.encode(data) [Zlib::Deflate.deflate(data)].pack('m') end def URLParameter.decode(data) Zlib::Inflate.inflate(data.unpack('m')[0]) end end end taskjuggler-3.5.0/lib/taskjuggler/Tj3SheetAppBase.rb0000644000175000017500000000313712614413013021652 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3SheetAppBase.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/Tj3AppBase' class TaskJuggler class Tj3SheetAppBase < Tj3AppBase def initialize super @dryRun = false @workingDir = nil end def processArguments(argv) super do @opts.on('-d', '--directory ', String, format('Use the specified directory as working ' + 'directory')) do |arg| @workingDir = arg end @opts.on('--dryrun', format("Don't send out any emails or do SCM commits")) do @dryRun = true end yield end end def optsEndDate @opts.on('-e', '--enddate ', String, format("The end date of the reporting period. Either as " + "YYYY-MM-DD or day of week. 0: Sunday, 1: Monday and " + "so on. The default value is #{@date}.")) do |arg| ymdFilter = /([0-9]{4})-([0-9]{2})-([0-9]{2})/ if ymdFilter.match(arg) @date = Time.mktime(*(ymdFilter.match(arg)[1..3])) else @date = TjTime.new.nextDayOfWeek(arg.to_i % 7) end @date = @date.strftime('%Y-%m-%d') end end end end taskjuggler-3.5.0/lib/taskjuggler/TextParser.rb0000644000175000017500000004212512614413013021066 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TextParser.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TextParser/Pattern' require 'taskjuggler/TextParser/Rule' require 'taskjuggler/TextParser/StackElement' require 'taskjuggler/MessageHandler' require 'taskjuggler/TjException' require 'taskjuggler/Log' class TaskJuggler # The TextParser implements a somewhat modified LL(1) parser. It uses a # dynamically compiled state machine. Dynamically means, that the syntax can # be extended during the parse process. This allows support for languages # that can extend their syntax during the parse process. The TaskJuggler # syntax is such an beast. # # This class is just a base class. A complete parser would derive from this # class and implement the rule set and the functions _nextToken()_ and # _returnToken()_. It also needs to set the array _variables_ to declare all # variables ($SOMENAME) that the scanner may deliver. # # To describe the syntax the functions TextParser#pattern, TextParser#optional # and TextParser#repeatable can be used. When the rule set is changed during # parsing, TextParser#updateParserTables must be called to make the changes # effective. The parser can also document the syntax automatically. To # document a pattern, the functions TextParser#doc, TextParser#descr, # TextParser#also and TextParser#arg can be used. # # In contrast to conventional LL grammars, we use a slightly improved syntax # descriptions. Repeated patterns are not described by recursive call but we # use a repeat flag for syntax rules that consists of repeatable patterns. # This removes the need for recursion elimination when compiling the state # machine and makes the syntax a lot more readable. However, it adds a bit # more complexity to the state machine. Optional patterns are described by # a rule flag, not by adding an empty pattern. # # To start parsing the input the function TextParser#parse needs to be called # with the name of the start rule. class TextParser include MessageHandler # Utility class so that we can distinguish Array results from the Array # containing the results of a repeatable rule. We define some merging # method with a slightly different behaviour. class TextParserResultArray < Array def initialize super end # If there is a repeatable rule that contains another repeatable loop, the # result of the inner rule is an Array that gets put into another Array by # the outer rule. In this case, the inner Array can be merged with the # outer Array. def <<(arg) if arg.is_a?(TextParserResultArray) self.concat(arg) else super end end end attr_reader :rules # Create a new TextParser object. def initialize # This Hash will store the ruleset that the parser is operating on. @rules = { } # Array to hold the token types that the scanner can return. @variables = [] # An list of token types that are not allowed in the current context. # For performance reasons we use a hash with the token as key. The value # is irrelevant. @blockedVariables = {} # The currently processed rule. @cr = nil @states = {} # The stack used by the FSM. @stack = nil end # Limit the allowed tokens of the scanner to the subset passed by the # _tokenSet_ Array. def limitTokenSet(tokenSet) return unless tokenSet # Create a copy of all supported variables. blockedVariables = @variables.dup # Then delete all that are in the limited set. blockedVariables.delete_if { |v| tokenSet.include?(v) } # And convert the list into a Hash for faster lookups. @blockedVariables = {} blockedVariables.each { |v| @blockedVariables[v] = true } end # Call all methods that start with 'rule_' to initialize the rules. def initRules methods.each do |m| if m[0, 5] == 'rule_' # Create a new rule with the suffix of the function name as name. newRule(m[5..-1]) # Call the function. send(m) end end end # Add a new rule to the rule set. _name_ must be a unique identifier. The # function also sets the class variable @cr to the new rule. Subsequent # calls to TextParser#pattern, TextParser#optional or # TextParser#repeatable will then implicitely operate on the most recently # added rule. def newRule(name) # Use a symbol instead of a String. name = name.intern raise "Fatal Error: Rule #{name} already exists" if @rules.has_key?(name) if block_given? saveCr = @cr @rules[name] = @cr = TextParser::Rule.new(name) yield @cr = saveCr else @rules[name] = @cr = TextParser::Rule.new(name) end end # Add a new pattern to the most recently added rule. _tokens_ is an array of # strings that specify the syntax elements of the pattern. Each token must # start with an character that identifies the type of the token. The # following types are supported. # # * ! a reference to another rule # * $ a variable token as delivered by the scanner # * _ a literal token. # # _func_ is a Proc object that is called whenever the parser has completed # the processing of this rule. def pattern(tokens, func = nil) @cr.addPattern(TextParser::Pattern.new(tokens, func)) end # Identify the patterns of the most recently added rule as optional syntax # elements. def optional @cr.setOptional end # Identify the patterns of the most recently added rule as repeatable syntax # elements. def repeatable @cr.setRepeatable end # This function needs to be called whenever new rules or patterns have been # added and before the next call to TextParser#parse. It's perfectly ok to # call this function from within a parse() call as long as the states that # are currently on the stack have not been modified. def updateParserTables saveFsmStack # Invalidate some cached data. @rules.each_value { |rule| rule.flushCache } @states = {} # Generate the parser states for all patterns of all rules. @rules.each_value do |rule| rule.generateStates(@rules).each do |s| @states[[ s.rule, s.pattern, s.index ]] = s end checkRule(rule) end # Compute the transitions between the generated states. @states.each_value do |state| state.addTransitions(@states, @rules) end restoreFsmStack end # To parse the input this function needs to be called with the name of the # rule to start with. It returns the result of the processing function of # the top-level parser rule that was specified by _ruleName_. In case of # an error, the result is false. def parse(ruleName) @stack = [] @@expectedTokens = [] begin result = parseFSM(@rules[ruleName]) rescue TjException => msg if msg.message && !msg.message.empty? critical('parse', msg.message) end return false end result end # Return the SourceFileInfo of the TextScanner at the beginning of the # currently processed TextParser::Rule. Or return nil if we don't have a # current position. def sourceFileInfo return @scanner.sourceFileInfo if @stack.nil? || @stack.length <= 1 @stack.last.firstSourceFileInfo end def error(id, text, sfi = nil, data = nil) sfi ||= sourceFileInfo if @scanner # The scanner has some more context information, so we pass the error # on to the TextScanner. @scanner.error(id, text, sfi, data) else error(id, text, sfi, data) end end def warning(id, text, sfi = nil, data = nil) sfi ||= sourceFileInfo if @scanner # The scanner has some more context information, so we pass the # warning on to the TextScanner. @scanner.warning(id, text, sfi, data) else warning(id, text, sfi, data) end end private def checkRule(rule) if rule.patterns.empty? raise "Rule #{rule.name} must have at least one pattern" end rule.patterns.each do |pat| pat.each do |type, name| if type == :variable if @variables.index(name).nil? error('unsupported_token', "The token #{name} is not supported here.") end elsif type == :reference if @rules[name].nil? raise "Fatal Error: Reference to unknown rule #{name} in " + "pattern '#{pat}' of rule #{rule.name}" end end end end end def parseFSM(rule) unless (state = @states[[ rule, nil, 0 ]]) error("no_start_state", "No start state for rule #{rule.name} found") end @stack = [ TextParser::StackElement.new(nil, state) ] loop do if state.transitions.empty? # The final states of each pattern have no pre-compiled transitions. # For such a state, we don't need to get a new token. transition = token = nil else transition = state.transition(token = getNextToken) end # If we have looped-back we need to finish the pattern first. Final # tokens of repeatable rules do have transitions! if transition && transition.loopBack finishPattern(token) transition = state.transition(token = getNextToken) end if transition # Shift: This is for normal state transitions. This may be from one # token of a pattern to the next token of the same pattern or to the # start of a new pattern. The transition tells us what state we have # to process next. state = transition.state # Transitions that enter rules generate states which we need to # resume at when a rule has been completely processed. We push this # list of states on the @stack. stackElement = @stack.last first = true transition.stateStack.each do |s| checkForOldSyntax(s, token) if first && s.pattern == stackElement.state.pattern # The first state in the list may just be another state of the # current pattern. In this case, we already have the # StackElement on the @stack. We only need to update the State # for the current StackElement. stackElement.state = s else # For other patterns, we just push a new StackElement onto the # @stack. @stack.push(TextParser::StackElement.new(nil, s)) end first = false end if state.index == 0 # If we have just started with a new pattern (or loop-ed back) we # need to push a new StackEntry onto the @stack. The StackEntry # stores the result of the pattern and keeps the State that we # need to return to in case we jump to other patterns from this # pattern. checkForOldSyntax(state, token) @stack.push(TextParser::StackElement.new(state.pattern.function, state)) end # Store the token value in the result Array. @stack.last.insert(state.index, token[1], token[2], false) else # Reduce: We've reached the end of a rule. There is no pre-compiled # transition available. The current token, if we have one, is of no # use to us during this state. We just return it to the scanner. The # next state is determined by the first matching state from the # @stack. if state.noReduce # Only states that finish a rule may trigger a reduce operation. # Other states have the noReduce flag set. If a reduce for such a # state is triggered, we found a token that is not supported by # the syntax rules. error("no_reduce", "Unexpected token '#{token[1]}' found. " + "Expecting #{@stack.last.state.expectedTokens.length > 1 ? 'one of ' : ''}" + "#{@stack.last.state.expectedTokens.join(', ')}", token[2]) end if finishPattern(token) # Accept: We're done with parsing. break end state = @stack.last.state end end @stack[0].val[0] end def finishPattern(token) # The method to finish this pattern may include another file or change # the parser rules. Therefor we have to return the token to the scanner. returnToken(token) if token #dumpStack # To finish a pattern we need to pop the StackElement with the token # values from the stack. stackEntry = @stack.pop if stackEntry.nil? || @stack.empty? # Check if we have reached the bottom of the stack. token = getNextToken if token[0] == :endOfText # If the token is the end of the top-level file, we're done. We push # back the StackEntry since it holds the overall result of the # parsing. @stack.push(stackEntry) return true end # If it's not the EOF token, we found a token that violates the syntax # rules. error('unexpctd_token', "Unexpected token '#{token[1]}' found. " + "Expecting one of " + "#{stackEntry.state.expectedTokens.join(', ')}", token[2]) end # Memorize if the rule for this pattern was repeatable. Then we will # store the result of the pattern in an Array. ruleIsRepeatable = stackEntry.state.rule.repeatable state = stackEntry.state result = nil if state.pattern.function # Make the token values and their SourceFileInfo available. @val = stackEntry.val @sourceFileInfo = stackEntry.sourceFileInfo # Now call the pattern action to compute the value of the pattern. begin result = state.pattern.function.call rescue AttributeOverwrite @scanner.warning('attr_overwrite', $!.to_s) end end # We use the SourceFileInfo of the first token of the pattern to store # it with the result of the pattern. firstSourceFileInfo = stackEntry.firstSourceFileInfo # Store the result at the correct position into the next lower level of # the stack. stackEntry = @stack.last stackEntry.insert(stackEntry.state.index, result, firstSourceFileInfo, ruleIsRepeatable) false end def dumpStack #puts "Stack level #{@stack.length}" @stack.each do |sl| print "#{@stack.index(sl)}: " sl.each do |v| if v.is_a?(Array) begin print "[#{v.join('|')}]|" rescue print "[#{v[0].class}...]|" end else begin print "#{v}|" rescue print v.class end end end puts " -> #{sl.state ? sl.state.to_s(true) : 'nil'}" + "#{sl.function.nil? ? '' : '(Called)'}" end end # Check if the current token matches a deprecated or removed syntax # element. def checkForOldSyntax(state, token) if state.pattern.supportLevel == :deprecated warning('deprecated_keyword', "The keyword '#{token[1]}' has been deprecated! " + "See the reference manual for details.") end if state.pattern.supportLevel == :removed error('removed_keyword', "The keyword '#{token[1]}' is no longer supported! " + "See the reference manual for details.") end end # Convert the FSM stack state entries from State objects into [ rule, # pattern, index ] equivalents. def saveFsmStack return unless @stack @stack.each do |s| next unless (st = s.state) s.state = [ st.rule, st.pattern, st.index ] end end # Convert the FSM stack state entries from [ rule, pattern, index ] into # the respective State objects again. def restoreFsmStack return unless @stack @stack.each do |s| next unless (state = @states[s.state]) raise "Stack restore failed. Cannot find state" unless state s.state = state end end def getNextToken token = nextToken # puts "Token: [#{token[0]}][#{token[1]}]" if @blockedVariables[token[0]] error('unsupported_token', "The token #{token[1]} is not supported in this context.", token[2]) end token end end end taskjuggler-3.5.0/lib/taskjuggler/Painter.rb0000644000175000017500000000510112614413013020360 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Painter.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/XMLElement' require 'taskjuggler/Painter/Primitives' require 'taskjuggler/Painter/Group' require 'taskjuggler/Painter/BasicShapes' require 'taskjuggler/Painter/Text' require 'taskjuggler/Painter/FontMetrics' class TaskJuggler # This is a vector drawing class. It can describe a canvas with lines, # rectangles, circles, ellipses and text elements on it. The elements can be # grouped. It currently only supports rendering as an SVG output. class Painter include Primitives # Create a canvas of dimension _width_ times _height_. The block can be # used to add elements to the drawing. If the block has an argument, the # block content is evaluated within the current context. If no argument is # provided, the newly created object will be the evaluation context of the # block. This will make instance variables of the caller inaccessible. # Methods of the caller will still be available. def initialize(width, height, &block) @width = width @height = height @elements = [] if block if block.arity == 1 # This is the traditional case where self is passed to the block. # All Primitives methods now must be prefixed with the block # variable to call them. yield self else # In order to have the primitives easily available in the block, we # use instance_eval to switch self to this object. But this makes the # methods of the original self no longer accessible. We work around # this by saving the original self and using method_missing to # delegate the method call to the original self. @originalSelf = eval('self', block.binding) instance_eval(&block) end end end # Delegator to @originalSelf. def method_missing(method, *args, &block) @originalSelf.send(method, *args, &block) end # Render the canvas as SVG output (tree of XMLElement objects). def to_svg XMLElement.new('svg', 'width' => "#{@width}px", 'height' => "#{@height}px") do @elements.map { |el| el.to_svg } end end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/0000755000175000017500000000000012614413013020166 5ustar bernatbernattaskjuggler-3.5.0/lib/taskjuggler/RichText/RTFWithQuerySupport.rb0000644000175000017500000000212112614413013024421 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = RTFWithQuerySupport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/FunctionHandler' class TaskJuggler class RTFWithQuerySupport < RichTextFunctionHandler def initialize(type, sourceFileInfo = nil) super @query = nil end # This function must be called to register the Query object that will be # used to resolve the queries. It will create a copy of the object since # it will modify it. def setQuery(query) @query = query.dup end end class RichTextIntermediate def setQuery(query) @functionHandlers.each_value do |handler| if handler.respond_to?('setQuery') handler.setQuery(query) end end end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/FunctionExample.rb0000644000175000017500000000337612614413013023625 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = FunctionExample.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/FunctionHandler' require 'taskjuggler/TjpExample' require 'taskjuggler/XMLElement' class TaskJuggler # This class is a specialized RichTextFunctionHandler that turns references to # TJP example code in the test/TestSuite/Syntax/Correct directory into # embedded example code. It currently only supports HTML. class RichTextFunctionExample < RichTextFunctionHandler def initialize super('example') @blockFunction = true end # Not supported for this function def to_s(args) '' end # Return a XMLElement tree that represents the example file as HTML code. def to_html(args) unless (file = args['file']) raise "'file' argument missing" end tag = args['tag'] example = TjpExample.new fileName = File.join(AppConfig.dataDirs('test')[0], 'TestSuite', 'Syntax', 'Correct', "#{file}.tjp") example.open(fileName) frame = XMLElement.new('div', 'class' => 'codeframe') frame << (pre = XMLElement.new('pre', 'class' => 'code')) unless (text = example.to_s(tag)) raise "There is no tag '#{tag}' in file " + "#{fileName}." end pre << XMLText.new(text) frame end # Not supported for this function. def to_tagged(args) nil end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/Element.rb0000644000175000017500000004312412614413013022110 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Element.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/UTF8String' require 'taskjuggler/TjException' require 'taskjuggler/XMLElement' require 'taskjuggler/TextFormatter' class TaskJuggler class RichTextImage attr_reader :fileName attr_accessor :altText, :verticalAlign def initialize(fileName) @fileName = fileName @altText = nil @verticalAlign = nil end end # The RichTextElement class models the nodes of the intermediate # representation that the RichTextParser generates. Each node can reference an # Array of other RichTextElement nodes, building a tree that represents the # syntactical structure of the parsed RichText. Each node has a certain # category that identifies the content of the node. class RichTextElement attr_reader :richText, :category, :children attr_writer :data attr_accessor :appendSpace # Create a new RichTextElement node. _rt_ is the RichText object this # element belongs to. _category_ is the type of the node. It can be :title, # :bold, etc. _arg_ is an overloaded argument. It can either be another node # or an Array of RichTextElement nodes. def initialize(rt, category, arg = nil) @richText = rt @category = category if arg if arg.is_a?(Array) @children = arg else unless arg.is_a?(RichTextElement) || arg.is_a?(String) raise "Element must be of type RichTextElement instead of " + "#{arg.class}" end @children = [ arg ] end else @children = [] end # Certain elements such as titles,references and numbered bullets can be # require additional data. This variable is used for this. It can hold an # Array of counters or a link label. @data = nil @appendSpace = false end # Remove a paragraph node from the richtext node if it is the only node in # richtext. The paragraph children will become richtext children. def cleanUp if @category == :richtext && @children.length == 1 && @children[0].category == :paragraph @children = @children[0].children end self end # Return true of the node contains an empty RichText tree. def empty? @category == :richtext && @children.empty? end # Recursively extract the section headings from the RichTextElement and # build the TableOfContents _toc_ with the gathered sections. _fileName_ # is the base name (without .html or other suffix) of the file the # TOCEntries should point to. def tableOfContents(toc, fileName) number = nil case @category when :title1 number = "#{@data[0]} " when :title2 number = "#{@data[0]}.#{@data[1]} " when :title3 number = "#{@data[0]}.#{@data[1]}.#{@data[2]} " when :title4 number = "#{@data[0]}.#{@data[1]}.#{@data[2]}.#{@data[3]} " end if number # We've found a section heading. The String value of the Element is the # title. title = children_to_s tag = convertToID(title) toc.addEntry(TOCEntry.new(number, title, fileName, tag)) else # Recursively extract the TOC from the child objects. @children.each do |el| el.tableOfContents(toc, fileName) if el.is_a?(RichTextElement) end end toc end # Return an Array with all other snippet names that are referenced by # internal references in this RichTextElement. def internalReferences references = [] if @category == :ref && !@data.include?(':') references << @data else @children.each do |el| if el.is_a?(RichTextElement) references += el.internalReferences end end end # We only need each reference once. references.uniq end # Conver the intermediate representation into a plain text again. All # elements that can't be represented in plain text easily will be ignored or # just their value will be included. def to_s pre = '' post = '' case @category when :richtext when :title1 return textBlockFormat(@richText.indent + @richText.titleIndent, sTitle(1), children_to_s, @richText.lineWidth) + "\n" when :title2 return textBlockFormat(@richText.indent + @richText.titleIndent, sTitle(2), children_to_s, @richText.lineWidth) + "\n" when :title3 return textBlockFormat(@richText.indent + @richText.titleIndent, sTitle(3), children_to_s, @richText.lineWidth) + "\n" when :title4 return textBlockFormat(@richText.indent + @richText.titleIndent, sTitle(4), children_to_s, @richText.lineWidth) + "\n" when :hline return "#{' ' * @richText.indent}" + "#{'-' * (@richText.lineWidth - @richText.indent)}\n" when :paragraph return textBlockFormat(@richText.indent + @richText.parIndent, '', children_to_s, @richText.lineWidth) + "\n" when :pre return TextFormatter.new(@richText.lineWidth, @richText.indent + @richText.preIndent). indent(children_to_s) + "\n" when :bulletlist1 when :bulletitem1 return textBlockFormat(@richText.indent + @richText.listIndent, '* ', children_to_s, @richText.lineWidth) + "\n" when :bulletlist2 when :bulletitem2 return textBlockFormat(@richText.indent + @richText.listIndent * 2, '* ', children_to_s, @richText.lineWidth) + "\n" when :bulletlist3 when :bulletitem3 return textBlockFormat(@richText.indent + @richText.listIndent * 3, '* ', children_to_s, @richText.lineWidth) + "\n" when :bulletlist4 when :bulletitem4 return textBlockFormat(@richText.indent + @richText.listIndent * 4, '* ', children_to_s, @richText.lineWidth) + "\n" when :numberlist1 when :numberitem1 return textBlockFormat(@richText.indent + @richText.listIndent, "#{@data[0]}. ", children_to_s, @richText.lineWidth) + "\n" when :numberlist2 when :numberitem2 return textBlockFormat(@richText.indent + @richText.listIndent, "#{@data[0]}.#{@data[1]} ", children_to_s, @richText.lineWidth) + "\n" when :numberlist3 when :numberitem3 return textBlockFormat(@richText.indent + @richText.listIndent, "#{@data[0]}.#{@data[1]}.#{@data[2]} ", children_to_s, @richText.lineWidth) + "\n" when :numberlist4 when :numberitem4 return textBlockFormat(@richText.indent + @richText.listIndent, "#{@data[0]}.#{@data[1]}.#{@data[2]}." + "#{@data[3]} ", children_to_s, @richText.lineWidth) + "\n" when :img pre = @data.altText if @data.altText when :ref when :href when :blockfunc when :inlinefunc checkHandler pre = @richText.functionHandler(@data[0]).to_s(@data[1]) when :italic when :bold when :fontCol when :code when :text when :htmlblob return '' else raise "Unknown RichTextElement category #{@category}" end pre + children_to_s + post end # Convert the tree of RichTextElement nodes into an XML like text # representation. This is primarily intended for unit testing. The tag names # are similar to HTML tags, but this is not meant to be valid HTML. def to_tagged pre = '' post = '' case @category when :richtext pre = '
' post = '
' when :title1 pre = "

#{@data[0]} " post = "

\n\n" when :title2 pre = "

#{@data[0]}.#{@data[1]} " post = "

\n\n" when :title3 pre = "

#{@data[0]}.#{@data[1]}.#{@data[2]} " post = "

\n\n" when :title4 pre = "

#{@data[0]}.#{@data[1]}.#{@data[2]}.#{@data[3]} " post = "

\n\n" when :hline pre = '
' post = "\n" when :paragraph pre = '

' post = "

\n\n" when :pre pre = '
'
        post = "
\n\n" when :bulletlist1 pre = '
    ' post = '
' when :bulletitem1 pre = '
  • * ' post = "
  • \n" when :bulletlist2 pre = '
      ' post = '
    ' when :bulletitem2 pre = '
  • * ' post = "
  • \n" when :bulletlist3 pre = '
      ' post = '
    ' when :bulletitem3 pre = '
  • * ' post = "
  • \n" when :bulletlist4 pre = '
      ' post = '
    ' when :bulletitem4 pre = '
  • * ' post = "
  • \n" when :numberlist1 pre = '
      ' post = '
    ' when :numberitem1 pre = "
  • #{@data[0]} " post = "
  • \n" when :numberlist2 pre = '
      ' post = '
    ' when :numberitem2 pre = "
  • #{@data[0]}.#{@data[1]} " post = "
  • \n" when :numberlist3 pre = '
      ' post = '
    ' when :numberitem3 pre = "
  • #{@data[0]}.#{@data[1]}.#{@data[2]} " post = "
  • \n" when :numberlist4 pre = '
      ' post = '
    ' when :numberitem4 pre = "
  • #{@data[0]}.#{@data[1]}.#{@data[2]}.#{@data[3]} " post = "
  • \n" when :img pre = "" when :ref pre = "" post = '' when :href pre = "" post = '' when :blockfunc pre = " href) when :href a = XMLElement.new('a', 'href' => @data.to_s) a['target'] = @richText.linkTarget if @richText.linkTarget a when :blockfunc noChilds = true checkHandler @richText.functionHandler(@data[0]).to_html(@data[1]) when :inlinefunc noChilds = true checkHandler @richText.functionHandler(@data[0]).to_html(@data[1]) when :italic XMLElement.new('i') when :bold XMLElement.new('b') when :fontCol XMLElement.new('span', 'style' => "color:#{@data}") when :code XMLElement.new('code', attrs) when :htmlblob noChilds = true XMLBlob.new(@children[0]) when :text noChilds = true XMLText.new(@children[0]) else raise "Unknown RichTextElement category #{@category}" end # Some elements never have leaves. return html if noChilds @children.each do |el_i| html << el_i.to_html html << XMLText.new(' ') if el_i.appendSpace end html end # Convert all childern into a single plain text String. def children_to_s text = '' @children.each do |c| text << c.to_s + (c.is_a?(RichTextElement) && c.appendSpace ? ' ' : '') end text end def checkHandler unless @data[0] && @data[0].is_a?(String) raise "Bad RichText function '#{@data[0]}' requested" end if @richText.functionHandler(@data[0]).nil? raise "No handler for #{@data[0]} registered" end end # This function converts a String into a new String that only contains # characters that are acceptable for HTML tag IDs. def convertToID(text) out = '' text.each_utf8_char do |c| out << c if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') out << '_' if c == ' ' end out.chomp('_') end private def sTitle(level) s = '' if @richText.sectionNumbers 1.upto(level) do |i| s += '.' unless s.empty? s += "#{@data[i - 1]}" end s += ') ' end s end def htmlTitle(level) attrs = { 'id' => convertToID(children_to_s) } attrs['class'] = @richText.cssClass if @richText.cssClass el = XMLElement.new("h#{level}", attrs) if @richText.sectionNumbers s = '' 1.upto(level) do |i| s += '.' unless s.empty? s += "#{@data[i - 1]}" end s += ' ' el << XMLText.new(s) end el end def htmlObject fileTypes = { 'png' => { 'type' => 'image/png' }, 'gif' => { 'type' => 'image/gif' }, 'jpg' => { 'type' => 'image/jpg' }, 'svg' => { 'type' => 'image/svg+xml', 'class' => 'img' }} # Error checking must have been done in the parser! # File types must be in sync with # RichTextSyntaxRules::rule_plainTextWithLinks return nil unless (index = @data.fileName.rindex('.')) extension = @data.fileName[index + 1..-1].downcase return nil unless (attributes = fileTypes[extension]) attributes['data'] = @data.fileName el = XMLElement.new('object', attributes) el['alt'] = @data.altText if @data.altText if @data.verticalAlign el['style'] = "vertical-align:#{@data.verticalAlign}; " end el end def textBlockFormat(indent, label, str, width) labLen = label.length TextFormatter.new(width, indent + labLen, indent).format(label + str) end def textBlockIndent(indent, str, width) TextFormatter.new(width, indent).indent(str) end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/RTFHandlers.rb0000644000175000017500000000201212614413013022622 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = RTFHandlers.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/RTFNavigator' require 'taskjuggler/RichText/RTFQuery' require 'taskjuggler/RichText/RTFReport' require 'taskjuggler/RichText/RTFReportLink' class TaskJuggler # This convenience class creates an Array containing all RichTextFunction # objects used by TaskJuggler. class RTFHandlers def RTFHandlers.create(project, sourceFileInfo = nil) [ RTFNavigator.new(project, sourceFileInfo), RTFQuery.new(project, sourceFileInfo), RTFReport.new(project, sourceFileInfo), RTFReportLink.new(project, sourceFileInfo) ] end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/FunctionHandler.rb0000644000175000017500000000257512614413013023607 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = FunctionHandler.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/MessageHandler' class TaskJuggler # This class is the abstract base class for all RichText function handlers. # A function handler is responsible for a certain function such as 'example' # or 'query'. functions are used in internal RichText references such as # '[[example:Allocation 2]]'. 'example' is the function, 'Allocation' is the # path and '2' is the first argument. Arguments are optional. The function # handler can turn such internal references into Strings or XMLElement # trees. Therefor, each derived handler needs to implement a to_s, to_html # and to_tagged method that takes two parameter. The first is the path, the # second is the argument Array. class RichTextFunctionHandler include MessageHandler attr_reader :function, :blockFunction def initialize(function, sourceFileInfo = nil) @function = function @blockFunction = false @sourceFileInfo = sourceFileInfo end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/Scanner.rb0000644000175000017500000001744412614413013022116 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Scanner.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/UTF8String' require 'taskjuggler/TextParser/Scanner' class TaskJuggler # The RichTextScanner is used by the RichTextParser to chop the input text # into digestable tokens. It specializes the TextScanner class for RichText # syntax. The scanner can operate in various modes. The current mode is # context dependent. The following modes are supported: # # :bop : at the begining of a paragraph. # :bol : at the begining of a line. # :inline : in the middle of a line # :nowiki : ignoring all MediaWiki special tokens # :html : read anything until # :ref : inside of a REF [[ .. ]] # :href : inside of an HREF [ .. ] # :func : inside of a block <[ .. ]> or inline <- .. -> function class RichTextScanner < TextParser::Scanner def initialize(masterFile, log) tokenPatterns = [ # :bol mode rules [ :LINEBREAK, /\s*\n/, :bol, method('linebreak') ], [ nil, /\s+/, :bol, method('inlineMode') ], # :bop mode rules [ :PRE, / [^\n]+\n?/, :bop, method('pre') ], [ nil, /\s*\n/, :bop, method('linebreak') ], # :inline mode rules [ :SPACE, /[ \t\n]+/, :inline, method('space') ], # :bop and :bol mode rules [ :INLINEFUNCSTART, /<-/, [ :bop, :bol, :inline ], method('functionStart') ], [ :BLOCKFUNCSTART, /<\[/, [ :bop, :bol ], method('functionStart') ], [ ':TITLE*', /={2,5}/, [ :bop, :bol ], method('titleStart') ], [ 'TITLE*END', /={2,5}/, :inline, method('titleEnd') ], [ 'BULLET*', /\*{1,4}[ \t]+/, [ :bop, :bol ], method('bullet') ], [ 'NUMBER*', /\#{1,4}[ \t]+/, [ :bop, :bol ], method('number') ], [ :HLINE, /----/, [ :bop, :bol ], method('inlineMode') ], # :bop, :bol and :inline mode rules # The token puts the scanner into :nowiki mode. [ nil, //, [ :bop, :bol, :inline ], method('nowikiStart') ], [ nil, //, [ :bop, :bol, :inline ], method('htmlStart') ], [ :FCOLSTART, //, [ :bop, :bol, :inline ], method('fontColorStart') ], [ :FCOLEND, /<\/fcol>/, [ :bop, :bol, :inline ], method('fontColorEnd') ], [ :QUOTES, /'{2,5}/, [ :bop, :bol, :inline ], method('quotes') ], [ :REF, /\[\[/, [ :bop, :bol, :inline ], method('refStart') ], [ :HREF, /\[/, [ :bop, :bol, :inline], method('hrefStart') ], [ :WORD, /.[^ \n\t\[<']*/, [ :bop, :bol, :inline ], method('inlineMode') ], # :nowiki mode rules [ nil, /<\/nowiki>/, :nowiki, method('nowikiEnd') ], [ :WORD, /(<(?!\/nowiki>)|[^ \t\n<])+/, :nowiki ], [ :SPACE, /[ \t]+/, :nowiki ], [ :LINEBREAK, /\s*\n/, :nowiki ], # :html mode rules [ :HTMLBLOB, /(.|\n)*<\/html>/ , :html, method('htmlEnd') ], [ :HTMLBLOB, /.*\n/ , :html ], # :ref mode rules [ :REFEND, /\]\]/, :ref, method('refEnd') ], [ :WORD, /(<(?!-)|(\](?!\])|[^|<\]]))+/, :ref ], [ :QUERY, /<-\w+->/, :ref, method('query') ], [ :LITERAL, /./, :ref ], # :href mode rules [ :HREFEND, /\]/, :href, method('hrefEnd') ], [ :WORD, /(<(?!-)|[^ \t\n\]<])+/, :href ], [ :QUERY, /<-\w+->/, :href, method('query') ], [ :SPACE, /[ \t\n]+/, :href ], # :func mode rules [ :INLINEFUNCEND, /->/ , :func, method('functionEnd') ], [ :BLOCKFUNCEND, /\]>/, :func, method('functionEnd') ], [ :ID, /[a-zA-Z_]\w*/, :func ], [ :STRING, /"(\\"|[^"])*"/, :func, method('dqString') ], [ :STRING, /'(\\'|[^'])*'/, :func, method('sqString') ], [ nil, /[ \t\n]+/, :func ], [ :LITERAL, /./, :func ] ] super(masterFile, log, tokenPatterns, :bop) end private def space(type, match) if match.index("\n") # If the match contains a linebreak we switch to :bol mode. self.mode = :bol # And return an empty string. match = '' end [ type, match ] end def linebreak(type, match) self.mode = :bop [ type, match ] end def inlineMode(type, match) self.mode = :inline [ type, match ] end def titleStart(type, match) self.mode = :inline [ "TITLE#{match.length - 1}".intern, match ] end def titleEnd(type, match) [ "TITLE#{match.length - 1}END".intern, match ] end def bullet(type, match) self.mode = :inline [ "BULLET#{match.count('*')}".intern, match ] end def number(type, match) self.mode = :inline [ "NUMBER#{match.count('#')}".intern, match ] end def fontColorStart(type, match) self.mode = :inline # Extract color name from colName = match[6..-2] if colName =~ /#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})/ # We've got a valid hex number. else validColors = %w( black maroon green olive navy purple teal silver gray red lime yellow blue fuchsia aqua white ) unless validColors.include?(colName) error('bad_color_name', "#{colName} is not a supported color. Use one of " + "#{validColors.join(', ')} or #RGB where 'R', 'G' and 'B' " + "are one or two digit hexadecimal numbers.") end end [ type, colName ] end def fontColorEnd(type, match) [ type, match ] end def quotes(type, match) self.mode = :inline types = [ nil, nil, :ITALIC, :BOLD , :CODE, :BOLDITALIC ] [ types[match.length], match ] end def htmlStart(type, match) self.mode = :html [ type, match ] end def htmlEnd(type, match) self.mode = :inline [ type, match[0..-8] ] end def nowikiStart(type, match) self.mode = :nowiki [ type, match ] end def nowikiEnd(type, match) self.mode = :inline [ type, match ] end def functionStart(type, match) # When restoring :bol or :bop mode, we need to switch to :inline mode. @funcLastMode = (@scannerMode == :bop || @scannerMode == :bol) ? :inline : @scannerMode self.mode = :func [ type, match ] end def functionEnd(type, match) self.mode = @funcLastMode @funcLastMode = nil [ type, match ] end def pre(type, match) [ type, match[1..-1] ] end def dqString(type, match) # Remove first and last character and remove backslashes from quoted # double quotes. [ type, match[1..-2].gsub(/\\"/, '"') ] end def sqString(type, match) # Remove first and last character and remove backslashes from quoted # single quotes. [ type, match[1..-2].gsub(/\\'/, "'") ] end def query(type, match) # Remove <- and ->. [ type, match[2..-3] ] end def hrefStart(type, match) # When restoring :bol or :bop mode, we need to switch to :inline mode. @hrefLastMode = (@scannerMode == :bop || @scannerMode == :bol) ? :inline : @scannerMode self.mode = :href [ type, match ] end def hrefEnd(type, match) self.mode = @hrefLastMode @hrefLastMode = nil [ type, match ] end def refStart(type, match) self.mode = :ref [ type, match ] end def refEnd(type, match) self.mode = :inline [ type, match ] end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/SyntaxRules.rb0000644000175000017500000003251312614413013023020 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = SyntaxRules.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler # This modules contains the syntax definition for the RichTextParser. The # defined syntax aims to be compatible to the most commonly used markup # elements of the MediaWiki system. See # http://en.wikipedia.org/wiki/Wikipedia:Cheatsheet for details. # # Linebreaks are treated just like spaces as word separators unless it is # followed by another newline or any of the start-of-line special # characters. These characters start sequences that mark headlines, bullet # items and such. The special meaning only gets activated when used at the # start of the line. # # The parser traverses the input text and creates a tree of RichTextElement # objects. This is the intermediate representation that can be converted to # the final output format. module RichTextSyntaxRules # This is the entry node. def rule_richtext pattern(%w( !sections . ), lambda { RichTextElement.new(@richTextI, :richtext, @val[0]) }) end def rule_sections optional repeatable pattern(%w( !section !blankLines ), lambda { @val[0] }) end # The following syntax elements are all block elements that can span # multiple lines. def rule_section pattern(%w( !headlines ), lambda { @val[0] }) pattern(%w( $HLINE ), lambda { RichTextElement.new(@richTextI, :hline, @val[0]) }) pattern(%w( !paragraph ), lambda { @val[0] }) pattern(%w( !pre ), lambda { RichTextElement.new(@richTextI, :pre, @val[0].join) }) pattern(%w( !bulletList1 ), lambda { RichTextElement.new(@richTextI, :bulletlist1, @val[0]) }) pattern(%w( !numberList1 ), lambda { @numberListCounter = [ 0, 0, 0, 0 ] RichTextElement.new(@richTextI, :numberlist1, @val[0]) }) pattern(%w( !blockFunction ), lambda { @val[0] }) end def rule_headlines pattern(%w( !title1 ), lambda { @val[0] }) pattern(%w( !title2 ), lambda { @val[0] }) pattern(%w( !title3 ), lambda { @val[0] }) pattern(%w( !title4 ), lambda { @val[0] }) end def rule_pre repeatable pattern(%w( $PRE ), lambda { @val[0] }) end def rule_title1 pattern(%w( $TITLE1 !space !text $TITLE1END ), lambda { el = RichTextElement.new(@richTextI, :title1, @val[2]) @sectionCounter[0] += 1 @sectionCounter[1] = @sectionCounter[2] = 0 el.data = @sectionCounter.dup el }) end def rule_title2 pattern(%w( $TITLE2 !space !text $TITLE2END ), lambda { el = RichTextElement.new(@richTextI, :title2, @val[2]) @sectionCounter[1] += 1 @sectionCounter[2] = 0 el.data = @sectionCounter.dup el }) end def rule_title3 pattern(%w( $TITLE3 !space !text $TITLE3END ), lambda { el = RichTextElement.new(@richTextI, :title3, @val[2]) @sectionCounter[2] += 1 @sectionCounter[3] = 0 el.data = @sectionCounter.dup el }) end def rule_title4 pattern(%w( $TITLE4 !space !text $TITLE4END ), lambda { el = RichTextElement.new(@richTextI, :title4, @val[2]) @sectionCounter[3] += 1 el.data = @sectionCounter.dup el }) end def rule_bulletList1 optional repeatable pattern(%w( $BULLET1 !text ), lambda { RichTextElement.new(@richTextI, :bulletitem1, @val[1]) }) pattern(%w( !bulletList2 ), lambda { RichTextElement.new(@richTextI, :bulletlist2, @val[0]) }) end def rule_bulletList2 repeatable pattern(%w( $BULLET2 !text ), lambda { RichTextElement.new(@richTextI, :bulletitem2, @val[1]) }) pattern(%w( !bulletList3 ), lambda { RichTextElement.new(@richTextI, :bulletlist3, @val[0]) }) end def rule_bulletList3 repeatable pattern(%w( $BULLET3 !text ), lambda { RichTextElement.new(@richTextI, :bulletitem3, @val[1]) }) pattern(%w( !bulletList4 ), lambda { RichTextElement.new(@richTextI, :bulletlist4, @val[0]) }) end def rule_bulletList4 repeatable pattern(%w( $BULLET4 !text ), lambda { RichTextElement.new(@richTextI, :bulletitem4, @val[1]) }) end def rule_numberList1 repeatable pattern(%w( $NUMBER1 !text !blankLines ), lambda { el = RichTextElement.new(@richTextI, :numberitem1, @val[1]) @numberListCounter[0] += 1 el.data = @numberListCounter.dup el }) pattern(%w( !numberList2 ), lambda { @numberListCounter[1, 2] = [ 0, 0 ] RichTextElement.new(@richTextI, :numberlist2, @val[0]) }) end def rule_numberList2 repeatable pattern(%w( $NUMBER2 !text !blankLines ), lambda { el = RichTextElement.new(@richTextI, :numberitem2, @val[1]) @numberListCounter[1] += 1 el.data = @numberListCounter.dup el }) pattern(%w( !numberList3 ), lambda { @numberListCounter[2] = 0 RichTextElement.new(@richTextI, :numberlist3, @val[0]) }) end def rule_numberList3 repeatable pattern(%w( $NUMBER3 !text !blankLines ), lambda { el = RichTextElement.new(@richTextI, :numberitem3, @val[1]) @numberListCounter[2] += 1 el.data = @numberListCounter.dup el }) pattern(%w( !numberList4 ), lambda { @numberListCounter[3] = 0 RichTextElement.new(@richTextI, :numberlist4, @val[0]) }) end def rule_numberList4 repeatable pattern(%w( $NUMBER4 !text !blankLines ), lambda { el = RichTextElement.new(@richTextI, :numberitem4, @val[1]) @numberListCounter[3] += 1 el.data = @numberListCounter.dup el }) end def rule_paragraph pattern(%w( !text ), lambda { RichTextElement.new(@richTextI, :paragraph, @val[0]) }) end def rule_text pattern(%w( !textWithSpace ), lambda { @val[0].last.appendSpace = false @val[0] }) end def rule_textWithSpace repeatable pattern(%w( !plainTextWithLinks ), lambda { @val[0] }) pattern(%w( !inlineFunction ), lambda { @val[0] }) pattern(%w( $ITALIC !space !plainTextWithLinks $ITALIC !space ), lambda { el = RichTextElement.new(@richTextI, :italic, @val[2]) # Since the italic end marker will disappear we need to make sure # there was no space before it. @val[2].last.appendSpace = false if @val[2].last el.appendSpace = !@val[4].nil? el }) pattern(%w( $BOLD !space !plainTextWithLinks $BOLD !space ), lambda { el = RichTextElement.new(@richTextI, :bold, @val[2]) @val[2].last.appendSpace = false if @val[2].last el.appendSpace = !@val[4].nil? el }) pattern(%w( $CODE !space !plainTextWithLinks $CODE !space ), lambda { el = RichTextElement.new(@richTextI, :code, @val[2]) @val[2].last.appendSpace = false if @val[2].last el.appendSpace = !@val[4].nil? el }) pattern(%w( $BOLDITALIC !space !plainTextWithLinks $BOLDITALIC !space ), lambda { el = RichTextElement.new(@richTextI, :bold, RichTextElement.new(@richTextI, :italic, @val[2])) @val[2].last.appendSpace = false if @val[2].last el.appendSpace = !@val[4].nil? el }) pattern(%w( $FCOLSTART !space !plainTextWithLinks $FCOLEND !space ), lambda { el = RichTextElement.new(@richTextI, :fontCol, @val[2]) el.data = @val[0] el.appendSpace = !@val[4].nil? el }) end def rule_plainTextWithLinks pattern(%w( !plainText ), lambda { @val[0] }) pattern(%w( $REF !refToken !moreRefToken $REFEND !space ), lambda { v1 = @val[1].join if v1.index(':') protocol, locator = v1.split(':') else protocol = nil end el = nil if protocol == 'File' el = RichTextElement.new(@richTextI, :img) unless (index = locator.rindex('.')) error('rt_file_no_ext', "File name without extension: #{locator}") end extension = locator[index + 1..-1].downcase unless %w( jpg gif png svg ).include?(extension) error('rt_file_bad_ext', "Unsupported file type: #{extension}") end el.data = img = RichTextImage.new(locator) if @val[2] @val[2].each do |token| if token[0, 4] == 'alt=' img.altText = token[4..-1] elsif %w( top middle bottom baseline sub super text-top text-bottom ).include?(token) img.verticalAlign = token else error('rt_bad_file_option', "Unknown option '#{token}' for file reference " + "#{v1}.") end end end else val = @val[2] || v1 el = RichTextElement.new(@richTextI, :ref, RichTextElement.new(@richTextI, :text, val)) el.data = v1 el.appendSpace = !@val[4].nil? end el }) pattern(%w( $HREF !wordWithQueries !space !plainTextWithQueries $HREFEND !space ), lambda { el = RichTextElement.new(@richTextI, :href, @val[3] || @val[1]) el.data = RichTextElement.new(@richTextI, :richtext, @val[1]) el.appendSpace = !@val[5].nil? el }) end def rule_moreRefToken repeatable optional pattern(%w( _| !refToken ), lambda { @val[1].join }) end def rule_refToken repeatable pattern(%w( $WORD ), lambda { @val[0] }) end def rule_wordWithQueries repeatable pattern(%w( $WORD ), lambda { RichTextElement.new(@richTextI, :text, @val[0]) }) pattern(%w( $QUERY ), lambda { # The <-attributeID-> syntax is a shortcut for an embedded query # inline function. It can only be used within a ReportTableCell # context that provides a property and a scope property. el = RichTextElement.new(@richTextI, :inlinefunc) # Data is a 2 element Array with the function name and a Hash for the # arguments. el.data = ['query', { 'attribute' => @val[0] } ] el }) end def rule_plainText repeatable optional pattern(%w( !htmlBlob !space ), lambda { el = RichTextElement.new(@richTextI, :htmlblob, @val[0].join) el.appendSpace = !@val[1].nil? el }) pattern(%w( $WORD !space ), lambda { el = RichTextElement.new(@richTextI, :text, @val[0]) el.appendSpace = !@val[1].nil? el }) end def rule_plainTextWithQueries repeatable optional pattern(%w( !wordWithQueries !space ), lambda { @val[0][-1].appendSpace = true if @val[1] @val[0] }) end def rule_htmlBlob repeatable pattern(%w( $HTMLBLOB ), lambda { @val[0] }) end def rule_space optional repeatable pattern(%w( $SPACE ), lambda { true }) end def rule_blankLines optional repeatable pattern(%w( $LINEBREAK )) pattern(%w( $SPACE )) end def rule_blockFunction pattern(%w( $BLOCKFUNCSTART $ID !functionArguments $BLOCKFUNCEND ), lambda { args = {} @val[2].each { |arg| args[arg[0]] = arg[1] } if @val[2] el = RichTextElement.new(@richTextI, :blockfunc) # Data is a 2 element Array with the function name and a Hash for the # arguments. unless @richTextI.richText.functionHandler(@val[1], true) error('bad_block_function', "Unsupported block function #{@val[1]}") end el.data = [@val[1], args ] el }) end def rule_inlineFunction pattern(%w( $INLINEFUNCSTART $ID !functionArguments $INLINEFUNCEND !space ), lambda { args = {} @val[2].each { |arg| args[arg[0]] = arg[1] } if @val[2] el = RichTextElement.new(@richTextI, :inlinefunc) # Data is a 2 element Array with the function name and a Hash for the # arguments. unless @richTextI.richText.functionHandler(@val[1], false) error('bad_inline_function', "Unsupported inline function #{@val[1]}") end el.data = [@val[1], args ] el.appendSpace = !@val[4].nil? el }) end def rule_functionArguments optional repeatable pattern(%w( $ID _= $STRING ), lambda { [ @val[0], @val[2] ] }) end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/Document.rb0000644000175000017500000001131112614413013022266 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Document.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/Snip' require 'taskjuggler/RichText/TableOfContents' require 'taskjuggler/RichText/FunctionHandler' class TaskJuggler # A RichTextDocument object collect a set of structured text files into a # single document. This document may have a consistent table of contents # across all files and can be turned into a set of corresponding HTML files. # This class is an abstract class. To use it, a derrived class must define # the functions generateHTMLCover, generateStyleSheet, generateHTMLHeader # and generateHTMLFooter. class RichTextDocument attr_reader :functionHandlers # Create a new empty RichTextDocument object. def initialize @functionHandlers = [] @snippets = [] @dirty = false @sectionCounter = [ 0, 0, 0 ] @linkTarget = nil @toc = nil @anchors = [] end # Register a new RichTextFunctionHandler for this document. def registerFunctionHandler(handler) @functionHandlers << handler end # Add a new structured text file to the document. _file_ must be the name of # a file with RichText compatible syntax elements. def addSnip(file) @snippets << (snippet = RichTextSnip.new(self, file, @sectionCounter)) snippet.linkTarget = @linkTarget @dirty = true snippet end # Call this method to generate a table of contents for all files that were # registered so far. The table of contents is stored internally and will be # used when the document is produced in a new format. This function also # collects a list of all snip names to @anchors and gathers a list of # all references to other snippets in @references. As these two lists will # be used by RichTextDocument#checkInternalReferences this function must be # called first. def tableOfContents @toc = TableOfContents.new @references = {} @anchors = [] # Collect all file names as potentencial anchors. @snippets.each do |snip| snip.tableOfContents(@toc, snip.name) @anchors << snip.name (refs = snip.internalReferences).empty? || @references[snip.name] = refs end # Then add all section entries as well. We use the HTML style # # notation. @toc.each do |tocEntry| @anchors << tocEntry.file + '#' + tocEntry.tag end end # Make sure that all internal references only point to known snippets. def checkInternalReferences @references.each do |snip, refs| refs.each do |reference| unless @anchors.include?(reference) # TODO: Probably an Exception is cleaner here. puts "Warning: Rich text file #{snip} references unknown " + "object #{reference}" end end end end # Generate HTML files for all registered text files. The files have the same # name as the orginal files with '.html' appended. The files will be # generated into the _directory_. _directory_ must be empty or a valid path # name that is terminated with a '/'. A table of contense is generated into # a file called 'toc.html'. def generateHTML(directory = '') crossReference generateHTMLTableOfContents(directory) @snippets.each do |snip| snip.generateHTML(directory) end end private # Register the previous and next file with each of the text files. This # function is used by the output generators to have links to the next and # previous file in the sequence embedded into the generated files. def crossReference return unless @dirty prevSnip = nil @snippets.each do |snip| if prevSnip snip.prevSnip = prevSnip prevSnip.nextSnip = snip end prevSnip = snip end @dirty = false end # Generate a HTML file with the table of contense for all registered files. def generateHTMLTableOfContents(directory) html = HTMLDocument.new head = html.generateHead('Index') html.html << (body = XMLElement.new('body')) body << generateHTMLCover << @toc.to_html << XMLElement.new('br', {}, true) << XMLElement.new('hr', {}, true) << XMLElement.new('br', {}, true) << generateHTMLFooter html.write(directory + 'toc.html') end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/TableOfContents.rb0000644000175000017500000000263112614413013023547 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TableOfContents.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/XMLElement' require 'taskjuggler/RichText/TOCEntry' class TaskJuggler # This class can be used to store a table of contents. It's just an Array of # TOCEntry objects. Each TOCEntry objects represents the title of a section. class TableOfContents # Create an empty TableOfContents object. def initialize @entries = [] end # This method must be used to add new TOCEntry objects to the # TableOfContents. _entry_ must be a TOCEntry object reference. def addEntry(entry) @entries << entry end def each @entries.each { |e| yield e } end # Return HTML elements that represent the content of the TableOfContents # object. The result is a tree of XMLElement objects. def to_html div = XMLElement.new('div', 'style' => 'margin-left:15%; margin-right:15%;') div << (table = XMLElement.new('table')) @entries.each { |e| table << e.to_html } div end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/RTFReport.rb0000644000175000017500000000435212614413013022346 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = RTFReport.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/FunctionHandler' require 'taskjuggler/XMLElement' class TaskJuggler # This class is a specialized RichTextFunctionHandler that includes a # report into the RichText output for supported formats. class RTFReport < RichTextFunctionHandler def initialize(project, sourceFileInfo = nil) @project = project super('report', sourceFileInfo) @blockFunction = true end # Not supported for this function def to_s(args) '' end # Return a HTML tree for the report. def to_html(args) if args.nil? || (id = args['id']).nil? error('rtp_report_id', "Argument 'id' missing to specify the report to be used.") return nil end unless (report = @project.report(id)) error('rtp_report_unknown_id', "Unknown report #{id}") return nil end # Detect recursive nesting found = false @project.reportContexts.each do |c| if c.report == report found = true break end end if found stack = "" @project.reportContexts.each do |context| stack += ' -> ' unless stack.empty? stack += '[ ' if context.report == report stack += context.report.fullId end stack += " -> #{report.fullId} ] ..." error('rtp_report_recursion', "Recursive nesting of reports detected: #{stack}") return nil end # Create a new context for the report. @project.reportContexts.push(ReportContext.new(@project, report)) # Generate the report with the new context report.generateIntermediateFormat html = report.to_html @project.reportContexts.pop html end # Not supported for this function. def to_tagged(args) nil end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/Snip.rb0000644000175000017500000000641712614413013021434 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Snip.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText' require 'taskjuggler/HTMLDocument' class TaskJuggler # A RichTextSnip is a building block for a RichTextDocument. It represents # the contense of a text file that contains structured text using the # RichText syntax. The class can read-in such a text file and generate an # equivalent HTML version. class RichTextSnip attr_reader :name attr_accessor :prevSnip, :nextSnip # Create a RichTextSnip object. _document_ is a reference to the # RichTextDocument. _fileName_ is the name of the structured text file # using RichText syntax. _sectionCounter_ is an 3 item Fixnum Array. These # 3 numbers are used to store the section counters over multiple # RichTextSnip objects. def initialize(document, fileName, sectionCounter) @document = document # Strip any directories from fileName. @name = fileName.index('/') ? fileName[fileName.rindex('/') + 1 .. -1] : fileName text = '' File.open(fileName) do |file| file.each_line { |line| text += line } end rText = RichText.new(text, @document.functionHandlers) unless (@richText = rText.generateIntermediateFormat(sectionCounter)) exit end @prevSnip = @nextSnip = nil end # Set the target for all anchor links in the document. def linkTarget=(target) @richText.linkTarget = target end # Set the CSS class. def cssClass=(css) @richText.cssClass = css end # Generate a TableOfContents object from the section headers of the # RichTextSnip. def tableOfContents(toc, fileName) @richText.tableOfContents(toc, fileName) end # Return an Array with all other snippet names that are referenced by # internal references in this snip. def internalReferences @richText.internalReferences end # Generate a HTML version of the structured text. The base file name is the # same as the original file. _directory_ is the name of the output # directory. def generateHTML(directory = '') html = HTMLDocument.new head = html.generateHead(@name) head << @document.generateStyleSheet html.html << (body = XMLElement.new('body')) body << @document.generateHTMLHeader body << generateHTMLNavigationBar body << (div = XMLElement.new('div', 'style' => 'width:90%; margin-left:5%; margin-right:5%')) div << @richText.to_html body << generateHTMLNavigationBar body << @document.generateHTMLFooter html.write(directory + @name + '.html') end private def generateHTMLNavigationBar @document.generateHTMLNavigationBar( @prevSnip ? @prevSnip.name : nil, @prevSnip ? "#{prevSnip.name}.html" : nil, @nextSnip ? @nextSnip.name : nil, @nextSnip ? "#{nextSnip.name}.html" : nil) end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/RTFQuery.rb0000644000175000017500000001647012614413013022204 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = RTFQuery.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/RTFWithQuerySupport' require 'taskjuggler/XMLElement' require 'taskjuggler/Query' class TaskJuggler # This class is a specialized RichTextFunctionHandler that can be used to # query the value of a project or property attribute. class RTFQuery < RTFWithQuerySupport def initialize(project, sourceFileInfo = nil) @project = project super('query', sourceFileInfo) @blockMode = false end # Return the result of the query as String. def to_s(args) return '' unless (query = prepareQuery(args)) if query.ok query.to_s else error('query_error', query.errorMessage + recreateQuerySyntax(args)) 'Query Error: ' + query.errorMessage end end # Return a XMLElement tree that represents the navigator in HTML code. def to_html(args) return nil unless (query = prepareQuery(args)) if query.ok if (rti = query.to_rti) rti.to_html elsif (str = query.to_s) XMLText.new(str) else nil end else error('query_error', query.errorMessage + recreateQuerySyntax(args)) font = XMLElement.new('font', 'color' => '#FF0000') font << XMLText.new('Query Error: ' + query.errorMessage) font end end # Not supported for this function. def to_tagged(args) nil end private def prepareQuery(args) unless @query raise "No Query has been registered for this RichText yet!" end query = @query.dup # Check the user provided arguments. Only the following list is allowed. validArgs = %w( attribute currencyformat end family journalattributes journalmode loadunit numberformat property scenario scopeproperty start timeformat ) expandedArgs = {} args.each do |arg, value| unless validArgs.include?(arg) error('bad_query_parameter', "Unknown query parameter '#{arg}'. " + "Use one of #{validArgs.join(', ')}!") return nil end expandedArgs[arg] = SimpleQueryExpander.new(value, @query, @sourceFileInfo).expand end if ((expandedArgs['property'] && expandedArgs['property'][0] != '!') || expandedArgs['scopeproperty']) && !(expandedArgs['family'] || @query.propertyType) error('missing_family', "If you provide a property or scope property you need to " + "provide a family type as well.") end # Every provided query parameter will overwrite the corresponding value # in the Query that was provided by the ReportContext. The name of the # arguments don't always exactly match the Query variables Let's start # with the easy ones. if expandedArgs['property'] query.propertyId = expandedArgs['property'] query.property = nil unless query.propertyId[0] == '!' end if expandedArgs['scopeproperty'] query.scopePropertyId = expandedArgs['scopeproperty'] query.scopeProperty = nil end query.attributeId = expandedArgs['attribute'] if expandedArgs['attribute'] query.start = TjTime.new(expandedArgs['start']) if expandedArgs['start'] query.end = TjTime.new(expandedArgs['end']) if expandedArgs['end'] if expandedArgs['numberformat'] query.numberFormat = expandedArgs['numberformat'] end query.timeFormat = expandedArgs['timeformat'] if expandedArgs['timeformat'] if expandedArgs['currencyformat'] query.currencyFormat = expandedArgs['currencyformat'] end query.project = @project # And now the slighly more complicated ones. setScenarioIdx(query, expandedArgs) setPropertyType(query, expandedArgs) setLoadUnit(query, expandedArgs) setJournalMode(query, expandedArgs) setJournalAttributes(query, expandedArgs) # Now that we have put together the query, we can process it and return # the query object for result extraction. query.process query end # Regenerate the original query text based on the argument list. def recreateQuerySyntax(args) queryText = "\n<-query" args.each do |a, v| queryText += " #{a}=\"#{v}\"" end queryText += "->" end def setPropertyType(query, args) validTypes = { 'account' => :Account, 'task' => :Task, 'resource' => :Resource } if args['family'] unless validTypes[args['family']] error('rtfq_bad_query_family', "Unknown query family type '#{args['family']}'. " + "Use one of #{validTypes.keys.join(', ')}!") end query.propertyType = validTypes[args['family']] if query.propertyType == :Task query.scopePropertyType = :Resource elsif query.propertyType == :Resource query.scopePropertyType = :Task end end end def setLoadUnit(query, args) units = { 'days' => :days, 'hours' => :hours, 'longauto' => :longauto, 'minutes' => :minutes, 'months' => :months, 'quarters' => :quarters, 'shortauto' => :shortauto, 'weeks' => :weeks, 'years' => :years } query.loadUnit = units[args['loadunit']] if args['loadunit'] end def setScenarioIdx(query, args) if args['scenario'] scenarioIdx = @project.scenarioIdx(args['scenario']) unless scenarioIdx error('rtfq_bad_scenario', "Unknown scenario #{args['scenario']}") end query.scenarioIdx = scenarioIdx end # Default to 0 in case no scenario was provided. query.scenarioIdx = 0 unless query.scenarioIdx end def setJournalMode(query, args) if (mode = args['journalmode']) validModes = %w( journal journal_sub status_up status_down alerts_down ) unless validModes.include?(mode) error('rtfq_bad_journalmode', "Unknown journalmode #{mode}. Must be one of " + "#{validModes.join(', ')}.") end query.journalMode = mode.intern elsif !query.journalMode query.journalMode = :journal end end def setJournalAttributes(query, args) if (attrListStr = args['journalattributes']) attrs = attrListStr.split(', ').map { |a| a.delete(' ') } query.journalAttributes = [] validAttrs = %w( author date details flags headline property propertyid summary timesheet ) attrs.each do |attr| if validAttrs.include?(attr) query.journalAttributes << attr else error('rtfq_bad_journalattr', "Unknown journalattribute #{attr}. Must be one of " + "#{validAttrs.join(', ')}.") end end elsif !query.journalAttributes query.journalAttributes = %w( date summary details ) end end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/Parser.rb0000644000175000017500000000603412614413013021752 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Parser.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/TextParser' require 'taskjuggler/RichText/Scanner' require 'taskjuggler/RichText/SyntaxRules' require 'taskjuggler/Log' class TaskJuggler # This is the parser class used by the RichText class to convert the input # text into an intermediate representation. Most of the actual work is done # by the generic TextParser class. The syntax description for the markup # language is provided by the RichTextSyntaxRules module. To break the input # String into tokens, the RichTextScanner class is used. class RichTextParser < TextParser include RichTextSyntaxRules # Create the parser and initialize the rule set. _rt_ is the RichText object # the resulting tree of RichTextElement objects should belong to. def initialize(rti, sectionCounter = [ 0, 0, 0, 0 ], tokenSet = nil) super() @richTextI = rti # These are the tokens that can be returned by the RichTextScanner. @variables = [ :LINEBREAK, :SPACE, :WORD, :BOLD, :ITALIC, :CODE, :BOLDITALIC, :PRE, :HREF, :HREFEND, :REF, :REFEND, :HLINE, :HTMLBLOB, :FCOLSTART, :FCOLEND, :QUERY, :INLINEFUNCSTART, :INLINEFUNCEND, :BLOCKFUNCSTART, :BLOCKFUNCEND, :ID, :STRING, :TITLE1, :TITLE2, :TITLE3, :TITLE4, :TITLE1END, :TITLE2END, :TITLE3END, :TITLE4END, :BULLET1, :BULLET2, :BULLET3, :BULLET4, :NUMBER1, :NUMBER2, :NUMBER3, :NUMBER4 ] limitTokenSet(tokenSet) # Load the rule set into the parser. initRules updateParserTables # The sections and numbered list can each nest 3 levels deep. We use these # counter Arrays to generate proper 1.2.3 type labels. @sectionCounter = sectionCounter @numberListCounter = [ 0, 0, 0, 0 ] end def reuse(rti, sectionCounter = [ 0, 0, 0, 0], tokenSet = nil) @blockedVariables = {} @stack = nil @richTextI = rti @sectionCounter = sectionCounter limitTokenSet(tokenSet) end # Construct the parser and get ready to read. def open(text) # Make sure that the last line is properly terminated with a newline. # Multiple newlines at the end are simply ignored. @scanner = RichTextScanner.new(text + "\n\n", Log) @scanner.open(true) end # Get the next token from the scanner. def nextToken @scanner.nextToken end # Return the last fetch token again to the scanner. def returnToken(token) @scanner.returnToken(token) end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/RTFNavigator.rb0000644000175000017500000000300512614413013023017 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = RTFNavigator.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/FunctionHandler' require 'taskjuggler/XMLElement' require 'taskjuggler/reports/Navigator' class TaskJuggler # This class is a specialized RichTextFunctionHandler that generates a # navigation bar for all reports that match the specified LogicalExpression. # It currently only supports HTML. class RTFNavigator < RichTextFunctionHandler def initialize(project, sourceFileInfo = nil) @project = project super('navigator', sourceFileInfo) @blockFunction = true end # Not supported for this function def to_s(args) '' end # Return a XMLElement tree that represents the navigator in HTML code. def to_html(args) if args.nil? || (id = args['id']).nil? error('rtf_nav_id_missing', "Argument 'id' missing to specify the navigator to be used.") end unless (navBar = @project['navigators'][id]) error('rtf_nav_unknown_id', "Unknown navigator #{id}") end navBar.to_html end # Not supported for this function. def to_tagged(args) nil end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/TOCEntry.rb0000644000175000017500000000515412614413013022167 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = TOCEntry.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/UTF8String' require 'taskjuggler/XMLElement' class TaskJuggler # A TOCEntry object is used to store the data of an entry in a TableOfContents # object. It stores the section number, the title, the file name and the name # of the tag in this file. The tag is optional and may be nil. The object can # be turned into an HTML tree. class TOCEntry attr_reader :number, :title, :file, :tag # Create a TOCEntry object. # _number_: The section number as String, e. g. '1.2.3' or 'A.3'. # _title_: The section title as String. # _file_: The name of the file. # _tag_: An optional tag within the file. def initialize(number, title, file, tag = nil) @number = number @title = title @file = file @tag = tag end # Return the TOCEntry as equivalent HTML elements. The result is an Array of # XMLElement objects. def to_html html = [] if level == 0 # A another table line for some extra distance above main chapters. html << (tr = XMLElement.new('tr')) tr << (td = XMLElement.new('td')) td << XMLElement.new('div', 'style' => 'height:10px') end # Use a different font size depending on the element level. fontSizes = [ 20, 17, 15, 14, 14 ] tr = XMLElement.new('tr', 'style' => "font-size:#{fontSizes[level]}px;") tr << (td = XMLElement.new('td', 'style' => "width:30px;")) # Top-level headings have their number in the left column. td << XMLText.new(@number) if level == 0 tr << (td = XMLElement.new('td')) if level > 0 # Lower level headings have their number in the right column with the # heading text. td << XMLElement.new('span', 'style' => 'padding-right:15px') do XMLText.new(@number) end end tag = @tag ? "##{@tag}" : '' td << (a = XMLElement.new('a', 'href' => "#{@file}.html#{tag}")) a << XMLText.new(@title) html << tr html end private # Returns the level of the section. It simply counts the number of dots in # the section number. def level lev = 0 @number.each_utf8_char { |c| lev += 1 if c == '.' } lev end end end taskjuggler-3.5.0/lib/taskjuggler/RichText/RTFReportLink.rb0000644000175000017500000000447712614413013023174 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = RTFReportLink.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/RTFWithQuerySupport' require 'taskjuggler/XMLElement' require 'taskjuggler/URLParameter' require 'taskjuggler/SimpleQueryExpander' class TaskJuggler # This class is a specialized RichTextFunctionHandler that generates a link # to another report. It's not available on all output formats. class RTFReportLink < RTFWithQuerySupport def initialize(project, sourceFileInfo = nil) @project = project super('reportlink', sourceFileInfo) @blockFunction = false @query = nil end # Not supported for this function def to_s(args) report = checkArgs(args) report.name end # Return a HTML tree for the report. def to_html(args) report = checkArgs(args) # The URL for interactive reports is different than for static reports. if report.interactive? # The project and report ID must be provided as query. url = "taskjuggler?project=#{@project['projectid']};" + "report=#{report.fullId}" if args['attributes'] qEx = SimpleQueryExpander.new(args['attributes'], @query, @sourceFileInfo) url += ";attributes=" + URLParameter.encode(qEx.expand) end else # The report name just gets a '.html' extension. url = report.name + ".html" end a = XMLElement.new('a', 'href'=> url) a << XMLText.new(report.name) a end # Not supported for this function. def to_tagged(args) nil end private def checkArgs(args) if args.nil? || (id = args['id']).nil? error('rtp_report_id', "Argument 'id' missing to specify the report to be used.") return nil end unless (report = @project.report(id)) error('rtp_report_unknown_id', "Unknown report #{id}") return nil end report end end end taskjuggler-3.5.0/lib/taskjuggler/Booking.rb0000644000175000017500000000253312614413013020354 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Booking.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # class TaskJuggler class Booking attr_reader :resource, :task, :intervals attr_accessor :sourceFileInfo, :overtime, :sloppy def initialize(resource, task, intervals) @resource = resource @task = task @intervals = intervals @sourceFileInfo = nil @overtime = 0 @sloppy = 0 end def to_s out = "#{@resource.fullId} " first = true @intervals.each do |iv| if first first = false else out += ", " end out += "#{iv.start} + #{(iv.end - iv.start) / 3600}h" end end def to_tjp(taskMode) out = taskMode ? "#{@task.fullId} " : "#{@resource.fullId} " first = true @intervals.each do |iv| if first first = false else out += ",\n" end out += "#{iv.start} + #{(iv.end - iv.start) / 3600}h" end out += ' { overtime 2 }' end end end taskjuggler-3.5.0/lib/taskjuggler/SyntaxReference.rb0000644000175000017500000002547612614413013022104 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = SyntaxReference.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/KeywordDocumentation' require 'taskjuggler/ProjectFileParser' require 'taskjuggler/HTMLDocument' require 'taskjuggler/MessageHandler' class TaskJuggler # This class can traverse the syntax rules of the ProjectFileParser and # extract all documented keywords including their arguments and relations. # All this work in done in the contructor. The documentation can then be # generated for all found keyword or just a single one. Currently plain text # output as well as HTML files are supported. class SyntaxReference attr_reader :keywords # The constructor is the most important function of this class. It creates # a parser object and then traverses all rules and extracts the documented # patterns. In a second pass the extracted KeywordDocumentation objects # are then cross referenced to capture their relationships. _manual_ is an # optional reference to the UserManual object that uses this # SyntaxReference. def initialize(manual = nil, ignoreOld = false) @manual = manual @parser = ProjectFileParser.new @parser.updateParserTables # This hash stores all documented keywords using the keyword as # index. @keywords = {} @parser.rules.each_value do |rule| rule.patterns.each do |pattern| # Only patterns that are documented are of interest. next if pattern.doc.nil? # Ignore deprecated and removed keywords if requested next if ignoreOld && [ :deprecated, :removed ].include?(pattern.supportLevel) # Make sure each keyword is unique. if @keywords.include?(pattern.keyword) raise "Multiple patterns have the same keyword #{pattern.keyword}" end argDocs = [] # Create a new KeywordDocumentation object and fill-in all extracted # values. kwd = KeywordDocumentation.new(rule, pattern, pattern.to_syntax(argDocs, @parser.rules), argDocs, optionalAttributes(pattern, {}), @manual) @keywords[pattern.keyword] = kwd end end # Make sure all references to other keywords are present. @keywords.each_value do |kwd| kwd.crossReference(@keywords, @parser.rules) end # Figure out whether the keyword describes an inheritable attribute or # not. @keywords.each_value do |kwd| kwd.computeInheritance(@keywords, @parser.rules) end end # Return a sorted Array with all keywords (as String objects). def all sorted = @keywords.keys.sort # Register the neighbours with each keyword so we can use this info in # navigation bars. pred = nil sorted.each do |kwd| keyword = @keywords[kwd] pred.successor = keyword if pred keyword.predecessor = pred pred = keyword end end # Generate entries for a TableOfContents for each of the keywords. The # entries are appended to the TableOfContents _toc_. _sectionPrefix_ is the # prefix that is used for the chapter numbers. In case we have 20 keywords # and _sectionPrefix_ is 'A', the keywords will be enumerated 'A.1' to # 'A.20'. def tableOfContents(toc, sectionPrefix) keywords = all # Set the chapter name to 'Syntax Reference' with a link to the first # keyword. toc.addEntry(TOCEntry.new(sectionPrefix, 'Syntax Reference', keywords[0])) i = 1 keywords.each do |keyword| title = @keywords[keyword].title toc.addEntry(TOCEntry.new("#{sectionPrefix}.#{i}", title, keyword)) i += 1 end end def internalReferences references = {} @keywords.each_value do |keyword| (refs = keyword.references.uniq).empty? || references[keyword.keyword] = refs end references end # Generate a documentation for the keyword or an error message. The result # is a multi-line plain text String for known keywords. In case of an error # the result is empty but an error message will be send to $stderr. def to_s(keyword) if checkKeyword(keyword) @keywords[keyword].to_s else '' end end # Generate a documentation for the keyword or an error message. The result # is a XML String for known keywords. In case of an error the result is # empty but an error message will be send to $stderr. def generateHTMLreference(directory, keyword) if checkKeyword(keyword) @keywords[keyword].generateHTML(directory) else '' end end # Generate 2 files named navbar.html and alphabet.html. They are used to # support navigating through the syntax reference. def generateHTMLnavbar(directory, keywords) html = HTMLDocument.new head = html.generateHead('TaskJuggler Syntax Reference Navigator') head << XMLElement.new('base', 'target' => 'display') html.html << (body = XMLElement.new('body')) body << XMLNamedText.new('Table Of Contents', 'a', 'href' => 'toc.html') body << XMLElement.new('br', {}, true) normalizedKeywords = {} keywords.each do |keyword| normalizedKeywords[@keywords[keyword].title] = keyword end letter = nil letters = [] normalizedKeywords.keys.sort!.each do |normalized| if normalized[0, 1] != letter letter = normalized[0, 1] letters << letter body << (h = XMLElement.new('h3')) h << XMLNamedText.new(letter.upcase, 'a', 'name' => letter) end keyword = normalizedKeywords[normalized] body << XMLNamedText.new("#{normalized}", 'a', 'href' => "#{keyword}.html") body << XMLElement.new('br', {}, true) end html.write(directory + 'navbar.html') html = HTMLDocument.new head = html.generateHead('TaskJuggler Syntax Reference Navigator') head << XMLElement.new('base', 'target' => 'navigator') html.html << (body = XMLElement.new('body')) body << (divf = XMLElement.new('div')) divf << (form = XMLElement.new( 'form', 'action' => 'http://www.google.com/search', 'method' => "get", 'target' => '_blank', 'style' => 'margin:0')) form << XMLElement.new('input', 'type' => 'text', 'value' => '', 'maxlength' => '255', 'size' => '25', 'name' => 'q') form << XMLElement.new('input', 'type' => 'submit', 'value' => 'Search') form << XMLElement.new('input', 'type' => 'hidden', 'value' => 'taskjuggler.org/manual', 'name' => 'sitesearch') body << (h3 = XMLElement.new('h3')) letters.each do |l| h3 << XMLNamedText.new(l.upcase, 'a', 'href' => "navbar.html##{l}") end html.write(directory + 'alphabet.html') end private # Find optional attributes and return them hashed by the defining pattern. def optionalAttributes(pattern, stack) # If we hit an endless recursion we won't find any attributes. So we push # each pattern we process on the 'stack'. If we hit it again, we just # return an empty hash. return {} if stack[pattern] # If we hit a pattern that is documented, we ignore it. return {} if !stack.empty? && pattern.doc # Push pattern onto 'stack'. stack[pattern] = true if pattern[0][1] == '{' && pattern[2][1] == '}' # We have found an optional attribute pattern! return attributes(pattern[1], false) end # If a token of the pattern is a reference, we recursively # follow the reference to the next pattern. pattern.each do |type, name| if type == :reference rule = @parser.rules[name] # Rules with multiple patterns won't lead to attributes. next if rule.patterns.length > 1 attrs = optionalAttributes(rule.patterns[0], stack) return attrs unless attrs.empty? end end {} end # For the rule referenced by token all patterns are collected that define # the terminal token of each first token of each pattern of the specified # rule. The patterns are returned as a hash. For each pattern the hashed # boolean value specifies whether the attribute is scenario specific or not. def attributes(token, scenarioSpecific) raise "Token #{token} must reference a rule" if token[0] != :reference token = token[1] # Find the matching rule. rule = @parser.rules[token] attrs = {} # Now we look at the first token of each pattern. rule.patterns.each do |pattern| if pattern[0][0] == :literal # If it's a terminal symbol, we found what we are looking for. We add # it to the attrs hash and mark it as non scenario specific. attrs[pattern] = scenarioSpecific elsif pattern[0][0] == :reference && pattern[0][1] == :scenarioIdCol # A reference to the !scenarioId rule marks the next token of the # pattern as a reference to a rule with all scenario specific # attributes. attrs.merge!(attributes(pattern[1], true)) elsif pattern[0][0] == :reference # In case we have a reference to another rule, we just follow the # reference. If the pattern is documented we don't have to follow the # reference. We can use the pattern instead. if pattern.doc.nil? attrs.merge!(attributes(pattern[0], scenarioSpecific)) else attrs[pattern] = scenarioSpecific end else raise "Hit unknown token #{token}" end end attrs end def checkKeyword(keyword) if keyword.nil? || @keywords[keyword].nil? unless keyword.nil? $stderr.puts "ERROR: #{keyword} is not a known keyword.\n\n" end # Create list of top-level keywords. kwdStr = '' @keywords.each_value do |kwd| if kwd.contexts.empty? || (kwd.contexts.length == 1 && kwd.contexts[0] == kwd) kwdStr += ', ' unless kwdStr.empty? kwdStr += kwd.keyword end end $stderr.puts "Try one of the following keywords as argument to this " + "program:\n" $stderr.puts "#{kwdStr}" return false end true end end end taskjuggler-3.5.0/lib/taskjuggler/RuntimeConfig.rb0000644000175000017500000000460412614413013021536 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = RuntimeConfig.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'yaml' # The RuntimeConfig searches for a YAML config file in a list of directories. # When a file is found it is read-in. The read-in config values are grouped in # a tree of sections. The values of a section can then be used to overwrite # the instance variable of a passed object. class RuntimeConfig attr_accessor :debugMode def initialize(appName, configFile = nil) @appName = appName @config = nil @debugMode = false if configFile # Read user specified config file. unless loadConfigFile(configFile) error("Config file #{configFile} not found!") end else # Search config files in certain directories. [ '.', ENV['HOME'], '/etc' ].each do |path| # Try UNIX style hidden file first, then .rc. [ "#{path}/.#{appName}rc", "#{path}/#{appName}.rc" ].each do |file| break if loadConfigFile(file) end end end end def configure(object, section) debug("Configuring object of type #{object.class}") sections = section.split('.') p = @config sections.each do |sec| if p.nil? || !p.include?('_' + sec) debug("Section #{section} not found in config file") return false end p = p['_' + sec] end object.instance_variables.each do |iv| ivName = iv[1..-1] debug("Processing class variable #{ivName}") if p.include?(ivName) debug("Setting @#{ivName} to #{p[ivName]}") object.instance_variable_set(iv, p[ivName]) end end true end private def loadConfigFile(fileName) if File.exist?(fileName) debug("Loading #{fileName}") begin @config = YAML::load(File.read(fileName)) rescue error("Error in config file #{fileName}: #{$!}") end debug(@config.to_s) return true end false end def debug(message) return unless @debugMode puts message end def error(message) $stderr.puts message exit 1 end end taskjuggler-3.5.0/lib/taskjuggler/SheetHandlerBase.rb0000644000175000017500000002040512614413013022123 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = SheetHandlerBase.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'mail' require 'taskjuggler/UTF8String' require 'taskjuggler/RichText' require 'taskjuggler/HTMLDocument' class TaskJuggler class SheetHandlerBase attr_accessor :workingDir, :dryRun def initialize(appName) @appName = appName # User specific settings @emailDeliveryMethod = 'smtp' @smtpServer = nil @senderEmail = nil @workingDir = nil @scmCommand = nil # The default project ID @projectId = 'prj' # Controls the amount of output that is sent to the terminal. # 0: No output # 1: only errors # 2: errors and warnings # 3: All messages @outputLevel = 2 # Controls the amount of information that is added to the log file. The # levels are identical to @outputLevel. @logLevel = 3 # Set to true to not send any emails. Instead the email (header + body) is # printed to the terminal. @dryRun = false @logFile = 'timesheets.log' @emailFailure = false end # Extract the text between the cut-marker lines and remove any email # quotes from the beginnin of the line. def cutOut(text) # Pattern for the section start marker mark1 = /(.*)# --------8<--------8<--------/ # Pattern for the section end marker mark2 = /# -------->8-------->8--------/ # The cutOut section cutOutText = nil quoteLen = 0 quoteMarks = emptyLine = '' text.each_line do |line| if cutOutText.nil? # We are looking for the line with the start marker (mark1) if (matches = mark1.match(line)) quoteMarks = matches[1] quoteLen = quoteMarks.length # Special case for quoted empty lines without trailing spaces. emptyLine = quoteMarks.chomp.chomp(' ') + "\n" cutOutText = line[quoteLen..-1] end else # Remove quote marks from the beginning of the line. line = line[quoteLen..-1] if line[0, quoteLen] == quoteMarks line = "\n" if line == emptyLine cutOutText << line # We are gathering text until we hit the end marker (mark2) return cutOutText if mark2.match(line) end end # There are no cut markers. We just return the original text. text end def setWorkingDir # Make sure the user has provided a properly setup config file. case @emailDeliveryMethod when 'smtp' error('\'smtpServer\' not configured') unless @smtpServer when 'sendmail' # nothing to check else error("Unknown emailDeliveryMethod #{@emailDeliveryMethod}") end error('\'senderEmail\' not configured') unless @senderEmail # Change into the specified working directory begin Dir.chdir(@workingDir) if @workingDir rescue error("Working directory #{@workingDir} not found") end end def addToScm(message, fileName) return unless @scmCommand cmd = @scmCommand.gsub(/%m/, message) cmd.gsub!(/%f/, fileName) unless @dryRun `#{cmd}` if $? == 0 info("Added #{fileName} to SCM") else error("SCM command #{cmd} failed: #{$?.class}") end end end def info(message) puts message if @outputLevel >= 3 log('INFO', message) if @logLevel >= 3 end def warning(message) puts message if @outputLevel >= 2 log('WARN', message) if @logLevel >= 2 end def error(message) $stderr.puts message if @outputLevel >= 1 log("ERROR", message) if @logLevel >= 1 raise TjRuntimeError end def log(type, message) timeStamp = Time.new.strftime("%Y-%m-%d %H:%M:%S") File.open(@logFile, 'a') do |f| f.write("#{timeStamp} #{type} #{@appName}: #{message}\n") end end # Like SheetHandlerBase::sendEmail but interpretes the _message_ as # RichText markup. The generated mail will have a text/plain and a # text/html part. def sendRichTextEmail(to, subject, message, attachment = nil, from = nil, inReplyTo = nil) rti = RichText.new(message).generateIntermediateFormat rti.lineWidth = 72 rti.indent = 2 rti.titleIndent = 0 rti.listIndent = 2 rti.parIndent = 2 rti.preIndent = 4 rti.sectionNumbers = false # Send out the email. sendEmail(to, subject, rti, attachment, from, inReplyTo) end def sendEmail(to, subject, message, attachment = nil, from = nil, inReplyTo = nil) case @emailDeliveryMethod when 'smtp' Mail.defaults do delivery_method :smtp, { :address => @smtpServer, :port => 25 } end when 'sendmail' Mail.defaults do delivery_method :sendmail end else raise "Unknown email delivery method: #{@emailDeliveryMethod}" end begin self_ = self mail = Mail.new do subject subject text_part do content_type [ 'text', 'plain', { 'charset' => 'UTF-8' } ] content_transfer_encoding 'base64' body message.to_s.to_base64 end if message.is_a?(RichTextIntermediate) html_part do content_type 'text/html; charset=UTF-8' content_transfer_encoding 'base64' body self_.htmlMailBody(message).to_base64 end end end mail.to = to mail.from = from || @senderEmail mail.in_reply_to = inReplyTo if inReplyTo mail['User-Agent'] = "#{AppConfig.softwareName}/#{AppConfig.version}" mail['X-TaskJuggler'] = @appName if attachment mail.add_file ({ :filename => File.basename(attachment), :content => File.read(attachment) }) end #raise "Mail header problem" unless mail.errors.empty? rescue @emailFailure = true error("Email processing failed: #{$!}") end if @dryRun # For testing and debugging, we only print out the email. puts "-- Email Start #{'-' * 60}\n#{mail.to_s}-- Email End #{'-' * 62}" log('INFO', "Show email '#{subject}' to #{to}") else # Actually send out the email. begin mail.deliver rescue # We try to send out another email. If that fails again, we abort # without further attempts. if @emailFailure log('ERROR', "Email double fault: #{$!}") raise TjRuntimeError else @emailFailure = true error("Email transmission failed: #{$!}") end end log('INFO', "Sent email '#{subject}' to #{to}") end end def htmlMailBody(message) html = HTMLDocument.new head = html.generateHead("TaskJuggler Report - #{@name}", 'description' => 'TaskJuggler Report', 'keywords' => 'taskjuggler, project, management') auxSrcDir = AppConfig.dataDirs('data/css')[0] cssFileName = (auxSrcDir ? auxSrcDir + '/tjreport.css' : '') # Raise an error if we haven't found the data directory if auxSrcDir.nil? || !File.exists?(cssFileName) dataDirError(cssFileName) end cssFile = IO.read(cssFileName) if cssFile.empty? raise TjException.new, <<"EOT" Cannot read '#{cssFileName}'. Make sure the file is not empty and you have read access permission. EOT end head << XMLElement.new('meta', 'http-equiv' => 'Content-Style-Type', 'content' => 'text/css; charset=utf-8') head << (style = XMLElement.new('style', 'type' => 'text/css')) style << XMLBlob.new("\n" + cssFile) html.html << (body = XMLElement.new('body')) body << message.to_html html.to_s end end end taskjuggler-3.5.0/lib/taskjuggler/Tj3Config.rb0000644000175000017500000000176712614413013020562 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Tj3Config.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/UTF8String' require 'taskjuggler/AppConfig' AppConfig.version = '3.5.0' AppConfig.packageName = 'taskjuggler' AppConfig.softwareName = 'TaskJuggler' AppConfig.packageInfo = 'A Project Management Software' AppConfig.copyright = [ (2006..2013).to_a ] AppConfig.authors = [ 'Chris Schlaeger ' ] AppConfig.contact = 'http://www.taskjuggler.org' AppConfig.license = <<'EOT' This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation. EOT taskjuggler-3.5.0/lib/taskjuggler/deep_copy.rb0000644000175000017500000000510012614413013020724 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = deep_copy.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # # This file extends some Ruby core classes to add deep-copying support. I'm # aware of the commonly suggested method using Marshal: # # class Object # def deep_copy # Marshal.load(Marshal.dump(self)) # end # end # # But just because I program in Ruby, I don't have to write bloatware. It's # like taking a trip to the moon and back to shop groceries at the store # around the corner. I'm not sure if I need more special cases than Array and # Hash, but this file works for me. # # In certain cases the full deep copy may not be desired. To preserve # references to objects, you need to overload deep_clone and handle the # special cases. Alternatively, an object can be frozen to prevent deep # copies. class Object # This is a variant of Object#clone that returns a deep copy of an object. def deep_clone # We can't clone frozen objects. So just return a reference to them. # Built-in classed can't be cloned either. The check below is probably # cheaper than the frequent (hiddent) exceptions from those objects. return self if frozen? || nil? || is_a?(Fixnum) || is_a?(Float) || is_a?(TrueClass) || is_a?(FalseClass) || is_a?(Symbol) # In case we have loops in our graph, we return references, not # deep-copied objects. if RUBY_VERSION < '1.9.0' return @clonedObject if instance_variables.include?('@clonedObject') else return @clonedObject if instance_variables.include?(:@clonedObject) end # Clone the current Object (shallow copy with internal state) begin @clonedObject = clone rescue TypeError return self end # Recursively copy all instance variables. @clonedObject.instance_variables.each do |var| val = instance_variable_get(var).deep_clone @clonedObject.instance_variable_set(var, val) end if kind_of?(Array) @clonedObject.collect! { |x| x.deep_clone } elsif kind_of?(Hash) @clonedObject.each { |key, val| store(key, val.deep_clone) } end # Remove the @clonedObject again. if RUBY_VERSION < '1.9.0' remove_instance_variable('@clonedObject') else remove_instance_variable(:@clonedObject) end end end taskjuggler-3.5.0/lib/taskjuggler/Scenario.rb0000644000175000017500000000123012614413013020520 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = Scenario.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/PropertyTreeNode' class TaskJuggler class Scenario < PropertyTreeNode def initialize(project, id, name, parent) super(project.scenarios, id, name, parent) project.addScenario(self) end end end taskjuggler-3.5.0/lib/taskjuggler/RichText.rb0000644000175000017500000001764212614413013020525 0ustar bernatbernat#!/usr/bin/env ruby -w # encoding: UTF-8 # # = RichText.rb -- The TaskJuggler III Project Management Software # # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 # by Chris Schlaeger # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as # published by the Free Software Foundation. # require 'taskjuggler/RichText/Element' require 'taskjuggler/RichText/Parser' require 'taskjuggler/MessageHandler' class TaskJuggler # RichText is a MediaWiki markup parser and HTML generator implemented in # pure Ruby. It can also generate plain text versions of the original markup # text. It is based on the TextParser class to implement the # RichTextParser. The scanner is implemented in the RichTextScanner class. # The read-in text is converted into a tree of RichTextElement objects. # These can then be turned into HTML element trees modelled by XMLElement or # plain text. # # This class supports the following mark-ups: # # The following markups are block commands and must start at the beginning of # the line. # # == Headline 1 == # === Headline 2 === # ==== Headline 3 ==== # # ---- creates a horizontal line # # * Bullet 1 # ** Bullet 2 # *** Bullet 3 # # # Enumeration Level 1 # ## Enumeration Level 2 # ### Enumeration Level 3 # # Preformatted text start with # a single space at the start of # each line. # # # The following are in-line mark-ups and can occur within any text block # # This is an ''italic'' word. # This is a '''bold''' word. # This is a ''''monospaced'''' word. This is not part of the original # MediaWiki markup, but we needed monospaced as well. # This is a '''''italic and bold''''' word. # # Linebreaks are ignored if not followed by a blank line. # # [http://www.taskjuggler.org] A web link # [http://www.taskjuggler.org The TaskJuggler Web Site] another link # # [[item]] site internal internal reference (in HTML .html gets appended # automatically) # [[item An item]] another internal reference # [[function:path arg1 arg2 ...]] # # ... Disable markup interpretation for the enclosed # portion of text. # class RichText attr_reader :inputText # The Parser uses complex to setup data structures that are identical for # all RichText instances. So, we'll share them across the instances. @@parser = nil # Create a rich text object by passing a String with markup elements to it. # _text_ must be plain text with MediaWiki compatible markup elements. In # case an error occurs, an exception of type TjException will be raised. # _functionHandlers_ is a Hash that maps RichTextFunctionHandler objects # by their function name. def initialize(text, functionHandlers = []) # Keep a copy of the original text. @inputText = text @functionHandlers = functionHandlers end # Convert the @inputText into an abstract syntax tree that can then be # converted into the various output formats. _sectionCounter_ is an Array # that holds the initial values for the section counters. def generateIntermediateFormat(sectionCounter = [ 0, 0, 0], tokenSet = nil) rti = RichTextIntermediate.new(self) # Copy the function handlers. @functionHandlers.each do |h| rti.registerFunctionHandler(h) end # We'll setup the RichTextParser once and share it across all instances. if @@parser # We already have a RichTextParser that we can reuse. @@parser.reuse(rti, sectionCounter, tokenSet) else # There is no RichTextParser yet, create one. @@parser = RichTextParser.new(rti, sectionCounter, tokenSet) end @@parser.open(@inputText) # Parse the input text and convert it to the intermediate representation. return nil if (tree = @@parser.parse(:richtext)) == false # In case the result is empty, use an empty RichTextElement as result tree = RichTextElement.new(rti, :richtext, nil) unless tree tree.cleanUp rti.tree = tree rti end # Return the RichTextFunctionHandler for the function _name_. _block_ # specifies whether we are looking for a block or inline function. def functionHandler(name, block) @functionHandlers.each do |handler| return handler if handler.function == name && handler.blockFunction == block end nil end private end # The RichTextIntermediate is a container for the intermediate # representation of a RichText object. By calling the to_* members it can be # converted into the respective formats. A RichTextIntermediate object is # generated by RichText::generateIntermediateFormat. class RichTextIntermediate attr_reader :richText, :functionHandlers attr_accessor :blockMode, :sectionNumbers, :lineWidth, :indent, :titleIndent, :parIndent, :listIndent, :preIndent, :linkTarget, :cssClass, :tree def initialize(richText) # A reference to the corresponding RichText object the RTI is derived # from. @richText = richText # The root of the generated intermediate format. This is a # RichTextElement. @tree = nil # The blockMode specifies whether the RichText should be interpreted as # a line of text or a block (default). @blockMode = true # Set this to false to disable automatically generated section numbers. @sectionNumbers = true # Set this to the maximum width used for text output. @lineWidth = 80 # The indentation used for all text output. @indent = 0 # Additional indentation used for titles in text output. @titleIndent = 0 # Additional indentation used for paragraph text output. @parIndent = 0 # Additional indentation used for lists in text output. @listIndent = 1 # Additional indentation used for
     sections in text output.
          @preIndent = 0
          # The target used for hypertext links.
          @linkTarget = nil
          # The CSS class used for some key HTML elements.
          @cssClass = nil
          # These are the RichTextFunctionHandler objects to handle references with
          # a function specification.
          @functionHandlers = {}
        end
    
        # Use this function to register new RichTextFunctionHandler objects with
        # this class.
        def registerFunctionHandler(functionHandler)
          raise "Bad function handler" unless functionHandler
          @functionHandlers[functionHandler.function] = functionHandler.dup
        end
    
        # Return the handler for the given _function_ or raise an exception if it
        # does not exist.
        def functionHandler(function)
          @functionHandlers[function]
        end
    
        # Return true if the RichText has no content.
        def empty?
          @tree.empty?
        end
    
        # Recursively extract the section headings from the RichTextElement and
        # build the TableOfContents _toc_ with the gathered sections.  _fileName_
        # is the base name (without .html or other suffix) of the file the
        # TOCEntries should point to.
        def tableOfContents(toc, fileName)
          @tree.tableOfContents(toc, fileName)
        end
    
        # Return an Array with all other snippet names that are referenced by
        # internal references in this RichTextElement.
        def internalReferences
          @tree.internalReferences
        end
    
        # Convert the intermediate format into a plain text String object.
        def to_s
          str = @tree.to_s
          str.chomp! while str[-1] == ?\n
          str
        end
    
        # Convert the intermediate format into a XMLElement objects tree.
        def to_html
          html = @tree.to_html
          html.chomp! while html[-1] == ?\n
          html
        end
    
        # Convert the intermediate format into a tagged syntax String object.
        def to_tagged
          @tree.to_tagged
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/Query.rb0000644000175000017500000003411512614413013020072 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = Query.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/TjException'
    
    class TaskJuggler
    
      # A query can be used to retrieve any property attribute after the scheduling
      # run has been completed. It is possible to make a Query before the scheduling
      # run has been completed, but it only produces good results for static
      # attributes. And for such queries, the PropertyTreeNode.get and [] functions
      # are a lot more efficient.
      #
      # When constructing a Query, a set of variables need to be set that is
      # sufficient enough to identify a unique attribute. Some attribute are
      # computed dynamically and further variables such as a start and end time will
      # be incorporated into the result computation.
      #
      # The result is returned as String (Query#result), in numerical form
      # (Query#numericalResult) if available as number, and as an entity that can be
      # used for sorting (Query#sortableResult). To get the result, Query#process
      # needs to be called. In case an error occured, Query#ok is set to false and
      # Query#errorMessage contains an error message.
      class Query
    
        @@ps = %w( project propertyType propertyId property
                   scopePropertyType scopePropertyId scopeProperty
                   attributeId scenario scenarioIdx
                   loadUnit numberFormat currencyFormat timeFormat
                   listItem listType hideJournalEntry
                   journalMode journalAttributes sortJournalEntries
                   costAccount revenueAccount selfContained )
        @@ps.each do |p|
          attr_accessor p.to_sym
        end
        attr_accessor :ok, :errorMessage
        attr_reader :end, :endIdx, :start, :startIdx
        attr_writer :sortable, :numerical, :string, :rti
    
        # Create a new Query object. The _parameters_ need to be sufficent to
        # uniquely identify an attribute.
        def initialize(parameters = { })
          @selfContained = false
          @@ps.each do |p|
            instance_variable_set('@' + p, parameters[p] ? parameters[p] : nil)
          end
    
          # instance_variable_set does not call writer functions. So we need to
          # handle @start, @end, @startIdx and @endIdx separately.
          %w( end endIdx start startIdx ).each do |p|
            send(p + '=', parameters[p]) if parameters[p]
          end
          # The custom data hash can be filled with results to be returned for
          # special attributes that are not directly property attributes or
          # computed attributes.
          @customData = {}
    
          reset
        end
    
        # We probably need the start and end dates as TjTime and Scoreboard index.
        # We store both, but we need to assure they are always in sync.
    
        def start=(date)
          if date.is_a?(TjTime)
            @start = date
          else
            raise "Unsupported type #{date.class}"
          end
          @startIdx = @project.dateToIdx(@start)
        end
    
        def startIdx=(idx)
          if idx.is_a?(Fixnum)
            @startIdx = idx
            @start = @project.idxToDate(idx)
          else
            raise "Unsupported type #{idx.class}"
          end
        end
    
        def end=(date)
          if date.is_a?(TjTime)
            @end = date
          else
            raise "Unsupported type #{date.class}"
          end
          @endIdx = @project.dateToIdx(@end)
        end
    
        def endIdx=(idx)
          if idx.is_a?(Fixnum)
            @endIdx = idx
            @end = @project.idxToDate(idx)
          else
            raise "Unsupported type #{idx.class}"
          end
        end
    
        # Set a custom data entry. _name_ is the name of the pseudo attribute.
        # _data_ must be a Hash that contains the value for :numberical, :string,
        # :sortable or :rti results.
        def setCustomData(name, data)
          @customData[name] = data
        end
    
        # This method tries to resolve the query and return a result. In case it
        # finds an attribute that matches the query, it returns true; false
        # otherwise. The actual result data is stored in the Query object. It can
        # then be retrieved by the caller with the methods to_s(), to_num(),
        # to_sort() and result().
        def process
          reset
          begin
            # Resolve property reference from property ID.
            if @propertyId && (@property.nil? || @propertyId[0] == '!')
              @property = resolvePropertyId(@propertyType, @propertyId)
              unless @property
                @errorMessage = "Unknown property #{@propertyId} queried"
                return @ok = false
              end
            end
    
            unless @property
              # No property was provided. We are looking for a project attribute.
              supportedAttrs = %w( copyright currency end journal name now projectid
                                   start version )
              unless supportedAttrs.include?(@attributeId)
                @errorMessage = "Unsupported project attribute '#{@attributeId}'"
                return @ok = false
              end
              if @project.respond_to?(attributeId)
                @project.send(attributeId, self)
              else
                attr = @project[@attributeId]
              end
              if attr.is_a?(TjTime)
                @sortable = @numerical = attr
                @string = attr.to_s(@timeFormat)
              else
                @sortable = @string = attr
              end
              return @ok = true
            end
    
            # Same for the scope property.
            if !@scopeProperty.nil? && !@scopePropertyId.nil?
              @scopeProperty = resolvePropertyId(@scopePropertyType,
                                                 @scopePropertyId)
              unless @scopeProperty
                @errorMessage = "Unknown scope property #{@scopePropertyId} queried"
                return @ok = false
              end
            end
            # Make sure the have a reference to the project.
            @project = @property.project unless @project
    
            if @scenario && !@scenarioIdx
              @scenarioIdx = @project.scenarioIdx(@scenario)
              unless @scenarioIdx
                raise "Query cannot resolve scenario '#{@scenario}'"
              end
            end
    
            queryMethodName = 'query_' + @attributeId
            # First we check for non-scenario-specific query functions.
            if (data = @customData[@attributeId])
              @sortable = data[:sortable]
              @numerical = data[:numerical]
              @string = data[:string]
              @rti = data[:rti]
            elsif @property.respond_to?(queryMethodName)
              @property.send(queryMethodName, self)
            elsif @scenarioIdx && @property.data &&
                  @property.data[@scenarioIdx].respond_to?(queryMethodName)
              # Then we check for scenario-specific ones via the @data member.
              @property.send(queryMethodName, @scenarioIdx, self)
            else
              # The result is a BaseAttribute
              begin
                # The user may also provide a scenario index for
                # non-scenario-specific values. We need to check if the attribute
                # is really scenario specific or not because
                # PropertyTreeNode::getAttribute can only handle an index for
                # scenario-specific attributs.
                aType = @property.attributeDefinition(@attributeId)
                raise ArgumentError unless aType
                scIdx = aType.scenarioSpecific ? @scenarioIdx : nil
                @attr = @property.getAttribute(@attributeId, scIdx)
              rescue ArgumentError
                @errorMessage = "Unknown attribute #{@attributeId} queried"
                return @ok = false
              end
            end
          rescue TjException
            @errorMessage = $!.message
            return @ok = false
          end
          @ok = true
        end
    
        # Converts the String items in _listItems_ into a RichTextIntermediate
        # objects and assigns it as result of the query.
        def assignList(listItems)
          list = ''
          listItems.each do |item|
            case @listType
            when nil, :comma
              list += ', ' unless list.empty?
              list += item
            when :bullets
              list += "* #{item}\n"
            when :numbered
              list += "# #{item}\n"
            end
          end
          @sortable = @string = list
          rText = RichText.new(list)
          @rti = rText.generateIntermediateFormat
        end
    
        # Return the result of the Query as String. The result may be nil.
        def to_s
          @attr ? @attr.to_s(self) : (@rti ? @rti.to_s : (@string || ''))
        end
    
        # Return the result of the Query as Fixnum or Float. The result may be
        # nil.
        def to_num
          @attr ? @attr.to_num : @numerical
        end
    
        # Return the result in the best suited type and format for sorting. The
        # result may be nil.
        def to_sort
          @attr ? @attr.to_sort : @sortable
        end
    
        # Return the result as RichTextIntermediate object. The result may be nil.
        def to_rti
          return @attr.value if @attr.is_a?(RichTextAttribute)
    
          @attr ? @attr.to_rti(self) : @rti
        end
    
        # Return the result in the orginal form. It may be nil.
        def result
          if @attr
            if @attr.value && @attr.is_a?(ReferenceAttribute)
              @attr.value[0]
            else
              @attr.value
            end
          elsif @numerical
            @numerical
          elsif @rti
            @rti
          else
            @string
          end
        end
    
        # Convert a duration to the format specified by @loadUnit.  _value_ is the
        # duration effort in days. The return value is the converted value with
        # optional unit as a String.
        def scaleDuration(value)
          scaleValue(value, [ 24 * 60, 24, 1, 1.0 / 7, 1.0 / 30.42,
                              1.0 / 91.25, 1.0 / 365 ])
        end
    
        # Convert a load or effort value to the format specified by @loadUnit.
        # _work_ is the effort in man days. The return value is the converted value
        # with optional unit as a String.
        def scaleLoad(value)
          scaleValue(value, [ @project.dailyWorkingHours * 60,
                              @project.dailyWorkingHours,
                              1.0,
                              1.0 / @project.weeklyWorkingDays,
                              1.0 / @project.monthlyWorkingDays,
                              1.0 / (@project.yearlyWorkingDays / 4),
                              1.0 / @project.yearlyWorkingDays ])
        end
    
      private
    
        def resolvePropertyId(pType, pId)
          unless @project
            raise "Need Project reference to process the query"
          end
          if pId[0] == '!'
            # This is the case where the property ID is just a sequence of
            # exclamation marks. Each one moves the scope 1 level up from the
            # current level.
            pId.each_utf8_char do |c|
              if c == '!'
                @property = @property.parent
              end
              break unless @property
            end
            @property
          else
            case pType
            when :Account
              @project.account(pId)
            when :Task
              @project.task(pId)
            when:Resource
              @project.resource(pId)
            else
              raise "Unknown property type #{pType}"
            end
          end
        end
    
        # This function converts number to strings that may include a unit. The
        # unit is determined by @loadUnit. In the automatic modes, the shortest
        # possible result is shown and the unit is always appended. _value_ is the
        # value to convert. _factors_ determines the conversion factors for the
        # different units.
        def scaleValue(value, factors)
          if @loadUnit == :shortauto || @loadUnit == :longauto
            # We try all possible units and store the resulting strings here.
            options = []
            # For each option we also save the delta between the String value and
            # the original value.
            delta = []
            # For each of the units we can define a maximum value that the value
            # should not exceed. nil means no limit. Never use quarters since it's
            # pretty uncommon to use.
            max = [ 60, 48, nil, 8, 24, 0, nil ]
            stdFormat = RealFormat.new([ '-', '', '', '.',
                                         @numberFormat.fractionDigits ])
    
            i = 0
            fSep = @numberFormat.fractionSeparator
            factors.each do |factor|
              scaledValue = value * factor
              str = @numberFormat.format(scaledValue)
              stdStr = stdFormat.format(scaledValue)
              delta[i] = (scaledValue - stdStr.to_f).abs
              # We ignore results that are 0 or exceed the maximum. To ensure that
              # we have at least one result the unscaled value is always taken.
              if (factor != 1.0 && /^[0.]*$/ =~ stdStr) ||
                 (max[i] && scaledValue > max[i])
                options << nil
              else
                options << str
              end
              i += 1
            end
    
            # Find the value that is the closest to the original value. This will be
            # the default if all values have the same length.
            shortest = 2
            delta.length.times do |j|
              shortest = j if options[j] && delta[j] < delta[shortest]
            end
    
            # Find the shortest option.
            6.times do |j|
              shortest = j if options[j] && options[j][0, 2] != '0' + fSep &&
                              options[j].length < options[shortest].length
            end
    
            str = options[shortest]
            if @loadUnit == :longauto
              # For the long units we handle singular and plural properly. For
              # English we just need to append an 's', but this code will work for
              # other languages as well.
              units = []
              if str == "1"
                units = %w( minute hour day week month quarter year )
              else
                units = %w( minutes hours days weeks months quarters years )
              end
              str += ' ' + units[shortest]
            else
              str += %w( min h d w m q y )[shortest]
            end
          else
            # For fixed units we just need to do the conversion. No unit is
            # included.
            units = [ :minutes, :hours, :days, :weeks, :months, :quarters, :years ]
            str = @numberFormat.format(value * factors[units.index(@loadUnit)])
          end
          str
        end
    
        private
    
        # Queries object can be reused. Calling this function will clear the query
        # result data.
        def reset
          @attr = @numerical = @sortable = @string = @rti = nil
          @ok = true
          @errorMessage = nil
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/Account.rb0000644000175000017500000000277412614413013020367 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = Account.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/PropertyTreeNode'
    require 'taskjuggler/AccountScenario'
    
    class TaskJuggler
    
      # An Account is an object to record financial transactions. Alternatively, an
      # Account can just be a container for a set of Accounts. In this case it
      # cannot directly record any transactions.
      class Account < PropertyTreeNode
    
        def initialize(project, id, name, parent)
          super(project.accounts, id, name, parent)
          project.addAccount(self)
    
          @data = Array.new(@project.scenarioCount, nil)
          @project.scenarioCount.times do |i|
            AccountScenario.new(self, i, @scenarioAttributes[i])
          end
        end
    
        # Many Account functions are scenario specific. These functions are
        # provided by the class AccountScenario. In case we can't find a
        # function called for the Account class we try to find it in
        # AccountScenario.
        def method_missing(func, scenarioIdx, *args)
          @data[scenarioIdx].method(func).call(*args)
        end
    
        # Return a reference to the _scenarioIdx_-th scenario.
        def scenario(scenarioIdx)
          return @data[scenarioIdx]
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/TimeSheetSender.rb0000644000175000017500000000403512614413013022013 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = TimeSheetSender.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/SheetSender'
    
    class TaskJuggler
    
      # The TimeSheetSender class generates time sheet templates for the current
      # week and sends them out to the project contributors. For this to work, the
      # resources must provide the 'Email' custom attribute with their email
      # address. The actual project data is accessed via tj3client on a tj3 server
      # process.
      class TimeSheetSender < SheetSender
    
        attr_accessor :date
    
        def initialize(appName)
          super(appName, 'time')
    
          # This is a LogicalExpression string that controls what resources should
          # not be getting a time sheet.
          @hideResource = '0'
          # The base directory of the time sheet templates.
          @templateDir = 'TimeSheetTemplates'
          # This file contains the time intervals that the TimeSheetReceiver will
          # accept as a valid interval.
          @signatureFile = "#{@templateDir}/acceptable_intervals"
          # The log file
          @logFile = 'timesheets.log'
    
          @signatureFilter = /^[ ]*timesheet\s[a-z][a-z0-9_]*\s([0-9:\-+]*\s-\s[0-9:\-+]*)/
          @introText = <<'EOT'
    Please find enclosed your weekly report template. Please fill out
    the form and send it back to the sender of this email. You can either
    use the attached file or the body of the email. In case you send it
    in the body of the email, make sure it only contains the 'timesheet'
    syntax. No quote marks are allowed. It must be plain text, UTF-8
    encoded and the time sheet header from 'timesheet' to the period end
    date must be in a single line that starts at the beginning of the line.
    
    EOT
          @mailSubject = 'Your weekly time sheet template for %s'
        end
    
      end
    
    end
    taskjuggler-3.5.0/lib/taskjuggler/IntervalList.rb0000644000175000017500000000611112614413013021400 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = IntervalList.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/Interval'
    
    class TaskJuggler
    
      # A list of Intervals. The intervals in the list must not overlap and must
      # be in ascending order.
      class IntervalList < Array
    
        alias append <<
    
        def &(list)
          res = IntervalList.new
          si = li = 0
          while si < length && li < list.length do
            if self[si].start < list[li].start
              # The current Interval of self starts earlier than the current
              # Interval of list.
              if self[si].end <= list[li].start
                # self[si] does not overlap with list[li]. Ignore it.
                si += 1
              elsif self[si].end < list[li].end
                # self[si] does overlap with list[li] but list[li] goes further
                res << self[si].class.new(list[li].start, self[si].end)
                si += 1
              else
                # self[si] does overlap with list[li] but self[si] goes further
                res << self[si].class.new(list[li].start, list[li].end)
                li += 1
              end
            elsif list[li].start < self[si].start
              # The current Interval of list starts earlier than the current
              # Interval of self.
              if list[li].end <= self[si].start
                # list[li] does not overlap with self[si]. Ignore it.
                li += 1
              elsif list[li].end < self[si].end
                # list[li] does overlap with self[si] but self[si] goes further
                res << self[si].class.new(self[si].start, list[li].end)
                li += 1
              else
                # list[li] does overlap with self[si] but list[li] goes further
                res << self[si].class.new(self[si].start, self[si].end)
                si += 1
              end
            else
              # self[si].start and list[li].start are identical
              if self[si].end == list[li].end
                # self[si] and list[li] are identical. Add the Interval and
                # increase both pointers.
                res << self[si]
                li += 1
                si += 1
              elsif self[si].end < list[li].end
                # self[si] ends earlier.
                res << self[si]
                si += 1
              else
                # list[li] ends earlier.
                res << list[li]
                li += 1
              end
            end
          end
    
          res
        end
    
        # Append the Interval _iv_. If the start of _iv_ matches the end of the
        # list list item, _iv_ is merged with the last item.
        def <<(iv)
          if last
            if last.end > iv.start
              raise "Intervals may not overlap and must be added in " +
                    "ascending order."
            elsif last.end == iv.start
              self[-1] = last.class.new(last.start, iv.end)
              return self
            end
          end
    
          append(iv)
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/RealFormat.rb0000644000175000017500000000634112614413013021021 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = RealFormat.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    class TaskJuggler
    
      # This class provides the functionality to format a Float according to certain
      # rules. These rules determine how negative values are represented, how the
      # fractional part is shown and how to structure the mantissa. The result is
      # always a String.
      #
      # The class uses the following parameters to control the formating.
      # signPrefix: Prefix used for negative numbers. (String)
      # signSuffix: Suffix used for negative numbers. (String)
      # thousandsSeparator: Separator used after 3 integer digits. (String)
      # fractionSeparator: Separator used between the inter part and the
      #                    fractional part. (String)
      # fractionDigits: Number of fractional digits to show. (Fixnum)
      class RealFormat
    
        attr_reader :signPrefix, :signSuffix, :thousandsSeparator,
                    :fractionSeparator, :fractionDigits
    
        # Create a new RealFormat object and define the formating rules.
        def initialize(args)
          iVars = %w( @signPrefix @signSuffix @thousandsSeparator
                      @fractionSeparator @fractionDigits )
          if args.is_a?(RealFormat)
            # The argument is another RealFormat object.
            iVars.each do |iVar|
              instance_variable_set(iVar, args.instance_variable_get(iVar))
            end
          elsif args.length == 5
            # The argument is a list of values.
            args.length.times do |i|
              instance_variable_set(iVars[i], args[i])
            end
          else
            raise RuntimeError, "Bad number of parameters #{args.length}"
          end
        end
    
        # Converts the Float _number_ into a String representation according to the
        # formating rules.
        def format(number)
          # Check for negative number. Continue with the absolute part.
          if number < 0
            negate = true
            number = -number
          else
            negate = false
          end
    
          # Determine the integer part.
          intNumber = (number * (10 ** @fractionDigits)).round.to_i.to_s
          if intNumber.length <= @fractionDigits
            intNumber = '0' * (@fractionDigits - intNumber.length + 1) + intNumber
          end
          intPart = intNumber[0..-(@fractionDigits + 1)]
          # Determinate the fractional part
          fracPart =
            @fractionDigits > 0 ? @fractionSeparator +
                                  intNumber[-(@fractionDigits)..-1] : ''
    
          if @thousandsSeparator.empty?
            out = intPart
          else
            out = ''
            1.upto(intPart.length) do |i|
              out = intPart[-i, 1] + out
              out = @thousandsSeparator + out if i % 3 == 0 && i < intPart.length
            end
          end
          out += fracPart
          # Now compose the result.
          out = @signPrefix + out + @signSuffix if negate
          out
        end
    
        def to_s
          [ @signPrefix, @signSuffix, @thousandsSeparator, @fractionSeparator,
            @fractionDigits ].collect { |s| "\"#{s}\"" }.join(' ')
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/PropertyList.rb0000644000175000017500000002411112614413013021440 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = PropertyList.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/PTNProxy'
    require 'taskjuggler/MessageHandler'
    
    class TaskJuggler
    
      # The PropertyList is a utility class that can be used to hold a list of
      # properties. It's derived from an Array, so it can hold the properties in a
      # well defined order. The order can be determined by an arbitrary number of
      # sorting levels. A sorting level specifies an attribute who's value should
      # be used for sorting, a scenario index if necessary and the sorting
      # direction (up/down). All nodes in the PropertyList must belong to the same
      # PropertySet.
      class PropertyList
    
        include MessageHandler
    
        attr_writer :query
        attr_reader :propertySet, :query, :sortingLevels, :sortingCriteria,
                    :sortingUp, :scenarioIdx
    
        # A PropertyList is always bound to a certain PropertySet. All properties
        # in the list must be of that set.
        def initialize(arg, copyItems = true)
          @items = copyItems ? arg.to_ary : []
          if arg.is_a?(PropertySet)
            # Create a PropertyList from the given PropertySet.
            @propertySet = arg
            # To keep the list sorted, we may have to access Property attributes.
            # Pre-scheduling, we can only use static attributes. Post-scheduling,
            # we can include dynamic attributes as well. This query template will
            # be used to query attributes when it has been set. Otherwise the list
            # can only be sorted by static attributes.
            @query = nil
            resetSorting
            addSortingCriteria('seqno', true, -1)
            sort!
          else
            # Create a PropertyList from a given other PropertyList.
            @propertySet = arg.propertySet
            @query = arg.query ? arg.query.dup : nil
            @sortingLevels = arg.sortingLevels
            @sortingCriteria = arg.sortingCriteria.dup
            @sortingUp = arg.sortingUp.dup
            @scenarioIdx = arg.scenarioIdx.dup
          end
        end
    
        # This class should be a derived class of Array. But since it re-defines
        # sort!() and still needs to call Array::sort!() I took a different route.
        # All missing methods will be propagated to the @items Array.
        def method_missing(func, *args, &block)
          @items.method(func).call(*args, &block)
        end
    
        def includeAdopted
          adopted = []
          @items.each do |p|
            p.adoptees.each do |ap|
              adopted += includeAdoptedR(ap, p)
            end
          end
          append(adopted)
        end
    
        # Make sure that the list does not contain the same PropertyTreeNode more
        # than once. This could happen for adopted tasks. If you use
        # includeAdopted(), you should call this method after filtering to see if
        # the filter was strict enough.
        def checkForDuplicates(sourceFileInfo)
          ptns = {}
          @items.each do |i|
            if ptns.include?(i.ptn)
              error('proplist_duplicate',
                    "An adopted property is included as #{i.logicalId} and " +
                    "as #{ptns[i.ptn].logicalId}. Please use stronger filtering " +
                    'to avoid including the property more than once!',
                    sourceFileInfo)
            end
            ptns[i.ptn] = i
          end
        end
    
    
        # Specialized version of Array::include? that also matches adopted tasks.
        def include?(node)
          !@items.find { |p| p.ptn == node.ptn }.nil?
        end
    
        def [](node)
          @items.find { |n| n.ptn == node.ptn }
        end
    
        def to_ary
          @items.dup
        end
    
        # Set all sorting levels as Array of triplets.
        def setSorting(modes)
          resetSorting
          modes.each do |mode|
            addSortingCriteria(*mode)
          end
        end
    
        # Clear all sorting levels.
        def resetSorting
          @sortingLevels = 0
          @sortingCriteria = []
          @sortingUp = []
          @scenarioIdx = []
        end
    
        # Append another Array of PropertyTreeNodes or a PropertyList to this. The
        # list will be sorted again.
        def append(list)
          if $DEBUG
            list.each do |node|
              unless node.propertySet == @propertySet
                raise "Fatal Error: All nodes must belong to the same PropertySet."
              end
            end
          end
    
          @items.concat(list)
          raise "Duplicate items" if @items != @items.uniq
          sort!
        end
    
        # If the first sorting level is 'tree' the breakdown structure of the
        # list is preserved. This is a somewhat special mode and this function
        # returns true if the mode is set.
        def treeMode?
          @sortingLevels > 0 && @sortingCriteria[0] == 'tree'
        end
    
        # Sort the properties according to the currently defined sorting criteria.
        def sort!
          if treeMode?
            # Tree sorting is somewhat complex. It will be based on the 'tree'
            # attribute of the PropertyTreeNodes but we have to update them first
            # based on the other sorting criteria.
    
            # Remove the tree sorting mode first.
            sc = @sortingCriteria.delete_at(0)
            su = @sortingUp.delete_at(0)
            si = @scenarioIdx.delete_at(0)
            @sortingLevels -= 1
    
            # Sort the list based on the rest of the modes.
            sortInternal
            # The update the 'index' attributes of the PropertyTreeNodes.
            index
            # An then the 'tree' attributes.
            indexTree
    
            # Restore the 'tree' sorting mode again.
            @sortingCriteria.insert(0, sc)
            @sortingUp.insert(0, su)
            @scenarioIdx.insert(0, si)
            @sortingLevels += 1
    
            # Sort again, now based on the updated 'tree' attributes.
            sortInternal
          else
            sortInternal
          end
          # Update indexes.
          index
        end
    
        # Return the Array index of _item_ or nil.
        def itemIndex(item)
          @items.index(item)
        end
    
        # This function sets the index attribute of all the properties in the list.
        # The index starts with 0 and increases for each property.
        def index
          i = 0
          @items.each do |p|
            p.force('index', i += 1)
          end
        end
    
        # Turn the list into a String. This is only used for debugging.
        def to_s # :nodoc:
          res = "Sorting: "
          @sortingLevels.times do |i|
            res += "#{@sortingCriteria[i]}/#{@sortingUp[i] ? 'up' : 'down'}/" +
                   "#{@scenarioIdx[i]}, "
          end
          res += "\n#{@items.length} properties:"
          @items.each { |i| res += "#{i.get('id')}: #{i.get('name')}\n" }
          res
        end
    
        private
    
        def includeAdoptedR(property, parent)
          # Create a proxy for the current PropertyTreeNode and add it to a list.
          adopted = [ parentProxy = PTNProxy.new(property, parent) ]
    
          # Add proxies for all children (adopted or not) and their children.
          property.kids.each do |p|
            adopted += includeAdoptedR(p, parentProxy)
          end
    
          adopted
        end
    
        # Append a new sorting level to the existing levels.
        def addSortingCriteria(criteria, up, scIdx)
          unless @propertySet.knownAttribute?(criteria) ||
                 @propertySet.hasQuery?(criteria, scIdx)
            raise TjException.new,
                  "Unknown attribute #{criteria} used for sorting criterium"
          end
          if scIdx == -1
            if @propertySet.scenarioSpecific?(criteria)
              raise TjException.new,
                    "Attribute #{criteria} is scenario specific." +
                    "You must specify a scenario id."
            end
          else
            if @propertySet.project.scenario(scIdx).nil?
              raise TjException.new, "Unknown scenario index #{scIdx} used."
            end
            if !@propertySet.scenarioSpecific?(criteria)
              raise TjException.new, "Attribute #{criteria} is not scenario specific"
            end
          end
          @sortingCriteria.push(criteria)
          @sortingUp.push(up)
          @scenarioIdx.push(scIdx)
          @sortingLevels += 1
        end
    
        # Update the 'tree' indicies that are needed for the 'tree' sorting mode.
        def indexTree
          @items.each do |property|
            # The indicies are an Array if the 'index' attributes for this
            # property and all its parents.
            treeIdcs = property.getIndicies
            # Now convert them to a String.
            tree = ''
            treeIdcs.each do |idx|
              # Prefix the level index with zeros so that we always have a 6
              # digit long String. 6 digits should be large enough for all
              # real-world projects.
              tree += idx.to_s.rjust(6, '0')
            end
            property.set('tree', tree)
          end
        end
    
        def sortInternal
          @items.sort! do |a, b|
            res = 0
            @sortingLevels.times do |i|
              if @query && @sortingCriteria[i] != 'tree'
                # In case we have a Query reference, we get the two values with this
                # query.
                @query.scenarioIdx = @scenarioIdx[i] < 0 ? nil : @scenarioIdx[i]
                @query.attributeId = @sortingCriteria[i]
    
                @query.property = a
                @query.process
                aVal = @query.to_sort
    
                @query.property = b
                @query.process
                bVal = @query.to_sort
              else
                # In case we don't have a query, we use the static mechanism.
                # If the scenario index is negative we have a non-scenario-specific
                # attribute.
                if @scenarioIdx[i] < 0
                  if @sortingCriteria[i] == 'id'
                    aVal = a.fullId
                    bVal = b.fullId
                  else
                    aVal = a.get(@sortingCriteria[i])
                    bVal = b.get(@sortingCriteria[i])
                  end
                else
                  aVal = a[@sortingCriteria[i], @scenarioIdx[i]]
                  bVal = b[@sortingCriteria[i], @scenarioIdx[i]]
                end
              end
              res = aVal <=> bVal
              # Invert the result if we have to sort in decreasing order.
              res = -res unless @sortingUp[i]
              # If the two elements are equal on this compare level we try the next
              # level.
              break if res != 0
            end
            res
          end
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/PTNProxy.rb0000644000175000017500000000671712614413013020477 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = PTNProxy.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    class TaskJuggler
    
      # This class provides objects that represent PropertyTreeNode objects that
      # were adopted (directly or indirectly) in their new parental context. Such
      # objects are used as elements of a PropertyList which can only hold each
      # PropertyTreeNode objects once. By using this class, we can add such
      # objects more than once, each time with a new parental context that was
      # created by an adoption.
      class PTNProxy
    
        attr_reader :parent
    
        def initialize(ptn, parent)
          @ptn = ptn
          raise "Adopted properties must have a parent" unless parent
          @parent = parent
          @indext =  nil
          @tree = nil
          @level = -1
        end
    
        # Return the logical ID of this node respesting adoptions. For PropertySet
        # objects with a flat namespace, this is just the ID. Otherwise, the
        # logical ID is composed of all IDs from the root node to this node,
        # separating the IDs by a dot. In contrast to PropertyTreeNode::fullId()
        # the logicalId takes the aption path into account.
        def logicalId
          if @ptn.propertySet.flatNamespace
            @ptn.id
          else
            if (dotPos = @ptn.id.rindex('.'))
              id = @ptn.id[(dotPos + 1)..-1]
            else
              id = @ptn.id
            end
            @parent.logicalId + '.' + id
          end
        end
    
        def set(attribute, val)
          if attribute == 'index'
            @index = val
          elsif attribute == 'tree'
            @tree = val
          else
            @ptn.set(attribute, val)
          end
        end
    
        def get(attribute)
          if attribute == 'index'
            @index
          elsif attribute == 'tree'
            @tree
          else
            @ptn.get(attribute)
          end
        end
    
        def [](attribute, scenarioIdx)
          if attribute == 'index'
            @index
          elsif attribute == 'tree'
            @tree
          else
            @ptn[attribute, scenarioIdx]
          end
        end
    
        # Returns the level that this property is on. Top-level properties return
        # 0, their children 1 and so on. This value is cached internally, so it does
        # not have to be calculated each time the function is called.
        def level
          return @level if @level >= 0
    
          t = self
          @level = 0
          until (t = t.parent).nil?
            @level += 1
          end
          @level
        end
    
        # Find out if this property is a direct or indirect child of _ancestor_.
        def isChildOf?(ancestor)
          parent = self
          while parent = parent.parent
            return true if (parent == ancestor)
          end
          false
        end
    
        # Return the 'index' attributes of this property, prefixed by the 'index'
        # attributes of all its parents. The result is an Array of Fixnums.
        def getIndicies
          idcs = []
          p = self
          begin
            parent = p.parent
            idcs.insert(0, p.get('index'))
            p = parent
          end while p
          idcs
        end
    
        def method_missing(func, *args, &block)
          @ptn.send(func, *args, &block)
        end
    
        alias_method :respond_to_?, :respond_to?
    
        def respond_to?(method)
          respond_to_?(method) || @ptn.respond_to?(method)
        end
    
        def is_a?(type)
          @ptn.is_a?(type)
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/Resource.rb0000644000175000017500000000641112614413013020552 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = Resource.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/PropertyTreeNode'
    require 'taskjuggler/ResourceScenario'
    
    class TaskJuggler
    
      class Resource < PropertyTreeNode
    
        def initialize(project, id, name, parent)
          super(project.resources, id, name, parent)
          project.addResource(self)
    
          @data = Array.new(@project.scenarioCount, nil)
          @project.scenarioCount.times do |i|
            ResourceScenario.new(self, i, @scenarioAttributes[i])
          end
        end
    
        # Just a shortcut to avoid the slower calls via method_missing.
        def book(scenarioIdx, sbIdx, task)
          @data[scenarioIdx].book(sbIdx, task)
        end
    
        # Many Resource functions are scenario specific. These functions are
        # provided by the class ResourceScenario. In case we can't find a
        # function called for the Resource class we try to find it in
        # ResourceScenario.
        def method_missing(func, scenarioIdx, *args, &block)
          @data[scenarioIdx].method(func).call(*args, &block)
        end
    
        def query_dashboard(query)
          dashboard(query)
        end
    
        private
    
        # Create a dashboard-like list of all task that have a current alert
        # status.
        def dashboard(query)
          scenarioIdx = @project['trackingScenarioIdx']
          taskList = []
          unless scenarioIdx
            rText = "No 'trackingscenario' defined."
          else
            @project.tasks.each do |task|
              if task['responsible', scenarioIdx].include?(self) &&
                !@project['journal'].currentEntries(query.end, task,
                                                    0, query.start,
                                                    query.hideJournalEntry).empty?
                taskList << task
              end
            end
          end
    
          if taskList.empty?
            rText = "We have no current status for any task that #{name} " +
                    "is responsible for."
          else
            # The components of the message are either UTF-8 text or RichText. For
            # the RichText components, we use the originally provided markup since
            # we compose the result as RichText markup first.
            rText = ''
    
            taskList.each do |task|
              rText += "=== [" +
                       "#{task.query_alert(query).richText.inputText}" +
                       "] Task: #{task.name} " +
                       "(#{task.fullId}) ===\n\n"
              rText += task.query_journalmessages(query).richText.inputText + "\n\n"
            end
          end
    
          # Now convert the RichText markup String into RichTextIntermediate
          # format.
          unless (rti = RichText.new(rText, RTFHandlers.create(@project)).
                  generateIntermediateFormat)
            warning('res_dashboard', 'Syntax error in dashboard text')
            return nil
          end
          # No section numbers, please!
          rti.sectionNumbers = false
          # We use a special class to allow CSS formating.
          rti.cssClass = 'tj_journal'
          query.rti = rti
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/daemon/0000755000175000017500000000000012614413013017677 5ustar  bernatbernattaskjuggler-3.5.0/lib/taskjuggler/daemon/Daemon.rb0000644000175000017500000000665312614413013021441 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = Daemon.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/MessageHandler'
    
    class TaskJuggler
    
      # This class provides the basic functionality to turn the current process
      # into a background process (daemon). To use it, derive you main class from
      # this class and call the start() method.
      class Daemon
    
        include MessageHandler
    
        attr_accessor :pidFile, :daemonize
    
        def initialize
          # You can set this flag to false to prevent the program from
          # disconnecting from the current terminal. This is useful for debugging
          # purposes.
          @daemonize = true
          # Save the PID of the running daemon as number into this file.
          @pidFile = nil
        end
    
        # Call this method to turn the process into a background process.
        def start
          return 0 unless @daemonize
    
          # Fork and have the parent exit
          if (pid = fork) == -1
            fatal('first_fork_failed', 'First fork failed')
          elsif !pid.nil?
            # This is the parent. We can exit now.
            debug('', "Forked a child process with PID #{pid}")
            exit! 0
          end
    
          # Create a new session
          Process.setsid
          # Fork again to make sure we lose the controlling terminal
          if (pid = fork) == -1
            fatal('second_fork_failed', 'Second fork failed')
          elsif !pid.nil?
            # This is the parent. We can exit now.
            debug('', "Forked a child process with PID #{pid}")
            exit! 0
          end
    
          @pid = Process.pid
    
          writePidFile
    
          # Change current working directory to the file system root
          Dir.chdir '/'
          # Make sure we can create files with any permission
          File.umask 0
    
          # We no longer have a controlling terminal, so these are useless.
          $stdin.reopen('/dev/null')
          $stdout.reopen('/dev/null', 'a')
          $stderr.reopen($stdout)
    
          info('daemon_pid',
               "The process is running as daemon now with PID #{@pid}")
    
          0
        end
    
        # This method may provide some cleanup functionality in the future. You
        # better call it before you exit.
        def stop
          if @pidFile
            begin
              File.delete(@pidFile)
            rescue
              warning('cannot_delete_pidfile',
                      "Cannote delete the PID file (#{@pidFile}): #{$!}")
            end
            info('daemon_deleted_pidfile', "PID file #{@pidFile} deleted")
          end
        end
    
        private
    
        def writePidFile
          if @pidFile
            # Prepend the current working dir to @pidFile unless it's already an
            # absolute path. The working dir is changed to '/' later. We need the
            # absolute name to be able to delete it on exit again.
            if @pidFile[0] != '/'
              @pidFile = File.join(Dir.getwd, @pidFile)
            end
    
            # If requested, write the PID of the daemon to the specified file.
            begin
              File.open(@pidFile, 'w') do |f|
                f.puts @pid
              end
            rescue
              warning('cannot_save_pidfile', "Cannot write PID to #{@pidFile}")
            end
            info('daemon_wrote_pidfile',
                 "PID file #{@pidFile} written with PID #{@pid}")
          end
    
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/daemon/WelcomePage.rb0000644000175000017500000000414012614413013022413 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = WelcomePage.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'webrick'
    
    require 'taskjuggler/Tj3Config'
    require 'taskjuggler/HTMLDocument'
    
    class TaskJuggler
    
      class WelcomePage < WEBrick::HTTPServlet::AbstractServlet
    
        def initialize(config, *options)
          super
        end
    
        def self.get_instance(config, options)
          self.new(config, *options)
        end
    
        def do_GET(req, res)
          @req = req
          @res = res
          begin
            generateWelcomePage
          #rescue
          end
        end
    
        private
    
        def generateWelcomePage()
          text = <<"EOT"
    == Welcome to TaskJuggler ==
    ----
    
    This is the welcome page of your TaskJuggler built-in web server.
    To access your loaded TaskJuggler projects, click [/taskjuggler here].
    
    If you are seeing this page instead of the site you expected, please contact
    the administrator of the site involved. Try sending mail to
    .
    
    Although this site is running the TaskJuggler software it almost certainly has
    no other connection to the TaskJuggler project, so please do not send mail
    about this site or its contents to the TaskJuggler authors. If you do, your
    message will be ignored.
    
    You can use the following links to learn more about TaskJuggler:
    
    * [#{AppConfig.contact} The TaskJuggler web site]
    * [#{AppConfig.contact+ "/tj3/manual/index.html"} User Manual]
    
    ----
    #{AppConfig.softwareName} v#{AppConfig.version}
    - Copyright (c) #{AppConfig.copyright.join(', ')}
    by #{AppConfig.authors.join(', ')}
    EOT
    
          rt = RichText.new(text)
          rti = rt.generateIntermediateFormat
          rti.sectionNumbers = false
          page = HTMLDocument.new
          page.generateHead("Welcome to TaskJuggler")
          page.html << rti.to_html
          @res['content-type'] = 'text/html'
          @res.body = page.to_s
        end
    
      end
    
    end
    taskjuggler-3.5.0/lib/taskjuggler/daemon/WebServer.rb0000644000175000017500000000757312614413013022144 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = WebServer.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'webrick'
    
    require 'taskjuggler/AppConfig'
    require 'taskjuggler/daemon/Daemon'
    require 'taskjuggler/daemon/WelcomePage'
    require 'taskjuggler/daemon/ReportServlet'
    
    class TaskJuggler
    
      # The WebServer class provides a self-contained HTTP server that can serve
      # HTML versions of Report objects that are generated on the fly.
      class WebServer < Daemon
    
        include DaemonConnectorMixin
    
        attr_accessor :authKey, :port, :uriFile, :webServerPort
    
        # Create a web server object that runs in a separate thread.
        def initialize
          super
          # For security reasons, this will probably not change. All DRb
          # operations are limited to localhost only. The client and the sever
          # must have access to the identical file system.
          @host = '127.0.0.1'
          # The default TCP/IP port. ASCII code decimals for 'T' and 'J'.
          @port = 8474
          # The file with the server URI in case port is 0.
          @uriFile = File.join(Dir.getwd, '.tj3d.uri')
          # We don't have a default key. The user must provice a key in the config
          # file. Otherwise the daemon will not start.
          @authKey = nil
    
          # Reference to WEBrick object.
          @webServer = nil
    
          # Port used by the web server
          @webServerPort = 8080
    
          Kernel.trap('TERM') do
            debug('webserver_term_signal', 'TERM signal received. Exiting...')
            # When the OS sends us a TERM signal, we try to exit gracefully.
            stop
          end
        end
    
        def start
          # In daemon mode, we fork twice and only the 2nd child continues here.
          super()
    
          debug('', "Starting web server")
          config = { :Port => @webServerPort }
          begin
            @server = WEBrick::HTTPServer.new(config)
            info('webserver_port',
                 "Web server is listening on port #{@webServerPort}")
          rescue
            fatal('webrick_start_failed', "Cannot start WEBrick: #{$!}")
          end
    
          begin
            @server.mount('/', WelcomePage, nil)
          rescue
            fatal('welcome_page_mount_failed',
                  "Cannot mount WEBrick welcome page: #{$!}")
          end
    
          begin
            @server.mount('/taskjuggler', ReportServlet,
                          [ @authKey, @host, @port, @uri ])
          rescue
            fatal('broker_page_mount_failed',
                  "Cannot mount WEBrick broker page: #{$!}")
          end
    
          # Serve some directories via the FileHandler servlet.
          %w( css icons scripts ).each do |dir|
            unless (fullDir = AppConfig.dataDirs("data/#{dir}")[0])
              error('dir_not_found', <<"EOT"
    Cannot find the #{dir} directory. This is usually the result of an
    improper TaskJuggler installation. If you know the directory, you can use the
    TASKJUGGLER_DATA_PATH environment variable to specify the location. The
    variable should be set to the path without the /data at the end. Multiple
    directories must be separated by colons.
    EOT
                        )
            end
    
            begin
              @server.mount("/#{dir}", WEBrick::HTTPServlet::FileHandler, fullDir)
            rescue
              fatal('dir_mount_failed',
                    "Cannot mount directory #{dir} in WEBrick: #{$!}")
            end
          end
    
          # Install signal handler to exit gracefully on CTRL-C.
          intHandler = Kernel.trap('INT') do
            stop
          end
    
          begin
            @server.start
          rescue
            fatal('web_server_error', "Web server error: #{$!}")
          end
        end
    
        # Stop the web server.
        def stop
          if @server
            @server.shutdown
            @server = nil
          end
          super
        end
    
       end
    
    end
    taskjuggler-3.5.0/lib/taskjuggler/daemon/ReportServlet.rb0000644000175000017500000001603112614413013023045 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = ReportServlet.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'webrick'
    require 'taskjuggler/MessageHandler'
    require 'taskjuggler/RichText'
    require 'taskjuggler/HTMLDocument'
    require 'taskjuggler/daemon/DaemonConnector'
    
    class TaskJuggler
    
      class ReportServlet < WEBrick::HTTPServlet::AbstractServlet
    
        def initialize(config, options)
          super
          @authKey = options[0]
          @host = options[1]
          @port = options[2]
          @uri = options[3]
        end
    
        def self.get_instance(config, options)
          self.new(config, options)
        end
    
        def do_GET(req, res)
          debug('', "Serving URL #{req}")
          @req = req
          @res = res
          begin
            # WEBrick is returning the query elements as FormData objects. We must
            # use to_s to explicitely convert them to String objects.
            projectId = req.query['project'].to_s
            debug('', "Project ID: #{projectId}")
            reportId = req.query['report'].to_s
            debug('', "Report ID: #{reportId}")
            if projectId.empty? || reportId.empty?
              debug('', "Project welcome page requested")
              generateWelcomePage(projectId)
            else
              debug('', "Report #{reportId} of project #{projectId} requested")
              attributes = req.query['attributes'] || ''
              attributes = URLParameter.decode(attributes) unless attributes.empty?
              generateReport(projectId, reportId, attributes)
            end
          rescue
          end
        end
    
        private
    
        def connectToBroker
          begin
            broker = DaemonConnector.new(@authKey, @host, @port, @uri)
          rescue
            error('cannot_connect_broker',
                  "Cannot connect to the TaskJuggler daemon: #{$!}\n" +
                  "Please make sure you have tj3d running and listening " +
                  "on port #{@port} or URI '#{@uri}'.")
          end
    
          broker
        end
    
        def generateReport(projectId, reportId, attributes)
          broker = connectToBroker
    
          # Request the Project credentials from the ProbjectBroker.
          begin
            @ps_uri, @ps_authKey = broker.getProject(projectId)
          rescue
            error('cannot_get_project_server',
                  "Cannot get project server for ID #{projectId}: #{$!}")
          end
    
          if @ps_uri.nil?
            error('ps_uri_nil', "No project with ID #{projectId} loaded")
          end
          # Get the responsible ReportServer that can generate the report.
          begin
            @projectServer = DRbObject.new(nil, @ps_uri)
            @rs_uri, @rs_authKey = @projectServer.getReportServer(@ps_authKey)
            @reportServer = DRbObject.new(nil, @rs_uri)
          rescue
            error('cannot_get_report_server',
                  "Cannot get report server: #{$!}")
          end
          # Create two StringIO buffers that will receive the $stdout and $stderr
          # text from the report server. This buffer will contain the generated
          # report as HTML encoded text. They will be send via DRb, so we have to
          # extend them with DRbUndumped.
          stdOut = StringIO.new('')
          stdOut.extend(DRbUndumped)
          stdErr = StringIO.new('')
          stdErr.extend(DRbUndumped)
    
          begin
            @reportServer.connect(@rs_authKey, stdOut, stdErr, $stdin, true)
          rescue => exception
            # TjRuntimeError exceptions are simply passed through.
            if exception.is_a?(TjRuntimeError)
              raise TjRuntimeError, $!
            end
    
            error('rs_io_connect_failed', "Can't connect IO: #{$!}")
          end
    
          # Ask the ReportServer to generate the reports with the provided ID.
          begin
            @reportServer.generateReport(@rs_authKey, reportId, false, nil,
                                         attributes)
          rescue
            stdOut.rewind
            stdErr.rewind
            error('rs_generate_report_failed',
                  "Report server crashed: #{$!}\n#{stdErr.read}\n#{stdOut.read}")
          end
          # Disconnect the ReportServer
          begin
            @reportServer.disconnect(@rs_authKey)
          rescue
            error('rs_io_disconnect_failed', "Can't disconnect IO: #{$!}")
          end
          # And send a termination request.
          begin
            @reportServer.terminate(@rs_authKey)
          rescue
            error('report_server_term_failed',
                  "Report server termination failed: #{$!}")
          end
          @reportServer = nil
          broker.disconnect
    
          @res['content-type'] = 'text/html'
          stdErr.rewind
          $stderr.puts stdErr.read
          # To read the $stdout of the ReportServer we need to rewind the buffer
          # and then read the full text.
          stdOut.rewind
          @res.body = stdOut.read
        end
    
        def generateWelcomePage(projectId)
          broker = connectToBroker
    
          begin
            projects = broker.getProjectList
          rescue
            error('cannot_get_project_list',
                  "Cannot get project list from daemon: #{$!}")
          end
    
          text = "== Welcome to the TaskJuggler Project Server ==\n----\n"
          projects.each do |id|
            if id == projectId
              # Show the list of reports for this project.
              text << "* [/taskjuggler #{getProjectName(id)}]\n"
              reports = getReportList(id)
              if reports.empty?
                text << "** This project has no reports defined.\n"
              else
                reports.each do |reportId, reportName|
                  text << "** [/taskjuggler?project=#{id};report=#{reportId} " +
                          "#{reportName}]\n"
                end
              end
            else
              # Just show a link to open the report list.
              text << "* [/taskjuggler?project=#{id} #{getProjectName(id)}]\n"
            end
          end
    
          # We no longer need the broker.
          broker.disconnect
    
          rt = RichText.new(text)
          rti = rt.generateIntermediateFormat
          rti.sectionNumbers = false
          page = HTMLDocument.new
          page.generateHead("The TaskJuggler Project Server")
          page.html << rti.to_html
          @res['content-type'] = 'text/html'
          @res.body = page.to_s
        end
    
        def getProjectName(id)
          broker = connectToBroker
    
          uri, authKey = broker.getProject(id)
          return nil unless uri
          projectServer = DRbObject.new(nil, uri)
          return nil unless projectServer
          res = projectServer.getProjectName(authKey)
    
          broker.disconnect
    
          res
        end
    
        def getReportList(id)
          broker = connectToBroker
    
          uri, authKey = broker.getProject(id)
          return [] unless uri
          projectServer = DRbObject.new(nil, uri)
          return [] unless projectServer
          res = projectServer.getReportList(authKey)
    
          broker.disconnect
    
          res
        end
    
        def error(id, message)
          @res.status = 412
          @res.body = "ERROR: #{message}"
          @res['content-type'] = 'text/plain'
          MessageHandlerInstance.instance.error(id, message)
        end
    
        def debug(id, message)
          MessageHandlerInstance.instance.debug(id, message)
        end
    
      end
    
    end
    taskjuggler-3.5.0/lib/taskjuggler/daemon/ProcessIntercom.rb0000644000175000017500000001253212614413013023346 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = ProcessIntercom.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/AppConfig'
    require 'taskjuggler/Log'
    require 'taskjuggler/MessageHandler'
    
    class TaskJuggler
    
      module ProcessIntercomIface
    
        include MessageHandler
    
        # This function catches all unhandled exceptions in the passed block.
        def trap
          begin
            MessageHandlerInstance.instance.trapSetup = true
            res = yield
            MessageHandlerInstance.instance.trapSetup = false
            res
          rescue => e
            # Any exception here is a fatal error. We try hard to terminate the DRb
            # thread and then exit the program.
            begin
              fatal('pi_crash_trap', "#{e}\n#{e.backtrace.join("\n")}\n\n" +
                    "#{'*' * 79}\nYou have triggered a bug in " +
                    "#{AppConfig.softwareName} version #{AppConfig.version}!\n" +
                    "Please see the user manual on how to get this bug fixed!\n" +
                    "#{'*' * 79}\n")
            rescue RuntimeError
              @server.terminate
              return false
            end
          end
        end
    
        def terminate(authKey)
          return false unless @server.checkKey(authKey, 'terminate')
    
          trap { @server.terminate }
        end
    
        def connect(authKey, stdout, stderr, stdin, silent)
          return false unless @server.checkKey(authKey, 'connect')
    
          trap { @server.connect(stdout, stderr, stdin, silent) }
        end
    
        def disconnect(authKey)
          return false unless @server.checkKey(authKey, 'disconnect')
    
          trap { @server.disconnect }
        end
    
      end
    
      module ProcessIntercom
    
        include MessageHandler
    
        def initIntercom
          # This is the authentication key that clients will need to provide to
          # execute DRb methods.
          @authKey = generateAuthKey
    
          # This flag will be set to true by DRb method calls to terminate the
          # process.
          @terminate = false
    
          # This mutex is locked while a client is connected.
          @clientConnection = Mutex.new
          # This lock protects the @timerStart
          @timeLock = Monitor.new
          # The time stamp of the last client interaction.
          @timerStart = nil
        end
    
        def terminate
          debug('', 'Terminating on external request')
          @terminate = true
        end
    
        def connect(stdout, stderr, stdin, silent)
          # Set the client lock.
          @clientConnection.lock
          debug('', 'Rerouting ProjectServer standard IO to client')
          # Make sure that all output to STDOUT and STDERR is sent to the client.
          # Input is read from the client STDIN.  We save a copy of the old file
          # handles so we can restore then later again.
          @stdout = $stdout
          @stderr = $stderr
          @stdin = $stdin
          $stdout = stdout if stdout
          $stderr = stderr if stdout
          $stdin = stdin if stdin
          debug('', 'IO is now routed to the client')
          Log.silent = silent
          true
        end
    
        def disconnect
          debug('', 'Restoring IO')
          Log.silent = true
          $stdout = @stdout if @stdout
          $stderr = @stderr if @stderr
          $stdin = @stdin if @stdin
          debug('', 'Standard IO has been restored')
          # Release the client lock
          @clientConnection.unlock
          true
        end
    
        def generateAuthKey
          rand(1000000000).to_s
        end
    
        def checkKey(authKey, command)
          if authKey == @authKey
            debug('', "Accepted authentication key for command '#{command}'")
          else
            warning('auth_key_rejected',
                    "Rejected wrong authentication key #{authKey}" +
                    "for command '#{command}'")
            return false
          end
          true
        end
    
        # This function must be called after each client interaction to restart the
        # client connection timer.
        def restartTimer
          @timeLock.synchronize do
            debug('', 'Reseting client connection timer')
            @timerStart = Time.new
          end
        end
    
        # Check if the client interaction timer has already expired.
        def timerExpired?
          res = nil
          @timeLock.synchronize do
            # We should see client interaction every 2 minutes.
            res = (Time.new > @timerStart + 2 * 60)
          end
          res
        end
    
        # This method starts a new thread and waits for the @terminate variable to
        # be true. If that happens, it waits for the @clientConnection lock or
        # forces an exit after the timeout has been reached. It shuts down the DRb
        # server.
        def startTerminator
          Thread.new do
            loop do
              if @terminate
                # We wait for the client to propery disconnect. In case this does
                # not happen, we'll wait for the timeout and exit anyway.
                restartTimer
                while @clientConnection.locked? && !timerExpired? do
                  sleep 1
                end
                if timerExpired?
                  warning('drb_timeout_shutdown',
                          'Shutting down DRb server due to timeout')
                else
                  debug('', 'Shutting down the DRb server')
                end
                DRb.stop_service
                break
              else
                sleep 1
              end
            end
          end
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/daemon/DaemonConnector.rb0000644000175000017500000000705212614413013023306 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = DaemonConnector.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'drb'
    require 'drb/acl'
    require 'taskjuggler/MessageHandler'
    
    class TaskJuggler
    
      module DaemonConnectorMixin
    
        include MessageHandler
    
        def connectDaemon
          unless @authKey
            error('missing_auth_key', <<'EOT'
    You must set an authentication key in the configuration file. Create a file
    named .taskjugglerrc or taskjuggler.rc that contains at least the following
    lines. Replace 'your_secret_key' with some random character sequence.
    
    _global:
      authKey: your_secret_key
    EOT
                 )
          end
    
          uri = "druby://#{@host}:#{@port}"
          if @port == 0
            # If the @port is configured to 0, we need to read the URI to connect
            # to the server from the .tj3d.uri file that has been generated by the
            # server.
            begin
              uri = File.read(@uriFile).chomp
            rescue
              error('tjc_port_0',
                    'The server port is configured to be 0, but no ' +
                    ".tj3d.uri file can be found: #{$!}")
            end
          end
          debug('', "DRb URI determined as #{uri}")
    
          # We try to play it safe here. The client also starts a DRb server, so
          # we need to make sure it's constricted to localhost only. We require
          # the DRb server for the standard IO redirection to work.
          $SAFE = 1 unless @unsafeMode
          DRb.install_acl(ACL.new(%w[ deny all
                                      allow 127.0.0.1 ]))
          DRb.start_service('druby://127.0.0.1:0')
          debug('', 'DRb service started')
    
          broker = nil
          begin
            # Get the ProjectBroker object from the tj3d.
            broker = DRbObject.new_with_uri(uri)
            # Client and server should always come from the same Gem. Since we
            # restict communication to localhost, that's probably not a problem.
            if (check = broker.apiVersion(@authKey, 1)) < 0
              error('tjc_too_old',
                    'This client is too old for the server. Please ' +
                    'upgrade to a more recent version of the software.')
            elsif check == 0
              error('tjc_auth_fail',
                    'Authentication failed. Please check your authentication ' +
                    'key to match the server key.')
            end
            debug('', "Connection with report broker on #{uri} established")
          rescue => e
            # If we ended up here due to a previously reported TjRuntimeError, we
            # just pass it through.
            if e.is_a?(TjRuntimeError)
              raise TjRuntimeError, $!
            end
            error('tjc_srv_not_responding',
                  "TaskJuggler server (tj3d) on URI '#{uri}' is not " +
                  "responding:\n#{$!}")
          end
    
          broker
        end
    
        def disconnectDaemon
          DRb.stop_service
        end
    
      end
    
      class DaemonConnector
    
        include DaemonConnectorMixin
    
        def initialize(authKey, host, port, uri)
          @authKey = authKey
          @host = host
          @port = port
          @uri = uri
          @unsafeMode = true
    
          @broker = connectDaemon
        end
    
        def disconnect
          disconnectDaemon
          @broker = nil
        end
    
        def getProject(projectId)
          @broker.getProject(@authKey, projectId)
        end
    
        def getProjectList
          @broker.getProjectList(@authKey)
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/daemon/ReportServer.rb0000644000175000017500000001525412614413013022675 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = ReportServer.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/daemon/ProcessIntercom'
    require 'taskjuggler/TjException'
    require 'taskjuggler/TjTime'
    
    class TaskJuggler
    
      class ReportServer
    
        include ProcessIntercom
    
        attr_reader :uri, :authKey
    
        def initialize(tj, logConsole = false)
          initIntercom
    
          @pid = nil
          @uri = nil
    
          # A reference to the TaskJuggler object that holds the project data.
          @tj = tj
          # This is set to the ID(s) of the reports that should be generated.
          @reportId = 'unknown'
    
          @lastPing = TjTime.new
    
          # We've started a DRb server before. This will continue to live somewhat
          # in the child. All attempts to create a DRb connection from the child
          # to the parent will end up in the child again. So we use a Pipe to
          # communicate the URI of the child DRb server to the parent. The
          # communication from the parent to the child is not affected by the
          # zombie DRb server in the child process.
          rd, wr = IO.pipe
    
          if (@pid = fork) == -1
            fatal('rs_fork_failed', 'ReportServer fork failed')
          elsif @pid.nil?
            if logConsole
              # If the Broker wasn't daemonized, log stdout and stderr to PID
              # specific files.
              $stderr.reopen("tj3d.rs.#{$$}.stderr", 'w')
              $stdout.reopen("tj3d.rs.#{$$}.stdout", 'w')
            end
            begin
              # This is the child
              $SAFE = 1
              DRb.install_acl(ACL.new(%w[ deny all
                                          allow 127.0.0.1 ]))
              iFace = ReportServerIface.new(self)
              begin
                uri = DRb.start_service('druby://127.0.0.1:0', iFace).uri
                debug('', "Report server is listening on #{uri}")
              rescue
                error('rs_cannot_start_drb',
                      "ReportServer can't start DRb: #{$!}")
              end
    
              # Send the URI of the newly started DRb server to the parent process.
              rd.close
              wr.write uri
              wr.close
    
              # Start a Thread that waits for the @terminate flag to be set and does
              # other background tasks.
              startTerminator
              startWatchDog
    
              # Cleanup the DRb threads
              DRb.thread.join
              debug('', 'Report server terminated')
              exit 0
            rescue => exception
              # TjRuntimeError exceptions are simply passed through.
              if exception.is_a?(TjRuntimeError)
                raise TjRuntimeError, $!
              end
    
              error('rs_unexp_excp',
                    "ReportServer caught unexpected exception: #{$!}")
            end
          else
            Process.detach(@pid)
            # This is the parent
            wr.close
            @uri = rd.read
            rd.close
          end
        end
    
        def ping
          @lastPing = TjTime.new
        end
    
        def addFile(file)
          begin
            @tj.parseFile(file, :reportPropertiesFile)
          rescue TjRuntimeError
            return false
          end
          restartTimer
          true
        end
    
        def generateReport(id, regExpMode, formats, dynamicAttributes)
          info('generating_report', "Generating report #{id}")
          startTime = Time.now
          @reportId = id
          begin
            if (ok = @tj.generateReport(id, regExpMode, formats, dynamicAttributes))
              info('report_id_generated',
                   "Report #{id} generated in #{Time.now - startTime} seconds")
            else
              error('report_generation_failed', "Report generation of #{id} failed")
            end
          rescue TjRuntimeError
            return false
          end
          restartTimer
          ok
        end
    
        def listReports(id, regExpMode)
          info('listing_report_id', "Listing report #{id}")
          begin
            if (ok = @tj.listReports(id, regExpMode))
              debug('', "Report list for #{id} generated")
            else
              error('repor_list_comp_failed',
                    "Report list compilation of #{id} failed")
            end
          rescue TjRuntimeError
            return false
          end
          restartTimer
          ok
        end
    
        def checkTimeSheet(sheet)
          info('check_time_sheet', "Checking time sheet #{sheet}")
          @reportId = 'timesheet'
          begin
            ok = @tj.checkTimeSheet(sheet)
            debug('', "Time sheet #{sheet} is #{ok ? '' : 'not '}ok")
          rescue TjRuntimeError
            return false
          end
          restartTimer
          ok
        end
    
        def checkStatusSheet(sheet)
          info('check_status_sheet', "Checking status sheet #{sheet}")
          @reportId = 'statussheet'
          begin
            ok = @tj.checkStatusSheet(sheet)
            debug('', "Status sheet #{sheet} is #{ok ? '' : 'not '}ok")
          rescue TjRuntimeError
            return false
          end
          restartTimer
          ok
        end
    
        private
    
        def startWatchDog
          Thread.new do
            loop do
              if TjTime.new - @lastPing > 120
                # Since the abort via error() is not thread safe, we issue a
                # warning and abort manually.
                warning('ps_heartbeat_lost',
                        "Report server (Project #{@tj.project['projectid']} " +
                        "report #{@reportId}) lost heartbeat " +
                        'from ProjectServer. Terminating.')
                exit 1
              end
              sleep 30
            end
          end
        end
    
      end
    
      class ReportServerIface
    
        include ProcessIntercomIface
    
        def initialize(server)
          @server = server
        end
    
        def ping(authKey)
          return false unless @server.checkKey(authKey, 'addFile')
    
          trap { @server.ping }
        end
    
        def addFile(authKey, file)
          return false unless @server.checkKey(authKey, 'addFile')
    
          trap { @server.addFile(file) }
        end
    
        def generateReport(authKey, reportId, regExpMode, formats,
                           dynamicAttributes)
          return false unless @server.checkKey(authKey, 'generateReport')
    
          trap do
            @server.generateReport(reportId, regExpMode, formats, dynamicAttributes)
          end
        end
    
    
        def listReports(authKey, reportId, regExpMode)
          return false unless @server.checkKey(authKey, 'generateReport')
    
          trap { @server.listReports(reportId, regExpMode) }
        end
    
        def checkTimeSheet(authKey, sheet)
          return false unless @server.checkKey(authKey, 'checkTimeSheet')
    
          trap { @server.checkTimeSheet(sheet) }
        end
    
        def checkStatusSheet(authKey, sheet)
          return false unless @server.checkKey(authKey, 'checkStatusSheet')
    
          trap { @server.checkStatusSheet(sheet) }
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/daemon/ProjectServer.rb0000644000175000017500000003612612614413013023031 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = ProjectServer.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'drb'
    require 'drb/acl'
    require 'monitor'
    require 'taskjuggler/daemon/ProcessIntercom'
    require 'taskjuggler/daemon/ReportServer'
    require 'taskjuggler/MessageHandler'
    require 'taskjuggler/TaskJuggler'
    require 'taskjuggler/TjTime'
    
    class TaskJuggler
    
      # The ProjectServer objects are created from the ProjectBroker to handle the
      # data of a particular project. Each ProjectServer runs in a separate
      # process that is forked-off in the constructor. Any action such as adding
      # more files or generating a report will cause the process to fork again,
      # creating a ReportServer object. This way the initially loaded project can
      # be modified but the original version is always preserved for subsequent
      # calls. Each ProjectServer process has a unique secret authentication key
      # that only the ProjectBroker knows. It will pass it with the URI of the
      # ProjectServer to the client to permit direct access to the ProjectServer.
      class ProjectServer
    
        include ProcessIntercom
    
        attr_reader :authKey, :uri
    
        def initialize(daemonAuthKey, projectData = nil, logConsole = false)
          @daemonAuthKey = daemonAuthKey
          @projectData = projectData
          # Since we are still in the ProjectBroker process, the current DRb
          # server is still the ProjectBroker DRb server.
          @daemonURI = DRb.current_server.uri
          # Used later to store the DRbObject of the ProjectBroker.
          @daemon = nil
          initIntercom
    
          @logConsole = logConsole
          @pid = nil
          @uri = nil
    
          # A reference to the TaskJuggler object that holds the project data.
          @tj = nil
          # The current state of the project.
          @state = :new
          # A time stamp when the last @state update happened.
          @stateUpdated = TjTime.new
          # A lock to protect access to @state
          @stateLock = Monitor.new
    
          # A Queue to asynchronously generate new ReportServer objects.
          @reportServerRequests = Queue.new
    
          # A list of active ReportServer objects
          @reportServers = []
          @reportServers.extend(MonitorMixin)
    
          @lastPing = TjTime.new
    
          # We've started a DRb server before. This will continue to live somewhat
          # in the child. All attempts to create a DRb connection from the child
          # to the parent will end up in the child again. So we use a Pipe to
          # communicate the URI of the child DRb server to the parent. The
          # communication from the parent to the child is not affected by the
          # zombie DRb server in the child process.
          rd, wr = IO.pipe
    
          if (@pid = fork) == -1
            fatal('ps_fork_failed', 'ProjectServer fork failed')
          elsif @pid.nil?
            # This is the child
            if @logConsole
              # If the Broker wasn't daemonized, log stdout and stderr to PID
              # specific files.
              $stderr.reopen("tj3d.ps.#{$$}.stderr", 'w')
              $stdout.reopen("tj3d.ps.#{$$}.stdout", 'w')
            end
            begin
              $SAFE = 1
              DRb.install_acl(ACL.new(%w[ deny all allow 127.0.0.1 ]))
              iFace = ProjectServerIface.new(self)
              begin
                @uri = DRb.start_service('druby://127.0.0.1:0', iFace).uri
                debug('', "Project server is listening on #{@uri}")
              rescue
                error('ps_cannot_start_drb', "ProjectServer can't start DRb: #{$!}")
              end
    
              # Send the URI of the newly started DRb server to the parent process.
              rd.close
              wr.write @uri
              wr.close
    
              # Start a Thread that waits for the @terminate flag to be set and does
              # other background tasks.
              startTerminator
              # Start another Thread that will be used to fork-off ReportServer
              # processes.
              startHousekeeping
    
              # Cleanup the DRb threads
              DRb.thread.join
              debug('', 'Project server terminated')
              exit 0
            rescue => exception
              # TjRuntimeError exceptions are simply passed through.
              if exception.is_a?(TjRuntimeError)
                raise TjRuntimeError, $!
              end
    
              error('ps_cannot_start_drb', "ProjectServer can't start DRb: #{$!}")
            end
          else
            # This is the parent
            Process.detach(@pid)
            wr.close
            @uri = rd.read
            rd.close
          end
        end
    
        # Wait until the project load has been finished. The result is true if the
        # project scheduled without errors. Otherwise the result is false.
        # _args_ is an Array of Strings. The first element is the working
        # directory. The second one is the master project file (.tjp file).
        # Additionally a list of optional .tji files can be provided.
        def loadProject(args)
          dirAndFiles = args.dup.untaint
          # The first argument is the working directory
          Dir.chdir(args.shift.untaint)
    
          # Save a time stamp of when the project file loading started.
          @modifiedCheck = TjTime.new
    
          updateState(:loading, dirAndFiles, false)
          begin
            @tj = TaskJuggler.new
            # Make sure that trace reports get CSV formats included so there
            # reports can be generated on request.
            @tj.generateTraces = true
    
            # Parse all project files
            unless @tj.parse(args, true)
              warning('parse_failed', "Parsing of #{args.join(' ')} failed")
              updateState(:failed, nil, false)
              @terminate = true
              return false
            end
    
            # Then schedule the project
            unless @tj.schedule
              warning('schedule_failed',
                      "Scheduling of project #{@tj.projectId} failed")
              updateState(:failed, @tj.projectId, false)
              @terminate = true
              return false
            end
          rescue TjRuntimeError
            updateState(:failed, nil, false)
            @terminate = true
            return false
          end
    
          # Great, everything went fine. We've got a project to work with.
          updateState(:ready, @tj.projectId, false)
          debug('', "Project #{@tj.projectId} loaded")
          restartTimer
          true
        end
    
        # Return the name of the loaded project or nil.
        def getProjectName
          return nil unless @tj
          restartTimer
          @tj.projectName
        end
    
        # Return a list of the HTML reports defined for the project.
        def getReportList
          return [] unless @tj && (project = @tj.project)
          list = []
          project.reports.each do |report|
            unless report.get('formats').empty?
              list << [ report.fullId, report.name ]
            end
          end
          restartTimer
          list
        end
    
        # This function triggers the creation of a new ReportServer process. It
        # will return the URI and the authentication key of this new server.
        def getReportServer
          # ReportServer objects only make sense for successfully scheduled
          # projects.
          return [ nil, nil ] unless @state == :ready
    
          # The ReportServer will be created asynchronously in another Thread. To
          # find it in the @reportServers list, we create a unique tag to identify
          # it.
          tag = rand(99999999999999)
          debug('', "Pushing #{tag} onto report server request queue")
          @reportServerRequests.push(tag)
    
          # Now wait until the new ReportServer shows up in the list.
          reportServer = nil
          while reportServer.nil?
            @reportServers.synchronize do
              @reportServers.each do |rs|
                reportServer = rs if rs.tag == tag
              end
            end
            # It should not take that long, so we use a short idle time here.
            sleep 0.1 if reportServer.nil?
          end
    
          debug('', "Got report server with URI #{reportServer.uri} for " +
                "tag #{tag}")
          restartTimer
          [ reportServer.uri, reportServer.authKey ]
        end
    
        # This function is called regularly by the ProjectBroker process to check
        # that the ProjectServer is still operating properly.
        def ping
          # Store the time stamp. If we don't get the ping for some time, we
          # assume the ProjectBroker has died.
          @lastPing = TjTime.new
    
          # Now also check our ReportServers if they are still there. If not, we
          # can remove them from the @reportServers list.
          @reportServers.synchronize do
            deadServers = []
            @reportServers.each do |rs|
              unless rs.ping
                deadServers << rs
              end
            end
            @reportServers.delete_if { |rs| deadServers.include?(rs) }
          end
        end
    
        private
    
        # Update the _state_, _id_ and _modified_ state of the project locally and
        # remotely.
        def updateState(state, filesOrId, modified)
          begin
            @daemon = DRbObject.new(nil, @daemonURI) unless @daemon
            @daemon.updateState(@daemonAuthKey, @authKey, filesOrId, state,
                                modified)
          rescue => exception
            # TjRuntimeError exceptions are simply passed through.
            if exception.is_a?(TjRuntimeError)
              raise TjRuntimeError, $!
            end
    
            error('cannot_update_daemon_state',
                  "Can't update state with daemon: #{$!}")
          end
          @stateLock.synchronize do
            @state = state
            @stateUpdated = TjTime.new
            @modified = modified
            @modifiedCheck = TjTime.new
          end
        end
    
        def startHousekeeping
          Thread.new do
            begin
              loop do
                # Exit this thread if the @terminate flag is set.
                break if @terminate
    
                # Was the project data provided during object creation?
                # Then we load the data here.
                if @projectData
                  loadProject(@projectData)
                  @projectData = nil
                end
    
                # Check every 60 seconds if the input files have been modified.
                # Don't check if we already know it has been modified.
                if @stateLock.synchronize { @state == :ready && !@modified &&
                                            @modifiedCheck + 60 < TjTime.new }
                  # Reset the timer
                  @stateLock.synchronize { @modifiedCheck = TjTime.new }
    
                  if @tj.project.inputFiles.modified?
                    debug('', "Project #{@tj.projectId} has been modified")
                    updateState(:ready, @tj.projectId, true)
                  end
                end
    
                # Check for pending requests for new ReportServers.
                unless @reportServerRequests.empty?
                  tag = @reportServerRequests.pop
                  debug('', "Popped #{tag}")
                  # Create an new entry for the @reportServers list.
                  rsr = ReportServerRecord.new(tag)
                  debug('', "RSR created")
                  # Create a new ReportServer object that runs as a separate
                  # process. The constructor will tell us the URI and authentication
                  # key of the new ReportServer.
                  rs = ReportServer.new(@tj, @logConsole)
                  rsr.uri = rs.uri
                  rsr.authKey = rs.authKey
                  debug('', "Adding ReportServer with URI #{rsr.uri} to list")
                  # Add the new ReportServer to our list.
                  @reportServers.synchronize do
                    @reportServers << rsr
                  end
                end
    
                # Some state changing operations are not atomic. Since the client
                # can die during the transaction, the server might hang in some
                # states. Here we define timeout for each state. If the timeout is
                # not 0 and exceeded, we immediately terminate the process.
                timeouts = { :new => 30, :loading => 15 * 60, :failed => 60,
                             :ready => 0 }
                if timeouts[@state] > 0 &&
                   TjTime.new - @stateUpdated > timeouts[@state]
                  error('state_timeout',
                        "Reached timeout for state #{@state}. Terminating.")
                end
    
                # If we have not received a ping from the ProjectBroker for 2
                # minutes, we assume it has died and terminate as well.
                if TjTime.new - @lastPing > 180
                  # Since the abort via error() is not thread safe, we issue a
                  # warning and abort manually.
                  warning('daemon_heartbeat_lost',
                          'Heartbeat from daemon lost. Terminating.')
                  exit 1
                end
                sleep 1
              end
            rescue => exception
              # TjRuntimeError exceptions are simply passed through.
              if exception.is_a?(TjRuntimeError)
                raise TjRuntimeError, $!
              end
    
              # Make sure we get a backtrace for this thread.
              fatal('ps_housekeeping_error',
                    "ProjectServer housekeeping error: #{$!}")
            end
          end
        end
    
      end
    
      # This is the DRb call interface of the ProjectServer class. All functions
      # must be authenticated with the proper key.
      class ProjectServerIface
    
        include ProcessIntercomIface
    
        def initialize(server)
          @server = server
        end
    
        def loadProject(authKey, args)
          return false unless @server.checkKey(authKey, 'loadProject')
    
          trap { @server.loadProject(args) }
        end
    
        def getProjectName(authKey)
          return false unless @server.checkKey(authKey, 'getReportServer')
    
          trap { @server.getProjectName }
        end
    
        def getReportList(authKey)
          return false unless @server.checkKey(authKey, 'getReportServer')
    
          trap { @server.getReportList }
        end
    
        def getReportServer(authKey)
          return false unless @server.checkKey(authKey, 'getReportServer')
    
          trap { @server.getReportServer }
        end
    
        def ping(authKey)
          return false unless @server.checkKey(authKey, 'ping')
    
          trap { @server.ping }
          true
        end
    
      end
    
      # This class stores the information about a ReportServer that was created by
      # the ProjectServer.
      class ReportServerRecord
    
        include MessageHandler
    
        attr_reader :tag
        attr_accessor :uri, :authKey
    
        def initialize(tag)
          # A random tag to uniquely identify the entry.
          @tag = tag
          # The URI of the ReportServer process.
          @uri = nil
          # The authentication key of the ReportServer.
          @authKey = nil
          # The DRbObject of the ReportServer.
          @reportServer = nil
        end
    
        # Send a ping to the ReportServer process to check that it is still
        # functioning properly. If not, it has probably terminated and we can
        # remove it from the list of active ReportServers.
        def ping
          return true unless @uri
    
          debug('', "Sending ping to ReportServer #{@uri}")
          begin
            @reportServer = DRbObject.new(nil, @uri) unless @reportServer
            @reportServer.ping(@authKey)
          rescue => exception
            # TjRuntimeError exceptions are simply passed through.
            if exception.is_a?(TjRuntimeError)
              raise TjRuntimeError, $!
            end
    
            # ReportServer processes terminate on request of their clients. Not
            # responding to a ping is a normal event.
            debug('', "ReportServer (#{@uri}) has terminated")
            return false
          end
          true
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/daemon/ProjectBroker.rb0000644000175000017500000005010512614413013023000 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = ProjectBroker.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'monitor'
    require 'thread'
    require 'drb'
    require 'drb/acl'
    require 'taskjuggler/daemon/Daemon'
    require 'taskjuggler/daemon/ProjectServer'
    require 'taskjuggler/TjTime'
    require 'taskjuggler/MessageHandler'
    
    class TaskJuggler
    
      # The ProjectBroker is the central object of the TaskJuggler daemon. It can
      # manage multiple scheduled projects that it keeps in separate sub
      # processes. Requests to a specific project will be redirected to the
      # specific ProjectServer process. Projects can be added or removed. Adding
      # an already existing one (identified by project ID) will replace the old
      # one as soon as the new one has been scheduled successfully.
      #
      # The daemon uses DRb to communicate with the client and it's sub processes.
      # The communication is restricted to localhost. All remote commands require
      # an authentication key.
      #
      # Currently only tj3client can be used to communicate with the TaskJuggler
      # daemon.
      class ProjectBroker < Daemon
    
        include MessageHandler
    
        attr_accessor :authKey, :port, :uriFile, :projectFiles, :logStdIO
    
        def initialize
          super
          # We don't have a default key. The user must provice a key in the config
          # file. Otherwise the daemon will not start.
          @authKey = nil
          # The default TCP/IP port. ASCII code decimals for 'T' and 'J'.
          @port = 8474
          # The name of the URI file.
          @uriFile = nil
          # A list of loaded projects as Array of ProjectRecord objects.
          @projects = []
          # We operate with multiple threads so we need a Monitor to synchronize
          # the access to the list.
          @projects.extend(MonitorMixin)
    
          # A list of the initial projects. Array with Array of files names.
          @projectFiles = []
    
          # This Queue is used to load new projects. The DRb thread pushes load
          # requests that the housekeeping thread will then perform.
          @projectsToLoad = Queue.new
    
          # Set this flag to true to have standard IO logged into files. There
          # will be seperate set of files for each process.
          @logStdIO = !@daemonize
    
          # This flag will be set to true to terminate the daemon.
          @terminate = false
        end
    
        def start
          # To ensure a certain level of security, the user must provide an
          # authentication key to authenticate the client to this server.
          unless @authKey
            error('pb_no_auth_key', <<'EOT'
    You must set an authentication key in the configuration file. Create a file
    named .taskjugglerrc or taskjuggler.rc that contains at least the following
    lines. Replace 'your_secret_key' with some random character sequence.
    
    _global:
      authKey: your_secret_key
    EOT
                      )
          end
    
          # In daemon mode, we fork twice and only the 2nd child continues here.
          super()
          debug('', "Starting project broker")
    
          # Setup a DRb server to handle the incomming requests from the clients.
          brokerIface = ProjectBrokerIface.new(self)
          begin
            $SAFE = 1
            DRb.install_acl(ACL.new(%w[ deny all
                                        allow 127.0.0.1 ]))
            @uri = DRb.start_service("druby://127.0.0.1:#{@port}", brokerIface).uri
            info('daemon_uri', "TaskJuggler daemon is listening on #{@uri}")
          rescue
            error('port_in_use', "Cannot listen on port #{@port}: #{$!}")
          end
    
          if @port == 0 && @uriFile
            # If the port is set to 0 (any port) we save the ProjectBroker URI in
            # the file .tj3d.uri. tj3client will look for it.
            begin
              File.open(@uriFile, 'w') { |f| f.write @uri }
            rescue
              error('cannot_write_uri', "Cannot write URI file #{@uriFile}: #{$!}")
            end
          end
    
          # If project files were specified on the command line, we add them here.
          i = 0
          @projectFiles.each do |project|
            @projectsToLoad.push(project)
          end
    
          # Start a Thread that waits for the @terminate flag to be set and does
          # some other work asynchronously.
          startHousekeeping
    
          debug('', 'Shutting down ProjectBroker DRb server')
          DRb.stop_service
    
          # If we have created a URI file, we need to delete it again.
          if @port == 0 && @uriFile
            begin
              File.delete(@uriFile)
            rescue
              error('cannot_delete_uri', "Cannot delete URI file .tj3d.uri: #{$!}")
            end
          end
    
          info('daemon_terminated', 'TaskJuggler daemon terminated')
        end
    
        # All remote commands must provide the proper authentication key. Usually
        # the client and the server get this secret key from the same
        # configuration file.
        def checkKey(authKey, command)
          if authKey == @authKey
            debug('', "Accepted authentication key for command '#{command}'")
          else
            warning('wrong_auth_key',
                    "Rejected wrong authentication key '#{authKey}' " +
                    "for command '#{command}'")
            return false
          end
          true
        end
    
        # This command will initiate the termination of the daemon.
        def stop
          debug('', 'Terminating on client request')
    
          # Shut down the web server if we've started one.
          if @webServer
            @webServer.stop
          end
    
          # Send termination signal to all ProjectServer instances
          @projects.synchronize do
            @projects.each { |p| p.terminateServer }
          end
    
          # Setting the @terminate flag to true will case the terminator Thread to
          # call DRb.stop_service
          @terminate = true
          super
        end
    
        # Generate a table with information about the loaded projects.
        def status
          if @projects.empty?
            "No projects registered\n"
          else
            format = "  %3s | %-25s | %-14s | %s | %-20s\n"
            out = sprintf(format, 'No.', 'Project ID', 'Status', 'M',
                                  'Loaded since')
            out += "  #{'-' * 4}+#{'-' * 27}+#{'-' * 16}+#{'-' * 3}+#{'-' * 20}\n"
            @projects.synchronize do
              i = 0
              @projects.each do |project|
                out += project.to_s(format, i += 1)
              end
            end
            out
          end
        end
    
        # Adding a new project or replacing an existing one. The command waits
        # until the project has been loaded or the load has failed.
        def addProject(cwd, args, stdOut, stdErr, stdIn, silent)
          # We need some tag to identify the ProjectRecord that this project was
          # associated to. Just use a large enough random number.
          tag = rand(9999999999999)
    
          debug('', "Pushing #{tag} to load Queue")
          @projectsToLoad.push(tag)
    
          # Now we have to wait until the project shows up in the @projects
          # list. We use our tag to identify the right entry.
          pr = nil
          while pr.nil?
            @projects.synchronize do
              @projects.each do |p|
                if p.tag == tag
                  pr = p
                  break
                end
              end
            end
            # The wait in this loop should be pretty short and we don't want to
            # miss IO from the ProjectServer process.
            sleep 0.1 unless pr
          end
    
          debug('', "Found tag #{tag} in list of loaded projects with URI " +
                "#{pr.uri}")
    
          res = false
    
          # Open a DRb connection to the ProjectServer process
          begin
            projectServer = DRbObject.new(nil, pr.uri)
          rescue
            warning('pb_cannot_get_ps', "Can't get ProjectServer object: #{$!}")
            return false
          end
          begin
            # Hook up IO from requestor to the ProjectServer process.
            projectServer.connect(pr.authKey, stdOut, stdErr, stdIn, silent)
          rescue
            warning('pb_cannot_connect_io', "Can't connect IO: #{$!}")
            return false
          end
    
          # Ask the ProjectServer to load the files in _args_ into the
          # ProjectServer.
          begin
            res = projectServer.loadProject(pr.authKey, [ cwd, *args ])
          rescue
            warning('pb_load_failed', "Loading of project failed: #{$!}")
            return false
          end
    
          # Disconnect the IO from the ProjectServer and close the DRb connection.
          begin
            projectServer.disconnect(pr.authKey)
          rescue
            warning('pb_cannot_disconnect_io', "Can't disconnect IO: #{$!}")
            return false
          end
    
          res
        end
    
        def removeProject(indexOrId)
          @projects.synchronize do
            # Find all projects with the IDs in indexOrId and mark them as
            # :obsolete.
            if /^[0-9]$/.match(indexOrId)
              index = indexOrId.to_i - 1
              if index >= 0 && index < @projects.length
                # If we have marked the project as obsolete, we return false to
                # indicate the double remove.
                return false if p.state == :obsolete
                @projects[index].state = :obsolete
                return true
              end
            else
              @projects.each do |p|
                if indexOrId == p.id
                  # If we have marked the project as obsolete, we return false to
                  # indicate the double remove.
                  return false if p.state == :obsolete
                  p.state = :obsolete
                  return true
                end
              end
            end
          end
          false
        end
    
        # Return the ProjectServer URI and authKey for the project with project ID
        # _projectId_.
        def getProject(projectId)
          # Find the project with the ID args[0].
          project = nil
          @projects.synchronize do
            @projects.each do |p|
              project = p if p.id == projectId && p.state == :ready
            end
          end
    
          if project.nil?
            debug('', "No project with ID #{projectId} found")
            return [ nil, nil ]
          end
          [ project.uri, project.authKey ]
        end
    
        # Reload all projects that have modified files and are not already being
        # reloaded.
        def update
          @projects.synchronize do
            @projects.each do |project|
              if project.modified && !project.reloading
                project.reloading = true
                @projectsToLoad.push(project.files)
              end
            end
          end
        end
    
        # Return a list of IDs of projects that are in state :ready.
        def getProjectList
          list = []
          @projects.synchronize do
            @projects.each do |project|
              list << project.id if project.state == :ready
            end
          end
          list
        end
    
        def report(projectId, reportId)
          uri, key = getProject(projectId)
        end
    
        # This is a callback from the ProjectServer process. It's used to update
        # the current state of the ProjectServer in the ProjectRecord list.
        # _projectKey_ is the authentication key for that project. It is used to
        # idenfity the entry in the ProjectRecord list to be updated.
        def updateState(projectKey, filesOrId, state, modified)
          result = false
          if filesOrId.is_a?(Array)
            files = filesOrId
            # Use the name of the master files for now.
            id = files[1]
          elsif filesOrId.is_a?(String)
            id = filesOrId
            files = nil
          else
            id = files = nil
          end
    
          @projects.synchronize do
            @projects.each do |project|
              # Don't accept updates for already obsolete entries.
              next if project.state == :obsolete
    
              debug('', "Updating state for #{id} to #{state}")
              # Only update the record that has the matching key
              if project.authKey == projectKey
                project.id = id if id
                # An Array of [ workingDir, tjpFile, ... other tji files ]
                project.files = files if files
    
                # If the state is being changed from something to :ready, this is
                # now the current project for the project ID.
                if state == :ready && project.state != :ready
                  # Mark other project records with same project ID as obsolete
                  @projects.each do |p|
                    if p != project && p.id == id
                      p.state = :obsolete
                      debug('', "Marking entry with ID #{id} as obsolete")
                    end
                  end
                  project.readySince = TjTime.new
                end
    
                # Failed ProjectServers are terminated automatically. We can't
                # reach them any more.
                project.uri = nil if state == :failed
    
                project.state = state
                project.modified = modified
                result = true
                break
              end
            end
          end
    
          result
        end
    
        private
    
        def startHousekeeping
          begin
            cntr = 0
            loop do
              if @terminate
                # Give the caller a chance to properly terminate the connection.
                sleep 0.5
                break
              elsif !@projectsToLoad.empty?
                loadProject(@projectsToLoad.pop)
              else
                # Send termination command to all obsolute ProjectServer
                # objects.  To minimize the locking of @projects we collect the
                # obsolete items first.
                termList = []
                @projects.synchronize do
                  @projects.each do |p|
                    if p.state == :obsolete
                      termList << p
                    elsif p.state == :failed
                      # Start removal of entries that didn't parse.
                      p.state = :obsolete
                    end
                  end
                end
                # And then send them a termination command.
                termList.each { |p| p.terminateServer }
    
                # Check every 10 seconds that the ProjectServer processes are
                # still alive. If not, remove them from the list.
                if (cntr += 1) > 10
                  @projects.synchronize do
                    @projects.each do |p|
                      unless p.ping
                        termList << p unless termList.include?(p)
                      end
                    end
                  end
                  cntr = 0
                end
    
                # The housekeeping thread rarely needs to so something. Make
                # sure it's sleeping most of the time.
                sleep 1
    
                # Remove the obsolete records from the @projects list.
                @projects.synchronize do
                  @projects.delete_if { |p| termList.include?(p) }
                end
              end
            end
          rescue => exception
            # TjRuntimeError exceptions are simply passed through.
            if exception.is_a?(TjRuntimeError)
              raise TjRuntimeError, $!
            end
    
            fatal('pb_housekeeping_error',
                  "ProjectBroker housekeeping error: #{$!}")
          end
        end
    
        def loadProject(tagOrProject)
          if tagOrProject.is_a?(Array)
            tag = rand(9999999999999)
            project = tagOrProject
            # The 2nd element of the Array is the *.tjp file name.
            debug('', "Loading project #{tagOrProject[1]} with tag #{tag}")
          else
            tag = tagOrProject
            project = nil
            debug('', "Loading project for tag #{tag}")
          end
          pr = ProjectRecord.new(tag)
          ps = ProjectServer.new(@authKey, project, @logStdIO)
          # The ProjectServer can be reached via this DRb URI
          pr.uri = ps.uri
          # Method calls must be authenticated with this key
          pr.authKey = ps.authKey
    
          # Add the ProjectRecord to the @projects list
          @projects.synchronize do
            @projects << pr
          end
        end
    
      end
    
      # This class is the DRb interface for ProjectBroker. We only want to expose
      # these methods for remote access.
      class ProjectBrokerIface
    
        include MessageHandler
    
        def initialize(broker)
          @broker = broker
        end
    
        # Check the authentication key and the client/server version match.
        # The following return values can be generated:
        # 0 : authKey does not match
        # 1 : client and server versions match
        # -1 : client and server versions don't match
        def apiVersion(authKey, version)
          return 0 unless @broker.checkKey(authKey, 'apiVersion')
    
          version == 1 ? 1 : -1
        end
    
        # This function catches all unhandled exceptions in the passed block.
        def trap
          begin
            yield
          rescue => e
            # TjRuntimeError exceptions are simply passed through.
            if e.is_a?(TjRuntimeError)
              raise TjRuntimeError, $!
            end
    
            # Any exception here is a fata error. We try hard to terminate the DRb
            # thread and then exit the program.
            begin
              fatal('pb_crash_trap', "#{e}\n#{e.backtrace.join("\n")}\n\n" +
                    "#{'*' * 79}\nYou have triggered a bug in " +
                    "#{AppConfig.softwareName} version #{AppConfig.version}!\n" +
                    "Please see the user manual on how to get this bug fixed!\n" +
                    "#{'*' * 79}\n")
            rescue RuntimeError
              @broker.stop
            end
          end
        end
    
        def command(authKey, cmd, args)
          return false unless @broker.checkKey(authKey, cmd)
    
          trap do
            case cmd
            when :status
              @broker.status
            when :stop
              @broker.stop
            when :addProject
              # To pass the DRbObject as separate arguments we need to convert it
              # into a real Array again.
              @broker.addProject(*Array.new(args))
            when :removeProject
              @broker.removeProject(args)
            when :getProject
              @broker.getProject(args)
            when :update
              @broker.update
            else
              fatal('unknown_command', 'Unknown command #{cmd} called')
            end
          end
        end
    
        def getProjectList(authKey)
          return false unless @broker.checkKey(authKey, 'getProjectList')
    
          trap { @broker.getProjectList }
        end
    
        def getProject(authKey, id)
          return false unless @broker.checkKey(authKey, 'getProject')
    
          debug('', "PID: #{id} Class: #{id.class}")
          trap { @broker.getProject(id) }
        end
    
        def updateState(authKey, projectKey, id, status, modified)
          return false unless @broker.checkKey(authKey, 'updateState')
    
          trap { @broker.updateState(projectKey, id, status, modified) }
        end
    
      end
    
      # The ProjectRecord objects are used to manage the loaded projects. There is
      # one entry for each project in the @projects list.
      class ProjectRecord < Monitor
    
        include MessageHandler
    
        attr_accessor :authKey, :uri, :files, :id, :state, :readySince, :modified,
                      :reloading
        attr_reader :tag
    
        def initialize(tag)
          # Before we know the project ID we use this tag to uniquely identify the
          # project.
          @tag = tag
          # Array of [ workingDir, tjp file, ... tji files ]
          @files = nil
          # The authentication key for the ProjectServer process.
          @authKey = nil
          # The DRb URI where the ProjectServer process is listening.
          @uri = nil
          # The ID of the project.
          @id = nil
          # The state of the project. :new, :loading, :ready, :failed
          # and :obsolete are supported.
          @state = :new
          # A time stamp when the project became ready for service.
          @readySince = nil
          # True if any of the input files have been modified after the load.
          @modified = false
          # True if the reload has already been triggered.
          @reloading = false
    
          @projectServer = nil
        end
    
        def ping
          return true unless @uri
    
          debug('', "Sending ping to ProjectServer #{@uri}")
          begin
            @projectServer = DRbObject.new(nil, @uri) unless @projectServer
            @projectServer.ping(@authKey)
          rescue
            warning('ping_failed', "Ping failed: #{$!}")
            return false
          end
          true
        end
    
        # Call this function to terminate the ProjectServer.
        def terminateServer
          return unless @uri
    
          begin
            debug('', "Sending termination request to ProjectServer #{@uri}")
            @projectServer = DRbObject.new(nil, @uri) unless @projectServer
            @projectServer.terminate(@authKey)
          rescue
            error('proj_serv_term_failed',
                  "Termination of ProjectServer failed: #{$!}")
          end
          @uri = nil
        end
    
        # This is used to generate the status table.
        def to_s(format, index)
          sprintf(format, index, @id, @state, @modified ? '*' : ' ',
                  @readySince ? @readySince.to_s('%Y-%m-%d %H:%M:%S') : '')
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/Attributes.rb0000644000175000017500000003226112614413013021113 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = Attributes.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/Allocation'
    require 'taskjuggler/AttributeBase'
    require 'taskjuggler/Charge'
    require 'taskjuggler/ChargeSet'
    require 'taskjuggler/Limits'
    require 'taskjuggler/LogicalOperation'
    require 'taskjuggler/ShiftAssignments'
    require 'taskjuggler/WorkingHours'
    
    class TaskJuggler
    
      class AccountAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def AccountAttribute::tjpId
          'account'
        end
    
        def to_s(query = nil)
          (v = get) ? v.id : ''
        end
    
        def to_tjp
          (v = get)? v.id : ''
        end
    
      end
    
      class AccountCreditListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
    
          set(Array.new)
        end
    
        def AccountCreditListAttribute::tjpId
          'credits'
        end
    
      end
    
      class AllocationAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
    
          set(Array.new)
        end
    
        def AllocationAttribute::tjpId
          'allocation'
        end
    
        def to_tjp
          out = []
          get.each do |allocation|
            out.push("allocate #{allocation.to_tjp}\n")
            # TODO: incomplete
          end
          out
        end
    
        def to_s(query = nil)
          out = ''
          first = true
          get.each do |allocation|
            if first
              first = false
            else
              out << "\n"
            end
            out << '[ '
            firstR = true
            allocation.candidates.each do |resource|
              if firstR
                firstR = false
              else
                out << ', '
              end
              out << resource.fullId
            end
            modes = %w(order lowprob lowload hiload random)
            out << " ] select by #{modes[allocation.selectionMode]} "
            out << 'mandatory ' if allocation.mandatory
            out << 'persistent ' if allocation.persistent
          end
          out
        end
    
      end
    
      class BookingListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def BookingListAttribute::tjpId
          'bookinglist'
        end
    
        def to_s(query = nil)
          get.collect{ |x| x.to_s }.join(', ')
        end
    
        def to_tjp
          raise "Don't call this method. This needs to be a special case."
        end
    
      end
    
      class BooleanAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def BooleanAttribute::tjpId
          'boolean'
        end
    
        def to_s(query = nil)
          get ? 'true' : 'false'
        end
    
        def to_tjp
          @type.id + ' ' + (get ? 'yes' : 'no')
        end
    
      end
    
      class ChargeListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def ChargeListAttribute::tjpId
          'charge'
        end
    
        def to_s(query = nil)
          get.join(', ')
        end
    
      end
    
      # A ChargeSetListAttribute encapsulates a list of ChargeSet objects as
      # PropertyTreeNode attributes.
      class ChargeSetListAttribute < ListAttributeBase
    
        def initialize(property, type, container)
          super
        end
    
        def ChargeSetListAttribute::tjpId
          'chargeset'
        end
    
        def to_s(query = nil)
          out = []
          get.each { |i| out << i.to_s }
          out.join(", ")
        end
    
        def to_tjp
          out = []
          get.each { |i| out << i.to_s }
          @type.id + " " + out.join(', ')
        end
    
      end
    
      class ColumnListAttribute < ListAttributeBase
    
        def initialize(property, type, container)
          super
        end
    
        def ColumnListAttribute::tjpId
          'columns'
        end
    
        def to_s(query = nil)
          "TODO"
        end
      end
    
      class DateAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def to_s(query = nil)
          if (v = get)
            v.to_s(query ? query.timeFormat : '%Y-%m-%d')
          else
            'Error'
          end
        end
    
        def DateAttribute::tjpId
          'date'
        end
      end
    
      class DefinitionListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
      end
    
      class DependencyListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def DependencyListAttribute::tjpId
          'dependencylist'
        end
    
        def to_s(query = nil)
          out = []
          get.each { |t| out << t.task.fullId if t.task }
          out.join(', ')
        end
    
        def to_tjp
          out = []
          get.each { |taskDep| out << taskDep.task.fullId }
          @type.id + " " + out.join(', ')
        end
    
      end
    
      class DurationAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def DurationAttribute::tjpId
          'duration'
        end
    
        def to_tjp
          @type.id + ' ' + get.to_s + 'h'
        end
    
        def to_s(query = nil)
          query ? query.scaleDuration(query.project.slotsToDays(get)) :
                  get.to_s
        end
    
      end
    
      class FixnumAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def FixnumAttribute::tjpId
          'integer'
        end
      end
    
      class FlagListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def FlagListAttribute::tjpId
          'flaglist'
        end
    
        def to_s(query = nil)
          get.join(', ')
        end
    
        def to_tjp
          "flags #{get.join(', ')}"
        end
    
      end
    
      class FloatAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def FloatAttribute::tjpId
          'number'
        end
    
        def to_tjp
          id + ' ' + get.to_s
        end
    
      end
    
      class FormatListAttribute < ListAttributeBase
    
        def initialize(property, type, container)
          super
        end
    
        def to_s(query = nil)
          get.join(', ')
        end
    
      end
    
      class JournalSortListAttribute < ListAttributeBase
    
        def initialize(property, type, container)
          super
        end
    
        def JournalSortListAttribute::tjpId
          'journalsorting'
        end
    
      end
    
      class TimeIntervalListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def TimeIntervalListAttribute::tjpId
          'intervallist'
        end
    
        def to_s(query = nil)
          out = []
          get.each { |i| out << i.to_s }
          out.join(", ")
        end
    
        def to_tjp
          out = []
          get.each { |i| out << i.to_s }
          @type.id + " " + out.join(', ')
        end
    
      end
    
      class LeaveAllowanceListAttribute < ListAttributeBase
    
        def initialize(property, type, container)
          super
        end
    
      end
    
      class LeaveListAttribute < ListAttributeBase
    
        def initialize(property, type, container)
          super
        end
    
        def LeaveListAttribute::tjpId
          'leave'
        end
    
        def to_tjp
          "leaves #{get.join(",\n")}"
        end
    
      end
    
      class LimitsAttribute < AttributeBase
    
        def initialize(property, type, container)
          super
          v = get
          v.setProject(property.project) if v
        end
    
        def LimitsAttribute::tjpId
          'limits'
        end
    
        def to_tjp
          'This code is still missing!'
        end
    
      end
    
      class LogicalExpressionAttribute < AttributeBase
    
        def initialize(property, type, container)
          super
        end
    
        def LogicalExpressionAttribute::tjpId
          'logicalexpressions'
        end
    
      end
    
      class LogicalExpressionListAttribute < ListAttributeBase
    
        def initialize(property, type, container)
          super
        end
    
        def LogicalExpressionListAttribute::tjpId
          'logicalexpressions'
        end
    
      end
    
      class NodeListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
      end
    
      class PropertyAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def PropertyAttribute::tjpId
          'property'
        end
      end
    
      class RealFormatAttribute < AttributeBase
    
        def initialize(property, type, container)
          super
        end
    
      end
    
      class ReferenceAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def ReferenceAttribute::tjpId
          'reference'
        end
    
        def to_s(query)
          url || ''
        end
    
        def to_rti(query)
          return nil unless get
    
          rText = RichText.new("[#{url} #{label}]")
          rText.generateIntermediateFormat
        end
    
        def to_tjp
          "#{@type.id} \"#{url}\"#{label ? " { label \"#{label}\" }" : ''}"
        end
    
        def url
          (v = get) ? v[0] : nil
        end
    
        def label
          (v = get) ? (v[1] ? v[1][0] : v[0]) : nil
        end
    
      end
    
      class ResourceListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def ResourceListAttribute::tjpId
          'resourcelist'
        end
    
        def to_s(query = nil)
          out = []
          get.each { |r| out << r.fullId }
          out.join(", ")
        end
    
        def to_rti(query = nil)
          out = []
          if query
            get.each do |r|
              if query.listItem
                rti = RichText.new(query.listItem, RTFHandlers.create(r.project)).
                  generateIntermediateFormat
                q = query.dup
                q.property = r
                rti.setQuery(q)
                out << "#{rti.to_s}"
              else
                out << "#{r.name}"
              end
            end
            query.assignList(out)
          else
            get.each { |r| out << r.name }
            rText = RichText.new(out.join(', '))
            rText.generateIntermediateFormat
          end
        end
    
        def to_tjp
          out = []
          get.each { |r| out << r.fullId }
          @type.id + " " + out.join(', ')
        end
    
      end
    
      class RichTextAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def inputText
          (v = get) ? v.richText.inputText : ''
        end
    
        def RichTextAttribute::tjpId
          'richtext'
        end
    
        def to_s(query = nil)
          (v = get) ? v.to_s : ''
        end
    
        def to_tjp
          "#{@type.id} #{quotedString(get.richText.inputText)}"
        end
    
      end
    
      class ScenarioListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def ScenarioListAttribute::tjpId
          'scenarios'
        end
    
        def to_s(query = nil)
          get.join(', ')
        end
    
      end
    
    
      class ShiftAssignmentsAttribute < AttributeBase
    
        def initialize(property, type, container)
          super
          v = get
          v.project = property.project if v
        end
    
        def ShiftAssignmentsAttribute::tjpId
          'shifts'
        end
    
        def to_tjp
          v = get
          first = true
          str = 'shifts '
          v.assignments.each do |sa|
            if first
              first = false
            else
              str += ",\n"
            end
    
            str += "#{sa.shiftScenario.property.fullId} #{sa.interval}"
          end
    
          str
        end
    
      end
    
      class SortListAttribute < ListAttributeBase
    
        def initialize(property, type, container)
          super
        end
    
        def SortListAttribute::tjpId
          'sorting'
        end
    
      end
    
      class StringAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def StringAttribute::tjpId
          'text'
        end
    
        def to_tjp
          "#{@type.id} #{quotedString(get)}"
        end
    
      end
    
      class SymbolAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def SymbolAttribute::tjpId
          'symbol'
        end
      end
    
      class SymbolListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def SymbolListAttribute::tjpId
          'symbollist'
        end
      end
    
      class TaskDepListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def TaskDepListAttribute::tjpId
          'taskdeplist'
        end
    
        def to_s(query = nil)
          out = []
          get.each { |t, onEnd| out << t.fullId }
          out.join(", ")
        end
    
        def to_tjp
          out = []
          get.each { |t, onEnd| out << t.fullId }
          @type.id + " " + out.join(', ')
        end
      end
    
      class TaskListAttribute < ListAttributeBase
        def initialize(property, type, container)
          super
        end
    
        def TaskListAttribute::tjpId
          'tasklist'
        end
    
        def to_s(query = nil)
          out = []
          get.each { |t| out << t.fullId }
          out.join(", ")
        end
    
        def to_tjp
          out = []
          get.each { |t| out << t.fullId }
          @type.id + " " + out.join(', ')
        end
      end
    
      class WorkingHoursAttribute < AttributeBase
        def initialize(property, type, container)
          super
        end
    
        def WorkingHoursAttribute::tjpId
          'workinghours'
        end
    
        def to_tjp
          dayNames = %w( sun mon tue wed thu fri sat )
          str = ''
          7.times do |day|
            str += "workinghours #{dayNames[day]} "
            whs = get.getWorkingHours(day)
            if whs.empty?
              str += "off"
              str += "\n" if day < 6
              next
            end
            first = true
            whs.each do |iv|
              if first
                first = false
              else
                str += ', '
              end
              str += "#{iv[0] / 3600}:#{iv[0] % 3600 == 0 ?
                                        '00' : (iv[0] % 3600) / 60} - " +
                     "#{iv[1] / 3600}:#{iv[1] % 3600 == 0 ?
                                        '00' : (iv[1] % 3600) / 60}"
            end
            str += "\n" if day < 6
          end
          str
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/ProjectFileParser.rb0000644000175000017500000004461012614413013022351 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = ProjectFileParser.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/TextParser'
    require 'taskjuggler/ProjectFileScanner'
    require 'taskjuggler/TjpSyntaxRules'
    require 'taskjuggler/RichText'
    require 'taskjuggler/RichText/RTFHandlers'
    
    class TaskJuggler
    
      # This class specializes the TextParser class for use with TaskJuggler project
      # files (TJP Files). The primary purpose is to provide functionality that make
      # it more comfortable to define the TaskJuggler syntax in a form that is human
      # creatable but also powerful enough to define the data structures the parser
      # needs to understand the syntax.
      #
      # By adding some additional information to the syntax rules, we can also
      # generate the complete reference manual from this rule set.
      class ProjectFileParser < TextParser
    
        include TjpSyntaxRules
    
        # Create the parser object.
        def initialize
          super
    
          # Define the token types that the ProjectFileScanner may return for
          # variable elements.
          @variables = [ :INTEGER, :FLOAT, :DATE, :TIME, :STRING, :LITERAL,
                         :ID, :ID_WITH_COLON, :ABSOLUTE_ID, :MACRO ]
    
          initRules
          updateParserTables
    
          @project = nil
        end
    
        # Call this function with the master file to start processing a TJP file or
        # a set of TJP files.
        def open(file, master, fileNameIsBuffer = false)
          @scanner = ProjectFileScanner.new(file)
          # We need the ProjectFileScanner object for error reporting.
          if master && !fileNameIsBuffer && file != '.' && file[-4, 4] != '.tjp'
            error('illegal_extension', "Project file name must end with " +
                  '\'.tjp\' extension')
          end
          @scanner.open(fileNameIsBuffer)
    
          @property = nil
          @scenarioIdx = 0
          initFileStack
          # Stack for property IDs. Needed to handle nested 'supplement'
          # statements.
          @idStack = []
        end
    
        # Call this function to cleanup the parser structures after the file
        # processing has been completed.
        def close
          @scanner.close
        end
    
        # This function will deliver the next token from the scanner. A token is a
        # two element Array that contains the ID or type of the token as well as the
        # text string of the token.
        def nextToken
          @scanner.nextToken
        end
    
        # This function can be used to return tokens. Returned tokens will be pushed
        # on a LIFO stack. To preserve the order of the original tokens the last
        # token must be returned first. This mechanism is used to implement
        # look-ahead functionality.
        def returnToken(token)
          @scanner.returnToken(token)
        end
    
        # A set of standard marcros is defined in all files as soon as the project
        # header has been read. Calling this functions gets the values from @project
        # and inserts the Macro objects into the ProjectFileScanner.
        def setGlobalMacros
          @scanner.addMacro(Macro.new('projectstart', @project['start'].to_s,
                                      @scanner.sourceFileInfo))
          @scanner.addMacro(Macro.new('projectend', @project['end'].to_s,
                                      @scanner.sourceFileInfo))
          @scanner.addMacro(Macro.new('now', @project['now'].to_s,
                                      @scanner.sourceFileInfo))
          @scanner.addMacro(Macro.new('today', @project['now'].
                                       to_s(@project['timeFormat']),
                                      @scanner.sourceFileInfo))
        end
    
        def parseReportAttributes(report, attributes)
          open(attributes, false, true)
          @property = report
          @project = report.project
          parse(:reportAttributes)
        end
    
      private
    
        # Utility function that convers English weekday names into their index
        # number and does some error checking. It returns 0 for 'sun', 1 for 'mon'
        # and so on.
        def weekDay(name)
          names = %w( sun mon tue wed thu fri sat )
          if (day = names.index(@val[0])).nil?
            error('weekday', "Weekday name expected (#{names.join(', ')})")
          end
          day
        end
    
        # Make sure that certain attributes are not used after sub properties have
        # been added to a property.
        def checkContainer(attribute)
          if @property.container?
            error('container_attribute',
                  "The attribute #{attribute} may not be used for this property " +
                  'after sub properties have been added.', @sourceFileInfo[0],
                  @property)
          end
        end
    
        # Convenience function to check that an TimeInterval fits completely
        # within the project time frame.
        def checkInterval(iv)
          # Make sure the interval is within the project time frame.
          if iv.start < @project['start'] || iv.start >= @project['end']
            error('interval_start_in_range',
                  "Start date #{iv.start} must be within the project time frame " +
                  "(#{@project['start']} - #{@project['end']})")
          end
          if iv.end <= @project['start'] || iv.end > @project['end']
            error('interval_end_in_range',
                  "End date #{iv.end} must be within the project time frame " +
                  "(#{@project['start']} - #{@project['end']})")
          end
        end
    
        # Convenience function to check the integrity of a booking statement.
        def checkBooking(task, resource)
          unless task.leaf?
            error('booking_no_leaf', "#{task.fullId} is not a leaf task",
                  @sourceFileInfo[0], task)
          end
          if task['milestone', @scenarioIdx]
            error('booking_milestone', "You cannot add bookings to a milestone",
                  @sourceFileInfo[0], task)
          end
          unless resource.leaf?
            error('booking_group', "You cannot book a group resource",
                  @sourceFileInfo[0], task)
          end
        end
    
        # The TaskJuggler syntax can be extended by the user when the properties are
        # extended with user-defined attributes. These attribute definitions
        # introduce keywords that have to be processed like the build-in keywords.
        # The parser therefor needs to adapt on the fly to the new syntax. By
        # calling this function, a TaskJuggler property can be extended with a new
        # attribute. @propertySet determines what property should be extended.
        # _type_ is the attribute type, _default_ is the default value.
        def extendPropertySetDefinition(type, default)
          if @propertySet.knownAttribute?(@val[1])
            error('extend_redefinition',
                  "The extended attribute #{@val[1]} has already been defined.")
          end
    
          # Determine the values for scenarioSpecific and inheritable.
          inherit = false
          scenarioSpecific = false
          unless @val[3].nil?
            @val[3].each do |option|
              case option
              when 'inherit'
                inherit = true
              when 'scenariospecific'
                scenarioSpecific = true
              end
            end
          end
          # Register the new Attribute type with the Property set it should belong
          # to.
          @propertySet.addAttributeType(AttributeDefinition.new(
            @val[1], @val[2], type, inherit, false, scenarioSpecific, default,
            true))
    
          # Add the new user-defined attribute as reportable attribute to the parser
          # rule.
          oldCurrentRule = @cr
          @cr = @rules[:reportableAttributes]
          unless @cr.include?(@val[1])
            singlePattern('_' + @val[1])
            descr(@val[2])
          end
          @cr = oldCurrentRule
    
          scenarioSpecific
        end
    
        # This function is primarily a wrapper around the RichText constructor. It
        # catches all RichTextScanner processing problems and converts the exception
        # data into a MessageHandler message that points to the correct location.
        # This is necessary, because the RichText parser knows nothing about the
        # actual input file. So we have to map the error location in the RichText
        # input stream back to the position in the project file. _sfi_ is the
        # SourceFileInfo of the input string. To limit the supported set of
        # variable tokens, a subset can be provided by _tokenSet_.
        def newRichText(text, sfi, tokenSet = nil)
          rText = RichText.new(text, RTFHandlers.create(@project, sfi))
          # The RichText is processed by a separate parser. Messages will not have
          # the proper source file info unless we baseline them with the original
          # source file info.
          mh = MessageHandlerInstance.instance
          mh.baselineSFI = sfi
          rti = rText.generateIntermediateFormat( [ 0, 0, 0 ], tokenSet)
          # Reset the baseline again.
          mh.baselineSFI = nil
          rti.sectionNumbers = false if rti
          rti
        end
    
        # This method is a convenience wrapper around Report.new. It checks if
        # the report name already exists. It also triggers the attribute
        # inheritance. +name+ is the name of the report, +type+ is the report
        # type. +sourceFileInfo+ is a SourceFileInfo of the report definition. The
        # method returns the newly created Report.
        def newReport(id, name, type, sourceFileInfo)
          # If there is no parent property and the report prefix is not empty, the
          # reportprefix defines the parent property.
          if @property.nil? && !@reportprefix.empty?
            @property = @project.report(@reportprefix)
          end
    
          # Report IDs must be unique. If an ID was provided, check if it exists
          # already.
          if id
            # If we have a scope property, we need to prepend the ID of the scope
            # property to the provided ID.
            id = (@property ? @property.fullId + '.' : '') + @val[1]
    
            if @project.report(id)
              error('report_exists', "report #{id} has already been defined.",
                    sourceFileInfo, @property)
            end
          end
    
          @reportCounter += 1
          if name != '.' && name != ''
            if @project.reportByName(name)
              error('report_redefinition',
                    "A report with the name #{name} has already been defined.")
            end
          end
          @property = Report.new(@project, id || "report#{@reportCounter}",
                                 name, @property)
          @property.typeSpec = type
          @property.sourceFileInfo = sourceFileInfo
          @property.inheritAttributes
    
          if block_given?
            # The default attribute values for this report type have to be set in
            # 'inherited' mode since they are not user provided.
            AttributeBase.setMode(1)
            yield
            AttributeBase.setMode(0)
          end
        end
    
        # If the @limitResources list is not empty, we have to create a Limits
        # object for each Resource. Otherwise, one Limits object is enough.
        def setLimit(name, value, interval)
          if @limitResources.empty?
            @limits.setLimit(name, value, interval)
          else
            @limitResources.each do |resource|
              @limits.setLimit(name, value, interval, resource)
            end
          end
        end
    
        # Set the _attribute_ to _value_ and reset all other duration attributes.
        def setDurationAttribute(attribute, value = true)
          checkContainer(attribute)
          { 'milestone' => false, 'duration' => 0,
            'length' => 0, 'effort' => 0 }.each do |attr, val|
            if attribute == attr
              @property[attr, @scenarioIdx] = value
            else
              if @property.getAttribute(attr, @scenarioIdx).provided
                error('multiple_durations',
                      "This duration criteria is overwriting a previously " +
                      "provided criteria (duration, effort, length or milestone).")
              end
              @property[attr, @scenarioIdx] = val
            end
          end
        end
    
        # The following functions are mostly conveniance functions to simplify the
        # syntax tree definition. The *Rule functions may only be used in _rule
        # functions. And only one function call per _rule function is allowed.
    
        # This function creates a set of rules to describe a list of keywords.
        # _name_ is the name of the top-level rule and _items_ can be a Hash or
        # Array. The array just contains the allowed keywords, the Hash contains
        # keyword/description pairs. The description is used to describe
        # the keyword in the manual. The syntax supports two special cases. A '*'
        # means all items in the list and '-' means the list is empty.
        def allOrNothingListRule(name, items)
          newRule(name) {
            # A '*' means all possible items should be in the list.
            pattern(%w( _* ), lambda {
              KeywordArray.new([ '*' ])
            })
            descr('A shortcut for all items')
            # A '-' means the list should be empty.
            pattern([ '_-' ], lambda {
              KeywordArray.new
            })
            descr('No items')
            # Or the list consists of one or more comma separated keywords.
            pattern([ "!#{name}_AoN_ruleItems" ], lambda {
              KeywordArray.new(@val[0])
            })
          }
          # Create the rule for the comma separated list.
          newRule("#{name}_AoN_ruleItems") {
            listRule("more#{name}_AoN_ruleItems", "!#{name}_AoN_ruleItem")
          }
          # Create the rule for the keywords with their description.
          newRule("#{name}_AoN_ruleItem") {
            if items.is_a?(Array)
              items.each { |keyword| singlePattern('_' + keyword) }
            else
              items.each do |keyword, description|
                singlePattern('_' + keyword)
                descr(description) if description
              end
            end
          }
        end
    
        def listRule(name, listItem)
          pattern([ "#{listItem}", "!#{name}" ], lambda {
            if @val[1] && @val[1].include?(@val[0])
              error('duplicate_in_list',
                    "Duplicate items in list.")
            end
            [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
          })
          newRule(name) {
            commaListRule(listItem)
          }
        end
    
        def commaListRule(listItem)
          optional
          repeatable
          pattern([ '_,', "#{listItem}" ], lambda {
            @val[1]
          })
        end
    
        # Create pattern that turns the rule into the definition for optional
        # attributes. _attributes_ is the rule that lists these attributes.
        def optionsRule(attributes)
          optional
          pattern([ '_{', "!#{attributes}", '_}' ], lambda {
            @val[1]
          })
        end
    
        # Create a pattern with just a single _item_. The pattern returns the value
        # of that item.
        def singlePattern(item)
          pattern([ item ], lambda {
            @val[0]
          })
        end
    
        # Add documentation for the current pattern of the currently processed rule.
        def doc(keyword, text)
          @cr.setDoc(keyword, text)
        end
    
        # Add documentation for patterns that only consists of a single terminal
        # token.
        def descr(text)
          if @cr.patterns[-1].length != 1 ||
             (@cr.patterns[-1][0][0] != :literal &&
              @cr.patterns[-1][0][0] != :variable)
            raise 'descr() may only be used for patterns with terminal tokens.'
          end
          arg(0, nil, text)
        end
    
        # Add documentation for the arguments with index _idx_ of the current
        # pattern of the currently processed rule. _name_ is that should be used for
        # this variable. _text_ is the documentation text.
        def arg(idx, name, text)
          @cr.setArg(idx, TextParser::TokenDoc.new(name, text))
        end
    
        # Restrict the syntax documentation of the previously defined pattern to
        # the first +idx+ tokens.
        def lastSyntaxToken(idx)
          @cr.setLastSyntaxToken(idx)
        end
    
        # Specify the support level for the current pattern.
        def level(level)
          @cr.setSupportLevel(level)
        end
    
        # Add a reference to another pattern. This information is only used to
        # generate the documentation for the patterns of this rule.
        def also(seeAlso)
          seeAlso = [ seeAlso ] unless seeAlso.is_a?(Array)
          @cr.setSeeAlso(seeAlso)
        end
    
        # Add a TJP file or parts of it as an example. The TJP _file_ must be in the
        # directory test/TestSuite/Syntax/Correct. _tag_ can be used to identify
        # that only a part of the file should be included.
        def example(file, tag = nil)
          @cr.setExample(file, tag)
        end
        # Determine the title of the column with the ID _colId_. The title may be
        # from the static set or be from a user defined attribute.
        def columnTitle(colId)
          if @property.typeSpec == :tracereport
            "<-id->:<-scenario->.#{colId}"
          else
            TableReport.defaultColumnTitle(colId) ||
              @project.attributeName(colId)
          end
        end
    
    
        # To manage certain variables that have file scope throughout a hierachie
        # of nested include files, we use a @fileStack to track those variables.
        # The values primarily live in their class instance variables. But upon
        # return from an included file, we need to restore the old values. This
        # function creates or resets the stack.
        def initFileStack
          @fileStackVariables = %w( taskprefix reportprefix
                                    resourceprefix accountprefix )
          stackEntry = {}
          @fileStackVariables.each do |var|
            stackEntry[var] = ''
            instance_variable_set('@' + var, '')
          end
          @fileStack = [ stackEntry ]
        end
    
        # Push a new set of variables onto the @fileStack.
        def pushFileStack
          stackEntry = {}
          @fileStackVariables.each do |var|
            stackEntry[var] = instance_variable_get('@' + var)
          end
          @fileStack << stackEntry
        end
    
        # Pop the last stack entry from the @fileStack and restore the class
        # variables according to the now top-entry.
        def popFileStack
          stackEntry = @fileStack.pop
          @fileStackVariables.each do |var|
            instance_variable_set('@' + var, stackEntry[var])
          end
          # Include files can only occur at global level or in the project header.
          # In both cases, the @property was nil on including and must be reset to
          # nil again after the include file.
          @property = nil
        end
    
        # This method most be used instead of the += operator for all list
        # attributes. += will always return an Array object. This will cause
        # trouble with the list attributes that are not plain Arrays.
        def appendScListAttribute(attrId, list)
          list.each do |v|
            @property[attrId, @scenarioIdx] << v
          end
          # The << operator does not set the 'provided' flag. Just do a self
          # assignment to trigget the flag to get set.
          begin
            @property[attrId, @scenarioIdx] = @property[attrId, @scenarioIdx]
          rescue AttributeOverwrite
            # Overwrites are ok here.
          end
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/FileList.rb0000644000175000017500000000316312614413013020477 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = FileList.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    class TaskJuggler
    
      # The FileRecord stores the name of a file and the modification time.
      class FileRecord
    
        def initialize(fileName)
          @name = fileName.dup.untaint
          @mtime = File.mtime(@name)
        end
    
        def modified?
          File.mtime(@name) > @mtime
        end
    
      end
    
      # The FileList class stores a list of file names. Each file name is unique
      # and more information about the file is contained in FileRecord entries.
      class FileList
    
        # Create a new, empty FileList.
        def initialize
          @files = {}
        end
    
        # Add the file with _fileName_ to the list. If it's already in the list,
        # it will not be added again.
        def <<(fileName)
          return if fileName == '.' || @files.include?(fileName)
    
          @files[fileName] = FileRecord.new(fileName)
        end
    
        # Return the name of the master file or nil of the master file was stdin.
        def masterFile
          @files.each_key do |file|
            return file if file[-4, 4] == '.tjp'
          end
    
          nil
        end
    
        # Return true if any of the files in the list have been modified after
        # they were added to the list.
        def modified?
          @files.each_value do |f|
            return true if f.modified?
          end
          false
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/TjpSyntaxRules.rb0000644000175000017500000075724012614413013021757 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = TjpSyntaxRules.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    class TaskJuggler
    
    # This module contains the rule definition for the TJP syntax. Every rule is
    # put in a function who's name must start with rule_. The functions are not
    # necessary but make the file more readable and receptable to syntax folding.
    module TjpSyntaxRules
    
      def rule_absoluteTaskId
        pattern(%w( !taskIdUnverifd ), lambda {
          id = (@taskprefix.empty? ? '' : @taskprefix + '.') + @val[0]
          if (task = @project.task(id)).nil?
            error('unknown_abs_task', "Unknown task #{id}", @sourceFileInfo[0])
          end
          task
        })
      end
    
      def rule_account
        pattern(%w( !accountHeader !accountBody ), lambda {
           @property = @property.parent
        })
        doc('account', <<'EOT'
    Declares an account. Accounts can be used to calculate costs of tasks or the
    whole project. Account declaration may be nested, but only leaf accounts may
    be used to track turnover. When the cost of a task is split over multiple
    accounts they all must have the same top-level group account. Top-level
    accounts can be used for profit/loss calculations. The sub-account structure
    of a top-level account should be organized accordingly.
    
    Accounts have a global name space. All IDs must be unique within the accounts of the project.
    EOT
           )
        example('Account', '1')
      end
    
      def rule_accountAttributes
        repeatable
        optional
        pattern(%w( !account))
        pattern(%w( !accountScenarioAttributes ))
        pattern(%w( !scenarioIdCol !accountScenarioAttributes ), lambda {
          @scenarioIdx = 0
        })
        # Other attributes will be added automatically.
      end
    
      def rule_accountBody
        optionsRule('accountAttributes')
      end
    
      def rule_accountCredit
        pattern(%w( !valDate $STRING !number ), lambda {
          AccountCredit.new(@val[0], @val[1], @val[2])
        })
        arg(1, 'description', 'Short description of the transaction')
        arg(2, 'amount', 'Amount to be booked.')
      end
    
      def rule_accountCredits
        listRule('moreAccountCredits', '!accountCredit')
      end
    
      def rule_accountHeader
        pattern(%w( _account !optionalID $STRING ), lambda {
          if @property.nil? && !@accountprefix.empty?
            @property = @project.accout(@accountprefix)
          end
          if @val[1] && @project.account(@val[1])
            error('account_exists', "Account #{@val[1]} has already been defined.",
                  @sourceFileInfo[1], @property)
          end
          @property = Account.new(@project, @val[1], @val[2], @property)
          @property.sourceFileInfo = @sourceFileInfo[0]
          @property.inheritAttributes
          @scenarioIdx = 0
        })
        arg(2, 'name', 'A name or short description of the account')
      end
    
      def rule_accountId
        pattern(%w( $ID ), lambda {
          id = @val[0]
          id = @accountprefix + '.' + id unless @accountprefix.empty?
          # In case we have a nested supplement, we need to prepend the parent ID.
          id = @property.fullId + '.' + id if @property && @property.is_a?(Account)
          if (account = @project.account(id)).nil?
            error('unknown_account', "Unknown account #{id}", @sourceFileInfo[0])
          end
          account
        })
      end
    
      def rule_accountReport
        pattern(%w( !accountReportHeader !reportBody ), lambda {
          @property = @property.parent
        })
        level(:beta)
        doc('accountreport', <<'EOT'
    The report lists accounts and their respective values in a table. The report
    can operate in two modes:
    
    # Balance mode: If a [[balance]] has been set, the report will include the
    defined cost and revenue accounts as well as all their sub accounts. To reduce
    the list of included accounts, you can use the [[hideaccount]],
    [[rollupaccount]] or [[accountroot]] attributes. The order of the task can
    be controlled with [[sortaccounts]]. If the first sorting criteria is tree
    sorting, the parent accounts will always be included to form the tree.
    Tree sorting is the default. You need to change it if you do not want certain
    parent accounts to be included in the report. Additionally, it will contain a line at the end that lists the balance (revenue - cost).
    
    # Normal mode: All reports are listed in the order and completeness as defined
    by the other report attributes. No balance line will be included.
    EOT
           )
        example('AccountReport')
      end
    
      def rule_accountReportHeader
        pattern(%w( _accountreport !optionalID !reportName ), lambda {
          newReport(@val[1], @val[2], :accountreport, @sourceFileInfo[0]) do
            unless @property.modified?('columns')
              # Set the default columns for this report.
              %w( bsi name monthly ).each do |col|
                @property.get('columns') <<
                TableColumnDefinition.new(col, columnTitle(col))
              end
            end
            # Show all accounts, sorted by tree, seqno-up.
            unless @property.modified?('hideAccount')
              @property.set('hideAccount',
                            LogicalExpression.new(LogicalOperation.new(0)))
            end
            unless @property.modified?('sortAccounts')
              @property.set('sortAccounts',
                            [ [ 'tree', true, -1 ],
                              [ 'seqno', true, -1 ] ])
            end
          end
        })
      end
    
      def rule_accountScenarioAttributes
        pattern(%w( _aggregate !aggregate ), lambda {
          @property.set('aggregate', @val[1])
        })
        doc('aggregate', <<'EOT'
    Specifies whether the account is used to track task or resource specific
    amounts. The default is to track tasks.
    EOT
           )
        example('AccountReport')
    
        pattern(%w( _credits !accountCredits ), lambda {
          @property['credits', @scenarioIdx] += @val[1]
        })
        doc('credits', <<'EOT'
    Book the specified amounts to the account at the specified date. The
    desciptions are just used for documentary purposes.
    EOT
           )
        example('Account', '1')
    
        pattern(%w( !flags ))
        doc('flags.account', <<'EOT'
    Attach a set of flags. The flags can be used in logical expressions to filter
    properties from the reports.
    EOT
           )
    
        # Other attributes will be added automatically.
      end
    
      def rule_aggregate
        pattern(%w( _resources ), lambda {
          :resources
        })
        descr('Aggregate resources')
    
        pattern(%w( _tasks ), lambda {
          :tasks
        })
        descr('Aggregate tasks')
      end
    
      def rule_alertLevel
        pattern(%w( $ID ), lambda {
          level = @project['alertLevels'].indexById(@val[0])
          unless level
            levels = @project['alertLevels'].map { |l| l.id }
            error('bad_alert', "Unknown alert level #{@val[0]}. Must be " +
                  "one of #{levels.join(', ')}", @sourceFileInfo[0])
          end
          level
        })
        arg(0, 'alert level', <<'EOT'
    By default supported values are ''''green'''', ''''yellow'''' and ''''red''''.
    The default value is ''''green''''. You can define your own levels with
    [[alertlevels]].
    EOT
           )
      end
    
      def rule_alertLevelDefinition
        pattern(%w( $ID $STRING !color ), lambda {
          [ @val[0], @val[1], @val[2] ]
        })
        arg(0, 'ID', "A unique ID for the alert level")
        arg(1, 'color name', 'A unique name of the alert level color')
      end
    
      def rule_alertLevelDefinitions
        listRule('moreAlertLevelDefinitions', '!alertLevelDefinition')
      end
    
      def rule_allocate
        pattern(%w( _allocate !allocations ), lambda {
          checkContainer('allocate')
          @property['allocate', @scenarioIdx] += @val[1]
        })
        doc('allocate', <<'EOT'
    Specify which resources should be allocated to the task. The
    attributes provide numerous ways to control which resource is used and when
    exactly it will be assigned to the task. Shifts and limits can be used to
    restrict the allocation to certain time intervals or to limit them to a
    certain maximum per time period. The purge statement can be used to remove
    inherited allocations or flags.
    
    For effort-based tasks the task duration is clipped to only extend from the
    begining of the first allocation to the end of the last allocation. This is
    done to optimize for an overall minimum project duration as dependent tasks
    can potentially use the unallocated, clipped slots.
    EOT
           )
        example('Allocate-1', '1')
      end
    
      def rule_allocation
        pattern(%w( !allocationHeader !allocationBody ), lambda {
          @val[0]
        })
      end
    
      def rule_allocationAttributes
        optional
        repeatable
    
        pattern(%w( _alternative !resourceId !moreAlternatives ), lambda {
          ([ @val[1] ] + (@val[2] ? @val[2] : [])).each do |candidate|
            @allocate.addCandidate(candidate)
          end
        })
        doc('alternative', <<'EOT'
    Specify which resources should be allocated to the task. The optional
    attributes provide numerous ways to control which resource is used and when
    exactly it will be assigned to the task. Shifts and limits can be used to
    restrict the allocation to certain time intervals or to limit them to a
    certain maximum per time period.
    EOT
           )
        example('Alternative', '1')
    
        pattern(%w( !limits ), lambda {
          limits = @property['limits', @scenarioIdx] = @val[0]
          @allocate.candidates.each do |resource|
             limits.limits.each do |l|
               l.resource = resource if resource.leaf?
             end
          end
        })
        level(:removed)
        doc('limits.allocate', '')
    
        pattern(%w( _select !allocationSelectionMode ), lambda {
          @allocate.setSelectionMode(@val[1])
        })
        doc('select', <<'EOT'
    The select functions controls which resource is picked from an allocation and
    it's alternatives. The selection is re-evaluated each time the resource used
    in the previous time slot becomes unavailable.
    
    Even for non-persistent allocations a change in the resource selection only
    happens if the resource used in the previous (or next for ASAP tasks) time
    slot has become unavailable.
    EOT
           )
    
        pattern(%w( _persistent ), lambda {
          @allocate.persistent = true
        })
        doc('persistent', <<'EOT'
    Specifies that once a resource is picked from the list of alternatives this
    resource is used for the whole task. This is useful when several alternative
    resources have been specified. Normally the selected resource can change after
    each break. A break is an interval of at least one timeslot where no resources
    were available.
    EOT
           )
    
        pattern(%w( _mandatory ), lambda {
          @allocate.mandatory = true
        })
        doc('mandatory', <<'EOT'
    Makes a resource allocation mandatory. This means, that for each time slot
    only then resources are allocated when all mandatory resources are available.
    So either all mandatory resources can be allocated for the time slot, or no
    resource will be allocated.
    EOT
           )
        pattern(%w( !allocateShiftAssignments !shiftAssignment ), lambda {
          begin
            @allocate.shifts = @shiftAssignments
          rescue AttributeOverwrite
            # Multiple shift assignments are a common idiom, so don't warn about
            # them.
          end
          @shiftAssignments = nil
        })
        level(:deprecated)
        also('shifts.allocate')
        doc('shift.allocate', <<'EOT'
    Limits the allocations of resources during the specified interval to the
    specified shift. Multiple shifts can be defined, but shift intervals may not
    overlap. Allocation shifts are an additional restriction to the
    [[shifts.task|task shifts]] and [[shifts.resource|resource shifts]] or
    [[workinghours.resource|resource working hours]]. Allocations will only be
    made for time slots that are specified as duty time in all relevant shifts.
    The restriction to the shift is only active during the specified time
    interval. Outside of this interval, no restrictions apply.
    EOT
           )
    
        pattern(%w( !allocateShiftsAssignments !shiftAssignments ), lambda {
          begin
            @allocate.shifts = @shiftAssignments
          rescue AttributeOverwrite
            # Multiple shift assignments are a common idiom, so don't warn about
            # them.
          end
          @shiftAssignments = nil
        })
        doc('shifts.allocate', <<'EOT'
    Limits the allocations of resources during the specified interval to the
    specified shift. Multiple shifts can be defined, but shift intervals may not
    overlap. Allocation shifts are an additional restriction to the
    [[shifts.task|task shifts]] and [[shifts.resource|resource shifts]] or
    [[workinghours.resource|resource working hours]]. Allocations will only be
    made for time slots that are specified as duty time in all relevant shifts.
    The restriction to the shift is only active during the specified time
    interval. Outside of this interval, no restrictions apply.
    EOT
           )
      end
    
      def rule_allocationBody
        optionsRule('allocationAttributes')
      end
    
      def rule_allocationHeader
        pattern(%w( !resourceId ), lambda {
          @allocate = Allocation.new([ @val[0] ])
        })
      end
    
      def rule_allocations
        listRule('moreAllocations', '!allocation')
      end
    
      def rule_allocationSelectionMode
        singlePattern('_maxloaded')
        descr('Pick the available resource that has been used the most so far.')
    
        singlePattern('_minloaded')
        descr('Pick the available resource that has been used the least so far.')
    
        singlePattern('_minallocated')
        descr(<<'EOT'
    Pick the resource that has the smallest allocation factor. The
    allocation factor is calculated from the various allocations of the resource
    across the tasks. This is the default setting.)
    EOT
             )
    
        singlePattern('_order')
        descr('Pick the first available resource from the list.')
    
        singlePattern('_random')
        descr('Pick a random resource from the list.')
      end
    
      def rule_allocateShiftAssignments
        pattern(%w( _shift ), lambda {
          @shiftAssignments = @allocate.shifts
        })
      end
    
      def rule_allocateShiftsAssignments
        pattern(%w( _shifts ), lambda {
          @shiftAssignments = @allocate.shifts
        })
      end
    
      def rule_allOrNone
        pattern(%w( _all ), lambda {
          1
        })
        pattern(%w( _none ), lambda {
          0
        })
      end
    
      def rule_argument
        singlePattern('$ABSOLUTE_ID')
        singlePattern('!date')
        singlePattern('$ID')
        singlePattern('$INTEGER')
        singlePattern('$FLOAT')
      end
    
      def rule_argumentList
        optional
        pattern(%w( _( !argumentListBody _) ), lambda {
          @val[1].nil? ? [] : @val[1]
        })
      end
    
      def rule_argumentListBody
        optional
        pattern(%w( !argument !moreArguments ), lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_author
        pattern(%w( _author !resourceId ), lambda {
          @journalEntry.author = @val[1]
        })
        doc('author', <<'EOT'
    This attribute can be used to capture the authorship or source of the
    information.
    EOT
           )
      end
    
      def rule_balance
        pattern(%w( _balance !balanceAccounts ), lambda {
          @val[1]
        })
        doc('balance', <<'EOT'
    During report generation, TaskJuggler can consider some accounts to be revenue accounts, while other can be considered cost accounts. By using the balance attribute, two top-level accounts can be designated for a profit-loss-analysis. This analysis includes all sub accounts of these two top-level accounts.
    
    To clear a previously set balance, just use a ''''-''''.
    EOT
           )
        example('AccountReport')
      end
    
      def rule_balanceAccounts
        pattern(%w( !accountId !accountId ), lambda {
          if @val[0].parent
            error('cost_acct_no_top',
                  "The cost account #{@val[0].fullId} is not a top-level account.",
                  @sourceFileInfo[0])
          end
          if @val[1].parent
            error('rev_acct_no_top',
                  "The revenue account #{@val[1].fullId} is not a top-level " +
                  "account.", @sourceFileInfo[1])
          end
          if @val[0] == @val[1]
            error('cost_rev_same',
                  'The cost and revenue accounts may not be the same.',
                  @sourceFileInfo[0])
          end
          [ @val[0], @val[1] ]
        })
        arg(0, 'cost account', <<'EOT'
    The top-level account that is used for all cost related charges.
    EOT
           )
        arg(2, 'revenue account', <<'EOT'
    The top-level account that is used for all revenue related charges.
    EOT
           )
    
        pattern([ '_-' ], lambda {
          [ nil, nil ]
        })
      end
    
      def rule_bookingAttributes
        optional
        repeatable
    
        pattern(%w( _overtime $INTEGER ), lambda {
          if @val[1] < 0 || @val[1] > 2
            error('overtime_range',
                  "Overtime value #{@val[1]} out of range (0 - 2).",
                  @sourceFileInfo[1], @property)
          end
          @booking.overtime = @val[1]
        })
        doc('overtime.booking', <<'EOT'
    This attribute enables bookings during off-hours and leaves. It implicitly
    sets the [[sloppy.booking|sloppy]] attribute accordingly.
    EOT
           )
        arg(1, 'value', <<'EOT'
    * '''0''': You can only book available working time. (Default)
    
    * '''1''': You can book off-hours as well.
    
    * '''2''': You can book working time, off-hours and vacation time.
    EOT
           )
    
        pattern(%w( _sloppy $INTEGER ), lambda {
          if @val[1] < 0 || @val[1] > 2
            error('sloppy_range',
                  "Sloppyness value #{@val[1]} out of range (0 - 2).",
                  @sourceFileInfo[1], @property)
          end
          @booking.sloppy = @val[1]
        })
        doc('sloppy.booking', <<'EOT'
    Controls how strict TaskJuggler checks booking intervals for conflicts with
    working periods and leaves. This attribute only affects the check for
    conflicts. No assignments will be made unless the [[overtime.booking|
    overtime]] attribute is set accordingly.
    EOT
           )
        arg(1, 'sloppyness', <<'EOT'
    * '''0''': Period may not contain any off-duty hours, vacation or other task
    assignments. (default)
    
    * '''1''': Period may contain off-duty hours, but no vacation time or other
    task assignments.
    
    * '''2''': Period may contain off-duty hours and vacation time, but no other
    task assignments.
    EOT
           )
      end
    
      def rule_bookingBody
        optionsRule('bookingAttributes')
      end
    
      def rule_calendarDuration
        pattern(%w( !number !durationUnit ), lambda {
          convFactors = [ 60.0, # minutes
                          60.0 * 60, # hours
                          60.0 * 60 * 24, # days
                          60.0 * 60 * 24 * 7, # weeks
                          60.0 * 60 * 24 * 30.4167, # months
                          60.0 * 60 * 24 * 365 # years
                         ]
          ((@val[0] * convFactors[@val[1]]) / @project['scheduleGranularity']).to_i
        })
        arg(0, 'value', 'A floating point or integer number')
      end
    
      def rule_chargeset
        pattern(%w( _chargeset !chargeSetItem !moreChargeSetItems ), lambda {
          checkContainer('chargeset')
          items = [ @val[1] ]
          items += @val[2] if @val[2]
          chargeSet = ChargeSet.new
          begin
            items.each do |item|
              chargeSet.addAccount(item[0], item[1])
            end
            chargeSet.complete
          rescue TjException
            error('chargeset', $!.message, @sourceFileInfo[0], @property)
          end
          masterAccounts = []
          @property['chargeset', @scenarioIdx].each do |set|
            masterAccounts << set.master
          end
          if masterAccounts.include?(chargeSet.master)
            error('chargeset_master',
                  "All charge sets for this property must have different " +
                  "top-level accounts.", @sourceFileInfo[0], @property)
          end
          @property['chargeset', @scenarioIdx] =
            @property['chargeset', @scenarioIdx] + [ chargeSet ]
        })
        doc('chargeset', <<'EOT'
    A chargeset defines how the turnover associated with the property will be
    charged to one or more accounts. A property may have any number of charge sets,
    but each chargeset must deal with a different top-level account. A charge set
    consists of one or more accounts. Each account must be a leaf account. The
    account ID may be followed by a percentage value that determines the share for
    this account. The total percentage of all accounts must be exactly 100%. If
    some accounts don't have a percentage specification, the remainder to 100% is
    distributed evenly between them.
    EOT
           )
      end
    
      def rule_chargeMode
        singlePattern('_onstart')
        descr('Charge the amount on starting the task.')
    
        singlePattern('_onend')
        descr('Charge the amount on finishing the task.')
    
        singlePattern('_perhour')
        descr('Charge the amount for every hour the task lasts.')
    
        singlePattern('_perday')
        descr('Charge the amount for every day the task lasts.')
    
        singlePattern('_perweek')
        descr('Charge the amount for every week the task lasts.')
      end
    
      def rule_chargeSetItem
        pattern(%w( !accountId !optionalPercent ), lambda {
          if @property.is_a?(Task)
            aggregate = :tasks
          elsif @property.is_a?(Resource)
            aggregate = :resources
          else
            raise "Unknown property type #{@property.class}"
          end
    
          if @val[0].get('aggregate') != aggregate
            error('account_bad_aggregate',
                  "The account #{@val[0].fullId} cannot aggregate amounts " +
                  "related to #{aggregate}.")
          end
    
          [ @val[0], @val[1] ]
        })
        arg(0, 'account', 'The ID of a previously defined leaf account.')
        arg(1, 'share', 'A percentage between 0 and 100%')
      end
    
      def rule_chartScale
        singlePattern('_hour')
        descr('Set chart resolution to 1 hour.')
    
        singlePattern('_day')
        descr('Set chart resolution to 1 day.')
    
        singlePattern('_week')
        descr('Set chart resolution to 1 week.')
    
        singlePattern('_month')
        descr('Set chart resolution to 1 month.')
    
        singlePattern('_quarter')
        descr('Set chart resolution to 1 quarter.')
    
        singlePattern('_year')
        descr('Set chart resolution to 1 year.')
      end
    
      def rule_color
        pattern(%w( $STRING ), lambda {
          col = @val[0]
          unless /#[0-9A-Fa-f]{3}/ =~ col || /#[0-9A-Fa-f]{3}/ =~ col
            error('bad_color',
                  "Color values must be specified as '#RGB' or '#RRGGBB' values",
                  @sourceFileInfo[0])
          end
          col
        })
        arg(0, 'color', <<'EOT'
    The RGB color values of the color. The following formats are supported: #RGB
    and #RRGGBB. Where R, G, B are hexadecimal values. See
    [http://en.wikipedia.org/wiki/Web_colors Wikipedia] for more details.
    EOT
           )
      end
    
      def rule_columnBody
        optionsRule('columnOptions')
      end
    
      def rule_columnDef
        pattern(%w( !columnId !columnBody ), lambda {
          @val[0]
        })
      end
    
      def rule_columnId
        pattern(%w( !reportableAttributes ), lambda {
          @column = TableColumnDefinition.new(@val[0], columnTitle(@val[0]))
        })
        doc('columnid', <<'EOT'
    This is a comprehensive list of all pre-defined [[columns]]. In addition to
    the listed IDs all user defined attributes can be used as column IDs.
    EOT
           )
      end
    
      def rule_columnOptions
        optional
        repeatable
    
        pattern(%w( _celltext !logicalExpression $STRING ), lambda {
          @column.cellText.addPattern(
            CellSettingPattern.new(newRichText(@val[2], @sourceFileInfo[2]),
                                   @val[1]))
        })
        doc('celltext.column', <<'EOT'
    Specifies an alternative content that is used for the cells of the column.
    Usually such a text contains a query function. Otherwise all cells of the
    column will have the same fixed value. The logical expression specifies for
    which cells the text should be used. If multiple celltext patterns are
    provided for a column, the first matching one is taken for each cell.
    EOT
            )
        arg(2, 'text',
            'Alterntive cell text specified as [[Rich_Text_Attributes|Rich Text]]')
    
        pattern(%w( _cellcolor !logicalExpression !color ), lambda {
          @column.cellColor.addPattern(
            CellSettingPattern.new(@val[2], @val[1]))
        })
        doc('cellcolor.column', <<'EOT'
    Specifies an alternative background color for the cells of this column. The
    [[logicalexpression|logical expression]] specifies for which cells the color
    should be used. If multiple cellcolor patterns are provided for a column, the
    first matching one is used for each cell.
    EOT
           )
    
        pattern(%w( _end !date ), lambda {
          @column.end = @val[1]
        })
        doc('end.column', <<'EOT'
    Normally, columns with calculated values take the specified report period into
    account when calculating their values. With this attribute, the user can
    specify an end date for the period that should be used when calculating the
    values of this column. It does not have an impact on column with time
    invariant values.
    EOT
           )
    
        pattern(%w( _fontcolor !logicalExpression !color ), lambda {
          @column.fontColor.addPattern(
            CellSettingPattern.new(@val[2], @val[1]))
        })
        doc('fontcolor.column', <<'EOT'
    Specifies an alternative font color for the cells of this column. The
    [[logicalexpression|logical expression]] specifies for which cells the color
    should be used. If multiple fontcolor patterns are provided for a column, the
    first matching one is used for each cell.
    EOT
           )
    
        pattern(%w( _halign !logicalExpression !hAlignment ), lambda {
          @column.hAlign.addPattern(
            CellSettingPattern.new(@val[2], @val[1]))
        })
        doc('halign.column', <<'EOT'
    Specifies the horizontal alignment of the cell content. The
    [[logicalexpression|logical expression]] specifies for which cells the alignment
    setting should be used. If multiple halign patterns are provided for a column,
    the first matching one is used for each cell.
    EOT
           )
    
        pattern(%w( _listitem $STRING ), lambda {
          @column.listItem = @val[1]
        })
        doc('listitem.column', <<'EOT'
    Specifies a RichText pattern that is used to generate the text for the list
    items. The pattern should contain at least one ''''<-query
    attribute='XXX'->'''' element that will be replaced with the value of
    attribute XXX. For the replacement, the property of the query will be the list
    item.
    EOT
           )
    
        pattern(%w( _listtype !listType ), lambda {
          @column.listType = @val[1]
        })
        also(%w( listitem.column ))
        doc('listtype.column', <<'EOT'
    Specifies what type of list should be used. This attribute only affects
    columns that contain a list of items.
    EOT
           )
    
        pattern(%w( _period !interval ), lambda {
          @column.start = @val[1].start
          @column.end = @val[1].end
        })
        doc('period.column', <<'EOT'
    This property is a shortcut for setting the [[start.column|start]] and
    [[end.column|end]] property at the same time.
    EOT
           )
    
        pattern(%w( _scale !chartScale ), lambda {
          @column.scale = @val[1]
        })
        doc('scale.column', <<'EOT'
    Specifies the scale that should be used for a chart column. This value is ignored for all other columns.
    EOT
           )
    
        pattern(%w( _start !date ), lambda {
          @column.start = @val[1]
        })
        doc('start.column', <<'EOT'
    Normally, columns with calculated values take the specified report period into
    account when calculating their values. With this attribute, the user can
    specify a start date for the period that should be used when calculating the
    values of this column. It does not have an impact on column with time
    invariant values.
    EOT
           )
    
        pattern(%w( _timeformat1 $STRING ), lambda {
          @column.timeformat1 = @val[1]
        })
        doc('timeformat1', <<'EOT'
    Specify an alternative format for the upper header line of calendar or Gantt
    chart columns.
    EOT
           )
        arg(1, 'format', 'See [[timeformat]] for details.')
    
        pattern(%w( _timeformat2 $STRING ), lambda {
          @column.timeformat2 = @val[1]
        })
        doc('timeformat2', <<'EOT'
    Specify an alternative format for the lower header line of calendar or Gantt
    chart columns.
    EOT
           )
        arg(1, 'format', 'See [[timeformat]] for details.')
    
        pattern(%w( _title $STRING ), lambda {
          @column.title = @val[1]
        })
        doc('title.column', <<'EOT'
    Specifies an alternative title for a report column.
    EOT
           )
        arg(1, 'text', 'The new column title.')
    
        pattern(%w( _tooltip !logicalExpression $STRING ), lambda {
          @column.tooltip.addPattern(
            CellSettingPattern.new(newRichText(@val[2], @sourceFileInfo[2]),
                                   @val[1]))
        })
        doc('tooltip.column', <<'EOT'
    Specifies an alternative content for the tooltip. This will replace the
    original content of the tooltip that would be available for columns with text
    that does not fit the column with.  The [[logicalexpression|logical expression]]
    specifies for which cells the text should be used. If multiple tooltip
    patterns are provided for a column, the first matching one is taken for each
    cell.
    EOT
           )
        arg(2, 'text', <<'EOT'
    The content of the tooltip. The text is interpreted as [[Rich_Text_Attributes|
    Rich Text]].
    EOT
           )
    
        pattern(%w( _width !number ), lambda {
          @column.width = @val[1]
        })
        doc('width.column', <<'EOT'
    Specifies the maximum width of the column in screen pixels. If the content of
    the column does not fit into this width, it will be cut off. In some cases a
    scrollbar is added or a tooltip window with the complete content is shown when
    the mouse is moved over the column. The latter is only supported in
    interactive output formats. The resulting column width may be smaller if the
    column has a fixed width (e. g. the chart column).
    EOT
           )
      end
    
      def rule_currencyFormat
        pattern(%w( _currencyformat $STRING $STRING $STRING $STRING $INTEGER ),
            lambda {
          RealFormat.new(@val.slice(1, 5))
        })
        doc('currencyformat',
            'These values specify the default format used for all currency ' +
            'values.')
        example('Currencyformat')
        arg(1, 'negativeprefix', 'Prefix for negative numbers')
        arg(2, 'negativesuffix', 'Suffix for negative numbers')
        arg(3, 'thousandsep', 'Separator used for every 3rd digit')
        arg(4, 'fractionsep', 'Separator used to separate the fraction digits')
        arg(5, 'fractiondigits', 'Number of fraction digits to show')
      end
    
      def rule_date
        pattern(%w( !dateCalcedOrNot ), lambda {
          resolution = @project.nil? ? Project.maxScheduleGranularity :
                                       @project['scheduleGranularity']
          if @val[0] % resolution != 0
            error('misaligned_date',
                  "The date must be aligned to the timing resolution (" +
                  "#{resolution / 60} min) of the project.",
                  @sourceFileInfo[0])
          end
          @val[0]
        })
        doc('date', <<'EOT'
    A DATE is date and time specification similar to the ISO 8601 date format.
    Instead of the hard to read ISO notation with a ''''T'''' between the date and
    time sections, we simply use the more intuitive and easier to read dash:
    ''''YYYY-MM-DD[-hh:mm[:ss]][-TIMEZONE]''''. Hour, minutes,
    seconds, and the ''''TIMEZONE'''' are optional. If not specified, the values
    are set to 0.  ''''TIMEZONE'''' must be an offset to GMT or UTC, specified as
    ''''+HHMM'''' or ''''-HHMM''''. Dates must always be aligned with the
    [[timingresolution]].
    
    TaskJuggler also supports simple date calculations. You can add or substract a
    given interval from a fixed date.
    
     %{2009-11-01 + 8m}
    
    This will result in an actual date of around 2009-07-01. Keep in mind that due
    to the varying lengths of months TaskJuggler cannot add exactly 8 calendar
    months. The date calculation functionality makes most sense when used with
    macros.
    
     %{${now} - 2w}
    
    This is result in a date 2 weeks earlier than the current (or specified) date.
    See [[duration]] for a complete list of supported time intervals. Don't forget
    to put at least one space character after the date to prevent TaskJuggler from
    interpreting the interval as an hour.
    
    Date attributes may be invalid in some cases. This needs special care in
    [[logicalexpression|logical expressions]].
    EOT
           )
      end
    
      def rule_dateCalcedOrNot
        singlePattern('$DATE')
        pattern(%w( _% _{ $DATE !plusOrMinus !intervalDuration _} ), lambda {
          @val[2] + ((@val[3] == '+' ? 1 : -1) * @val[4])
        })
      end
    
      def rule_declareFlagList
        listRule('moreDeclareFlagList', '$ID')
      end
    
      def rule_details
        pattern(%w( _details $STRING ), lambda {
          return if @val[1].empty?
    
          rtTokenSetMore =
            [ :LINEBREAK, :SPACE, :WORD, :BOLD, :ITALIC, :CODE, :BOLDITALIC,
              :PRE, :HREF, :HREFEND, :REF, :REFEND, :HLINE, :TITLE2, :TITLE3,
              :TITLE4, :TITLE2END, :TITLE3END, :TITLE4END,
              :BULLET1, :BULLET2, :BULLET3, :BULLET4, :NUMBER1, :NUMBER2, :NUMBER3,
              :NUMBER4 ]
          if @val[1] == "Some more details\n"
            error('ts_default_details',
                  "'Some more details' is not a valid value",
                  @sourceFileInfo[1])
          end
          @journalEntry.details = newRichText(@val[1], @sourceFileInfo[1],
                                              rtTokenSetMore)
        })
        doc('details', <<'EOT'
    This is a continuation of the [[summary]] of the journal or status entry. It
    can be several paragraphs long.
    EOT
           )
        arg(1, 'text', <<'EOT'
    The text will be interpreted as [[Rich_Text_Attributes|Rich Text]]. Only a
    subset of the markup is supported for this attribute. You can use word
    formatting, paragraphs, hyperlinks, lists, section and subsection
    headers.
    EOT
           )
      end
    
      def rule_durationUnit
        pattern(%w( _min ), lambda { 0 })
        descr('minutes')
    
        pattern(%w( _h ), lambda { 1 })
        descr('hours')
    
        pattern(%w( _d ), lambda { 2 })
        descr('days')
    
        pattern(%w( _w ), lambda { 3 })
        descr('weeks')
    
        pattern(%w( _m ), lambda { 4 })
        descr('months')
    
        pattern(%w( _y ), lambda { 5 })
        descr('years')
      end
    
      def rule_durationUnitOrPercent
        pattern(%w( _% ), lambda { -1 })
        descr('percentage of reported period')
    
        pattern(%w( _min ), lambda { 0 })
        descr('minutes')
    
        pattern(%w( _h ), lambda { 1 })
        descr('hours')
    
        pattern(%w( _d ), lambda { 2 })
        descr('days')
      end
    
      def rule_export
        pattern(%w( !exportHeader !exportBody ), lambda {
          @property = @property.parent
        })
        doc('export', <<'EOT'
    The export report looks like a regular TaskJuggler file with the provided
    input data complemented by the results of the scheduling process. The content
    of the report can be controlled with the [[definitions]] attribute. In case
    the file contains the project header, a ''''.tjp'''' extension is added to the
    file name. Otherwise, a ''''.tji'''' extension is used.
    
    The [[resourceattributes]] and [[taskattributes]] attributes provide even more
    control over the content of the file.
    
    The export report can be used to share certain tasks or milestones with other
    projects or to save past resource allocations as immutable part for future
    scheduling runs. When an export report is included the project IDs of the
    included tasks must be declared first with the project id property.
    EOT
           )
        example('Export')
      end
    
      def rule_exportAttributes
        optional
        repeatable
    
        pattern(%w( _definitions !exportDefinitions ), lambda {
          @property.set('definitions', @val[1])
        })
        doc('definitions', <<"EOT"
    This attributes controls what definitions will be contained in the report. If
    the list includes ''project'', the generated file will have a ''''.tjp''''
    extension. Otherwise it will have a ''''.tji'''' extension.
    
    By default, the report contains everything and the generated files has a ''''.tjp'''' extension.
    EOT
           )
        allOrNothingListRule('exportDefinitions',
                             { 'flags' => 'Include flag definitions',
                               'project' => 'Include project header',
                               'projecids' => 'Include project IDs',
                               'tasks' => 'Include task definitions',
                               'resources' => 'Include resource definitions' })
    
        pattern(%w( _formats !exportFormats ), lambda {
          @property.set('formats', @val[1])
        })
        level(:beta)
        doc('formats.export', <<'EOT'
    This attribute defines for which output formats the export report should be
    generated. By default, the TJP format will be used.
    EOT
           )
    
        pattern(%w( !hideresource ))
        pattern(%w( !hidetask ))
    
        pattern(%w( !loadunit ))
    
        pattern(%w( !purge ))
        pattern(%w( !reportEnd ))
        pattern(%w( !reportPeriod ))
        pattern(%w( !reports ))
        pattern(%w( !reportStart ))
    
        pattern(%w( _resourceattributes !exportableResourceAttributes ), lambda {
          @property.set('resourceAttributes', @val[1])
        })
        doc('resourceattributes', <<"EOT"
    Define a list of resource attributes that should be included in the report.
    EOT
           )
        allOrNothingListRule('exportableResourceAttributes',
                             { 'booking' => 'Include bookings',
                               'leaves' => 'Include leaves',
                               'workinghours' => 'Include working hours' })
    
        pattern(%w( !rollupresource ))
        pattern(%w( !rolluptask ))
    
        pattern(%w( _scenarios !scenarioIdList ), lambda {
          # Don't include disabled scenarios in the report
          @val[1].delete_if { |sc| !@project.scenario(sc).get('active') }
          @property.set('scenarios', @val[1])
        })
        doc('scenarios.export', <<'EOT'
    List of scenarios that should be included in the report. By default, all
    scenarios will be included. This attribute can be used to limit the included
    scenarios to a defined list.
    EOT
           )
    
        pattern(%w( _taskattributes !exportableTaskAttributes ), lambda {
          @property.set('taskAttributes', @val[1])
        })
        doc('taskattributes', <<"EOT"
    Define a list of task attributes that should be included in the report.
    EOT
           )
        allOrNothingListRule('exportableTaskAttributes',
                             { 'booking' => 'Include bookings',
                               'complete' => 'Include completion values',
                               'depends' => 'Include dependencies',
                               'flags' => 'Include flags',
                               'maxend' => 'Include maximum end dates',
                               'maxstart' => 'Include maximum start dates',
                               'minend' =>  'Include minimum end dates',
                               'minstart' => 'Include minimum start dates',
                               'note' => 'Include notes',
                               'priority' => 'Include priorities',
                               'responsible' => 'Include responsible resource' })
    
        pattern(%w( _taskroot !taskId), lambda {
          if @val[1].leaf?
            error('taskroot_leaf',
                  "#{@val[1].fullId} is not a container task",
                  @sourceFileInfo[1])
          end
          @property.set('taskroot', @val[1])
        })
        level(:experimental)
        doc('taskroot.export', <<'EOT'
    Only tasks below the specified root-level tasks are exported. The exported
    tasks will have the ID of the root-level task stripped from their ID, so that
    the sub-tasks of the root-level task become top-level tasks in the report
    file.
    EOT
           )
        example('TaskRoot')
    
        pattern(%w( _timezone !validTimeZone ), lambda {
          @property.set('timezone', @val[1])
        })
        doc('timezone.export',
            "Set the time zone to be used for all dates in the report.")
      end
    
      def rule_exportBody
        optionsRule('exportAttributes')
      end
    
      def rule_exportFormat
        pattern(%w( _tjp ), lambda {
          :tjp
        })
        descr('Export of the scheduled project in TJP syntax.')
    
        pattern(%w( _mspxml ), lambda {
          :mspxml
        })
        descr(<<'EOT'
    Export of the scheduled project in Microsoft Project XML format. This will
    export the data of the fully scheduled project. The exported data include the
    tasks, resources and the assignments of resources to task. This is only a
    small subset of the data that TaskJuggler can manage. This export is intended
    to share resource assignment data with other teams using Microsoft Project.
    TaskJuggler manages assignments with a larger accuracy than the Microsft
    Project XML format can represent. This will inevitably lead to some rounding
    errors and different interpretation of the data. The numbers you will see in
    Project are not necessarily an exact match of the numbers you see in
    TaskJuggler.
    EOT
             )
      end
    
      def rule_exportFormats
        pattern(%w( !exportFormat !moreExportFormats ), lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_exportHeader
        pattern(%w( _export !optionalID $STRING ), lambda {
          newReport(@val[1], @val[2], :export, @sourceFileInfo[0]) do
            unless @property.modified?('formats')
              @property.set('formats', [ :tjp ])
            end
    
            # By default, we export all scenarios.
            unless @property.modified?('scenarios')
              scenarios = Array.new(@project.scenarios.items) { |i| i }
              scenarios.delete_if { |sc| !@project.scenario(sc).get('active') }
              @property.set('scenarios', scenarios)
            end
            # Show all tasks, sorted by seqno-up.
            unless @property.modified?('hideTask')
              @property.set('hideTask',
                            LogicalExpression.new(LogicalOperation.new(0)))
            end
            unless @property.modified?('sortTasks')
              @property.set('sortTasks', [ [ 'seqno', true, -1 ] ])
            end
            # Show all resources, sorted by seqno-up.
            unless @property.modified?('hideResource')
              @property.set('hideResource',
                            LogicalExpression.new(LogicalOperation.new(0)))
            end
            unless @property.modified?('sortResources')
              @property.set('sortResources', [ [ 'seqno', true, -1 ] ])
            end
          end
        })
        arg(2, 'file name', <<'EOT'
    The name of the report file to generate. It must end with a .tjp or .tji
    extension, or use . to use the standard output channel.
    EOT
           )
      end
    
      def rule_extendAttributes
        optional
        repeatable
    
        pattern(%w( _date !extendId  $STRING !extendOptionsBody ), lambda {
          # Extend the propertySet definition and parser rules
          if extendPropertySetDefinition(DateAttribute, nil)
            @ruleToExtendWithScenario.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '!date' ], lambda {
                @property[@val[0], @scenarioIdx] = @val[1]
              }))
          else
            @ruleToExtend.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '!date' ], lambda {
                @property.set(@val[0], @val[1])
              }))
          end
        })
        doc('date.extend', <<'EOT'
    Extend the property with a new attribute of type date.
    EOT
           )
        arg(2, 'name', 'The name of the new attribute. It is used as header ' +
                       'in report columns and the like.')
    
        pattern(%w( _number !extendId  $STRING !extendOptionsBody ), lambda {
          # Extend the propertySet definition and parser rules
          if extendPropertySetDefinition(FloatAttribute, nil)
            @ruleToExtendWithScenario.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '!number' ], lambda {
                @property[@val[0], @scenarioIdx] = @val[1]
              }))
          else
            @ruleToExtend.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '!number' ], lambda {
                @property.set(@val[0], @val[1])
              }))
          end
        })
        doc('number.extend', <<'EOT'
    Extend the property with a new attribute of type number. Possible values for
    this attribute could be integer or floating point numbers.
    EOT
           )
        arg(2, 'name', 'The name of the new attribute. It is used as header ' +
                       'in report columns and the like.')
    
        pattern(%w( _reference !extendId $STRING !extendOptionsBody ), lambda {
          # Extend the propertySet definition and parser rules
          if extendPropertySetDefinition(ReferenceAttribute, nil)
            @ruleToExtendWithScenario.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '$STRING', '!referenceBody' ], lambda {
                @property[@val[0], @scenarioIdx] = [ @val[1], @val[2] ]
              }))
          else
            @ruleToExtend.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '$STRING', '!referenceBody' ], lambda {
                @property.set(@val[0], [ @val[1], @val[2] ])
              }))
          end
        })
        doc('reference.extend', <<'EOT'
    Extend the property with a new attribute of type reference. A reference is a
    URL and an optional text that will be shown instead of the URL if needed.
    EOT
           )
        arg(2, 'name', 'The name of the new attribute. It is used as header ' +
                       'in report columns and the like.')
    
        pattern(%w( _richtext !extendId $STRING !extendOptionsBody ), lambda {
          # Extend the propertySet definition and parser rules
          if extendPropertySetDefinition(RichTextAttribute, nil)
            @ruleToExtendWithScenario.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '$STRING' ], lambda {
                @property[@val[0], @scenarioIdx] =
                  newRichText(@val[1], @sourceFileInfo[1])
              }))
          else
            @ruleToExtend.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '$STRING' ], lambda {
                @property.set(@val[0], newRichText(@val[1], @sourceFileInfo[1]))
              }))
          end
        })
        doc('richtext.extend', <<'EOT'
    Extend the property with a new attribute of type [[Rich_Text_Attributes|Rich
    Text]].
    EOT
           )
        arg(2, 'name', 'The name of the new attribute. It is used as header ' +
                       'in report columns and the like.')
    
        pattern(%w( _text !extendId $STRING !extendOptionsBody ), lambda {
          # Extend the propertySet definition and parser rules
          if extendPropertySetDefinition(StringAttribute, nil)
            @ruleToExtendWithScenario.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '$STRING' ], lambda {
                @property[@val[0], @scenarioIdx] = @val[1]
              }))
          else
            @ruleToExtend.addPattern(TextParser::Pattern.new(
              [ '_' + @val[1], '$STRING' ], lambda {
                @property.set(@val[0], @val[1])
              }))
          end
        })
        doc('text.extend', <<'EOT'
    Extend the property with a new attribute of type text. A text is a character
    sequence enclosed in single or double quotes.
    EOT
           )
        arg(2, 'name', 'The name of the new attribute. It is used as header ' +
                       'in report columns and the like.')
    
      end
    
      def rule_extendBody
        optionsRule('extendAttributes')
      end
    
      def rule_extendId
        pattern(%w( $ID ), lambda {
          unless (?A..?Z) === @val[0][0]
            error('extend_id_cap',
                  "User defined attributes IDs must start with a capital letter",
                  @sourceFileInfo[0])
          end
          @val[0]
        })
        arg(0, 'id', 'The ID of the new attribute. It can be used like the ' +
                     'built-in IDs.')
      end
    
      def rule_extendOptions
        optional
        repeatable
    
        singlePattern('_inherit')
        doc('inherit.extend', <<'EOT'
    If the this attribute is used, the property extension will be inherited by
    child properties from their parent property.
    EOT
           )
    
        singlePattern('_scenariospecific')
        doc('scenariospecific.extend', <<'EOT'
    If this attribute is used, the property extension is scenario specific. A
    different value can be set for each scenario.
    EOT
           )
      end
    
      def rule_extendOptionsBody
        optionsRule('extendOptions')
      end
    
      def rule_extendProperty
        pattern(%w( !extendPropertyId ), lambda {
          case @val[0]
          when 'task'
            @ruleToExtend = @rules[:taskAttributes]
            @ruleToExtendWithScenario = @rules[:taskScenarioAttributes]
            @propertySet = @project.tasks
          when 'resource'
            @ruleToExtend = @rules[:resourceAttributes]
            @ruleToExtendWithScenario = @rules[:resourceScenarioAttributes]
            @propertySet = @project.resources
          end
        })
      end
    
      def rule_extendPropertyId
        singlePattern('_task')
        singlePattern('_resource')
      end
    
      def rule_fail
        pattern(%w( _fail !logicalExpression ), lambda {
          begin
            @property.set('fail', @property.get('fail') + [ @val[1] ])
          rescue AttributeOverwrite
          end
        })
        doc('fail', <<'EOT'
    The fail attribute adds a [[logicalexpression|logical expression]] to the
    property. The condition described by the logical expression is checked after
    the scheduling and an error is raised if the condition evaluates to true. This
    attribute is primarily intended for testing purposes.
    EOT
           )
      end
    
      def rule_flag
        pattern(%w( $ID ), lambda {
          unless @project['flags'].include?(@val[0])
            error('undecl_flag', "Undeclared flag '#{@val[0]}'",
                  @sourceFileInfo[0])
          end
          @val[0]
        })
      end
    
      def rule_flagLogicalExpression
        pattern(%w( !flagOperation ), lambda {
          LogicalExpression.new(@val[0], sourceFileInfo)
        })
        doc('logicalflagexpression', <<'EOT'
    A logical flag expression is a combination of operands and mathematical
    operations.  The final result of a logical expression is always true or false.
    Logical expressions are used the reduce the properties in a report to a
    certain subset or to select alternatives for the cell content of a table. When
    used with attributes like [[hidejournalentry]] the logical expression
    evaluates to true for a certain property, this property is hidden or rolled-up
    in the report.
    
    Operands must be previously declared flags or another logical expression.
    When you combine logical operations to a more complex expression, the
    operators are evaluated from left to right. '''a | b & c''' is identical to
    '''(a | b) & c'''. It's highly recommended that you always use brackets to
    control the evaluation sequence. Currently, TaskJuggler does not support the
    concept of operator precedence or right-left associativity. This may change in
    the future.
    EOT
           )
        also(%w( functions ))
      end
    
      def rule_flagOperand
        pattern(%w( _( !flagOperation _) ), lambda {
          @val[1]
        })
        pattern(%w( _~ !flagOperand ), lambda {
          operation = LogicalOperation.new(@val[1])
          operation.operator = '~'
          operation
        })
    
        pattern(%w( $ID ), lambda {
          unless @project['flags'].include?(@val[0])
            error('operand_unkn_flag', "Undeclared flag '#{@val[0]}'",
                  @sourceFileInfo[0])
          end
          LogicalFlag.new(@val[0])
        })
      end
    
      def rule_flagOperation
        pattern(%w( !flagOperand !flagOperationChain ), lambda {
          operation = LogicalOperation.new(@val[0])
          if @val[1]
            # Further operators/operands create an operation tree.
            @val[1].each do |ops|
              operation = LogicalOperation.new(operation)
              operation.operator = ops[0]
              operation.operand2 = ops[1]
            end
          end
          operation
        })
        arg(0, 'operand', <<'EOT'
    An operand is a declared flag. An operand can be a negated operand by
    prefixing a ~ charater or it can be another logical expression enclosed in
    braces.
    EOT
            )
      end
    
      def rule_flagOperationChain
        optional
        repeatable
        pattern(%w( !flagOperatorAndOperand), lambda {
          @val[0]
        })
      end
    
      def rule_flagOperatorAndOperand
        pattern(%w( !flagOperator !flagOperand), lambda{
          [ @val[0], @val[1] ]
        })
        arg(1, 'operand', <<'EOT'
    An operand is a declared flag. An operand can be a negated operand by
    prefixing a ~ charater or it can be another logical expression enclosed in
    braces.
    EOT
            )
      end
    
      def rule_flagOperator
        singlePattern('_|')
        descr('The \'or\' operator')
    
        singlePattern('_&')
        descr('The \'and\' operator')
      end
    
    
      def rule_flags
        pattern(%w( _flags !flagList ), lambda {
          @val[1].each do |flag|
            next if @property['flags', @scenarioIdx].include?(flag)
    
            @property['flags', @scenarioIdx] += [ flag ]
          end
        })
      end
    
      def rule_flagList
        listRule('moreFlagList', '!flag')
      end
    
      def rule_formats
        pattern(%w( _formats !outputFormats ), lambda {
          @property.set('formats', @val[1])
        })
        doc('formats', <<'EOT'
    This attribute defines for which output formats the report should be
    generated. By default, this list is empty. Unless a formats attribute was
    added to a report definition, no output will be generated for this report.
    
    As reports are composable, a report may include other report definitions. A
    format definition is only needed for the outermost report that includes the
    others.
    EOT
           )
      end
    
      def rule_functions
        # This rule is not used by the parser. It's only for the documentation.
        pattern(%w( !functionsBody ))
        doc('functions', <<'EOT'
    The following functions are supported in logical expressions. These functions
    are evaluated in logical conditions such as hidetask or rollupresource. For
    the evaluation, implicit and explicit parameters are used.
    
    All functions may operate on the current property and the scope property. The
    scope property is the enclosing property in reports with nested properties.
    Imagine e. g a task report with nested resources. When the function is called
    for a task line, the task is the property and we don't have a scope property.
    When the function is called for a resource line, the resource is the property
    and the enclosing task is the scope property.
    
    These number of arguments that are passed in brackets to the function depends
    on the specific function. See the reference for details on each function.
    
    All functions can be suffixed with an underscore character. In that case, the
    function is operating on the scope property as if it were the property. The
    original property is ignored in that case. In our task report example from
    above, calling a function with an appended dash would mean that a task
    line would be evaluated for the enclosing resource.
    
    In the example below you can see how this can be used. To generate a task
    report that lists all assigned leaf resources for leaf task lines only we use
    the expression
    
     hideresource ~(isleaf() & isleaf_())
    
    The tilde in front of the bracketed expression means not that expression. In
    other words: show resources that are leaf resources and show them for leaf
    tasks only. The regular form isleaf() (without the appended underscore)
    operates on the resource. The isleaf_() variant operates on the
    enclosing task.
    EOT
           )
        example('LogicalFunction', '1')
      end
    
      def rule_functionsBody
        # This rule is not used by the parser. It's only for the documentation.
        optionsRule('functionPatterns')
      end
    
      def rule_functionPatterns
        # This rule is not used by the parser. It's only for the documentation.
        pattern(%w( _hasalert _( $INTEGER _, !date _) ))
        doc('hasalert', <<'EOT'
    Will evaluate to true if the current property has a current alert message within the report time frame and with at least the provided alert level.
    EOT
           )
        arg(2, 'Level', 'The minimum required alert level to be considered.')
    
        pattern(%w( _isactive _( $ID _) ))
        doc('isactive', <<'EOT'
    Will evaluate to true for tasks and resources if they have bookings in
    the scenario during the report time frame.
    EOT
           )
        arg(2, 'ID', 'A scenario ID')
    
        pattern(%w( _ischildof _( $ID _) ))
        doc('ischildof', <<'EOT'
    Will evaluate to true for tasks and resources if current property is a child
    of the provided parent property.
    EOT
           )
        arg(2, 'ID', 'The ID of the parent')
    
        pattern(%w( _isdependencyof _( $ID _, $ID _, $INTEGER _) ))
        doc('isdependencyof', <<'EOT'
    Will evaluate to true for tasks that depend on the specified task in
    the specified scenario and are no more than distance tasks away. If
    distance is 0, all dependencies are considered independent of their
    distance.
    EOT
           )
        arg(2, 'Task ID', 'The ID of a defined task')
        arg(4, 'Scenario ID', 'A scenario ID')
        arg(6, 'Distance', 'The maximum task distance to be considered')
    
        pattern(%w( _isdutyof _( $ID _, $ID _) ))
        doc('isdutyof', <<'EOT'
    Will evaluate to true for tasks that have the specified resource
    assigned to it in the specified scenario.
    EOT
           )
        arg(2, 'Resource ID', 'The ID of a defined resource')
        arg(4, 'Scenario ID', 'A scenario ID')
    
        pattern(%w( _isfeatureof _( $ID _, $ID _) ))
        doc('isfeatureof', <<'EOT'
    If the provided task or any of its sub-tasks depend on this task or any of its
    sub-tasks, we call this task a feature of the provided task.
    EOT
           )
        arg(2, 'Task ID', 'The ID of a defined task')
        arg(4, 'Scenario ID', 'A scenario ID')
    
        pattern(['_isleaf', '_(', '_)' ])
        doc('isleaf', 'The result is true if the property is not a container.')
    
        pattern(%w( _ismilestone _( $ID _) ))
        doc('ismilestone', <<'EOT'
    The result is true if the property is a milestone in the provided scenario.
    EOT
           )
        arg(2, 'Scenario ID', 'A scenario ID')
    
        pattern(%w( _isongoing _( $ID _) ))
        doc('isongoing', <<'EOT'
    Will evaluate to true for tasks that overlap with the report period in given
    scenario.
    EOT
           )
        arg(2, 'ID', 'A scenario ID')
    
        pattern(['_isresource', '_(', '_)' ])
        doc('isresource', 'The result is true if the property is a resource.')
    
        pattern(%w( _isresponsibilityof _( $ID _, $ID _) ))
        doc('isresponsibilityof', <<'EOT'
    Will evaluate to true for tasks that have the specified resource
    assigned as [[responsible]] in the specified scenario.
    EOT
           )
        arg(2, 'Resource ID', 'The ID of a defined resource')
        arg(4, 'Scenario ID', 'A scenario ID')
    
        pattern(['_istask', '_(', '_)' ])
        doc('istask', 'The result is true if the property is a task.')
    
        pattern(%w( _isvalid _( $ID _) ))
        doc('isvalid', 'Returns false if argument is not an assigned or ' +
                       'properly computed value.')
    
        pattern(%w( _treelevel _( _) ))
        doc('treelevel', <<'EOT'
    Returns the nesting level of a property in the property tree.
    Top level properties have a level of 1, their children 2 and so on.
    EOT
           )
      end
    
      def rule_hAlignment
        pattern(%w( _center ), lambda {
          :center
        })
        doc('halign.center', 'Center the cell content')
    
        pattern(%w( _left ), lambda {
          :left
        })
        doc('halign.left', 'Left align the cell content')
    
        pattern(%w( _right ), lambda {
          :right
        })
        doc('halign.right', 'Right align the cell content')
      end
    
      def rule_headline
        pattern(%w( _headline $STRING ), lambda {
          @property.set('headline', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('headline', <<'EOT'
    Specifies the headline for a report.
    EOT
           )
        arg(1, 'text', <<'EOT'
    The text used for the headline. It is interpreted as
    [[Rich_Text_Attributes|Rich Text]].
    EOT
           )
      end
    
      def rule_hideaccount
        pattern(%w( _hideaccount !logicalExpression ), lambda {
          @property.set('hideAccount', @val[1])
        })
        doc('hideaccount', <<'EOT'
    Do not include accounts that match the specified [[logicalexpression|logical
    expression]]. If the report is sorted in ''''tree'''' mode (default) then
    enclosing accounts are
    listed even if the expression matches the account.
    EOT
           )
        also(%w( sortaccounts ))
      end
    
      def rule_hidejournalentry
        pattern(%w( _hidejournalentry !flagLogicalExpression ), lambda {
          @property.set('hideJournalEntry', @val[1])
        })
        doc('hidejournalentry', <<'EOT'
    Do not include journal entries that match the specified logical expression.
    EOT
           )
      end
    
      def rule_hideresource
        pattern(%w( _hideresource !logicalExpression ), lambda {
          @property.set('hideResource', @val[1])
        })
        doc('hideresource', <<'EOT'
    Do not include resources that match the specified [[logicalexpression|logical
    expression]]. If the report is sorted in ''''tree'''' mode (default) then
    enclosing resources are listed even if the expression matches the resource.
    EOT
           )
        also(%w( sortresources ))
      end
    
      def rule_hidetask
        pattern(%w( _hidetask !logicalExpression ), lambda {
          @property.set('hideTask', @val[1])
        })
        doc('hidetask', <<'EOT'
    Do not include tasks that match the specified [[logicalexpression|logical
    expression]]. If the report is sorted in ''''tree'''' mode (default) then
    enclosing tasks are listed even if the expression matches the task.
    EOT
           )
        also(%w( sorttasks ))
      end
      def rule_iCalReport
        pattern(%w( !iCalReportHeader !iCalReportBody ), lambda {
          @property = nil
        })
        doc('icalreport', <<'EOT'
    Generates an RFC5545 compliant iCalendar file. This file can be used to export
    task information to calendar applications or other tools that read iCalendar
    files.
    EOT
           )
      end
      def rule_iCalReportBody
        optionsRule('iCalReportAttributes')
      end
    
      def rule_iCalReportAttributes
        optional
        repeatable
    
        pattern(%w( !hideresource ))
        pattern(%w( !hidejournalentry ))
        pattern(%w( !hidetask ))
        pattern(%w( !reportEnd ))
        pattern(%w( !reportPeriod ))
        pattern(%w( !reportStart ))
        pattern(%w( !rollupresource ))
        pattern(%w( !rolluptask ))
    
        pattern(%w( _scenario !scenarioId ), lambda {
          # Don't include disabled scenarios in the report
          sc = @val[1]
          unless @project.scenario(sc).get('active')
            warning('ical_sc_disabled',
                    "Scenario #{sc} has been disabled")
          else
            @property.set('scenarios', [ @val[1] ])
          end
        })
        doc('scenario.ical', <<'EOT'
    Id of the scenario that should be included in the report. By default, the
    top-level scenario will be included. This attribute can be used select another
    scenario.
    EOT
           )
      end
    
      def rule_iCalReportHeader
        pattern(%w( _icalreport !optionalID $STRING ), lambda {
          newReport(@val[1], @val[2], :iCal, @sourceFileInfo[0]) do
            @property.set('formats', [ :iCal ])
    
            # By default, we export only the first scenario.
            unless @project.scenario(0).get('active')
              @property.set('scenarios', [ 0 ])
            end
            # Show all tasks, sorted by seqno-up.
            @property.set('hideTask', LogicalExpression.new(LogicalOperation.new(0)))
            @property.set('sortTasks', [ [ 'seqno', true, -1 ] ])
            # Show all resources, sorted by seqno-up.
            @property.set('hideResource',
                          LogicalExpression.new(LogicalOperation.new(0)))
            @property.set('sortResources', [ [ 'seqno', true, -1 ] ])
            # Show all journal entries.
            @property.set('hideJournalEntry',
                          LogicalExpression.new(LogicalOperation.new(0)))
          end
        })
        arg(1, 'file name', <<'EOT'
    The name of the report file to generate without an extension.  Use . to use
    the standard output channel.
    EOT
           )
      end
    
      def rule_idOrAbsoluteId
        singlePattern('$ID')
        singlePattern('$ABSOLUTE_ID')
      end
    
      def rule_includeAttributes
        optionsRule('includeAttributesBody')
      end
    
      def rule_includeAttributesBody
        optional
        repeatable
    
        pattern(%w( _accountprefix !accountId ), lambda {
          @accountprefix = @val[1].fullId
        })
        doc('accountprefix', <<'EOT'
    This attribute can be used to insert the accounts of the included file as
    sub-account of the account specified by ID. The parent account must already be
    defined.
    EOT
        )
        arg(1, 'account ID', 'The absolute ID of an already defined account')
    
        pattern(%w( _reportprefix !reportId ), lambda {
          @reportprefix = @val[1].fullId
        })
        doc('reportprefix', <<'EOT'
    This attribute can be used to insert the reports of the included file as
    sub-report of the report specified by ID. The parent report must already
    be defined.
    EOT
        )
        arg(1, 'report ID', 'The absolute ID of an already defined report.')
    
        pattern(%w( _resourceprefix !resourceId ), lambda {
          @resourceprefix = @val[1].fullId
        })
        doc('resourceprefix', <<'EOT'
    This attribute can be used to insert the resources of the included file as
    sub-resource of the resource specified by ID. The parent resource must already
    be defined.
    EOT
        )
        arg(1, 'resource ID', 'The ID of an already defined resource')
    
        pattern(%w( _taskprefix !taskId ), lambda {
          @taskprefix = @val[1].fullId
        })
        doc('taskprefix', <<'EOT'
    This attribute can be used to insert the tasks of the included file as
    sub-task of the task specified by ID. The parent task must already be defined.
    EOT
        )
        arg(1, 'task ID', 'The absolute ID of an already defined task.')
      end
    
      def rule_includeFile
        pattern(%w( !includeFileName ), lambda {
          unless @project
            error('include_before_project',
                  "You must declare the project header before you include other " +
                  "files.")
          end
          @project.inputFiles << @scanner.include(@val[0], @sourceFileInfo[0]) do
            popFileStack
          end
        })
      end
    
      def rule_includeFileName
        pattern(%w( $STRING ), lambda {
          unless @val[0][-4, 4] == '.tji'
            error('bad_include_suffix', "Included files must have a '.tji'" +
                                        "extension: '#{@val[0]}'",
                  @sourceFileInfo[0])
          end
          pushFileStack
          @val[0]
        })
        arg(0, 'filename', <<'EOT'
    Name of the file to include. This must have a ''''.tji'''' extension. The name
    may have an absolute or relative path. You need to use ''''/'''' characters to
    separate directories.
    EOT
           )
      end
    
      def rule_includeProperties
        pattern(%w( !includeFileName !includeAttributes ), lambda {
          @project.inputFiles << @scanner.include(@val[0], @sourceFileInfo[0]) do
            popFileStack
          end
        })
      end
    
      def rule_intervalOrDate
        pattern(%w( !date !intervalOptionalEnd ), lambda {
          if @val[1]
            mode = @val[1][0]
            endSpec = @val[1][1]
            if mode == 0
              unless @val[0] < endSpec
                error('start_before_end', "The end date (#{endSpec}) must be " +
                      "after the start date (#{@val[0]}).", @sourceFileInfo[0])
              end
              TimeInterval.new(@val[0], endSpec)
            else
              TimeInterval.new(@val[0], @val[0] + endSpec)
            end
          else
            TimeInterval.new(@val[0], @val[0].sameTimeNextDay)
          end
        })
        doc('interval3', <<'EOT'
    There are three ways to specify a date interval. The first is the most
    obvious. A date interval consists of a start and end DATE. Watch out for end
    dates without a time specification! Date specifications are 0 extended. An
    end date without a time is expanded to midnight that day. So the day of the
    end date is not included in the interval! The start and end dates must be separated by a hyphen character.
    
    In the second form, the end date is omitted. A 24 hour interval is assumed.
    
    The third form specifies the start date and an interval duration. The duration must be prefixed by a plus character.
    EOT
           )
      end
    
      def rule_interval
        pattern(%w( !date !intervalEnd ), lambda {
          mode = @val[1][0]
          endSpec = @val[1][1]
          if mode == 0
            unless @val[0] < endSpec
              error('start_before_end', "The end date (#{endSpec}) must be after " +
                    "the start date (#{@val[0]}).", @sourceFileInfo[0])
            end
            TimeInterval.new(@val[0], endSpec)
          else
            TimeInterval.new(@val[0], @val[0] + endSpec)
          end
        })
        doc('interval2', <<'EOT'
    There are two ways to specify a date interval. The first is the most
    obvious. A date interval consists of a start and end DATE. Watch out for end
    dates without a time specification! Date specifications are 0 extended. An
    end date without a time is expanded to midnight that day. So the day of the
    end date is not included in the interval! The start and end dates must be separated by a hyphen character.
    
    In the second form specifies the start date and an interval duration. The
    duration must be prefixed by a plus character.
    EOT
           )
      end
    
      def rule_intervalDuration
        pattern(%w( !number !durationUnit ), lambda {
          convFactors = [ 60, # minutes
                          60 * 60, # hours
                          60 * 60 * 24, # days
                          60 * 60 * 24 * 7, # weeks
                          60 * 60 * 24 * 30.4167, # months
                          60 * 60 * 24 * 365 # years
                         ]
          if @val[0] == 0.0
            error('zero_duration', "The interval duration may not be 0.",
                  @sourceFileInfo[1])
          end
          duration = (@val[0] * convFactors[@val[1]]).to_i
          resolution = @project.nil? ? 60 * 60 : @project['scheduleGranularity']
          if @val[1] == 4
            # If the duration unit is months, we have to align the duration with
            # the timing resolution of the project.
            duration = (duration / resolution).to_i * resolution
          end
          # Make sure the interval aligns with the timing resolution.
          if duration % resolution != 0
            error('iv_duration_not_aligned',
                  "The interval duration must be a multiple of the specified " +
                  "timing resolution (#{resolution / 60} min) of the project.")
          end
          duration
        })
        arg(0, 'duration', 'The duration of the interval. May not be 0 and must ' +
                           'be a multiple of [[timingresolution]].')
      end
    
      def rule_intervalEnd
        pattern([ '_-', '!date' ], lambda {
          [ 0, @val[1] ]
        })
    
        pattern(%w( _+ !intervalDuration ), lambda {
          [ 1, @val[1] ]
        })
      end
    
      def rule_intervalOptionalEnd
        optional
        pattern([ '_-', '!date' ], lambda {
          [ 0, @val[1] ]
        })
    
        pattern(%w( _+ !intervalDuration ), lambda {
          [ 1, @val[1] ]
        })
      end
    
      def rule_intervals
        listRule('moreIntervals', '!intervalOrDate')
      end
    
      def rule_intervalOptional
        optional
        singlePattern('!interval')
      end
    
      def rule_intervalsOptional
        optional
        singlePattern('!intervals')
      end
    
      def rule_journalReportAttributes
        pattern(%w( _journalattributes !journalReportAttributesList ), lambda {
          @property.set('journalAttributes', @val[1])
        })
        doc('journalattributes', <<'EOT'
    A list that determines which of the journal attributes should be included in
    the journal report.
    EOT
           )
        allOrNothingListRule('journalReportAttributesList',
                             { 'alert' => 'Include the alert status',
                               'author' => 'Include the author if known',
                               'date' => 'Include the date',
                               'details' => 'Include the details',
                               'flags' => 'Include the flags',
                               'headline' => 'Include the headline',
                               'property' => 'Include the task or resource name',
                               'propertyid' => 'Include the property ID. ' +
                                               'Requires \'property\'.',
                               'summary' => 'Include the summary',
                               'timesheet' => 'Include the timesheet information.' +
                                              ' Requires \'property\'.'})
      end
    
      def rule_journalReportMode
        pattern(%w( _journal ), lambda { :journal })
        descr(<<'EOT'
    This is the regular journal. It contains all journal entries that are dated in
    the query interval. If a property is given, only entries of this property are
    included. Without a property context, all the project entries are included
    unless hidden by other attributes like [[hidejournalentry]].
    EOT
           )
        pattern(%w( _journal_sub ), lambda { :journal_sub })
        descr(<<'EOT'
    This mode only yields entries if used in the context of a task. It contains
    all journal entries that are dated in the query interval for the task and all
    its sub tasks.
    EOT
           )
        pattern(%w( _status_dep ), lambda { :status_dep })
        descr(<<'EOT'
    In this mode only the last entries before the report end date for each
    property and all its sub-properties and their dependencies are included. If
    there are multiple entries at the exact same date, then all these entries are
    included.
    EOT
           )
        pattern(%w( _status_down ), lambda { :status_down })
        descr(<<'EOT'
    In this mode only the last entries before the report end date for each
    property and all its sub-properties are included. If there are multiple entries
    at the exact same date, then all these entries are included.
    EOT
           )
        pattern(%w( _status_up ), lambda { :status_up })
        descr(<<'EOT'
    In this mode only the last entries before the report end date for each
    property are included. If there are multiple entries at the exact same date,
    then all these entries are included. If any of the parent properties has a
    more recent entry that is still before the report end date, no entries will be
    included.
    EOT
           )
        pattern(%w( _alerts_dep ), lambda { :alerts_dep })
        descr(<<'EOT'
    In this mode only the last entries before the report end date for the context
    property and all its sub-properties and their dependencies is included. If
    there are multiple entries at the exact same date, then all these entries are
    included. In contrast to the ''''status_down'''' mode, only entries with an
    alert level above the default level, and only those with the highest overall
    alert level are included.
    EOT
           )
        pattern(%w( _alerts_down ), lambda { :alerts_down })
        descr(<<'EOT'
    In this mode only the last entries before the report end date for the context
    property and all its sub-properties is included. If there are multiple entries
    at the exact same date, then all these entries are included. In contrast to
    the ''''status_down'''' mode, only entries with an alert level above the
    default level, and only those with the highest overall alert level are
    included.
    EOT
           )
      end
    
      def rule_journalEntry
        pattern(%w( !journalEntryHeader !journalEntryBody ), lambda {
          @val[0]
        })
        doc('journalentry', <<'EOT'
    This attribute adds an entry to the journal of the project. A journal can be
    used to record events, decisions or news that happened at a particular moment
    during the project. Depending on the context, a journal entry may or may not
    be associated with a specific property or author.
    
    A journal entry can consists of up to three parts. The headline is mandatory
    and should be only 5 to 10 words long. The introduction is optional and should
    be only one or two sentences long. All other details should be put into the
    third part.
    
    Depending on the context, journal entries are listed with headlines only, as
    headlines plus introduction or in full.
    EOT
           )
      end
    
      def rule_journalEntryAttributes
        optional
        repeatable
    
        pattern(%w( _alert !alertLevel ), lambda {
          @journalEntry.alertLevel = @val[1]
        })
        doc('alert', <<'EOT'
    Specify the alert level for this entry. This attribute is inteded to be used for
    status reporting. When used for a journal entry that is associated with a
    property, the value can be reported in the alert column. When multiple entries
    have been specified for the property, the entry with the date closest to the
    report end date will be used. Container properties will inherit the highest
    alert level of all its sub properties unless it has an own journal entry dated
    closer to the report end than all of its sub properties.
    EOT
           )
    
        pattern(%w( !author ))
    
        pattern(%w( _flags !flagList ), lambda {
          @val[1].each do |flag|
            next if @journalEntry.flags.include?(flag)
    
            @journalEntry.flags << flag
          end
        })
        doc('flags.journalentry', <<'EOT'
    Journal entries can have flags attached to them. These can be used to
    include only entries in a report that have a certain flag.
    EOT
           )
    
        pattern(%w( !summary ))
    
        pattern(%w( !details ))
      end
    
      def rule_journalEntryBody
        optionsRule('journalEntryAttributes')
      end
    
      def rule_journalEntryHeader
        pattern(%w( _journalentry !valDate $STRING ), lambda {
          @journalEntry = JournalEntry.new(@project['journal'], @val[1], @val[2],
                                           @property, @sourceFileInfo[0])
        })
        arg(2, 'headline', <<'EOT'
    The headline of the journal entry. It will be interpreted as
    [[Rich_Text_Attributes|Rich Text]].
    EOT
           )
      end
    
      def rule_journalSortCriteria
        pattern(%w( !journalSortCriterium !moreJournalSortCriteria ), lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_journalSortCriterium
        pattern(%w( $ABSOLUTE_ID ), lambda {
          supported = []
          JournalEntryList::SortingAttributes.each do |attr|
            supported << "#{attr}.up"
            supported << "#{attr}.down"
          end
          unless supported.include?(@val[0])
            error('bad_journal_sort_criterium',
                  "Unsupported sorting criterium #{@val[0]}. Must be one of " +
                  "#{supported.join(', ')}.")
          end
          attr, direction = @val[0].split('.')
          [ attr.intern, direction == 'up' ? 1 : -1 ]
        })
      end
    
      def rule_leafResourceId
        pattern(%w( !resourceId ), lambda {
          resource = @val[0]
          unless resource.leaf?
            error('leaf_resource_id_expected',
                  "#{resource.id} is not a leaf resource.", @sourceFileInfo[0])
          end
          resource
        })
        arg(0, 'resource', 'The ID of a leaf resource')
      end
    
      def rule_leave
        pattern(%w( !leaveType !vacationName !intervalOrDate ), lambda {
          Leave.new(@val[0].intern, @val[2], @val[1])
        })
      end
    
      def rule_leaveList
        listRule('moreLeaveList', '!leave')
      end
    
      def rule_leaveName
        optional
        pattern(%w( $STRING ), lambda {
          @val[0]
        })
        arg(0, 'name', 'An optional name or reason for the leave')
      end
    
      def rule_leaveAllowance
        pattern(%w( _annual !valDate !optionalMinus !workingDuration ), lambda {
          LeaveAllowance.new(:annual, @val[1], (@val[2] ? -1 : 1) * @val[3])
        })
      end
    
      def rule_leaveAllowanceList
        listRule('moreLeaveAllowanceList', '!leaveAllowance')
      end
    
      def rule_leaveAllowances
        pattern(%w( _leaveallowances !leaveAllowanceList ), lambda {
          appendScListAttribute('leaveallowances', @val[1])
        })
        doc('leaveallowance', <<'EOT'
    Add or subtract leave allowances. Currently, only allowances for the annual
    leaves are supported. Allowances can be negative to deal with expired
    allowances. The ''''leaveallowancebalance'''' report [[columns|column]] can be
    used to report the current annual leave balance.
    
    Leaves outside of the project period are silently ignored and will not be
    considered in the leave balance calculation. Therefor, leave allowances are
    only allowed within the project period.
    EOT
          )
        level(:beta)
        example('Leave')
      end
    
      def rule_leaves
        pattern(%w( _leaves !leaveList ), lambda {
          LeaveList.new(@val[1])
        })
        doc('leaves', <<'EOT'
    Describe a list of leave periods. A leave can be due to a public holiday,
    personal or sick leave. At global scope, the leaves determine which day is
    considered a working day. Subsequent resource definitions will inherit the
    leave list.
    
    Leaves can be defined at global level, at resource level and at shift level
    and intervals may overlap. The leave types have different priorities. A higher
    priority leave type can overwrite a lower priority type. This means that
    resource level leaves can overwrite global leaves when they have a higher
    priority. A sub resource can overwrite a leave of a enclosing resource.
    
    Leave periods outside of the project interval are silently ignored. For leave
    periods that are partially outside of the project period only the part inside
    the project period will be considered.
    EOT
           )
        example('Leave')
      end
    
      def rule_leaveType
        singlePattern('_project')
        descr('Assignment to another project (lowest priority)')
    
        singlePattern('_annual')
        descr('Personal leave based on annual allowance')
    
        singlePattern('_special')
        descr('Personal leave based on a special occasion')
    
        singlePattern('_sick')
        descr('Sick leave')
    
        singlePattern('_unpaid')
        descr('Unpaid leave')
    
        singlePattern('_holiday')
        descr('Public or bank holiday (highest priority)')
      end
    
      def rule_limitAttributes
        optionsRule('limitAttributesBody')
      end
    
      def rule_limitAttributesBody
        optional
        repeatable
    
        pattern(%w( _end !valDate ), lambda {
          @limitInterval.end = @val[1]
        })
        doc('end.limit', <<'EOT'
    The end date of the limit interval. It must be within the project time frame.
    EOT
        )
    
        pattern(%w( _period !valInterval ), lambda {
          @limitInterval = ScoreboardInterval.new(@project['start'],
                                                  @project['scheduleGranularity'],
                                                  @val[1].start, @val[1].end)
        })
        doc('period.limit', <<'EOT'
    This property is a shortcut for setting the start and end dates of the limit
    interval. Both dates must be within the project time frame.
    EOT
           )
    
        pattern(%w( _resources !resourceLeafList ), lambda {
          @limitResources = @val[1]
        })
        doc('resources.limit', <<'EOT'
    When [[limits]] are used in a [[task]] context, the limits can be restricted
    to a list of resources that are allocated to the task. In that case each
    listed resource will not be allocated more than the specified upper limit.
    Lower limits have no impact on the scheduler but do generate a warning when
    not met.  All specified resources must be leaf resources.
    EOT
           )
        example('Limits-1', '5')
    
        pattern(%w( _start !valDate ), lambda {
          @limitInterval.start = @val[1]
        })
        doc('start.limit', <<'EOT'
    The start date of the limit interval. It must be within the project time frame.
    EOT
        )
      end
    
      def rule_limitValue
        pattern([ '!workingDuration' ], lambda {
          @limitInterval = ScoreboardInterval.new(@project['start'],
                                                  @project['scheduleGranularity'],
                                                  @project['start'], @project['end'])
          @limitResources = []
          @val[0]
        })
      end
    
      def rule_limits
        pattern(%w( !limitsHeader !limitsBody ), lambda {
          @val[0]
        })
      end
    
      def rule_limitsAttributes
        optional
        repeatable
    
        pattern(%w( _dailymax !limitValue !limitAttributes), lambda {
          setLimit(@val[0], @val[1], @limitInterval)
        })
        doc('dailymax', <<'EOT'
    Set a maximum limit for each calendar day.
    EOT
           )
        example('Limits-1', '1')
    
        pattern(%w( _dailymin !limitValue !limitAttributes), lambda {
          setLimit(@val[0], @val[1], @limitInterval)
        })
        doc('dailymin', <<'EOT'
    Minimum required effort for any calendar day. This value cannot be guaranteed by
    the scheduler. It is only checked after the schedule is complete. In case the
    minium required amount has not been reached, a warning will be generated.
    EOT
           )
        example('Limits-1', '4')
    
        pattern(%w( _maximum !limitValue !limitAttributes), lambda {
          setLimit(@val[0], @val[1], @limitInterval)
        })
        doc('maximum', <<'EOT'
    Set a maximum limit for the specified period. You must ensure that the overall
    effort can be achieved!
    EOT
           )
    
        pattern(%w( _minimum !limitValue !limitAttributes), lambda {
          setLimit(@val[0], @val[1], @limitInterval)
        })
        doc('minimum', <<'EOT'
    Set a minim limit for each calendar month. This will only result in a warning
    if not met.
    EOT
           )
    
        pattern(%w( _monthlymax !limitValue !limitAttributes), lambda {
          setLimit(@val[0], @val[1], @limitInterval)
        })
        doc('monthlymax', <<'EOT'
    Set a maximum limit for each calendar month.
    EOT
           )
    
        pattern(%w( _monthlymin !limitValue !limitAttributes), lambda {
          setLimit(@val[0], @val[1], @limitInterval)
        })
        doc('monthlymin', <<'EOT'
    Minimum required effort for any calendar month. This value cannot be
    guaranteed by the scheduler. It is only checked after the schedule is
    complete. In case the minium required amount has not been reached, a warning
    will be generated.
    EOT
           )
    
        pattern(%w( _weeklymax !limitValue !limitAttributes), lambda {
          setLimit(@val[0], @val[1], @limitInterval)
        })
        doc('weeklymax', <<'EOT'
    Set a maximum limit for each calendar week.
    EOT
           )
    
        pattern(%w( _weeklymin !limitValue !limitAttributes), lambda {
          setLimit(@val[0], @val[1], @limitInterval)
        })
        doc('weeklymin', <<'EOT'
    Minimum required effort for any calendar week. This value cannot be guaranteed by
    the scheduler. It is only checked after the schedule is complete. In case the
    minium required amount has not been reached, a warning will be generated.
    EOT
           )
      end
    
      def rule_limitsBody
        optionsRule('limitsAttributes')
      end
    
      def rule_limitsHeader
        pattern(%w( _limits ), lambda {
          @limits = Limits.new
          @limits.setProject(@project)
          @limits
        })
      end
    
      def rule_listOfDays
        pattern(%w( !weekDayInterval !moreListOfDays), lambda {
          weekDays = Array.new(7, false)
          ([ @val[0] ] + (@val[1] ? @val[1] : [])).each do |dayList|
            7.times { |i| weekDays[i] = true if dayList[i] }
          end
          weekDays
        })
      end
    
      def rule_listOfTimes
        pattern(%w( _off ), lambda {
          [ ]
        })
        pattern(%w( !timeInterval !moreTimeIntervals ), lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_listType
        pattern([ '_bullets' ], lambda { :bullets })
        descr('List items as bullet list')
    
        pattern([ '_comma' ], lambda { :comma })
        descr('List items as comma separated list')
    
        pattern([ '_numbered' ], lambda { :numbered })
        descr('List items as numbered list')
      end
    
      def rule_loadunit
        pattern(%w( _loadunit !loadunitName ), lambda {
          @property.set('loadUnit', @val[1])
        })
        doc('loadunit', <<'EOT'
    Determines what unit should be used to display all load values in this report.
    EOT
           )
      end
    
      def rule_loadunitName
        pattern([ '_days' ], lambda { :days })
        descr('Display all load and duration values as days.')
    
        pattern([ '_hours' ], lambda { :hours })
        descr('Display all load and duration values as hours.')
    
        pattern([ '_longauto'] , lambda { :longauto })
        descr(<<'EOT'
    Automatically select the unit that produces the shortest and most readable
    value. The unit name will not be abbreviated. It will not use quarters since
    it is not common.
    EOT
             )
    
        pattern([ '_minutes' ], lambda { :minutes })
        descr('Display all load and duration values as minutes.')
    
        pattern([ '_months' ], lambda { :months })
        descr('Display all load and duration values as months.')
    
        pattern([ '_quarters' ], lambda { :quarters })
        descr('Display all load and duration values as quarters.')
    
        pattern([ '_shortauto' ], lambda { :shortauto })
        descr(<<'EOT'
    Automatically select the unit that produces the shortest and most readable
    value. The unit name will be abbreviated. It will not use quarters since it is
    not common.
    EOT
             )
    
        pattern([ '_weeks' ], lambda { :weeks })
        descr('Display all load and duration values as weeks.')
    
        pattern([ '_years' ], lambda { :years })
        descr('Display all load and duration values as years.')
      end
    
      def rule_logicalExpression
        pattern(%w( !operation ), lambda {
          LogicalExpression.new(@val[0], sourceFileInfo)
        })
        pattern(%w( _@ !allOrNone ), lambda {
          LogicalExpression.new(LogicalOperation.new(@val[1]), sourceFileInfo)
        })
        doc('logicalexpression', <<'EOT'
    A logical expression is a combination of operands and mathematical operations.
    The final result of a logical expression is always true or false. Logical
    expressions are used the reduce the properties in a report to a certain subset
    or to select alternatives for the cell content of a table. When used with
    attributes like [[hidetask]] or [[hideresource]] the logical expression
    evaluates to true for a certain property, this property is hidden or rolled-up
    in the report.
    
    Operands can be previously declared flags, built-in [[functions]], property
    attributes (specified as scenario.attribute) or another logical expression.
    When you combine logical operations to a more complex expression, the
    operators are evaluated from left to right. ''''a | b & c'''' is identical to
    ''''(a | b) & c''''. It's highly recommended that you always use brackets to
    control the evaluation sequence. Currently, TaskJuggler does not support the
    concept of operator precedence or right-left associativity. This may change in
    the future.
    
    An operand can also be just a number. 0 evaluates to false, all other numbers
    to true. The logical expression can also be the special constants ''''@all''''
    or ''''@none''''. The first always evaluates to true, the latter to false.
    
    Date attributes needs special attention. Attributes like [[maxend]] can
    be undefined. To use such an attribute in a comparison, you need to test for
    the validity first. E. g. to compare the end date of the ''''plan''''
    scenario with the ''''maxend'''' value use ''''isvalid(plan.maxend) &
    (plan.end > plan.maxend)''''. The ''''&'''' and ''''|'''' operators are lazy.
    If the result is already known after evaluation the first operand, the second
    operand will not be evaluated any more.
    EOT
           )
        also(%w( functions ))
        example('LogicalExpression', '1')
      end
    
      def rule_macro
        pattern(%w( _macro $ID $MACRO ), lambda {
          if @scanner.macroDefined?(@val[1])
            warning('marco_redefinition', "Redefining macro #{@val[1]}")
          end
          @scanner.addMacro(TextParser::Macro.new(@val[1], @val[2],
                                                  @sourceFileInfo[0]))
        })
        doc('macro', <<'EOT'
    Defines a text fragment that can later be inserted by using the specified ID.
    To insert the text fragment anywhere in the text you need to write ${ID}.The
    body is not optional. It must be enclosed in square brackets. Macros can be
    declared like this:
    
     macro FOO [ This text ]
    
    If later ''''${FOO}'''' is found in the project file, it is expanded to
    ''''This text''''.
    
    Macros may have arguments. Arguments are accessed with special macros with
    numbers as names.  The number specifies the index of the argument.
    
     macro FOO [ This ${1} text ]
    
    will expand to ''''This stupid text'''' if called as ''''${FOO "stupid"}''''.
    Macros may call other macros. All macro arguments must be enclosed by double
    quotes. In case the argument contains a double quote, it must be escaped by a
    slash (''''/'''').
    
    User defined macro IDs must have at least one uppercase letter as all
    lowercase letter IDs are reserved for built-in macros.
    
    To terminate the macro definition, the '''']'''' must be the
    last character in the line. If there are any other characters trailing it
    (even spaces or comments) the '''']'''' will not be
    considered the end of the macro definition.
    
    In macro calls the macro names can be prefixed by a question mark. In this
    case the macro will expand to nothing if the macro is not defined. Otherwise
    the undefined macro would be flagged with an error message.
    
    The macro call
    
     ${?foo}
    
    will expand to nothing if foo is undefined.
    EOT
           )
        example('Macro-1')
      end
    
      def rule_moreBangs
        optional
        repeatable
        singlePattern('_!')
      end
    
      def rule_moreAlternatives
        commaListRule('!resourceId')
      end
    
      def rule_moreArguments
        commaListRule('!argument')
      end
    
      def rule_moreChargeSetItems
        commaListRule('!chargeSetItem')
      end
    
      def rule_moreColumnDef
        commaListRule('!columnDef')
      end
    
      def rule_moreDepTasks
        commaListRule('!taskDep')
      end
    
      def rule_moreExportFormats
        commaListRule('!exportFormat')
      end
    
      def rule_moreJournalSortCriteria
        commaListRule('!journalSortCriterium')
      end
    
      def rule_moreListOfDays
        commaListRule('!weekDayInterval')
      end
    
      def rule_moreOutputFormats
        commaListRule('!outputFormat')
      end
    
      def rule_moreProjectIDs
        commaListRule('$ID')
      end
    
      def rule_morePredTasks
        commaListRule('!taskPred')
      end
    
      def rule_moreSortCriteria
        commaListRule('!sortNonTree')
      end
    
      def rule_moreTimeIntervals
        commaListRule('!timeInterval')
      end
    
      def rule_navigator
        pattern(%w( !navigatorHeader !navigatorBody ), lambda {
          @project['navigators'][@navigator.id] = @navigator
        })
        doc('navigator', <<'EOT'
    Defines a navigator object with the specified ID. This object can be used in
    reports to include a navigation bar with references to other reports.
    EOT
              )
        example('navigator')
      end
    
      def rule_navigatorAttributes
        optional
        repeatable
        pattern(%w( _hidereport !logicalExpression ), lambda {
          @navigator.hideReport = @val[1]
        })
        doc('hidereport', <<'EOT'
    This attribute can be used to exclude the reports that match the specified
    [[logicalexpression|logical expression]] from the navigation bar.
    EOT
              )
      end
    
      def rule_navigatorBody
        optional
        pattern(%w( _{ !navigatorAttributes _} ))
      end
    
      def rule_navigatorHeader
        pattern(%w( _navigator $ID ), lambda {
          if @project['navigators'][@val[1]]
            error('navigator_exists',
                  "The navigator #{@val[1]} has already been defined.",
                  @sourceFileInfo[0])
          end
          @navigator = Navigator.new(@val[1], @project)
        })
      end
    
      def rule_nikuReportAttributes
        optional
        repeatable
    
        pattern(%w( !formats ))
        pattern(%w( !headline ))
        pattern(%w( !hideresource ))
        pattern(%w( !hidetask ))
    
        pattern(%w( !numberFormat ), lambda {
          @property.set('numberFormat', @val[0])
        })
    
        pattern(%w( !reportEnd ))
        pattern(%w( !reportPeriod ))
        pattern(%w( !reportStart ))
        pattern(%w( !reportTitle ))
    
        pattern(%w( _timeoff $STRING $STRING ), lambda {
          @property.set('timeOffId', @val[1])
          @property.set('timeOffName', @val[2])
        })
        doc('timeoff.nikureport', < 1
            error('operand_attribute',
                  'Attributes must be specified as .',
                  @sourceFileInfo[0])
          end
          scenario, attribute = @val[0].split('.')
          if (scenarioIdx = @project.scenarioIdx(scenario)).nil?
            error('operand_unkn_scen', "Unknown scenario ID #{scenario}",
                  @sourceFileInfo[0])
          end
          # TODO: Do at least some basic sanity checks of the attribute is valid.
          LogicalAttribute.new(attribute, @project.scenario(scenarioIdx))
        })
        pattern(%w( !date ), lambda {
          LogicalOperation.new(@val[0])
        })
        pattern(%w( $ID !argumentList ), lambda {
          if @val[1].nil?
            unless @project['flags'].include?(@val[0])
              error('operand_unkn_flag', "Undeclared flag '#{@val[0]}'",
                    @sourceFileInfo[0])
            end
            LogicalFlag.new(@val[0])
          else
            func = LogicalFunction.new(@val[0])
            res = func.setArgumentsAndCheck(@val[1])
            unless res.nil?
              error(*res)
            end
            func
          end
        })
        pattern(%w( $INTEGER ), lambda {
          LogicalOperation.new(@val[0])
        })
        pattern(%w( $FLOAT ), lambda {
          LogicalOperation.new(@val[0])
        })
        pattern(%w( $STRING ), lambda {
          LogicalOperation.new(@val[0])
        })
      end
    
      def rule_operation
        pattern(%w( !operand !operationChain ), lambda {
          operation = LogicalOperation.new(@val[0])
          if @val[1]
            # Further operators/operands create an operation tree.
            @val[1].each do |ops|
              operation = LogicalOperation.new(operation)
              operation.operator = ops[0]
              operation.operand2 = ops[1]
            end
          end
          operation
        })
        arg(0, 'operand', <<'EOT'
    An operand can consist of a date, a text string, a [[functions|function]], a
    property attribute or a numerical value. It can also be the name of a declared
    flag.  Use the ''''scenario_id.attribute'''' notation to use an attribute of
    the currently evaluated property. The scenario ID always has to be specified,
    also for non-scenario specific attributes. This is necessary to distinguish
    them from flags. See [[columnid]] for a list of available attributes. The use
    of list attributes is not recommended. User defined attributes are available
    as well.
    
    An operand can be a negated operand by prefixing a ~ charater or it can be
    another logical expression enclosed in braces.
    EOT
            )
      end
    
      def rule_operationChain
        optional
        repeatable
        pattern(%w( !operatorAndOperand), lambda {
          @val[0]
        })
      end
    
      def rule_operatorAndOperand
        pattern(%w( !operator !operand), lambda{
          [ @val[0], @val[1] ]
        })
        arg(1, 'operand', <<'EOT'
    An operand can consist of a date, a text string or a numerical value. It can also be the name of a declared flag. Finally, an operand can be a negated operand by prefixing a ~ charater or it can be another operation enclosed in braces.
    EOT
            )
      end
    
      def rule_operator
        singlePattern('_|')
        descr('The \'or\' operator')
    
        singlePattern('_&')
        descr('The \'and\' operator')
    
        singlePattern('_>')
        descr('The \'greater than\' operator')
    
        singlePattern('_<')
        descr('The \'smaller than\' operator')
    
        singlePattern('_=')
        descr('The \'equal\' operator')
    
        singlePattern('_>=')
        descr('The \'greater-or-equal\' operator')
    
        singlePattern('_<=')
        descr('The \'smaller-or-equal\' operator')
    
        singlePattern('_!=')
        descr('The \'not-equal\' operator')
      end
    
      def rule_optionalID
        optional
        pattern(%w( $ID ), lambda {
          @val[0]
        })
        arg(0, 'id', <<"EOT"
    An optional ID. If you ever want to reference this property, you must specify
    your own unique ID. If no ID is specified one will be automatically generated.
    These IDs may become visible in reports, but may change at any time. You may
    never rely on automatically generated IDs.
    EOT
           )
      end
    
      def rule_optionalMinus
        optional
        pattern(%w( _- ), lambda {
          true
        })
      end
    
      def rule_optionalPercent
        optional
        pattern(%w( !number _% ), lambda {
          @val[0] / 100.0
        })
      end
      def rule_optionalScenarioIdCol
        optional
        pattern(%w( $ID_WITH_COLON ), lambda {
          if (@scenarioIdx = @project.scenarioIdx(@val[0])).nil?
            error('unknown_scenario_id', "Unknown scenario: #{@val[0]}",
                  @sourceFileInfo[0])
          end
          @scenarioIdx
        })
      end
    
    
      def rule_optionalVersion
        optional
        pattern(%w( $STRING ), lambda {
          @val[0]
        })
        arg(0, 'version', <<"EOT"
    An optional version ID. This can be something simple as "4.2" or an ID tag of
    a revision control system. If not specified, it defaults to "1.0".
    EOT
           )
      end
    
      def rule_outputFormat
        pattern(%w( _csv ), lambda {
          :csv
        })
        descr(<<'EOT'
    The report lists the resources and their respective values as
    colon-separated-value (CSV) format. Due to the very simple nature of the CSV
    format, only a small subset of features will be supported for CSV output.
    Including tasks or listing multiple scenarios will result in very difficult to
    read reports.
    EOT
             )
    
        pattern(%w( _html ), lambda {
          :html
        })
        descr('Generate a web page (HTML file)')
    
        pattern(%w( _niku ), lambda {
          :niku
        })
        descr('Generate a XOG XML file to be used with Clarity.')
      end
    
      def rule_outputFormats
        pattern(%w( !outputFormat !moreOutputFormats ), lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_plusOrMinus
        singlePattern('_+')
        singlePattern('_-')
      end
    
      def rule_project
        pattern(%w( !projectProlog !projectDeclaration !properties . ), lambda {
          @val[1]
        })
      end
    
      def rule_projectBody
        optionsRule('projectBodyAttributes')
      end
    
      def rule_projectBodyAttributes
        repeatable
        optional
    
        pattern(%w( _alertlevels !alertLevelDefinitions ), lambda {
          if @val[1].length < 2
            error('too_few_alert_levels',
                  'You must specify at least 2 different alert levels.',
                  @sourceFileInfo[1])
          end
          levels = @project['alertLevels']
          levels.clear
          @val[1].each do |level|
            if levels.indexById(level[0])
              error('alert_level_redef',
                    "Alert level '#{level[0]}' has been defined multiple times.",
                    @sourceFileInfo[1])
            end
    
            if levels.indexByName(level[1])
              error('alert_name_redef',
                    "Alert level name '#{level[1]}' has been defined multiple " +
                    "times.", @sourceFileInfo[1])
            end
    
            @project['alertLevels'].add(AlertLevelDefinition.new(*level))
          end
        })
        level(:beta)
        doc('alertlevels', <<'EOT'
    By default TaskJuggler supports the pre-defined alert levels: green, yellow
    and red. This attribute can be used to replace them with your own set of alert
    levels. You can define any number of levels, but you need to define at least
    two and they must be specified in ascending order from the least severity to
    highest severity. Additionally, you need to provide a 15x15 pixel image file
    with the name ''''flag-X.png'''' for each level where ''''X'''' matches the ID
    of the alert level. These files need to be in the ''''icons'''' directory to
    be found by the browser when showing HTML reports.
    EOT
           )
        example('AlertLevels')
    
        pattern(%w( !currencyFormat ), lambda {
          @project['currencyFormat'] = @val[0]
        })
    
        pattern(%w( _currency $STRING ), lambda {
          @project['currency'] = @val[1]
        })
        doc('currency', 'The default currency unit.')
        example('Account')
        arg(1, 'symbol', 'Currency symbol')
    
        pattern(%w( _dailyworkinghours !number ), lambda {
          @project['dailyworkinghours'] = @val[1]
        })
        doc('dailyworkinghours', <<'EOT'
    Set the average number of working hours per day. This is used as
    the base to convert working hours into working days. This affects
    for example the length task attribute. The default value is 8 hours
    and should work for most Western countries. The value you specify should match
    the settings you specified as your default [[workinghours.project|working
    hours]].
    EOT
           )
        example('Project')
        arg(1, 'hours', 'Average number of working hours per working day')
    
        pattern(%w( _extend !extendProperty !extendBody ), lambda {
          updateParserTables
        })
        doc('extend', <<'EOT'
    Often it is desirable to collect more information in the project file than is
    necessary for task scheduling and resource allocation. To add such information
    to tasks, resources or accounts the user can extend these properties with
    user-defined attributes. The new attributes can be of various types such as
    text, date or reference to capture various types of data. Optionally the user
    can specify if the attribute value should be inherited from the enclosing
    property.
    EOT
           )
        example('CustomAttributes')
    
        pattern(%w( !projectBodyInclude ))
    
        pattern(%w( !journalEntry ))
    
        pattern(%w( _now !date ), lambda {
          @project['now'] = @val[1]
          @scanner.addMacro(TextParser::Macro.new('now', @val[1].to_s,
                                                  @sourceFileInfo[0]))
          @scanner.addMacro(TextParser::Macro.new(
            'today', @val[1].to_s(@project['timeFormat']), @sourceFileInfo[0]))
        })
        doc('now', <<'EOT'
    Specify the date that TaskJuggler uses for calculation as current
    date. If no value is specified, the current value of the system
    clock is used.
    EOT
           )
        arg(1, 'date', 'Alternative date to be used as current date for all ' +
            'computations')
    
        pattern(%w( !numberFormat ), lambda {
          @project['numberFormat'] = @val[0]
        })
    
        pattern(%w( _outputdir $STRING ), lambda {
          # Directory name must be terminated by a slash.
          if @val[1].empty?
            error('outdir_empty', 'Output directory may not be empty.')
          end
          if !File.directory?(@val[1])
            error('outdir_missing',
                  "Output directory '#{@val[1]}' does not exist or is not " +
                  "a directory!")
          end
          @project.outputDir = @val[1] + (@val[1][-1] == ?/ ? '' : '/')
        })
        doc('outputdir',
            'Specifies the directory into which the reports should be generated. ' +
            'This will not affect reports whos name start with a slash. This ' +
            'setting can be overwritten by the command line option.')
        arg(1, 'directory', 'Path to an existing directory')
    
        pattern(%w( !scenario ))
        pattern(%w( _shorttimeformat $STRING ), lambda {
          @project['shortTimeFormat'] = @val[1]
        })
        doc('shorttimeformat',
            'Specifies time format for time short specifications. This is normal' +
            'just the hour and minutes.')
        arg(1, 'format', 'strftime like format string')
    
        pattern(%w( !timeformat ), lambda {
          @project['timeFormat'] = @val[0]
        })
    
        pattern(%w( !timezone ), lambda {
          @val[0]
        })
    
        pattern(%w( _timingresolution $INTEGER _min ), lambda {
          goodValues = [ 5, 10, 15, 20, 30, 60 ]
          unless goodValues.include?(@val[1])
            error('bad_timing_res',
                  "Timing resolution must be one of #{goodValues.join(', ')} min.",
                  @sourceFileInfo[1])
          end
          if @val[1] > (Project.maxScheduleGranularity / 60)
            error('too_large_timing_res',
                  'The maximum allowed timing resolution for the timezone is ' +
                  "#{Project.maxScheduleGranularity / 60} minutes.",
                  @sourceFileInfo[1])
          end
          @project['scheduleGranularity'] = @val[1] * 60
        })
        doc('timingresolution', <<'EOT'
    Sets the minimum timing resolution. The smaller the value, the longer the
    scheduling process lasts and the more memory the application needs. The
    default and maximum value is 1 hour. The smallest value is 5 min.
    This value is a pretty fundamental setting of TaskJuggler. It has a severe
    impact on memory usage and scheduling performance. You should set this value
    to the minimum required resolution. Make sure that all values that you specify
    are aligned with the resolution.
    
    Changing the timing resolution will reset the [[workinghours.project|working
    hours]] to the default times. It's recommended that this is the very first
    option in the project header section.
    
    Do not use this option after you've set the time zone!
    EOT
            )
    
        pattern(%w( _trackingscenario !scenarioId ), lambda {
          @project['trackingScenarioIdx'] = @val[1]
          # The tracking scenario and all child scenarios will always be scheduled
          # in projection mode.
          @project.scenario(@val[1]).all.each do |scenario|
            scenario.set('projection', true)
          end
        })
        doc('trackingscenario', <<'EOT'
    Specifies which scenario is used to capture what actually has happened with
    the project. All sub-scenarios of this scenario inherit the bookings of the
    tracking scenario and may not have any bookings of their own. The tracking
    scenario must also be specified to use time and status sheet reports.
    
    The tracking scenario must be defined after all scenario have been defined.
    
    The tracking scenario and all scenarios derived from it will be scheduled in
    projection mode. This means that the scheduler will only add bookings after
    the current date or the date specified by [[now]]. It is assumed that all
    allocations prior to this date have been provided as [[booking.task|
    task bookings]] or [[booking.resource|resource bookings]].
    EOT
           )
        example('TimeSheet1', '2')
    
        pattern(%w( _weekstartsmonday ), lambda {
          @project['weekStartsMonday'] = true
        })
        doc('weekstartsmonday',
            'Specify that you want to base all week calculation on weeks ' +
            'starting on Monday. This is common in many European countries.')
    
        pattern(%w( _weekstartssunday ), lambda {
          @project['weekStartsMonday'] = false
        })
        doc('weekstartssunday',
            'Specify that you want to base all week calculation on weeks ' +
            'starting on Sunday. This is common in the United States of America.')
    
        pattern(%w( !workinghoursProject ))
        pattern(%w( _yearlyworkingdays !number ), lambda {
          @project['yearlyworkingdays'] = @val[1]
        })
        doc('yearlyworkingdays', <<'EOT'
    Specifies the number of average working days per year. This should correlate
    to the specified workinghours and vacation. It affects the conversion of
    working hours, working days, working weeks, working months and working years
    into each other.
    
    When public holidays and leaves are disregarded, this value should be equal
    to the number of working days per week times 52.1428 (the average number of
    weeks per year). E. g. for a culture with 5 working days it is 260.714 (the
    default), for 6 working days it is 312.8568 and for 7 working days it is
    365.
    EOT
           )
        arg(1, 'days', 'Number of average working days for a year')
      end
    
      def rule_projectDeclaration
        pattern(%w( !projectHeader !projectBody ), lambda {
          # If the user has specified a tracking scenario, we mark all children of
          # that scenario to disallow own bookings. These scenarios will inherit
          # their bookings from the tracking scenario.
          if (idx = @project['trackingScenarioIdx'])
            @project.scenario(idx).allLeaves(true).each do |scenario|
              scenario.set('ownbookings', false)
            end
          end
          @val[0]
        })
        doc('project', <<'EOT'
    The project property is mandatory and should be the first property
    in a project file. It is used to capture basic attributes such as
    the project id, name and the expected time frame.
    
    Be aware that the dates for the project period default to UTC times. See [[interval2]] for details.
    EOT
           )
      end
    
      def rule_projectHeader
        pattern(%w( _project !optionalID $STRING !optionalVersion !interval ), lambda {
          @project = Project.new(@val[1], @val[2], @val[3])
          @project['start'] = @val[4].start
          @project['end'] = @val[4].end
          @projectId = @val[1]
          setGlobalMacros
          @property = nil
          @reportCounter = 0
          @project
        })
        arg(2, 'name', 'The name of the project')
      end
    
      def rule_projectIDs
        pattern(%w( $ID !moreProjectIDs ), lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_projection
        optionsRule('projectionAttributes')
      end
    
      def rule_projectionAttributes
        optional
        repeatable
        pattern(%w( _sloppy ))
        level(:deprecated)
        also('trackingscenario')
        doc('sloppy.projection', '')
    
        pattern(%w( _strict ), lambda {
          warning('projection_strict',
                  'The strict mode is now always used.')
        })
        level(:deprecated)
        also('trackingscenario')
        doc('strict.projection', '')
      end
    
      def rule_projectProlog
        optional
        repeatable
        pattern(%w( !prologInclude ))
        pattern(%w( !macro ))
      end
    
      def rule_projectProperties
        # This rule is not defining actual syntax. It's only used for the
        # documentation.
        pattern(%w( !projectPropertiesBody ))
        doc('properties', <<'EOT'
    The project properties. Every project must consists of at least one task. The other properties are optional. To save the scheduled data at least one output generating property should be used.
    EOT
           )
      end
    
      def rule_projectPropertiesBody
        # This rule is not defining actual syntax. It's only used for the
        # documentation.
        optionsRule('properties')
      end
    
      def rule_projectBodyInclude
        pattern(%w( _include !includeFile !projectBodyAttributes . ))
        lastSyntaxToken(1)
        doc('include.project', <<'EOT'
    Includes the specified file name as if its contents would be written
    instead of the include property. When the included files contains other
    include statements or report definitions, the filenames are relative to file
    where they are defined in.
    
    This version of the include directive may only be used inside the [[project]]
    header section. The included files must only contain content that may be
    present in a project header section.
    EOT
           )
      end
    
      def rule_prologInclude
        pattern(%w( _include !includeFile !projectProlog . ))
        lastSyntaxToken(1)
        doc('include.macro', <<'EOT'
    Includes the specified file name as if its contents would be written
    instead of the include property. The only exception is the include
    statement itself. When the included files contains other include
    statements or report definitions, the filenames are relative to file
    where they are defined in.
    
    The included file may only contain macro definitions. This version of the
    include directive can only be used before the [[project]] header.
    EOT
           )
      end
    
      def rule_properties
        pattern(%w( !propertiesBody ))
      end
    
      def rule_propertiesBody
        repeatable
        optional
    
        pattern(%w( !account ))
    
        pattern(%w( _auxdir $STRING ), lambda {
          auxdir = @val[1]
          # Ensure that the directory always ends with a '/'.
          auxdir += '/' unless auxdir[-1] == ?/
          @project['auxdir'] = auxdir
        })
        level(:beta)
        doc('auxdir', <<'EOT'
    Specifies an alternative directory for the auxiliary report files such as CSS,
    JavaScript and icon files. This setting will affect all subsequent report
    definitions unless it gets overridden. If this attribute is not set, the
    directory and its contents will be generated automatically. If this attribute
    is provided, the user has to ensure that the directory exists and is filled
    with the proper data. The specified path can be absolute or relative to the
    generated report file.
    EOT
           )
    
        pattern(%w( _copyright $STRING ), lambda {
          @project['copyright'] = @val[1]
        })
        doc('copyright', <<'EOT'
    Set a copyright notice for the project file and its content. This copyright notice will be added to all reports that can support it.
    EOT
           )
        example('Caption', '2')
    
        pattern(%w( !balance ), lambda {
          @project['costaccount'] = @val[0][0]
          @project['revenueaccount'] = @val[0][1]
        })
    
        pattern(%w( _flags !declareFlagList ), lambda {
          unless @project['flags'].include?(@val[1])
            @project['flags'] += @val[1]
          end
        })
        doc('flags', <<'EOT'
    Declare one or more flag for later use. Flags can be used to mark tasks, resources or other properties to filter them in reports.
    EOT
           )
    
        pattern(%w( !propertiesInclude ))
    
        pattern(%w( !leaves ), lambda {
          @val[0].each do |v|
            @project['leaves'] << v
          end
        })
    
        pattern(%w( !limits ), lambda {
          @project['limits'] = @val[0]
        })
        doc('limits', <<'EOT'
    Set per-interval allocation limits for the following resource definitions.
    The limits can be overwritten in each resource definition and the global
    limits can be changed later.
    EOT
           )
    
        pattern(%w( !macro ))
    
        pattern(%w( !navigator ))
    
        pattern(%w( _projectid $ID ), lambda {
          @project['projectids'] << @val[1]
          @project['projectids'].uniq!
          @project['projectid'] = @projectId = @val[1]
        })
        doc('projectid', <<'EOT'
    This declares a new project id and activates it. All subsequent
    task definitions will inherit this ID. The tasks of a project can have
    different IDs.  This is particularly helpful if the project is merged from
    several sub projects that each have their own ID.
    EOT
           )
    
        pattern(%w( _projectids !projectIDs ), lambda {
          @project['projectids'] += @val[1]
          @project['projectids'].uniq!
        })
        doc('projectids', <<'EOT'
    Declares a list of project IDs. When an include file that was generated from another project brings different project IDs, these need to be declared first.
    EOT
            )
    
        pattern(%w( _rate !number ), lambda {
          @project['rate'] = @val[1].to_f
        })
        doc('rate', <<'EOT'
    Set the default rate for all subsequently defined resources. The rate describes the daily cost of a resource.
    EOT
            )
    
        pattern(%w( !reportProperties ))
        pattern(%w( !resource ))
        pattern(%w( !shift ))
        pattern(%w( !statusSheet ))
    
        pattern(%w( _supplement !supplement ))
        doc('supplement', <<'EOT'
    The supplement keyword provides a mechanism to add more attributes to already
    defined accounts, tasks or resources. The additional attributes must obey the
    same rules as in regular task or resource definitions and must be enclosed by
    curly braces.
    
    This construct is primarily meant for situations where the information about a
    task or resource is split over several files. E. g. the vacation dates for the
    resources may be in a separate file that was generated by some other tool.
    EOT
           )
        example('Supplement')
    
        pattern(%w( !task ))
        pattern(%w( !timeSheet ))
        pattern(%w( _vacation !vacationName !intervals ), lambda {
          @val[2].each do |interval|
            @project['leaves'] << Leave.new(:holiday, interval)
          end
        })
        doc('vacation', <<'EOT'
    Specify a global vacation period for all subsequently defined resources. A
    vacation can also be used to block out the time before a resource joined or
    after it left. For employees changing their work schedule from full-time to
    part-time, or vice versa, please refer to the 'Shift' property.
    EOT
           )
        arg(1, 'name', 'Name or purpose of the vacation')
      end
    
      def rule_propertiesFile
        pattern(%w( !propertiesBody . ))
      end
    
      def rule_propertiesInclude
        pattern(%w( _include !includeProperties !properties . ), lambda {
        })
        lastSyntaxToken(1)
        doc('include.properties', <<'EOT'
    Includes the specified file name as if its contents would be written
    instead of the include property. The only exception is the include
    statement itself. When the included files contains other include
    statements or report definitions, the filenames are relative to file
    where they are defined in. include commands can be used in the project
    header, at global scope or between property declarations of tasks,
    resources, and accounts.
    
    For technical reasons you have to supply the optional pair of curly
    brackets if the include is followed immediately by a macro call that
    is defined within the included file.
    EOT
           )
      end
    
      def rule_purge
        pattern(%w( _purge !optionalScenarioIdCol $ID ), lambda {
          attrId = @val[2]
          if (attributeDefinition = @property.attributeDefinition(attrId)).nil?
            error('purge_unknown_id',
                  "#{attrId} is not a known attribute for this property",
                  @sourceFileInfo[2])
          end
          if attributeDefinition.scenarioSpecific
            @scenarioIdx = 0 unless @val[1]
            attr = @property[attrId, 0]
          else
            if @val[1]
              error('purge_non_sc_spec_attr',
                    'Scenario specified for a non-scenario specific attribute')
            end
            attr = @property.get(attrId)
          end
          if @property.attributeDefinition(attrId).scenarioSpecific
            @property.getAttribute(attrId, @scenarioIdx).reset
          else
            @property.getAttribute(attrId).reset
          end
        })
        doc('purge', <<'EOT'
    Many attributes inherit their values from the enclosing property or the global
    scope. In certain circumstances, this is not desirable, e. g. for list
    attributes. A list attribute is any attribute that takes a comma separated
    list of values as argument. [[allocate]] and [[flags.task]] are
    good examples of commonly used list attributes. By defining values for
    such a list attribute in a nested property, the new values will be appended to
    the list that was inherited from the enclosing property. The purge
    attribute resets any attribute to its default value. A subsequent definition
    for the attribute within the property will then add their values to an empty
    list. The value of the enclosing property is not affected by purge.
    
    For scenario specific attributes, an optional scenario ID can be specified
    before the attribute ID. If it's missing, the default (first) scenario will be
    used.
    EOT
           )
        arg(1, 'attribute', 'Any name of a list attribute')
      end
    
      def rule_referenceAttributes
        optional
        repeatable
        pattern(%w( _label $STRING ), lambda {
          @val[1]
        })
      end
    
      def rule_referenceBody
        optionsRule('referenceAttributes')
      end
    
      def rule_relativeId
        pattern(%w( _! !moreBangs !idOrAbsoluteId ), lambda {
          str = '!'
          if @val[1]
            @val[1].each { |bang| str += bang }
          end
          str += @val[2]
          str
        })
      end
    
    
      def rule_reports
        pattern(%w( !accountReport ))
        pattern(%w( !export ))
        pattern(%w( !resourceReport ))
        pattern(%w( !taskReport ))
        pattern(%w( !textReport ))
        pattern(%w( !traceReport ))
      end
    
      def rule_reportableAttributes
        singlePattern('_activetasks')
        descr(<<'EOT'
    The number of sub-tasks (including the current task) that are active in the
    reported time period. Active means that they are ongoing at the current time
    or [[now]] date.
    EOT
             )
    
        singlePattern('_annualleave')
        descr(<<'EOT'
    The number of annual leave units within the reported time period. The unit
    can be adjusted with [[loadunit]].
    EOT
             )
    
        singlePattern('_annualleavebalance')
        descr(<<'EOT'
    The balance of the annual leave at the end of the reporting interval. The unit
    can be adjusted with [[loadunit]].
    EOT
             )
    
        singlePattern('_alert')
        descr(<<'EOT'
    The alert level of the property that was reported with the date closest to the
    end date of the report. Container properties that don't have their own alert
    level reported with a date equal or newer than the alert levels of all their
    sub properties will get the highest alert level of their direct sub
    properties.
    EOT
             )
    
        singlePattern('_alertmessages')
        level(:deprecated)
        also('journal')
        descr('Deprecated. Please use ''''journal'''' instead')
    
        singlePattern('_alertsummaries')
        level(:deprecated)
        also('journal')
        descr('Deprecated. Please use ''''journal'''' instead')
    
        singlePattern('_alerttrend')
        descr(<<'EOT'
    Shows how the alert level at the end of the report period compares to the
    alert level at the begining of the report period. Possible values are
    ''''Up'''', ''''Down'''' or ''''Flat''''.
    EOT
             )
    
        singlePattern('_balance')
        descr(<<'EOT'
    The account balance at the beginning of the reported period. This is the
    balance before any transactions of the reported period have been credited.
    EOT
             )
    
        singlePattern('_bsi')
        descr('The hierarchical or work breakdown structure index (i. e. 1.2.3)')
    
        singlePattern('_chart')
        descr(<<'EOT'
    A Gantt chart. This column type requires all lines to have the same fixed
    height. This does not work well with rich text columns in some browsers. Some
    show a scrollbar for the compressed table cells, others don't. It is
    recommended, that you don't use rich text columns in conjuction with the chart
    column.
    EOT
             )
    
        singlePattern('_closedtasks')
        descr(<<'EOT'
    The number of sub-tasks (including the current task) that have been closed
    during the reported time period.  Closed means that they have and end date
    before the current time or [[now]] date.
    EOT
             )
    
        singlePattern('_competitorcount')
        descr(<<'EOT'
    The number of tasks that have successfully competed for the same resources and
    have potentially delayed the completion of this task.
    EOT
             )
        singlePattern('_competitors')
        descr(<<'EOT'
    A list of tasks that have successfully competed for the same resources and
    have potentially delayed the completion of this task.
    EOT
             )
    
        singlePattern('_complete')
        descr(<<'EOT'
    The completion degree of a task. Unless a completion degree is manually
    provided, this is a computed value relative the [[now]] date of the project. A
    task that has ended before the now date is always 100% complete. A task that
    starts at or after the now date is always 0%. For [[effort]] based task the
    computation degree is the percentage of done effort of the overall effort. For
    other leaf task, the completion degree is the percentage of the already passed
    duration of the overall task duration. For container task, it's always the
    average of the direct sub tasks. If the sub tasks consist of a mixture of
    effort and non-effort tasks, the completion value is only of limited value.
    EOT
             )
    
        pattern([ '_completed' ], lambda {
          'complete'
        })
        level(:deprecated)
        also('complete')
        descr('Deprecated alias for complete')
    
        singlePattern('_criticalness')
        descr('A measure for how much effort the resource is allocated for, or' +
              'how strained the allocated resources of a task are')
    
        singlePattern('_cost')
        descr(<<'EOT'
    The cost of the task or resource. The use of this column requires that a cost
    account has been set for the report using the [[balance]] attribute.
    EOT
             )
    
        singlePattern('_daily')
        descr('A group of columns with one column for each day')
    
        singlePattern('_directreports')
        descr(<<'EOT'
    The resources that have this resource assigned as manager.
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    EOT
             )
    
        singlePattern('_duration')
        descr('The duration of a task')
    
        singlePattern('_duties')
        descr('List of tasks that the resource is allocated to')
    
        singlePattern('_efficiency')
        descr('Measure for how efficient a resource can perform tasks')
    
        singlePattern('_effort')
        descr('The allocated effort during the reporting period')
    
        singlePattern('_effortdone')
        descr('The already completed effort as of now')
    
        singlePattern('_effortleft')
        descr('The remaining allocated effort as of now')
    
        singlePattern('_email')
        descr('The email address of a resource')
    
        singlePattern('_end')
        descr('The end date of a task')
    
        singlePattern('_flags')
        descr('List of attached flags')
    
        singlePattern('_followers')
        descr(<<'EOT'
    A list of tasks that depend on the current task. The list contains the names,
    the IDs, the date and the type of dependency. For the type the following
    symbols are used for .
    
    * ''']->[''': End-to-Start dependency
    * '''[->[''': Start-to-Start dependency
    * ''']->]''': End-to-End dependency
    * '''[->]''': Start-to-End dependency
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    The dependency symbol can be generated via the ''''dependency'''' attribute
    inthe query, the target date via the ''''date'''' attribute.
    EOT
             )
    
        singlePattern('_freetime')
        descr(<<'EOT'
    The amount of unallocated work time of a resource during the reporting period.
    EOT
             )
    
        singlePattern('_freework')
        descr(<<'EOT'
    The amount of unallocated work capacity of a resource during the reporting
    period. This is the product of unallocated work time times the efficiency of
    the resource.
    EOT
             )
    
        singlePattern('_fte')
        descr(<<'EOT'
    The Full-Time-Equivalent of a resource or group. This is the ratio of the
    resource working time and the global working time. Working time is defined by
    working hours and leaves. The FTE value can vary over time and is
    calculated for the report interval or the user specified interval.
    EOT
             )
    
        singlePattern('_gauge')
        descr(<<'EOT'
    When [[complete]] values have been provided to capture the actual progress on
    tasks, the gauge column will list whether the task is ahead of, behind or on
    schedule.
    EOT
             )
    
        singlePattern('_headcount')
        descr(<<'EOT'
    For resources this is the headcount number of the resource or resource group.
    For a single resource this is the [[efficiency]] rounded to the next integer.
    For a group it is the sum of the sub resources headcount.
    
    For tasks it's the number of different resources allocated to the task during
    the report interval. Resources are weighted with their rounded efficiencies.
    EOT
             )
    
        pattern([ '_hierarchindex' ], lambda {
          'bsi'
        })
        level(:deprecated)
        also('bsi')
        descr('Deprecated alias for bsi')
    
        singlePattern('_hourly')
        descr('A group of columns with one column for each hour')
    
        singlePattern('_id')
        descr('The id of the item')
    
        singlePattern('_index')
        descr('The index of the item based on the nesting hierachy')
    
        singlePattern('_inputs')
        descr(<<'EOT'
    A list of milestones that are a prerequiste for the current task. For
    container tasks it will also include the inputs of the child tasks. Inputs may
    not have any predecessors.
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    EOT
             )
    
        singlePattern('_journal')
        descr(<<'EOT'
    The journal entries for the task or resource for the reported interval. The
    generated text can be customized with the [[journalmode]],
    [[journalattributes]], [[hidejournalentry]] and [[sortjournalentries]]. If
    used in queries without a property context, the journal for the complete
    project is generated.
    EOT
             )
    
        singlePattern('_journal_sub')
        level(:deprecated)
        also('journal')
        descr('Deprecated. Please use ''''journal'''' instead')
    
        singlePattern('_journalmessages')
        level(:deprecated)
        also('journal')
        descr('Deprecated. Please use ''''journal'''' instead')
    
        singlePattern('_journalsummaries')
        level(:deprecated)
        also('journal')
        descr('Deprecated. Please use ''''journal'''' instead')
    
        singlePattern('_line')
        descr('The line number in the report')
    
        singlePattern('_managers')
        descr(<<'EOT'
    A list of managers that the resource reports to.
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    EOT
            )
    
        singlePattern('_maxend')
        descr('The latest allowed end of a task')
    
        singlePattern('_maxstart')
        descr('The lastest allowed start of a task')
    
        singlePattern('_minend')
        descr('The earliest allowed end of a task')
    
        singlePattern('_minstart')
        descr('The earliest allowed start of a task')
    
        singlePattern('_monthly')
        descr('A group of columns with one column for each month')
    
        singlePattern('_no')
        descr('The object line number in the report (Cannot be used for sorting!)')
    
        singlePattern('_name')
        descr('The name or description of the item')
    
        singlePattern('_note')
        descr('The note attached to a task')
    
        singlePattern('_opentasks')
        descr(<<'EOT'
    The number of sub-tasks (including the current task) that have not yet been
    closed during the reported time period. Closed means that they have and end
    date before the current time or [[now]] date.
    EOT
             )
    
        singlePattern('_pathcriticalness')
        descr('The criticalness of the task with respect to all the paths that ' +
              'it is a part of.')
    
        singlePattern('_precursors')
        descr(<<'EOT'
    A list of tasks the current task depends on. The list contains the names, the
    IDs, the date and the type of dependency. For the type the following symbols
    are used
    
    * ''']->[''': End-to-Start dependency
    * '''[->[''': Start-to-Start dependency
    * ''']->]''': End-to-End dependency
    * '''[->]''': Start-to-End dependency
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    The dependency symbol can be generated via the ''''dependency'''' attribute
    inthe query, the target date via the ''''date'''' attribute.
    EOT
             )
    
        singlePattern('_priority')
        descr('The priority of a task')
    
        singlePattern('_quarterly')
        descr('A group of columns with one column for each quarter')
    
        singlePattern('_rate')
        descr('The daily cost of a resource.')
    
        singlePattern('_reports')
        descr(<<'EOT'
    All resources that have this resource assigned as a direct or indirect manager.
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    EOT
             )
    
        singlePattern('_resources')
        descr(<<'EOT'
    A list of resources that are assigned to the task in the report time frame.
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    EOT
             )
    
        singlePattern('_responsible')
        descr(<<'EOT'
    The responsible people for this task.
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    EOT
             )
    
        singlePattern('_revenue')
        descr(<<'EOT'
    The revenue of the task or resource. The use of this column requires that a
    revenue account has been set for the report using the [[balance]] attribute.
    EOT
             )
    
        singlePattern('_scenario')
        descr('The name of the scenario')
    
        singlePattern('_scheduling')
        descr(<<'EOT'
    The scheduling mode of the leaf tasks. ASAP tasks are scheduled start to end while ALAP tasks are scheduled end to start.
    EOT
             )
    
        singlePattern('_seqno')
        descr('The index of the item based on the declaration order')
    
        singlePattern('_sickleave')
        descr(<<'EOT'
    The number of sick leave units within the reported time period. The unit can
    be adjusted with [[loadunit]].
    EOT
             )
    
        singlePattern('_specialleave')
        descr(<<'EOT'
    The number of special leave units within the reported time period. The unit
    can be adjusted with [[loadunit]].
    EOT
             )
    
        singlePattern('_start')
        descr('The start date of the task')
    
        singlePattern('_status')
        descr(<<'EOT'
    The status of a task. It is determined based on the current date or the date
    specified by [[now]].
    EOT
             )
    
        singlePattern('_targets')
        descr(<<'EOT'
    A list of milestones that depend on the current task. For container tasks it
    will also include the targets of the child tasks. Targets may not have any
    follower tasks.
    
    The list can be customized by the [[listitem.column|listitem]] attribute.
    EOT
             )
    
        singlePattern('_turnover')
        descr(<<'EOT'
    The financial turnover of an account during the reporting interval.
    EOT
             )
    
        pattern([ '_wbs' ], lambda {
          'bsi'
        })
        level(:deprecated)
        also('bsi')
        descr('Deprecated alias for bsi.')
    
        singlePattern('_unpaidleave')
        descr(<<'EOT'
    The number of unpaid leave units within the reported time period. The unit
    can be adjusted with [[loadunit]].
    EOT
             )
    
        singlePattern('_weekly')
        descr('A group of columns with one column for each week')
    
        singlePattern('_yearly')
        descr('A group of columns with one column for each year')
    
      end
    
      def rule_reportAttributes
        optional
        repeatable
    
        pattern(%w( _accountroot !accountId), lambda {
          if @val[1].leaf?
            error('accountroot_leaf',
                  "#{@val[1].fullId} is not a container account",
                  @sourceFileInfo[1])
          end
          @property.set('accountroot', @val[1])
        })
        doc('accountroot', <<'EOT'
    Only accounts below the specified root-level accounts are exported. The exported
    accounts will have the ID of the root-level account stripped from their ID, so that
    the sub-accounts of the root-level account become top-level accounts in the report
    file.
    EOT
           )
        example('AccountReport')
    
        pattern(%w( _auxdir $STRING ), lambda {
          auxdir = @val[1]
          # Ensure that the directory always ends with a '/'.
          auxdir += '/' unless auxdir[-1] == ?/
          @property.set('auxdir', auxdir)
        })
        level(:beta)
        doc('auxdir.report', <<'EOT'
    Specifies an alternative directory for the auxiliary report files such as CSS,
    JavaScript and icon files. If this attribute is not set, the directory will be
    generated automatically. If this attribute is provided, the user has to ensure
    that the directory exists and is filled with the proper data. The specified
    path can be absolute or relative to the generated report file.
    EOT
           )
    
        pattern(%w( !balance ), lambda {
          @property.set('costaccount', @val[0][0])
          @property.set('revenueaccount', @val[0][1])
        })
    
        pattern(%w( _caption $STRING ), lambda {
          @property.set('caption', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('caption', <<'EOT'
    The caption will be embedded in the footer of the table or data segment. The
    text will be interpreted as [[Rich_Text_Attributes|Rich Text]].
    EOT
           )
        arg(1, 'text', 'The caption text.')
        example('Caption', '1')
    
        pattern(%w( _center $STRING ), lambda {
          @property.set('center', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('center', <<'EOT'
    This attribute defines the center section of the [[textreport]]. The text will
    be interpreted as [[Rich_Text_Attributes|Rich Text]].
    EOT
           )
        arg(1, 'text', 'The text')
        example('textreport')
    
        pattern(%w( _columns !columnDef !moreColumnDef ), lambda {
          columns = [ @val[1] ]
          columns += @val[2] if @val[2]
          @property.set('columns', columns)
        })
        doc('columns', <<'EOT'
    Specifies which columns shall be included in a report. Some columns show
    values that are constant over the course of the project. Other columns show
    calculated values that depend on the time period that was chosen for the
    report.
    EOT
           )
    
        pattern(%w( !currencyFormat ), lambda {
          @property.set('currencyFormat', @val[0])
        })
    
        pattern(%w( !reportEnd ))
    
        pattern(%w( _epilog $STRING ), lambda {
          @property.set('epilog', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('epilog', <<'EOT'
    Define a text section that is printed right after the actual report data. The
    text will be interpreted as [[Rich_Text_Attributes|Rich Text]].
    EOT
           )
        also(%w( footer header prolog ))
    
        pattern(%w( !flags ))
        doc('flags.report', <<'EOT'
    Attach a set of flags. The flags can be used in logical expressions to filter
    properties from the reports.
    EOT
           )
    
        pattern(%w( _footer $STRING ), lambda {
          @property.set('footer', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('footer', <<'EOT'
    Define a text section that is put at the bottom of the report. The
    text will be interpreted as [[Rich_Text_Attributes|Rich Text]].
    EOT
           )
        example('textreport')
        also(%w( epilog header prolog ))
    
        pattern(%w( !formats ))
    
        pattern(%w( _header $STRING ), lambda {
          @property.set('header', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('header', <<'EOT'
    Define a text section that is put at the top of the report. The
    text will be interpreted as [[Rich_Text_Attributes|Rich Text]].
    EOT
           )
        example('textreport')
        also(%w( epilog footer prolog ))
    
        pattern(%w( !headline ))
        pattern(%w( !hidejournalentry ))
        pattern(%w( !hideaccount ))
        pattern(%w( !hideresource ))
        pattern(%w( !hidetask ))
    
        pattern(%w( _height $INTEGER ), lambda {
          if @val[1] < 200
            error('min_report_height',
                  "The report must have a minimum height of 200 pixels.")
          end
          @property.set('height', @val[1])
        })
        doc('height', <<'EOT'
    Set the height of the report in pixels. This attribute is only used for
    reports that cannot determine the height based on the content. Such report can
    be freely resized to fit in. The vast majority of reports can determine their
    height based on the provided content. These reports will simply ignore this
    setting.
    EOT
           )
        also('width')
    
        pattern(%w( !journalReportAttributes ))
        pattern(%w( _journalmode !journalReportMode ), lambda {
          @property.set('journalMode', @val[1])
        })
        doc('journalmode', <<'EOT'
    This attribute controls what journal entries are aggregated into the report.
    EOT
           )
    
        pattern(%w( _left $STRING ), lambda {
          @property.set('left', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('left', <<'EOT'
    This attribute defines the left margin section of the [[textreport]]. The text
    will be interpreted as [[Rich_Text_Attributes|Rich Text]]. The margin will not
    span the [[header]] or [[footer]] sections.
    EOT
           )
        example('textreport')
    
        pattern(%w( !loadunit ))
    
        pattern(%w( !numberFormat ), lambda {
          @property.set('numberFormat', @val[0])
        })
    
        pattern(%w( _opennodes !nodeIdList ), lambda {
          @property.set('openNodes', @val[1])
        })
        doc('opennodes', 'For internal use only!')
    
        pattern(%w( !reportPeriod ))
    
        pattern(%w( _prolog $STRING ), lambda {
          @property.set('prolog', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('prolog', <<'EOT'
    Define a text section that is printed right before the actual report data. The
    text will be interpreted as [[Rich_Text_Attributes|Rich Text]].
    EOT
           )
        also(%w( epilog footer header ))
    
        pattern(%w( !purge ))
    
        pattern(%w( _rawhtmlhead $STRING ), lambda {
          @property.set('rawHtmlHead', @val[1])
        })
        doc('rawhtmlhead', <<'EOT'
    Define a HTML fragment that will be inserted at the end of the HTML head
    section.
    EOT
           )
    
        pattern(%w( !reports ))
    
        pattern(%w( _right $STRING ), lambda {
          @property.set('right', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('right', <<'EOT'
    This attribute defines the right margin section of the [[textreport]]. The text
    will be interpreted as [[Rich_Text_Attributes|Rich Text]]. The margin will not
    span the [[header]] or [[footer]] sections.
    EOT
           )
        example('textreport')
    
        pattern(%w( !rollupaccount ))
        pattern(%w( !rollupresource ))
        pattern(%w( !rolluptask ))
    
        pattern(%w( _scenarios !scenarioIdList ), lambda {
          # Don't include disabled scenarios in the report
          @val[1].delete_if { |sc| !@project.scenario(sc).get('active') }
          @property.set('scenarios', @val[1])
        })
        doc('scenarios', <<'EOT'
    List of scenarios that should be included in the report. By default, only the
    top-level scenario will be included. You can use this attribute to include
    data from the defined set of scenarios. Not all reports support reporting data
    from multiple scenarios. They will only include data from the first one in the
    list.
    EOT
           )
    
        pattern(%w( _selfcontained !yesNo ), lambda {
          @property.set('selfcontained', @val[1])
        })
        doc('selfcontained', <<'EOT'
    Try to generate selfcontained output files when the format supports this. E.
    g. for HTML reports, the style sheet will be included and no icons will be
    used.
    EOT
           )
    
        pattern(%w( !sortAccounts ))
        pattern(%w( !sortJournalEntries ))
        pattern(%w( !sortResources ))
        pattern(%w( !sortTasks ))
    
        pattern(%w( !reportStart ))
    
        pattern(%w( _resourceroot !resourceId), lambda {
          if @val[1].leaf?
            error('resourceroot_leaf',
                  "#{@val[1].fullId} is not a group resource",
                  @sourceFileInfo[1])
          end
          @property.set('resourceroot', @val[1])
        })
        doc('resourceroot', <<'EOT'
    Only resources below the specified root-level resources are exported. The
    exported resources will have the ID of the root-level resource stripped from
    their ID, so that the sub-resourcess of the root-level resource become
    top-level resources in the report file.
    EOT
           )
        example('ResourceRoot')
    
        pattern(%w( _taskroot !taskId), lambda {
          if @val[1].leaf?
            error('taskroot_leaf',
                  "#{@val[1].fullId} is not a container task",
                  @sourceFileInfo[1])
          end
          @property.set('taskroot', @val[1])
        })
        doc('taskroot', <<'EOT'
    Only tasks below the specified root-level tasks are exported. The exported
    tasks will have the ID of the root-level task stripped from their ID, so that
    the sub-tasks of the root-level task become top-level tasks in the report
    file.
    EOT
           )
        example('TaskRoot')
    
        pattern(%w( !timeformat ), lambda {
          @property.set('timeFormat', @val[0])
        })
    
        pattern(%w( _timezone !validTimeZone ), lambda {
          @property.set('timezone', @val[1])
        })
        doc('timezone.report', <<'EOT'
    Sets the time zone used for all dates in the report. This setting is ignored
    if the report is embedded into another report. Embedded in this context means
    the report is part of another generated report. It does not mean that the
    report definition is a sub report of another report definition.
    EOT
           )
    
        pattern(%w( !reportTitle ))
    
        pattern(%w( _width $INTEGER ), lambda {
          if @val[1] < 400
            error('min_report_width',
                  "The report must have a minimum width of 400 pixels.")
          end
          @property.set('width', @val[1])
        })
        doc('width', <<'EOT'
    Set the width of the report in pixels. This attribute is only used for
    reports that cannot determine the width based on the content. Such report can
    be freely resized to fit in. The vast majority of reports can determine their
    width based on the provided content. These reports will simply ignore this
    setting.
    EOT
           )
        also('height')
      end
    
      def rule_reportEnd
        pattern(%w( _end !date ), lambda {
          if @val[1] < @property.get('start')
            error('report_end',
                  "End date must be before start date #{@property.get('start')}",
                  @sourceFileInfo[1])
          end
          @property.set('end', @val[1])
        })
        doc('end.report', <<'EOT'
    Specifies the end date of the report. In task reports only tasks that start
    before this end date are listed.
    EOT
           )
        example('Export', '2')
      end
      def rule_reportId
        pattern(%w( !reportIdUnverifd ), lambda {
          id = @val[0]
          if @property && @property.is_a?(Report)
            id = @property.fullId + '.' + id
          else
            id = @reportprefix + '.' + id unless @reportprefix.empty?
          end
          # In case we have a nested supplement, we need to prepend the parent ID.
          if (report = @project.report(id)).nil?
            error('report_id_expected', "#{id} is not a defined report.",
                  @sourceFileInfo[0])
          end
          report
        })
        arg(0, 'report', 'The ID of a defined report')
      end
    
      def rule_reportIdUnverifd
        singlePattern('$ABSOLUTE_ID')
        singlePattern('$ID')
      end
    
      def rule_reportName
        pattern(%w( $STRING ), lambda {
          @val[0]
        })
        arg(0, 'name', <<'EOT'
    The name of the report. This will be the base name for generated output files.
    The suffix will depend on the specified [[formats]]. It will also be used in
    navigation bars.
    
    By default, report definitions do not generate any files. With more complex
    projects, most report definitions will be used to describe elements of
    composed reports. If you want to generate a file from this report, you must
    specify the list of [[formats]] that you want to generate. The report name
    will then be used as a base name to create the file. The suffix will be
    appended based on the generated format.
    
    Reports have a local name space. All IDs and file names must be unique within
    the reports that belong to the same enclosing report. To reference a report
    for inclusion into another report, you need to specify the full report ID.
    This is composed of the report ID, prefixed by a dot-separated list of all
    parent report IDs.
    EOT
           )
      end
    
      def rule_reportPeriod
        pattern(%w( _period !interval ), lambda {
          @property.set('start', @val[1].start)
          @property.set('end', @val[1].end)
        })
        doc('period.report', <<'EOT'
    This property is a shortcut for setting the start and end property at the
    same time.
    EOT
           )
      end
    
      def rule_reportProperties
        pattern(%w( !iCalReport ))
        pattern(%w( !nikuReport ))
        pattern(%w( !reports ))
        pattern(%w( !tagfile ))
        pattern(%w( !statusSheetReport ))
        pattern(%w( !timeSheetReport ))
      end
    
      def rule_reportPropertiesBody
        optional
        repeatable
    
        pattern(%w( !macro ))
        pattern(%w( !reportProperties ))
      end
    
      def rule_reportPropertiesFile
        pattern(%w( !reportPropertiesBody . ))
      end
    
      def rule_reportStart
        pattern(%w( _start !date ), lambda {
          if @val[1] > @property.get('end')
            error('report_start',
                  "Start date must be before end date #{@property.get('end')}",
                  @sourceFileInfo[1])
          end
          @property.set('start', @val[1])
        })
        doc('start.report', <<'EOT'
    Specifies the start date of the report. In task reports only tasks that end
    after this end date are listed.
    EOT
           )
      end
    
      def rule_reportBody
        optionsRule('reportAttributes')
      end
    
      def rule_reportTitle
        pattern(%w( _title $STRING ), lambda {
          @property.set('title', @val[1])
        })
        doc('title', <<'EOT'
    The title of the report will be used in external references to the report. It
    will not show up in the reports directly. It's used e. g. by [[navigator]].
    EOT
           )
      end
    
      def rule_resource
        pattern(%w( !resourceHeader !resourceBody ), lambda {
           @property = @property.parent
        })
        doc('resource', <<'EOT'
    Tasks that have an effort specification need to have at least one resource
    assigned to do the work. Use this property to define resources or groups of
    resources.
    
    Resources have a global name space. All IDs must be unique within the resources
    of the project.
    EOT
           )
      end
    
      def rule_resourceAttributes
        repeatable
        optional
        pattern(%w( _email $STRING ), lambda {
          @property.set('email', @val[1])
        })
        doc('email',
            'The email address of the resource.')
    
        pattern(%w( !journalEntry ))
        pattern(%w( !purge ))
        pattern(%w( !resource ))
        pattern(%w( !resourceScenarioAttributes ))
        pattern(%w( !scenarioIdCol !resourceScenarioAttributes ), lambda {
          @scenarioIdx = 0
        })
    
        pattern(%w( _supplement !resourceId !resourceBody ), lambda {
          @property = @idStack.pop
        })
        doc('supplement.resource', <<'EOT'
    The supplement keyword provides a mechanism to add more attributes to already
    defined resources. The additional attributes must obey the same rules as in
    regular resource definitions and must be enclosed by curly braces.
    
    This construct is primarily meant for situations where the information about a
    resource is split over several files. E. g. the vacation dates for the
    resources may be in a separate file that was generated by some other tool.
    EOT
           )
        example('Supplement', 'resource')
    
        # Other attributes will be added automatically.
      end
    
      def rule_resourceBody
        optionsRule('resourceAttributes')
      end
    
      def rule_resourceBooking
        pattern(%w( !resourceBookingHeader !bookingBody ), lambda {
          unless @project.scenario(@scenarioIdx).get('ownbookings')
            error('no_own_resource_booking',
                  "The scenario #{@project.scenario(@scenarioIdx).fullId} " +
                  'inherits its bookings from the tracking ' +
                  'scenario. You cannot specificy additional bookings for it.')
          end
          @val[0].task.addBooking(@scenarioIdx, @val[0])
        })
      end
    
      def rule_resourceBookingHeader
        pattern(%w( !taskId !valIntervals ), lambda {
          checkBooking(@val[0], @property)
          @booking = Booking.new(@property, @val[0], @val[1])
          @booking.sourceFileInfo = @sourceFileInfo[0]
          @booking
        })
        arg(0, 'id', 'Absolute ID of a defined task')
      end
    
      def rule_resourceId
        pattern(%w( $ID ), lambda {
          id = (@resourceprefix.empty? ? '' : @resourceprefix + '.') + @val[0]
          if (resource = @project.resource(id)).nil?
            error('resource_id_expected', "#{id} is not a defined resource.",
                  @sourceFileInfo[0])
          end
          resource
        })
        arg(0, 'resource', 'The ID of a defined resource')
      end
    
      def rule_resourceHeader
        pattern(%w( _resource !optionalID $STRING ), lambda {
          if @property.nil? && !@resourceprefix.empty?
            @property = @project.resource(@resourceprefix)
          end
          if @val[1] && @project.resource(@val[1])
            error('resource_exists',
                  "Resource #{@val[1]} has already been defined.",
                  @sourceFileInfo[1], @property)
          end
          @property = Resource.new(@project, @val[1], @val[2], @property)
          @property.sourceFileInfo = @sourceFileInfo[0]
          @property.inheritAttributes
          @scenarioIdx = 0
        })
    #    arg(1, 'id', <<'EOT'
    #The ID of the resource. Resources have a global name space. The ID must be
    #unique within the whole project.
    #EOT
    #       )
        arg(2, 'name', 'The name of the resource')
      end
    
      def rule_resourceLeafList
        listRule('moreResourceLeafList', '!leafResourceId')
      end
    
      def rule_resourceList
        listRule('moreResources', '!resourceId')
      end
    
      def rule_resourceReport
        pattern(%w( !resourceReportHeader !reportBody ), lambda {
          @property = @property.parent
        })
        doc('resourcereport', <<'EOT'
    The report lists resources and their respective values in a table. The task
    that are the resources are allocated to can be listed as well. To reduce the
    list of included resources, you can use the [[hideresource]],
    [[rollupresource]] or [[resourceroot]] attributes. The order of the task can
    be controlled with [[sortresources]]. If the first sorting criteria is tree
    sorting, the parent resources will always be included to form the tree.
    Tree sorting is the default. You need to change it if you do not want certain
    parent resources to be included in the report.
    
    By default, all the tasks that the resources are allocated to are hidden, but
    they can be listed as well. Use the [[hidetask]] attribute to select which
    tasks should be included.
    EOT
           )
      end
    
      def rule_resourceReportHeader
        pattern(%w( _resourcereport !optionalID !reportName ), lambda {
          newReport(@val[1], @val[2], :resourcereport, @sourceFileInfo[0]) do
            unless @property.modified?('columns')
              # Set the default columns for this report.
              %w( no name ).each do |col|
                @property.get('columns') <<
                TableColumnDefinition.new(col, columnTitle(col))
              end
            end
            # Show all resources, sorted by tree and id-up.
            unless @property.modified?('hideResource')
              @property.set('hideResource',
                            LogicalExpression.new(LogicalOperation.new(0)))
            end
            unless @property.modified?('sortResources')
              @property.set('sortResources', [ [ 'tree', true, -1 ],
                            [ 'id', true, -1 ] ])
            end
            # Hide all resources, but set sorting to tree, start-up, seqno-up.
            unless @property.modified?('hideTask')
              @property.set('hideTask',
                            LogicalExpression.new(LogicalOperation.new(1)))
            end
            unless @property.modified?('sortTasks')
              @property.set('sortTasks',
                            [ [ 'tree', true, -1 ],
                              [ 'start', true, 0 ],
                              [ 'seqno', true, -1 ] ])
            end
          end
        })
      end
    
      def rule_resourceScenarioAttributes
        pattern(%w( !chargeset ))
    
        pattern(%w( _efficiency !number ), lambda {
          @property['efficiency', @scenarioIdx] = @val[1]
        })
        doc('efficiency', <<'EOT'
    The efficiency of a resource can be used for two purposes. First you can use
    it as a crude way to model a team. A team of 5 people should have an
    efficiency of 5.0. Keep in mind that you cannot track the members of the team
    individually if you use this feature. They always act as a group.
    
    The other use is to model performance variations between your resources. Again, this is a fairly crude mechanism and should be used with care. A resource that isn't every good at some task might be pretty good at another. This can't be taken into account as the resource efficiency can only set globally for all tasks.
    
    All resources that do not contribute effort to the task, should have an
    efficiency of 0.0. A typical example would be a conference room. It's necessary for a meeting, but it does not contribute any work.
    EOT
           )
        example('Efficiency')
        pattern(%w( !flags ))
        doc('flags.resource', <<'EOT'
    Attach a set of flags. The flags can be used in logical expressions to filter
    properties from the reports.
    EOT
           )
    
        pattern(%w( _booking !resourceBooking ))
        doc('booking.resource', <<'EOT'
    The booking attribute can be used to report actually completed work.  A task
    with bookings must be [[scheduling|scheduled]] in ''''asap'''' mode.  If the
    scenario is not the [[trackingscenario|tracking scenario]] or derived from it,
    the scheduler will not allocate resources prior to the current date or the
    date specified with [[now]] when a task has at least one booking.
    
    Bookings are only valid in the scenario they have been defined in. They will
    in general not be passed to any other scenario. If you have defined a
    [[trackingscenario|tracking scenario]], the bookings of this scenario will be
    passed to all the derived scenarios of the tracking scenario.
    
    The sloppy attribute can be used when you want to skip non-working time or
    other allocations automatically. If it's not given, all bookings must only
    cover working time for the resource.
    
    The booking attributes is designed to capture the exact amount of completed
    work. This attribute is not really intended to specify completed effort by
    hand. Usually, booking statements are generated by [[export]] reports. The
    [[sloppy.booking|sloppy]] and [[overtime.booking|overtime]] attributes are
    only kludge for users who want to write them manually.
    Bookings can be used to report already completed work by specifying the exact
    time intervals a certain resource has worked on this task.
    
    Bookings can be defined in the task or resource context. If you move tasks
    around very often, put your bookings in the task context.
    EOT
           )
        also(%w( scheduling booking.task ))
        example('Booking')
    
        pattern(%w( !fail ))
    
        pattern(%w( !leaveAllowances ))
    
        pattern(%w( !leaves ), lambda {
          @property['leaves', @scenarioIdx] += @val[0]
        })
    
        pattern(%w( !limits ), lambda {
          @property['limits', @scenarioIdx] = @val[0]
        })
        doc('limits.resource', <<'EOT'
    Set per-interval usage limits for the resource.
    EOT
           )
        example('Limits-1', '6')
    
        pattern(%w( _managers !resourceList ), lambda {
          @property['managers', @scenarioIdx] =
            @property['managers', @scenarioIdx] + @val[1]
        })
        doc('managers', <<'EOT'
    Defines one or more resources to be the manager who is responsible for this
    resource. Managers must be leaf resources. This attribute does not impact the
    scheduling. It can only be used for documentation purposes.
    
    You must only specify direct managers here. Do not list higher level managers
    here. If necessary, use the [[purge]] attribute to clear
    inherited managers. For most use cases, there should be only one manager. But
    TaskJuggler is not limited to just one manager. Dotted reporting lines can be
    captured as well as long as the managers are not reporting to each other.
    EOT
           )
        also(%w( statussheet ))
        example('Manager')
    
    
        pattern(%w( _rate !number ), lambda {
          @property['rate', @scenarioIdx] = @val[1]
        })
        doc('rate.resource', <<'EOT'
    The rate specifies the daily cost of the resource.
    EOT
           )
    
        pattern(%w( !resourceShiftAssignments !shiftAssignments ), lambda {
          checkContainer('shifts')
          # Set same value again to set the 'provided' state for the attribute.
          begin
            @property['shifts', @scenarioIdx] = @shiftAssignments
          rescue AttributeOverwrite
            # Multiple shift assignments are a common idiom, so don't warn about
            # them.
          end
          @shiftAssignments = nil
        })
        level(:deprecated)
        also('shift.resource')
        doc('shift.resource', <<'EOT'
    This keyword has been deprecated. Please use [[shifts.resource|shifts
    (resource)]] instead.
    EOT
           )
    
        pattern(%w( !resourceShiftsAssignments !shiftAssignments ), lambda {
          checkContainer('shifts')
          # Set same value again to set the 'provided' state for the attribute.
          begin
            @property['shifts', @scenarioIdx] = @shiftAssignments
          rescue AttributeOverwrite
            # Multiple shift assignments are a common idiom, so don't warn about
            # them.
          end
          @shiftAssignments = nil
        })
        doc('shifts.resource', <<'EOT'
    Limits the working time of a resource to a defined shift during the specified
    interval. Multiple shifts can be defined, but shift intervals may not overlap.
    In case a shift is defined for a certain interval, the shift working hours
    replace the standard resource working hours for this interval.
    EOT
            )
    
        pattern(%w( _vacation !vacationName !intervals ), lambda {
          @val[2].each do |interval|
            # We map the old 'vacation' attribute to public holidays.
            @property['leaves', @scenarioIdx] += [ Leave.new(:holiday, interval) ]
          end
        })
        doc('vacation.resource', <<'EOT'
    Specify a vacation period for the resource. It can also be used to block out
    the time before a resource joined or after it left. For employees changing
    their work schedule from full-time to part-time, or vice versa, please refer
    to the 'Shift' property.
    EOT
           )
    
        pattern(%w( !warn ))
    
        pattern(%w( !workinghoursResource ))
        # Other attributes will be added automatically.
      end
    
      def rule_resourceShiftAssignments
        pattern(%w( _shift ), lambda {
          @shiftAssignments = @property['shifts', @scenarioIdx]
        })
      end
    
      def rule_resourceShiftsAssignments
        pattern(%w( _shifts ), lambda {
          @shiftAssignments = @property['shifts', @scenarioIdx]
        })
      end
    
      def rule_rollupaccount
        pattern(%w( _rollupaccount !logicalExpression ), lambda {
          @property.set('rollupAccount', @val[1])
        })
        doc('rollupaccount', <<'EOT'
    Do not show sub-accounts of accounts that match the specified
    [[logicalexpression|logical expression]].
    EOT
           )
      end
    
      def rule_rollupresource
        pattern(%w( _rollupresource !logicalExpression ), lambda {
          @property.set('rollupResource', @val[1])
        })
        doc('rollupresource', <<'EOT'
    Do not show sub-resources of resources that match the specified
    [[logicalexpression|logical expression]].
    EOT
           )
        example('RollupResource')
      end
    
      def rule_rolluptask
        pattern(%w( _rolluptask !logicalExpression ), lambda {
          @property.set('rollupTask', @val[1])
        })
        doc('rolluptask', <<'EOT'
    Do not show sub-tasks of tasks that match the specified
    [[logicalexpression|logical expression]].
    EOT
           )
      end
    
    
      def rule_scenario
        pattern(%w( !scenarioHeader !scenarioBody ), lambda {
          @property = @property.parent
        })
        doc('scenario', <<'EOT'
    Defines a new project scenario. By default, the project has only one scenario
    called ''''plan''''. To do plan vs. actual comparisons or to do a
    what-if-analysis, you can define a set of scenarios. There can only be one
    top-level scenario. Additional scenarios are either derived from this
    top-level scenario or other scenarios.
    
    Each nested scenario is a variation of the enclosing scenario. All scenarios
    share the same set of properties (task, resources, etc.) but the attributes
    that are listed as scenario specific may differ between the various
    scenarios. A nested scenario uses all attributes from the enclosing scenario
    unless the user has specified a different value for this attribute.
    
    By default, the scheduler assigns resources to task beginning with the project
    start date. If the scenario is switched to projection mode, no assignments
    will be made prior to the current date or the date specified by [[now]]. In
    this case, TaskJuggler assumes, that all assignements prior to the
    current date have been provided by [[booking.task]] statements.
    EOT
           )
      end
    
      def rule_scenarioAttributes
        optional
        repeatable
    
        pattern(%w( _active !yesNo), lambda {
          @property.set('active', @val[1])
        })
        doc('active', <<'EOT'
    Enable the scenario to be scheduled or not. By default, all scenarios will be
    scheduled. If a scenario is marked as inactive, it not be scheduled and will
    be ignored in the reports.
    EOT
           )
        pattern(%w( _disabled ), lambda {
          @property.set('active', false)
        })
        level(:deprecated)
        also('active')
        doc('disabled', <<'EOT'
    This attribute is deprecated. Please use [active] instead.
    
    Disable the scenario for scheduling. The default for the top-level
    scenario is to be enabled.
    EOT
           )
        example('Scenario')
        pattern(%w( _enabled ), lambda {
          @property.set('active', true)
        })
        level(:deprecated)
        also('active')
        doc('enabled', <<'EOT'
    This attribute is deprecated. Please use [active] instead.
    
    Enable the scenario for scheduling. This is the default for the top-level
    scenario.
    EOT
           )
    
        pattern(%w( _projection !projection ))
        level(:deprecated)
        also('booking.task')
        doc('projection', <<'EOT'
    This keyword has been deprecated! Don't use it anymore!
    
    Projection mode is now automatically enabled as soon as a scenario has
    bookings.
    EOT
           )
    
        pattern(%w( !scenario ))
      end
    
      def rule_scenarioBody
        optionsRule('scenarioAttributes')
      end
    
      def rule_scenarioHeader
    
        pattern(%w( _scenario $ID $STRING ), lambda {
          # If this is the top-level scenario, we must delete the default scenario
          # first.
          @project.scenarios.each do |scenario|
            if scenario.get('projection')
              error('scenario_after_tracking',
                    'Scenarios must be defined before a tracking scenario is set.')
            end
          end
          @project.scenarios.clearProperties if @property.nil?
          if @project.scenario(@val[1])
            error('scenario_exists',
                  "Scenario #{@val[1]} has already been defined.",
                  @sourceFileInfo[1])
          end
          @property = Scenario.new(@project, @val[1], @val[2], @property)
          @property.inheritAttributes
    
          if @project.scenarios.length > 1
            MessageHandlerInstance.instance.hideScenario = false
          end
        })
        arg(1, 'id', 'The ID of the scenario')
        arg(2, 'name', 'The name of the scenario')
      end
    
      def rule_scenarioId
        pattern(%w( $ID ), lambda {
          if (@scenarioIdx = @project.scenarioIdx(@val[0])).nil?
            error('unknown_scenario_id', "Unknown scenario: #{@val[0]}",
                  @sourceFileInfo[0])
          end
          @scenarioIdx
        })
        arg(0, 'scenario', 'ID of a defined scenario')
      end
    
      def rule_scenarioIdCol
        pattern(%w( $ID_WITH_COLON ), lambda {
          if (@scenarioIdx = @project.scenarioIdx(@val[0])).nil?
            error('unknown_scenario_id', "Unknown scenario: #{@val[0]}",
                  @sourceFileInfo[0])
          end
        })
      end
    
      def rule_scenarioIdList
        listRule('moreScnarioIdList', '!scenarioIdx')
      end
    
      def rule_scenarioIdx
        pattern(%w( $ID ), lambda {
          if (scenarioIdx = @project.scenarioIdx(@val[0])).nil?
            error('unknown_scenario_idx', "Unknown scenario #{@val[0]}",
                  @sourceFileInfo[0])
          end
          scenarioIdx
        })
      end
    
      def rule_schedulingDirection
        singlePattern('_alap')
        singlePattern('_asap')
      end
    
      def rule_shift
        pattern(%w( !shiftHeader !shiftBody ), lambda {
          @property = @property.parent
        })
        doc('shift', <<'EOT'
    A shift combines several workhours related settings in a reusable entity.
    Besides the weekly working hours it can also hold information such as leaves
    and a time zone. It lets you create a work time calendar that can be used to
    limit the working time for resources or tasks.
    
    Shifts have a global name space. All IDs must be unique within the shifts of
    the project.
    EOT
           )
        also(%w( shifts.task shifts.resource ))
      end
    
      def rule_shiftAssignment
        pattern(%w( !shiftId !intervalOptional ), lambda {
          # Make sure we have a ShiftAssignment for the property.
          unless @shiftAssignments
            @shiftAssignments = ShiftAssignments.new
            @shiftAssignments.project = @project
          end
    
          if @val[1].nil?
            interval = TimeInterval.new(@project['start'], @project['end'])
          else
            interval = @val[1]
          end
          if !@shiftAssignments.addAssignment(
             ShiftAssignment.new(@val[0].scenario(@scenarioIdx), interval))
            error('shift_assignment_overlap',
                  'Shifts may not overlap each other.',
                  @sourceFileInfo[0], @property)
          end
          @shiftAssignments.assignments.last
        })
      end
    
      def rule_shiftAssignments
        listRule('moreShiftAssignments', '!shiftAssignment')
      end
    
      def rule_shiftAttributes
        optional
        repeatable
    
        pattern(%w( !shift ))
        pattern(%w( !shiftScenarioAttributes ))
        pattern(%w( !scenarioIdCol !shiftScenarioAttributes ), lambda {
          @scenarioIdx = 0
        })
      end
    
      def rule_shiftBody
        optionsRule('shiftAttributes')
      end
    
      def rule_shiftHeader
        pattern(%w( _shift !optionalID $STRING ), lambda {
          if @val[1] && @project.shift(@val[1])
            error('shift_exists', "Shift #{@val[1]} has already been defined.",
                  @sourceFileInfo[1])
          end
          @property = Shift.new(@project, @val[1], @val[2], @property)
          @property.sourceFileInfo = @sourceFileInfo[0]
          @property.inheritAttributes
          @scenarioIdx = 0
        })
        arg(2, 'name', 'The name of the shift')
      end
    
      def rule_shiftId
        pattern(%w( $ID ), lambda {
          if (shift = @project.shift(@val[0])).nil?
            error('shift_id_expected', "#{@val[0]} is not a defined shift.",
                  @sourceFileInfo[0])
          end
          shift
        })
        arg(0, 'shift', 'The ID of a defined shift')
      end
    
      def rule_shiftScenarioAttributes
        pattern(%w( !leaves ), lambda {
          @property['leaves', @scenarioIdx] += @val[0]
        })
    
        pattern(%w( _replace ), lambda {
          @property['replace', @scenarioIdx] = true
        })
        doc('replace', <<'EOT'
    This replace mode is only effective for shifts that are assigned to resources
    directly. When replace mode is activated the leave definitions of the shift
    will replace all the leave definitions of the resource for the given period.
    
    The mode is not effective for shifts that are assigned to tasks or allocations.
    EOT
           )
    
        pattern(%w( _timezone !validTimeZone ), lambda {
          @property['timezone', @scenarioIdx] = @val[1]
        })
        doc('timezone.shift', <<'EOT'
    Sets the time zone of the shift. The working hours of the shift are assumed to
    be within the specified time zone. The time zone does not effect the vaction
    interval. The latter is assumed to be within the project time zone.
    
    TaskJuggler stores all dates internally as UTC. Since all events must align
    with the [[timingresolution|timing resolution]] for time zones you may have to
    change the timing resolution appropriately. The time zone difference compared
    to UTC must be a multiple of the used timing resolution.
    EOT
            )
        arg(1, 'zone', <<'EOT'
    Time zone to use. E. g. 'Europe/Berlin' or 'America/Denver'. Don't use the 3
    letter acronyms. See
    [http://en.wikipedia.org/wiki/List_of_zoneinfo_time_zones Wikipedia] for
    possible values.
    EOT
           )
    
        pattern(%w( _vacation !vacationName !intervalsOptional ), lambda {
          @val[2].each do |interval|
            # We map the old 'vacation' attribute to public holidays.
            @property['leaves', @scenarioIdx] += [ Leave.new(:holiday, interval) ]
          end
        })
        doc('vacation.shift', <<'EOT'
    Specify a vacation period associated with this shift.
    EOT
           )
    
        pattern(%w( !workinghoursShift ))
      end
    
      def rule_sortCriteria
        pattern([ "!sortCriterium", "!moreSortCriteria" ], lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_sortCriterium
        pattern(%w( !sortTree ), lambda {
          @val[0]
        })
        pattern(%w( !sortNonTree ), lambda {
          @val[0]
        })
      end
    
      def rule_sortNonTree
        pattern(%w( $ABSOLUTE_ID ), lambda {
          args = @val[0].split('.')
          case args.length
          when 2
            # .
            scenario = -1
            direction = args[1] == 'up'
            attribute = args[0]
          when 3
            # ..
            if (scenario = @project.scenarioIdx(args[0])).nil?
              error('sort_unknown_scen',
                    "Unknown scenario #{args[0]} in sorting criterium",
                    @sourceFileInfo[0])
            end
            attribute = args[1]
            if args[2] != 'up' && args[2] != 'down'
              error('sort_direction', "Sorting direction must be 'up' or 'down'",
                    @sourceFileInfo[0])
            end
            direction = args[2] == 'up'
          else
            error('sorting_crit_exptd1',
                  "Sorting criterium expected (e.g. tree, start.up or " +
                  "plan.end.down).", @sourceFileInfo[0])
          end
          if attribute == 'bsi'
            error('sorting_bsi',
                  "Sorting by bsi is not supported. Please use 'tree' " +
                  '(without appended .up or .down) instead.',
                  @sourceFileInfo[0])
          end
          [ attribute, direction, scenario ]
        })
        arg(0, 'criteria', <<'EOT'
    The sorting criteria must consist of a property attribute ID. See [[columnid]]
    for a complete list of available attributes. The ID must be suffixed by '.up'
    or '.down' to determine the sorting direction. Optionally the ID may be
    prefixed with a scenario ID and a dot to determine the scenario that should be
    used for sorting. So, possible values are 'plan.start.up' or 'priority.down'.
    EOT
             )
      end
    
      def rule_sortJournalEntries
        pattern(%w( _sortjournalentries !journalSortCriteria ), lambda {
          @property.set('sortJournalEntries', @val[1])
        })
        doc('sortjournalentries', <<'EOT'
    Determines how the entries in a journal are sorted. Multiple criteria can be
    specified as a comma separated list. If one criteria is not sufficient to sort
    a group of journal entries, the next criteria will be used to sort the entries
    in this group.
    
    The following values are supported:
    * ''''date.down'''': Sort descending order by the date of the journal entry
    * ''''date.up'''': Sort ascending order by the date of the journal entry
    * ''''alert.down'''': Sort in descending order by the alert level of the
    journal entry
    * ''''alert.up'''': Sort in ascending order by the alert level of the
    journal entry
     ''''property.down'''': Sort in descending order by the task or resource
    the journal entry is associated with
    * ''''property.up'''': Sort in ascending order by the task or resource the
    journal entry is associated with
    EOT
            )
      end
    
      def rule_sortAccounts
        pattern(%w( _sortaccounts !sortCriteria ), lambda {
          @property.set('sortAccounts', @val[1])
        })
        doc('sortaccounts', <<'EOT'
    Determines how the accounts are sorted in the report. Multiple criteria can be
    specified as a comma separated list. If one criteria is not sufficient to sort
    a group of accounts, the next criteria will be used to sort the accounts in
    this group.
    EOT
           )
      end
    
      def rule_sortResources
        pattern(%w( _sortresources !sortCriteria ), lambda {
          @property.set('sortResources', @val[1])
        })
        doc('sortresources', <<'EOT'
    Determines how the resources are sorted in the report. Multiple criteria can be
    specified as a comma separated list. If one criteria is not sufficient to sort
    a group of resources, the next criteria will be used to sort the resources in
    this group.
    EOT
           )
      end
    
      def rule_sortTasks
        pattern(%w( _sorttasks !sortCriteria ), lambda {
          @property.set('sortTasks', @val[1])
        })
        doc('sorttasks', <<'EOT'
    Determines how the tasks are sorted in the report. Multiple criteria can be
    specified as comma separated list. If one criteria is not sufficient to sort a
    group of tasks, the next criteria will be used to sort the tasks within
    this group.
    EOT
           )
      end
    
      def rule_sortTree
        pattern(%w( $ID ), lambda {
          if @val[0] != 'tree'
            error('sorting_crit_exptd2',
                  "Sorting criterium expected (e.g. tree, start.up or " +
                  "plan.end.down).", @sourceFileInfo[0])
          end
          [ 'tree', true, -1 ]
        })
        arg(0, 'tree',
            'Use \'tree\' as first criteria to keep the breakdown structure.')
      end
      def rule_ssReportHeader
        pattern(%w( _statussheetreport !optionalID $STRING ), lambda {
          newReport(@val[1], @val[2], :statusSheet, @sourceFileInfo[0]) do
            @property.set('formats', [ :tjp ])
    
            unless (@project['trackingScenarioIdx'])
              error('ss_no_tracking_scenario',
                    'You must have a tracking scenario defined to use status sheets.')
            end
            # Show all tasks, sorted by id-up.
            @property.set('hideTask', LogicalExpression.new(LogicalOperation.new(0)))
            @property.set('sortTasks', [ [ 'id', true, -1 ] ])
            # Show all resources, sorted by seqno-up.
            @property.set('hideResource',
                          LogicalExpression.new(LogicalOperation.new(0)))
            @property.set('sortResources', [ [ 'seqno', true, -1 ] ])
            @property.set('loadUnit', :hours)
            @property.set('definitions', [])
          end
        })
        arg(2, 'file name', <<'EOT'
    The name of the status sheet report file to generate. It must end with a .tji
    extension, or use . to use the standard output channel.
    EOT
           )
      end
    
      def rule_ssReportAttributes
        optional
        repeatable
    
        pattern(%w( !hideresource ))
        pattern(%w( !hidetask ))
        pattern(%w( !reportEnd ))
        pattern(%w( !reportPeriod ))
        pattern(%w( !reportStart ))
        pattern(%w( !sortResources ))
        pattern(%w( !sortTasks ))
      end
    
      def rule_ssReportBody
        optionsRule('ssReportAttributes')
      end
    
      def rule_ssStatusAttributes
        optional
        repeatable
    
        pattern(%w( !author ))
        pattern(%w( !details ))
    
        pattern(%w( _flags !flagList ), lambda {
          @val[1].each do |flag|
            next if @journalEntry.flags.include?(flag)
    
            @journalEntry.flags << flag
          end
        })
        doc('flags.statussheet', <<'EOT'
    Status sheet entries can have flags attached to them. These can be used to
    include only entries in a report that have a certain flag.
    EOT
           )
    
        pattern(%w( !summary ))
      end
    
      def rule_ssStatusBody
        optional
        pattern(%w( _{ !ssStatusAttributes _} ))
      end
    
      def rule_ssStatusHeader
        pattern(%w( _status !alertLevel $STRING ), lambda {
          @journalEntry = JournalEntry.new(@project['journal'], @sheetEnd,
                                           @val[2], @property,
                                           @sourceFileInfo[0])
          @journalEntry.alertLevel = @val[1]
          @journalEntry.author = @sheetAuthor
          @journalEntry.moderators << @sheetModerator
        })
      end
    
      def rule_ssStatus
        pattern(%w( !ssStatusHeader !ssStatusBody ))
        doc('status.statussheet', <<'EOT'
    The status attribute can be used to describe the current status of the task or
    resource. The content of the status messages is added to the project journal.
    EOT
           )
      end
    
      def rule_statusSheet
        pattern(%w( !statusSheetHeader !statusSheetBody ), lambda {
          [ @sheetAuthor, @sheetStart, @sheetEnd ]
        })
        doc('statussheet', <<'EOT'
    A status sheet can be used to capture the status of various tasks outside of
    the regular task tree definition. It is intended for use by managers that
    don't directly work with the full project plan, but need to report the current
    status of each task or task-tree that they are responsible for.
    EOT
           )
        example('StatusSheet')
      end
    
      def rule_statusSheetAttributes
        optional
        repeatable
    
        pattern(%w( !statusSheetTask ))
      end
    
      def rule_statusSheetBody
        optionsRule('statusSheetAttributes')
      end
    
      def rule_statusSheetFile
        pattern(%w( !statusSheet . ), lambda {
          @val[0]
        })
        lastSyntaxToken(1)
      end
    
    
      def rule_statusSheetHeader
        pattern(%w( _statussheet !resourceId !valIntervalOrDate ), lambda {
          unless @project['trackingScenarioIdx']
            error('ss_no_tracking_scenario',
                  'No trackingscenario defined.')
          end
          @sheetAuthor = @val[1]
          @sheetModerator = @val[1]
          @sheetStart = @val[2].start
          @sheetEnd = @val[2].end
          # Make sure that we don't have any status sheet entries from the same
          # author for the same report period. There may have been a previous
          # submission of the same report and this is an update to it. All old
          # entries must be removed before we process the sheet.
          @project['journal'].delete_if do |e|
            # Journal entries from status sheets have the sheet end date as entry
            # date.
            e.moderators.include?(@sheetModerator) && e.date == @sheetEnd
          end
        })
        arg(1, 'reporter', <<'EOT'
    The ID of a defined resource. This identifies the status reporter. Unless the
    status entries provide a different author, the sheet author will be used as
    status entry author.
    EOT
           )
      end
    
      def rule_statusSheetReport
        pattern(%w( !ssReportHeader !ssReportBody ), lambda {
          @property = nil
        })
        doc('statussheetreport', <<'EOT'
    A status sheet report is a template for a status sheet. It collects all the
    status information of the top-level task that a resource is responsible for.
    This report is typically used by managers or team leads to review the time
    sheet status information and destill it down to a summary that can be
    forwarded to the next person in the reporting chain. The report will be for
    the specified [trackingscenario].
    EOT
           )
      end
    
      def rule_statusSheetTask
        pattern(%w( !statusSheetTaskHeader !statusSheetTaskBody), lambda {
          @property = @propertyStack.pop
        })
        doc('task.statussheet', <<'EOT'
    Opens the task with the specified ID to add a status report. Child task can be
    opened inside this context by specifying their relative ID to this parent.
    EOT
           )
      end
    
      def rule_statusSheetTaskAttributes
        optional
        repeatable
        pattern(%w( !ssStatus ))
        pattern(%w( !statusSheetTask ), lambda {
        })
      end
    
      def rule_statusSheetTaskBody
        optionsRule('statusSheetTaskAttributes')
      end
    
      def rule_statusSheetTaskHeader
        pattern(%w( _task !taskId ), lambda {
          if @property
            @propertyStack.push(@property)
          else
            @propertyStack = []
          end
          @property = @val[1]
        })
      end
    
      def rule_subNodeId
        optional
        pattern(%w( _: !idOrAbsoluteId ), lambda {
          @val[1]
        })
      end
    
      def rule_summary
        pattern(%w( _summary $STRING ), lambda {
          return if @val[1].empty?
    
          if @val[1].length > 480
            error('ts_summary_too_long',
                  "The summary text must be 480 characters long or shorter. " +
                  "This text has #{@val[1].length} characters.",
                  @sourceFileInfo[1])
          end
          if @val[1] == "A summary text\n"
              error('ts_default_summary',
                    "'A summary text' is not a valid summary",
                    @sourceFileInfo[1])
          end
          rtTokenSetIntro =
            [ :LINEBREAK, :SPACE, :WORD, :BOLD, :ITALIC, :CODE, :BOLDITALIC,
              :HREF, :HREFEND ]
          @journalEntry.summary = newRichText(@val[1], @sourceFileInfo[1],
                                              rtTokenSetIntro)
        })
        doc('summary', <<'EOT'
    This is the introductory part of the journal or status entry. It should
    only summarize the full entry but should contain more details than the
    headline. The text including formatting characters must be 240 characters long
    or less.
    EOT
           )
        arg(1, 'text', <<'EOT'
    The text will be interpreted as [[Rich_Text_Attributes|Rich Text]]. Only a
    small subset of the markup is supported for this attribute. You can use word
    formatting, hyperlinks and paragraphs.
    EOT
           )
      end
    
      def rule_supplement
        pattern(%w( !supplementAccount !accountBody ), lambda {
          @property = @idStack.pop
        })
        pattern(%w( !supplementReport !reportBody ), lambda {
          @property = @idStack.pop
        })
        pattern(%w( !supplementResource !resourceBody ), lambda {
          @property = @idStack.pop
        })
        pattern(%w( !supplementTask !taskBody ), lambda {
          @property = @idStack.pop
        })
      end
    
      def rule_supplementAccount
        pattern(%w( _account !accountId ), lambda {
          @idStack.push(@property)
          @property = @val[1]
        })
        arg(1, 'account ID', 'The ID of an already defined account.')
      end
    
      def rule_supplementReport
        pattern(%w( _report !reportId ), lambda {
          @idStack.push(@property)
          @property = @val[1]
        })
        arg(1, 'report ID', 'The absolute ID of an already defined report.')
      end
    
      def rule_supplementResource
        pattern(%w( _resource !resourceId ), lambda {
          @idStack.push(@property)
          @property = @val[1]
        })
        arg(1, 'resource ID', 'The ID of an already defined resource.')
      end
    
      def rule_supplementTask
        pattern(%w( _task !taskId ), lambda {
          @idStack.push(@property)
          @property = @val[1]
        })
        arg(1, 'task ID', 'The absolute ID of an already defined task.')
      end
    
      def rule_tagfile
        pattern(%w( !tagfileHeader !tagfileBody ), lambda {
          @property = nil
        })
        doc('tagfile', <<'EOT'
    The tagfile report generates a file that maps properties to source file
    locations. This can be used by editors to quickly jump to a certain task or
    resource definition. Currently only the ctags format is supported that is used
    by editors like [http://www.vim.org|vim].
    EOT
           )
      end
    
      def rule_tagfileHeader
        pattern(%w( _tagfile !optionalID $STRING ), lambda {
          newReport(@val[1], @val[2], :tagfile, @sourceFileInfo[0]) do
            @property.set('formats', [ :ctags ])
    
            # Include all tasks.
            @property.set('hideTask', LogicalExpression.new(LogicalOperation.new(0)))
            @property.set('sortTasks', [ [ 'seqno', true, -1 ] ])
            # Include all resources.
            @property.set('hideResource',
                          LogicalExpression.new(LogicalOperation.new(0)))
            @property.set('sortResources', [ [ 'seqno', true, -1 ] ])
          end
        })
        arg(2, 'file name', <<'EOT'
    The name of the tagfile to generate. Use ''''tags'''' if you want vim and
    other tools to find it automatically.
    EOT
           )
      end
    
      def rule_tagfileAttributes
        optional
        repeatable
    
        pattern(%w( !hideresource ))
        pattern(%w( !hidetask ))
        pattern(%w( !rollupresource ))
        pattern(%w( !rolluptask ))
      end
    
      def rule_tagfileBody
        optionsRule('tagfileAttributes')
      end
    
    
      def rule_task
        pattern(%w( !taskHeader !taskBody ), lambda {
          @property = @property.parent
        })
        doc('task', <<'EOT'
    Tasks are the central elements of a project plan. Use a task to specify the
    various steps and phases of the project. Depending on the attributes of that
    task, a task can be a container task, a milestone or a regular leaf task. The
    latter may have resources assigned. By specifying dependencies the user can
    force a certain sequence of tasks.
    
    Tasks have a local name space. All IDs must be unique within the tasks
    that belong to the same enclosing task.
    EOT
           )
      end
    
      def rule_taskAttributes
        repeatable
        optional
    
        pattern(%w( _adopt !taskList ), lambda {
          @val[1].each do |task|
            @property.adopt(task)
          end
        })
        level(:experimental)
        doc('adopt.task', <<'EOT'
    Add a previously defined task and its sub-tasks to this task. This can be used
    to create virtual projects that contain task (sub-)trees that are originally
    defined in another task context. Adopted tasks don't inherit anything from
    their step parents. However, the adopting task is scheduled to fit all adopted
    sub-tasks.
    
    A top-level task and all its sub-tasks must never contain the same task more
    than once. All reports must use appropriate filters by setting [[taskroot]],
    [[hidetask]] or [[rolluptask]] to ensure that no tasks are contained more than
    once in the report.
    EOT
           )
    
        pattern(%w( !journalEntry ))
    
        pattern(%w( _note $STRING ), lambda {
          @property.set('note', newRichText(@val[1], @sourceFileInfo[1]))
        })
        doc('note.task', <<'EOT'
    Attach a note to the task. This is usually a more detailed specification of
    what the task is about.
    EOT
           )
    
        pattern(%w( !purge ))
    
        pattern(%w( _supplement !supplementTask !taskBody ), lambda {
          @property = @idStack.pop
        })
        doc('supplement.task', <<'EOT'
    The supplement keyword provides a mechanism to add more attributes to already
    defined tasks. The additional attributes must obey the same rules as in
    regular task definitions and must be enclosed by curly braces.
    
    This construct is primarily meant for situations where the information about a
    task is split over several files. E. g. the vacation dates for the
    resources may be in a separate file that was generated by some other tool.
    EOT
           )
        example('Supplement', 'task')
    
        pattern(%w( !task ))
        pattern(%w( !taskScenarioAttributes ))
        pattern(%w( !scenarioIdCol !taskScenarioAttributes ), lambda {
          @scenarioIdx = 0
        })
        # Other attributes will be added automatically.
      end
    
      def rule_taskBody
        optionsRule('taskAttributes')
      end
    
      def rule_taskBooking
        pattern(%w( !taskBookingHeader !bookingBody ), lambda {
          unless @project.scenario(@scenarioIdx).get('ownbookings')
            error('no_own_task_booking',
                  "The scenario #{@project.scenario(@scenarioIdx).fullId} " +
                  'inherits its bookings from the tracking ' +
                  'scenario. You cannot specificy additional bookings for it.')
          end
          @val[0].task.addBooking(@scenarioIdx, @val[0])
        })
      end
    
      def rule_taskBookingHeader
        pattern(%w( !resourceId !valIntervals ), lambda {
          checkBooking(@property, @val[0])
          @booking = Booking.new(@val[0], @property, @val[1])
          @booking.sourceFileInfo = @sourceFileInfo[0]
          @booking
        })
      end
    
      def rule_taskDep
        pattern(%w( !taskDepHeader !taskDepBody ), lambda {
          @val[0]
        })
      end
    
      def rule_taskDepAttributes
        optional
        repeatable
    
        pattern(%w( _gapduration !intervalDuration ), lambda {
          @taskDependency.gapDuration = @val[1]
        })
        doc('gapduration', <<'EOT'
    Specifies the minimum required gap between the end of a preceding task and the
    start of this task, or the start of a following task and the end of this task.
    This is calendar time, not working time. 7d means one week.
    EOT
           )
    
        pattern(%w( _gaplength !workingDuration ), lambda {
          @taskDependency.gapLength = @val[1]
        })
        doc('gaplength', <<'EOT'
    Specifies the minimum required gap between the end of a preceding task and the
    start of this task, or the start of a following task and the end of this task.
    This is working time, not calendar time. 7d means 7 working days, not one
    week. Whether a day is considered a working day or not depends on the defined
    working hours and global leaves.
    EOT
           )
    
        pattern(%w( _onend ), lambda {
          @taskDependency.onEnd = true
        })
        doc('onend', <<'EOT'
    The target of the dependency is the end of the task.
    EOT
           )
    
        pattern(%w( _onstart ), lambda {
          @taskDependency.onEnd = false
        })
        doc('onstart', <<'EOT'
    The target of the dependency is the start of the task.
    EOT
           )
      end
    
      def rule_taskDepBody
        optionsRule('taskDepAttributes')
      end
    
      def rule_taskDepHeader
        pattern(%w( !taskDepId ), lambda {
          @taskDependency = TaskDependency.new(@val[0], true)
        })
      end
    
      def rule_taskDepId
        singlePattern('$ABSOLUTE_ID')
        arg(0, 'ABSOLUTE ID', <<'EOT'
    A reference using the full qualified ID of a task. The IDs of all enclosing
    parent tasks must be prepended to the task ID and separated with a dot, e.g.
    ''''proj.plan.doc''''.
    EOT
             )
    
        singlePattern('$ID')
        arg(0, 'ID', 'Just the ID of the task without and parent IDs.')
    
        pattern(%w( !relativeId ), lambda {
          task = @property
          id = @val[0]
          while task && id[0] == ?!
            id = id.slice(1, id.length)
            task = task.parent
          end
          error('too_many_bangs',
                "Too many '!' for relative task in this context.",
                @sourceFileInfo[0], @property) if id[0] == ?!
          if task
            task.fullId + '.' + id
          else
            id
          end
        })
        arg(0, 'RELATIVE ID', <<'EOT'
    A relative task ID always starts with one or more exclamation marks and is
    followed by a task ID. Each exclamation mark lifts the scope where the ID is
    looked for to the enclosing task. The ID may contain some of the parent IDs
    separated by dots, e. g. ''''!!plan.doc''''.
    EOT
             )
      end
    
      def rule_taskDepList
        pattern(%w( !taskDep !moreDepTasks ), lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_taskHeader
        pattern(%w( _task !optionalID $STRING ), lambda {
          if @property.nil? && !@taskprefix.empty?
            @property = @project.task(@taskprefix)
          end
          if @val[1]
            id = (@property ? @property.fullId + '.' : '') + @val[1]
            if @project.task(id)
              error('task_exists', "Task #{id} has already been defined.",
                    @sourceFileInfo[0])
            end
          end
          @property = Task.new(@project, @val[1], @val[2], @property)
          @property['projectid', 0] = @projectId
          @property.sourceFileInfo = @sourceFileInfo[0]
          @property.inheritAttributes
          @scenarioIdx = 0
        })
        arg(2, 'name', 'The name of the task')
      end
    
      def rule_taskId
        pattern(%w( !taskIdUnverifd ), lambda {
          id = @val[0]
          if @property && @property.is_a?(Task)
            # In case we have a nested supplement, we need to prepend the parent ID.
            id = @property.fullId + '.' + id
          else
            id = @taskprefix + '.' + id unless @taskprefix.empty?
          end
          if (task = @project.task(id)).nil?
            error('unknown_task', "Unknown task #{id}", @sourceFileInfo[0])
          end
          task
        })
      end
    
      def rule_taskIdUnverifd
        singlePattern('$ABSOLUTE_ID')
        singlePattern('$ID')
      end
    
      def rule_taskList
        listRule('moreTasks', '!absoluteTaskId')
      end
    
      def rule_taskPeriod
        pattern(%w( _period !valInterval), lambda {
          @property['start', @scenarioIdx] = @val[1].start
          @property['end', @scenarioIdx] = @val[1].end
        })
        doc('period.task', <<'EOT'
    This property is a shortcut for setting the start and end property at the same
    time. In contrast to using these, it does not change the scheduling direction.
    EOT
           )
      end
    
      def rule_taskPred
        pattern(%w( !taskPredHeader !taskDepBody ), lambda {
          @val[0]
        })
      end
    
      def rule_taskPredHeader
        pattern(%w( !taskDepId ), lambda {
          @taskDependency = TaskDependency.new(@val[0], false)
        })
      end
    
      def rule_taskPredList
        pattern(%w( !taskPred !morePredTasks ), lambda {
          [ @val[0] ] + (@val[1].nil? ? [] : @val[1])
        })
      end
    
      def rule_taskReport
        pattern(%w( !taskReportHeader !reportBody ), lambda {
          @property = @property.parent
        })
        doc('taskreport', <<'EOT'
    The report lists tasks and their respective values in a table. To reduce the
    list of included tasks, you can use the [[hidetask]], [[rolluptask]] or
    [[taskroot]] attributes. The order of the task can be controlled with
    [[sorttasks]]. If the first sorting criteria is tree sorting, the parent tasks
    will always be included to form the tree. Tree sorting is the default. You
    need to change it if you do not want certain parent tasks to be included in
    the report.
    
    By default, all the resources that are allocated to each task are hidden, but
    they can be listed as well. Use the [[hideresource]] attribute to select which
    resources should be included.
    EOT
           )
        example('HtmlTaskReport')
      end
    
      def rule_taskReportHeader
        pattern(%w( _taskreport !optionalID !reportName ), lambda {
          newReport(@val[1], @val[2], :taskreport, @sourceFileInfo[0]) do
            unless @property.modified?('columns')
              # Set the default columns for this report.
              %w( bsi name start end effort chart ).each do |col|
                @property.get('columns') <<
                TableColumnDefinition.new(col, columnTitle(col))
              end
            end
            # Show all tasks, sorted by tree, start-up, seqno-up.
            unless @property.modified?('hideTask')
              @property.set('hideTask',
                            LogicalExpression.new(LogicalOperation.new(0)))
            end
            unless @property.modified?('sortTasks')
              @property.set('sortTasks',
                            [ [ 'tree', true, -1 ],
                              [ 'start', true, 0 ],
                              [ 'seqno', true, -1 ] ])
            end
            # Show no resources, but set sorting to id-up.
            unless @property.modified?('hideResource')
              @property.set('hideResource',
                            LogicalExpression.new(LogicalOperation.new(1)))
            end
            unless @property.modified?('sortResources')
              @property.set('sortResources', [ [ 'id', true, -1 ] ])
            end
          end
        })
      end
    
      def rule_taskScenarioAttributes
    
        pattern(%w( _account $ID ))
        level(:removed)
        also('chargeset')
        doc('account.task', '')
    
        pattern(%w( !allocate ))
    
        pattern(%w( _booking !taskBooking ))
        doc('booking.task', <<'EOT'
    The booking attribute can be used to report actually completed work.  A task
    with bookings must be [[scheduling|scheduled]] in ''''asap'''' mode.  If the
    scenario is not the [[trackingscenario|tracking scenario]] or derived from it,
    the scheduler will not allocate resources prior to the current date or the
    date specified with [[now]] when a task has at least one booking.
    
    Bookings are only valid in the scenario they have been defined in. They will
    in general not be passed to any other scenario. If you have defined a
    [[trackingscenario|tracking scenario]], the bookings of this scenario will be
    passed to all the derived scenarios of the tracking scenario.
    
    The sloppy attribute can be used when you want to skip non-working time or
    other allocations automatically. If it's not given, all bookings must only
    cover working time for the resource.
    
    The booking attributes is designed to capture the exact amount of completed
    work. This attribute is not really intended to specify completed effort by
    hand. Usually, booking statements are generated by [[export]] reports. The
    [[sloppy.booking|sloppy]] and [[overtime.booking|overtime]] attributes are
    only kludge for users who want to write them manually.
    Bookings can be used to report already completed work by specifying the exact
    time intervals a certain resource has worked on this task.
    
    Bookings can be defined in the task or resource context. If you move tasks
    around very often, put your bookings in the task context.
    EOT
           )
        also(%w( booking.resource ))
        example('Booking')
    
        pattern(%w( _charge !number !chargeMode ), lambda {
          checkContainer('charge')
    
          if @property['chargeset', @scenarioIdx].empty?
            error('task_without_chargeset',
                  'The task does not have a chargeset defined.',
                  @sourceFileInfo[0], @property)
          end
          case @val[2]
          when 'onstart'
            mode = :onStart
            amount = @val[1]
          when 'onend'
            mode = :onEnd
            amount = @val[1]
          when 'perhour'
            mode = :perDiem
            amount = @val[1] * 24
          when 'perday'
            mode = :perDiem
            amount = @val[1]
          when 'perweek'
            mode = :perDiem
            amount = @val[1] / 7.0
          end
          @property['charge', @scenarioIdx] +=
            [ Charge.new(amount, mode, @property, @scenarioIdx) ]
        })
        doc('charge', <<'EOT'
    Specify a one-time or per-period charge to a certain account. The charge can
    occur at the start of the task, at the end of it, or continuously over the
    duration of the task. The accounts to be charged are determined by the
    [[chargeset]] setting of the task.
    EOT
           )
        arg(1, 'amount', 'The amount to charge')
    
        pattern(%w( !chargeset ))
    
        pattern(%w( _complete !number), lambda {
          if @val[1] < 0.0 || @val[1] > 100.0
            error('task_complete', "Complete value must be between 0 and 100",
                  @sourceFileInfo[1], @property)
          end
          @property['complete', @scenarioIdx] = @val[1]
        })
        doc('complete', <<'EOT'
    Specifies what percentage of the task is already completed. This can be useful
    for simple progress tracking like in a TODO list. The provided completion
    degree is used for the ''''complete'''' and ''''gauge'''' columns in reports.
    Reports with calendar elements may show the completed part of the task in a
    different color.
    
    The completion percentage has no impact on the scheduler. It's meant for
    documentation purposes only.
    EOT
            )
        example('Complete', '1')
    
        arg(1, 'percent', 'The percent value. It must be between 0 and 100.')
    
        pattern(%w( _depends !taskDepList ), lambda {
          checkContainer('depends')
          @property['depends', @scenarioIdx] += @val[1]
          begin
            @property['forward', @scenarioIdx] = true
          rescue AttributeOverwrite
          end
        })
        doc('depends', <<'EOT'
    Specifies that the task cannot start before the specified tasks have been
    finished.
    
    By using the 'depends' attribute, the scheduling policy is automatically set
    to asap. If both depends and precedes are used, the last policy counts.
    EOT
            )
        example('Depends1')
        pattern(%w( _duration !calendarDuration ), lambda {
          setDurationAttribute('duration', @val[1])
        })
        doc('duration', <<'EOT'
    Specifies the time the task should last. This is calendar time, not working
    time. 7d means one week. If resources are specified they are allocated when
    available. Availability of resources has no impact on the duration of the
    task. It will always be the specified duration.
    
    Tasks may not have subtasks if this attribute is used. Setting this attribute
    will reset the [[effort]] and [[length]] attributes.
    EOT
           )
        example('Durations')
        also(%w( effort length ))
    
        pattern(%w( _effort !workingDuration ), lambda {
          if @val[1] <= 0
            error('effort_zero', "Effort value must at least as large as the " +
                                 "timing resolution " +
                                 "(#{@project['scheduleGranularity'] / 60}min).",
                  @sourceFileInfo[1], @property)
          end
          setDurationAttribute('effort', @val[1])
        })
        doc('effort', <<'EOT'
    Specifies the effort needed to complete the task. An effort of ''''6d'''' (6
    resource-days) can be done with 2 full-time resources in 3 working days. The
    task will not finish before the allocated resources have contributed the
    specified effort. Hence the duration of the task will depend on the
    availability of the allocated resources. The specified effort value must be at
    least as large as the [[timingresolution]].
    
    WARNING: In almost all real world projects effort is not the product of time
    and resources. This is only true if the task can be partitioned without adding
    any overhead. For more information about this read ''The Mythical Man-Month'' by
    Frederick P. Brooks, Jr.
    
    Tasks may not have subtasks if this attribute is used. Setting this attribute
    will reset the [[duration]] and [[length]] attributes. A task with an effort
    value cannot be a [[milestone]].
    EOT
           )
        example('Durations')
        also(%w( duration length ))
    
        pattern(%w( _end !valDate ), lambda {
          @property['end', @scenarioIdx] = @val[1]
          begin
            @property['forward', @scenarioIdx] = false
          rescue AttributeOverwrite
          end
        })
        doc('end', <<'EOT'
    The end attribute provides a guideline to the scheduler when the task should
    end. It will never end later, but it may end earlier when allocated
    resources are not available that long. When an end date is provided for a
    container task, it will be passed down to ALAP task that don't have a well
    defined end criteria.
    
    Setting an end date will implicitely set the scheduling policy for this task
    to ALAP.
    EOT
           )
        example('Export', '1')
        pattern(%w( _endcredit !number ), lambda {
          @property['charge', @scenarioIdx] =
            @property['charge', @scenarioIdx] +
            [ Charge.new(@val[1], :onEnd, @property, @scenarioIdx) ]
        })
        level(:deprecated)
        doc('endcredit', <<'EOT'
    Specifies an amount that is credited to the accounts specified by the
    [[chargeset]] attributes at the moment the tasks ends.
    EOT
           )
        also('charge')
        example('Account', '1')
        pattern(%w( !flags ))
        doc('flags.task', <<'EOT'
    Attach a set of flags. The flags can be used in logical expressions to filter
    properties from the reports.
    EOT
           )
    
        pattern(%w( !fail ))
    
        pattern(%w( _length !workingDuration ), lambda {
          setDurationAttribute('length', @val[1])
        })
        doc('length', <<'EOT'
    Specifies the duration of this task as working time, not calendar time. 7d
    means 7 working days, or 7 times 8 hours (assuming default settings), not one
    week.
    
    A task with a length specification may have resource allocations. Resources
    are allocated when they are available.  There is no guarantee that the task
    will get any resources allocated.  The availability of resources has no impact
    on the duration of the task. A time slot where none of the specified resources
    is available is still considered working time, if there is no global vacation
    and global working hours are defined accordingly.
    
    For the length calculation, the global working hours and the global leaves
    matter unless the task has [[shifts.task|shifts]] assigned. In the latter case
    the working hours and leaves of the shift apply for the specified period to
    determine if a slot is working time or not. If a resource has additinal
    working hours defined, it's quite possible that a task with a length of 5d
    will have an allocated effort larger than 40 hours.  Resource working hours
    only have an impact on whether an allocation is made or not for a particular
    time slot. They don't effect the resulting duration of the task.
    
    Tasks may not have subtasks if this attribute is used. Setting this attribute
    will reset the [[duration]], [[effort]] and [[milestone]] attributes.
    EOT
           )
        also(%w( duration effort ))
    
        pattern(%w( !limits ), lambda {
          checkContainer('limits')
          @property['limits', @scenarioIdx] = @val[0]
        })
        doc('limits.task', <<'EOT'
    Set per-interval allocation limits for the task. This setting affects all allocations for this task.
    EOT
           )
        example('Limits-1', '2')
    
        pattern(%w( _maxend !valDate ), lambda {
          @property['maxend', @scenarioIdx] = @val[1]
        })
        doc('maxend', <<'EOT'
    Specifies the maximum wanted end time of the task. The value is not used
    during scheduling, but is checked after all tasks have been scheduled. If the
    end of the task is later than the specified value, then an error is reported.
    EOT
           )
    
        pattern(%w( _maxstart !valDate ), lambda {
          @property['maxstart', @scenarioIdx] = @val[1]
        })
        doc('maxstart', <<'EOT'
    Specifies the maximum wanted start time of the task. The value is not used
    during scheduling, but is checked after all tasks have been scheduled. If the
    start of the task is later than the specified value, then an error is
    reported.
    EOT
           )
    
        pattern(%w( _milestone ), lambda {
          setDurationAttribute('milestone')
        })
        doc('milestone', <<'EOT'
    Turns the task into a special task that has no duration. You may not specify a
    duration, length, effort or subtasks for a milestone task.
    
    A task that only has a start or an end specification and no duration
    specification, inherited start or end dates, no dependencies or sub tasks,
    will be recognized as milestone automatically.
    EOT
           )
    
        pattern(%w( _minend !valDate ), lambda {
          @property['minend', @scenarioIdx] = @val[1]
        })
        doc('minend', <<'EOT'
    Specifies the minimum wanted end time of the task. The value is not used
    during scheduling, but is checked after all tasks have been scheduled. If the
    end of the task is earlier than the specified value, then an error is
    reported.
    EOT
           )
    
        pattern(%w( _minstart !valDate ), lambda {
          @property['minstart', @scenarioIdx] = @val[1]
        })
        doc('minstart', <<'EOT'
    Specifies the minimum wanted start time of the task. The value is not used
    during scheduling, but is checked after all tasks have been scheduled. If the
    start of the task is earlier than the specified value, then an error is
    reported.
    EOT
           )
    
        pattern(%w( _startcredit !number ), lambda {
          @property['charge', @scenarioIdx] +=
            [ Charge.new(@val[1], :onStart, @property, @scenarioIdx) ]
        })
        level(:deprecated)
        doc('startcredit', <<'EOT'
    Specifies an amount that is credited to the account specified by the
    [[chargeset]] attributes at the moment the tasks starts.
    EOT
           )
        also('charge')
        pattern(%w( !taskPeriod ))
    
        pattern(%w( _precedes !taskPredList ), lambda {
          checkContainer('precedes')
            @property['precedes', @scenarioIdx] += @val[1]
          begin
            @property['forward', @scenarioIdx] = false
          rescue AttributeOverwrite
          end
        })
        doc('precedes', <<'EOT'
    Specifies that the tasks with the specified IDs cannot start before the task
    has been finished. If multiple IDs are specified, they must be separated by
    commas. IDs must be either global or relative. A relative ID starts with a
    number of '!'. Each '!' moves the scope to the parent task. Global IDs do not
    contain '!', but have IDs separated by dots.
    
    By using the 'precedes' attribute, the scheduling policy is automatically set
    to alap. If both depends and precedes are used within a task, the last policy
    counts.
    EOT
           )
    
        pattern(%w( _priority $INTEGER ), lambda {
          if @val[1] < 0 || @val[1] > 1000
            error('task_priority', "Priority must have a value between 0 and 1000",
                  @sourceFileInfo[1], @property)
          end
          @property['priority', @scenarioIdx] = @val[1]
        })
        doc('priority', <<'EOT'
    Specifies the priority of the task. A task with higher priority is more
    likely to get the requested resources. The default priority value of all tasks
    is 500. Don't confuse the priority of a tasks with the importance or urgency
    of a task. It only increases the chances that the tasks gets the requested
    resources. It does not mean that the task happens earlier, though that is
    usually the effect you will see. It also does not have any effect on tasks
    that don't have any resources assigned (e.g. milestones).
    
    For milestones it will raise or lower the chances that task leading up the
    milestone will get their resources over task with equal priority that compete
    for the same resources.
    
    This attribute is inherited by subtasks if specified prior to the definition
    of the subtask.
    EOT
           )
        arg(1, 'value', 'Priority value (1 - 1000)')
        example('Priority')
    
        pattern(%w( _projectid $ID ), lambda {
          unless @project['projectids'].include?(@val[1])
            error('unknown_projectid', "Unknown project ID #{@val[1]}",
                  @sourceFileInfo[1])
          end
          begin
            @property['projectid', @scenarioIdx] = @val[1]
          rescue AttributeOverwrite
            # This attribute always overwrites the implicitely provided ID.
          end
        })
        doc('projectid.task', <<'EOT'
    In larger projects it may be desireable to work with different project IDs for
    parts of the project. This attribute assignes a new project ID to this task an
    all subsequently defined sub tasks. The project ID needs to be declared first using [[projectid]] or [[projectids]].
    EOT
           )
    
        pattern(%w( _responsible !resourceList ), lambda {
          @property['responsible', @scenarioIdx] += @val[1]
          @property['responsible', @scenarioIdx].uniq!
        })
        doc('responsible', <<'EOT'
    The ID of the resource that is responsible for this task. This value is for
    documentation purposes only. It's not used by the scheduler.
    EOT
           )
    
        pattern(%w( _scheduled ), lambda {
          if (@property['milestone', @scenarioIdx] &&
              @property['start', @scenarioIdx].nil? &&
              @property['end', @scenarioIdx].nil?) ||
             (!@property['milestone', @scenarioIdx] &&
              (@property['start', @scenarioIdx].nil? ||
               @property['end', @scenarioIdx].nil?))
            error('not_scheduled',
                  "Task #{@property.fullId} is marked as scheduled but does not " +
                  'have a fixed start and end date.',
                  @sourceFileInfo[0], @property)
          end
          @property['scheduled', @scenarioIdx] = true
        })
        doc('scheduled', <<'EOT'
    This is mostly for internal use. It specifies that the task should be ignored
    for scheduling in the scenario. This option only makes sense if you provide
    all resource [[booking.resource|bookings]] manually. Without booking
    statements, the task will be reported with 0 effort and no resources assigned.
    EOT
           )
    
        pattern(%w( _scheduling !schedulingDirection ), lambda {
          if @val[1] == 'alap'
            begin
              @property['forward', @scenarioIdx] = false
            rescue AttributeOverwrite
            end
          elsif @val[1] == 'asap'
            begin
              @property['forward', @scenarioIdx] = true
            rescue AttributeOverwrite
            end
          end
        })
        doc('scheduling', <<'EOT'
    Specifies the scheduling policy for the task. A task can be scheduled from
    start to end (As Soon As Possible, asap) or from end to start (As Late As
    Possible, alap).
    
    A task can be scheduled from start to end (ASAP mode) when it has a hard
    (start) or soft (depends) criteria for the start time. A task can be scheduled
    from end to start (ALAP mode) when it has a hard (end) or soft (precedes)
    criteria for the end time.
    
    Some task attributes set the scheduling policy implicitly. This attribute can
    be used to explicitly set the scheduling policy of the task to a certain
    direction. To avoid it being overwritten again by an implicit attribute this
    attribute should always be the last attribute of the task.
    
    A random mixture of ASAP and ALAP tasks can have unexpected side effects on
    the scheduling of the project. It increases significantly the scheduling
    complexity and results in much longer scheduling times. Especially in projects
    with many hundreds of tasks the scheduling time of a project with a mixture of
    ASAP and ALAP times can be 2 to 10 times longer. When the projects contains
    chains of ALAP and ASAP tasks the tasks further down the dependency chain will
    be served much later than other non-chained task even when they have a much
    higher priority. This can result in situations where high priority tasks do
    not get their resources even though the parallel competing tasks have a much
    lower priority.
    
    ALAP tasks may not have [[booking.task|bookings]] since the first booked slot
    determines the start date of the task and prevents it from being scheduled
    from end to start.
    
    As a general rule, try to avoid ALAP tasks whenever possible. Have a close
    eye on tasks that have been switched implicitly to ALAP mode because the
    end attribute comes after the start attribute.
    EOT
           )
    
        pattern(%w( !taskShiftAssignments !shiftAssignments ), lambda {
          checkContainer('shift')
          # Set same value again to set the 'provided' state for the attribute.
          begin
            @property['shifts', @scenarioIdx] = @shiftAssignments
          rescue AttributeOverwrite
            # Multiple shift assignments are a common idiom, so don't warn about
            # them.
          end
          @shiftAssignments = nil
        })
        level(:deprecated)
        doc('shift.task', <<'EOT'
    This keyword has been deprecated. Please use [[shifts.task|shifts
    (task)]] instead.
    EOT
           )
        also('shifts.task')
    
        pattern(%w( !taskShiftsAssignments !shiftAssignments ), lambda {
          checkContainer('shifts')
          begin
            @property['shifts', @scenarioIdx] = @shiftAssignments
          rescue AttributeOverwrite
            # Multiple shift assignments are a common idiom, so don't warn about
            # them.
          end
          @shiftAssignments = nil
        })
        doc('shifts.task', <<'EOT'
    Limits the working time for this task during the during the specified interval
    to the working hours of the given shift. Multiple shifts can be defined, but
    shift intervals may not overlap. This is an additional working time
    restriction ontop of the working hours of the allocated resources. It does not
    replace the resource working hour restrictions. For a resource to be assigned
    to a time slot, both the respective task shift as well as the resource working
    hours must declare the time slot as duty slot.
    EOT
            )
    
        pattern(%w( _start !valDate), lambda {
          @property['start', @scenarioIdx] = @val[1]
          begin
            @property['forward', @scenarioIdx] = true
          rescue AttributeOverwrite
          end
        })
        doc('start', <<'EOT'
    The start attribute provides a guideline to the scheduler when the task should
    start. It will never start earlier, but it may start later when allocated
    resources are not available immediately. When a start date is provided for a
    container task, it will be passed down to ASAP task that don't have a well
    defined start criteria.
    
    Setting a start date will implicitely set the scheduling policy for this task
    to ASAP.
    EOT
           )
        also(%w( end period.task maxstart minstart scheduling ))
    
        pattern(%w( !warn ))
    
        # Other attributes will be added automatically.
      end
    
      def rule_taskShiftAssignments
        pattern(%w( _shift ), lambda {
          @shiftAssignments = @property['shifts', @scenarioIdx]
        })
      end
    
      def rule_taskShiftsAssignments
        pattern(%w( _shifts ), lambda {
          @shiftAssignments = @property['shifts', @scenarioIdx]
        })
      end
    
      def rule_textReport
        pattern(%w( !textReportHeader !reportBody ), lambda {
          @property = @property.parent
        })
        doc('textreport', <<'EOT'
    This report consists of 5 RichText sections, a header, a center section with a
    left and right margin and a footer. The sections may contain the output of
    other defined reports.
    EOT
           )
        example('textreport')
      end
    
      def rule_textReportHeader
        pattern(%w( _textreport !optionalID !reportName ), lambda {
          newReport(@val[1], @val[2], :textreport, @sourceFileInfo[0])
        })
      end
    
      def rule_timeformat
        pattern(%w( _timeformat $STRING ), lambda {
          @val[1]
        })
        doc('timeformat', <<'EOT'
    Determines how time specifications in reports look like.
    EOT
           )
        arg(1, 'format', <<'EOT'
    Ordinary characters placed in the format string are copied without
    conversion. Conversion specifiers are introduced by a `%' character, and are
    replaced in s as follows:
    
    * ''''%a''''  The abbreviated weekday name according to the current locale.
    
    * ''''%A''''  The full weekday name according to the current locale.
    
    * ''''%b''''  The abbreviated month name according to the current locale.
    
    * ''''%B''''  The full month name according to the current locale.
    
    * ''''%c''''  The preferred date and time representation for the current locale.
    
    * ''''%C''''  The century number (year/100) as a 2-digit integer. (SU)
    
    * ''''%d''''  The day of the month as a decimal number (range 01 to 31).
    
    * ''''%e''''  Like ''''%d'''', the day of the month as a decimal number, but a
    leading zero is replaced by a space. (SU)
    
    * ''''%E''''  Modifier: use alternative format, see below. (SU)
    
    * ''''%F''''  Equivalent to ''''%Y-%m-%d'''' (the ISO 8601 date format). (C99)
    
    * ''''%G''''  The ISO 8601 year with century as a decimal number. The 4-digit
    year corresponding to the ISO week number (see %V). This has the same format
    and value as ''''%y'''', except that if the ISO week number belongs to the
    previous or next year, that year is used instead. (TZ)
    
    * ''''%g''''  Like %G, but without century, i.e., with a 2-digit year (00-99).
    (TZ)
    
    * ''''%h''''  Equivalent to ''''%b''''. (SU)
    
    * ''''%H''''  The hour as a decimal number using a 24-hour clock (range 00 to
    23).
    
    * ''''%I''''  The hour as a decimal number using a 12-hour clock (range 01 to
    12).
    
    * ''''%j''''  The day of the year as a decimal number (range 001 to 366).
    
    * ''''%k''''  The hour (24-hour clock) as a decimal number (range 0 to 23);
    single digits are preceded by a blank. (See also ''''%H''''.) (TZ)
    
    * ''''%l''''  The hour (12-hour clock) as a decimal number (range 1 to 12);
    single digits are preceded by a blank. (See also ''''%I''''.) (TZ)
    
    * ''''%m''''  The month as a decimal number (range 01 to 12).
    
    * ''''%M''''  The minute as a decimal number (range 00 to 59).
    
    * ''''%n''''  A newline character. (SU)
    
    * ''''%O''''  Modifier: use alternative format, see below. (SU)
    
    * ''''%p''''  Either 'AM' or 'PM' according to the given time value, or the
    corresponding strings for the current locale. Noon is treated as `pm' and
    midnight as 'am'.
    
    * ''''%P''''  Like %p but in lowercase: 'am' or 'pm' or ''''%a''''
    corresponding string for the current locale. (GNU)
    
    * ''''%r''''  The time in a.m. or p.m. notation. In the POSIX locale this is
    equivalent to ''''%I:%M:%S %p''''. (SU)
    
    * ''''%R''''  The time in 24-hour notation (%H:%M). (SU) For a version
    including the seconds, see ''''%T'''' below.
    
    * ''''%s''''  The number of seconds since the Epoch, i.e., since 1970-01-01
    00:00:00 UTC.  (TZ)
    
    * ''''%S''''  The second as a decimal number (range 00 to 61).
    
    * ''''%t''''  A tab character. (SU)
    
    * ''''%T''''  The time in 24-hour notation (%H:%M:%S). (SU)
    
    * ''''%u''''  The day of the week as a decimal, range 1 to 7, Monday being 1.
    See also ''''%w''''. (SU)
    
    * ''''%U''''  The week number of the current year as a decimal number, range
    00 to 53, starting with the first Sunday as the first day of week 01. See also
    ''''%V'''' and ''''%W''''.
    
    * ''''%V''''  The ISO 8601:1988 week number of the current year as a decimal
    number, range 01 to 53, where week 1 is the first week that has at least 4
    days in the current year, and with Monday as the first day of the week. See
    also ''''%U'''' and ''''%W''''. %(SU)
    
    * ''''%w''''  The day of the week as a decimal, range 0 to 6, Sunday being 0. See also ''''%u''''.
    
    * ''''%W''''  The week number of the current %year as a decimal number, range
    00 to 53, starting with the first Monday as the first day of week 01.
    
    * ''''%x''''  The preferred date representation for the current locale without
    the time.
    
    * ''''%X''''  The preferred time representation for the current locale without
    the date.
    
    * ''''%y''''  The year as a decimal number without a century (range 00 to 99).
    
    * ''''%Y''''   The year as a decimal number including the century.
    
    * ''''%z''''   The time zone as hour offset from GMT. Required to emit
    RFC822-conformant dates (using ''''%a, %d %%b %Y %H:%M:%S %%z''''). (GNU)
    
    * ''''%Z''''  The time zone or name or abbreviation.
    
    * ''''%+''''  The date and time in date(1) format. (TZ)
    
    * ''''%%''''  A literal ''''%'''' character.
    
    Some conversion specifiers can be modified by preceding them by the E or O
    modifier to indicate that an alternative format should be used. If the
    alternative format or specification does not exist for the current locale, the
    behavior will be as if the unmodified conversion specification were used.
    
    (SU) The Single Unix Specification mentions %Ec, %EC, %Ex, %%EX, %Ry, %EY,
    %Od, %Oe, %OH, %OI, %Om, %OM, %OS, %Ou, %OU, %OV, %Ow, %OW, %Oy, where the
    effect of the O modifier is to use alternative numeric symbols (say, Roman
    numerals), and that of the E modifier is to use a locale-dependent alternative
    representation.
    
    This documentation of the timeformat attribute has been taken from the man page
    of the GNU strftime function.
    EOT
           )
    
      end
    
      def rule_timeInterval
        pattern([ '$TIME', '_-', '$TIME' ], lambda {
          if @val[0] >= @val[2]
            error('time_interval',
                  "End time of interval must be larger than start time",
                  @sourceFileInfo[0])
          end
          [ @val[0], @val[2] ]
        })
      end
    
      def rule_timeSheet
        pattern(%w( !timeSheetHeader !timeSheetBody ), lambda {
          @timeSheet
        })
        doc('timesheet', <<'EOT'
    A time sheet record can be used to capture the current status of the tasks
    assigned to a specific resource and the achieved progress for a given period
    of time. The status is assumed to be for the end of this time period. There
    must be a separate time sheet record for each resource per period. Different
    resources can use different reporting periods and reports for the same
    resource may have different reporting periods as long as they don't overlap.
    For the time after the last time sheet, TaskJuggler will project the result
    based on the plan data. For periods without a time sheet record prior to the
    last record for this resource, TaskJuggler assumes that no work has been done.
    The work is booked for the scenario specified by [[trackingscenario]].
    
    The intended use for time sheets is to have all resources report a time sheet
    every day, week or month. All time sheets can be added to the project plan.
    The status information is always used to determin the current status of the
    project. The [[work]], [[remaining]] and [[end.timesheet|end]] attributes are
    ignored if there are also [[booking.task|bookings]] for the resource in the
    time sheet period. The non-ignored attributes of the time sheets will be
    converted into [[booking.task|booking]] statements internally. These bookings
    can then be [[export|exported]] into a file which can then be added to the
    project again. This way, you can use time sheets to incrementally record
    progress of your project. There is a possibility that time sheets conflict
    with other data in the plan. In case TaskJuggler cannot automatically resolve
    them, these conflicts have to be manually resolved by either changing the plan
    or the time sheet.
    
    The status messages are interpreted as [[journalentry|journal entries]]. The
    alert level will be evaluated and the current state of the project can be put
    into a dashboard using the ''''alert'''' and ''''alertmessage'''' [[columnid|
    columns]].
    
    Currently, the provided effort values and dates are not yet used to
    automatically update the plan data. This feature will be added in future
    versions.
    EOT
           )
        example('TimeSheet1', '1')
      end
    
      def rule_timeSheetAttributes
        optional
        repeatable
    
        pattern(%w( !tsNewTaskHeader !tsTaskBody ), lambda {
          @property = nil
          @timeSheetRecord = nil
        })
        doc('newtask', <<'EOT'
    The keyword can be used to request a new task to the project. If the task ID
    requires further parent task that don't exist yet, these tasks will be
    requested as well. If the task exists already, an error will be generated. The
    newly requested task can be used immediately to report progress and status
    against it. These tasks will not automatically be added to the project plan.
    The project manager has to manually create them after reviewing the request
    during the time sheet reviews.
    EOT
           )
        example('TimeSheet1', '3')
    
        pattern(%w( _shift !shiftId ), lambda {
          #TODO
        })
        doc('shift.timesheet', <<'EOT'
    Specifies an alternative [[shift]] for the time sheet period. This shift will
    override any existing working hour definitions for the resource. It will not
    override already declared [[leaves]] though.
    
    The primary use of this feature is to let the resources report different total
    work time for the report period.
    EOT
           )
    
        pattern(%w( !tsStatus ))
    
        pattern(%w( !tsTaskHeader !tsTaskBody ), lambda {
          @property = nil
          @timeSheetRecord = nil
        })
        doc('task.timesheet', <<'EOT'
    Specifies an existing task that progress and status should be reported
    against.
    EOT
           )
        example('TimeSheet1', '4')
      end
    
      def rule_timeSheetFile
        pattern(%w( !timeSheet . ), lambda {
          @val[0]
        })
        lastSyntaxToken(1)
      end
    
      def rule_timeSheetBody
        pattern(%w( _{ !timeSheetAttributes _} ), lambda {
    
        })
      end
    
      def rule_timeSheetHeader
        pattern(%w( _timesheet !resourceId !valIntervalOrDate ), lambda {
          @sheetAuthor = @val[1]
          @property = nil
          unless @sheetAuthor.leaf?
            error('ts_group_author',
                  'A resource group cannot file a time sheet',
                  @sourceFileInfo[1])
          end
          unless (scenarioIdx = @project['trackingScenarioIdx'])
            error('ts_no_tracking_scenario',
                  'No trackingscenario defined.')
          end
          # Currently time sheets are hardcoded for scenario 0.
          @timeSheet = TimeSheet.new(@sheetAuthor, @val[2], scenarioIdx)
          @timeSheet.sourceFileInfo = @sourceFileInfo[0]
          @project.timeSheets << @timeSheet
        })
      end
    
      def rule_timeSheetReport
        pattern(%w( !tsReportHeader !tsReportBody ), lambda {
          @property = nil
        })
        doc('timesheetreport', <<'EOT'
    For projects that flow mostly according to plan, TaskJuggler already knows
    much of the information that should be contained in the time sheets. With this
    property, you can generate a report that contains drafts of the time sheets
    for one or more resources. The time sheet drafts will be for the
    specified report period and the specified [trackingscenario].
    EOT
           )
      end
    
      def rule_timezone
        pattern(%w( _timezone !validTimeZone ), lambda{
          TjTime.setTimeZone(@val[1])
          @project['timezone'] = @val[1]
        })
        doc('timezone', <<'EOT'
    Sets the default time zone of the project. All dates and times that have no
    time zones specified will be assumed to be in this time zone. If no time zone
    is specified for the project, UTC is assumed.
    
    The project start and end time are not affected by this setting. They are
    always considered to be UTC unless specified differently.
    
    In case the specified time zone is not hour-aligned with UTC, the
    [[timingresolution]] will automatically be decreased accordingly. Do not
    change the timingresolution after you've set the time zone!
    
    Changing the time zone will reset the [[workinghours.project|working hours]]
    to the default times. It's recommended that you declare your working hours
    after the time zone.
    EOT
            )
        arg(1, 'zone', <<'EOT'
    Time zone to use. E. g. 'Europe/Berlin' or 'America/Denver'. Don't use the 3
    letter acronyms. See
    [http://en.wikipedia.org/wiki/List_of_zoneinfo_time_zones Wikipedia] for
    possible values.
    EOT
           )
      end
    
      def rule_traceReport
        pattern(%w( !traceReportHeader !reportBody ), lambda {
          @property = @property.parent
        })
        doc('tracereport', <<'EOT'
    The trace report works noticeably different than all other TaskJuggler
    reports. It uses a CSV file to track the values of the selected attributes.
    Each time ''''tj3'''' is run with the ''''--add-trace'''' option, a new set of
    values is appended to the CSV file. The first column of the CSV file holds the
    date when the snapshot was taken. This is either the current date or the
    ''''now'''' date if provided. There is no need to specify CSV as output format
    for the report. You can either use these tracked values directly by specifying other report formats or by importing the CSV file into another program.
    
    The first column always contains the current date when that
    table row was added. All subsequent columns can be defined by the user with
    the [[columns]] attribute. This column set is then repeated for all properties
    that are not hidden by [[hideaccount]], [[hideresource]] and [[hidetask]]. By
    default, all properties are excluded. You must provide at least one of the
    ''''hide...'''' attributes to select the properties you want to have included
    in the report. Please be aware that total number of columns is the product of
    attributes defined with [[columns]] times the number of included properties.
    Select you values carefully or you will end up with very large reports.
    
    The column headers can be customized by using the [[title.column|title]]
    attribute.  When you include multiple properties, these headers are not unique
    unless you include mini-queries to modify them based on the property they
    colum is represeting.  You can use the queries
    ''''<-id->'''', ''''<-name->'''',
    ''''<-scenario->'''' and
    ''''<-attribute->''''. ''''<-id->'''' is
    replaced with the ID of the property, ''''<-name->'''' with
    the name and so on.
    
    You can change the set of tracked values over time. Old values will be
    preserved and the corresponding columns will be the last ones in the CSV file.
    
    When other formats are requested, the CSV file is read in and a report that
    shows the tracked values over time will be generated. The CSV file may contain
    all kinds of values that are being tracked. Report formats that don't support
    a mix of different values will just show the values of the second column.
    
    The HTML version generates SVG graphs that are embedded in the HTML page.
    These graphs are only visble if the web browser supports HTML5. This is true
    for the latest generation of browsers, but older browsers may not support this
    format.
    EOT
           )
        example('TraceReport')
      end
    
      def rule_traceReportHeader
        pattern(%w( _tracereport !optionalID !reportName ), lambda {
          newReport(@val[1], @val[2], :tracereport, @sourceFileInfo[0]) do
            # The top-level always inherits the global timeFormat setting. This is
            # not desireable in this case, so we ignore this.
            if (@property.level == 0 && !@property.provided('timeFormat')) ||
               (@property.level > 0 && !@property.modified?('timeFormat'))
              # CSV readers such of Libre-/OpenOffice can't deal with time zones. We
              # probably also don't need seconds.
              @property.set('timeFormat', '%Y-%m-%d-%H:%M')
            end
            unless @property.modified?('columns')
              # Set the default columns for this report.
              %w( end ).each do |col|
                @property.get('columns') <<
                TableColumnDefinition.new(col, columnTitle(col))
              end
            end
            # Hide all accounts.
            unless @property.modified?('hideAccount')
              @property.set('hideAccount',
                            LogicalExpression.new(LogicalOperation.new(1)))
            end
            unless @property.modified?('sortAccounts')
              @property.set('sortAccounts',
                            [ [ 'tree', true, -1 ],
                              [ 'seqno', true, -1 ] ])
            end
            # Show all tasks, sorted by tree, start-up, seqno-up.
            unless @property.modified?('hideTask')
              @property.set('hideTask',
                            LogicalExpression.new(LogicalOperation.new(0)))
            end
            unless @property.modified?('sortTasks')
              @property.set('sortTasks',
                            [ [ 'tree', true, -1 ],
                              [ 'start', true, 0 ],
                              [ 'seqno', true, -1 ] ])
            end
            # Show no resources, but set sorting to id-up.
            unless @property.modified?('hideResource')
              @property.set('hideResource',
                            LogicalExpression.new(LogicalOperation.new(1)))
            end
            unless @property.modified?('sortResources')
              @property.set('sortResources', [ [ 'id', true, -1 ] ])
            end
          end
        })
      end
    
      def rule_tsNewTaskHeader
        pattern(%w( _newtask !taskIdUnverifd $STRING ), lambda {
          @timeSheetRecord = TimeSheetRecord.new(@timeSheet, @val[1])
          @timeSheetRecord.name = @val[2]
          @timeSheetRecord.sourceFileInfo = @sourceFileInfo[0]
        })
        arg(1, 'task', 'ID of the new task')
      end
      def rule_tsReportHeader
        pattern(%w( _timesheetreport !optionalID $STRING ), lambda {
          newReport(@val[1], @val[2], :timeSheet, @sourceFileInfo[0]) do
            @property.set('formats', [ :tjp ])
    
            unless (scenarioIdx = @project['trackingScenarioIdx'])
              error('ts_no_tracking_scenario',
                    'You must have a tracking scenario defined to use time sheets.')
            end
            @property.set('scenarios', [ scenarioIdx ])
            # Show all tasks, sorted by seqno-up.
            @property.set('hideTask', LogicalExpression.new(LogicalOperation.new(0)))
            @property.set('sortTasks', [ [ 'seqno', true, -1 ] ])
            # Show all resources, sorted by seqno-up.
            @property.set('hideResource',
                          LogicalExpression.new(LogicalOperation.new(0)))
            @property.set('sortResources', [ [ 'seqno', true, -1 ] ])
            @property.set('loadUnit', :hours)
            @property.set('definitions', [])
          end
        })
        arg(2, 'file name', <<'EOT'
    The name of the time sheet report file to generate. It must end with a .tji
    extension, or use . to use the standard output channel.
    EOT
           )
      end
    
      def rule_tsReportAttributes
        optional
        repeatable
    
        pattern(%w( !hideresource ))
        pattern(%w( !hidetask ))
        pattern(%w( !reportEnd ))
        pattern(%w( !reportPeriod ))
        pattern(%w( !reportStart ))
        pattern(%w( !sortResources ))
        pattern(%w( !sortTasks ))
      end
    
      def rule_tsReportBody
        optionsRule('tsReportAttributes')
      end
    
      def rule_tsStatusAttributes
        optional
        repeatable
    
        pattern(%w( !details ))
    
        pattern(%w( _flags !flagList ), lambda {
          @val[1].each do |flag|
            next if @journalEntry.flags.include?(flag)
    
            @journalEntry.flags << flag
          end
        })
        doc('flags.timesheet', <<'EOT'
    Time sheet entries can have flags attached to them. These can be used to
    include only entries in a report that have a certain flag.
    EOT
           )
    
        pattern(%w( !summary ))
      end
    
      def rule_tsStatusBody
        optional
        pattern(%w( _{ !tsStatusAttributes _} ))
      end
    
      def rule_tsStatusHeader
        pattern(%w( _status !alertLevel $STRING ), lambda {
          if @val[2].length > 120
            error('ts_headline_too_long',
                  "The headline must be 120 or less characters long. This one " +
                  "has #{@val[2].length} characters.", @sourceFileInfo[2])
          end
          if @val[2] == 'Your headline here!'
            error('ts_no_headline',
                  "'Your headline here!' is not a valid headline",
                  @sourceFileInfo[2])
          end
          @journalEntry = JournalEntry.new(@project['journal'],
                                           @timeSheet.interval.end,
                                           @val[2],
                                           @property || @timeSheet.resource,
                                           @sourceFileInfo[0])
          @journalEntry.alertLevel = @val[1]
          @journalEntry.timeSheetRecord = @timeSheetRecord
          @journalEntry.author = @sheetAuthor
          @timeSheetRecord.status = @journalEntry if @timeSheetRecord
        })
      end
    
      def rule_tsStatus
        pattern(%w( !tsStatusHeader !tsStatusBody ))
        doc('status.timesheet', <<'EOT'
    The status attribute can be used to describe the current status of the task or
    resource. The content of the status messages is added to the project journal.
    The status section is optional for tasks that have been worked on less than
    one day during the report interval.
    EOT
           )
        arg(2, 'headline', <<'EOT'
    A short headline for the status. Must be 60 characters or shorter.
    EOT
           )
        example('TimeSheet1', '4')
      end
    
      def rule_tsTaskAttributes
        optional
        repeatable
    
        pattern(%w( _end !valDate ), lambda {
          if @val[1] < @timeSheet.interval.start
            error('ts_end_too_early',
                  "The expected task end date must be after the start date of " +
                  "this time sheet report.", @sourceFileInfo[1])
          end
          @timeSheetRecord.expectedEnd = @val[1]
        })
        doc('end.timesheet', <<'EOT'
    The expected end date for the task. This can only be used for duration based
    task. For effort based task [[remaining]] has to be used.
    EOT
           )
        example('TimeSheet1', '5')
    
        pattern(%w( _priority $INTEGER ), lambda {
          priority = @val[1]
          if priority < 1 || priority > 1000
            error('ts_bad_priority',
                  "Priority value #{priority} must be between 1 and 1000.",
                  @sourceFileInfo[1])
          end
          @timeSheetRecord.priority = priority
        })
        doc('priority.timesheet', <<'EOT'
    The priority is a value between 1 and 1000. It is used to determine the
    sequence of task when converting [[work]] to [[booking.task|bookings]]. Tasks
    that need to finish earlier in the period should have a high priority, tasks
    that end later in the period should have a low priority. For tasks that don't
    get finished in the reported period the priority should be set to 1.
    EOT
           )
    
        pattern(%w( _remaining !workingDuration ), lambda {
          @timeSheetRecord.remaining = @val[1]
        })
        doc('remaining', <<'EOT'
    The remaining effort for the task. This value is ignored if there are
    [[booking.task|bookings]] for the resource that overlap with the time sheet
    period.  If there are no bookings, the value is compared with the [[effort]]
    specification of the task. If there a mismatch between the accumulated effort
    specified with bookings, [[work]] and [[remaining]] on one side and the
    specified [[effort]] on the other, a warning is generated.
    
    This attribute can only be used with tasks that are effort based. Duration
    based tasks need to have an [[end.timesheet|end]] attribute.
    EOT
           )
        example('TimeSheet1', '6')
    
        pattern(%w( !tsStatus ))
    
        pattern(%w( _work !workingDurationPercent ), lambda {
          @timeSheetRecord.work = @val[1]
        })
        doc('work', <<'EOT'
    The amount of time that the resource has spend with the task during the
    reported period. This value is ignored when there are
    [[booking.task|bookings]] for the resource overlapping with the time sheet
    period. If there are no bookings, TaskJuggler will try to convert the work
    specification into bookings internally before the actual scheduling is
    started.
    
    Every task listed in the time sheet needs to have a work attribute. The total
    accumulated work time that is reported must match exactly the total working
    hours for the resource for that period.
    
    If a resource has no vacation during the week that is reported and it has a
    regular 40 hour work week, exactly 40 hours total or 5 working days have to be
    reported.
    EOT
           )
        example('TimeSheet1', '4')
      end
    
      def rule_tsTaskBody
        pattern(%w( _{ !tsTaskAttributes _} ))
      end
    
      def rule_tsTaskHeader
        pattern(%w( _task !taskId ), lambda {
          @property = @val[1]
          unless @property.leaf?
            error('ts_task_not_leaf',
                  'You cannot specify a task that has sub tasks here.',
                  @sourceFileInfo[1], @property)
          end
    
          @timeSheetRecord = TimeSheetRecord.new(@timeSheet, @property)
          @timeSheetRecord.sourceFileInfo = @sourceFileInfo[0]
        })
        arg(1, 'task', 'ID of an already existing task')
      end
    
      def rule_vacationName
        optional
        pattern(%w( $STRING )) # We just throw the name away
        arg(0, 'name', 'An optional name or reason for the leave')
      end
    
      def rule_valDate
        pattern(%w( !date ), lambda {
          if @val[0] < @project['start'] || @val[0] > @project['end']
            error('date_in_range',
                  "Date #{@val[0]} must be within the project time frame " +
                  "#{@project['start']}  - #{@project['end']}",
                  @sourceFileInfo[0])
          end
          @val[0]
        })
      end
    
      def rule_validTimeZone
        pattern(%w( $STRING ), lambda {
          unless TjTime.checkTimeZone(@val[0])
            error('bad_time_zone', "#{@val[0]} is not a known time zone",
                  @sourceFileInfo[0])
          end
          @val[0]
        })
      end
    
      def rule_valIntervalOrDate
        pattern(%w( !date !intervalOptionalEnd ), lambda {
          if @val[1]
            mode = @val[1][0]
            endSpec = @val[1][1]
            if mode == 0
              unless @val[0] < endSpec
                error('start_before_end', "The end date (#{endSpec}) must be " +
                      "after the start date (#{@val[0]}).",
                      @sourceFileInfo[1])
              end
              iv = TimeInterval.new(@val[0], endSpec)
            else
              iv = TimeInterval.new(@val[0], @val[0] + endSpec)
            end
          else
            iv = TimeInterval.new(@val[0], @val[0].sameTimeNextDay)
          end
          checkInterval(iv)
          iv
        })
        doc('interval4', <<'EOT'
    There are three ways to specify a date interval. The first is the most
    obvious. A date interval consists of a start and end DATE. Watch out for end
    dates without a time specification! Date specifications are 0 extended. An
    end date without a time is expanded to midnight that day. So the day of the
    end date is not included in the interval! The start and end dates must be separated by a hyphen character.
    
    In the second form, the end date is omitted. A 24 hour interval is assumed.
    
    The third form specifies the start date and an interval duration. The duration must be prefixed by a plus character.
    
    The start and end date of the interval must be within the specified project
    time frame.
    EOT
           )
      end
    
      def rule_valInterval
        pattern(%w( !date !intervalEnd ), lambda {
          mode = @val[1][0]
          endSpec = @val[1][1]
          if mode == 0
            unless @val[0] < endSpec
              error('start_before_end', "The end date (#{endSpec}) must be after " +
                    "the start date (#{@val[0]}).", @sourceFileInfo[1])
            end
            iv = TimeInterval.new(@val[0], endSpec)
          else
            iv = TimeInterval.new(@val[0], @val[0] + endSpec)
          end
          checkInterval(iv)
          iv
        })
        doc('interval1', <<'EOT'
    There are two ways to specify a date interval. The start and end date must lie within the specified project period.
    
    The first is the most obvious. A date interval consists of a start and end
    DATE. Watch out for end dates without a time specification! Date
    specifications are 0 extended. An end date without a time is expanded to
    midnight that day. So the day of the end date is not included in the interval!
    The start and end dates must be separated by a hyphen character.
    
    In the second form specifies the start date and an interval duration. The
    duration must be prefixed by a plus character.
    EOT
           )
      end
    
      def rule_valIntervals
        listRule('moreValIntervals', '!valIntervalOrDate')
      end
    
      def rule_warn
        pattern(%w( _warn !logicalExpression ), lambda {
          begin
            @property.set('warn', @property.get('warn') + [ @val[1] ])
          rescue AttributeOverwrite
          end
        })
        doc('warn', <<'EOT'
    The warn attribute adds a [[logicalexpression|logical expression]] to the
    property. The condition described by the logical expression is checked after
    the scheduling and an warning is generated if the condition evaluates to true.
    This attribute is primarily intended for testing purposes.
    EOT
           )
      end
    
    
      def rule_weekday
        pattern(%w( _sun ), lambda { 0 })
        pattern(%w( _mon ), lambda { 1 })
        pattern(%w( _tue ), lambda { 2 })
        pattern(%w( _wed ), lambda { 3 })
        pattern(%w( _thu ), lambda { 4 })
        pattern(%w( _fri ), lambda { 5 })
        pattern(%w( _sat ), lambda { 6 })
      end
    
      def rule_weekDayInterval
        pattern(%w( !weekday !weekDayIntervalEnd ), lambda {
          weekdays = Array.new(7, false)
          if @val[1].nil?
            weekdays[@val[0]] = true
          else
            d = @val[0]
            loop do
              weekdays[d] = true
              break if d == @val[1]
              d = (d + 1) % 7
            end
          end
    
          weekdays
        })
        arg(0, 'weekday', 'Weekday (sun - sat)')
      end
    
      def rule_weekDayIntervalEnd
        optional
        pattern([ '_-', '!weekday' ], lambda {
          @val[1]
        })
        arg(1, 'end weekday',
            'Weekday (sun - sat). It is included in the interval.')
      end
    
      def rule_workingDuration
        pattern(%w( !number !durationUnit ), lambda {
          convFactors = [ 60, # minutes
                          60 * 60, # hours
                          60 * 60 * @project['dailyworkinghours'], # days
                          60 * 60 * @project['dailyworkinghours'] *
                          (@project.weeklyWorkingDays), # weeks
                          60 * 60 * @project['dailyworkinghours'] *
                          (@project['yearlyworkingdays'] / 12), # months
                          60 * 60 * @project['dailyworkinghours'] *
                          @project['yearlyworkingdays'] # years
                        ]
          # The result will always be in number of time slots.
          (@val[0] * convFactors[@val[1]] /
           @project['scheduleGranularity']).round.to_i
        })
        arg(0, 'value', 'A floating point or integer number')
      end
    
      def rule_workingDurationPercent
        pattern(%w( !number !durationUnitOrPercent ), lambda {
          if @val[1] >= 0
            # Absolute value in minutes, hours or days.
            convFactors = [ 60, # minutes
              60 * 60, # hours
              60 * 60 * @project['dailyworkinghours'] # days
            ]
            # The result will always be in number of time slots.
            (@val[0] * convFactors[@val[1]] /
             @project['scheduleGranularity']).round.to_i
          else
            # Percentage values are always returned as Float in the rage of 0.0 to
            # 1.0.
            if @val[0] < 0.0 || @val[0] > 100.0
              error('illegal_percentage',
                    "Percentage values must be between 0 and 100%.",
                    @sourceFileInfo[1])
            end
            @val[0] / 100.0
          end
        })
        arg(0, 'value', 'A floating point or integer number')
      end
    
      def rule_workinghours
        pattern(%w( _workinghours !listOfDays !listOfTimes), lambda {
          if @property.nil?
            # We are changing global working hours.
            wh = @project['workinghours']
          else
            unless (wh = @property['workinghours', @scenarioIdx])
              # The property does not have it's own WorkingHours yet.
              wh = WorkingHours.new(@project['workinghours'])
            end
          end
          wh.timezone = @project['timezone']
          begin
            7.times { |i| wh.setWorkingHours(i, @val[2]) if @val[1][i] }
          rescue
            error('bad_workinghours', $!.message)
          end
    
          if @property
            # Make sure we actually assign something so the attribute is marked as
            # set by the user.
            begin
              @property['workinghours', @scenarioIdx] = wh
            rescue AttributeOverwrite
              # Working hours can be set multiple times.
            end
          end
        })
      end
    
      def rule_workinghoursProject
        pattern(%w( !workinghours ))
        doc('workinghours.project', <<'EOT'
    Set the default working hours for all subsequent resource definitions. The
    standard working hours are 9:00am - 12:00am, 1:00pm - 18:00pm, Monday to
    Friday. The working hours specification limits the availability of resources
    to certain time slots of week days.
    
    These default working hours can be replaced with other working hours for
    individual resources.
    EOT
           )
        also(%w( dailyworkinghours workinghours.resource workinghours.shift ))
        example('Project')
      end
    
      def rule_workinghoursResource
        pattern(%w( !workinghours ))
        doc('workinghours.resource', <<'EOT'
    Set the working hours for a specific resource. The working hours specification
    limits the availability of resources to certain time slots of week days.
    EOT
           )
        also(%w( workinghours.project workinghours.shift ))
      end
    
      def rule_workinghoursShift
        pattern(%w( !workinghours ))
        doc('workinghours.shift', <<'EOT'
    Set the working hours for the shift. The working hours specification limits
    the availability of resources or the activity on a task to certain time
    slots of week days.
    
    The shift working hours will replace the default or resource working hours for
    the specified time frame when assigning the shift to a resource.
    
    In case the shift is used for a task, resources are only assigned during the
    working hours of this shift and during the working hours of the allocated
    resource. Allocations only happen when both the task shift and the resource
    work hours allow work to happen.
    EOT
           )
        also(%w( workinghours.project workinghours.resource ))
      end
    
      def rule_yesNo
        pattern(%w( _yes ), lambda {
          true
        })
        pattern(%w( _no ), lambda {
          false
        })
      end
    
    end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/Journal.rb0000644000175000017500000006437412614413013020411 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = Journal.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/MessageHandler'
    
    class TaskJuggler
    
      # A JournalEntry stores some RichText strings to describe a status or a
      # property of the project at a certain point in time. Additionally, the
      # entry can contain a reference to a Resource as author and an alert level.
      # The text is structured in 3 different elements, a headline, a short
      # summary and a longer text segment. The headline is mandatory, the
      # summary and details sections are optional.
      class JournalEntry
    
        attr_reader :date, :headline, :property, :sourceFileInfo
        attr_accessor :author, :moderators, :summary, :details, :alertLevel, :flags,
                      :timeSheetRecord
    
        # Create a new JournalEntry object.
        def initialize(journal, date, headline, property, sourceFileInfo = nil)
          # A reference to the Journal object this entry belongs to.
          @journal = journal
          # The date of the entry.
          @date = date
          # A very short description. Should not be longer than about 40
          # characters.
          @headline = headline
          # A reference to a PropertyTreeNode object.
          @property = property
          # Source file location of this entry of type SourceFileInfo
          @sourceFileInfo = sourceFileInfo
          # A reference to a Resource.
          @author = nil
          # A list of Resource objects that have moderated this entry.
          @moderators = []
          # An introductory or summarizing RichText paragraph.
          @summary = nil
          # A RichText of arbitrary length.
          @details = nil
          # The alert level.
          @alertLevel = 0
          # A list of flags.
          @flags = []
          # A reference to a time sheet record that was used to create this
          # JournalEntry object.
          @timeSheetRecord = nil
    
          # Add the new entry to the journal.
          @journal.addEntry(self)
        end
    
        # Convert the entry into a RichText string. The formatting is controlled
        # by the Query parameters.
        def to_rText(query)
          # We use the alert level a sortable and numerical result.
          if query.journalAttributes.include?('alert')
            levelRecord = query.project['alertLevels'][alertLevel]
            if query.selfContained
              alertName = "[" +
                          "#{levelRecord.name}]"
            else
              alertName = "[[File:icons/flag-#{levelRecord.id}.png|" +
              "alt=[#{levelRecord.name}]|text-bottom]] "
            end
          else
            alertName = ''
          end
    
          # The String that will hold the result as RichText markup.
          rText = ''
    
          # Markup to use for headlines.
          hlMark = '==='
    
          if query.journalAttributes.include?('property') && @property
            if @property.is_a?(Task)
              # Include the alert level, task name and ID.
              rText += "#{hlMark} #{alertName} #{@property.name}"
              if query.journalAttributes.include?('propertyid')
                rText += " (ID: #{@property.fullId})"
              end
              rText += " #{hlMark}\n\n"
    
              if query.journalAttributes.include?('timesheet') && @timeSheetRecord
                # Include the reported time sheet data for this task.
                rText += "'''Work:''' #{@timeSheetRecord.actualWorkPercent.to_i}% "
                if @timeSheetRecord.actualWorkPercent !=
                   @timeSheetRecord.planWorkPercent
                  rText += "(#{@timeSheetRecord.planWorkPercent.to_i}%) "
                end
                if @timeSheetRecord.remaining
                  rText += "'''Remaining:''' #{@timeSheetRecord.actualRemaining}d "
                  if @timeSheetRecord.actualRemaining !=
                     @timeSheetRecord.planRemaining
                    rText += "(#{@timeSheetRecord.planRemaining}d) "
                  end
                else
                  rText += "'''End:''' " +
                    "#{@timeSheetRecord.actualEnd.to_s(query.timeFormat)} "
                  if @timeSheetRecord.actualEnd != @timeSheetRecord.planEnd
                    rText += "(#{@timeSheetRecord.planEnd.to_s(query.timeFormat)}) "
                  end
                end
                rText += "\n\n"
              end
            elsif !(@timeSheetRecord = @timeSheetRecord).nil? &&
                  @timeSheetRecord.task.is_a?(String)
              # There is only an entry in the timesheet, but we don't have a
              # corresponding Task in the Project. This must be a new task created
              # by the timesheet submitter.
              rText += "#{hlMark} #{alertName} [New Task] " +
                       "#{@timeSheetRecord.name}"
              if query.journalAttributes.include?('propertyid')
                rText += " (ID: #{@timeSheetRecord.task})"
              end
              rText += " #{hlMark}\n\n"
    
              if query.journalAttributes.include?('timesheet') && @timeSheetRecord
                # We don't have any plan data since it's a new task. Just include
                # the reported time sheet actuals.
                rText += "'''Work:''' #{@timeSheetRecord.actualWorkPercent}% "
                if @timeSheetRecord.remaining
                  rText += "'''Remaining:''' #{@timeSheetRecord.actualRemaining}d "
                else
                  rText += "'''End:''' " +
                           "#{@timeSheetRecord.actualEnd.to_s(query.timeFormat)} "
                end
                rText += "\n\n"
              end
            else
              # Property must be a Resource
              rText += "#{hlMark} #{alertName} Personal Notes #{hlMark}\n\n"
            end
    
            # We've shown the alert now. Don't show it again with the headline.
            alertName = ''
            # Increase level for subsequent headlines.
            hlMark += '='
          end
    
          if query.journalAttributes.include?('headline')
            rText += "#{hlMark} #{alertName}" + @headline +
                     " #{hlMark}\n\n"
          end
    
          showDate = query.journalAttributes.include?('date')
          showAuthor = query.journalAttributes.include?('author') && @author
          if showDate || showAuthor
            rText += "''Reported "
          end
          if showDate
            rText += "on #{@date.to_s(query.timeFormat)} "
          end
          if showAuthor
            rText += "by #{@author.name}"
          end
          rText += "''\n\n" if showDate || showAuthor
    
          if query.journalAttributes.include?('flags') && !@flags.empty?
            rText += "''Flags:'' #{@flags.join(', ')}\n\n"
          end
    
          if query.journalAttributes.include?('summary') && @summary
            rText += @summary.richText.inputText + "\n\n"
          end
          if query.journalAttributes.include?('details') && @details
            rText += @details.richText.inputText + "\n\n"
          end
          rText
        end
    
        # Just for debugging
        def to_s # :nodoc:
          "Headline: #{@headline}\nProperty: #{@property.class}: #{@property.fullId}"
        end
    
      end
    
      # The JournalEntryList is an Array with a twist. Before any data retrieval
      # function is called, the list of JournalEntry objects will be sorted by
      # date. This is a utility class only. Use Journal to store a journal.
      class JournalEntryList
    
        attr_reader :entries
    
        JournalEntryList::SortingAttributes = [ :alert, :date, :seqno ]
    
        def initialize
          @entries = []
          @sorted = false
          @sortBy = [ [ :date, 1 ], [ :alert, 1 ], [ :seqno, 1 ] ]
        end
    
        def setSorting(by)
          by.each do |attr, direction|
            unless SortingAttributes.include?(attr)
              raise ArgumentError, "Unknown attribute #{attr}"
            end
            if (direction != 1) && (direction != -1)
              raise ArgumentError, "Unknown direction #{direction}"
            end
          end
          @sortBy = by
        end
    
    
        # Return the number of entries.
        def count
          @entries.length
        end
    
        # Add a new JournalEntry to the list. The list will be marked as unsorted.
        def <<(entry)
          @entries << entry
          @sorted = false
        end
    
        # Add a list of JournalEntry objects to the existing list. The list will
        # be marked unsorted.
        def +(list)
          @entries += list.entries
          @sorted = false
          self
        end
    
        # Return the _index_-th entry.
        def[](index)
          sort!
          @entries[index]
        end
    
        # The well known iterator. The list will be sorted first.
        def each
          sort!
          @entries.each do |entry|
            yield entry
          end
        end
    
        # Like Array::delete
        def delete(e)
          @entries.delete(e)
        end
    
        # Like Array::delete_if
        def delete_if
          @entries.delete_if { |e| yield(e) }
        end
    
        # Like Array::empty?
        def empty?
          @entries.empty?
        end
    
        # Like Array:length
        def length
          @entries.length
        end
    
        # Like Array::include?
        def include?(entry)
          @entries.include?(entry)
        end
    
        # Like Array::first but list is first sorted.
        def first
          sort!
          @entries.first
        end
    
        # Returns the last elements (by date) if date is nil or the last elements
        # right before the given _date_. If there are multiple entries with
        # exactly the same date, all are returned. Otherwise the result Array will
        # only contain one element. In case no matching entry is found, the Array
        # will be empty.
        def last(date = nil)
          result = JournalEntryList.new
          sort!
    
          @entries.reverse_each do |e|
            if result.empty?
              # We haven't found any yet. So add the first one we find before the
              # cut-off date.
              result << e if e.date <= date
            elsif result.first.date == e.date
              # Now we only accept other entries with the exact same date.
              result << e
            else
              # We've found all entries we are looking for.
              break
            end
          end
          result.sort!
        end
    
        # Sort the list of entries. First by ascending by date, than by alertLevel
        # and finally by PropertyTreeNode sequence number.
        def sort!
          if block_given?
            @entries.sort! { |a, b| yield(a, b) }
          else
            return self if @sorted
    
            @entries.sort! do |a, b|
              res = 0
              @sortBy.each do |attr, direction|
                res = case attr
                      when :date
                        a.date <=> b.date
                      when :alert
                        a.alertLevel <=> b.alertLevel
                      when :seqno
                        a.property.sequenceNo <=> b.property.sequenceNo
                      end * direction
                break if res != 0
              end
              res
            end
          end
          @sorted = true
          self
        end
    
        # Eliminate duplicate entries.
        def uniq!
          @entries.uniq!
        end
    
      end
    
      # A Journal is a list of JournalEntry objects. It provides methods to add
      # JournalEntry objects and retrieve specific entries or other processed
      # information.
      class Journal
    
        include MessageHandler
    
        # Create a new Journal object.
        def initialize
          # This list holds all entries.
          @entries = JournalEntryList.new
          # This hash holds a list of entries for each property.
          @propertyToEntries = {}
        end
    
        # Add a new JournalEntry to the Journal.
        def addEntry(entry)
          return if @entries.include?(entry)
          @entries << entry
    
          return if entry.property.nil?
    
          # When we store the property into the @propertyToEntries hash, we need
          # to make sure that we store the PropertyTreeNode object and not a
          # PTNProxy object.
          unless @propertyToEntries.include?(entry.property.ptn)
            @propertyToEntries[entry.property.ptn] = JournalEntryList.new
          end
          @propertyToEntries[entry.property.ptn] << entry
        end
    
        def getEntries(property)
          @propertyToEntries[property.ptn]
        end
    
        # Delete all entries of the Journal for which the block yields true.
        def delete_if
          @entries.delete_if do |e|
            res = yield(e)
            @propertyToEntries[e.property.ptn].delete(e) if res
            res
          end
        end
    
    
    
        def to_rti(query)
          entries = JournalEntryList.new
    
          case query.journalMode
          when :journal
            # This is the regular journal. It contains all journal entries that
            # are dated in the query interval. If a property is given, only
            # entries of this property are included.
            if query.property
              if query.property.is_a?(Task)
                entries = entriesByTask(query.property, query.start, query.end,
                                        query.hideJournalEntry)
              elsif query.property.is_a?(Resource)
                entries = entriesByResource(query.property, query.start, query.end,
                                            query.hideJournalEntry)
              end
            else
              entries = self.entries(query.start, query.end, query.hideJournalEntry)
            end
          when :journal_sub
            # This mode also contains all journal entries that are dated in the
            # query interval. A property must be given and only entries of this
            # property and all its children are included.
            if query.property.is_a?(Task)
              entries = entriesByTaskR(query.property, query.start, query.end,
                                       query.hideJournalEntry)
            end
          when :status_up
            # In this mode only the last entries before the query end date for
            # each task are included. An entry is not included if any of the
            # parent tasks has a more recent entry that is still before the query
            # end date.
            if query.property
              if query.property.is_a?(Task)
                entries += currentEntries(query.end, query.property, 0, query.start,
                                          query.hideJournalEntry)
              end
            else
              query.project.tasks.each do |task|
                # We only care about top-level tasks.
                next if task.parent
    
                entries += currentEntries(query.end, task, 0, query.start,
                                          query.hideJournalEntry)
                # Eliminate duplicates due to entries from adopted tasks
                entries.uniq!
              end
            end
          when :status_down, :status_dep
            # In this mode only the last entries before the query end date for
            # each task (incl. sub tasks) are included.
            if query.property
              if query.property.is_a?(Task)
                entries += currentEntriesR(query.end, query.property, 0,
                                           query.start, query)
              end
            else
              query.project.tasks.each do |task|
                # We only care about top-level tasks.
                next if task.parent
    
                entries += currentEntriesR(query.end, task, 0, query.start, query)
                # Eliminate duplicates due to entries from adopted tasks
                entries.uniq!
              end
            end
          when :alerts_down, :alerts_dep
            # In this mode only the last entries before the query end date for
            # each task (incl. sub tasks) and only the ones with the highest alert
            # level are included.
            if query.property
              if query.property.is_a?(Task)
                entries += alertEntries(query.end, query.property, 1, query.start,
                                        query)
              end
            else
              query.project.tasks.each do |task|
                # We only care about top-level tasks.
                next if task.parent
    
                entries += alertEntries(query.end, task, 1, query.start, query)
                # Eliminate duplicates due to entries from adopted tasks
                entries.uniq!
              end
            end
          else
            raise "Unknown jourmal mode: #{query.journalMode}"
          end
          # Sort entries according to the user specified sorting criteria.
          entries.setSorting(query.sortJournalEntries)
          entries.sort!
    
          # The components of the message are either UTF-8 text or RichText. For
          # the RichText components, we use the originally provided markup since
          # we compose the result as RichText markup first.
          rText = ''
          entries.each do |entry|
            rText += entry.to_rText(query)
          end
    
          # Now convert the RichText markup String into RichTextIntermediate
          # format.
          unless (rti = RichText.new(rText, RTFHandlers.create(query.project)).
                                     generateIntermediateFormat)
            warning('ptn_journal', "Syntax error in journal: #{rText}")
            return nil
          end
          # No section numbers, please!
          rti.sectionNumbers = false
          # We use a special class to allow CSS formating.
          rti.cssClass = 'tj_journal'
          query.rti = rti
        end
    
        # Return a list of all JournalEntry objects for the given _resource_ that
        # are dated between _startDate_ and _endDate_, are not hidden by their
        # flags matching _logExp_, are for Task _task_ and have at least the alert
        # level _alertLevel. If an optional parameter is nil, it always matches
        # the entry.
        def entriesByResource(resource, startDate = nil, endDate = nil,
                              logExp = nil, task = nil, alertLevel = nil)
          list = JournalEntryList.new
          @entries.each do |entry|
            if entry.author == resource.ptn &&
               (startDate.nil? || entry.date > startDate) &&
               (endDate.nil? || entry.date <= endDate) &&
               (task.nil? || entry.property == task.ptn) &&
               (alertLevel.nil? || entry.alertLevel >= alertLevel) &&
               !entry.headline.empty? && !hidden(entry, logExp)
              list << entry
            end
          end
          list
        end
    
        # Return a list of all JournalEntry objects for the given _task_ that are
        # dated between _startDate_ and _endDate_ (end date not included), are not
        # hidden by their flags matching _logExp_ are from Author _resource_ and
        # have at least the alert level _alertLevel. If an optional parameter is
        # nil, it always matches the entry.
        def entriesByTask(task, startDate = nil, endDate = nil, logExp = nil,
                          resource = nil, alertLevel = nil)
          list = JournalEntryList.new
          @entries.each do |entry|
            if entry.property == task.ptn &&
               (startDate.nil? || entry.date >= startDate) &&
               (endDate.nil? || entry.date < endDate) &&
               (resource.nil? || entry.author == resource) &&
               (alertLevel.nil? || entry.alertLevel >= alertLevel) &&
               !entry.headline.empty? && !hidden(entry, logExp)
              list << entry
            end
          end
          list
        end
    
        # Return a list of all JournalEntry objects for the given _task_ or any of
        # its sub tasks that are dated between _startDate_ and _endDate_, are not
        # hidden by their flags matching _logExp_, are from Author _resource_ and
        # have at least the alert level _alertLevel. If an optional parameter is
        # nil, it always matches the entry.
        def entriesByTaskR(task, startDate = nil, endDate = nil, logExp = nil,
                           resource = nil, alertLevel = nil)
          list = entriesByTask(task, startDate, endDate, logExp, resource,
                               alertLevel)
    
          task.kids.each do |t|
            list += entriesByTaskR(t, startDate, endDate, logExp, resource,
                                   alertLevel)
          end
    
          list
        end
    
        def entries(startDate = nil, endDate = nil, logExp = nil, property = nil,
                    alertLevel = nil)
          list = JournalEntryList.new
          @entries.each do |entry|
            if (startDate.nil? || startDate <= entry.date) &&
               (endDate.nil? || endDate >= entry.date) &&
               (property.nil? || property.ptn == entry.property ||
                                 entry.property.isChildOf?(property.ptn)) &&
               (alertLevel.nil? || alertLevel == entry.alertLevel) &&
               !hidden(entry, logExp)
              list << entry
            end
          end
          list
        end
    
        # Determine the alert level for the given _property_ at the given _date_.
        # If the property does not have any JournalEntry objects or they are out
        # of date compared to the child properties, the level is computed based on
        # the highest level of the children. Only take the entries that are not
        # filtered by _query_.hideJournalEntry into account.
        def alertLevel(date, property, query)
          maxLevel = 0
          # Gather all the current (as of the specified _date_) JournalEntry
          # objects for the property and than find the highest level.
          currentEntriesR(date, property, 0, nil, query).each do |e|
            maxLevel = e.alertLevel if maxLevel < e.alertLevel
          end
          maxLevel
        end
    
        # Return the list of JournalEntry objects that are dated at or before
        # _date_, are for _property_ or any of its childs, have at least _level_
        # alert and are after _minDate_. We only return those entries with the
        # highest overall alert level.
        def alertEntries(date, property, minLevel, minDate, query)
          maxLevel = 0
          entries = []
          # Gather all the current (as of the specified _date_) JournalEntry
          # objects for the property and than find the highest level.
          currentEntriesR(date, property, minLevel, minDate, query).each do |e|
            if maxLevel < e.alertLevel
              maxLevel = e.alertLevel
              entries = [ e ]
            elsif maxLevel == e.alertLevel
              entries << e
            end
          end
          entries
        end
    
        # This function returns a list of entries that have all the exact same
        # date and are the last entries before the deadline _date_. Only messages
        # with at least the required alert level _minLevel_ are returned. Messages
        # with alert level _minLevel_ must be newer than _minDate_.
        def currentEntries(date, property, minLevel, minDate, logExp)
          pEntries = getEntries(property) ?  getEntries(property).last(date) :
                     JournalEntryList.new
          # Remove entries below the minium alert level or before the timeout
          # date.
          pEntries.delete_if do |e|
            e.headline.empty? || e.alertLevel < minLevel ||
            (e.alertLevel == minLevel && minDate && e.date < minDate)
          end
    
          unless pEntries.empty?
            # Check parents for a more important or more up-to-date message.
            p = property.parent
            while p do
              ppEntries = getEntries(p) ?
                          getEntries(p).last(date) : JournalEntryList.new
    
              # A parent has a more up-to-date message.
              if !ppEntries.empty? && ppEntries.first.date >= pEntries.first.date
                return JournalEntryList.new
              end
    
              p = p.parent
            end
          end
    
          # Remove all entries that are filtered by logExp.
          if logExp
            pEntries.delete_if { |e| hidden(e, logExp) }
          end
    
          pEntries
        end
    
        # This function recursively traverses a tree of PropertyTreeNode objects
        # from bottom to top. It returns the last entries before _date_ for each
        # property unless there is a property in the sub-tree specified by the
        # root _property_ with more up-to-date entries. The result is a
        # JournalEntryList.
        def currentEntriesR(date, property, minLevel, minDate, query)
          DataCache.instance.cached(self, :currentEntriesR, date, property,
                                    minLevel, minDate, query) do
            # See if this property has any current JournalEntry objects.
            pEntries = getEntries(property) ? getEntries(property).last(date) :
                       JournalEntryList.new
            # Remove entries below the minium alert level or before the timeout
            # date.
            pEntries.delete_if do |e|
              e.headline.empty? || e.alertLevel < minLevel ||
              (e.alertLevel == minLevel && minDate && e.date < minDate)
            end
    
            # Determine the highest alert level of the pEntries.
            maxPAlertLevel = 0
            pEntries.each do |e|
              maxPAlertLevel = e.alertLevel if e.alertLevel > maxPAlertLevel
            end
    
            cEntries = JournalEntryList.new
            latestDate = nil
            maxAlertLevel = 0
            # If we have an entry from this property, we only care about child
            # entries that are from a later date.
            minDate = pEntries.first.date + 1 unless pEntries.empty?
    
            # Now gather all current entries of the child properties and find the
            # date that is closest to and right before the given _date_.
            property.kids.each do |p|
              currentEntriesR(date, p, minLevel, minDate, query).each do |e|
                # Find the date of the most recent entry.
                latestDate = e.date if latestDate.nil? || e.date > latestDate
                # Find the highest alert level.
                maxAlertLevel = e.alertLevel if e.alertLevel > maxAlertLevel
                cEntries << e unless cEntries.include?(e)
              end
            end
    
            # Only Task properties have dependencies.
            if (query.journalMode == :status_dep ||
                query.journalMode == :alerts_dep) && property.is_a?(Task)
              # Now gather all current entries of the dependency properties and find
              # the date that is closest to and right before the given _date_.
              property['startpreds', query.scenarioIdx].each do |p, onEnd|
                # We only follow end->start dependencies.
                next unless onEnd
    
                currentEntriesR(date, p, minLevel, minDate, query).each do |e|
                  # Find the date of the most recent entry.
                  latestDate = e.date if latestDate.nil? || e.date > latestDate
                  # Find the highest alert level.
                  maxAlertLevel = e.alertLevel if e.alertLevel > maxAlertLevel
                  cEntries << e unless cEntries.include?(e)
                end
              end
            end
    
            if !pEntries.empty? && (maxPAlertLevel > maxAlertLevel ||
                                    latestDate.nil? ||
                                    pEntries.first.date >= latestDate)
              # If no child property has a more current JournalEntry or one with a
              # higher alert level than this property and this property has
              # JournalEntry objects, than those are taken.
              entries = pEntries
            else
              # Otherwise we take the entries from the kids.
              entries = cEntries
            end
    
            # Remove all entries that are filtered by query.hideJournalEntry.
            if query.hideJournalEntry
              entries.delete_if { |e| hidden(e, query.hideJournalEntry) }
            end
    
            # Otherwise return the list provided by the childen.
            entries
          end
        end
    
        private
    
        def hidden(entry, logExp)
          logExp.nil? ? false : logExp.eval(entry)
        end
    
      end
    
    end
    taskjuggler-3.5.0/lib/taskjuggler/PropertySet.rb0000644000175000017500000002326612614413013021272 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = PropertySet.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/AttributeDefinition'
    require 'taskjuggler/PropertyTreeNode'
    
    class TaskJuggler
    
      # A PropertySet is a collection of properties of the same kind. Properties can
      # be Task, Resources, Scenario, Shift or Accounts objects. All properties of
      # the same kind belong to the same PropertySet. A property may only belong to
      # one PropertySet in the Project. The PropertySet holds the definitions for
      # the attributes. All Properties of the set will have a set of these
      # attributes.
      class PropertySet
    
        attr_reader :project, :flatNamespace, :attributeDefinitions
    
        def initialize(project, flatNamespace)
          if $DEBUG && project.nil?
            raise "project parameter may not be NIL"
          end
          # Indicates whether the namespace of this PropertySet is flat or not. In a
          # flat namespace all property IDs must be unique. Otherwise only the IDs
          # within a group of siblings must be unique. The full ID of the Property
          # is then composed of the siblings ID prefixed by the parent ID. ID fields
          # are separated by dots.
          @flatNamespace = flatNamespace
          # The main Project data structure reference.
          @project = project
          # A list of all PropertyTreeNodes in this set.
          @properties = Array.new
          # A hash of all PropertyTreeNodes in this set, hashed by their ID. This is
          # the same data as in @properties, but hashed by ID for faster access.
          @propertyMap = Hash.new
    
          # This is the blueprint for PropertyTreeNode attribute sets. Whever a new
          # PropertTreeNode is created, an attribute is created for each definition
          # in this list.
          @attributeDefinitions = Hash.new
          [
            [ 'id',   'ID',       StringAttribute, false,  false,  false,  '' ],
            [ 'name', 'Name',     StringAttribute, false,  false,  false,  '' ],
            [ 'seqno', 'Seq. No', FixnumAttribute, false,  false,  false,  0 ]
          ].each { |a| addAttributeType(AttributeDefinition.new(*a)) }
        end
    
        # Use the function to declare the various attributes that properties of this
        # PropertySet can have. The attributes must be declared before the first
        # property is added to the set.
        def addAttributeType(attributeType)
          if !@properties.empty?
            raise "Fatal Error: Attribute types must be defined before " +
                  "properties are added."
          end
    
          @attributeDefinitions[attributeType.id] = attributeType
        end
    
        # Iterate over all attribute definitions.
        def eachAttributeDefinition
          @attributeDefinitions.sort.each do |key, value|
            yield(value)
          end
        end
    
        # Return true if there is an AttributeDefinition for _attrId_.
        def knownAttribute?(attrId)
          @attributeDefinitions.include?(attrId)
        end
    
        # Check whether the PropertyTreeNode has a calculated attribute with the
        # ID _attrId_. For scenarioSpecific attributes _scenarioIdx_ needs to be
        # provided.
        def hasQuery?(attrId, scenarioIdx = nil)
          return false if @properties.empty?
    
          property = @properties.first
          methodName = 'query_' + attrId
          # First we check for non-scenario-specific query functions.
          if property.respond_to?(methodName)
            return true
          elsif scenarioIdx
            # Then we check for scenario-specific ones via the @data member.
            return property.data[scenarioIdx].respond_to?(methodName)
          end
          false
        end
    
        # Return whether the attribute with _attrId_ is scenario specific or not.
        def scenarioSpecific?(attrId)
          if @attributeDefinitions[attrId]
            # Check the 'scenarioSpecific' flag of the attribute definition.
            @attributeDefinitions[attrId].scenarioSpecific
          elsif (property = @properties.first) &&
                property && property.data &&
                property.data[0].respond_to?("query_#{attrId}")
            # We've found a query_ function for the attrId that is scenario
            # specific.
            true
          else
            # All hardwired, non-existing and non-scenario-specific query_
            # candidates.
            false
          end
        end
    
        # Return whether the attribute with _attrId_ is inherited from the global
        # scope.
        def inheritedFromProject?(attrId)
          # All hardwired attributes are not inherited.
          return false if @attributeDefinitions[attrId].nil?
    
          @attributeDefinitions[attrId].inheritedFromProject
        end
    
        # Return whether the attribute with _attrId_ is inherited from parent.
        def inheritedFromParent?(attrId)
          # All hardwired attributes are not inherited.
          return false if @attributeDefinitions[attrId].nil?
    
          @attributeDefinitions[attrId].inheritedFromParent
        end
    
        # Return whether or not the attribute was user defined.
        def userDefined?(attrId)
          return false if @attributeDefinitions[attrId].nil?
    
          @attributeDefinitions[attrId].userDefined
        end
    
        # Return the default value of the attribute.
        def defaultValue(attrId)
          return nil if @attributeDefinitions[attrId].nil?
    
          @attributeDefinitions[attrId].default
        end
    
        # Returns the name (human readable description) of the attribute with the
        # Id specified by _attrId_.
        def attributeName(attrId)
          # Some attributes are hardwired into the properties. These need to be
          # treated separately.
          if @attributeDefinitions.include?(attrId)
            return @attributeDefinitions[attrId].name
          end
    
          nil
        end
    
        # Return the type of the attribute with the Id specified by _attrId_.
        def attributeType(attrId)
          # Hardwired attributes need special treatment.
          if @attributeDefinitions.has_key?(attrId)
            @attributeDefinitions[attrId].objClass
          else
            nil
          end
        end
    
        # Add the new PropertyTreeNode object _property_ to the set. The set is
        # indexed by ID. In case an object with the same ID already exists in the
        # set it will be overwritten.
        #
        # Whenever the set has been extended, the 'bsi' and 'tree' attributes of the
        # properties are no longer up-to-date. You must call index() before using
        # these attributes.
        def addProperty(property)
          # The PropertyTreeNode objects are indexed by ID or hierachical ID
          # depending on the name space setting of this set.
          @propertyMap[property.id] = property
          @properties << property
        end
    
        # Remove the PropertyTreeNode (and all its children) object from the set.
        # _prop_ can either be a property ID or a reference to the PropertyTreeNode.
        #
        # TODO: This function does not take care of references to this PTN!
        def removeProperty(prop)
          if prop.is_a?(String)
            property = @propertyMap[prop]
          else
            property = prop
          end
    
          # Iterate over all properties and eliminate references to this the
          # PropertyTreeNode to be removed.
          @properties.each do |p|
            p.removeReferences(p)
          end
    
          # Recursively remove all sub-nodes. The children list is modified during
          # the call, so we can't use an iterator here.
          until property.children.empty? do
            removeProperty(property.children.first)
          end
    
          @properties.delete(property)
          @propertyMap.delete(property.fullId)
    
          # Remove this node from the child list of the parent node.
          property.parent.children.delete(property) if property.parent
    
    
          property
        end
    
        # Call this function to delete all registered properties.
        def clearProperties
          @properties.clear
          @propertyMap.clear
        end
    
        # Return the PropertyTreeNode object with ID _id_ from the set or nil if not
        # present.
        def [](id)
          @propertyMap[id]
        end
    
        # Update the breakdown structure indicies (bsi). This method needs to
        # be called whenever the set has been modified.
        def index
          each do |p|
            bsIdcs = p.getBSIndicies
            bsi = ""
            first = true
            bsIdcs.each do |idx|
              if first
                first = false
              else
                bsi += '.'
              end
              bsi += idx.to_s
            end
            p.force('bsi', bsi)
          end
        end
    
        # Return the index of the top-level _property_ in the set.
        def levelSeqNo(property)
          seqNo = 1
          @properties.each do |p|
            unless p.parent
              return seqNo if p == property
              seqNo += 1
            end
          end
          raise "Fatal Error: Unknow property #{property}"
        end
    
        # Return the maximum used number of breakdown levels. A flat list has a
        # maxDepth of 1. A list with one sub level has a maxDepth of 2 and so on.
        def maxDepth
          md = 0
          each do |p|
            md = p.level if p.level > md
          end
          md + 1
        end
    
        # Return the number of PropertyTreeNode objects in this set.
        def items
          @properties.length
        end
    
        alias length items
    
    
        # Return true if the set is empty.
        def empty?
          @properties.empty?
        end
    
        # Return the number of top-level PropertyTreeNode objects. Top-Level items
        # are no children.
        def topLevelItems
          items = 0
          @properties.each do |p|
            items += 1 unless p.parent
          end
          items
        end
    
        # Iterator over all PropertyTreeNode objects in this set.
        def each
          @properties.each do |value|
            yield(value)
          end
        end
    
        # Return the set of PropertyTreeNode objects as flat Array.
        def to_ary
          @properties.dup
        end
    
        def to_s
          PropertyList.new(self).to_s
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/TaskScenario.rb0000644000175000017500000030200512614413013021347 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = TaskScenario.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/ScenarioData'
    require 'taskjuggler/DataCache'
    
    class TaskJuggler
    
      class TaskScenario < ScenarioData
    
        attr_reader :isRunAway, :hasDurationSpec
    
        # Create a new TaskScenario object.
        def initialize(task, scenarioIdx, attributes)
          super
          # Attributed are only really created when they are accessed the first
          # time. So make sure some needed attributes really exist so we don't
          # have to check for existance each time we access them.
          %w( allocate assignedresources booking charge chargeset complete
              competitors criticalness depends duration
              effort end forward gauge length
              maxend maxstart minend minstart milestone pathcriticalness
              precedes priority scheduled shifts start status ).each do |attr|
            @property[attr, @scenarioIdx]
          end
    
          # A list of all allocated leaf resources.
          @candidates = []
          @dCache = DataCache.instance
        end
    
        def markAsScheduled
          return if @scheduled
          @scheduled = true
          if @milestone
            typename = 'Milestone'
          elsif @property.leaf?
            typename = 'Task'
          else
            typename = 'Container'
          end
    
          Log.msg { "#{typename} #{@property.fullId} has been scheduled." }
        end
    
        # Call this function to reset all scheduling related data prior to
        # scheduling.
        def prepareScheduling
          @property['startpreds', @scenarioIdx] = []
          @property['startsuccs', @scenarioIdx] = []
          @property['endpreds', @scenarioIdx] = []
          @property['endsuccs', @scenarioIdx] = []
    
          @isRunAway = false
    
          # And as global scoreboard index
          @currentSlotIdx = nil
          # The 'done' variables count scheduled values in number of time slots.
          @doneDuration = 0
          @doneLength = 0
          # Due to the 'efficiency' factor the effort slots must be a float.
          @doneEffort = 0.0
    
          @projectionMode = @project.scenario(@scenarioIdx).get('projection')
          @nowIdx = @project.dateToIdx(@project['now'])
    
          @startIsDetermed = nil
          @endIsDetermed = nil
    
          # To avoid multiple calls to propagateDate() we use these flags to know
          # when we've done it already.
          @startPropagated = false
          @endPropagated = false
    
          @durationType =
            if @effort > 0
              @hasDurationSpec = true
              :effortTask
            elsif @length > 0
              @hasDurationSpec = true
              :lengthTask
            elsif @duration > 0
              @hasDurationSpec = true
              :durationTask
            else
              # If the task is set as milestone it has a duration spec.
              @hasDurationSpec = @milestone
              :startEndTask
            end
    
          markAsMilestone
    
          # For start-end-tasks without allocation, we don't have to do
          # anything but to set the 'scheduled' flag.
          if @durationType == :startEndTask && @start && @end && @allocate.empty?
            markAsScheduled
          end
    
          # Collect the limits of this task and all parent tasks into a single
          # Array.
          @allLimits = []
          task = @property
          # Reset the counters of all limits of this task.
          task['limits', @scenarioIdx].reset if task['limits', @scenarioIdx]
          until task.nil?
            if task['limits', @scenarioIdx]
              @allLimits << task['limits', @scenarioIdx]
            end
            task = task.parent
          end
    
          @contendedResources = Hash.new { |hash, key| hash[key] = Hash.new(0) }
    
          # Collect the mandatory allocations.
          @mandatories = []
          @allocate.each do |allocation|
            @mandatories << allocation if allocation.mandatory
            allocation.lockedResource = nil
          end
    
          bookBookings
    
          if @durationType == :startEndTask
            @startIdx = @project.dateToIdx(@start) if @start
            @endIdx = @project.dateToIdx(@end) if @end
          end
        end
    
        # The parser only stores the full task IDs for each of the dependencies.
        # This function resolves them to task references and checks them. In
        # addition to the 'depends' and 'precedes' property lists we also keep 4
        # additional lists.
        # startpreds: All precedessors to the start of this task
        # startsuccs: All successors to the start of this task
        # endpreds: All predecessors to the end of this task
        # endsuccs: All successors to the end of this task
        # Each list element consists of a reference/boolean pair. The reference
        # points to the dependent task and the boolean specifies whether the
        # dependency originates from the end of the task or not.
        def Xref
          @depends.each do |dependency|
            depTask = checkDependency(dependency, 'depends')
            @startpreds.push([ depTask, dependency.onEnd ])
            depTask[dependency.onEnd ? 'endsuccs' : 'startsuccs', @scenarioIdx].
              push([ @property, false ])
          end
    
          @precedes.each do |dependency|
            predTask = checkDependency(dependency, 'precedes')
            @endsuccs.push([ predTask, dependency.onEnd ])
            predTask[dependency.onEnd ? 'endpreds' : 'startpreds', @scenarioIdx].
              push([@property, true ])
          end
        end
    
        # Return true of this Task has a dependency [ _target_, _onEnd_ ] in the
        # dependency category _depType_.
        def hasDependency?(depType, target, onEnd)
          a(depType).include?([target, onEnd])
        end
    
        def propagateInitialValues
          unless @startPropagated
            if @start
              propagateDate(@start, false, true)
            elsif @property.parent.nil? &&
                  @property.canInheritDate?(@scenarioIdx, false)
              propagateDate(@project['start'], false, true)
            end
          end
    
          unless @endPropagated
            if @end
              propagateDate(@end, true, true)
            elsif @property.parent.nil? &&
                  @property.canInheritDate?(@scenarioIdx, true)
              propagateDate(@project['end'], true, true)
            end
          end
        end
    
        # Before the actual scheduling work can be started, we need to do a few
        # consistency checks on the task.
        def preScheduleCheck
          # Accounts can have sub accounts added after being used in a chargetset.
          # So we need to re-test here.
          @chargeset.each do |chargeset|
            chargeset.each do |account, share|
              unless account.leaf?
                error('account_no_leaf',
                    "Chargesets may not include group account #{account.fullId}.")
              end
            end
          end
    
          # Leaf tasks can be turned into containers after bookings have been added.
          # We need to check for this.
          unless @property.leaf? || @booking.empty?
            error('container_booking',
                  "Container task #{@property.fullId} may not have bookings.")
          end
    
          # Milestones may not have bookings.
          if @milestone && !@booking.empty?
            error('milestone_booking',
                  "Milestone #{@property.fullId} may not have bookings.")
          end
    
          # All 'scheduled' tasks must have a fixed start and end date.
          if @scheduled && (@start.nil? || @end.nil?)
            error('not_scheduled',
                  "Task #{@property.fullId} is marked as scheduled but does not " +
                  'have a fixed start and end date.')
          end
    
          # If an effort has been specified resources must be allocated as well.
          if @effort > 0 && @allocate.empty?
            error('effort_no_allocations',
                  "Task #{@property.fullId} has an effort but no resource " +
                  "allocations.")
          end
    
          durationSpecs = 0
          durationSpecs += 1 if @effort > 0
          durationSpecs += 1 if @length > 0
          durationSpecs += 1 if @duration > 0
          durationSpecs += 1 if @milestone
    
          # The rest of this function performs a number of plausibility tests with
          # regards to task start and end critiria. To explain the various cases,
          # the following symbols are used:
          #
          # |: fixed start or end date
          # -: no fixed start or end date
          # M: Milestone
          # D: start or end dependency
          # x->: ASAP task with duration criteria
          # <-x: ALAP task with duration criteria
          # -->: ASAP task without duration criteria
          # <--: ALAP task without duration criteria
    
          if @property.container?
            if durationSpecs > 0
              error('container_duration',
                    "Container task #{@property.fullId} may not have a duration " +
                    "or be marked as milestones.")
            end
          elsif @milestone
            if durationSpecs > 1
              error('milestone_duration',
                    "Milestone task #{@property.fullId} may not have a duration.")
            end
            # Milestones can have the following cases:
            #
            #   |  M -   ok     |D M -   ok     - M -   err1   -D M -   ok
            #   |  M |   err2   |D M |   err2   - M |   ok     -D M |   ok
            #   |  M -D  ok     |D M -D  ok     - M -D  ok     -D M -D  ok
            #   |  M |D  err2   |D M |D  err2   - M |D  ok     -D M |D  ok
    
            # err1: no start and end
            # already handled by 'start_undetermed' or 'end_undetermed'
    
            # err2: differnt start and end dates
            if @start && @end && @start != @end
              error('milestone_start_end',
                    "Start (#{@start}) and end (#{@end}) dates of " +
                    "milestone task #{@property.fullId} must be identical.")
            end
          else
            #   Error table for non-container, non-milestone tasks:
            #   AMP: Automatic milestone promotion for underspecified tasks when
            #        no bookings or allocations are present.
            #   AMPi: Automatic milestone promotion when no bookings or
            #   allocations are present. When no bookings but allocations are
            #   present the task inherits start and end date.
            #   Ref. implicitXref()|
            #   inhS: Inherit start date from parent task or project
            #   inhE: Inherit end date from parent task or project
            #
            #   | x-> -   ok     |D x-> -   ok     - x-> -   inhS   -D x-> -   ok
            #   | x-> |   err1   |D x-> |   err1   - x-> |   inhS   -D x-> |   err1
            #   | x-> -D  ok     |D x-> -D  ok     - x-> -D  inhS   -D x-> -D  ok
            #   | x-> |D  err1   |D x-> |D  err1   - x-> |D  inhS   -D x-> |D  err1
            #   | --> -   AMP    |D --> -   AMP    - --> -   AMPi   -D --> -   AMP
            #   | --> |   ok     |D --> |   ok     - --> |   inhS   -D --> |   ok
            #   | --> -D  ok     |D --> -D  ok     - --> -D  inhS   -D --> -D  ok
            #   | --> |D  ok     |D --> |D  ok     - --> |D  inhS   -D --> |D  ok
            #   | <-x -   inhE   |D <-x -   inhE   - <-x -   inhE   -D <-x -   inhE
            #   | <-x |   err1   |D <-x |   err1   - <-x |   ok     -D <-x |   ok
            #   | <-x -D  err1   |D <-x -D  err1   - <-x -D  ok     -D <-x -D  ok
            #   | <-x |D  err1   |D <-x |D  err1   - <-x |D  ok     -D <-x |D  ok
            #   | <-- -   inhE   |D <-- -   inhE   - <-- -   AMP    -D <-- -   inhE
            #   | <-- |   ok     |D <-- |   ok     - <-- |   AMP    -D <-- |   ok
            #   | <-- -D  ok     |D <-- -D  ok     - <-- -D  AMP    -D <-- -D  ok
            #   | <-- |D  ok     |D <-- |D  ok     - <-- |D  AMP    -D <-- |D  ok
    
            # These cases are normally autopromoted to milestones or inherit their
            # start or end dates. But this only works for tasks that have no
            # allocations or bookings.
            #   -  --> -
            #   |  --> -
            #   |D --> -
            #   -D --> -
            #   -  <-- -
            #   -  <-- |
            #   -  <-- -D
            #   -  <-- |D
            if durationSpecs == 0 &&
               ((@forward && @end.nil? && !hasDependencies(true)) ||
                (!@forward && @start.nil? && !hasDependencies(false)))
              error('task_underspecified',
                    "Task #{@property.fullId} has too few specifations to be " +
                    "scheduled.")
            end
    
            #   err1: Overspecified (12 cases)
            #   |  x-> |
            #   |  <-x |
            #   |  x-> |D
            #   |  <-x |D
            #   |D x-> |
            #   |D <-x |
            #   |D <-x |D
            #   |D x-> |D
            #   -D x-> |
            #   -D x-> |D
            #   |D <-x -D
            #   |  <-x -D
            if durationSpecs > 1
              error('multiple_durations',
                    "Tasks may only have either a duration, length or effort or " +
                    "be a milestone.")
            end
            startSpeced = @property.provided('start', @scenarioIdx)
            endSpeced = @property.provided('end', @scenarioIdx)
            if ((startSpeced && endSpeced) ||
                (hasDependencies(false) && @forward && endSpeced) ||
                (hasDependencies(true) && !@forward && startSpeced)) &&
               durationSpecs > 0 && !@property.provided('scheduled', @scenarioIdx)
              error('task_overspecified',
                    "Task #{@property.fullId} has a start, an end and a " +
                    'duration specification.')
            end
          end
    
          if !@booking.empty? && !@forward && !@scheduled
            error('alap_booking',
                  'A task scheduled in ALAP mode may only have bookings if it ' +
                  'has been marked as fully scheduled. Keep in mind that ' +
                  'certain attributes like \'end\' or \'precedes\' automatically ' +
                  'switch the task to ALAP mode.')
          end
    
          @startsuccs.each do |task, onEnd|
            unless task['forward', @scenarioIdx]
              task.data[@scenarioIdx].error(
                'onstart_wrong_direction',
                'Tasks with on-start dependencies must be ASAP scheduled')
            end
          end
          @endpreds.each do |task, onEnd|
            if task['forward', @scenarioIdx]
              task.data[@scenarioIdx].error(
                'onend_wrong_direction',
                'Tasks with on-end dependencies must be ALAP scheduled')
            end
          end
        end
    
        # When the actual scheduling process has been completed, this function must
        # be called to do some more housekeeping. It computes some derived data
        # based on the just scheduled values.
        def finishScheduling
          # Recursively descend into all child tasks.
          @property.children.each do |task|
            task.finishScheduling(@scenarioIdx)
          end
    
          @property.parents.each do |pTask|
            # Add the assigned resources to the parent task's list.
            @assignedresources.each do |resource|
              unless pTask['assignedresources', @scenarioIdx].include?(resource)
                pTask['assignedresources', @scenarioIdx] << resource
              end
            end
          end
    
          # These lists are no longer needed, so let's save some memory. Set it to
          # nil so we can detect accidental use.
          @candidates = nil
          @mandatories = nil
          @allLimits = nil
        end
    
        # This function is not essential but does perform a large number of
        # consistency checks. It should be called after the scheduling run has been
        # finished.
        def postScheduleCheck
          @errors = 0
          @property.children.each do |task|
            @errors += 1 unless task.postScheduleCheck(@scenarioIdx)
          end
    
          # There is no point to check the parent if the child(s) have errors.
          return false if @errors > 0
    
          # Same for runaway tasks. They have already been reported.
          if @isRunAway
            error('sched_runaway', "Some tasks did not fit into the project time " +
                  "frame.")
          end
    
          # Make sure the task is marked complete
          unless @scheduled
            error('not_scheduled',
                  "Task #{@property.fullId} has not been marked as scheduled.")
          end
    
          # If the task has a follower or predecessor that is a runaway this task
          # is also incomplete.
          (@startsuccs + @endsuccs).each do |task, onEnd|
            return false if task.isRunAway(@scenarioIdx)
          end
          (@startpreds + @endpreds).each do |task, onEnd|
            return false if task.isRunAway(@scenarioIdx)
          end
    
          # Check if the start time is ok
          if @start.nil?
            error('task_start_undef',
                  "Task #{@property.fullId} has undefined start time")
          end
          if @start < @project['start'] || @start > @project['end']
            error('task_start_range',
                  "The start time (#{@start}) of task #{@property.fullId} " +
                  "is outside the project interval (#{@project['start']} - " +
                  "#{@project['end']})")
          end
          if !@minstart.nil? && @start < @minstart
            warning('minstart',
                   "The start time (#{@start}) of task #{@property.fullId} " +
                   "is too early. Must be after #{@minstart}.")
          end
          if !@maxstart.nil? && @start > @maxstart
            warning('maxstart',
                   "The start time (#{@start}) of task #{@property.fullId} " +
                   "is too late. Must be before #{@maxstart}.")
          end
          # Check if the end time is ok
          error('task_end_undef',
                "Task #{@property.fullId} has undefined end time") if @end.nil?
          if @end < @project['start'] || @end > @project['end']
            error('task_end_range',
                  "The end time (#{@end}) of task #{@property.fullId} " +
                  "is outside the project interval (#{@project['start']} - " +
                  "#{@project['end']})")
          end
          if !@minend.nil? && @end < @minend
            warning('minend',
                    "The end time (#{@end}) of task #{@property.fullId} " +
                    "is too early. Must be after #{@minend}.")
          end
          if !@maxend.nil? && @end > @maxend
            warning('maxend',
                    "The end time (#{@end}) of task #{@property.fullId} " +
                    "is too late. Must be before #{@maxend}.")
          end
          # Make sure the start is before the end
          if @start > @end
            error('start_after_end',
                  "The start time (#{@start}) of task #{@property.fullId} " +
                  "is after the end time (#{@end}).")
          end
    
    
          # Check that tasks fits into parent task.
          unless (parent = @property.parent).nil? ||
                  parent['start', @scenarioIdx].nil? ||
                  parent['end', @scenarioIdx].nil?
            if @start < parent['start', @scenarioIdx]
              error('task_start_in_parent',
                    "The start date (#{@start}) of task #{@property.fullId} " +
                    "is before the start date (#{parent['start', @scenarioIdx]}) " +
                    "of the enclosing task.")
            end
            if @end > parent['end', @scenarioIdx]
              error('task_end_in_parent',
                    "The end date (#{@end}) of task #{@property.fullId} " +
                    "is after the end date (#{parent['end', @scenarioIdx]}) " +
                    "of the enclosing task.")
            end
          end
    
          # Check that all preceding tasks start/end before this task.
          @depends.each do |dependency|
            task = dependency.task
            limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
            next if limit.nil?
            if @start < limit ||
               (dependency.gapDuration > 0 &&
                limit + dependency.gapDuration > @start) ||
               (dependency.gapLength > 0 &&
                calcLength(limit, @start) < dependency.gapLength)
              error('task_pred_before',
                    "Task #{@property.fullId} (#{@start}) must start " +
                    (dependency.gapDuration > 0 ?
                      "#{dependency.gapDuration / (60 * 60 * 24)} days " :
                      (dependency.gapLength > 0 ?
                        "#{@project.slotsToDays(dependency.gapLength)} " +
                        "working days " : '')) +
                    "after " +
                    "#{dependency.onEnd ? 'end' : 'start'} (#{limit}) of task " +
                    "#{task.fullId}. This condition could not be met.")
            end
          end
    
          # Check that all following tasks end before this task
          @precedes.each do |dependency|
            task = dependency.task
            limit = task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
            next if limit.nil?
            if limit < @end ||
               (dependency.gapDuration > 0 &&
                limit - dependency.gapDuration < @end) ||
               (dependency.gapLength > 0 &&
                calcLength(@end, limit) < dependency.gapLength)
              error('task_succ_after',
                    "Task #{@property.fullId} (#{@end}) must end " +
                    (dependency.gapDuration > 0 ?
                       "#{dependency.gapDuration / (60 * 60 * 24)} days " :
                       (dependency.gapLength > 0 ?
                         "#{@project.slotsToDays(dependency.gapLength)} " +
                         "working days " : '')) +
                    "before " +
                    "#{dependency.onEnd ? 'end' : 'start'} (#{limit}) of task " +
                    "#{task.fullId}. This condition could not be met.")
            end
          end
    
          if @milestone && @start != @end
            error('milestone_times_equal',
                  "Milestone #{@property.fullId} must have identical start and " +
                  "end date.")
          end
    
          if @property.leaf? && @effort == 0 && !@milestone && !@allocate.empty? &&
             @assignedresources.empty?
            # The user used an 'allocate' for the task, but did not specify any
            # 'effort'. Actual allocations will only happen when resources are
            # available by chance. If there are no assigned resources, we generate
            # a warning as this is probably not what the user intended.
            warning('allocate_no_assigned',
                    "Task #{@property.id} has resource allocation requested, but " +
                    "did not get any resources assigned. Either use 'effort' " +
                    "to ensure allocations or use a higher 'priority'.")
          end
    
          thieves = []
          @competitors.each do |t|
            thieves << t if t['priority', @scenarioIdx] < @priority
          end
          unless thieves.empty?
            warning('priority_inversion',
                    "Due to a mix of ALAP and ASAP scheduled tasks or a " +
                    "dependency on a lower priority tasks the following " +
                    "task#{thieves.length > 1 ? 's' : ''} stole resources from " +
                    "#{@property.fullId} despite having a lower priority:")
            thieves.each do |t|
              info('priority_inversion_info', "Task #{t.fullId}", t.sourceFileInfo)
            end
          end
    
          @errors == 0
        end
    
        def resetLoopFlags
          @deadEndFlags = Array.new(4, false)
        end
    
        # To ensure that we can properly schedule the project, we need to make
        # sure that it does not contain any circular dependencies. This method
        # recursively checks for such loops by remembering the _path_. Each entry
        # is marks the start or end of a task. _atEnd_ specifies whether we are
        # currently at the start or end of the task. _fromOutside_ specifies
        # whether we are coming from a inside or outside that tasks. See
        # specification below. _forward_ specifies whether we are checking the
        # dependencies from start to end or in the opposite direction. If we are
        # moving forward, we only move from start to end of ASAP tasks, not ALAP
        # tasks and vice versa. For milestones, we ignore the scheduling
        # direction.
        def checkForLoops(path, atEnd, fromOutside, forward)
          # Check if we have been here before on this path.
          if path.include?([ @property, atEnd ])
            warning('loop_detected',
                    "Dependency loop detected at #{atEnd ? 'end' : 'start'} " +
                    "of task #{@property.fullId}", false)
            skip = true
            path.each do |t, e|
              if t == @property && e == atEnd
                skip = false
                next
              end
              next if skip
              info("loop_at_#{e ? 'end' : 'start'}",
                   "Loop ctnd. at #{e ? 'end' : 'start'} of task #{t.fullId}",
                   t.sourceFileInfo)
            end
            error('loop_end', "Aborting")
          end
          # Used for debugging only
          if false
            pathText = ''
            path.each do |t, e|
              pathText += "#{t.fullId}(#{e ? 'end' : 'start'}) -> "
            end
            pathText += "#{@property.fullId}(#{atEnd ? 'end' : 'start'})"
            puts pathText
          end
          return if @deadEndFlags[(atEnd ? 2 : 0) + (fromOutside ? 1 : 0)]
          path << [ @property, atEnd ]
    
          # To find loops we have to traverse the graph in a certain order. When we
          # enter a task we can either come from outside or inside. The following
          # graph explains these definitions:
          #
          #             |      /          \      |
          #  outside    v    /              \    v   outside
          #          +------------------------------+
          #          |    /        Task        \    |
          #       -->|  o   <---          --->   o  |<--
          #          |/ Start                  End \|
          #         /+------------------------------+\
          #       /     ^                        ^     \
          #             |         inside         |
          #
          # At the top we have the parent task. At the botton the child tasks.
          # The horizontal arrors are start predecessors or end successors.
          # As the graph is doubly-linked, we need to becareful to only find real
          # loops. When coming from outside, we only continue to the inside and vice
          # versa. Horizontal moves are only made when we are in a leaf task.
          unless atEnd
            if fromOutside
              if @property.container?
                #
                #         |
                #         v
                #       +--------
                #    -->| o--+
                #       +----|---
                #            |
                #            V
                #
                @property.children.each do |child|
                  child.checkForLoops(@scenarioIdx, path, false, true, forward)
                end
              else
                #         |
                #         v
                #       +--------
                #    -->| o---->
                #       +--------
                #
                if (forward && @forward) || @milestone
                  checkForLoops(path, true, false, true)
                end
              end
            else
              if @startpreds.empty?
                #
                #         ^
                #         |
                #       +-|------
                #       | o <--
                #       +--------
                #         ^
                #         |
                #
                if @property.parent
                  @property.parent.checkForLoops(@scenarioIdx, path, false, false,
                                                 forward)
                end
              else
    
                #       +--------
                #    <---- o <--
                #       +--------
                #          ^
                #          |
                #
                @startpreds.each do |task, targetEnd|
                  task.checkForLoops(@scenarioIdx, path, targetEnd, true, forward)
                end
              end
            end
          else
            if fromOutside
              if @property.container?
                #
                #          |
                #          v
                #    --------+
                #       +--o |<--
                #    ---|----+
                #       |
                #       v
                #
                @property.children.each do |child|
                  child.checkForLoops(@scenarioIdx, path, true, true, forward)
                end
              else
                #          |
                #          v
                #    --------+
                #     <----o |<--
                #    --------+
                #
                if (!forward && !@forward) || @milestone
                  checkForLoops(path, false, false, false)
                end
              end
            else
              if @endsuccs.empty?
                #
                #          ^
                #          |
                #    ------|-+
                #      --> o |
                #    --------+
                #          ^
                #          |
                #
                if @property.parent
                  @property.parent.checkForLoops(@scenarioIdx, path, true, false,
                                                 forward)
                end
              else
                #    --------+
                #      --> o---->
                #    --------+
                #          ^
                #          |
                #
                @endsuccs.each do |task, targetEnd|
                  task.checkForLoops(@scenarioIdx, path, targetEnd, true, forward)
                end
              end
            end
          end
    
          path.pop
          @deadEndFlags[(atEnd ? 2 : 0) + (fromOutside ? 1 : 0)] = true
          # puts "Finished with #{@property.fullId} #{atEnd ? 'end' : 'start'} " +
          #      "#{fromOutside ? 'outside' : 'inside'}"
        end
    
        # This function must be called before prepareScheduling(). It compiles the
        # list of leaf resources that are allocated to this task.
        def candidates
          @candidates = []
          @allocate.each do |allocation|
            allocation.candidates.each do |candidate|
              candidate.allLeaves.each do |resource|
                @candidates << resource unless @candidates.include?(resource)
              end
            end
          end
          @candidates
        end
    
        # This function does some prep work for other functions like
        # calcCriticalness. It compiles a list of all allocated leaf resources and
        # stores it in @candidates. It also adds the allocated effort to
        # the 'alloctdeffort' counter of each resource.
        def countResourceAllocations
          return if @candidates.empty? || @effort <= 0
    
          avgEffort = @effort / @candidates.length
          @candidates.each do |resource|
            resource['alloctdeffort', @scenarioIdx] += avgEffort
          end
        end
    
        # Determine the criticalness of the individual task. This is a measure for
        # the likelyhood that this task will get the resources that it needs to
        # complete the effort. Tasks without effort are not cricital. The only
        # exception are milestones which get an arbitrary value between 0 and 2
        # based on their priority.
        def calcCriticalness
          @criticalness = 0.0
          @pathcriticalness = nil
    
          # Users feel that milestones are somewhat important. So we use an
          # arbitrary value larger than 0 for them. We make it priority dependent,
          # so the user has some control over it. Priority 0 is 0, 500 is 1.0 and
          # 1000 is 2.0. These values are pretty much randomly picked and probably
          # require some more tuning based on real projects.
          if @milestone
            @criticalness = @priority / 500.0
          end
    
          # Task without efforts of allocations are not critical.
          return if @effort <= 0 || @candidates.empty?
    
          # Determine the average criticalness of all allocated resources.
          criticalness = 0.0
          @candidates.each do |resource|
            criticalness += resource['criticalness', @scenarioIdx]
          end
          criticalness /= @candidates.length
    
          # The task criticalness is the product of effort and average resource
          # criticalness.
          @criticalness = @effort * criticalness
        end
    
        # The path criticalness is a measure for the overall criticalness of the
        # task taking the dependencies into account. The fact that a task is part
        # of a chain of effort-based task raises all the task in the chain to a
        # higher criticalness level than the individual tasks. In fact, the path
        # criticalness of this chain is equal to the sum of the individual
        # criticalnesses of the tasks.
        def calcPathCriticalness(atEnd = false)
          # If we have computed this already, just return the value. If we are only
          # at the end of the task, we do not include the criticalness of this task
          # as it is not really part of the path.
          if @pathcriticalness
            return @pathcriticalness - (atEnd ? 0 : @criticalness)
          end
    
          maxCriticalness = 0.0
    
          if atEnd
            # At the end, we only care about pathes through the successors of this
            # task or its parent tasks.
            if (criticalness = calcPathCriticalnessEndSuccs) > maxCriticalness
              maxCriticalness = criticalness
            end
          else
            # At the start of the task, we have two options.
            if @property.container?
              # For container tasks, we ignore all dependencies and check the pathes
              # through all the children.
              @property.children.each do |task|
                if (criticalness = task.calcPathCriticalness(@scenarioIdx, false)) >
                  maxCriticalness
                  maxCriticalness = criticalness
                end
              end
            else
              # For leaf tasks, we check all pathes through the start successors and
              # then the pathes through the end successors of this task and all its
              # parent tasks.
              @startsuccs.each do |task, onEnd|
                if (criticalness = task.calcPathCriticalness(@scenarioIdx, onEnd)) >
                  maxCriticalness
                  maxCriticalness = criticalness
                end
              end
    
              if (criticalness = calcPathCriticalnessEndSuccs) > maxCriticalness
                maxCriticalness = criticalness
              end
    
              maxCriticalness += @criticalness
            end
          end
    
          @pathcriticalness = maxCriticalness
        end
    
        # Check if the task is ready to be scheduled. For this it needs to have at
        # least one specified end date and a duration criteria or the other end
        # date.
        def readyForScheduling?
          # If the tasks has already been scheduled, we still call it 'ready' so
          # it will be removed from the todo list.
          return true if @scheduled
    
          return false if @isRunAway
    
          if @forward
            return true if @start && (@hasDurationSpec || @end)
          else
            return true if @end && (@hasDurationSpec || @start)
          end
    
          false
        end
    
        # This function is the entry point for the core scheduling algorithm. It
        # schedules the task to completion.  The function returns true if a start
        # or end date has been determined and other tasks may be ready for
        # scheduling now.
        def schedule
          # Check if the task has already been scheduled e. g. by propagateDate().
          return true if @scheduled
    
          logTag = "schedule_#{@property.id}"
          Log.enter(logTag, "Scheduling task #{@property.id}")
          # Compute the date of the next slot this task wants to have scheduled.
          # This must either be the first slot ever or it must be directly
          # adjecent to the previous slot. If this task has not yet been scheduled
          # at all, @currentSlotIdx is still nil. Otherwise it contains the index
          # of the last scheduled slot.
          if @forward
            # On first call, the @currentSlotIdx is not set yet. We set it to the
            # start slot index or the 'now' slot if we are in projection mode and
            # the tasks has allocations.
            if @currentSlotIdx.nil?
              @currentSlotIdx = @project.dateToIdx(
                @projectionMode && (@project['now'] > @start) && !@allocate.empty? ?
                @project['now'] : @start)
            end
          else
            # On first call, the @currentSlotIdx is not set yet. We set it to the
            # slot index of the slot before the end slot.
            if @currentSlotIdx.nil?
              @currentSlotIdx = @project.dateToIdx(@end) - 1
            end
          end
    
          # Schedule all time slots from slot in the scheduling direction until
          # the task is completed or a problem has been found.
          # The task may not excede the project interval.
          lowerLimit = @project.dateToIdx(@project['start'])
          upperLimit = @project.dateToIdx(@project['end'])
          delta = @forward ? 1 : -1
          while scheduleSlot
            @currentSlotIdx += delta
            if @currentSlotIdx < lowerLimit || upperLimit < @currentSlotIdx
              markAsRunaway
              Log.exit(logTag, "Scheduling of task #{@property.id} failed")
              return false
            end
          end
    
          Log.exit(logTag, "Scheduling of task #{@property.id} completed")
          true
        end
    
        # Set a new start or end date and propagate the value to all other
        # task ends that have a direct dependency to this end of the task.
        def propagateDate(date, atEnd, ignoreEffort = false)
          logTag = "propagateDate_#{@property.id}_#{atEnd ? 'end' : 'start'}"
          Log.enter(logTag, "Propagating #{atEnd ? 'end' : 'start'} date " +
                            "to task #{@property.id}")
          thisEnd = atEnd ? 'end' : 'start'
          otherEnd = atEnd ? 'start' : 'end'
          #puts "Propagating #{thisEnd} date #{date} of #{@property.fullId} " +
          #     "#{ignoreEffort ? "ignoring effort" : "" }"
    
          # These flags are just used to avoid duplicate calls of this function
          # during propagateInitialValues().
          if atEnd
            @endPropagated = true
          else
            @startPropagated = true
          end
    
          # For leaf tasks, propagate start may set the date. Container task dates
          # are only set in scheduleContainer().
          if @property.leaf?
            # If we already have a date, we will only shrink the task period with
            # the new date.
            if (setDate = instance_variable_get('@' + thisEnd)) &&
               atEnd ? date > setDate : date < setDate
              Log.msg { "Preserving #{thisEnd} date of #{typename} " +
                        "#{@property.fullId}: #{setDate}" }
              return
            end
    
            instance_variable_set(('@' + thisEnd).intern, date)
            typename = 'Task'
            if @durationType == :startEndTask
              instance_variable_set(('@' + thisEnd + 'Idx').intern,
                                    @project.dateToIdx(date))
              if @milestone
                typename = 'Milestone'
              end
            end
            Log.msg { "Update #{typename} #{@property.fullId}: #{period_to_s}" }
          end
    
          if @milestone
            # Start and end date of a milestone are identical.
            markAsScheduled
            if a(otherEnd).nil?
              propagateDate(a(thisEnd), !atEnd)
            end
          elsif !@scheduled && @start && @end &&
                !(@length == 0 && @duration == 0 && @effort == 0 &&
                  !@allocate.empty?)
            markAsScheduled
          end
    
          # Propagate date to all dependent tasks. Don't do this for start
          # successors or end predecessors if this task is effort based. In this
          # case, the date might still change to align with the first/last
          # allocation. In these cases, bookResource() has to propagate the final
          # date.
          if atEnd
            if ignoreEffort || @effort == 0
              @endpreds.each do |task, onEnd|
                propagateDateToDep(task, onEnd)
              end
            end
            @endsuccs.each do |task, onEnd|
              propagateDateToDep(task, onEnd)
            end
          else
            if ignoreEffort || @effort == 0
              @startsuccs.each do |task, onEnd|
                propagateDateToDep(task, onEnd)
              end
            end
            @startpreds.each do |task, onEnd|
              propagateDateToDep(task, onEnd)
            end
          end
    
          # Propagate date to sub tasks which have only an implicit
          # dependency on the parent task and no other criteria for this end of
          # the task.
          @property.children.each do |task|
            if task.canInheritDate?(@scenarioIdx, atEnd)
              task.propagateDate(@scenarioIdx, date, atEnd)
            end
          end
    
          # The date propagation might have completed the date set of the enclosing
          # containter task. If so, we can schedule it as well.
          @property.parents.each do |parent|
            parent.scheduleContainer(@scenarioIdx)
          end
          Log.exit(logTag, "Finished propagation of " +
                           "#{atEnd ? 'end' : 'start'} date " +
                           "to task #{@property.id}")
        end
    
        # This function determines if a task can inherit the start or end date
        # from a parent task or the project time frame. +atEnd+ specifies whether
        # the check should be done for the task end (true) or task start (false).
        def canInheritDate?(atEnd)
          # Inheriting a start or end date from the enclosing task or the project
          # is allowed for the following scenarios:
          #   -  --> -   inhS*1  -  <-- -   inhE*1
          #   -  --> |   inhS    |  <-- -   inhE
          #   -  x-> -   inhS    -  <-x -   inhE
          #   -  x-> |   inhS    |  <-x -   inhE
          #   -  x-> -D  inhS    -D <-x -   inhE
          #   -  x-> |D  inhS    |D <-x -   inhE
          #   -  --> -D  inhS    -D <-- -   inhE
          #   -  --> |D  inhS    |D <-- -   inhE
          #   -  <-- |   inhS    |  --> -   inhE
          #
          #   *1 when no bookings but allocations are present
    
          thisEnd, thatEnd = atEnd ? [ 'end', 'start' ] : [ 'start', 'end' ]
          # Return false if we already have a date for this end or if we have a
          # strong dependency for this end.
          return false if instance_variable_get('@' + thisEnd) ||
                          hasStrongDeps?(atEnd)
    
          # Containter task can inherit the date if they have no dependencies at
          # this end.
          return true if @property.container?
    
          hasThatSpec = !instance_variable_get('@' + thatEnd).nil? ||
                        hasStrongDeps?(!atEnd)
    
          # Check for tasks that have no start and end spec, no duration spec but
          # allocates. They can inherit the start and end date.
          return true if hasThatSpec && !@hasDurationSpec && !@allocate.empty?
    
          if @forward ^ atEnd
            # the scheduling direction is pointing away from this end
            return true if @hasDurationSpec || !@booking.empty?
    
            return hasThatSpec
          else
            # the scheduling direction is pointing towards this end
            return !instance_variable_get('@' + thatEnd).nil? &&
                   !@hasDurationSpec && @booking.empty? #&& @allocate.empty?
          end
        end
    
        # Find the smallest possible interval that encloses all child tasks. Abort
        # the operation if any of the child tasks are not yet scheduled.
        def scheduleContainer
          return if @scheduled || !@property.container?
    
          nStart = nil
          nEnd = nil
    
          @property.kids.each do |task|
            # Abort if a child has not yet been scheduled. Since we haven't done
            # the consistency check yet, we can't rely on start and end being set
            # if 'scheduled' is set.
            return if (!task['scheduled', @scenarioIdx] ||
                       task['start', @scenarioIdx].nil? ||
                       task['end', @scenarioIdx].nil?)
    
            if nStart.nil? || task['start', @scenarioIdx] < nStart
              nStart = task['start', @scenarioIdx]
            end
            if nEnd.nil? || task['end', @scenarioIdx] > nEnd
              nEnd = task['end', @scenarioIdx]
            end
          end
    
          startSet = endSet = false
          # Propagate the dates to other dependent tasks.
          if @start.nil? || @start > nStart
            @start = nStart
            startSet = true
          end
          if @end.nil? || @end < nEnd
            @end = nEnd
            endSet = true
          end
          unless @start && @end
            raise "Start (#{@start}) and end (#{@end}) must be set"
          end
          Log.msg { "Container task #{@property.fullId} completed: #{period_to_s}" }
          markAsScheduled
    
          # If we have modified the start or end date, we need to communicate this
          # new date to surrounding tasks.
          propagateDate(nStart, false) if startSet
          propagateDate(nEnd, true) if endSet
        end
    
        # Find the earliest possible start date for the task. This date must be
        # after the end date of all the task that this task depends on.
        # Dependencies may also require a minimum gap between the tasks.
        def earliestStart
          # This is the date that we will return.
          startDate = nil
          @depends.each do |dependency|
            potentialStartDate =
              dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
            return nil if potentialStartDate.nil?
    
            # Determine the end date of a 'length' gap.
            dateAfterLengthGap = potentialStartDate
            gapLength = dependency.gapLength
            while gapLength > 0 && dateAfterLengthGap < @project['end'] do
              if @project.isWorkingTime(dateAfterLengthGap)
                gapLength -= 1
              end
              dateAfterLengthGap += @project['scheduleGranularity']
            end
    
            # Determine the end date of a 'duration' gap.
            if dateAfterLengthGap > potentialStartDate + dependency.gapDuration
              potentialStartDate = dateAfterLengthGap
            else
              potentialStartDate += dependency.gapDuration
            end
    
            startDate = potentialStartDate if startDate.nil? ||
                                              startDate < potentialStartDate
          end
    
          # If any of the parent tasks has an explicit start date, the task must
          # start at or after this date.
          task = @property
          while (task = task.parent) do
            if task['start', @scenarioIdx] &&
               (startDate.nil? || task['start', @scenarioIdx] > startDate)
              startDate = task['start', @scenarioIdx]
              break
            end
          end
    
          # When the computed start date is after the already determined end date
          # of the task, the start dependencies were too weak. This happens when
          # task B depends on A and they are specified this way:
          # task A: | --> D-
          # task B: -D <-- |
          if @end && (startDate.nil? || startDate > @end)
            error('impossible_start_dep',
                  "Task #{@property.fullId} has start date dependencies " +
                  "that conflict with the end date #{@end}.")
          end
    
          startDate
        end
    
        # Find the latest possible end date for the task. This date must be
        # before the start date of all the task that this task precedes.
        # Dependencies may also require a minimum gap between the tasks.
        def latestEnd
          # This is the date that we will return.
          endDate = nil
          @precedes.each do |dependency|
            potentialEndDate =
              dependency.task[dependency.onEnd ? 'end' : 'start', @scenarioIdx]
            return nil if potentialEndDate.nil?
    
            # Determine the end date of a 'length' gap.
            dateBeforeLengthGap = potentialEndDate
            gapLength = dependency.gapLength
            while gapLength > 0 && dateBeforeLengthGap > @project['start'] do
              if @project.isWorkingTime(dateBeforeLengthGap -
                                        @project['scheduleGranularity'])
                gapLength -= 1
              end
              dateBeforeLengthGap -= @project['scheduleGranularity']
            end
    
            # Determine the end date of a 'duration' gap.
            if dateBeforeLengthGap < potentialEndDate - dependency.gapDuration
              potentialEndDate = dateBeforeLengthGap
            else
              potentialEndDate -= dependency.gapDuration
            end
    
            endDate = potentialEndDate if endDate.nil? || endDate > potentialEndDate
          end
    
          # If any of the parent tasks has an explicit end date, the task must end
          # at or before this date.
          task = @property
          while (task = task.parent) do
            if task['end', @scenarioIdx] &&
               (endDate.nil? || task['end', @scenarioIdx] < endDate)
              endDate = task['end', @scenarioIdx]
              break
            end
          end
    
          # When the computed end date is before the already determined start date
          # of the task, the end dependencies were too weak. This happens when
          # task A precedes B and they are specified this way:
          # task A: | --> D-
          # task B: -D <-- |
          if @start && (endDate.nil? || endDate < @start)
            error('impossible_end_dep',
                  "Task #{@property.fullId} has end date dependencies " +
                  "that conflict with the start date #{@start}.")
          end
    
          endDate
        end
    
        def addBooking(booking)
          # This append operation will not trigger a copy to sub-scenarios.
          # Bookings are only valid for the scenario they are defined in.
          @booking << booking
        end
    
        def query_activetasks(query)
          count = activeTasks(query)
    
          query.sortable = query.numerical = count
          # For the string output, we only use integer numbers.
          query.string = "#{count.to_i}"
        end
    
        def query_closedtasks(query)
          count = closedTasks(query)
    
          query.sortable = query.numerical = count
          # For the string output, we only use integer numbers.
          query.string = "#{count.to_i}"
        end
    
        def query_competitorcount(query)
          query.sortable = query.numerical = @competitors.length
          query.string = "#{@competitors.length}"
        end
    
        def query_complete(query)
          # If we haven't calculated the value yet, calculate it first.
          unless @complete
            calcCompletion
          end
    
          query.sortable = query.numerical = @complete
          # For the string output, we only use integer numbers.
          query.string = "#{@complete.to_i}%"
        end
    
        # Compute the cost generated by this Task for a given Account during a given
        # interval.  If a Resource is provided as scopeProperty only the cost
        # directly generated by the resource is taken into account.
        def query_cost(query)
          if query.costAccount
            query.sortable = query.numerical = cost =
              turnover(query.startIdx, query.endIdx, query.costAccount,
                       query.scopeProperty)
            query.string = query.currencyFormat.format(cost)
          else
            query.string = 'No \'balance\' defined!'
          end
        end
    
        # The duration of the task. After scheduling, it can be determined for
        # all tasks. Also for those who did not have a 'duration' attribute.
        def query_duration(query)
          query.sortable = query.numerical = duration =
            (@end - @start) / (60 * 60 * 24)
          query.string = query.scaleDuration(duration)
        end
    
        # The completed (as of 'now') effort allocated for the task in the
        # specified interval.  In case a Resource is given as scope property only
        # the effort allocated for this resource is taken into account.
        def query_effortdone(query)
          # For this query, we always override the query period.
          query.sortable = query.numerical = effort =
            getEffectiveWork(@project.dateToIdx(@project['start'], false),
                             @project.dateToIdx(@project['now']),
                             query.scopeProperty)
          query.string = query.scaleLoad(effort)
        end
    
        # The remaining (as of 'now') effort allocated for the task in the
        # specified interval.  In case a Resource is given as scope property only
        # the effort allocated for this resource is taken into account.
        def query_effortleft(query)
          # For this query, we always override the query period.
          query.sortable = query.numerical = effort =
            getEffectiveWork(@project.dateToIdx(@project['now']),
                             @project.dateToIdx(@project['end'], false),
                             query.scopeProperty)
          query.string = query.scaleLoad(effort)
        end
    
        # The effort allocated for the task in the specified interval. In case a
        # Resource is given as scope property only the effort allocated for this
        # resource is taken into account.
        def query_effort(query)
          query.sortable = query.numerical = work =
            getEffectiveWork(query.startIdx, query.endIdx, query.scopeProperty)
          query.string = query.scaleLoad(work)
        end
    
        def query_followers(query)
          list = []
    
          # First gather the task that depend on the start of this task.
          @startsuccs.each do |task, onEnd|
            if onEnd
              date = task['end', query.scenarioIdx].to_s(query.timeFormat)
              dep = "[->]"
            else
              date = task['start', query.scenarioIdx].to_s(query.timeFormat)
              dep = "[->["
            end
            list << generateDepencyListItem(query, task, dep, date)
          end
          # Than add the tasks that depend on the end of this task.
          @endsuccs.each do |task, onEnd|
            if onEnd
              date = task['end', query.scenarioIdx].to_s(query.timeFormat)
              dep = "]->]"
            else
              date = task['start', query.scenarioIdx].to_s(query.timeFormat)
              dep = "]->["
            end
            list << generateDepencyListItem(query, task, dep, date)
          end
    
          query.assignList(list)
        end
    
        def query_gauge(query)
          # If we haven't calculated the schedule status yet, calculate it first.
          calcGauge unless @gauge
    
          query.string = @gauge
        end
    
        # The number of different resources assigned to the task during the query
        # interval. Each resource is counted based on their mathematically rounded
        # efficiency.
        def query_headcount(query)
          headcount = 0
          assignedResources(Interval.new(query.start, query.end)).each do |res|
            headcount += res['efficiency', @scenarioIdx].round
          end
    
          query.sortable = query.numerical = headcount
          query.string = query.numberFormat.format(headcount)
        end
    
        def query_inputs(query)
          inputList = PropertyList.new(@project.tasks, false)
          inputs(inputList, true)
          inputList.delete(@property)
          inputList.setSorting([['start', true, @scenarioIdx],
                                ['seqno', true, -1 ]])
          inputList.sort!
    
          query.assignList(generateTaskList(inputList, query))
        end
    
        def query_maxend(query)
          queryDateLimit(query, @maxend)
        end
    
        def query_maxstart(query)
          queryDateLimit(query, @maxstart)
        end
    
        def query_minend(query)
          queryDateLimit(query, @minend)
        end
    
        def query_minstart(query)
          queryDateLimit(query, @minstart)
        end
    
        def query_opentasks(query)
          count = openTasks(query)
    
          query.sortable = query.numerical = count
          # For the string output, we only use integer numbers.
          query.string = "#{count.to_i}"
        end
    
        def query_precursors(query)
          list = []
    
          # First gather the task that depend on the start of this task.
          @startpreds.each do |task, onEnd|
            if onEnd
              date = task['end', query.scenarioIdx].to_s(query.timeFormat)
              dep = "]->["
            else
              date = task['start', query.scenarioIdx].to_s(query.timeFormat)
              dep = "[->["
            end
            list << generateDepencyListItem(query, task, dep, date)
          end
          # Than add the tasks that depend on the end of this task.
          @endpreds.each do |task, onEnd|
            if onEnd
              date = task['end', query.scenarioIdx].to_s(query.timeFormat)
              dep = "]->]"
            else
              date = task['start', query.scenarioIdx].to_s(query.timeFormat)
              dep = "[->]"
            end
            list << generateDepencyListItem(query, task, dep, date)
          end
    
          query.assignList(list)
        end
    
        # A list of the resources that have been allocated to work on the task in
        # the report time frame.
        def query_resources(query)
          list = []
          iv = TimeInterval.new(query.start, query.end)
          assignedResources(iv).each do |resource|
            if resource.allocated?(@scenarioIdx, iv, @property)
              if query.listItem
                rti = RichText.new(query.listItem, RTFHandlers.create(@project)).
                  generateIntermediateFormat
                unless rti
                  error('bad_resource_ts_query',
                        "Syntax error in query statement for task attribute " +
                        "'resources'.")
                end
                q = query.dup
                q.property = resource
                rti.setQuery(q)
                list << "#{rti.to_s}"
              else
                list << "#{resource.name} (#{resource.fullId})"
              end
            end
          end
          query.assignList(list)
        end
    
        # Compute the revenue generated by this Task for a given Account during a
        # given interval.  If a Resource is provided as scopeProperty only the
        # revenue directly generated by the resource is taken into account.
        def query_revenue(query)
          if query.revenueAccount
            query.sortable = query.numerical = revenue =
              turnover(query.startIdx, query.endIdx, query.revenueAccount,
                       query.scopeProperty)
            query.string = query.currencyFormat.format(revenue)
          else
            query.string = 'No \'balance\' defined!'
          end
        end
    
        def query_scheduling(query)
          query.string = @forward ? 'ASAP' : 'ASAP' if @property.leaf?
        end
    
        def query_status(query)
          # If we haven't calculated the completion yet, calculate it first.
          calcStatus if @status.empty?
    
          query.string = @status
        end
    
        def query_targets(query)
          targetList = PropertyList.new(@project.tasks, false)
          targets(targetList, true)
          targetList.delete(@property)
          targetList.setSorting([['start', true, @scenarioIdx],
                                 ['seqno', true, -1 ]])
          targetList.sort!
    
          query.assignList(generateTaskList(targetList, query))
        end
    
    
        # Compute the total time _resource_ or all resources are allocated during
        # interval specified by _startIdx_ and _endIdx_.
        def getAllocatedTime(startIdx, endIdx, resource = nil)
          return 0.0 if @milestone || startIdx >= endIdx ||
                        (resource && !@assignedresources.include?(resource))
    
          @dCache.cached(self, :TaskScenarioAllocatedTime, startIdx, endIdx, resource) do
            allocatedTime = 0.0
            if @property.container?
              @property.kids.each do |task|
                allocatedTime += task.getAllocatedTime(@scenarioIdx,
                                                       startIdx, endIdx, resource)
              end
            else
              if resource
                allocatedTime += resource.getAllocatedTime(@scenarioIdx,
                                                           startIdx, endIdx,
                                                           @property)
              else
                @assignedresources.each do |r|
                  allocatedTime += r.getAllocatedTime(@scenarioIdx, startIdx, endIdx,
                                                      @property)
                end
              end
            end
            allocatedTime
          end
        end
    
        # Compute the effective work a _resource_ or all resources do during the
        # interval specified by _startIdx_ and _endIdx_. The effective work is the
        # actual work multiplied by the efficiency of the resource.
        def getEffectiveWork(startIdx, endIdx, resource = nil)
          # Make sure we have the real Resource and not a proxy.
          resource = resource.ptn if resource
          return 0.0 if @milestone || startIdx >= endIdx ||
                        (resource && !@assignedresources.include?(resource))
    
          @dCache.cached(self, :TaskScenarioEffectiveWork, startIdx, endIdx,
                         resource) do
            workLoad = 0.0
            if @property.container?
              @property.kids.each do |task|
                workLoad += task.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
                                                  resource)
              end
            else
              if resource
                workLoad += resource.getEffectiveWork(@scenarioIdx, startIdx,
                                                      endIdx, @property)
              else
                @assignedresources.each do |r|
                  workLoad += r.getEffectiveWork(@scenarioIdx, startIdx, endIdx,
                                                 @property)
                end
              end
            end
            workLoad
          end
        end
    
        # Return a list of intervals that lay within _iv_ and are at least
        # minDuration long and contain no working time.
        def collectTimeOffIntervals(iv, minDuration)
          # This function is often called recursively for the same parameters. We
          # store the results in the cache to avoid repeated computations of the
          # same results.
          @dCache.cached(self, :TaskScenarioCollectTimeOffIntervals, iv,
                         minDuration) do
            il = IntervalList.new
            il << TimeInterval.new(@project['start'], @project['end'])
            if @property.leaf?
              unless (resources = @assignedresources).empty?
                # The task has assigned resources, so we can use their common time
                # off intervals.
                resources.each do |resource|
                  il &= resource.collectTimeOffIntervals(@scenarioIdx, iv,
                                                         minDuration)
                end
              else
                # The task has no assigned resources. We simply use the global time
                # off intervals.
                il &= @project.collectTimeOffIntervals(iv, minDuration)
              end
            else
              @property.kids.each do |task|
                il &= task.collectTimeOffIntervals(@scenarioIdx, iv, minDuration)
              end
            end
    
            il
          end
        end
    
        # Check if the Task _task_ depends on this task. _depth_ specifies how
        # many dependent task are traversed at max. A value of 0 means no limit.
        # TODO: Change this to a non-recursive implementation.
        def isDependencyOf(task, depth, list = [])
          return true if task == @property
    
          # If this task is already in the list of traversed task, we can ignore
          # it.
          return false if list.include?(@property)
          list << @property
    
          @startsuccs.each do |t, onEnd|
            unless onEnd
              # must be a start->start dependency
              return true if t.isDependencyOf(@scenarioIdx, task, depth, list)
            end
          end
    
          # For task to depend on this task, the start of task must be after the
          # end of this task.
          if task['start', @scenarioIdx] && @end
            return false if task['start', @scenarioIdx] < @end
          end
    
          # Check if any of the parent tasks is a dependency of _task_.
          t = @property.parent
          while t
            # If the parent is a dependency, than all childs are as well.
            return true if t.isDependencyOf(@scenarioIdx, task, depth, list)
            t = t.parent
          end
    
          return false if depth == 1
    
          @endsuccs.each do |ta, onEnd|
            unless onEnd
              # must be an end->start dependency
              return true if ta.isDependencyOf(@scenarioIdx, task, depth - 1, list)
            end
          end
    
          false
        end
    
        # If _task_ or any of its sub-tasks depend on this task or any of its
        # sub-tasks, we call this task a feature of _task_.
        def isFeatureOf(task)
          sources = @property.all
          destinations = task.all
    
          sources.each do |s|
            destinations.each do |d|
              return true if s.isDependencyOf(@scenarioIdx, d, 0)
            end
          end
    
          false
        end
    
        # Returns true of the _resource_ is assigned to this task or any of its
        # children.
        def hasResourceAllocated?(interval, resource)
          return false unless @assignedresources.include?(resource)
    
          if @property.leaf?
            return resource.allocated?(@scenarioIdx, interval, @property)
          else
            @property.kids.each do |t|
              return true if t.hasResourceAllocated?(@scenarioIdx, interval,
                                                     resource)
            end
          end
          false
        end
    
        # Gather a list of Resource objects that have been assigned to the task
        # (including sub tasks) for the given Interval _interval_.
        def assignedResources(interval = nil)
          interval = Interval.new(a('start'), a('end')) unless interval
          list = []
    
          if @property.container?
            @property.kids.each do |task|
              list += task.assignedResources(@scenarioIdx, interval)
            end
            list.uniq!
          else
            @assignedresources.each do |resource|
              if resource.allocated?(@scenarioIdx, interval, @property)
                list << resource
              end
            end
          end
    
          list
        end
    
      private
    
        def scheduleSlot
          # Tasks must always be scheduled in a single contigous fashion.
          # Depending on the scheduling direction the next slot must be scheduled
          # either right before or after this slot. If the current slot is not
          # directly aligned, we'll wait for another call with a proper slot. The
          # function returns false if the task has been completely scheduled.
          case @durationType
          when :effortTask
            bookResources if @doneEffort < @effort
            if @doneEffort >= @effort
              # The specified effort has been reached. The task has been fully
              # scheduled now.
              if @forward
                propagateDate(@project.idxToDate(@currentSlotIdx + 1), true, true)
              else
                propagateDate(@project.idxToDate(@currentSlotIdx), false, true)
              end
              return false
            end
          when :lengthTask
            bookResources
            # The doneLength is only increased for global working time slots.
            @doneLength += 1 if onShift?(@currentSlotIdx)
    
            # If we have reached the specified duration or lengths, we set the end
            # or start date and propagate the value to neighbouring tasks.
            if @doneLength >= @length
              if @forward
                propagateDate(@project.idxToDate(@currentSlotIdx + 1), true)
              else
                propagateDate(@project.idxToDate(@currentSlotIdx), false)
              end
              return false
            end
          when :durationTask
            # The doneDuration counts the number of scheduled slots. It is increased
            # by one with every scheduled slot.
            bookResources
            @doneDuration += 1
    
            # If we have reached the specified duration or lengths, we set the end
            # or start date and propagate the value to neighbouring tasks.
            if @doneDuration >= @duration
              if @forward
                propagateDate(@project.idxToDate(@currentSlotIdx + 1), true)
              else
                propagateDate(@project.idxToDate(@currentSlotIdx), false)
              end
              return false
            end
          when :startEndTask
            # Task with start and end date but no duration criteria
            bookResources
    
            # Depending on the scheduling direction we can mark the task as
            # scheduled once we have reached the other end.
            if (@forward && @currentSlotIdx >= @endIdx) |
               (!@forward && @currentSlotIdx <= @startIdx)
              markAsScheduled
              @property.parents.each do |parent|
                parent.scheduleContainer(@scenarioIdx)
              end
              return false
            end
          else
            raise "Unknown task duration type #{@durationType}"
          end
    
          true
        end
    
        def bookResources
          # First check if there is any resource at all for this slot.
          return if !@project.anyResourceAvailable?(@currentSlotIdx) ||
                    (@projectionMode && (@nowIdx > @currentSlotIdx))
    
    
          # If the task has resource independent allocation limits we need to make
          # sure that none of them is already exceeded.
          return unless limitsOk?(@currentSlotIdx)
    
          # If the task has shifts to limit the allocations, we check that we are
          # within a defined shift interval. If yes, we need to be on shift to
          # continue.
          if @shifts && @shifts.assigned?(@currentSlotIdx)
             return if !@shifts.onShift?(@currentSlotIdx)
          end
    
          # We first have to make sure that if there are mandatory resources
          # that these are all available for the time slot.
          takenMandatories = []
          @mandatories.each do |allocation|
            return unless allocation.onShift?(@currentSlotIdx)
    
            # For mandatory allocations with alternatives at least one of the
            # alternatives must be available.
            found = false
            allocation.candidates(@scenarioIdx).each do |candidate|
              # When a resource group is marked mandatory, all members of the
              # group must be available.
              allAvailable = true
              candidate.allLeaves.each do |resource|
                if !limitsOk?(@currentSlotIdx, resource) ||
                   !resource.available?(@scenarioIdx, @currentSlotIdx) ||
                   takenMandatories.include?(resource)
                  # We've found a mandatory resource that is not available for
                  # the slot.
                  allAvailable = false
                  break
                else
                  takenMandatories << resource
                end
              end
              if allAvailable
                found = true
                break
              end
            end
    
            # At least one mandatory resource is not available. We cannot continue.
            return unless found
          end
    
          @allocate.each do |allocation|
            next unless allocation.onShift?(@currentSlotIdx)
    
            # In case we have a persistent allocation we need to check if there
            # is already a locked resource and use it.
            locked_candidate = allocation.lockedResource
            if locked_candidate
              next if bookResource(locked_candidate)
    
              if allocation.atomic &&
                 locked_candidate.bookedTask(@scenarioIdx, @currentSlotIdx)
                rollbackBookings
                return
              end
    
              if @forward
                next if @currentSlotIdx < locked_candidate.getMaxSlot(@scenarioIdx)
              else
                next if @currentSlotIdx > locked_candidate.getMinSlot(@scenarioIdx)
              end
              # Persistent candidate is gone for the rest of the project!
              # Warn and assign somebody else, if available!
              warning('broken_persistence',
                      "Persistence broken for Task #{@property.fullId} " +
                      "- resource #{locked_candidate.name} is gone")
              allocation.lockedResource = nil
            end
    
            # Create a list of candidates in the proper order and
            # assign the first one available.
            allocation.candidates(@scenarioIdx).each do |candidate|
              if bookResource(candidate)
                allocation.lockedResource = candidate if allocation.persistent
                break
              end
            end
          end
        end
    
        def bookResource(resource)
          booked = false
          resource.allLeaves.each do |r|
            # Prevent overbooking when multiple resources are allocated and
            # available. If the task has allocation limits we need to make sure
            # that none of them is already exceeded.
            break if (@effort > 0 && r['efficiency', @scenarioIdx] > 0.0 &&
                      @doneEffort >= @effort) || !limitsOk?(@currentSlotIdx, r)
    
            if r.book(@scenarioIdx, @currentSlotIdx, @property)
              # This method is _very_ performance sensitive. Uncomment this log
              # message only if you really need it.
              #Log.msg { "Book #{resource.name} on task #{@property.fullId}" }
    
              # For effort based task we adjust the the start end (as defined by
              # the scheduling direction) to align with the first booked time
              # slot.
              if @effort > 0 && @assignedresources.empty?
                if @forward
                  @start = @project.idxToDate(@currentSlotIdx)
                  Log.msg { "Task #{@property.fullId} first assignment: " +
                            "#{period_to_s}" }
                  @startsuccs.each do |task, onEnd|
                    task.propagateDate(@scenarioIdx, @start, false, true)
                  end
                else
                  @end = @project.idxToDate(@currentSlotIdx + 1)
                  Log.msg { "Task #{@property.fullId} last assignment: " +
                            "#{period_to_s}" }
                  @endpreds.each do |task, onEnd|
                    task.propagateDate(@scenarioIdx, @end, true, true)
                  end
                end
              end
    
              @doneEffort += r['efficiency', @scenarioIdx]
    
              unless @assignedresources.include?(r)
                @assignedresources << r
              end
              booked = true
            elsif (competitor = r.bookedTask(@scenarioIdx, @currentSlotIdx))
              # Keep a list of all the Tasks that have successfully competed for
              # the same resources and are potentially delaying the progress of
              # this Task.
              @competitors << competitor unless @competitors.include?(competitor)
              @contendedResources[competitor][r] += 1
            end
          end
    
          booked
        end
    
        def onShift?(sbIdx)
          if @shifts && @shifts.assigned?(sbIdx)
            return @shifts.onShift?(sbIdx)
          else
            return @project.isWorkingTime(sbIdx)
          end
        end
    
        # Check if all of the task limits are not exceded at the given _sbIdx_. If
        # a _resource_ is provided, the limit for that particular resource is
        # checked. If no resource is provided, only non-resource-specific limits
        # are checked.
        def limitsOk?(sbIdx, resource = nil)
          @allLimits.each do |limit|
            return false unless limit.ok?(sbIdx, true, resource)
          end
          true
        end
    
        # Limits do not take efficiency into account. Limits are usage limits, not
        # effort limits.
        def incLimits(sbIdx, resource = nil)
          @allLimits.each do |limit|
            limit.inc(sbIdx, resource)
          end
        end
    
    
        # Calculate the number of general working time slots between the TjTime
        # objects _d1_ and _d2_.
        def calcLength(d1, d2)
          slots = 0
          while d1 < d2
            slots += 1 if @project.isWorkingTime(d1)
            d1 += @project['scheduleGranularity']
          end
          slots
        end
    
        # Register the user provided bookings with the Resource scoreboards. A
        # booking describes the assignment of a Resource to a certain Task for a
        # specified TimeInterval.
        def bookBookings
          firstSlotIdx = nil
          lastSlotIdx = nil
          findBookings.each do |booking|
            unless booking.resource.leaf?
              error('booking_resource_not_leaf',
                    "Booked resources may not be group resources",
                    booking.sourceFileInfo)
            end
            unless @forward || @scheduled
              error('booking_forward_only',
                    "Only forward scheduled tasks may have booking statements.")
            end
            booked = false
            booking.intervals.each do |interval|
              startIdx = @project.dateToIdx(interval.start, false)
              endIdx = @project.dateToIdx(interval.end, false)
              startIdx.upto(endIdx - 1) do |idx|
                if booking.resource.bookBooking(@scenarioIdx, idx, booking)
                  # Booking was successful for this time slot.
                  @doneEffort += booking.resource['efficiency', @scenarioIdx]
                  booked = true
    
                  # Store the indexes of the first slot and the slot after the
                  # last slot.
                  firstSlotIdx = idx if !firstSlotIdx || firstSlotIdx > idx
                  lastSlotIdx = idx if !lastSlotIdx || lastSlotIdx < idx
                end
              end
            end
            if booked && !@assignedresources.include?(booking.resource)
              @assignedresources << booking.resource
            end
          end
    
          # For effort based tasks, or tasks without a start date, with bookings
          # that have not yet been marked as scheduled we set the start date to
          # the date of the first booked slot.
          if (@start.nil? || (@doneEffort > 0 && @effort > 0)) &&
             !@scheduled && firstSlotIdx
            firstSlotDate = @project.idxToDate(firstSlotIdx)
            if @start.nil? || firstSlotDate > @start
              @start = firstSlotDate
              Log.msg { "Task #{@property.fullId} first booking: #{period_to_s}" }
            end
          end
    
          # Check if the the duration criteria has already been reached by the
          # supplied bookings and set the task end to the last booked slot.
          # Also the task is marked as scheduled.
          if lastSlotIdx && !@scheduled
            tentativeEnd = @project.idxToDate(lastSlotIdx + 1)
            slotDuration = @project['scheduleGranularity']
    
            if @effort > 0
              if @doneEffort >= @effort
                @end = tentativeEnd
                markAsScheduled
              end
            elsif @length > 0
              @doneLength = 0
              startIdx = @project.dateToIdx(date = @start)
              endIdx = @project.dateToIdx(@project['now'])
              startIdx.upto(endIdx) do |idx|
                @doneLength += 1 if onShift?(idx)
                date += slotDuration
                # Continue not only until the @length has been reached, but also
                # the tentativeEnd date. This allows us to detect overbookings.
                if @doneLength >= @length && date >= tentativeEnd
                  endDate = @project.idxToDate(idx + 1)
                  @end = [ endDate, tentativeEnd ].max
                  markAsScheduled
                  break
                end
              end
            elsif @duration > 0
              @doneDuration = ((tentativeEnd - @start) / slotDuration).to_i
              if @doneDuration >= @duration
                @end = tentativeEnd
                markAsScheduled
              elsif @duration * slotDuration < (@project['now'] - @start)
                # This handles the case where the bookings don't provide enough
                # @doneDuration to reach @duration, but the now date would be
                # after the @start + @duration date.
                @end = @start + @duration * slotDuration
                markAsScheduled
              end
            end
          end
    
          # If the task has bookings, we assume that the bookings describe all
          # work up to the 'now' date.
          if @doneEffort > 0
            @currentSlotIdx = @project.dateToIdx(@project['now'])
          end
    
          # Finally, we check if the bookings caused more effort, length or
          # duration than was requested by the user. This is only flagged as a
          # warning.
          if @effort > 0
            effort = @project.slotsToDays(@doneEffort)
            effortHours = effort * @project['dailyworkinghours']
            requestedEffort = @project.slotsToDays(@effort)
            requestedEffortHours = requestedEffort * @project['dailyworkinghours']
            if effort > requestedEffort
              warning('overbooked_effort',
                      "The total effort (#{effort}d or #{effortHours}h) of the " +
                      "provided bookings for task #{@property.fullId} exceeds " +
                      "the specified effort of #{requestedEffort}d or " +
                      "#{requestedEffortHours}h.")
            end
          end
          if @length > 0 && @doneLength > @length
            length = @project.slotsToDays(@doneLength)
            requestedLength = @project.slotsToDays(@length)
            warning('overbooked_length',
                    "The total length (#{length}d) of the provided bookings " +
                    "for task #{@property.fullId} exceeds the specified length of " +
                    "#{requestedLength}d.")
          end
          if @duration > 0 && @doneDuration > @duration
            duration = @doneDuration * @project['scheduleGranularity'] /
                       (60.0 * 60 * 24)
            requestedDuration = @duration * @project['scheduleGranularity'] /
                                (60.0 * 60 * 24)
            warning('overbooked_duration',
                    "The total duration (#{duration}d) of the provided bookings " +
                    "for task #{@property.fullId} exceeds the specified duration " +
                    "of #{requestedDuration}d.")
          end
        end
    
        def rollbackBookings
          @doneEffort = 0.0
    
          @allocate.each do |allocation|
            allocation.lockedResource = nil
            allocation.candidates(@scenarioIdx).each do |resource|
              resource.allLeaves.each do |r|
                r.rollbackBookings(@scenarioIdx, @property)
              end
            end
          end
        end
    
        # This function checks if the task has a dependency on another task or
        # fixed date for a certain end. If +atEnd+ is true, the task end will be
        # checked.  Otherwise the start.
        def hasDependencies(atEnd)
          thisEnd = atEnd ? 'end' : 'start'
          !a(thisEnd + 'succs').empty? || !a(thisEnd + 'preds').empty?
        end
    
        # Return true if this task or any of its parent tasks has at least one
        # predecessor task.
        def hasPredecessors
          t = @property
          while t
            return true unless t['startpreds', @scenarioIdx].empty?
            t = t.parent
          end
    
          false
        end
    
        # Return true if this task or any of its parent tasks has at least one
        # sucessor task.
        def hasSuccessors
          t = @property
          while t
            return true unless t['endsuccs', @scenarioIdx].empty?
            t = t.parent
          end
    
          false
        end
    
        # Return true if the task has a 'strong' dependency at the start if
        # _atEnd_ is false or at the end. A 'strong' dependency is an outer
        # dependency. At the start a predecessor is strong, and the end a
        # successor. start successors or end predecessors are considered weak
        # dependencies since this task will always have to get the date first and
        # then pass it on to the inner dependencies.
        def hasStrongDeps?(atEnd)
          if atEnd
            return !@endsuccs.empty?
          else
            return !@startpreds.empty?
          end
        end
    
        def markAsRunaway
          @isRunAway = true
          remainingEffort =
            @project.convertToDailyLoad(@project['scheduleGranularity'] *
                (@effort - @doneEffort))
          warning('runaway', "#{remainingEffort}d of effort of task " +
                             "#{@property.fullId} " +
                             "does not fit into the project time frame. ")
          unless @competitors.empty?
            multi = @competitors.length > 1
            info('runaway_tasks',
                 "The following task#{multi ? 's' : ''} " +
                 "compete#{multi ? '' : 's'} for the same resources that " +
                 "this task is requesting: ")
            @competitors.each do |t|
              res = @contendedResources[t].to_a
              res.sort! { |i, j| j[1] <=> i[1] }
              resList = res.map { |r| "#{r[0].id}(#{r[1]})" }.join(', ')
              info('runaway_competitor',
                   "Task #{t.fullId} has conflicts for the following " +
                   "resource#{res.length > 1 ? 's' : ''}: #{resList}",
                   t.sourceFileInfo)
            end
          end
        end
    
        # This function determines if a task is a milestones and marks it
        # accordingly.
        def markAsMilestone
          # Containers may not be milestones
          if @milestone && @property.container?
            error('container_milestone',
                  "Container task #{@property.fullId} may not be marked " +
                  "as a milestone.")
          end
    
          return if @property.container? || @hasDurationSpec ||
            !@booking.empty? || !@allocate.empty?
    
          # The following cases qualify for an automatic milestone promotion.
          #   -  --> -
          #   |  --> -
          #   |D --> -
          #   -D --> -
          #   -  <-- -
          #   -  <-- |
          #   -  <-- -D
          #   -  <-- |D
          hasStartSpec = !@start.nil? || !@depends.empty?
          hasEndSpec = !@end.nil? || !@precedes.empty?
    
          @milestone = (hasStartSpec && @forward && !hasEndSpec) ||
                       (!hasStartSpec && !@forward && hasEndSpec) ||
                       (!hasStartSpec && !hasEndSpec)
    
          # Milestones may only have start or end date even when the 'scheduled'
          # attribute is set. For further processing, we need to add the missing
          # date.
          if @milestone
            @hasDurationSpec = true
            @end = @start if @start && !@end
            @start = @end if !@start && @end
            Log.msg { "Mark as milestone #{@property.fullId}" }
          end
        end
    
        def checkDependency(dependency, depType)
          depList = instance_variable_get(('@' + depType).intern)
          if (depTask = dependency.resolve(@project)).nil?
            # Remove the broken dependency. It could cause trouble later on.
            depList.delete(dependency)
            error('task_depend_unknown',
                  "Task #{@property.fullId} has unknown #{depType} " +
                  "#{dependency.taskId}")
          end
    
          if depTask == @property
            # Remove the broken dependency. It could cause trouble later on.
            depList.delete(dependency)
            error('task_depend_self', "Task #{@property.fullId} cannot " +
                  "depend on self")
          end
    
          if depTask.isChildOf?(@property)
            # Remove the broken dependency. It could cause trouble later on.
            depList.delete(dependency)
            error('task_depend_child',
                  "Task #{@property.fullId} cannot depend on child " +
                  "#{depTask.fullId}")
          end
    
          if @property.isChildOf?(depTask)
            # Remove the broken dependency. It could cause trouble later on.
            depList.delete(dependency)
            error('task_depend_parent',
                  "Task #{@property.fullId} cannot depend on parent " +
                  "#{depTask.fullId}")
          end
    
          depList.each do |dep|
            if dep.task == depTask && !dep.equal?(dependency)
              # Remove the broken dependency. It could cause trouble later on.
              depList.delete(dependency)
              error('task_depend_multi',
                    "No need to specify dependency #{depTask.fullId} multiple " +
                    "times for task #{@property.fullId}.")
            end
          end
    
          depTask
        end
    
        # Set @startIsDetermed or @endIsDetermed (depending on _setStart) to
        # _value_.
        def setDetermination(setStart, value)
          setStart ? @startIsDetermed = value : @endIsDetermed = value
        end
    
        # This function is called to propagate the start or end date of the
        # current task to a dependend Task +task+. If +atEnd+ is true, the date
        # should be propagated to the end of the +task+, otherwise to the start.
        def propagateDateToDep(task, atEnd)
          #puts "Propagate #{atEnd ? 'end' : 'start'} to dep. #{task.fullId}"
          # Don't propagate if the task is already completely scheduled or is a
          # container.
          return if task['scheduled', @scenarioIdx] || task.container?
    
          # Don't propagate if the task already has a date for that end.
          return unless task[atEnd ? 'end' : 'start', @scenarioIdx].nil?
    
          # Don't propagate if the task has a duration or is a milestone and the
          # task end to set is in the scheduling direction.
          return if task.hasDurationSpec(@scenarioIdx) &&
                    !(atEnd ^ task['forward', @scenarioIdx])
    
          # Check if all other dependencies for that task end have been determined
          # already and use the latest or earliest possible date. Don't propagate
          # if we don't have all dates yet.
          return if (nDate = (atEnd ? task.latestEnd(@scenarioIdx) :
                                      task.earliestStart(@scenarioIdx))).nil?
    
          # Looks like it is ok to propagate the date.
          task.propagateDate(@scenarioIdx, nDate, atEnd)
          #puts "Propagate #{atEnd ? 'end' : 'start'} to dep. #{task.fullId} done"
        end
    
        # This is a helper function for calcPathCriticalness(). It computes the
        # larges criticalness of the pathes through the end-successors of this task
        # and all its parent tasks.
        def calcPathCriticalnessEndSuccs
          maxCriticalness = 0.0
          # Gather a list of all end-successors of this task and its parent task.
          tList = []
          depStruct = Struct.new(:task, :onEnd)
          p = @property
          while (p)
            p['endsuccs', @scenarioIdx].each do |task, onEnd|
              dep = depStruct.new(task, onEnd)
              tList << dep unless tList.include?(dep)
            end
            p = p.parent
          end
    
          tList.each do |dep|
            criticalness = dep.task.calcPathCriticalness(@scenarioIdx, dep.onEnd)
            maxCriticalness = criticalness if criticalness > maxCriticalness
          end
    
          maxCriticalness
        end
    
        # Calculate the current completion degree for tasks that have no user
        # specified completion value.
        def calcCompletion
          # If we already have a value for @complete, we don't need to calculate
          # anything.
          return @complete if @complete
    
          # We cannot compute a completion degree without a start or end date.
          if @start.nil? || @end.nil?
            @complete = 0.0
            return nil
          end
    
          @complete = calcTaskCompletion
        end
    
        def calcTaskCompletion
          completion = 0.0
    
          if @property.container?
            # For container task the completion degree is the average of the
            # sub tasks.
            @property.kids.each do |child|
              return nil unless (comp = child.calcCompletion(@scenarioIdx))
              completion += comp
            end
            completion /= @property.kids.length
          else
            # For leaf tasks we first compare the start and end dates against the
            # current date.
            if @end <= @project['now']
              # The task has ended already. It's 100% complete.
              completion = 100.0
            elsif @project['now'] <= @start
              # The task has not started yet. Its' 0% complete.
              completion = 0.0
            elsif @effort > 0
              # Effort based leaf tasks. The completion degree is the percentage
              # of effort that has been done already.
              done = getEffectiveWork(@project.dateToIdx(@start, false),
                                      @project.dateToIdx(@project['now']))
              total = @project.convertToDailyLoad(
                @effort * @project['scheduleGranularity'])
              completion = done / total * 100.0
            else
              # Length/duration leaf tasks.
              completion = ((@project['now'] - @start) / (@end - @start)) * 100.0
            end
          end
    
          completion
        end
    
        # Calculate the status of the task based on the 'complete' attribute.
        def calcStatus
          # If the completion degree is not yet available, we need to calculate it
          # first.
          calcCompletion unless @complete
    
          if @complete
            @status = if @complete == 0.0
                        # Milestones are reached, normal tasks started.
                        @milestone ? 'not reached' : 'not started'
                      elsif @complete >= 100.0
                        'done'
                      else
                        'in progress'
                      end
          else
            # The completion degree could not be calculated due to errors. We set
            # the state to unknown.
            @status = 'unknown'
          end
        end
    
        # The gauge shows if a task is ahead, behind or on schedule. The measure
        # is based on the provided 'complete' value and the current date.
        def calcGauge
          # If the completion degree is not yet available, we need to calculate it
          # first.
          calcCompletion unless @complete
    
          return @gauge if @gauge
    
          if @property.container?
            states = [ 'on schedule', 'ahead of schedule', 'behind schedule',
                       'unknown' ]
    
            gauge = 0
            @property.kids.each do |child|
              if (idx = states.index(child.calcGauge(@scenarioIdx))) > gauge
                gauge = idx
              end
            end
            @gauge = states[gauge]
          else
            @gauge =
              if (calculatedComplete = calcTaskCompletion).nil?
                # The completion degree could not be calculated due to errors. We
                # set the state to unknown.
                'unknown'
              elsif @complete == calculatedComplete
                'on schedule'
              elsif @complete < calculatedComplete
                'behind schedule'
              else
                'ahead of schedule'
              end
          end
        end
    
        def activeTasks(query)
          return 0 unless TimeInterval.new(@start, @end).
            overlaps?(TimeInterval.new(query.start, query.end))
    
          if @property.leaf?
            now = @project['now']
            return @start <= now && now < @end ? 1 : 0
          else
            cnt = 0
            @property.kids.each do |task|
              cnt += task.closedTasks(@scenarioIdx, query)
            end
            return cnt
          end
        end
    
        def closedTasks(query)
          return 0 unless TimeInterval.new(@start, @end).
            overlaps?(TimeInterval.new(query.start, query.end))
    
          if @property.leaf?
            return @end <= @project['now'] ? 1 : 0
          else
            cnt = 0
            @property.kids.each do |task|
              cnt += task.closedTasks(@scenarioIdx, query)
            end
            return cnt
          end
        end
    
        def openTasks(query)
          return 0 unless TimeInterval.new(@start, @end).
            overlaps?(TimeInterval.new(query.start, query.end))
    
          if @property.leaf?
            return @end > @project['now'] ? 1 : 0
          else
            cnt = 0
            @property.kids.each do |task|
              cnt += task.openTasks(@scenarioIdx, query)
            end
            return cnt
          end
        end
    
        # Recursively compile a list of Task properties which depend on the
        # current task.
        def inputs(foundInputs, includeChildren, checkedTasks = {})
          # Ignore tasks that we have already included in the checked tasks list.
          taskSignature = [ @property, includeChildren ]
          return if checkedTasks.include?(taskSignature)
          checkedTasks[taskSignature] = true
    
          # An "input" must be a leaf task that has no direct or indirect (through
          # parent) following tasks. Only milestones are recognized as inputs.
          if @property.leaf? && !hasPredecessors && @milestone
            foundInputs << @property
            return
          end
    
          # We also include inputs of child tasks if requested. The recursive
          # iteration of child tasks is limited to the tested task only. The
          # predecessors children are not iterated. (see further below)
          if includeChildren
            @property.kids.each do |child|
              child.inputs(@scenarioIdx, foundInputs, true, checkedTasks)
            end
          end
    
          # Now check the direct predecessors.
          @startpreds.each do |t, onEnd|
            t.inputs(@scenarioIdx, foundInputs, false, checkedTasks)
          end
    
          # Check for indirect predecessors inherited from the ancestors.
          if @property.parent
            @property.parent.inputs(@scenarioIdx, foundInputs, false, checkedTasks)
          end
        end
    
        # Recursively compile a list of Task properties which depend on the
        # current task.
        def targets(foundTargets, includeChildren, checkedTasks = {})
          # Ignore tasks that we have already included in the checked tasks list.
          taskSignature = [ @property, includeChildren ]
          return if checkedTasks.include?(taskSignature)
          checkedTasks[taskSignature] = true
    
          # A target must be a leaf function that has no direct or indirect
          # (through parent) following tasks. Only milestones are recognized as
          # targets.
          if @property.leaf? && !hasSuccessors && @milestone
            foundTargets << @property
            return
          end
    
          @endsuccs.each do |t, onEnd|
            t.targets(@scenarioIdx, foundTargets, false, checkedTasks)
          end
    
          # Check for indirect followers.
          if @property.parent
            @property.parent.targets(@scenarioIdx, foundTargets, false, checkedTasks)
          end
    
          # Also include targets of child tasks. The recursive iteration of child
          # tasks is limited to the tested task only. The followers are not
          # iterated.
          if includeChildren
            @property.kids.each do |child|
              child.targets(@scenarioIdx, foundTargets, true, checkedTasks)
            end
          end
        end
    
        # Compute the turnover generated by this Task for a given Account _account_
        # during the interval specified by _startIdx_ and _endIdx_. These can either
        # be TjTime values or Scoreboard indexes. If a Resource _resource_ is given,
        # only the turnover directly generated by the resource is taken into
        # account.
        def turnover(startIdx, endIdx, account, resource = nil, includeKids = true)
          amount = 0.0
          if @property.container? && includeKids
            @property.kids.each do |child|
              amount += child.turnover(@scenarioIdx, startIdx, endIdx, account,
                                       resource)
            end
          end
    
          # If we are evaluating the task in the context of a specific resource,
          # we use the chargeset of that resource, not the chargeset of the task.
          chargeset = resource ? resource['chargeset', @scenarioIdx] : @chargeset
    
          # If there are no chargeset defined for this task, we don't need to
          # compute the resource related or other cost.
          unless chargeset.empty?
            resourceCost = 0.0
            otherCost = 0.0
    
            # Container tasks don't have resource cost.
            unless @property.container?
              if resource
                resourceCost = resource.cost(@scenarioIdx, startIdx, endIdx,
                                             @property)
              else
                @assignedresources.each do |r|
                  resourceCost += r.cost(@scenarioIdx, startIdx, endIdx, @property)
                end
              end
            end
    
            unless @charge.empty?
              # Add one-time and periodic charges to the amount.
              startDate = startIdx.is_a?(TjTime) ? startIdx :
                @project.idxToDate(startIdx)
              endDate = endIdx.is_a?(TjTime) ? endIdx :
                @project.idxToDate(endIdx)
              iv = TimeInterval.new(startDate, endDate)
              @charge.each do |charge|
                otherCost += charge.turnover(iv)
              end
            end
    
            totalCost = resourceCost + otherCost
            # Now weight the total cost by the share of the account
            chargeset.each do |set|
              set.each do |accnt, share|
                if share > 0.0 && (accnt == account || accnt.isChildOf?(account))
                  amount += totalCost * share
                end
              end
            end
          end
    
          amount
        end
    
        def generateDepencyListItem(query, task, dep, date)
          if query.listItem
            rti = RichText.new(query.listItem, RTFHandlers.create(@project)).
              generateIntermediateFormat
            q = query.dup
            q.property = task
            q.setCustomData('dependency', { :string => dep })
            q.setCustomData('date', { :string => date })
            rti.setQuery(q)
            "#{rti.to_s}"
          else
            "#{task.name} (#{task.fullId}) #{dep} #{date}"
          end
        end
    
        def generateTaskList(taskList, query)
          list = []
          taskList.each do |task|
            date = task['start', @scenarioIdx].
                   to_s(@property.project['timeFormat'])
            if query.listItem
              rti = RichText.new(query.listItem, RTFHandlers.create(@project)).
                generateIntermediateFormat
              q = query.dup
              q.property = task
              q.setCustomData('date', { :string => date })
              rti.setQuery(q)
              list << "#{rti.to_s}"
            else
              list << "#{task.name} (#{task.fullId}) #{date}"
            end
          end
          list
        end
    
        def findBookings
          # Map the index back to the Scenario object.
          scenario = @property.project.scenario(@scenarioIdx)
          # Check if the current scenario should inherit its bookings from the
          # parent. If so, redirect 'scenario' to the parent. The top-level
          # scenario can never inherit bookings.
          while !scenario.get('ownbookings') do
            scenario = scenario.parent
          end
          # Return the bookings of the found scenario.
          @property['booking', @property.project.scenarioIdx(scenario)]
        end
    
        # Date limits may be nil and this is not an error. TjTime.to_s() would
        # report it as such if we don't use this wrapper method.
        def queryDateLimit(query, date)
          if date
            query.sortable = query.numerical = date
            query.string = date.to_s(query.timeFormat)
          else
            query.sortable = query.numerical = -1
            query.string = ''
          end
        end
    
        def period_to_s
          "#{@start ? @start.to_s : ''} -> #{@end ? @end.to_s : ''}"
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/LeaveList.rb0000644000175000017500000000505212614413013020653 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = LeaveList.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    class TaskJuggler
    
      # This class describes a leave.
      class Leave
    
        attr_reader :interval, :type, :reason
    
        # This Hash defines the supported leave types. It maps the symbol to its
        # index. The index sequence is important when multiple leaves are defined
        # for the same time slot. A subsequent definition with a type with a
        # larger index will override the old leave type.
        Types = {
          :project => 1,
          :annual => 2,
          :special => 3,
          :sick => 4,
          :unpaid => 5,
          :holiday => 6
        }
    
        # Create a new Leave object. _interval_ should be an Interval describing
        # the leave period. _type_ must be one of the supported leave types
        # (:holiday, :annual, :special, :unpaid, :sick and :project ). The
        # _reason_ is an optional String that describes the leave reason.
        def initialize(type, interval, reason = nil)
          unless Types[type]
            raise ArgumentError, "Unsupported leave type #{type}"
          end
          @type = type
          @interval = interval
          @reason = reason
        end
    
        def typeIdx
          Types[@type]
        end
    
        def to_s
          "#{@type} #{@reason ? "\"#{@reason}\"" : ""} #{@interval}"
        end
    
      end
    
      # A list of leaves.
      class LeaveList < Array
    
        def initialize(*args)
          super(*args)
        end
    
      end
    
      class LeaveAllowance < Struct.new(:type, :date, :slots)
    
        def initialize(type, date, slots)
          unless Leave::Types[type]
            raise ArgumentError, "Unsupported leave type #{type}"
          end
          super
        end
    
      end
    
      # The LeaveAllowanceList can store lists of LeaveAllowance objects.
      # Allowances are counted in time slots and can be negative to substract
      # expired allowances.
      class LeaveAllowanceList < Array
    
        # Create a new empty LeaveAllowanceList.
        def initialize(*args)
          super(*args)
        end
    
        def balance(type, startDate, endDate)
          unless Leave::Types[type]
            raise ArgumentError, "Unsupported leave type #{type}"
          end
    
          balance = 0.0
          each do |al|
            balance += al.slots if al.type == type && al.date >= startDate &&
                                   al.date < endDate
          end
          balance
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/taskjuggler/LogicalFunction.rb0000644000175000017500000002062712614413013022050 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = LogicalFunction.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/LogicalOperation'
    
    class TaskJuggler
    
      # The LogicalFunction is a specialization of the LogicalOperation. It models a
      # function call in a LogicalExpression.
      class LogicalFunction
    
        attr_accessor :name, :arguments
    
        # A map with the names of the supported functions and the number of
        # arguments they require.
        @@functions = {
            'hasalert' => 1,
            'isactive' => 1,
            'ischildof' => 1,
            'isdependencyof' => 3,
            'isdutyof' => 2,
            'isfeatureof' => 2,
            'isleaf' => 0,
            'ismilestone' => 1,
            'isongoing' => 1,
            'isresource' => 0,
            'isresponsibilityof' => 2,
            'istask' => 0,
            'isvalid' => 1,
            'treelevel' => 0
        }
    
        # Create a new LogicalFunction. _opnd_ is the name of the function.
        def initialize(opnd)
          if opnd[-1] == ?_
            # Function names with a trailing _ are like their counterparts without
            # the _. But during evaluation the property and the scope properties
            # will be switched.
            @name = opnd[0..-2]
            @invertProperties = true
          else
            @name = opnd
            @invertProperties = false
          end
          @arguments = []
        end
    
        # Register the arguments of the function and check if the name is a known
        # function and the number of arguments match this function. If not, return
        # an [ id, message ] error. Otherwise nil.
        def setArgumentsAndCheck(args)
          unless @@functions.include?(@name)
            return [ 'unknown_function',
                     "Unknown function #{@name} used in logical expression." ]
          end
          if @@functions[@name] != args.length
            return [ 'wrong_no_func_arguments',
                     "Wrong number of arguments for function #{@name}. Got " +
                     "#{args.length} instead of #{@@functions[@name]}." ]
          end
          @arguments = args
          nil
        end
    
        # Evaluate the function by calling it with the arguments.
        def eval(expr)
          # Call the function and return the result.
          send(name, expr, @arguments)
        end
    
        # Return a textual expression of the function call.
        def to_s
          "#{@name}(#{@arguments.join(', ')})"
        end
    
      private
    
        # Return the property and scope property as determined by the
        # @invertProperties setting.
        def properties(expr)
          if @invertProperties
            return expr.query.scopeProperty, nil
          else
            return expr.query.property, expr.query.scopeProperty
          end
        end
    
        def hasalert(expr, args)
          property = properties(expr)[0]
          query = expr.query
          project = property.project
          !project['journal'].currentEntries(query.end, property,
                                             args[0], query.start,
                                             query.hideJournalEntry).empty?
        end
    
        def isactive(expr, args)
          property, scopeProperty = properties(expr)
          # The result can only be true when called for a Task property.
          return false unless property.is_a?(Task) ||
                              property.is_a?(Resource)
          project = property.project
          # 1st arg must be a scenario index.
          if (scenarioIdx = project.scenarioIdx(args[0])).nil?
            expr.error("Unknown scenario '#{args[0]}' used for function isactive()")
          end
    
          query = expr.query
          property.getAllocatedTime(scenarioIdx, query.startIdx, query.endIdx,
                                    scopeProperty) > 0.0
        end
    
        def ischildof(expr, args)
          # The the context property.
          property = properties(expr)[0]
          # Find the prospective parent ID in the current PropertySet.
          return false unless (parent = property.propertySet[args[0]])
    
          property.isChildOf?(parent)
        end
    
        def isdependencyof(expr, args)
          property = properties(expr)[0]
          # The result can only be true when called for a Task property.
          return false unless property.is_a?(Task)
          project = property.project
          # 1st arg must be a task ID.
          return false if (task = project.task(args[0])).nil?
          # 2nd arg must be a scenario index.
          return false if (scenarioIdx = project.scenarioIdx(args[1])).nil?
          # 3rd arg must be an integer number.
          return false unless args[2].is_a?(Fixnum)
    
          property.isDependencyOf(scenarioIdx, task, args[2])
        end
    
        def isdutyof(expr, args)
          property = properties(expr)[0]
          # The result can only be true when called for a Task property.
          return false unless (task = property).is_a?(Task)
          project = task.project
          # 1st arg must be a resource ID.
          return false if (resource = project.resource(args[0])).nil?
          # 2nd arg must be a scenario index.
          return false if (scenarioIdx = project.scenarioIdx(args[1])).nil?
    
          task['assignedresources', scenarioIdx].include?(resource)
        end
    
        def isfeatureof(expr, args)
          property = properties(expr)[0]
          # The result can only be true when called for a Task property.
          return false unless property.is_a?(Task)
          project = property.project
          # 1st arg must be a task ID.
          return false if (task = project.task(args[0])).nil?
          # 2nd arg must be a scenario index.
          return false if (scenarioIdx = project.scenarioIdx(args[1])).nil?
    
          property.isFeatureOf(scenarioIdx, task)
        end
    
        def isleaf(expr, args)
          property = properties(expr)[0]
          return false unless property
          property.leaf?
        end
    
        def ismilestone(expr, args)
          property = properties(expr)[0]
          return false unless property
          # 1st arg must be a scenario index.
          return false if (scenarioIdx = property.project.scenarioIdx(args[0])).nil?
    
          property.is_a?(Task) && property['milestone', scenarioIdx]
        end
    
        def isongoing(expr, args)
          property = properties(expr)[0]
          # The result can only be true when called for a Task property.
          return false unless (task = property).is_a?(Task)
          project = task.project
          # 1st arg must be a scenario index.
          if (scenarioIdx = project.scenarioIdx(args[0])).nil?
            expr.error("Unknown scenario '#{args[0]}' used for function " +
                       "isongoing()")
          end
    
          query = expr.query
          iv1 = TimeInterval.new(query.start, query.end)
          tStart = task['start', scenarioIdx]
          tEnd = task['end', scenarioIdx]
          # This helps to show tasks with scheduling errors.
          return true unless tStart && tEnd
          iv2 = TimeInterval.new(tStart, tEnd)
    
          return iv1.overlaps?(iv2)
        end
    
        def isresource(expr, args)
          property = properties(expr)[0]
          return false unless property
          property.is_a?(Resource)
        end
    
        def isresponsibilityof(expr, args)
          property = properties(expr)[0]
          # The result can only be true when called for a Task property.
          return false unless (task = property).is_a?(Task)
          project = task.project
          # 1st arg must be a resource ID.
          return false if (resource = project.resource(args[0])).nil?
          # 2nd arg must be a scenario index.
          return false if (scenarioIdx = project.scenarioIdx(args[1])).nil?
    
          task['responsible', scenarioIdx].include?(resource)
        end
    
        def istask(expr, args)
          property = properties(expr)[0]
          return false unless property
          property.is_a?(Task)
        end
    
        def isvalid(expr, args)
          property = properties(expr)[0]
          project = property.project
          attr = args[0]
          scenario, attr = args[0].split('.')
          if attr.nil?
            attr = scenario
            scenario = nil
          end
          expr.error("Argument must not be empty") unless attr
          if scenario && (scenarioIdx = project.scenarioIdx(scenario)).nil?
            expr.error("Unknown scenario '#{scenario}' used for function " +
                       "isvalid()")
          end
          unless property.propertySet.knownAttribute?(attr)
            expr.error("Unknown attribute '#{attr}' used for function " +
                       "isvalid()")
          end
          !property[attr, scenarioIdx].nil?
        end
    
        def treelevel(expr, args)
          property = properties(expr)[0]
          return 0 unless property
          property.level + 1
        end
    
      end
    
    end
    
    taskjuggler-3.5.0/lib/header.tmpl0000644000175000017500000000063612614413013016245 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = $FILE -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    taskjuggler-3.5.0/lib/tj3ts_receiver.rb0000644000175000017500000000100012614413013017361 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = tj3ts_receiver.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/apps/Tj3TsReceiver'
    
    exit TaskJuggler::Tj3TsReceiver.new.main()
    
    taskjuggler-3.5.0/lib/updateheader.sh0000644000175000017500000000101312614413013017074 0ustar  bernatbernatfiles="*.rb taskjuggler/*.rb taskjuggler/apps/*.rb
    taskjuggler/daemon/*.rb taskjuggler/reports/*.rb
    taskjuggler/RichText/*.rb taskjuggler/TextParser/*.rb ../test/*.rb
    ../spec/*.rb ../spec/support/*.rb" 
    
    for f in $files; do
      basename=`basename $f`
      sed "s/\$FILE/$basename/g" header.tmpl > header
      firstLine=`head -1 $f`
      if test "$firstLine" == "#!/usr/bin/env ruby -w"; then
        sed '1,/^$/d' $f > tmpfile
        mv -f tmpfile $f
      else
        echo "$f has no header"
      fi
      cat header $f > tmpfile
      mv -f tmpfile $f
    done
    taskjuggler-3.5.0/lib/tj3d.rb0000644000175000017500000000075612614413013015313 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = tj3d.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/apps/Tj3Daemon'
    
    exit TaskJuggler::Tj3Daemon.new.main()
    
    taskjuggler-3.5.0/lib/tj3ts_summary.rb0000644000175000017500000000077512614413013017274 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = tj3ts_summary.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/apps/Tj3TsSummary'
    
    exit TaskJuggler::Tj3TsSummary.new.main()
    
    taskjuggler-3.5.0/lib/tj3man.rb0000644000175000017500000000075112614413013015636 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = tj3man.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/apps/Tj3Man'
    
    exit TaskJuggler::Tj3Man.new.main()
    taskjuggler-3.5.0/lib/tj3.rb0000644000175000017500000000074112614413013015141 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = tj3.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/apps/Tj3'
    
    exit TaskJuggler::Tj3.new.main()
    
    taskjuggler-3.5.0/lib/tj3webd.rb0000644000175000017500000000075512614413013016010 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = tj3webd.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/apps/Tj3WebD'
    
    exit TaskJuggler::Tj3WebD.new.main()
    
    taskjuggler-3.5.0/lib/tj3ss_receiver.rb0000644000175000017500000000100012614413013017360 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = tj3ss_receiver.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/apps/Tj3SsReceiver'
    
    exit TaskJuggler::Tj3SsReceiver.new.main()
    
    taskjuggler-3.5.0/lib/tj3client.rb0000644000175000017500000000076712614413013016350 0ustar  bernatbernat#!/usr/bin/env ruby -w
    # encoding: UTF-8
    #
    # = tj3client.rb -- The TaskJuggler III Project Management Software
    #
    # Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    #               by Chris Schlaeger 
    #
    # This program is free software; you can redistribute it and/or modify
    # it under the terms of version 2 of the GNU General Public License as
    # published by the Free Software Foundation.
    #
    
    require 'taskjuggler/apps/Tj3Client'
    
    exit TaskJuggler::Tj3Client.new.main(ARGV)
    
    taskjuggler-3.5.0/bin/0000755000175000017500000000000012614413013014114 5ustar  bernatbernattaskjuggler-3.5.0/bin/tj3d0000755000175000017500000000015712614413013014711 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj3ts_summary0000755000175000017500000000015712614413013016671 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj3webd0000755000175000017500000000015712614413013015407 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj3ts_receiver0000755000175000017500000000015712614413013017000 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj3ss_receiver0000755000175000017500000000015712614413013016777 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj3client0000755000175000017500000000015712614413013015744 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj30000755000175000017500000000015712614413013014545 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj3man0000755000175000017500000000015712614413013015241 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj3ts_sender0000755000175000017500000000015712614413013016454 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)
    taskjuggler-3.5.0/bin/tj3ss_sender0000755000175000017500000000015712614413013016453 0ustar  bernatbernat#!/usr/bin/env ruby
    
    $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
    require File.basename(__FILE__)