Exemplo n.º 1
0
    def validate(self, *, tags: List[str], project: Optional[str]) -> None:
        for tag in tags:
            if tag not in self._tags_by_name:
                raise utils.UsageError(f"Unknown tag {tag}")

        if project is not None and project not in self._projects_by_name:
            raise utils.UsageError(
                f"Unknown project {project}\n"
                f"Available projects: "
                f"[yellow]{', '.join(self._projects_by_name)}[/yellow]")
Exemplo n.º 2
0
    def _parse_time(self, time_str: str) -> datetime.time:
        if time_str == "now":
            now = datetime.datetime.now()
            if self.date != now.date():
                raise utils.UsageError(
                    "Can't combine 'now' with different date")
            return now.time()
        elif time_str == "/":
            return None

        try:
            return datetime.datetime.strptime(time_str, "%H:%M").time()
        except ValueError as e:
            raise utils.UsageError(str(e))
Exemplo n.º 3
0
    def _parse_date(self, arg: str) -> None:
        if self.date != datetime.datetime.now().date():
            raise utils.UsageError("Multiple dates")

        midnight = datetime.datetime.combine(datetime.datetime.now(),
                                             datetime.time())
        parsed = dateparser.parse(arg, settings={"RELATIVE_BASE": midnight})

        if parsed is None:
            raise utils.UsageError(f"Couldn't parse date {arg}")

        if parsed.time() != datetime.time():
            raise utils.UsageError(f"Date {arg} contains unexpected time")

        self.date = parsed.date()
Exemplo n.º 4
0
    def _parse_timespan(self, arg: str) -> None:
        try:
            start_str, end_str = arg.split("-")
        except ValueError:
            raise utils.UsageError(
                f"Couldn't parse timespan {arg} (too many '-')")

        start_time = self._parse_time(start_str)
        end_time = self._parse_time(end_str)

        if start_time is None and end_time is None:
            raise utils.UsageError(
                "Either start or end time needs to be given")

        self._timespans.append((start_time, end_time))
Exemplo n.º 5
0
    def _fetch_workspace_id(self) -> None:
        assert self.workspace_name is not None
        workspaces = self._api_get("workspaces")
        for workspace in workspaces:
            if workspace["name"] == self.workspace_name:
                self._workspace_id = workspace["id"]
                return

        names = [workspace["name"] for workspace in workspaces]
        raise utils.UsageError(
            f"No workspace [yellow]{self.workspace_name}[/yellow] found!\n"
            f"Available workspaces: [yellow]{', '.join(names)}[/yellow]")
Exemplo n.º 6
0
    def __init__(self, debug: bool = False, workspace: str = None) -> None:
        self._debug = debug
        try:
            key = os.environ["CLOCKIFY_API_KEY"]
        except KeyError as e:
            raise utils.UsageError(f"{e} not defined in environment")

        if workspace is None:
            try:
                self.workspace_name = os.environ["CLOCKIFY_WORKSPACE"]
            except KeyError as e:
                self.workspace_name = None
        else:
            self.workspace_name = workspace

        self._headers = {"X-Api-Key": key}
        self._user_id = None
        self._workspace_id = None

        self._projects_by_name: Dict[str, str] = {}
        self._projects_by_id: Dict[str, str] = {}

        self._tags_by_name: Dict[str, str] = {}
        self._tags_by_id: Dict[str, str] = {}
Exemplo n.º 7
0
    def parse(self, args: List[str] = None) -> None:
        parsed = self._parser.parse_args(args)
        self.debug = parsed.debug
        self.pager = not parsed.no_pager
        self.conky = parsed.conky
        self.conky_error_color = parsed.conky_error_color

        time_pattern = r"(\d\d?:\d\d?|/|now)"
        timespan_re = re.compile(f"{time_pattern}-{time_pattern}")

        for arg in parsed.inputs:
            if timespan_re.fullmatch(arg):
                self._parse_timespan(arg)
            elif arg[0] == "+":
                self._parse_tag(arg[1:])
            elif arg[0] == "@":
                self._parse_project(arg[1:])
            elif arg[0] == "$":
                self._parse_billable(arg[1:])
            elif arg[0] == ".":
                self._parse_date(arg[1:])
            elif arg[0] == "^":
                self._parse_workspace(arg[1:])
            elif arg == "start":
                self._parse_timespan("now-/")
            elif arg == "stop":
                self._parse_timespan("/-now")
            else:
                self._parse_description(arg)

        self.entries = [
            client.Entry(
                start=self._combine_date(start_time),
                end=self._combine_date(end_time),
                description=self._description,
                billable=self._billable,
                project=self.project,
            ) for (start_time, end_time) in self._timespans
        ]

        if parsed.dump:
            try:
                self.dump = datetime.datetime.strptime(parsed.dump, "%Y-%m")
            except ValueError:
                raise utils.UsageError(
                    f"Unparseable month {parsed.dump} (use YYYY-MM)")

        has_new_entries = any(entry.start is not None
                              for entry in self.entries)
        if not has_new_entries:
            if self._description:
                raise utils.UsageError(
                    f"Description {self._description} given without new entries"
                )
            elif self._billable:
                raise utils.UsageError("Billable given without new entries")
            elif self.project:
                raise utils.UsageError(
                    f"Project {self.project} given without new entries")
            elif self.tags:
                raise utils.UsageError(
                    f"Tags {self.tags} given without new entries")

        if parsed.dump and self.date != datetime.datetime.now().date():
            raise utils.UsageError(f"Date {self.date} given with --dump")
Exemplo n.º 8
0
 def _parse_billable(self, arg: str) -> None:
     if arg:
         raise utils.UsageError(f"Invalid billable arg {arg}")
     self._billable = True
Exemplo n.º 9
0
 def _parse_workspace(self, arg: str) -> None:
     if self.workspace is not None:
         raise utils.UsageError(
             f"Multiple workspaces: {self.workspace}, {arg}")
     self.workspace = arg