rundoc 0.0.1 → 1.1.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.
- checksums.yaml +5 -5
- data/.github/workflows/check_changelog.yml +13 -0
- data/.gitignore +9 -0
- data/.travis.yml +8 -0
- data/CHANGELOG.md +22 -0
- data/Dockerfile +24 -0
- data/Gemfile +1 -0
- data/README.md +276 -74
- data/bin/rundoc +4 -52
- data/lib/rundoc.rb +15 -1
- data/lib/rundoc/cli.rb +84 -0
- data/lib/rundoc/code_command.rb +25 -6
- data/lib/rundoc/code_command/background.rb +9 -0
- data/lib/rundoc/code_command/background/log/clear.rb +17 -0
- data/lib/rundoc/code_command/background/log/read.rb +16 -0
- data/lib/rundoc/code_command/background/process_spawn.rb +96 -0
- data/lib/rundoc/code_command/background/start.rb +38 -0
- data/lib/rundoc/code_command/background/stop.rb +17 -0
- data/lib/rundoc/code_command/background/wait.rb +19 -0
- data/lib/rundoc/code_command/bash.rb +1 -1
- data/lib/rundoc/code_command/bash/cd.rb +21 -3
- data/lib/rundoc/code_command/file_command/append.rb +16 -11
- data/lib/rundoc/code_command/file_command/remove.rb +6 -5
- data/lib/rundoc/code_command/no_such_command.rb +4 -0
- data/lib/rundoc/code_command/pipe.rb +18 -5
- data/lib/rundoc/code_command/raw.rb +18 -0
- data/lib/rundoc/code_command/rundoc/depend_on.rb +37 -0
- data/lib/rundoc/code_command/rundoc/require.rb +41 -0
- data/lib/rundoc/code_command/rundoc_command.rb +6 -2
- data/lib/rundoc/code_command/website.rb +7 -0
- data/lib/rundoc/code_command/website/driver.rb +111 -0
- data/lib/rundoc/code_command/website/navigate.rb +29 -0
- data/lib/rundoc/code_command/website/screenshot.rb +28 -0
- data/lib/rundoc/code_command/website/visit.rb +47 -0
- data/lib/rundoc/code_command/write.rb +22 -7
- data/lib/rundoc/code_section.rb +41 -66
- data/lib/rundoc/parser.rb +5 -4
- data/lib/rundoc/peg_parser.rb +282 -0
- data/lib/rundoc/version.rb +2 -2
- data/rundoc.gemspec +9 -3
- data/test/fixtures/build_logs/rundoc.md +56 -0
- data/test/fixtures/depend_on/dependency/rundoc.md +5 -0
- data/test/fixtures/depend_on/main/rundoc.md +10 -0
- data/test/fixtures/java/rundoc.md +9 -0
- data/test/fixtures/rails_4/rundoc.md +151 -188
- data/test/fixtures/rails_5/rundoc.md +445 -0
- data/test/fixtures/rails_6/rundoc.md +451 -0
- data/test/fixtures/require/dependency/rundoc.md +5 -0
- data/test/fixtures/require/main/rundoc.md +10 -0
- data/test/fixtures/screenshot/rundoc.md +10 -0
- data/test/rundoc/code_commands/append_file_test.rb +33 -6
- data/test/rundoc/code_commands/background_test.rb +69 -0
- data/test/rundoc/code_commands/bash_test.rb +1 -1
- data/test/rundoc/code_commands/pipe_test.rb +1 -1
- data/test/rundoc/code_commands/remove_contents_test.rb +3 -4
- data/test/rundoc/code_section_test.rb +95 -2
- data/test/rundoc/parser_test.rb +7 -13
- data/test/rundoc/peg_parser_test.rb +381 -0
- data/test/rundoc/regex_test.rb +6 -6
- data/test/rundoc/test_parse_java.rb +1 -1
- data/test/test_helper.rb +1 -3
- metadata +143 -18
- data/Gemfile.lock +0 -38
    
        data/bin/rundoc
    CHANGED
    
    | @@ -15,12 +15,11 @@ $: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib') | |
