{"id":19392056,"url":"https://github.com/davidraab/sq","last_synced_at":"2025-07-20T05:32:52.841Z","repository":{"id":221981995,"uuid":"753947141","full_name":"DavidRaab/Sq","owner":"DavidRaab","description":"Sq - A Language hosted in Perl","archived":false,"fork":false,"pushed_at":"2025-06-11T11:27:58.000Z","size":1714,"stargazers_count":2,"open_issues_count":9,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-11T12:38:29.958Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Perl","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/DavidRaab.png","metadata":{"files":{"readme":"README.md","changelog":"Changes","contributing":null,"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,"zenodo":null}},"created_at":"2024-02-07T04:56:37.000Z","updated_at":"2025-06-11T11:28:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"8ac0f1ac-8ced-49df-abe4-b840c1cb7b0f","html_url":"https://github.com/DavidRaab/Sq","commit_stats":null,"previous_names":["davidraab/sq"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/DavidRaab/Sq","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidRaab%2FSq","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidRaab%2FSq/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidRaab%2FSq/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidRaab%2FSq/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DavidRaab","download_url":"https://codeload.github.com/DavidRaab/Sq/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidRaab%2FSq/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266071519,"owners_count":23871940,"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-11-10T10:30:13.721Z","updated_at":"2025-07-20T05:32:52.815Z","avatar_url":"https://github.com/DavidRaab.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NAME\n\nSq - A Language hosted in Perl\n\n# Parser\n\nHere is an example of the Parser to parse a number with suffix.\n\n```perl\nuse Sq;\nuse Sq::Parser;\n\nmy $num = assign {\n    my $to_num = sub($num,$suffix) {\n        return $num                      if $suffix eq 'b';\n        return $num * 1024               if $suffix eq 'kb';\n        return $num * 1024 * 1024        if $suffix eq 'mb';\n        return $num * 1024 * 1024 * 1024 if $suffix eq 'gb';\n    };\n\n    p_many(\n        p_maybe(p_match(qr/\\s* , \\s*/x)), # optional ,\n        p_map(\n            $to_num,\n            p_many (p_strc(0 .. 9)), # digits\n            p_match(qr/\\s*/),        # whitespace\n            p_strc (qw/b kb mb gb/), # suffix\n        )\n    );\n};\n\n# Tests\nis(p_run($num, \"1  b, 1kb\"),         Some([1, 1024]), '1 b \u0026 1kb');\nis(p_run($num, \"1 kb, 1gb\"), Some([1024,1073741824]), '1 kb \u0026 1gb');\nis(p_run($num, \"1 mb\"),              Some([1048576]), '1 mb');\nis(p_run($num, \"1 gb\"),           Some([1073741824]), '1 gb');\n```\n\nthis is an exhausted example. `Sq::Parser` does not try to replace Regexes. Quite\nthe opposite. It allows creating Parser with regexes in mind and for good\nperformance you should try to cramp as much as possible into Perl's regeyes. So\nhere is the above parser re-written using Perl Regexes.\n\n```perl\nuse Sq;\nuse Sq::Parser;\n\nmy $num = assign {\n    my $to_num = sub($num,$suffix) {\n        return $num                      if fc $suffix eq fc 'b';\n        return $num * 1024               if fc $suffix eq fc 'kb';\n        return $num * 1024 * 1024        if fc $suffix eq fc 'mb';\n        return $num * 1024 * 1024 * 1024 if fc $suffix eq fc 'gb';\n    };\n\n    p_many(\n        p_matchf(qr/\\s* ,? \\s* (\\d+) \\s* (b|kb|mb|gb)/xi, $to_num),\n    );\n};\n\nis(p_run($num, \"1  b, 1kb\"),         Some([1, 1024]), '1 b \u0026 1kb');\nis(p_run($num, \"1 kb, 1gb\"), Some([1024,1073741824]), '1 kb \u0026 1gb');\nis(p_run($num, \"1 Mb\"),              Some([1048576]), '1 mb');\nis(p_run($num, \"1 gb\"),           Some([1073741824]), '1 gb');\n```\n\n# Data over Classes\n\n```perl\nuse Sq;\n\nmy $album = sq {\n    artist =\u003e 'Michael Jackson',\n    title  =\u003e 'Thriller',\n    tracks =\u003e [\n        {title =\u003e \"Wanna Be Startin’ Somethin\", duration =\u003e 363},\n        {title =\u003e \"Baby Be Mine\",               duration =\u003e 260},\n        {title =\u003e \"The Girl Is Mine\",           duration =\u003e 242},\n        {title =\u003e \"Thriller\",                   duration =\u003e 357},\n        {title =\u003e \"Beat It\",                    duration =\u003e 258},\n        {title =\u003e \"Billie Jean\",                duration =\u003e 294},\n        {title =\u003e \"Human Nature\",               duration =\u003e 246},\n        {title =\u003e \"P.Y.T.\",                     duration =\u003e 239},\n        {title =\u003e \"The Lady in My Life\",        duration =\u003e 300},\n    ],\n};\n\nmy $length = $album-\u003elength;           # 3 - hash has 3 keys\nmy $tracks = $album-\u003e{tracks}-\u003elength; # 9 - nine tracks\n\n# 2559 - shortest version\nmy $album_runtime = $album-\u003eget('tracks')-\u003emap(call 'sum_by', key 'duration')-\u003eor(0);\n\n# 2559 - expanded the \"call\" function\nmy $album_runtime = $album-\u003eget('tracks')-\u003emap(sub ($tracks) {\n    $tracks-\u003esum_by(key 'duration');\n})-\u003eor(0);\n\n# 2559 - expanded the \"key\" function\nmy $album_runtime = $album-\u003eget('tracks')-\u003emap(sub ($tracks) {\n    $tracks-\u003esum_by(sub($hash) {\n        $hash-\u003e{duration}\n    });\n})-\u003eor(0);\n\n# 2559 - Pure Perl version\nmy $album_runtime = assign {\n    my $sum    = 0;\n    my $tracks = $album-\u003e{tracks};\n    if ( defined $tracks ) {\n        for my $track ( @$tracks ) {\n            $sum += $track-\u003e{duration};\n        }\n    }\n    return $sum;\n};\n```\n\n# Default Equality\n\nLoading `Sq` automatically loads an `equal` function that can recursively check\na data-structure to be equal or not. By default it supports checking of `Array`,\n`Hash`, `Seq`, `Option` and `Result` and sure also comparing numbers and strings.\n\nBy default this function is also installed as a method/function into the above\npackages, so you also can call `equal` as a method on those types.\n\n```perl\n# Sq enhanced data-structure\nmy $album1 = sq {\n    Artist =\u003e 'Queen',\n    Title  =\u003e 'Greatest Hits',\n    Tracks =\u003e Seq-\u003enew(\n        { Title =\u003e 'We will Rock You'          },\n        { Title =\u003e 'Radio Gaga'                },\n        { Title =\u003e 'Who Wants To Life Forever' },\n        { Title =\u003e \"You Don't Fool Me\"         },\n    ),\n    Tags =\u003e Some(qw/80/),\n};\n\n# pure perl data-structure\nmy $album2 = {\n    Artist =\u003e 'Queen',\n    Title  =\u003e 'Greatest Hits',\n    Tracks =\u003e Seq-\u003enew(\n        { Title =\u003e 'We will Rock You'          },\n        { Title =\u003e 'Radio Gaga'                },\n        { Title =\u003e 'Who Wants To Life Forever' },\n        { Title =\u003e \"You Don't Fool Me\"         },\n    ),\n    Tags =\u003e Some(qw/80/),\n};\n\nmy $bool = equal($album1, $album2); # 1\nmy $bool = $album1-\u003eequal($album2); # 1\n```\n\n# Typing\n\n```perl\nuse Sq;\nuse Sq::Type;\n\n# Describes an address\nmy $address = t_hash(t_keys(\n    street =\u003e t_str,\n    city   =\u003e t_str,\n    state  =\u003e t_str,\n    zip    =\u003e t_match(qr/\\A\\d+\\z/),\n));\n\n# A user containing an address\nmy $user  = t_hash(t_keys(\n    id      =\u003e t_str,\n    first   =\u003e t_str,\n    last    =\u003e t_str,\n    address =\u003e $address,\n));\n\nmy $user1 = {\n    id      =\u003e 1,\n    first   =\u003e \"David\",\n    last    =\u003e \"Raab\",\n    address =\u003e {\n        street =\u003e 'Wonder Street',\n        city   =\u003e 'Wonder City',\n        state  =\u003e 'Wonder State',\n        zip    =\u003e '12345',\n    },\n};\n\nmy $user2 = {\n    id      =\u003e 1,\n    frist   =\u003e \"David\",   # Typo\n    last    =\u003e \"Raab\",\n    address =\u003e {\n        street =\u003e 'Wonder Street',\n        city   =\u003e 'Wonder City',\n        state  =\u003e 'Wonder State',\n        zip    =\u003e '12345',\n    },\n};\n\n# Tests\nis(t_run($address, $users[0]{address}), Ok(1),\n    '$users[0] is addr');\nis(t_run($user, $users[0]), Ok(1),\n    '$users[0] is a user');\nis(t_run($user, $users[1]), Err(\"hash: keys: 'first' not defined\"),\n    '$users[1] has a typo');\n\n# describes an album\nmy $is_album = assign {\n    # checks for format and if min:seconds are not \u003e= 60\n    my $duration = t_matchf(qr/\\A(\\d\\d):(\\d\\d)\\z/, sub($min,$sec) {\n        return if $min \u003e= 60;\n        return if $sec \u003e= 60;\n        return 1;\n    });\n\n    return\n        t_hash(\n            # This check is not needed. t_keys also does that. But you could\n            # have this line alone. Or just add additional fields that don't\n            # need to be specified with a type. Also the type check\n            # can be faster. All rules are executed one after another. So when\n            # t_with_keys fails, everything fails. and further tests don't need\n            # to be checked. t_keys must recurse into it's checks.\n            t_with_keys(qw/artist title tracks/),\n            t_keys(\n                artist =\u003e t_str(t_min 1),  # string must have at least 1 char\n                title  =\u003e t_str(t_min(1), t_max(255)),\n                tracks =\u003e t_array(\n                    t_min(1),              # Array must have at least 1 entry\n                    t_of(t_hash(           # All entries must be hashes\n                        t_with_keys(qw/name duration/),\n                        t_keys(\n                            name     =\u003e t_str,\n                            duration =\u003e $duration))))));\n};\n\nmy $result = t_run  ($is_album, $album); # Returns Result\nmy $bool   = t_valid($is_album, $album); # Returns boolean\nt_assert($is_album, $album);             # Throws exception when not valid\n```\n\n# Signatures\n\nThe Type system can be used to add type-checking to any function. But the\nidea is that this kind of type-checking is only added in developing / testing.\nFor code running in production the type-check is removed. It works like\nthe **Memoize** module by replacing a function with type-checking.\n\nSo in production you don't pay the price of type-checking in every function.\nYou just enable it when you need to find errors/bugs or during normal development\nto find quickly type-errors.\n\n```perl\nuse Sq;\nuse Sq::Sig; # this adds type-checking to all kind of functions in Sq.\n\n# throws an exception when Sq::Sig is loaded complaining that the array is not\n# even-sized. Otherwise without Sq::Sig it gives some warnings but continues.\nmy $hash = sq([1,2,3])-\u003eas_hash;\n```\n\nYou can add type-checking to any function.\n\n```perl\nuse Sq;\nuse Sq::Type;\nuse Sq::Signature;\n\nsub whatever($int, $str, $array_of_nums) {\n    # ...\n    return $hash;\n}\n\n# this adds type-checking to the function. Usually you put those signatures\n# in its own file that can be loaded at will. This also correctly checks\n# the return value of a function. So when you refactor/change code you get\n# errors when you return the wrong things.\nsig('main::whatever', t_int, t_str, t_array(t_of t_num), t_hash);\n\nwhatever(\"foo\", \"foo\", [1,2,3]); # fails\nwhatever(  123, \"foo\", [\"foo\"]); # fails\nwhatever(  123,    [], [1,2,3]); # fails\nwhatever(123.3,   123,      []); # fails\nwhatever(  123, \"123\",      []); # ok - because \"123\" is also a valid string\nwhatever(  123, \"foo\",      []); # ok\nwhatever(  123, \"foo\", [1,2,3]); # ok\n```\n\nThis is the signature of `Option::match`.\n\n```perl\nmy $matches = t_hash(t_keys(\n    Some =\u003e t_sub,\n    None =\u003e t_sub,\n));\nsigt('Option::match', t_tuplev($opt, t_as_hash($matches)), $any);\n\n# this is how a match call looks\nmy $result =\n    $opt-\u003ematch(\n        Some =\u003e sub($x) { $x * $x },\n        None =\u003e sub     { 0       },\n    );\n\n# this will throw an exception because \"some/none\" instead of \"Some/None\"\n# was passed.\nmy $result =\n    $opt-\u003ematch(\n        some =\u003e sub($x) { $x * $x },\n        none =\u003e sub     { 0       },\n    );\n```\n\n# Seq Module\n\n`Seq` is a lazy data-structure that only starts computing things when you\nask it. It only computes as much things it needs. You can think\nof them as *immutable-iterators*.\n\n```perl\nuse Sq;\n\n# Does nothing.\nmy $big = Seq-\u003erange(1, 1_000_000_000);\n\n# Fibonacci Generator - also does no computation.\n# It will generate fibonacci forever when this would be possible. It isn't\n# because it works on 64-bit floats\nmy $fib =\n    Seq-\u003econcat(\n        seq { 1,1 },\n        Seq-\u003eunfold([1,1], sub($state) {\n            my $next = $state-\u003e[0] + $state-\u003e[1];\n            return $next, [$state-\u003e[1],$next];\n        })\n    );\n\n# Still does nothing. But $smaller will only contain the first 10_000 items\n# when you ask it for data\nmy $smaller = $big-\u003etake(10_000);\n\n# prints: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765\n# never more than one number needs to be in memory.\n$fib-\u003etake(20)-\u003eiter(sub($x) {\n    say $x;\n});\n\n# you can use the same $fib again, now prints: 1 1 2 3 5\n# this freshly recomputes the first 5 items.\n$fib-\u003etake(5)-\u003eiter(sub($x) {\n    say $x;\n});\n\n# Represents all possible combinations\n# seq { [clubs =\u003e 7], [clubs =\u003e 8], [clubs =\u003e 9], ... }\nmy $cards =\n    Seq::cartesian(\n        seq { qw/clubs spades hearts diamond/ },\n        seq { qw/7 8 9 10 J Q K A/            },\n    );\n\nuse Path::Tiny qw(path);\n# get the maximum id from test-files so far\nmy $maximum_id =\n    Sq-\u003efs\n    -\u003echildren('t')              # a sequence of Path::Tiny objects\n    -\u003emap(call 'basename')       # calls -\u003ebasename method on objects\n    -\u003erxm(qr/\\A(\\d+) .*\\.t/xms)  # matches and auto extract all () in an array\n    -\u003efsts                       # returns idx0 of inner array\n    -\u003emax                        # pick highest numbers - starts computation\n    -\u003eor(0);                     # max returns optional\n                                 #   or(0) extracts or gives default value\n\n# Now starts calculating the 10_000 items and prints them\n$smaller-\u003eiter(sub($x) {\n    say $x;\n});\n```\n\n# Seq counting to 1 Billion\n\nTry running: `examples/1bill.pl`\n\n```perl\nuse Sq;\nuse Sq::Sig;\nuse Time::HiRes qw(time);\n\nmy $first  =\n    Seq\n    -\u003erange(1,1_000_000_000)\n    -\u003edo_every(100_000, sub($num,$idx){ print \"$num\\n\" });\n\nmy $second =\n    Seq-\u003erange(1,1_000_000_000);\n\n# this executes the subroutine and prints how long it took when finished.\nSq-\u003ebench-\u003eit(sub {\n    print \"Are those sequences lazy?\\n\";\n    if ( equal($first,$second) ) {\n        print \"Yes, and they are the same!\\n\";\n    }\n    else {\n        print \"Yes, but not the same!\\n\";\n    }\n});\n```\n\nor: `t/Seq/11-lazy.t`\n\n```perl\n# 1 billion\nmy $big = Seq-\u003erange(1,1_000_000_000);\n\n# two different branches of 1 billion\nmy $double  = $big-\u003emap(sub($x) { $x * 2  });\nmy $squared = $big-\u003emap(sub($x) { $x * $x });\n\n# zip those together\nmy $zipped = Seq::zip($double, $squared);\n\n# only take(10) elements from it.\nmy $only10 = $zipped-\u003etake(10);\n\n# compare\nis(\n    $only10,\n    seq {\n        [  2,   1 ],\n        [  4,   4 ],\n        [  6,   9 ],\n        [  8,  16 ],\n        [ 10,  25 ],\n        [ 12,  36 ],\n        [ 14,  49 ],\n        [ 16,  64 ],\n        [ 18,  81 ],\n        [ 20, 100 ]\n    },\n    'build something small');\n```\n\n# EXPORT\n\nIt exports the following functions by default:\n\n| Category    | Functions                        |\n|----         | ---                              |\n| Creation    | sq key Some None Ok Err new type array hash |\n| Functions   | fn multi static with_dispatch type_cond record |\n| Equality    | equal                            |\n| OBJ         | call                             |\n| Scope       | assign lazy                      |\n| Helpers     | id fst snd dump dumps            |\n| Comparision | by_num by_str by_stri            |\n| Type-checks | is_num is_str is_array is_hash is_seq is_opt is_result is_ref is_regex is_type |\n\n# SYNOPSIS\n\nWhy Sq?\n\nBecause I didn't liked the way that Perl and many other languages evolved. This crazy stuff and automatic \"crying\" that everything not object-oriented must be\nbad is horrible.\n\nSo I just wanted another direction.\n\nI just read all beginner books of Perl. Had the Perl Bible. Perl\nBest Practices, Object-Oriented Perl, 2-3 Catalyst Books. Developers Testing,\nAdvanced Perl Programming 1st and 2nd Edition. Algorithms with Perl.\nNetzwerk Programmiern mit Perl (Network Programming with Perl. Don't know\nif there was an english book) and some others.\n\nThe most important one was [Higher-Order Perl](https://hop.perl.plover.com/) (HOP) in 2008.\n\nI guess it completely shaped how I thought about programming and found a better\nway of writing programs.\n\nAs I actually wanted todo Game Development I learned C# with Unity. Again C#\nwas a nightmare. Coming from Perl with Moose, C# looked horrible outdated. But\nit was faster.\n\nI then learned F# as after HOP i wanted to learn more deeper about functional\nprogramming. I mostly learned F# from [F# For Fun and Profit](https://fsharpforfunandprofit.com/)\n\nI really liked F# and will continue using it to make a game in the future.\n\nStill I am loving and using Perl nearly 20 years. Working on a Linux machine\nwith Perl. You can do a lot of stuff like automation or other file/text\nprocessing that are just easier compared to other languages.\n\nI worked as a proffesional Perl developer in the past, did web-development,\ndatabase, linux-administration and wrote and automated a full CMS with all kind of\nauto-deployment, database upgrades, all written in Perl. I did my\njob so good that the software was finished and I basically got fired.\n\n`Sq` is now somewhat my own system to help me solve the typical problems I have.\nI could have picked F# as I really liked the language. But working with F#\nis, the funny part, too slow.\n\nMy complete test-suite with over 2000 tests runs in under one real-time second.\n`prove -lrj 24`. And consider it must first parse and compile every single\nfile. It starts 42+ test files in parallel.\n\nIn that time I didn't get a simple \"Hello, World!\" printed in F#. It takes\naround 3 seconds to start.\n\nAs I knew Perl the best, I choosed to write it in Perl.\n\nOtherwise F# is fast, but I usually didn't liked how complex the community\ntries to solve problems. Even APIs seems over complicated. I guess that\nis what dynamic-typed developers better do. They know better how an API\nmust be, because you must remember it. Usually the IDE doesn't help you.\n\nI know it because I basically worked most of my time just with Geanny and Perl.\nSyntax Highlithing is all we had back then. But you know. You become faster this\nway.\n\nAnyway `Sq` is my take on programming in a procedural, LISP-like, dynamic-typed\nML language.\n\n# Implemented so Far\n\nSome interesting things like a Type-System, Equality, Parser, Testing-System\nand a whole feature rich Array, Hash and lazy sequence is already solved to a\ngreat part. Still not complete. It also offers loadable Signatures.\n\nThe API itself is not fixed, means some stuff is very likely to change.\n\nI wouldn't recommend this module at the moment to build something critical\nunless you are fine that you very likely need fixes to make code\nworking again.\n\nIn the next time I guess I will create more complex tests. So not just tests\nthat covers all code paths. More actual useful ones. This way it also\ncan be seen how things are solved.\n\nBut I already write my own console apps with it and try to improve those.\nSo somehow `Sq` is practical. I try to solve problems with it, and change\nthings when they seems to make more sense in practice over theory.\n\n* [Sq::Type](lib/Sq/Manual/Type/00-Intro.pod)\n* [Sq::Signature](lib/Sq/Manual/Type/01-Signature.pod)\n* [Sq::Parser](lib/Sq/Manual/Parser/00-intro.pod)\n* [Sq::Equality](lib/Sq/Equality.pm) implements `equal` function and add methods\n* [Sq::Fs](lib/Sq/Fs.pod) file-system operations\n* [Sq::Evaluator](lib/Sq/Evaluator) Implements functions to eval data-structures. See [Type](t/Evaluator/00-type.t) and [Parser](t/Evaluator/01-parser.t)\n* [Sq::Test](lib/Sq/Test.pm) minimal Test-Suite that is already used to test Sq itself\n* [Sq::Dump](lib/Sq/Dump.pm) implements `dump` and `dumps` and add methods\n* [Sq::Collections::Seq](lib/Sq/Collections/Seq.pod)\n* [Sq::Collections::Array](lib/Sq/Collections/Array.pod)\n* [Sq::Collections::Hash](lib/Sq/Collections/Hash.pod)\n* [Sq::Core::Option](lib/Sq/Core/Option.pod)\n* [Sq::Core::Result](lib/Sq/Core/Result.pod) (Partially implemented)\n* [Sq::Collections::Queue](lib/Sq/Collections/Queue.pm)\n* [Sq::Collections::List](lib/Sq/Collections/List.pm)\n* [Sq::Collections::Heap](lib/Sq/Collections/Heap.pod)\n\n# SUPPORT\n\nDevelopment project is on Github [Perl-Sq](https://github.com/DavidRaab/Sq)\n\nYou can find documentation for this module with the perldoc command.\n\n    perldoc Sq\n\nYou can also look for information at [my Blog on Perl Sq](https://davidraab.github.io/tags/sq/)\n\n# AUTHOR\n\nDavid Raab, **davidraab83 at gmail.com**\n\n# LICENSE AND COPYRIGHT\n\nThis software is Copyright (c) by David Raab.\n\nThis is free software, licensed under:\n\n  The MIT (X11) License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidraab%2Fsq","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidraab%2Fsq","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidraab%2Fsq/lists"}