Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

YAML-like support #17

Open
aparcar opened this issue Jul 8, 2021 · 8 comments
Open

YAML-like support #17

aparcar opened this issue Jul 8, 2021 · 8 comments

Comments

@aparcar
Copy link
Contributor

aparcar commented Jul 8, 2021

Would it be possible to allow input via YAML-like files which follow the YAML-structure but but don't necessarily allow all the complexity of it? Specifically anchor and inheritance seem a bit complex, but allowing human input via YAML would effectively allow to replace UCI config files.

@jow-
Copy link
Owner

jow- commented Jul 9, 2021

Not sure I completely follow your reasoning here but it shouldn't be a problem to add bindings to libyaml to expose its functionality to ucode. What exactly do you have in mind?

@aparcar
Copy link
Contributor Author

aparcar commented Jul 9, 2021

Long term parts of UCI could be removed by ucode so ash/lua is less of a requirement to generate config files. Hand crafted JSON is a bit of a pain so YAML would be a nice input format.

@aparcar
Copy link
Contributor Author

aparcar commented Oct 16, 2021

I looked briefly at cloud-init because it currently lacks OpenWrt support. As a result common cloud providers (i.e. Hetzner) don't offer OpenWrt instances. I'm wondering if ucode with YAML support could digest those cloud files.

I'm not thinking of full support since they offer all kinds of special modules but using ucode to configure SSH and network and make it therefore cloud-init ready could be feasible.

@jow- how complex is it to add YAML bindings?

@jow-
Copy link
Owner

jow- commented Oct 22, 2021

Can you sketch a hypothetical api for the yaml binding and suggest a library to bind?

I'd rather not add a super low level callback driven parser api but something high level, like
yaml.parse(input_str, { optional_dict_to_override_sane_defaults_with_exotic_settings })
and
yaml.tostring(ucode_dict_value, { optional_dict_to_override_default_formatting_options })

@aparcar
Copy link
Contributor Author

aparcar commented Oct 22, 2021

your yaml example seems fine, I think that would work just fine
yaml version1.1 seem to be the default at most places, so I'd use https://pyyaml.org/wiki/LibYAML
on the other hand libfyaml seems more future proof https://github.com/pantoniou/libfyaml with the upcoming YAML 1.3. However since the last point release (1.2.2) took 12 years, maybe we're safe for another century by using LibYAML.

@aparcar
Copy link
Contributor Author

aparcar commented Mar 18, 2022

Friendly ping, this would still be a great feature to have

@pktpls
Copy link
Contributor

pktpls commented Mar 15, 2023

Maybe a simple-yet-useful compromise would be to work only with the JSON-compatible subset of YAML 1.2+? I'm not super familiar with YAML libraries though.

Built-in YAML support would be very useful for its human friendliness.

@pktpls
Copy link
Contributor

pktpls commented Dec 21, 2023

So I've started giving this a shot and libfyaml seems very convenient to work with. I have a simple yaml() parsing function working and wired up. I'll package libfyaml over the next few days.

#include <libfyaml.h>

#include "ucode/module.h"
#include "ucode/platform.h"

static uc_value_t *
ucv_from_fyaml(uc_vm_t *vm, struct fy_node *fyn)
{
  uc_value_t *uv;
  void *iter;
  int len, i = 0;
  struct fy_node *fysn;
  struct fy_node_pair *fynp;

  switch (fy_node_get_type(fyn)) {
  case FYNT_SCALAR:
    return ucv_string_new_length(fy_node_get_scalar0(fyn), fy_node_get_scalar_length(fyn));
  case FYNT_MAPPING:
    uv = ucv_object_new(vm);
    iter = NULL;
    len = fy_node_mapping_item_count(fyn);
    while (i < len) {
      fynp = fy_node_mapping_iterate(fyn, &iter);
      ucv_object_add(uv, fy_node_get_scalar0(fy_node_pair_key(fynp)),
                     ucv_from_fyaml(vm, fy_node_pair_value(fynp)));
      i++;
    }
    return uv;
  case FYNT_SEQUENCE:
    uv = ucv_array_new(vm);
    iter = NULL;
    len = fy_node_sequence_item_count(fyn);
    while (i < len) {
      fysn = fy_node_sequence_iterate(fyn, &iter);
      ucv_array_push(uv, ucv_from_fyaml(vm, fysn));
      i++;
    }
    return uv;
  default:
    uc_vm_raise_exception(vm, EXCEPTION_TYPE,
                          "Passed YAML node is neither a mapping nor a scalar value");
  }

  return NULL;
}

static uc_value_t *
uc_yaml(uc_vm_t *vm, size_t nargs)
{
  uc_value_t *rv = NULL, *src = uc_fn_arg(0);
  struct fy_document *fyd;
  struct fy_node *fyn;

  switch (ucv_type(src)) {
  case UC_STRING:
    fyd = fy_document_build_from_string(NULL, ucv_string_get(src),
                                        ucv_string_length(src));
    if (!fyd)
      uc_vm_raise_exception(vm, EXCEPTION_SYNTAX,
                            "Failed to parse YAML document");
    break;
  default:
    uc_vm_raise_exception(vm, EXCEPTION_TYPE,
                          "Passed value is not a string");
  }

  if (!fyd)
    goto out;

  fyn = fy_document_root(fyd);
  rv = ucv_from_fyaml(vm, fyn);

out:
  if (fyd)
    fy_document_destroy(fyd);

  return rv;
}

static const uc_function_list_t global_fns[] = {
  { "yaml", uc_yaml },
};

void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
{
  uc_function_list_register(scope, global_fns);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants