power_stencil 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +194 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/bin/update_plugin_template +79 -0
  13. data/doc/builds.md +267 -0
  14. data/doc/entities.md +964 -0
  15. data/doc/example_use_cases.md +59 -0
  16. data/doc/images/power-stencil-architecture.svg +151 -0
  17. data/doc/images/power-stencil-entity-build.svg +481 -0
  18. data/doc/images/power-stencil-entity-creation.svg +375 -0
  19. data/doc/images/power-stencil-project-overview.svg +2534 -0
  20. data/doc/images/power-stencil-simple-flow.svg +265 -0
  21. data/doc/plugins.md +169 -0
  22. data/doc/templates.md +332 -0
  23. data/etc/base_commands_definition.yml +259 -0
  24. data/etc/meta_templates/plugin_seed/etc/command_line.yaml +12 -0
  25. data/etc/meta_templates/plugin_seed/etc/plugin_config.yaml +4 -0
  26. data/etc/meta_templates/plugin_seed/etc/templates/.git_keep +0 -0
  27. data/etc/meta_templates/plugin_seed/lib/{entity}.rb.erb +34 -0
  28. data/etc/meta_templates/plugin_seed/lib/{entity}/entity_definitions/{entity}_entity.rb.erb +11 -0
  29. data/etc/meta_templates/plugin_seed/lib/{entity}/plugin_helper.rb.erb +17 -0
  30. data/etc/meta_templates/plugin_seed/lib/{entity}/version.rb.erb +3 -0
  31. data/etc/meta_templates/plugin_seed/lib/{entity}/{entity}_processor.rb.erb +19 -0
  32. data/etc/meta_templates/plugin_seed/psplugin_{entity}.gemspec +36 -0
  33. data/etc/power_stencil.yaml +92 -0
  34. data/etc/templates/plugin_definition/.gitignore +11 -0
  35. data/etc/templates/plugin_definition/.rspec +3 -0
  36. data/etc/templates/plugin_definition/.travis.yml +5 -0
  37. data/etc/templates/plugin_definition/CODE_OF_CONDUCT.md +74 -0
  38. data/etc/templates/plugin_definition/Gemfile +6 -0
  39. data/etc/templates/plugin_definition/LICENSE.txt +21 -0
  40. data/etc/templates/plugin_definition/README.md +43 -0
  41. data/etc/templates/plugin_definition/Rakefile +6 -0
  42. data/etc/templates/plugin_definition/bin/console +14 -0
  43. data/etc/templates/plugin_definition/bin/setup +8 -0
  44. data/etc/templates/plugin_definition/etc/command_line.yaml +12 -0
  45. data/etc/templates/plugin_definition/etc/plugin_config.yaml +4 -0
  46. data/etc/templates/plugin_definition/etc/templates/.git_keep +1 -0
  47. data/etc/templates/plugin_definition/lib/{entity}.rb.erb +34 -0
  48. data/etc/templates/plugin_definition/lib/{entity}/entity_definitions/{entity}_entity.rb.erb +11 -0
  49. data/etc/templates/plugin_definition/lib/{entity}/plugin_helper.rb.erb +17 -0
  50. data/etc/templates/plugin_definition/lib/{entity}/version.rb.erb +3 -0
  51. data/etc/templates/plugin_definition/lib/{entity}/{entity}_processor.rb.erb +19 -0
  52. data/etc/templates/plugin_definition/psplugin_{entity}.gemspec +36 -0
  53. data/etc/templates/plugin_definition/spec/spec_helper.rb +14 -0
  54. data/etc/templates/plugin_definition/spec/{entity}_spec.rb +9 -0
  55. data/etc/templates/project/.copy_ignore +2 -0
  56. data/etc/templates/project/.gitignore.erb +6 -0
  57. data/etc/templates/project/.ps_project/entities/.gitkeep +0 -0
  58. data/etc/templates/project/.ps_project/entities/README.md +11 -0
  59. data/etc/templates/project/.ps_project/entity_definitions/.gitkeep +0 -0
  60. data/etc/templates/project/.ps_project/entity_definitions/README.md +15 -0
  61. data/etc/templates/project/.ps_project/personal-config.yaml +8 -0
  62. data/etc/templates/project/.ps_project/plugins/.gitkeep +0 -0
  63. data/etc/templates/project/.ps_project/templates-templates/.gitkeep +0 -0
  64. data/etc/templates/project/.ps_project/templates-templates/README.md +13 -0
  65. data/etc/templates/project/.ps_project/user_entities/.gitkeep +0 -0
  66. data/etc/templates/project/.ps_project/user_entities/README.md +12 -0
  67. data/etc/templates/project/.ps_project/versioned-config.yaml +6 -0
  68. data/etc/templates/simple_exec/main.sh +4 -0
  69. data/exe/power_stencil +28 -0
  70. data/lib/power_stencil.rb +53 -0
  71. data/lib/power_stencil/command_processors/build.rb +43 -0
  72. data/lib/power_stencil/command_processors/check.rb +35 -0
  73. data/lib/power_stencil/command_processors/create.rb +70 -0
  74. data/lib/power_stencil/command_processors/delete.rb +38 -0
  75. data/lib/power_stencil/command_processors/edit.rb +35 -0
  76. data/lib/power_stencil/command_processors/entity_helper.rb +105 -0
  77. data/lib/power_stencil/command_processors/get.rb +22 -0
  78. data/lib/power_stencil/command_processors/info.rb +41 -0
  79. data/lib/power_stencil/command_processors/init.rb +47 -0
  80. data/lib/power_stencil/command_processors/new_plugin.rb +31 -0
  81. data/lib/power_stencil/command_processors/root.rb +27 -0
  82. data/lib/power_stencil/command_processors/shell.rb +37 -0
  83. data/lib/power_stencil/command_processors/trace_helper.rb +20 -0
  84. data/lib/power_stencil/dsl/base.rb +24 -0
  85. data/lib/power_stencil/dsl/entities.rb +46 -0
  86. data/lib/power_stencil/dsl/plugin_generation.rb +17 -0
  87. data/lib/power_stencil/engine/base.rb +50 -0
  88. data/lib/power_stencil/engine/build_handling.rb +77 -0
  89. data/lib/power_stencil/engine/directory_processor.rb +111 -0
  90. data/lib/power_stencil/engine/entities_definitions.rb +42 -0
  91. data/lib/power_stencil/engine/entities_handling.rb +76 -0
  92. data/lib/power_stencil/engine/entity_engine.rb +20 -0
  93. data/lib/power_stencil/engine/init_engine.rb +18 -0
  94. data/lib/power_stencil/engine/project_engine.rb +75 -0
  95. data/lib/power_stencil/engine/renderers/erb.rb +21 -0
  96. data/lib/power_stencil/error.rb +20 -0
  97. data/lib/power_stencil/initializer.rb +87 -0
  98. data/lib/power_stencil/plugins/base.rb +54 -0
  99. data/lib/power_stencil/plugins/capabilities.rb +30 -0
  100. data/lib/power_stencil/plugins/command_line.rb +27 -0
  101. data/lib/power_stencil/plugins/config.rb +32 -0
  102. data/lib/power_stencil/plugins/dependencies.rb +32 -0
  103. data/lib/power_stencil/plugins/gem.rb +57 -0
  104. data/lib/power_stencil/plugins/require.rb +77 -0
  105. data/lib/power_stencil/plugins/templates.rb +21 -0
  106. data/lib/power_stencil/project/base.rb +79 -0
  107. data/lib/power_stencil/project/config.rb +54 -0
  108. data/lib/power_stencil/project/create.rb +30 -0
  109. data/lib/power_stencil/project/info.rb +72 -0
  110. data/lib/power_stencil/project/paths.rb +119 -0
  111. data/lib/power_stencil/project/plugins.rb +89 -0
  112. data/lib/power_stencil/project/proxy.rb +13 -0
  113. data/lib/power_stencil/project/templates.rb +56 -0
  114. data/lib/power_stencil/project/versioning.rb +29 -0
  115. data/lib/power_stencil/system_entity_definitions/all.rb +14 -0
  116. data/lib/power_stencil/system_entity_definitions/buildable.rb +23 -0
  117. data/lib/power_stencil/system_entity_definitions/entity_override.rb +8 -0
  118. data/lib/power_stencil/system_entity_definitions/entity_project_common.rb +33 -0
  119. data/lib/power_stencil/system_entity_definitions/has_associated_files.rb +13 -0
  120. data/lib/power_stencil/system_entity_definitions/non_persistent.rb +14 -0
  121. data/lib/power_stencil/system_entity_definitions/plugin.rb +17 -0
  122. data/lib/power_stencil/system_entity_definitions/process_descriptor.rb +15 -0
  123. data/lib/power_stencil/system_entity_definitions/project_config.rb +24 -0
  124. data/lib/power_stencil/system_entity_definitions/project_entity.rb +16 -0
  125. data/lib/power_stencil/system_entity_definitions/simple_exec.rb +47 -0
  126. data/lib/power_stencil/utils/directory_processor.rb +54 -0
  127. data/lib/power_stencil/utils/file_edit.rb +87 -0
  128. data/lib/power_stencil/utils/file_helper.rb +56 -0
  129. data/lib/power_stencil/utils/gem_utils.rb +13 -0
  130. data/lib/power_stencil/utils/secure_require.rb +26 -0
  131. data/lib/power_stencil/utils/semantic_version.rb +128 -0
  132. data/lib/power_stencil/version.rb +3 -0
  133. data/power_stencil.gemspec +32 -0
  134. metadata +287 -0
@@ -0,0 +1,964 @@
1
+ Entities
2
+ ========
3
+
4
+ <!-- TOC -->
5
+
6
+ - [What are entities ?](#what-are-entities-)
7
+ - [Default entity types](#default-entity-types)
8
+ - [Default entities](#default-entities)
9
+ - [Structure overview](#structure-overview)
10
+ - [Loose schema entities](#loose-schema-entities)
11
+ - [Manipulating entities](#manipulating-entities)
12
+ - [Checking entities](#checking-entities)
13
+ - [Querying entities](#querying-entities)
14
+ - [Creating entities](#creating-entities)
15
+ - [Versioned entities](#versioned-entities)
16
+ - [Local (unversioned) entities](#local-unversioned-entities)
17
+ - [Updating entities](#updating-entities)
18
+ - [Deleting entities](#deleting-entities)
19
+ - [The `PowerStencil` shell](#the-powerstencil-shell)
20
+ - [Creating custom entity types](#creating-custom-entity-types)
21
+ - [Anatomy of an entity type](#anatomy-of-an-entity-type)
22
+ - [Where do I create new entity types ?](#where-do-i-create-new-entity-types-)
23
+ - [My first custom entity](#my-first-custom-entity)
24
+ - [Directives](#directives)
25
+ - [entity_type](#entity_type)
26
+ - [auto_named_entity_type](#auto_named_entity_type)
27
+ - [field](#field)
28
+ - [has_one](#has_one)
29
+ - [has_many](#has_many)
30
+ - [is_array](#is_array)
31
+ - [is_hash](#is_hash)
32
+ - [not_null](#not_null)
33
+ - [not_empty](#not_empty)
34
+ - [should_match](#should_match)
35
+ - [class_name](#class_name)
36
+ - [buildable / buildable_by](#buildable--buildable_by)
37
+ - [Module you could include in your entity types](#module-you-could-include-in-your-entity-types)
38
+ - [Adding functional code](#adding-functional-code)
39
+
40
+ <!-- /TOC -->
41
+ [:back:][Documentation root]
42
+
43
+ # What are entities ?
44
+
45
+ In a nutshell, `entities` are [Ruby] objects persisted into [YAML] files, but unless you have the need the create your own entity definitions (entity types), you don't really need to know the internals as you can fully manage entities without even knowing anything about code.
46
+
47
+ There is a lot to read there but you may find surprisingly easy how it is to master entities...
48
+
49
+ ## Default entity types
50
+
51
+ The `power_stencil` CLI provides a number of options to interact with entities. But first let's have a look at default entities and entities definitions. The `power_stencil info` is the perfect command for that:
52
+
53
+ ```shell
54
+ $ power_stencil info
55
+ --------------------------------------------------------------------------------
56
+ PROJECT REPORT
57
+ --------------------------------------------------------------------------------
58
+ General information:
59
+ - Project required version: 0.2.18
60
+ - PowerStencil version: 0.2.18
61
+ --------------------------------------------------------------------------------
62
+ Paths:
63
+ - Project root path: '/tmp/tst2'
64
+ - Project configuration path: '/tmp/tst2/.ps_project'
65
+ - Local user configuration file: '/tmp/tst2/.ps_project/personal-config.yaml'
66
+ - Project (versioned) configuration file: '/tmp/tst2/.ps_project/versioned-config.yaml'
67
+ - Project entities: '/tmp/tst2/.ps_project/entities'
68
+ - User (unversioned) entities: '/tmp/tst2/.ps_project/user_entities'
69
+ - Project specific entity definitions: '/tmp/tst2/.ps_project/entity_definitions'
70
+ --------------------------------------------------------------------------------
71
+ Entities:
72
+ - Contains 1 entities.
73
+ - project_config: 1
74
+ --------------------------------------------------------------------------------
75
+ Available entity types:
76
+ - Type 'base_entity' --> PowerStencil::SystemEntityDefinitions::ProjectEntity
77
+ - Type 'entity_override' --> UniverseCompiler::Entity::Override
78
+ - Type 'plugin_definition' --> PowerStencil::SystemEntityDefinitions::Plugin (template-template path: '/opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/power_stencil-0.2.18/etc/templates/plugin_definition')
79
+ - Type 'process_descriptor' --> PowerStencil::SystemEntityDefinitions::ProcessDescriptor
80
+ - Type 'project_config' --> PowerStencil::SystemEntityDefinitions::ProjectConfig
81
+ - Type 'simple_exec' --> PowerStencil::SystemEntityDefinitions::SimpleExec (template-template path: '/opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/power_stencil-0.2.18/etc/templates/simple_exec')
82
+ ```
83
+
84
+ It provides a lot of information about the project, but let's focus on the `Entities` and `Available entity types` parts of the report.
85
+
86
+ You can see here above that only 6 types of entity are defined by default in a brand new created `PowerStencil` project. You can find them below with some of their core features:
87
+
88
+ | entity type | system | persisted | buildable | has associated files |
89
+ |-------------|:------:|:---------:|:---------:|:--------------------:|
90
+ |base_entity||X
91
+ |entity_override||X
92
+ |plugin_definition|X
93
+ |process_descriptor||X
94
+ |project_config|X
95
+ |simple_exec||X|X|X
96
+
97
+ - You should not care about _system_ entities unless you are developing features in `PowerStencil`.
98
+ - By default any entity type, including those you may create, is _persistent_.
99
+ - A _buildable_ entity is an entity you can run `power_stencil build` against. More about this in the [builds] part.
100
+ - When an entity _has associated files_, it means its [templates] are in `<project_root>/<entity_type>/<entity_name>` folder. We'll talk about this more in depth in the [templates] and [builds] part.
101
+
102
+ On top of this `power_stencil info` brings you information about the actual Ruby classes implementing those entity types.
103
+
104
+ For a typical project which goal is only to generate some files from templates, the only entity types you may need are:
105
+
106
+ - base_entity
107
+ - entity_override
108
+ - simple_exec
109
+
110
+ More information about this in [builds].
111
+
112
+
113
+ ## Default entities
114
+
115
+ From the `power_stencil info` command output another important information, is that it says that the project contains only one entity, and that this entity is of the type `project_config`.
116
+
117
+ `project_config` is a special entity type containing the full config of the project as seen by `PowerStencil`, we will here after how to query it (like any other entity). But there are 2 very important things to note about this special entity:
118
+
119
+ - First, this singleton entity will be available at [build time][builds] from any [template][templates].
120
+ - You can add your own properties in this entity by adding properties to either the project (versioned) configuration file, or to the local user configuration file (see in the `Paths` part of the `power_stencil info` output).
121
+
122
+
123
+ ## Structure overview
124
+
125
+ As said in introduction, `entities` are persisted as [YAML] files, and therefore only a light knowledge of the [YAML] format is required.
126
+
127
+ The type of an entity is actually not part of the entity YAML properties, but it is determined when you [create a new entity](#creating-entities), and it appears at the top of the generated YAML file as a reference to the Ruby class representing the entity type (the ones you can see in the output of `power_stencil info`).
128
+ Then the YAML file should at least contain the `:name` property.
129
+ So for a `basic_entity` which is the most simple entity type, the minimalistic generated YAML file may look like:
130
+
131
+ ```yaml
132
+ --- !ruby/object:PowerStencil::SystemEntityDefinitions::ProjectEntity
133
+ :name: my_1st_entity
134
+ ```
135
+
136
+ The `:name` property may contain any string including space or weird characters. It just has to be a valid YAML string...
137
+
138
+ :warning: You may have noticed the leading `:` in the YAML file for the name property. We will come back to this in [Creating custom entity types](#creating-custom-entity-types), but a good pratice would be to prefix all your properties with this colon `:` character. Although it is not always mandatory it will ease the readability of your [templates]. Yet it is mandatory for the name property.
139
+
140
+ :information_source: Each entity has an optional `:description` property.
141
+
142
+ **:information_source: Each entity has a unique ID which is of the form: `<entity_type>/<entity_name>`**
143
+
144
+ :warning: Meaning that **for a particular type of entity, its name has to be unique** !
145
+
146
+ Any valid YAML is actually a valid entity !
147
+
148
+ ## Loose schema entities
149
+
150
+ An interesting feature of `PowerStencil`, is that we could describe it as _loose-schema enforced_. What does it mean ? Actually entity types can define some of their properties as mandatory, and the rest is completely free !
151
+ For example the `basic_entity` has only `:name` declared mandatory property, and for the rest you do whatever you want.
152
+
153
+ So as a result the following YAML is a perfectly valid `basic_entity`:
154
+
155
+ ```yaml
156
+ --- !ruby/object:PowerStencil::SystemEntityDefinitions::ProjectEntity
157
+ :name: my_2nd_entity
158
+ :an_array_property:
159
+ - item 1
160
+ - item 2
161
+ :a_hash_property:
162
+ item1: value1
163
+ item2: value2
164
+ a_non_colon_prefixed_property: blah blah blah
165
+ ```
166
+ You will be able to access all those properties in your [templates]. Even those not known by the entity type. Only the way you will access them may differ a bit. We will cover that more in detail when we will create [custom entity types](#creating-custom-entity-types).
167
+
168
+
169
+ # Manipulating entities
170
+
171
+ ## Checking entities
172
+
173
+ We have seen the `power_stencil info` returns (among other) the number of entities present in the repository (grouped by entity type). But we can have a bit more information about each entity using the `power_stencil check` command:
174
+
175
+ ```shell
176
+ $ power_stencil check
177
+ RAW ENTITIES
178
+ 'base_entity/my_1st_entity':
179
+ - Storage path: '/tmp/tst2/.ps_project/entities/base_entity/my_1st_entity.yaml'
180
+ - Status : Valid
181
+ - Buildable : false
182
+ 'base_entity/my_2nd_entity':
183
+ - Storage path: '/tmp/tst2/.ps_project/entities/base_entity/my_2nd_entity.yaml'
184
+ - Status : Valid
185
+ - Buildable : false
186
+ 'project_config/Project Config':
187
+ - Storage path: ''
188
+ - Status : Valid
189
+ - Buildable : false
190
+ ```
191
+
192
+ The goal of this command is normally to check the validity of entities, but it brings as well extra information like if the entity is _buildable_ or even where it is stored in the repository.
193
+
194
+ :information_source: You can notice that the `project_config` entity has no storage path, the reason being [it is not persisted](#default-entity-types).
195
+
196
+ This command may show some extra information, for more complex entities. We will see that later on.
197
+
198
+ By default `power_stencil check` will check all entities in the repository but you can specify on the command line an arbitrary list of entities:
199
+
200
+ ```shell
201
+ $ power_stencil check base_entity/my_1st_entity 'project_config/Project Config'
202
+ RAW ENTITIES
203
+ 'base_entity/my_1st_entity':
204
+ - Storage path: '/tmp/tst2/.ps_project/entities/base_entity/my_1st_entity.yaml'
205
+ - Status : Valid
206
+ - Buildable : false
207
+ 'project_config/Project Config':
208
+ - Storage path: ''
209
+ - Status : Valid
210
+ - Buildable : false
211
+ ```
212
+ Another possibility is to check a list of objects using a regular expression by using the `--regexp` option:
213
+
214
+ ```shell
215
+ $ power_stencil check base_ --regexp
216
+ RAW ENTITIES
217
+ 'base_entity/my_1st_entity':
218
+ - Storage path: '/tmp/tst2/.ps_project/entities/base_entity/my_1st_entity.yaml'
219
+ - Status : Valid
220
+ - Buildable : false
221
+ 'base_entity/my_2nd_entity':
222
+ - Storage path: '/tmp/tst2/.ps_project/entities/base_entity/my_2nd_entity.yaml'
223
+ - Status : Valid
224
+ - Buildable : false
225
+ ```
226
+
227
+ ## Querying entities
228
+
229
+ The command to query entities from the repository is `power_stencil get`, and it works basically like the `power_stencil check` command.
230
+
231
+ No parameter will return all entities, you can pass multiple ids or regexp on the command line.
232
+
233
+ By default it will return entities in a format closer to their Ruby counterpart:
234
+
235
+ ```shell
236
+ $ power_stencil get base_entity/my_2nd_entity
237
+ ---
238
+ PowerStencil::SystemEntityDefinitions::ProjectEntity:
239
+ :type: :base_entity
240
+ :fields:
241
+ :name: my_2nd_entity
242
+ :an_array_property:
243
+ - item 1
244
+ - item 2
245
+ :a_hash_property:
246
+ item1: value1
247
+ item2: value2
248
+ a_non_colon_prefixed_property: blah blah blah
249
+ ```
250
+ We will see later why this format.
251
+
252
+ But you can get the exact YAML version as well using the `--raw` option:
253
+
254
+ ```shell
255
+ $ power_stencil get base_entity/my_2nd_entity --raw
256
+ --- !ruby/object:PowerStencil::SystemEntityDefinitions::ProjectEntity
257
+ :name: my_2nd_entity
258
+ :an_array_property:
259
+ - item 1
260
+ - item 2
261
+ :a_hash_property:
262
+ item1: value1
263
+ item2: value2
264
+ a_non_colon_prefixed_property: blah blah blah
265
+ ```
266
+
267
+ You can also get the names only, which is basically only interesting if you query using a regexp:
268
+
269
+ ```shell
270
+ $ power_stencil get base_entity --names-only --regexp
271
+ - my_2nd_entity
272
+
273
+ - my_1st_entity
274
+ ```
275
+ There are 2 other options (`--compiled` and `--scenario`) but they will be covered in the scope of [builds].
276
+
277
+ ## Creating entities
278
+
279
+ ### Versioned entities
280
+
281
+ You can create new entities in the repository using the `power_stencil create` command.
282
+
283
+ ```
284
+ $ power_stencil create base_entity/my_3rd_entity
285
+ Created 'base_entity/my_3rd_entity'
286
+ ```
287
+
288
+ You can also immediately edit the generated entity using the `--edit` option, which is particularly useful if the entity to created requires some mandatory properties. `PowerStencil` will not let you create invalid entities, so you may have to fill in all mandatory properties before saving.
289
+
290
+ The editor used can be specified by multiple ways:
291
+
292
+ - In the `EDITOR` environment variable
293
+ - In one of the two project config files using the `:editor` property
294
+ - Directly on the command line using the `--editor` option
295
+
296
+ After you saved the entity and exited the editor, if the entity you saved is invalid, `PowerStencil` will reopen the file to give you a chance to fix the issue.
297
+
298
+ By default the number of retries is specified in the config by the `:max_file_edit_retry_times` property. You can check that in the
299
+ `project_config/Project Config` entity by issuing:
300
+
301
+ $ power_stencil get 'project_config/Project Config'
302
+
303
+ And check the `:max_file_edit_retry_times` property in the output. The value of this property can be overridden in one of the two project config files (in this case typically the one that is not versioned).
304
+
305
+ Alternatively, instead of using an editor, you can directly specify a property value directly on the command line using the `--property` option.
306
+
307
+ ```shell
308
+ $ power_stencil create base_entity/my_4th_entity --property extra_prop:foo
309
+ Created 'base_entity/my_4th_entity'
310
+
311
+ $ power_stencil get base_entity/my_4th_entity --raw
312
+ --- !ruby/object:PowerStencil::SystemEntityDefinitions::ProjectEntity
313
+ :name: my_4th_entity
314
+ :extra_prop: foo
315
+ ```
316
+ :warning: Be careful, that any property created this way will be prefixed using `:`
317
+
318
+ ### Local (unversioned) entities
319
+
320
+ Entities created there are stored in `.ps_project/entities` which is a directory where everything is versioned by default. **But you have the possibility to create entities that will not be versioned**.
321
+ This is very **useful for developers** for a specific test for example, without risking to commit something they don't want.
322
+
323
+ For that you just need to use the `--user-storage` option. Then the entity will be persisted in the `.ps_project/user_entities` which is not versioned (see the root `.gitignore` file).
324
+
325
+ ```shell
326
+ $ power_stencil create base_entity/dev_entity --user-storage
327
+ Created 'base_entity/dev_entity'
328
+
329
+ $ power_stencil check base_entity/dev_entity
330
+ RAW ENTITIES
331
+ 'base_entity/dev_entity':
332
+ - Storage path: '/tmp/tst2/.ps_project/user_entities/base_entity/dev_entity.yaml'
333
+ - Status : Valid
334
+ - Buildable : false
335
+ ```
336
+ You can notice where the YAML file has been created.
337
+
338
+ Of course you can then delete the entity like any other...
339
+ ```shell
340
+ $ power_stencil delete base_entity/dev_entity --auto
341
+ Deleted 'base_entity/dev_entity'
342
+
343
+ ```
344
+
345
+
346
+ ## Updating entities
347
+
348
+ Not so much to say about updating entities. It works exactly the same way as the `power_stencil create`.
349
+
350
+ The command for that is `power_stencil edit`.
351
+
352
+ ## Deleting entities
353
+
354
+ To delete an entity I guess you get it you have to use the `power_stencil delete` command.
355
+
356
+ You can delete multiple object in one command using `--regexp`, but `PowerStencil` will ask confirmation for any of the entity requested:
357
+
358
+ ```shell
359
+ $ power_stencil delete base_entity/my_4th_entity
360
+ Are you sure you want delete 'base_entity/my_4th_entity' ? (Yes/y/[No]/n):
361
+ Cancelled by user input.
362
+ $ power_stencil delete base_entity/my_4th_entity
363
+ Are you sure you want delete 'base_entity/my_4th_entity' ? (Yes/y/[No]/n): y
364
+ Deleted 'base_entity/my_4th_entity'
365
+ ```
366
+
367
+ :warning: You can also pass the `--auto` option that will bypass the user confirmation. **Use with caution!**
368
+
369
+ ## The `PowerStencil` shell
370
+
371
+ `PowerStencil` provides a full Ruby shell (REPL) based on [Pry].
372
+ It could deserve a whole documentation in its own, because you could do a lot of things there.
373
+ Basically it is interesting, because you can automate easily the creation of a bunch of entities... But you can actually do anything as it is a fully fledged Ruby console.
374
+
375
+ Here under just an example of a short session:
376
+
377
+ ```ruby
378
+ $ power_stencil shell
379
+ -------------------------------------------------------------------------------
380
+ # Welcome to the PowerStencil shell session
381
+ # In this shell you have access to anything the templating engine has access to.
382
+ # You can view, edit, save entities.
383
+ #
384
+ # - Retrieve and manipulate entities using the `entities` hash.
385
+ # - Persist your changes using the `save` method on each entity.
386
+ # - Create new project or user entities using `new_<type>` and `user_new_<type>`
387
+ # methods (see `available_entity_types` for a list of possible types).
388
+ # - And of course, it is a fully fledged Ruby Pry REPL, so you can do anything
389
+ # you want...
390
+ #
391
+ # Type `exit` to end your session.
392
+ -------------------------------------------------------------------------------
393
+ PowerStencil DSL> entities
394
+ => [#<PowerStencil::SystemEntityDefinitions::ProjectConfig:47381736532780 composite_key=[:project_config, "Project Config"], @universe='Project entities (1566398755.703200)'>,
395
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381735892000 composite_key=[:base_entity, "my_2nd_entity"], @universe='Project entities (1566398755.703200)'>,
396
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381735871600 composite_key=[:base_entity, "my_3rd_entity"], @universe='Project entities (1566398755.703200)'>,
397
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381735869240 composite_key=[:base_entity, "my_1st_entity"], @universe='Project entities (1566398755.703200)'>]
398
+ PowerStencil DSL> %w(entity1 entity2 entity3).each do |entity_name|
399
+ PowerStencil DSL --> e = new_base_entity name: entity_name
400
+ PowerStencil DSL --> e.save
401
+ PowerStencil DSL --> end
402
+ => ["entity1", "entity2", "entity3"]
403
+ PowerStencil DSL> entities
404
+ => [#<PowerStencil::SystemEntityDefinitions::ProjectConfig:47381736532780 composite_key=[:project_config, "Project Config"], @universe='Project entities (1566398755.703200)'>
405
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381735892000 composite_key=[:base_entity, "my_2nd_entity"], @universe='Project entities (1566398755.703200)'>,
406
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381735871600 composite_key=[:base_entity, "my_3rd_entity"], @universe='Project entities (1566398755.703200)'>,
407
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381735869240 composite_key=[:base_entity, "my_1st_entity"], @universe='Project entities (1566398755.703200)'>,
408
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381741017600 composite_key=[:base_entity, "entity1"], @universe='Project entities (1566398755.703200)'>,
409
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381740982060 composite_key=[:base_entity, "entity2"], @universe='Project entities (1566398755.703200)'>,
410
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47381740930300 composite_key=[:base_entity, "entity3"], @universe='Project entities (1566398755.703200)'>]
411
+ PowerStencil DSL> exit
412
+ ```
413
+ And of course after this session the entities are really saved:
414
+
415
+ ```shell
416
+ $ power_stencil get base_entity/ent --regexp --raw
417
+ --- !ruby/object:PowerStencil::SystemEntityDefinitions::ProjectEntity
418
+ :name: entity3
419
+
420
+ --- !ruby/object:PowerStencil::SystemEntityDefinitions::ProjectEntity
421
+ :name: entity1
422
+
423
+ --- !ruby/object:PowerStencil::SystemEntityDefinitions::ProjectEntity
424
+ :name: entity2
425
+ ```
426
+
427
+ Basically, in this shell you can do anything you could do within [templates]. Use it to experiment with `PowerStencil`. It is important to understand that **you can't mess up anything within the shell** (Ok not completely true, as it is a [Pry] console under the hood, you could do everything you could do using the [Ruby] language, including wiping your hard drive, creating botnets or skynet terminators... but let's remain serious, I can't imagine you would do such bad things without knowing what you do ;)), if you don't save your entities, nothing is actually persisted. And if you do, who cares ? Git, your wingman, is there to back you up...
428
+
429
+ :sparkles: Use the shell Luke !! :sparkles:
430
+
431
+
432
+ # Creating custom entity types
433
+
434
+
435
+ :warning: As opposed to everything we've done until now, creating custom entity types requires a bit of knowledge of the [Ruby] programming language.
436
+
437
+ ## Anatomy of an entity type
438
+
439
+ An entity type is actually a Ruby class inheriting from [PowerStencil::SystemEntityDefinitions::ProjectEntity]. Simply inherit from this class and you're good to go with a brand new valid entity type.
440
+
441
+ Internally an entity is a proxy to a Hash `#fields` which content is exactly the content of the YAML file where the entity is persisted. For example in the `PowerStencil` shell:
442
+
443
+ ```ruby
444
+ PowerStencil DSL> e = entity :base_entity, "my_2nd_entity"
445
+ => #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47113681164160 composite_key=[:base_entity, "my_2nd_entity"], @universe='Project entities (1566475157.369747)'>
446
+ PowerStencil DSL> e.fields
447
+ => {:name=>"my_2nd_entity", :an_array_property=>["item 1", "item 2"], :a_hash_property=>{"item1"=>"value1", "item2"=>"value2"}, "a_non_colon_prefixed_property"=>"blah blah blah"}
448
+ PowerStencil DSL> e.fields.to_yaml
449
+ ```
450
+ We'll come back later on the DSL included but `entity(type, name)` is a method to retrieve an entity from the repository. So here we retrieve `my_2nd_entity` and we see that the content of the `#fields` is exactly what we defined.
451
+ This is as well a way to see that properties defined with a `:` prefix end up being `Symbols` whereas non-prefixed properties become `Strings`.
452
+
453
+ The [PowerStencil::SystemEntityDefinitions::ProjectEntity] class extends itself a class from the [universe_compiler] Gem and most of the complexity is hidden there in terms of fields, persistence, relationship between entities, builds, _inheritance_ (we are not talking about class inheritance there), overrides...
454
+ We will clarify some of those concepts here and some other in the [templates] and [builds] part, but you have to know that you don't really need to dig inside the [universe_compiler] unless you are really interested in the deepest internals...
455
+
456
+ :+1: Basically the main thing you need to understand is that the custom entity types you will create will inherit from that class.
457
+
458
+ ## Where do I create new entity types ?
459
+
460
+ There mainly two places where you can create custom entity types.
461
+
462
+ 1. Within a plugin. But plugins are the highest level of customization, and are [covered in a separated document][plugins]. But regarding what we will say here the definition of the classes will follow the same rules.
463
+ 2. In the `.ps_project/entity_definitions/` project folder. Ruby files that will be created there will be required in their alphabetical order. You can name them how you want (provided the `.rb` extension), as their name don't need to match the name of the classes you will create.
464
+
465
+ ## My first custom entity
466
+
467
+ Let's create a new file in `.ps_project/entity_definitions/` and let's name it `my_first_custom_entity_type.rb`, then fill it with the following content:
468
+
469
+ ```ruby
470
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
471
+ entity_type :custom_entity
472
+ end
473
+ ```
474
+ That's all folks ! You have created your first custom entity type. A proof ?
475
+
476
+ ```shell
477
+ $ power_stencil info
478
+ .
479
+ .
480
+ .
481
+ --------------------------------------------------------------------------------
482
+ Available entity types:
483
+ - Type 'base_entity' --> PowerStencil::SystemEntityDefinitions::ProjectEntity
484
+ - Type 'custom_entity' --> MyCustomEntity
485
+ - Type 'entity_override' --> UniverseCompiler::Entity::Override
486
+ - Type 'plugin_definition' --> PowerStencil::SystemEntityDefinitions::Plugin (template-template path: '/opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/power_stencil-0.2.18/etc/templates/plugin_definition')
487
+ - Type 'process_descriptor' --> PowerStencil::SystemEntityDefinitions::ProcessDescriptor
488
+ - Type 'project_config' --> PowerStencil::SystemEntityDefinitions::ProjectConfig
489
+ - Type 'simple_exec' --> PowerStencil::SystemEntityDefinitions::SimpleExec (template-template path: '/opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/power_stencil-0.2.18/etc/templates/simple_exec')
490
+ ```
491
+ You see that the `custom_entity` type appeared in the list. Need another proof ?
492
+ ```shell
493
+ $ power_stencil create custom_entity/my_1st_custom_entity
494
+ Created 'custom_entity/my_1st_custom_entity'
495
+
496
+ $ power_stencil check cust -r
497
+ RAW ENTITIES
498
+ 'custom_entity/my_1st_custom_entity':
499
+ - Storage path: '/tmp/tst2/.ps_project/entities/custom_entity/my_1st_custom_entity.yaml'
500
+ - Status : Valid
501
+ - Buildable : false
502
+
503
+ $ power_stencil get custom_entity/my_1st_custom_entity --raw
504
+ --- !ruby/object:MyCustomEntity
505
+ :name: my_1st_custom_entity
506
+ ```
507
+ Ok, I must admit it's a pretty useless entity type, as we didn't add anything compared to the `base_entity` we are inheriting from, but it works, and notice how easy it was ! No complex configuration, no complex code... a breeze.
508
+
509
+ ## Directives
510
+
511
+ You may have noticed in the previous example the directive `entity_type :custom_entity`... This looks a lot like directives some of you may be used to use in frameworks like [Ruby On Rails], and more specifically in [ActiveRecord]. If it is the case, then you won't be lost in how entity types work in `PowerStencil`. For the others you will discover how nice the Ruby syntax can be.
512
+
513
+ As said before, an entity is basically a proxy to an internal Hash accessible through the `#fields` accessor. That said, most directives are actually a simple way to define constraints and control over the content of that Hash.
514
+
515
+ Here are the available directives:
516
+
517
+ ### entity_type
518
+
519
+ Obviously defines the type of entity. See previous example for how it is used. Although this is not mandatory, if you don't define an `entity_type`, the entity type in not listed in the output of `power_stencil info`. It doesn't mean the entity type is invalid. It just becomes an internal `entity_type`.
520
+
521
+ ### auto_named_entity_type
522
+
523
+ Sometimes you don't want to give real names to some entity types. Without any parameter this directive will use the `entity_type` as default seed else of course the seed passed to this directive. The name of entities will be of the form `<seed>_ <an incremental counter>`.
524
+
525
+ ```ruby
526
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
527
+ entity_type :custom_entity
528
+
529
+ auto_named_entity_type
530
+ end
531
+ ```
532
+
533
+ ```ruby
534
+ PowerStencil DSL> new_custom_entity
535
+ => #<MyCustomEntity:47058432471380 composite_key=[:custom_entity, "custom_entity_1"], @universe='Project entities (1566480766.837926)'>
536
+ PowerStencil DSL> new_custom_entity
537
+ => #<MyCustomEntity:47058432258540 composite_key=[:custom_entity, "custom_entity_2"], @universe='Project entities (1566480766.837926)'>
538
+ PowerStencil DSL> new_custom_entity
539
+ => #<MyCustomEntity:47058432038400 composite_key=[:custom_entity, "custom_entity_3"], @universe='Project entities (1566480766.837926)'>
540
+ PowerStencil DSL> MyCustomEntity.auto_named_entity_type?
541
+ => true
542
+ PowerStencil DSL> MyCustomEntity.auto_named_entity_type_seed
543
+ => "custom_entity"
544
+ ```
545
+
546
+
547
+ ### field
548
+
549
+ As any persisted data is actually in the `#fields` Hash, you can declare shortcuts using the `field` directive. Let's say we have the following entity type:
550
+
551
+ ```ruby
552
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
553
+ entity_type :custom_entity
554
+
555
+ field :my_custom_field
556
+ end
557
+ ```
558
+ Then it means I can access the `my_custom_field` property in two different ways:
559
+
560
+ ```ruby
561
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
562
+ => #<MyCustomEntity:47209361877520 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566476673.765155)'>
563
+ PowerStencil DSL> e.my_custom_field
564
+ => nil
565
+ PowerStencil DSL> e.my_custom_field = 'foo'
566
+ => "foo"
567
+ PowerStencil DSL> e.my_custom_field
568
+ => "foo"
569
+ PowerStencil DSL> e.fields[:my_custom_field]
570
+ => "foo"
571
+ ```
572
+ :information_source: You can notice the `new_<entity_type>` DSL method which is a shortcut to create entities.
573
+
574
+ So you can see the field directive will actually create accessor methods for first level properties in the `#fields` Hash.
575
+
576
+ :warning: You can do pretty tricky things like:
577
+
578
+ ```ruby
579
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
580
+ entity_type :custom_entity
581
+
582
+ field :my_custom_field
583
+ field 'another_field'
584
+ end
585
+ ```
586
+
587
+ ```ruby
588
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
589
+ => #<MyCustomEntity:47332930509380 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566477499.215253)'>
590
+ PowerStencil DSL> e.my_custom_field = :foo
591
+ => :foo
592
+ PowerStencil DSL> e.another_field = :bar
593
+ => :bar
594
+ PowerStencil DSL> e.fields
595
+ => {:name=>"test_entity", :my_custom_field=>:foo, "another_field"=>:bar}
596
+ PowerStencil DSL> e.fields[:my_custom_field]
597
+ => :foo
598
+ PowerStencil DSL> e.fields['another_field']
599
+ => :bar
600
+ ```
601
+
602
+ :warning: Do not create two fields with the "same" name, once as a `Symbol` and another as a `String` ! Although it is perfectly legit at `#fields` level or in the YAML persisted file, the accessor methods generated would have the same name and you could expect some strange behaviour...
603
+
604
+ :information_source: By default the [base_entity][PowerStencil::SystemEntityDefinitions::ProjectEntity] has only two fields declared:
605
+ - The `name` field that we already saw and which is mandatory.
606
+ - The `description` field which is optional, and which is pretty self-explanatory.
607
+
608
+ ### has_one
609
+
610
+ This property may look familiar to users of [ActiveRecord], it introduces the concept of relationship between entities.
611
+
612
+ ```ruby
613
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
614
+ entity_type :custom_entity
615
+
616
+ has_one :custom_entity, name: :parent
617
+ end
618
+ ```
619
+
620
+ ```ruby
621
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
622
+ => #<MyCustomEntity:47215552142260 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566478203.584915)'>
623
+ PowerStencil DSL> p = new_custom_entity name: :foo
624
+ => #<MyCustomEntity:47215551934000 composite_key=[:custom_entity, "foo"], @universe='Project entities (1566478203.584915)'>
625
+ PowerStencil DSL> e.parent = p
626
+ => #<MyCustomEntity:47215551934000 composite_key=[:custom_entity, "foo"], @universe='Project entities (1566478203.584915)'>
627
+ PowerStencil DSL> e.fields
628
+ => {:name=>"test_entity", :parent=>#<MyCustomEntity:47215551934000 composite_key=[:custom_entity, "foo"], @universe='Project entities (1566478203.584915)'>}
629
+ PowerStencil DSL> p.save=> #<MyCustomEntity:47215551934000 composite_key=[:custom_entity, "foo"], @universe='Project entities (1566478203.584915)'>
630
+ PowerStencil DSL> e.save
631
+ => #<MyCustomEntity:47215552142260 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566478203.584915)'>
632
+ PowerStencil DSL> exit
633
+ ```
634
+
635
+ Which translates at persistence level as:
636
+
637
+ ```shell
638
+ $ power_stencil get custom_entity/test_entity --raw
639
+ --- !ruby/object:MyCustomEntity
640
+ :name: test_entity
641
+ :parent: !psref
642
+ type: :custom_entity
643
+ name: foo
644
+ ```
645
+ Here we are referencing an entity of the same type, but of course you can reference any type of entity. Using `has_one` will enforce the type of data you reference. Let's try to mess-up:
646
+
647
+ ```ruby
648
+ PowerStencil DSL> e.parent = :bar
649
+ => :bar
650
+ PowerStencil DSL> e.fields
651
+ => {:name=>"test_entity", :parent=>:bar}
652
+ PowerStencil DSL> e.valid?
653
+ => false
654
+ PowerStencil DSL> e.valid? raise_error: true
655
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields parent !
656
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.7/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
657
+ PowerStencil DSL> e.save
658
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields parent !
659
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.7/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
660
+ ```
661
+ So you see that if we try to set parent with something wrong, the accessor seems to accept, you can even see the `#fields` Hash updated. But as soon as you try to save to entity, or if you use the `#valid?` method, it complains about the type... Cool.
662
+
663
+ :information_source: The `name` property of the `has_one` directive is optional. If not present the field will be named from the entity_type referenced instead...
664
+
665
+ ### has_many
666
+
667
+ Once you know the `has_one` directive, you should not be surprised by the `has_many` directive...
668
+
669
+ ```ruby
670
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
671
+ entity_type :custom_entity
672
+
673
+ has_many :base_entity, name: :sub_properties
674
+ end
675
+ ```
676
+
677
+ ```ruby
678
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
679
+ => #<MyCustomEntity:47312117001560 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566482056.789871)'>
680
+ PowerStencil DSL> sub_prop1 = new_base_entity name: :prop1
681
+ => #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47312116793880 composite_key=[:base_entity, "prop1"], @universe='Project entities (1566482056.789871)'>
682
+ PowerStencil DSL> e.sub_properties << sub_prop1
683
+ => [#<PowerStencil::SystemEntityDefinitions::ProjectEntity:47312116793880 composite_key=[:base_entity, "prop1"], @universe='Project entities (1566482056.789871)'>]
684
+ PowerStencil DSL> sub_prop2 = new_base_entity name: :prop2
685
+ => #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47312116175620 composite_key=[:base_entity, "prop2"], @universe='Project entities (1566482056.789871)'>
686
+ PowerStencil DSL> e.sub_properties << sub_prop2
687
+ => [#<PowerStencil::SystemEntityDefinitions::ProjectEntity:47312116793880 composite_key=[:base_entity, "prop1"], @universe='Project entities (1566482056.789871)'>,
688
+ #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47312116175620 composite_key=[:base_entity, "prop2"], @universe='Project entities (1566482056.789871)'>]
689
+ PowerStencil DSL> sub_prop1.save
690
+ => #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47312116793880 composite_key=[:base_entity, "prop1"], @universe='Project entities (1566482056.789871)'>
691
+ PowerStencil DSL> sub_prop2.save
692
+ => #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47312116175620 composite_key=[:base_entity, "prop2"], @universe='Project entities (1566482056.789871)'>
693
+ PowerStencil DSL> e.save
694
+ => #<MyCustomEntity:47312117001560 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566482056.789871)'>
695
+ PowerStencil DSL> exit
696
+ ```
697
+
698
+ Which translates at persistence level as:
699
+
700
+ ```shell
701
+ --- !ruby/object:MyCustomEntity
702
+ :sub_properties:
703
+ - !psref
704
+ type: :base_entity
705
+ name: prop1
706
+ - !psref
707
+ type: :base_entity
708
+ name: prop2
709
+ :name: test_entity
710
+ ```
711
+ Nice !
712
+
713
+ ### is_array
714
+
715
+ You can define a field as being an array:
716
+
717
+ ```ruby
718
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
719
+ entity_type :custom_entity
720
+
721
+ field :an_array
722
+ is_array :an_array
723
+
724
+ field :another_array, :is_array
725
+ end
726
+ ```
727
+
728
+ ```ruby
729
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
730
+ => #<MyCustomEntity:47285777457100 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566482755.453123)'>
731
+ PowerStencil DSL> e.fields
732
+ => {:name=>"test_entity", :an_array=>[], :another_array=>[]}
733
+ ```
734
+
735
+ :information_source: Please note the two syntaxes are equivalent.
736
+
737
+ ### is_hash
738
+
739
+ You can define a field as being an array:
740
+
741
+ ```ruby
742
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
743
+ entity_type :custom_entity
744
+
745
+ field :a_hash
746
+ is_hash :a_hash
747
+
748
+ field :another_hash, :is_hash
749
+ end
750
+ ```
751
+
752
+ ```ruby
753
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
754
+ => #<MyCustomEntity:46940048986180 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566483001.470293)'>
755
+ PowerStencil DSL> e.fields
756
+ => {:name=>"test_entity", :a_hash=>{}, :another_hash=>{}}
757
+ ```
758
+
759
+ :information_source: Please note the two syntaxes are equivalent.
760
+
761
+ ### not_null
762
+
763
+ I guess you start getting used to what happens:
764
+
765
+ ```ruby
766
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
767
+ entity_type :custom_entity
768
+
769
+ field :a_field_not_null
770
+ not_null :a_field_not_null
771
+
772
+ field :another_field_not_null, :not_null
773
+ end
774
+ ```
775
+
776
+ ```ruby
777
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
778
+ => #<MyCustomEntity:47124565766660 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566483255.255375)'>
779
+ PowerStencil DSL> e.valid? raise_error: true
780
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields a_field_not_null, another_field_not_null !
781
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.7/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
782
+ PowerStencil DSL> e.a_field_not_null = ''
783
+ => ""
784
+ PowerStencil DSL> e.valid? raise_error: true
785
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields another_field_not_null !
786
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.7/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
787
+ PowerStencil DSL> e.another_field_not_null = :yeah
788
+ => :yeah
789
+ PowerStencil DSL> e.fields
790
+ => {:name=>"test_entity", :a_field_not_null=>"", :another_field_not_null=>:yeah}
791
+ PowerStencil DSL> e.valid?
792
+ => true
793
+ ```
794
+ Nothing very surprising there. Please note an empty string is not a null value...
795
+
796
+ ### not_empty
797
+
798
+ This directive will adapt depending on the field type
799
+
800
+ ```ruby
801
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
802
+ entity_type :custom_entity
803
+
804
+ field :a_non_empty_field, :not_empty
805
+ field :a_non_empty_array, :is_array, :not_empty
806
+ field :a_non_empty_hash, :is_hash, :not_empty
807
+
808
+ has_many :base_entity, name: :friends
809
+ not_empty :friends
810
+ end
811
+ ```
812
+
813
+ ```ruby
814
+ PowerStencil DSL> e = new_custom_entity name: :test_entity, a_non_empty_field: :foo, a_non_empty_array: [:bar], a_non_empty_hash: {foo: :bar}
815
+ => #<MyCustomEntity:47261985525080 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566484354.658921)'>
816
+ PowerStencil DSL> e.valid? raise_error: true
817
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields friends !
818
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.7/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
819
+ PowerStencil DSL> sub_prop1 = new_base_entity name: :prop1
820
+ => #<PowerStencil::SystemEntityDefinitions::ProjectEntity:47261985113260 composite_key=[:base_entity, "prop1"], @universe='Project entities (1566484354.658921)'>
821
+ PowerStencil DSL> e.friends << sub_prop1
822
+ => [#<PowerStencil::SystemEntityDefinitions::ProjectEntity:47261985113260 composite_key=[:base_entity, "prop1"], @universe='Project entities (1566484354.658921)'>]
823
+ PowerStencil DSL> e.valid?
824
+ => true
825
+ PowerStencil DSL> e.fields
826
+ => {:name=>"test_entity",
827
+ :a_non_empty_field=>:foo,
828
+ :a_non_empty_array=>[:bar],
829
+ :a_non_empty_hash=>{:foo=>:bar},
830
+ :friends=>[#<PowerStencil::SystemEntityDefinitions::ProjectEntity:47261985113260 composite_key=[:base_entity, "prop1"], @universe='Project entities (1566484354.658921)'>]}
831
+ ```
832
+
833
+ Pretty simple nope ?
834
+
835
+ :warning: Please note that neither `has_one` nor `has_many` support the "_condensed_" version of the syntax (like `field` can). You have to specify the directive on a separated line.
836
+
837
+ :information_source: `has_one` does not support `not_empty` but support `not_null`, and the opposite for `has_many`...
838
+
839
+ ### should_match
840
+
841
+ `should_match` will perform a validation of the content regarding a regular expression or a string. It should work on strings or symbols:
842
+
843
+ ```ruby
844
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
845
+ entity_type :custom_entity
846
+
847
+ field :ipv4, should_match: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
848
+ field :title, :not_null, should_match: /(Mr)|(Ms)/
849
+ end
850
+ ```
851
+
852
+ ```ruby
853
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
854
+ => #<MyCustomEntity:47277853516360 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566485485.196701)'>
855
+ PowerStencil DSL> e.valid? raise_error: true
856
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields title !
857
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.7/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
858
+ PowerStencil DSL> e.title = 'PhD'
859
+ => "PhD"
860
+ PowerStencil DSL> e.valid? raise_error: true
861
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields title !
862
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.7/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
863
+ PowerStencil DSL> e.title = 'Mr'
864
+ => "Mr"
865
+ PowerStencil DSL> e.valid? raise_error: true
866
+ => true
867
+ PowerStencil DSL> e.ipv4 = 'a.b.c.d'
868
+ => "a.b.c.d"
869
+ PowerStencil DSL> e.valid? raise_error: true
870
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields ipv4 !
871
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.7/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
872
+ PowerStencil DSL> e.ipv4 = '192.168.0.1'
873
+ => "192.168.0.1"
874
+ PowerStencil DSL> e.valid? raise_error: true
875
+ => true
876
+ ```
877
+
878
+ :warning: As you can see there, unless specified as `not_null` or `not_empty`, the `should_match` validation will not be performed ! Pretty normal if you think about it...
879
+
880
+ ### class_name
881
+
882
+ ```ruby
883
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
884
+ entity_type :custom_entity
885
+
886
+ field :should_be_a_string, class_name: String
887
+ end
888
+ ```
889
+
890
+ ```ruby
891
+ PowerStencil DSL> e = new_custom_entity name: :test_entity
892
+ => #<MyCustomEntity:47164532040760 composite_key=[:custom_entity, "test_entity"], @universe='Project entities (1566486839.687064)'>
893
+ PowerStencil DSL> e.valid? raise_error: true
894
+ => true
895
+ PowerStencil DSL> e.should_be_a_string = 42
896
+ => 42
897
+ PowerStencil DSL> e.valid? raise_error: true
898
+ UniverseCompiler::Error: Invalid entity '[:custom_entity, "test_entity"]' for fields should_be_a_string !
899
+ from /opt/rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/universe_compiler-0.2.8/lib/universe_compiler/utils/error_propagation.rb:10:in `false_or_raise'
900
+ PowerStencil DSL> e.should_be_a_string = 'okay... then'
901
+ => "okay... then"
902
+ PowerStencil DSL> e.valid? raise_error: true
903
+ => true
904
+ ```
905
+ `class_name` supports actually both a class (like here) or a class name.
906
+
907
+ :warning: Use this one with caution, as it does not test anything regarding inheritance...
908
+
909
+ ### buildable / buildable_by
910
+
911
+ One of the most important directives !
912
+ More information is provided in the [builds] documentation, but basically this defines an `entity_type` as a potential target for the build process.
913
+
914
+ Most probably you will use `buildable`, `buildable_by` being used in the context of [plugins].
915
+
916
+ ```ruby
917
+ class MyCustomEntity < PowerStencil::SystemEntityDefinitions::ProjectEntity
918
+ entity_type :custom_entity
919
+
920
+ buildable
921
+ end
922
+ ```
923
+
924
+ ## Module you could include in your entity types
925
+
926
+ There is one special module you could include in your entity types.
927
+
928
+ If you want to define an entity which is not supposed to be persisted you could just include the [PowerStencil::SystemEntityDefinitions::NonPersistent] module et voilà... It could be a directive, but this is not something your are supposed to do a lot. It could become a directive in the future if the need appears.
929
+
930
+
931
+ ## Adding functional code
932
+
933
+ Now you know an entity type is just a regular Ruby class. As such you could add any code you want to, for example, get a token from a server, perform a task on a db...whatever...
934
+
935
+ **:warning: Although you can do anything there, if you see yourself starting writing a lot of code in your custom entities, you should probably consider creating a real [plugin][plugins] ! It will help you maintain and structure your code, on top of providing you a ton of extra cool stuff like extending command line by adding new sub-commands and/or options, having plugin-specific config, having templates-templates (:rofl:), plus the possibility to share/re-use your nice work across multiple projects !!**
936
+
937
+
938
+
939
+ [:back:][Documentation root]
940
+ <!-- End of Document -->
941
+
942
+ <!-- Pages -->
943
+ [Documentation root]: ../README.md "Back to documentation root"
944
+ [templates]: templates.md "Templates in PowerStencil"
945
+ [builds]: builds.md "Builds in PowerStencil"
946
+ [plugins]: plugins.md "Plugins in PowerStencil"
947
+ [example use cases]: example_use_cases.md "Example uses cases using PowerStencil"
948
+
949
+
950
+ <!-- Code links -->
951
+ [PowerStencil::SystemEntityDefinitions::ProjectEntity]: ../lib/power_stencil/system_entity_definitions/project_entity.rb "The base_entity entity type"
952
+ [PowerStencil::SystemEntityDefinitions::NonPersistent]: ../lib/power_stencil/system_entity_definitions/non_persistent.rb "To make your entity type not persistent"
953
+ [PowerStencil::SystemEntityDefinitions::HasAssociatedFiles]: ../lib/power_stencil/system_entity_definitions/has_associated_files.rb "Brings the templating power to your entity types"
954
+
955
+ <!-- Illustrations -->
956
+
957
+
958
+ <!-- External links -->
959
+ [YAML]: https://yaml.org/ "The YAML official site"
960
+ [Ruby]: https://www.ruby-lang.org "The powerful Ruby language"
961
+ [Pry]: https://github.com/pry/pry "The awesome Pry console"
962
+ [ActiveRecord]: https://guides.rubyonrails.org/active_record_basics.html "The ultimate ORM"
963
+ [Ruby On Rails]: https://rubyonrails.org/ "One of the best Web framework"
964
+ [universe_compiler]: https://gitlab.com/tools4devops/universe_compiler "The underlying engine to manage entities and compilation !"