HaskerDeux

Check-in [ab44c0ddfb]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Enable commands for any date

Previously Haskerdeux worked around the assumption of "today" and
although you could move todos to any date, view todos for tomorrow and
put off things until tomorrow there was no way to, say, delete a todo on
a random date or list todos from a selected day next week.

This does change the syntax a little bit. Supplying a date argument is
now compulsory, but it "understands" the dates of "today" and
"tomorrow".

The command "today" is now called "todos" since it can list todos for
any date. The command "tomorrow" has been removed similarly. Variables
have also been renamed to suit (`todays_date` is now `todos_date`).

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | origin/issue1 | trunk | master
Files: files | file ages | folders
SHA3-256: ab44c0ddfb1748797b3f16155647b11c6b51460afe9fd327b5163c2927e49492
User & Date: base@atomicules.co.uk 2017-02-26 15:02:20
Context
2017-10-22
10:44
Enable auto-login if token has expired

Note: I am aware this is terribly unhaskelly, but making it work was my
main aim, making it less terrible will come. There's also a fair bit of
repetition here.

This tries to get a list of todos whatever the command and checks the
response for an invalid csrf token, if that occurs it removes the file
and calls login again.

This means that if you are actually calling for a list of todos it ends
up doing it twice which is a bit rubbish really. It would be nicer for
it to:

1. Call whatever command is asked for
2. Catch a invalid_csrf_token in the response
3. Re-login
4. Continue to call the original command

It might never be that good, but I do hope at least to get rid of all
the if/then/else though. check-in: e53b9e78fb user: base@atomicules.co.uk tags: origin/issue1, trunk, master

2017-02-26
15:02
Enable commands for any date

Previously Haskerdeux worked around the assumption of "today" and
although you could move todos to any date, view todos for tomorrow and
put off things until tomorrow there was no way to, say, delete a todo on
a random date or list todos from a selected day next week.

This does change the syntax a little bit. Supplying a date argument is
now compulsory, but it "understands" the dates of "today" and
"tomorrow".

The command "today" is now called "todos" since it can list todos for
any date. The command "tomorrow" has been removed similarly. Variables
have also been renamed to suit (`todays_date` is now `todos_date`). check-in: ab44c0ddfb user: base@atomicules.co.uk tags: origin/issue1, trunk, master

2017-01-29
14:33
Add a tomorrow command to fetch tomorrow's todos

Since there is no way to interact with todos from tomorrow don't number
these when outputing them. Rename some variable names from todays_date
to date since they can be any date really.

Really I should think about how to make this work nicely with todos on
any date, but for now this will do. check-in: 674b1e8fba user: base@atomicules.co.uk tags: origin/issue1, trunk, master

Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to README.markdown.

17
18
19
20
21
22
23






24
25

26
27
28
29
30


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
- Text.JSON

I also suggest you compile it to use it - it's much faster to use that way. Just do `ghc --make haskerdeux.hs`. If you don't compile it then replace `./haskerdeux` in the examples below with `runhaskell haskerdeux.hs`.


##Features/Commands







Haskerdeux currently includes the following commands: 


###Today

For listing today's todos only (as that is all I want to see)

`haskerdeux today <username> <password>`



This returns a numbered list, like so:

	0 - Write README for Haskerdeux
	1 - Write Blog post about Haskerdeux
	2 - Split development work in different branch
	3 - Perhaps do some actual work you are paid to do

You can use those numbers with the PutOff and CheckOff commands.

###New

For creating new tasks

`haskerdeux new "<A todo item>"`


###PutOff

For putting off a task until tomorrow.

`haskerdeux putoff <tasknumber from today list>`

E.g:

`haskerdeux putoff 3`

###MoveTo

For moving a task to another date.

`haskerdeux moveto <tasknumber from today list> <date in YYYY:MM:DD>`

E.g:

`haskerdeux moveto 11 2012-09-01`

###CrossOff

For marking a task as complete

`haskerdeux crossoff <tasknumber from today list>`

###Delete

For completely removing a task

`haskerdeux delete <tasknumber from today list>`


##Using .netrc For Storing Username and Password

It's compulsory. It used to support passing username/password as command line args, but no more. The `<username>` and `<password>` are read from `.netrc`. Just add an entry to `.netrc` as follows:

	machine teuxdeux.com







>
>
>
>
>
>
|

>
|

|

|
>
>








|





|
>



|

|



|





|



|





|





|







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
- Text.JSON

I also suggest you compile it to use it - it's much faster to use that way. Just do `ghc --make haskerdeux.hs`. If you don't compile it then replace `./haskerdeux` in the examples below with `runhaskell haskerdeux.hs`.


##Features/Commands

Haskerdeux works in the following way:

`haskerdeux <date> <command> <optional args>`

I.e. the date supplied is the date the commands act on. It understands "today", "tomorrow" and dates in "YYYY-MM-DD" format. All commands recognise and require a date.

