3 using System
.Collections
;
4 using System
.Text
.RegularExpressions
;
8 namespace Wv
.Schedulator
10 public class StringSource
: Source
12 protected string[] lines
;
15 public StringSource(Schedulator s
, string name
, string[] lines
)
18 log
= new WvLog(name
);
21 // process "import" lines right away, to create additional
23 foreach (string line
in lines
)
25 string[] args
= word_split(line
.Trim());
26 string cmd
= wv
.shift(ref args
).ToLower();
28 if (cmd
== "import" || cmd
== "plugin")
30 log
.print("Creating plugin from line: '{0}'\n", line
);
32 err(0, "Not enough parameters to '{0}'", cmd
);
35 SourceRegistry reg
= new SourceRegistry();
36 reg
.create(s
, dequote(args
[0]), dequote(args
[1]));
42 public static Source
create(Schedulator s
, string name
,
43 string prefix
, string suffix
)
45 return new StringSource(s
, name
, suffix
.Split('\n'));
48 static string[] get_file(string filename
)
52 StreamReader r
= File
.OpenText(filename
);
53 return r
.ReadToEnd().Split('\n');
59 return "".Split('\n');
62 public static Source
create_from_file(Schedulator s
, string name
,
63 string prefix
, string suffix
)
65 return new StringSource(s
, name
, get_file(suffix
));
68 public static string[] word_split(string s
)
70 string bra
= "\"'([{";
71 string ket
= "\"')]}";
73 ArrayList list
= new ArrayList();
74 Stack nest
= new Stack();
75 string buf
= ""; // even if it's empty, always add the first word
76 bool last_was_white
= true;
79 int is_bra
= bra
.IndexOf(c
);
80 int is_ket
= ket
.IndexOf(c
);
82 if (c
== '\'' && !last_was_white
)
87 if (c
== ' ' || c
== '\t')
100 if (is_ket
>= 0 && (char)nest
.Peek() == c
)
103 last_was_white
= true;
109 nest
.Push(ket
[is_bra
]);
111 last_was_white
= (c
== ' ' || c
== '\t');
114 // even if it's empty, always add the last word
115 list
.Add(buf
==null ? "" : buf
);
117 string[] result
= new string[list
.Count
];
119 foreach (string ss
in list
)
124 void err(int lineno
, string fmt
, params object[] args
)
126 log
.print(lineno
.ToString() + ": " + fmt
, args
);
129 static string dequote(string s
, string bra
, string ket
)
131 int idx_bra
= bra
.IndexOf(s
[0]);
132 int idx_ket
= ket
.IndexOf(s
[s
.Length
-1]);
134 if (idx_bra
!= -1 && idx_bra
== idx_ket
)
135 return s
.Substring(1, s
.Length
-2);
140 static string dequote(string s
, string bra
)
142 return dequote(s
, bra
, bra
);
145 static string dequote(string s
)
147 return dequote(s
, "\"'");
150 public static TimeSpan
parse_estimate(int lineno
, string s
)
152 Regex re
= new Regex(@"([0-9]*(\.[0-9]*)?)\s*([a-zA-Z]*)");
153 Match match
= re
.Match(s
);
154 GroupCollection grp
= match
.Groups
;
155 if (!match
.Success
|| grp
.Count
< 4)
157 //err(lineno, "Can't parse estimate '{0}'", s);
158 return TimeSpan
.FromHours(0);
161 double num
= wv
.atod(grp
[1].ToString());
162 // wv.printerr("parsing time '{0}' as '{1}'\n", grp[1].ToString(), num);
163 string units
= grp
[3].ToString().ToLower() + " ";
167 return TimeSpan
.FromHours(num
*8);
170 return TimeSpan
.FromHours(num
);
172 return TimeSpan
.FromMinutes(num
);
174 return TimeSpan
.FromSeconds(num
); // wow, you're fast!
177 //err(lineno, "Unknown unit '{0}' in '{1}'", units, s);
178 return TimeSpan
.FromHours(0);
183 public Source source
;
184 public bool external
;
185 public Task oldtask
, task
;
187 public DelayedTask dtparent
;
192 public FixFor fixfor
;
195 public DateTime donedate
, startdate
, duedate
;
197 public TimeSpan currest
= TimeSpan
.MaxValue
;
198 public TimeSpan elapsed
= TimeSpan
.MaxValue
;
200 public DateSlider habits
;
202 public string make_id()
204 // this needs to be "as unique as possible" given that
205 // all these tasks come from a text file, and yet not
206 // change when the text file changes. It's impossible to
207 // be perfect here, so we do what we can.
208 if (dtparent
!= null)
209 return dtparent
.make_id() + ":" + name
;
214 public void apply_from(Task t
)
219 priority
= t
.priority
;
221 if (wv
.isempty(donedate
))
222 donedate
= t
.donedate
;
223 if (wv
.isempty(startdate
))
224 startdate
= t
.startdate
;
225 if (wv
.isempty(duedate
))
227 if (currest
== TimeSpan
.MaxValue
)
229 if (elapsed
== TimeSpan
.MaxValue
)
237 public void apply_to(Task t
)
240 if (dtparent
!= null && dtparent
.task
!= null)
241 parent
= dtparent
.task
;
245 throw new ArgumentException("task is its own parent!");
250 t
.priority
= priority
;
252 if (!wv
.isempty(donedate
))
253 t
.donedate
= donedate
;
254 else if (wv
.isempty(t
.donedate
))
255 t
.donedate
= source
.s
.align
;
256 if (!wv
.isempty(startdate
))
257 t
.startdate
= startdate
;
258 if (!wv
.isempty(duedate
))
260 if (currest
!= TimeSpan
.MaxValue
&& wv
.isempty(t
.currest
))
262 if (elapsed
!= TimeSpan
.MaxValue
&& wv
.isempty(t
.elapsed
))
270 public class IdCompare
: IComparer
272 public int Compare(object _x
, object _y
)
274 DelayedTask x
= (DelayedTask
)_x
;
275 DelayedTask y
= (DelayedTask
)_y
;
277 return x
.lineno
.CompareTo(y
.lineno
);
281 // this is a simple ordering that makes parents come out
282 // before their children, so that we can be sure to fill in
283 // the parent's values before we fill in its children's values
284 // by copying the parent.
285 public class TopologicalCompare
: IComparer
287 public int Compare(object _x
, object _y
)
289 DelayedTask x
= (DelayedTask
)_x
;
290 DelayedTask y
= (DelayedTask
)_y
;
292 if (x
.dtparent
== y
.dtparent
)
293 return 0; // effectively equal, even if null
294 else if (x
.dtparent
== null)
295 return Compare(x
, y
.dtparent
);
296 else if (y
.dtparent
== null)
297 return Compare(x
.dtparent
, y
);
298 else if (x
.dtparent
!= y
.dtparent
)
299 return Compare(x
.dtparent
, y
.dtparent
);
301 return x
.lineno
.CompareTo(y
.lineno
);
308 public DelayedTask parent
;
311 public DtIndex(DelayedTask parent
, string name
)
313 this.parent
= parent
;
317 public override bool Equals(object o
)
319 DtIndex y
= o
as DtIndex
;
323 return parent
== y
.parent
&& name
== y
.name
;
326 public override int GetHashCode()
328 return name
.GetHashCode();
332 ArrayList dtasks
= new ArrayList();
333 Hashtable created_tasks
= new Hashtable();
335 Task
find_task(string title
)
337 foreach (Task t
in s
.tasks
)
338 if (t
.name
== title
|| t
.moniker
== title
)
343 void parse_task(int level
, string[] args
,
344 ArrayList parents
, int lineno
,
345 ref FixFor last_fixfor
, DateSlider habits
,
348 if (level
> parents
.Count
)
349 err(lineno
, "Level-{0} bug with only {1} parents!",
350 level
, parents
.Count
);
351 else if (level
<= parents
.Count
)
352 parents
.RemoveRange(level
, parents
.Count
- level
);
355 DateTime startdate
= DateTime
.MinValue
;
356 DateTime duedate
= DateTime
.MinValue
;
357 DateTime donedate
= DateTime
.MinValue
;
358 TimeSpan currest
= TimeSpan
.MaxValue
;
359 TimeSpan elapsed
= TimeSpan
.MaxValue
;
360 for (int i
= 0; i
< args
.Length
; i
++)
363 if (a
[0] == '[' && a
[a
.Length
-1] == ']')
365 a
= dequote(wv
.shift(ref args
, i
), "[", "]");
366 --i
; // we just ate this array element!
368 string[] words
= word_split(a
);
369 for (int wi
= 0; wi
< words
.Length
; wi
++)
371 string word
= words
[wi
].ToLower();
372 if (word
== "start" && wi
+1 < words
.Length
)
374 startdate
= wv
.date(words
[wi
+1]).Date
;
377 else if ((word
== "end" || word
== "due")
378 && wi
+1 < words
.Length
)
380 duedate
= wv
.date(words
[wi
+1]).Date
;
383 else if ((word
== "done" || word
== "finished")
384 && wi
+1 < words
.Length
)
386 donedate
= wv
.date(words
[wi
+1]).Date
;
389 else if (word
[0] == 'p')
390 pri
= Int32
.Parse(word
.Substring(1));
391 else if (Char
.IsDigit(word
[0]) || word
[0] == '.')
393 if (currest
== TimeSpan
.MaxValue
)
394 currest
= parse_estimate(lineno
, word
);
395 else if (elapsed
== TimeSpan
.MaxValue
)
396 elapsed
= parse_estimate(lineno
, word
);
398 err(lineno
, "Extra time '{0}'", word
);
401 err(lineno
, "Unknown flag '{0}' in '{1}'\n",
407 DelayedTask parent
= (parents
.Count
> 0
408 ? (DelayedTask
)parents
[parents
.Count
-1]
410 string title
= String
.Join(" ", args
);
412 DelayedTask d
= new DelayedTask();
416 d
.external
= external
;
419 d
.fixfor
= last_fixfor
;
421 d
.donedate
= donedate
;
422 d
.startdate
= startdate
;
426 if (elapsed
!= TimeSpan
.MaxValue
&& currest
== elapsed
)
430 //log.print("Parent of '{0}' is '{1}'\n",
431 // d.name, d.dtparent==null ? "(none)" : d.dtparent.name);
437 public override void make_basic()
440 ArrayList parents
= new ArrayList();
441 string projname
= "";
442 FixFor last_fixfor
= null;
443 DateSlider habits
= null;
445 foreach (string str
in lines
)
447 string[] args
= word_split(str
.Trim());
448 string cmd
= wv
.shift(ref args
).ToLower();
452 if (cmd
== "" || cmd
[0] == '#') // blank or comment
459 // already handled earlier
467 string fixforname
= dequote(args
[0]);
468 int idx
= fixforname
.IndexOf(':');
471 projname
= fixforname
.Substring(0, idx
);
472 fixforname
= fixforname
.Substring(idx
+1);
475 s
.fixfors
.Add(s
.projects
.Add(projname
),
479 err(lineno
, "'{0}' requires an argument", cmd
);
481 last_fixfor
.add_release(wv
.date(args
[1]));
483 log
.print("New milestone: {0}\n", last_fixfor
.name
);
487 if (last_fixfor
!= null)
488 foreach (string arg
in args
)
489 last_fixfor
.add_release(wv
.date(arg
));
492 "Can't 'bounce' until we have a 'milestone'");
497 habits
= s
.default_habits
;
498 habits
= habits
.new_loadfactor(wv
.atod(args
[0]));
503 habits
= s
.default_habits
;
506 "'Workinghours' needs exactly 7 numbers");
509 double[] hpd
= new double[7];
510 for (int i
= 0; i
< 7; i
++)
511 hpd
[i
] = wv
.atod(args
[i
]);
512 habits
= habits
.new_hours_per_day(hpd
);
517 s
.align
= wv
.date(args
[0]);
521 s
.now
= wv
.date(args
[0]);
534 parse_task(cmd
.Length
-1, args
,
535 parents
, lineno
, ref last_fixfor
, habits
,
540 parse_task(parents
.Count
, args
,
541 parents
, lineno
, ref last_fixfor
, habits
,
546 err(lineno
, "Unknown command '{0}'!", cmd
);
552 s
.default_habits
= habits
;
553 if (last_fixfor
!= null)
555 last_fixfor
.default_habits
= habits
;
556 last_fixfor
.project
.default_habits
= habits
;
562 public override Task
[] make_tasks()
564 // go back to the original order before adding the tasks,
565 // so the list looks the way the user wanted it
566 dtasks
.Sort(new DelayedTask
.IdCompare());
568 foreach (DelayedTask d
in dtasks
)
572 DtIndex idx
= new DtIndex(d
.dtparent
, d
.name
);
574 // prevent creation of duplicate tasks: simply modify
575 // the existing task instead
576 d
.task
= (Task
)created_tasks
[idx
];
579 d
.task
= s
.tasks
.Add(this, d
.make_id(), d
.name
);
580 created_tasks
.Add(idx
, d
.task
);
588 public override void cleanup_tasks()
590 foreach (DelayedTask d
in dtasks
)
594 d
.task
= find_task(d
.name
);
596 d
.apply_from(d
.task
);
599 d
.task
= s
.tasks
.Add(this, d
.make_id(), d
.name
);
602 // let's fill any optional information from parents into
603 // children. First, we'll want to sort so that parents
604 // always come before children, so we're guaranteed to have
605 // finished any given parent before we get to its child.
606 dtasks
.Sort(new DelayedTask
.TopologicalCompare());
608 foreach (DelayedTask d
in dtasks
)
610 DelayedTask p
= d
.dtparent
;
613 if (d
.fixfor
== null)
616 d
.priority
= p
.priority
;
617 if (d
.habits
== null)