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.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/check_changelog.yml +13 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +22 -0
  6. data/Dockerfile +24 -0
  7. data/Gemfile +1 -0
  8. data/README.md +276 -74
  9. data/bin/rundoc +4 -52
  10. data/lib/rundoc.rb +15 -1
  11. data/lib/rundoc/cli.rb +84 -0
  12. data/lib/rundoc/code_command.rb +25 -6
  13. data/lib/rundoc/code_command/background.rb +9 -0
  14. data/lib/rundoc/code_command/background/log/clear.rb +17 -0
  15. data/lib/rundoc/code_command/background/log/read.rb +16 -0
  16. data/lib/rundoc/code_command/background/process_spawn.rb +96 -0
  17. data/lib/rundoc/code_command/background/start.rb +38 -0
  18. data/lib/rundoc/code_command/background/stop.rb +17 -0
  19. data/lib/rundoc/code_command/background/wait.rb +19 -0
  20. data/lib/rundoc/code_command/bash.rb +1 -1
  21. data/lib/rundoc/code_command/bash/cd.rb +21 -3
  22. data/lib/rundoc/code_command/file_command/append.rb +16 -11
  23. data/lib/rundoc/code_command/file_command/remove.rb +6 -5
  24. data/lib/rundoc/code_command/no_such_command.rb +4 -0
  25. data/lib/rundoc/code_command/pipe.rb +18 -5
  26. data/lib/rundoc/code_command/raw.rb +18 -0
  27. data/lib/rundoc/code_command/rundoc/depend_on.rb +37 -0
  28. data/lib/rundoc/code_command/rundoc/require.rb +41 -0
  29. data/lib/rundoc/code_command/rundoc_command.rb +6 -2
  30. data/lib/rundoc/code_command/website.rb +7 -0
  31. data/lib/rundoc/code_command/website/driver.rb +111 -0
  32. data/lib/rundoc/code_command/website/navigate.rb +29 -0
  33. data/lib/rundoc/code_command/website/screenshot.rb +28 -0
  34. data/lib/rundoc/code_command/website/visit.rb +47 -0
  35. data/lib/rundoc/code_command/write.rb +22 -7
  36. data/lib/rundoc/code_section.rb +41 -66
  37. data/lib/rundoc/parser.rb +5 -4
  38. data/lib/rundoc/peg_parser.rb +282 -0
  39. data/lib/rundoc/version.rb +2 -2
  40. data/rundoc.gemspec +9 -3
  41. data/test/fixtures/build_logs/rundoc.md +56 -0
  42. data/test/fixtures/depend_on/dependency/rundoc.md +5 -0
  43. data/test/fixtures/depend_on/main/rundoc.md +10 -0
  44. data/test/fixtures/java/rundoc.md +9 -0
  45. data/test/fixtures/rails_4/rundoc.md +151 -188
  46. data/test/fixtures/rails_5/rundoc.md +445 -0
  47. data/test/fixtures/rails_6/rundoc.md +451 -0
  48. data/test/fixtures/require/dependency/rundoc.md +5 -0
  49. data/test/fixtures/require/main/rundoc.md +10 -0
  50. data/test/fixtures/screenshot/rundoc.md +10 -0
  51. data/test/rundoc/code_commands/append_file_test.rb +33 -6
  52. data/test/rundoc/code_commands/background_test.rb +69 -0
  53. data/test/rundoc/code_commands/bash_test.rb +1 -1
  54. data/test/rundoc/code_commands/pipe_test.rb +1 -1
  55. data/test/rundoc/code_commands/remove_contents_test.rb +3 -4
  56. data/test/rundoc/code_section_test.rb +95 -2
  57. data/test/rundoc/parser_test.rb +7 -13
  58. data/test/rundoc/peg_parser_test.rb +381 -0
  59. data/test/rundoc/regex_test.rb +6 -6
  60. data/test/rundoc/test_parse_java.rb +1 -1
  61. data/test/test_helper.rb +1 -3
  62. metadata +143 -18
  63. 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 RundocCLI < Thor
18
+ class RundocThorCLI < Thor
19
19
 
20
20
  def initialize(*args)
21
21
  super
22
- @path = options[: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
- raise "#{@path} does not exist" unless File.exist?(@path)
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
- RundocCLI.start(ARGV)
34
+ RundocThorCLI.start(ARGV)
@@ -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
- cc = klass.new(args)
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'
@@ -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
@@ -1,10 +1,19 @@
1
1
  module Rundoc
2
+ # Generic CodeCommand class to be inherited
3
+ #
2
4
  class CodeCommand
3
- attr_accessor :hidden, :render_result, :command, :contents, :keyword
4
- alias :hidden? :hidden
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 initialize(arg)
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
- # executes command to build project
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)