|
8 | 8 | "bytes"
|
9 | 9 | "fmt"
|
10 | 10 | "os"
|
| 11 | + "sort" |
11 | 12 | "text/tabwriter"
|
12 | 13 |
|
13 | 14 | "github.com/sdboyer/gps"
|
@@ -74,6 +75,11 @@ type BasicStatus struct {
|
74 | 75 | PackageCount int
|
75 | 76 | }
|
76 | 77 |
|
| 78 | +type MissingStatus struct { |
| 79 | + ProjectRoot string |
| 80 | + MissingPackages string |
| 81 | +} |
| 82 | + |
77 | 83 | func runStatus(args []string) error {
|
78 | 84 | p, err := loadProject("")
|
79 | 85 | if err != nil {
|
@@ -130,15 +136,22 @@ func runStatusAll(p *project, sm *gps.SourceMgr) error {
|
130 | 136 | }
|
131 | 137 |
|
132 | 138 | cm := collectConstraints(ptree, p, sm)
|
133 |
| - tw := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0) |
134 |
| - // Print header row |
135 |
| - fmt.Fprintf(tw, "Project\tConstraint\tVersion\tRevision\tLatest\tPkgs Used\t\n") |
| 139 | + |
| 140 | + // Get the project list and sort it so that the printed output users see is |
| 141 | + // deterministically ordered. (This may be superfluous if the lock is always |
| 142 | + // written in alpha order, but it doesn't hurt to double down.) |
| 143 | + slp := p.l.Projects() |
| 144 | + sort.Sort(sortedLockedProjects(slp)) |
136 | 145 |
|
137 | 146 | if bytes.Equal(s.HashInputs(), p.l.Memo) {
|
138 | 147 | // If these are equal, we're guaranteed that the lock is a transitively
|
139 | 148 | // complete picture of all deps. That eliminates the need for at least
|
140 | 149 | // some checks.
|
141 |
| - for _, proj := range p.l.Projects() { |
| 150 | + |
| 151 | + tw := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0) |
| 152 | + fmt.Fprintf(tw, "Project\tConstraint\tVersion\tRevision\tLatest\tPkgs Used\t\n") |
| 153 | + |
| 154 | + for _, proj := range slp { |
142 | 155 | bs := BasicStatus{
|
143 | 156 | ProjectRoot: string(proj.Ident().ProjectRoot),
|
144 | 157 | PackageCount: len(proj.Packages()),
|
@@ -201,29 +214,70 @@ func runStatusAll(p *project, sm *gps.SourceMgr) error {
|
201 | 214 | bs.PackageCount,
|
202 | 215 | )
|
203 | 216 | }
|
| 217 | + |
| 218 | + tw.Flush() |
204 | 219 | } else {
|
205 |
| - // Not equal - the lock may or may not be a complete picture, and even |
206 |
| - // if it does have all the deps, it may not be a valid set of |
207 |
| - // constraints. |
| 220 | + // Hash digest mismatch may indicate that some deps are no longer |
| 221 | + // needed, some are missing, or that some constraints or source |
| 222 | + // locations have changed. |
208 | 223 | //
|
209 |
| - // TODO |
| 224 | + // It's possible for digests to not match, but still have a correct |
| 225 | + // lock. |
| 226 | + tw := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0) |
| 227 | + fmt.Fprintf(tw, "Project\tMissing Packages\t\n") |
210 | 228 |
|
211 | 229 | external := ptree.ExternalReach(true, false, nil).ListExternalImports()
|
| 230 | + roots := make(map[gps.ProjectRoot][]string) |
| 231 | + var errs []string |
| 232 | + for _, e := range external { |
| 233 | + root, err := sm.DeduceProjectRoot(e) |
| 234 | + if err != nil { |
| 235 | + errs = append(errs, string(root)) |
| 236 | + continue |
| 237 | + } |
| 238 | + |
| 239 | + roots[root] = append(roots[root], e) |
| 240 | + } |
| 241 | + |
| 242 | + outer: |
| 243 | + for root, pkgs := range roots { |
| 244 | + // TODO also handle the case where the project is present, but there |
| 245 | + // are items missing from just the package list |
| 246 | + for _, lp := range slp { |
| 247 | + if lp.Ident().ProjectRoot == root { |
| 248 | + continue outer |
| 249 | + } |
| 250 | + } |
212 | 251 |
|
213 |
| - // List imports? |
214 |
| - for _, importPath := range external { |
215 | 252 | fmt.Fprintf(tw,
|
216 |
| - "%s\t\t\t\t\t\t\n", |
217 |
| - importPath, |
| 253 | + "%s\t%s\t\n", |
| 254 | + root, |
| 255 | + pkgs, |
218 | 256 | )
|
219 |
| - |
220 | 257 | }
|
| 258 | + tw.Flush() |
221 | 259 | }
|
222 |
| - tw.Flush() |
223 | 260 |
|
224 | 261 | return nil
|
225 | 262 | }
|
226 | 263 |
|
| 264 | +type sortedLockedProjects []gps.LockedProject |
| 265 | + |
| 266 | +func (s sortedLockedProjects) Len() int { return len(s) } |
| 267 | +func (s sortedLockedProjects) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| 268 | +func (s sortedLockedProjects) Less(i, j int) bool { |
| 269 | + l, r := s[i].Ident(), s[j].Ident() |
| 270 | + |
| 271 | + if l.ProjectRoot < r.ProjectRoot { |
| 272 | + return true |
| 273 | + } |
| 274 | + if r.ProjectRoot < l.ProjectRoot { |
| 275 | + return false |
| 276 | + } |
| 277 | + |
| 278 | + return l.NetworkName < r.NetworkName |
| 279 | +} |
| 280 | + |
227 | 281 | func runStatusDetailed(p *project, sm *gps.SourceMgr, args []string) error {
|
228 | 282 | // TODO
|
229 | 283 | return fmt.Errorf("not implemented")
|
|
0 commit comments