| 15 15 | 
             
            require 'rundoc'
         | 
| 16 16 | 
             
            require 'thor'
         | 
| 17 17 |  | 
| 18 | 
            -
            class  | 
| 18 | 
            +
            class RundocThorCLI < Thor
         | 
| 19 19 |  | 
| 20 20 | 
             
              def initialize(*args)
         | 
| 21 21 | 
             
                super
         | 
| 22 | 
            -
                @path | 
| 23 | 
            -
                @working_dir  = Pathname.new(File.expand_path("../", @path))
         | 
| 22 | 
            +
                @path = options[:path]
         | 
| 24 23 | 
             
              end
         | 
| 25 24 |  | 
| 26 25 | 
             
              default_task :help
         | 
| @@ -28,55 +27,8 @@ class RundocCLI < Thor | |
| 28 27 | 
             
              desc "build", "turns rundoc file into docs and a project"
         | 
| 29 28 | 
             
              class_option  :path,   banner: "path/to/file.md",          optional: true, default: 'rundoc.md'
         | 
| 30 29 | 
             
              def build
         | 
| 31 | 
            -
                 | 
| 32 | 
            -
                source_contents = File.read(@path)
         | 
| 33 | 
            -
                tmp_dir         = @working_dir.join("tmp")
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                FileUtils.remove_entry_secure(tmp_dir) if tmp_dir.exist?
         | 
| 36 | 
            -
                tmp_dir.mkdir
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                puts "== Running your docs"
         | 
| 39 | 
            -
                Dir.chdir(tmp_dir) do
         | 
| 40 | 
            -
                  @output = Rundoc::Parser.new(source_contents, Rundoc.parser_options).to_md
         | 
| 41 | 
            -
                  Rundoc.sanitize(@output)
         | 
| 42 | 
            -
                  banner = "<!-- STOP\nThis file was generated by a rundoc script, don't modify it. \n"
         | 
| 43 | 
            -
                  banner << "Instead modify the rundoc script and re-run it.\nSTOP -->"
         | 
| 44 | 
            -
                  @output = "#{banner}\n#{@output}"
         | 
| 45 | 
            -
                end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                puts "== Done, run was successful"
         | 
| 48 | 
            -
                project_name = if Rundoc.project_root
         | 
| 49 | 
            -
                  Rundoc.project_root.split('/').last
         | 
| 50 | 
            -
                else
         | 
| 51 | 
            -
                  'project'
         | 
| 52 | 
            -
                end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                project_dir  = @working_dir.join(project_name)
         | 
| 55 | 
            -
             | 
| 56 | 
            -
                FileUtils.remove_entry_secure(project_dir) if project_dir.exist?
         | 
| 57 | 
            -
             | 
| 58 | 
            -
                cp_root = if Rundoc.project_root
         | 
| 59 | 
            -
                  tmp_dir.join(Rundoc.project_root, ".")
         | 
| 60 | 
            -
                else
         | 
| 61 | 
            -
                  tmp_dir.join(".")
         | 
| 62 | 
            -
                end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                FileUtils.cp_r(cp_root, project_dir)
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                FileUtils.remove_entry_secure(tmp_dir) if tmp_dir.exist?
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                puts "== Done, writing original source to #{project_dir}/source.md"
         | 
| 69 | 
            -
                source_path = project_dir.join("README.md")
         | 
| 70 | 
            -
                File.open(source_path, "w") { |f| f.write @output }
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                puts "== Copying source"
         | 
| 73 | 
            -
                source_path = project_dir.join("coppied-#{@path.split('/').last}")
         | 
| 74 | 
            -
                File.open(source_path, "w") { |f| f.write source_contents }
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                Dir.chdir(project_dir) do
         | 
