rundoc 1.0.0 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check_changelog.yml +13 -0
  3. data/.gitignore +7 -0
  4. data/CHANGELOG.md +27 -0
  5. data/Dockerfile +24 -0
  6. data/README.md +221 -100
  7. data/bin/rundoc +4 -68
  8. data/lib/rundoc.rb +1 -0
  9. data/lib/rundoc/cli.rb +84 -0
  10. data/lib/rundoc/code_command.rb +3 -2
  11. data/lib/rundoc/code_command/background/process_spawn.rb +30 -4
  12. data/lib/rundoc/code_command/background/start.rb +2 -0
  13. data/lib/rundoc/code_command/bash.rb +3 -2
  14. data/lib/rundoc/code_command/bash/cd.rb +20 -2
  15. data/lib/rundoc/code_command/no_such_command.rb +4 -0
  16. data/lib/rundoc/code_command/pipe.rb +17 -4
  17. data/lib/rundoc/code_command/rundoc/depend_on.rb +37 -0
  18. data/lib/rundoc/code_command/rundoc/require.rb +41 -0
  19. data/lib/rundoc/code_command/rundoc_command.rb +4 -1
  20. data/lib/rundoc/code_command/website.rb +7 -0
  21. data/lib/rundoc/code_command/website/driver.rb +111 -0
  22. data/lib/rundoc/code_command/website/navigate.rb +29 -0
  23. data/lib/rundoc/code_command/website/screenshot.rb +28 -0
  24. data/lib/rundoc/code_command/website/visit.rb +47 -0
  25. data/lib/rundoc/code_section.rb +13 -4
  26. data/lib/rundoc/parser.rb +4 -3
  27. data/lib/rundoc/peg_parser.rb +1 -1
  28. data/lib/rundoc/version.rb +1 -1
  29. data/rundoc.gemspec +7 -2
  30. data/test/fixtures/build_logs/rundoc.md +56 -0
  31. data/test/fixtures/depend_on/dependency/rundoc.md +5 -0
  32. data/test/fixtures/depend_on/main/rundoc.md +10 -0
  33. data/test/fixtures/java/rundoc.md +9 -0
  34. data/test/fixtures/rails_4/rundoc.md +8 -5
  35. data/test/fixtures/rails_5/rundoc.md +17 -7
  36. data/test/fixtures/{rails_5_beta → rails_6}/rundoc.md +97 -88
  37. data/test/fixtures/require/dependency/rundoc.md +5 -0
  38. data/test/fixtures/require/main/rundoc.md +10 -0
  39. data/test/fixtures/screenshot/rundoc.md +10 -0
  40. data/test/rundoc/code_commands/background_test.rb +26 -0
  41. data/test/rundoc/code_commands/bash_test.rb +7 -0
  42. data/test/rundoc/code_commands/pipe_test.rb +11 -1
  43. data/test/rundoc/parser_test.rb +0 -1
  44. data/test/rundoc/peg_parser_test.rb +43 -0
  45. 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,7 @@
1
+ class Rundoc::CodeCommand::Website
2
+ end
3
+
4
+ require 'rundoc/code_command/website/driver'
5
+ require 'rundoc/code_command/website/screenshot'
6
+ require 'rundoc/code_command/website/visit'
7
+ require 'rundoc/code_command/website/navigate'
@@ -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)
@@ -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] = "#{fence}"
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
- return array.flatten.compact.map(&:rstrip).reject(&:empty?).join("\n") << "\n"
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
@@ -10,10 +10,11 @@ module Rundoc
10
10
 
11
11
  attr_reader :contents, :keyword, :stack
12
12
 
13
- def initialize(contents, options = {})
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 = options[:keyword] || DEFAULT_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
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module Rundoc
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.3"
3
3
  end
@@ -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{runDOC turns docs to runable code}
12
- gem.summary = %q{runDOC generates runable code from docs}
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
+ ```
@@ -0,0 +1,5 @@
1
+ ```
2
+ :::>> $ mkdir foo
3
+ :::>> $ cd foo
4
+ :::>> $ echo "hello" >> hello.txt
5
+ ```
@@ -0,0 +1,10 @@
1
+ # Hello
2
+
3
+ ```
4
+ :::-- rundoc.depend_on "../dependency/rundoc.md"
5
+ ```
6
+
7
+ ```
8
+ :::>> $ ruby -e "raise 'nope' unless File.read('hello.txt').chomp == 'hello'"
9
+ ```
10
+
@@ -0,0 +1,9 @@
1
+
2
+ Hello
3
+
4
+ ```
5
+ :::>- $ git clone https://github.com/heroku/java-getting-started
6
+ :::>- $ cd java-getting-started
7
+ ```
8
+
9
+ world
@@ -9,7 +9,7 @@ end
9
9
  ```
10
10
  <!--
11
11
  rundoc src:
12
- https://github.com/schneems/rundoc/blob/master/test/fixtures/rails_4/rundoc.md
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-ri --no-rdoc
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 master
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 master
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