rundoc 1.0.0 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/check_changelog.yml +13 -0
- data/.gitignore +7 -0
- data/CHANGELOG.md +27 -0
- data/Dockerfile +24 -0
- data/README.md +221 -100
- data/bin/rundoc +4 -68
- data/lib/rundoc.rb +1 -0
- data/lib/rundoc/cli.rb +84 -0
- data/lib/rundoc/code_command.rb +3 -2
- data/lib/rundoc/code_command/background/process_spawn.rb +30 -4
- data/lib/rundoc/code_command/background/start.rb +2 -0
- data/lib/rundoc/code_command/bash.rb +3 -2
- data/lib/rundoc/code_command/bash/cd.rb +20 -2
- data/lib/rundoc/code_command/no_such_command.rb +4 -0
- data/lib/rundoc/code_command/pipe.rb +17 -4
- 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 +4 -1
- 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_section.rb +13 -4
- data/lib/rundoc/parser.rb +4 -3
- data/lib/rundoc/peg_parser.rb +1 -1
- data/lib/rundoc/version.rb +1 -1
- data/rundoc.gemspec +7 -2
- 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 +8 -5
- data/test/fixtures/rails_5/rundoc.md +17 -7
- data/test/fixtures/{rails_5_beta → rails_6}/rundoc.md +97 -88
- 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/background_test.rb +26 -0
- data/test/rundoc/code_commands/bash_test.rb +7 -0
- data/test/rundoc/code_commands/pipe_test.rb +11 -1
- data/test/rundoc/parser_test.rb +0 -1
- data/test/rundoc/peg_parser_test.rb +43 -0
- metadata +90 -11
@@ -19,5 +19,8 @@ module ::Rundoc
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
22
|
Rundoc.register_code_command(:rundoc, RundocCommand)
|
23
|
+
Rundoc.register_code_command(:"rundoc.configure", RundocCommand)
|
24
|
+
|
25
|
+
require 'rundoc/code_command/rundoc/depend_on'
|
26
|
+
require 'rundoc/code_command/rundoc/require'
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
|
3
|
+
Capybara::Selenium::Driver.load_selenium
|
4
|
+
|
5
|
+
class Rundoc::CodeCommand::Website
|
6
|
+
class Driver
|
7
|
+
attr_reader :session
|
8
|
+
|
9
|
+
def initialize(name: , url: , width: 1024, height: 720, visible: false)
|
10
|
+
browser_options = ::Selenium::WebDriver::Chrome::Options.new
|
11
|
+
browser_options.args << '--headless' unless visible
|
12
|
+
browser_options.args << '--disable-gpu' if Gem.win_platform?
|
13
|
+
browser_options.args << '--hide-scrollbars'
|
14
|
+
# browser_options.args << "--window-size=#{width},#{height}"
|
15
|
+
@width = width
|
16
|
+
@height = height
|
17
|
+
|
18
|
+
@driver = Capybara::Selenium::Driver.new(nil, browser: :chrome, options: browser_options)
|
19
|
+
driver_name = "rundoc_driver_#{name}".to_sym
|
20
|
+
Capybara.register_driver(driver_name) do |app|
|
21
|
+
@driver
|
22
|
+
end
|
23
|
+
|
24
|
+
@session = Capybara::Session.new(driver_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def visit(url)
|
28
|
+
@session.visit(url)
|
29
|
+
end
|
30
|
+
|
31
|
+
def timestamp
|
32
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S%L%N")
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_url
|
36
|
+
session.current_url
|
37
|
+
end
|
38
|
+
|
39
|
+
def scroll(value = 100)
|
40
|
+
session.execute_script "window.scrollBy(0,#{value})"
|
41
|
+
end
|
42
|
+
|
43
|
+
def teardown
|
44
|
+
session.reset_session!
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.tasks
|
48
|
+
@tasks
|
49
|
+
end
|
50
|
+
|
51
|
+
def safe_eval(code)
|
52
|
+
@driver.send(:eval, code)
|
53
|
+
rescue => e
|
54
|
+
msg = String.new
|
55
|
+
msg << "Error running code #{code.inspect} at #{current_url.inspect}\n"
|
56
|
+
msg << "saving a screenshot to: `tmp/error.png`"
|
57
|
+
puts msg
|
58
|
+
session.save_screenshot("tmp/error.png")
|
59
|
+
raise e
|
60
|
+
end
|
61
|
+
|
62
|
+
def screenshot(upload: false)
|
63
|
+
@driver.resize_window_to(@driver.current_window_handle, @width, @height)
|
64
|
+
FileUtils.mkdir_p("tmp/rundoc_screenshots")
|
65
|
+
file_name = self.class.next_screenshot_name
|
66
|
+
file_path = "tmp/rundoc_screenshots/#{file_name}"
|
67
|
+
session.save_screenshot(file_path)
|
68
|
+
|
69
|
+
return file_path unless upload
|
70
|
+
|
71
|
+
case upload
|
72
|
+
when 's3', 'aws'
|
73
|
+
puts "Uploading screenshot to S3"
|
74
|
+
require 'aws-sdk-s3'
|
75
|
+
ENV.fetch('AWS_ACCESS_KEY_ID')
|
76
|
+
s3 = Aws::S3::Resource.new(region: ENV.fetch('AWS_REGION'))
|
77
|
+
|
78
|
+
key = "#{timestamp}/#{file_name}"
|
79
|
+
obj = s3.bucket(ENV.fetch('AWS_BUCKET_NAME')).object(key)
|
80
|
+
obj.upload_file(file_path)
|
81
|
+
|
82
|
+
obj.client.put_object_acl(
|
83
|
+
acl: 'public-read' ,
|
84
|
+
bucket: ENV.fetch('AWS_BUCKET_NAME'),
|
85
|
+
key: key
|
86
|
+
)
|
87
|
+
|
88
|
+
obj.public_url
|
89
|
+
else
|
90
|
+
raise "Upload #{upload.inspect} is not valid, use 's3' instead"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
@tasks = {}
|
95
|
+
def self.add(name, value)
|
96
|
+
raise "Task named #{name.inspect} is already started, choose a different name" if @tasks[name]
|
97
|
+
@tasks[name] = value
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.find(name)
|
101
|
+
raise "Could not find task with name #{name.inspect}, known task names: #{@tasks.keys.inspect}" unless @tasks[name]
|
102
|
+
@tasks[name]
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.next_screenshot_name
|
106
|
+
@count ||= 0
|
107
|
+
@count += 1
|
108
|
+
return "screenshot_#{@count}.png"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Rundoc::CodeCommand::Website
|
2
|
+
class Navigate < Rundoc::CodeCommand
|
3
|
+
def initialize(name: )
|
4
|
+
@name = name
|
5
|
+
@driver = Rundoc::CodeCommand::Website::Driver.find(name)
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_md(env = {})
|
9
|
+
""
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env = {})
|
13
|
+
puts "website.navigate [#{@name}]: #{contents}"
|
14
|
+
@driver.safe_eval(contents)
|
15
|
+
""
|
16
|
+
end
|
17
|
+
|
18
|
+
def hidden?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def not_hidden?
|
23
|
+
!hidden?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Rundoc.register_code_command(:"website.nav", Rundoc::CodeCommand::Website::Navigate)
|
29
|
+
Rundoc.register_code_command(:"website.navigate", Rundoc::CodeCommand::Website::Navigate)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Rundoc::CodeCommand::Website
|
2
|
+
class Screenshot < Rundoc::CodeCommand
|
3
|
+
def initialize(name: , upload: false)
|
4
|
+
@driver = Rundoc::CodeCommand::Website::Driver.find(name)
|
5
|
+
@upload = upload
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_md(env = {})
|
9
|
+
""
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env = {})
|
13
|
+
puts "Taking screenshot: #{@driver.current_url}"
|
14
|
+
filename = @driver.screenshot(upload: @upload)
|
15
|
+
env[:replace] = "![Screenshot of #{@driver.current_url}](#{filename})"
|
16
|
+
""
|
17
|
+
end
|
18
|
+
|
19
|
+
# def hidden?
|
20
|
+
# true
|
21
|
+
# end
|
22
|
+
|
23
|
+
# def not_hidden?
|
24
|
+
# !hidden?
|
25
|
+
# end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
Rundoc.register_code_command(:"website.screenshot", Rundoc::CodeCommand::Website::Screenshot)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Rundoc::CodeCommand::Website
|
2
|
+
class Visit < Rundoc::CodeCommand
|
3
|
+
def initialize(name: , url: nil, scroll: nil, height: 720, width: 1024, visible: false)
|
4
|
+
@name = name
|
5
|
+
@url = url
|
6
|
+
@scroll = scroll
|
7
|
+
@driver = Driver.new(
|
8
|
+
name: name,
|
9
|
+
url: url,
|
10
|
+
height: height,
|
11
|
+
width: width,
|
12
|
+
visible: visible
|
13
|
+
)
|
14
|
+
Driver.add(@name, @driver)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_md(env = {})
|
18
|
+
""
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env = {})
|
22
|
+
message = String.new("Visting: #{@url}")
|
23
|
+
message << "and executing:\n#{contents}" unless contents.nil? || contents.empty?
|
24
|
+
|
25
|
+
puts message
|
26
|
+
|
27
|
+
@driver.visit(@url) if @url
|
28
|
+
@driver.scroll(@scroll) if @scroll
|
29
|
+
|
30
|
+
|
31
|
+
return "" if contents.nil? || contents.empty?
|
32
|
+
@driver.safe_eval(contents)
|
33
|
+
|
34
|
+
return ""
|
35
|
+
end
|
36
|
+
|
37
|
+
def hidden?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def not_hidden?
|
42
|
+
!hidden?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
Rundoc.register_code_command(:"website.visit", Rundoc::CodeCommand::Website::Visit)
|
data/lib/rundoc/code_section.rb
CHANGED
@@ -32,6 +32,7 @@ module Rundoc
|
|
32
32
|
@commands = []
|
33
33
|
@stack = []
|
34
34
|
@keyword = options[:keyword] or raise "keyword is required"
|
35
|
+
@document_path = options[:document_path]
|
35
36
|
@fence = match[:fence]
|
36
37
|
@lang = match[:lang]
|
37
38
|
@code = match[:contents]
|
@@ -42,8 +43,9 @@ module Rundoc
|
|
42
43
|
result = []
|
43
44
|
env = {}
|
44
45
|
env[:commands] = []
|
45
|
-
env[:before] = "#{fence}#{lang}"
|
46
|
-
env[:after] =
|
46
|
+
env[:before] = String.new("#{fence}#{lang}")
|
47
|
+
env[:after] = String.new(fence)
|
48
|
+
env[:document_path] = @document_path
|
47
49
|
|
48
50
|
@stack.each do |s|
|
49
51
|
unless s.respond_to?(:call)
|
@@ -55,7 +57,7 @@ module Rundoc
|
|
55
57
|
code_output = code_command.call(env) || ""
|
56
58
|
code_line = code_command.to_md(env) || ""
|
57
59
|
|
58
|
-
env[:commands] << { object: code_command, output: code_output, command: code_line}
|
60
|
+
env[:commands] << { object: code_command, output: code_output, command: code_line }
|
59
61
|
|
60
62
|
tmp_result = []
|
61
63
|
tmp_result << code_line if code_command.render_command?
|
@@ -65,10 +67,17 @@ module Rundoc
|
|
65
67
|
result
|
66
68
|
end
|
67
69
|
|
70
|
+
return env[:replace] if env[:replace]
|
71
|
+
|
68
72
|
return "" if hidden?
|
69
73
|
|
70
74
|
array = [env[:before], result, env[:after]]
|
71
|
-
|
75
|
+
array.flatten!
|
76
|
+
array.compact!
|
77
|
+
array.map!(&:rstrip)
|
78
|
+
array.reject!(&:empty?)
|
79
|
+
|
80
|
+
return array.join("\n") << "\n"
|
72
81
|
end
|
73
82
|
|
74
83
|
# all of the commands are hidden
|
data/lib/rundoc/parser.rb
CHANGED
@@ -10,10 +10,11 @@ module Rundoc
|
|
10
10
|
|
11
11
|
attr_reader :contents, :keyword, :stack
|
12
12
|
|
13
|
-
def initialize(contents,
|
13
|
+
def initialize(contents, keyword: DEFAULT_KEYWORD, document_path: nil)
|
14
|
+
@document_path = document_path
|
14
15
|
@contents = contents
|
15
16
|
@original = contents.dup
|
16
|
-
@keyword =
|
17
|
+
@keyword = keyword
|
17
18
|
@stack = []
|
18
19
|
partition
|
19
20
|
end
|
@@ -42,7 +43,7 @@ module Rundoc
|
|
42
43
|
@stack << head unless head.empty?
|
43
44
|
unless code.empty?
|
44
45
|
match = code.match(CODEBLOCK_REGEX)
|
45
|
-
@stack << CodeSection.new(match, keyword: keyword)
|
46
|
+
@stack << CodeSection.new(match, keyword: keyword, document_path: @document_path)
|
46
47
|
end
|
47
48
|
@contents = tail
|
48
49
|
end
|
data/lib/rundoc/peg_parser.rb
CHANGED
@@ -119,7 +119,7 @@ module Rundoc
|
|
119
119
|
(
|
120
120
|
start_command >>
|
121
121
|
visability.as(:cmd_visability) >> spaces? >>
|
122
|
-
method_call.as(:cmd_method_call) >> newline #>> match(/\z/)
|
122
|
+
method_call.as(:cmd_method_call) >> newline.maybe #>> match(/\z/)
|
123
123
|
).as(:command)
|
124
124
|
}
|
125
125
|
|
data/lib/rundoc/version.rb
CHANGED
data/rundoc.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = Rundoc::VERSION
|
9
9
|
gem.authors = ["Richard Schneeman"]
|
10
10
|
gem.email = ["richard.schneeman+rubygems@gmail.com"]
|
11
|
-
gem.description = %q{
|
12
|
-
gem.summary = %q{
|
11
|
+
gem.description = %q{RunDOC turns docs to runable code}
|
12
|
+
gem.summary = %q{RunDOC generates runable code from docs}
|
13
13
|
gem.homepage = "https://github.com/schneems/rundoc"
|
14
14
|
gem.license = "MIT"
|
15
15
|
|
@@ -21,6 +21,11 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.add_dependency "thor"
|
22
22
|
gem.add_dependency "repl_runner"
|
23
23
|
gem.add_dependency 'parslet', '~> 1'
|
24
|
+
gem.add_dependency 'capybara', '~> 3'
|
25
|
+
gem.add_dependency 'selenium-webdriver', '~> 3'
|
26
|
+
|
27
|
+
gem.add_dependency 'aws-sdk-s3', '~> 1'
|
28
|
+
gem.add_dependency 'dotenv'
|
24
29
|
|
25
30
|
gem.add_development_dependency "rake"
|
26
31
|
gem.add_development_dependency "mocha"
|
@@ -0,0 +1,56 @@
|
|
1
|
+
Logs produced while building your application (deploying) are separated from your [runtime logs](https://devcenter.heroku.com/articles/logging#log-retrieval). The build logs for failed and successful deploys are available via the dashboard of the application.
|
2
|
+
|
3
|
+
```
|
4
|
+
:::-- $ touch Gemfile
|
5
|
+
:::-- $ bundle _1.15.2_ install
|
6
|
+
:::-- $ git init && git add . && git commit -m first
|
7
|
+
:::-- $ heroku create
|
8
|
+
:::-- $ git push heroku master
|
9
|
+
```
|
10
|
+
|
11
|
+
To view your build logs, first visit the dashboard for the application (`https://dashboard.heroku.com/apps/<app-name>`):
|
12
|
+
|
13
|
+
```
|
14
|
+
:::>> website.visit(name: "dashboard", url: "https://dashboard.heroku.com", visible: true)
|
15
|
+
|
16
|
+
while current_url != "https://dashboard.heroku.com/apps"
|
17
|
+
puts "waiting for successful login: #{current_url}"
|
18
|
+
sleep 1
|
19
|
+
end
|
20
|
+
|
21
|
+
git_url = `git config --get remote.heroku.url`.chomp
|
22
|
+
app_name = git_url.split("/").last.gsub(".git", "")
|
23
|
+
session.visit "https://dashboard.heroku.com/apps/#{app_name}"
|
24
|
+
sleep 2
|
25
|
+
|
26
|
+
email = ENV['HEROKU_EMAIL'] || `heroku auth:whoami`
|
27
|
+
session.execute_script %Q{$("span:contains(#{email}").html('developer@example.com')}
|
28
|
+
|
29
|
+
:::>> website.screenshot(name: "dashboard", upload: "s3")
|
30
|
+
```
|
31
|
+
|
32
|
+
Next click on the "Activity" tab:
|
33
|
+
|
34
|
+
```
|
35
|
+
:::>> website.nav(name: "dashboard")
|
36
|
+
session.first(:link, "Activity").click
|
37
|
+
sleep 2
|
38
|
+
|
39
|
+
email = ENV['HEROKU_EMAIL'] || `heroku auth:whoami`
|
40
|
+
session.execute_script %Q{$("span:contains(#{email}").html('developer@example.com')}
|
41
|
+
|
42
|
+
:::>> website.screenshot(name: "dashboard", upload: "s3")
|
43
|
+
```
|
44
|
+
|
45
|
+
From here you can click on "View build log" to see your most recent build:
|
46
|
+
|
47
|
+
```
|
48
|
+
:::>> website.nav(name: "dashboard")
|
49
|
+
session.first(:link, "View build log").click
|
50
|
+
sleep 2
|
51
|
+
|
52
|
+
email = ENV['HEROKU_EMAIL'] || `heroku auth:whoami`
|
53
|
+
session.execute_script %Q{$("span:contains(#{email}").html('developer@example.com')}
|
54
|
+
|
55
|
+
:::>> website.screenshot(name: "dashboard", upload: "s3")
|
56
|
+
```
|
@@ -9,7 +9,7 @@ end
|
|
9
9
|
```
|
10
10
|
<!--
|
11
11
|
rundoc src:
|
12
|
-
https://github.com/schneems/rundoc/blob/
|
12
|
+
https://github.com/schneems/rundoc/blob/main/test/fixtures/rails_4/rundoc.md
|
13
13
|
-->
|
14
14
|
|
15
15
|
> warning
|
@@ -56,7 +56,7 @@ Press enter at the prompt to upload your existing `ssh` key or create a new one,
|
|
56
56
|
You may be starting from an existing app, if so [upgrade to Rails 4](http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0) before continuing. If not, a vanilla Rails 4 app will serve as a suitable sample app. To build a new app make sure that you're using the Rails 4.x using `$ rails -v`. You can get the new version of rails by running,
|
57
57
|
|
58
58
|
```term
|
59
|
-
:::>> $ gem install rails -v 4.2.9 --no-
|
59
|
+
:::>> $ gem install rails -v 4.2.9 --no-document
|
60
60
|
```
|
61
61
|
|
62
62
|
Note: There may be a [more recent version of Rails](https://rubygems.org/gems/rails/versions) available, we recommend always running the latest. You may want to [run Rails 5 on Heroku](https://devcenter.heroku.com/articles/getting-started-with-rails5).
|
@@ -224,15 +224,18 @@ Make sure you are in the directory that contains your Rails app, then create an
|
|
224
224
|
You can verify that the remote was added to your project by running
|
225
225
|
|
226
226
|
```term
|
227
|
-
:::>> $ git config --list | grep heroku
|
227
|
+
:::>> $ git config --list --local | grep heroku
|
228
228
|
```
|
229
229
|
|
230
230
|
If you see `fatal: not in a git directory` then you are likely not in the correct directory. Otherwise you may deploy your code. After you deploy your code, you will need to migrate your database, make sure it is properly scaled and use logs to debug any issues that come up.
|
231
231
|
|
232
|
+
>note
|
233
|
+
>Following changes in the industry, Heroku has updated our default git branch name to `main`. If the project you’re deploying uses `master` as its default branch name, use `git push heroku master`.
|
234
|
+
|
232
235
|
Deploy your code:
|
233
236
|
|
234
237
|
```term
|
235
|
-
:::>> $ git push heroku
|
238
|
+
:::>> $ git push heroku main
|
236
239
|
```
|
237
240
|
|
238
241
|
It is always a good idea to check to see if there are any warnings or errors in the output. If everything went well you can migrate your database.
|
@@ -394,7 +397,7 @@ Looks good, so press Ctrl-C to exit and you can deploy your changes to Heroku:
|
|
394
397
|
```term
|
395
398
|
:::> $ git add .
|
396
399
|
:::> $ git commit -m "use puma via procfile"
|
397
|
-
:::> $ git push heroku
|
400
|
+
:::> $ git push heroku main
|
398
401
|
```
|
399
402
|
|
400
403
|
Check `ps`, you'll see the web process uses your new command specifying Puma as the web server
|