diff --git a/docs/api/tutorials/start.md b/docs/api/tutorials/start.md new file mode 100644 index 00000000..43c366b7 --- /dev/null +++ b/docs/api/tutorials/start.md @@ -0,0 +1,206 @@ +## Red starter tutorial + +This document is an introduction tutorial which shows the most basic usage examples of Red. +For more in-depth introduction about Red architecture visit [Red architecture](tutorials/architecture) page. + +### Models and tables + +Red is an Object-Relational Mapping (ORM) tool for Perl 6. + +Simply speaking, it allows you to "hide" the layer of interaction +with your relational database and work with Perl 6 objects instead. + +Currently, PostgreSQL and SQLite3 relation databases are supported. + +Let's start with a simple table: + +```sql +CREATE TABLE person( + id integer NOT NULL PRIMARY KEY AUTOINCREMENT, + name varchar(255) NOT NULL +); +``` + +With this query executed in your database console, a table named `person` was created with +two columns: `id` which is an integer ID of the record, it is a primary key and is +incremented automatically, and `name` which is a string of maximum 255 characters, which is not null. + +In Red, each table is represented using a special type of class called "model". It can do +everything what a usual class can do, but also helps you to interact with your table. + +Red models use `model` keyword instead of `class`: + +```perl6 +model Person { + has Int $.id is serial; + has Str $.name is column is rw; +} + +my $*RED-DB = database “SQLite”; +``` + +We described a model called `Person`. The first attribute `$.id` is typed to be `Int` +and is marked with `is serial` trait. This trait marks the column as a primary one with +autoincrement enabled. The next attribute `$.name` is marked with `is column` trait, which +means this attribute will be mapped onto a column in the table, and is typed as Str. + +Note we don't need to specify that the column is not nullable, as it is the default. + +The second statement specifies a database to work with. In this case, +an in-memory SQLite database is used, which means all changes will be lost after +the script termination. To avoid this, we can specify a name for the database file: + +```perl6 +my $*RED-DB = database “SQLite”, database => 'test.sqlite3'; # Now a file `test.sqlite3` will be created +``` + +Next, we need to create a table itself: + + +```perl6 +Person.^create-table; +``` + +Methods marked with `^` are called "meta methods" and are used in Red +for all kinds of operations on models. In this case, calling `^create-table` +creates a table with name `person`. + +### Insertion of new records + +Let's insert a new record into it. In SQL it could be: + +```sql +INSERT INTO person (name) VALUES 'John'; +``` + +In Red we can express it this way: + +```perl6 +my $person = Person.^create(name => 'John'); +``` + +We call the `^create` method on type object `Person` and assign the result +to the `$person` variable. The assignment is not necessary: + +```perl6 +Person.^create(name => 'John'); +``` + +The `^create` method returns the created object to work with, though +this result can be simply ignored. + +The `$.id` attribute is auto-generated and there is no need to specify it, +while `$.name` attribute must not be null, so we have to specify it: + +```perl6 +Person.^create; # error +``` + +### Update of records + +Let's try to update our record. In SQL it could be: + +```sql +UPDATE person SET name = ‘John Doe’ WHERE id = 1; +``` + +To do the same in Red, we use setters and a call to `^save`: + +```perl6 +$person.name = 'John Doe'; +$person.^save; +``` + +All changes to an object that represents the record are lazy, +which means the database connection is not used until the `^save` +method is called. + +The method `^save` is useful not only for UPDATE operation, but it can be used on +INSERT too: + +```perl6 +my $person2 = Person.new(name => 'Joan'); # ^create is not used +$person2.^save; # does INSERT, not UPDATE +``` + +### Selecting records + +Lets add some more records: + +```perl6 +Person.new(name => "Paul"); # Method call with parentheses and an arrow pair +Person.new: :name; # Semicolon form of method call is used +``` + +Lets begin with selecting all records of the table: + +```sql +SELECT * FROM person; +``` + +In Red, `^all` method is used: + +```perl6 +for Person.^all -> $person { say $person } +``` + +Method `^all` returns an instance of class `Seq` that is a lazy sequence of records returned. + +```sql +SELECT * FROM person WHERE person.name like 'Jo%'; +``` + +The query above selects all records where name starts with 'Jo'. In Red, you can use Perl 6 `grep` +method to specify clauses of the select query: + +```perl6 +for Person.^all.grep(*.name.starts-with('Jo')) -> $person { say $person } +``` + +Note that this call chain will result into an equivalent of the SQL code above, +filtering values happens at the database level, not at Perl 6 level. + +```sql +SELECT * FROM person WHERE person.name like 'Jo%' AND person.id = 2; +``` + +To express the query above, calls to `grep` can be combined: + +```perl6 +for Person.^all.grep(*.name.starts-with('Jo')).grep(*.id == 2) -> $person { say $person } +``` + +### Selecting a single record + +To get a single record, `^load` method is used. It accepts an arbitrary number of pairs +describing the object properties for the WHERE clause of a SELECT statement. One difference +between using `^all` and `^load` is that the `^load` method returns either a value or a `Nil` +if there are no fitting records, while `^all` returns a `Seq` that might come +with an arbitrary number of elements. The second difference is that `^all` can express +various SELECT statements, while `^load` is restricted to work with columns marked as PRIMARY +and UNIQUE only. + +```perl6 +say Person.^load(id => 4); # correct +# when the primary column is unambiguous, only its value can be passed +say Person.^load(4); # correct, same as `id => 4` +# however, `^load does not work for non-primary columns: +say Person.^load(:name); # error +``` + +### Deleting rows + +To delete rows, the `^delete` method is used. It can be called on an individual +object or on a model to delete all records: + +```perl6 +# DELETE FROM person WHERE person.id = 42; +$p.^delete; +# DELETE FROM person; +Person.^delete; +``` + +Here, we covered basics of Red usage. Refer to Red cookbook +for different examples without a particular order or visit the next +tutorial in this series, related to expressing table relationships +using Red, [here](tutorials/relationships). \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 954f1fa2..09587a7c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,72 @@ -- [introduction](introduction) -- [general](https://github.com/FCO/Red/wiki) -- [API](API) \ No newline at end of file +### Red ORM documentation + +Welcome to Red ORM documentation. + +#### Tutorials + +If you are looking for tutorials, you can visit: + +* [Beginner tutorial](tutorials/start) +* Relationships tutorial +* Red CLI +* Working with PostgreSQL +* Working with SQLite +* Red Cookbook + +#### API documentation + +If you are looking for pure API docs, here you go: + +* [API Index page](API) + +#### Wiki + +More examples can be found at the project [Wiki](https://github.com/FCO/Red/wiki) page. + +#### For developers + +* [Red architecture](tutorials/architecture) +* How to create a new driver +* How to create a new cache + +#### How to contribute to documentation + +##### I want to document something + +To document an entity of Red itself (class, operator, +routine etc), do it as Pod6 in the source file. For example, before: + +```perl6 +sub prefix:<Σ>( *@number-list ) is export { + [+] @number-list +} +``` + +After: + +```perl6 +#| A prefix operator that sums numbers +#| It accepts an arbitrary length list of numbers and returns their sum +#| Example: say Σ (13, 16, 1); # 30 +sub prefix:<Σ>( *@number-list ) is export { + [+] @number-list +} +``` + +Then execute `perl6 tools/make-docs.p6` to generate documentation +pages with your symbols included. + +If you want to add a tutorial, write it as Markdown and add to `docs` +directory of this repository. + +##### I want to change existing documentation + +Depending on what it is, the documentation might be generated or not. + +* Try to run a search in the repository for a line you want to change, for example, `grep -R "bad typo" .` +* If you see more than two files, try to narrow your search pattern +* If you see two files found, most likely one will be corresponding to sources or generated Markdown documentation. Please, patch the documentation in sources and, after re-generating pages with `tools/make-docs.p6` script, send a PR. +* If you see a single file found, Markdown file with a tutorial or this introduction text, please, patch it and send a PR. +* When not sure, please, create a ticket at [Red bugtracker](https://github.com/FCO/Red/issues) + +All pull requests are welcome! diff --git a/docs/introduction.md b/docs/introduction.md deleted file mode 100644 index 74508a47..00000000 --- a/docs/introduction.md +++ /dev/null @@ -1,67 +0,0 @@ -### Red ORM documentation - -Welcome to Red ORM documentation. - -#### Tutorials - -If you are looking for tutorials, you can visit: - -* Beginner tutorial -* Relationships tutorial -* Red CLI -* Working with PostgreSQL -* Working with SQLite -* Red Cookbook - -#### API documentation - -If you are looking for pure API docs, here you go: - -* API Index page - -#### For developers - -* How to create a new driver -* How to create a new cache - -#### How to contribute to documentation - -##### I want to document something - -To document an entity of Red itself (class, operator, -routine etc), do it as Pod6 in the source file. For example, before: - -```perl6 -sub prefix:<Σ>( *@number-list ) is export { - [+] @number-list -} -``` - -After: - -```perl6 -#| A prefix operator that sums numbers -#| It accepts an arbitrary length list of numbers and returns their sum -#| Example: say Σ (13, 16, 1); # 30 -sub prefix:<Σ>( *@number-list ) is export { - [+] @number-list -} -``` - -Then execute `perl6 tools/make-docs.p6` to generate documentation -pages with your symbols included. - -If you want to add a tutorial, write it as Markdown and add to `docs` -directory of this repository. - -##### I want to change existing documentation - -Depending on what it is, the documentation might be generated or not. - -* Try to run a search in the repository for a line you want to change, for example, `grep -R "bad typo" .` -* If you see more than two files, try to narrow your search pattern -* If you see two files found, most likely one will be corresponding to sources or generated Markdown documentation. Please, patch the documentation in sources and, after re-generating pages with `tools/make-docs.p6` script, send a PR. -* If you see a single file found, Markdown file with a tutorial or this introduction text, please, patch it and send a PR. -* When not sure, please, create a ticket at [Red bugtracker](https://github.com/FCO/Red/issues) - -All pull requests are welcome! diff --git a/lib/MetamodelX/Red/Comparate.pm6 b/lib/MetamodelX/Red/Comparate.pm6 index 84f094a3..3993d6f5 100644 --- a/lib/MetamodelX/Red/Comparate.pm6 +++ b/lib/MetamodelX/Red/Comparate.pm6 @@ -4,7 +4,8 @@ use Red::Attr::Column; unit role MetamodelX::Red::Comparate; has SetHash $!added-method .= new; -#| Creates methods to return columns +#| An internal method that generates Red getters and setters for an +#| attribute $attr of a type. method add-comparate-methods(Mu:U \type, Red::Attr::Column $attr --> Empty) { unless $!added-method{"{ type.^name }|$attr"} { if $attr.rw { diff --git a/lib/MetamodelX/Red/Describable.pm6 b/lib/MetamodelX/Red/Describable.pm6 index 9743b876..b4d27619 100644 --- a/lib/MetamodelX/Red/Describable.pm6 +++ b/lib/MetamodelX/Red/Describable.pm6 @@ -15,23 +15,24 @@ method !create-column($_ --> Red::Cli::Column) { |(:references(%(table => .ref.attr.package.^table, column => .ref.name)) if .references) } -#| Return a `Red::Cli::Table` describing the table +#| Returns an object of type `Red::Cli::Table` that represents +#| a database table of the caller. method describe(\model --> Red::Cli::Table) { Red::Cli::Table.new: :name(self.table(model)), :model-name(self.name(model)), :columns(self.columns>>.column.map({self!create-column($_)}).cache) } -#| Returns the difference to transform this model to the database version +#| Returns the difference to transform this model to the database version. method diff-to-db(\model --> Red::Cli::Table) { model.^describe.diff: $*RED-DB.schema-reader.table-definition: model.^table } -#| Returns the difference to transform the DB table into this model +#| Returns the difference to transform the DB table into this model. method diff-from-db(\model --> Red::Cli::Table) { $*RED-DB.schema-reader.table-definition(model.^table).diff: model.^describe } -#| Returns the difference between two models +#| Returns the difference between two models. method diff(\model, \other-model --> Red::Cli::Table) { model.^describe.diff: other-model.^describe } diff --git a/lib/MetamodelX/Red/Dirtable.pm6 b/lib/MetamodelX/Red/Dirtable.pm6 index ec7010cb..086a927d 100644 --- a/lib/MetamodelX/Red/Dirtable.pm6 +++ b/lib/MetamodelX/Red/Dirtable.pm6 @@ -118,16 +118,19 @@ method compose-dirtable(Mu \type) { } } -#| Receives a Set of attributes ans set this attributes as dirt +#| Accepts a Set of attributes of model and enables dirtiness flag for them, +#| which means that the values were changed and need a database sync. multi method set-dirty(\obj, Set() $attr) { $!dirty-cols-attr.get_value(obj).{$_}++ for $attr.keys } -#| Returns `True` is the object is dirt +#| Returns `True` if any of the object attributes were changed +#| from original database record values. method is-dirty(Any:D \obj --> Bool) { so $!dirty-cols-attr.get_value(obj) } -#| Returns the dirt columns +#| Returns dirty columns of the object. method dirty-columns(Any:D \obj) { $!dirty-cols-attr.get_value(obj) } -#| Cleans up the object +#| Erases dirty status from all model's attributes, but does not (!) +#| revert their values to original ones. method clean-up(Any:D \obj) { $!dirty-cols-attr.set_value: obj, SetHash.new; $!dirty-old-values-attr.set_value: obj, {} diff --git a/lib/MetamodelX/Red/Id.pm6 b/lib/MetamodelX/Red/Id.pm6 index 08524bc3..63614648 100644 --- a/lib/MetamodelX/Red/Id.pm6 +++ b/lib/MetamodelX/Red/Id.pm6 @@ -24,7 +24,7 @@ method set-helper-attrs(Mu \type) { type.^add_attribute: $!id-values-attr; } -#| Returns if the given attr is an id +#| Checks if the given attribute is a primary key of the model. multi method is-id($, Red::Attr::Column $attr) { so $attr.column.id } @@ -36,7 +36,7 @@ method id(Mu \type) { self.columns(type).grep(*.column.id).list } -#| get a list of ids and uniques +#| Returns a list of attributes that are either primary keys or marked as unique. method general-ids(\model) { (|model.^id, |model.^unique-constraints) } diff --git a/lib/MetamodelX/Red/Migration.pm6 b/lib/MetamodelX/Red/Migration.pm6 index 3f10100c..d43d3737 100644 --- a/lib/MetamodelX/Red/Migration.pm6 +++ b/lib/MetamodelX/Red/Migration.pm6 @@ -6,12 +6,12 @@ unit role MetamodelX::Red::Migration; my Callable @migration-blocks; my Pair @migrations; -#| Creates a new migration +#| Creates a new migration for the model. multi method migration(\model, &migr) { @migration-blocks.push: &migr } -#| Runs migrations +#| Executes migrations. multi method migrate(\model, Red::Model:U :$from) { my Red::Attr::Column %old-cols = $from.^columns.map: { .name.substr(2) => $_ }; my Str @new-cols = model.^columns.map: { .name.substr(2) }; @@ -34,7 +34,7 @@ multi method migrate(\model, Red::Model:U :$from) { .(Type) for @migration-blocks } -#| Prints the migrations +#| Prints the migrations. method dump-migrations(|) { say "{ .key } => { .value.gist }" for @migrations } diff --git a/lib/MetamodelX/Red/Model.pm6 b/lib/MetamodelX/Red/Model.pm6 index 588f8e1f..747f86d0 100644 --- a/lib/MetamodelX/Red/Model.pm6 +++ b/lib/MetamodelX/Red/Model.pm6 @@ -44,16 +44,16 @@ has @!constraints; has $.table; has Bool $!temporary; -#| Returns a list of columns names +#| Returns a list of columns names.of the model. method column-names(|) { @!columns>>.column>>.name } -#| Returns a hash of contraints classified by type +#| Returns a hash of model constraints classified by type. method constraints(|) { @!constraints.unique.classify: *.key, :as{ .value } } -#| Returns a hash of foreign keys +#| Returns a hash of foreign keys of the model. method references(|) { %!references } -#| Returns the table name +#| Returns the table name for the model. method table(Mu \type) is rw { $!table //= camel-to-snake-case type.^name } #| Returns the table alias @@ -82,7 +82,7 @@ method id-values(Red::Model:D $model) { self.id($model).map({ .get_value: $model }).list } -#| Is it nullable by default? +#| Check if the model is nullable by default. method default-nullable(|) is rw { $ //= False } #| Returns all columns with the unique counstraint @@ -146,28 +146,28 @@ method compose(Mu \type) { } } -#| Creates a new reference (foreign key) +#| Creates a new reference (foreign key). method add-reference($name, Red::Column $col) { %!references{$name} = $col } -#| Creates a new unique constraint +#| Creates a new unique constraint. method add-unique-constraint(Mu:U \type, &columns) { @!constraints.push: "unique" => columns(type) } -#| Creates a new unique constraint +#| Creates a new primary key constraint. multi method add-pk-constraint(Mu:U \type, &columns) { nextwith type, columns(type) } -#| Creates the primary key +#| Creates the primary key constraint. multi method add-pk-constraint(Mu:U \type, @columns) { @!constraints.push: "pk" => @columns } my UInt $alias_num = 1; -#| Creates a new alias for the model +#| Creates a new alias for the model. method alias(Red::Model:U \type, Str $name = "{type.^name}_{$alias_num++}") { my \alias = ::?CLASS.new_type(:$name); my role RAlias[Red::Model:U \rtype, Str $rname] { @@ -195,7 +195,7 @@ method alias(Red::Model:U \type, Str $name = "{type.^name}_{$alias_num++}") { alias } -#| Creates a new column +#| Creates a new column and adds it to the model. method add-column(::T Red::Model:U \type, Red::Attr::Column $attr) { if @!columns ∌ $attr { @!columns.push: $attr; diff --git a/lib/MetamodelX/Red/OnDB.pm6 b/lib/MetamodelX/Red/OnDB.pm6 index 31e1664f..9d4507db 100644 --- a/lib/MetamodelX/Red/OnDB.pm6 +++ b/lib/MetamodelX/Red/OnDB.pm6 @@ -18,7 +18,10 @@ method set-helper-attrs(Mu \type) { type.^add_attribute: $!is-on-db-attr; } -#| Is that object on DB? +#| Checks if the instance of model has a record in the database or not. +#| For example, `Person.^create(...).^is-on-db` returns True, because `^create` was called, +#| but `Person.new(...).^is-on-db` will return False, because the created object does not have +#| a representation in the database without calls to `^create` or `^save` done. multi method is-on-db(\instance) { $!is-on-db-attr.get_value(instance) } @@ -27,4 +30,3 @@ multi method is-on-db(\instance) { multi method saved-on-db(\instance) { $!is-on-db-attr.get_value(instance) = True } - diff --git a/lib/MetamodelX/Red/Relationship.pm6 b/lib/MetamodelX/Red/Relationship.pm6 index b3e13926..49dfd1aa 100644 --- a/lib/MetamodelX/Red/Relationship.pm6 +++ b/lib/MetamodelX/Red/Relationship.pm6 @@ -38,24 +38,24 @@ method prepare-relationships(::Type Mu \type) { } } -#| Adds a new relationship +#| Adds a new relationship by column. multi method add-relationship(Mu:U $self, Attribute $attr, Str :$column!, Str :$model!, Str :$require = $model ) { self.add-relationship: $self, $attr, { ."$column"() }, :$model, :$require } -#| Adds a new relationship +#| Adds a new relationship by reference. multi method add-relationship(Mu:U $self, Attribute $attr, &reference, Str :$model, Str :$require = $model) { $attr does Red::Attr::Relationship[&reference, :$model, :$require]; self.add-relationship: $self, $attr } -#| Adds a new relationship +#| Adds a new relationship by two references. multi method add-relationship(Mu:U $self, Attribute $attr, &ref1, &ref2, Str :$model, Str :$require = $model) { $attr does Red::Attr::Relationship[&ref1, &ref2, :$model, :$require]; self.add-relationship: $self, $attr } -#| Adds a new relationship +#| Adds a new relationship using an attribute of type `Red::Attr::Relationship`. multi method add-relationship(::Type Mu:U $self, Red::Attr::Relationship $attr) { %!relationships ∪= $attr; my $name = $attr.name.substr: 2; diff --git a/lib/Red/Database.pm6 b/lib/Red/Database.pm6 index e2a1e041..7a981338 100644 --- a/lib/Red/Database.pm6 +++ b/lib/Red/Database.pm6 @@ -2,14 +2,21 @@ use Red::Driver; proto database(|c) is export { * } -#| Receives a driver name and parameters to creates it +#| Accepts an SQL driver name and parameters and uses them to create +#| an instance of `Red::Driver` class. +#| The driver name is used to specify a particular driver from +#| `Red::Driver::` family of modules, so `SQLite` results in +#| constructing an instance of `Red::Driver::SQLite` class. +#| All subsequent attributes after the driver name will be +#| directly passed to the constructor of the driver. multi sub database(Str $type, |c --> Red::Driver) is export { my $driver-name = "Red::Driver::$type"; require ::($driver-name); my Red::Driver $driver = ::($driver-name).new: |c; } -#| Receives a driver name and a dbh and creates a driver +#| Accepts an SQL driver name and a database handle, and +#| creates an instance of `Red::Driver` passing it the handle. multi sub database(Str $type, $dbh --> Red::Driver) { my $driver-name = "Red::Driver::$type"; require ::($driver-name); diff --git a/lib/Red/Driver/SQLite/SchemaReader.pm6 b/lib/Red/Driver/SQLite/SchemaReader.pm6 index 73794f5a..6ae02d68 100644 --- a/lib/Red/Driver/SQLite/SchemaReader.pm6 +++ b/lib/Red/Driver/SQLite/SchemaReader.pm6 @@ -1,7 +1,7 @@ use Red::SchemaReader; use Red::Driver::SQLite::SQLiteMaster; -#| class to read SQLite schema +#| An internal class to read SQLite schemes. unit class Red::Driver::SQLite::SchemaReader; also does Red::SchemaReader; diff --git a/lib/Red/Traits.pm6 b/lib/Red/Traits.pm6 index e199607d..b4b6c451 100644 --- a/lib/Red/Traits.pm6 +++ b/lib/Red/Traits.pm6 @@ -8,7 +8,7 @@ unit module Red::Traits; #| This trait marks the corresponding table of the #| model as TEMPORARY (so it only exists for the time -#| of Red being connected to the database) +#| of Red being connected to the database). multi trait_mod:(Mu:U $model, Bool :$temp! --> Empty) { $model.^temp = True; } @@ -39,13 +39,13 @@ multi trait_mod:(Attribute $attr, Str :$column! --> Empty) is export { trait_mod:($attr, :column{:name($column)}) if $column } -#| This trait marks an attribute (column) as SQL PRIMARY KEY +#| This trait marks an attribute (column) as SQL PRIMARY KEY. multi trait_mod:(Attribute $attr, Bool :$id! where $_ == True --> Empty) is export { trait_mod:($attr, :column{:id, :!nullable}) } #| This trait marks an attribute (column) as SQL PRIMARY KEY with SERIAL data type, which -#| means it auto-increments on each insertion +#| means it auto-increments on each insertion. multi trait_mod:(Attribute $attr, Bool :$serial! where $_ == True --> Empty) is export { trait_mod:($attr, :column{:id, :!nullable, :auto-increment}) } @@ -74,7 +74,7 @@ multi trait_mod:(Attribute $attr, :$referencing! (Str :$model!, Str :$column #| This trait allows setting a custom name for a table corresponding to a model. #| For example, `model MyModel is table {}` will use `custom_table_name` -#| as the name of the underlying database table +#| as the name of the underlying database table. multi trait_mod:(Mu:U $model, Str :$table! where .chars > 0 --> Empty) { $model.HOW.^attributes.first({ .name eq '$!table' }).set_value($model.HOW, $table) } diff --git a/lib/Red/Utils.pm6 b/lib/Red/Utils.pm6 index fa119aae..2bbc8c95 100644 --- a/lib/Red/Utils.pm6 +++ b/lib/Red/Utils.pm6 @@ -1,14 +1,14 @@ =head2 Red::Utils -#| Accepts a string and converts snake case (`foo_bar`) into kebab case (`foo-bar`) +#| Accepts a string and converts snake case (`foo_bar`) into kebab case (`foo-bar`). sub snake-to-kebab-case(Str() $_ --> Str) is export { S:g/'_'/-/ } -#| Accepts a string and converts kebab case (`foo-bar`) into snake case (`foo_bar`) +#| Accepts a string and converts kebab case (`foo-bar`) into snake case (`foo_bar`). sub kebab-to-snake-case(Str() $_ --> Str) is export { S:g/'-'/_/ } -#| Accepts a string and converts camel case (`fooBar`) into snake case (`foo_bar`) +#| Accepts a string and converts camel case (`fooBar`) into snake case (`foo_bar`). sub camel-to-snake-case(Str() $_ --> Str) is export { kebab-to-snake-case lc S:g/(\w)>/$0_/ } -#| Accepts a string and converts camel case (`fooBar`) into kebab case (`foo-bar`) +#| Accepts a string and converts camel case (`fooBar`) into kebab case (`foo-bar`). sub camel-to-kebab-case(Str() $_ --> Str) is export { lc S:g/(\w)>/$0-/ } -#| Accepts a string and converts kebab case (`foo-bar`) into camel case (`fooBar`) +#| Accepts a string and converts kebab case (`foo-bar`) into camel case (`fooBar`). sub kebab-to-camel-case(Str() $_ --> Str) is export { S:g/"-"(\w)/{$0.uc}/ with .wordcase } -#| Accepts a string and converts snake case (`foo_bar`) into camel case (`fooBar`) +#| Accepts a string and converts snake case (`foo_bar`) into camel case (`fooBar`). sub snake-to-camel-case(Str() $_ --> Str) is export { S:g/"_"(\w)/{$0.uc}/ with .wordcase }