alki 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/README.adoc +132 -20
  4. data/Rakefile +2 -27
  5. data/alki.gemspec +1 -3
  6. data/bin/bundler +17 -0
  7. data/bin/rake +17 -0
  8. data/doc/assemblies.adoc +2 -3
  9. data/doc/assembly_dsl.adoc +191 -30
  10. data/doc/{projects.adoc → executables.adoc} +40 -16
  11. data/doc/index.adoc +4 -3
  12. data/lib/alki.rb +3 -0
  13. data/lib/alki/assembly.rb +4 -34
  14. data/lib/alki/assembly/builder.rb +3 -3
  15. data/lib/alki/assembly/instance.rb +31 -5
  16. data/lib/alki/assembly/instance_builder.rb +48 -0
  17. data/lib/alki/assembly/meta/overlay.rb +7 -2
  18. data/lib/alki/assembly/meta/tags.rb +4 -4
  19. data/lib/alki/assembly/types.rb +9 -0
  20. data/lib/alki/assembly/types/assembly.rb +3 -3
  21. data/lib/alki/assembly/types/group.rb +16 -25
  22. data/lib/alki/assembly/types/original.rb +12 -0
  23. data/lib/alki/assembly/types/override.rb +0 -2
  24. data/lib/alki/assembly/types/service.rb +4 -4
  25. data/lib/alki/circular_reference_error.rb +25 -0
  26. data/lib/alki/dsls/assembly.rb +1 -1
  27. data/lib/alki/dsls/assembly_group.rb +28 -12
  28. data/lib/alki/execution/context_class_builder.rb +1 -1
  29. data/lib/alki/execution/helpers.rb +4 -4
  30. data/lib/alki/execution/overlay_map.rb +37 -0
  31. data/lib/alki/execution/tag_map.rb +42 -0
  32. data/lib/alki/executor.rb +140 -0
  33. data/lib/alki/override_builder.rb +30 -24
  34. data/lib/alki/overrides.rb +4 -0
  35. data/lib/alki/version.rb +1 -1
  36. data/test/feature/mounts_test.rb +15 -0
  37. data/test/feature/multithreading_test.rb +0 -3
  38. data/test/feature/overlays_test.rb +2 -2
  39. data/test/feature/overrides_test.rb +26 -1
  40. data/test/feature/references_test.rb +35 -0
  41. data/test/feature/try_mounts_test.rb +23 -0
  42. data/test/feature/values_test.rb +14 -0
  43. data/test/feature_test_helper.rb +1 -0
  44. data/test/fixtures/example/config/assembly.rb +17 -8
  45. data/test/fixtures/example/config/handlers.rb +10 -5
  46. data/test/fixtures/example/lib/dsls/num_handler.rb +2 -2
  47. data/test/fixtures/example/lib/example/array_output.rb +13 -0
  48. data/test/fixtures/example/lib/example/echo_handler.rb +11 -0
  49. data/test/fixtures/example/lib/example/log_overlay.rb +12 -0
  50. data/test/fixtures/example/lib/example/num_handler.rb +13 -0
  51. data/test/fixtures/example/lib/example/range_handler.rb +13 -0
  52. data/test/fixtures/example/lib/example/switch_handler.rb +11 -0
  53. metadata +39 -44
  54. data/lib/alki/assembly/executor.rb +0 -137
  55. data/test/fixtures/example/lib/array_output.rb +0 -11
  56. data/test/fixtures/example/lib/echo_handler.rb +0 -9
  57. data/test/fixtures/example/lib/log_overlay.rb +0 -10
  58. data/test/fixtures/example/lib/num_handler.rb +0 -11
  59. data/test/fixtures/example/lib/range_handler.rb +0 -11
  60. data/test/fixtures/example/lib/switch_handler.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d65c6ea707a3c2b160cf29edcb6cdd092a9239f3
4
- data.tar.gz: 9ec9b017122f9a6a0cd2c5741835e2a0b0bd570b
3
+ metadata.gz: c3e661fb5cb33a76f94430ca79d62dae03fb2527
4
+ data.tar.gz: 4c288022884681ff99906ac0787cc4c82bb25c1d
5
5
  SHA512:
