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  }