| 77 | 
            -
                  Rundoc.run_after_build
         | 
| 78 | 
            -
                end
         | 
| 30 | 
            +
                Rundoc::CLI.new.build(path: @path)
         | 
| 79 31 | 
             
              end
         | 
| 80 32 | 
             
            end
         | 
| 81 33 |  | 
| 82 | 
            -
             | 
| 34 | 
            +
            RundocThorCLI.start(ARGV)
         | 
    
        data/lib/rundoc.rb
    CHANGED
    
    | @@ -7,9 +7,21 @@ module Rundoc | |
| 7 7 |  | 
| 8 8 | 
             
              def code_command_from_keyword(keyword, args)
         | 
| 9 9 | 
             
                klass      = code_command(keyword.to_sym) || Rundoc::CodeCommand::NoSuchCommand
         | 
| 10 | 
            -
                 | 
| 10 | 
            +
                original_args = args.dup
         | 
| 11 | 
            +
                if args.is_a?(Array) && args.last.is_a?(Hash)
         | 
| 12 | 
            +
                  kwargs = args.pop
         | 
| 13 | 
            +
                  cc = klass.new(*args, **kwargs)
         | 
| 14 | 
            +
                elsif args.is_a?(Hash)
         | 
| 15 | 
            +
                  cc = klass.new(**args)
         | 
| 16 | 
            +
                else
         | 
| 17 | 
            +
                  cc = klass.new(*args)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                cc.original_args = original_args
         | 
| 11 21 | 
             
                cc.keyword = keyword
         | 
| 12 22 | 
             
                cc
         | 
| 23 | 
            +
              rescue ArgumentError => e
         | 
| 24 | 
            +
                raise ArgumentError, "Wrong method signature for #{keyword} with arguments: #{original_args.inspect}, error:\n #{e.message}"
         | 
| 13 25 | 
             
              end
         | 
| 14 26 |  | 
| 15 27 | 
             
              def parser_options
         | 
| @@ -74,3 +86,5 @@ end | |
| 74 86 | 
             
            require 'rundoc/parser'
         | 
| 75 87 | 
             
            require 'rundoc/code_section'
         | 
| 76 88 | 
             
            require 'rundoc/code_command'
         | 
| 89 | 
            +
            require 'rundoc/peg_parser'
         | 
| 90 | 
            +
            require 'rundoc/cli'
         | 
    
        data/lib/rundoc/cli.rb
    ADDED
    
    | @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            module Rundoc
         | 
| 2 | 
            +
              class CLI
         | 
| 3 | 
            +
                def build(path: )
         | 
| 4 | 
            +
                  @path = Pathname.new(path).expand_path
         | 
| 5 | 
            +
                  raise "#{@path} does not exist" unless File.exist?(@path)
         | 
| 6 | 
            +
                  raise "Expecting #{@path} to be a rundoc markdown file" unless File.file?(@path)
         | 
| 7 | 
            +
                  @working_dir  = Pathname.new(File.expand_path("../", @path))
         | 
| 8 | 
            +
             | 
| 9 | 
            +
             | 
| 10 | 
            +
                  dot_env_path = File.expand_path("../.env", @path)
         | 
| 11 | 
            +
                  if File.exist?(dot_env_path)
         | 
| 12 | 
            +
                    require 'dotenv'
         | 
| 13 | 
            +
                    Dotenv.load(dot_env_path)
         | 
| 14 | 
            +
                    ENV['AWS_ACCESS_KEY_ID']     ||= ENV['BUCKETEER_AWS_ACCESS_KEY_ID']
         | 
| 15 | 
            +
                    ENV['AWS_REGION']            ||= ENV['BUCKETEER_AWS_REGION']
         | 
| 16 | 
            +
                    ENV['AWS_SECRET_ACCESS_KEY'] ||= ENV['BUCKETEER_AWS_SECRET_ACCESS_KEY']
         | 