6
- metadata.gz: ad08a969ea2df6707a7e92139a52d70d1cbf5549d5bf5566dfe11f1253b5b014d2e4db986dd94585db0a85faf9a0aacc93cffcd04d7e01efe41695f940e47216
7
- data.tar.gz: b0ba6db2280081859a064f287b144ec7425de30f0d090370c900c8b0e37c2b56562a4599e9be97f4b2ba6044f9a4fb7f9102db2cb6cb1bec7e1c266777b03f9c
6
+ metadata.gz: c3c44874d4ce17e340d25224b8c5131bc00d4f9595bd09bdf85f22aab2d296a493d1cee68e5f97ffa7794c2e8859bb6bb7cfb18e40c8af5727ecc6777982ba69
7
+ data.tar.gz: f86c11fed68fb98600efb661fb6c1c255212690e18f42bf7fa2bef59df220c5f469d9d2a461a94f7e59c765b0473e605ee7abcd95d6fbbe86758d9a399fdc239
data/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'alki-testing'
4
+ gem 'rake'
4
5
 
5
6
  gemspec
@@ -1,34 +1,146 @@
1
1
  # What is Alki?
2
2
 
3
- Alki is a small framework for Ruby that provides a powerful dependency injection and program
4
- management system with helpers for testing, creating executables, and writing DSLs.
3
+ Alki is a small library to help organize and scale your Ruby project, so you can focus on the
4
+ important stuff. It can be used alongside frameworks such as Ruby on Rails.
5
5
 
6
- It's goal is to remove uncertainty and friction when building Ruby projects, allowing developers to focus on
7
- implementing business logic. It can be used alongside other frameworks such as Ruby on Rails.
6
+ Some high level features:
8
7
 
9
- Alki tries to combine time tested software engineering concepts such as
10
- https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)[SOLID],
11
- and https://en.wikipedia.org/wiki/Inversion_of_control[Inversion of Control], with
12
- Ruby style ease of use, DSLs, and
13
- https://en.wikipedia.org/wiki/Convention_over_configuration[Convention over Configuration].
8
+ * Easily manage objects and dependencies
9
+ * Enables writing reusable and testable code
10
+ * Developer console (built on pry)
11
+ * Automatic code reloading on changes in development
12
+ * Powerful DSL toolkit
13
+ * Extensible
14
14
 
15
- Further documentation can be found at https://github.com/alki-project/alki/blob/master/doc/index.adoc
16
15
 
17
- Example projects can be found at https://github.com/alki-project/alki-examples
16
+ ## Installation
18
17
 
19
- ## Getting Started
18
+ Add this line to your application's Gemfile:
20
19
 
21
- Install from rubygems
20
+ ```ruby
21
+ gem 'alki'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install alki
31
+
32
+ ## Introduction
33
+
34
+ [NOTE]
35
+ Full "todo" example can be found https://github.com/alki-project/alki-examples/tree/master/todo[here]
36
+
37
+ Alki simplifies project organization by pulling out all of the "connective tissue"
38
+ that connects our classes and modules together, and puts them into a special object
39
+ called an Assembly.
40
+
41
+ There are many ways to use Assemblies, but the most common is to have a single
42
+ Assembly for you project. For example, if you hade a "todo" command line utility
43
+ project that you wanted to use Alki with, all you would need to do to create
44
+ an Assembly is add this file.
22
45
 
23
- gem install alki
46
+ .lib/todo.rb
47
+ ```ruby
48
+ require 'alki'
49
+ Alki.project_assembly!
50
+ ```
24
51
 
25
- Setting up Alki in your project requires only creating a couple of files, but the
26
- `alki` command line tool can also be used to automate the process. Project names
27
- should be given in lowercase using underscores and forward slashes as separators.
52
+ This will create a module called `Todo` that is an empty assembly:
28
53
 
29
- .Example project
30
54
  ```
