From dec5d13cbfa1d13e59ca3f7dadf763aec6c635ac Mon Sep 17 00:00:00 2001 From: TehBrian <32250137+TehBrian@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:11:09 -0500 Subject: [PATCH] [hocon/en] Add HOCON (#4149) * Add HOCON * Reorganize include section, cleanup --- hocon.html.markdown | 574 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 574 insertions(+) create mode 100644 hocon.html.markdown diff --git a/hocon.html.markdown b/hocon.html.markdown new file mode 100644 index 00000000..67ea30a2 --- /dev/null +++ b/hocon.html.markdown @@ -0,0 +1,574 @@ +--- +language: hocon +filename: learnhocon.conf +contributors: +- [TehBrian, 'https://tehbrian.xyz'] +--- + +Human-Optimized Configuration Object Notation, or HOCON, is a configuration and +data serialization format designed to be easily editable by humans. + +It's a superset of JSON, meaning that any valid JSON is valid HOCON, but it +differs in being much less pedantic and opinionated. With its flexible yet +easily determinable syntax, resulting configuration files are often much less +noisy than some other formats. + +Additionally, its support for comments makes it much better suited for +user-facing configurations than JSON. + +```hocon +// Comments can either look like this, +# or they can look like this. +// Anything after // or # is a comment. + +################## +### THE BASICS ### +################## + +# Everything in HOCON is either a key, a value, or a separator. +# : and = are separators. They separate the key from the value. +key: value +another_key = another_value + +# You can use either separator with or without whitespace on either side. +colon1:value +colon2: value +colon3 : value +equals1=value +equals2= value +equals3 = value +# As you'll see, HOCON is very nonrestrictive regarding its syntax style. + +# HOCON also isn't opinionated on how keys look. +THIS_IS_A_VALID_KEY: value +this-is-also-a-valid-key: value +keys can have spaces too: value +or even numbers like 12345: value +"you can even quote keys if you'd like!": value + +# A key, followed by any separator, and then finally a value, is called a field. +this_entire_line_is: a field + +################### +### VALUE TYPES ### +################### + +# The types that a value can be are string, number, object, array, boolean, and +# null. Every value type except for array and object are called simple values. + +## SIMPLE VALUES ## + +quoted_string: "I like quoting my strings." +unquoted_string: I don't like quoting my strings. +# Special characters that cannot be used in unquoted strings are: +# $ " { } [ ] : = , + # ` ^ ? ! @ * & +# Unquoted strings do not support any kind of escaping. If using one of those +# special characters is desired, use a quoted string. +multi-line_string: """ + This entire thing is a string! + One giant, multi-line string. + You can put 'single' and "double" quotes without it being invalid. +""" + +number: 123 +negative: -123 +fraction: 3.1415926536 +scientific_notation: 1.2e6 // same as 1.2 * (10^6) + +boolean: true # or false +empty: null + +## ARRAYS ## + +# Arrays hold lists of values. +# Values in arrays can be separated with commas.. +array: [ 1, 2, 3, 4, 5 ] +fibonacci: [1,1,2,3,5,8,13] +multiples_of_5: [5, 10, 15, 20,] # Notice the trailing comma. That's okay here. +# or newlines.. +friends: [ + "Brian" + "Sophie" + "Maya" + "Sabina" +] +# or both! +ingredients: [ + "Egg", + "Sugar", + "Oil", + "Flour", # Notice the trailing comma. That's okay here too. +] +# Once again, HOCON has a very loose syntax. Use whichever style you prefer. +no newline before or after bracket: ["This" + "is" + "an" + "array!"] + +# Just like any other value, arrays can hold other arrays. +array in array: [ [1, 2, 3], ["a", "b", "c"] ] +array in array in array: [ [ [1, 2], [8, 9] ], [ ["a", "b" ], ["y", "z"] ] ] + +## OBJECTS ## + +# Objects hold fields. +# Just like arrays, fields in objects can be separated with commas.. +object: { key: value, another_key: another_value } +server_connection: {ip: "127.0.0.1", port: 80} +first: {letter: a, number: 1,} # Notice the trailing comma. +# or newlines.. +power_grid: { + max_capacity: 15000 + current_power: 1200 +} +# or both! +food_colors: { + carrot: orange, + pear: green, + apple: red, + plum: purple, + banana: yellow, # Trailing commas are okay here too! +} + +# Arrays can hold objects just like any other value type. +coworkers: [ + { + name: Jeff + age: 27 + }, + { + name: Henry + age: 35 + }, + { + name: Timmy + age: 12 + } +] + +# The field separator may be omitted if the key is followed by { +no_separator { + key: value + speed_of_light: very fast + ten: 10 + + # Objects can go inside other objects just like any other value. + another_object { + twenty: 20 + speed_of_sound: also pretty fast + } +} + +# In fact, the entirety of any HOCON document is an actually just an object. +# That object is called the root object. The only difference between it and any +# other object is that the curly brackets at the top and bottom of the document +# may be omitted. + +# This means that HOCON documents can be formatted in the same way that +# regular objects can be formatted, including separating fields with commas +# rather than with newlines. + +# Additionally, while the entirety of a HOCON document can be and is usually an +# object, it can also be an array. If it is an array, the opening and closing +# brackets at the top and bottom of the document must be explicitly written. + +###################### +### DUPLICATE KEYS ### +###################### + +is_happy: false +# If there is a duplicate key, the new value overrides the previous value. +is_happy: true +online_users: [Jacob, Mike] +# Same with arrays. +online_users: [Jacob, Mike, Henry] + +# For objects, it's a bit different. +my_car: { + color: blue + speed: 9001 + passengers: null + + engine: { + running: true + temperature: 137 + } +} +# If there is a duplicate key and both values are objects, +# then the objects are merged. +my_car: { + // These fields are added to the old, previous object. + nickname: "My Favorite Car" + type: 2-door sedan + + // Since the value of this duplicate key is NOT an object, + // it simply overrides the previous value. + speed: 60 + // Same with arrays. They override, not merge. + passengers: ["Nate", "Ty"] + + // This object is recursively merged with the other object. + engine: { + // These two fields are added to the previous object. + type: gas + oil_level: 10 + // This field overrides the previous value. + temperature: 179 + } +} + +# Object merging is done two at a time. That is to say, the first two objects +# merge into one, then that object merges with the next object, and so on. + +# Because of this, if you set a field with an object value to a non-object value +# and then back to an object value, the new object will completely override any +# previous value. + +// Null, a non-object value, completely overrides the object value. +my_car: null + +// Then, this object completely overrides null. +my_car: { + nickname: "My New Car" + type: 4-door minivan + color: gray + speed: 90 + passengers: ["Ayden", "Liz"] +} + +########################### +### VALUE CONCATENATION ### +########################### + +## SIMPLE VALUE CONCATENATION ## + +# Simple values (all value types except objects and arrays) separated by +# whitespace are concatenated into a single string. The whitespace between +# values is preserved. +number_concatenation: 1 2 3 12.5 -3 2e5 // same as: "1 2 3 12.5 -3 2e5" +boolean_concat: true false true // "true false true" +null_concat: null null null // "null null null" +mixed_concat: 1 true null // "1 true null" + +# String value concatenation can appear anywhere that a quoted string can. +number_concat_in_array: [1 2, 3 4, 5 6] // same as: ["1 2", "3 4", "5 6"] + +# In fact, unquoted strings are actually just string value concatenations. +unquoted_string_concat: his name is jeff // same as: "his name is jeff" + +# Going further, even keys that are unquoted strings are actually just string +# value concatenations. +this is a key: value // the KEY is the same as: "this is a key" +# The following field is identical to the field above. +"this is a key": value + +# Quoted strings can also be concatenated. This will be useful later, +# when we cover substitutions. +quoted_string_concat: "her"" name" "is ""jenna" // same as: "her name is jenna" +# Notice that the whitespace (or lack thereof) between values is preserved. + +## ARRAY CONCATENATION ## + +# Arrays separated by whitespace are merged into a single array. +array_concat: [1, 2, 3] [4, 5, 6] // same as: [1, 2, 3, 4, 5, 6] + +# Arrays cannot be concatenated with a non-array value. +//array_concat: true [false] results in an error +//array_concat: 1 [2] results in an error + +## OBJECT CONCATENATION ## + +# Objects separated by whitespace are merged into a single object. +# The merge functionality is identical to that of duplicate key object merging. +lamp: {on: true} {color: tan} // same as: {on: true, color: tan} + +# Similarly to arrays, objects cannot be concatenated with a non-object value. +//object_concat: true {on: false} results in an error +//object_concat: 1 {number: 2} results in an error + +######################## +### PATH EXPRESSIONS ### +######################## + +# Path expressions are used to write out a path through the object graph. +# Think of it as navigating through objects to a specific field. +# Each object to traverse through is called an element, and each element is +# separated with a period. + +country: { + city: { + neighborhood: { + house: { + name: "My House" + address: 123 Example Dr. + } + } + } +} +# For example, the path to the address of the house could be written as: +# country.city.neighborhood.house.address +# Country, city, neighborhood, house, and address are all elements. + +# Path expressions are used in two places: substitutions (which will be +# covered in a moment), and as keys. +# That's right: keys themselves can also be path expressions. +foo: { + bar: { + baz: { + number: 12 + } + } +} +# Rather than tediously specifying each object, a path expression can be used. +# The following field represents the same object found above. +foo.bar.baz.number: 12 + +# Fields and objects specified with path expressions are merged in the same way +# that any object is usually merged. +foo.bar.baz.bool: true +// the object foo's value is: foo { bar { baz { number: 12, bool: true } } } + +##################### +### SUBSTITUTIONS ### +##################### + +# Substitutions refer to a specific value from some path expression. +# They're only allowed in values, not keys or nested inside other substitutions. + +me: { + favorite_animal: parrots + favorite_food: cookies +} +# The syntax for a substitution is either ${path_expression} or +# ${?path_expression}. The latter syntax will be discussed in a moment. +my_fav_animal: ${me.favorite_animal} +my_fav_food: ${me.favorite_food} + +# Substitutions are not parsed inside quoted strings. To get around this, +# either use an unquoted string or value concatenation. +animal_announcement: My favorite animal is ${my_fav_animal} +// the value is: My favorite animal is parrots +food_announcement: "My favorite food is "${my_fav_food}"!" +// the value is: "My favorite food is cookies!" + +# Substitutions are parsed last in the document. Because of this, you can +# reference a key that hasn't been defined yet. +color_announcement: "My favorite color is" ${my_fav_color}"!" +// the value is: "My favorite color is blue!" +my_fav_color: blue + +# Another effect of substitutions being parsed last is that substitutions will +# always use the latest, as in last, value assigned in the entire document, +# which includes merged objects. +color: green +their_favorite_color: ${color} // the value is: orange +color: orange + +random_object: { + number: 12 +} +the_number: ${random_object.number} // the value is: 15 +random_object: { + number: 15 +} + +############################### +### UNDEFINED SUBSTITUTIONS ### +############################### + +# A substitution using the ${path_expression} syntax with an undefined path +# expression, meaning a path expression that does not point to a defined value, +# is invalid and will therefore generate an error. +//${does.not.exist} will throw an error + +# However, an undefined substitution using the ${?path_expression} syntax +# has different behavior depending on what it is the value of. +request: { + # If it is the value of a field, then the field will not be created. + response: ${?does.not.exist} // this field won't be created and does not exist + type: HTTP +} + +request: { + # Additionally, if it would have overridden a previous value, then the + # previous value remains unchanged. + type: ${?does.not.exist} // request.type is still HTTP +} + +# If it is a value in an array, then it is simply not added. +values: [ 172, "Brian", ${?does.not.exist}, null, true, ] +// the value is: [ 172, "Brian", null, true ] + +# If it is part of simple value concatenation, it acts as an empty string. +final_string: "String One"${?does.not.exist}"String Two" +// the value is: "String OneString Two" + +# If it is part of array concatenation, it acts as an empty array. +final_array: [ 1, 2, 3 ] ${?does.not.exist} [ 7, 8, 9 ] +// the value is: [ 1, 2, 3, 7, 8, 9 ] + +# If it is part of object concatenation, it acts as an empty object. +final_array: { a: 1 } ${?does.not.exist} { c: 3 } +// the value is: { a: 1, c: 3 } + +###################################### +### SELF-REFERENTIAL SUBSTITUTIONS ### +###################################### + +# Substitutions normally "look forward" and use the final value defined in the +# document. However, in cases when this would create a cycle, the substitution +# looks only backwards. + +# A field which contains a substitution that points to itself or points to +# other fields that eventually point back to itself is called a +# self-referential field. +letters: "a b c" // the value is: "a b c" +letters: ${letters}" d" // "a b c d" +letters: ${letters}" e" // "a b c d e" + +PATH: [/bin] // the value is: [/bin] +PATH: ${PATH} [/usr/bin] // [/bin, /usr/bin] +PATH: ${PATH} [/usr/local/bin] // [/bin, /usr/bin, /usr/local/bin] + +x: "x" // the value is: "x" +y: ${x}"y" // "xy" +x: ${y}"z" // "xyz" + +########################## +### += FIELD SEPARATOR ### +########################## + +# In addition to : and =, there actually exists another separator: += +# A field separated with += acts as a self-referential array concatenation. +# In short, it appends an element to a previously defined array. + +a: [1] +b: [1] +# This field: +a += 2 // the value is: [1, 2] +# functions the same as: +b: ${?b} [2] // the value is: [1, 2] + +USERS: [/usr/luke] // the value is: [/usr/luke] +USERS += /usr/devon // [/usr/luke, /usr/devon] +USERS += /usr/michael // [/usr/luke, /usr/devon, /usr/michael] + +# Since += only appends elements to a previously existing array, if the previous +# value was not an array, an error will be generated. +OTHER_USERS: /usr/luke +//OTHER_USERS += /usr/devon results in an error + +# Notice that the underlying substitution syntax used is ${?path}, not ${path}. +# Recall that, using the ${?} syntax, an undefined substitution in array +# concatenation acts as an empty array. Because of this, it is perfectly +# acceptable if the field that is being set is initially undefined. +//z: [] not necessary +z += 3 // the value is: [3] +z += 4 // the value is: [3, 4] + +NEW_USERS += /usr/sandra // the value is: [/usr/sandra] +NEW_USERS += /usr/kennedy // [/usr/sandra, /usr/kennedy] +NEW_USERS += /usr/robin // [/usr/sandra, /usr/kennedy, /usr/robin] + +################ +### INCLUDES ### +################ + +# Includes allow you to "import" one HOCON document into another. + +# An include statement consists of the unquoted string "include" followed by +# whitespace and then a resource name, which is one of the following: +# - a single quoted string which is heuristically interpreted as a URL, +# filename, or a Java classpath resource. +# - url(), file(), or classpath(), with the parentheses surrounding a quoted +# string which is either a URL, filename, or classpath resource respectively. +# - required(), with the parentheses surrounding one of the above. +include "https://example.com/config.conf" +include "/foo/bar/config.conf" +include "config.conf" + +include url("https://example.com/config.conf") +include file("/foo/bar/config.conf") +include classpath("config.conf") + +# If the included file does not exist, it will be silently ignored and act as if +# it were an empty object. However, if it is wrapped around required(), then +# parsing will explicitly error if the file cannot be resolved. +//include required("doesnt_exist.conf") will error +//include required(url("https://example.com/doesnt_exist.conf")) will error +//include required(file("doesnt_exist.conf")) will error +//include required(classpath("doesnt_exist.conf")) will error + +# The file specified by the include statement is called the included file, and +# the file which contains the include statement is called the including file. + +# Including a file functions as if you directly replaced the include statement, +# wherever it may be, with the contents of the included file's root object. + +# An included file must have an object as its root value and not an array. +# If the included file has an array as its root value, then it is invalid and +# an error will be generated. + +# Pretend that the following is in a file called user_config.conf: +username: RandomUser1337 +auto_login: true +color_theme: dark +screensaver: { + image: usr/images/screensaver.jpg + turn_on_after: 1m +} + +# And then we include that file. +include file("user_config.conf") + +# We can now reference values from that file! +path_to_user_screensaver: ${screensaver.image} // +greeting: "Welcome, "${username}"!" // the value is: "Welcome, RandomUser1337!" + +# Duplicate keys override as they normally do. +status: "Auto Login: "${auto_login} // the value is: "Auto Login: true" +auto_login: false +status: "Auto Login: "${auto_login} // the value is: "Auto Login: false" + +# Object merging is also the same as usual. +screensaver: { + // This gets added to the screensaver object. + enable_during_day: false + // This overrides the previous value. + turn_on_after: 30s +} + +# Include statements can appear in place of a field. Anywhere that a field +# could appear, an include statement could appear as well. + +# Pretend that the following is in a file called server_settings.conf: +max_connections: 10 +url: example.com +port: 80 +admin_page: { + username: admin + password: pass12345 +} + +# And then we include that file nested inside another object. +websites: { + my_epic_website: { + include file("server_settings.conf") + } +} + +# Now, we can reference the contents of server_settings.conf as if they +# had been written directly into the object my_epic_website. +server_port: ${websites.my_epic_website.port} + +the_password: "The password is: "${websites.my_epic_website.admin_page.password} +// the value is: The password is: pass12345 + +max_conn: "Max Connections: "${websites.my_epic_website.max_connections} +// the value is: Max Connections: 10 +``` + +### More Resources + ++ [Official HOCON Specification](https://github.com/lightbend/config/blob/master/HOCON.md) ++ [HOCON Playground](https://hocon-playground.herokuapp.com)