feat: begin the repo a bit late but better than never

This commit is contained in:
David Bailey 2023-04-03 22:36:01 +02:00
commit 607159334b
22 changed files with 898 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

13
.rubocop.yml Normal file
View file

@ -0,0 +1,13 @@
AllCops:
TargetRubyVersion: 2.6
Style/StringLiterals:
Enabled: true
EnforcedStyle: double_quotes
Style/StringLiteralsInInterpolation:
Enabled: true
EnforcedStyle: double_quotes
Layout/LineLength:
Max: 120

14
Gemfile Normal file
View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
source "https://rubygems.org"
# Specify your gem's dependencies in comfpile.gemspec
gemspec
gem "rake", "~> 13.0"
gem "pry"
gem "minitest", "~> 5.0"
gem "debug", "~> 1.0"
gem "rubocop", "~> 1.21"

59
Gemfile.lock Normal file
View file

@ -0,0 +1,59 @@
PATH
remote: .
specs:
comfpile (0.1.0)
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
coderay (1.1.3)
debug (1.6.2)
irb (>= 1.3.6)
reline (>= 0.3.1)
io-console (0.5.11)
irb (1.4.1)
reline (>= 0.3.0)
json (2.6.3)
method_source (1.0.0)
minitest (5.18.0)
parallel (1.22.1)
parser (3.2.1.1)
ast (~> 2.4.1)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.7.0)
reline (0.3.1)
io-console (~> 0.5)
rexml (3.2.5)
rubocop (1.48.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.26.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.27.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
unicode-display_width (2.4.2)
PLATFORMS
x86_64-linux
DEPENDENCIES
comfpile!
debug (~> 1.0)
minitest (~> 5.0)
pry
rake (~> 13.0)
rubocop (~> 1.21)
BUNDLED WITH
2.3.22

29
README.md Normal file
View file

@ -0,0 +1,29 @@
# Comfpile
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/comfpile`. To experiment with that code, run `bin/console` for an interactive prompt.
TODO: Delete this and the text above, and describe your gem
## Installation
Install the gem and add to the application's Gemfile by executing:
$ bundle add comfpile
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install comfpile
## Usage
TODO: Write usage instructions here
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/comfpile.

16
Rakefile Normal file
View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
require "bundler/gem_tasks"
require "rake/testtask"
Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/test_*.rb"]
end
require "rubocop/rake_task"
RuboCop::RakeTask.new
task default: %i[test rubocop]

101
bin/console Executable file
View file

@ -0,0 +1,101 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup"
require "comfpile"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
$core = Comfpile::Core.new()
`mkdir /tmp/test`
`touch /tmp/test/main.cpp`
`mkdir /tmp/test2/`
`touch /tmp/test2/main.cpp`
$core.add_artefact_engine Comfpile::FilesourceEngine, root_path: "/tmp/test2"
$core.add_artefact_engine Comfpile::FilesourceEngine, root_path: "/tmp/test"
$core.add_artefact_engine do |engine|
engine.add_recipe(:parsed, /^(.+)\.(h|c|cpp)$/) do |match, a|
a.parent_artefact :sourcefile, a.target
a.add_step do
puts "Parsing file #{@target}..."
@linked_artefacts = []
File.readlines(@required_artefacts[:sourcefile][@target][:file]).each do |l|
case l
when /^#include\s*[<"](.+)[>"]/
puts "Got include for file #{$1}!"
@linked_artefacts << craft_artefact(:parsed, $1)
when /\/\/+\s*require\s*[<"]((?:.+)\.(?:c|cpp))[>"]/
puts "Got require for file #{$1}!"
@linked_artefacts << require_artefact(:parsed, $1)
end
end
end
end
engine.add_recipe(:dependency_list, /^(.+)\.(h|c|cpp)$/) do |match, a|
a.parent_artefact :parsed, a.target
a.add_step do
puts "Generating dependency list for #{@target}..."
parsed_marker = {}
parsing_list = [@parent_artefact]
loop do
break if parsing_list.empty?
a = parsing_list.pop
next if a.nil?
next unless a.stage == :parsed
next unless a.succeeded?
next if parsed_marker[a.target]
parsed_marker[a.target] = true
parsing_list += a.linked_artefacts
end
@parameters[:dependency_list] = parsed_marker.keys
end
end
engine.add_recipe(:x86_debug, /^run (.+)/) do |match, a|
a.require_artefact :parsed, "#{match[1]}.cpp"
end
engine.add_recipe(:x86_debug, /(.+)\.o$/) do |match, a|
a.require_artefact :sourcefile, "#{match[1]}.cpp"
a.add_step do
end
true
end
end
$tst = $core.craft_artefact(:dependency_list, "main.cpp");
50.times do
$core.execute_step
end
puts "Dependency list is: #{$tst[:dependency_list]}"
# (If you use this, don't forget to add pry to your Gemfile!)
require "pry"
Pry.start

8
bin/setup Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here

8
comfpile.code-workspace Normal file
View file

@ -0,0 +1,8 @@
{
"folders": [
{
"path": ".."
}
],
"settings": {}
}

35
comfpile.gemspec Normal file
View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
require_relative "lib/comfpile/version"
Gem::Specification.new do |spec|
spec.name = "comfpile"
spec.version = Comfpile::VERSION
spec.authors = ["David Bailey"]
spec.email = ["davidbailey.2889@gmail.com"]
spec.summary = "Comfortably compile source code together"
spec.description = "Compile based on simple, understandable source dependencies in-sourcecode, rather than a makefile or whatever other hot mess."
spec.homepage = "https://github.com/lucidergs/comfpile"
spec.required_ruby_version = ">= 2.6.0"
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(__dir__) do
`git ls-files -z`.split("\x0").reject do |f|
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
end
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
# Uncomment to register a new dependency of your gem
# spec.add_dependency "example-gem", "~> 1.0"
# For more information and examples about making a new gem, check out our
# guide at: https://bundler.io/guides/creating_gem.html
end

12
lib/comfpile.rb Normal file
View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
require_relative "comfpile/version"
require_relative 'comfpile/core.rb'
require_relative 'comfpile/engines/filesource_engine.rb'
module Comfpile
class Error < StandardError; end
# Your code goes here...
end

216
lib/comfpile/artefact.rb Normal file
View file

@ -0,0 +1,216 @@
require 'debug'
module Comfpile
class ArtefactExecSkipError < StandardError
end
class Artefact
attr_reader :core, :engine
attr_reader :exit_state
attr_reader :stage, :target
attr_reader :linked_artefacts
# ARTEFACT STATES
#
# The following states are known to the system:
# - blocked: The Artefact is blocked and waiting on other artefacts
# - waiting: The Artefact is idle and ready to be queued
# - running: The Artefact is currently being run
# Note that this is not a state, but is determined by the
# @running flag
# - succeeded: it has finished its work without issue
# - skipped: it didn't run/won't run because of failed dependencies
# - failed: it has failed due to a requirement not being met
#
# Meta-States exist:
# - in_progress/completed: Anything but/Only succeeded, skipped, failed
def initialize(core, engine, stage, target)
@core = core
@engine = engine
@stage = stage
@target = target
@age = Time.at(0)
@parent_artefact = nil
@required_artefacts = nil
@linked_artefacts = nil
@steps = []
@step_additions = nil
@waitlist = []
@steps_done_ctr = 0
@parameters = {}
@exit_state = nil
@running = false
end
def [](key)
v = @parameters[key]
return v unless v.nil?
if @parent_artefact
return @parent_artefact[key]
end
end
def []=(key, value)
@parameters[key] = value
end
private def add_step_data(data)
@step_additions ||= []
@step_additions << data
end
private def process_additional_step_data
unless @step_additions.nil?
@steps.insert(@steps_done_ctr, @step_additions)
@steps.flatten!
@step_additions = nil
end
end
def add_step(&block)
add_step_data({
type: :block,
executed: false,
block: block
})
end
def parent_artefact(stage, target)
@parent_artefact = require_artefact(stage, target)
end
def require_artefact(stage, target)
artefact = @core.craft_artefact(stage, target)
if(artefact.nil?)
fail! "Missing artefact dependency for #{stage} #{target}!"
else
@waitlist << {
artefact: artefact,
required: true
}
end
@required_artefacts ||= {}
@required_artefacts[stage] ||= {}
@required_artefacts[stage][target] = artefact
artefact
end
def craft_artefact(stage, target)
artefact = @core.craft_artefact(stage, target)
artefact
end
def waitlist_empty?
return true if completed?
loop do
return true if @waitlist.empty?
item = @waitlist[-1]
return false if item[:artefact].in_progress?
if not item[:required]
@waitlist.pop
elsif item[:artefact].succeeded?
@waitlist.pop
else
skip! skip! "Failed artefact dependency: #{item[:artefact]}"
return true
end
end
end
def state
return :blocked unless waitlist_empty?
return @exit_state unless @exit_state.nil?
return :running if @running
return :waiting
end
def completed?
not @exit_state.nil?
end
def succeeded?
@exit_state == :succeeded
end
def in_progress?
not completed?
end
def waiting?
self.state == :waiting
end
private def mark_state_change(state, reason, abort: false)
puts "#{@stage} #{target}: Reached state #{state}: #{reason}"
@exit_state = state
@reason = reason
abort_step! if abort
end
def skip!(reason, **opts)
mark_state_change(:skipped, reason, **opts)
end
def fail!(reason, **opts)
mark_state_change(:failed, reason, **opts)
end
def succeed!(reason, **opts)
mark_state_change(:succeeded, reason, **opts)
end
def abort_step!
raise ArtefactExecSkipError
end
def execute_step
return unless waiting?
@running = true
process_additional_step_data
next_step = @steps[@steps_done_ctr]
succeed! "All done", abort: true if next_step.nil?
case next_step[:type]
when :block
instance_exec &next_step[:block]
else
fail! "Unknown artefact step taken!", abort: true
end
@steps_done_ctr += 1
succeed! "All done", abort: true if @steps_done_ctr >= @steps.length
ensure
@running = false
end
def to_s
"#<Compfile::Artefact #{@stage} #{@target}>"
end
end
end

View file

@ -0,0 +1,60 @@
require_relative 'artefact.rb'
module Comfpile
class ArtefactEngine
attr_accessor :priority, :subpriority
def initialize(core, **options)
@core = core
@priority = options[:priority] || 0
@subpriority = options[:subpriority] || 0
@recipes = []
end
def craft(stage, target)
@recipes.each do |recipe|
match = target
if recipe[:stage]
next unless stage == recipe[:stage]
end
if r = recipe[:regex]
if r.is_a? String
next unless target == r
else
match = r.match target
next if match.nil?
end
end
new_artefact = Artefact.new(@core, self, stage, target)
item = recipe[:block].call(match, new_artefact)
return new_artefact if item
end
nil
end
def <=>(other)
prio = other.priority <=> self.priority
return prio unless prio == 0
other.subpriority <=> self.subpriority
end
def add_recipe(stage = nil, target_regex, &block)
@recipes << {
regex: target_regex,
stage: stage,
block: block
}
end
end
end

113
lib/comfpile/core.rb Normal file
View file

@ -0,0 +1,113 @@
require_relative 'artefact_engine.rb'
module Comfpile
class Core
attr_reader :processing_stack
def initialize()
@artefact_engines = []
@artefact_prio_counter = 0
# Artefacts are arranged in a double-hash.
# First query is stage, second filename.
@artefacts = {}
# Stack-style processing queue for item processing
@processing_stack = []
end
def find_artefact(stage, file = :none)
@artefacts.dig(stage, file)
end
def craft_artefact(stage, file = :none)
artefact = find_artefact(stage, file)
return artefact unless artefact.nil?
@artefact_engines.each do |engine|
artefact = engine.craft stage, file
if artefact
@artefacts[stage] ||= {}
@artefacts[stage][file] = artefact
@processing_stack.push artefact
return artefact
end
end
nil
end
def add_artefact(stage, file = :none, engine: nil, artefact_class: Comfpile::Artefact)
return unless find_artefact(stage, file).nil?
a = Artefact.new(self, engine, stage, file);
@artefacts[stage] ||= {}
@artefacts[stage][file] = a
yield(a) if block_given?
nil
end
def add_artefact_engine(engine_class = Comfpile::ArtefactEngine, **options)
new_engine = engine_class.new(self,
subpriority: @artefact_prio_counter, **options)
@artefact_prio_counter += 1
yield(new_engine) if block_given?
@artefact_engines << new_engine
@artefact_engines.sort!
new_engine
end
def processing_stack_prune()
loop do
return if @processing_stack.empty?
if @processing_stack[-1].completed?
@processing_stack.pop
else
break
end
end
nil
end
def processing_stack_find_next()
@processing_stack.reverse_each do |a|
return a if a.waiting?
end
nil
end
def execute_step
return if @processing_stack.empty?
puts "Got #{@processing_stack.length} items..."
processing_stack_prune
artefact = processing_stack_find_next
return nil if artefact.nil?
puts "Processing artefact #{artefact.stage} #{artefact.target}"
begin
artefact.execute_step
rescue ArtefactExecSkipError
end
nil
end
end
end

View file

@ -0,0 +1,25 @@
module Comfpile
class FilesourceEngine < ArtefactEngine
def initialize(core, **options)
super(core, **options)
@root_path = options[:root_path]
end
def craft(stage, target)
return nil unless stage == :sourcefile
full_path = File.join(@root_path, target)
return nil unless File.exists? full_path
a = Artefact.new(@core, self, stage, target);
a[:file] = full_path
a[:filepath] = full_path
a
end
end
end

View file

@ -0,0 +1,88 @@
module Compfile
class ParserArtefact < Artefact
attr_reader :included_files
attr_reader :required_files
def initialize(*args)
super(*args)
@included_files = []
@required_files = []
parent_artefact :sourcefile, @target
add_step do
end
end
end
class ParserEngine < ArtefactEngine
def initialize(core, **options)
super(core, **options)
@input_file_regex = options[:allowed_files]
@require_regex = options[:require_reg]
@include_regex = options[:include_reg]
end
def generate_parser_artefact(stage, target)
match = @input_file_regex.match target
return nil if match.nil?
a = Artefact.new(@core, self, stage, target)
a.parent_artefact(:sourcefile, target)
a.add_step do
@parameters[:included_files] = []
@parameters[:required_files] = []
File.readlines(@parent_artefact[:file]) do |l|
case l
when @require_regex
filename = $~[:file]
own_dir = File.dirname(@target)
relative_file = File.join(own_dir, filename)
unless craft_artefact(:sourcefile, relative_file).nil?
@parameters[:required_files] << require_artefact(:parsed, relative_file)
else
@parameters[:required_files] << require_artefact(:parsed, filename)
end
when @include_regex
filename = $~[:file]
own_dir = File.dirname(@target)
relative_file = File.join(own_dir, filename)
unless craft_artefact(:sourcefile, relative_file).nil?
@parameters[:included_files] << craft_artefact(:parsed, relative_file)
else
@parameters[:included_files] << craft_artefact(:parsed, filename)
end
end
end
end
end
def generate_dependency_artefact(stage, target)
end
def craft(stage, target)
case stage
when :parsed
generate_parser_artefact(stage, target)
when :dependency_list
else
nil
end
end
end

View file

@ -0,0 +1,49 @@
require_relative 'sourcefile.rb'
module Comfpile
class ResourceLocation
attr_reader :base_path
attr_reader :files
attr_reader :priority, :subpriority
def initialize(core, base_path, **options)
raise ArgumentError, "Resource path must be a string!" unless base_path.is_a? String
raise ArgumentError, "Resource path must be a valid file or directory" unless File.exists?(base_path)
@base_path = File.expand_path(base_path)
@name = options[:name] || @location
@core = core
@priority = options[:priority] || 0;
@subpriority = options[:subpriority] || 0;
@settings_map = Hash.new()
@known_files = Hash.new()
end
def find_sourcefile(item_key)
return @known_files[item_key] if @known_files.include? item_key
item_path = File.join(@base_path, item_key)
new_item = nil
if File.exists? item_path
new_item = Sourcefile.new(self, item_path, item_key)
@known_files[item_key] = new_item
end
new_item
end
def <=>(other)
prio = other.priority <=> self.priority
return prio unless prio == 0
other.subpriority <=> self.subpriority
end
end
end

View file

@ -0,0 +1,16 @@
module Comfpile
class Sourcefile
attr_reader :full_path, :local_path, :resource_location
def initialize(resource_location, full_path, local_path)
@resource_location = resource_location
@full_path = full_path
@local_path = local_path
end
def to_s
@full_path
end
end
end

5
lib/comfpile/version.rb Normal file
View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
module Comfpile
VERSION = "0.1.0"
end

4
sig/comfpile.rbs Normal file
View file

@ -0,0 +1,4 @@
module Comfpile
VERSION: String
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
end

13
test/test_comfpile.rb Normal file
View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
require "test_helper"
class TestComfpile < Minitest::Test
def test_that_it_has_a_version_number
refute_nil ::Comfpile::VERSION
end
def test_it_does_something_useful
assert false
end
end

6
test/test_helper.rb Normal file
View file

@ -0,0 +1,6 @@
# frozen_string_literal: true
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "comfpile"
require "minitest/autorun"