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}'", 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 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
78 int is_bra
= bra
.IndexOf(c
);
79 int is_ket
= ket
.IndexOf(c
);
83 if (c
== ' ' || c
== '\t')
96 if (is_ket
>= 0 && (char)nest
.Peek() == c
)
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
];
112 foreach (string ss
in list
)
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);
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() + " ";
159 return TimeSpan
.FromHours(num
*8);
162 return TimeSpan
.FromHours(num
);
164 return TimeSpan
.FromMinutes(num
);
166 return TimeSpan
.FromSeconds(num
); // wow, you're fast!
169 //err(lineno, "Unknown unit '{0}' in '{1}'", units, s);
170 return TimeSpan
.FromHours(0);
175 public Source source
;
176 public bool external
;
177 public Task oldtask
, task
;
179 public DelayedTask dtparent
;
184 public FixFor fixfor
;
187 public DateTime donedate
, startdate
, duedate
;
189 public TimeSpan currest
= TimeSpan
.MaxValue
;
190 public TimeSpan elapsed
= TimeSpan
.MaxValue
;
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
;
206 public void apply_from(Task t
)
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
))
219 if (currest
== TimeSpan
.MaxValue
)
221 if (elapsed
== TimeSpan
.MaxValue
)
229 public void apply_to(Task t
)
232 if (dtparent
!= null && dtparent
.task
!= null)
233 parent
= dtparent
.task
;
237 throw new ArgumentException("task is its own parent!");
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
))
252 if (currest
!= TimeSpan
.MaxValue
&& wv
.isempty(t
.currest
))
254 if (elapsed
!= TimeSpan
.MaxValue
&& wv
.isempty(t
.elapsed
))
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
);
293 return x
.lineno
.CompareTo(y
.lineno
);
300 public DelayedTask parent
;
303 public DtIndex(DelayedTask parent
, string name
)
305 this.parent
= parent
;
309 public override bool Equals(object o
)
311 DtIndex y
= o
as DtIndex
;
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
)
335 void parse_task(int level
, string[] args
,
336 ArrayList parents
, int lineno
,
337 ref FixFor last_fixfor
, DateSlider habits
,
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
);
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
++)
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();
366 startdate
= wv
.date(words
[wi
+1]).Date
;
369 else if (word
== "end" || word
== "due")
371 duedate
= wv
.date(words
[wi
+1]).Date
;
374 else if (word
== "done" || word
== "finished")
376 donedate
= wv
.date(words
[wi
+1]).Date
;
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
);
388 err(lineno
, "Extra time '{0}'", word
);
391 err(lineno
, "Unknown flag '{0}' in '{1}'",
397 DelayedTask parent
= (parents
.Count
> 0
398 ? (DelayedTask
)parents
[parents
.Count
-1]
400 string title
= String
.Join(" ", args
);
402 DelayedTask d
= new DelayedTask();
406 d
.external
= external
;
409 d
.fixfor
= last_fixfor
;
411 d
.donedate
= donedate
;
412 d
.startdate
= startdate
;
416 if (elapsed
!= TimeSpan
.MaxValue
&& currest
== elapsed
)
420 //log.print("Parent of '{0}' is '{1}'",
421 // d.name, d.dtparent==null ? "(none)" : d.dtparent.name);
427 public override void make_basic()
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();
442 if (cmd
== "" || cmd
[0] == '#') // blank or comment
449 // already handled earlier
457 string fixforname
= dequote(args
[0]);
458 int idx
= fixforname
.IndexOf(':');
461 projname
= fixforname
.Substring(0, idx
);
462 fixforname
= fixforname
.Substring(idx
+1);
465 s
.fixfors
.Add(s
.projects
.Add(projname
),
469 err(lineno
, "'{0}' requires an argument", cmd
);
471 last_fixfor
.add_release(wv
.date(args
[1]));
473 log
.print("New milestone: {0}", last_fixfor
.name
);
477 if (last_fixfor
!= null)
478 foreach (string arg
in args
)
479 last_fixfor
.add_release(wv
.date(arg
));
482 "Can't 'bounce' until we have a 'milestone'");
487 habits
= s
.default_habits
;
488 habits
= habits
.new_loadfactor(wv
.atod(args
[0]));
493 habits
= s
.default_habits
;
496 "'Workinghours' needs exactly 7 numbers");
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
);
507 s
.align
= wv
.date(args
[0]);
511 s
.now
= wv
.date(args
[0]);
524 parse_task(cmd
.Length
-1, args
,
525 parents
, lineno
, ref last_fixfor
, habits
,
530 parse_task(parents
.Count
, args
,
531 parents
, lineno
, ref last_fixfor
, habits
,
536 err(lineno
, "Unknown command '{0}'!", cmd
);
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
)
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
];
569 d
.task
= s
.tasks
.Add(this, d
.make_id(), d
.name
);
570 created_tasks
.Add(idx
, d
.task
);
578 public override void cleanup_tasks()
580 foreach (DelayedTask d
in dtasks
)
584 d
.task
= find_task(d
.name
);
586 d
.apply_from(d
.task
);
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
;
603 if (d
.fixfor
== null)
606 d
.priority
= p
.priority
;
607 if (d
.habits
== null)