31
- $ cd <project dir>
32
- $ alki init --addons console <project name>
55
+ $ bundle exec irb -Ilib
56
+ 2.4.0 :001 > require 'todo'
57
+ => true
58
+ 2.4.0 :002 > todo = Todo.new
59
+ => #<Todo:21964520>
60
+ ```
61
+
62
+ ### Defining Elements
63
+
64
+ Add things to the assembly requires an assembly definition file. By convention this is
65
+ named `config/assembly.rb` and is built using a simple DSL. There are
66
+ a handful of different element types in Assemblies. Below are a few of the
67
+ most common. Full documentation of the DSL can be found
68
+ https://github.com/alki-project/alki/blob/master/doc/assembly_dsl.adoc[here]
69
+
70
+ .config/assembly.rb
71
+ ```ruby
72
+ Alki do
73
+ group :settings do <1>
74
+ set(:home) { ENV['HOME'] } <2>
75
+ set(:db_path) { ENV['TODO_DB_PATH'] || File.join(home,'.todo_db2') }
76
+ set :prompt, 'todo> '
77
+ end
78
+
79
+ service :interface do <3>
80
+ require 'todo/readline_interface'
81
+ Todo::ReadlineInterface.new settings.prompt, handler
82
+ end
83
+
84
+ service :handler do
85
+ require 'todo/command_handler'
86
+ Todo::CommandHandler.new db
87
+ end
88
+
89
+ service :db do
90
+ require 'todo/store_db'
91
+ Todo::StoreDb.new file_store
92
+ end
93
+
94
+ service :file_store do
95
+ require 'todo/json_file_store'
96
+ Todo::JsonFileStore.new settings.db_path
97
+ end
98
+ end
33
99
  ```
100
+ <1> `group` allows bundling together subelements and can be moved to their own files
101
+ <2> `set` defines simple values
102
+ <3> `service` defines our main application objects
103
+
104
+ Any element can be accessed directly from the assembly object.
105
+
106
+ ```
107
+ $ bundle exec irb -Ilib
108
+ 2.4.0 :001 > require 'todo'
109
+ => true
110
+ 2.4.0 :002 > todo = Todo.new
111
+ => #<Todo:21964520>
112
+ 2.4.0 :003 > todo.settings.prompt
113
+ => 'todo> '
114
+ ```
115
+
116
+ The 'alki-console' tool can also be used to quickly work with assemblies.
117
+ Add `gem 'alki-console'` to your Gemspec and run `bundle --binstubs`.
118
+
119
+ ```
120
+ $ bin/alki-console
121
+ todo> settings.prompt
122
+ => 'todo> '
123
+ ```
124
+
125
+ ### Creating an executable
126
+
127
+ Read more about creating executables with Alki
128
+ https://github.com/alki-project/alki/blob/master/doc/executables.adoc[here]
129
+
130
+ In the todo example, it's a CLI utility so it requires an executable.
131
+
132
+ .exe/todo
133
+ ```ruby
134
+ require 'todo'
135
+ Todo.new.interface.run
136
+ ```
137
+
138
+ ## Further Documentation
139
+
140
+ Further documentation can be found https://github.com/alki-project/alki/blob/master/doc/index.adoc[here]
141
+
142
+ Example projects can be found https://github.com/alki-project/alki-examples[here]
143
+
144
+ ## Authors
34
145
 
146
+ Written by Matt Edlefsen
data/Rakefile CHANGED
@@ -1,29 +1,4 @@
1
1
  require "bundler/gem_tasks"
2
- require 'rake/testtask'
2
+ require 'alki/testing/tasks'
3
3
 
4
- Rake::TestTask.new do |t|
5
- t.name = 'test:unit'
6
- t.pattern = "test/unit/*_test.rb"
7
- end
8
-
9
- Rake::TestTask.new do |t|
10
- t.name = 'test:feature'
11
- t.pattern = "test/feature/*_test.rb"
12
- end
13
-
14
- Rake::TestTask.new do |t|
15
- t.name = 'test:integration'
16
- t.pattern = "test/integration/*_test.rb"
17
- end
18
-
19
- Rake::TestTask.new do |t|
20
- t.name = 'test:page'
21
- t.pattern = "test/page/*_test.rb"
22
- end
23
-
24
- Rake::TestTask.new do |t|
25
- t.name = 'test'
26
- t.pattern = "test/*/*_test.rb"
27
- end
28
-
29
- task default: [:test]
4
+ task default: [:test]
@@ -18,9 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.6"
22
- spec.add_development_dependency "rake", '~> 10.0'
23
- spec.add_dependency "alki-dsl", "~> 0.5"
21
+ spec.add_dependency "alki-dsl", "~> 0.5", '>= 0.5.1'
24
22
  spec.add_dependency "alki-support", "~> 0.7"
25
23
  spec.add_dependency "concurrent-ruby", "~> 1.0"
26
24
  spec.add_dependency "ice_nine", "~> 0.11"
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'bundler' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("bundler", "bundler")
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rake' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rake", "rake")
@@ -17,7 +17,7 @@ Project Assemblies
17
17
  Most of the time, a project will have a single assembly, so Alki makes having a single project wide
18
18
  assembly especially easy.
19
19
 
20
- First. in your project's `lib` directory create a ruby file for your assembly. If your assembly is
20
+ First, in your project's `lib` directory create a ruby file for your assembly. If your assembly is
21
21
  to be called `MyAssembly`, create `lib/my_assembly.rb`. If it's namespaced put it in a subdirectory
22
22
  as usual (i.e. `MyModule::MyAssembly` would go in `lib/my_module/my_assembly.rb`).
23
23
 
@@ -126,7 +126,6 @@ The limitiation of this is that it can only override basic values. To override m
126
126
  a block can be given to `new` allowing the full assembly DSL.
127
127
 
128
128
  ```ruby
