[FIX] Renamed faces and removed print statements in it
bzr revid: rvo@tinyerp.co.in-20100129074800-cjtob9irn861g2ff
This commit is contained in:
parent
404bf5ca1a
commit
557de24693
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
from pcalendar import Calendar, WorkingDate, StartDate, EndDate, Minutes
|
||||||
|
|
||||||
|
from task import Project, BalancedProject, AdjustedProject, Task, \
|
||||||
|
STRICT, SLOPPY, SMART, Multi, YearlyMax, WeeklyMax, MonthlyMax, \
|
||||||
|
DailyMax, VariableLoad
|
||||||
|
|
||||||
|
from resource import Resource
|
||||||
|
|
||||||
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,79 @@
|
||||||
|
#@+leo-ver=4
|
||||||
|
#@+node:@file observer.py
|
||||||
|
#@@language python
|
||||||
|
#@<< Copyright >>
|
||||||
|
#@+node:<< Copyright >>
|
||||||
|
############################################################################
|
||||||
|
# Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
|
||||||
|
# mreithinger@web.de
|
||||||
|
#
|
||||||
|
# This file is part of faces.
|
||||||
|
#
|
||||||
|
# faces is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# faces is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the
|
||||||
|
# Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
#@-node:<< Copyright >>
|
||||||
|
#@nl
|
||||||
|
"""
|
||||||
|
This module contains the base class for all observer objects
|
||||||
|
"""
|
||||||
|
#@<< Imports >>
|
||||||
|
#@+node:<< Imports >>
|
||||||
|
#@-node:<< Imports >>
|
||||||
|
#@nl
|
||||||
|
_is_source_ = True
|
||||||
|
#@+others
|
||||||
|
#@+node:class Observer
|
||||||
|
class Observer(object):
|
||||||
|
"""
|
||||||
|
Base Class for all charts and reports.
|
||||||
|
|
||||||
|
@var visible: Specifies if the observer is visible
|
||||||
|
at the navigation bar inside the gui.
|
||||||
|
|
||||||
|
@var link_view: syncronizes the marked objects in all views.
|
||||||
|
|
||||||
|
"""
|
||||||
|
#@ << declarations >>
|
||||||
|
#@+node:<< declarations >>
|
||||||
|
__type_name__ = None
|
||||||
|
__type_image__ = None
|
||||||
|
visible = True
|
||||||
|
link_view = True
|
||||||
|
|
||||||
|
__attrib_completions__ = { "visible" : 'visible = False',
|
||||||
|
"link_view" : "link_view = False" }
|
||||||
|
|
||||||
|
|
||||||
|
#@-node:<< declarations >>
|
||||||
|
#@nl
|
||||||
|
|
||||||
|
#@ @+others
|
||||||
|
#@+node:register_editors
|
||||||
|
def register_editors(cls, registry):
|
||||||
|
pass
|
||||||
|
|
||||||
|
register_editors = classmethod(register_editors)
|
||||||
|
|
||||||
|
#@-node:register_editors
|
||||||
|
#@-others
|
||||||
|
|
||||||
|
#@-node:class Observer
|
||||||
|
#@-others
|
||||||
|
factories = { }
|
||||||
|
clear_cache_funcs = {}
|
||||||
|
#@-node:@file observer.py
|
||||||
|
#@-leo
|
|
@ -0,0 +1,960 @@
|
||||||
|
#@+leo-ver=4
|
||||||
|
#@+node:@file pcalendar.py
|
||||||
|
#@@language python
|
||||||
|
#@<< Copyright >>
|
||||||
|
#@+node:<< Copyright >>
|
||||||
|
############################################################################
|
||||||
|
# Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
|
||||||
|
# mreithinger@web.de
|
||||||
|
#
|
||||||
|
# This file is part of faces.
|
||||||
|
#
|
||||||
|
# faces is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# faces is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the
|
||||||
|
# Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
#@-node:<< Copyright >>
|
||||||
|
#@nl
|
||||||
|
"""
|
||||||
|
This module contains all classes and functions for the project plan calendar
|
||||||
|
"""
|
||||||
|
#@<< Imports >>
|
||||||
|
#@+node:<< Imports >>
|
||||||
|
from string import *
|
||||||
|
import datetime
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import locale
|
||||||
|
import bisect
|
||||||
|
import sys
|
||||||
|
|
||||||
|
TIME_RANGE_PATTERN = re.compile("(\\d+):(\\d+)\\s*-\\s*(\\d+):(\\d+)")
|
||||||
|
TIME_DELTA_PATTERN = re.compile("([-+]?\\d+(\\.\\d+)?)([dwmyMH])")
|
||||||
|
|
||||||
|
DEFAULT_MINIMUM_TIME_UNIT = 15
|
||||||
|
DEFAULT_WORKING_DAYS_PER_WEEK = 5
|
||||||
|
DEFAULT_WORKING_DAYS_PER_MONTH = 20
|
||||||
|
DEFAULT_WORKING_DAYS_PER_YEAR = 200
|
||||||
|
DEFAULT_WORKING_HOURS_PER_DAY = 8
|
||||||
|
|
||||||
|
DEFAULT_WORKING_TIMES = ( (8 * 60, 12 * 60 ),
|
||||||
|
(13 * 60, 17 * 60 ) )
|
||||||
|
DEFAULT_WORKING_DAYS = { 0 : DEFAULT_WORKING_TIMES,
|
||||||
|
1 : DEFAULT_WORKING_TIMES,
|
||||||
|
2 : DEFAULT_WORKING_TIMES,
|
||||||
|
3 : DEFAULT_WORKING_TIMES,
|
||||||
|
4 : DEFAULT_WORKING_TIMES,
|
||||||
|
5 : (),
|
||||||
|
6 : () }
|
||||||
|
|
||||||
|
#@-node:<< Imports >>
|
||||||
|
#@nl
|
||||||
|
#@+others
|
||||||
|
#@+node:to_time_range
|
||||||
|
def to_time_range(src):
|
||||||
|
"""
|
||||||
|
converts a string to a timerange, i.e
|
||||||
|
(from, to)
|
||||||
|
from, to are ints, specifing the minutes since midnight
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not src: return ()
|
||||||
|
|
||||||
|
mo = TIME_RANGE_PATTERN.match(src)
|
||||||
|
if not mo:
|
||||||
|
raise ValueError("%s is no time range" % src)
|
||||||
|
|
||||||
|
from_time = int(mo.group(1)) * 60 + int(mo.group(2))
|
||||||
|
to_time = int(mo.group(3)) * 60 + int(mo.group(4))
|
||||||
|
return from_time, to_time
|
||||||
|
#@-node:to_time_range
|
||||||
|
#@+node:to_datetime
|
||||||
|
def to_datetime(src):
|
||||||
|
"""
|
||||||
|
a tolerant conversion function to convert different strings
|
||||||
|
to a datetime.dateime
|
||||||
|
"""
|
||||||
|
|
||||||
|
#to get the original value for wrappers
|
||||||
|
new = getattr(src, "_value", src)
|
||||||
|
while new is not src:
|
||||||
|
src = new
|
||||||
|
new = getattr(src, "_value", src)
|
||||||
|
|
||||||
|
if isinstance(src, _WorkingDateBase):
|
||||||
|
src = src.to_datetime()
|
||||||
|
|
||||||
|
if isinstance(src, datetime.datetime):
|
||||||
|
return src
|
||||||
|
|
||||||
|
src = str(src)
|
||||||
|
|
||||||
|
formats = [ "%x %H:%M",
|
||||||
|
"%x",
|
||||||
|
"%Y-%m-%d %H:%M",
|
||||||
|
"%y-%m-%d %H:%M",
|
||||||
|
"%d.%m.%Y %H:%M",
|
||||||
|
"%d.%m.%y %H:%M",
|
||||||
|
"%Y%m%d %H:%M",
|
||||||
|
"%d/%m/%y %H:%M",
|
||||||
|
"%d/%m/%Y %H:%M",
|
||||||
|
"%d/%m/%Y",
|
||||||
|
"%d/%m/%y",
|
||||||
|
"%Y-%m-%d",
|
||||||
|
"%y-%m-%d",
|
||||||
|
"%d.%m.%Y",
|
||||||
|
"%d.%m.%y",
|
||||||
|
"%Y%m%d" ]
|
||||||
|
for f in formats:
|
||||||
|
try:
|
||||||
|
conv = time.strptime(src, f)
|
||||||
|
|
||||||
|
return datetime.datetime(*conv[0:-3])
|
||||||
|
except Exception, e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise TypeError("'%s' (%s) is not a datetime" % (src, str(type(src))))
|
||||||
|
#@-node:
|
||||||
|
#@+node:_to_days
|
||||||
|
def _to_days(src):
|
||||||
|
"""
|
||||||
|
converts a string of the day abreviations mon, tue, wed,
|
||||||
|
thu, fri, sat, sun to a dir with correct weekday indices.
|
||||||
|
For Example
|
||||||
|
convert_to_days('mon, tue, thu') results in
|
||||||
|
{ 0:1, 1:1, 3:1 }
|
||||||
|
"""
|
||||||
|
|
||||||
|
tokens = src.split(",")
|
||||||
|
result = { }
|
||||||
|
for t in tokens:
|
||||||
|
try:
|
||||||
|
index = { "mon" : 0,
|
||||||
|
"tue" : 1,
|
||||||
|
"wed" : 2,
|
||||||
|
"thu" : 3,
|
||||||
|
"fri" : 4,
|
||||||
|
"sat" : 5,
|
||||||
|
"sun" : 6 } [ lower(t.strip()) ]
|
||||||
|
result[index] = 1
|
||||||
|
except:
|
||||||
|
raise ValueError("%s is not a day" % (t))
|
||||||
|
|
||||||
|
return result
|
||||||
|
#@-node:_to_days
|
||||||
|
#@+node:_add_to_time_spans
|
||||||
|
def _add_to_time_spans(src, to_add, is_free):
|
||||||
|
if not isinstance(to_add, (tuple, list)):
|
||||||
|
to_add = (to_add,)
|
||||||
|
|
||||||
|
tmp = []
|
||||||
|
for start, end, f in src:
|
||||||
|
tmp.append((start, True, f))
|
||||||
|
tmp.append((end, False, f))
|
||||||
|
|
||||||
|
for v in to_add:
|
||||||
|
if isinstance(v, (tuple, list)):
|
||||||
|
start = to_datetime(v[0])
|
||||||
|
end = to_datetime(v[1])
|
||||||
|
else:
|
||||||
|
start = to_datetime(v)
|
||||||
|
end = start.replace(hour=0, minute=0) + datetime.timedelta(1)
|
||||||
|
|
||||||
|
tmp.append((start, start <= end, is_free))
|
||||||
|
tmp.append((end, start > end, is_free))
|
||||||
|
|
||||||
|
tmp.sort()
|
||||||
|
|
||||||
|
# 0: date
|
||||||
|
# 1: is_start
|
||||||
|
# 2: is_free
|
||||||
|
sequence = []
|
||||||
|
free_count = 0
|
||||||
|
work_count = 0
|
||||||
|
last = None
|
||||||
|
for date, is_start, is_free in tmp:
|
||||||
|
if is_start:
|
||||||
|
if is_free:
|
||||||
|
if not free_count and not work_count:
|
||||||
|
last = date
|
||||||
|
|
||||||
|
free_count += 1
|
||||||
|
else:
|
||||||
|
if not work_count:
|
||||||
|
if free_count: sequence.append((last, date, True))
|
||||||
|
last = date
|
||||||
|
work_count += 1
|
||||||
|
else:
|
||||||
|
if is_free:
|
||||||
|
assert(free_count > 0)
|
||||||
|
free_count -= 1
|
||||||
|
if not free_count and not work_count:
|
||||||
|
sequence.append((last, date, True))
|
||||||
|
else:
|
||||||
|
assert(work_count > 0)
|
||||||
|
work_count -= 1
|
||||||
|
if not work_count: sequence.append((last, date, False))
|
||||||
|
if free_count: last = date
|
||||||
|
|
||||||
|
return tuple(sequence)
|
||||||
|
#@-node:_add_to_time_spans
|
||||||
|
#@+node:to_timedelta
|
||||||
|
def to_timedelta(src, cal=None, is_duration=False):
|
||||||
|
"""
|
||||||
|
converts a string to a datetime.timedelta. If cal is specified
|
||||||
|
it will be used for getting the working times. if is_duration=True
|
||||||
|
working times will not be considered. Valid units are
|
||||||
|
d for Days
|
||||||
|
w for Weeks
|
||||||
|
m for Months
|
||||||
|
y for Years
|
||||||
|
H for Hours
|
||||||
|
M for Minutes
|
||||||
|
"""
|
||||||
|
|
||||||
|
cal = cal or _default_calendar
|
||||||
|
if isinstance(src, datetime.timedelta):
|
||||||
|
return datetime.timedelta(src.days, seconds=src.seconds, calendar=cal)
|
||||||
|
|
||||||
|
if isinstance(src, (long, int, float)):
|
||||||
|
src = "%sM" % str(src)
|
||||||
|
|
||||||
|
if not isinstance(src, basestring):
|
||||||
|
raise ValueError("%s is not a duration" % (repr(src)))
|
||||||
|
|
||||||
|
src = src.strip()
|
||||||
|
|
||||||
|
if is_duration:
|
||||||
|
d_p_w = 7
|
||||||
|
d_p_m = 30
|
||||||
|
d_p_y = 360
|
||||||
|
d_w_h = 24
|
||||||
|
else:
|
||||||
|
d_p_w = cal.working_days_per_week
|
||||||
|
d_p_m = cal.working_days_per_month
|
||||||
|
d_p_y = cal.working_days_per_year
|
||||||
|
d_w_h = cal.working_hours_per_day
|
||||||
|
|
||||||
|
def convert_minutes(minutes):
|
||||||
|
minutes = int(minutes)
|
||||||
|
hours = minutes / 60
|
||||||
|
minutes = minutes % 60
|
||||||
|
days = hours / d_w_h
|
||||||
|
hours = hours % d_w_h
|
||||||
|
return [ days, 0, 0, 0, minutes, hours ]
|
||||||
|
|
||||||
|
def convert_days(value):
|
||||||
|
days = int(value)
|
||||||
|
value -= days
|
||||||
|
value *= d_w_h
|
||||||
|
hours = int(value)
|
||||||
|
value -= hours
|
||||||
|
value *= 60
|
||||||
|
minutes = round(value)
|
||||||
|
return [ days, 0, 0, 0, minutes, hours ]
|
||||||
|
|
||||||
|
sum_args = [ 0, 0, 0, 0, 0, 0 ]
|
||||||
|
|
||||||
|
split = src.split(" ")
|
||||||
|
for s in split:
|
||||||
|
mo = TIME_DELTA_PATTERN.match(s)
|
||||||
|
if not mo:
|
||||||
|
raise ValueError(src +
|
||||||
|
" is not a valid duration: valid"
|
||||||
|
" units are: d w m y M H")
|
||||||
|
|
||||||
|
unit = mo.group(3)
|
||||||
|
val = float(mo.group(1))
|
||||||
|
|
||||||
|
if unit == 'd':
|
||||||
|
args = convert_days(val)
|
||||||
|
elif unit == 'w':
|
||||||
|
args = convert_days(val * d_p_w)
|
||||||
|
elif unit == 'm':
|
||||||
|
args = convert_days(val * d_p_m)
|
||||||
|
elif unit == 'y':
|
||||||
|
args = convert_days(val * d_p_y)
|
||||||
|
elif unit == 'M':
|
||||||
|
args = convert_minutes(val)
|
||||||
|
elif unit == 'H':
|
||||||
|
args = convert_minutes(val * 60)
|
||||||
|
|
||||||
|
sum_args = [ a + b for a, b in zip(sum_args, args) ]
|
||||||
|
|
||||||
|
sum_args = tuple(sum_args)
|
||||||
|
return datetime.timedelta(*sum_args)
|
||||||
|
#@-node:to_timedelta
|
||||||
|
#@+node:timedelta_to_str
|
||||||
|
def timedelta_to_str(delta, format, cal=None, is_duration=False):
|
||||||
|
cal = cal or _default_calendar
|
||||||
|
if is_duration:
|
||||||
|
d_p_w = 7
|
||||||
|
d_p_m = 30
|
||||||
|
d_p_y = 365
|
||||||
|
d_w_h = 24
|
||||||
|
else:
|
||||||
|
d_p_w = cal.working_days_per_week
|
||||||
|
d_p_m = cal.working_days_per_month
|
||||||
|
d_p_y = cal.working_days_per_year
|
||||||
|
d_w_h = cal.working_hours_per_day
|
||||||
|
|
||||||
|
has_years = format.find("%y") > -1
|
||||||
|
has_minutes = format.find("%M") > -1
|
||||||
|
has_hours = format.find("%H") > -1 or has_minutes
|
||||||
|
has_days = format.find("%d") > -1
|
||||||
|
has_weeks = format.find("%w") > -1
|
||||||
|
has_months = format.find("%m") > -1
|
||||||
|
|
||||||
|
result = format
|
||||||
|
days = delta.days
|
||||||
|
|
||||||
|
d_r = (days, format)
|
||||||
|
minutes = delta.seconds / 60
|
||||||
|
|
||||||
|
def rebase(d_r, cond1, cond2, letter, divisor):
|
||||||
|
#rebase the days
|
||||||
|
if not cond1: return d_r
|
||||||
|
|
||||||
|
days, result = d_r
|
||||||
|
|
||||||
|
if cond2:
|
||||||
|
val = days / divisor
|
||||||
|
if not val:
|
||||||
|
result = re.sub("{[^{]*?%" + letter + "[^}]*?}", "", result)
|
||||||
|
|
||||||
|
result = result.replace("%" + letter, str(val))
|
||||||
|
days %= divisor
|
||||||
|
else:
|
||||||
|
result = result.replace("%" + letter,
|
||||||
|
locale.format("%.2f",
|
||||||
|
(float(days) / divisor)))
|
||||||
|
|
||||||
|
return (days, result)
|
||||||
|
|
||||||
|
d_r = rebase(d_r, has_years, has_months or has_weeks or has_days, "y", d_p_y)
|
||||||
|
d_r = rebase(d_r, has_months, has_weeks or has_days, "m", d_p_m)
|
||||||
|
d_r = rebase(d_r, has_weeks, has_days, "w", d_p_w)
|
||||||
|
days, result = d_r
|
||||||
|
|
||||||
|
if not has_days:
|
||||||
|
minutes += days * d_w_h * 60
|
||||||
|
days = 0
|
||||||
|
|
||||||
|
if has_hours:
|
||||||
|
if not days:
|
||||||
|
result = re.sub("{[^{]*?%d[^}]*?}", "", result)
|
||||||
|
|
||||||
|
result = result.replace("%d", str(days))
|
||||||
|
else:
|
||||||
|
result = result.replace("%d",
|
||||||
|
"%.2f" % (days + float(minutes)
|
||||||
|
/ (d_w_h * 60)))
|
||||||
|
|
||||||
|
if has_hours:
|
||||||
|
if has_minutes:
|
||||||
|
val = minutes / 60
|
||||||
|
if not val:
|
||||||
|
result = re.sub("{[^{]*?%H[^}]*?}", "", result)
|
||||||
|
|
||||||
|
result = result.replace("%H", str(val))
|
||||||
|
minutes %= 60
|
||||||
|
else:
|
||||||
|
result = result.replace("%H", "%.2f" % (float(minutes) / 60))
|
||||||
|
|
||||||
|
if not minutes:
|
||||||
|
result = re.sub("{[^{]*?%M[^}]*?}", "", result)
|
||||||
|
|
||||||
|
result = result.replace("%M", str(minutes))
|
||||||
|
|
||||||
|
result = result.replace("{", "")
|
||||||
|
result = result.replace("}", "")
|
||||||
|
return result.strip()
|
||||||
|
#@-node:timedelta_to_str
|
||||||
|
#@+node:strftime
|
||||||
|
def strftime(dt, format):
|
||||||
|
"""
|
||||||
|
an extended version of strftime, that introduces some new
|
||||||
|
directives:
|
||||||
|
%IW iso week number
|
||||||
|
%IY iso year
|
||||||
|
%IB full month name appropriate to iso week
|
||||||
|
%ib abbreviated month name appropriate to iso week
|
||||||
|
%im month as decimal number appropriate to iso week
|
||||||
|
"""
|
||||||
|
iso = dt.isocalendar()
|
||||||
|
if iso[0] != dt.year:
|
||||||
|
iso_date = dt.replace(day=1, month=1)
|
||||||
|
format = format \
|
||||||
|
.replace("%IB", iso_date.strftime("%B"))\
|
||||||
|
.replace("%ib", iso_date.strftime("%b"))\
|
||||||
|
.replace("%im", iso_date.strftime("%m"))
|
||||||
|
else:
|
||||||
|
format = format \
|
||||||
|
.replace("%IB", "%B")\
|
||||||
|
.replace("%ib", "%b")\
|
||||||
|
.replace("%im", "%m")
|
||||||
|
|
||||||
|
format = format \
|
||||||
|
.replace("%IW", str(iso[1]))\
|
||||||
|
.replace("%IY", str(iso[0]))\
|
||||||
|
|
||||||
|
return dt.strftime(format)
|
||||||
|
#@-node:strftime
|
||||||
|
#@+node:union
|
||||||
|
def union(*calendars):
|
||||||
|
"""
|
||||||
|
returns a calendar that unifies all working times
|
||||||
|
"""
|
||||||
|
#@ << check arguments >>
|
||||||
|
#@+node:<< check arguments >>
|
||||||
|
if len(calendars) == 1:
|
||||||
|
calendars = calendars[0]
|
||||||
|
#@nonl
|
||||||
|
#@-node:<< check arguments >>
|
||||||
|
#@nl
|
||||||
|
#@ << intersect vacations >>
|
||||||
|
#@+node:<< intersect vacations >>
|
||||||
|
free_time = []
|
||||||
|
for c in calendars:
|
||||||
|
for start, end, is_free in c.time_spans:
|
||||||
|
if is_free:
|
||||||
|
free_time.append((start, False))
|
||||||
|
free_time.append((end, True))
|
||||||
|
|
||||||
|
count = len(calendars)
|
||||||
|
open = 0
|
||||||
|
time_spans = []
|
||||||
|
free_time.sort()
|
||||||
|
for date, is_end in free_time:
|
||||||
|
if is_end:
|
||||||
|
if open == count:
|
||||||
|
time_spans.append((start, date, True))
|
||||||
|
open -= 1
|
||||||
|
else:
|
||||||
|
open += 1
|
||||||
|
start = date
|
||||||
|
#@-node:<< intersect vacations >>
|
||||||
|
#@nl
|
||||||
|
#@ << unify extra worktime >>
|
||||||
|
#@+node:<< unify extra worktime >>
|
||||||
|
for c in calendars:
|
||||||
|
for start, end, is_free in c.time_spans:
|
||||||
|
if not is_free:
|
||||||
|
time_spans = _add_to_time_spans(time_spans, start, end)
|
||||||
|
#@nonl
|
||||||
|
#@-node:<< unify extra worktime >>
|
||||||
|
#@nl
|
||||||
|
#@ << unify working times >>
|
||||||
|
#@+node:<< unify working times >>
|
||||||
|
working_times = {}
|
||||||
|
for d in range(0, 7):
|
||||||
|
times = []
|
||||||
|
for c in calendars:
|
||||||
|
for start, end in c.working_times.get(d, []):
|
||||||
|
times.append((start, False))
|
||||||
|
times.append((end, True))
|
||||||
|
|
||||||
|
times.sort()
|
||||||
|
open = 0
|
||||||
|
ti = []
|
||||||
|
start = None
|
||||||
|
for time, is_end in times:
|
||||||
|
if not is_end:
|
||||||
|
if not start: start = time
|
||||||
|
open += 1
|
||||||
|
else:
|
||||||
|
open -= 1
|
||||||
|
if not open:
|
||||||
|
ti.append((start, time))
|
||||||
|
start = None
|
||||||
|
|
||||||
|
if ti:
|
||||||
|
working_times[d] = ti
|
||||||
|
#@-node:<< unify working times >>
|
||||||
|
#@nl
|
||||||
|
#@ << create result calendar >>
|
||||||
|
#@+node:<< create result calendar >>
|
||||||
|
result = Calendar()
|
||||||
|
result.working_times = working_times
|
||||||
|
result.time_spans = time_spans
|
||||||
|
result._recalc_working_time()
|
||||||
|
result._build_mapping()
|
||||||
|
#@nonl
|
||||||
|
#@-node:<< create result calendar >>
|
||||||
|
#@nl
|
||||||
|
return result
|
||||||
|
#@nonl
|
||||||
|
#@-node:union
|
||||||
|
#@+node:class _CalendarItem
|
||||||
|
class _CalendarItem(int):
|
||||||
|
#@ << class _CalendarItem declarations >>
|
||||||
|
#@+node:<< class _CalendarItem declarations >>
|
||||||
|
__slots__ = ()
|
||||||
|
calender = None
|
||||||
|
|
||||||
|
|
||||||
|
#@-node:<< class _CalendarItem declarations >>
|
||||||
|
#@nl
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__new__
|
||||||
|
def __new__(cls, val):
|
||||||
|
try:
|
||||||
|
return int.__new__(cls, val)
|
||||||
|
except OverflowError:
|
||||||
|
return int.__new__(cls, sys.maxint)
|
||||||
|
#@-node:__new__
|
||||||
|
#@+node:round
|
||||||
|
def round(self, round_up=True):
|
||||||
|
m_t_u = self.calendar.minimum_time_unit
|
||||||
|
|
||||||
|
minutes = int(self)
|
||||||
|
base = (minutes / m_t_u) * m_t_u
|
||||||
|
minutes %= m_t_u
|
||||||
|
|
||||||
|
round_up = round_up and minutes > 0 or minutes > m_t_u / 2
|
||||||
|
if round_up: base += m_t_u
|
||||||
|
return self.__class__(base)
|
||||||
|
#@-node:round
|
||||||
|
#@-others
|
||||||
|
#@-node:class _CalendarItem
|
||||||
|
#@+node:class _Minutes
|
||||||
|
class _Minutes(_CalendarItem):
|
||||||
|
#@ << class _Minutes declarations >>
|
||||||
|
#@+node:<< class _Minutes declarations >>
|
||||||
|
__slots__ = ()
|
||||||
|
STR_FORMAT = "{%dd}{ %HH}{ %MM}"
|
||||||
|
|
||||||
|
|
||||||
|
#@-node:<< class _Minutes declarations >>
|
||||||
|
#@nl
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__new__
|
||||||
|
def __new__(cls, src=0, is_duration=False):
|
||||||
|
"""
|
||||||
|
converts a timedelta in working minutes.
|
||||||
|
"""
|
||||||
|
if isinstance(src, cls) or type(src) is int:
|
||||||
|
return _CalendarItem.__new__(cls, src)
|
||||||
|
|
||||||
|
cal = cls.calendar
|
||||||
|
if not isinstance(src, datetime.timedelta):
|
||||||
|
src = to_timedelta(src, cal, is_duration)
|
||||||
|
|
||||||
|
d_w_h = is_duration and 24 or cal.working_hours_per_day
|
||||||
|
src = src.days * d_w_h * 60 + src.seconds / 60
|
||||||
|
return _CalendarItem.__new__(cls, src)
|
||||||
|
#@-node:__new__
|
||||||
|
#@+node:__cmp__
|
||||||
|
def __cmp__(self, other):
|
||||||
|
return cmp(int(self), int(self.__class__(other)))
|
||||||
|
#@-node:__cmp__
|
||||||
|
#@+node:__add__
|
||||||
|
def __add__(self, other):
|
||||||
|
try:
|
||||||
|
return self.__class__(int(self) + int(self.__class__(other)))
|
||||||
|
except:
|
||||||
|
return NotImplemented
|
||||||
|
#@-node:__add__
|
||||||
|
#@+node:__sub__
|
||||||
|
def __sub__(self, other):
|
||||||
|
try:
|
||||||
|
return self.__class__(int(self) - int(self.__class__(other)))
|
||||||
|
except:
|
||||||
|
return NotImplemented
|
||||||
|
#@-node:__sub__
|
||||||
|
#@+node:to_timedelta
|
||||||
|
def to_timedelta(self, is_duration=False):
|
||||||
|
d_w_h = is_duration and 24 or self.calendar.working_hours_per_day
|
||||||
|
minutes = int(self)
|
||||||
|
hours = minutes / 60
|
||||||
|
minutes = minutes % 60
|
||||||
|
days = hours / d_w_h
|
||||||
|
hours = hours % d_w_h
|
||||||
|
return datetime.timedelta(days, hours=hours, minutes=minutes)
|
||||||
|
#@nonl
|
||||||
|
#@-node:to_timedelta
|
||||||
|
#@+node:strftime
|
||||||
|
def strftime(self, format=None, is_duration=False):
|
||||||
|
td = self.to_timedelta(is_duration)
|
||||||
|
return timedelta_to_str(td, format or self.STR_FORMAT,
|
||||||
|
self.calendar, is_duration)
|
||||||
|
#@nonl
|
||||||
|
#@-node:strftime
|
||||||
|
#@-others
|
||||||
|
#@-node:class _Minutes
|
||||||
|
#@+node:class _WorkingDateBase
|
||||||
|
class _WorkingDateBase(_CalendarItem):
|
||||||
|
"""
|
||||||
|
A daytetime which has only valid values within the
|
||||||
|
workingtimes of a specific calendar
|
||||||
|
"""
|
||||||
|
#@ << class _WorkingDateBase declarations >>
|
||||||
|
#@+node:<< class _WorkingDateBase declarations >>
|
||||||
|
timetuple = True
|
||||||
|
STR_FORMAT = "%x %H:%M"
|
||||||
|
_minutes = _Minutes
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
|
||||||
|
#@-node:<< class _WorkingDateBase declarations >>
|
||||||
|
#@nl
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__new__
|
||||||
|
def __new__(cls, src):
|
||||||
|
#cls.__bases__[0] is the base of
|
||||||
|
#the calendar specific StartDate and EndDate
|
||||||
|
|
||||||
|
if isinstance(src, cls.__bases__[0]) or type(src) in (int, float):
|
||||||
|
return _CalendarItem.__new__(cls, src)
|
||||||
|
|
||||||
|
|
||||||
|
src = cls.calendar.from_datetime(to_datetime(src))
|
||||||
|
return _CalendarItem.__new__(cls, src)
|
||||||
|
#@-node:__new__
|
||||||
|
#@+node:__repr__
|
||||||
|
def __repr__(self):
|
||||||
|
return self.strftime()
|
||||||
|
#@-node:__repr__
|
||||||
|
#@+node:to_datetime
|
||||||
|
def to_datetime(self):
|
||||||
|
return self.to_starttime()
|
||||||
|
#@-node:to_datetime
|
||||||
|
#@+node:to_starttime
|
||||||
|
def to_starttime(self):
|
||||||
|
return self.calendar.to_starttime(self)
|
||||||
|
#@-node:to_starttime
|
||||||
|
#@+node:to_endtime
|
||||||
|
def to_endtime(self):
|
||||||
|
return self.calendar.to_endtime(self)
|
||||||
|
#@-node:to_endtime
|
||||||
|
#@+node:__cmp__
|
||||||
|
def __cmp__(self, other):
|
||||||
|
return cmp(int(self), int(self.__class__(other)))
|
||||||
|
#@-node:__cmp__
|
||||||
|
#@+node:__add__
|
||||||
|
def __add__(self, other):
|
||||||
|
try:
|
||||||
|
return self.__class__(int(self) + int(self._minutes(other)))
|
||||||
|
except ValueError, e:
|
||||||
|
raise e
|
||||||
|
except:
|
||||||
|
return NotImplemented
|
||||||
|
#@-node:__add__
|
||||||
|
#@+node:__sub__
|
||||||
|
def __sub__(self, other):
|
||||||
|
if isinstance(other, (datetime.timedelta, str, _Minutes)):
|
||||||
|
try:
|
||||||
|
other = self._minutes(other)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if isinstance(other, self._minutes):
|
||||||
|
return self.__class__(int(self) - int(other))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self._minutes(int(self) - int(self.__class__(other)))
|
||||||
|
except:
|
||||||
|
return NotImplemented
|
||||||
|
#@-node:__sub__
|
||||||
|
#@+node:strftime
|
||||||
|
def strftime(self, format=None):
|
||||||
|
return strftime(self.to_datetime(), format or self.STR_FORMAT)
|
||||||
|
#@-node:strftime
|
||||||
|
#@-others
|
||||||
|
#@-node:class _WorkingDateBase
|
||||||
|
#@+node:class Calendar
|
||||||
|
class Calendar(object):
|
||||||
|
"""
|
||||||
|
A calendar to specify working times and vacations.
|
||||||
|
The calendars epoch start at 1.1.1979
|
||||||
|
"""
|
||||||
|
#@ << declarations >>
|
||||||
|
#@+node:<< declarations >>
|
||||||
|
# january the first must be a monday
|
||||||
|
EPOCH = datetime.datetime(1979, 1, 1)
|
||||||
|
minimum_time_unit = DEFAULT_MINIMUM_TIME_UNIT
|
||||||
|
working_days_per_week = DEFAULT_WORKING_DAYS_PER_WEEK
|
||||||
|
working_days_per_month = DEFAULT_WORKING_DAYS_PER_MONTH
|
||||||
|
working_days_per_year = DEFAULT_WORKING_DAYS_PER_YEAR
|
||||||
|
working_hours_per_day = DEFAULT_WORKING_HOURS_PER_DAY
|
||||||
|
now = EPOCH
|
||||||
|
|
||||||
|
|
||||||
|
#@-node:<< declarations >>
|
||||||
|
#@nl
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__init__
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.time_spans = ()
|
||||||
|
self._dt_num_can = ()
|
||||||
|
self._num_dt_can = ()
|
||||||
|
self.working_times = { }
|
||||||
|
self._recalc_working_time()
|
||||||
|
self._make_classes()
|
||||||
|
#@-node:__init__
|
||||||
|
#@+node:__or__
|
||||||
|
def __or__(self, other):
|
||||||
|
if isinstance(other, Calendar):
|
||||||
|
return union(self, other)
|
||||||
|
|
||||||
|
return NotImplemented
|
||||||
|
#@nonl
|
||||||
|
#@-node:__or__
|
||||||
|
#@+node:clone
|
||||||
|
def clone(self):
|
||||||
|
result = Calendar()
|
||||||
|
result.working_times = self.working_times.copy()
|
||||||
|
result.time_spans = self.time_spans
|
||||||
|
result._recalc_working_time()
|
||||||
|
result._build_mapping()
|
||||||
|
return result
|
||||||
|
#@nonl
|
||||||
|
#@-node:clone
|
||||||
|
#@+node:set_working_days
|
||||||
|
def set_working_days(self, day_range, trange, *further_tranges):
|
||||||
|
"""
|
||||||
|
Sets the working days of an calendar
|
||||||
|
day_range is a string of day abbreviations like 'mon, tue'
|
||||||
|
trange and further_tranges is a time range string like
|
||||||
|
'8:00-10:00'
|
||||||
|
"""
|
||||||
|
time_ranges = [ trange ] + list(further_tranges)
|
||||||
|
time_ranges = filter(bool, map(to_time_range, time_ranges))
|
||||||
|
days = _to_days(day_range)
|
||||||
|
|
||||||
|
for k in days.keys():
|
||||||
|
self.working_times[k] = time_ranges
|
||||||
|
|
||||||
|
self._recalc_working_time()
|
||||||
|
self._build_mapping()
|
||||||
|
#@-node:set_working_days
|
||||||
|
#@+node:set_vacation
|
||||||
|
def set_vacation(self, value):
|
||||||
|
"""
|
||||||
|
Sets vacation time.
|
||||||
|
value is either a datetime literal or
|
||||||
|
a sequence of items that can be
|
||||||
|
a datetime literals and or pair of datetime literals
|
||||||
|
"""
|
||||||
|
self.time_spans = _add_to_time_spans(self.time_spans, value, True)
|
||||||
|
self._build_mapping()
|
||||||
|
#@-node:set_vacation
|
||||||
|
#@+node:set_extra_work
|
||||||
|
def set_extra_work(self, value):
|
||||||
|
"""
|
||||||
|
Sets extra working time
|
||||||
|
value is either a datetime literal or
|
||||||
|
a sequence of items that can be
|
||||||
|
a datetime literals and or pair of datetime literals
|
||||||
|
"""
|
||||||
|
self.time_spans = _add_to_time_spans(self.time_spans, value, False)
|
||||||
|
self._build_mapping()
|
||||||
|
#@-node:set_extra_work
|
||||||
|
#@+node:from_datetime
|
||||||
|
def from_datetime(self, value):
|
||||||
|
assert(isinstance(value, datetime.datetime))
|
||||||
|
delta = value - self.EPOCH
|
||||||
|
days = delta.days
|
||||||
|
minutes = delta.seconds / 60
|
||||||
|
|
||||||
|
# calculate the weektime
|
||||||
|
weeks = days / 7
|
||||||
|
wtime = self.week_time * weeks
|
||||||
|
|
||||||
|
# calculate the daytime
|
||||||
|
days %= 7
|
||||||
|
dtime = sum(self.day_times[:days])
|
||||||
|
|
||||||
|
# calculate the minute time
|
||||||
|
slots = self.working_times.get(days, DEFAULT_WORKING_DAYS[days])
|
||||||
|
mtime = 0
|
||||||
|
for start, end in slots:
|
||||||
|
if minutes > end:
|
||||||
|
mtime += end - start
|
||||||
|
else:
|
||||||
|
if minutes > start:
|
||||||
|
mtime += minutes - start
|
||||||
|
break
|
||||||
|
|
||||||
|
result = wtime + dtime + mtime
|
||||||
|
|
||||||
|
# map exceptional timespans
|
||||||
|
dt_num_can = self._dt_num_can
|
||||||
|
pos = bisect.bisect(dt_num_can, (value,)) - 1
|
||||||
|
if pos >= 0:
|
||||||
|
start, end, nstart, nend, cend = dt_num_can[pos]
|
||||||
|
if value < end:
|
||||||
|
if nstart < nend:
|
||||||
|
delta = value - start
|
||||||
|
delta = delta.days * 24 * 60 + delta.seconds / 60
|
||||||
|
result = nstart + delta
|
||||||
|
else:
|
||||||
|
result = nstart
|
||||||
|
else:
|
||||||
|
result += (nend - cend) # == (result - cend) + nend
|
||||||
|
|
||||||
|
return result
|
||||||
|
#@-node:from_datetime
|
||||||
|
#@+node:split_time
|
||||||
|
def split_time(self, value):
|
||||||
|
#map exceptional timespans
|
||||||
|
num_dt_can = self._num_dt_can
|
||||||
|
pos = bisect.bisect(num_dt_can, (value, sys.maxint)) - 1
|
||||||
|
if pos >= 0:
|
||||||
|
nstart, nend, start, end, cend = num_dt_can[pos]
|
||||||
|
if value < nend:
|
||||||
|
value = start + datetime.timedelta(minutes=value - nstart)
|
||||||
|
delta = value - self.EPOCH
|
||||||
|
return delta.days / 7, delta.days % 7, delta.seconds / 60, -1
|
||||||
|
else:
|
||||||
|
value += (cend - nend) # (value - nend + cend)
|
||||||
|
#calculate the weeks since the epoch
|
||||||
|
|
||||||
|
weeks = value / self.week_time
|
||||||
|
value %= self.week_time
|
||||||
|
|
||||||
|
#calculate the remaining days
|
||||||
|
days = 0
|
||||||
|
for day_time in self.day_times:
|
||||||
|
if value < day_time: break
|
||||||
|
value -= day_time
|
||||||
|
days += 1
|
||||||
|
|
||||||
|
#calculate the remaining minutes
|
||||||
|
minutes = 0
|
||||||
|
slots = self.working_times.get(days, DEFAULT_WORKING_DAYS[days])
|
||||||
|
index = 0
|
||||||
|
for start, end in slots:
|
||||||
|
delta = end - start
|
||||||
|
if delta > value:
|
||||||
|
minutes = start + value
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
value -= delta
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return weeks, days, minutes, index
|
||||||
|
#@-node:split_time
|
||||||
|
#@+node:to_starttime
|
||||||
|
def to_starttime(self, value):
|
||||||
|
weeks, days, minutes, index = self.split_time(value)
|
||||||
|
return self.EPOCH + datetime.timedelta(weeks=weeks,
|
||||||
|
days=days,
|
||||||
|
minutes=minutes)
|
||||||
|
#@-node:to_starttime
|
||||||
|
#@+node:to_endtime
|
||||||
|
def to_endtime(self, value):
|
||||||
|
return self.to_starttime(value - 1) + datetime.timedelta(minutes=1)
|
||||||
|
#@-node:to_endtime
|
||||||
|
#@+node:get_working_times
|
||||||
|
def get_working_times(self, day):
|
||||||
|
return self.working_times.get(day, DEFAULT_WORKING_DAYS[day])
|
||||||
|
#@-node:get_working_times
|
||||||
|
#@+node:_build_mapping
|
||||||
|
def _build_mapping(self):
|
||||||
|
self._dt_num_can = self._num_dt_can = ()
|
||||||
|
dt_num_can = []
|
||||||
|
num_dt_can = []
|
||||||
|
|
||||||
|
delta = self.Minutes()
|
||||||
|
for start, end, is_free in self.time_spans:
|
||||||
|
cstart = self.StartDate(start)
|
||||||
|
cend = self.EndDate(end)
|
||||||
|
nstart = cstart + delta
|
||||||
|
|
||||||
|
if not is_free:
|
||||||
|
d = end - start
|
||||||
|
d = d.days * 24 * 60 + d.seconds / 60
|
||||||
|
nend = nstart + d
|
||||||
|
else:
|
||||||
|
nend = nstart
|
||||||
|
|
||||||
|
delta += (nend - nstart) - (cend - cstart)
|
||||||
|
dt_num_can.append((start, end, nstart, nend, cend))
|
||||||
|
num_dt_can.append((nstart, nend, start, end, cend))
|
||||||
|
|
||||||
|
self._dt_num_can = tuple(dt_num_can)
|
||||||
|
self._num_dt_can = tuple(num_dt_can)
|
||||||
|
|
||||||
|
#@-node:_build_mapping
|
||||||
|
#@+node:_recalc_working_time
|
||||||
|
def _recalc_working_time(self):
|
||||||
|
def slot_sum_time(day):
|
||||||
|
slots = self.working_times.get(day, DEFAULT_WORKING_DAYS[day])
|
||||||
|
return sum(map(lambda slot: slot[1] - slot[0], slots))
|
||||||
|
|
||||||
|
self.day_times = map(slot_sum_time, range(0, 7))
|
||||||
|
self.week_time = sum(self.day_times)
|
||||||
|
|
||||||
|
#@-node:_recalc_working_time
|
||||||
|
#@+node:_make_classes
|
||||||
|
def _make_classes(self):
|
||||||
|
#ensure that the clases are instance specific
|
||||||
|
class minutes(_Minutes):
|
||||||
|
calendar = self
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
class db(_WorkingDateBase):
|
||||||
|
calendar = self
|
||||||
|
_minutes = minutes
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
class wdt(db): __slots__ = ()
|
||||||
|
class edt(db):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def to_datetime(self):
|
||||||
|
return self.to_endtime()
|
||||||
|
|
||||||
|
self.Minutes, self.StartDate, self.EndDate = minutes, wdt, edt
|
||||||
|
|
||||||
|
self.WorkingDate = self.StartDate
|
||||||
|
|
||||||
|
#@-node:_make_classes
|
||||||
|
#@-others
|
||||||
|
|
||||||
|
|
||||||
|
_default_calendar = Calendar()
|
||||||
|
|
||||||
|
WorkingDate = _default_calendar.WorkingDate
|
||||||
|
StartDate = _default_calendar.StartDate
|
||||||
|
EndDate = _default_calendar.EndDate
|
||||||
|
Minutes = _default_calendar.Minutes
|
||||||
|
#@-node:class Calendar
|
||||||
|
#@-others
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
cal = Calendar()
|
||||||
|
|
||||||
|
start = EndDate("10.1.2005")
|
||||||
|
print "start", start.strftime(), type(start)
|
||||||
|
|
||||||
|
delay = Minutes("4H")
|
||||||
|
print "delay", delay, delay.strftime()
|
||||||
|
|
||||||
|
print "Start", cal.StartDate is StartDate
|
||||||
|
print "base", cal.StartDate.__bases__[0] == StartDate.__bases__[0]
|
||||||
|
print "type", type(start)
|
||||||
|
|
||||||
|
print "convert start"
|
||||||
|
start2 = cal.StartDate(start)
|
||||||
|
print "convert end"
|
||||||
|
|
||||||
|
start3 = cal.StartDate("10.1.2005")
|
||||||
|
print "start2", start2.strftime(), type(start2)
|
||||||
|
#@-node:@file pcalendar.py
|
||||||
|
#@-leo
|
|
@ -0,0 +1,54 @@
|
||||||
|
############################################################################
|
||||||
|
# Copyright (C) 2005 by Reithinger GmbH
|
||||||
|
# mreithinger@web.de
|
||||||
|
#
|
||||||
|
# This file is part of faces.
|
||||||
|
#
|
||||||
|
# faces is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# faces is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the
|
||||||
|
# Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
import gettext
|
||||||
|
import os.path
|
||||||
|
import locale
|
||||||
|
|
||||||
|
|
||||||
|
def _get_translation():
|
||||||
|
try:
|
||||||
|
return gettext.translation("faces")
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
if sys.frozen:
|
||||||
|
path = os.path.dirname(sys.argv[0])
|
||||||
|
path = os.path.join(path, "resources", "faces", "locale")
|
||||||
|
else:
|
||||||
|
path = os.path.split(__file__)[0]
|
||||||
|
path = os.path.join(path, "locale")
|
||||||
|
|
||||||
|
return gettext.translation("faces", path)
|
||||||
|
except Exception, e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_gettext():
|
||||||
|
trans = _get_translation()
|
||||||
|
if trans: return trans.ugettext
|
||||||
|
return lambda msg: msg
|
||||||
|
|
||||||
|
|
||||||
|
def get_encoding():
|
||||||
|
trans = _get_translation()
|
||||||
|
if trans: return trans.charset()
|
||||||
|
return locale.getpreferredencoding()
|
||||||
|
|
|
@ -0,0 +1,866 @@
|
||||||
|
#@+leo-ver=4
|
||||||
|
#@+node:@file resource.py
|
||||||
|
#@@language python
|
||||||
|
#@<< Copyright >>
|
||||||
|
#@+node:<< Copyright >>
|
||||||
|
############################################################################
|
||||||
|
# Copyright (C) 2005, 2006, 2007, 2008 by Reithinger GmbH
|
||||||
|
# mreithinger@web.de
|
||||||
|
#
|
||||||
|
# This file is part of faces.
|
||||||
|
#
|
||||||
|
# faces is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# faces is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the
|
||||||
|
# Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
#@-node:<< Copyright >>
|
||||||
|
#@nl
|
||||||
|
#@<< Imports >>
|
||||||
|
#@+node:<< Imports >>
|
||||||
|
import pcalendar
|
||||||
|
import datetime
|
||||||
|
import utils
|
||||||
|
import string
|
||||||
|
import bisect
|
||||||
|
import plocale
|
||||||
|
#@-node:<< Imports >>
|
||||||
|
#@nl
|
||||||
|
|
||||||
|
_is_source = True
|
||||||
|
_to_datetime = pcalendar.to_datetime
|
||||||
|
_ = plocale.get_gettext()
|
||||||
|
|
||||||
|
#@+others
|
||||||
|
#@+node:_isattrib
|
||||||
|
#@+doc
|
||||||
|
#@nonl
|
||||||
|
# is used to find snapshot attributes
|
||||||
|
#@-doc
|
||||||
|
#@@code
|
||||||
|
def _isattrib(obj, a):
|
||||||
|
return a[0] != "_" \
|
||||||
|
and not callable(getattr(obj, a)) \
|
||||||
|
and not a.endswith("_members") \
|
||||||
|
and a not in ("name")
|
||||||
|
#@-node:_isattrib
|
||||||
|
#@+node:class ResourceCalendar
|
||||||
|
class ResourceCalendar(object):
|
||||||
|
"""
|
||||||
|
The resource calendar saves the load time of a resource.
|
||||||
|
Is ia sequence of time intervals of loads. An example of
|
||||||
|
such a sequence is:
|
||||||
|
[ (datetime.min, 0),
|
||||||
|
(2006/1/1, 1.0),
|
||||||
|
(2006/1/10, 0.5),
|
||||||
|
(2006/1/15, 0) ]
|
||||||
|
|
||||||
|
That means the resource:
|
||||||
|
is free till january the first 2006
|
||||||
|
is fully booked from january the first to january 10th
|
||||||
|
is half booked from january 10th to january 15th
|
||||||
|
is free since january 15th
|
||||||
|
"""
|
||||||
|
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__init__
|
||||||
|
def __init__(self, src=None):
|
||||||
|
if src:
|
||||||
|
self.bookings = list(src.bookings)
|
||||||
|
else:
|
||||||
|
self.bookings = [ (datetime.datetime.min, 0) ]
|
||||||
|
#@-node:__init__
|
||||||
|
#@+node:__str__
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.bookings)
|
||||||
|
#@-node:__str__
|
||||||
|
#@+node:__repr__
|
||||||
|
def __repr__(self):
|
||||||
|
return "<ResourceCalendar %s>" % (str(self))
|
||||||
|
#@-node:__repr__
|
||||||
|
#@+node:add_load
|
||||||
|
def add_load(self, start, end, load):
|
||||||
|
start = _to_datetime(start)
|
||||||
|
end = _to_datetime(end)
|
||||||
|
|
||||||
|
bookings = self.bookings
|
||||||
|
|
||||||
|
# the load will be converted in an integer to avoid
|
||||||
|
# rouning problems
|
||||||
|
load = int(load * 10000)
|
||||||
|
|
||||||
|
start_item = (start, 0)
|
||||||
|
start_pos = bisect.bisect_left(bookings, start_item)
|
||||||
|
|
||||||
|
left_load = 0
|
||||||
|
left_load = bookings[start_pos - 1][1]
|
||||||
|
|
||||||
|
if start_pos < len(bookings) and bookings[start_pos][0] == start:
|
||||||
|
prev_load = bookings[start_pos][1]
|
||||||
|
if prev_load + load == left_load:
|
||||||
|
del bookings[start_pos]
|
||||||
|
else:
|
||||||
|
bookings[start_pos] = (start, prev_load + load)
|
||||||
|
start_pos += 1
|
||||||
|
else:
|
||||||
|
bookings.insert(start_pos, (start, load + left_load))
|
||||||
|
start_pos += 1
|
||||||
|
|
||||||
|
item = (datetime.datetime.min, 0)
|
||||||
|
for i in range(start_pos, len(bookings)):
|
||||||
|
end_pos = i
|
||||||
|
item = bookings[i]
|
||||||
|
if item[0] >= end: break
|
||||||
|
bookings[i] = (item[0], item[1] + load)
|
||||||
|
else:
|
||||||
|
end_pos = len(bookings)
|
||||||
|
|
||||||
|
left_load = bookings[end_pos - 1][1]
|
||||||
|
if item[0] == end:
|
||||||
|
if item[1] == left_load:
|
||||||
|
del bookings[end_pos]
|
||||||
|
else:
|
||||||
|
bookings.insert(end_pos, (end, left_load - load))
|
||||||
|
#@-node:add_load
|
||||||
|
#@+node:end_of_booking_interval
|
||||||
|
def end_of_booking_interval(self, date):
|
||||||
|
date = _to_datetime(date)
|
||||||
|
bookings = self.bookings
|
||||||
|
date_item = (date, 999999)
|
||||||
|
date_pos = bisect.bisect_left(bookings, date_item) - 1
|
||||||
|
next_date = datetime.datetime.max
|
||||||
|
load = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
book_item = bookings[date_pos]
|
||||||
|
load = bookings[date_pos][1] / 10000.0
|
||||||
|
next_date = bookings[date_pos + 1][0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return next_date, load
|
||||||
|
#@-node:end_of_booking_interval
|
||||||
|
#@+node:find_free_time
|
||||||
|
def find_free_time(self, start, length, load, max_load):
|
||||||
|
bookings = self.bookings
|
||||||
|
|
||||||
|
if isinstance(start, datetime.datetime):
|
||||||
|
adjust_date = _to_datetime
|
||||||
|
else:
|
||||||
|
adjust_date = start.calendar.EndDate
|
||||||
|
|
||||||
|
start = _to_datetime(start)
|
||||||
|
load = int(load * 10000)
|
||||||
|
max_load = int(max_load * 10000)
|
||||||
|
lb = len(bookings)
|
||||||
|
|
||||||
|
def next_possible(index):
|
||||||
|
while index < lb:
|
||||||
|
sd, lo = bookings[index]
|
||||||
|
if lo + load <= max_load:
|
||||||
|
break
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
sd = adjust_date(max(start, sd))
|
||||||
|
ed = sd + length
|
||||||
|
end = _to_datetime(ed)
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
while index < lb:
|
||||||
|
date, lo = bookings[index]
|
||||||
|
|
||||||
|
if date >= end:
|
||||||
|
#I found a good start date
|
||||||
|
return None, sd
|
||||||
|
|
||||||
|
if lo + load > max_load:
|
||||||
|
return index + 1, None
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return None, sd
|
||||||
|
|
||||||
|
start_item = (start, 1000000)
|
||||||
|
i = bisect.bisect_left(bookings, start_item) - 1
|
||||||
|
|
||||||
|
next_start = None
|
||||||
|
while not next_start and i < lb:
|
||||||
|
i, next_start = next_possible(i)
|
||||||
|
|
||||||
|
assert(next_start is not None)
|
||||||
|
return next_start
|
||||||
|
#@-node:find_free_time
|
||||||
|
#@+node:get_bookings
|
||||||
|
def get_bookings(self, start, end):
|
||||||
|
start = _to_datetime(start)
|
||||||
|
end = _to_datetime(end)
|
||||||
|
bookings = self.bookings
|
||||||
|
start_item = (start, 0)
|
||||||
|
start_pos = bisect.bisect_left(bookings, start_item)
|
||||||
|
if start_pos >= len(bookings) or bookings[start_pos][0] > start:
|
||||||
|
start_pos -= 1
|
||||||
|
|
||||||
|
end_item = (end, 0)
|
||||||
|
end_pos = bisect.bisect_left(bookings, end_item)
|
||||||
|
return start_pos, end_pos, bookings
|
||||||
|
#@-node:get_bookings
|
||||||
|
#@+node:get_load
|
||||||
|
def get_load(self, date):
|
||||||
|
date = _to_datetime(date)
|
||||||
|
bookings = self.bookings
|
||||||
|
item = (date, 100000)
|
||||||
|
pos = bisect.bisect_left(bookings, item) - 1
|
||||||
|
return bookings[pos][1] / 10000.0
|
||||||
|
#@-node:get_load
|
||||||
|
#@-others
|
||||||
|
#@-node:class ResourceCalendar
|
||||||
|
#@+node:class _ResourceBase
|
||||||
|
class _ResourceBase(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
#@-node:class _ResourceBase
|
||||||
|
#@+node:class _MetaResource
|
||||||
|
class _MetaResource(type):
|
||||||
|
doc_template = """
|
||||||
|
A resource class. The resources default attributes can
|
||||||
|
be changed when the class ist instanciated, i.e.
|
||||||
|
%(name)s(max_load=2.0)
|
||||||
|
|
||||||
|
@var max_load:
|
||||||
|
Specify the maximal allowed load sum of all simultaneously
|
||||||
|
allocated tasks of a resource. A ME{max_load} of 1.0 (default)
|
||||||
|
means the resource may be fully allocated. A ME{max_load} of 1.3
|
||||||
|
means the resource may be allocated with 30%% overtime.
|
||||||
|
|
||||||
|
@var title:
|
||||||
|
Specifies an alternative more descriptive name for the task.
|
||||||
|
|
||||||
|
@var efficiency:
|
||||||
|
The efficiency of a resource can be used for two purposes. First
|
||||||
|
you can use it as a crude way to model a team. A team of 5 people
|
||||||
|
should have an efficiency of 5.0. Keep in mind that you cannot
|
||||||
|
track the member of the team individually if you use this
|
||||||
|
feature. The other use is to model performance variations between
|
||||||
|
your resources.
|
||||||
|
|
||||||
|
@var vacation:
|
||||||
|
Specifies the vacation of the resource. This attribute is
|
||||||
|
specified as a list of date literals or date literal intervals.
|
||||||
|
Be aware that the end of an interval is excluded, i.e. it is
|
||||||
|
the first working date.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__init__
|
||||||
|
def __init__(self, name, bases, dict_):
|
||||||
|
super(_MetaResource, self).__init__(name, bases, dict_)
|
||||||
|
self.name = name
|
||||||
|
self.title = dict_.get("title", name)
|
||||||
|
self._calendar = { None: ResourceCalendar() }
|
||||||
|
self._tasks = { }
|
||||||
|
self.__set_vacation()
|
||||||
|
self.__add_resource(bases[0])
|
||||||
|
self.__doc__ = dict_.get("__doc__", self.doc_template) % locals()
|
||||||
|
#@-node:__init__
|
||||||
|
#@+node:__or__
|
||||||
|
def __or__(self, other):
|
||||||
|
return self().__or__(other)
|
||||||
|
#@-node:__or__
|
||||||
|
#@+node:__and__
|
||||||
|
def __and__(self, other):
|
||||||
|
return self().__and__(other)
|
||||||
|
#@-node:__and__
|
||||||
|
#@+node:__cmp__
|
||||||
|
def __cmp__(self, other):
|
||||||
|
return cmp(self.name, getattr(other, "name", None))
|
||||||
|
#@-node:__cmp__
|
||||||
|
#@+node:__repr__
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Resource %s>" % self.name
|
||||||
|
#@-node:__repr__
|
||||||
|
#@+node:__str__
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
#@-node:__str__
|
||||||
|
#@+node:__set_vacation
|
||||||
|
def __set_vacation(self):
|
||||||
|
vacation = self.vacation
|
||||||
|
|
||||||
|
if isinstance(vacation, (tuple, list)):
|
||||||
|
for v in vacation:
|
||||||
|
if isinstance(v, (tuple, list)):
|
||||||
|
self.add_vacation(v[0], v[1])
|
||||||
|
else:
|
||||||
|
self.add_vacation(v)
|
||||||
|
else:
|
||||||
|
self.add_vacation(vacation)
|
||||||
|
#@-node:__set_vacation
|
||||||
|
#@+node:__add_resource
|
||||||
|
def __add_resource(self, base):
|
||||||
|
if issubclass(base, _ResourceBase):
|
||||||
|
members = getattr(base, base.__name__ + "_members", [])
|
||||||
|
members.append(self)
|
||||||
|
setattr(base, base.__name__ + "_members", members)
|
||||||
|
#@-node:__add_resource
|
||||||
|
#@+node:get_members
|
||||||
|
def get_members(self):
|
||||||
|
return getattr(self, self.__name__ + "_members", [])
|
||||||
|
#@-node:get_members
|
||||||
|
#@+node:add_vacation
|
||||||
|
def add_vacation(self, start, end=None):
|
||||||
|
start_date = _to_datetime(start)
|
||||||
|
|
||||||
|
if not end:
|
||||||
|
end_date = start_date.replace(hour=23, minute=59)
|
||||||
|
else:
|
||||||
|
end_date = _to_datetime(end)
|
||||||
|
|
||||||
|
for cal in self._calendar.itervalues():
|
||||||
|
cal.add_load(start_date, end_date, 1)
|
||||||
|
|
||||||
|
tp = Booking()
|
||||||
|
tp.start = start_date
|
||||||
|
tp.end = end_date
|
||||||
|
tp.book_start = start_date
|
||||||
|
tp.book_end = end_date
|
||||||
|
tp.work_time = end_date - start_date
|
||||||
|
tp.load = 1.0
|
||||||
|
tp.name = tp.title = _("(vacation)")
|
||||||
|
tp._id = ""
|
||||||
|
self._tasks.setdefault("", []).append(tp)
|
||||||
|
#@-node:add_vacation
|
||||||
|
#@+node:calendar
|
||||||
|
def calendar(self, scenario):
|
||||||
|
try:
|
||||||
|
return self._calendar[scenario]
|
||||||
|
except KeyError:
|
||||||
|
cal = self._calendar[scenario] = ResourceCalendar(self._calendar[None])
|
||||||
|
return cal
|
||||||
|
|
||||||
|
#@-node:calendar
|
||||||
|
#@-others
|
||||||
|
#@-node:class _MetaResource
|
||||||
|
#@+node:make_team
|
||||||
|
def make_team(resource):
|
||||||
|
members = resource.get_members()
|
||||||
|
if not members:
|
||||||
|
return resource
|
||||||
|
|
||||||
|
result = make_team(members[0])
|
||||||
|
for r in members[1:]:
|
||||||
|
result = result & make_team(r)
|
||||||
|
|
||||||
|
return result
|
||||||
|
#@-node:make_team
|
||||||
|
#@+node:class Booking
|
||||||
|
class Booking(object):
|
||||||
|
"""
|
||||||
|
A booking unit for a task.
|
||||||
|
"""
|
||||||
|
#@ << declarations >>
|
||||||
|
#@+node:<< declarations >>
|
||||||
|
book_start = datetime.datetime.min
|
||||||
|
book_end = datetime.datetime.max
|
||||||
|
actual = False
|
||||||
|
_id = ""
|
||||||
|
|
||||||
|
#@-node:<< declarations >>
|
||||||
|
#@nl
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__init__
|
||||||
|
def __init__(self, task=None):
|
||||||
|
self.__task = task
|
||||||
|
#@-node:__init__
|
||||||
|
#@+node:__cmp__
|
||||||
|
def __cmp__(self, other):
|
||||||
|
return cmp(self._id, other._id)
|
||||||
|
#@-node:__cmp__
|
||||||
|
#@+node:path
|
||||||
|
def path(self):
|
||||||
|
first_dot = self._id.find(".")
|
||||||
|
return "root" + self._id[first_dot:]
|
||||||
|
|
||||||
|
path = property(path)
|
||||||
|
#@nonl
|
||||||
|
#@-node:path
|
||||||
|
#@+node:_idendity_
|
||||||
|
def _idendity_(self):
|
||||||
|
return self._id
|
||||||
|
#@-node:_idendity_
|
||||||
|
#@+node:__getattr__
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if self.__task:
|
||||||
|
return getattr(self.__task, name)
|
||||||
|
|
||||||
|
raise AttributeError("'%s' is not a valid attribute" % (name))
|
||||||
|
#@-node:__getattr__
|
||||||
|
#@-others
|
||||||
|
#@-node:class Booking
|
||||||
|
#@+node:class ResourceList
|
||||||
|
class ResourceList(list):
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__init__
|
||||||
|
def __init__(self, *args):
|
||||||
|
if args: self.extend(args)
|
||||||
|
#@-node:__init__
|
||||||
|
#@-others
|
||||||
|
#@-node:class ResourceList
|
||||||
|
#@+node:class Resource
|
||||||
|
class Resource(_ResourceBase):
|
||||||
|
#@ << declarations >>
|
||||||
|
#@+node:<< declarations >>
|
||||||
|
__metaclass__ = _MetaResource
|
||||||
|
__attrib_completions__ = {\
|
||||||
|
"max_load": 'max_load = ',
|
||||||
|
"title": 'title = "|"',
|
||||||
|
"efficiency": 'efficiency = ',
|
||||||
|
"vacation": 'vacation = [("|2002-02-01", "2002-02-05")]' }
|
||||||
|
|
||||||
|
__type_image__ = "resource16"
|
||||||
|
|
||||||
|
max_load = None # the maximum sum load for all task
|
||||||
|
vacation = ()
|
||||||
|
efficiency = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
#@-node:<< declarations >>
|
||||||
|
#@nl
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__init__
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for k, v in kwargs.iteritems():
|
||||||
|
setattr(self, k, v)
|
||||||
|
#@-node:__init__
|
||||||
|
#@+node:_idendity_
|
||||||
|
def _idendity_(cls):
|
||||||
|
return "resource:" + cls.__name__
|
||||||
|
|
||||||
|
_idendity_ = classmethod(_idendity_)
|
||||||
|
#@-node:_idendity_
|
||||||
|
#@+node:__repr__
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Resource %s>" % self.__class__.__name__
|
||||||
|
#@-node:__repr__
|
||||||
|
#@+node:__str__
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
#@-node:__str__
|
||||||
|
#@+node:__call__
|
||||||
|
def __call__(self):
|
||||||
|
return self
|
||||||
|
#@-node:__call__
|
||||||
|
#@+node:__hash__
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.__class__)
|
||||||
|
#@-node:__hash__
|
||||||
|
#@+node:__cmp__
|
||||||
|
def __cmp__(self, other):
|
||||||
|
return cmp(self.name, other.name)
|
||||||
|
#@-node:__cmp__
|
||||||
|
#@+node:__or__
|
||||||
|
def __or__(self, other):
|
||||||
|
if type(other) is _MetaResource:
|
||||||
|
other = other()
|
||||||
|
|
||||||
|
result = Resource()
|
||||||
|
result._subresource = _OrResourceGroup(self, other)
|
||||||
|
return result
|
||||||
|
#@-node:__or__
|
||||||
|
#@+node:__and__
|
||||||
|
def __and__(self, other):
|
||||||
|
if type(other) is _MetaResource:
|
||||||
|
other = other()
|
||||||
|
|
||||||
|
result = Resource()
|
||||||
|
result._subresource = _AndResourceGroup(self, other)
|
||||||
|
return result
|
||||||
|
#@-node:__and__
|
||||||
|
#@+node:_permutation_count
|
||||||
|
def _permutation_count(self):
|
||||||
|
if hasattr(self, "_subresource"):
|
||||||
|
return self._subresource._permutation_count()
|
||||||
|
|
||||||
|
return 1
|
||||||
|
#@-node:_permutation_count
|
||||||
|
#@+node:_get_resources
|
||||||
|
def _get_resources(self, state):
|
||||||
|
if hasattr(self, "_subresource"):
|
||||||
|
result = self._subresource._get_resources(state)
|
||||||
|
|
||||||
|
if self.name != "Resource":
|
||||||
|
result.name = self.name
|
||||||
|
|
||||||
|
if self.title != "Resource":
|
||||||
|
result.title = self.title
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
result = ResourceList(self)
|
||||||
|
return result
|
||||||
|
#@-node:_get_resources
|
||||||
|
#@+node:all_members
|
||||||
|
def all_members(self):
|
||||||
|
if hasattr(self, "_subresource"):
|
||||||
|
return self._subresource.all_members()
|
||||||
|
|
||||||
|
return [ self.__class__ ]
|
||||||
|
#@-node:all_members
|
||||||
|
#@+node:unbook_tasks_of_project
|
||||||
|
def unbook_tasks_of_project(cls, project_id, scenario):
|
||||||
|
try:
|
||||||
|
task_list = cls._tasks[scenario]
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
|
||||||
|
add_load = cls.calendar(scenario).add_load
|
||||||
|
for task_id, bookings in task_list.items():
|
||||||
|
if task_id.startswith(project_id):
|
||||||
|
for item in bookings:
|
||||||
|
add_load(item.book_start, item.book_end, -item.load)
|
||||||
|
|
||||||
|
del task_list[task_id]
|
||||||
|
|
||||||
|
if not task_list:
|
||||||
|
del cls._tasks[scenario]
|
||||||
|
|
||||||
|
unbook_tasks_of_project = classmethod(unbook_tasks_of_project)
|
||||||
|
#@-node:unbook_tasks_of_project
|
||||||
|
#@+node:unbook_task
|
||||||
|
def unbook_task(cls, task):
|
||||||
|
identdity = task._idendity_()
|
||||||
|
scenario = task.scenario
|
||||||
|
|
||||||
|
try:
|
||||||
|
task_list = cls._tasks[scenario]
|
||||||
|
bookings = task_list[identdity]
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
|
||||||
|
add_load = cls.calendar(scenario).add_load
|
||||||
|
for b in bookings:
|
||||||
|
add_load(b.book_start, b.book_end, -b.load)
|
||||||
|
|
||||||
|
del task_list[identdity]
|
||||||
|
if not task_list:
|
||||||
|
del cls._tasks[scenario]
|
||||||
|
|
||||||
|
unbook_task = classmethod(unbook_task)
|
||||||
|
#@-node:unbook_task
|
||||||
|
#@+node:correct_bookings
|
||||||
|
def correct_bookings(cls, task):
|
||||||
|
#correct the booking data with the actual task data
|
||||||
|
try:
|
||||||
|
tasks = cls._tasks[task.scenario][task._idendity_()]
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
|
||||||
|
for t in tasks:
|
||||||
|
t.start = task.start.to_datetime()
|
||||||
|
t.end = task.end.to_datetime()
|
||||||
|
|
||||||
|
correct_bookings = classmethod(correct_bookings)
|
||||||
|
#@-node:correct_bookings
|
||||||
|
#@+node:book_task
|
||||||
|
def book_task(cls, task, start, end, load, work_time, actual):
|
||||||
|
if not work_time: return
|
||||||
|
|
||||||
|
start = _to_datetime(start)
|
||||||
|
end = _to_datetime(end)
|
||||||
|
|
||||||
|
identdity = task._idendity_()
|
||||||
|
task_list = cls._tasks.setdefault(task.scenario, {})
|
||||||
|
bookings = task_list.setdefault(identdity, [])
|
||||||
|
add_load = cls.calendar(task.scenario).add_load
|
||||||
|
|
||||||
|
tb = Booking(task)
|
||||||
|
tb.book_start = start
|
||||||
|
tb.book_end = end
|
||||||
|
tb._id = identdity
|
||||||
|
tb.load = load
|
||||||
|
tb.start = _to_datetime(task.start)
|
||||||
|
tb.end = _to_datetime(task.end)
|
||||||
|
tb.title = task.title
|
||||||
|
tb.name = task.name
|
||||||
|
tb.work_time = int(work_time)
|
||||||
|
tb.actual = actual
|
||||||
|
bookings.append(tb)
|
||||||
|
result = add_load(start, end, load)
|
||||||
|
return result
|
||||||
|
|
||||||
|
book_task = classmethod(book_task)
|
||||||
|
#@-node:book_task
|
||||||
|
#@+node:length_of
|
||||||
|
def length_of(cls, task):
|
||||||
|
cal = task.root.calendar
|
||||||
|
bookings = cls.get_bookings(task)
|
||||||
|
return sum(map(lambda b: task._to_delta(b.work_time).round(), bookings))
|
||||||
|
|
||||||
|
length_of = classmethod(length_of)
|
||||||
|
#@-node:length_of
|
||||||
|
#@+node:done_of
|
||||||
|
def done_of(self, task):
|
||||||
|
cal = task.root.calendar
|
||||||
|
now = cal.now
|
||||||
|
bookings = self.get_bookings(task)
|
||||||
|
|
||||||
|
if task.__dict__.has_key("effort"):
|
||||||
|
efficiency = self.efficiency * task.efficiency
|
||||||
|
else:
|
||||||
|
efficiency = 1
|
||||||
|
|
||||||
|
def book_done(booking):
|
||||||
|
if booking.book_start >= now:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
factor = 1
|
||||||
|
if booking.book_end > now:
|
||||||
|
start = task._to_start(booking.book_start)
|
||||||
|
end = task._to_end(booking.book_end)
|
||||||
|
cnow = task._to_start(now)
|
||||||
|
factor = float(cnow - start) / ((end - start) or 1)
|
||||||
|
|
||||||
|
return factor * booking.work_time * efficiency
|
||||||
|
|
||||||
|
return task._to_delta(sum(map(book_done, bookings)))
|
||||||
|
#@-node:done_of
|
||||||
|
#@+node:todo_of
|
||||||
|
def todo_of(self, task):
|
||||||
|
cal = task.root.calendar
|
||||||
|
now = cal.now
|
||||||
|
|
||||||
|
bookings = self.get_bookings(task)
|
||||||
|
if task.__dict__.has_key("effort"):
|
||||||
|
efficiency = self.efficiency * task.efficiency
|
||||||
|
else:
|
||||||
|
efficiency = 1
|
||||||
|
|
||||||
|
def book_todo(booking):
|
||||||
|
if booking.book_end <= now:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
factor = 1
|
||||||
|
if booking.book_start < now:
|
||||||
|
start = task._to_start(booking.book_start)
|
||||||
|
end = task._to_end(booking.book_end)
|
||||||
|
cnow = task._to_start(now)
|
||||||
|
factor = float(end - cnow) / ((end - start) or 1)
|
||||||
|
|
||||||
|
return factor * booking.work_time * efficiency
|
||||||
|
|
||||||
|
return task._to_delta(sum(map(book_todo, bookings)))
|
||||||
|
#@-node:todo_of
|
||||||
|
#@+node:get_bookings
|
||||||
|
def get_bookings(cls, task):
|
||||||
|
return cls._tasks.get(task.scenario, {}).get(task._idendity_(), ())
|
||||||
|
|
||||||
|
get_bookings = classmethod(get_bookings)
|
||||||
|
#@-node:get_bookings
|
||||||
|
#@+node:get_bookings_at
|
||||||
|
def get_bookings_at(cls, start, end, scenario):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
items = cls._tasks[scenario].iteritems()
|
||||||
|
except KeyError:
|
||||||
|
return ()
|
||||||
|
|
||||||
|
for task_id, bookings in items:
|
||||||
|
result += [ booking for booking in bookings
|
||||||
|
if booking.book_start < end
|
||||||
|
and booking.book_end > start ]
|
||||||
|
|
||||||
|
vacations = cls._tasks.get("", ())
|
||||||
|
result += [ booking for booking in vacations
|
||||||
|
if booking.book_start < end
|
||||||
|
and booking.book_end > start ]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
get_bookings_at = classmethod(get_bookings_at)
|
||||||
|
#@-node:get_bookings_at
|
||||||
|
#@+node:find_free_time
|
||||||
|
def find_free_time(cls, start, length, load, max_load, scenario):
|
||||||
|
return cls.calendar(scenario).find_free_time(start, length, load, max_load)
|
||||||
|
|
||||||
|
find_free_time = classmethod(find_free_time)
|
||||||
|
#@-node:find_free_time
|
||||||
|
#@+node:get_load
|
||||||
|
def get_load(cls, date, scenario):
|
||||||
|
return cls.calendar(scenario).get_load(date)
|
||||||
|
|
||||||
|
get_load = classmethod(get_load)
|
||||||
|
#@-node:get_load
|
||||||
|
#@+node:end_of_booking_interval
|
||||||
|
def end_of_booking_interval(cls, date, task):
|
||||||
|
return cls.calendar(task.scenario).end_of_booking_interval(date)
|
||||||
|
|
||||||
|
end_of_booking_interval = classmethod(end_of_booking_interval)
|
||||||
|
#@-node:end_of_booking_interval
|
||||||
|
#@+node:snapshot
|
||||||
|
def snapshot(self):
|
||||||
|
from task import _as_string
|
||||||
|
def isattrib(a):
|
||||||
|
if a == "max_load" and self.max_load is None: return False
|
||||||
|
if a in ("name", "title", "vacation"): return False
|
||||||
|
return _isattrib(self, a)
|
||||||
|
|
||||||
|
attribs = filter(isattrib, dir(self))
|
||||||
|
attribs = map(lambda a: "%s=%s" % (a, _as_string(getattr(self, a))),
|
||||||
|
attribs)
|
||||||
|
|
||||||
|
return self.name + "(%s)" % ", ".join(attribs)
|
||||||
|
#@-node:snapshot
|
||||||
|
#@-others
|
||||||
|
#@-node:class Resource
|
||||||
|
#@+node:class _ResourceGroup
|
||||||
|
|
||||||
|
|
||||||
|
class _ResourceGroup(object):
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__init__
|
||||||
|
def __init__(self, *args):
|
||||||
|
self.resources = []
|
||||||
|
for a in args:
|
||||||
|
self.__append(a)
|
||||||
|
#@-node:__init__
|
||||||
|
#@+node:all_members
|
||||||
|
def all_members(self):
|
||||||
|
group = reduce(lambda a, b: a + b.all_members(),
|
||||||
|
self.resources, [])
|
||||||
|
group = map(lambda r: (r, True), group)
|
||||||
|
group = dict(group)
|
||||||
|
group = group.keys()
|
||||||
|
return group
|
||||||
|
#@-node:all_members
|
||||||
|
#@+node:_permutation_count
|
||||||
|
def _permutation_count(self):
|
||||||
|
abstract
|
||||||
|
#@-node:_permutation_count
|
||||||
|
#@+node:_refactor
|
||||||
|
def _refactor(self, arg):
|
||||||
|
pass
|
||||||
|
#@-node:_refactor
|
||||||
|
#@+node:__append
|
||||||
|
def __append(self, arg):
|
||||||
|
if isinstance(arg, self.__class__):
|
||||||
|
self.resources += arg.resources
|
||||||
|
for r in arg.resources:
|
||||||
|
self._refactor(r)
|
||||||
|
return
|
||||||
|
elif isinstance(arg, Resource):
|
||||||
|
subresources = getattr(arg, "_subresource", None)
|
||||||
|
if subresources:
|
||||||
|
self.__append(subresources)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.resources.append(arg)
|
||||||
|
else:
|
||||||
|
assert(isinstance(arg, _ResourceGroup))
|
||||||
|
self.resources.append(arg)
|
||||||
|
|
||||||
|
self._refactor(arg)
|
||||||
|
#@-node:__append
|
||||||
|
#@+node:__str__
|
||||||
|
def __str__(self):
|
||||||
|
op = lower(self.__class__.__name__[0:-13])
|
||||||
|
return "(" + \
|
||||||
|
string.join([str(r) for r in self.resources],
|
||||||
|
" " + op + " ") + \
|
||||||
|
")"
|
||||||
|
#@-node:__str__
|
||||||
|
#@-others
|
||||||
|
#@-node:class _ResourceGroup
|
||||||
|
#@+node:class _OrResourceGroup
|
||||||
|
|
||||||
|
|
||||||
|
class _OrResourceGroup(_ResourceGroup):
|
||||||
|
#@ @+others
|
||||||
|
#@+node:_get_resources
|
||||||
|
def _get_resources(self, state):
|
||||||
|
for r in self.resources:
|
||||||
|
c = r._permutation_count()
|
||||||
|
if c <= state:
|
||||||
|
state -= c
|
||||||
|
else:
|
||||||
|
return r._get_resources(state)
|
||||||
|
|
||||||
|
assert(0)
|
||||||
|
#@-node:_get_resources
|
||||||
|
#@+node:_permutation_count
|
||||||
|
def _permutation_count(self):
|
||||||
|
return sum([ r._permutation_count() for r in self.resources])
|
||||||
|
#@-node:_permutation_count
|
||||||
|
#@-others
|
||||||
|
#@-node:class _OrResourceGroup
|
||||||
|
#@+node:class _AndResourceGroup
|
||||||
|
|
||||||
|
|
||||||
|
class _AndResourceGroup(_ResourceGroup):
|
||||||
|
#@ @+others
|
||||||
|
#@+node:__init__
|
||||||
|
def __init__(self, *args):
|
||||||
|
self.factors = [ 1 ]
|
||||||
|
_ResourceGroup.__init__(self, *args)
|
||||||
|
#@-node:__init__
|
||||||
|
#@+node:_refactor
|
||||||
|
def _refactor(self, arg):
|
||||||
|
count = arg._permutation_count()
|
||||||
|
self.factors = [ count * f for f in self.factors ]
|
||||||
|
self.factors.append(1)
|
||||||
|
#@-node:_refactor
|
||||||
|
#@+node:_permutation_count
|
||||||
|
#print "AndResourceGroup", count, arg, self.factors
|
||||||
|
|
||||||
|
|
||||||
|
def _permutation_count(self):
|
||||||
|
return self.factors[0]
|
||||||
|
#@-node:_permutation_count
|
||||||
|
#@+node:_get_resources
|
||||||
|
def _get_resources(self, state):
|
||||||
|
"""delivers None when there are duplicate resources"""
|
||||||
|
result = []
|
||||||
|
for i in range(1, len(self.factors)):
|
||||||
|
f = self.factors[i]
|
||||||
|
substate = state / f
|
||||||
|
state %= f
|
||||||
|
result.append(self.resources[i - 1]._get_resources(substate))
|
||||||
|
|
||||||
|
result = ResourceList(*list(utils.flatten(result)))
|
||||||
|
dupl_test = { }
|
||||||
|
for r in result:
|
||||||
|
if dupl_test.has_key(r):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
dupl_test[r] = 1
|
||||||
|
|
||||||
|
return result
|
||||||
|
#@-node:_get_resources
|
||||||
|
#@+node:_has_duplicates
|
||||||
|
def _has_duplicates(self, state):
|
||||||
|
resources = self._get_resources(state)
|
||||||
|
tmp = { }
|
||||||
|
for r in resources:
|
||||||
|
if tmp.has_key(r):
|
||||||
|
return True
|
||||||
|
|
||||||
|
tmp[r] = 1
|
||||||
|
|
||||||
|
return False
|
||||||
|
#@-node:_has_duplicates
|
||||||
|
#@-others
|
||||||
|
#@-node:class _AndResourceGroup
|
||||||
|
#@-others
|
||||||
|
#@-node:@file resource.py
|
||||||
|
#@-leo
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,113 @@
|
||||||
|
############################################################################
|
||||||
|
# Copyright (C) 2005 by Reithinger GmbH
|
||||||
|
# mreithinger@web.de
|
||||||
|
#
|
||||||
|
# This file is part of faces.
|
||||||
|
#
|
||||||
|
# faces is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# faces is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the
|
||||||
|
# Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
import faces.pcalendar as pcal
|
||||||
|
import matplotlib.cbook as cbook
|
||||||
|
import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class TimeScale(object):
|
||||||
|
def __init__(self, calendar):
|
||||||
|
self.data_calendar = calendar
|
||||||
|
self._create_chart_calendar()
|
||||||
|
self.now = self.to_num(self.data_calendar.now)
|
||||||
|
|
||||||
|
|
||||||
|
def to_datetime(self, xval):
|
||||||
|
return xval.to_datetime()
|
||||||
|
|
||||||
|
|
||||||
|
def to_num(self, date):
|
||||||
|
return self.chart_calendar.WorkingDate(date)
|
||||||
|
|
||||||
|
|
||||||
|
def is_free_slot(self, value):
|
||||||
|
dt1 = self.chart_calendar.to_starttime(value)
|
||||||
|
dt2 = self.data_calendar.to_starttime\
|
||||||
|
(self.data_calendar.from_datetime(dt1))
|
||||||
|
return dt1 != dt2
|
||||||
|
|
||||||
|
|
||||||
|
def is_free_day(self, value):
|
||||||
|
dt1 = self.chart_calendar.to_starttime(value)
|
||||||
|
dt2 = self.data_calendar.to_starttime\
|
||||||
|
(self.data_calendar.from_datetime(dt1))
|
||||||
|
return dt1.date() != dt2.date()
|
||||||
|
|
||||||
|
|
||||||
|
def _create_chart_calendar(self):
|
||||||
|
dcal = self.data_calendar
|
||||||
|
ccal = self.chart_calendar = pcal.Calendar()
|
||||||
|
ccal.minimum_time_unit = 1
|
||||||
|
|
||||||
|
#pad worktime slots of calendar (all days should be equally long)
|
||||||
|
slot_sum = lambda slots: sum(map(lambda slot: slot[1] - slot[0], slots))
|
||||||
|
day_sum = lambda day: slot_sum(dcal.get_working_times(day))
|
||||||
|
|
||||||
|
max_work_time = max(map(day_sum, range(7)))
|
||||||
|
|
||||||
|
#working_time should have 2/3
|
||||||
|
sum_time = 3 * max_work_time / 2
|
||||||
|
|
||||||
|
#now create timeslots for ccal
|
||||||
|
def create_time_slots(day):
|
||||||
|
src_slots = dcal.get_working_times(day)
|
||||||
|
slots = [0, src_slots, 24*60]
|
||||||
|
slots = tuple(cbook.flatten(slots))
|
||||||
|
slots = zip(slots[:-1], slots[1:])
|
||||||
|
|
||||||
|
#balance non working slots
|
||||||
|
work_time = slot_sum(src_slots)
|
||||||
|
non_work_time = sum_time - work_time
|
||||||
|
|
||||||
|
non_slots = filter(lambda s: s not in src_slots, slots)
|
||||||
|
non_slots = map(lambda s: (s[1] - s[0], s), non_slots)
|
||||||
|
non_slots.sort()
|
||||||
|
|
||||||
|
slots = []
|
||||||
|
i = 0
|
||||||
|
for l, s in non_slots:
|
||||||
|
delta = non_work_time / (len(non_slots) - i)
|
||||||
|
delta = min(l, delta)
|
||||||
|
non_work_time -= delta
|
||||||
|
slots.append((s[0], s[0] + delta))
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
slots.extend(src_slots)
|
||||||
|
slots.sort()
|
||||||
|
return slots
|
||||||
|
|
||||||
|
min_delta = sys.maxint
|
||||||
|
for i in range(7):
|
||||||
|
slots = create_time_slots(i)
|
||||||
|
ccal.working_times[i] = slots
|
||||||
|
min_delta = min(min_delta, min(map(lambda s: s[1] - s[0], slots)))
|
||||||
|
|
||||||
|
ccal._recalc_working_time()
|
||||||
|
|
||||||
|
self.slot_delta = min_delta
|
||||||
|
self.day_delta = sum_time
|
||||||
|
self.week_delta = ccal.week_time
|
||||||
|
|
||||||
|
|
||||||
|
_default_scale = TimeScale(pcal._default_calendar)
|
|
@ -0,0 +1,124 @@
|
||||||
|
############################################################################
|
||||||
|
# Copyright (C) 2005 by Reithinger GmbH
|
||||||
|
# mreithinger@web.de
|
||||||
|
#
|
||||||
|
# This file is part of faces.
|
||||||
|
#
|
||||||
|
# faces is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# faces is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the
|
||||||
|
# Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
############################################################################
|
||||||
|
|
||||||
|
import observer
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
_call_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
|
||||||
|
|
||||||
|
def get_installation_path():
|
||||||
|
try:
|
||||||
|
if sys.frozen:
|
||||||
|
path = _call_dir
|
||||||
|
else:
|
||||||
|
raise AttributeError()
|
||||||
|
except AttributeError:
|
||||||
|
path = os.path.abspath(observer.__file__)
|
||||||
|
path = os.path.split(path)[0]
|
||||||
|
|
||||||
|
path = os.path.normcase(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_resource_path():
|
||||||
|
try:
|
||||||
|
if sys.frozen:
|
||||||
|
path = _call_dir
|
||||||
|
path = os.path.join(path, "resources", "faces", "gui")
|
||||||
|
else:
|
||||||
|
raise AttributeError()
|
||||||
|
except AttributeError:
|
||||||
|
path = get_installation_path()
|
||||||
|
path = os.path.join(path, "gui", "resources")
|
||||||
|
|
||||||
|
path = os.path.normcase(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_template_path():
|
||||||
|
try:
|
||||||
|
if sys.frozen:
|
||||||
|
path = _call_dir
|
||||||
|
path = os.path.join(path, "resources", "faces", "templates")
|
||||||
|
else:
|
||||||
|
raise AttributeError()
|
||||||
|
except AttributeError:
|
||||||
|
path = get_installation_path()
|
||||||
|
path = os.path.join(path, "templates")
|
||||||
|
|
||||||
|
path = os.path.normcase(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def get_howtos_path():
|
||||||
|
try:
|
||||||
|
if sys.frozen:
|
||||||
|
path = _call_dir
|
||||||
|
else:
|
||||||
|
raise AttributeError()
|
||||||
|
except AttributeError:
|
||||||
|
path = get_installation_path()
|
||||||
|
|
||||||
|
path = os.path.join(path, "howtos")
|
||||||
|
path = os.path.normcase(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def flatten(items):
|
||||||
|
if isinstance(items, tuple):
|
||||||
|
items = list(items)
|
||||||
|
|
||||||
|
if not isinstance(items, list):
|
||||||
|
yield items
|
||||||
|
|
||||||
|
stack = [iter(items)]
|
||||||
|
while stack:
|
||||||
|
for item in stack[-1]:
|
||||||
|
if isinstance(item, tuple):
|
||||||
|
item = list(item)
|
||||||
|
|
||||||
|
if isinstance(item, list):
|
||||||
|
stack.append(iter(item))
|
||||||
|
break
|
||||||
|
yield item
|
||||||
|
else:
|
||||||
|
stack.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def do_yield():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def progress_start(title, maximum, message=""):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def progress_update(value, message=""):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def progress_end():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue