diff --git a/docs/npm_translate_lock.md b/docs/npm_translate_lock.md index 61d60a1bac..ce2338757e 100644 --- a/docs/npm_translate_lock.md +++ b/docs/npm_translate_lock.md @@ -67,7 +67,7 @@ npm_translate_lock(name, lifecycle_hooks_use_default_shell_env, replace_packages, bins, verify_node_modules_ignored, verify_patches, quiet, external_repository_action_cache, link_workspace, pnpm_version, use_pnpm, - npm_package_target_name, use_starlark_yaml_parser, kwargs) + npm_package_target_name, kwargs) Repository macro to generate starlark code from a lock file. @@ -132,7 +132,6 @@ For more about how to use npm_translate_lock, read [pnpm and rules_js](/docs/pnp | pnpm_version | pnpm version to use when generating the @pnpm repository. Set to None to not create this repository.

Can be left unspecified and the rules_js default LATEST_PNPM_VERSION will be used.

Use use_pnpm for bzlmod. | "8.15.3" | | use_pnpm | label of the pnpm extension to use.

Can be left unspecified and the rules_js default pnpm extension (with the LATEST_PNPM_VERSION) will be used.

Use pnpm_version for non-bzlmod. | None | | npm_package_target_name | The name of linked npm_package targets. When npm_package targets are linked as pnpm workspace packages, the name of the target must align with this value.

The {dirname} placeholder is replaced with the directory name of the target.

By default the directory name of the target is used.

Default: {dirname} | "{dirname}" | -| use_starlark_yaml_parser | Opt-out of using yq to parse the pnpm-lock file which was added in https://github.com/aspect-build/rules_js/pull/1458 and use the legacy starlark yaml parser instead.

This opt-out is a return safety in cases where yq is not able to parse the pnpm generated yaml file. For example, this has been observed to happen due to a line such as the following in the pnpm generated lock file:

 resolution: {tarball: https://gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%40blockprotocol/type-system-web?6526c0e} 


where the ? character in the tarball value causes yq to fail with:

 $ yq pnpm-lock.yaml -o=json Error: bad file 'pnpm-lock.yaml': yaml: line 7129: did not find expected ',' or '}' 


If the tarball value is quoted or escaped then yq would accept it but as of this writing, the latest version of pnpm (8.14.3) does not quote or escape such a value and the latest version of yq (4.40.5) does not handle it as is.

Possibly related to https://github.com/pnpm/pnpm/issues/5414. | False | | kwargs | Internal use only | none | diff --git a/e2e/npm_translate_lock/WORKSPACE b/e2e/npm_translate_lock/WORKSPACE index 7f014805bb..1a8d239b82 100644 --- a/e2e/npm_translate_lock/WORKSPACE +++ b/e2e/npm_translate_lock/WORKSPACE @@ -19,7 +19,6 @@ npm_translate_lock( npmrc = "//:.npmrc", pnpm_lock = "//:pnpm-lock.yaml", update_pnpm_lock = True, - use_starlark_yaml_parser = True, # test coverage for this flag verify_node_modules_ignored = "//:.bazelignore", ) diff --git a/e2e/webpack_devserver/MODULE.bazel b/e2e/webpack_devserver/MODULE.bazel index aff87e2617..7d5521409e 100644 --- a/e2e/webpack_devserver/MODULE.bazel +++ b/e2e/webpack_devserver/MODULE.bazel @@ -12,7 +12,6 @@ npm.npm_translate_lock( name = "npm", npmrc = "//:.npmrc", pnpm_lock = "//:pnpm-lock.yaml", - use_starlark_yaml_parser = True, # test coverage for this flag verify_node_modules_ignored = "//:.bazelignore", ) use_repo(npm, "npm") diff --git a/npm/extensions.bzl b/npm/extensions.bzl index dd253478ea..878f54de8c 100644 --- a/npm/extensions.bzl +++ b/npm/extensions.bzl @@ -79,21 +79,19 @@ def _npm_lock_imports_bzlmod(module_ctx, attr): lock_packages = {} lock_patched_dependencies = {} lock_parse_err = None - if attr.use_starlark_yaml_parser: - lock_importers, lock_packages, lock_patched_dependencies, lock_parse_err = utils.parse_pnpm_lock_yaml(module_ctx.read(attr.pnpm_lock)) + + is_windows = repo_utils.is_windows(module_ctx) + host_yq = Label("@{}_{}//:yq{}".format(attr.yq_toolchain_prefix, repo_utils.platform(module_ctx), ".exe" if is_windows else "")) + yq_args = [ + str(module_ctx.path(host_yq)), + str(module_ctx.path(attr.pnpm_lock)), + "-o=json", + ] + result = module_ctx.execute(yq_args) + if result.return_code: + lock_parse_err = "failed to parse pnpm lock file with yq. '{}' exited with {}: \nSTDOUT:\n{}\nSTDERR:\n{}".format(" ".join(yq_args), result.return_code, result.stdout, result.stderr) else: - is_windows = repo_utils.is_windows(module_ctx) - host_yq = Label("@{}_{}//:yq{}".format(attr.yq_toolchain_prefix, repo_utils.platform(module_ctx), ".exe" if is_windows else "")) - yq_args = [ - str(module_ctx.path(host_yq)), - str(module_ctx.path(attr.pnpm_lock)), - "-o=json", - ] - result = module_ctx.execute(yq_args) - if result.return_code: - lock_parse_err = "failed to parse pnpm lock file with yq. '{}' exited with {}: \nSTDOUT:\n{}\nSTDERR:\n{}".format(" ".join(yq_args), result.return_code, result.stdout, result.stderr) - else: - lock_importers, lock_packages, lock_patched_dependencies, lock_parse_err = utils.parse_pnpm_lock_json(result.stdout if result.stdout != "null" else None) # NB: yq will return the string "null" if the yaml file is empty + lock_importers, lock_packages, lock_patched_dependencies, lock_parse_err = utils.parse_pnpm_lock_json(result.stdout if result.stdout != "null" else None) # NB: yq will return the string "null" if the yaml file is empty if lock_parse_err != None: msg = """ diff --git a/npm/private/BUILD.bazel b/npm/private/BUILD.bazel index 2a4d4ea616..117f037c5f 100644 --- a/npm/private/BUILD.bazel +++ b/npm/private/BUILD.bazel @@ -211,7 +211,6 @@ bzl_library( srcs = ["utils.bzl"], visibility = ["//npm:__subpackages__"], deps = [ - ":yaml", "@aspect_bazel_lib//lib:paths", "@aspect_bazel_lib//lib:repo_utils", "@aspect_bazel_lib//lib:utils", @@ -304,9 +303,3 @@ bzl_library( srcs = ["versions.bzl"], visibility = ["//npm:__subpackages__"], ) - -bzl_library( - name = "yaml", - srcs = ["yaml.bzl"], - visibility = ["//npm:__subpackages__"], -) diff --git a/npm/private/npm_translate_lock.bzl b/npm/private/npm_translate_lock.bzl index 3fe5a694cc..98c8810c52 100644 --- a/npm/private/npm_translate_lock.bzl +++ b/npm/private/npm_translate_lock.bzl @@ -76,7 +76,6 @@ _ATTRS = { "root_package": attr.string(default = DEFAULT_ROOT_PACKAGE), "update_pnpm_lock": attr.bool(), "use_home_npmrc": attr.bool(), - "use_starlark_yaml_parser": attr.bool(), "verify_node_modules_ignored": attr.label(), "verify_patches": attr.label(), "yarn_lock": attr.label(), @@ -186,7 +185,6 @@ def npm_translate_lock( pnpm_version = LATEST_PNPM_VERSION, use_pnpm = None, npm_package_target_name = "{dirname}", - use_starlark_yaml_parser = False, **kwargs): """Repository macro to generate starlark code from a lock file. @@ -514,31 +512,6 @@ def npm_translate_lock( Default: `{dirname}` - use_starlark_yaml_parser: Opt-out of using `yq` to parse the pnpm-lock file which was added - in https://github.com/aspect-build/rules_js/pull/1458 and use the legacy starlark yaml - parser instead. - - This opt-out is a return safety in cases where yq is not able to parse the pnpm generated - yaml file. For example, this has been observed to happen due to a line such as the following - in the pnpm generated lock file: - - ``` - resolution: {tarball: https://gitpkg.vercel.app/blockprotocol/blockprotocol/packages/%40blockprotocol/type-system-web?6526c0e} - ``` - - where the `?` character in the `tarball` value causes `yq` to fail with: - - ``` - $ yq pnpm-lock.yaml -o=json - Error: bad file 'pnpm-lock.yaml': yaml: line 7129: did not find expected ',' or '}' - ``` - - If the tarball value is quoted or escaped then yq would accept it but as of this writing, the latest - version of pnpm (8.14.3) does not quote or escape such a value and the latest version of yq (4.40.5) - does not handle it as is. - - Possibly related to https://github.com/pnpm/pnpm/issues/5414. - **kwargs: Internal use only """ @@ -632,7 +605,6 @@ def npm_translate_lock( use_pnpm = use_pnpm, yq_toolchain_prefix = yq_toolchain_prefix, npm_package_target_name = npm_package_target_name, - use_starlark_yaml_parser = use_starlark_yaml_parser, bzlmod = bzlmod, ) diff --git a/npm/private/npm_translate_lock_state.bzl b/npm/private/npm_translate_lock_state.bzl index 07c97797de..c6d90faeb3 100644 --- a/npm/private/npm_translate_lock_state.bzl +++ b/npm/private/npm_translate_lock_state.bzl @@ -518,19 +518,17 @@ def _load_lockfile(priv, rctx, label_store): packages = {} patched_dependencies = {} lock_parse_err = None - if rctx.attr.use_starlark_yaml_parser: - importers, packages, patched_dependencies, lock_parse_err = utils.parse_pnpm_lock_yaml(rctx.read(label_store.path("pnpm_lock"))) + + yq_args = [ + str(label_store.path("host_yq")), + str(label_store.path("pnpm_lock")), + "-o=json", + ] + result = rctx.execute(yq_args) + if result.return_code: + lock_parse_err = "failed to parse pnpm lock file with yq. '{}' exited with {}: \nSTDOUT:\n{}\nSTDERR:\n{}".format(" ".join(yq_args), result.return_code, result.stdout, result.stderr) else: - yq_args = [ - str(label_store.path("host_yq")), - str(label_store.path("pnpm_lock")), - "-o=json", - ] - result = rctx.execute(yq_args) - if result.return_code: - lock_parse_err = "failed to parse pnpm lock file with yq. '{}' exited with {}: \nSTDOUT:\n{}\nSTDERR:\n{}".format(" ".join(yq_args), result.return_code, result.stdout, result.stderr) - else: - importers, packages, patched_dependencies, lock_parse_err = utils.parse_pnpm_lock_json(result.stdout if result.stdout != "null" else None) # NB: yq will return the string "null" if the yaml file is empty + importers, packages, patched_dependencies, lock_parse_err = utils.parse_pnpm_lock_json(result.stdout if result.stdout != "null" else None) # NB: yq will return the string "null" if the yaml file is empty priv["importers"] = importers priv["packages"] = packages diff --git a/npm/private/test/BUILD.bazel b/npm/private/test/BUILD.bazel index c5fb073c15..3df12c61e3 100644 --- a/npm/private/test/BUILD.bazel +++ b/npm/private/test/BUILD.bazel @@ -9,7 +9,6 @@ load(":pkg_glob_tests.bzl", "pkg_glob_tests") load(":transitive_closure_tests.bzl", "transitive_closure_tests") load(":translate_lock_helpers_tests.bzl", "translate_lock_helpers_tests") load(":utils_tests.bzl", "utils_tests") -load(":yaml_tests.bzl", "yaml_tests") # gazelle:exclude **/snapshots/**/* @@ -26,8 +25,6 @@ transitive_closure_tests(name = "test_transitive_closure") translate_lock_helpers_tests(name = "test_translate_lock") -yaml_tests(name = "test_yaml") - parse_pnpm_lock_tests(name = "test_parse_pnpm_lock") generated_pkg_json_test(name = "test_generated_pkg_json") diff --git a/npm/private/test/parse_pnpm_lock_tests.bzl b/npm/private/test/parse_pnpm_lock_tests.bzl index 72ac2d2424..9221d3181b 100644 --- a/npm/private/test/parse_pnpm_lock_tests.bzl +++ b/npm/private/test/parse_pnpm_lock_tests.bzl @@ -6,55 +6,18 @@ load("//npm/private:utils.bzl", "utils") def _parse_empty_lock_test_impl(ctx): env = unittest.begin(ctx) - parsed_yaml = utils.parse_pnpm_lock_yaml("") parsed_json_a = utils.parse_pnpm_lock_json("") parsed_json_b = utils.parse_pnpm_lock_json("{}") expected = ({}, {}, {}, None) - asserts.equals(env, expected, parsed_yaml) asserts.equals(env, expected, parsed_json_a) asserts.equals(env, expected, parsed_json_b) return unittest.end(env) -def _parse_merge_conflict_test_impl(ctx): - env = unittest.begin(ctx) - - parsed = utils.parse_pnpm_lock_yaml(""" -importers: - .: - dependencies: -<<<<< HEAD""") - expected = ({}, {}, {}, "expected lockfileVersion key in lockfile") - - asserts.equals(env, expected, parsed) - - return unittest.end(env) - def _parse_lockfile_v5_test_impl(ctx): env = unittest.begin(ctx) - parsed_yaml = utils.parse_pnpm_lock_yaml("""\ -lockfileVersion: 5.4 - -specifiers: - '@aspect-test/a': 5.0.0 - -dependencies: - '@aspect-test/a': 5.0.0 - -packages: - - /@aspect-test/a/5.0.0: - resolution: {integrity: sha512-t/lwpVXG/jmxTotGEsmjwuihC2Lvz/Iqt63o78SI3O5XallxtFp5j2WM2M6HwkFiii9I42KdlAF8B3plZMz0Fw==} - hasBin: true - dependencies: - '@aspect-test/b': 5.0.0 - '@aspect-test/c': 1.0.0 - '@aspect-test/d': 2.0.0_@aspect-test+c@1.0.0 - dev: false -""") - parsed_json = utils.parse_pnpm_lock_json("""\ { "lockfileVersion": 5.4, @@ -109,7 +72,6 @@ packages: None, ) - asserts.equals(env, expected, parsed_yaml) asserts.equals(env, expected, parsed_json) return unittest.end(env) @@ -117,26 +79,6 @@ packages: def _parse_lockfile_v6_test_impl(ctx): env = unittest.begin(ctx) - parsed_yaml = utils.parse_pnpm_lock_yaml("""\ -lockfileVersion: '6.0' - -dependencies: - '@aspect-test/a': - specifier: 5.0.0 - version: 5.0.0 - -packages: - - /@aspect-test/a@5.0.0: - resolution: {integrity: sha512-t/lwpVXG/jmxTotGEsmjwuihC2Lvz/Iqt63o78SI3O5XallxtFp5j2WM2M6HwkFiii9I42KdlAF8B3plZMz0Fw==} - hasBin: true - dependencies: - '@aspect-test/b': 5.0.0 - '@aspect-test/c': 1.0.0 - '@aspect-test/d': 2.0.0(@aspect-test/c@1.0.0) - dev: false -""") - parsed_json = utils.parse_pnpm_lock_json("""\ { "lockfileVersion": "6.0", @@ -191,7 +133,6 @@ packages: None, ) - asserts.equals(env, expected, parsed_yaml) asserts.equals(env, expected, parsed_json) return unittest.end(env) @@ -199,13 +140,11 @@ packages: a_test = unittest.make(_parse_empty_lock_test_impl, attrs = {}) b_test = unittest.make(_parse_lockfile_v5_test_impl, attrs = {}) c_test = unittest.make(_parse_lockfile_v6_test_impl, attrs = {}) -d_test = unittest.make(_parse_merge_conflict_test_impl, attrs = {}) TESTS = [ a_test, b_test, c_test, - d_test, ] def parse_pnpm_lock_tests(name): diff --git a/npm/private/test/yaml_tests.bzl b/npm/private/test/yaml_tests.bzl deleted file mode 100644 index c6d05a848c..0000000000 --- a/npm/private/test/yaml_tests.bzl +++ /dev/null @@ -1,579 +0,0 @@ -"Unit tests for yaml.bzl" - -load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//npm/private:yaml.bzl", "parse") - -def _parse_basic_test_impl(ctx): - env = unittest.begin(ctx) - - # Scalars - asserts.equals(env, (True, None), parse("true")) - asserts.equals(env, (False, None), parse("false")) - asserts.equals(env, (1, None), parse("1")) - asserts.equals(env, (3.14, None), parse("3.14")) - asserts.equals(env, ("foo", None), parse("foo")) - asserts.equals(env, ("foo", None), parse("'foo'")) - asserts.equals(env, ("foo", None), parse("\"foo\"")) - asserts.equals(env, ("foo", None), parse(" foo")) - asserts.equals(env, ("foo", None), parse(" foo ")) - asserts.equals(env, ("foo", None), parse("foo ")) - asserts.equals(env, ("foo", None), parse("\nfoo")) - asserts.equals(env, ("foo", None), parse("foo\n")) - asserts.equals(env, ("-foo", None), parse("-foo")) - asserts.equals(env, ("foo{[]}", None), parse("foo{[]}")) - - return unittest.end(env) - -def _parse_sequences_test_impl(ctx): - env = unittest.begin(ctx) - - # Sequences (- notation) - asserts.equals(env, (["foo"], None), parse("- foo")) - asserts.equals(env, (["foo - bar"], None), parse("- foo - bar")) - asserts.equals(env, (["foo", "bar"], None), parse("""\ -- foo -- bar -""")) - asserts.equals(env, (["foo"], None), parse("""\ -- - foo -""")) - asserts.equals(env, (["foo", "bar"], None), parse("""\ -- - foo -- - bar -""")) - asserts.equals(env, (["foo", "bar"], None), parse("""\ -- foo -- - bar -""")) - - # Sequences ([] notation) - asserts.equals(env, ([], None), parse("[]")) - asserts.equals(env, (["foo"], None), parse("[foo]")) - asserts.equals(env, (["fo o"], None), parse("[fo o]")) - asserts.equals(env, (["fo\no"], None), parse("[fo\no]")) - asserts.equals(env, (["foo", "bar"], None), parse("[foo,bar]")) - asserts.equals(env, (["foo", "bar"], None), parse("[foo, bar]")) - asserts.equals(env, ([1, True, "false"], None), parse("[1, true, \"false\"]")) - asserts.equals(env, (["foo", "bar"], None), parse("""\ -[ - foo, - bar -] -""")) - asserts.equals(env, (["foo", "bar"], None), parse("""\ -[ - foo, - bar, -] -""")) - asserts.equals(env, (["foo", "bar"], None), parse("""\ -[ - 'foo', - "bar", -] -""")) - asserts.equals(env, (["foo", "bar"], None), parse("""\ -[ - foo, - - bar -] -""")) - asserts.equals(env, ([["foo", "bar"]], None), parse("[[foo,bar]]")) - asserts.equals(env, ([["foo", [1, True]]], None), parse("[[foo,[1, true]]]")) - asserts.equals(env, ([["foo", "bar"]], None), parse("[[foo, bar]]")) - asserts.equals(env, ([["foo", "bar"]], None), parse("""\ -[ - [ - foo, - bar - ] -] -""")) - - return unittest.end(env) - -def _parse_maps_test_impl(ctx): - env = unittest.begin(ctx) - - # Maps - scalar properties - asserts.equals(env, ({"foo": "bar"}, None), parse("foo: bar")) - asserts.equals(env, ({"foo": "bar"}, None), parse("foo: 'bar'")) - asserts.equals(env, ({"foo": "bar"}, None), parse("foo: \"bar\"")) - asserts.equals(env, ({"foo": "bar"}, None), parse("foo: bar ")) - asserts.equals(env, ({"foo": "bar"}, None), parse("'foo': bar")) - asserts.equals(env, ({"foo": "bar"}, None), parse("\"foo\": bar")) - asserts.equals(env, ({"foo": 1.5}, None), parse("foo: 1.5")) - asserts.equals(env, ({"foo": True}, None), parse("foo: true")) - asserts.equals(env, ({"foo": False}, None), parse("foo: false")) - asserts.equals(env, ({"foo": "bar:baz"}, None), parse("foo: bar:baz")) - - # Maps - flow notation - asserts.equals(env, ({}, None), parse("{}")) - asserts.equals(env, ({"foo": "bar"}, None), parse("{foo: bar}")) - asserts.equals(env, ({"foo": "bar"}, None), parse("{foo: 'bar'}")) - asserts.equals(env, ({"foo": "bar"}, None), parse("{foo: \"bar\"}")) - asserts.equals(env, ({"foo": "bar"}, None), parse("{foo: bar }")) - asserts.equals(env, ({"foo": "bar"}, None), parse("{'foo': bar}")) - asserts.equals(env, ({"foo": "bar"}, None), parse("{\"foo\": bar}")) - asserts.equals(env, ({"foo": 1.5}, None), parse("{foo: 1.5}")) - asserts.equals(env, ({"foo": True}, None), parse("{foo: true}")) - asserts.equals(env, ({"foo": False}, None), parse("{foo: false}")) - asserts.equals(env, ({"foo": "bar:baz"}, None), parse("{foo: bar:baz}")) - asserts.equals(env, ({"foo": {"bar": 5}}, None), parse("{foo: {bar: 5}}")) - asserts.equals(env, ({"foo": 5, "bar": 6}, None), parse("{foo: 5, bar: 6}")) - asserts.equals(env, ({"foo": 5, "bar": {"moo": "cow"}, "faz": "baz"}, None), parse("{foo: 5, bar: {moo: cow}, faz: baz}")) - asserts.equals(env, ({"foo": {"bar": 5}}, None), parse("""\ -{ - foo: - { - bar: 5 - } -} -""")) - - return unittest.end(env) - -def _parse_multiline_test_impl(ctx): - env = unittest.begin(ctx) - - # Literal multiline strings (|), strip (-) and keep (+) - asserts.equals(env, ({"foo": "bar\n"}, None), parse("""\ -foo: | - bar -""")) - asserts.equals(env, ({"foo": "bar\n", "moo": "cow\n"}, None), parse("""\ -foo: | - bar -moo: | - cow -""")) - asserts.equals(env, ({"foo": {"bar": "baz \n faz\n"}}, None), parse("""\ -foo: - bar: | - baz - faz -""")) - asserts.equals(env, ({"a": "b\nc\nd\n"}, None), parse("""\ -a: | - b - c - d - - -""")) - asserts.equals(env, ({"a": "\n\nb\n\nc\n\nd\n"}, None), parse("""\ -a: | - - - b - - c - - d - - -""")) - asserts.equals(env, ({"foo": "bar"}, None), parse("""\ -foo: |- - bar -""")) - asserts.equals(env, ({"foo": "bar", "moo": "cow"}, None), parse("""\ -foo: |- - bar -moo: |- - cow -""")) - asserts.equals(env, ({"foo": "bar\n"}, None), parse("""\ -foo: |+ - bar -""")) - asserts.equals(env, ({"a": "\n\nb\n\nc\n\nd\n\n\n"}, None), parse("""\ -a: |+ - - - b - - c - - d - - -""")) - asserts.equals(env, ({"foo": "bar\n", "moo": "cow", "faz": "baz\n\n"}, None), parse("""\ -foo: | - bar -moo: |- - cow - -faz: |+ - baz - -""")) - - return unittest.end(env) - -def _parse_mixed_test_impl(ctx): - env = unittest.begin(ctx) - - # Mixed sequence and map flows - asserts.equals(env, ({"foo": ["moo"]}, None), parse("{foo: [moo]}")) - asserts.equals(env, (["foo", {"moo": "cow"}], None), parse("[foo, {moo: cow}]")) - asserts.equals(env, ({"foo": {"moo": "cow", "faz": ["baz", 123]}}, None), parse("{foo: {moo: cow, faz: [baz, 123]}}")) - asserts.equals(env, ([{"foo": ["bar", {"moo": ["cow"]}], "json": "bearded"}], None), parse("[{foo: [bar, {moo: [cow]}], json: bearded}]")) - - # Multi-level maps - asserts.equals(env, ({"foo": {"moo": "cow"}}, None), parse("""\ -foo: - moo: cow -""")) - asserts.equals(env, ({"foo": {"bar": {"moo": 5, "cow": True}}}, None), parse("""\ -foo: - bar: - moo: 5 - cow: true -""")) - asserts.equals(env, ({"a": {"b": {"c": {"d": 1, "e": 2, "f": 3}, "g": {"h": 4}}}}, None), parse("""\ -a: - b: - c: - d: 1 - e: 2 - f: 3 - g: - h: 4 -""")) - - # More than one root property - asserts.equals(env, ({"a": True, "b": False}, None), parse("""\ -a: true -b: false -""")) - asserts.equals(env, ({"a": {"b": True}, "c": {"d": False}}, None), parse("""\ -a: - b: true -c: - d: false -""")) - - # Value begins on next line at an indent - asserts.equals(env, ({"moo": "cow"}, None), parse("""\ -moo: - cow -""")) - - # Mixed flow and non-flow maps - asserts.equals(env, ({"foo": {"bar": {"moo": 5, "cow": True}}}, None), parse("""\ -foo: {bar: {moo: 5, cow: true}} -""")) - asserts.equals(env, ({"foo": {"bar": {"moo": 5, "cow": True}, "baz": "faz"}}, None), parse("""\ -foo: {bar: {moo: 5, cow: true}, baz: faz} -""")) - asserts.equals(env, ({"foo": {"bar": {"moo": 5, "cow": True}}}, None), parse("""\ -foo: - bar: {moo: 5, cow: true} -""")) - asserts.equals(env, ({"foo": {"bar": {"moo": 5, "cow": True}, "baz": "faz"}, "json": ["bearded"]}, None), parse("""\ -foo: - bar: - { - moo: 5, cow: true - } - baz: - faz -json: [bearded] -""")) - asserts.equals(env, ({"foo": {"bar": {"moo": [{"cow": True}]}}}, None), parse("""\ -foo: - bar: {moo: [ - {cow: true} - ]} -""")) - - # Miscellaneous - asserts.equals(env, ({"foo": {"bar": "b-ar", "moo": ["cow"]}, "loo": ["roo", "goo"]}, None), parse("""\ -foo: - bar: b-ar - moo: [cow] -loo: - - roo - - goo -""")) - - asserts.equals(env, ({"foo": "bar\n", "baz": 5}, None), parse("""\ -foo: | - bar -baz: 5 -""")) - - return unittest.end(env) - -def _parse_complex_map_test_impl(ctx): - env = unittest.begin(ctx) - - # Basic complex-object - asserts.equals(env, ({"a": True}, None), parse("""\ -? a -: true -""")) - - # Multiple bsic complex-object - asserts.equals(env, ({"a": True, "b": False}, None), parse("""\ -? a -: true -? b -: false -""")) - - # Whitespace in various places - asserts.equals(env, ({"a": True, "b": False, "c": "foo", " d ": " e "}, None), parse("""\ -? a -: true - -? b - -: false - -? 'c' -: foo - -? " d " -: " e " -""")) - - # Object in complex mapping key - asserts.equals(env, ({"a": {"b": {"c": 1, "d": True}}}, None), parse("""\ -a: - ? b - : c: 1 - d: true -""")) - - # Array in complex mapping key - asserts.equals(env, ({"a": {"b": [1, True]}}, None), parse("""\ -a: - ? b - : [1, true] -""")) - - # Arrays in complex mapping key - asserts.equals(env, ({"a": [1, 2]}, None), parse("""\ -? a -: - 1 - - 2 -""")) - - # Multiple nesting, arrays in objects - asserts.equals(env, ({"a": {"b": {"c": [1, 2]}}, "d": 3}, None), parse("""\ -? a -: ? b - : ? c - : - 1 - - 2 -? d -: 3 -""")) - - # Popping in/out of nested maps - asserts.equals(env, ({ - "a": { - "b": { - "c": 1, - "d": { - "e": "f", - }, - "g": 3, - }, - "e": 3, - }, - }, None), parse("""\ -a: - ? b - : c: 1 - d: - ? e - : f - g: 3 - ? e - : 3 -""")) - - return unittest.end(env) - -def _parse_lockfile_test_impl(ctx): - env = unittest.begin(ctx) - - # Partial lock file - asserts.equals(env, ({ - "lockfileVersion": 5.4, - "specifiers": { - "@aspect-test/a": "5.0.0", - }, - "dependencies": { - "@aspect-test/a": "5.0.0", - }, - "packages": { - "/@aspect-test/a/5.0.0": { - "resolution": { - "integrity": "sha512-t/lwpVXG/jmxTotGEsmjwuihC2Lvz/Iqt63o78SI3O5XallxtFp5j2WM2M6HwkFiii9I42KdlAF8B3plZMz0Fw==", - }, - "hasBin": True, - "dependencies": { - "@aspect-test/b": "5.0.0", - "@aspect-test/c": "1.0.0", - "@aspect-test/d": "2.0.0_@aspect-test+c@1.0.0", - }, - "dev": False, - }, - }, - }, None), parse("""\ -lockfileVersion: 5.4 - -specifiers: - '@aspect-test/a': 5.0.0 - -dependencies: - '@aspect-test/a': 5.0.0 - -packages: - - /@aspect-test/a/5.0.0: - resolution: {integrity: sha512-t/lwpVXG/jmxTotGEsmjwuihC2Lvz/Iqt63o78SI3O5XallxtFp5j2WM2M6HwkFiii9I42KdlAF8B3plZMz0Fw==} - hasBin: true - dependencies: - '@aspect-test/b': 5.0.0 - '@aspect-test/c': 1.0.0 - '@aspect-test/d': 2.0.0_@aspect-test+c@1.0.0 - dev: false -""")) - - asserts.equals(env, ({ - "lockfileVersion": "6.0", - "packages": { - "/pkg-a@1.2.3": { - "resolution": { - "integrity": "sha512-asdf", - }, - "dev": False, - }, - "/pkg-c@3.2.1": { - "resolution": { - "integrity": "sha512-fdsa", - }, - "dev": True, - }, - }, - }, None), parse("""\ -lockfileVersion: '6.0' - -packages: - ? /pkg-a@1.2.3 - : resolution: {integrity: sha512-asdf} - dev: false - - ? /pkg-c@3.2.1 - : resolution: {integrity: sha512-fdsa} - dev: true -""")) - - return unittest.end(env) - -def _parse_conflict(ctx): - env = unittest.begin(ctx) - - # Similar test to https://github.com/pnpm/pnpm/blob/37fffbefa5a9136f2e189c01a5edf3c00ac48018/packages/supi/test/lockfile.ts#L1237C1-L1249C13 - asserts.equals(env, (None, "Unknown result state: <<<<< HEAD for value 100.0.0"), parse("""\ -importers: - .: - dependencies: -<<<<< HEAD - dep-of-pkg-with-1-dep: 100.0.0 -===== - dep-of-pkg-with-1-dep: 100.1.0 ->>>>> next - specifiers: - dep-of-pkg-with-1-dep: '>100.0.0' -lockfileVersion: 123 -packages: -<<<<<<< HEAD -""")) - - return unittest.end(env) - -def _parse_errors(ctx): - env = unittest.begin(ctx) - - asserts.equals(env, (None, "Unknown result state: sdlkfdslkjf for value +"), parse("""\ -asdljfk - sdlkfdslkjf - - - + - [- - -- sldkjf -""")) - - asserts.equals(env, (None, "Unexpected EOF"), parse("""\ -[asdljfk -""")) - - asserts.equals(env, (None, "Unexpected EOF"), parse("""\ -a: [ -""")) - - return unittest.end(env) - -parse_basic_test = unittest.make( - _parse_basic_test_impl, - attrs = {}, -) -parse_sequences_test = unittest.make( - _parse_sequences_test_impl, - attrs = {}, -) -parse_maps_test = unittest.make( - _parse_maps_test_impl, - attrs = {}, -) -parse_multiline_test = unittest.make( - _parse_multiline_test_impl, - attrs = {}, -) - -parse_mixed_test = unittest.make( - _parse_mixed_test_impl, - attrs = {}, -) -parse_complex_map_test = unittest.make( - _parse_complex_map_test_impl, - attrs = {}, -) -parse_lockfile_test = unittest.make( - _parse_lockfile_test_impl, - attrs = {}, -) -parse_conflict_test = unittest.make( - _parse_conflict, - attrs = {}, -) -parse_errors_test = unittest.make( - _parse_errors, - attrs = {}, -) - -def yaml_tests(name): - unittest.suite( - name, - parse_basic_test, - parse_sequences_test, - parse_maps_test, - parse_multiline_test, - parse_mixed_test, - parse_complex_map_test, - parse_lockfile_test, - parse_conflict_test, - parse_errors_test, - ) diff --git a/npm/private/utils.bzl b/npm/private/utils.bzl index 75f88f558b..e4d365edfc 100644 --- a/npm/private/utils.bzl +++ b/npm/private/utils.bzl @@ -5,7 +5,6 @@ load("@aspect_bazel_lib//lib:repo_utils.bzl", "repo_utils") load("@aspect_bazel_lib//lib:utils.bzl", bazel_lib_utils = "utils") load("@bazel_skylib//lib:paths.bzl", "paths") load("@bazel_skylib//lib:types.bzl", "types") -load(":yaml.bzl", _parse_yaml = "parse") INTERNAL_ERROR_MSG = "ERROR: rules_js internal error, please file an issue: https://github.com/aspect-build/rules_js/issues" DEFAULT_REGISTRY_PROTOCOL = "https" @@ -162,18 +161,6 @@ def _convert_v6_packages(packages): result[_convert_pnpm_v6_package_name(package)] = package_info return result -def _parse_pnpm_lock_yaml(content): - """Parse the content of a pnpm-lock.yaml file. - - Args: - content: lockfile content - - Returns: - A tuple of (importers dict, packages dict, patched_dependencies dict, error string) - """ - parsed, err = _parse_yaml(content) - return _parse_pnpm_lock_common(parsed, err) - def _parse_pnpm_lock_json(content): """Parse the content of a pnpm-lock.yaml file. @@ -186,7 +173,7 @@ def _parse_pnpm_lock_json(content): return _parse_pnpm_lock_common(json.decode(content) if content else None, None) def _parse_pnpm_lock_common(parsed, err): - """Helper function used by _parse_pnpm_lock_yaml and _parse_pnpm_lock_json. + """Helper function used by _parse_pnpm_lock_json. Args: parsed: lockfile content object @@ -458,7 +445,6 @@ utils = struct( pnpm_name = _pnpm_name, assert_lockfile_version = _assert_lockfile_version, parse_pnpm_package_key = _parse_pnpm_package_key, - parse_pnpm_lock_yaml = _parse_pnpm_lock_yaml, parse_pnpm_lock_json = _parse_pnpm_lock_json, friendly_name = _friendly_name, package_store_name = _package_store_name, diff --git a/npm/private/yaml.bzl b/npm/private/yaml.bzl deleted file mode 100644 index 0639e96834..0000000000 --- a/npm/private/yaml.bzl +++ /dev/null @@ -1,602 +0,0 @@ -"An experimental (and incomplete) yaml parser for starlark" -# https://github.com/bazelbuild/starlark/issues/219 - -_STATE = struct( - # Consume extraneous space and keep track of indent level - CONSUME_SPACE = 0, - # Consume whitespace within a flow, where indentation isn't a factor - CONSUME_SPACE_FLOW = 1, - # Parse the next key or value - PARSE_NEXT = 2, - # Parse a multiline string - PARSE_MULTILINE_STRING = 3, - # Parse the next key or value inside of a flow - PARSE_NEXT_FLOW = 4, - # Pseudo-states that don't perform any logic but indicate the current - # hierarchical result being formed in the starlark output. - KEY = 5, - SEQUENCE = 6, -) - -EOF = {} - -def parse(yaml): - """Parse yaml into starlark - - Args: - yaml: string, the yaml content to parse - - Returns: - A tuple containing an equivalent mapping to native starlark types and error. - """ - yaml = _normalize_yaml(yaml) - - starlark = {"result": None, "error": None} - stack = [] - stack.append(_new_CONSUME_SPACE(indent = "")) - - inputs = yaml.elems()[:] - inputs.append(EOF) - - for input in inputs: - state = _peek(stack) - - if state["id"] == _STATE.CONSUME_SPACE: - _handle_CONSUME_SPACE(state, input, stack, starlark) - elif state["id"] == _STATE.CONSUME_SPACE_FLOW: - _handle_CONSUME_SPACE_FLOW(state, input, stack, starlark) - elif state["id"] == _STATE.PARSE_NEXT: - _handle_PARSE_NEXT(state, input, stack, starlark) - elif state["id"] == _STATE.PARSE_MULTILINE_STRING: - _handle_PARSE_MULTILINE_STRING(state, input, stack, starlark) - elif state["id"] == _STATE.PARSE_NEXT_FLOW: - _handle_PARSE_NEXT_FLOW(state, input, stack, starlark) - else: - msg = "Unknown state {}".format(state["id"]) - fail(msg) - - if starlark["error"] != None: - return None, starlark["error"] - - return starlark["result"], None - -def _handle_CONSUME_SPACE(state, input, stack, starlark): - if input == EOF: - return - - if input == "\n": - # Reset the indentation - state["indent"] = "" - elif input.isspace(): - # Count the leading indentation - state["indent"] += input - elif input == "{": - stack.pop() - - # We are at the beginning of a new flow map - stack.append(_new_KEY( - key = "", - flow = True, - )) - - _initialize_result_value(starlark, stack) - - # Consume any space following the { - stack.append(_new_CONSUME_SPACE_FLOW()) - - elif input == "?": - # Complex mapping keys are indicated by a "?" at the beginning of the line. - # NOTE: currently only supports string value keys. - stack.pop() - - # We just started parsing a key - _pop_higher_indented_states(stack, state["indent"]) - - if len(stack) < 1 or len(state["indent"]) > len(_peek(stack, _STATE.KEY)["indent"]): - # The key is part of a new map - stack.append(_new_KEY( - key = "", - indent = state["indent"], - flow = False, - complex = True, - )) - else: - # The key is a sibling in the map - _peek(stack, _STATE.KEY)["key"] = "" - _peek(stack, _STATE.KEY)["complex"] = True - - _initialize_result_value(starlark, stack) - - # Consume any space following the ?. Add an extra space in place of the "?" - stack.append(_new_CONSUME_SPACE(indent = state["indent"] + " ")) - - elif input == ":" and len(stack) > 1 and stack[-2]["id"] == _STATE.KEY and stack[-2]["complex"]: - stack.pop() - - # Add an extra space in place of the ":" - stack.append(_new_CONSUME_SPACE(indent = state["indent"] + " ")) - - elif input == "[": - stack.pop() - - # We are at the beginning of a new flow sequence - stack.append(_new_SEQUENCE( - index = 0, - flow = True, - )) - - _initialize_result_value(starlark, stack) - - # Consume any space following the [ - stack.append(_new_CONSUME_SPACE_FLOW()) - elif input == "|": - stack.pop() - - # We are at the beginning of a multiline string - stack.append(_new_PARSE_MULTILINE_STRING( - indent = state["indent"], - )) - else: - # Reached the beginning of a value or key - stack.pop() - stack.append(_new_PARSE_NEXT( - indent = state["indent"], - buffer = input, - )) - -def _handle_PARSE_NEXT(state, input, stack, starlark): - if input == EOF: - stack.pop() - - # We just parsed a scalar - _set_result_value(starlark, stack, _parse_scalar(state["buffer"])) - - # Consume any space following the scalar - stack.append(_new_CONSUME_SPACE( - indent = "", - )) - elif input.isspace(): - if state["buffer"].endswith(":"): - stack.pop() - - # We just parsed a key - _pop_higher_indented_states(stack, state["indent"]) - - if len(stack) < 1 or len(state["indent"]) > len(_peek(stack, _STATE.KEY)["indent"]): - # The key is part of a new map - stack.append(_new_KEY( - key = _parse_key(state["buffer"][0:-1]), - indent = state["indent"], - flow = False, - complex = False, - )) - else: - # The key is a sibling in the map - _peek(stack, _STATE.KEY)["key"] = _parse_key(state["buffer"][0:-1]) - _peek(stack, _STATE.KEY)["complex"] = False - - _initialize_result_value(starlark, stack) - - # Consume any space following the key - stack.append(_new_CONSUME_SPACE( - indent = state["indent"] if not input == "\n" else "", - )) - elif state["buffer"] == "-": - stack.pop() - - if len(stack) > 0 and stack[-1]["id"] == _STATE.SEQUENCE: - # We are at the next item in a non-flow sequence - stack[-1]["index"] += 1 - else: - # We are at the beginning of a non-flow sequence - stack.append(_new_SEQUENCE( - indent = state["indent"], - index = 0, - flow = False, - )) - - _initialize_result_value(starlark, stack) - - # Consume any space following the sequence marker - stack.append(_new_CONSUME_SPACE( - indent = state["indent"] if not input == "\n" else "", - )) - elif input == "\n": - stack.pop() - - top = _peek(stack) if len(stack) > 0 else None - if top and top["id"] == _STATE.KEY and top["complex"] and not top["key"]: - # We just parsed a ?: key - top["key"] = _parse_scalar(state["buffer"]) - else: - # We just parsed a scalar - _set_result_value(starlark, stack, _parse_scalar(state["buffer"])) - - # Consume any space following the scalar - stack.append(_new_CONSUME_SPACE( - indent = "", - )) - else: - # Accumulate the space as part of the next thing to parse - state["buffer"] += input - else: - # Accumulate the current text until we know what to do with it - state["buffer"] += input - -def _handle_PARSE_MULTILINE_STRING(state, input, stack, starlark): - if not state["consumed_first_newline"]: - # Consume the rest of the line after the multiline indicator - if input == "+": - state["keep"] = True - elif input == "-": - state["strip"] = True - elif input == "\n": - state["consumed_first_newline"] = True - elif input.isspace(): - pass - else: - msg = "Unexpected input '{}' after start of multiline string".format(input) - fail(msg) - - elif state["value_indent"] == None: - # Establish the indent of the value - if input == "\n" and state["buffer"] == "": - state["value"] += "\n" - elif input.isspace(): - state["buffer"] += input - else: - state["value_indent"] = state["buffer"] - if not state["value_indent"].startswith(state["indent"]): - fail("Value indent of multiline string does not match indent of property") - elif len(state["value_indent"]) <= len(state["indent"]): - fail("Value indent of multiline is not greater than the indent of the property") - state["buffer"] += input - - elif input == EOF: - # Parse all lines of the string value - state["value"] = _finalize_multiline_string(state["value"], state["keep"], state["strip"]) - _set_result_value(starlark, stack, state["value"]) - - elif input == "\n": - state["buffer"] += input - if state["buffer"] == "\n": - state["value"] += "\n" - else: - state["value"] += state["buffer"][len(state["value_indent"]):] - state["buffer"] = "" - - elif not input.isspace() and not state["buffer"].startswith(state["value_indent"]): - state["value"] = _finalize_multiline_string(state["value"], state["keep"], state["strip"]) - _set_result_value(starlark, stack, state["value"]) - - stack.pop() - - # Parse the next thing - stack.append(_new_PARSE_NEXT( - indent = state["buffer"], - buffer = input, - )) - - else: - state["buffer"] += input - -def _handle_CONSUME_SPACE_FLOW(_, input, stack, starlark): - if input == EOF: - starlark["error"] = "Unexpected EOF" - return - - if input.isspace(): - pass - elif input == "[": - # We started a new inner flow sequence - stack.pop() - stack.append(_new_SEQUENCE( - index = 0, - flow = True, - )) - - _initialize_result_value(starlark, stack) - - # Consume any space following the [ - stack.append(_new_CONSUME_SPACE_FLOW()) - elif input == "{": - # We started a new inner flow map - stack.pop() - stack.append(_new_KEY( - key = "", - flow = True, - )) - - _initialize_result_value(starlark, stack) - - # Consume any space following the { - stack.append(_new_CONSUME_SPACE_FLOW()) - elif input == "]" and _in_flow_sequence(stack): - # We are at the end of a flow sequence - stack.pop() - _pop(stack, _STATE.SEQUENCE) - - # Consume any space before the next thing to parse and escape the flow if needed - stack.append(_new_CONSUME_SPACE_FLOW() if _in_flow(stack) else _new_CONSUME_SPACE( - indent = _peek(stack)["indent"] if len(stack) > 0 else "", - )) - elif input == "}" and _in_flow_map(stack): - # We are at the end of a flow map - stack.pop() - _pop(stack, _STATE.KEY) - - # Consume any space before the next thing to parse and escape the flow if needed - stack.append(_new_CONSUME_SPACE_FLOW() if _in_flow(stack) else _new_CONSUME_SPACE( - indent = _peek(stack)["indent"] if len(stack) > 0 else "", - )) - elif input == "," and _in_flow_map(stack): - # If we come across a comma but we are in the consume space state, - # then it means we just parsed a non-scalar which is already in the - # result, so just move on (I think...) - stack.pop() - - # Consume any space before the next sequence value - stack.append(_new_CONSUME_SPACE_FLOW()) - else: - # Reached the beginning of a value or key - stack.pop() - stack.append(_new_PARSE_NEXT_FLOW( - buffer = input, - )) - -def _handle_PARSE_NEXT_FLOW(state, input, stack, starlark): - if input == EOF: - starlark["error"] = "Unexpected EOF" - return - - if input == "[": - fail("Unhandled case") - elif input == "," and _in_flow_sequence(stack): - # We parsed the next value in a flow sequence - _set_result_value(starlark, stack, _parse_scalar(state["buffer"])) - stack.pop() - - sequence_flow_state = _peek(stack, _STATE.SEQUENCE) - sequence_flow_state["index"] += 1 - - # Consume any space before the next sequence value - stack.append(_new_CONSUME_SPACE_FLOW()) - elif input == "," and _in_flow_map(stack): - # We parsed the a value corresponding to the current key - _set_result_value(starlark, stack, _parse_scalar(state["buffer"])) - stack.pop() - - # Reset the key - map_flow_state = _peek(stack, _STATE.KEY) - map_flow_state["key"] = "" - - # Consume any space before the next sequence value - stack.append(_new_CONSUME_SPACE_FLOW()) - elif input == "]" and _in_flow_sequence(stack): - # We are at the end of a flow sequence - _set_result_value(starlark, stack, _parse_scalar(state["buffer"])) - stack.pop() - _pop(stack, _STATE.SEQUENCE) - - # Consume any space before the next thing to parse and escape the flow if needed - stack.append(_new_CONSUME_SPACE_FLOW() if _in_flow(stack) else _new_CONSUME_SPACE( - indent = _peek(stack)["indent"] if len(stack) > 0 else "", - )) - elif input == "}" and _in_flow_map(stack): - # We are at the end of a flow map - _set_result_value(starlark, stack, _parse_scalar(state["buffer"])) - stack.pop() - _pop(stack, _STATE.KEY) - - # Consume any space before the next thing to parse and escape the flow if needed - stack.append(_new_CONSUME_SPACE_FLOW() if _in_flow(stack) else _new_CONSUME_SPACE( - indent = _peek(stack)["indent"] if len(stack) > 0 else "", - )) - elif input.isspace() and state["buffer"].endswith(":") and _in_flow_map(stack): - # We just parsed a key - stack.pop() - _peek(stack, _STATE.KEY)["key"] = _parse_key(state["buffer"][0:-1]) - - stack.append(_new_CONSUME_SPACE_FLOW()) - else: - state["buffer"] += input - -def _new_CONSUME_SPACE(indent): - return { - "id": _STATE.CONSUME_SPACE, - "indent": indent, - } - -def _new_CONSUME_SPACE_FLOW(): - return { - "id": _STATE.CONSUME_SPACE_FLOW, - } - -def _new_PARSE_NEXT(indent, buffer): - return { - "id": _STATE.PARSE_NEXT, - "indent": indent, - "buffer": buffer, - } - -def _new_PARSE_MULTILINE_STRING(indent): - return { - "id": _STATE.PARSE_MULTILINE_STRING, - "type": "literal", # In case we support "folded" (>) later - "indent": indent, - "value_indent": None, - "buffer": "", - "value": "", - "strip": False, - "keep": False, - "consumed_first_newline": False, - } - -def _new_PARSE_NEXT_FLOW(buffer): - return { - "id": _STATE.PARSE_NEXT_FLOW, - "buffer": buffer, - } - -def _new_KEY(key, flow, complex = False, indent = None): - return { - "id": _STATE.KEY, - "key": key, - "indent": indent if not flow else None, - "flow": flow, - "complex": complex, - } - -def _new_SEQUENCE(index, flow, indent = None): - return { - "id": _STATE.SEQUENCE, - "indent": indent if not flow else None, - "index": index, - "flow": flow, - } - -def _normalize_yaml(yaml): - yaml = yaml.replace("\r", "") - return yaml - -def _initialize_result_value(starlark, stack): - "Initialize empty starlark maps or list values for the current pseudostates in the stack" - kns_states = _get_key_and_sequence_states(stack) - - if len(kns_states) == 0: - return - else: - if starlark["result"] == None: - starlark["result"] = _empty_value_for_state(kns_states[0]) - curr_result = starlark["result"] - for (i, state) in enumerate(kns_states[0:-1]): - if type(curr_result) == "dict": - curr_result = curr_result.setdefault(state["key"], _empty_value_for_state(kns_states[i + 1])) - elif type(curr_result) == "list": - if not "index" in state: - fail("Invalid yaml state under {}".format(_stack_path(stack))) - - if state["index"] >= len(curr_result): - curr_result.append(_empty_value_for_state(kns_states[i + 1])) - curr_result = curr_result[state["index"]] - else: - starlark["error"] = "Unknown result state: " + curr_result - return - -def _stack_path(stack): - p = [] - for s in stack: - p.append(s["key"]) - return p - -def _set_result_value(starlark, stack, value): - "Add a new value to the starlark result corresponding to the last pseudostate in the stack" - kns_states = _get_key_and_sequence_states(stack) - if len(kns_states) == 0: - starlark["result"] = value - else: - curr_result = starlark["result"] - for state in kns_states[0:-1]: - if type(curr_result) == "dict": - curr_result = curr_result[state["key"]] - else: - curr_result = curr_result[state["index"]] - if type(curr_result) == "dict": - curr_result[kns_states[-1]["key"]] = value - elif type(curr_result) == "list": - curr_result.append(value) - else: - starlark["error"] = "Unknown result state: " + curr_result + " for value " + value - return - -def _empty_value_for_state(state): - if state["id"] == _STATE.KEY: - return {} - elif state["id"] == _STATE.SEQUENCE: - return [] - else: - msg = "State {} has no empty type".format(state["id"]) - fail(msg) - -def _peek(stack, expected = False): - top = stack[-1] - if expected and top and top["id"] != expected: - fail("Expected state {} but got {}".format(expected, top["id"])) - - return top - -def _parse_scalar(value): - value = value.strip() - if _is_int(value): - return int(value) - elif _is_float(value): - return float(value) - elif _is_bool(value): - return _to_bool(value) - elif value.startswith("'"): - return value.strip("'") - else: - return value.strip("\"") - -def _finalize_multiline_string(value, keep, strip): - if keep and strip: - fail("Error: a multiline string cannot both keep and strip trailing newlines. This is probably a bug.") - if not keep and not strip: - value = value.rstrip(" \t\n") + "\n" - elif strip: - value = value.rstrip(" \t\n") - - return value - -def _pop(stack, *types): - for t in types: - if _peek(stack)["id"] != t: - fail("Expected state {} but found {}".format(t, _peek(stack)["id"])) - stack.pop() - -def _is_float(value): - return value.replace(".", "", 1).isdigit() - -def _is_int(value): - return value.isdigit() - -def _is_bool(value): - return value == "true" or value == "false" - -def _to_bool(value): - if value == "true": - return True - elif value == "false": - return False - msg = "Cannot convert scalar {} to a starlark boolean".format(value) - fail(msg) - -def _parse_key(key): - if key.startswith("'"): - return key.strip("'") - elif key.startswith("\""): - return key.strip("\"") - return key - -def _get_key_and_sequence_states(stack): - return [state for state in stack if state["id"] in [_STATE.KEY, _STATE.SEQUENCE]] - -def _in_flow(stack): - kns_states = _get_key_and_sequence_states(stack) - return len(kns_states) > 0 and kns_states[-1]["id"] in [_STATE.SEQUENCE, _STATE.KEY] and kns_states[-1]["flow"] - -def _in_flow_sequence(stack): - kns_states = _get_key_and_sequence_states(stack) - return len(kns_states) > 0 and kns_states[-1]["id"] == _STATE.SEQUENCE and kns_states[-1]["flow"] - -def _in_flow_map(stack): - kns_states = _get_key_and_sequence_states(stack) - return len(kns_states) > 0 and kns_states[-1]["id"] == _STATE.KEY and kns_states[-1]["flow"] - -def _pop_higher_indented_states(stack, indent): - remove = [] - for state in stack: - if state["id"] in [_STATE.KEY, _STATE.SEQUENCE] and len(state["indent"]) > len(indent): - remove.append(state) - for state in remove: - stack.remove(state)