{"id":16701465,"url":"https://github.com/ferd/hubble","last_synced_at":"2025-04-10T04:11:17.578Z","repository":{"id":6605252,"uuid":"7848514","full_name":"ferd/hubble","owner":"ferd","description":"create, read, and update deep Erlang data structures, accessible through explicit paths.","archived":false,"fork":false,"pushed_at":"2013-01-27T05:33:25.000Z","size":99,"stargazers_count":18,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-24T05:27:06.162Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ferd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-01-27T05:18:31.000Z","updated_at":"2022-01-01T14:29:10.000Z","dependencies_parsed_at":"2022-09-09T02:10:50.032Z","dependency_job_id":null,"html_url":"https://github.com/ferd/hubble","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ferd%2Fhubble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ferd%2Fhubble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ferd%2Fhubble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ferd%2Fhubble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ferd","download_url":"https://codeload.github.com/ferd/hubble/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248154990,"owners_count":21056543,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-12T18:44:11.170Z","updated_at":"2025-04-10T04:11:17.541Z","avatar_url":"https://github.com/ferd.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hubble #\n\nHubble is a prototype library allowing to create, read, and update deep\nErlang data structures in a manner that would be somewhat similar to using dot\nnotation in languages with their roots closer to OO.\n\n### What's a Hubble? ###\n\nIt's a terrible pun on wanting a library that lets you zoom is in your\ndata structure's deep space.\n\n`Hubble` is the name given to whatever the library produces as a data structure.\n\nAt this time, it's equivalent to a recursive tuple-list of the form:\n\n    [{Key1, [{Key2, [{Key3, Val1}, {Key4, Val2}]},\n             {KeyA, Val3}]},\n     {KeyB, Val4}]\n\nWhere elements are accessible by specifying how to reach them (see *Using\nthe library*).\n\n## Building ##\n\n`$ erl -make`\n\nThe library should also be inherently compatible with rebar.\n\n## Running Tests ##\n\n`$ erl -make \u0026\u0026 ct_run -pa ebin -suite test/* -logdir logs/`\n\nThe suites obviously use Common Test.\n\n## Using the library ##\n### Sane Mode ###\n\nSane mode is a more verbose mode that uses no magic. It is the recommended mode\nfor the library.\n\nThe 4 basic operations are:\n\n- `hubble:new() -\u003e Hubble`: initiates an empty data structure to be filled\n  later. The data structure is called a `Hubble`.\n- `hubble:put(Hubble, [Path,To,Item], Value)`: this uses a given `Hubble`\n  data structure, and creates every intermediary path (if they do not exist) up\n  until the last one, where `Value` is attributed to it.\n- `hubble:get(Hubble, [Path,To,Item])`\": this will fetch a value currently\n  stored within a given path.\n- `hubble:up(Hubble, [Path,To,Item], NewVal)`: Will update the value in a given\n  Path to a new one, if and only if the path exists. Otherwise, the operation\n  errors out.\n\nTwo shortcut operations for mass updating are provided. They're not more\nefficient, just nicer to look at:\n\n- `hubble:puts(Hubble, [{[path,1], my_value},\n                        {[somewhat, \"deeper\",{path}], other_val}]).`\n- `hubble:ups(Hubble, [{[path,1], my_value},\n                       {[somewhat, \"deeper\",{path}], other_val}]).`\n\nThe first one does multiple `put` operations, while the latter does multiple\n`up` operations.\n\n### Sane example ###\n\nLet's create a fictional RPG character with given statistics:\n\n    1\u003e Stats = hubble:puts(hubble:new(),\n    1\u003e                     [{[strength], 12},\n    1\u003e                      {[wisdom], 9},\n    1\u003e                      {[luck], 10},\n    1\u003e                      {[dexterity], 5}]).\n    ...\n\nAnd let's make a baseline character bio that integrates the stats:\n\n    2\u003e Char = hubble:puts(hubble:new(),\n    2\u003e                    [{[name], \u003c\u003c\"karl\"\u003e\u003e},\n    2\u003e                     {[bio, age], 219},\n    2\u003e                     {[bio, hometown], \u003c\u003c\"The Internet\"\u003e\u003e},\n    2\u003e                     {[bio, parent, father], undefined},\n    2\u003e                     {[bio, parent, mother], \u003c\u003c\"A Unicorn\"\u003e\u003e},\n    2\u003e                     {[stats], Stats}]).\n    ...\n\nOh yeah and let's not forget its level!\n\n    3\u003e Char2 = hubble:up(Char, [level], 1). \n        ** exception error: badpath\n         in function  hubble:up/3 (src/hubble.erl, line 41)\n    4\u003e Char2 = hubble:put(Char, [level], 1).\n        ...\n\nWe can only update fields that exist! Let's say for instance our friend karl finds\nout his father was a man named Randalf. Let's update his profile:\n\n    5\u003e Char3 = hubble:up(Char2, [bio,parent,father], \u003c\u003c\"Randalf\"\u003e\u003e).\n    ...\n\nThat worked. We can fetch back any information by using a path, or partial one too:\n\n    6\u003e hubble:get(Char3, [stats,wisdom]).\n    9\n    7\u003e hubble:get(Char3, [bio,parent,mother]).\n    \u003c\u003c\"A Unicorn\"\u003e\u003e\n    8\u003e hubble:get(hubble:get(Char3, [bio,parent]), [father]).\n    \u003c\u003c\"Randalf\"\u003e\u003e\n\nAnd that's about it.\n\n### insane mode ###\n\nThis is a mode that uses a parse transform to introduce syntactic sugar\nfor people who really can't tolerate using all these damn tokens to do\ntheir thing.\n\nThe insane mode is enabled by using a parse transform:\n\n    -compile({parse_transform, hubble_trans}).\n\nThis will automatically allow you to use hubble functions locally with the following\nsyntax:\n\n    Hubble = new()\n\n    put(Hubble, part,of,a,path, Val)\n    up(Hubble, part,of,a,path, NewVal)\n\n    get(Hubble, part, of, a, path)\n\n    puts(Hubble, [part,of,a,path, Val],\n                 [another,path, OtherVal]).\n    ups(Hubble, [part,of,a,path, Val],\n                [another,path, OtherVal]).\n\nThe functions get a variable number of arguments, which gets translated back into\nthe long 'sane' form. This means the example above could now be written as:\n\n    demo() -\u003e\n        Stats = puts(new(),\n                     [strength, 12],\n                     [wisdom, 9],\n                     [luck, 10],\n                     [dexterity, 5]),\n        Char = puts(new(),\n                    [name, \u003c\u003c\"karl\"\u003e\u003e],\n                    [bio, age, 219],\n                    [bio, hometown, \u003c\u003c\"The Internet\"\u003e\u003e],\n                    [bio, parent, father, undefined],\n                    [bio, parent, mother, \u003c\u003c\"A Unicorn\"\u003e\u003e],\n                    [stats, Stats]),\n        Char2 = put(Char, level, 1),\n        Char3 = up(Char2, bio,parent,father, \u003c\u003c\"Randalf\"\u003e\u003e),\n        \n        9 = get(Char3, stats, wisdom),\n        \u003c\u003c\"A Unicorn\"\u003e\u003e = get(Char3, bio, parent, mother),\n        \u003c\u003c\"Randalf\"\u003e\u003e = get(get(Char3, bio, parent), father).\n\nNote that functions prefixed with `hubble:` (a fully qualified call) will *not* be\nparse-transformed. This means you could make a mixed usage of both forms of\nfunctions if you felt like it, although I have no idea why you would do that (but\nhey, it's the insane mode after all!).\n\nOh yeah, one more precaution. The `get/1` function and `put/2` function can *never* happen naturally with the `hubble` app. These are not parse-transformed back to\nanything -- they are the auto-imported functions `erlang:get/1` and `erlang:put/2`,\nused to access the process dictionary. To avoid confusion and highlight errors,\nconsider adding:\n\n    -no_auto_import([get/1, put/2]).\n\nTo the modules that use the parse transforms, although I firmly believe you should\nsuffer for your bad decisions of using them.\n\n## Future Development ##\n\nI would like it if it could be possible to support specifying the types of the\nunderlying Hubble data structure for parse transforms and regular functions.\nBasically, I think it would be neat if the user could specify something like:\n\n    -hubble(dict).\n\nIn conjunction with the parse transform to allow specifying how data is stored and\nretrieved (not sure if I'd like it to have impact on the fully qualified calls too).\n\nAlternatively, using this form, it could be interesting to allow custom\nimplementation of functions as a very weird callback module:\n\n    -hubble(puts, fun ?MODULE:my_puts/3).\n    -hubble(ups, fun ?MODULE:my_ups/3).\n\nSo someone could overload one or all of the functions to either support custom\ndata structures, or temporarily allow to patch functions the user thinks are\nbroken.\n\nThis would need explicit support from the library.\n\n## Why the hell did you write that ##\n\nI had a free day while sitting on a bus and on a plane. People were arguing on\nthe Erlang mailing list about a library a bit like that so I decided to write\nit just to see, and flex my parse transforming muscles.\n\n## Should I use this in production? ##\n\nWell given the underlying representation is a list, and that `lists:key*` functions\nare used to navigate around a Hubble, you have to decide if an O(n) behaviour is\nsuitable for you.\n\nUnder the current implementation, the first elements added to the hubble are the\nfirst ones to be found when looking data up. This means if you have some paths you\nare likely to take more often than others, it can be possible to set them in stone\nwhen first creating the structure to get generally very fast reads and updates.\n\nOtherwise, you may judge that simple navigation is worth the (potential) performance\nhit, although a benchmark of your own should show how things go.\n\nI would not recommend using the parse transform version of the module in production\nbecause I bet people who will look at your code will not like that you're using\nauto-imported functions with variable arities, something that could easy make you\nbe seen as a heretic. I will personally offer no support to code using that form\nunless I find the free time to do it. I'll also offer no commitment to backwards\ncompatibility of the parse transformed code on that one. Maybe I'll offer sed\ncommand to help port it. Eh.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fferd%2Fhubble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fferd%2Fhubble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fferd%2Fhubble/lists"}