129
- require 'alki'
130
129
  class MyLogger
131
130
  def initialize(io)
132
131
  @io = io
@@ -150,4 +149,4 @@ instance.util.logger.info "test"
150
149
 
151
150
  One thing of note is that elements from the assembly are accessible in overrides via the `original`
152
151
  method, as seen above. This can also be used to access the original versions of elements that have
153
- been overriden.
152
+ been overriden.
@@ -2,7 +2,7 @@ Assembly DSL
2
2
  ============
3
3
  :toc:
4
4
 
5
- Building Assemblies is done via a DSL that makes putting together the pieces of your project easy.
5
+ Building Assemblies is done via a small DSL
6
6
 
7
7
  Groups (group)
8
8
  --------------
@@ -23,8 +23,7 @@ puts assembly.new.sub_group.val
23
23
  #output: hello world
24
24
  ```
25
25
 
26
- Scoping is also done by group, so that an element will be found by searching through parent groups until
27
- it is found.
26
+ Scoping is also done by group, so that an element will be found by searching through parent groups.
28
27
 
29
28
  ```ruby
30
29
  require 'alki'
@@ -213,8 +212,7 @@ puts assembly.new.greet "Matt"
213
212
 
214
213
  Services are the key element Assemblies are typically made up of. Like the block form of `set`,
215
214
  `service` takes a name and block, which will be evaluated once on-demand and the result cached.
216
- One difference between the block form of `set` and `service` is that services are affected
217
- by overlays, whereas basic values are not.
215
+ Whereas `set` is a lightweight element for simple values, `service` provides more functionality.
218
216
 
219
217
  Commonly a service will require the file that defines a class, and then constructs an instance of
220
218
  that class.
@@ -235,9 +233,13 @@ assembly.new.logger << "hello\n"
235
233
  ### Factories (factory)
236
234
 
237
235
  Factories are a mix between services and funcs. Like services, they take a block which is evaluated
238
- once. Unlike services though, that block must return a callable object (like a Proc). This object
239
- is then called directly when the factory is referenced. This allows you to require files or construct
240
- a factory object once but still run code on every reference.
236
+ once. Unlike services though, that block must return a callable "builder" (like a Proc).
237
+
238
+ If a factory is referenced as a service (i.e. no arguments) it returns a factory object
239
+ that responds to either `#call` or `#new` and will call the builder in turn.
240
+
241
+ If a factory is instead referenced like a method (i.e. with arguments) it will
242
+ call the builder directly.
241
243
 