| 17 | 
            +
                    ENV['AWS_BUCKET_NAME']       ||= ENV['BUCKETEER_BUCKET_NAME']
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  source_contents = File.read(@path)
         | 
| 21 | 
            +
                  tmp_dir         = @working_dir.join("tmp")
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  FileUtils.remove_entry_secure(tmp_dir) if tmp_dir.exist?
         | 
| 24 | 
            +
                  tmp_dir.mkdir
         | 
| 25 | 
            +
                  banner = <<~HEREDOC
         | 
| 26 | 
            +
                    <!-- STOP
         | 
| 27 | 
            +
                      This file was generated by a rundoc script, do not modify it.
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      Instead modify the rundoc script and re-run it.
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      Command: #{ $0 } #{$*.join(' ')}
         | 
| 32 | 
            +
                    STOP -->
         | 
| 33 | 
            +
                  HEREDOC
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  puts "== Running your docs"
         | 
| 36 | 
            +
                  Dir.chdir(tmp_dir) do
         | 
| 37 | 
            +
                    @output = Rundoc::Parser.new(source_contents, document_path: @path).to_md
         | 
| 38 | 
            +
                    Rundoc.sanitize(@output)
         | 
| 39 | 
            +
                    @output = "#{banner}\n#{@output}"
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  puts "== Done, run was successful"
         | 
| 43 | 
            +
                  project_name = if Rundoc.project_root
         | 
| 44 | 
            +
                    Rundoc.project_root.split('/').last
         | 
| 45 | 
            +
                  else
         | 
| 46 | 
            +
                    'project'
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  project_dir  = @working_dir.join(project_name)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  FileUtils.remove_entry_secure(project_dir) if project_dir.exist?
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  cp_root = if Rundoc.project_root
         | 
| 54 | 
            +
                    tmp_dir.join(Rundoc.project_root, ".")
         | 
| 55 | 
            +
                  else
         | 
| 56 | 
            +
                    tmp_dir.join(".")
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  FileUtils.cp_r(cp_root, project_dir)
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  FileUtils.remove_entry_secure(tmp_dir) if tmp_dir.exist?
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  source_path = project_dir.join("README.md")
         | 
| 64 | 
            +
                  puts "== Done, writing original source to #{source_path}"
         | 
| 65 | 
            +
                  File.open(source_path, "w") { |f| f.write @output }
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  puts "== Copying source"
         | 
| 68 | 
            +
                  source_path = project_dir.join("coppied-#{@path.to_s.split('/').last}")
         | 
| 69 | 
            +
                  File.open(source_path, "w") { |f| f.write source_contents }
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  Dir.chdir(project_dir) do
         | 
| 72 | 
            +
                    Rundoc.run_after_build
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                ensure
         | 
| 76 | 
            +
                  Rundoc::CodeCommand::Background::ProcessSpawn.tasks.each do |name, task|
         | 
| 77 | 
            +
                    next unless task.alive?
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    puts "Warning background task is still running, cleaning up: name: #{name}"
         | 
| 80 | 
            +
                    task.stop
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
    
        data/lib/rundoc/code_command.rb
    CHANGED
    
    | @@ -1,10 +1,19 @@ | |
| 1 1 | 
             
            module Rundoc
         | 
| 2 | 
            +
              # Generic CodeCommand class to be inherited
         | 
| 3 | 
            +
              #
         | 
| 2 4 | 
             
              class CodeCommand
         | 
| 3 | 
            -
                attr_accessor : | 
| 4 | 
            -
             | 
| 5 | 
            +
                attr_accessor :render_result, :render_command,
         | 
| 6 | 
            +
                              :command, :contents, :keyword,
         | 
| 7 | 
            +
                              :original_args
         | 
| 8 | 
            +
             | 
| 5 9 | 
             
                alias :render_result? :render_result
         | 
| 10 | 
            +
                alias :render_command? :render_command
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(*args)
         | 
