taskjuggler 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +44 -0
- data/bin/tj3webd +4 -0
- data/data/css/tjreport.css +14 -5
- data/data/tjp.vim +22 -7
- data/examples/Fedora-20/reports.tji +2 -4
- data/examples/Scrum/Product Burndown.csv +26 -0
- data/examples/Scrum/Sprint 1 Burndown.csv +26 -0
- data/examples/Scrum/Sprint 2 Burndown.csv +26 -0
- data/examples/Scrum/Sprint 3 Burndown.csv +26 -0
- data/examples/Scrum/scrum.tjp +141 -0
- data/examples/Tutorial/tutorial.tjp +13 -7
- data/lib/taskjuggler/Attributes.rb +2 -3
- data/lib/taskjuggler/HTMLDocument.rb +25 -18
- data/lib/taskjuggler/Journal.rb +85 -65
- data/lib/taskjuggler/KeywordDocumentation.rb +25 -13
- data/lib/taskjuggler/LeaveList.rb +1 -1
- data/lib/taskjuggler/Limits.rb +3 -5
- data/lib/taskjuggler/MessageHandler.rb +173 -19
- data/lib/taskjuggler/Painter.rb +75 -0
- data/lib/taskjuggler/Painter/BasicShapes.rb +76 -0
- data/lib/taskjuggler/Painter/Color.rb +273 -0
- data/lib/taskjuggler/Painter/Element.rb +56 -0
- data/lib/taskjuggler/Painter/FontData.rb +221 -0
- data/lib/taskjuggler/Painter/FontMetrics.rb +125 -0
- data/lib/taskjuggler/Painter/FontMetricsData.rb +151 -0
- data/lib/taskjuggler/Painter/Group.rb +77 -0
- data/lib/taskjuggler/Painter/Points.rb +47 -0
- data/lib/taskjuggler/Painter/Primitives.rb +100 -0
- data/lib/taskjuggler/Painter/SVGSupport.rb +36 -0
- data/lib/taskjuggler/Painter/Text.rb +36 -0
- data/lib/taskjuggler/Project.rb +46 -29
- data/lib/taskjuggler/ProjectFileParser.rb +24 -22
- data/lib/taskjuggler/ProjectFileScanner.rb +2 -2
- data/lib/taskjuggler/PropertyTreeNode.rb +26 -34
- data/lib/taskjuggler/Query.rb +8 -5
- data/lib/taskjuggler/RealFormat.rb +3 -0
- data/lib/taskjuggler/Resource.rb +3 -5
- data/lib/taskjuggler/ResourceScenario.rb +19 -7
- data/lib/taskjuggler/RichText.rb +4 -6
- data/lib/taskjuggler/RichText/FunctionExample.rb +1 -1
- data/lib/taskjuggler/RichText/FunctionHandler.rb +5 -6
- data/lib/taskjuggler/RichText/Parser.rb +4 -6
- data/lib/taskjuggler/RichText/RTFNavigator.rb +1 -1
- data/lib/taskjuggler/RichText/RTFQuery.rb +2 -3
- data/lib/taskjuggler/RichText/RTFReport.rb +1 -1
- data/lib/taskjuggler/RichText/RTFReportLink.rb +1 -2
- data/lib/taskjuggler/RichText/RTFWithQuerySupport.rb +1 -1
- data/lib/taskjuggler/RichText/Scanner.rb +6 -6
- data/lib/taskjuggler/RichText/Snip.rb +1 -2
- data/lib/taskjuggler/RuntimeConfig.rb +9 -6
- data/lib/taskjuggler/ScenarioData.rb +4 -3
- data/lib/taskjuggler/Scoreboard.rb +6 -0
- data/lib/taskjuggler/SheetHandlerBase.rb +25 -8
- data/lib/taskjuggler/SimpleQueryExpander.rb +14 -5
- data/lib/taskjuggler/SyntaxReference.rb +1 -2
- data/lib/taskjuggler/TableColumnSorter.rb +84 -0
- data/lib/taskjuggler/Task.rb +3 -5
- data/lib/taskjuggler/TaskJuggler.rb +36 -29
- data/lib/taskjuggler/TaskScenario.rb +154 -66
- data/lib/taskjuggler/TextParser.rb +24 -17
- data/lib/taskjuggler/TextParser/Scanner.rb +16 -11
- data/lib/taskjuggler/TextParser/SourceFileInfo.rb +20 -15
- data/lib/taskjuggler/TimeSheets.rb +6 -12
- data/lib/taskjuggler/Tj3AppBase.rb +35 -16
- data/lib/taskjuggler/Tj3Config.rb +1 -1
- data/lib/taskjuggler/TjpSyntaxRules.rb +239 -49
- data/lib/taskjuggler/XMLElement.rb +9 -2
- data/lib/taskjuggler/apps/Tj3.rb +43 -37
- data/lib/taskjuggler/apps/Tj3Client.rb +62 -112
- data/lib/taskjuggler/apps/Tj3Daemon.rb +66 -29
- data/lib/taskjuggler/apps/Tj3Man.rb +5 -5
- data/lib/taskjuggler/apps/Tj3SsReceiver.rb +10 -13
- data/lib/taskjuggler/apps/Tj3SsSender.rb +13 -16
- data/lib/taskjuggler/apps/Tj3TsReceiver.rb +10 -13
- data/lib/taskjuggler/apps/Tj3TsSender.rb +12 -15
- data/lib/taskjuggler/apps/Tj3TsSummary.rb +12 -15
- data/lib/taskjuggler/apps/Tj3WebD.rb +99 -0
- data/lib/taskjuggler/daemon/Daemon.rb +50 -10
- data/lib/taskjuggler/daemon/DaemonConnector.rb +127 -0
- data/lib/taskjuggler/daemon/ProcessIntercom.rb +36 -21
- data/lib/taskjuggler/daemon/ProjectBroker.rb +122 -112
- data/lib/taskjuggler/daemon/ProjectServer.rb +78 -46
- data/lib/taskjuggler/daemon/ReportServer.rb +52 -28
- data/lib/taskjuggler/daemon/ReportServlet.rb +92 -21
- data/lib/taskjuggler/daemon/WebServer.rb +75 -22
- data/lib/taskjuggler/daemon/WelcomePage.rb +1 -0
- data/lib/taskjuggler/reports/AccountListRE.rb +3 -3
- data/lib/taskjuggler/reports/CSVFile.rb +9 -2
- data/lib/taskjuggler/reports/ChartPlotter.rb +453 -0
- data/lib/taskjuggler/reports/Navigator.rb +1 -0
- data/lib/taskjuggler/reports/NikuReport.rb +4 -4
- data/lib/taskjuggler/reports/Report.rb +6 -18
- data/lib/taskjuggler/reports/ReportBase.rb +9 -9
- data/lib/taskjuggler/reports/ReportContext.rb +2 -2
- data/lib/taskjuggler/reports/StatusSheetReport.rb +6 -6
- data/lib/taskjuggler/reports/TableReport.rb +24 -15
- data/lib/taskjuggler/reports/TimeSheetReport.rb +5 -5
- data/lib/taskjuggler/reports/TraceReport.rb +251 -0
- data/lib/tj3webd.rb +17 -0
- data/manual/Day_To_Day_Juggling +10 -3
- data/manual/Installation +38 -19
- data/manual/Software +25 -19
- data/manual/Tutorial +119 -110
- data/manual/html/Day_To_Day_Juggling.html +7 -5
- data/manual/html/Getting_Started.html +4 -4
- data/manual/html/How_To_Contribute.html +4 -4
- data/manual/html/Installation.html +19 -11
- data/manual/html/Intro.html +4 -4
- data/manual/html/Reporting_Bugs.html +4 -4
- data/manual/html/Rich_Text_Attributes.html +4 -4
- data/manual/html/Software.html +15 -11
- data/manual/html/TaskJuggler_2x_Migration.html +4 -4
- data/manual/html/TaskJuggler_Internals.html +4 -4
- data/manual/html/The_TaskJuggler_Syntax.html +4 -4
- data/manual/html/Tutorial.html +41 -32
- data/manual/html/account.html +4 -4
- data/manual/html/account.task.html +4 -4
- data/manual/html/accountprefix.html +4 -4
- data/manual/html/accountreport.html +27 -9
- data/manual/html/accountroot.html +5 -5
- data/manual/html/active.html +4 -4
- data/manual/html/adopt.task.html +4 -4
- data/manual/html/aggregate.html +4 -4
- data/manual/html/alert.html +4 -4
- data/manual/html/alertlevels.html +4 -4
- data/manual/html/allocate.html +5 -5
- data/manual/html/alphabet.html +4 -4
- data/manual/html/alternative.html +4 -4
- data/manual/html/author.html +4 -4
- data/manual/html/balance.html +5 -5
- data/manual/html/booking.resource.html +4 -4
- data/manual/html/booking.task.html +4 -4
- data/manual/html/caption.html +5 -5
- data/manual/html/cellcolor.column.html +43 -8
- data/manual/html/celltext.column.html +4 -4
- data/manual/html/center.html +5 -5
- data/manual/html/charge.html +4 -4
- data/manual/html/chargeset.html +4 -4
- data/manual/html/columnid.html +27 -15
- data/manual/html/columns.html +5 -5
- data/manual/html/complete.html +4 -4
- data/manual/html/copyright.html +4 -4
- data/manual/html/credits.html +4 -4
- data/manual/html/css/tjreport.css +14 -5
- data/manual/html/currency.html +4 -4
- data/manual/html/currencyformat.html +5 -5
- data/manual/html/dailymax.html +5 -5
- data/manual/html/dailymin.html +5 -5
- data/manual/html/dailyworkinghours.html +4 -4
- data/manual/html/date.extend.html +4 -4
- data/manual/html/date.html +5 -5
- data/manual/html/definitions.html +4 -4
- data/manual/html/depends.html +4 -4
- data/manual/html/details.html +4 -4
- data/manual/html/disabled.html +4 -4
- data/manual/html/duration.html +4 -4
- data/manual/html/efficiency.html +4 -4
- data/manual/html/effort.html +4 -4
- data/manual/html/email.html +4 -4
- data/manual/html/enabled.html +4 -4
- data/manual/html/end.column.html +4 -4
- data/manual/html/end.html +4 -4
- data/manual/html/end.limit.html +4 -4
- data/manual/html/end.report.html +5 -5
- data/manual/html/end.timesheet.html +4 -4
- data/manual/html/endcredit.html +4 -4
- data/manual/html/epilog.html +5 -5
- data/manual/html/export.html +4 -4
- data/manual/html/extend.html +4 -4
- data/manual/html/fail.html +43 -8
- data/manual/html/fdl.html +4 -4
- data/manual/html/flags.account.html +4 -4
- data/manual/html/flags.html +4 -4
- data/manual/html/flags.journalentry.html +4 -4
- data/manual/html/flags.report.html +5 -5
- data/manual/html/flags.resource.html +4 -4
- data/manual/html/flags.statussheet.html +4 -4
- data/manual/html/flags.task.html +4 -4
- data/manual/html/flags.timesheet.html +4 -4
- data/manual/html/fontcolor.column.html +43 -8
- data/manual/html/footer.html +5 -5
- data/manual/html/formats.html +5 -5
- data/manual/html/functions.html +4 -4
- data/manual/html/gapduration.html +4 -4
- data/manual/html/gaplength.html +4 -4
- data/manual/html/halign.center.html +4 -4
- data/manual/html/halign.column.html +43 -8
- data/manual/html/halign.left.html +4 -4
- data/manual/html/halign.right.html +4 -4
- data/manual/html/hasalert.html +4 -4
- data/manual/html/header.html +5 -5
- data/manual/html/headline.html +7 -7
- data/manual/html/height.html +72 -0
- data/manual/html/hideaccount.html +46 -11
- data/manual/html/hidejournalentry.html +5 -5
- data/manual/html/hidereport.html +43 -8
- data/manual/html/hideresource.html +44 -9
- data/manual/html/hidetask.html +44 -9
- data/manual/html/icalreport.html +4 -4
- data/manual/html/include.macro.html +4 -4
- data/manual/html/include.project.html +4 -4
- data/manual/html/include.properties.html +4 -4
- data/manual/html/index.html +2 -1
- data/manual/html/inherit.extend.html +4 -4
- data/manual/html/interval1.html +4 -4
- data/manual/html/interval2.html +4 -4
- data/manual/html/interval3.html +4 -4
- data/manual/html/interval4.html +4 -4
- data/manual/html/isactive.html +4 -4
- data/manual/html/ischildof.html +4 -4
- data/manual/html/isdependencyof.html +4 -4
- data/manual/html/isdutyof.html +4 -4
- data/manual/html/isfeatureof.html +4 -4
- data/manual/html/isleaf.html +4 -4
- data/manual/html/ismilestone.html +4 -4
- data/manual/html/isongoing.html +4 -4
- data/manual/html/isresource.html +4 -4
- data/manual/html/isresponsibilityof.html +4 -4
- data/manual/html/istask.html +4 -4
- data/manual/html/journalattributes.html +11 -7
- data/manual/html/journalentry.html +4 -4
- data/manual/html/journalmode.html +5 -5
- data/manual/html/leaveallowance.html +5 -5
- data/manual/html/leaves.html +5 -6
- data/manual/html/left.html +5 -5
- data/manual/html/length.html +4 -4
- data/manual/html/limits.allocate.html +4 -4
- data/manual/html/limits.html +4 -4
- data/manual/html/limits.resource.html +4 -4
- data/manual/html/limits.task.html +4 -4
- data/manual/html/listitem.column.html +4 -4
- data/manual/html/listtype.column.html +4 -4
- data/manual/html/loadunit.html +5 -5
- data/manual/html/logicalexpression.html +8 -44
- data/manual/html/logicalflagexpression.html +4 -4
- data/manual/html/macro.html +4 -4
- data/manual/html/managers.html +4 -4
- data/manual/html/mandatory.html +4 -4
- data/manual/html/maxend.html +4 -4
- data/manual/html/maximum.html +5 -5
- data/manual/html/maxstart.html +4 -4
- data/manual/html/milestone.html +4 -4
- data/manual/html/minend.html +4 -4
- data/manual/html/minimum.html +5 -5
- data/manual/html/minstart.html +4 -4
- data/manual/html/monthlymax.html +5 -5
- data/manual/html/monthlymin.html +5 -5
- data/manual/html/navbar.html +10 -4
- data/manual/html/navigator.html +4 -4
- data/manual/html/newtask.html +4 -4
- data/manual/html/nikureport.html +4 -4
- data/manual/html/note.task.html +4 -4
- data/manual/html/now.html +4 -4
- data/manual/html/numberformat.html +5 -5
- data/manual/html/onend.html +4 -4
- data/manual/html/onstart.html +4 -4
- data/manual/html/opennodes.html +5 -5
- data/manual/html/overtime.booking.html +4 -4
- data/manual/html/period.column.html +4 -4
- data/manual/html/period.limit.html +4 -4
- data/manual/html/period.report.html +5 -5
- data/manual/html/period.task.html +4 -4
- data/manual/html/persistent.html +4 -4
- data/manual/html/precedes.html +4 -4
- data/manual/html/priority.html +4 -4
- data/manual/html/priority.timesheet.html +4 -4
- data/manual/html/project.html +4 -4
- data/manual/html/projectid.html +4 -4
- data/manual/html/projectid.task.html +4 -4
- data/manual/html/projectids.html +4 -4
- data/manual/html/projection.html +5 -7
- data/manual/html/prolog.html +5 -5
- data/manual/html/properties.html +11 -5
- data/manual/html/purge.html +5 -5
- data/manual/html/rate.html +4 -4
- data/manual/html/rate.resource.html +4 -4
- data/manual/html/reference.extend.html +4 -4
- data/manual/html/remaining.html +4 -4
- data/manual/html/replace.html +4 -4
- data/manual/html/reportprefix.html +4 -4
- data/manual/html/resource.html +4 -10
- data/manual/html/resourceattributes.html +4 -4
- data/manual/html/resourceprefix.html +4 -4
- data/manual/html/resourcereport.html +28 -10
- data/manual/html/resourceroot.html +5 -5
- data/manual/html/resources.limit.html +4 -4
- data/manual/html/responsible.html +4 -4
- data/manual/html/richtext.extend.html +4 -4
- data/manual/html/right.html +5 -5
- data/manual/html/rollupaccount.html +44 -9
- data/manual/html/rollupresource.html +44 -9
- data/manual/html/rolluptask.html +44 -9
- data/manual/html/scale.column.html +4 -4
- data/manual/html/scenario.html +4 -22
- data/manual/html/scenario.ical.html +4 -4
- data/manual/html/scenarios.export.html +4 -4
- data/manual/html/scenarios.html +5 -5
- data/manual/html/scenariospecific.extend.html +4 -4
- data/manual/html/scheduled.html +4 -4
- data/manual/html/scheduling.html +4 -4
- data/manual/html/select.html +4 -4
- data/manual/html/selfcontained.html +5 -5
- data/manual/html/shift.allocate.html +4 -4
- data/manual/html/shift.html +4 -4
- data/manual/html/shift.resource.html +5 -5
- data/manual/html/shift.task.html +4 -4
- data/manual/html/shift.timesheet.html +4 -4
- data/manual/html/shifts.allocate.html +4 -4
- data/manual/html/shifts.resource.html +4 -4
- data/manual/html/shifts.task.html +4 -4
- data/manual/html/shorttimeformat.html +4 -4
- data/manual/html/sloppy.booking.html +4 -4
- data/manual/html/sloppy.projection.html +5 -5
- data/manual/html/sortaccounts.html +5 -5
- data/manual/html/sortjournalentries.html +5 -5
- data/manual/html/sortresources.html +5 -5
- data/manual/html/sorttasks.html +5 -5
- data/manual/html/start.column.html +4 -4
- data/manual/html/start.html +4 -4
- data/manual/html/start.limit.html +4 -4
- data/manual/html/start.report.html +5 -5
- data/manual/html/startcredit.html +4 -4
- data/manual/html/status.statussheet.html +4 -4
- data/manual/html/status.timesheet.html +4 -4
- data/manual/html/statussheet.html +4 -4
- data/manual/html/statussheetreport.html +4 -4
- data/manual/html/strict.projection.html +5 -5
- data/manual/html/summary.html +4 -4
- data/manual/html/supplement.html +4 -4
- data/manual/html/supplement.resource.html +4 -10
- data/manual/html/supplement.task.html +4 -28
- data/manual/html/tagfile.html +4 -4
- data/manual/html/task.html +4 -28
- data/manual/html/task.statussheet.html +4 -4
- data/manual/html/task.timesheet.html +4 -4
- data/manual/html/taskattributes.html +4 -4
- data/manual/html/taskprefix.html +4 -4
- data/manual/html/taskreport.html +28 -10
- data/manual/html/taskroot.html +5 -5
- data/manual/html/text.extend.html +4 -4
- data/manual/html/textreport.html +27 -9
- data/manual/html/timeformat.html +5 -5
- data/manual/html/timeoff.nikureport.html +4 -4
- data/manual/html/timesheet.html +4 -4
- data/manual/html/timesheetreport.html +23 -5
- data/manual/html/timezone.export.html +4 -4
- data/manual/html/timezone.html +4 -4
- data/manual/html/timezone.report.html +5 -5
- data/manual/html/timezone.shift.html +4 -4
- data/manual/html/timingresolution.html +4 -4
- data/manual/html/title.column.html +4 -4
- data/manual/html/title.html +5 -5
- data/manual/html/toc.html +207 -179
- data/manual/html/tooltip.column.html +45 -10
- data/manual/html/tracereport.html +405 -0
- data/manual/html/trackingscenario.html +6 -6
- data/manual/html/treelevel.html +4 -4
- data/manual/html/vacation.html +4 -4
- data/manual/html/vacation.resource.html +4 -4
- data/manual/html/vacation.shift.html +4 -4
- data/manual/html/warn.html +43 -8
- data/manual/html/weeklymax.html +5 -5
- data/manual/html/weeklymin.html +5 -5
- data/manual/html/weekstartsmonday.html +4 -4
- data/manual/html/weekstartssunday.html +6 -6
- data/manual/html/width.column.html +6 -6
- data/manual/html/width.html +72 -0
- data/manual/html/work.html +4 -4
- data/manual/html/workinghours.project.html +4 -4
- data/manual/html/workinghours.resource.html +4 -4
- data/manual/html/workinghours.shift.html +4 -4
- data/manual/html/yearlyworkingdays.html +4 -4
- data/spec/Color_spec.rb +60 -0
- data/spec/ProjectBroker_spec.rb +3 -2
- data/spec/StatusSheets_spec.rb +5 -4
- data/spec/TableColumnSorter_spec.rb +78 -0
- data/spec/TimeSheets_spec.rb +6 -2
- data/spec/Tj3Daemon_spec.rb +2 -2
- data/spec/TraceReport_spec.rb +117 -0
- data/taskjuggler.gemspec +1 -1
- data/test/MessageChecker.rb +3 -1
- data/test/ReferenceGenerator.rb +1 -1
- data/test/TestSuite/CSV-Reports/Leave.tjp +1 -1
- data/test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv +3 -0
- data/test/TestSuite/CSV-Reports/refs/taskcounter.csv +9 -0
- data/test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv +19 -16
- data/test/TestSuite/CSV-Reports/refs/weekly.csv +1 -0
- data/test/TestSuite/Export-Reports/refs/LogicalExpression.tjp +14 -2
- data/test/TestSuite/Export-Reports/refs/tutorial.tjp +98 -86
- data/test/TestSuite/Scheduler/Correct/Leaves.tjp +25 -0
- data/test/TestSuite/Syntax/Correct/Leave.tjp +1 -1
- data/test/TestSuite/Syntax/Correct/LogicalExpression.tjp +9 -1
- data/test/TestSuite/Syntax/Correct/TraceReport.tjp +10 -0
- data/test/TestSuite/Syntax/Correct/tutorial.tjp +10 -4
- data/test/test_CSV-Reports.rb +3 -3
- data/test/test_Export-Reports.rb +91 -86
- data/test/test_Journal.rb +15 -12
- data/test/test_Limits.rb +3 -3
- data/test/test_Project.rb +1 -2
- data/test/test_ProjectFileScanner.rb +1 -1
- data/test/test_PropertySet.rb +1 -1
- data/test/test_Query.rb +5 -6
- data/test/test_ReportGenerator.rb +15 -7
- data/test/test_RichText.rb +4 -3
- data/test/test_Scheduler.rb +19 -7
- data/test/test_ShiftAssignments.rb +2 -2
- data/test/test_SimpleQueryExpander.rb +29 -2
- data/test/test_Syntax.rb +14 -5
- metadata +49 -10
- data/lib/taskjuggler/LogFile.rb +0 -73
@@ -23,7 +23,7 @@ class TaskJuggler
|
|
23
23
|
|
24
24
|
def initialize(project, sourceFileInfo = nil)
|
25
25
|
@project = project
|
26
|
-
super(
|
26
|
+
super('query', sourceFileInfo)
|
27
27
|
@blockMode = false
|
28
28
|
end
|
29
29
|
|
@@ -83,8 +83,7 @@ class TaskJuggler
|
|
83
83
|
return nil
|
84
84
|
end
|
85
85
|
expandedArgs[arg] =
|
86
|
-
SimpleQueryExpander.new(value, @query, @
|
87
|
-
@sourceFileInfo).expand
|
86
|
+
SimpleQueryExpander.new(value, @query, @sourceFileInfo).expand
|
88
87
|
end
|
89
88
|
|
90
89
|
if (expandedArgs['property'] || expandedArgs['scopeproperty']) &&
|
@@ -24,7 +24,7 @@ class TaskJuggler
|
|
24
24
|
|
25
25
|
def initialize(project, sourceFileInfo = nil)
|
26
26
|
@project = project
|
27
|
-
super(
|
27
|
+
super('reportlink', sourceFileInfo)
|
28
28
|
@blockFunction = false
|
29
29
|
@query = nil
|
30
30
|
end
|
@@ -54,7 +54,6 @@ class TaskJuggler
|
|
54
54
|
|
55
55
|
if args['attributes']
|
56
56
|
qEx = SimpleQueryExpander.new(args['attributes'], @query,
|
57
|
-
@project.messageHandler,
|
58
57
|
@sourceFileInfo)
|
59
58
|
url += ";attributes=" + URLParameter.encode(qEx.expand)
|
60
59
|
end
|
@@ -30,7 +30,7 @@ class TaskJuggler
|
|
30
30
|
# :func : inside of a block <[ .. ]> or inline <- .. -> function
|
31
31
|
class RichTextScanner < TextParser::Scanner
|
32
32
|
|
33
|
-
def initialize(masterFile,
|
33
|
+
def initialize(masterFile, log)
|
34
34
|
tokenPatterns = [
|
35
35
|
# :bol mode rules
|
36
36
|
[ :LINEBREAK, /\s*\n/, :bol, method('linebreak') ],
|
@@ -49,8 +49,8 @@ class TaskJuggler
|
|
49
49
|
[ :BLOCKFUNCSTART, /<\[/, [ :bop, :bol ], method('functionStart') ],
|
50
50
|
[ ':TITLE*', /={2,5}/, [ :bop, :bol ], method('titleStart') ],
|
51
51
|
[ 'TITLE*END', /={2,5}/, :inline, method('titleEnd') ],
|
52
|
-
[ 'BULLET*', /\*{1,4}
|
53
|
-
[ 'NUMBER*', /\#{1,4}
|
52
|
+
[ 'BULLET*', /\*{1,4}[ \t]+/, [ :bop, :bol ], method('bullet') ],
|
53
|
+
[ 'NUMBER*', /\#{1,4}[ \t]+/, [ :bop, :bol ], method('number') ],
|
54
54
|
[ :HLINE, /----/, [ :bop, :bol ], method('inlineMode') ],
|
55
55
|
|
56
56
|
# :bop, :bol and :inline mode rules
|
@@ -94,7 +94,7 @@ class TaskJuggler
|
|
94
94
|
[ nil, /[ \t\n]+/, :func ],
|
95
95
|
[ :LITERAL, /./, :func ]
|
96
96
|
]
|
97
|
-
super(masterFile,
|
97
|
+
super(masterFile, log, tokenPatterns, :bop)
|
98
98
|
end
|
99
99
|
|
100
100
|
private
|
@@ -130,12 +130,12 @@ class TaskJuggler
|
|
130
130
|
|
131
131
|
def bullet(type, match)
|
132
132
|
self.mode = :inline
|
133
|
-
[ "BULLET#{match.
|
133
|
+
[ "BULLET#{match.count('*')}".intern, match ]
|
134
134
|
end
|
135
135
|
|
136
136
|
def number(type, match)
|
137
137
|
self.mode = :inline
|
138
|
-
[ "NUMBER#{match.
|
138
|
+
[ "NUMBER#{match.count('#')}".intern, match ]
|
139
139
|
end
|
140
140
|
|
141
141
|
def fontColorStart(type, match)
|
@@ -40,8 +40,7 @@ class TaskJuggler
|
|
40
40
|
File.open(fileName) do |file|
|
41
41
|
file.each_line { |line| text += line }
|
42
42
|
end
|
43
|
-
rText = RichText.new(text, @document.functionHandlers
|
44
|
-
MessageHandler.new(true))
|
43
|
+
rText = RichText.new(text, @document.functionHandlers)
|
45
44
|
unless (@richText = rText.generateIntermediateFormat(sectionCounter))
|
46
45
|
exit
|
47
46
|
end
|
@@ -29,8 +29,7 @@ class RuntimeConfig
|
|
29
29
|
if configFile
|
30
30
|
# Read user specified config file.
|
31
31
|
unless loadConfigFile(configFile)
|
32
|
-
|
33
|
-
exit 1
|
32
|
+
error("Config file #{configFile} not found!")
|
34
33
|
end
|
35
34
|
else
|
36
35
|
# Search config files in certain directories.
|
@@ -71,14 +70,13 @@ class RuntimeConfig
|
|
71
70
|
|
72
71
|
def loadConfigFile(fileName)
|
73
72
|
if File.exist?(fileName)
|
73
|
+
debug("Loading #{fileName}")
|
74
74
|
begin
|
75
|
-
debug("Loading #{fileName}")
|
76
75
|
@config = YAML::load(File.read(fileName))
|
77
|
-
debug(@config.to_s)
|
78
76
|
rescue
|
79
|
-
|
80
|
-
exit 1
|
77
|
+
error("Error in config file #{fileName}: #{$!}")
|
81
78
|
end
|
79
|
+
debug(@config.to_s)
|
82
80
|
return true
|
83
81
|
end
|
84
82
|
false
|
@@ -90,5 +88,10 @@ class RuntimeConfig
|
|
90
88
|
puts message
|
91
89
|
end
|
92
90
|
|
91
|
+
def error(message)
|
92
|
+
$stderr.puts message
|
93
|
+
exit 1
|
94
|
+
end
|
95
|
+
|
93
96
|
end
|
94
97
|
|
@@ -25,6 +25,7 @@ class TaskJuggler
|
|
25
25
|
@project = property.project
|
26
26
|
@scenarioIdx = idx
|
27
27
|
@attributes = attributes
|
28
|
+
@messageHandler = MessageHandlerInstance.instance
|
28
29
|
|
29
30
|
# Register the scenario with the Task.
|
30
31
|
@property.data[idx] = self
|
@@ -42,21 +43,21 @@ class TaskJuggler
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def error(id, text, sourceFileInfo = nil, property = nil)
|
45
|
-
@
|
46
|
+
@messageHandler.error(
|
46
47
|
id, text, sourceFileInfo || @property.sourceFileInfo, nil,
|
47
48
|
property || @property,
|
48
49
|
@project.scenario(@scenarioIdx))
|
49
50
|
end
|
50
51
|
|
51
52
|
def warning(id, text, sourceFileInfo = nil, property = nil)
|
52
|
-
@
|
53
|
+
@messageHandler.warning(
|
53
54
|
id, text, sourceFileInfo || @property.sourceFileInfo, nil,
|
54
55
|
property || @property,
|
55
56
|
@project.scenario(@scenarioIdx))
|
56
57
|
end
|
57
58
|
|
58
59
|
def info(id, text, sourceFileInfo = nil, property = nil)
|
59
|
-
@
|
60
|
+
@messageHandler.info(
|
60
61
|
id, text, sourceFileInfo || @property.sourceFileInfo, nil,
|
61
62
|
property || @property,
|
62
63
|
@project.scenario(@scenarioIdx))
|
@@ -25,7 +25,8 @@ class TaskJuggler
|
|
25
25
|
def initialize(appName)
|
26
26
|
@appName = appName
|
27
27
|
# User specific settings
|
28
|
-
@
|
28
|
+
@emailDeliveryMethod = 'smtp'
|
29
|
+
@smtpServer = nil
|
29
30
|
@senderEmail = nil
|
30
31
|
@workingDir = nil
|
31
32
|
@scmCommand = nil
|
@@ -87,7 +88,14 @@ class TaskJuggler
|
|
87
88
|
|
88
89
|
def setWorkingDir
|
89
90
|
# Make sure the user has provided a properly setup config file.
|
90
|
-
|
91
|
+
case @emailDeliveryMethod
|
92
|
+
when 'smtp'
|
93
|
+
error('\'smtpServer\' not configured') unless @smtpServer
|
94
|
+
when 'sendmail'
|
95
|
+
# nothing to check
|
96
|
+
else
|
97
|
+
error("Unknown emailDeliveryMethod #{@emailDeliveryMethod}")
|
98
|
+
end
|
91
99
|
error('\'senderEmail\' not configured') unless @senderEmail
|
92
100
|
|
93
101
|
# Change into the specified working directory
|
@@ -156,11 +164,20 @@ class TaskJuggler
|
|
156
164
|
|
157
165
|
def sendEmail(to, subject, message, attachment = nil, from = nil,
|
158
166
|
inReplyTo = nil)
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
:
|
163
|
-
|
167
|
+
case @emailDeliveryMethod
|
168
|
+
when 'smtp'
|
169
|
+
Mail.defaults do
|
170
|
+
delivery_method :smtp, {
|
171
|
+
:address => @smtpServer,
|
172
|
+
:port => 25
|
173
|
+
}
|
174
|
+
end
|
175
|
+
when 'sendmail'
|
176
|
+
Mail.defaults do
|
177
|
+
delivery_method :sendmail
|
178
|
+
end
|
179
|
+
else
|
180
|
+
raise "Unknown email delivery method: #{@emailDeliveryMethod}"
|
164
181
|
end
|
165
182
|
|
166
183
|
begin
|
@@ -202,7 +219,7 @@ class TaskJuggler
|
|
202
219
|
puts "-- Email Start #{'-' * 60}\n#{mail.to_s}-- Email End #{'-' * 62}"
|
203
220
|
log('INFO', "Show email '#{subject}' to #{to}")
|
204
221
|
else
|
205
|
-
# Actually send out the email
|
222
|
+
# Actually send out the email.
|
206
223
|
begin
|
207
224
|
mail.deliver
|
208
225
|
rescue
|
@@ -12,6 +12,7 @@
|
|
12
12
|
#
|
13
13
|
|
14
14
|
require 'strscan'
|
15
|
+
require 'taskjuggler/MessageHandler'
|
15
16
|
|
16
17
|
class TaskJuggler
|
17
18
|
|
@@ -22,20 +23,29 @@ class TaskJuggler
|
|
22
23
|
# context of the query.
|
23
24
|
class SimpleQueryExpander
|
24
25
|
|
26
|
+
include MessageHandler
|
27
|
+
|
25
28
|
# _inputStr_ is the String with the embedded queries. _query_ is the Query
|
26
29
|
# with that provides the evaluation context. _messageHandle_ is a
|
27
30
|
# MessageHandler that will be used for error reporting. _sourceFileInfo_
|
28
31
|
# is a SourceFileInfo reference used for error reporting.
|
29
|
-
def initialize(inputStr, query,
|
32
|
+
def initialize(inputStr, query, sourceFileInfo)
|
30
33
|
@inputStr = inputStr
|
31
34
|
@query = query.dup
|
32
|
-
@messageHandler = messageHandler
|
33
35
|
@sourceFileInfo = sourceFileInfo
|
34
36
|
end
|
35
37
|
|
36
38
|
def expand
|
37
39
|
# Create a copy of the input string since we will modify it.
|
38
40
|
str = @inputStr.dup
|
41
|
+
|
42
|
+
# The scenario name is not an attribute that can be queried. We need to
|
43
|
+
# handle this separately
|
44
|
+
if @query.scenarioIdx
|
45
|
+
str.gsub!(/<-scenario->/,
|
46
|
+
@query.project.scenario(@query.scenarioIdx).id)
|
47
|
+
end
|
48
|
+
|
39
49
|
# Replace all occurences of <-name->.
|
40
50
|
str.gsub!(/<-[a-zA-Z][_a-zA-Z]*->/) do |match|
|
41
51
|
attribute = match[2..-3]
|
@@ -45,9 +55,8 @@ class TaskJuggler
|
|
45
55
|
@query.to_s
|
46
56
|
else
|
47
57
|
# The query failed. We report an error.
|
48
|
-
|
49
|
-
|
50
|
-
@sourceFileInfo)
|
58
|
+
error('sqe_expand_failed', "Unknown attribute #{attribute}",
|
59
|
+
@sourceFileInfo)
|
51
60
|
end
|
52
61
|
end
|
53
62
|
str
|
@@ -35,8 +35,7 @@ class TaskJuggler
|
|
35
35
|
# SyntaxReference.
|
36
36
|
def initialize(manual = nil, ignoreOld = false)
|
37
37
|
@manual = manual
|
38
|
-
@
|
39
|
-
@parser = ProjectFileParser.new(@messageHandler)
|
38
|
+
@parser = ProjectFileParser.new
|
40
39
|
@parser.updateParserTables
|
41
40
|
|
42
41
|
# This hash stores all documented keywords using the keyword as
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
#
|
4
|
+
# = TableColumnSorter.rb -- The TaskJuggler III Project Management Software
|
5
|
+
#
|
6
|
+
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012
|
7
|
+
# by Chris Schlaeger <chris@linux.com>
|
8
|
+
#
|
9
|
+
# This program is free software; you can redistribute it and/or modify
|
10
|
+
# it under the terms of version 2 of the GNU General Public License as
|
11
|
+
# published by the Free Software Foundation.
|
12
|
+
#
|
13
|
+
|
14
|
+
class TaskJuggler
|
15
|
+
|
16
|
+
# This class can rearrange the columns of a table according to a new order
|
17
|
+
# determined by an Array of table headers. The table is an Array of table
|
18
|
+
# lines. Each line is another Array. The first line of the table is an Array
|
19
|
+
# of the headers of the columns.
|
20
|
+
class TableColumnSorter
|
21
|
+
|
22
|
+
attr_reader :discontinuedColumns
|
23
|
+
|
24
|
+
# Register a new table for rearranging.
|
25
|
+
def initialize(table)
|
26
|
+
@oldTable = table
|
27
|
+
@discontinuedColumns = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Rearrange the registered table. The old table won't be modified. The
|
31
|
+
# method returns a new table (Array of Arrays). _newHeaders_ is an Array
|
32
|
+
# that represents the new column headers. The columns that are not in the
|
33
|
+
# new header will be the last columns of the new table.
|
34
|
+
def sort(newHeaders)
|
35
|
+
# Maps old index to new index.
|
36
|
+
columnIdxMap = {}
|
37
|
+
newHeaderIndex = newHeaders.length
|
38
|
+
oldHeaders = @oldTable[0]
|
39
|
+
discontinuedHeaders = []
|
40
|
+
oldHeaders.length.times do |i|
|
41
|
+
if (ni = newHeaders.index(oldHeaders[i]))
|
42
|
+
# This old column is still in the new header
|
43
|
+
columnIdxMap[i] = ni
|
44
|
+
else
|
45
|
+
# This old column is no longer contained in the new header. We
|
46
|
+
# append it at the end.
|
47
|
+
columnIdxMap[i] = newHeaderIndex
|
48
|
+
discontinuedHeaders << oldHeaders[i]
|
49
|
+
newHeaderIndex += 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# We construct a new table from scratch. All values from the old table
|
54
|
+
# are copied over. columns in the new table that were not contained in
|
55
|
+
# the old table will be filled with nil.
|
56
|
+
newTable = []
|
57
|
+
@oldTable.length.times do |lineIdx|
|
58
|
+
oldLine = @oldTable[lineIdx]
|
59
|
+
if lineIdx == 0
|
60
|
+
# Insert the new headers. The discontinued ones will be added below.
|
61
|
+
newTable[0] = newHeaders
|
62
|
+
else
|
63
|
+
# Add a line of nils to the new table.
|
64
|
+
newTable[lineIdx] = Array.new(newHeaderIndex, nil)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Copy the old column to the new position.
|
68
|
+
columnIdxMap.each do |oldColIdx, newColIdx|
|
69
|
+
newTable[lineIdx][newColIdx] = oldLine[oldColIdx]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Now we need to add the new column headers that were not in the old
|
74
|
+
# headers.
|
75
|
+
#newTable[0] += discontinuedHeaders
|
76
|
+
|
77
|
+
@discontinuedColumns = discontinuedHeaders.length
|
78
|
+
newTable
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
data/lib/taskjuggler/Task.rb
CHANGED
@@ -96,11 +96,9 @@ class TaskJuggler
|
|
96
96
|
|
97
97
|
# Now convert the RichText markup String into RichTextIntermediate
|
98
98
|
# format.
|
99
|
-
unless (rti = RichText.new(rText, RTFHandlers.create(@project)
|
100
|
-
|
101
|
-
|
102
|
-
@project.messageHandler.warning(
|
103
|
-
'task_journal_text', 'Syntax error in journal text')
|
99
|
+
unless (rti = RichText.new(rText, RTFHandlers.create(@project)).
|
100
|
+
generateIntermediateFormat)
|
101
|
+
warning('task_journal_text', 'Syntax error in journal text')
|
104
102
|
return nil
|
105
103
|
end
|
106
104
|
|
@@ -23,17 +23,19 @@ require 'taskjuggler/Log'
|
|
23
23
|
# files, schedule them and generate the reports.
|
24
24
|
class TaskJuggler
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
include MessageHandler
|
27
|
+
|
28
|
+
attr_reader :project
|
29
|
+
attr_accessor :maxCpuCores, :warnTsDeltas, :generateTraces
|
28
30
|
|
29
31
|
# Create a new TaskJuggler object. _console_ is a boolean that determines
|
30
32
|
# whether or not messsages can be written to $stderr.
|
31
|
-
def initialize
|
33
|
+
def initialize
|
32
34
|
@project = nil
|
33
35
|
@parser = nil
|
34
|
-
@messageHandler = MessageHandler.new(console)
|
35
36
|
@maxCpuCores = 1
|
36
37
|
@warnTsDeltas = false
|
38
|
+
@generateTraces = false
|
37
39
|
TjTime.setTimeZone('UTC')
|
38
40
|
end
|
39
41
|
|
@@ -41,18 +43,21 @@ class TaskJuggler
|
|
41
43
|
# construct a Project object. In case of success true is returned.
|
42
44
|
# Otherwise false.
|
43
45
|
def parse(files, keepParser = false)
|
46
|
+
# Reset the MessageHandler to clear all errors.
|
47
|
+
MessageHandlerInstance.instance.clear
|
48
|
+
|
44
49
|
Log.enter('parser', 'Parsing files ...')
|
45
50
|
master = true
|
46
51
|
@project = nil
|
47
52
|
|
48
53
|
#RubyProf.start
|
49
|
-
@parser = ProjectFileParser.new
|
54
|
+
@parser = ProjectFileParser.new
|
50
55
|
files.each do |file|
|
51
56
|
begin
|
52
57
|
@parser.open(file, master)
|
53
58
|
rescue TjException => msg
|
54
59
|
if msg.message && !msg.message.empty?
|
55
|
-
|
60
|
+
critical('parse', msg.message)
|
56
61
|
end
|
57
62
|
Log.exit('parser')
|
58
63
|
return false
|
@@ -91,7 +96,7 @@ class TaskJuggler
|
|
91
96
|
@parser = nil unless keepParser
|
92
97
|
|
93
98
|
Log.exit('parser')
|
94
|
-
|
99
|
+
MessageHandlerInstance.instance.errors == 0
|
95
100
|
end
|
96
101
|
|
97
102
|
# Parse a file and add the content to the existing project. _fileName_ is
|
@@ -102,7 +107,7 @@ class TaskJuggler
|
|
102
107
|
@project.inputFiles << fileName
|
103
108
|
rescue TjException => msg
|
104
109
|
if msg.message && !msg.message.empty?
|
105
|
-
|
110
|
+
critical('parse_file', msg.message)
|
106
111
|
end
|
107
112
|
return nil
|
108
113
|
end
|
@@ -125,11 +130,13 @@ class TaskJuggler
|
|
125
130
|
res = @project.schedule
|
126
131
|
rescue TjException => msg
|
127
132
|
if msg.message && !msg.message.empty?
|
128
|
-
|
133
|
+
critical('scheduling_error', msg.message)
|
129
134
|
end
|
130
135
|
return false
|
131
136
|
end
|
132
137
|
|
138
|
+
@project.enableTraceReports(@generateTraces)
|
139
|
+
|
133
140
|
Log.exit('scheduler')
|
134
141
|
res
|
135
142
|
end
|
@@ -157,7 +164,7 @@ class TaskJuggler
|
|
157
164
|
#end
|
158
165
|
rescue TjException => msg
|
159
166
|
if msg.message && !msg.message.empty?
|
160
|
-
|
167
|
+
critical('generate_reports', msg.message)
|
161
168
|
end
|
162
169
|
return false
|
163
170
|
end
|
@@ -179,7 +186,7 @@ class TaskJuggler
|
|
179
186
|
@project.generateReport(reportId, regExpMode, formats, dynamicAttributes)
|
180
187
|
rescue TjException => msg
|
181
188
|
if msg.message && !msg.message.empty?
|
182
|
-
|
189
|
+
critical('generate_report', msg.message)
|
183
190
|
end
|
184
191
|
Log.exit('generateReport')
|
185
192
|
return false
|
@@ -196,7 +203,7 @@ class TaskJuggler
|
|
196
203
|
@project.listReports(reportId, regExpMode)
|
197
204
|
rescue TjException => msg
|
198
205
|
if msg.message && !msg.message.empty?
|
199
|
-
|
206
|
+
critical('list_reports', msg.message)
|
200
207
|
end
|
201
208
|
Log.exit('listReports')
|
202
209
|
return false
|
@@ -210,9 +217,9 @@ class TaskJuggler
|
|
210
217
|
begin
|
211
218
|
# Check the master file is really a file and not stdin.
|
212
219
|
unless (masterFile = @project.inputFiles.masterFile)
|
213
|
-
|
214
|
-
|
215
|
-
|
220
|
+
error('cannot_freeze_stdin',
|
221
|
+
"The project freeze feature only when the " +
|
222
|
+
"master file is a real file, not standard input.")
|
216
223
|
end
|
217
224
|
|
218
225
|
# Derive the file names for the header and bookings file from the base
|
@@ -223,10 +230,10 @@ class TaskJuggler
|
|
223
230
|
bookingsFile = bookingsFileBase + '.tji'
|
224
231
|
|
225
232
|
if !File.exists?(bookingsFile) || !File.exists?(headerFile)
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
233
|
+
info('incl_freeze_files',
|
234
|
+
"Please make sure you include #{headerFile} at " +
|
235
|
+
"the end of the project header and " +
|
236
|
+
"#{bookingsFile} at the end of #{masterFile}.")
|
230
237
|
end
|
231
238
|
|
232
239
|
# Generate the project header include file with the new 'now' date.
|
@@ -235,9 +242,9 @@ class TaskJuggler
|
|
235
242
|
f.puts("now #{freezeDate}")
|
236
243
|
end
|
237
244
|
rescue
|
238
|
-
|
239
|
-
|
240
|
-
|
245
|
+
error('write_header_incl',
|
246
|
+
"Cannote write header include file " +
|
247
|
+
"#{headerFile}")
|
241
248
|
end
|
242
249
|
|
243
250
|
# Generate an export report for the bookings.
|
@@ -248,8 +255,7 @@ class TaskJuggler
|
|
248
255
|
|
249
256
|
# We export only the tracking scenario.
|
250
257
|
unless (trackingScenarioIdx = @project['trackingScenarioIdx'])
|
251
|
-
|
252
|
-
'No trackingscenario defined')
|
258
|
+
error('no_tracking_scen', 'No trackingscenario defined')
|
253
259
|
end
|
254
260
|
report.set('scenarios', [ trackingScenarioIdx ])
|
255
261
|
|
@@ -304,8 +310,9 @@ class TaskJuggler
|
|
304
310
|
'start' => ts.interval.start,
|
305
311
|
'end' => ts.interval.end,
|
306
312
|
'journalMode' => :journal,
|
307
|
-
'journalAttributes' => %w( alert property
|
308
|
-
|
313
|
+
'journalAttributes' => %w( alert property propertyid
|
314
|
+
headline flags timesheet
|
315
|
+
summary details ),
|
309
316
|
'sortJournalEntries' => [ [ :seqno, 1 ] ],
|
310
317
|
'timeFormat' => '%Y-%m-%d',
|
311
318
|
'selfContained' => true }
|
@@ -313,7 +320,7 @@ class TaskJuggler
|
|
313
320
|
puts ts.resource.query_journal(query).richText.inputText
|
314
321
|
rescue TjException => msg
|
315
322
|
if msg.message && !msg.message.empty?
|
316
|
-
|
323
|
+
critical('check_time_sheet', msg.message)
|
317
324
|
end
|
318
325
|
Log.exit('checkTimeSheet')
|
319
326
|
return false
|
@@ -349,7 +356,7 @@ class TaskJuggler
|
|
349
356
|
puts ss[0].query_dashboard(query).richText.inputText
|
350
357
|
rescue TjException => msg
|
351
358
|
if msg.message && !msg.message.empty?
|
352
|
-
|
359
|
+
critical('check_status_sheet', msg.message)
|
353
360
|
end
|
354
361
|
Log.exit('checkStatusSheet')
|
355
362
|
return false
|
@@ -372,7 +379,7 @@ class TaskJuggler
|
|
372
379
|
|
373
380
|
# Return the number of errors that had been reported during processing.
|
374
381
|
def errors
|
375
|
-
|
382
|
+
MessageHandlerInstance.instance.errors
|
376
383
|
end
|
377
384
|
|
378
385
|
end
|