rundoc 0.0.2 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/check_changelog.yml +13 -0
  3. data/.gitignore +7 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +26 -0
  6. data/Dockerfile +24 -0
  7. data/Gemfile +1 -0
  8. data/README.md +261 -92
  9. data/bin/rundoc +4 -60
  10. data/lib/rundoc.rb +15 -1
  11. data/lib/rundoc/cli.rb +84 -0
  12. data/lib/rundoc/code_command.rb +20 -5
  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 +2 -0
  23. data/lib/rundoc/code_command/no_such_command.rb +4 -0
  24. data/lib/rundoc/code_command/pipe.rb +17 -4
  25. data/lib/rundoc/code_command/raw.rb +18 -0
  26. data/lib/rundoc/code_command/rundoc/depend_on.rb +37 -0
  27. data/lib/rundoc/code_command/rundoc/require.rb +41 -0
  28. data/lib/rundoc/code_command/rundoc_command.rb +6 -2
  29. data/lib/rundoc/code_command/website.rb +7 -0
  30. data/lib/rundoc/code_command/website/driver.rb +111 -0
  31. data/lib/rundoc/code_command/website/navigate.rb +29 -0
  32. data/lib/rundoc/code_command/website/screenshot.rb +28 -0
  33. data/lib/rundoc/code_command/website/visit.rb +47 -0
  34. data/lib/rundoc/code_section.rb +34 -78
  35. data/lib/rundoc/parser.rb +4 -3
  36. data/lib/rundoc/peg_parser.rb +282 -0
  37. data/lib/rundoc/version.rb +1 -1
  38. data/rundoc.gemspec +9 -3
  39. data/test/fixtures/build_logs/rundoc.md +56 -0
  40. data/test/fixtures/depend_on/dependency/rundoc.md +5 -0
  41. data/test/fixtures/depend_on/main/rundoc.md +10 -0
  42. data/test/fixtures/java/rundoc.md +9 -0
  43. data/test/fixtures/rails_4/rundoc.md +1 -1
  44. data/test/fixtures/rails_5/rundoc.md +76 -74
  45. data/test/fixtures/{rails_5_beta → rails_6}/rundoc.md +93 -87
  46. data/test/fixtures/require/dependency/rundoc.md +5 -0
  47. data/test/fixtures/require/main/rundoc.md +10 -0
  48. data/test/fixtures/screenshot/rundoc.md +10 -0
  49. data/test/rundoc/code_commands/append_file_test.rb +2 -2
  50. data/test/rundoc/code_commands/background_test.rb +69 -0
  51. data/test/rundoc/code_commands/bash_test.rb +1 -1
  52. data/test/rundoc/code_commands/pipe_test.rb +12 -2
  53. data/test/rundoc/code_commands/remove_contents_test.rb +1 -1
  54. data/test/rundoc/code_section_test.rb +6 -5
  55. data/test/rundoc/parser_test.rb +2 -8
  56. data/test/rundoc/peg_parser_test.rb +391 -0
  57. data/test/rundoc/regex_test.rb +1 -1
  58. data/test/rundoc/test_parse_java.rb +1 -1
  59. data/test/test_helper.rb +1 -1
  60. metadata +120 -12
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,63 +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
- raise "Expecting #{@path} to be a rundoc markdown file" unless File.file?(@path)
33
- source_contents = File.read(@path)
34
- tmp_dir = @working_dir.join("tmp")
35
-
36
- FileUtils.remove_entry_secure(tmp_dir) if tmp_dir.exist?
37
- tmp_dir.mkdir
38
- banner = <<~HEREDOC
39
- <!-- STOP
40
- This file was generated by a rundoc script, do not modify it.
41
-
42
- Instead modify the rundoc script and re-run it.
43
-
44
- Command: #{ $0 } #{$*.join(' ')}
45
- STOP -->
46
- HEREDOC
47
-
48
- puts "== Running your docs"
49
- Dir.chdir(tmp_dir) do
50
- @output = Rundoc::Parser.new(source_contents, Rundoc.parser_options).to_md
51
- Rundoc.sanitize(@output)
52
- @output = "#{banner}\n#{@output}"
53
- end
54
-
55
- puts "== Done, run was successful"
56
- project_name = if Rundoc.project_root
57
- Rundoc.project_root.split('/').last
58
- else
59
- 'project'
60
- end
61
-
62
- project_dir = @working_dir.join(project_name)
63
-
64
- FileUtils.remove_entry_secure(project_dir) if project_dir.exist?
65
-
66
- cp_root = if Rundoc.project_root
67
- tmp_dir.join(Rundoc.project_root, ".")
68
- else
69
- tmp_dir.join(".")
70
- end
71
-
72
- FileUtils.cp_r(cp_root, project_dir)
73
-
74
- FileUtils.remove_entry_secure(tmp_dir) if tmp_dir.exist?
75
-
76
- source_path = project_dir.join("README.md")
77
- puts "== Done, writing original source to #{source_path}"
78
- File.open(source_path, "w") { |f| f.write @output }
79
-
80
- puts "== Copying source"
81
- source_path = project_dir.join("coppied-#{@path.split('/').last}")
82
- File.open(source_path, "w") { |f| f.write source_contents }
83
-
84
- Dir.chdir(project_dir) do
85
- Rundoc.run_after_build
86
- end
30
+ Rundoc::CLI.new.build(path: @path)
87
31
  end
88
32
  end
89
33
 
90
- 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,15 @@
1
1
  module Rundoc
2
+ # Generic CodeCommand class to be inherited
3
+ #
2
4
  class CodeCommand
3
- attr_accessor :render_result, :render_command, :command, :contents, :keyword
5
+ attr_accessor :render_result, :render_command,
6
+ :command, :contents, :keyword,
7
+ :original_args
8
+
4
9
  alias :render_result? :render_result
5
10
  alias :render_command? :render_command
6
11
 
7
- def initialize(arg)
12
+ def initialize(*args)
8
13
  end
9
14
 
10
15
  def hidden?
@@ -21,16 +26,26 @@ module Rundoc
21
26
  end
22
27
  alias :<< :push
23
28
 
24
- # executes command to build project
29
+ # Executes command to build project
30
+ # Is expected to return the result of the command
25
31
  def call(env = {})
26
- 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}"
27
38
  end
28
39
  end
29
40
  end
30
41
 
42
+
31
43
  require 'rundoc/code_command/bash'
32
44
  require 'rundoc/code_command/pipe'
33
45
  require 'rundoc/code_command/write'
34
46
  require 'rundoc/code_command/repl'
35
47
  require 'rundoc/code_command/rundoc_command'
36
- 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)