Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thoughts on scriptability and alternative UIs #36

Closed
dontlaugh opened this issue Jul 23, 2020 · 9 comments
Closed

Thoughts on scriptability and alternative UIs #36

dontlaugh opened this issue Jul 23, 2020 · 9 comments

Comments

@dontlaugh
Copy link
Contributor

dontlaugh commented Jul 23, 2020

When I used taskwarrior I was a fan of vit, an alternative terminal UI, based on vim.

As we develop, I hope we can make sure that we can keep the following use cases in mind.

  • serialization of tasks to json or toml or yaml or awk-scriptable tab-separated table view
  • maintaining consistency around exit codes and output for commands
  • behavior when stdout is not a tty
  • customization around sorting, color (or lack of color), context
  • customization around displayed fields
  • context-independent commands, e.g. adding tasks that do not "take" a context, even if one is active globally

In principle, any application could parse the ~/.dstask directory tree, but that application would need extra configuration, and would need to re-implement dstask's logic. If I were designing a simple web app to display and update tasks, shelling out to the dstask binary in my http handlers seems like a really straightforward way to go about it.

There's no action associated with this issue, but if others have thoughts on enabling tools and scripts around dstask, I would be interested.

@naggie
Copy link
Owner

naggie commented Jul 23, 2020

I totally agree that we should think about this and I do have some ideas.

Currently the architecture is such that the cmd/ main CLI module effectively consumes the root level dstask as a library. Doing the same for other interfaces would avoid the re-implementation of the logic which could lead to bugs.

Whether the serialised/non-human output is generated by the main dstask exe or a separate one is up for consideration; I suppose it would be neater to have one EXE but I think a small amount of refactoring is required.

As for the refactoring, when I get some time I want to factor the main cmd/dstask.go as it's getting a bit unwieldy as we add commands; commands also have quite a bit in common such as ordering/display. The display part could be abstracted, too, to allow transparent handling of output format.

As for sorting, I'm not sure I'd like that to be customisable as dstask is intended to be quite opinionated rather than endlessly configurable -- though if real use cases present themselves I'd consider it.

context-independent commands, e.g. adding tasks that do not "take" a context, even if one is active globally

The -- operator ignores the current context, though did you mean something else?

@dontlaugh
Copy link
Contributor Author

-- indeed solves my use case for adding tasks with tags that aren't affected by an active context 👍

@dontlaugh
Copy link
Contributor Author

Here's an example of a function that iterates a specified list of contexts (zsh only afaict). Needs rlwrap installed.

taskreview () {
    clear
    declare -A contexts
    contexts=(
        "+work -wf"      "Work tasks"        
        "+work +wf"      "Work waiting for"
        "+personal -wf"  "Personsal tasks"
        "+personal +wf"  "Personsal waiting for"
    )

    for ctx desc in ${(kv)contexts}
    do
        while true
        do
            sh -c "dstask $ctx --"
            echo ""
            choice=$(rlwrap -t dumb -pYellow -S "[$ctx] $desc; (continue/dstask cmd) " -o cat)
            case $choice in
                c*)
                    break
                ;;
                *)
                    sh -c "dstask $choice --"
                ;;
            esac
        done
        clear
    done
}

This is currently my way to step through a predefined series of "named contexts". Moving those outside of a shell data structure would probably imply a config file format.

Also note that we need to use sh -c to shell out to dstask, because $ctx would otherwise be interpreted as a single argument, even though it contains multiple space-separated tags. Eventually we might consider tokenising the command line ourselves, beyond what go's os.Args array gives us.

@naggie
Copy link
Owner

naggie commented Jul 27, 2020

Interesting function, though I've always thought good contexts are semantic enough not to need a description. For instance, wf could be waitingfor which is annoying to type but not with completions. That said, defining the contexts in a file does enable you to switch them without recalling the tags though which is useful, as sometimes I'll miss a tag/project.

I'll give taskreview a go tomorrow, it could help me with reviewing my tasks.

Reviewing tasks is definitely a thing that needs to be done periodically in order to keep the task list manageable. Exploring mechanisms like this is worthwhile, perhaps a workflow could be integrated in dstask itself; or maybe not as I just do it manually. Not sure. I tried the review system built into taskwarrior/tasksh -- but I could never get into regular use with it though never really tried.