It includes the following commands: 


###Todos

For listing todos only (as that is all I want to see)

`haskerdeux today todos`
`haskerdeux tomorrow todos`
`haskerdeux 2017-02-28 todos`

This returns a numbered list, like so:

	0 - Write README for Haskerdeux
	1 - Write Blog post about Haskerdeux
	2 - Split development work in different branch
	3 - Perhaps do some actual work you are paid to do

You can use those numbers with the PutOff and CrossOff commands, etc.

###New

For creating new tasks

`haskerdeux today new "<A todo item for today>"`
`haskerdeux tomorrow new "<A todo item for tomorrow>"`

###PutOff

For putting off a task until the next day.

`haskerdeux today putoff <tasknumber from todos list>`

E.g:

`haskerdeux today putoff 3`

###MoveTo

For moving a task to another date.

`haskerdeux today moveto <tasknumber from todos list> <date in YYYY:MM:DD>`

E.g:

`haskerdeux today moveto 11 2012-09-01`

###CrossOff

For marking a task as complete

`haskerdeux today crossoff <tasknumber from todos list>`

###Delete

For completely removing a task

`haskerdeux today delete <tasknumber from todos list>`


##Using .netrc For Storing Username and Password

It's compulsory. It used to support passing username/password as command line args, but no more. The `<username>` and `<password>` are read from `.netrc`. Just add an entry to `.netrc` as follows:

	machine teuxdeux.com

Changes to haskerdeux.hs.

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

34
35
36




37
38
39
40
41
42
43
44
45
46
47
48
...
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import System.Time
import System.Locale (defaultTimeLocale)
import System.Directory
import Network.URI.Encode --need to install

--Note to self: to run you type `runhaskell haskerdeux.hs test "me" "this" "that"`, etc
dispatch :: String -> (String, [String]) -> IO()
dispatch "today" = today
dispatch "tomorrow" = tomorrow
dispatch "new" = new
dispatch "crossoff" = crossoff
dispatch "putoff" = putoff
dispatch "moveto" = moveto
dispatch "delete" = remove


main = do 

	time <- getClockTime >>= toCalendarTime --https://wiki.haskell.org/Unix_tools/Date
	let todays_date = formatCalendarTime defaultTimeLocale "%Y-%m-%d" time
	(command:argList) <- getArgs




	when ((command `elem` ["today", "tomorrow"] && Data.List.null argList) || (command `elem` ["new", "crossoff", "putoff", "delete"] && length argList == 1) || (command == "moveto" && length argList == 2)) $ do
		username <- fmap fst readnetrc
		password <- fmap snd readnetrc
		token <- login [username, password]
		dispatch command (token, todays_date:argList)


readnetrc = do
	home <- getHomeDirectory
	netrc <- lines Control.Applicative.<$> readFile (home ++ "/.netrc")
	let netrc' = dropWhile (not . ("teuxdeux" `isInfixOf`)) netrc
	let (username, password) = if "login" `isInfixOf` head netrc'
................................................................................
			--can probably use one post?
			let curlheader = "X-CSRF-Token: " ++ token
			let curlpostfields = "username=" ++ username ++ "&password=" ++ password ++ "&authenticity_token=" ++ token
			body <- readProcess "curl" ["-s", "-L", "-c", "haskerdeux.cookies", "-b", "haskerdeux.cookies", "-H", curlheader, "-d", curlpostfields, "https://teuxdeux.com/login"] []
			return token


today (token, [todays_date]) = do
	tdsf <- curlget (token, todays_date)
	putStr $ unlines $ zipWith (\n td -> show n ++ " - " ++ td) [0..] $ Data.List.map text tdsf --numbering from LYAH


tomorrow (token, [todays_date]) = do
	let tomorrows_date = show (addDays 1 $ read todays_date::Data.Time.Day)
	tdsf <- curlget (token, tomorrows_date)
	putStr $ unlines $ Data.List.map ("- " ++) $ Data.List.map text tdsf


new (token, [todays_date, todo]) = do 
	let encodedtodo = Network.URI.Encode.encode todo
	curlpost (token, [todays_date, "text", todo, "https://teuxdeux.com/api/v1/todos/", "Added!"]) Nothing


crossoff (token, [todays_date, number]) =
	curlput (token, [todays_date, "{ \"done\": true }", "https://teuxdeux.com/api/v1/todos/", "Crossed Off!"]) number


putoff (token, [todays_date, number]) = do
	let tomorrows_date = show (addDays 1 $ read todays_date::Data.Time.Day)
	curlpost (token, [todays_date, "current_date", tomorrows_date, "https://teuxdeux.com/api/v1/todos/reposition/", "Put Off!"]) (Just number)


