├── .gitignore ├── LICENSE ├── README.md ├── doc.go ├── todo.go ├── todo_test.go └── wercker.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Stretchr, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | todo 2 | ==== 3 | 4 | todo is a simple package that helps you remember the DO in TODO. Oftentimes developers write a TODO and forget about it. The todo package helps you remember those TODOs by allowing you to set an expiration date with the message. If that expiration date is hit, the program will exit and print the message, along with the location of the TODO. 5 | 6 | [![wercker status](https://app.wercker.com/status/5c46f31b4c8b2ba071426379ef692894/m "wercker status")](https://app.wercker.com/project/bykey/5c46f31b4c8b2ba071426379ef692894) 7 | 8 | ##Usage 9 | 10 | Usage of todo is very simple. Here is a complete example: 11 | 12 | ```go 13 | package main 14 | 15 | import "github.com/stretchr/todo" 16 | 17 | func main() { 18 | to.Do("2014-May-21","Write the main function") 19 | } 20 | ``` 21 | 22 | ## Enabling 23 | 24 | By default, todo is not enabled. This is to prevent accidental termination of the application in a production environment. 25 | 26 | When disabled, the function it calls is empty. There is still a bit of overhead in passing the arguments by value, however. 27 | 28 | In order to enable the todo package, set `TODO_ENABLED=1` as an environment variable. We recommend setting it in `.bashrc` or `.bash_profile` or similar so it will always be set. 29 | 30 | As a result of this requirement, the tests also depend on this environment variable being set. Use `TODO_ENABLED=1 go test` to test. Simlarly, to run the benchmarks, use `TODO_ENABLED=1 go test -bench=.`. 31 | 32 | ## Behavior 33 | 34 | The `Do(by, msg string)` function takes a "short form" date (`YYYY-Month-DD`) as the first argument. Internally, the date is parsed and used for comparisons. If the date fails to parse, the function panics to inform the programmer of the error. 35 | 36 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package to provides enforceable TODOs 2 | package to 3 | -------------------------------------------------------------------------------- /todo.go: -------------------------------------------------------------------------------- 1 | package to 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // expire is a separate function variable to enable testing 12 | var expire = func(msg string) { 13 | fmt.Printf("%s TODO Expired: - %s\n", callerInfo(), msg) 14 | os.Exit(1) 15 | } 16 | 17 | // shortForm is the format string expected by time.Parse 18 | const shortForm = "2006-Jan-02" 19 | 20 | // enabledKey is the string for the environment variable that must be set to activate todo 21 | const enabledKey = "TODO_ENABLED" 22 | 23 | // times caches the parsed time.Time arguments so subsequent calls will be quicker. 24 | var times = map[string]time.Time{} 25 | 26 | // Do checks to see if the by time has passed. 27 | // If it has, "file:line: TODO Expired - msg" is printed and the program exits. 28 | var Do = func(by, msg string) { 29 | var byDate time.Time 30 | ok := false 31 | var err error 32 | if byDate, ok = times[by]; !ok { 33 | byDate, err = time.Parse(shortForm, by) 34 | if err != nil { 35 | panic(fmt.Sprintf("Unable to parse Do date: %s", err)) 36 | } 37 | times[by] = byDate 38 | } 39 | if time.Now().After(byDate) { 40 | expire(msg) 41 | } 42 | } 43 | 44 | func init() { 45 | if os.Getenv(enabledKey) == "" { 46 | Do = func(by, msg string) { 47 | // this line intentionally left blank 48 | } 49 | } 50 | } 51 | 52 | // callerInfo returns a string containing the file and line number of the Do call 53 | // that expired. 54 | func callerInfo() string { 55 | 56 | file := "" 57 | line := 0 58 | ok := false 59 | 60 | for i := 0; ; i++ { 61 | _, file, line, ok = runtime.Caller(i) 62 | if !ok { 63 | return "" 64 | } 65 | parts := strings.Split(file, "/") 66 | dir := parts[len(parts)-2] 67 | file = parts[len(parts)-1] 68 | if dir != "todo" || file == "todo_test.go" { 69 | break 70 | } 71 | } 72 | 73 | return fmt.Sprintf("%s:%d", file, line) 74 | } 75 | -------------------------------------------------------------------------------- /todo_test.go: -------------------------------------------------------------------------------- 1 | package to 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestTODOExpires(t *testing.T) { 12 | expired := false 13 | printed := "" 14 | expire = func(msg string) { 15 | expired = true 16 | printed = fmt.Sprintf("%s: TODO Expired: - %s", callerInfo(), msg) 17 | } 18 | 19 | Do(time.Now().Format(shortForm), "Testing expiration!") 20 | assert.True(t, expired, "Do function should have expired!") 21 | assert.Equal(t, "todo_test.go:16: TODO Expired: - Testing expiration!", printed) 22 | } 23 | 24 | func TestTODONotExpires(t *testing.T) { 25 | expired := false 26 | expire = func(msg string) { 27 | expired = true 28 | } 29 | Do(time.Now().AddDate(0, 0, 1).Format(shortForm), "Testing expiration!") 30 | assert.False(t, expired, "Do function should not have expired!") 31 | } 32 | 33 | func TestTODOCacheDate(t *testing.T) { 34 | expired := false 35 | expire = func(msg string) { 36 | expired = true 37 | } 38 | 39 | now := time.Now() 40 | parsed, _ := time.Parse(shortForm, now.Format(shortForm)) 41 | 42 | Do(now.Format(shortForm), "Testing expiration!") 43 | assert.True(t, expired, "Do function should have expired!") 44 | assert.Equal(t, times[now.Format(shortForm)], parsed) 45 | 46 | Do(now.Format(shortForm), "Testing expiration!") 47 | assert.True(t, expired, "Do function should have expired!") 48 | 49 | } 50 | 51 | func BenchmarkTODOCachedParse(b *testing.B) { 52 | 53 | b.StopTimer() 54 | 55 | benchTimes := map[string]time.Time{} 56 | by := time.Now().Format(shortForm) 57 | 58 | b.StartTimer() 59 | 60 | for i := 0; i < b.N; i++ { 61 | var byDate time.Time 62 | ok := false 63 | var err error 64 | if byDate, ok = benchTimes[by]; !ok { 65 | byDate, err = time.Parse(shortForm, by) 66 | if err != nil { 67 | panic(fmt.Sprintf("Unable to parse Do date: %s", err)) 68 | } 69 | benchTimes[by] = byDate 70 | } 71 | } 72 | } 73 | 74 | func BenchmarkTODOParseAlways(b *testing.B) { 75 | 76 | b.StopTimer() 77 | 78 | by := time.Now().Format(shortForm) 79 | 80 | b.StartTimer() 81 | 82 | for i := 0; i < b.N; i++ { 83 | byDate, err := time.Parse(shortForm, by) 84 | if err != nil { 85 | panic(fmt.Sprintf("Unable to parse Do date: %s", err)) 86 | } 87 | _ = byDate 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: wercker/golang@1.1.2 2 | --------------------------------------------------------------------------------