taskjuggler 3.7.1 → 3.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +26 -0
- data/Rakefile +4 -1
- data/data/tjp.vim +296 -296
- data/examples/ProjectTemplate/template.tjp +3 -3
- data/examples/Tutorial/tutorial.tjp +3 -3
- data/lib/taskjuggler/AppConfig.rb +1 -1
- data/lib/taskjuggler/BatchProcessor.rb +68 -57
- data/lib/taskjuggler/FileList.rb +1 -1
- data/lib/taskjuggler/MessageHandler.rb +0 -1
- data/lib/taskjuggler/ResourceScenario.rb +29 -1
- data/lib/taskjuggler/RuntimeConfig.rb +1 -1
- data/lib/taskjuggler/SheetHandlerBase.rb +5 -4
- data/lib/taskjuggler/StatusSheetReceiver.rb +2 -2
- data/lib/taskjuggler/StatusSheetSender.rb +1 -1
- data/lib/taskjuggler/TaskScenario.rb +7 -2
- data/lib/taskjuggler/TextParser/Scanner.rb +1 -1
- data/lib/taskjuggler/TimeSheetReceiver.rb +2 -2
- data/lib/taskjuggler/TimeSheetSender.rb +1 -1
- data/lib/taskjuggler/TimeSheets.rb +3 -2
- data/lib/taskjuggler/TjTime.rb +13 -6
- data/lib/taskjuggler/TjpSyntaxRules.rb +9 -3
- data/lib/taskjuggler/XMLDocument.rb +1 -1
- data/lib/taskjuggler/apps/Tj3Daemon.rb +2 -2
- data/lib/taskjuggler/apps/Tj3WebD.rb +1 -1
- data/lib/taskjuggler/daemon/ProjectServer.rb +2 -2
- data/lib/taskjuggler/reports/CSVFile.rb +1 -1
- data/lib/taskjuggler/reports/MspXmlRE.rb +5 -3
- data/lib/taskjuggler/reports/Report.rb +5 -5
- data/lib/taskjuggler/reports/ReportTableCell.rb +7 -5
- data/lib/taskjuggler/reports/TableReport.rb +2 -1
- data/lib/taskjuggler/reports/TraceReport.rb +2 -2
- data/lib/taskjuggler/version.rb +1 -1
- data/manual/html/Day_To_Day_Juggling.html +1 -1
- data/manual/html/Getting_Started.html +1 -1
- data/manual/html/How_To_Contribute.html +1 -1
- data/manual/html/Installation.html +1 -1
- data/manual/html/Intro.html +1 -1
- data/manual/html/List_Attributes.html +1 -1
- data/manual/html/Reporting_Bugs.html +1 -1
- data/manual/html/Rich_Text_Attributes.html +1 -1
- data/manual/html/Software.html +1 -1
- data/manual/html/TaskJuggler_2x_Migration.html +1 -1
- data/manual/html/TaskJuggler_Internals.html +1 -1
- data/manual/html/The_TaskJuggler_Syntax.html +1 -1
- data/manual/html/Tutorial.html +1 -1
- data/manual/html/account.html +1 -1
- data/manual/html/account.task.html +1 -1
- data/manual/html/accountprefix.html +1 -1
- data/manual/html/accountreport.html +2 -2
- data/manual/html/accountroot.html +1 -1
- data/manual/html/active.html +1 -1
- data/manual/html/adopt.task.html +1 -1
- data/manual/html/aggregate.html +1 -1
- data/manual/html/alert.html +1 -1
- data/manual/html/alertlevels.html +1 -1
- data/manual/html/allocate.html +1 -1
- data/manual/html/alphabet.html +1 -1
- data/manual/html/alternative.html +1 -1
- data/manual/html/author.html +1 -1
- data/manual/html/auxdir.html +1 -1
- data/manual/html/auxdir.report.html +1 -1
- data/manual/html/balance.html +1 -1
- data/manual/html/booking.resource.html +1 -1
- data/manual/html/booking.task.html +1 -1
- data/manual/html/caption.html +1 -1
- data/manual/html/cellcolor.column.html +1 -1
- data/manual/html/celltext.column.html +1 -1
- data/manual/html/center.html +1 -1
- data/manual/html/charge.html +1 -1
- data/manual/html/chargeset.html +1 -1
- data/manual/html/columnid.html +5 -2
- data/manual/html/columns.html +1 -1
- data/manual/html/complete.html +1 -1
- data/manual/html/copyright.html +1 -1
- data/manual/html/credits.html +1 -1
- data/manual/html/currency.html +1 -1
- data/manual/html/currencyformat.html +1 -1
- data/manual/html/dailymax.html +1 -1
- data/manual/html/dailymin.html +1 -1
- data/manual/html/dailyworkinghours.html +1 -1
- data/manual/html/date.extend.html +1 -1
- data/manual/html/date.html +1 -1
- data/manual/html/definitions.html +1 -1
- data/manual/html/depends.html +1 -1
- data/manual/html/details.html +1 -1
- data/manual/html/disabled.html +1 -1
- data/manual/html/duration.html +1 -1
- data/manual/html/efficiency.html +1 -1
- data/manual/html/effort.html +1 -1
- data/manual/html/effortdone.html +1 -1
- data/manual/html/effortleft.html +1 -1
- data/manual/html/email.html +1 -1
- data/manual/html/enabled.html +1 -1
- data/manual/html/end.column.html +1 -1
- data/manual/html/end.html +1 -1
- data/manual/html/end.limit.html +1 -1
- data/manual/html/end.report.html +2 -2
- data/manual/html/end.timesheet.html +1 -1
- data/manual/html/endcredit.html +1 -1
- data/manual/html/epilog.html +1 -1
- data/manual/html/export.html +2 -2
- data/manual/html/extend.html +1 -1
- data/manual/html/fail.html +1 -1
- data/manual/html/fdl.html +1 -1
- data/manual/html/flags.account.html +1 -1
- data/manual/html/flags.html +1 -1
- data/manual/html/flags.journalentry.html +1 -1
- data/manual/html/flags.report.html +1 -1
- data/manual/html/flags.resource.html +1 -1
- data/manual/html/flags.statussheet.html +1 -1
- data/manual/html/flags.task.html +1 -1
- data/manual/html/flags.timesheet.html +1 -1
- data/manual/html/fontcolor.column.html +1 -1
- data/manual/html/footer.html +1 -1
- data/manual/html/formats.export.html +1 -1
- data/manual/html/formats.html +1 -1
- data/manual/html/functions.html +1 -1
- data/manual/html/gapduration.html +1 -1
- data/manual/html/gaplength.html +1 -1
- data/manual/html/halign.center.html +1 -1
- data/manual/html/halign.column.html +1 -1
- data/manual/html/halign.left.html +1 -1
- data/manual/html/halign.right.html +1 -1
- data/manual/html/hasalert.html +1 -1
- data/manual/html/header.html +1 -1
- data/manual/html/headline.html +1 -1
- data/manual/html/height.html +1 -1
- data/manual/html/hideaccount.html +1 -1
- data/manual/html/hidejournalentry.html +1 -1
- data/manual/html/hidereport.html +1 -1
- data/manual/html/hideresource.html +2 -2
- data/manual/html/hidetask.html +2 -2
- data/manual/html/icalreport.html +1 -1
- data/manual/html/include.macro.html +1 -1
- data/manual/html/include.project.html +1 -1
- data/manual/html/include.properties.html +1 -1
- data/manual/html/index.html +1 -1
- data/manual/html/inherit.extend.html +1 -1
- data/manual/html/interval1.html +1 -1
- data/manual/html/interval2.html +1 -1
- data/manual/html/interval3.html +1 -1
- data/manual/html/interval4.html +1 -1
- data/manual/html/isactive.html +1 -1
- data/manual/html/ischildof.html +1 -1
- data/manual/html/isdependencyof.html +1 -1
- data/manual/html/isdutyof.html +1 -1
- data/manual/html/isfeatureof.html +1 -1
- data/manual/html/isleaf.html +1 -1
- data/manual/html/ismilestone.html +1 -1
- data/manual/html/isongoing.html +1 -1
- data/manual/html/isresource.html +1 -1
- data/manual/html/isresponsibilityof.html +1 -1
- data/manual/html/istask.html +1 -1
- data/manual/html/isvalid.html +1 -1
- data/manual/html/journalattributes.html +1 -1
- data/manual/html/journalentry.html +1 -1
- data/manual/html/journalmode.html +1 -1
- data/manual/html/leaveallowance.html +1 -1
- data/manual/html/leaves.html +1 -1
- data/manual/html/left.html +1 -1
- data/manual/html/length.html +1 -1
- data/manual/html/limits.allocate.html +1 -1
- data/manual/html/limits.html +1 -1
- data/manual/html/limits.resource.html +1 -1
- data/manual/html/limits.task.html +1 -1
- data/manual/html/listitem.column.html +1 -1
- data/manual/html/listtype.column.html +1 -1
- data/manual/html/loadunit.html +2 -2
- data/manual/html/logicalexpression.html +1 -1
- data/manual/html/logicalflagexpression.html +1 -1
- data/manual/html/macro.html +1 -1
- data/manual/html/managers.html +1 -1
- data/manual/html/mandatory.html +1 -1
- data/manual/html/markdate.html +1 -1
- data/manual/html/maxend.html +1 -1
- data/manual/html/maximum.html +1 -1
- data/manual/html/maxstart.html +1 -1
- data/manual/html/milestone.html +1 -1
- data/manual/html/minend.html +1 -1
- data/manual/html/minimum.html +1 -1
- data/manual/html/minstart.html +1 -1
- data/manual/html/monthlymax.html +1 -1
- data/manual/html/monthlymin.html +1 -1
- data/manual/html/navbar.html +1 -1
- data/manual/html/navigator.html +1 -1
- data/manual/html/newtask.html +1 -1
- data/manual/html/nikureport.html +1 -1
- data/manual/html/note.task.html +1 -1
- data/manual/html/novevents.html +1 -1
- data/manual/html/now.html +1 -1
- data/manual/html/number.extend.html +1 -1
- data/manual/html/numberformat.html +1 -1
- data/manual/html/onend.html +1 -1
- data/manual/html/onstart.html +1 -1
- data/manual/html/opennodes.html +1 -1
- data/manual/html/outputdir.html +1 -1
- data/manual/html/overtime.booking.html +1 -1
- data/manual/html/period.column.html +1 -1
- data/manual/html/period.limit.html +1 -1
- data/manual/html/period.report.html +2 -2
- data/manual/html/period.task.html +1 -1
- data/manual/html/persistent.html +1 -1
- data/manual/html/precedes.html +1 -1
- data/manual/html/priority.html +1 -1
- data/manual/html/priority.timesheet.html +1 -1
- data/manual/html/project.html +1 -1
- data/manual/html/projectid.html +1 -1
- data/manual/html/projectid.task.html +1 -1
- data/manual/html/projectids.html +1 -1
- data/manual/html/projection.html +1 -1
- data/manual/html/prolog.html +1 -1
- data/manual/html/properties.html +1 -1
- data/manual/html/purge.html +2 -2
- data/manual/html/rate.html +1 -1
- data/manual/html/rate.resource.html +1 -1
- data/manual/html/rawhtmlhead.html +1 -1
- data/manual/html/reference.extend.html +1 -1
- data/manual/html/remaining.html +1 -1
- data/manual/html/replace.html +1 -1
- data/manual/html/reportprefix.html +1 -1
- data/manual/html/resource.html +1 -1
- data/manual/html/resourceattributes.html +1 -1
- data/manual/html/resourceprefix.html +1 -1
- data/manual/html/resourcereport.html +2 -2
- data/manual/html/resourceroot.html +1 -1
- data/manual/html/resources.limit.html +1 -1
- data/manual/html/responsible.html +1 -1
- data/manual/html/richtext.extend.html +1 -1
- data/manual/html/right.html +1 -1
- data/manual/html/rollupaccount.html +1 -1
- data/manual/html/rollupresource.html +2 -2
- data/manual/html/rolluptask.html +2 -2
- data/manual/html/scale.column.html +1 -1
- data/manual/html/scenario.html +1 -1
- data/manual/html/scenario.ical.html +1 -1
- data/manual/html/scenarios.export.html +1 -1
- data/manual/html/scenarios.html +1 -1
- data/manual/html/scenariospecific.extend.html +1 -1
- data/manual/html/scheduled.html +1 -1
- data/manual/html/scheduling.html +1 -1
- data/manual/html/schedulingmode.html +1 -1
- data/manual/html/select.html +1 -1
- data/manual/html/selfcontained.html +1 -1
- data/manual/html/shift.allocate.html +1 -1
- data/manual/html/shift.html +1 -1
- data/manual/html/shift.resource.html +1 -1
- data/manual/html/shift.task.html +1 -1
- data/manual/html/shift.timesheet.html +1 -1
- data/manual/html/shifts.allocate.html +1 -1
- data/manual/html/shifts.resource.html +1 -1
- data/manual/html/shifts.task.html +1 -1
- data/manual/html/shorttimeformat.html +1 -1
- data/manual/html/sloppy.booking.html +1 -1
- data/manual/html/sloppy.projection.html +1 -1
- data/manual/html/sortaccounts.html +1 -1
- data/manual/html/sortjournalentries.html +1 -1
- data/manual/html/sortresources.html +1 -1
- data/manual/html/sorttasks.html +1 -1
- data/manual/html/start.column.html +1 -1
- data/manual/html/start.html +1 -1
- data/manual/html/start.limit.html +1 -1
- data/manual/html/start.report.html +2 -2
- data/manual/html/startcredit.html +1 -1
- data/manual/html/status.statussheet.html +1 -1
- data/manual/html/status.timesheet.html +1 -1
- data/manual/html/statussheet.html +1 -1
- data/manual/html/statussheetreport.html +1 -1
- data/manual/html/strict.projection.html +1 -1
- data/manual/html/summary.html +1 -1
- data/manual/html/supplement.html +1 -1
- data/manual/html/supplement.resource.html +1 -1
- data/manual/html/supplement.task.html +1 -1
- data/manual/html/tagfile.html +1 -1
- data/manual/html/task.html +1 -1
- data/manual/html/task.statussheet.html +1 -1
- data/manual/html/task.timesheet.html +1 -1
- data/manual/html/taskattributes.html +1 -1
- data/manual/html/taskprefix.html +1 -1
- data/manual/html/taskreport.html +2 -2
- data/manual/html/taskroot.export.html +1 -1
- data/manual/html/taskroot.html +1 -1
- data/manual/html/text.extend.html +1 -1
- data/manual/html/textreport.html +2 -2
- data/manual/html/timeformat.html +1 -1
- data/manual/html/timeformat1.html +1 -1
- data/manual/html/timeformat2.html +1 -1
- data/manual/html/timeoff.nikureport.html +1 -1
- data/manual/html/timesheet.html +1 -1
- data/manual/html/timesheetreport.html +1 -1
- data/manual/html/timezone.export.html +1 -1
- data/manual/html/timezone.html +1 -1
- data/manual/html/timezone.report.html +1 -1
- data/manual/html/timezone.shift.html +1 -1
- data/manual/html/timingresolution.html +1 -1
- data/manual/html/title.column.html +1 -1
- data/manual/html/title.html +1 -1
- data/manual/html/toc.html +3 -3
- data/manual/html/tooltip.column.html +1 -1
- data/manual/html/tracereport.html +2 -2
- data/manual/html/trackingscenario.html +1 -1
- data/manual/html/treelevel.html +1 -1
- data/manual/html/vacation.html +1 -1
- data/manual/html/vacation.resource.html +1 -1
- data/manual/html/vacation.shift.html +1 -1
- data/manual/html/warn.html +1 -1
- data/manual/html/weeklymax.html +1 -1
- data/manual/html/weeklymin.html +1 -1
- data/manual/html/weekstartsmonday.html +1 -1
- data/manual/html/weekstartssunday.html +1 -1
- data/manual/html/width.column.html +1 -1
- data/manual/html/width.html +1 -1
- data/manual/html/work.html +1 -1
- data/manual/html/workinghours.project.html +1 -1
- data/manual/html/workinghours.resource.html +1 -1
- data/manual/html/workinghours.shift.html +1 -1
- data/manual/html/yearlyworkingdays.html +1 -1
- data/spec/TraceReport_spec.rb +1 -1
- data/spec/support/DaemonControl.rb +2 -3
- data/taskjuggler.gemspec +0 -3
- data/tasks/gem.rake +0 -1
- data/tasks/help2man.rake +1 -1
- data/tasks/manual.rake +1 -1
- data/test/TestSuite/CSV-Reports/quotes.tjp +20 -0
- data/test/TestSuite/CSV-Reports/refs/celltext.csv +11 -11
- data/test/TestSuite/CSV-Reports/refs/quotes.csv +5 -0
- data/test/TestSuite/CSV-Reports/refs/resourcereport.csv +3 -3
- data/test/TestSuite/CSV-Reports/refs/resourcereport_with_tasks.csv +21 -21
- data/test/TestSuite/CSV-Reports/refs/sortByTree.csv +11 -11
- data/test/TestSuite/CSV-Reports/refs/sortBy_effort.up.csv +11 -11
- data/test/TestSuite/CSV-Reports/refs/sortBy_plan.start.down.csv +11 -11
- data/test/TestSuite/CSV-Reports/refs/taskreport.csv +11 -11
- data/test/TestSuite/CSV-Reports/refs/taskreport_with_resources.csv +29 -29
- data/test/TestSuite/Syntax/Correct/Timezone2.tjp +8 -0
- data/test/TestSuite/Syntax/Correct/template.tjp +3 -3
- data/test/TestSuite/Syntax/Correct/tutorial.tjp +3 -3
- data/test/TestSuite/Syntax/Errors/time_error.tjp +9 -0
- data/test/test_BatchProcessor.rb +6 -3
- metadata +14 -17
- data/man/tj3.1 +0 -130
- data/man/tj3client.1 +0 -145
- data/man/tj3d.1 +0 -93
- data/man/tj3man.1 +0 -76
- data/man/tj3ss_receiver.1 +0 -86
- data/man/tj3ss_sender.1 +0 -100
- data/man/tj3ts_receiver.1 +0 -86
- data/man/tj3ts_sender.1 +0 -92
- data/man/tj3ts_summary.1 +0 -104
- data/man/tj3webd.1 +0 -86
|
@@ -69,16 +69,16 @@ class TaskJuggler
|
|
|
69
69
|
def initialize(maxCpuCores)
|
|
70
70
|
@maxCpuCores = maxCpuCores
|
|
71
71
|
# Jobs submitted by calling queue() are put in the @toRunQueue. The
|
|
72
|
-
#
|
|
72
|
+
# launcher Thread will pick them up and fork them off into another
|
|
73
73
|
# process.
|
|
74
|
-
@toRunQueue =
|
|
74
|
+
@toRunQueue = [ ]
|
|
75
75
|
# A hash that maps the JobInfo objects of running jobs by their PID.
|
|
76
76
|
@runningJobs = { }
|
|
77
77
|
# A list of jobs that wait to complete their writing.
|
|
78
78
|
@spoolingJobs = [ ]
|
|
79
79
|
# The wait() method will then clean the @toDropQueue, executes the post
|
|
80
80
|
# processing block and removes all JobInfo related objects.
|
|
81
|
-
@toDropQueue =
|
|
81
|
+
@toDropQueue = []
|
|
82
82
|
|
|
83
83
|
# A semaphore to guard accesses to @runningJobs, @spoolingJobs and
|
|
84
84
|
# following shared data structures.
|
|
@@ -106,28 +106,34 @@ class TaskJuggler
|
|
|
106
106
|
# to identify the job upon completion. +block+ is a Ruby code block to be
|
|
107
107
|
# executed in a separate process.
|
|
108
108
|
def queue(tag = nil, &block)
|
|
109
|
-
raise 'You cannot call queue() while wait() is running!' if @jobsOut > 0
|
|
110
|
-
|
|
111
|
-
# If this is the first queued job for this run, we have to start the
|
|
112
|
-
# helper threads.
|
|
113
|
-
if @jobsIn == 0
|
|
114
|
-
# The JobInfo objects in the @toRunQueue are processed by the pusher
|
|
115
|
-
# thread. It forkes off processes to execute the code block associated
|
|
116
|
-
# with the JobInfo.
|
|
117
|
-
@pusher = Thread.new { pusher }
|
|
118
|
-
# The popper thread waits for terminated childs and picks up the
|
|
119
|
-
# results.
|
|
120
|
-
@popper = Thread.new { popper }
|
|
121
|
-
# The grabber thread collects $stdout and $stderr data from each child
|
|
122
|
-
# process and stores them in the corresponding JobInfo.
|
|
123
|
-
@grabber = Thread.new { grabber }
|
|
124
|
-
end
|
|
125
109
|
|
|
126
110
|
# Create a new JobInfo object for the job and push it to the @toRunQueue.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
111
|
+
@lock.synchronize do
|
|
112
|
+
raise 'You cannot call queue() while wait() is running!' if @jobsOut > 0
|
|
113
|
+
|
|
114
|
+
# If this is the first queued job for this run, we have to start the
|
|
115
|
+
# helper threads.
|
|
116
|
+
if @jobsIn == 0
|
|
117
|
+
# The JobInfo objects in the @toRunQueue are processed by the
|
|
118
|
+
# launcher thread. It forkes off processes to execute the code
|
|
119
|
+
# block associated with the JobInfo.
|
|
120
|
+
@launcher = Thread.new { launcher }
|
|
121
|
+
# The receiver thread waits for terminated child processes and picks
|
|
122
|
+
# up the results.
|
|
123
|
+
@receiver = Thread.new { receiver }
|
|
124
|
+
# The grabber thread collects $stdout and $stderr data from each
|
|
125
|
+
# child process and stores them in the corresponding JobInfo.
|
|
126
|
+
@grabber = Thread.new { grabber }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# To track a job through the queues, we use a JobInfo object to hold
|
|
130
|
+
# all data associated with a job.
|
|
131
|
+
job = JobInfo.new(@jobsIn, block, tag)
|
|
132
|
+
# Increase job counter
|
|
133
|
+
@jobsIn += 1
|
|
134
|
+
# Push the job to the toRunQueue.
|
|
135
|
+
@toRunQueue.push(job)
|
|
136
|
+
end
|
|
131
137
|
end
|
|
132
138
|
|
|
133
139
|
# Wait for all jobs to complete. The code block will get the JobInfo
|
|
@@ -138,29 +144,27 @@ class TaskJuggler
|
|
|
138
144
|
|
|
139
145
|
# When we have received as many jobs in the @toDropQueue than we have
|
|
140
146
|
# started then we're done.
|
|
141
|
-
while
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
# We have completed jobs.
|
|
146
|
-
while !@toDropQueue.empty?
|
|
147
|
-
# Pop a job from the @toDropQueue and call the block with it.
|
|
148
|
-
job = @toDropQueue.pop
|
|
149
|
-
# Remove the job related entries from the housekeeping tables.
|
|
150
|
-
@lock.synchronize { @jobsOut += 1 }
|
|
151
|
-
|
|
147
|
+
while @lock.synchronize { @jobsOut < @jobsIn }
|
|
148
|
+
job = nil
|
|
149
|
+
@lock.synchronize do
|
|
150
|
+
if !@toDropQueue.empty? && (job = @toDropQueue.pop)
|
|
152
151
|
# Call the post-processing block that was passed to wait() with
|
|
153
152
|
# the JobInfo object as argument.
|
|
153
|
+
@jobsOut += 1
|
|
154
154
|
yield(job)
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
|
+
|
|
158
|
+
unless job
|
|
159
|
+
sleep(@timeout)
|
|
160
|
+
end
|
|
157
161
|
end
|
|
158
162
|
|
|
159
163
|
# Signal threads to stop
|
|
160
164
|
@terminate = true
|
|
161
165
|
# Wait for treads to finish
|
|
162
|
-
@
|
|
163
|
-
@
|
|
166
|
+
@launcher.join
|
|
167
|
+
@receiver.join
|
|
164
168
|
@grabber.join
|
|
165
169
|
|
|
166
170
|
# Reset some variables so we can reuse the object for further job runs.
|
|
@@ -175,25 +179,22 @@ class TaskJuggler
|
|
|
175
179
|
|
|
176
180
|
# This function runs in a separate thread to pop JobInfo items from the
|
|
177
181
|
# @toRunQueue and create child processes for them.
|
|
178
|
-
def
|
|
182
|
+
def launcher
|
|
179
183
|
# Run until the terminate flag is set.
|
|
180
184
|
until @terminate
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
job = nil
|
|
186
|
+
unless @lock.synchronize { @runningJobs.length < @maxCpuCores &&
|
|
187
|
+
(job = @toRunQueue.pop) }
|
|
183
188
|
# We have no jobs in the @toRunQueue or all CPU cores in use already.
|
|
184
189
|
sleep(@timeout)
|
|
185
190
|
else
|
|
186
191
|
@lock.synchronize do
|
|
187
|
-
# Get a new job from the @toRunQueue
|
|
188
|
-
job = @toRunQueue.pop
|
|
189
|
-
|
|
190
192
|
job.openPipes
|
|
191
|
-
# Add the receiver end of the pipe to the
|
|
193
|
+
# Add the receiver end of the pipe to the pipes Arrays.
|
|
192
194
|
@pipes << job.stdoutP
|
|
195
|
+
@pipes << job.stderrP
|
|
193
196
|
# Map the pipe end to this JobInfo object.
|
|
194
197
|
@pipeToJob[job.stdoutP] = job
|
|
195
|
-
# Same for $stderr.
|
|
196
|
-
@pipes << job.stderrP
|
|
197
198
|
@pipeToJob[job.stderrP] = job
|
|
198
199
|
|
|
199
200
|
pid = fork do
|
|
@@ -224,15 +225,20 @@ class TaskJuggler
|
|
|
224
225
|
|
|
225
226
|
# This function runs in a separate thread to wait for completed jobs. It
|
|
226
227
|
# waits for the process completion and stores the result in the
|
|
227
|
-
# corresponding JobInfo object.
|
|
228
|
-
|
|
228
|
+
# corresponding JobInfo object. Aborted jobs are pushed to the
|
|
229
|
+
# @toDropQueue while completed jobs are pushed to the @spoolingJobs queue.
|
|
230
|
+
def receiver
|
|
229
231
|
until @terminate
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
sleep(@timeout)
|
|
233
|
-
else
|
|
232
|
+
pid = retVal = nil
|
|
233
|
+
begin
|
|
234
234
|
# Wait for the next job to complete.
|
|
235
235
|
pid, retVal = Process.wait2
|
|
236
|
+
rescue Errno::ECHILD
|
|
237
|
+
# No running jobs. Wait a bit.
|
|
238
|
+
sleep(@timeout)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
if pid && retVal
|
|
236
242
|
job = nil
|
|
237
243
|
@lock.synchronize do
|
|
238
244
|
# Get the JobInfo object that corresponds to the process ID. The
|
|
@@ -242,7 +248,7 @@ class TaskJuggler
|
|
|
242
248
|
# Remove the job from the @runningJobs Hash.
|
|
243
249
|
@runningJobs.delete(pid)
|
|
244
250
|
# Save the return value.
|
|
245
|
-
job.retVal = retVal.
|
|
251
|
+
job.retVal = retVal.exitstatus
|
|
246
252
|
if retVal.signaled?
|
|
247
253
|
cleanPipes(job)
|
|
248
254
|
# Aborted jobs will probably not send an EOT. So we fastrack
|
|
@@ -269,22 +275,27 @@ class TaskJuggler
|
|
|
269
275
|
res = nil
|
|
270
276
|
begin
|
|
271
277
|
@lock.synchronize do
|
|
272
|
-
if (res = select(@pipes, nil,
|
|
278
|
+
if (res = IO.select(@pipes, nil, nil, @timeout))
|
|
273
279
|
# We have output data from at least one child. Check which pipe
|
|
274
280
|
# actually triggered the select.
|
|
275
281
|
res[0].each do |pipe|
|
|
276
282
|
# Find the corresponding JobInfo object.
|
|
277
283
|
job = @pipeToJob[pipe]
|
|
278
|
-
|
|
284
|
+
|
|
285
|
+
# Store the standard output.
|
|
279
286
|
if pipe == job.stdoutP
|
|
280
287
|
# Look for the EOT character to signal the end of the text.
|
|
281
|
-
if (c = pipe.
|
|
288
|
+
if pipe.closed? || (c = pipe.read_nonblock(1)) == ?\004
|
|
282
289
|
job.stdoutEOT = true
|
|
283
290
|
else
|
|
284
291
|
job.stdout << c
|
|
285
292
|
end
|
|
286
|
-
|
|
287
|
-
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Store the error output.
|
|
296
|
+
if pipe == job.stderrP
|
|
297
|
+
# Look for the EOT character to signal the end of the text.
|
|
298
|
+
if pipe.closed? || (c = pipe.read_nonblock(1)) == ?\004
|
|
288
299
|
job.stderrEOT = true
|
|
289
300
|
else
|
|
290
301
|
job.stderr << c
|
data/lib/taskjuggler/FileList.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# = ResourceScenario.rb -- The TaskJuggler III Project Management Software
|
|
5
5
|
#
|
|
6
|
-
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
|
|
6
|
+
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2020
|
|
7
7
|
# by Chris Schlaeger <cs@taskjuggler.org>
|
|
8
8
|
#
|
|
9
9
|
# This program is free software; you can redistribute it and/or modify
|
|
@@ -322,6 +322,34 @@ class TaskJuggler
|
|
|
322
322
|
end
|
|
323
323
|
end
|
|
324
324
|
|
|
325
|
+
# A list of the tasks that the resource has been allocated to work on in
|
|
326
|
+
# the report time frame.
|
|
327
|
+
def query_duties(query)
|
|
328
|
+
list = []
|
|
329
|
+
iv = TimeInterval.new(query.start, query.end)
|
|
330
|
+
@duties.each do |task|
|
|
331
|
+
if task.hasResourceAllocated?(@scenarioIdx, iv, @property)
|
|
332
|
+
if query.listItem
|
|
333
|
+
rti = RichText.new(query.listItem, RTFHandlers.create(@project)).
|
|
334
|
+
generateIntermediateFormat
|
|
335
|
+
unless rti
|
|
336
|
+
error('bad_resource_ts_query',
|
|
337
|
+
"Syntax error in query statement for task attribute " +
|
|
338
|
+
"'resources'.")
|
|
339
|
+
end
|
|
340
|
+
q = query.dup
|
|
341
|
+
q.property = task
|
|
342
|
+
q.scopeProperty = @property
|
|
343
|
+
rti.setQuery(q)
|
|
344
|
+
list << "<nowiki>#{rti.to_s}</nowiki>"
|
|
345
|
+
else
|
|
346
|
+
list << "<nowiki>#{task.name} (#{task.id})</nowiki>"
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
query.assignList(list)
|
|
351
|
+
end
|
|
352
|
+
|
|
325
353
|
# The effort allocated to the Resource in the specified interval. In case a
|
|
326
354
|
# Task is given as scope property only the effort allocated to this Task is
|
|
327
355
|
# taken into account.
|
|
@@ -166,11 +166,12 @@ class TaskJuggler
|
|
|
166
166
|
inReplyTo = nil)
|
|
167
167
|
case @emailDeliveryMethod
|
|
168
168
|
when 'smtp'
|
|
169
|
-
|
|
170
|
-
delivery_method :smtp, {
|
|
169
|
+
settings_dto = {
|
|
171
170
|
:address => @smtpServer,
|
|
172
|
-
:port => 25
|
|
173
|
-
|
|
171
|
+
:port => 25,
|
|
172
|
+
}
|
|
173
|
+
Mail.defaults do
|
|
174
|
+
delivery_method :smtp, settings_dto
|
|
174
175
|
end
|
|
175
176
|
when 'sendmail'
|
|
176
177
|
Mail.defaults do
|
|
@@ -35,9 +35,9 @@ class TaskJuggler
|
|
|
35
35
|
@logFile = 'statussheets.log'
|
|
36
36
|
|
|
37
37
|
# Regular expression to identify status sheets.
|
|
38
|
-
@sheetHeader = /^[ ]*statussheet\s([a-
|
|
38
|
+
@sheetHeader = /^[ ]*statussheet\s([a-zA-Z_][a-zA-Z0-9_]*)\s[0-9\-:+]*\s-\s([0-9]*-[0-9]*-[0-9]*)/
|
|
39
39
|
# Regular expression to extract the sheet signature (time period).
|
|
40
|
-
@signatureFilter = /^[ ]*statussheet\s[a-
|
|
40
|
+
@signatureFilter = /^[ ]*statussheet\s[a-zA-Z_][a-zA-Z0-9_]*\s([0-9:\-+]*\s-\s[0-9:\-+]*)/
|
|
41
41
|
@emailSubject = "Status report from %s for %s"
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -39,7 +39,7 @@ class TaskJuggler
|
|
|
39
39
|
# The log file
|
|
40
40
|
@logFile = 'statussheets.log'
|
|
41
41
|
|
|
42
|
-
@signatureFilter = /^[ ]*statussheet\s[a-
|
|
42
|
+
@signatureFilter = /^[ ]*statussheet\s[a-zA-Z_][a-zA-Z0-9_]*\s([0-9:\-+]*\s-\s[0-9:\-+]*)/
|
|
43
43
|
@introText = <<'EOT'
|
|
44
44
|
Please find enclosed your weekly status report template. Please fill out the
|
|
45
45
|
form and send it back to the sender of this email. You can either use the
|
|
@@ -1508,6 +1508,7 @@ class TaskJuggler
|
|
|
1508
1508
|
end
|
|
1509
1509
|
q = query.dup
|
|
1510
1510
|
q.property = resource
|
|
1511
|
+
q.scopeProperty = @property
|
|
1511
1512
|
rti.setQuery(q)
|
|
1512
1513
|
list << "<nowiki>#{rti.to_s}</nowiki>"
|
|
1513
1514
|
else
|
|
@@ -2179,8 +2180,12 @@ class TaskJuggler
|
|
|
2179
2180
|
# been set already.
|
|
2180
2181
|
if @scheduled && @effort == 0 && @length == 0 && @duration == 0 &&
|
|
2181
2182
|
!@milestone
|
|
2182
|
-
@start
|
|
2183
|
-
|
|
2183
|
+
unless @start || !firstSlotIdx
|
|
2184
|
+
@start = @project.idxToDate(firstSlotIdx)
|
|
2185
|
+
end
|
|
2186
|
+
unless @end || !lastSlotIdx
|
|
2187
|
+
@end = @project.idxToDate(lastSlotIdx + 1)
|
|
2188
|
+
end
|
|
2184
2189
|
end
|
|
2185
2190
|
end
|
|
2186
2191
|
|
|
@@ -169,7 +169,7 @@ class TaskJuggler::TextParser
|
|
|
169
169
|
|
|
170
170
|
def initialize(fileName, log, textScanner)
|
|
171
171
|
super(log, textScanner)
|
|
172
|
-
@fileName = fileName.dup
|
|
172
|
+
@fileName = fileName.dup
|
|
173
173
|
data = (fileName == '.' ? $stdin : File.new(@fileName, 'r')).read
|
|
174
174
|
begin
|
|
175
175
|
@stream = StringIO.new(data.forceUTF8Encoding)
|
|
@@ -32,9 +32,9 @@ class TaskJuggler
|
|
|
32
32
|
@logFile = 'timesheets.log'
|
|
33
33
|
|
|
34
34
|
# Regular expression to identify time sheets.
|
|
35
|
-
@sheetHeader = /^[ ]*timesheet\s([a-
|
|
35
|
+
@sheetHeader = /^[ ]*timesheet\s([a-zA-Z_][a-zA-Z0-9_]*)\s[0-9\-:+]*\s-\s([0-9]*-[0-9]*-[0-9]*)/
|
|
36
36
|
# Regular expression to extract the sheet signature (time period).
|
|
37
|
-
@signatureFilter = /^[ ]*timesheet\s[a-
|
|
37
|
+
@signatureFilter = /^[ ]*timesheet\s[a-zA-Z_][a-zA-Z0-9_]*\s([0-9:\-+]*\s-\s[0-9:\-+]*)/
|
|
38
38
|
@emailSubject = "Report from %s for %s"
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -38,7 +38,7 @@ class TaskJuggler
|
|
|
38
38
|
# The log file
|
|
39
39
|
@logFile = 'timesheets.log'
|
|
40
40
|
|
|
41
|
-
@signatureFilter = /^[ ]*timesheet\s[a-
|
|
41
|
+
@signatureFilter = /^[ ]*timesheet\s[a-zA-Z_][a-zA-Z0-9_]*\s([0-9:\-+]*\s-\s[0-9:\-+]*)/
|
|
42
42
|
@introText = <<'EOT'
|
|
43
43
|
Please find enclosed your weekly report template. Please fill out
|
|
44
44
|
the form and send it back to the sender of this email. You can either
|
|
@@ -325,9 +325,10 @@ class TaskJuggler
|
|
|
325
325
|
def totalGrossWorkingSlots
|
|
326
326
|
project = @resource.project
|
|
327
327
|
# Calculate the number of weeks in the report
|
|
328
|
-
weeksToReport = (@interval.end - @interval.start) /
|
|
328
|
+
weeksToReport = (@interval.end - @interval.start).to_f /
|
|
329
|
+
(60 * 60 * 24 * 7)
|
|
329
330
|
|
|
330
|
-
daysToSlots(project.weeklyWorkingDays * weeksToReport)
|
|
331
|
+
daysToSlots((project.weeklyWorkingDays * weeksToReport).to_i)
|
|
331
332
|
end
|
|
332
333
|
|
|
333
334
|
# Compute the total number of actual working time slots of the
|
data/lib/taskjuggler/TjTime.rb
CHANGED
|
@@ -501,18 +501,25 @@ class TaskJuggler
|
|
|
501
501
|
end
|
|
502
502
|
|
|
503
503
|
@time = Time.utc(year, month, day, hour, minute, second)
|
|
504
|
-
sign = zone[0] == ?- ? 1 :
|
|
504
|
+
sign = zone[0] == ?- ? -1 : 1
|
|
505
505
|
tzHour = zone[1..2].to_i
|
|
506
|
-
if tzHour < 0 || tzHour > 12
|
|
507
|
-
raise TjException.new, "Time zone adjustment hour out of range " +
|
|
508
|
-
"(0 - 12) but is #{tzHour}"
|
|
509
|
-
end
|
|
510
506
|
tzMinute = zone[3..4].to_i
|
|
511
507
|
if tzMinute < 0 || tzMinute > 59
|
|
512
508
|
raise TjException.new, "Time zone adjustment minute out of range " +
|
|
513
509
|
"(0 - 59) but is #{tzMinute}"
|
|
514
510
|
end
|
|
515
|
-
|
|
511
|
+
|
|
512
|
+
time_offset = sign * (tzHour * 3600 + tzMinute * 60)
|
|
513
|
+
# UTC-1200 is the most westerly time zone but UTC+1400 is the most
|
|
514
|
+
# easterly time zone (Republic of Kiribati).
|
|
515
|
+
if time_offset < -12 * 3600 || time_offset > 14 * 3600
|
|
516
|
+
raise TjException.new, "Time zone adjustment out of range " +
|
|
517
|
+
"(-1200 - +1400} but is #{zone})"
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# The time offset must be substracted from the base time to convert it
|
|
521
|
+
# to UTC.
|
|
522
|
+
@time -= time_offset
|
|
516
523
|
else
|
|
517
524
|
@time = Time.mktime(year, month, day, hour, minute, second)
|
|
518
525
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# = TjpSyntaxRules.rb -- The TaskJuggler III Project Management Software
|
|
5
5
|
#
|
|
6
|
-
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
|
|
6
|
+
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2020
|
|
7
7
|
# by Chris Schlaeger <cs@taskjuggler.org>
|
|
8
8
|
#
|
|
9
9
|
# This program is free software; you can redistribute it and/or modify
|
|
@@ -673,7 +673,7 @@ EOT
|
|
|
673
673
|
def rule_color
|
|
674
674
|
pattern(%w( $STRING ), lambda {
|
|
675
675
|
col = @val[0]
|
|
676
|
-
unless /#[0-9A-Fa-f]{3}/ =~ col || /#[0-9A-Fa-f]{
|
|
676
|
+
unless /#[0-9A-Fa-f]{3}/ =~ col || /#[0-9A-Fa-f]{6}/ =~ col
|
|
677
677
|
error('bad_color',
|
|
678
678
|
"Color values must be specified as '#RGB' or '#RRGGBB' values",
|
|
679
679
|
@sourceFileInfo[0])
|
|
@@ -3932,7 +3932,13 @@ EOT
|
|
|
3932
3932
|
descr('The duration of a task')
|
|
3933
3933
|
|
|
3934
3934
|
singlePattern('_duties')
|
|
3935
|
-
descr('
|
|
3935
|
+
descr(<<'EOT'
|
|
3936
|
+
List of tasks that the resource is allocated to
|
|
3937
|
+
|
|
3938
|
+
The list can be customized by the [[listitem.column|listitem]] and
|
|
3939
|
+
[[listtype.column|listtype]] attribute.
|
|
3940
|
+
EOT
|
|
3941
|
+
)
|
|
3936
3942
|
|
|
3937
3943
|
singlePattern('_efficiency')
|
|
3938
3944
|
descr('Measure for how efficient a resource can perform tasks')
|
|
@@ -52,7 +52,7 @@ class TaskJuggler
|
|
|
52
52
|
|
|
53
53
|
# Write the XMLDocument to the specified file.
|
|
54
54
|
def write(filename)
|
|
55
|
-
f = filename == '.' ? $stdout : File.new(filename
|
|
55
|
+
f = filename == '.' ? $stdout : File.new(filename, 'w')
|
|
56
56
|
@elements.each do |element|
|
|
57
57
|
f.puts element.to_s(0)
|
|
58
58
|
end
|
|
@@ -37,7 +37,7 @@ class TaskJuggler
|
|
|
37
37
|
@port = nil
|
|
38
38
|
@webServer = false
|
|
39
39
|
@webServerPort = 8080
|
|
40
|
-
@webdPidFile = File.join(Dir.getwd, ".tj3webd-#{$$}.pid")
|
|
40
|
+
@webdPidFile = File.join(Dir.getwd, ".tj3webd-#{$$}.pid")
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def processArguments(argv)
|
|
@@ -92,7 +92,7 @@ EOT
|
|
|
92
92
|
# Set some config variables if corresponding data was provided via the
|
|
93
93
|
# command line.
|
|
94
94
|
broker.port = @port if @port
|
|
95
|
-
broker.uriFile = @uriFile
|
|
95
|
+
broker.uriFile = @uriFile
|
|
96
96
|
broker.projectFiles = sortInputFiles(files) unless files.empty?
|
|
97
97
|
broker.daemonize = @daemonize
|
|
98
98
|
# Create log files for standard IO for each child process if the daemon
|
|
@@ -89,7 +89,7 @@ EOT
|
|
|
89
89
|
# Set some config variables if corresponding data was provided via the
|
|
90
90
|
# command line.
|
|
91
91
|
webServer.port = @port if @port
|
|
92
|
-
webServer.uriFile = @uriFile
|
|
92
|
+
webServer.uriFile = @uriFile
|
|
93
93
|
webServer.webServerPort = @webServerPort if @webServerPort
|
|
94
94
|
webServer.daemonize = @daemonize
|
|
95
95
|
webServer.pidFile = @pidFile
|
|
@@ -137,9 +137,9 @@ class TaskJuggler
|
|
|
137
137
|
# directory. The second one is the master project file (.tjp file).
|
|
138
138
|
# Additionally a list of optional .tji files can be provided.
|
|
139
139
|
def loadProject(args)
|
|
140
|
-
dirAndFiles = args.dup
|
|
140
|
+
dirAndFiles = args.dup
|
|
141
141
|
# The first argument is the working directory
|
|
142
|
-
Dir.chdir(args.shift
|
|
142
|
+
Dir.chdir(args.shift)
|
|
143
143
|
|
|
144
144
|
# Save a time stamp of when the project file loading started.
|
|
145
145
|
@modifiedCheck = TjTime.new
|
|
@@ -221,7 +221,7 @@ class TaskJuggler
|
|
|
221
221
|
field.to_s
|
|
222
222
|
else
|
|
223
223
|
# Duplicate quote characters.
|
|
224
|
-
f = field.gsub(
|
|
224
|
+
f = field.gsub(Regexp.new(@quote), "#{@quote * 2}")
|
|
225
225
|
# Enclose the field in quote characters
|
|
226
226
|
@quote + f.to_s + @quote
|
|
227
227
|
end
|
|
@@ -249,11 +249,15 @@ EOT
|
|
|
249
249
|
t << XMLNamedText.new('1', 'Manual')
|
|
250
250
|
t << XMLNamedText.new('0', 'Summary')
|
|
251
251
|
t << XMLNamedText.new('0', 'Estimated')
|
|
252
|
-
t << XMLNamedText.new('
|
|
252
|
+
t << XMLNamedText.new('7', 'DurationFormat')
|
|
253
253
|
if task['milestone', @scenarioIdx]
|
|
254
254
|
t << XMLNamedText.new('1', 'Milestone')
|
|
255
255
|
else
|
|
256
|
+
duration = task['end', @scenarioIdx] - task['start', @scenarioIdx]
|
|
257
|
+
t << XMLNamedText.new(durationToMsp(duration), 'Duration')
|
|
258
|
+
t << XMLNamedText.new(durationToMsp(duration), 'Work')
|
|
256
259
|
t << XMLNamedText.new('0', 'Milestone')
|
|
260
|
+
t << XMLNamedText.new('1', 'EffortDriven')
|
|
257
261
|
t << XMLNamedText.new(percentComplete.to_i.to_s,
|
|
258
262
|
'PercentComplete')
|
|
259
263
|
t << XMLNamedText.new(percentComplete.to_i.to_s,
|
|
@@ -394,8 +398,6 @@ EOT
|
|
|
394
398
|
end
|
|
395
399
|
|
|
396
400
|
def durationToMsp(duration)
|
|
397
|
-
return '' if duration == 0
|
|
398
|
-
|
|
399
401
|
hours = (duration / (60 * 60)).to_i
|
|
400
402
|
minutes = ((duration - (hours * 60 * 60)) / 60).to_i
|
|
401
403
|
seconds = (duration % 60).to_i
|
|
@@ -401,7 +401,7 @@ EOT
|
|
|
401
401
|
# The directory needs to be in the same directory as the HTML report.
|
|
402
402
|
auxDstDir = File.dirname(absoluteFileName(@name)) + '/'
|
|
403
403
|
# Find the data directory that came with the TaskJuggler installation.
|
|
404
|
-
auxSrcDir = AppConfig.dataDirs("data/#{dirName}")[0]
|
|
404
|
+
auxSrcDir = AppConfig.dataDirs("data/#{dirName}")[0]
|
|
405
405
|
# Raise an error if we haven't found the data directory
|
|
406
406
|
if auxSrcDir.nil? || !File.exist?(auxSrcDir)
|
|
407
407
|
dataDirError(dirName, AppConfig.dataSearchDirs("data/#{dirName}"))
|
|
@@ -419,13 +419,13 @@ EOT
|
|
|
419
419
|
end
|
|
420
420
|
|
|
421
421
|
def directoryUpToDate?(auxSrcDir, auxDstDir)
|
|
422
|
-
return false unless File.exist?(auxDstDir
|
|
422
|
+
return false unless File.exist?(auxDstDir)
|
|
423
423
|
|
|
424
424
|
Dir.entries(auxSrcDir).each do |file|
|
|
425
425
|
next if file == '.' || file == '..'
|
|
426
426
|
|
|
427
|
-
srcFile = (auxSrcDir + '/' + file)
|
|
428
|
-
dstFile = (auxDstDir + '/' + file)
|
|
427
|
+
srcFile = (auxSrcDir + '/' + file)
|
|
428
|
+
dstFile = (auxDstDir + '/' + file)
|
|
429
429
|
return false if !File.exist?(dstFile) ||
|
|
430
430
|
File.mtime(srcFile) > File.mtime(dstFile)
|
|
431
431
|
end
|
|
@@ -473,7 +473,7 @@ EOT
|
|
|
473
473
|
end
|
|
474
474
|
|
|
475
475
|
def absoluteFileName(name)
|
|
476
|
-
(
|
|
476
|
+
(absoluteFileName?(name) ? '' : @project.outputDir) + name
|
|
477
477
|
end
|
|
478
478
|
|
|
479
479
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# = ReportTableCell.rb -- The TaskJuggler III Project Management Software
|
|
5
5
|
#
|
|
6
|
-
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
|
|
6
|
+
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2024
|
|
7
7
|
# by Chris Schlaeger <cs@taskjuggler.org>
|
|
8
8
|
#
|
|
9
9
|
# This program is free software; you can redistribute it and/or modify
|
|
@@ -21,7 +21,7 @@ class TaskJuggler
|
|
|
21
21
|
class ReportTableCell
|
|
22
22
|
|
|
23
23
|
attr_reader :line
|
|
24
|
-
attr_accessor :data, :category, :hidden, :alignment, :padding,
|
|
24
|
+
attr_accessor :data, :category, :hidden, :alignment, :padding, :force_string,
|
|
25
25
|
:text, :tooltip, :showTooltipHint,
|
|
26
26
|
:iconTooltip,
|
|
27
27
|
:cellColor, :indent, :icon, :fontSize, :fontColor,
|
|
@@ -60,6 +60,8 @@ class TaskJuggler
|
|
|
60
60
|
@alignment = :center
|
|
61
61
|
# Horizontal padding between frame and cell content
|
|
62
62
|
@padding = 3
|
|
63
|
+
# Don't convert Strings that look like numbers to String
|
|
64
|
+
@force_string = false
|
|
63
65
|
# Whether or not to indent the cell. If not nil, it is an Integer
|
|
64
66
|
# indicating the indentation level.
|
|
65
67
|
@indent = nil
|
|
@@ -181,11 +183,11 @@ class TaskJuggler
|
|
|
181
183
|
|
|
182
184
|
# Try to convert numbers and other types to their native Ruby type if
|
|
183
185
|
# they are supported by CSVFile.
|
|
184
|
-
native = CSVFile.strToNative(cell)
|
|
186
|
+
native = @force_string ? cell : CSVFile.strToNative(cell)
|
|
185
187
|
|
|
186
188
|
# Only for String objects, we add the indentation.
|
|
187
|
-
csv[lineIdx][columnIdx] = (native.is_a?(String)
|
|
188
|
-
|
|
189
|
+
csv[lineIdx][columnIdx] = (native.is_a?(String) && !@force_string ?
|
|
190
|
+
indent + native : native)
|
|
189
191
|
end
|
|
190
192
|
|
|
191
193
|
return columns
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# = TableReport.rb -- The TaskJuggler III Project Management Software
|
|
5
5
|
#
|
|
6
|
-
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
|
|
6
|
+
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2024
|
|
7
7
|
# by Chris Schlaeger <cs@taskjuggler.org>
|
|
8
8
|
#
|
|
9
9
|
# This program is free software; you can redistribute it and/or modify
|
|
@@ -755,6 +755,7 @@ class TaskJuggler
|
|
|
755
755
|
def genCalculatedCell(query, line, columnDef)
|
|
756
756
|
# Create a new cell
|
|
757
757
|
cell = newCell(query, line)
|
|
758
|
+
cell.force_string = true if columnDef.id == 'bsi'
|
|
758
759
|
|
|
759
760
|
unless setScenarioSettings(cell, query.scenarioIdx,
|
|
760
761
|
TableReport.scenarioSpecific?(columnDef.id))
|