{"id":16759526,"url":"https://github.com/amogorkon/stay","last_synced_at":"2026-05-08T09:35:04.548Z","repository":{"id":57471299,"uuid":"158734946","full_name":"amogorkon/stay","owner":"amogorkon","description":"Simple, even Trivial Alternative to Yaml","archived":false,"fork":false,"pushed_at":"2024-05-27T19:16:01.000Z","size":109,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-02-20T23:26:44.041Z","etag":null,"topics":["alternative","json","simple","toml","yaml"],"latest_commit_sha":null,"homepage":null,"language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amogorkon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-22T17:59:52.000Z","updated_at":"2024-05-27T19:16:04.000Z","dependencies_parsed_at":"2025-03-18T07:22:38.539Z","dependency_job_id":"991a98ba-bd2c-44f4-a434-d3fc2c24e37f","html_url":"https://github.com/amogorkon/stay","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/amogorkon/stay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amogorkon%2Fstay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amogorkon%2Fstay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amogorkon%2Fstay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amogorkon%2Fstay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amogorkon","download_url":"https://codeload.github.com/amogorkon/stay/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amogorkon%2Fstay/sbom","scorecard":{"id":190231,"data":{"date":"2025-08-11","repo":{"name":"github.com/amogorkon/stay","commit":"5c3843f7a6a1344c2de40141024af4cffd360fb7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-16T20:31:42.342Z","repository_id":57471299,"created_at":"2025-08-16T20:31:42.342Z","updated_at":"2025-08-16T20:31:42.342Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32775110,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"ssl_error","status_checked_at":"2026-05-08T08:22:45.650Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["alternative","json","simple","toml","yaml"],"created_at":"2024-10-13T04:08:21.979Z","updated_at":"2026-05-08T09:35:04.507Z","avatar_url":"https://github.com/amogorkon.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# STAY - Simple, even Trivial Alternative to Yaml\n\n## Purpose\n\n### Background\nThere are several different types of communication: communication between humans, between humans and computers and between computers. The communication between machines can also be distinguished between one that is easily readable (but not writable) by humans and one that is not. In the area of communication between machines that is also readable by humans, there is no alternative to JSON - and if readability is of no concern, there is MessagePack, which is also an excellent choice.\nFor the communication between humans on the other hand, there is pseudo-code and all kinds of ad-hoc micro-language that - hopefully - is understood by the recipient.\nFinally, there is the area of communication between humans and machines. In this area exist all kinds of languages that were designed for various purposes like configuration, all programming languages, specifications for mini-languages like regex or descriptive languages like html, latex, SQL and the like. The semantic web adds yet another aspect to this collection, with JSON-LD as possible solution. Here, it is not enough to simply put down data but it is also required to specify how this data must be interpreted - which necessitates more complex notation.\n\n### Why STAY?\nThere already are several languages that address the area of communication between humans and machines for configuration, for instance YAML, TOML or INI. However, while YAML and the others may be readable by humans, they all suffer from various false premises.\nOne such premise is that \"data should be self-documenting\", meaning that it should be obvious if something is a string, number, truth value or somesuch. However, for a human reader there is very little point for this hint since the type normally is obvious merely by looking at the document. On the other hand, the program parsing the document MUST ALWAYS validate the data types - as only a simple typo could crash the program otherwise. So, no point in writing all those quotation marks, is there?\nAnother problem with various markup languages is that the basic language already allows too much, like unrestricted execution of statements within the parser by eval(). It should be the other way round: the basic language should do nothing but specify datastructures and provide a mechanism to specify behaviours. The receiving end then can decide how to handle the data as they see fit.\n\nWith pydantic there is a simple, yet powerful framework to validate and convert values into the desired format when the content is read. This means there simply is no point in implicite type-hinting within the document, adding unnecessary complexity and visual clutter, not to mention the annoying manual escaping of special characters that does nothing for usability.\n\nSTAY removes all the overhead and boils syntax down to the bare minimum, which can easily be parsed into pydantic or some other type converter/validator to get whatever data type is specified.\n\nAll of computer science revolves on an abstract level around three types of datastructures: lists, hashtables and graphs. It is STAYs objective to make it possible to mix and match these high-level datastructures without making simple things complicated. \n\n## Syntax\nSTAY is line-based. The file is read line by line, translated into a generator of dictionaries.\n\n### Documents\nA document represents a basic dictionary. Documents are the basic units that are yielded from the STAY parser. In text form, a document is seperated from the next by a line that starts with **===** or **---**. For instance, in a configuration this allows defaults on top of the file, user-defined values below, which overwrite the default. The seperator may also be used as a headline like **=== this is the next chapter**.\n\n### Simple Values\nIn a document, simple key/value pairs are written like **key: value** on a single line. Leading and trailing whitespace is stripped. If whitespace is meaningful around key or value, you can signify that with a ***\"*** on either or both sides, directly adjacent of the ***:*** and its partner around the key/value.\n\n    \"  foo   \":\"    bar     \"\n\n### Hierachy\nAs with JSON or YAML, dictionaries may be nested. Levels are indicated by indentation of tabs or spaces (4 is default).\n    \n    a:\n        b:\n            c:3\n        foo: 4\n    bar: 6\n    \n### Simple list\nThe arguments of a bash command is a simple list of arguments, which you can easily write as **key: \\[1 2 3 asdf \"foo bar\"\\]**.\n\n### Comments\nComments are line-based. Any line that starts with # is ignored. Additionally, a block can be commented out by putting ### above and below of the block.\n\n### Long values = text blocks\nAnything that involves linebreak (\\n) characters would need to be manually escaped, but there is a simple solution to that: long values. A key: with **:::** instead of : will start a block of long value, where everything is escaped until a single line starting with triple colons (if inside the block, it can be manually escaped by \\\\:::, which is the only exception, everything else is parsed as-is).\n \n\tkey:::\n\tlong\n\tvalue\n\t:::\n\nWithin a block, you can ignore any outside indentation level. \nThis is useful to store long text passages that can be copy\u0026pasted with no modification. Please note that the end-block signifier also acts as comment, allowing to mirror the block header or add meta-data that may be used in a directive. Mirroring the block header hardly seems useful in a small example (which is why it isn't enforced by code), however if the block is very large it can be a big help since it is no problem to search for \":::key\".\n \n### List blocks\nSimilarly, you can make a list of strings where each line is an item (spaces, newlines and tabs at beginning and end are removed!):\n\n\tkey:::[\n\ta\n\tb\n\tc\n\t]\n\nHowever, unlike long values, long lists also work with the list syntax, so you can easily write a matrix like this:\n\n\tmatrix:::[\n\t[1 2 3]\n\t[4 5 6]\n\t[7 8 9]\n\t]\n    \n### Graph blocks\nFor graph blocks braces are used like in the DOT language.\nHowever, unlike other data structures, graph blocks only exist in STAY as abstract syntax that require a directive for implementation. This is because there are many different ways a graph can be represented and many different libraries exist for this purpose.\nWhile this seems like a backdraw at first, it also leaves the freedom to use {} blocks for other purposes, like directives to implement list of dicts or more exotic datastructures.\n\nA graph block works as a block with { and }. The end } signifier allows the same annotation as all the other blocks, so that\n\n\t\u003cDOTgraph\u003e\n    graph name:::{\n    a -\u003e b -\u003e c\n    }:::graph\n    \nis valid syntax with the \":::graph\" as an optional comment. While it may seem strange to have \"blank\" syntax with no specific meaning, I think it will be much more useful to have one explicit wildcase than to have to change the behaviour of established syntax case by case, which is more work and more confusing for the user.\n\n## Modifying behaviour\nWhile all STAY documents MUST follow the language specification for interoperability, a document also can include statements that a parser MAY follow, but has no obligation to. In the contrary, it is advised to start with a bare parser and only add functionality that is required to properly handle a given document. Since it is possible to change all of the internal machinery of the parser and adding arbitrary functionality, there is a considerable risk of a security breach if unnecessary functionality is added and exploited in a document by an untrusted source. All additional functionality that modifies parser behaviour are called 'directives'.\n\n### Commands\nThe simplest way to let the parser do additional work besides turning a source of strings into a datastructure (of strings) is by issuing simple line commands within the document.\nThis is done by the following syntax:\n\n\t@ cmd args1 args2\n\nwhich only has an effect if \"cmd\" has been passed into the parser as possible command, for instance like\n\n\tdecode = Decoder(commands={\"include\": drv.include})\n\nmaking the following a valid expression:\n\n\t@ include include-test\n\ninserting the content of the file \"include-test\" at the line currently being parsed.\nBe aware that if the exact same line is part of the file being inserted, this will result in a recursion and possibly an infinite loop!\nHowever, if include has not been passed into the Parser as valid command, only an error may be logged while the rest of the content of the file is still valid for all intents and purposes. \nMultiple commands also can be concatenated (piped) with the results of the first passed into the next as input:\n\n\t@ cmd1 args11 args12 @ cmd2 args21 args 22\n\nYou also can redefine the functions the parser uses by replacing them in the cases dict, but the recommended way\nfor this (for instance for graph parsing) is to pass in a custom_cases dict into the Decoder class on init, which\noverwrites the default cases for this instance.\n\n### Directives\nWhile commands only operate on single lines, it is possible to define **directives** (or environments or contexts, however you want to call them), which are functions the parser applies to everything it operates on beyond the activation. The simplest form of directive is a global one on a single line, after having passed in the function to the Decoder class:\n\n\tdecode = Decoder(line_directives={\"comments\": drv.inline_comments)})\n\nwhich enables the user to activate the function within the document like so\n\n\t\u003ccomments\u003e\n\tkey: value # comment for this curious new value!\n\nthere is no need to deactivate the comment manually, but it simply can be done by:\n\n\t\u003c/comments\u003e\n\tkey: value # this is now part of the value again\n\nArguments can be given just like commands:\n\n\t\u003cstep func arg1 arg2\u003e\n\nIt is important to note that the \u003c \u003e are mandatory to clearly identify the part of the document that belongs to the specification of the directive/environment.\n\n\t\u003creplace\n\ta: b\n\tb: c\n\t\u003e\n\nwhich, if \"replace\" is defined accordingly, could instruct the parser to replace all \"a\"s with \"b\"s and so on, from this point on.\n\nDepending on whether the function has been defined as line, key, value or struct directive, the function only gets access to this particular data.\nIf the same function is passed as key and value directive in the definition of the Parser, it would be called for both steps - key and value construction, replacing letters indiscrimantly.\nHowever, if different functions were passed under the same \"replace\" tag to the Parser in the beginning as key and value directives, special cases for either keys or values can be handled more elegantly.\n\nFor instance, you can have comments only for keys or for values. For this, you start similar like above:\n\n\tdecode = Decoder(key_directives={\"comments\": drv.inline_comments})\n\nwhich is basically the same code, just \"line_directive\" replaced by \"key_directive\". Now you can have a document like so\n\n\t\u003ccomments\u003e\n\tstrange name for a key # explanation : value\n\nAnd even both, key and value:\n\n\tdecode = Decoder(key_directives={\"comments\": drv.inline_comments},\n\t\t\t\t\tvalue_directives={\"comments\": drv.inline_comments})\n\nwhich allows the following:\n\n\t\u003ccomments\u003e\n\tstrange name for a key # explanation : strange value # explanation\n\nAnother useful directive can be 'context', which works pretty much like in JSON-LD:\n\n\timport stay.directives as drv\n\tload = Decoder(key_directives={\"context\"=drv.context})\n\ts = \"\"\"\n\t\u003ccontext\n\tg: http://google.de/\n\td: g:test\n\t\u003e\n\td: hello\n\t\"\"\"\n\tload(s)\n\nwhich would result in the dictionary {\"http://google.de/test\": \"hello\"}\n\nContext creates an internal dictionary that first replaces leading '{str}:' of the values by previous occurance of the key, then replaces all keys in the actual document by the values in the context.\nThis is useful to use shorthand in the document while the actual key can be an arbitrarily complex url or other composite.\n\nIf there is any need for self-documented datatypes within a document, it can easily be done with a directive like inline_spec which maps arbitrary specifiers to conversion functions.\nThis can be extended to any number of datatypes, including numpy or ctypes.\n\n\n### Meta-Directives\nFinally, it is possible to check and alter directives and their arguments before they are executed.\nThis can be done for instance to ensure backwards-compatibility if changes were made to directives or to check for security issues before a directive is executed.\n\n\n\n\n### Known Limitations\nWith the current implementation it isn't possible to make arbitrary lists of lists and lists of dicts/other structures. It is also not possible to use a list/tuple as key.\n\n\n## First steps\nFirst you need to build a decoder instance - functions to take care of special directives need to be passed in explicitely, which should be no issue to begin with. The instance can be called directly to \n\t\n\tfrom stay import Decoder\n\t\n\tload = Decoder()\n\n\twith open(somefilename) as file:\n\t\tlist(load(file))\n\n\nThis can be used to read a STAY file, while the Encoder can be used to convert an iterator of dictionaries into a STAY generator of documents.\nExamples can be found in the Showcase Jupyter Notebook (in /docs) or by looking at the tests.\n\n***That's it - enjoy!***\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famogorkon%2Fstay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famogorkon%2Fstay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famogorkon%2Fstay/lists"}