Skip to content

Commit 1dee146

Browse files
committed
mod/modfile: add FixLegacy function
This implements the core functionality required to migrate legacy module.cue files to the new syntax. For #3137 Signed-off-by: Roger Peppe <[email protected]> Change-Id: I27cf3e7241d64d56398289c1400d51ef79b0f813 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1194769 Unity-Result: CUE porcuepine <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Reviewed-by: Daniel Martí <[email protected]>
1 parent 1591fd8 commit 1dee146

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed

mod/modfile/modfile.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,75 @@ func ParseNonStrict(modfile []byte, filename string) (*File, error) {
262262
return parse(modfile, filename, false)
263263
}
264264

265+
// FixLegacy converts a legacy module.cue file as parsed by [ParseLegacy]
266+
// into a format suitable for parsing with [Parse]. It adds a language.version
267+
// field and moves all unrecognized fields into custom.legacy.
268+
//
269+
// If there is no module field or it is empty, it is set to "test.example@v0".
270+
//
271+
// If the file already parses OK with [ParseNonStrict], it returns the
272+
// result of that.
273+
func FixLegacy(modfile []byte, filename string) (*File, error) {
274+
f, err := ParseNonStrict(modfile, filename)
275+
if err == nil {
276+
// It parses OK so it doesn't need fixing.
277+
return f, nil
278+
}
279+
ctx := cuecontext.New()
280+
file, err := parseDataOnlyCUE(ctx, modfile, filename)
281+
if err != nil {
282+
return nil, errors.Wrapf(err, token.NoPos, "invalid module.cue file syntax")
283+
}
284+
v := ctx.BuildFile(file)
285+
if err := v.Validate(cue.Concrete(true)); err != nil {
286+
return nil, errors.Wrapf(err, token.NoPos, "invalid module.cue file value")
287+
}
288+
var allFields map[string]any
289+
if err := v.Decode(&allFields); err != nil {
290+
return nil, err
291+
}
292+
mpath := "test.example@v0"
293+
if m, ok := allFields["module"]; ok {
294+
if mpath1, ok := m.(string); ok && mpath1 != "" {
295+
mpath = mpath1
296+
} else if !ok {
297+
return nil, fmt.Errorf("module field has unexpected type %T", m)
298+
}
299+
// TODO decide what to do if the module path isn't OK according to the new rules.
300+
}
301+
customLegacy := make(map[string]any)
302+
for k, v := range allFields {
303+
if k != "module" {
304+
customLegacy[k] = v
305+
}
306+
}
307+
var custom map[string]map[string]any
308+
if len(customLegacy) > 0 {
309+
custom = map[string]map[string]any{
310+
"legacy": customLegacy,
311+
}
312+
}
313+
f = &File{
314+
Module: mpath,
315+
Language: &Language{
316+
Version: cueversion.LanguageVersion(),
317+
},
318+
Custom: custom,
319+
}
320+
// Round-trip through [Parse] so that we get exactly the same
321+
// result as a later parse of the same data will. This also
322+
// adds a major version to the module path if needed.
323+
data, err := f.Format()
324+
if err != nil {
325+
return nil, fmt.Errorf("cannot format fixed file: %v", err)
326+
}
327+
f, err = ParseNonStrict(data, "fixed-"+filename)
328+
if err != nil {
329+
return nil, fmt.Errorf("cannot round-trip fixed module file %q: %v", data, err)
330+
}
331+
return f, nil
332+
}
333+
265334
func parse(modfile []byte, filename string, strict bool) (*File, error) {
266335
// Unfortunately we need a new context. See the note inside [moduleSchemaDo].
267336
ctx := cuecontext.New()

mod/modfile/modfile_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,65 @@ custom: "somewhere.com": foo: true
342342
wantDefaults: map[string]string{
343343
"foo.com/bar": "v0",
344344
},
345+
}, {
346+
testName: "FixLegacyWithModulePath",
347+
parse: FixLegacy,
348+
data: `
349+
module: "foo.com/bar"
350+
`,
351+
want: &File{
352+
Module: "foo.com/bar@v0",
353+
Language: &Language{Version: "v0.9.0"},
354+
},
355+
wantDefaults: map[string]string{
356+
"foo.com/bar": "v0",
357+
},
358+
}, {
359+
testName: "FixLegacyWithoutModulePath",
360+
parse: FixLegacy,
361+
data: `
362+
`,
363+
want: &File{
364+
Module: "test.example@v0",
365+
Language: &Language{Version: "v0.9.0"},
366+
},
367+
wantDefaults: map[string]string{
368+
"test.example": "v0",
369+
},
370+
}, {
371+
testName: "FixLegacyWithEmptyModulePath",
372+
parse: FixLegacy,
373+
data: `
374+
module: ""
375+
`,
376+
want: &File{
377+
Module: "test.example@v0",
378+
Language: &Language{Version: "v0.9.0"},
379+
},
380+
wantDefaults: map[string]string{
381+
"test.example": "v0",
382+
},
383+
}, {
384+
testName: "FixLegacyWithCustomFields",
385+
parse: FixLegacy,
386+
data: `
387+
module: "foo.com"
388+
some: true
389+
other: field: 123
390+
`,
391+
want: &File{
392+
Module: "foo.com@v0",
393+
Language: &Language{Version: "v0.9.0"},
394+
Custom: map[string]map[string]any{
395+
"legacy": {
396+
"some": true,
397+
"other": map[string]any{"field": 123},
398+
},
399+
},
400+
},
401+
wantDefaults: map[string]string{
402+
"foo.com": "v0",
403+
},
345404
}}
346405

347406
func TestParse(t *testing.T) {

0 commit comments

Comments
 (0)