Beginnings of a switch to use latest wvdotnet library.
[schedulator.git] / stringsource.cs
blobed35160c77dd90814ab0ec02f9f73a465e754dae
1 using System;
2 using System.IO;
3 using System.Collections;
4 using System.Text.RegularExpressions;
5 using Wv;
6 using Wv.Schedulator;
8 namespace Wv.Schedulator
10 public class StringSource : Source
12 protected string[] lines;
13 WvLog log;
15 public StringSource(Schedulator s, string name, string[] lines)
16 : base(s, name)
18 log = new WvLog(name);
19 this.lines = lines;
21 // process "import" lines right away, to create additional
22 // sources.
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}'", line);
31 if (args.Length < 2)
32 err(0, "Not enough parameters to '{0}'", cmd);
33 else
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)
50 try
52 StreamReader r = File.OpenText(filename);
53 return r.ReadToEnd().Split('\n');
55 catch (IOException)
57 // nothing
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 static public 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 foreach (char c in s)
78 int is_bra = bra.IndexOf(c);
79 int is_ket = ket.IndexOf(c);
81 if (nest.Count == 0)
83 if (c == ' ' || c == '\t')
85 if (buf != null)
86 list.Add(buf);
87 buf = null;
89 else
90 buf += c;
92 else
94 buf += c;
96 if (is_ket >= 0 && (char)nest.Peek() == c)
98 nest.Pop();
99 continue;
103 if (is_bra >= 0)
104 nest.Push(ket[is_bra]);
107 // even if it's empty, always add the last word
108 list.Add(buf==null ? "" : buf);
110 string[] result = new string[list.Count];
111 int ii = 0;
112 foreach (string ss in list)
113 result[ii++] = ss;
114 return result;
117 void err(int lineno, string fmt, params object[] args)
119 log.print(lineno.ToString() + ": " + fmt, args);
122 static string dequote(string s, string bra, string ket)
124 int idx_bra = bra.IndexOf(s[0]);
125 int idx_ket = ket.IndexOf(s[s.Length-1]);
127 if (idx_bra != -1 && idx_bra == idx_ket)
128 return s.Substring(1, s.Length-2);
129 else
130 return s;
133 static string dequote(string s, string bra)
135 return dequote(s, bra, bra);
138 static string dequote(string s)
140 return dequote(s, "\"'");
143 public static TimeSpan parse_estimate(int lineno, string s)
145 Regex re = new Regex(@"([0-9]*(\.[0-9]*)?)\s*([a-zA-Z]*)");
146 Match match = re.Match(s);
147 GroupCollection grp = match.Groups;
148 if (!match.Success || grp.Count < 4)
150 //err(lineno, "Can't parse estimate '{0}'", s);
151 return TimeSpan.FromHours(0);
154 double num = wv.atod(grp[1].ToString());
155 string units = grp[3].ToString().ToLower() + " ";
156 switch (units[0])
158 case 'd':
159 return TimeSpan.FromHours(num*8);
160 case 'h':
161 case ' ':
162 return TimeSpan.FromHours(num);
163 case 'm':
164 return TimeSpan.FromMinutes(num);
165 case 's':
166 return TimeSpan.FromSeconds(num); // wow, you're fast!
169 //err(lineno, "Unknown unit '{0}' in '{1}'", units, s);
170 return TimeSpan.FromHours(0);
173 class DelayedTask
175 public Source source;
176 public bool external;
177 public Task oldtask, task;
178 public Task parent;
179 public DelayedTask dtparent;
181 public int lineno;
182 public string name;
184 public FixFor fixfor;
185 public int priority;
187 public DateTime donedate, startdate, duedate;
189 public TimeSpan currest = TimeSpan.MaxValue;
190 public TimeSpan elapsed = TimeSpan.MaxValue;
191 public bool done;
192 public DateSlider habits;
194 public string make_id()
196 // this needs to be "as unique as possible" given that
197 // all these tasks come from a text file, and yet not
198 // change when the text file changes. It's impossible to
199 // be perfect here, so we do what we can.
200 if (dtparent != null)
201 return dtparent.make_id() + ":" + name;
202 else
203 return name;
206 public void apply_from(Task t)
208 if (fixfor == null)
209 fixfor = t.fixfor;
210 if (priority <= 0)
211 priority = t.priority;
213 if (wv.isempty(donedate))
214 donedate = t.donedate;
215 if (wv.isempty(startdate))
216 startdate = t.startdate;
217 if (wv.isempty(duedate))
218 duedate = t.duedate;
219 if (currest == TimeSpan.MaxValue)
220 currest = t.currest;
221 if (elapsed == TimeSpan.MaxValue)
222 elapsed = t.elapsed;
223 if (!done)
224 done = t.done;
225 if (habits == null)
226 habits = t.habits;
229 public void apply_to(Task t)
231 task = t;
232 if (dtparent != null && dtparent.task != null)
233 parent = dtparent.task;
234 if (parent != null)
235 t.parent = parent;
236 if (t.parent == t)
237 throw new ArgumentException("task is its own parent!");
239 if (fixfor != null)
240 t.fixfor = fixfor;
241 if (priority > 0)
242 t.priority = priority;
244 if (!wv.isempty(donedate))
245 t.donedate = donedate;
246 else if (wv.isempty(t.donedate))
247 t.donedate = source.s.align;
248 if (!wv.isempty(startdate))
249 t.startdate = startdate;
250 if (!wv.isempty(duedate))
251 t.duedate = duedate;
252 if (currest != TimeSpan.MaxValue && wv.isempty(t.currest))
253 t.currest = currest;
254 if (elapsed != TimeSpan.MaxValue && wv.isempty(t.elapsed))
255 t.elapsed = elapsed;
256 if (done)
257 t.done = done;
258 if (habits != null)
259 t.habits = habits;
262 public class IdCompare : IComparer
264 public int Compare(object _x, object _y)
266 DelayedTask x = (DelayedTask)_x;
267 DelayedTask y = (DelayedTask)_y;
269 return x.lineno.CompareTo(y.lineno);
273 // this is a simple ordering that makes parents come out
274 // before their children, so that we can be sure to fill in
275 // the parent's values before we fill in its children's values
276 // by copying the parent.
277 public class TopologicalCompare : IComparer
279 public int Compare(object _x, object _y)
281 DelayedTask x = (DelayedTask)_x;
282 DelayedTask y = (DelayedTask)_y;
284 if (x.dtparent == y.dtparent)
285 return 0; // effectively equal, even if null
286 else if (x.dtparent == null)
287 return Compare(x, y.dtparent);
288 else if (y.dtparent == null)
289 return Compare(x.dtparent, y);
290 else if (x.dtparent != y.dtparent)
291 return Compare(x.dtparent, y.dtparent);
292 else
293 return x.lineno.CompareTo(y.lineno);
298 class DtIndex
300 public DelayedTask parent;
301 public string name;
303 public DtIndex(DelayedTask parent, string name)
305 this.parent = parent;
306 this.name = name;
309 public override bool Equals(object o)
311 DtIndex y = o as DtIndex;
312 if (y == null)
313 return false;
314 else
315 return parent == y.parent && name == y.name;
318 public override int GetHashCode()
320 return name.GetHashCode();
324 ArrayList dtasks = new ArrayList();
325 Hashtable created_tasks = new Hashtable();
327 Task find_task(string title)
329 foreach (Task t in s.tasks)
330 if (t.name == title || t.moniker == title)
331 return t;
332 return null;
335 void parse_task(int level, string[] args,
336 ArrayList parents, int lineno,
337 ref FixFor last_fixfor, DateSlider habits,
338 bool external)
340 if (level > parents.Count)
341 err(lineno, "Level-{0} bug with only {1} parents!",
342 level, parents.Count);
343 else if (level <= parents.Count)
344 parents.RemoveRange(level, parents.Count - level);
346 int pri = -1;
347 DateTime startdate = DateTime.MinValue;
348 DateTime duedate = DateTime.MinValue;
349 DateTime donedate = DateTime.MinValue;
350 TimeSpan currest = TimeSpan.MaxValue;
351 TimeSpan elapsed = TimeSpan.MaxValue;
352 for (int i = 0; i < args.Length; i++)
354 string a = args[i];
355 if (a[0] == '[' && a[a.Length-1] == ']')
357 a = dequote(wv.shift(ref args, i), "[", "]");
358 --i; // we just ate this array element!
360 string[] words = word_split(a);
361 for (int wi = 0; wi < words.Length; wi++)
363 string word = words[wi].ToLower();
364 if (word == "start")
366 startdate = wv.date(words[wi+1]).Date;
367 wi++;
369 else if (word == "end" || word == "due")
371 duedate = wv.date(words[wi+1]).Date;
372 wi++;
374 else if (word == "done" || word == "finished")
376 donedate = wv.date(words[wi+1]).Date;
377 wi++;
379 else if (word[0] == 'p')
380 pri = Int32.Parse(word.Substring(1));
381 else if (Char.IsDigit(word[0]))
383 if (currest == TimeSpan.MaxValue)
384 currest = parse_estimate(lineno, word);
385 else if (elapsed == TimeSpan.MaxValue)
386 elapsed = parse_estimate(lineno, word);
387 else
388 err(lineno, "Extra time '{0}'", word);
390 else
391 err(lineno, "Unknown flag '{0}' in '{1}'",
392 word, a);
397 DelayedTask parent = (parents.Count > 0
398 ? (DelayedTask)parents[parents.Count-1]
399 : null);
400 string title = String.Join(" ", args);
402 DelayedTask d = new DelayedTask();
403 d.source = this;
404 d.lineno = lineno;
405 d.name = title;
406 d.external = external;
408 d.dtparent = parent;
409 d.fixfor = last_fixfor;
410 d.priority = pri;
411 d.donedate = donedate;
412 d.startdate = startdate;
413 d.duedate = duedate;
414 d.currest = currest;
415 d.elapsed = elapsed;
416 if (elapsed != TimeSpan.MaxValue && currest == elapsed)
417 d.done = true;
418 d.habits = habits;
420 //log.print("Parent of '{0}' is '{1}'",
421 // d.name, d.dtparent==null ? "(none)" : d.dtparent.name);
423 parents.Add(d);
424 dtasks.Add(d);
427 public override void make_basic()
429 int lineno = 0;
430 ArrayList parents = new ArrayList();
431 string projname = "";
432 FixFor last_fixfor = null;
433 DateSlider habits = null;
435 foreach (string str in lines)
437 string[] args = word_split(str.Trim());
438 string cmd = wv.shift(ref args).ToLower();
440 ++lineno;
442 if (cmd == "" || cmd[0] == '#') // blank or comment
443 continue;
445 switch (cmd)
447 case "import":
448 case "plugin":
449 // already handled earlier
450 break;
452 case "milestone":
453 case "release":
454 case "version":
455 if (args.Length > 0)
457 string fixforname = dequote(args[0]);
458 int idx = fixforname.IndexOf(':');
459 if (idx >= 0)
461 projname = fixforname.Substring(0, idx);
462 fixforname = fixforname.Substring(idx+1);
464 last_fixfor =
465 s.fixfors.Add(s.projects.Add(projname),
466 fixforname);
468 else
469 err(lineno, "'{0}' requires an argument", cmd);
470 if (args.Length > 1)
471 last_fixfor.add_release(wv.date(args[1]));
473 log.print("New milestone: {0}", last_fixfor.name);
474 break;
476 case "bounce":
477 if (last_fixfor != null)
478 foreach (string arg in args)
479 last_fixfor.add_release(wv.date(arg));
480 else
481 err(lineno,
482 "Can't 'bounce' until we have a 'milestone'");
483 break;
485 case "loadfactor":
486 if (habits == null)
487 habits = s.default_habits;
488 habits = habits.new_loadfactor(wv.atod(args[0]));
489 break;
491 case "workinghours":
492 if (habits == null)
493 habits = s.default_habits;
494 if (args.Length < 7)
495 err(lineno,
496 "'Workinghours' needs exactly 7 numbers");
497 else
499 double[] hpd = new double[7];
500 for (int i = 0; i < 7; i++)
501 hpd[i] = wv.atod(args[i]);
502 habits = habits.new_hours_per_day(hpd);
504 break;
506 case "alignday":
507 s.align = wv.date(args[0]);
508 break;
510 case "today":
511 s.now = wv.date(args[0]);
512 break;
514 case "*":
515 case "**":
516 case "***":
517 case "****":
518 case "*****":
519 case "******":
520 case "*******":
521 case "********":
522 case "*********":
523 case "**********":
524 parse_task(cmd.Length-1, args,
525 parents, lineno, ref last_fixfor, habits,
526 false);
527 break;
529 case "!":
530 parse_task(parents.Count, args,
531 parents, lineno, ref last_fixfor, habits,
532 true);
533 break;
535 default:
536 err(lineno, "Unknown command '{0}'!", cmd);
537 break;
540 if (habits != null)
542 s.default_habits = habits;
543 if (last_fixfor != null)
545 last_fixfor.default_habits = habits;
546 last_fixfor.project.default_habits = habits;
552 public override Task[] make_tasks()
554 // go back to the original order before adding the tasks,
555 // so the list looks the way the user wanted it
556 dtasks.Sort(new DelayedTask.IdCompare());
558 foreach (DelayedTask d in dtasks)
560 if (!d.external)
562 DtIndex idx = new DtIndex(d.dtparent, d.name);
564 // prevent creation of duplicate tasks: simply modify
565 // the existing task instead
566 d.task = (Task)created_tasks[idx];
567 if (d.task == null)
569 d.task = s.tasks.Add(this, d.make_id(), d.name);
570 created_tasks.Add(idx, d.task);
575 return null;
578 public override void cleanup_tasks()
580 foreach (DelayedTask d in dtasks)
582 if (d.external)
584 d.task = find_task(d.name);
585 if (d.task != null)
586 d.apply_from(d.task);
588 if (d.task == null)
589 d.task = s.tasks.Add(this, d.make_id(), d.name);
592 // let's fill any optional information from parents into
593 // children. First, we'll want to sort so that parents
594 // always come before children, so we're guaranteed to have
595 // finished any given parent before we get to its child.
596 dtasks.Sort(new DelayedTask.TopologicalCompare());
598 foreach (DelayedTask d in dtasks)
600 DelayedTask p = d.dtparent;
601 if (p != null)
603 if (d.fixfor == null)
604 d.fixfor = p.fixfor;
605 if (d.priority < 0)
606 d.priority = p.priority;
607 if (d.habits == null)
608 d.habits = p.habits;
610 d.apply_to(d.task);