def __init__(self, date, config): self.today = date self.num_reg = re.compile('^-?\d+$') self.infstr = config['Journal']['INFSTR'] self.template = Regexplate(config['Journal']['DATE']) super().__init__() return
def __init__(self, date, config): t00 = datetime.datetime(date.year, date.month, date.day, 0, 0, 0) t24 = t00 + datetime.timedelta(1) self.t00 = int(t00.timestamp()) self.t24 = int(t24.timestamp()) self.template = Regexplate(config['Journal']['TIME']) self.delim = config['Journal']['DELIM'] super().__init__()
def __init__(self, date, config): self.year = date.year self.month = date.month self.day = date.day self.template = Regexplate(config['Journal']['TIME']) self.delim = config['Journal']['DELIM'] super().__init__() return
class MemoParser(Logger): """メモ解析器""" def __init__(self, config): self.root = config['Main']['ROOT'] self.template = Regexplate(config['Journal']['MEMOTITLE']) super().__init__() return @Logger.logging def parse(self, string): texts = self.template.split(string)[1:] titles = self.template.findall(string) paths = [self.root + self.template.parse(s)['PATH'] for s in titles] return [{'PATH': p, 'TEXT': t} for p, t in zip(paths, texts)]
class TaskLineBuilder(Logger): """""" def __init__(self, date, config): self.root = config['Main']['ROOT'] self.dlb = DeadLineBuilder(date, config) self.ttb = TimeTableBuilder(date, config) self.template = Regexplate(config['Journal']['TASKLINE']) super().__init__() @Logger.logging def build(self, task): return self.template.substitute(self.task_map(task)) @Logger.logging def task_map(self, task): return { 'PATH': self.short_path(task.path), 'DEADLINE': self.dlb.build(task.deadline), 'TIMETABLE': self.ttb.build(task.timetable), } @Logger.logging def short_path(self, path): if self.root not in path or path.index(self.root) != 0: return path else: return path.replace(self.root, '')
class Memo(Logger): stamp = Regexplate('## Written at %Y/%m/%d %H:%M') head_blank_reg = re.compile(r'^\n*') tail_blank_reg = re.compile(r'\n*$') body_blank_reg = re.compile(r'\n{3,}') # コンストラクタ def __init__(self, dirpath, filename): self.dirpath = dirpath self.filepath = os.path.join(dirpath, filename) super().__init__() # メモ取得 @Logger.logging def get(self): # ファイルが無ければ空リストを返す if not os.path.isdir(self.dirpath): return [] if not os.path.isfile(self.filepath): return [] # ファイルを読み込んでテキストリストにして返す with open(self.filepath, 'r', encoding='utf-8-sig') as f: text = f.read() return [s for _, s in type(self).parse(text, type(self).stamp)] # メモ追記 @Logger.logging def put(self, timestamp, text): # ディレクトリが無ければ作成する if not os.path.isdir(self.dirpath): os.makedirs(self.dirpath) # 余計な空白行を削除する text = type(self).trim(text) # 既に記載されていれば無視する if text in self.get(): return False # 追記する with open(self.filepath, 'a', encoding='utf-8') as f: f.write("{}\n\n{}\n\n".format( timestamp.strftime(type(self).stamp.template), text)) return True # 補助メソッド @classmethod @Logger.logging def parse(cls, text, template): titles = template.findall(text) texts = [cls.trim(s) for s in template.split(text)[1:]] return list(zip(titles, texts)) @classmethod @Logger.logging def trim(cls, string): string = Memo.head_blank_reg.sub('', string) string = Memo.tail_blank_reg.sub('', string) string = Memo.body_blank_reg.sub(r'\n\n', string) return string
class TimeTableParser(Logger): """タイムテーブル解析器""" def __init__(self, date, config): self.year = date.year self.month = date.month self.day = date.day self.template = Regexplate(config['Journal']['TIME']) self.delim = config['Journal']['DELIM'] super().__init__() return @Logger.logging def parse(self, string): phrases = [t for t in string.replace(' ', '').split(self.delim) if t] terms = [self.template.parse(p) for p in phrases] return [self.timetuple(t) for t in terms] @Logger.logging def timetuple(self, term): s = self.timestamp(term['SHOUR'], term['SMIN']) e = self.timestamp(term['EHOUR'], term['EMIN']) return (s, e - s) @Logger.logging def timestamp(self, hour, minute): return int( datetime.datetime(self.year, self.month, self.day, int(hour), int(minute), 0).timestamp())
class TimeTableBuilder(Logger): def __init__(self, date, config): t00 = datetime.datetime(date.year, date.month, date.day, 0, 0, 0) t24 = t00 + datetime.timedelta(1) self.t00 = int(t00.timestamp()) self.t24 = int(t24.timestamp()) self.template = Regexplate(config['Journal']['TIME']) self.delim = config['Journal']['DELIM'] super().__init__() @Logger.logging def build(self, timetable): maps = [ self.time_map(s, s + t) for s, t in timetable if self.at_date(s) ] return self.delim.join([self.template.substitute(m) for m in maps]) @Logger.logging def at_date(self, ts): return self.t00 <= ts and ts < self.t24 @Logger.logging def time_map(self, s, e): start = datetime.datetime.fromtimestamp(s) end = datetime.datetime.fromtimestamp(e) return { 'SHOUR': start.hour, 'SMIN': '{:02}'.format(start.minute), 'EHOUR': end.hour, 'EMIN': '{:02}'.format(end.minute) }
class JournalBuilder(Logger): def __init__(self, tmpl, config): self.template = Regexplate(tmpl) self.config = config super().__init__() @Logger.logging def build(self, date, tasks, memo): self.tcb = TaskChunkBuilder(date, self.config) return self.template.substitute({ 'YEAR': date.year, 'MONTH': date.month, 'DAY': date.day, 'OPENCHUNK': self.tcb.build([t for t in tasks if t.status == Tasktory.OPEN]), 'WAITCHUNK': self.tcb.build([t for t in tasks if t.status == Tasktory.WAIT]), 'CLOSECHUNK': self.tcb.build([t for t in tasks if t.status == Tasktory.CLOSE]), 'MEMO': memo, })
class TimeTableParser(Logger): """タイムテーブル解析器""" def __init__(self, date, config): self.year = date.year self.month = date.month self.day = date.day self.template = Regexplate(config['Journal']['TIME']) self.delim = config['Journal']['DELIM'] super().__init__() return @Logger.logging def parse(self, string): phrases = [t for t in string.replace(' ', '').split(self.delim) if t] terms = [self.template.parse(p) for p in phrases] return [self.timetuple(t) for t in terms] @Logger.logging def timetuple(self, term): s = self.timestamp(term['SHOUR'], term['SMIN']) e = self.timestamp(term['EHOUR'], term['EMIN']) return (s, e-s) @Logger.logging def timestamp(self, hour, minute): return int(datetime.datetime( self.year, self.month, self.day, int(hour), int(minute), 0 ).timestamp())
class TimeTableBuilder(Logger): def __init__(self, date, config): t00 = datetime.datetime(date.year, date.month, date.day, 0, 0, 0) t24 = t00 + datetime.timedelta(1) self.t00 = int(t00.timestamp()) self.t24 = int(t24.timestamp()) self.template = Regexplate(config['Journal']['TIME']) self.delim = config['Journal']['DELIM'] super().__init__() @Logger.logging def build(self, timetable): maps = [self.time_map(s, s+t) for s, t in timetable if self.at_date(s)] return self.delim.join([self.template.substitute(m) for m in maps]) @Logger.logging def at_date(self, ts): return self.t00 <= ts and ts < self.t24 @Logger.logging def time_map(self, s, e): start = datetime.datetime.fromtimestamp(s) end = datetime.datetime.fromtimestamp(e) return {'SHOUR': start.hour, 'SMIN': '{:02}'.format(start.minute), 'EHOUR': end.hour, 'EMIN': '{:02}'.format(end.minute)}
class TaskLineParser(Logger): """タスクライン解析器""" def __init__(self, date, config): self.dlp = DeadLineParser(date, config) self.ttp = TimeTableParser(date, config) self.template = Regexplate(config['Journal']['TASKLINE']) super().__init__() return @Logger.logging def parse(self, string): attrs = self.template.parse(string) return {'PATH': attrs['PATH'], 'DEADLINE': self.dlp.parse(attrs['DEADLINE']), 'TIMETABLE': self.ttp.parse(attrs['TIMETABLE'])} @Logger.logging def match(self, string): return self.template.match(string)
class TaskLineParser(Logger): """タスクライン解析器""" def __init__(self, date, config): self.dlp = DeadLineParser(date, config) self.ttp = TimeTableParser(date, config) self.template = Regexplate(config['Journal']['TASKLINE']) super().__init__() return @Logger.logging def parse(self, string): attrs = self.template.parse(string) return { 'PATH': attrs['PATH'], 'DEADLINE': self.dlp.parse(attrs['DEADLINE']), 'TIMETABLE': self.ttp.parse(attrs['TIMETABLE']) } @Logger.logging def match(self, string): return self.template.match(string)
class DeadLineParser(Logger): """期日解析器""" def __init__(self, date, config): self.today = date self.num_reg = re.compile('^-?\d+$') self.infstr = config['Journal']['INFSTR'] self.template = Regexplate(config['Journal']['DATE']) super().__init__() return @Logger.logging def parse(self, string): # @ if string == '': return None # @@ if string == self.infstr: return float('inf') # @d m = self.num_reg.match(string) if m: return self.today.toordinal() + int(m.group()) date = self.template.parse(string) year = date['YEAR'] month = int(date['MONTH']) day = int(date['DAY']) # @mm/dd if year is None: tmp_date = datetime.date(self.today.year, month, day) year = self.today.year + (0 if tmp_date >= self.today else 1) # @yy/mm/dd elif len(year) == 2: year = int(year) + self.today.year // 100 * 100 tmp_date = datetime.date(year, month, day) year += 0 if tmp_date >= self.today else 100 # YYYY/mm/dd else: year = int(year) return datetime.date(year, month, day).toordinal()
class JournalBuilder(Logger): def __init__(self, tmpl, config): self.template = Regexplate(tmpl) self.config = config super().__init__() @Logger.logging def build(self, date, tasks, memo): self.tcb = TaskChunkBuilder(date, self.config) return self.template.substitute({ 'YEAR': date.year, 'MONTH': date.month, 'DAY': date.day, 'OPENCHUNK': self.tcb.build( [t for t in tasks if t.status == Tasktory.OPEN]), 'WAITCHUNK': self.tcb.build( [t for t in tasks if t.status == Tasktory.WAIT]), 'CLOSECHUNK': self.tcb.build( [t for t in tasks if t.status == Tasktory.CLOSE]), 'MEMO': memo, })
def __init__(self, date, config): self.root = config['Main']['ROOT'] self.dlb = DeadLineBuilder(date, config) self.ttb = TimeTableBuilder(date, config) self.template = Regexplate(config['Journal']['TASKLINE']) super().__init__()
class JournalParser(Logger): """ジャーナル解析器""" def __init__(self, tmpl, config): self.template = Regexplate(tmpl) self.config = config self.mp = MemoParser(config) super().__init__() return @Logger.logging def parse(self, text): # ジャーナルをパース if not self.template.match(text): raise JournalParserNoMatchTemplateError() attrs = self.template.parse(text) # 日付を取得 date = datetime.date(int(attrs['YEAR']), int(attrs['MONTH']), int(attrs['DAY'])) # 各タスクチャンクをパース tcp = TaskChunkParser(date, self.config) tasks = [ self.ps(d, Tasktory.OPEN) for d in tcp.parse(attrs['OPENCHUNK']) ] tasks += [ self.ps(d, Tasktory.WAIT) for d in tcp.parse(attrs['WAITCHUNK']) ] tasks += [ self.ps(d, Tasktory.CLOSE) for d in tcp.parse(attrs['CLOSECHUNK']) ] # タスクの重複チェック if self.duplicate_task(tasks): raise JournalParserDuplicateTaskError() # 作業時間の重複チェック if self.duplicate_timetable(tasks): raise JournalParserOverlapTimeTableError() # メモを取得 memo = self.mp.parse(attrs['MEMO']) return date, tasks, memo, attrs["MEMO"] @staticmethod def duplicate_task(tasks): """同じタスクが複数存在する場合Trueを返す""" return len(tasks) != len(set([t['PATH'] for t in tasks])) @staticmethod def duplicate_timetable(tasks): """作業時間に重複があればTrueを返す""" tbl = sorted(sum([t['TIMETABLE'] for t in tasks], []), key=lambda t: t[0]) return not all(convolute(lambda v, a: (sum(a) <= v[0], v), tbl, (0, 0))) @staticmethod def ps(d, s): d['STATUS'] = s return d
def __init__(self, tmpl, config): self.template = Regexplate(tmpl) self.config = config super().__init__()
def __init__(self, date, config): self.dlp = DeadLineParser(date, config) self.ttp = TimeTableParser(date, config) self.template = Regexplate(config['Journal']['TASKLINE']) super().__init__() return
def __init__(self, tmpl, config): self.template = Regexplate(tmpl) self.config = config self.mp = MemoParser(config) super().__init__() return
class JournalParser(Logger): """ジャーナル解析器""" def __init__(self, tmpl, config): self.template = Regexplate(tmpl) self.config = config self.mp = MemoParser(config) super().__init__() return @Logger.logging def parse(self, text): # ジャーナルをパース if not self.template.match(text): raise JournalParserNoMatchTemplateError() attrs = self.template.parse(text) # 日付を取得 date = datetime.date( int(attrs['YEAR']), int(attrs['MONTH']), int(attrs['DAY'])) # 各タスクチャンクをパース tcp = TaskChunkParser(date, self.config) tasks = [ self.ps(d, Tasktory.OPEN) for d in tcp.parse(attrs['OPENCHUNK'])] tasks += [ self.ps(d, Tasktory.WAIT) for d in tcp.parse(attrs['WAITCHUNK'])] tasks += [ self.ps(d, Tasktory.CLOSE) for d in tcp.parse(attrs['CLOSECHUNK'])] # タスクの重複チェック if self.duplicate_task(tasks): raise JournalParserDuplicateTaskError() # 作業時間の重複チェック if self.duplicate_timetable(tasks): raise JournalParserOverlapTimeTableError() # メモを取得 memo = self.mp.parse(attrs['MEMO']) return date, tasks, memo, attrs["MEMO"] @staticmethod def duplicate_task(tasks): """同じタスクが複数存在する場合Trueを返す""" return len(tasks) != len(set([t['PATH'] for t in tasks])) @staticmethod def duplicate_timetable(tasks): """作業時間に重複があればTrueを返す""" tbl = sorted( sum([t['TIMETABLE'] for t in tasks], []), key=lambda t: t[0]) return not all(convolute( lambda v, a: (sum(a) <= v[0], v), tbl, (0, 0) )) @staticmethod def ps(d, s): d['STATUS'] = s return d
def __init__(self, config): self.root = config['Main']['ROOT'] self.template = Regexplate(config['Journal']['MEMOTITLE']) super().__init__() return