| 13 | 
            +
                end
         | 
| 6 14 |  | 
| 7 | 
            -
                def  | 
| 15 | 
            +
                def hidden?
         | 
| 16 | 
            +
                  !render_command? && !render_result?
         | 
| 8 17 | 
             
                end
         | 
| 9 18 |  | 
| 10 19 | 
             
                def not_hidden?
         | 
| @@ -17,16 +26,26 @@ module Rundoc | |
| 17 26 | 
             
                end
         | 
| 18 27 | 
             
                alias :<< :push
         | 
| 19 28 |  | 
| 20 | 
            -
                #  | 
| 29 | 
            +
                # Executes command to build project
         | 
| 30 | 
            +
                # Is expected to return the result of the command
         | 
| 21 31 | 
             
                def call(env = {})
         | 
| 22 | 
            -
                  raise "not implemented"
         | 
| 32 | 
            +
                  raise "not implemented on #{self.inspect}"
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                # the output of the command, i.e. `$ cat foo.txt`
         | 
| 36 | 
            +
                def to_md(env = {})
         | 
| 37 | 
            +
                  raise "not implemented on #{self.inspect}"
         | 
| 23 38 | 
             
                end
         | 
| 24 39 | 
             
              end
         | 
| 25 40 | 
             
            end
         | 
| 26 41 |  | 
| 42 | 
            +
             | 
| 27 43 | 
             
            require 'rundoc/code_command/bash'
         | 
| 28 44 | 
             
            require 'rundoc/code_command/pipe'
         | 
| 29 45 | 
             
            require 'rundoc/code_command/write'
         | 
| 30 46 | 
             
            require 'rundoc/code_command/repl'
         | 
| 31 47 | 
             
            require 'rundoc/code_command/rundoc_command'
         | 
| 32 | 
            -
            require 'rundoc/code_command/no_such_command'
         | 
| 48 | 
            +
            require 'rundoc/code_command/no_such_command'
         | 
| 49 | 
            +
            require 'rundoc/code_command/raw'
         | 
| 50 | 
            +
            require 'rundoc/code_command/background'
         | 
| 51 | 
            +
            require 'rundoc/code_command/website'
         | 
| @@ -0,0 +1,9 @@ | |
| 1 | 
            +
            class Rundoc::CodeCommand::Background
         | 
| 2 | 
            +
            end
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'rundoc/code_command/background/process_spawn'
         | 
| 5 | 
            +
            require 'rundoc/code_command/background/start'
         | 
| 6 | 
            +
            require 'rundoc/code_command/background/stop'
         | 
| 7 | 
            +
            require 'rundoc/code_command/background/wait'
         | 
| 8 | 
            +
            require 'rundoc/code_command/background/log/clear'
         | 
| 9 | 
            +
            require 'rundoc/code_command/background/log/read'
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            class Rundoc::CodeCommand::Background::Log
         | 
| 2 | 
            +
              class Clear < Rundoc::CodeCommand
         | 
| 3 | 
            +
                def initialize(name: )
         | 
| 4 | 
            +
                  @spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def to_md(env = {})
         | 
| 8 | 
            +
                  ""
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def call(env = {})
         | 
| 12 | 
            +
                  @spawn.log.truncate(0)
         | 
| 13 | 
            +
                  ""
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
            Rundoc.register_code_command(:"background.log.clear", Rundoc::CodeCommand::Background::Log::Clear)
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            class Rundoc::CodeCommand::Background::Log
         | 
| 2 | 
            +
              class Read < Rundoc::CodeCommand
         | 
| 3 | 
            +
                def initialize(name: )
         | 
| 4 | 
            +
                  @spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def to_md(env = {})
         | 
| 8 | 
            +
                  ""
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def call(env = {})
         | 
| 12 | 
            +
                  @spawn.log.read
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
            Rundoc.register_code_command(:"background.log.read", Rundoc::CodeCommand::Background::Log::Read)
         | 
