taskjuggler 3.7.1 → 3.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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))
|