242
244
  ```ruby
243
245
  require 'alki'
@@ -249,6 +251,8 @@ assembly = Alki.create_assembly do
249
251
 
250
252
  service :main_logger do
251
253
  logger STDOUT
254
+ # -or-
255
+ logger.call STDOUT
252
256
  end
253
257
  end
254
258
  assembly.new.main_logger << "hello\n"
@@ -256,29 +260,23 @@ assembly.new.main_logger << "hello\n"
256
260
  #output: hello
257
261
  ```
258
262
 
259
- ## Overlays (overlay)
260
-
261
- Overlays are a way to intercept and transform calls made to all services in a given group or it's
262
- sub-groups.
263
-
264
- Overlays are often most useful in groups where all services adhere to a common interface, and overlays
265
- can be used to perform aspect oriented programming like logging, validation, or access controls.
266
-
267
263
 
268
264
  ## Mounting Assemblies (mount)
269
265
 
270
266
  Other assemblies can be mounted into your Assembly using the `mount` command.
271
267
 
272
- The first argument is what the element should be named in the parent assembly. The optional second argument
273
- is the name of the assembly to be mounted. This should be formatted like a require string (relative path but
274
- no `.rb`) and will default to the value of the first argument. If a classified version of that name
275
- can't be found, Alki will attempt to `require` it, and then look for it again.
268
+ The first argument is what the element should be named in the parent assembly.
269
+ The optional second argument is the assembly to be mounted.
270
+ This can either be the assembly module,
271
+ or be a "require" string (relative path but no `.rb`).
272
+ It defaults to the element name.
273
+ If a string, Alki will attempt to `require` it, and then look for a matching constant.
274
+
276
275
 
277
276
  ```ruby
278
277
  require 'alki'
279
278
 
280
- # Creates OtherAssembly
281
- Alki.create_assembly name: 'other_assembly' do
279
+ other_assembly = Alki.create_assembly do
282
280
  set :val, "one"
283
281
 
284
282
  # This is invalid as there is no such element as 'val2'
@@ -293,12 +291,11 @@ Alki.create_assembly name: 'other_assembly' do
293
291
  end
294
292
  end
295
293
 
296
- Alki.create_assembly name: 'main_assembly' do
294
+ assembly = Alki.create_assembly do
297
295
  set :val2, "two"
298
- # Mounts OtherAssembly as 'other'
299
- mount :other, 'other_assembly'
296
+ mount :other, other_assembly
300
297
  end
301
- instance = MainAssembly.new
298
+ instance = assembly.new
302
299
  puts instance.other.val
303
300
  #output: one
304
301
 
@@ -322,22 +319,186 @@ automatically in scope for overrides.
322
319
 
323
320
  ```ruby
324
321
  require 'alki'
325
- Alki.create_assembly name: 'other_assembly' do
322
+ other_assembly = Alki.create_assembly do
326
323
  set :msg, nil
327
324
  func :print do
328
325
  puts msg
329
326
  end
330
327
  end
331
328
 
332
- Alki.create_assembly name: 'main_assembly' do
329
+ assembly = Alki.create_assembly do
333
330
  set :val, "hello"
334
- mount :other, 'other_assembly' do
331
+ mount :other, other_assembly do
335
332
  set :msg do
336
333
  val
337
334
  end
338
335
  end
339
336
  end
340
- MainAssembly.new.other.print
337
+ assembly.new.other.print
341
338
 
342
339
  #output: hello