@dontlaugh
Copy link
Contributor Author

dontlaugh commented Aug 2, 2020

I'm working a bit today on printing to stdout for CMD_NEXT, even if stdout is not a TTY. My goal is to hack this change inline, unless a really obvious and minimal refactoring presents itself. Today I'll simply try to do something like

  • if stdout is not a tty, don't print colors
  • if stdout is a tty, print colors

Update: I think I'll attempt to extract a function or method from this method here:
https://github.com/naggie/dstask/blob/master/display.go#L11
Construction of the TaskSet seems to really be the business logic part of our code. We (usually) construct one of these per command invocation, and depending on user input, we call different builder methods on it, then call display methods on it. Actual UI rendering (writing to stdout), happens in both TaskSet methods and table.Render(). That's absolutely fine, as long as control of this rendering is centralized to TaskSet. We could imagine calling other kinds of builder methods on a TaskSet, for instance an OutputType("json"), and internally TaskSet would create a json array, rather than a table, etc.

Update 2: Actually I think I'll stop work on this for now. This is not yet a critical feature for me, personally, so it's probably mroe productive to open a new issue to talk about a TaskSet refactoring. I have a couple of ideas in mind, but I'll want others thoughts before opening a bigger PR. Opened #41

@dontlaugh
Copy link
Contributor Author

dontlaugh commented Sep 25, 2020

After the initial refactor in #43 there are some more things to be done to consolidate the construction of a sorted, filtered TaskSet into a single function. I mention this here, since the whole impetus behind my refactor was in the service of non-interactive UIs, scripts, alternative UIs, etc.

Whether alternative UIs import dstask (our root package) or wrap the dstask binary, they will be dealing with the TaskSet. This is the object that is constructed when you invoke any dstask command. It is a filtered, sorted subset of your task database.

How it is filtered, and how it is sorted, is the subject of my refactoring. I am hoping that it turns out to be a good design. :) What we have so far is a new constructor NewTaskSet, which takes some non-optional parameters (the path to the task database, state file, ids file), as well as optional parameters (which statuses to read from disk). The constructor yields a TaskSet. Right now, additional sorting and filtering takes places in each of the commands in commands.go, but I propose that we fold this sorting into new options:

func WithIDs(ids ...int) TaskSetOpt {...}
func WithTags(tags ...string) TaskSetOpt {...}
func WithProjects(projects ...string) TaskSetOpt {...}
func WithoutTags(tags ...string) TaskSetOpt {...}
func WithoutProjects(projects ..string) TaskSetOpt {...}
func SortBy(attribute, direction string) TaskSetOpt {...}

This would continue to push more logic into a single function, NewTaskSet, but the reasoning here is that since dstask is a one-shot command line program, it should always be possible to construct a particular TaskSet from a given set of tags, projects, and task db state.

@dontlaugh
Copy link
Contributor Author

dontlaugh commented Sep 26, 2020

I am working on integrating sorting and filtering into the NewTaskSet constructor. I'm sketching out Sorting first, since it seems to be a natural next step.

The diff with master can be viewed here. I'll open a PR once it actually works 😅

Update: here is a PR that introduces SortBy options #53

@Dieterbe
Copy link
Contributor

Dieterbe commented Nov 15, 2020

As for the refactoring, when I get some time I want to factor the main cmd/dstask.go as it's getting a bit unwieldy as we add commands;

Seems like much of this is mapping command names to an invocation of the respective function.
I heard some good things about urfave/cli in terms of it being a lightweight library to take care of such things, auto generating a rich help output, etc. But I don't know how well it maps to what dstask' needs are (seeing how dstask.ParseCmdLine is commonly used for most commands but there's some differences between different commands still)

alternative UIs

There's some interesting tech out there for building UI's such as tview for the terminal, wails for webapp (there was a gotimeFM episode about it. the main thing about this is how it automatically generates functions (that map to your go methods) to call them directly from javascript, or you can compile Go code to javascript to run go code as a web frontend, there's a lot more in this ecosystem but i'm not that familiar with it. I think there was even a vue/reactjs-like framework in Go. Also https://gioui.org/ is interesting (there's a gotime FM episode about it as well)

@dontlaugh
Copy link
Contributor Author

Closing this, as it's speculative and now we have a chat room for discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants