github.com/aretext/aretext@v1.3.0/syntax/languages/todotxt.go (about) 1 package languages 2 3 import ( 4 "unicode" 5 6 "github.com/aretext/aretext/syntax/parser" 7 ) 8 9 type todoTxtParseState uint8 10 11 const ( 12 todoTxtStartOfLineState = todoTxtParseState(iota) 13 todoTxtWithinLineState 14 ) 15 16 func (s todoTxtParseState) Equals(other parser.State) bool { 17 otherState, ok := other.(todoTxtParseState) 18 return ok && s == otherState 19 } 20 21 const ( 22 todoTxtCompletedTaskRole = parser.TokenRoleCustom1 23 todoTxtPriorityRole = parser.TokenRoleCustom2 24 todoTxtDateRole = parser.TokenRoleCustom3 25 todoTxtProjectTagRole = parser.TokenRoleCustom4 26 todoTxtContextTagRole = parser.TokenRoleCustom5 27 todoTxtKeyTagRole = parser.TokenRoleCustom6 28 todoTxtValTagRole = parser.TokenRoleCustom7 29 ) 30 31 // TodoTxtParseFunc returns a parse func for the todo.txt file format. 32 // See https://github.com/todotxt/todo.txt for details. 33 func TodoTxtParseFunc() parser.Func { 34 // Helper parse funcs. 35 consumeToEndOfWord := consumeRunesLike(func(r rune) bool { 36 return !unicode.IsSpace(r) 37 }) 38 39 consumeNumbers := func(n int) parser.Func { 40 return func(iter parser.TrackingRuneIter, state parser.State) parser.Result { 41 for i := 0; i < n; i++ { 42 r, err := iter.NextRune() 43 if !(err == nil && (r >= '0' && r <= '9')) { 44 return parser.FailedResult 45 } 46 } 47 return parser.Result{ 48 NumConsumed: uint64(n), 49 NextState: state, 50 } 51 } 52 } 53 54 // Parse a completed task. This is an "x " at the start of a line, then all chars to the end of the line. 55 // Transitions: todoTxtStartOfLineState -> todoTxtStartOfLineState 56 parseCompletedTask := matchState( 57 todoTxtStartOfLineState, 58 consumeString("x "). 59 ThenMaybe(consumeToNextLineFeed). 60 Map(recognizeToken(todoTxtCompletedTaskRole)), 61 ) 62 63 // Parse a priority, like "(A)" at the start of a line, followed by a space. 64 // Transitions: todoTxtStartOfLineState -> todoTxtWithinLineState 65 parsePriority := matchState( 66 todoTxtStartOfLineState, 67 consumeString("("). 68 Then(consumeSingleRuneLike(func(r rune) bool { return r >= 'A' && r <= 'Z' })). 69 Then(consumeString(")")). 70 Map(recognizeToken(todoTxtPriorityRole)). 71 Then(consumeString(" ")). 72 Map(setState(todoTxtWithinLineState))) 73 74 // Parse date formatted as YYYY-MM-DD. 75 // Transitions: any -> todoTxtWithinLineState 76 parseDate := consumeNumbers(4). 77 Then(consumeString("-")). 78 Then(consumeNumbers(2)). 79 Then(consumeString("-")). 80 Then(consumeNumbers(2)). 81 Map(recognizeToken(todoTxtDateRole)). 82 Map(setState(todoTxtWithinLineState)) 83 84 // Parse project tag like "+project". 85 // Transitions: any -> todoTxtWithinLineState 86 parseProjectTag := consumeString("+"). 87 Then(consumeToEndOfWord). 88 Map(recognizeToken(todoTxtProjectTagRole)). 89 Map(setState(todoTxtWithinLineState)) 90 91 // Parse context tag like "@context" 92 // Transitions: any -> todoTxtWithinLineState 93 parseContextTag := consumeString("@"). 94 Then(consumeToEndOfWord). 95 Map(recognizeToken(todoTxtContextTagRole)). 96 Map(setState(todoTxtWithinLineState)) 97 98 // Parse a key-value tag like "key:val" 99 // Transitions: any -> todoTxtWithinLineState 100 parseKey := consumeRunesLike(func(r rune) bool { 101 return !unicode.IsSpace(r) && r != ':' 102 }).Then(consumeString(":")). 103 Map(recognizeToken(todoTxtKeyTagRole)) 104 105 parseVal := consumeToEndOfWord. 106 Map(recognizeToken(todoTxtValTagRole)) 107 108 parseKeyValTag := parseKey.Then(parseVal). 109 Map(setState(todoTxtWithinLineState)) 110 111 // Fallback to transition from todoTxtStartOfLineState -> todoTxtWithinLineState 112 // if none of the other parse funcs succeed. 113 parseOtherStartOfLine := matchState( 114 todoTxtStartOfLineState, 115 consumeSingleRuneLike(func(r rune) bool { return r != '\n' }). 116 Map(setState(todoTxtWithinLineState)), 117 ) 118 119 // Transition back to todoTxtStartOfLineState once we hit a newline. 120 parseNewline := consumeString("\n").Map(setState(todoTxtStartOfLineState)) 121 122 // Construct parse func for incomplete tasks. 123 parseIncompleteTask := parsePriority. 124 Or(parseDate). 125 Or(parseProjectTag). 126 Or(parseContextTag). 127 Or(parseKeyValTag). 128 Or(consumeToEndOfWord). 129 Or(parseOtherStartOfLine). 130 Or(parseNewline) 131 132 // Construct the final parse func (either complete or incomplete tasks). 133 return initialState(todoTxtStartOfLineState, parseCompletedTask.Or(parseIncompleteTask)) 134 }