343
340
  ```
341
+
342
+ ## Overlays and Tags
343
+
344
+ Overlays are a way to transparently wrap services. They work by taking the
345
+ name of a service or a group, in which case they are applied to all services in that group,
346
+ along with the name of an element to be used as the overlay, plus some optional arguments.
347
+
348
+ When the named service is built, the overlay element will be called (with `.call`), with
349
+ the built service object and the optional arguments, and it's result will be what's
350
+ returned.
351
+
352
+ Factories work well as overlay elements.
353
+
354
+ ```ruby
355
+ require 'alki'
356
+
357
+ assembly = Alki.create_assembly do
358
+ overlay :greeting, :exclaim, 3
359
+
360
+ service :greeting do
361
+ 'Hello World'
362
+ end
363
+
364
+ factory :exclaim do
365
+ -> (string,count) do
366
+ string + ('!' * count)
367
+ end
368
+ end
369
+ end
370
+
371
+ puts assembly.new.greeting
372
+
373
+ #output: Hello World!!!
374
+ ```
375
+
376
+ ### Tags (tag)
377
+
378
+ Tags are way of adding metadata to your elements. They can either be just a name, or
379
+ optionally carry a value
380
+
381
+ ```ruby
382
+ require 'alki'
383
+
384
+ assembly = Alki.create_assembly do
385
+ tag :tag1, with_value: 123
386
+ service :tagged do
387
+ meta[:tags]
388
+ end
389
+ end
390
+
391
+ puts assembly.new.tagged
392
+
393
+ #output: {:with_value=>123, :tag1=>true}
394
+ ```
395
+
396
+ Tags can be applied to groups to tag everything within that group
397
+
398
+ ```ruby
399
+ require 'alki'
400
+
401
+ assembly = Alki.create_assembly do
402
+ tag :tag1
403
+ group :grp do
404
+ tag :tag2
405
+ service :tagged do
406
+ meta[:tags]
407
+ end
408
+ end
409
+ end
410
+
411
+ puts assembly.new.grp.tagged
412
+
413
+ #output: {:tag1=>true, :tag2=>true}
414
+ ```
415
+
416
+ #### Overlaying tags (overlay_tag)
417
+
418
+ Instead of overlaying services directly, it's often useful to overlay all services
419
+ with a given tag.
420
+
421
+ ```ruby
422
+ require 'alki'
423
+
424
+ assembly = Alki.create_assembly do
425
+ overlay_tag :exclaimed, :exclaim, 3
426
+
427
+ tag :exclaimed
428
+ service :greeting do
429
+ 'Hello World'
430
+ end
431
+
432
+ factory :exclaim do
433
+ -> (string,count) do
434
+ string + ('!' * count)
435
+ end
436
+ end
437
+ end
438
+
439
+ puts assembly.new.greeting
440
+
441
+ #output: Hello World!!!
442
+ ```
443
+
444
+ Factories can access the tags of the services their being called from, allowing you
445
+ to customize the build based on what tags are present
446
+
447
+ ```ruby
448
+ require 'alki'
449
+
450
+ assembly = Alki.create_assembly do
451
+ overlay_tag :process, :process_string
452
+
453
+ tag :process, exclaim: 3
454
+ service :greeting do
455
+ 'Hello World'
456
+ end
457
+
458
+ factory :process_string do
459
+ -> (string) do
460
+ if exclaim = meta[:tags][:exclaim]
461
+ string = string + ('!' * exclaim)
462
+ end
463
+ string
464
+ end
465
+ end
466
+ end
467
+
468
+ puts assembly.new.greeting
469
+
470
+ #output: Hello World!!!
471
+ ```
472
+
473
+ Finally, tag overlays work even across assembly mounts, allowing overlays to
474
+ be defined in a library, and then applied by tagging services.
475
+
476
+ ```ruby
477
+ require 'alki'
478
+
479
+ string_processor = Alki.create_assembly do
480
+ overlay_tag :process, :process_string
481
+
482
+ factory :process_string do
483
+ -> (string) do
484
+ if exclaim = meta[:tags][:exclaim]
485
+ string = string + ('!' * exclaim)
486
+ end
487
+ string
488
+ end
489
+ end
490
+ end
491
+
492
+ assembly = Alki.create_assembly do
493
+ mount :string_processor, string_processor
494
+
495
+ tag :process, exclaim: 3
496
+ service :greeting do
497
+ 'Hello World'
498
+ end
499
+ end
500
+
501
+ puts assembly.new.greeting
502
+
503
+ #output: Hello World!!!
504
+ ```