├── README.md ├── container_list.go ├── demo └── demo.go ├── gen └── main.go ├── int.go ├── int_slice.go ├── shared.go └── string.go /README.md: -------------------------------------------------------------------------------- 1 | # queue 2 | 3 | A generator for creating typed queues + the queue implementations themselves. 4 | 5 | If you want to learn about code generation and see what points I was trying to demonstrate when I created this repo, you can check out the blog post here - 6 | 7 | 8 | ## Example usage 9 | 10 | All of the sample files here were created by navigating to thie `queue` directory and then running the following: 11 | 12 | ``` 13 | go run gen/main.go -name=String -type=string > string.go 14 | go run gen/main.go -name=Int -type=int > int.go 15 | go run gen/main.go -name=IntSlice -type="[]int" > int_slice.go 16 | # Getting a little meta 17 | go run gen/main.go -name=List -type="*list.List" > container_list.go 18 | ``` 19 | -------------------------------------------------------------------------------- /container_list.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "container/list" 5 | ) 6 | 7 | func NewList() *List { 8 | return &List{list.New()} 9 | } 10 | 11 | // List is a queue implementation for the *list.List type. 12 | // Behind the scenes it is a linked list FIFO queue 13 | // that uses container/list under the hood. The primary 14 | // motivation in creating this type is to allow the compiler 15 | // to verify that we are using the correct types with our 16 | // queue rather than dealing with the interface{} type in 17 | // the rest of our code. 18 | type List struct { 19 | list *list.List 20 | } 21 | 22 | // Len returns the total length of the queue 23 | func (q *List) Len() int { 24 | return q.list.Len() 25 | } 26 | 27 | // Enqueue adds an item to the back of the queue 28 | func (q *List) Enqueue(i *list.List) { 29 | q.list.PushBack(i) 30 | } 31 | 32 | // Dequeue removes and returns the front item in the queue 33 | func (q *List) Dequeue() *list.List { 34 | if q.list.Len() == 0 { 35 | // You could opt to return errors here, but I personally 36 | // prefer to leave length checking up to end users kinda 37 | // like bounds checking in slices. 38 | panic(ErrEmptyQueue) 39 | } 40 | 41 | raw := q.list.Remove(q.list.Front()) 42 | if typed, ok := raw.(*list.List); ok { 43 | return typed 44 | } 45 | 46 | // This won't ever happen unless someone has access to 47 | // insert things into the list with an invalid type or 48 | // your code has bug. 49 | panic(ErrInvalidType) 50 | } 51 | -------------------------------------------------------------------------------- /demo/demo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "container/list" 5 | "fmt" 6 | 7 | "github.com/joncalhoun/queue" 8 | ) 9 | 10 | func main() { 11 | iq := queue.NewInt() 12 | iq.Enqueue(123) 13 | iq.Enqueue(456) 14 | fmt.Println(iq.Dequeue()) 15 | 16 | isq := queue.NewIntSlice() 17 | isq.Enqueue([]int{1, 2, 3}) 18 | isq.Enqueue([]int{4, 5, 6}) 19 | fmt.Println(isq.Dequeue()) 20 | 21 | sq := queue.NewString() 22 | sq.Enqueue("jon") 23 | sq.Enqueue("calhoun") 24 | fmt.Println(sq.Dequeue()) 25 | 26 | // And we can even get a little meta 27 | lq := queue.NewList() 28 | list1 := list.New() 29 | list1.PushBack("jon") 30 | list2 := list.New() 31 | list2.PushBack(456) 32 | lq.Enqueue(list1) 33 | lq.Enqueue(list2) 34 | fmt.Println(lq.Dequeue().Front().Value) 35 | fmt.Println(lq.Dequeue().Front().Value) 36 | } 37 | -------------------------------------------------------------------------------- /gen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "text/template" 9 | 10 | "github.com/joncalhoun/pipe" 11 | ) 12 | 13 | type data struct { 14 | Type string 15 | Name string 16 | } 17 | 18 | func main() { 19 | var d data 20 | flag.StringVar(&d.Type, "type", "", "The subtype used for the queue being generated") 21 | flag.StringVar(&d.Name, "name", "", "The name used for the queue being generated. This should start with a capital letter so that it is exported.") 22 | flag.Parse() 23 | 24 | // Create our template + other commands we want to run 25 | t := template.Must(template.New("queue").Parse(queueTemplate)) 26 | rc, wc, errCh := pipe.Commands( 27 | exec.Command("gofmt"), 28 | exec.Command("goimports"), 29 | ) 30 | go func() { 31 | select { 32 | case err, ok := <-errCh: 33 | if ok && err != nil { 34 | panic(err) 35 | } 36 | } 37 | }() 38 | t.Execute(wc, d) 39 | wc.Close() 40 | io.Copy(os.Stdout, rc) 41 | } 42 | 43 | var queueTemplate = ` 44 | package queue 45 | 46 | import ( 47 | "container/list" 48 | ) 49 | 50 | func New{{.Name}}() *{{.Name}} { 51 | return &{{.Name}}{list.New()} 52 | } 53 | 54 | // {{.Name}} is a queue implementation for the {{.Type}} type. 55 | // Behind the scenes it is a linked list FIFO queue 56 | // that uses container/list under the hood. The primary 57 | // motivation in creating this type is to allow the compiler 58 | // to verify that we are using the correct types with our 59 | // queue rather than dealing with the interface{} type in 60 | // the rest of our code. 61 | type {{.Name}} struct { 62 | list *list.List 63 | } 64 | 65 | // Len returns the total length of the queue 66 | func (q *{{.Name}}) Len() int { 67 | return q.list.Len() 68 | } 69 | 70 | // Enqueue adds an item to the back of the queue 71 | func (q *{{.Name}}) Enqueue(i {{.Type}}) { 72 | q.list.PushBack(i) 73 | } 74 | 75 | // Dequeue removes and returns the front item in the queue 76 | func (q *{{.Name}}) Dequeue() {{.Type}} { 77 | if q.list.Len() == 0 { 78 | // You could opt to return errors here, but I personally 79 | // prefer to leave length checking up to end users kinda 80 | // like bounds checking in slices. 81 | panic(ErrEmptyQueue) 82 | } 83 | 84 | raw := q.list.Remove(q.list.Front()) 85 | if typed, ok := raw.({{.Type}}); ok { 86 | return typed 87 | } 88 | 89 | // This won't ever happen unless someone has access to 90 | // insert things into the list with an invalid type or 91 | // your code has bug. 92 | panic(ErrInvalidType) 93 | } 94 | ` 95 | -------------------------------------------------------------------------------- /int.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "container/list" 5 | ) 6 | 7 | func NewInt() *Int { 8 | return &Int{list.New()} 9 | } 10 | 11 | // Int is a queue implementation for the int type. 12 | // Behind the scenes it is a linked list FIFO queue 13 | // that uses container/list under the hood. The primary 14 | // motivation in creating this type is to allow the compiler 15 | // to verify that we are using the correct types with our 16 | // queue rather than dealing with the interface{} type in 17 | // the rest of our code. 18 | type Int struct { 19 | list *list.List 20 | } 21 | 22 | // Len returns the total length of the queue 23 | func (q *Int) Len() int { 24 | return q.list.Len() 25 | } 26 | 27 | // Enqueue adds an item to the back of the queue 28 | func (q *Int) Enqueue(i int) { 29 | q.list.PushBack(i) 30 | } 31 | 32 | // Dequeue removes and returns the front item in the queue 33 | func (q *Int) Dequeue() int { 34 | if q.list.Len() == 0 { 35 | // You could opt to return errors here, but I personally 36 | // prefer to leave length checking up to end users kinda 37 | // like bounds checking in slices. 38 | panic(ErrEmptyQueue) 39 | } 40 | 41 | raw := q.list.Remove(q.list.Front()) 42 | if typed, ok := raw.(int); ok { 43 | return typed 44 | } 45 | 46 | // This won't ever happen unless someone has access to 47 | // insert things into the list with an invalid type or 48 | // your code has bug. 49 | panic(ErrInvalidType) 50 | } 51 | -------------------------------------------------------------------------------- /int_slice.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "container/list" 5 | ) 6 | 7 | func NewIntSlice() *IntSlice { 8 | return &IntSlice{list.New()} 9 | } 10 | 11 | // IntSlice is a queue implementation for the []int type. 12 | // Behind the scenes it is a linked list FIFO queue 13 | // that uses container/list under the hood. The primary 14 | // motivation in creating this type is to allow the compiler 15 | // to verify that we are using the correct types with our 16 | // queue rather than dealing with the interface{} type in 17 | // the rest of our code. 18 | type IntSlice struct { 19 | list *list.List 20 | } 21 | 22 | // Len returns the total length of the queue 23 | func (q *IntSlice) Len() int { 24 | return q.list.Len() 25 | } 26 | 27 | // Enqueue adds an item to the back of the queue 28 | func (q *IntSlice) Enqueue(i []int) { 29 | q.list.PushBack(i) 30 | } 31 | 32 | // Dequeue removes and returns the front item in the queue 33 | func (q *IntSlice) Dequeue() []int { 34 | if q.list.Len() == 0 { 35 | // You could opt to return errors here, but I personally 36 | // prefer to leave length checking up to end users kinda 37 | // like bounds checking in slices. 38 | panic(ErrEmptyQueue) 39 | } 40 | 41 | raw := q.list.Remove(q.list.Front()) 42 | if typed, ok := raw.([]int); ok { 43 | return typed 44 | } 45 | 46 | // This won't ever happen unless someone has access to 47 | // insert things into the list with an invalid type or 48 | // your code has bug. 49 | panic(ErrInvalidType) 50 | } 51 | -------------------------------------------------------------------------------- /shared.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import "errors" 4 | 5 | var ( 6 | // Queues will panic with this error when empty and 7 | // the Dequeue method is called. 8 | ErrEmptyQueue = errors.New("queue: the queue is empty and the requested operation could not be performed") 9 | 10 | // Queues will panic with this error when they encounter a 11 | // value in the underlying list that isn't of the expected 12 | // type. This SHOULD NOT ever happen, and if it does it 13 | // indicates that the underlying `container/list` was 14 | // exported and manipulated by outside code, or that there 15 | // is a bug in this code. Both are bad and shouldn't be 16 | // allowed to happen! 17 | ErrInvalidType = errors.New("queue: invalid type encountered - this indicates a bug.") 18 | ) 19 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "container/list" 5 | ) 6 | 7 | func NewString() *String { 8 | return &String{list.New()} 9 | } 10 | 11 | // String is a queue implementation for the string type. 12 | // Behind the scenes it is a linked list FIFO queue 13 | // that uses container/list under the hood. The primary 14 | // motivation in creating this type is to allow the compiler 15 | // to verify that we are using the correct types with our 16 | // queue rather than dealing with the interface{} type in 17 | // the rest of our code. 18 | type String struct { 19 | list *list.List 20 | } 21 | 22 | // Len returns the total length of the queue 23 | func (q *String) Len() int { 24 | return q.list.Len() 25 | } 26 | 27 | // Enqueue adds an item to the back of the queue 28 | func (q *String) Enqueue(i string) { 29 | q.list.PushBack(i) 30 | } 31 | 32 | // Dequeue removes and returns the front item in the queue 33 | func (q *String) Dequeue() string { 34 | if q.list.Len() == 0 { 35 | // You could opt to return errors here, but I personally 36 | // prefer to leave length checking up to end users kinda 37 | // like bounds checking in slices. 38 | panic(ErrEmptyQueue) 39 | } 40 | 41 | raw := q.list.Remove(q.list.Front()) 42 | if typed, ok := raw.(string); ok { 43 | return typed 44 | } 45 | 46 | // This won't ever happen unless someone has access to 47 | // insert things into the list with an invalid type or 48 | // your code has bug. 49 | panic(ErrInvalidType) 50 | } 51 | --------------------------------------------------------------------------------