plover 1.0.0
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +201 -0
- data/Rakefile +14 -0
- data/lib/plover.rb +215 -0
- data/sig/plover.rbs +4 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c02ad2ecd60c71f6183a2db31ba64dd0f15769cbf7774273913595dfb72ccd93
|
4
|
+
data.tar.gz: c6fb12369964a4aa2238ffde51d4675e5b716eae056e12311193bb3570ec204e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 15a087dd706ac377d6535956b9f62bb3a94e7811ce15c09e2a1b81ee3e79a2c25b26d495eedf5dd1a561e54b495d6c3867d9342a0d28ebbefc48fed7800c48b8
|
7
|
+
data.tar.gz: 7c8eee52f8818b246f1a57dd01947cb1ee398f12e92d0811559e0c3425ca93e5abfba0ecc2597c0dd091f74a48cf34feb7f159d43257f263c0b1eaab27c3d30b
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Charlton Trezevant
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
# Plover
|
2
|
+
|
3
|
+
Plover is a tiny, embeddable Ruby build system designed to provide "Just Enough to Be Comfortable".
|
4
|
+
|
5
|
+
Plover is not a Make/Rake clone. It's intended to make it easier to write clean, organized build scripts in plain Ruby.
|
6
|
+
|
7
|
+
To help you with that, Plover provides a lightweight DSL for defining build phases, managing configuration flags, and tracking build artifacts, making it easier to manage configuration, state, and shared logic across your build and release processes.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
#### Tiny & Embeddable
|
12
|
+
|
13
|
+
Plover consists of ~200 lines of plain Ruby, with no dependencies outside of the standard library.
|
14
|
+
|
15
|
+
Because Plover is built to be embeddable, you can copy/paste `lib/plover.rb` into your project if you'd rather not use the gem.
|
16
|
+
|
17
|
+
#### Phased Build Process
|
18
|
+
|
19
|
+
Define your build steps in distinct phases (setup, before_build, build, after_build, teardown) using a clean DSL that doesn't get in your way.
|
20
|
+
|
21
|
+
#### Flags
|
22
|
+
|
23
|
+
Easily configure your builds using flags passed via environment variables or directly as parameters.
|
24
|
+
|
25
|
+
Plover will automatically pick up environment variables prefixed with `PLOVER_FLAG_` and merge them with your build configuration.
|
26
|
+
|
27
|
+
#### Artifacts
|
28
|
+
|
29
|
+
Track outputs from each build phase and retrieve them for further processing or validation.
|
30
|
+
|
31
|
+
#### Extensible
|
32
|
+
|
33
|
+
Plover provides a Concern system similar to ActiveSupport::Concern to help you organize and reuse common build functionality.
|
34
|
+
|
35
|
+
|
36
|
+
## Installation
|
37
|
+
|
38
|
+
You can install Plover as a gem:
|
39
|
+
|
40
|
+
```
|
41
|
+
gem install plover
|
42
|
+
```
|
43
|
+
|
44
|
+
Or, add it to your project’s Gemfile:
|
45
|
+
|
46
|
+
```
|
47
|
+
gem "plover"
|
48
|
+
```
|
49
|
+
|
50
|
+
Because Plover is built to be embeddable, you can also just copy/paste `lib/plover.rb` into your project if you'd rather not use RubyGems (it's ~200 lines of plain Ruby, with no dependencies outside of the standard library).
|
51
|
+
|
52
|
+
## Usage
|
53
|
+
|
54
|
+
Plover uses Plover to publish itself, so a real-world example can be found [in this repository](https://github.com/chtzvt/plover/blob/master/.github/workflows/publish.rb).
|
55
|
+
|
56
|
+
|
57
|
+
### Basic Example
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
require "plover" # or require_relative "path/to/your/plover.rb"
|
61
|
+
|
62
|
+
class MyBuilder < Plover::Builder
|
63
|
+
# Require a flag named :my_flag
|
64
|
+
expect_flags(:my_flag)
|
65
|
+
|
66
|
+
# Setup phase: print a message
|
67
|
+
phase(:setup) do
|
68
|
+
puts "Setting up the build..."
|
69
|
+
end
|
70
|
+
|
71
|
+
# Build phase: simulate a build operation
|
72
|
+
phase(:build) do
|
73
|
+
puts "Building the project..."
|
74
|
+
do_a_thing if flag(:my_flag)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Teardown phase: print a cleanup message
|
78
|
+
phase(:teardown) do
|
79
|
+
puts "Cleaning up..."
|
80
|
+
end
|
81
|
+
|
82
|
+
def do_a_thing
|
83
|
+
puts "I did a thing! #{flag(:my_flag)}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Create an instance with the required flag and run the build phases.
|
88
|
+
MyBuilder.new(my_flag: "cool").run
|
89
|
+
```
|
90
|
+
|
91
|
+
### Shared Concerns
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
require "plover"
|
95
|
+
|
96
|
+
# Define a concern under the Plover::Builder::Common namespace.
|
97
|
+
module Plover::Builder::Common::Greeting
|
98
|
+
extend Plover::Concern
|
99
|
+
|
100
|
+
# When this module is included, the `included` block is stored and later evaluated
|
101
|
+
# in the context of the including Builder.
|
102
|
+
included do
|
103
|
+
# Define an instance method.
|
104
|
+
def greet_builder
|
105
|
+
puts "(Concern) Hey there #{flag(:name)}!"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add a phase step in the setup phase.
|
109
|
+
phase(:setup) do
|
110
|
+
puts "A Builder and a Concern enter:"
|
111
|
+
end
|
112
|
+
|
113
|
+
# Prepend a step to the build phase that calls the class method from the concern.
|
114
|
+
prepend_phase(:build) do
|
115
|
+
self.class.greet_class
|
116
|
+
end
|
117
|
+
|
118
|
+
# Prepend a step to the after_build phase.
|
119
|
+
prepend_phase(:after_build) do
|
120
|
+
greet_builder
|
121
|
+
puts "(Concern) See you later."
|
122
|
+
end
|
123
|
+
|
124
|
+
# Add a step to the teardown phase.
|
125
|
+
phase(:teardown) do
|
126
|
+
puts "fin."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Define class methods that will be extended onto the including class.
|
131
|
+
class_methods do
|
132
|
+
def greet_class
|
133
|
+
puts "(Concern) Hello! I'm a Concern. We can prepend or append steps to phases, define class or instance methods, and access state."
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Define a builder subclass that uses the Greeting concern.
|
139
|
+
class MyBuilder < Plover::Builder
|
140
|
+
# Include the concern by specifying its name.
|
141
|
+
common_include "Greeting"
|
142
|
+
|
143
|
+
# Optionally, you could include multiple concerns:
|
144
|
+
# common_include "Greeting", "SomethingElse"
|
145
|
+
# or include all common modules:
|
146
|
+
# common_include_all
|
147
|
+
|
148
|
+
# Register a build phase step that calls a builder method.
|
149
|
+
phase(:build) do
|
150
|
+
builder_greeting
|
151
|
+
end
|
152
|
+
|
153
|
+
# Register an after_build phase step.
|
154
|
+
phase(:after_build) do
|
155
|
+
builder_goodbye
|
156
|
+
end
|
157
|
+
|
158
|
+
def builder_greeting
|
159
|
+
# Set a flag that can be used by the concern.
|
160
|
+
set_flag(:name, self.class.name)
|
161
|
+
puts "(Builder) Hi! I'm a Builder named #{flag(:name)}."
|
162
|
+
end
|
163
|
+
|
164
|
+
def builder_goodbye
|
165
|
+
puts "(Builder) Goodbye!"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Instantiate and run the builder.
|
170
|
+
MyBuilder.new.run
|
171
|
+
```
|
172
|
+
|
173
|
+
|
174
|
+
## Running Your Build
|
175
|
+
|
176
|
+
Plover is typically invoked by running your build script (e.g. ruby path/to/your/Ploverfile.rb). You can pass configuration flags via environment variables:
|
177
|
+
|
178
|
+
```
|
179
|
+
PLOVER_FLAG_GEM_VERSION="v0.1.0" \
|
180
|
+
PLOVER_FLAG_RUBYGEMS_RELEASE_TOKEN="YOUR_TOKEN" \
|
181
|
+
PLOVER_FLAG_GITHUB_RELEASE_TOKEN="YOUR_TOKEN" \
|
182
|
+
ruby path/to/your/Ploverfile.rb
|
183
|
+
```
|
184
|
+
|
185
|
+
Environment variables will automatically be configured as Builder flags with a common convention (`PLOVER_FLAG_MY_OPTION` becomes `flag(:my_option)`).
|
186
|
+
|
187
|
+
## Contributing
|
188
|
+
|
189
|
+
Contributions, issues, and feature requests are welcome!
|
190
|
+
Feel free to check issues page.
|
191
|
+
|
192
|
+
1. Fork the repository.
|
193
|
+
2. Create a new branch (`git switch -c feature/my-feature`).
|
194
|
+
3. Make your changes.
|
195
|
+
4. Submit a pull request.
|
196
|
+
|
197
|
+
|
198
|
+
## License
|
199
|
+
|
200
|
+
This project is licensed under the MIT License. See LICENSE for details.
|
201
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
|
5
|
+
require "rspec/core/rake_task"
|
6
|
+
|
7
|
+
require "standard/rake"
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
t.ruby_opts = %w[-w]
|
11
|
+
end
|
12
|
+
|
13
|
+
task test: %i[spec standard]
|
14
|
+
task default: %i[test]
|
data/lib/plover.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Plover
|
4
|
+
require "shellwords"
|
5
|
+
require "fileutils"
|
6
|
+
|
7
|
+
VERSION = "1.0.0"
|
8
|
+
|
9
|
+
class PloverError < StandardError; end
|
10
|
+
|
11
|
+
module Concern
|
12
|
+
def self.extended(base)
|
13
|
+
base.instance_variable_set(:@_included_blocks, [])
|
14
|
+
end
|
15
|
+
|
16
|
+
def included(base = nil, &block)
|
17
|
+
return unless base.nil?
|
18
|
+
|
19
|
+
@_included_blocks << block if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def class_methods(&block)
|
23
|
+
const_set(:ClassMethods, Module.new(&block))
|
24
|
+
end
|
25
|
+
|
26
|
+
def apply_concern(base)
|
27
|
+
base.extend(const_get(:ClassMethods)) if const_defined?(:ClassMethods)
|
28
|
+
@_included_blocks.each { |blk| base.class_eval(&blk) }
|
29
|
+
base.include(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Builder
|
34
|
+
class FlagError < PloverError; end
|
35
|
+
|
36
|
+
class BuildError < PloverError; end
|
37
|
+
|
38
|
+
class ArtifactError < BuildError; end
|
39
|
+
|
40
|
+
module Common; end
|
41
|
+
|
42
|
+
attr_reader :configuration
|
43
|
+
|
44
|
+
@configuration = {
|
45
|
+
steps: {
|
46
|
+
setup: [],
|
47
|
+
before_build: [],
|
48
|
+
build: [],
|
49
|
+
after_build: [],
|
50
|
+
teardown: []
|
51
|
+
},
|
52
|
+
options: {
|
53
|
+
flags: {},
|
54
|
+
expected_flags: []
|
55
|
+
},
|
56
|
+
include_common: :none,
|
57
|
+
artifacts: {
|
58
|
+
setup: {},
|
59
|
+
before_build: {},
|
60
|
+
build: {},
|
61
|
+
after_build: {},
|
62
|
+
teardown: {}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
|
66
|
+
class << self
|
67
|
+
attr_reader :configuration
|
68
|
+
|
69
|
+
def inherited(subclass)
|
70
|
+
auto_include_common
|
71
|
+
subclass.instance_variable_set(:@configuration, deep_copy(configuration))
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_flag(name, value)
|
75
|
+
configuration[:options][:flags][name.to_sym] = value
|
76
|
+
end
|
77
|
+
|
78
|
+
def expect_flags(*flags)
|
79
|
+
configuration[:options][:expected_flags] = flags.map(&:to_sym)
|
80
|
+
end
|
81
|
+
|
82
|
+
def common_include_all
|
83
|
+
configuration[:options][:common_include] = :all
|
84
|
+
end
|
85
|
+
|
86
|
+
def common_include_none
|
87
|
+
configuration[:options][:common_include] = :none
|
88
|
+
end
|
89
|
+
|
90
|
+
def common_include(*modules)
|
91
|
+
configuration[:options][:common_include] = [] unless configuration[:options][:common_include].is_a?(Array)
|
92
|
+
|
93
|
+
configuration[:options][:common_include].concat(modules)
|
94
|
+
end
|
95
|
+
|
96
|
+
def env_flags
|
97
|
+
ENV.select { |key, _| key.start_with?("PLOVER_FLAG_") }
|
98
|
+
.map { |key, value| [key.sub(/^PLOVER_FLAG_/, "").downcase.to_sym, value] }
|
99
|
+
.to_h
|
100
|
+
end
|
101
|
+
|
102
|
+
def phase(phase, &block)
|
103
|
+
configuration[:steps][phase] << block
|
104
|
+
end
|
105
|
+
|
106
|
+
def prepend_phase(phase, &block)
|
107
|
+
configuration[:steps][phase].unshift(block)
|
108
|
+
end
|
109
|
+
|
110
|
+
def auto_include_common
|
111
|
+
return if configuration[:options][:common_include] == :none
|
112
|
+
|
113
|
+
ObjectSpace.each_object(Module).select { |m| m&.name&.start_with?("Plover::Builder::Common::") }.each do |mod|
|
114
|
+
next if mod.name.to_s.end_with?("::ClassMethods")
|
115
|
+
|
116
|
+
next if configuration[:options][:common_include].is_a?(Array) && !configuration[:options][:common_include].any? { |m| m.end_with?("::#{mod.name}") }
|
117
|
+
|
118
|
+
mod.apply_concern(self) if mod.respond_to?(:apply_concern) && !included_modules.include?(mod)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def deep_copy(obj)
|
123
|
+
case obj
|
124
|
+
when Hash
|
125
|
+
obj.each_with_object({}) { |(k, v), h| h[k] = deep_copy(v) }
|
126
|
+
when Array
|
127
|
+
obj.map { |e| deep_copy(e) }
|
128
|
+
else
|
129
|
+
obj
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def initialize(flags = {})
|
135
|
+
self.class.auto_include_common
|
136
|
+
@configuration = self.class.configuration
|
137
|
+
@configuration[:options][:flags] = @configuration[:options][:flags].merge(flags).merge(self.class.env_flags)
|
138
|
+
|
139
|
+
return unless @configuration[:options][:expected_flags].any?
|
140
|
+
|
141
|
+
missing_flags = @configuration[:options][:expected_flags].reject { |sym| @configuration[:options][:flags].key?(sym) }
|
142
|
+
raise BuildError.new("Missing required flags: #{missing_flags.join(", ")}") if missing_flags.any?
|
143
|
+
end
|
144
|
+
|
145
|
+
def prepend_phase(phase, &block)
|
146
|
+
@configuration[:steps][phase].unshift(block)
|
147
|
+
end
|
148
|
+
|
149
|
+
def append_phase(phase, &block)
|
150
|
+
@configuration[:steps][phase] << block
|
151
|
+
end
|
152
|
+
|
153
|
+
def esc(str)
|
154
|
+
Shellwords.escape(str)
|
155
|
+
end
|
156
|
+
|
157
|
+
def flag(name)
|
158
|
+
@configuration[:options][:flags][name.to_sym]
|
159
|
+
end
|
160
|
+
|
161
|
+
def esc_flag(name)
|
162
|
+
flag(name) ? esc(flag(name)) : nil
|
163
|
+
end
|
164
|
+
|
165
|
+
def set_flag(name, value)
|
166
|
+
@configuration[:options][:flags][name.to_sym] = value
|
167
|
+
end
|
168
|
+
|
169
|
+
def raise_unless_flag(name, message)
|
170
|
+
raise FlagError.new(message) unless flag(name)
|
171
|
+
end
|
172
|
+
|
173
|
+
def push_artifact(name, value)
|
174
|
+
return unless @current_phase
|
175
|
+
@configuration[:artifacts][@current_phase][name] = value
|
176
|
+
end
|
177
|
+
|
178
|
+
def esc_artifact(phase, name)
|
179
|
+
artifact = self.artifact(phase, name)
|
180
|
+
artifact ? esc(artifact) : nil
|
181
|
+
end
|
182
|
+
|
183
|
+
def artifact(phase, name)
|
184
|
+
@configuration[:artifacts][phase][name]
|
185
|
+
end
|
186
|
+
|
187
|
+
def artifacts(phase = nil)
|
188
|
+
phase ? @configuration[:artifacts][phase] : @configuration[:artifacts]
|
189
|
+
end
|
190
|
+
|
191
|
+
def raise_unless_artifact(phase, name, message)
|
192
|
+
raise ArtifactError.new(message) unless artifact(phase, name)
|
193
|
+
end
|
194
|
+
|
195
|
+
def fail_build(message)
|
196
|
+
raise BuildError.new(message)
|
197
|
+
end
|
198
|
+
|
199
|
+
def run_phase(phase)
|
200
|
+
@current_phase = phase
|
201
|
+
@configuration[:steps][phase].each { |block| instance_exec(&block) }
|
202
|
+
@current_phase = nil
|
203
|
+
end
|
204
|
+
|
205
|
+
def run
|
206
|
+
run_phase(:setup)
|
207
|
+
Dir.chdir(flag(:build_root) || ".") do
|
208
|
+
run_phase(:before_build)
|
209
|
+
run_phase(:build)
|
210
|
+
run_phase(:after_build)
|
211
|
+
end
|
212
|
+
run_phase(:teardown)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
data/sig/plover.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: plover
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Charlton Trezevant
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-03-17 00:00:00.000000000 Z
|
11
|
+
dependencies: []
|
12
|
+
description: Plover is a tiny, embeddable Ruby build system.
|
13
|
+
email:
|
14
|
+
- ct@ctis.me
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- LICENSE.txt
|
20
|
+
- README.md
|
21
|
+
- Rakefile
|
22
|
+
- lib/plover.rb
|
23
|
+
- sig/plover.rbs
|
24
|
+
homepage: https://github.com/chtzvt/plover
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata:
|
28
|
+
homepage_uri: https://github.com/chtzvt/plover
|
29
|
+
source_code_uri: https://github.com/chtzvt/plover
|
30
|
+
changelog_uri: https://github.com/chtzvt/plover/releases
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 3.1.0
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubygems_version: 3.6.2
|
46
|
+
specification_version: 4
|
47
|
+
summary: Plover is a tiny, embeddable Ruby build system.
|
48
|
+
test_files: []
|