| @@ -0,0 +1,96 @@ | |
| 1 | 
            +
            require 'shellwords'
         | 
| 2 | 
            +
            require 'timeout'
         | 
| 3 | 
            +
            require 'fileutils'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Rundoc::CodeCommand::Background
         | 
| 6 | 
            +
              # This class is responsible for running processes in the background
         | 
| 7 | 
            +
              #
         | 
| 8 | 
            +
              # By default it logs output to a file. This can be used to "wait" for a
         | 
| 9 | 
            +
              # specific output before continuing:
         | 
| 10 | 
            +
              #
         | 
| 11 | 
            +
              #   server = ProcessSpawn("rails server")
         | 
| 12 | 
            +
              #   server.wait("Use Ctrl-C to stop")
         | 
| 13 | 
            +
              #
         | 
| 14 | 
            +
              # The process can be queried for it's status to check if it is still booted or not.
         | 
| 15 | 
            +
              # the process can also be manually stopped:
         | 
| 16 | 
            +
              #
         | 
| 17 | 
            +
              #   server = ProcessSpawn("rails server")
         | 
| 18 | 
            +
              #   server.alive? # => true
         | 
| 19 | 
            +
              #   server.stop
         | 
| 20 | 
            +
              #   server.alive? # => false
         | 
| 21 | 
            +
              #
         | 
| 22 | 
            +
              #
         | 
| 23 | 
            +
              # There are class level methods that can be used to "name" and record
         | 
| 24 | 
            +
              # background processes. They can be used like this:
         | 
| 25 | 
            +
              #
         | 
| 26 | 
            +
              #   server = ProcessSpawn("rails server")
         | 
| 27 | 
            +
              #   ProcessSpawn.add("muh_server", server)
         | 
| 28 | 
            +
              #   ProcessSpawn.find("muh_server") # => <# ProcessSpawn instance >
         | 
| 29 | 
            +
              #   ProcessSpawn.find("foo") # => RuntimeError "Could not find task with name 'foo', ..."
         | 
| 30 | 
            +
              class ProcessSpawn
         | 
| 31 | 
            +
                def self.tasks
         | 
| 32 | 
            +
                  @tasks
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                @tasks = {}
         | 
| 36 | 
            +
                def self.add(name, value)
         | 
| 37 | 
            +
                  raise "Task named #{name.inspect} is already started, choose a different name" if @tasks[name]
         | 
| 38 | 
            +
                  @tasks[name] = value
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def self.find(name)
         | 
| 42 | 
            +
                  raise "Could not find task with name #{name.inspect}, known task names: #{@tasks.keys.inspect}" unless @tasks[name]
         | 
| 43 | 
            +
                  @tasks[name]
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                attr_reader :log, :pid
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def initialize(command , timeout: 5, log: Tempfile.new("log"), out: "2>&1")
         | 
| 49 | 
            +
                  @command = command
         | 
| 50 | 
            +
                  @timeout_value = timeout
         | 
| 51 | 
            +
                  @log_reference = log # https://twitter.com/schneems/status/1285289971083907075
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  @log = Pathname.new(log)
         | 
| 54 | 
            +
                  @log.dirname.mkpath
         | 
| 55 | 
            +
                  FileUtils.touch(@log)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  @command = "/usr/bin/env bash -c #{@command.shellescape} >> #{@log} #{out}"
         | 
| 58 | 
            +
                  @pid = nil
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def wait(wait_value = nil, timeout_value = @timeout_value)
         | 
| 62 | 
            +
                  call
         | 
| 63 | 
            +
                  return unless wait_value
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  Timeout.timeout(Integer(timeout_value)) do
         | 
| 66 | 
            +
                    until @log.read.match(wait_value)
         | 
| 67 | 
            +
                      sleep 0.01
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                rescue Timeout::Error
         | 
