Example #1
0
def import_structure(file=None, string=None, request=None, scope=None):
    """
    Parses either an org-mode file or an org-mode string and saves the
    resulting heirerarchy to the OrgWolf models in the gtd module.
    # TODO: rewrite this without PyOrgMode
    """
    # We want pre & post save signals skipped
    if file and string:
        raise AttributeError("Please supply either a file or a string, not both.")
    elif string:
        source = StringIO(string)
    elif file:
        source = io.open(file, 'r', encoding='utf8')
        if not scope: # Automatic scope detection
            scope_match = re.search(r'([^/]+)\.org', file)
            if scope_match:
                scope_string = scope_match.groups()[0]
                scope = Scope.objects.filter(name__iexact=scope_string)
                if scope.exists():
                    scope = scope[0]
                else:
                    scope = Scope(name=scope_string, display=scope_string)
                    scope.save()
    else:
        raise AttributeError("Please supply a file or a string")
    # First, build a list of dictionaries that hold the pieces of each line.
    data_list = []
    if request:
        current_user = request.user
    else:
        current_user = User.objects.get(id=1)
    for line in source:
        data_list.append({'original': line})
    def save_text(parent, text):
        # A helper function the tries to save some new text
        if parent:
            parent.text = current_text
            parent.save()
        elif current_text: # Warn the user about some dropped text
            print("---")
            print("Warning, dropping text (no parent node)")
            print("Text: %s", current_text)
            print("---")
    # Now go through each line and see if it matches a regex
    current_indent = 0 # counter
    current_order = 0
    parent_stack = []
    todo_state_list = TodoState.objects.all() # Todo: filter by current user
    current_text = ''
    for line in data_list:
        heading_match = HEADING_RE.search(line['original'].strip("\n"))
        if heading_match: # It's a heading
            line_indent = len(heading_match.groups()[0])
            line['todo'] = heading_match.groups()[1]
            line['priority'] = heading_match.groups()[2]
            line['heading'] = heading_match.groups()[3]
            line['tag_string'] = heading_match.groups()[4]
            new_node = Node()
            if line_indent > current_indent: # New child
                # TODO: what if the user skips a level
                current_indent = current_indent + 1
                current_order = 0
                # Save any text associated with the parent
                parent = getattr(parent_stack.head, 'value', None)
                save_text(parent, current_text)
                current_text = ''
            elif line_indent == current_indent: # Another child
                # Save any text associated with the parent
                parent = getattr(parent_stack.head, 'value', None)
                save_text(parent, current_text)
                current_text = ''
                # Adjust the parent
                parent_stack.pop()
            elif line_indent < current_indent: # Back up to parent
                # Save any text associated with the parent
                parent = getattr(parent_stack.head, 'value', None)
                save_text(parent, current_text)
                current_text = ''
                parent_stack.pop()
                for x in range(current_indent - line_indent):
                    # Move up the stack
                    current_order = parent_stack.head.value.order
                    parent_stack.pop()
                    current_indent = current_indent - 1
            # See if the 'todo' captured by the regex matches a current TodoState,
            #   if not then it's parts of the heading # TODO: find a way to not destroy whitespace
            if line['todo']:
                found = False
                for todo_state in todo_state_list:
                    if todo_state.abbreviation.lower() == line['todo'].lower():
                        new_node.todo_state = todo_state
                        found = True
                        break
                if found == False:
                    if line['heading']:
                        line['heading'] = line['todo'] + ' ' + str(line['heading'])
                    else:
                        line['heading'] = line['todo']
            if current_indent > 1:
                new_node.parent = parent_stack.head.value
            if line['heading']:
                new_node.title = line['heading']
            else:
                new_node.title = ''
            new_node.owner = current_user
            new_node.order = current_order + 10
            if line['priority']:
                new_node.priority = line['priority']
            else:
                new_node.priority = ''
            if line['tag_string']:
                new_node.tag_string = line['tag_string']
            else:
                new_node.tag_string = ''
            new_node.auto_close = False # Disable closed timestamp
            new_node.save()
            # Add scope (passed as argument or auto-detected)
            if scope:
                new_node.scope.add(scope)
            # Update current state variables
            current_order = new_node.order
            parent_stack.push(new_node)
        else: # Some sort of text item
            # Test to see if it's a scheduled, deadline or closed modifier
            time_sensitive_match = TIME_SENSITIVE_RE.findall(line['original'])
            if time_sensitive_match:
                parent = parent_stack.head.value
                for match in time_sensitive_match:
                    # Bump a match for "CLOSED:" up to the 0 and 1 position
                    if match[2] and match[3]:
                        match = (match[2], match[3], "", "")
                    date_match = DATE_RE.search(match[1]).groups()
                    if date_match:
                        # Set some variables to make things easier to read
                        year = int(date_match[0])
                        month = int(date_match[1])
                        day = int(date_match[2])
                        if date_match[4]:
                            hour = int(date_match[4])
                        else:
                            hour = 0
                        if date_match[5]:
                            minute = int(date_match[5])
                        else:
                            minute = 0
                        naive_datetime = datetime(year,
                                                month,
                                                day,
                                                hour,
                                                minute)
                        new_datetime = timezone.get_current_timezone().localize(naive_datetime) # TODO: set to user's preferred timezone
                        if date_match[4] and date_match[5]:
                            time_specific = True
                        else:
                            time_specific = False
                        if date_match[6]: # repeating
                            parent.repeats = True
                            parent.repeating_number = date_match[6][1]
                            parent.repeating_unit = date_match[6][2]
                            if date_match[6][0] == "+":
                                parent.repeats_from_completion = False
                            elif date_match[6][0] == ".":
                                parent.repeats_from_completion = True
                        # Set the appropriate fields
                        if match[0] == "SCHEDULED:":
                            parent.scheduled_date = new_datetime.date()
                            if time_specific:
                                parent.scheduled_time = new_datetime.time()
                        elif match[0] == "DEADLINE:":
                            parent.deadline_date = new_datetime.date()
                            if time_specific:
                                parent.deadline_time = new_datetime.date()
                        elif match[0] == "CLOSED:":
                            parent.closed = new_datetime
                        parent.auto_close = False # Disable closed timestamp
                        parent.save()
            else: # It's just a regular text item
                current_text += line['original']
