commit 5fd6457c7a6abf55be842f1e51e6b40547c54e9f Author: Dominic Grimm Date: Sun Feb 13 20:59:26 2022 +0100 Init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..163eb75 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*.cr] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bbd4a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/docs/ +/lib/ +/bin/ +/.shards/ +*.dwarf + +# Libraries don't need dependency lock +# Dependencies will be locked in applications that use them +/shard.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d380e85 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Dominic Grimm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0cd025 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# service + +TODO: Write a description here + +## Installation + +1. Add the dependency to your `shard.yml`: + + ```yaml + dependencies: + service: + git: https://git.dergrimm.net/dergrimm/service.git + ``` + +2. Run `shards install` + +## Usage + +```crystal +require "service" +``` + +TODO: Write usage instructions here + +## Development + +TODO: Write development instructions here + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request + +## Contributors + +- [Dominic Grimm](https://git.dergrimm.net/dergrimm) - creator and maintainer diff --git a/examples/example.cr b/examples/example.cr new file mode 100644 index 0000000..63edfd3 --- /dev/null +++ b/examples/example.cr @@ -0,0 +1,42 @@ +require "../src/service" +require "log" + +class MessageService < Service + Log = ::Log.for(self) + + @@i = 0 + + getter message + getter delay + getter times + + def initialize(@message : String, @delay : Int32, @times : Int32) + end + + def run(_unit : Unit) : Service::Unit? + puts @message + @@i += 1 + Log.info { @@i } + sleep @delay + + if @@i < @times + Service::Unit.new(self) + else + Log.info { "Done." } + + nil + end + end +end + +class Runner < Service::Runner + def starters : Array(Service::Starter) + [ + Service::AsynchronousStarter.new([ + MessageService.new("Hello world from AsynchronousStarter!", delay: 1, times: 5).as(Service), + ]).as(Service::Starter), + ] + end +end + +Runner.new.run diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..6035a0e --- /dev/null +++ b/shard.yml @@ -0,0 +1,13 @@ +name: service +version: 0.1.0 + +authors: + - Dominic Grimm + +crystal: 1.3.2 + +license: MIT + +dependencies: + version_from_shard: + github: hugopl/version_from_shard diff --git a/src/service.cr b/src/service.cr new file mode 100644 index 0000000..c5c5acc --- /dev/null +++ b/src/service.cr @@ -0,0 +1,114 @@ +require "version_from_shard" + +abstract class Service + VersionFromShard.declare + + abstract def run(unit : Unit) : Unit? + + def handle(unit : Unit?) : Unit? + if unit + unit.handle(self) + end + end + + class Unit + property service + property handler_proc + + alias Handler = (self, Service) -> Unit? + + DEFAULT_HANLDER = ->(unit : self, service : Service) { service.run(unit).as(Unit?) } + NO_RESTART_HANDLER = ->(_unit : self, _service : Service) { nil } + + def initialize(@service : Service, @handler_proc : Handler = DEFAULT_HANLDER) + end + + def initialize(service : Service, &handler_proc : Handler) + initialize(service, handler_proc) + end + + def handle(service : Service) : Unit? + handler_proc.call(self, service) + end + end + + abstract class Starter + abstract def logic : Array(Service) + + abstract def start : self + + def self.handle(service : Service) : Nil + unit = service.run(Unit.new(service)) + + if unit + while unit = service.handle(unit.not_nil!) + end + end + end + end + + abstract class ArrayStarter < Starter + getter services + + def initialize(@services : Array(Service)) + end + + def logic : Array(Service) + @services + end + end + + class SynchronousStarter < ArrayStarter + def start : self + l = logic + + channel = Channel(Nil).new(l.size) + logic.each do |service| + spawn do + Starter.handle(service) + channel.send(nil) + end + end + l.size.times do + channel.receive + end + Fiber.yield + + self + end + end + + class AsynchronousStarter < ArrayStarter + def start : self + logic.each do |service| + Starter.handle(service) + end + + self + end + end + + abstract class Runner + Log = Log.for(self) + + abstract def starters : Array(Starter) + + def run : self + s = starters + + channel = Channel(Nil).new(s.size) + s.each do |starter| + spawn do + starter.start + channel.send(nil) + end + end + s.size.times do + channel.receive + end + Fiber.yield + + self + end + end +end