moveto (token, [todays_date, number, new_date]) =
	--TODO: Need to figure out moving to bottom of a list
	curlpost (token, [todays_date, "current_date", new_date, "https://teuxdeux.com/api/v1/todos/reposition", "Moved!"]) (Just number)


remove (token, [todays_date, number]) =
	curldelete (token, [todays_date, "https://teuxdeux.com/api/v1/todos/", "Deleted!"]) number


--Thanks to http://www.amateurtopologist.com/blog/2010/11/05/a-haskell-newbies-guide-to-text-json/ and http://hpaste.org/41263/parsing_json_with_textjson
data Teuxdeux = Teuxdeux {
    id :: Integer,
	current_date :: String, 
	text :: String,
	done :: Bool
} deriving (Eq, Show, Data, Typeable) 







|
<








>


<
>
>
>
>
|



|







 







|
|



<
<
<
<
<
<
|

|


|
|


|
|
|


|

|


|
|









17
18
19
20
21
22
23
24

25
26
27
28
29
30
31
32
33
34
35

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
...
129
130
131
132
133
134
135
136
137
138
139
140






141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import System.Time
import System.Locale (defaultTimeLocale)
import System.Directory
import Network.URI.Encode --need to install

--Note to self: to run you type `runhaskell haskerdeux.hs test "me" "this" "that"`, etc
dispatch :: String -> (String, [String]) -> IO()
dispatch "todos" = todos

dispatch "new" = new
dispatch "crossoff" = crossoff
dispatch "putoff" = putoff
dispatch "moveto" = moveto
dispatch "delete" = remove


main = do 
	(date:command:argList) <- getArgs
	time <- getClockTime >>= toCalendarTime --https://wiki.haskell.org/Unix_tools/Date
	let todays_date = formatCalendarTime defaultTimeLocale "%Y-%m-%d" time

	let tomorrows_date = show (addDays 1 $ read todays_date::Data.Time.Day)
	let  todos_date | date == "today" = todays_date
	                | date == "tomorrow" = tomorrows_date
	                | otherwise = date
	when ((command `elem` ["todos"] && Data.List.null argList) || (command `elem` ["new", "crossoff", "putoff", "delete"] && length argList == 1) || (command == "moveto" && length argList == 2)) $ do
		username <- fmap fst readnetrc
		password <- fmap snd readnetrc
		token <- login [username, password]
		dispatch command (token, todos_date:argList)


readnetrc = do
	home <- getHomeDirectory
	netrc <- lines Control.Applicative.<$> readFile (home ++ "/.netrc")
	let netrc' = dropWhile (not . ("teuxdeux" `isInfixOf`)) netrc
	let (username, password) = if "login" `isInfixOf` head netrc'
................................................................................
			--can probably use one post?
			let curlheader = "X-CSRF-Token: " ++ token
			let curlpostfields = "username=" ++ username ++ "&password=" ++ password ++ "&authenticity_token=" ++ token
			body <- readProcess "curl" ["-s", "-L", "-c", "haskerdeux.cookies", "-b", "haskerdeux.cookies", "-H", curlheader, "-d", curlpostfields, "https://teuxdeux.com/login"] []
			return token


todos (token, [todos_date]) = do
	tdsf <- curlget (token, todos_date)
	putStr $ unlines $ zipWith (\n td -> show n ++ " - " ++ td) [0..] $ Data.List.map text tdsf --numbering from LYAH








new (token, [todos_date, todo]) = do 
	let encodedtodo = Network.URI.Encode.encode todo
	curlpost (token, [todos_date, "text", todo, "https://teuxdeux.com/api/v1/todos/", "Added!"]) Nothing


crossoff (token, [todos_date, number]) =
	curlput (token, [todos_date, "{ \"done\": true }", "https://teuxdeux.com/api/v1/todos/", "Crossed Off!"]) number


putoff (token, [todos_date, number]) = do
	let tomorrows_date = show (addDays 1 $ read todos_date::Data.Time.Day)
	curlpost (token, [todos_date, "current_date", tomorrows_date, "https://teuxdeux.com/api/v1/todos/reposition/", "Put Off!"]) (Just number)


moveto (token, [todos_date, number, new_date]) =
	--TODO: Need to figure out moving to bottom of a list
	curlpost (token, [todos_date, "current_date", new_date, "https://teuxdeux.com/api/v1/todos/reposition", "Moved!"]) (Just number)


remove (token, [todos_date, number]) =
	curldelete (token, [todos_date, "https://teuxdeux.com/api/v1/todos/", "Deleted!"]) number


--Thanks to http://www.amateurtopologist.com/blog/2010/11/05/a-haskell-newbies-guide-to-text-json/ and http://hpaste.org/41263/parsing_json_with_textjson
data Teuxdeux = Teuxdeux {
    id :: Integer,
	current_date :: String, 
	text :: String,
	done :: Bool
} deriving (Eq, Show, Data, Typeable)