| 71 | 
            +
                  raise "Timeout waiting for #{@command.inspect} to find a match using #{ wait_value.inspect } in \n'#{ log.read }'"
         | 
| 72 | 
            +
                  false
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def alive?
         | 
| 76 | 
            +
                  return false unless @pid
         | 
| 77 | 
            +
                  Process.kill(0, @pid)
         | 
| 78 | 
            +
                rescue Errno::ESRCH, Errno::EPERM
         | 
| 79 | 
            +
                  false
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def stop
         | 
| 83 | 
            +
                  return unless alive?
         | 
| 84 | 
            +
                  Process.kill('TERM', -Process.getpgid(@pid))
         | 
| 85 | 
            +
                  Process.wait(@pid)
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def check_alive!
         | 
| 89 | 
            +
                  raise "#{@original_command} has exited unexpectedly: #{@log.read}" unless alive?
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                private def call
         | 
| 93 | 
            +
                  @pid ||= Process.spawn(@command, pgroup: true)
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            require 'tempfile'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Rundoc::CodeCommand::Background
         | 
| 4 | 
            +
              class Start < Rundoc::CodeCommand
         | 
| 5 | 
            +
                def initialize(command, name: , wait: nil, timeout: 5, log: Tempfile.new("log"), out: "2>&1", allow_fail: false)
         | 
| 6 | 
            +
                  @command = command
         | 
| 7 | 
            +
                  @name    = name
         | 
| 8 | 
            +
                  @wait    = wait
         | 
| 9 | 
            +
                  @allow_fail = allow_fail
         | 
| 10 | 
            +
                  FileUtils.touch(log)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  @spawn = ProcessSpawn.new(
         | 
| 13 | 
            +
                    @command,
         | 
| 14 | 
            +
                    timeout: timeout,
         | 
| 15 | 
            +
                    log:     log,
         | 
| 16 | 
            +
                    out:     out
         | 
| 17 | 
            +
                  )
         | 
| 18 | 
            +
                  ProcessSpawn.add(@name, @spawn)
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def to_md(env = {})
         | 
| 22 | 
            +
                  return "$ #{@command}"
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def call(env = {})
         | 
| 26 | 
            +
                  @spawn.wait(@wait)
         | 
| 27 | 
            +
                  @spawn.check_alive! unless @allow_fail
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  @spawn.log.read
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def alive?
         | 
| 33 | 
            +
                  !!@spawn.alive?
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            Rundoc.register_code_command(:"background.start", Rundoc::CodeCommand::Background::Start)
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            class Rundoc::CodeCommand::Background
         | 
| 2 | 
            +
              class Stop < Rundoc::CodeCommand
         | 
| 3 | 
            +
                def initialize(name: )
         | 
| 4 | 
            +
                  @spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def to_md(env = {})
         | 
| 8 | 
            +
                  ""
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def call(env = {})
         | 
| 12 | 
            +
                  @spawn.stop
         | 
| 13 | 
            +
                  ""
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
            Rundoc.register_code_command(:"background.stop", Rundoc::CodeCommand::Background::Stop)
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            class Rundoc::CodeCommand::Background
         | 
| 2 | 
            +
              class Wait < Rundoc::CodeCommand
         | 
| 3 | 
            +
                def initialize(name: , wait:, timeout: 5)
         | 
| 4 | 
            +
                  @spawn = Rundoc::CodeCommand::Background::ProcessSpawn.find(name)
         | 
| 5 | 
            +
                  @wait  = wait
         | 
| 6 | 
            +
                  @timeout_value = Integer(timeout)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def to_md(env = {})
         | 
| 10 | 
            +
                  ""
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def call(env = {})
         | 
| 14 | 
            +
                  @spawn.wait(@wait, @timeout_value)
         | 
| 15 | 
            +
                  ""
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| 19 | 
            +
            Rundoc.register_code_command(:"background.wait", Rundoc::CodeCommand::Background::Wait)
         |