commit 607159334b46fae1e183934bdb9c8617080387d3 Author: David Bailey Date: Mon Apr 3 22:36:01 2023 +0200 feat: :sparkles: begin the repo a bit late but better than never diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9106b2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..e3462a7 --- /dev/null +++ b/.rubocop.yml @@ -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 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..ff8515e --- /dev/null +++ b/Gemfile @@ -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" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..0c00d9a --- /dev/null +++ b/Gemfile.lock @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..cab2564 --- /dev/null +++ b/README.md @@ -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. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..21ef734 --- /dev/null +++ b/Rakefile @@ -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] diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..fe6a8ab --- /dev/null +++ b/bin/console @@ -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 + diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -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 diff --git a/comfpile.code-workspace b/comfpile.code-workspace new file mode 100644 index 0000000..bab1b7f --- /dev/null +++ b/comfpile.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "path": ".." + } + ], + "settings": {} +} \ No newline at end of file diff --git a/comfpile.gemspec b/comfpile.gemspec new file mode 100644 index 0000000..1625592 --- /dev/null +++ b/comfpile.gemspec @@ -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 diff --git a/lib/comfpile.rb b/lib/comfpile.rb new file mode 100644 index 0000000..fb50f65 --- /dev/null +++ b/lib/comfpile.rb @@ -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 diff --git a/lib/comfpile/artefact.rb b/lib/comfpile/artefact.rb new file mode 100644 index 0000000..e29fff6 --- /dev/null +++ b/lib/comfpile/artefact.rb @@ -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 + "#" + end + end +end \ No newline at end of file diff --git a/lib/comfpile/artefact_engine.rb b/lib/comfpile/artefact_engine.rb new file mode 100644 index 0000000..5f83db9 --- /dev/null +++ b/lib/comfpile/artefact_engine.rb @@ -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 \ No newline at end of file diff --git a/lib/comfpile/core.rb b/lib/comfpile/core.rb new file mode 100644 index 0000000..99ae8ab --- /dev/null +++ b/lib/comfpile/core.rb @@ -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 \ No newline at end of file diff --git a/lib/comfpile/engines/filesource_engine.rb b/lib/comfpile/engines/filesource_engine.rb new file mode 100644 index 0000000..d040256 --- /dev/null +++ b/lib/comfpile/engines/filesource_engine.rb @@ -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 \ No newline at end of file diff --git a/lib/comfpile/engines/parser_engine.rb b/lib/comfpile/engines/parser_engine.rb new file mode 100644 index 0000000..024b159 --- /dev/null +++ b/lib/comfpile/engines/parser_engine.rb @@ -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 \ No newline at end of file diff --git a/lib/comfpile/resource_location.rb b/lib/comfpile/resource_location.rb new file mode 100644 index 0000000..f8e7c28 --- /dev/null +++ b/lib/comfpile/resource_location.rb @@ -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 \ No newline at end of file diff --git a/lib/comfpile/sourcefile.rb b/lib/comfpile/sourcefile.rb new file mode 100644 index 0000000..96794fa --- /dev/null +++ b/lib/comfpile/sourcefile.rb @@ -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 \ No newline at end of file diff --git a/lib/comfpile/version.rb b/lib/comfpile/version.rb new file mode 100644 index 0000000..ba0f811 --- /dev/null +++ b/lib/comfpile/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Comfpile + VERSION = "0.1.0" +end diff --git a/sig/comfpile.rbs b/sig/comfpile.rbs new file mode 100644 index 0000000..9996703 --- /dev/null +++ b/sig/comfpile.rbs @@ -0,0 +1,4 @@ +module Comfpile + VERSION: String + # See the writing guide of rbs: https://github.com/ruby/rbs#guides +end diff --git a/test/test_comfpile.rb b/test/test_comfpile.rb new file mode 100644 index 0000000..f851859 --- /dev/null +++ b/test/test_comfpile.rb @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..fcf7e7f --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) +require "comfpile" + +require "minitest/autorun"