Example #2
0
def import_structure(file=None, string=None, request=None, scope=None):
    """
    Parses either an org-mode file or an org-mode string and saves the
    resulting heirerarchy to the OrgWolf models in the gtd module.
    # TODO: rewrite this without PyOrgMode
    """
    # We want pre & post save signals skipped
    if file and string:
        raise AttributeError(
            "Please supply either a file or a string, not both.")
    elif string:
        source = StringIO(string)
    elif file:
        source = io.open(file, 'r', encoding='utf8')
        if not scope:  # Automatic scope detection
            scope_match = re.search(r'([^/]+)\.org', file)
            if scope_match:
                scope_string = scope_match.groups()[0]
                scope = Scope.objects.filter(name__iexact=scope_string)
                if scope.exists():
                    scope = scope[0]
                else:
                    scope = Scope(name=scope_string, display=scope_string)
                    scope.save()
    else:
        raise AttributeError("Please supply a file or a string")
    # First, build a list of dictionaries that hold the pieces of each line.
    data_list = []
    if request:
        current_user = request.user
    else:
        current_user = User.objects.get(id=1)
    for line in source:
        data_list.append({'original': line})

    def save_text(parent, text):
        # A helper function the tries to save some new text
        if parent:
            parent.text = current_text
            parent.save()
        elif current_text:  # Warn the user about some dropped text
            print("---")
            print("Warning, dropping text (no parent node)")
            print("Text: %s", current_text)
            print("---")

    # Now go through each line and see if it matches a regex
    current_indent = 0  # counter
    current_order = 0
    parent_stack = []
    todo_state_list = TodoState.objects.all()  # Todo: filter by current user
    current_text = ''
    for line in data_list:
        heading_match = HEADING_RE.search(line['original'].strip("\n"))
        if heading_match:  # It's a heading
            line_indent = len(heading_match.groups()[0])
            line['todo'] = heading_match.groups()[1]
            line['priority'] = heading_match.groups()[2]
            line['heading'] = heading_match.groups()[3]
            line['tag_string'] = heading_match.groups()[4]
            new_node = Node()
            if line_indent > current_indent:  # New child
                # TODO: what if the user skips a level
                current_indent = current_indent + 1
                current_order = 0
                # Save any text associated with the parent
                parent = getattr(parent_stack.head, 'value', None)
                save_text(parent, current_text)
                current_text = ''
            elif line_indent == current_indent:  # Another child
                # Save any text associated with the parent
                parent = getattr(parent_stack.head, 'value', None)
                save_text(parent, current_text)
                current_text = ''
                # Adjust the parent
                parent_stack.pop()
            elif line_indent < current_indent:  # Back up to parent
                # Save any text associated with the parent
                parent = getattr(parent_stack.head, 'value', None)
                save_text(parent, current_text)
                current_text = ''
                parent_stack.pop()
                for x in range(current_indent - line_indent):
                    # Move up the stack
                    current_order = parent_stack.head.value.order
                    parent_stack.pop()
                    current_indent = current_indent - 1
            # See if the 'todo' captured by the regex matches a current TodoState,
            #   if not then it's parts of the heading # TODO: find a way to not destroy whitespace
            if line['todo']:
                found = False
                for todo_state in todo_state_list:
                    if todo_state.abbreviation.lower() == line['todo'].lower():
                        new_node.todo_state = todo_state
                        found = True
                        break
                if found == False:
                    if line['heading']:
                        line['heading'] = line['todo'] + ' ' + str(
                            line['heading'])
                    else:
                        line['heading'] = line['todo']
            if current_indent > 1:
                new_node.parent = parent_stack.head.value
            if line['heading']:
                new_node.title = line['heading']
            else:
                new_node.title = ''
            new_node.owner = current_user
            new_node.order = current_order + 10
            if line['priority']:
                new_node.priority = line['priority']
            else:
                new_node.priority = ''
            if line['tag_string']:
                new_node.tag_string = line['tag_string']
            else:
                new_node.tag_string = ''
            new_node.auto_close = False  # Disable closed timestamp
            new_node.save()
            # Add scope (passed as argument or auto-detected)
            if scope:
                new_node.scope.add(scope)
            # Update current state variables
            current_order = new_node.order
            parent_stack.push(new_node)
        else:  # Some sort of text item
            # Test to see if it's a scheduled, deadline or closed modifier
            time_sensitive_match = TIME_SENSITIVE_RE.findall(line['original'])
            if time_sensitive_match:
                parent = parent_stack.head.value
                for match in time_sensitive_match:
                    # Bump a match for "CLOSED:" up to the 0 and 1 position
                    if match[2] and match[3]:
                        match = (match[2], match[3], "", "")
                    date_match = DATE_RE.search(match[1]).groups()
                    if date_match:
                        # Set some variables to make things easier to read
                        year = int(date_match[0])
                        month = int(date_match[1])
                        day = int(date_match[2])
                        if date_match[4]:
                            hour = int(date_match[4])
                        else:
                            hour = 0
                        if date_match[5]:
                            minute = int(date_match[5])
                        else:
                            minute = 0
                        naive_datetime = datetime(year, month, day, hour,
                                                  minute)
                        new_datetime = timezone.get_current_timezone(
                        ).localize(naive_datetime
                                   )  # TODO: set to user's preferred timezone
                        if date_match[4] and date_match[5]:
                            time_specific = True
                        else:
                            time_specific = False
                        if date_match[6]:  # repeating
                            parent.repeats = True
                            parent.repeating_number = date_match[6][1]
                            parent.repeating_unit = date_match[6][2]
                            if date_match[6][0] == "+":
                                parent.repeats_from_completion = False
                            elif date_match[6][0] == ".":
                                parent.repeats_from_completion = True
                        # Set the appropriate fields
                        if match[0] == "SCHEDULED:":
                            parent.scheduled_date = new_datetime.date()
                            if time_specific:
                                parent.scheduled_time = new_datetime.time()
                        elif match[0] == "DEADLINE:":
                            parent.deadline_date = new_datetime.date()
                            if time_specific:
                                parent.deadline_time = new_datetime.date()
                        elif match[0] == "CLOSED:":
                            parent.closed = new_datetime
                        parent.auto_close = False  # Disable closed timestamp
                        parent.save()
            else:  # It's just a regular text item
                current_text += line['original']