├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── cover.out
├── dagger.go
├── dagger_test.go
├── go.mod
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | tmp
3 | testing.json
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .DEFAULT_GOAL := help
2 |
3 | .PHONY: help
4 | help:
5 | @echo "Makefile Commands:"
6 | @echo "----------------------------------------------------------------"
7 | @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'
8 | @echo "----------------------------------------------------------------"
9 |
10 | test: ## run unit tests
11 | go test -cover -v -coverprofile cover.out -race .
12 |
13 | coverage: ## show test coverage
14 | go tool cover -func cover.out
15 |
16 | docs:
17 | @echo "Generating docs..."
18 | @gomarkdoc --output README.md .
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # dagger
4 |
5 | ```go
6 | import "github.com/autom8ter/dagger/v3"
7 | ```
8 |
9 | Package dagger is a collection of generic, concurrency safe datastructures including a Directed Acyclic Graph and others. Datastructures are implemented using generics in Go 1.18.
10 |
11 | Supported Datastructures:
12 |
13 | DAG: thread safe directed acyclic graph
14 |
15 | Queue: unbounded thread safe fifo queue
16 |
17 | Stack: unbounded thread safe lifo stack
18 |
19 | BoundedQueue: bounded thread safe fifo queue with a fixed capacity
20 |
21 | PriorityQueue: thread safe priority queue
22 |
23 | HashMap: thread safe hashmap
24 |
25 | Set: thread safe set
26 |
27 | ChannelGroup: thread safe group of channels for broadcasting 1 value to N channels
28 |
29 | MultiContext: thread safe context for coordinating the cancellation of multiple contexts
30 |
31 | Borrower: thread safe object ownership manager
32 |
33 | ## Index
34 |
35 | - [func UniqueID\(prefix string\) string](<#UniqueID>)
36 | - [type BoundedQueue](<#BoundedQueue>)
37 | - [func NewBoundedQueue\[T any\]\(maxSize int\) \*BoundedQueue\[T\]](<#NewBoundedQueue>)
38 | - [func \(q \*BoundedQueue\[T\]\) Close\(\)](<#BoundedQueue[T].Close>)
39 | - [func \(q \*BoundedQueue\[T\]\) Len\(\) int](<#BoundedQueue[T].Len>)
40 | - [func \(q \*BoundedQueue\[T\]\) Pop\(\) \(T, bool\)](<#BoundedQueue[T].Pop>)
41 | - [func \(q \*BoundedQueue\[T\]\) PopContext\(ctx context.Context\) \(T, bool\)](<#BoundedQueue[T].PopContext>)
42 | - [func \(q \*BoundedQueue\[T\]\) Push\(val T\) bool](<#BoundedQueue[T].Push>)
43 | - [func \(q \*BoundedQueue\[T\]\) PushContext\(ctx context.Context, val T\) bool](<#BoundedQueue[T].PushContext>)
44 | - [func \(q \*BoundedQueue\[T\]\) Range\(fn func\(element T\) bool\)](<#BoundedQueue[T].Range>)
45 | - [func \(q \*BoundedQueue\[T\]\) RangeContext\(ctx context.Context, fn func\(element T\) bool\)](<#BoundedQueue[T].RangeContext>)
46 | - [type DAG](<#DAG>)
47 | - [func NewDAG\[T Node\]\(opts ...DagOpt\) \(\*DAG\[T\], error\)](<#NewDAG>)
48 | - [func \(g \*DAG\[T\]\) Acyclic\(\) bool](<#DAG[T].Acyclic>)
49 | - [func \(g \*DAG\[T\]\) BFS\(ctx context.Context, reverse bool, start \*GraphNode\[T\], search GraphSearchFunc\[T\]\) error](<#DAG[T].BFS>)
50 | - [func \(g \*DAG\[T\]\) DFS\(ctx context.Context, reverse bool, start \*GraphNode\[T\], fn GraphSearchFunc\[T\]\) error](<#DAG[T].DFS>)
51 | - [func \(g \*DAG\[T\]\) GetEdge\(id string\) \(\*GraphEdge\[T\], bool\)](<#DAG[T].GetEdge>)
52 | - [func \(g \*DAG\[T\]\) GetEdges\(\) \[\]\*GraphEdge\[T\]](<#DAG[T].GetEdges>)
53 | - [func \(g \*DAG\[T\]\) GetNode\(id string\) \(\*GraphNode\[T\], bool\)](<#DAG[T].GetNode>)
54 | - [func \(g \*DAG\[T\]\) GetNodes\(\) \[\]\*GraphNode\[T\]](<#DAG[T].GetNodes>)
55 | - [func \(g \*DAG\[T\]\) GraphViz\(\) \(image.Image, error\)](<#DAG[T].GraphViz>)
56 | - [func \(g \*DAG\[T\]\) HasEdge\(id string\) bool](<#DAG[T].HasEdge>)
57 | - [func \(g \*DAG\[T\]\) HasNode\(id string\) bool](<#DAG[T].HasNode>)
58 | - [func \(g \*DAG\[T\]\) RangeEdges\(fn func\(e \*GraphEdge\[T\]\) bool\)](<#DAG[T].RangeEdges>)
59 | - [func \(g \*DAG\[T\]\) RangeNodes\(fn func\(n \*GraphNode\[T\]\) bool\)](<#DAG[T].RangeNodes>)
60 | - [func \(g \*DAG\[T\]\) SetNode\(node Node\) \*GraphNode\[T\]](<#DAG[T].SetNode>)
61 | - [func \(g \*DAG\[T\]\) Size\(\) \(int, int\)](<#DAG[T].Size>)
62 | - [func \(g \*DAG\[T\]\) TopologicalSort\(reverse bool\) \(\[\]\*GraphNode\[T\], error\)](<#DAG[T].TopologicalSort>)
63 | - [type DagOpt](<#DagOpt>)
64 | - [func WithVizualization\(\) DagOpt](<#WithVizualization>)
65 | - [type GraphEdge](<#GraphEdge>)
66 | - [func \(n \*GraphEdge\[T\]\) From\(\) \*GraphNode\[T\]](<#GraphEdge[T].From>)
67 | - [func \(n \*GraphEdge\[T\]\) ID\(\) string](<#GraphEdge[T].ID>)
68 | - [func \(n \*GraphEdge\[T\]\) Metadata\(\) map\[string\]string](<#GraphEdge[T].Metadata>)
69 | - [func \(n \*GraphEdge\[T\]\) Relationship\(\) string](<#GraphEdge[T].Relationship>)
70 | - [func \(n \*GraphEdge\[T\]\) SetMetadata\(metadata map\[string\]string\)](<#GraphEdge[T].SetMetadata>)
71 | - [func \(n \*GraphEdge\[T\]\) To\(\) \*GraphNode\[T\]](<#GraphEdge[T].To>)
72 | - [type GraphNode](<#GraphNode>)
73 | - [func \(n \*GraphNode\[T\]\) Ancestors\(fn func\(node \*GraphNode\[T\]\) bool\)](<#GraphNode[T].Ancestors>)
74 | - [func \(n \*GraphNode\[T\]\) BFS\(ctx context.Context, reverse bool, fn GraphSearchFunc\[T\]\) error](<#GraphNode[T].BFS>)
75 | - [func \(n \*GraphNode\[T\]\) DFS\(ctx context.Context, reverse bool, fn GraphSearchFunc\[T\]\) error](<#GraphNode[T].DFS>)
76 | - [func \(n \*GraphNode\[T\]\) Descendants\(fn func\(node \*GraphNode\[T\]\) bool\)](<#GraphNode[T].Descendants>)
77 | - [func \(n \*GraphNode\[T\]\) EdgesFrom\(relationship string, fn func\(e \*GraphEdge\[T\]\) bool\)](<#GraphNode[T].EdgesFrom>)
78 | - [func \(n \*GraphNode\[T\]\) EdgesTo\(relationship string, fn func\(e \*GraphEdge\[T\]\) bool\)](<#GraphNode[T].EdgesTo>)
79 | - [func \(n \*GraphNode\[T\]\) Graph\(\) \*DAG\[T\]](<#GraphNode[T].Graph>)
80 | - [func \(n \*GraphNode\[T\]\) IsConnectedTo\(node \*GraphNode\[T\]\) bool](<#GraphNode[T].IsConnectedTo>)
81 | - [func \(n \*GraphNode\[T\]\) Remove\(\) error](<#GraphNode[T].Remove>)
82 | - [func \(n \*GraphNode\[T\]\) RemoveEdge\(edgeID string\)](<#GraphNode[T].RemoveEdge>)
83 | - [func \(n \*GraphNode\[T\]\) SetEdge\(relationship string, toNode Node, metadata map\[string\]string\) \(\*GraphEdge\[T\], error\)](<#GraphNode[T].SetEdge>)
84 | - [type GraphSearchFunc](<#GraphSearchFunc>)
85 | - [type HashMap](<#HashMap>)
86 | - [func NewHashMap\[K comparable, V any\]\(\) \*HashMap\[K, V\]](<#NewHashMap>)
87 | - [func \(n \*HashMap\[K, V\]\) Clear\(\)](<#HashMap[K, V].Clear>)
88 | - [func \(n \*HashMap\[K, V\]\) Delete\(key K\)](<#HashMap[K, V].Delete>)
89 | - [func \(n \*HashMap\[K, V\]\) Exists\(key K\) bool](<#HashMap[K, V].Exists>)
90 | - [func \(n \*HashMap\[K, V\]\) Filter\(f func\(key K, value V\) bool\) \*HashMap\[K, V\]](<#HashMap[K, V].Filter>)
91 | - [func \(n \*HashMap\[K, V\]\) Get\(key K\) \(V, bool\)](<#HashMap[K, V].Get>)
92 | - [func \(n \*HashMap\[K, V\]\) Keys\(\) \[\]K](<#HashMap[K, V].Keys>)
93 | - [func \(n \*HashMap\[K, V\]\) Len\(\) int](<#HashMap[K, V].Len>)
94 | - [func \(n \*HashMap\[K, V\]\) Map\(\) map\[K\]V](<#HashMap[K, V].Map>)
95 | - [func \(n \*HashMap\[K, V\]\) Range\(f func\(key K, value V\) bool\)](<#HashMap[K, V].Range>)
96 | - [func \(n \*HashMap\[K, V\]\) Set\(key K, value V\)](<#HashMap[K, V].Set>)
97 | - [func \(n \*HashMap\[K, V\]\) Values\(\) \[\]V](<#HashMap[K, V].Values>)
98 | - [type Node](<#Node>)
99 | - [type PriorityQueue](<#PriorityQueue>)
100 | - [func NewPriorityQueue\[T any\]\(\) \*PriorityQueue\[T\]](<#NewPriorityQueue>)
101 | - [func \(q \*PriorityQueue\[T\]\) Len\(\) int](<#PriorityQueue[T].Len>)
102 | - [func \(q \*PriorityQueue\[T\]\) Peek\(\) \(T, bool\)](<#PriorityQueue[T].Peek>)
103 | - [func \(q \*PriorityQueue\[T\]\) Pop\(\) \(T, bool\)](<#PriorityQueue[T].Pop>)
104 | - [func \(q \*PriorityQueue\[T\]\) Push\(item T, weight float64\)](<#PriorityQueue[T].Push>)
105 | - [func \(q \*PriorityQueue\[T\]\) UpdatePriority\(value T, priority float64\)](<#PriorityQueue[T].UpdatePriority>)
106 | - [type Queue](<#Queue>)
107 | - [func NewQueue\[T any\]\(\) \*Queue\[T\]](<#NewQueue>)
108 | - [func \(s \*Queue\[T\]\) Len\(\) int](<#Queue[T].Len>)
109 | - [func \(s \*Queue\[T\]\) Peek\(\) \(T, bool\)](<#Queue[T].Peek>)
110 | - [func \(s \*Queue\[T\]\) Pop\(\) \(T, bool\)](<#Queue[T].Pop>)
111 | - [func \(s \*Queue\[T\]\) Push\(f T\)](<#Queue[T].Push>)
112 | - [func \(q \*Queue\[T\]\) Range\(fn func\(element T\) bool\)](<#Queue[T].Range>)
113 | - [func \(q \*Queue\[T\]\) RangeUntil\(fn func\(element T\) bool, done chan struct\{\}\)](<#Queue[T].RangeUntil>)
114 | - [type Set](<#Set>)
115 | - [func NewSet\[T comparable\]\(\) \*Set\[T\]](<#NewSet>)
116 | - [func \(s \*Set\[T\]\) Add\(val T\)](<#Set[T].Add>)
117 | - [func \(s \*Set\[T\]\) Contains\(val T\) bool](<#Set[T].Contains>)
118 | - [func \(s \*Set\[T\]\) Len\(\) int](<#Set[T].Len>)
119 | - [func \(s \*Set\[T\]\) Range\(fn func\(element T\) bool\)](<#Set[T].Range>)
120 | - [func \(s \*Set\[T\]\) Remove\(val T\)](<#Set[T].Remove>)
121 | - [func \(s \*Set\[T\]\) Sort\(lessFunc func\(i T, j T\) bool\) \[\]T](<#Set[T].Sort>)
122 | - [func \(s \*Set\[T\]\) Values\(\) \[\]T](<#Set[T].Values>)
123 | - [type Stack](<#Stack>)
124 | - [func NewStack\[T any\]\(\) \*Stack\[T\]](<#NewStack>)
125 | - [func \(s \*Stack\[T\]\) Clear\(\)](<#Stack[T].Clear>)
126 | - [func \(s \*Stack\[T\]\) Len\(\) int](<#Stack[T].Len>)
127 | - [func \(s \*Stack\[T\]\) Peek\(\) \(T, bool\)](<#Stack[T].Peek>)
128 | - [func \(s \*Stack\[T\]\) Pop\(\) \(T, bool\)](<#Stack[T].Pop>)
129 | - [func \(s \*Stack\[T\]\) Push\(f T\)](<#Stack[T].Push>)
130 | - [func \(s \*Stack\[T\]\) Range\(fn func\(element T\) bool\)](<#Stack[T].Range>)
131 | - [func \(s \*Stack\[T\]\) RangeUntil\(fn func\(element T\) bool, done chan struct\{\}\)](<#Stack[T].RangeUntil>)
132 | - [func \(s \*Stack\[T\]\) Sort\(lessFunc func\(i T, j T\) bool\) \[\]T](<#Stack[T].Sort>)
133 | - [func \(s \*Stack\[T\]\) Values\(\) \[\]T](<#Stack[T].Values>)
134 |
135 |
136 |
137 | ## func [UniqueID]()
138 |
139 | ```go
140 | func UniqueID(prefix string) string
141 | ```
142 |
143 | UniqueID returns a unique identifier with the given prefix
144 |
145 |
146 | ## type [BoundedQueue]()
147 |
148 | BoundedQueue is a basic FIFO BoundedQueue based on a buffered channel
149 |
150 | ```go
151 | type BoundedQueue[T any] struct {
152 | // contains filtered or unexported fields
153 | }
154 | ```
155 |
156 |
157 | ### func [NewBoundedQueue]()
158 |
159 | ```go
160 | func NewBoundedQueue[T any](maxSize int) *BoundedQueue[T]
161 | ```
162 |
163 | NewBoundedQueue returns a new BoundedQueue with the given max size. When the max size is reached, the queue will block until a value is removed. If maxSize is 0, the queue will always block until a value is removed. The BoundedQueue is concurrent\-safe.
164 |
165 |
166 | ### func \(\*BoundedQueue\[T\]\) [Close]()
167 |
168 | ```go
169 | func (q *BoundedQueue[T]) Close()
170 | ```
171 |
172 | Close closes the BoundedQueue channel.
173 |
174 |
175 | ### func \(\*BoundedQueue\[T\]\) [Len]()
176 |
177 | ```go
178 | func (q *BoundedQueue[T]) Len() int
179 | ```
180 |
181 | Len returns the number of elements in the BoundedQueue.
182 |
183 |
184 | ### func \(\*BoundedQueue\[T\]\) [Pop]()
185 |
186 | ```go
187 | func (q *BoundedQueue[T]) Pop() (T, bool)
188 | ```
189 |
190 | Pop removes and returns an element from the beginning of the BoundedQueue.
191 |
192 |
193 | ### func \(\*BoundedQueue\[T\]\) [PopContext]()
194 |
195 | ```go
196 | func (q *BoundedQueue[T]) PopContext(ctx context.Context) (T, bool)
197 | ```
198 |
199 | PopContext removes and returns an element from the beginning of the BoundedQueue. If no element is available, it will block until an element is available or the context is cancelled.
200 |
201 |
202 | ### func \(\*BoundedQueue\[T\]\) [Push]()
203 |
204 | ```go
205 | func (q *BoundedQueue[T]) Push(val T) bool
206 | ```
207 |
208 | Push adds an element to the end of the BoundedQueue and returns a channel that will block until the element is added. If the queue is full, it will block until an element is removed.
209 |
210 |
211 | ### func \(\*BoundedQueue\[T\]\) [PushContext]()
212 |
213 | ```go
214 | func (q *BoundedQueue[T]) PushContext(ctx context.Context, val T) bool
215 | ```
216 |
217 | PushContext adds an element to the end of the BoundedQueue and returns a channel that will block until the element is added. If the queue is full, it will block until an element is removed or the context is cancelled.
218 |
219 |
220 | ### func \(\*BoundedQueue\[T\]\) [Range]()
221 |
222 | ```go
223 | func (q *BoundedQueue[T]) Range(fn func(element T) bool)
224 | ```
225 |
226 | Range executes a provided function once for each BoundedQueue element until it returns false.
227 |
228 |
229 | ### func \(\*BoundedQueue\[T\]\) [RangeContext]()
230 |
231 | ```go
232 | func (q *BoundedQueue[T]) RangeContext(ctx context.Context, fn func(element T) bool)
233 | ```
234 |
235 | RangeContext executes a provided function once for each BoundedQueue element until it returns false or a value is sent to the done channel. Use this function when you want to continuously process items from the queue until a done signal is received.
236 |
237 |
238 | ## type [DAG]()
239 |
240 | DAG is a concurrency safe, mutable, in\-memory directed graph
241 |
242 | ```go
243 | type DAG[T Node] struct {
244 | // contains filtered or unexported fields
245 | }
246 | ```
247 |
248 |
249 | ### func [NewDAG]()
250 |
251 | ```go
252 | func NewDAG[T Node](opts ...DagOpt) (*DAG[T], error)
253 | ```
254 |
255 | NewDAG creates a new Directed Acyclic Graph instance
256 |
257 |
258 | ### func \(\*DAG\[T\]\) [Acyclic]()
259 |
260 | ```go
261 | func (g *DAG[T]) Acyclic() bool
262 | ```
263 |
264 | Acyclic returns true if the graph contains no cycles.
265 |
266 |
267 | ### func \(\*DAG\[T\]\) [BFS]()
268 |
269 | ```go
270 | func (g *DAG[T]) BFS(ctx context.Context, reverse bool, start *GraphNode[T], search GraphSearchFunc[T]) error
271 | ```
272 |
273 | BFS executes a depth first search on the graph starting from the current node. The reverse parameter determines whether the search is reversed or not. The fn parameter is a function that is called on each node in the graph. If the function returns false, the search is stopped.
274 |
275 |
276 | ### func \(\*DAG\[T\]\) [DFS]()
277 |
278 | ```go
279 | func (g *DAG[T]) DFS(ctx context.Context, reverse bool, start *GraphNode[T], fn GraphSearchFunc[T]) error
280 | ```
281 |
282 | DFS executes a depth first search on the graph starting from the current node. The reverse parameter determines whether the search is reversed or not. The fn parameter is a function that is called on each node in the graph. If the function returns false, the search is stopped.
283 |
284 |
285 | ### func \(\*DAG\[T\]\) [GetEdge]()
286 |
287 | ```go
288 | func (g *DAG[T]) GetEdge(id string) (*GraphEdge[T], bool)
289 | ```
290 |
291 | GetEdge returns the edge with the given id
292 |
293 |
294 | ### func \(\*DAG\[T\]\) [GetEdges]()
295 |
296 | ```go
297 | func (g *DAG[T]) GetEdges() []*GraphEdge[T]
298 | ```
299 |
300 | GetEdges returns all edges in the graph
301 |
302 |
303 | ### func \(\*DAG\[T\]\) [GetNode]()
304 |
305 | ```go
306 | func (g *DAG[T]) GetNode(id string) (*GraphNode[T], bool)
307 | ```
308 |
309 | GetNode returns the node with the given id
310 |
311 |
312 | ### func \(\*DAG\[T\]\) [GetNodes]()
313 |
314 | ```go
315 | func (g *DAG[T]) GetNodes() []*GraphNode[T]
316 | ```
317 |
318 | GetNodes returns all nodes in the graph
319 |
320 |
321 | ### func \(\*DAG\[T\]\) [GraphViz]()
322 |
323 | ```go
324 | func (g *DAG[T]) GraphViz() (image.Image, error)
325 | ```
326 |
327 | GraphViz returns a graphviz image
328 |
329 |
330 | ### func \(\*DAG\[T\]\) [HasEdge]()
331 |
332 | ```go
333 | func (g *DAG[T]) HasEdge(id string) bool
334 | ```
335 |
336 | HasEdge returns true if the edge with the given id exists in the graph
337 |
338 |
339 | ### func \(\*DAG\[T\]\) [HasNode]()
340 |
341 | ```go
342 | func (g *DAG[T]) HasNode(id string) bool
343 | ```
344 |
345 | HasNode returns true if the node with the given id exists in the graph
346 |
347 |
348 | ### func \(\*DAG\[T\]\) [RangeEdges]()
349 |
350 | ```go
351 | func (g *DAG[T]) RangeEdges(fn func(e *GraphEdge[T]) bool)
352 | ```
353 |
354 | RangeEdges iterates over all edges in the graph
355 |
356 |
357 | ### func \(\*DAG\[T\]\) [RangeNodes]()
358 |
359 | ```go
360 | func (g *DAG[T]) RangeNodes(fn func(n *GraphNode[T]) bool)
361 | ```
362 |
363 | RangeNodes iterates over all nodes in the graph
364 |
365 |
366 | ### func \(\*DAG\[T\]\) [SetNode]()
367 |
368 | ```go
369 | func (g *DAG[T]) SetNode(node Node) *GraphNode[T]
370 | ```
371 |
372 | SetNode sets a node in the graph \- it will use the node's ID as the key and overwrite any existing node with the same ID
373 |
374 |
375 | ### func \(\*DAG\[T\]\) [Size]()
376 |
377 | ```go
378 | func (g *DAG[T]) Size() (int, int)
379 | ```
380 |
381 | Size returns the number of nodes and edges in the graph
382 |
383 |
384 | ### func \(\*DAG\[T\]\) [TopologicalSort]()
385 |
386 | ```go
387 | func (g *DAG[T]) TopologicalSort(reverse bool) ([]*GraphNode[T], error)
388 | ```
389 |
390 |
391 |
392 |
393 | ## type [DagOpt]()
394 |
395 | DagOpt is an option for configuring a DAG
396 |
397 | ```go
398 | type DagOpt func(*dagOpts)
399 | ```
400 |
401 |
402 | ### func [WithVizualization]()
403 |
404 | ```go
405 | func WithVizualization() DagOpt
406 | ```
407 |
408 | WithVizualization enables graphviz visualization on the DAG
409 |
410 |
411 | ## type [GraphEdge]()
412 |
413 | GraphEdge is a relationship between two nodes
414 |
415 | ```go
416 | type GraphEdge[T Node] struct {
417 | // contains filtered or unexported fields
418 | }
419 | ```
420 |
421 |
422 | ### func \(\*GraphEdge\[T\]\) [From]()
423 |
424 | ```go
425 | func (n *GraphEdge[T]) From() *GraphNode[T]
426 | ```
427 |
428 | From returns the from node of the edge
429 |
430 |
431 | ### func \(\*GraphEdge\[T\]\) [ID]()
432 |
433 | ```go
434 | func (n *GraphEdge[T]) ID() string
435 | ```
436 |
437 | ID returns the unique identifier of the node
438 |
439 |
440 | ### func \(\*GraphEdge\[T\]\) [Metadata]()
441 |
442 | ```go
443 | func (n *GraphEdge[T]) Metadata() map[string]string
444 | ```
445 |
446 | Metadata returns the metadata of the node
447 |
448 |
449 | ### func \(\*GraphEdge\[T\]\) [Relationship]()
450 |
451 | ```go
452 | func (n *GraphEdge[T]) Relationship() string
453 | ```
454 |
455 | Relationship returns the relationship between the two nodes
456 |
457 |
458 | ### func \(\*GraphEdge\[T\]\) [SetMetadata]()
459 |
460 | ```go
461 | func (n *GraphEdge[T]) SetMetadata(metadata map[string]string)
462 | ```
463 |
464 | SetMetadata sets the metadata of the node
465 |
466 |
467 | ### func \(\*GraphEdge\[T\]\) [To]()
468 |
469 | ```go
470 | func (n *GraphEdge[T]) To() *GraphNode[T]
471 | ```
472 |
473 | To returns the to node of the edge
474 |
475 |
476 | ## type [GraphNode]()
477 |
478 | GraphNode is a node in the graph. It can be connected to other nodes via edges.
479 |
480 | ```go
481 | type GraphNode[T Node] struct {
482 | Node
483 | // contains filtered or unexported fields
484 | }
485 | ```
486 |
487 |
488 | ### func \(\*GraphNode\[T\]\) [Ancestors]()
489 |
490 | ```go
491 | func (n *GraphNode[T]) Ancestors(fn func(node *GraphNode[T]) bool)
492 | ```
493 |
494 | Ancestors returns the ancestors of the current node
495 |
496 |
497 | ### func \(\*GraphNode\[T\]\) [BFS]()
498 |
499 | ```go
500 | func (n *GraphNode[T]) BFS(ctx context.Context, reverse bool, fn GraphSearchFunc[T]) error
501 | ```
502 |
503 | BFS performs a breadth\-first search on the graph starting from the current node
504 |
505 |
506 | ### func \(\*GraphNode\[T\]\) [DFS]()
507 |
508 | ```go
509 | func (n *GraphNode[T]) DFS(ctx context.Context, reverse bool, fn GraphSearchFunc[T]) error
510 | ```
511 |
512 | DFS performs a depth\-first search on the graph starting from the current node
513 |
514 |
515 | ### func \(\*GraphNode\[T\]\) [Descendants]()
516 |
517 | ```go
518 | func (n *GraphNode[T]) Descendants(fn func(node *GraphNode[T]) bool)
519 | ```
520 |
521 | Descendants returns the descendants of the current node
522 |
523 |
524 | ### func \(\*GraphNode\[T\]\) [EdgesFrom]()
525 |
526 | ```go
527 | func (n *GraphNode[T]) EdgesFrom(relationship string, fn func(e *GraphEdge[T]) bool)
528 | ```
529 |
530 | EdgesFrom iterates over the edges from the current node to other nodes with the given relationship. If the relationship is empty, all relationships will be iterated over.
531 |
532 |
533 | ### func \(\*GraphNode\[T\]\) [EdgesTo]()
534 |
535 | ```go
536 | func (n *GraphNode[T]) EdgesTo(relationship string, fn func(e *GraphEdge[T]) bool)
537 | ```
538 |
539 | EdgesTo iterates over the edges from other nodes to the current node with the given relationship. If the relationship is empty, all relationships will be iterated over.
540 |
541 |
542 | ### func \(\*GraphNode\[T\]\) [Graph]()
543 |
544 | ```go
545 | func (n *GraphNode[T]) Graph() *DAG[T]
546 | ```
547 |
548 | DirectedGraph returns the graph the node belongs to
549 |
550 |
551 | ### func \(\*GraphNode\[T\]\) [IsConnectedTo]()
552 |
553 | ```go
554 | func (n *GraphNode[T]) IsConnectedTo(node *GraphNode[T]) bool
555 | ```
556 |
557 | IsConnectedTo returns true if the current node is connected to the given node in any direction
558 |
559 |
560 | ### func \(\*GraphNode\[T\]\) [Remove]()
561 |
562 | ```go
563 | func (n *GraphNode[T]) Remove() error
564 | ```
565 |
566 | Remove removes the current node from the graph
567 |
568 |
569 | ### func \(\*GraphNode\[T\]\) [RemoveEdge]()
570 |
571 | ```go
572 | func (n *GraphNode[T]) RemoveEdge(edgeID string)
573 | ```
574 |
575 | RemoveEdge removes an edge from the current node by edgeID
576 |
577 |
578 | ### func \(\*GraphNode\[T\]\) [SetEdge]()
579 |
580 | ```go
581 | func (n *GraphNode[T]) SetEdge(relationship string, toNode Node, metadata map[string]string) (*GraphEdge[T], error)
582 | ```
583 |
584 | SetEdge sets an edge from the current node to the node with the given nodeID. If the nodeID does not exist, an error is returned. If the edgeID is empty, a unique id will be generated. If the metadata is nil, an empty map will be used.
585 |
586 |
587 | ## type [GraphSearchFunc]()
588 |
589 | GraphSearchFunc is a function that is called on each node in the graph during a search
590 |
591 | ```go
592 | type GraphSearchFunc[T Node] func(ctx context.Context, relationship string, node *GraphNode[T]) bool
593 | ```
594 |
595 |
596 | ## type [HashMap]()
597 |
598 | HashMap is a thread safe map
599 |
600 | ```go
601 | type HashMap[K comparable, V any] struct {
602 | // contains filtered or unexported fields
603 | }
604 | ```
605 |
606 |
607 | ### func [NewHashMap]()
608 |
609 | ```go
610 | func NewHashMap[K comparable, V any]() *HashMap[K, V]
611 | ```
612 |
613 | NewHashMap creates a new generic hash map
614 |
615 |
616 | ### func \(\*HashMap\[K, V\]\) [Clear]()
617 |
618 | ```go
619 | func (n *HashMap[K, V]) Clear()
620 | ```
621 |
622 | Clear clears the map
623 |
624 |
625 | ### func \(\*HashMap\[K, V\]\) [Delete]()
626 |
627 | ```go
628 | func (n *HashMap[K, V]) Delete(key K)
629 | ```
630 |
631 | Delete deletes the key from the map
632 |
633 |
634 | ### func \(\*HashMap\[K, V\]\) [Exists]()
635 |
636 | ```go
637 | func (n *HashMap[K, V]) Exists(key K) bool
638 | ```
639 |
640 | Exists returns true if the key exists in the map
641 |
642 |
643 | ### func \(\*HashMap\[K, V\]\) [Filter]()
644 |
645 | ```go
646 | func (n *HashMap[K, V]) Filter(f func(key K, value V) bool) *HashMap[K, V]
647 | ```
648 |
649 | Filter returns a new hashmap with the values that return true from the function
650 |
651 |
652 | ### func \(\*HashMap\[K, V\]\) [Get]()
653 |
654 | ```go
655 | func (n *HashMap[K, V]) Get(key K) (V, bool)
656 | ```
657 |
658 | Get gets the value from the key
659 |
660 |
661 | ### func \(\*HashMap\[K, V\]\) [Keys]()
662 |
663 | ```go
664 | func (n *HashMap[K, V]) Keys() []K
665 | ```
666 |
667 | Keys returns a copy of the keys in the map as a slice
668 |
669 |
670 | ### func \(\*HashMap\[K, V\]\) [Len]()
671 |
672 | ```go
673 | func (n *HashMap[K, V]) Len() int
674 | ```
675 |
676 | Len returns the length of the map
677 |
678 |
679 | ### func \(\*HashMap\[K, V\]\) [Map]()
680 |
681 | ```go
682 | func (n *HashMap[K, V]) Map() map[K]V
683 | ```
684 |
685 | Map returns a copy of the hashmap as a map\[string\]T
686 |
687 |
688 | ### func \(\*HashMap\[K, V\]\) [Range]()
689 |
690 | ```go
691 | func (n *HashMap[K, V]) Range(f func(key K, value V) bool)
692 | ```
693 |
694 | Range ranges over the map with a function until false is returned
695 |
696 |
697 | ### func \(\*HashMap\[K, V\]\) [Set]()
698 |
699 | ```go
700 | func (n *HashMap[K, V]) Set(key K, value V)
701 | ```
702 |
703 | Set sets the key to the value
704 |
705 |
706 | ### func \(\*HashMap\[K, V\]\) [Values]()
707 |
708 | ```go
709 | func (n *HashMap[K, V]) Values() []V
710 | ```
711 |
712 | Values returns a copy of the values in the map as a slice
713 |
714 |
715 | ## type [Node]()
716 |
717 | Node is a node in the graph. It can be connected to other nodes via edges.
718 |
719 | ```go
720 | type Node interface {
721 | // ID returns the unique identifier of the node
722 | ID() string
723 | // Metadata returns the metadata of the node
724 | Metadata() map[string]string
725 | // SetMetadata sets the metadata of the node
726 | SetMetadata(metadata map[string]string)
727 | }
728 | ```
729 |
730 |
731 | ## type [PriorityQueue]()
732 |
733 | PriorityQueue is a thread safe priority queue
734 |
735 | ```go
736 | type PriorityQueue[T any] struct {
737 | // contains filtered or unexported fields
738 | }
739 | ```
740 |
741 |
742 | ### func [NewPriorityQueue]()
743 |
744 | ```go
745 | func NewPriorityQueue[T any]() *PriorityQueue[T]
746 | ```
747 |
748 | NewPriorityQueue creates a new priority queue
749 |
750 |
751 | ### func \(\*PriorityQueue\[T\]\) [Len]()
752 |
753 | ```go
754 | func (q *PriorityQueue[T]) Len() int
755 | ```
756 |
757 | Len returns the length of the queue
758 |
759 |
760 | ### func \(\*PriorityQueue\[T\]\) [Peek]()
761 |
762 | ```go
763 | func (q *PriorityQueue[T]) Peek() (T, bool)
764 | ```
765 |
766 | Peek returns the next item in the queue without removing it
767 |
768 |
769 | ### func \(\*PriorityQueue\[T\]\) [Pop]()
770 |
771 | ```go
772 | func (q *PriorityQueue[T]) Pop() (T, bool)
773 | ```
774 |
775 | Pop pops an item off the queue
776 |
777 |
778 | ### func \(\*PriorityQueue\[T\]\) [Push]()
779 |
780 | ```go
781 | func (q *PriorityQueue[T]) Push(item T, weight float64)
782 | ```
783 |
784 | Push pushes an item onto the queue
785 |
786 |
787 | ### func \(\*PriorityQueue\[T\]\) [UpdatePriority]()
788 |
789 | ```go
790 | func (q *PriorityQueue[T]) UpdatePriority(value T, priority float64)
791 | ```
792 |
793 |
794 |
795 |
796 | ## type [Queue]()
797 |
798 | Queue is a thread safe non\-blocking queue
799 |
800 | ```go
801 | type Queue[T any] struct {
802 | // contains filtered or unexported fields
803 | }
804 | ```
805 |
806 |
807 | ### func [NewQueue]()
808 |
809 | ```go
810 | func NewQueue[T any]() *Queue[T]
811 | ```
812 |
813 | NewQueue returns a new Queue
814 |
815 |
816 | ### func \(\*Queue\[T\]\) [Len]()
817 |
818 | ```go
819 | func (s *Queue[T]) Len() int
820 | ```
821 |
822 | Len returns the length of the queue
823 |
824 |
825 | ### func \(\*Queue\[T\]\) [Peek]()
826 |
827 | ```go
828 | func (s *Queue[T]) Peek() (T, bool)
829 | ```
830 |
831 | Peek returns the next item in the queue without removing it
832 |
833 |
834 | ### func \(\*Queue\[T\]\) [Pop]()
835 |
836 | ```go
837 | func (s *Queue[T]) Pop() (T, bool)
838 | ```
839 |
840 | Pop and return top element of Queue. Return false if Queue is empty.
841 |
842 |
843 | ### func \(\*Queue\[T\]\) [Push]()
844 |
845 | ```go
846 | func (s *Queue[T]) Push(f T)
847 | ```
848 |
849 | Push a new value onto the Queue
850 |
851 |
852 | ### func \(\*Queue\[T\]\) [Range]()
853 |
854 | ```go
855 | func (q *Queue[T]) Range(fn func(element T) bool)
856 | ```
857 |
858 | Range executes a provided function once for each Queue element until it returns false or the Queue is empty.
859 |
860 |
861 | ### func \(\*Queue\[T\]\) [RangeUntil]()
862 |
863 | ```go
864 | func (q *Queue[T]) RangeUntil(fn func(element T) bool, done chan struct{})
865 | ```
866 |
867 | RangeUntil executes a provided function once for each Queue element until it returns false or a value is sent on the done channel. Use this function when you want to continuously process items from the queue until a done signal is received.
868 |
869 |
870 | ## type [Set]()
871 |
872 | Set is a basic thread\-safe Set implementation.
873 |
874 | ```go
875 | type Set[T comparable] struct {
876 | // contains filtered or unexported fields
877 | }
878 | ```
879 |
880 |
881 | ### func [NewSet]()
882 |
883 | ```go
884 | func NewSet[T comparable]() *Set[T]
885 | ```
886 |
887 | NewSet returns a new Set with the given initial size.
888 |
889 |
890 | ### func \(\*Set\[T\]\) [Add]()
891 |
892 | ```go
893 | func (s *Set[T]) Add(val T)
894 | ```
895 |
896 | Add adds an element to the Set.
897 |
898 |
899 | ### func \(\*Set\[T\]\) [Contains]()
900 |
901 | ```go
902 | func (s *Set[T]) Contains(val T) bool
903 | ```
904 |
905 | Contains returns true if the Set contains the element.
906 |
907 |
908 | ### func \(\*Set\[T\]\) [Len]()
909 |
910 | ```go
911 | func (s *Set[T]) Len() int
912 | ```
913 |
914 | Len returns the number of elements in the Set.
915 |
916 |
917 | ### func \(\*Set\[T\]\) [Range]()
918 |
919 | ```go
920 | func (s *Set[T]) Range(fn func(element T) bool)
921 | ```
922 |
923 | Range executes a provided function once for each Set element until it returns false.
924 |
925 |
926 | ### func \(\*Set\[T\]\) [Remove]()
927 |
928 | ```go
929 | func (s *Set[T]) Remove(val T)
930 | ```
931 |
932 | Remove removes an element from the Set.
933 |
934 |
935 | ### func \(\*Set\[T\]\) [Sort]()
936 |
937 | ```go
938 | func (s *Set[T]) Sort(lessFunc func(i T, j T) bool) []T
939 | ```
940 |
941 | Sort returns the values of the set as an array sorted by the provided less function
942 |
943 |
944 | ### func \(\*Set\[T\]\) [Values]()
945 |
946 | ```go
947 | func (s *Set[T]) Values() []T
948 | ```
949 |
950 | Values returns the values of the set as an array
951 |
952 |
953 | ## type [Stack]()
954 |
955 | Stack is a basic LIFO Stack
956 |
957 | ```go
958 | type Stack[T any] struct {
959 | // contains filtered or unexported fields
960 | }
961 | ```
962 |
963 |
964 | ### func [NewStack]()
965 |
966 | ```go
967 | func NewStack[T any]() *Stack[T]
968 | ```
969 |
970 | NewStack returns a new Stack instance
971 |
972 |
973 | ### func \(\*Stack\[T\]\) [Clear]()
974 |
975 | ```go
976 | func (s *Stack[T]) Clear()
977 | ```
978 |
979 | Clear removes all elements from the Stack
980 |
981 |
982 | ### func \(\*Stack\[T\]\) [Len]()
983 |
984 | ```go
985 | func (s *Stack[T]) Len() int
986 | ```
987 |
988 | Len returns the number of elements in the Stack.
989 |
990 |
991 | ### func \(\*Stack\[T\]\) [Peek]()
992 |
993 | ```go
994 | func (s *Stack[T]) Peek() (T, bool)
995 | ```
996 |
997 | Peek returns the top element of the Stack without removing it. Return false if Stack is empty.
998 |
999 |
1000 | ### func \(\*Stack\[T\]\) [Pop]()
1001 |
1002 | ```go
1003 | func (s *Stack[T]) Pop() (T, bool)
1004 | ```
1005 |
1006 | Pop removes and return top element of Stack. Return false if Stack is empty.
1007 |
1008 |
1009 | ### func \(\*Stack\[T\]\) [Push]()
1010 |
1011 | ```go
1012 | func (s *Stack[T]) Push(f T)
1013 | ```
1014 |
1015 | Push a new value onto the Stack \(LIFO\)
1016 |
1017 |
1018 | ### func \(\*Stack\[T\]\) [Range]()
1019 |
1020 | ```go
1021 | func (s *Stack[T]) Range(fn func(element T) bool)
1022 | ```
1023 |
1024 | Range executes a provided function once for each Stack element until it returns false.
1025 |
1026 |
1027 | ### func \(\*Stack\[T\]\) [RangeUntil]()
1028 |
1029 | ```go
1030 | func (s *Stack[T]) RangeUntil(fn func(element T) bool, done chan struct{})
1031 | ```
1032 |
1033 | RangeUntil executes a provided function once after calling Pop on the stack until the function returns false or a value is sent on the done channel. Use this function when you want to continuously process items from the stack until a done signal is received.
1034 |
1035 |
1036 | ### func \(\*Stack\[T\]\) [Sort]()
1037 |
1038 | ```go
1039 | func (s *Stack[T]) Sort(lessFunc func(i T, j T) bool) []T
1040 | ```
1041 |
1042 | Sort returns the values of the stack as an array sorted by the provided less function
1043 |
1044 |
1045 | ### func \(\*Stack\[T\]\) [Values]()
1046 |
1047 | ```go
1048 | func (s *Stack[T]) Values() []T
1049 | ```
1050 |
1051 | Values returns the values of the stack as an array
1052 |
1053 | Generated by [gomarkdoc]()
1054 |
--------------------------------------------------------------------------------
/cover.out:
--------------------------------------------------------------------------------
1 | mode: atomic
2 | github.com/autom8ter/dagger/v3/dagger.go:50.37,53.16 3 100
3 | github.com/autom8ter/dagger/v3/dagger.go:53.16,54.13 1 0
4 | github.com/autom8ter/dagger/v3/dagger.go:56.2,56.18 1 100
5 | github.com/autom8ter/dagger/v3/dagger.go:56.18,58.3 1 0
6 | github.com/autom8ter/dagger/v3/dagger.go:59.2,59.60 1 100
7 | github.com/autom8ter/dagger/v3/dagger.go:78.36,80.2 1 1532
8 | github.com/autom8ter/dagger/v3/dagger.go:83.53,85.2 1 0
9 | github.com/autom8ter/dagger/v3/dagger.go:88.45,90.2 1 200
10 | github.com/autom8ter/dagger/v3/dagger.go:93.43,95.2 1 1200
11 | github.com/autom8ter/dagger/v3/dagger.go:98.46,100.2 1 200
12 | github.com/autom8ter/dagger/v3/dagger.go:103.64,104.29 1 0
13 | github.com/autom8ter/dagger/v3/dagger.go:104.29,106.3 1 0
14 | github.com/autom8ter/dagger/v3/dagger.go:129.92,131.2 1 0
15 | github.com/autom8ter/dagger/v3/dagger.go:134.92,136.2 1 0
16 | github.com/autom8ter/dagger/v3/dagger.go:140.86,143.62 3 2
17 | github.com/autom8ter/dagger/v3/dagger.go:143.62,144.64 1 1
18 | github.com/autom8ter/dagger/v3/dagger.go:144.64,146.4 1 0
19 | github.com/autom8ter/dagger/v3/dagger.go:147.3,147.18 1 1
20 | github.com/autom8ter/dagger/v3/dagger.go:153.84,156.60 3 2
21 | github.com/autom8ter/dagger/v3/dagger.go:156.60,157.64 1 1
22 | github.com/autom8ter/dagger/v3/dagger.go:157.64,159.4 1 0
23 | github.com/autom8ter/dagger/v3/dagger.go:160.3,160.18 1 1
24 | github.com/autom8ter/dagger/v3/dagger.go:168.117,169.21 1 507
25 | github.com/autom8ter/dagger/v3/dagger.go:169.21,171.3 1 0
26 | github.com/autom8ter/dagger/v3/dagger.go:172.2,175.9 4 507
27 | github.com/autom8ter/dagger/v3/dagger.go:175.9,177.3 1 0
28 | github.com/autom8ter/dagger/v3/dagger.go:178.2,187.31 5 507
29 | github.com/autom8ter/dagger/v3/dagger.go:187.31,189.17 2 1
30 | github.com/autom8ter/dagger/v3/dagger.go:189.17,191.4 1 0
31 | github.com/autom8ter/dagger/v3/dagger.go:192.3,193.41 2 1
32 | github.com/autom8ter/dagger/v3/dagger.go:193.41,195.4 1 0
33 | github.com/autom8ter/dagger/v3/dagger.go:196.3,196.41 1 1
34 | github.com/autom8ter/dagger/v3/dagger.go:196.41,198.4 1 0
35 | github.com/autom8ter/dagger/v3/dagger.go:199.3,199.49 1 1
36 | github.com/autom8ter/dagger/v3/dagger.go:199.49,201.4 1 0
37 | github.com/autom8ter/dagger/v3/dagger.go:202.3,202.43 1 1
38 | github.com/autom8ter/dagger/v3/dagger.go:202.43,205.4 2 0
39 | github.com/autom8ter/dagger/v3/dagger.go:206.3,206.47 1 1
40 | github.com/autom8ter/dagger/v3/dagger.go:206.47,209.4 2 0
41 | github.com/autom8ter/dagger/v3/dagger.go:210.3,210.14 1 1
42 | github.com/autom8ter/dagger/v3/dagger.go:212.2,212.15 1 507
43 | github.com/autom8ter/dagger/v3/dagger.go:216.50,220.2 3 1
44 | github.com/autom8ter/dagger/v3/dagger.go:222.50,224.9 2 2
45 | github.com/autom8ter/dagger/v3/dagger.go:224.9,226.3 1 0
46 | github.com/autom8ter/dagger/v3/dagger.go:227.2,230.22 4 2
47 | github.com/autom8ter/dagger/v3/dagger.go:230.22,232.3 1 0
48 | github.com/autom8ter/dagger/v3/dagger.go:236.39,237.60 1 1
49 | github.com/autom8ter/dagger/v3/dagger.go:237.60,240.3 2 0
50 | github.com/autom8ter/dagger/v3/dagger.go:241.2,241.62 1 1
51 | github.com/autom8ter/dagger/v3/dagger.go:241.62,244.3 2 1
52 | github.com/autom8ter/dagger/v3/dagger.go:245.2,246.31 2 1
53 | github.com/autom8ter/dagger/v3/dagger.go:246.31,248.3 1 0
54 | github.com/autom8ter/dagger/v3/dagger.go:249.2,249.12 1 1
55 | github.com/autom8ter/dagger/v3/dagger.go:253.40,255.2 1 0
56 | github.com/autom8ter/dagger/v3/dagger.go:258.68,263.2 4 0
57 | github.com/autom8ter/dagger/v3/dagger.go:265.90,266.60 1 0
58 | github.com/autom8ter/dagger/v3/dagger.go:266.60,267.41 1 0
59 | github.com/autom8ter/dagger/v3/dagger.go:267.41,269.4 1 0
60 | github.com/autom8ter/dagger/v3/dagger.go:270.3,271.23 2 0
61 | github.com/autom8ter/dagger/v3/dagger.go:271.23,273.4 1 0
62 | github.com/autom8ter/dagger/v3/dagger.go:274.3,275.14 2 0
63 | github.com/autom8ter/dagger/v3/dagger.go:280.70,285.2 4 0
64 | github.com/autom8ter/dagger/v3/dagger.go:287.92,288.62 1 0
65 | github.com/autom8ter/dagger/v3/dagger.go:288.62,289.39 1 0
66 | github.com/autom8ter/dagger/v3/dagger.go:289.39,291.4 1 0
67 | github.com/autom8ter/dagger/v3/dagger.go:292.3,293.21 2 0
68 | github.com/autom8ter/dagger/v3/dagger.go:293.21,295.4 1 0
69 | github.com/autom8ter/dagger/v3/dagger.go:296.3,297.14 2 0
70 | github.com/autom8ter/dagger/v3/dagger.go:302.63,304.62 2 0
71 | github.com/autom8ter/dagger/v3/dagger.go:304.62,305.24 1 0
72 | github.com/autom8ter/dagger/v3/dagger.go:305.24,308.4 2 0
73 | github.com/autom8ter/dagger/v3/dagger.go:309.3,309.14 1 0
74 | github.com/autom8ter/dagger/v3/dagger.go:311.2,311.60 1 0
75 | github.com/autom8ter/dagger/v3/dagger.go:311.60,312.26 1 0
76 | github.com/autom8ter/dagger/v3/dagger.go:312.26,315.4 2 0
77 | github.com/autom8ter/dagger/v3/dagger.go:316.3,316.14 1 0
78 | github.com/autom8ter/dagger/v3/dagger.go:318.2,318.15 1 0
79 | github.com/autom8ter/dagger/v3/dagger.go:339.33,340.29 1 1
80 | github.com/autom8ter/dagger/v3/dagger.go:340.29,342.3 1 1
81 | github.com/autom8ter/dagger/v3/dagger.go:346.54,349.27 3 13
82 | github.com/autom8ter/dagger/v3/dagger.go:349.27,351.3 1 1
83 | github.com/autom8ter/dagger/v3/dagger.go:352.2,358.23 2 13
84 | github.com/autom8ter/dagger/v3/dagger.go:358.23,361.3 2 1
85 | github.com/autom8ter/dagger/v3/dagger.go:362.2,362.15 1 13
86 | github.com/autom8ter/dagger/v3/dagger.go:366.51,374.25 3 522
87 | github.com/autom8ter/dagger/v3/dagger.go:374.25,376.17 2 2
88 | github.com/autom8ter/dagger/v3/dagger.go:376.17,377.14 1 0
89 | github.com/autom8ter/dagger/v3/dagger.go:379.3,380.48 2 2
90 | github.com/autom8ter/dagger/v3/dagger.go:380.48,382.4 1 0
91 | github.com/autom8ter/dagger/v3/dagger.go:383.3,383.48 1 2
92 | github.com/autom8ter/dagger/v3/dagger.go:383.48,385.4 1 0
93 | github.com/autom8ter/dagger/v3/dagger.go:387.3,387.14 1 2
94 | github.com/autom8ter/dagger/v3/dagger.go:390.2,390.10 1 522
95 | github.com/autom8ter/dagger/v3/dagger.go:394.42,397.2 2 0
96 | github.com/autom8ter/dagger/v3/dagger.go:400.42,403.2 2 0
97 | github.com/autom8ter/dagger/v3/dagger.go:406.59,409.2 2 1
98 | github.com/autom8ter/dagger/v3/dagger.go:412.36,414.2 1 5
99 | github.com/autom8ter/dagger/v3/dagger.go:417.45,419.57 2 0
100 | github.com/autom8ter/dagger/v3/dagger.go:419.57,422.3 2 0
101 | github.com/autom8ter/dagger/v3/dagger.go:423.2,423.14 1 0
102 | github.com/autom8ter/dagger/v3/dagger.go:427.45,429.57 2 0
103 | github.com/autom8ter/dagger/v3/dagger.go:429.57,432.3 2 0
104 | github.com/autom8ter/dagger/v3/dagger.go:433.2,433.14 1 0
105 | github.com/autom8ter/dagger/v3/dagger.go:437.59,440.2 2 3
106 | github.com/autom8ter/dagger/v3/dagger.go:443.60,444.57 1 0
107 | github.com/autom8ter/dagger/v3/dagger.go:444.57,446.3 1 0
108 | github.com/autom8ter/dagger/v3/dagger.go:450.60,451.57 1 0
109 | github.com/autom8ter/dagger/v3/dagger.go:451.57,453.3 1 0
110 | github.com/autom8ter/dagger/v3/dagger.go:462.111,474.17 5 1
111 | github.com/autom8ter/dagger/v3/dagger.go:474.17,476.3 1 0
112 | github.com/autom8ter/dagger/v3/dagger.go:477.2,477.48 1 1
113 | github.com/autom8ter/dagger/v3/dagger.go:477.48,478.38 1 101
114 | github.com/autom8ter/dagger/v3/dagger.go:478.38,480.4 1 100
115 | github.com/autom8ter/dagger/v3/dagger.go:481.3,481.14 1 1
116 | github.com/autom8ter/dagger/v3/dagger.go:483.2,483.12 1 1
117 | github.com/autom8ter/dagger/v3/dagger.go:489.107,494.23 5 1
118 | github.com/autom8ter/dagger/v3/dagger.go:494.23,504.18 2 1
119 | github.com/autom8ter/dagger/v3/dagger.go:504.18,506.4 1 0
120 | github.com/autom8ter/dagger/v3/dagger.go:507.3,507.37 1 1
121 | github.com/autom8ter/dagger/v3/dagger.go:507.37,509.4 1 0
122 | github.com/autom8ter/dagger/v3/dagger.go:510.3,511.13 2 1
123 | github.com/autom8ter/dagger/v3/dagger.go:513.2,513.23 1 1
124 | github.com/autom8ter/dagger/v3/dagger.go:513.23,514.54 1 1
125 | github.com/autom8ter/dagger/v3/dagger.go:514.54,515.39 1 101
126 | github.com/autom8ter/dagger/v3/dagger.go:515.39,517.5 1 100
127 | github.com/autom8ter/dagger/v3/dagger.go:518.4,518.15 1 1
128 | github.com/autom8ter/dagger/v3/dagger.go:520.3,520.13 1 1
129 | github.com/autom8ter/dagger/v3/dagger.go:522.2,522.36 1 1
130 | github.com/autom8ter/dagger/v3/dagger.go:522.36,524.3 1 0
131 | github.com/autom8ter/dagger/v3/dagger.go:525.2,525.12 1 1
132 | github.com/autom8ter/dagger/v3/dagger.go:543.99,544.22 1 101
133 | github.com/autom8ter/dagger/v3/dagger.go:544.22,546.3 1 0
134 | github.com/autom8ter/dagger/v3/dagger.go:547.2,547.23 1 101
135 | github.com/autom8ter/dagger/v3/dagger.go:547.23,549.3 1 0
136 | github.com/autom8ter/dagger/v3/dagger.go:550.2,550.46 1 101
137 | github.com/autom8ter/dagger/v3/dagger.go:550.46,556.20 3 101
138 | github.com/autom8ter/dagger/v3/dagger.go:556.20,557.71 1 0
139 | github.com/autom8ter/dagger/v3/dagger.go:557.71,567.5 2 0
140 | github.com/autom8ter/dagger/v3/dagger.go:568.9,569.73 1 101
141 | github.com/autom8ter/dagger/v3/dagger.go:569.73,579.5 2 100
142 | github.com/autom8ter/dagger/v3/dagger.go:582.2,582.18 1 101
143 | github.com/autom8ter/dagger/v3/dagger.go:595.95,596.22 1 101
144 | github.com/autom8ter/dagger/v3/dagger.go:596.22,598.3 1 0
145 | github.com/autom8ter/dagger/v3/dagger.go:599.2,599.23 1 101
146 | github.com/autom8ter/dagger/v3/dagger.go:599.23,601.3 1 0
147 | github.com/autom8ter/dagger/v3/dagger.go:602.2,602.46 1 101
148 | github.com/autom8ter/dagger/v3/dagger.go:602.46,608.20 3 101
149 | github.com/autom8ter/dagger/v3/dagger.go:608.20,609.30 1 0
150 | github.com/autom8ter/dagger/v3/dagger.go:609.30,610.72 1 0
151 | github.com/autom8ter/dagger/v3/dagger.go:610.72,621.6 2 0
152 | github.com/autom8ter/dagger/v3/dagger.go:622.5,622.15 1 0
153 | github.com/autom8ter/dagger/v3/dagger.go:624.9,625.30 1 101
154 | github.com/autom8ter/dagger/v3/dagger.go:625.30,626.74 1 101
155 | github.com/autom8ter/dagger/v3/dagger.go:626.74,637.6 2 100
156 | github.com/autom8ter/dagger/v3/dagger.go:638.5,638.15 1 101
157 | github.com/autom8ter/dagger/v3/dagger.go:642.2,642.18 1 101
158 | github.com/autom8ter/dagger/v3/dagger.go:646.33,648.58 2 5
159 | github.com/autom8ter/dagger/v3/dagger.go:648.58,649.31 1 505
160 | github.com/autom8ter/dagger/v3/dagger.go:649.31,652.42 3 401
161 | github.com/autom8ter/dagger/v3/dagger.go:652.42,655.5 2 0
162 | github.com/autom8ter/dagger/v3/dagger.go:657.3,657.14 1 505
163 | github.com/autom8ter/dagger/v3/dagger.go:659.2,659.18 1 5
164 | github.com/autom8ter/dagger/v3/dagger.go:663.96,667.65 4 401
165 | github.com/autom8ter/dagger/v3/dagger.go:667.65,668.39 1 500
166 | github.com/autom8ter/dagger/v3/dagger.go:668.39,669.47 1 0
167 | github.com/autom8ter/dagger/v3/dagger.go:669.47,672.5 2 0
168 | github.com/autom8ter/dagger/v3/dagger.go:673.9,673.46 1 500
169 | github.com/autom8ter/dagger/v3/dagger.go:673.46,676.4 2 0
170 | github.com/autom8ter/dagger/v3/dagger.go:677.3,677.14 1 500
171 | github.com/autom8ter/dagger/v3/dagger.go:679.2,679.15 1 401
172 | github.com/autom8ter/dagger/v3/dagger.go:682.73,683.18 1 2
173 | github.com/autom8ter/dagger/v3/dagger.go:683.18,685.3 1 0
174 | github.com/autom8ter/dagger/v3/dagger.go:686.2,689.58 4 2
175 | github.com/autom8ter/dagger/v3/dagger.go:689.58,692.3 2 202
176 | github.com/autom8ter/dagger/v3/dagger.go:693.2,694.22 2 2
177 | github.com/autom8ter/dagger/v3/dagger.go:694.22,697.3 2 202
178 | github.com/autom8ter/dagger/v3/dagger.go:698.2,698.13 1 2
179 | github.com/autom8ter/dagger/v3/dagger.go:698.13,699.56 1 1
180 | github.com/autom8ter/dagger/v3/dagger.go:699.56,701.4 1 50
181 | github.com/autom8ter/dagger/v3/dagger.go:703.2,703.20 1 2
182 | github.com/autom8ter/dagger/v3/dagger.go:706.125,707.35 1 402
183 | github.com/autom8ter/dagger/v3/dagger.go:707.35,709.3 1 200
184 | github.com/autom8ter/dagger/v3/dagger.go:710.2,710.35 1 202
185 | github.com/autom8ter/dagger/v3/dagger.go:710.35,711.21 1 0
186 | github.com/autom8ter/dagger/v3/dagger.go:713.2,714.13 2 202
187 | github.com/autom8ter/dagger/v3/dagger.go:714.13,715.64 1 202
188 | github.com/autom8ter/dagger/v3/dagger.go:715.64,718.4 2 200
189 | github.com/autom8ter/dagger/v3/dagger.go:719.8,720.66 1 0
190 | github.com/autom8ter/dagger/v3/dagger.go:720.66,723.4 2 0
191 | github.com/autom8ter/dagger/v3/dagger.go:725.2,727.18 3 202
192 | github.com/autom8ter/dagger/v3/dagger.go:731.50,732.18 1 1
193 | github.com/autom8ter/dagger/v3/dagger.go:732.18,734.3 1 0
194 | github.com/autom8ter/dagger/v3/dagger.go:735.2,738.16 4 1
195 | github.com/autom8ter/dagger/v3/dagger.go:738.16,740.3 1 0
196 | github.com/autom8ter/dagger/v3/dagger.go:741.2,741.17 1 1
197 | github.com/autom8ter/dagger/v3/dagger.go:745.55,749.2 1 1071
198 | github.com/autom8ter/dagger/v3/dagger.go:757.35,759.49 2 717
199 | github.com/autom8ter/dagger/v3/dagger.go:759.49,762.3 2 21805
200 | github.com/autom8ter/dagger/v3/dagger.go:763.2,763.14 1 717
201 | github.com/autom8ter/dagger/v3/dagger.go:767.46,769.9 2 513
202 | github.com/autom8ter/dagger/v3/dagger.go:769.9,771.3 1 3
203 | github.com/autom8ter/dagger/v3/dagger.go:772.2,772.18 1 510
204 | github.com/autom8ter/dagger/v3/dagger.go:776.45,778.2 1 2143
205 | github.com/autom8ter/dagger/v3/dagger.go:781.39,783.2 1 107
206 | github.com/autom8ter/dagger/v3/dagger.go:786.44,789.2 2 0
207 | github.com/autom8ter/dagger/v3/dagger.go:792.33,793.49 1 0
208 | github.com/autom8ter/dagger/v3/dagger.go:793.49,796.3 2 0
209 | github.com/autom8ter/dagger/v3/dagger.go:800.36,802.49 2 0
210 | github.com/autom8ter/dagger/v3/dagger.go:802.49,805.3 2 0
211 | github.com/autom8ter/dagger/v3/dagger.go:806.2,806.13 1 0
212 | github.com/autom8ter/dagger/v3/dagger.go:810.38,812.49 2 0
213 | github.com/autom8ter/dagger/v3/dagger.go:812.49,815.3 2 0
214 | github.com/autom8ter/dagger/v3/dagger.go:816.2,816.15 1 0
215 | github.com/autom8ter/dagger/v3/dagger.go:820.60,821.49 1 918
216 | github.com/autom8ter/dagger/v3/dagger.go:821.49,823.3 1 11610
217 | github.com/autom8ter/dagger/v3/dagger.go:827.76,829.49 2 0
218 | github.com/autom8ter/dagger/v3/dagger.go:829.49,830.28 1 0
219 | github.com/autom8ter/dagger/v3/dagger.go:830.28,832.4 1 0
220 | github.com/autom8ter/dagger/v3/dagger.go:833.3,833.14 1 0
221 | github.com/autom8ter/dagger/v3/dagger.go:835.2,835.17 1 0
222 | github.com/autom8ter/dagger/v3/dagger.go:839.39,841.49 2 0
223 | github.com/autom8ter/dagger/v3/dagger.go:841.49,844.3 2 0
224 | github.com/autom8ter/dagger/v3/dagger.go:845.2,845.15 1 0
225 | github.com/autom8ter/dagger/v3/dagger.go:861.50,866.2 1 1
226 | github.com/autom8ter/dagger/v3/dagger.go:868.70,871.31 3 0
227 | github.com/autom8ter/dagger/v3/dagger.go:871.31,872.43 1 0
228 | github.com/autom8ter/dagger/v3/dagger.go:872.43,874.44 2 0
229 | github.com/autom8ter/dagger/v3/dagger.go:874.44,876.5 1 0
230 | github.com/autom8ter/dagger/v3/dagger.go:877.4,877.10 1 0
231 | github.com/autom8ter/dagger/v3/dagger.go:883.38,887.2 3 2
232 | github.com/autom8ter/dagger/v3/dagger.go:890.57,894.42 4 100
233 | github.com/autom8ter/dagger/v3/dagger.go:894.42,896.3 1 5673
234 | github.com/autom8ter/dagger/v3/dagger.go:900.44,903.23 3 100
235 | github.com/autom8ter/dagger/v3/dagger.go:903.23,905.3 1 0
236 | github.com/autom8ter/dagger/v3/dagger.go:906.2,908.25 3 100
237 | github.com/autom8ter/dagger/v3/dagger.go:912.45,915.23 3 1
238 | github.com/autom8ter/dagger/v3/dagger.go:915.23,917.3 1 0
239 | github.com/autom8ter/dagger/v3/dagger.go:918.2,918.31 1 1
240 | github.com/autom8ter/dagger/v3/dagger.go:929.59,932.2 2 2
241 | github.com/autom8ter/dagger/v3/dagger.go:935.58,936.6 1 0
242 | github.com/autom8ter/dagger/v3/dagger.go:936.6,937.10 1 0
243 | github.com/autom8ter/dagger/v3/dagger.go:938.24,939.11 1 0
244 | github.com/autom8ter/dagger/v3/dagger.go:939.11,941.5 1 0
245 | github.com/autom8ter/dagger/v3/dagger.go:942.4,942.14 1 0
246 | github.com/autom8ter/dagger/v3/dagger.go:942.14,944.5 1 0
247 | github.com/autom8ter/dagger/v3/dagger.go:945.11,946.22 1 0
248 | github.com/autom8ter/dagger/v3/dagger.go:946.22,948.5 1 0
249 | github.com/autom8ter/dagger/v3/dagger.go:955.83,956.6 1 1
250 | github.com/autom8ter/dagger/v3/dagger.go:956.6,957.10 1 102
251 | github.com/autom8ter/dagger/v3/dagger.go:958.15,959.10 1 1
252 | github.com/autom8ter/dagger/v3/dagger.go:960.24,961.11 1 101
253 | github.com/autom8ter/dagger/v3/dagger.go:961.11,963.5 1 0
254 | github.com/autom8ter/dagger/v3/dagger.go:964.4,964.14 1 101
255 | github.com/autom8ter/dagger/v3/dagger.go:964.14,966.5 1 0
256 | github.com/autom8ter/dagger/v3/dagger.go:972.35,973.24 1 0
257 | github.com/autom8ter/dagger/v3/dagger.go:973.24,975.3 1 0
258 | github.com/autom8ter/dagger/v3/dagger.go:979.39,981.2 1 201
259 | github.com/autom8ter/dagger/v3/dagger.go:984.43,985.9 1 100
260 | github.com/autom8ter/dagger/v3/dagger.go:986.23,987.15 1 100
261 | github.com/autom8ter/dagger/v3/dagger.go:992.37,994.2 1 2
262 | github.com/autom8ter/dagger/v3/dagger.go:1003.34,1006.2 2 1
263 | github.com/autom8ter/dagger/v3/dagger.go:1009.30,1013.2 3 100
264 | github.com/autom8ter/dagger/v3/dagger.go:1016.36,1019.24 3 100
265 | github.com/autom8ter/dagger/v3/dagger.go:1019.24,1021.3 1 0
266 | github.com/autom8ter/dagger/v3/dagger.go:1021.8,1026.3 4 100
267 | github.com/autom8ter/dagger/v3/dagger.go:1030.30,1034.2 3 2
268 | github.com/autom8ter/dagger/v3/dagger.go:1037.37,1040.24 3 0
269 | github.com/autom8ter/dagger/v3/dagger.go:1040.24,1042.3 1 0
270 | github.com/autom8ter/dagger/v3/dagger.go:1043.2,1043.40 1 0
271 | github.com/autom8ter/dagger/v3/dagger.go:1047.51,1048.6 1 0
272 | github.com/autom8ter/dagger/v3/dagger.go:1048.6,1050.10 2 0
273 | github.com/autom8ter/dagger/v3/dagger.go:1050.10,1052.4 1 0
274 | github.com/autom8ter/dagger/v3/dagger.go:1053.3,1053.15 1 0
275 | github.com/autom8ter/dagger/v3/dagger.go:1053.15,1055.4 1 0
276 | github.com/autom8ter/dagger/v3/dagger.go:1061.76,1062.6 1 0
277 | github.com/autom8ter/dagger/v3/dagger.go:1062.6,1063.10 1 0
278 | github.com/autom8ter/dagger/v3/dagger.go:1064.15,1065.10 1 0
279 | github.com/autom8ter/dagger/v3/dagger.go:1066.11,1068.11 2 0
280 | github.com/autom8ter/dagger/v3/dagger.go:1068.11,1070.5 1 0
281 | github.com/autom8ter/dagger/v3/dagger.go:1071.4,1071.16 1 0
282 | github.com/autom8ter/dagger/v3/dagger.go:1071.16,1073.5 1 0
283 | github.com/autom8ter/dagger/v3/dagger.go:1079.34,1082.2 2 4
284 | github.com/autom8ter/dagger/v3/dagger.go:1092.76,1093.6 1 0
285 | github.com/autom8ter/dagger/v3/dagger.go:1093.6,1094.10 1 0
286 | github.com/autom8ter/dagger/v3/dagger.go:1095.15,1096.10 1 0
287 | github.com/autom8ter/dagger/v3/dagger.go:1097.11,1099.11 2 0
288 | github.com/autom8ter/dagger/v3/dagger.go:1099.11,1101.5 1 0
289 | github.com/autom8ter/dagger/v3/dagger.go:1102.4,1102.16 1 0
290 | github.com/autom8ter/dagger/v3/dagger.go:1102.16,1104.5 1 0
291 | github.com/autom8ter/dagger/v3/dagger.go:1110.30,1114.2 3 403
292 | github.com/autom8ter/dagger/v3/dagger.go:1117.36,1120.24 3 404
293 | github.com/autom8ter/dagger/v3/dagger.go:1120.24,1122.3 1 1
294 | github.com/autom8ter/dagger/v3/dagger.go:1122.8,1127.3 4 403
295 | github.com/autom8ter/dagger/v3/dagger.go:1131.28,1135.2 3 0
296 | github.com/autom8ter/dagger/v3/dagger.go:1138.51,1139.6 1 1
297 | github.com/autom8ter/dagger/v3/dagger.go:1139.6,1141.10 2 102
298 | github.com/autom8ter/dagger/v3/dagger.go:1141.10,1143.4 1 1
299 | github.com/autom8ter/dagger/v3/dagger.go:1144.3,1144.13 1 101
300 | github.com/autom8ter/dagger/v3/dagger.go:1144.13,1146.4 1 0
301 | github.com/autom8ter/dagger/v3/dagger.go:1151.33,1155.2 3 0
302 | github.com/autom8ter/dagger/v3/dagger.go:1158.59,1160.41 2 0
303 | github.com/autom8ter/dagger/v3/dagger.go:1160.41,1162.3 1 0
304 | github.com/autom8ter/dagger/v3/dagger.go:1163.2,1163.15 1 0
305 | github.com/autom8ter/dagger/v3/dagger.go:1167.30,1171.2 3 206
306 | github.com/autom8ter/dagger/v3/dagger.go:1174.37,1177.24 3 0
307 | github.com/autom8ter/dagger/v3/dagger.go:1177.24,1179.3 1 0
308 | github.com/autom8ter/dagger/v3/dagger.go:1179.8,1183.3 3 0
309 | github.com/autom8ter/dagger/v3/dagger.go:1193.37,1196.2 2 809
310 | github.com/autom8ter/dagger/v3/dagger.go:1199.29,1203.2 3 1508
311 | github.com/autom8ter/dagger/v3/dagger.go:1206.32,1210.2 3 302
312 | github.com/autom8ter/dagger/v3/dagger.go:1213.39,1218.2 4 1906
313 | github.com/autom8ter/dagger/v3/dagger.go:1221.49,1224.26 3 0
314 | github.com/autom8ter/dagger/v3/dagger.go:1224.26,1225.13 1 0
315 | github.com/autom8ter/dagger/v3/dagger.go:1225.13,1227.4 1 0
316 | github.com/autom8ter/dagger/v3/dagger.go:1232.28,1236.2 3 203
317 | github.com/autom8ter/dagger/v3/dagger.go:1239.31,1243.29 4 0
318 | github.com/autom8ter/dagger/v3/dagger.go:1243.29,1245.3 1 0
319 | github.com/autom8ter/dagger/v3/dagger.go:1246.2,1246.15 1 0
320 | github.com/autom8ter/dagger/v3/dagger.go:1250.57,1252.41 2 0
321 | github.com/autom8ter/dagger/v3/dagger.go:1252.41,1254.3 1 0
322 | github.com/autom8ter/dagger/v3/dagger.go:1255.2,1255.15 1 0
323 | github.com/autom8ter/dagger/v3/dagger.go:1273.67,1279.2 1 1
324 | github.com/autom8ter/dagger/v3/dagger.go:1282.60,1284.12 2 100
325 | github.com/autom8ter/dagger/v3/dagger.go:1284.12,1288.74 4 100
326 | github.com/autom8ter/dagger/v3/dagger.go:1288.74,1291.11 3 10000
327 | github.com/autom8ter/dagger/v3/dagger.go:1292.22,1293.17 1 0
328 | github.com/autom8ter/dagger/v3/dagger.go:1294.28,1295.16 1 0
329 | github.com/autom8ter/dagger/v3/dagger.go:1296.25,1297.16 1 10000
330 | github.com/autom8ter/dagger/v3/dagger.go:1301.2,1301.8 1 100
331 | github.com/autom8ter/dagger/v3/dagger.go:1306.65,1315.12 6 100
332 | github.com/autom8ter/dagger/v3/dagger.go:1315.12,1324.3 8 100
333 | github.com/autom8ter/dagger/v3/dagger.go:1325.2,1325.11 1 100
334 | github.com/autom8ter/dagger/v3/dagger.go:1329.37,1331.2 1 2
335 | github.com/autom8ter/dagger/v3/dagger.go:1334.35,1337.2 2 1
336 | github.com/autom8ter/dagger/v3/dagger.go:1348.57,1354.12 3 1
337 | github.com/autom8ter/dagger/v3/dagger.go:1354.12,1355.10 1 1
338 | github.com/autom8ter/dagger/v3/dagger.go:1356.19,1358.37 2 1
339 | github.com/autom8ter/dagger/v3/dagger.go:1358.37,1360.5 1 300
340 | github.com/autom8ter/dagger/v3/dagger.go:1361.4,1361.17 1 1
341 | github.com/autom8ter/dagger/v3/dagger.go:1364.2,1364.10 1 1
342 | github.com/autom8ter/dagger/v3/dagger.go:1369.73,1375.2 5 300
343 | github.com/autom8ter/dagger/v3/dagger.go:1378.33,1381.35 3 1
344 | github.com/autom8ter/dagger/v3/dagger.go:1381.35,1383.3 1 300
345 | github.com/autom8ter/dagger/v3/dagger.go:1384.2,1384.12 1 1
346 | github.com/autom8ter/dagger/v3/dagger.go:1397.47,1407.2 5 1
347 | github.com/autom8ter/dagger/v3/dagger.go:1410.35,1412.2 1 4
348 | github.com/autom8ter/dagger/v3/dagger.go:1415.46,1416.9 1 0
349 | github.com/autom8ter/dagger/v3/dagger.go:1417.27,1418.10 1 0
350 | github.com/autom8ter/dagger/v3/dagger.go:1418.10,1420.4 1 0
351 | github.com/autom8ter/dagger/v3/dagger.go:1421.3,1421.21 1 0
352 | github.com/autom8ter/dagger/v3/dagger.go:1422.10,1423.20 1 0
353 | github.com/autom8ter/dagger/v3/dagger.go:1428.70,1431.9 3 1
354 | github.com/autom8ter/dagger/v3/dagger.go:1432.27,1433.10 1 0
355 | github.com/autom8ter/dagger/v3/dagger.go:1433.10,1435.4 1 0
356 | github.com/autom8ter/dagger/v3/dagger.go:1436.3,1436.20 1 0
357 | github.com/autom8ter/dagger/v3/dagger.go:1437.20,1438.24 1 1
358 | github.com/autom8ter/dagger/v3/dagger.go:1445.44,1448.23 3 4
359 | github.com/autom8ter/dagger/v3/dagger.go:1448.23,1450.3 1 0
360 | github.com/autom8ter/dagger/v3/dagger.go:1451.2,1451.19 1 4
361 | github.com/autom8ter/dagger/v3/dagger.go:1451.19,1453.3 1 1
362 | github.com/autom8ter/dagger/v3/dagger.go:1454.2,1456.12 3 3
363 | github.com/autom8ter/dagger/v3/dagger.go:1460.33,1462.2 1 1
364 | github.com/autom8ter/dagger/v3/dagger.go:1465.45,1469.2 3 1
365 | github.com/autom8ter/dagger/v3/dagger.go:1472.43,1474.9 2 0
366 | github.com/autom8ter/dagger/v3/dagger.go:1474.9,1476.3 1 0
367 | github.com/autom8ter/dagger/v3/dagger.go:1477.2,1478.25 2 0
368 | github.com/autom8ter/dagger/v3/dagger.go:1483.37,1485.19 2 1
369 | github.com/autom8ter/dagger/v3/dagger.go:1485.19,1488.20 3 1
370 | github.com/autom8ter/dagger/v3/dagger.go:1488.20,1491.4 2 0
371 | github.com/autom8ter/dagger/v3/dagger.go:1492.3,1492.17 1 1
372 | github.com/autom8ter/dagger/v3/dagger.go:1492.17,1496.4 3 1
373 | github.com/autom8ter/dagger/v3/dagger.go:1498.2,1498.12 1 1
374 | github.com/autom8ter/dagger/v3/dagger.go:1503.46,1504.37 1 0
375 | github.com/autom8ter/dagger/v3/dagger.go:1504.37,1507.3 2 0
376 |
--------------------------------------------------------------------------------
/dagger.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package dagger is a collection of generic, concurrency safe datastructures including a Directed Acyclic Graph and others.
3 | Datastructures are implemented using generics in Go 1.18.
4 |
5 | Supported Datastructures:
6 |
7 | DAG: thread safe directed acyclic graph
8 |
9 | Queue: unbounded thread safe fifo queue
10 |
11 | Stack: unbounded thread safe lifo stack
12 |
13 | BoundedQueue: bounded thread safe fifo queue with a fixed capacity
14 |
15 | PriorityQueue: thread safe priority queue
16 |
17 | HashMap: thread safe hashmap
18 |
19 | Set: thread safe set
20 |
21 | ChannelGroup: thread safe group of channels for broadcasting 1 value to N channels
22 |
23 | MultiContext: thread safe context for coordinating the cancellation of multiple contexts
24 |
25 | Borrower: thread safe object ownership manager
26 | */
27 | package dagger
28 |
29 | import (
30 | "context"
31 | "crypto/rand"
32 | "encoding/hex"
33 | "fmt"
34 | "image"
35 | "reflect"
36 | "sort"
37 | "strconv"
38 | "strings"
39 | "sync"
40 | "time"
41 |
42 | "github.com/goccy/go-graphviz"
43 | "github.com/goccy/go-graphviz/cgraph"
44 | "golang.org/x/sync/errgroup"
45 |
46 | "github.com/autom8ter/async"
47 | )
48 |
49 | // UniqueID returns a unique identifier with the given prefix
50 | func UniqueID(prefix string) string {
51 | b := make([]byte, 4)
52 | _, err := rand.Read(b)
53 | if err != nil {
54 | panic(err)
55 | }
56 | if prefix == "" {
57 | prefix = "id"
58 | }
59 | return fmt.Sprintf("%s-%s", prefix, hex.EncodeToString(b))
60 | }
61 |
62 | // GraphEdge is a relationship between two nodes
63 | type GraphEdge[T Node] struct {
64 | // ID is the unique identifier of the edge
65 | id string
66 | // Metadata is the metadata of the edge
67 | metadata map[string]string
68 | // From returns the root node of the edge
69 | from *GraphNode[T]
70 | // To returns the target node of the edge
71 | to *GraphNode[T]
72 | // Relationship is the relationship between the two nodes
73 | relationship string
74 | edge *cgraph.Edge
75 | }
76 |
77 | // ID returns the unique identifier of the node
78 | func (n *GraphEdge[T]) ID() string {
79 | return n.id
80 | }
81 |
82 | // Metadata returns the metadata of the node
83 | func (n *GraphEdge[T]) Metadata() map[string]string {
84 | return n.metadata
85 | }
86 |
87 | // From returns the from node of the edge
88 | func (n *GraphEdge[T]) From() *GraphNode[T] {
89 | return n.from
90 | }
91 |
92 | // To returns the to node of the edge
93 | func (n *GraphEdge[T]) To() *GraphNode[T] {
94 | return n.to
95 | }
96 |
97 | // Relationship returns the relationship between the two nodes
98 | func (n *GraphEdge[T]) Relationship() string {
99 | return n.relationship
100 | }
101 |
102 | // SetMetadata sets the metadata of the node
103 | func (n *GraphEdge[T]) SetMetadata(metadata map[string]string) {
104 | for k, v := range metadata {
105 | n.metadata[k] = v
106 | }
107 | }
108 |
109 | // Node is a node in the graph. It can be connected to other nodes via edges.
110 | type Node interface {
111 | // ID returns the unique identifier of the node
112 | ID() string
113 | // Metadata returns the metadata of the node
114 | Metadata() map[string]string
115 | // SetMetadata sets the metadata of the node
116 | SetMetadata(metadata map[string]string)
117 | }
118 |
119 | // GraphNode is a node in the graph. It can be connected to other nodes via edges.
120 | type GraphNode[T Node] struct {
121 | Node
122 | edgesFrom *HashMap[string, *GraphEdge[T]]
123 | edgesTo *HashMap[string, *GraphEdge[T]]
124 | graph *DAG[T]
125 | node *cgraph.Node
126 | }
127 |
128 | // DFS performs a depth-first search on the graph starting from the current node
129 | func (n *GraphNode[T]) DFS(ctx context.Context, reverse bool, fn GraphSearchFunc[T]) error {
130 | return n.graph.DFS(ctx, reverse, n, fn)
131 | }
132 |
133 | // BFS performs a breadth-first search on the graph starting from the current node
134 | func (n *GraphNode[T]) BFS(ctx context.Context, reverse bool, fn GraphSearchFunc[T]) error {
135 | return n.graph.BFS(ctx, reverse, n, fn)
136 | }
137 |
138 | // EdgesFrom iterates over the edges from the current node to other nodes with the given relationship.
139 | // If the relationship is empty, all relationships will be iterated over.
140 | func (n *GraphNode[T]) EdgesFrom(relationship string, fn func(e *GraphEdge[T]) bool) {
141 | n.graph.mu.RLock()
142 | defer n.graph.mu.RUnlock()
143 | n.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool {
144 | if relationship != "" && edge.Relationship() != relationship {
145 | return true
146 | }
147 | return fn(edge)
148 | })
149 | }
150 |
151 | // EdgesTo iterates over the edges from other nodes to the current node with the given relationship.
152 | // If the relationship is empty, all relationships will be iterated over.
153 | func (n *GraphNode[T]) EdgesTo(relationship string, fn func(e *GraphEdge[T]) bool) {
154 | n.graph.mu.RLock()
155 | defer n.graph.mu.RUnlock()
156 | n.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool {
157 | if relationship != "" && edge.Relationship() != relationship {
158 | return true
159 | }
160 | return fn(edge)
161 | })
162 | }
163 |
164 | // SetEdge sets an edge from the current node to the node with the given nodeID.
165 | // If the nodeID does not exist, an error is returned.
166 | // If the edgeID is empty, a unique id will be generated.
167 | // If the metadata is nil, an empty map will be used.
168 | func (n *GraphNode[T]) SetEdge(relationship string, toNode Node, metadata map[string]string) (*GraphEdge[T], error) {
169 | if metadata == nil {
170 | metadata = make(map[string]string)
171 | }
172 | to, ok := n.graph.nodes.Get(toNode.ID())
173 | if !ok {
174 | to = n.graph.SetNode(toNode)
175 | }
176 | n.graph.mu.Lock()
177 | defer n.graph.mu.Unlock()
178 | e := &GraphEdge[T]{
179 | id: strings.ReplaceAll(strings.ToLower(fmt.Sprintf("%v-(%v)-%v", n.ID(), relationship, toNode.ID())), " ", "-"),
180 | metadata: metadata,
181 | from: n,
182 | to: to,
183 | }
184 | n.graph.edges.Set(e.ID(), e)
185 | to.edgesTo.Set(e.ID(), e)
186 | n.edgesFrom.Set(e.ID(), e)
187 | if n.graph.options.vizualize {
188 | ge, err := n.graph.viz.CreateEdge(e.ID(), n.node, to.node)
189 | if err != nil {
190 | return nil, err
191 | }
192 | ge.SetLabel(e.ID())
193 | if label, ok := metadata["label"]; ok {
194 | ge.SetLabel(label)
195 | }
196 | if color, ok := metadata["color"]; ok {
197 | ge.SetColor(color)
198 | }
199 | if fontColor, ok := metadata["fontcolor"]; ok {
200 | ge.SetFontColor(fontColor)
201 | }
202 | if weight, ok := metadata["weight"]; ok {
203 | weightFloat, _ := strconv.ParseFloat(weight, 64)
204 | ge.SetWeight(weightFloat)
205 | }
206 | if penWidth, ok := metadata["penwidth"]; ok {
207 | penWidthFloat, _ := strconv.ParseFloat(penWidth, 64)
208 | ge.SetPenWidth(penWidthFloat)
209 | }
210 | e.edge = ge
211 | }
212 | return e, nil
213 | }
214 |
215 | // RemoveEdge removes an edge from the current node by edgeID
216 | func (n *GraphNode[T]) RemoveEdge(edgeID string) {
217 | n.graph.mu.Lock()
218 | defer n.graph.mu.Unlock()
219 | n.removeEdge(edgeID)
220 | }
221 |
222 | func (n *GraphNode[T]) removeEdge(edgeID string) {
223 | edge, ok := n.graph.edges.Get(edgeID)
224 | if !ok {
225 | return
226 | }
227 | n.graph.edges.Delete(edgeID)
228 | n.edgesFrom.Delete(edgeID)
229 | n.edgesTo.Delete(edgeID)
230 | if edge.edge != nil {
231 | n.graph.viz.DeleteEdge(edge.edge)
232 | }
233 | }
234 |
235 | // Remove removes the current node from the graph
236 | func (n *GraphNode[T]) Remove() error {
237 | n.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool {
238 | n.removeEdge(edge.ID())
239 | return true
240 | })
241 | n.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool {
242 | n.removeEdge(edge.ID())
243 | return true
244 | })
245 | n.graph.nodes.Delete(n.ID())
246 | if n.graph.options.vizualize {
247 | n.graph.viz.DeleteNode(n.node)
248 | }
249 | return nil
250 | }
251 |
252 | // DirectedGraph returns the graph the node belongs to
253 | func (n *GraphNode[T]) Graph() *DAG[T] {
254 | return n.graph
255 | }
256 |
257 | // Ancestors returns the ancestors of the current node
258 | func (n *GraphNode[T]) Ancestors(fn func(node *GraphNode[T]) bool) {
259 | n.graph.mu.RLock()
260 | defer n.graph.mu.RUnlock()
261 | visited := NewSet[string]()
262 | n.ancestors(visited, fn)
263 | }
264 |
265 | func (n *GraphNode[T]) ancestors(visited *Set[string], fn func(node *GraphNode[T]) bool) {
266 | n.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool {
267 | if visited.Contains(edge.From().ID()) {
268 | return true
269 | }
270 | visited.Add(edge.From().ID())
271 | if !fn(edge.From()) {
272 | return false
273 | }
274 | edge.From().ancestors(visited, fn)
275 | return true
276 | })
277 | }
278 |
279 | // Descendants returns the descendants of the current node
280 | func (n *GraphNode[T]) Descendants(fn func(node *GraphNode[T]) bool) {
281 | n.graph.mu.RLock()
282 | defer n.graph.mu.RUnlock()
283 | visited := NewSet[string]()
284 | n.descendants(visited, fn)
285 | }
286 |
287 | func (n *GraphNode[T]) descendants(visited *Set[string], fn func(node *GraphNode[T]) bool) {
288 | n.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool {
289 | if visited.Contains(edge.To().ID()) {
290 | return true
291 | }
292 | visited.Add(edge.To().ID())
293 | if !fn(edge.To()) {
294 | return false
295 | }
296 | edge.To().descendants(visited, fn)
297 | return true
298 | })
299 | }
300 |
301 | // IsConnectedTo returns true if the current node is connected to the given node in any direction
302 | func (n *GraphNode[T]) IsConnectedTo(node *GraphNode[T]) bool {
303 | var result bool
304 | n.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool {
305 | if edge.To() == node {
306 | result = true
307 | return false
308 | }
309 | return true
310 | })
311 | n.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool {
312 | if edge.From() == node {
313 | result = true
314 | return false
315 | }
316 | return true
317 | })
318 | return result
319 | }
320 |
321 | // DAG is a concurrency safe, mutable, in-memory directed graph
322 | type DAG[T Node] struct {
323 | nodes *HashMap[string, *GraphNode[T]]
324 | edges *HashMap[string, *GraphEdge[T]]
325 | gviz *graphviz.Graphviz
326 | viz *cgraph.Graph
327 | mu sync.RWMutex
328 | options *dagOpts
329 | }
330 |
331 | type dagOpts struct {
332 | vizualize bool
333 | }
334 |
335 | // DagOpt is an option for configuring a DAG
336 | type DagOpt func(*dagOpts)
337 |
338 | // WithVizualization enables graphviz visualization on the DAG
339 | func WithVizualization() DagOpt {
340 | return func(opts *dagOpts) {
341 | opts.vizualize = true
342 | }
343 | }
344 |
345 | // NewDAG creates a new Directed Acyclic Graph instance
346 | func NewDAG[T Node](opts ...DagOpt) (*DAG[T], error) {
347 | var err error
348 | options := &dagOpts{}
349 | for _, opt := range opts {
350 | opt(options)
351 | }
352 | g := &DAG[T]{
353 | nodes: NewHashMap[string, *GraphNode[T]](),
354 | edges: NewHashMap[string, *GraphEdge[T]](),
355 | gviz: graphviz.New(),
356 | options: options,
357 | }
358 | if options.vizualize {
359 | graph, _ := g.gviz.Graph()
360 | g.viz = graph
361 | }
362 | return g, err
363 | }
364 |
365 | // SetNode sets a node in the graph - it will use the node's ID as the key and overwrite any existing node with the same ID
366 | func (g *DAG[T]) SetNode(node Node) *GraphNode[T] {
367 | n := &GraphNode[T]{
368 | Node: node,
369 | edgesTo: NewHashMap[string, *GraphEdge[T]](),
370 | edgesFrom: NewHashMap[string, *GraphEdge[T]](),
371 | graph: g,
372 | }
373 | g.nodes.Set(node.ID(), n)
374 | if g.options.vizualize {
375 | gn, err := g.viz.CreateNode(fmt.Sprintf("%v", node.ID()))
376 | if err != nil {
377 | panic(err)
378 | }
379 | gn.SetLabel(fmt.Sprintf("%v", node.ID()))
380 | if label, ok := node.Metadata()["label"]; ok {
381 | gn.SetLabel(label)
382 | }
383 | if color, ok := node.Metadata()["color"]; ok {
384 | gn.SetColor(color)
385 | }
386 |
387 | n.node = gn
388 | }
389 |
390 | return n
391 | }
392 |
393 | // HasNode returns true if the node with the given id exists in the graph
394 | func (g *DAG[T]) HasNode(id string) bool {
395 | _, ok := g.nodes.Get(id)
396 | return ok
397 | }
398 |
399 | // HasEdge returns true if the edge with the given id exists in the graph
400 | func (g *DAG[T]) HasEdge(id string) bool {
401 | _, ok := g.edges.Get(id)
402 | return ok
403 | }
404 |
405 | // GetNode returns the node with the given id
406 | func (g *DAG[T]) GetNode(id string) (*GraphNode[T], bool) {
407 | val, ok := g.nodes.Get(id)
408 | return val, ok
409 | }
410 |
411 | // Size returns the number of nodes and edges in the graph
412 | func (g *DAG[T]) Size() (int, int) {
413 | return g.nodes.Len(), g.edges.Len()
414 | }
415 |
416 | // GetNodes returns all nodes in the graph
417 | func (g *DAG[T]) GetNodes() []*GraphNode[T] {
418 | nodes := make([]*GraphNode[T], 0, g.nodes.Len())
419 | g.nodes.Range(func(key string, val *GraphNode[T]) bool {
420 | nodes = append(nodes, val)
421 | return true
422 | })
423 | return nodes
424 | }
425 |
426 | // GetEdges returns all edges in the graph
427 | func (g *DAG[T]) GetEdges() []*GraphEdge[T] {
428 | edges := make([]*GraphEdge[T], 0, g.edges.Len())
429 | g.edges.Range(func(key string, val *GraphEdge[T]) bool {
430 | edges = append(edges, val)
431 | return true
432 | })
433 | return edges
434 | }
435 |
436 | // GetEdge returns the edge with the given id
437 | func (g *DAG[T]) GetEdge(id string) (*GraphEdge[T], bool) {
438 | val, ok := g.edges.Get(id)
439 | return val, ok
440 | }
441 |
442 | // RangeEdges iterates over all edges in the graph
443 | func (g *DAG[T]) RangeEdges(fn func(e *GraphEdge[T]) bool) {
444 | g.edges.Range(func(key string, val *GraphEdge[T]) bool {
445 | return fn(val)
446 | })
447 | }
448 |
449 | // RangeNodes iterates over all nodes in the graph
450 | func (g *DAG[T]) RangeNodes(fn func(n *GraphNode[T]) bool) {
451 | g.nodes.Range(func(key string, val *GraphNode[T]) bool {
452 | return fn(val)
453 | })
454 | }
455 |
456 | // GraphSearchFunc is a function that is called on each node in the graph during a search
457 | type GraphSearchFunc[T Node] func(ctx context.Context, relationship string, node *GraphNode[T]) bool
458 |
459 | // BFS executes a depth first search on the graph starting from the current node.
460 | // The reverse parameter determines whether the search is reversed or not.
461 | // The fn parameter is a function that is called on each node in the graph. If the function returns false, the search is stopped.
462 | func (g *DAG[T]) BFS(ctx context.Context, reverse bool, start *GraphNode[T], search GraphSearchFunc[T]) error {
463 | var visited = NewSet[string]()
464 | stack := NewStack[*searchItem[T]]()
465 | ctx, cancel := context.WithCancel(ctx)
466 | defer cancel()
467 | if err := g.breadthFirstSearch(ctx, &breadthFirstSearchState[T]{
468 | visited: visited,
469 | stack: stack,
470 | reverse: reverse,
471 | root: start,
472 | next: start,
473 | relationship: "",
474 | }); err != nil {
475 | return err
476 | }
477 | stack.Range(func(element *searchItem[T]) bool {
478 | if element.node.ID() != start.ID() {
479 | return search(ctx, element.relationship, element.node)
480 | }
481 | return true
482 | })
483 | return nil
484 | }
485 |
486 | // DFS executes a depth first search on the graph starting from the current node.
487 | // The reverse parameter determines whether the search is reversed or not.
488 | // The fn parameter is a function that is called on each node in the graph. If the function returns false, the search is stopped.
489 | func (g *DAG[T]) DFS(ctx context.Context, reverse bool, start *GraphNode[T], fn GraphSearchFunc[T]) error {
490 | ctx, cancel := context.WithCancel(ctx)
491 | var visited = NewSet[string]()
492 | queue := NewBoundedQueue[*searchItem[T]](0)
493 | egp1, ctx := errgroup.WithContext(ctx)
494 | egp1.Go(func() error {
495 | egp2, ctx := errgroup.WithContext(ctx)
496 | if err := g.depthFirstSearch(ctx, &depthFirstSearchState[T]{
497 | visited: visited,
498 | egp: egp2,
499 | queue: queue,
500 | reverse: reverse,
501 | root: start,
502 | next: start,
503 | relationship: "",
504 | }); err != nil {
505 | return err
506 | }
507 | if err := egp2.Wait(); err != nil {
508 | return err
509 | }
510 | cancel()
511 | return nil
512 | })
513 | egp1.Go(func() error {
514 | queue.RangeContext(ctx, func(element *searchItem[T]) bool {
515 | if element.node.ID() != start.ID() {
516 | return fn(ctx, element.relationship, element.node)
517 | }
518 | return true
519 | })
520 | return nil
521 | })
522 | if err := egp1.Wait(); err != nil {
523 | return err
524 | }
525 | return nil
526 | }
527 |
528 | // searchItem is an item that is used in the search queue/stack in DFS/BFS
529 | type searchItem[T Node] struct {
530 | node *GraphNode[T]
531 | relationship string
532 | }
533 |
534 | type breadthFirstSearchState[T Node] struct {
535 | visited *Set[string]
536 | stack *Stack[*searchItem[T]]
537 | reverse bool
538 | root *GraphNode[T]
539 | next *GraphNode[T]
540 | relationship string
541 | }
542 |
543 | func (g *DAG[T]) breadthFirstSearch(ctx context.Context, state *breadthFirstSearchState[T]) error {
544 | if ctx.Err() != nil {
545 | return ctx.Err()
546 | }
547 | if state.next == nil {
548 | state.next = state.root
549 | }
550 | if !state.visited.Contains(state.next.ID()) {
551 | state.visited.Add(state.next.ID())
552 | state.stack.Push(&searchItem[T]{
553 | node: state.next,
554 | relationship: state.relationship,
555 | })
556 | if state.reverse {
557 | state.next.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool {
558 | g.breadthFirstSearch(ctx, &breadthFirstSearchState[T]{
559 | stack: state.stack,
560 | reverse: state.reverse,
561 | root: state.root,
562 | next: edge.From(),
563 | relationship: edge.Relationship(),
564 | visited: state.visited,
565 | })
566 | return state.visited.Len() < g.nodes.Len() && ctx.Err() == nil
567 | })
568 | } else {
569 | state.next.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool {
570 | g.breadthFirstSearch(ctx, &breadthFirstSearchState[T]{
571 | visited: state.visited,
572 | stack: state.stack,
573 | reverse: state.reverse,
574 | root: state.root,
575 | next: edge.To(),
576 | relationship: edge.Relationship(),
577 | })
578 | return state.visited.Len() < g.nodes.Len() && ctx.Err() == nil
579 | })
580 | }
581 | }
582 | return ctx.Err()
583 | }
584 |
585 | type depthFirstSearchState[T Node] struct {
586 | visited *Set[string]
587 | egp *errgroup.Group
588 | queue *BoundedQueue[*searchItem[T]]
589 | reverse bool
590 | root *GraphNode[T]
591 | next *GraphNode[T]
592 | relationship string
593 | }
594 |
595 | func (g *DAG[T]) depthFirstSearch(ctx context.Context, state *depthFirstSearchState[T]) error {
596 | if ctx.Err() != nil {
597 | return ctx.Err()
598 | }
599 | if state.next == nil {
600 | state.next = state.root
601 | }
602 | if !state.visited.Contains(state.next.ID()) {
603 | state.visited.Add(state.next.ID())
604 | state.queue.Push(&searchItem[T]{
605 | node: state.next,
606 | relationship: state.relationship,
607 | })
608 | if state.reverse {
609 | state.egp.Go(func() error {
610 | state.next.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool {
611 | g.depthFirstSearch(ctx, &depthFirstSearchState[T]{
612 | visited: state.visited,
613 | egp: state.egp,
614 | queue: state.queue,
615 | reverse: state.reverse,
616 | root: state.root,
617 | next: edge.From(),
618 | relationship: edge.Relationship(),
619 | })
620 | return state.visited.Len() < g.nodes.Len() && ctx.Err() == nil
621 | })
622 | return nil
623 | })
624 | } else {
625 | state.egp.Go(func() error {
626 | state.next.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool {
627 | g.depthFirstSearch(ctx, &depthFirstSearchState[T]{
628 | visited: state.visited,
629 | egp: state.egp,
630 | queue: state.queue,
631 | reverse: state.reverse,
632 | root: state.root,
633 | next: edge.To(),
634 | relationship: edge.Relationship(),
635 | })
636 | return state.visited.Len() < g.nodes.Len() && ctx.Err() == nil
637 | })
638 | return nil
639 | })
640 | }
641 | }
642 | return ctx.Err()
643 | }
644 |
645 | // Acyclic returns true if the graph contains no cycles.
646 | func (g *DAG[T]) Acyclic() bool {
647 | isAcyclic := true
648 | g.nodes.Range(func(key string, node *GraphNode[T]) bool {
649 | if node.edgesFrom.Len() > 0 {
650 | visited := NewSet[string]()
651 | onStack := NewSet[string]()
652 | if g.isCyclic(node, visited, onStack) {
653 | isAcyclic = false
654 | return false
655 | }
656 | }
657 | return true
658 | })
659 | return isAcyclic
660 | }
661 |
662 | // isAcyclic returns true if the graph contains no cycles.
663 | func (g *DAG[T]) isCyclic(node *GraphNode[T], visited *Set[string], onStack *Set[string]) bool {
664 | visited.Add(node.ID())
665 | onStack.Add(node.ID())
666 | result := false
667 | node.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool {
668 | if visited.Contains(edge.To().ID()) {
669 | if g.isCyclic(edge.To(), visited, onStack) {
670 | result = true
671 | return false
672 | }
673 | } else if onStack.Contains(edge.To().ID()) {
674 | result = true
675 | return false
676 | }
677 | return true
678 | })
679 | return result
680 | }
681 |
682 | func (g *DAG[T]) TopologicalSort(reverse bool) ([]*GraphNode[T], error) {
683 | if !g.Acyclic() {
684 | return nil, fmt.Errorf("topological sort cannot be computed on cyclical graph")
685 | }
686 | stack := NewStack[*GraphNode[T]]()
687 | permanent := NewSet[string]()
688 | temporary := NewSet[string]()
689 | g.nodes.Range(func(key string, node *GraphNode[T]) bool {
690 | g.topology(true, stack, node, permanent, temporary)
691 | return true
692 | })
693 | var sorted []*GraphNode[T]
694 | for stack.Len() > 0 {
695 | val, _ := stack.Pop()
696 | sorted = append(sorted, val)
697 | }
698 | if reverse {
699 | for i, j := 0, len(sorted)-1; i < j; i, j = i+1, j-1 {
700 | sorted[i], sorted[j] = sorted[j], sorted[i]
701 | }
702 | }
703 | return sorted, nil
704 | }
705 |
706 | func (g *DAG[T]) topology(reverse bool, stack *Stack[*GraphNode[T]], node *GraphNode[T], permanent, temporary *Set[string]) {
707 | if permanent.Contains(node.ID()) {
708 | return
709 | }
710 | if temporary.Contains(node.ID()) {
711 | panic("not a DAG")
712 | }
713 | temporary.Add(node.ID())
714 | if reverse {
715 | node.edgesTo.Range(func(key string, edge *GraphEdge[T]) bool {
716 | g.topology(reverse, stack, edge.From(), permanent, temporary)
717 | return true
718 | })
719 | } else {
720 | node.edgesFrom.Range(func(key string, edge *GraphEdge[T]) bool {
721 | g.topology(reverse, stack, edge.From(), permanent, temporary)
722 | return true
723 | })
724 | }
725 | temporary.Remove(node.ID())
726 | permanent.Add(node.ID())
727 | stack.Push(node)
728 | }
729 |
730 | // GraphViz returns a graphviz image
731 | func (g *DAG[T]) GraphViz() (image.Image, error) {
732 | if g.viz == nil {
733 | return nil, fmt.Errorf("graphviz not configured")
734 | }
735 | g.mu.RLock()
736 | defer g.mu.RUnlock()
737 | img, err := g.gviz.RenderImage(g.viz)
738 | if err != nil {
739 | return nil, err
740 | }
741 | return img, nil
742 | }
743 |
744 | // NewHashMap creates a new generic hash map
745 | func NewHashMap[K comparable, V any]() *HashMap[K, V] {
746 | return &HashMap[K, V]{
747 | data: sync.Map{},
748 | }
749 | }
750 |
751 | // HashMap is a thread safe map
752 | type HashMap[K comparable, V any] struct {
753 | data sync.Map
754 | }
755 |
756 | // Len returns the length of the map
757 | func (n *HashMap[K, V]) Len() int {
758 | count := 0
759 | n.data.Range(func(key, value interface{}) bool {
760 | count++
761 | return true
762 | })
763 | return count
764 | }
765 |
766 | // Get gets the value from the key
767 | func (n *HashMap[K, V]) Get(key K) (V, bool) {
768 | c, ok := n.data.Load(key)
769 | if !ok {
770 | return *new(V), ok
771 | }
772 | return c.(V), ok
773 | }
774 |
775 | // Set sets the key to the value
776 | func (n *HashMap[K, V]) Set(key K, value V) {
777 | n.data.Store(key, value)
778 | }
779 |
780 | // Delete deletes the key from the map
781 | func (n *HashMap[K, V]) Delete(key K) {
782 | n.data.Delete(key)
783 | }
784 |
785 | // Exists returns true if the key exists in the map
786 | func (n *HashMap[K, V]) Exists(key K) bool {
787 | _, ok := n.Get(key)
788 | return ok
789 | }
790 |
791 | // Clear clears the map
792 | func (n *HashMap[K, V]) Clear() {
793 | n.data.Range(func(key, value interface{}) bool {
794 | n.data.Delete(key)
795 | return true
796 | })
797 | }
798 |
799 | // Keys returns a copy of the keys in the map as a slice
800 | func (n *HashMap[K, V]) Keys() []K {
801 | var keys []K
802 | n.data.Range(func(key, value interface{}) bool {
803 | keys = append(keys, key.(K))
804 | return true
805 | })
806 | return keys
807 | }
808 |
809 | // Values returns a copy of the values in the map as a slice
810 | func (n *HashMap[K, V]) Values() []V {
811 | var values []V
812 | n.data.Range(func(key, value interface{}) bool {
813 | values = append(values, value.(V))
814 | return true
815 | })
816 | return values
817 | }
818 |
819 | // Range ranges over the map with a function until false is returned
820 | func (n *HashMap[K, V]) Range(f func(key K, value V) bool) {
821 | n.data.Range(func(key, value interface{}) bool {
822 | return f(key.(K), value.(V))
823 | })
824 | }
825 |
826 | // Filter returns a new hashmap with the values that return true from the function
827 | func (n *HashMap[K, V]) Filter(f func(key K, value V) bool) *HashMap[K, V] {
828 | filtered := NewHashMap[K, V]()
829 | n.data.Range(func(key, value interface{}) bool {
830 | if f(key.(K), value.(V)) {
831 | filtered.Set(key.(K), value.(V))
832 | }
833 | return true
834 | })
835 | return filtered
836 | }
837 |
838 | // Map returns a copy of the hashmap as a map[string]T
839 | func (n *HashMap[K, V]) Map() map[K]V {
840 | copied := map[K]V{}
841 | n.data.Range(func(key, value interface{}) bool {
842 | copied[key.(K)] = value.(V)
843 | return true
844 | })
845 | return copied
846 | }
847 |
848 | // priorityQueueItem is an item in the priority queue
849 | type priorityQueueItem[T any] struct {
850 | value T
851 | priority float64
852 | }
853 |
854 | // PriorityQueue is a thread safe priority queue
855 | type PriorityQueue[T any] struct {
856 | items []*priorityQueueItem[T]
857 | mu sync.RWMutex
858 | }
859 |
860 | // NewPriorityQueue creates a new priority queue
861 | func NewPriorityQueue[T any]() *PriorityQueue[T] {
862 | return &PriorityQueue[T]{
863 | items: []*priorityQueueItem[T]{},
864 | mu: sync.RWMutex{},
865 | }
866 | }
867 |
868 | func (q *PriorityQueue[T]) UpdatePriority(value T, priority float64) {
869 | q.mu.Lock()
870 | defer q.mu.Unlock()
871 | for i, item := range q.items {
872 | if reflect.DeepEqual(item.value, value) {
873 | q.items[i].priority = priority
874 | sort.Slice(q.items, func(i, j int) bool {
875 | return q.items[i].priority < q.items[j].priority
876 | })
877 | return
878 | }
879 | }
880 | }
881 |
882 | // Len returns the length of the queue
883 | func (q *PriorityQueue[T]) Len() int {
884 | q.mu.RLock()
885 | defer q.mu.RUnlock()
886 | return len(q.items)
887 | }
888 |
889 | // Push pushes an item onto the queue
890 | func (q *PriorityQueue[T]) Push(item T, weight float64) {
891 | q.mu.Lock()
892 | defer q.mu.Unlock()
893 | q.items = append(q.items, &priorityQueueItem[T]{value: item, priority: weight})
894 | sort.Slice(q.items, func(i, j int) bool {
895 | return q.items[i].priority < q.items[j].priority
896 | })
897 | }
898 |
899 | // Pop pops an item off the queue
900 | func (q *PriorityQueue[T]) Pop() (T, bool) {
901 | q.mu.Lock()
902 | defer q.mu.Unlock()
903 | if len(q.items) == 0 {
904 | return *new(T), false
905 | }
906 | item := q.items[0]
907 | q.items = q.items[1:]
908 | return item.value, true
909 | }
910 |
911 | // Peek returns the next item in the queue without removing it
912 | func (q *PriorityQueue[T]) Peek() (T, bool) {
913 | q.mu.RLock()
914 | defer q.mu.RUnlock()
915 | if len(q.items) == 0 {
916 | return *new(T), false
917 | }
918 | return q.items[0].value, true
919 | }
920 |
921 | // BoundedQueue is a basic FIFO BoundedQueue based on a buffered channel
922 | type BoundedQueue[T any] struct {
923 | closeOnce sync.Once
924 | ch *async.Channel[T]
925 | }
926 |
927 | // NewBoundedQueue returns a new BoundedQueue with the given max size. When the max size is reached, the queue will block until a value is removed.
928 | // If maxSize is 0, the queue will always block until a value is removed. The BoundedQueue is concurrent-safe.
929 | func NewBoundedQueue[T any](maxSize int) *BoundedQueue[T] {
930 | return &BoundedQueue[T]{ch: async.NewChannel[T](context.Background(), async.WithBufferSize[T](maxSize))}
931 | }
932 |
933 | // Range executes a provided function once for each BoundedQueue element until it returns false.
934 | func (q *BoundedQueue[T]) Range(fn func(element T) bool) {
935 | for {
936 | value, ok := q.ch.Recv(context.Background())
937 | if !ok {
938 | return
939 | }
940 | if !fn(value) {
941 | return
942 | }
943 | }
944 | }
945 |
946 | // RangeContext executes a provided function once for each BoundedQueue element until it returns false or a value is sent to the done channel.
947 | // Use this function when you want to continuously process items from the queue until a done signal is received.
948 | func (q *BoundedQueue[T]) RangeContext(ctx context.Context, fn func(element T) bool) {
949 | for {
950 | value, ok := q.ch.Recv(ctx)
951 | if !ok {
952 | return
953 | }
954 | if !fn(value) {
955 | return
956 | }
957 | }
958 | }
959 |
960 | // Close closes the BoundedQueue channel.
961 | func (q *BoundedQueue[T]) Close() {
962 | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
963 | defer cancel()
964 | q.ch.Close(ctx)
965 | }
966 |
967 | // Push adds an element to the end of the BoundedQueue and returns a channel that will block until the element is added.
968 | // If the queue is full, it will block until an element is removed.
969 | func (q *BoundedQueue[T]) Push(val T) bool {
970 | return q.ch.Send(context.Background(), val)
971 | }
972 |
973 | // PushContext adds an element to the end of the BoundedQueue and returns a channel that will block until the element is added.
974 | // If the queue is full, it will block until an element is removed or the context is cancelled.
975 | func (q *BoundedQueue[T]) PushContext(ctx context.Context, val T) bool {
976 | return q.ch.Send(ctx, val)
977 | }
978 |
979 | // Pop removes and returns an element from the beginning of the BoundedQueue.
980 | func (q *BoundedQueue[T]) Pop() (T, bool) {
981 | return q.ch.Recv(context.Background())
982 | }
983 |
984 | // PopContext removes and returns an element from the beginning of the BoundedQueue.
985 | // If no element is available, it will block until an element is available or the context is cancelled.
986 | func (q *BoundedQueue[T]) PopContext(ctx context.Context) (T, bool) {
987 | return q.ch.Recv(ctx)
988 | }
989 |
990 | // Len returns the number of elements in the BoundedQueue.
991 | func (q *BoundedQueue[T]) Len() int {
992 | return q.ch.Len()
993 | }
994 |
995 | // Queue is a thread safe non-blocking queue
996 | type Queue[T any] struct {
997 | mu sync.RWMutex
998 | values []T
999 | }
1000 |
1001 | // NewQueue returns a new Queue
1002 | func NewQueue[T any]() *Queue[T] {
1003 | vals := &Queue[T]{values: []T{}}
1004 | return vals
1005 | }
1006 |
1007 | // Push a new value onto the Queue
1008 | func (s *Queue[T]) Push(f T) {
1009 | s.mu.Lock()
1010 | defer s.mu.Unlock()
1011 | s.values = append(s.values, f) // Simply append the new value to the end of the Queue
1012 | }
1013 |
1014 | // Pop and return top element of Queue. Return false if Queue is empty.
1015 | func (s *Queue[T]) Pop() (T, bool) {
1016 | s.mu.Lock()
1017 | defer s.mu.Unlock()
1018 | if len(s.values) == 0 {
1019 | return *new(T), false
1020 | } else {
1021 | index := len(s.values) - 1
1022 | val := s.values[index]
1023 | s.values = s.values[:index]
1024 | return val, true
1025 | }
1026 | }
1027 |
1028 | // Len returns the length of the queue
1029 | func (s *Queue[T]) Len() int {
1030 | s.mu.RLock()
1031 | defer s.mu.RUnlock()
1032 | return len(s.values)
1033 | }
1034 |
1035 | // Peek returns the next item in the queue without removing it
1036 | func (s *Queue[T]) Peek() (T, bool) {
1037 | s.mu.RLock()
1038 | defer s.mu.RUnlock()
1039 | if len(s.values) == 0 {
1040 | return *new(T), false
1041 | }
1042 | return s.values[len(s.values)-1], true
1043 | }
1044 |
1045 | // Range executes a provided function once for each Queue element until it returns false or the Queue is empty.
1046 | func (q *Queue[T]) Range(fn func(element T) bool) {
1047 | for {
1048 | val, ok := q.Pop()
1049 | if !ok {
1050 | return
1051 | }
1052 | if !fn(val) {
1053 | return
1054 | }
1055 | }
1056 | }
1057 |
1058 | // RangeUntil executes a provided function once for each Queue element until it returns false or a value is sent on the done channel.
1059 | // Use this function when you want to continuously process items from the queue until a done signal is received.
1060 | func (q *Queue[T]) RangeUntil(fn func(element T) bool, done chan struct{}) {
1061 | for {
1062 | select {
1063 | case <-done:
1064 | return
1065 | default:
1066 | val, ok := q.Pop()
1067 | if !ok {
1068 | return
1069 | }
1070 | if !fn(val) {
1071 | return
1072 | }
1073 | }
1074 | }
1075 | }
1076 |
1077 | // NewStack returns a new Stack instance
1078 | func NewStack[T any]() *Stack[T] {
1079 | vals := &Stack[T]{values: []T{}}
1080 | return vals
1081 | }
1082 |
1083 | // Stack is a basic LIFO Stack
1084 | type Stack[T any] struct {
1085 | mu sync.RWMutex
1086 | values []T
1087 | }
1088 |
1089 | // RangeUntil executes a provided function once after calling Pop on the stack until the function returns false or a value is sent on the done channel.
1090 | // Use this function when you want to continuously process items from the stack until a done signal is received.
1091 | func (s *Stack[T]) RangeUntil(fn func(element T) bool, done chan struct{}) {
1092 | for {
1093 | select {
1094 | case <-done:
1095 | return
1096 | default:
1097 | val, ok := s.Pop()
1098 | if !ok {
1099 | return
1100 | }
1101 | if !fn(val) {
1102 | return
1103 | }
1104 | }
1105 | }
1106 | }
1107 |
1108 | // Push a new value onto the Stack (LIFO)
1109 | func (s *Stack[T]) Push(f T) {
1110 | s.mu.Lock()
1111 | defer s.mu.Unlock()
1112 | s.values = append(s.values, f) // Simply append the new value to the end of the Stack
1113 | }
1114 |
1115 | // Pop removes and return top element of Stack. Return false if Stack is empty.
1116 | func (s *Stack[T]) Pop() (T, bool) {
1117 | s.mu.Lock()
1118 | defer s.mu.Unlock()
1119 | if len(s.values) == 0 {
1120 | return *new(T), false
1121 | } else {
1122 | index := len(s.values) - 1 // Get the index of the top most element.
1123 | element := s.values[index] // Index into the slice and obtain the element.
1124 | s.values = s.values[:index] // Remove it from the Stack by slicing it off.
1125 | return element, true
1126 | }
1127 | }
1128 |
1129 | // Clear removes all elements from the Stack
1130 | func (s *Stack[T]) Clear() {
1131 | s.mu.Lock()
1132 | defer s.mu.Unlock()
1133 | s.values = []T{}
1134 | }
1135 |
1136 | // Range executes a provided function once for each Stack element until it returns false.
1137 | func (s *Stack[T]) Range(fn func(element T) bool) {
1138 | for {
1139 | r, ok := s.Pop()
1140 | if !ok {
1141 | return
1142 | }
1143 | if !fn(r) {
1144 | return
1145 | }
1146 | }
1147 | }
1148 |
1149 | // Values returns the values of the stack as an array
1150 | func (s *Stack[T]) Values() []T {
1151 | s.mu.RLock()
1152 | defer s.mu.RUnlock()
1153 | return s.values
1154 | }
1155 |
1156 | // Sort returns the values of the stack as an array sorted by the provided less function
1157 | func (s *Stack[T]) Sort(lessFunc func(i T, j T) bool) []T {
1158 | values := s.Values()
1159 | sort.Slice(values, func(i, j int) bool {
1160 | return lessFunc(values[i], values[j])
1161 | })
1162 | return values
1163 | }
1164 |
1165 | // Len returns the number of elements in the Stack.
1166 | func (s *Stack[T]) Len() int {
1167 | s.mu.RLock()
1168 | defer s.mu.RUnlock()
1169 | return len(s.values)
1170 | }
1171 |
1172 | // Peek returns the top element of the Stack without removing it. Return false if Stack is empty.
1173 | func (s *Stack[T]) Peek() (T, bool) {
1174 | s.mu.RLock()
1175 | defer s.mu.RUnlock()
1176 | if len(s.values) == 0 {
1177 | return *new(T), false
1178 | } else {
1179 | index := len(s.values) - 1 // Get the index of the top most element.
1180 | element := s.values[index] // Index into the slice and obtain the element.
1181 | return element, true
1182 | }
1183 | }
1184 |
1185 | // Set is a basic thread-safe Set implementation.
1186 | type Set[T comparable] struct {
1187 | mu sync.RWMutex
1188 | values map[T]struct{}
1189 | }
1190 |
1191 | // NewSet returns a new Set with the given initial size.
1192 | func NewSet[T comparable]() *Set[T] {
1193 | vals := &Set[T]{values: map[T]struct{}{}}
1194 | return vals
1195 | }
1196 |
1197 | // Add adds an element to the Set.
1198 | func (s *Set[T]) Add(val T) {
1199 | s.mu.Lock()
1200 | defer s.mu.Unlock()
1201 | s.values[val] = struct{}{}
1202 | }
1203 |
1204 | // Remove removes an element from the Set.
1205 | func (s *Set[T]) Remove(val T) {
1206 | s.mu.Lock()
1207 | defer s.mu.Unlock()
1208 | delete(s.values, val)
1209 | }
1210 |
1211 | // Contains returns true if the Set contains the element.
1212 | func (s *Set[T]) Contains(val T) bool {
1213 | s.mu.RLock()
1214 | defer s.mu.RUnlock()
1215 | _, ok := s.values[val]
1216 | return ok
1217 | }
1218 |
1219 | // Range executes a provided function once for each Set element until it returns false.
1220 | func (s *Set[T]) Range(fn func(element T) bool) {
1221 | s.mu.RLock()
1222 | defer s.mu.RUnlock()
1223 | for k := range s.values {
1224 | if !fn(k) {
1225 | return
1226 | }
1227 | }
1228 | }
1229 |
1230 | // Len returns the number of elements in the Set.
1231 | func (s *Set[T]) Len() int {
1232 | s.mu.RLock()
1233 | defer s.mu.RUnlock()
1234 | return len(s.values)
1235 | }
1236 |
1237 | // Values returns the values of the set as an array
1238 | func (s *Set[T]) Values() []T {
1239 | s.mu.RLock()
1240 | defer s.mu.RUnlock()
1241 | values := make([]T, len(s.values))
1242 | for k, _ := range s.values {
1243 | values = append(values, k)
1244 | }
1245 | return values
1246 | }
1247 |
1248 | // Sort returns the values of the set as an array sorted by the provided less function
1249 | func (s *Set[T]) Sort(lessFunc func(i T, j T) bool) []T {
1250 | values := s.Values()
1251 | sort.Slice(values, func(i, j int) bool {
1252 | return lessFunc(values[i], values[j])
1253 | })
1254 | return values
1255 | }
1256 |
--------------------------------------------------------------------------------
/dagger_test.go:
--------------------------------------------------------------------------------
1 | package dagger_test
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "strconv"
8 | "strings"
9 | "testing"
10 | "time"
11 |
12 | "github.com/autom8ter/dagger/v3"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func init() {
17 | os.Setenv("DAGGER_DEBUG", "true")
18 | }
19 |
20 | type User struct {
21 | id string
22 | Name string
23 | metadata map[string]string
24 | }
25 |
26 | func (u *User) ID() string {
27 | return u.id
28 | }
29 |
30 | func (u *User) Metadata() map[string]string {
31 | return u.metadata
32 | }
33 |
34 | func (u *User) SetMetadata(metadata map[string]string) {
35 | if u.metadata == nil {
36 | u.metadata = map[string]string{}
37 | }
38 | for k, v := range metadata {
39 | u.metadata[k] = v
40 | }
41 | }
42 |
43 | var Jane = &User{
44 | id: "1",
45 | Name: "Jane",
46 | }
47 |
48 | var John = &User{
49 | id: "2",
50 | Name: "John",
51 | }
52 |
53 | var Jake = &User{
54 | id: "3",
55 | Name: "Jake",
56 | }
57 |
58 | var users = []*User{
59 | Jane,
60 | John,
61 | Jake,
62 | }
63 |
64 | func randUser() *User {
65 | return &User{
66 | id: strconv.Itoa(int(time.Now().UnixNano())),
67 | Name: "Jane - " + strconv.Itoa(int(time.Now().UnixNano())),
68 | }
69 | }
70 |
71 | func TestGraph(t *testing.T) {
72 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
73 | defer cancel()
74 | t.Run("set node", func(t *testing.T) {
75 | graph, err := dagger.NewDAG[*User]()
76 | assert.NoError(t, err)
77 | for _, user := range users {
78 | node := graph.SetNode(user)
79 | assert.NotNil(t, node)
80 | }
81 | })
82 | t.Run("set edge", func(t *testing.T) {
83 | graph, err := dagger.NewDAG[*User]()
84 | assert.NoError(t, err)
85 | jane := graph.SetNode(Jane)
86 | john := graph.SetNode(John)
87 | edge, err := jane.SetEdge("knows", john, map[string]string{})
88 | assert.Nil(t, err)
89 | assert.NotNil(t, edge)
90 | })
91 | t.Run("set edge with node", func(t *testing.T) {
92 | graph, err := dagger.NewDAG[*User]()
93 | assert.NoError(t, err)
94 | jane := graph.SetNode(Jane)
95 | john := graph.SetNode(John)
96 | edge, err := jane.SetEdge("knows", john, map[string]string{})
97 | assert.Nil(t, err)
98 | assert.NotNil(t, edge)
99 | })
100 | t.Run("set edge with node then get", func(t *testing.T) {
101 | graph, err := dagger.NewDAG[*User]()
102 | assert.NoError(t, err)
103 | jane := graph.SetNode(Jane)
104 | john := graph.SetNode(John)
105 | edge, err := jane.SetEdge("knows", john, map[string]string{})
106 | assert.Nil(t, err)
107 | assert.NotNil(t, edge)
108 | edge, ok := graph.GetEdge(edge.ID())
109 | assert.True(t, ok)
110 | assert.NotNil(t, edge)
111 | })
112 | t.Run("check edges from/to", func(t *testing.T) {
113 | graph, err := dagger.NewDAG[*User]()
114 | assert.NoError(t, err)
115 | jane := graph.SetNode(Jane)
116 | john := graph.SetNode(John)
117 | edge, err := jane.SetEdge("knows", john, map[string]string{})
118 | assert.Nil(t, err)
119 | assert.NotNil(t, edge)
120 | jane.EdgesFrom("", func(e *dagger.GraphEdge[*User]) bool {
121 | assert.Equal(t, e.ID(), edge.ID())
122 | return true
123 | })
124 | jane.EdgesTo("", func(e *dagger.GraphEdge[*User]) bool {
125 | assert.NotEqual(t, e.ID(), edge.ID())
126 | return true
127 | })
128 | john.EdgesTo("", func(e *dagger.GraphEdge[*User]) bool {
129 | assert.Equal(t, e.ID(), edge.ID())
130 | return true
131 | })
132 | john.EdgesFrom("", func(e *dagger.GraphEdge[*User]) bool {
133 | assert.NotEqual(t, e.ID(), edge.ID())
134 | return true
135 | })
136 | })
137 | t.Run("remove node", func(t *testing.T) {
138 | graph, err := dagger.NewDAG[*User]()
139 | assert.NoError(t, err)
140 | jane := graph.SetNode(Jane)
141 | john := graph.SetNode(John)
142 | edge, err := jane.SetEdge("knows", john, map[string]string{})
143 | assert.Nil(t, err)
144 | assert.NotNil(t, edge)
145 | assert.NoError(t, jane.Remove())
146 | _, ok := graph.GetNode(jane.ID())
147 | assert.False(t, ok)
148 | _, ok = graph.GetEdge(edge.ID())
149 | assert.False(t, ok)
150 | })
151 | t.Run("remove edge", func(t *testing.T) {
152 | graph, err := dagger.NewDAG[*User]()
153 | assert.NoError(t, err)
154 | jane := graph.SetNode(Jane)
155 | john := graph.SetNode(John)
156 | edge, err := jane.SetEdge("knows", john, map[string]string{})
157 | assert.Nil(t, err)
158 | assert.NotNil(t, edge)
159 | jane.RemoveEdge(edge.ID())
160 | _, ok := graph.GetEdge(edge.ID())
161 | assert.False(t, ok)
162 |
163 | })
164 | t.Run("graphviz", func(t *testing.T) {
165 | graph, err := dagger.NewDAG[*User](dagger.WithVizualization())
166 | assert.NoError(t, err)
167 | jane := graph.SetNode(Jane)
168 | john := graph.SetNode(John)
169 | edge, err := jane.SetEdge("knows", john, map[string]string{})
170 | assert.Nil(t, err)
171 | assert.NotNil(t, edge)
172 | img, err := graph.GraphViz()
173 | assert.NoError(t, err)
174 | assert.NotNil(t, img)
175 | })
176 | t.Run("breadthFirstSearch", func(t *testing.T) {
177 | graph, err := dagger.NewDAG[*User]()
178 | assert.NoError(t, err)
179 | lastNode := graph.SetNode(Jane)
180 | for i := 0; i < 100; i++ {
181 | node := graph.SetNode(randUser())
182 | edge, err := lastNode.SetEdge("knows", node, map[string]string{
183 | "weight": fmt.Sprintf("%d", i),
184 | })
185 | assert.Nil(t, err)
186 | assert.NotNil(t, edge)
187 | }
188 | nc, ec := graph.Size()
189 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID())
190 | nodes := make([]*dagger.GraphNode[*User], 0)
191 | assert.NoError(t, graph.BFS(ctx, false, lastNode, func(ctx context.Context, relationship string, node *dagger.GraphNode[*User]) bool {
192 | nodes = append(nodes, node)
193 | return true
194 | }))
195 |
196 | assert.Equal(t, 100, len(nodes))
197 | })
198 | t.Run("depthFirstSearch", func(t *testing.T) {
199 | graph, err := dagger.NewDAG[*User]()
200 | assert.NoError(t, err)
201 | lastNode := graph.SetNode(Jane)
202 | for i := 0; i < 100; i++ {
203 | node := graph.SetNode(randUser())
204 | edge, err := lastNode.SetEdge("knows", node, map[string]string{
205 | "weight": fmt.Sprintf("%d", i),
206 | })
207 | assert.Nil(t, err)
208 | assert.NotNil(t, edge)
209 | }
210 | nc, ec := graph.Size()
211 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID())
212 | nodes := make([]*dagger.GraphNode[*User], 0)
213 |
214 | assert.NoError(t, graph.DFS(ctx, false, lastNode, func(ctx context.Context, relationship string, node *dagger.GraphNode[*User]) bool {
215 | nodes = append(nodes, node)
216 | return true
217 | }))
218 | assert.Equal(t, 100, len(nodes))
219 | })
220 | t.Run("acyclic", func(t *testing.T) {
221 | graph, err := dagger.NewDAG[*User]()
222 | assert.NoError(t, err)
223 | lastNode := graph.SetNode(Jane)
224 | for i := 0; i < 100; i++ {
225 | node := graph.SetNode(randUser())
226 | edge, err := lastNode.SetEdge("knows", node, map[string]string{
227 | "weight": fmt.Sprintf("%d", i),
228 | })
229 | assert.Nil(t, err)
230 | assert.NotNil(t, edge)
231 | }
232 | nc, ec := graph.Size()
233 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID())
234 | assert.True(t, graph.Acyclic())
235 | })
236 | t.Run("topological reverse sort", func(t *testing.T) {
237 | graph, err := dagger.NewDAG[*User]()
238 | assert.NoError(t, err)
239 | lastNode := graph.SetNode(Jane)
240 | for i := 0; i < 100; i++ {
241 | node := graph.SetNode(randUser())
242 | edge, err := lastNode.SetEdge("knows", node, map[string]string{
243 | "weight": fmt.Sprintf("%d", i),
244 | })
245 | assert.Nil(t, err)
246 | assert.NotNil(t, edge)
247 | lastNode = node
248 | }
249 | nc, ec := graph.Size()
250 | assert.True(t, graph.Acyclic())
251 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID())
252 | nodes, err := graph.TopologicalSort(true)
253 | assert.NoError(t, err)
254 | var n *dagger.GraphNode[*User]
255 | for _, node := range nodes {
256 | if n == nil {
257 | n = node
258 | continue
259 | }
260 | split := strings.Split(n.ID(), "-")
261 | lastNodeID, _ := strconv.Atoi(split[len(split)-1])
262 | split = strings.Split(node.ID(), "-")
263 | nodeID, _ := strconv.Atoi(split[len(split)-1])
264 | assert.LessOrEqual(t, lastNodeID, nodeID)
265 | n = node
266 | }
267 | assert.Equal(t, 101, len(nodes))
268 | })
269 | t.Run("topological sort", func(t *testing.T) {
270 | graph, err := dagger.NewDAG[*User]()
271 | assert.NoError(t, err)
272 | lastNode := graph.SetNode(Jane)
273 | for i := 0; i < 100; i++ {
274 | node := graph.SetNode(randUser())
275 | edge, err := lastNode.SetEdge("knows", node, map[string]string{
276 | "weight": fmt.Sprintf("%d", i),
277 | })
278 | assert.Nil(t, err)
279 | assert.NotNil(t, edge)
280 | lastNode = node
281 | }
282 | nc, ec := graph.Size()
283 | assert.True(t, graph.Acyclic())
284 | t.Logf("nodes=%v edges=%v last node: %s", nc, ec, lastNode.ID())
285 | nodes, err := graph.TopologicalSort(false)
286 | assert.NoError(t, err)
287 | var n *dagger.GraphNode[*User]
288 | for _, node := range nodes {
289 | if n == nil {
290 | n = node
291 | continue
292 | }
293 | split := strings.Split(n.ID(), "-")
294 | lastNodeID, _ := strconv.Atoi(split[len(split)-1])
295 | split = strings.Split(node.ID(), "-")
296 | nodeID, _ := strconv.Atoi(split[len(split)-1])
297 | assert.GreaterOrEqual(t, lastNodeID, nodeID)
298 | n = node
299 | }
300 | assert.Equal(t, 101, len(nodes))
301 | })
302 | //t.Run("strongly knows", func(t *testing.T) {
303 | // graph, err := dagger.NewDAG[*User]()
304 | // {
305 | // lastNode := graph.SetNode(Jane)
306 | // for i := 0; i < 50; i++ {
307 | // node := graph.SetNode(randUser())
308 | // edge, err := lastNode.SetEdge("knows", node, map[string]string{
309 | // "weight": fmt.Sprintf("%d", i),
310 | // })
311 | // assert.Nil(t, err)
312 | // assert.NotNil(t, edge)
313 | // lastNode = node
314 | // }
315 | // }
316 | // {
317 | // lastNode := graph.SetNode(dagger.UniqueID("jane"))
318 | // for i := 51; i < 100; i++ {
319 | // node := graph.SetNode(randUser())
320 | // edge, err := lastNode.SetEdge("knows", node, map[string]string{
321 | // "weight": fmt.Sprintf("%d", i),
322 | // })
323 | // assert.Nil(t, err)
324 | // assert.NotNil(t, edge)
325 | // lastNode = node
326 | // }
327 | // }
328 | // assert.True(t, graph.Acyclic())
329 | // components, err := graph.StronglyConnected()
330 | // assert.NoError(t, err)
331 | // assert.Equal(t, 2, len(components))
332 | // bits, _ := json.MarshalIndent(components, "", " ")
333 | // t.Logf("strongly knows components: %v", string(bits))
334 | //})
335 | }
336 |
337 | func TestBoundedQueue(t *testing.T) {
338 | q := dagger.NewBoundedQueue[string](100)
339 | for i := 0; i < 100; i++ {
340 | q.Push(string(fmt.Sprintf("node-%d", i)))
341 | }
342 | assert.Equal(t, q.Len(), 100)
343 | for i := 0; i < 100; i++ {
344 | v, ok := q.Pop()
345 | assert.NotNil(t, v)
346 | assert.True(t, ok)
347 | }
348 | assert.Equal(t, q.Len(), 0)
349 | }
350 |
351 | func TestStack(t *testing.T) {
352 | s := dagger.NewStack[string]()
353 | for i := 0; i < 100; i++ {
354 | s.Push(string(fmt.Sprintf("node-%d", i)))
355 | }
356 | assert.Equal(t, s.Len(), 100)
357 | for i := 0; i < 100; i++ {
358 | v, ok := s.Pop()
359 | assert.NotNil(t, v)
360 | assert.True(t, ok)
361 | }
362 | assert.Equal(t, s.Len(), 0)
363 | }
364 |
365 | func TestSet(t *testing.T) {
366 | s := dagger.NewSet[string]()
367 | for i := 0; i < 100; i++ {
368 | s.Add(string(fmt.Sprintf("node-%d", i)))
369 | }
370 | assert.Equal(t, s.Len(), 100)
371 | for i := 0; i < 100; i++ {
372 | ok := s.Contains(string(fmt.Sprintf("node-%d", i)))
373 | assert.True(t, ok)
374 | }
375 | assert.Equal(t, s.Len(), 100)
376 | for i := 0; i < 100; i++ {
377 | s.Remove(string(fmt.Sprintf("node-%d", i)))
378 | }
379 | assert.Equal(t, s.Len(), 0)
380 | }
381 |
382 | func TestNewPriorityQueue(t *testing.T) {
383 | q := dagger.NewPriorityQueue[string]()
384 | for i := 0; i < 100; i++ {
385 | q.Push(string(fmt.Sprintf("node-%d", i)), float64(i))
386 | }
387 | assert.Equal(t, q.Len(), 100)
388 | first, _ := q.Peek()
389 | assert.EqualValues(t, first, string("node-0"))
390 | for i := 0; i < 100; i++ {
391 | v, ok := q.Pop()
392 | assert.NotNil(t, v)
393 | assert.True(t, ok)
394 | }
395 | assert.Equal(t, q.Len(), 0)
396 | }
397 |
398 | func TestQueue(t *testing.T) {
399 | q := dagger.NewQueue[string]()
400 | for i := 0; i < 100; i++ {
401 | q.Push(string(fmt.Sprintf("node-%d", i)))
402 | }
403 | assert.Equal(t, q.Len(), 100)
404 | for i := 0; i < 100; i++ {
405 | v, ok := q.Pop()
406 | assert.NotNil(t, v)
407 | assert.True(t, ok)
408 | }
409 | assert.Equal(t, q.Len(), 0)
410 | }
411 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/autom8ter/dagger/v3
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/goccy/go-graphviz v0.1.1
7 | github.com/stretchr/testify v1.8.4
8 | golang.org/x/sync v0.3.0
9 | )
10 |
11 | require (
12 | github.com/autom8ter/async v1.0.1 // indirect
13 | github.com/davecgh/go-spew v1.1.1 // indirect
14 | github.com/fogleman/gg v1.3.0 // indirect
15 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
16 | github.com/kr/text v0.2.0 // indirect
17 | github.com/pkg/errors v0.9.1 // indirect
18 | github.com/pmezard/go-difflib v1.0.0 // indirect
19 | golang.org/x/image v0.6.0 // indirect
20 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
21 | gopkg.in/yaml.v3 v3.0.1 // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/autom8ter/async v1.0.1 h1:Wr/hEgyq0u1KxaQgnMxZykHDhLy/ot+/1T1U6uI9ir8=
2 | github.com/autom8ter/async v1.0.1/go.mod h1:b0XDjGNFJp+Af0/En4A8CTZxKHe4m/YS43eSd/p1kh0=
3 | github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA=
4 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
8 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
9 | github.com/goccy/go-graphviz v0.1.1 h1:MGrsnzBxTyt7KG8FhHsFPDTGvF7UaQMmSa6A610DqPg=
10 | github.com/goccy/go-graphviz v0.1.1/go.mod h1:lpnwvVDjskayq84ZxG8tGCPeZX/WxP88W+OJajh+gFk=
11 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
12 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
13 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
14 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
15 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
16 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
17 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
18 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
19 | github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
20 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
21 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
24 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
25 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
26 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
27 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
28 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
29 | golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
30 | golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
31 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
32 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
33 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
34 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
35 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
36 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
37 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
38 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
39 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
40 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
41 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
42 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
43 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
44 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
45 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
46 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
47 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
48 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
49 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
50 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
51 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
52 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
53 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
54 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
55 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
56 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
57 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
58 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
59 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
60 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
61 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
62 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
63 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
64 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
65 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
66 |
--------------------------------------------------------------------------------