{"id":22730932,"url":"https://github.com/benrosen/knife","last_synced_at":"2025-03-30T01:29:36.154Z","repository":{"id":258739106,"uuid":"861060187","full_name":"benrosen/knife","owner":"benrosen","description":null,"archived":false,"fork":false,"pushed_at":"2024-10-18T01:05:16.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-05T03:27:19.679Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/benrosen.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-21T22:24:17.000Z","updated_at":"2024-10-18T01:05:19.000Z","dependencies_parsed_at":"2024-10-20T20:09:34.883Z","dependency_job_id":null,"html_url":"https://github.com/benrosen/knife","commit_stats":null,"previous_names":["benrosen/knife"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrosen%2Fknife","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrosen%2Fknife/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrosen%2Fknife/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benrosen%2Fknife/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benrosen","download_url":"https://codeload.github.com/benrosen/knife/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246264395,"owners_count":20749456,"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-12-10T19:15:59.530Z","updated_at":"2025-03-30T01:29:36.133Z","avatar_url":"https://github.com/benrosen.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🔪\n\nKnife is a practical programming language.\n\n## Hello World\n\nInstall knife globally with npm.\n\n```shell\n$ npm i -g knife\n```\n\nCreate a file with a `.k` extension; for this tutorial we will make one called `hello_world.k`.\n\n```knife\n~\nhello_world.k\n~\n\noutput \"Hello, World!\"\n```\n\n\u003e [!NOTE]\n\u003e The `~` character starts and ends multiline comments.\n\nRun your `.k` file from your terminal.\n\n```shell\n$ knife run hello_world.k\n```\n\n## Learning Knife by Example\n\n### Output\n\nTo return a value from a Knife program, use the `output` keyword.\n\n```knife\noutput \"foo\"\n```\n\nThis program \"outputs\" the string `\"foo\"` to the console.\n\n\u003e [!NOTE]\n\u003e The `output` keyword is synonymous with `return` in other languages.\n\nKnife programs always produce string outputs.\n\nThe `output` keyword will automatically JSON encode any non-string values before outputting them.\n\n### Input\n\nKnife programs access input through the `input` keyword.\n\n```knife\noutput input\n```\n\nThis program outputs the input value.\n\nFor example, running the following command:\n\n```shell\n$ knife run hello_world.k bar\n```\n\nwill output:\n\n```shell\nbar\n```\n\n\u003e [!IMPORTANT]\n\u003e You can only pass one argument to a Knife program.\n\nKnife programs will accept any _non-null_ JSON-serializable value as input; the `input` keyword will automatically JSON decode the input value.\n\n```knife\n~\nmain.k\n~\n\noutput input.foo\n```\n\nThis program will output the value of the `foo` key in the input object.\n\nThen, running the following command:\n\n```shell\n$ knife run main.k '{\"foo\": \"bar\"}'\n```\n\nwill output:\n\n```shell\nbar\n```\n\nSince arrays are also JSON-serializable, you can pass an array as input.\n\n```knife\n~\nmain.k\n~\n\noutput input[0]\n```\n\nThis program will output the first element of the input array.\n\nThen, running the following command:\n\n```shell\n$ knife run main.k '[\"foo\", \"bar\"]'\n```\n\nwill output:\n\n```shell\nfoo\n```\n\nAlternatively, you can pass a JSON file path as input.\n\nFor this example, let's assume we have a file called `input.json` with the following content:\n\n```json\n{\n  \"foo\": \"bar\"\n}\n```\n\nOur Knife program will look like this:\n\n```knife\n~\nmain.k\n~\n\noutput input.foo\n```\n\nThen, running the following command:\n\n```shell\n$ knife run main.k input.json\n```\n\nwill output:\n\n```shell\nbar\n```\n\n### Keys\n\nTo assign a value to a key, use the `set ... to` keyword.\n\n```knife\nset foo to \"bar\"\n```\n\nIn this example, `foo` is the name of the key and `\"bar\"` is the value.\n\n\u003e [!IMPORTANT]\n\u003e Keys are always strings.\n\n\u003e [!NOTE]\n\u003e Any non-null JSON-serializable value can be assigned to a key.\n\nYou can access the value of a key by its name.\n\n```knife\nset foo to \"bar\"\n\noutput foo\n```\n\nYou can also create a subscription to a key using the `watch ... as` keyword.\n\n```knife\nwatch foo as foo_watcher\n  output foo\n```\n\nThis program will output the value of `foo` every time it is set.\n\nTo \"unwatch\" a key, use the `retire` keyword.\n\n```knife\nwatch foo as foo_watcher\n  retire foo_watcher\n  output foo\n```\n\nUnlike the first program, this program will only output the value of `foo` once, scince the `foo_watcher` subscription is retired after the first change.\n\nAs another example, here's one way that you could retire a watcher after three assignments:\n\n```knife\nset foo to 0\nset foo_assignments to 0\n\nwatch foo as foo_watcher\n  set foo_assignments to foo_assignments plus 1\n\nwatch foo_assignments as foo_assignments_watcher\n  if foo_assignments is greater than 2\n\tretire foo_watcher\n\tretire foo_assignments_watcher\n```\n\n### Operators\n\nKnife has the following operators:\n\n- `plus` for addition\n- `minus` for subtraction\n- `times` for multiplication\n- `divided by` for division\n- `remainder of ... when divided by` for modulo\n- `is` for equality\n- `is not` for inequality\n- `is greater than` for greater than\n- `is less than` for less than\n- `is greater than or equal to` for greater than or equal to\n- `is less than or equal to` for less than or equal to\n- `and` for logical AND\n- `or` for logical OR\n- `not` for logical NOT\n\n### Strings\n\nStrings are enclosed in double quotes.\n\nYou can concatenate strings with the `plus` operator.\n\n```knife\nset foo to \"foo\"\nset foobar to foo plus \"bar\"\n```\n\nYou can remove substrings from a string with the `minus` operator.\n\n```knife\nset foobar to \"foobar\"\nset foo to foobar minus \"bar\"\n```\n\nYou can access substrings by their index.\n\n```knife\nset foobar to \"foobar\"\nset first_char to foobar[0]\n```\n\n\u003e [!NOTE]\n\u003e String indices are zero-based.\n\nYou can get the length of a string with the `number of characters in` operator.\n\n```knife\nset foobar to \"foobar\"\nset length to number of characters in foobar\n```\n\nYou can split a string into an array with the `divided by` operator.\n\n```knife\nset foobar to \"foo bar\"\nset words to foobar divided by \" \"\n\noutput words\n```\n\nThis program will output `[\"foo\", \"bar\"]`.\n\nYou can also use a regular expression to match a string.\n\n```knife\nset foobar to \"foobar\"\noutput foobar matches \"^foo\"\n```\n\nThis program will output `true` because `\"foobar\"` starts with `\"foo\"`.\n\n### JSON\n\nYou can encode and decode JSON objects with the `encoded` and `decoded` keywords respectively.\n\n```knife\nset encoded_obj to encoded {\"foo\": \"bar\"}\nset obj to decoded encoded_obj\n\noutput obj.foo\n```\n\nThis program will output `bar`.\n\n### Arrays\n\nSet an empty array like this:\n\n```knife\nset items to []\n```\n\nSet an array with values like this:\n\n```knife\nset items to [\"foo\", \"bar\"]\n```\n\nYou can access values in an array by their index.\n\n```knife\nset items to [\"foo\", \"bar\"]\nset first_item to items[0]\n```\n\n\u003e [!NOTE]\n\u003e Array indices are zero-based.\n\nYou can get the length of an array with the `number of items in` operator.\n\n```knife\nset items to [\"foo\", \"bar\"]\nset length to number of items in items\n```\n\n### Objects\n\nSet an empty object like this:\n\n```knife\nset obj to {}\n```\n\nSet an object with key-value pairs like this:\n\n```knife\nset obj to {\"foo\": \"bar\"}\n```\n\nYou can do multiline object assignments like this:\n\n```knife\nset obj to {\n  \"foo\": \"bar\",\n  \"baz\": \"qux\"\n}\n```\n\nYou can access values in an object by their key.\n\n```knife\nset obj to {\"foo\": \"bar\"}\noutput obj.foo\n```\n\nYou can get the keys of an object with the `keys` operator.\n\n```knife\nset item to {\n  \"foo\": \"bar\",\n  \"baz\": \"qux\"\n}\n\noutput item keys\n```\n\nThis program will output `[\"foo\", \"baz\"]`.\n\nYou can get the values of an object with the `values` operator.\n\n```knife\nset item to {\n  \"foo\": \"bar\",\n  \"baz\": \"qux\"\n}\n\noutput item values\n```\n\nThis program will output `[\"bar\", \"qux\"]`.\n\n### Time\n\nYou can get the current epoch timestamp with the `now` keyword.\n\n```knife\noutput now\n```\n\n### Randomization\n\nYou can generate a random number between 0 and 1 with the `random` keyword.\n\n```knife\noutput random\n```\n\n\n### Functions\n\nIn Knife, each file is a function.\n\n```knife\n~\nincrement.k\n~\n\noutput input plus 1\n```\n\nThis function lives in a file called `increment.k` and outputs the input value plus one.\n\nTo import a function from another file, use the `use` keyword.\n\n```knife\n~\nmain.k\n~\n\nuse increment\n\toutput increment 1\n```\n\n\u003e [!NOTE]\n\u003e If we run `main.k`, it will output `2`.\n\n\u003e [!NOTE]\n\u003e Only code in the `use` block has access to the imported function.\n\nYou can import multiple functions by separating them with commas.\n\n```knife\nuse increment, decrement\n```\n\nYou can alias imports with the `as` keyword.\n\n```knife\nuse increment as plus_one\n\toutput plus_one 1\n```\n\nYou can dynamically import a function using a combination of `input` and `use`.\n\n```knife\n~\nmain.k\n~\n\nuse input as callback\n\toutput callback 1\n```\n\n\u003e [!CAUTION]\n\u003e Calling `use input` without aliasing it will overwrite the `input` keyword.\n\nThis program will import the function whose name is passed as an argument.\n\n```shell\n$ knife run main.k increment\n```\n\nRunning this command will output `2`. On the other hand, running this command:\n\n```shell\n$ knife run main.k decrement\n```\n\nwill output `0`.\n\nSometimes you may want to know about the invocation history of a function, like how many times it has been called or what input it was last called with.\n\nYou can create a \"history\" for a function with the `use ... with` keyword.\n\n```knife\nuse increment with increment_history\n\tset result to increment 1\n\tset result to increment result\n\n\tif number of items in increment_history is not 2\n\t\toutput \"increment_history should have two items since the increment function was called twice\"\n```\n\nIf you want to use an alias _and_ a history for your imported function, you can do so with the `use ... as ... with` keyword.\n\n```knife\nuse increment as plus_one with plus_one_history\n```\n\n### Conditionals\n\nTo create a conditional, use the `if` and `otherwise` keywords.\n\n```knife\nif input is \"foo\"\n  output \"bar\"\notherwise\n  output \"baz\"\n```\n\n### Loops\n\nTo create a loop, use the `while` keyword.\n\n```knife\nset number to 0\nset sum to 0\n\nwhile number is less than or equal to 10\n  set sum to sum plus number\n  set number to number plus 1\n\noutput sum\n```\n\nThis program will sum the numbers from 0 to 10 and output the result.\n\n### Comments\n\nTo create a single-line comment, use the `~` character.\n\n```knife\n~ This is a comment\n```\n\nTo create a multiline comment, use the `~` character before the first line of the comment and after the last line of the comment.\n\nMultiline comments support Markdown syntax.\n\n```knife\n~\n# Multiline Comments\n\nTo create a multiline comment, use the `~` character before the first line of the comment and after the last line of the comment.\n~\n```\n\n### Error Handling\n\nKnife does not have special keywords for error handling.\n\n\u003e [!TIP]\n\u003e Use conditionals to check for errors.\n\n### Parallelism\n\nNormally, Knife programs run sequentially.\n\nUse the `concurrently` keyword to run lines in parallel.\n\n```knife\nset a to 0\nset b to 0\n\nwhen a is set\n  output a\n\nwhen b is set\n  output b\n\nconcurrently\n  set a to a plus 1\n  set b to b plus 2\n```\n\nThis program will (intentionally) induce a race condition, outputting either `1` or `2` depending on which key is set faster.\n\n### Logging\n\nKnife does not have special keywords for logging.\n\nInstead, you're encouraged to decide on your own logging strategy.\n\nOne approach is to pass an array of log messages to your functions, which then return the array with additional log messages.\n\nWhen your program finishes, its final output will contain all the log messages from the execution.\n\n```knife\n~\nincrement.k\n~\n\nset logger to input.logger\n\noutput {\n  \"result\": input.value plus 1,\n  \"logger\": logger plus [\"incremented value from\n}\n```\n\n### Testing\n\nYou don't need any special keywords to start writing basic tests in Knife.\n\nIn this example, we'll define and test a `double` function.\n\n```knife\n~\ndouble.k\n~\n\noutput input times 2\n```\n\n```knife\n~\ntest_double.k\n~\n\nuse double\n\tif double 2 is not 4\n\t\toutput \"double 2 should be 4\"\n```\n\nWe can run this test like any other Knife program.\n\n```shell\n$ knife run test_double.k\n```\n\nYou can import tests like any other function.\n\n```knife\nuse test_double\n```\n\nLike all other Knife programs, tests output strings.\n\n```knife\nuse test_double\n\toutput test_double\n```\n\n\u003e [!NOTE]\n\u003e This program will output the result of the `test_double` function.\n\nYou can parallelize tests like any other function calls.\n\n```knife\nset test_results to []\n\nwhen test_results is set\n\tif number of items in test_results is 2\n\t\toutput test_results\n\nuse test_double, test_triple\n\tconcurrently\n\t\tset test_results to test_results plus [test_double]\n\t\tset test_results to test_results plus [test_triple]\n```\n\n\u003e [!NOTE]\n\u003e This program will run the `test_double` and `test_triple` functions in parallel and then output the results when both tests are complete.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenrosen%2Fknife","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenrosen%2Fknife","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenrosen%2Fknife/lists"}