omnitest-psychic 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +5 -0
  5. data/.rubocop_todo.yml +36 -0
  6. data/.travis.yml +10 -0
  7. data/CONTRIBUTING.md +61 -0
  8. data/Gemfile +23 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +234 -0
  11. data/Rakefile +12 -0
  12. data/appveyor.yml +9 -0
  13. data/bin/psychic +4 -0
  14. data/docs/code_samples.md +92 -0
  15. data/docs/index.md +128 -0
  16. data/lib/omnitest/output_helper.rb +84 -0
  17. data/lib/omnitest/psychic.rb +191 -0
  18. data/lib/omnitest/psychic/cli.rb +171 -0
  19. data/lib/omnitest/psychic/code2doc.rb +75 -0
  20. data/lib/omnitest/psychic/code2doc/code_helper.rb +122 -0
  21. data/lib/omnitest/psychic/code2doc/code_segmenter.rb +170 -0
  22. data/lib/omnitest/psychic/code2doc/comment_styles.rb +92 -0
  23. data/lib/omnitest/psychic/command_template.rb +35 -0
  24. data/lib/omnitest/psychic/error.rb +15 -0
  25. data/lib/omnitest/psychic/execution/default_strategy.rb +82 -0
  26. data/lib/omnitest/psychic/execution/env_strategy.rb +12 -0
  27. data/lib/omnitest/psychic/execution/flag_strategy.rb +14 -0
  28. data/lib/omnitest/psychic/execution/token_strategy.rb +68 -0
  29. data/lib/omnitest/psychic/factories/go_factories.rb +14 -0
  30. data/lib/omnitest/psychic/factories/hot_read_task_factory.rb +54 -0
  31. data/lib/omnitest/psychic/factories/java_factories.rb +81 -0
  32. data/lib/omnitest/psychic/factories/powershell_factories.rb +53 -0
  33. data/lib/omnitest/psychic/factories/ruby_factories.rb +56 -0
  34. data/lib/omnitest/psychic/factories/shell_factories.rb +89 -0
  35. data/lib/omnitest/psychic/factories/travis_factories.rb +33 -0
  36. data/lib/omnitest/psychic/factory_manager.rb +46 -0
  37. data/lib/omnitest/psychic/file_finder.rb +69 -0
  38. data/lib/omnitest/psychic/hints.rb +21 -0
  39. data/lib/omnitest/psychic/magic_task_factory.rb +98 -0
  40. data/lib/omnitest/psychic/script.rb +146 -0
  41. data/lib/omnitest/psychic/script_factory.rb +66 -0
  42. data/lib/omnitest/psychic/script_factory_manager.rb +24 -0
  43. data/lib/omnitest/psychic/script_runner.rb +45 -0
  44. data/lib/omnitest/psychic/task.rb +6 -0
  45. data/lib/omnitest/psychic/task_factory_manager.rb +19 -0
  46. data/lib/omnitest/psychic/task_runner.rb +30 -0
  47. data/lib/omnitest/psychic/tokens.rb +51 -0
  48. data/lib/omnitest/psychic/version.rb +5 -0
  49. data/lib/omnitest/psychic/workflow.rb +27 -0
  50. data/lib/omnitest/shell.rb +27 -0
  51. data/lib/omnitest/shell/buff_shellout_executor.rb +41 -0
  52. data/lib/omnitest/shell/execution_result.rb +61 -0
  53. data/lib/omnitest/shell/mixlib_shellout_executor.rb +66 -0
  54. data/mkdocs.yml +6 -0
  55. data/omnitest-psychic.gemspec +36 -0
  56. data/spec/fabricators/shell_fabricator.rb +9 -0
  57. data/spec/omnitest/psychic/code2doc/code_helper_spec.rb +123 -0
  58. data/spec/omnitest/psychic/execution/default_strategy_spec.rb +17 -0
  59. data/spec/omnitest/psychic/execution/env_strategy_spec.rb +17 -0
  60. data/spec/omnitest/psychic/execution/flag_strategy_spec.rb +26 -0
  61. data/spec/omnitest/psychic/execution/token_strategy_spec.rb +26 -0
  62. data/spec/omnitest/psychic/factories/java_factories_spec.rb +35 -0
  63. data/spec/omnitest/psychic/factories/powershell_factories_spec.rb +59 -0
  64. data/spec/omnitest/psychic/factories/ruby_factories_spec.rb +91 -0
  65. data/spec/omnitest/psychic/factories/shell_factories_spec.rb +79 -0
  66. data/spec/omnitest/psychic/factories/travis_factories_spec.rb +78 -0
  67. data/spec/omnitest/psychic/hot_read_task_factory_spec.rb +51 -0
  68. data/spec/omnitest/psychic/script_factory_manager_spec.rb +57 -0
  69. data/spec/omnitest/psychic/script_spec.rb +55 -0
  70. data/spec/omnitest/psychic/shell_spec.rb +68 -0
  71. data/spec/omnitest/psychic/workflow_spec.rb +46 -0
  72. data/spec/omnitest/psychic_spec.rb +170 -0
  73. data/spec/spec_helper.rb +52 -0
  74. metadata +352 -0
@@ -0,0 +1,146 @@
1
+ require 'shellwords'
2
+
3
+ module Omnitest
4
+ class Psychic
5
+ class Script # rubocop:disable Metrics/ClassLength
6
+ include Code2Doc::CodeHelper
7
+ include Omnitest::OutputHelper
8
+
9
+ attr_reader :psychic
10
+
11
+ # @return [String] a name for the script
12
+ attr_reader :name
13
+ # @return [Pathname] the location of the script
14
+ attr_reader :source_file
15
+ # @return [Hash] options controlling how the script is executed
16
+ attr_reader :opts
17
+ # @return [Hash] params key/value pairs to bind to script input
18
+ attr_accessor :params
19
+ attr_accessor :arguments
20
+ attr_accessor :env
21
+
22
+ def initialize(psychic, name, source_file, opts = {})
23
+ fail ArgumentError if psychic.nil?
24
+ fail ArgumentError if name.nil?
25
+ fail ArgumentError if source_file.nil?
26
+ @name = name.to_s
27
+ @source_file, *@arguments = Shellwords.shellsplit(source_file.to_s)
28
+ @source_file = Pathname(@source_file)
29
+ @opts ||= opts
30
+ @env = opts[:env] || psychic.env
31
+ @params = opts[:params] ||= psychic.parameters
32
+ @psychic = psychic
33
+ end
34
+
35
+ def basedir
36
+ @psychic.basedir
37
+ end
38
+
39
+ def extname
40
+ source_file.extname
41
+ end
42
+
43
+ def command
44
+ execution_strategy.command
45
+ end
46
+
47
+ def execute(*extra_args)
48
+ execution_strategy.execute *extra_args
49
+ end
50
+
51
+ def tokens
52
+ return [] unless detection_strategy.respond_to? :tokens
53
+ @tokens ||= detection_strategy.tokens
54
+ end
55
+
56
+ def to_s(verbose = false)
57
+ build_string do
58
+ status('Script Name', name)
59
+ display_tokens
60
+ status('Source File', formatted_file_name)
61
+ display_source if verbose
62
+ end
63
+ end
64
+
65
+ def to_path
66
+ # So coercion to Pathname is possible
67
+ source_file.to_s
68
+ end
69
+
70
+ def absolute_source_file
71
+ return nil if source_file.nil?
72
+
73
+ File.expand_path source_file, basedir
74
+ end
75
+
76
+ def detection_strategy
77
+ @detection_strategy ||= create_detection_strategy
78
+ end
79
+
80
+ def execution_strategy
81
+ @execution_strategy ||= create_execution_strategy
82
+ end
83
+
84
+ def interactive?
85
+ @psychic.interactive?
86
+ end
87
+
88
+ private
89
+
90
+ def display_source
91
+ return unless source?
92
+ status 'Source Code'
93
+ say highlighted_code
94
+ end
95
+
96
+ def display_tokens
97
+ return status 'Tokens', '(None)' if tokens.empty?
98
+
99
+ status 'Tokens'
100
+ indent do
101
+ tokens.each do | token |
102
+ say "- #{token}"
103
+ end
104
+ end
105
+ end
106
+
107
+ def formatted_file_name
108
+ if source?
109
+ Omnitest::Core::FileSystem.relativize(absolute_source_file, Dir.pwd)
110
+ else
111
+ colorize('<No script>', :red)
112
+ end
113
+ end
114
+
115
+ def create_execution_strategy
116
+ strategy = opts[:execution_strategy]
117
+ case strategy
118
+ when nil
119
+ Execution::DefaultStrategy.new self
120
+ when 'tokens'
121
+ Execution::TokenStrategy.new self
122
+ when 'environment_variables'
123
+ Execution::EnvStrategy.new self
124
+ when 'flags'
125
+ Execution::FlagStrategy.new self
126
+ else
127
+ # TODO: Need support for custom commands with positional args
128
+ fail "Unknown binding strategy #{strategy}"
129
+ end
130
+ end
131
+
132
+ def create_detection_strategy
133
+ strategy = opts[:detection_strategy] || opts[:execution_strategy]
134
+ case strategy
135
+ when nil
136
+ nil
137
+ when 'tokens'
138
+ Tokens::RegexpTokenHandler.new(source, /'\{(\w+)\}'/, "'\\1'")
139
+ else
140
+ # TODO: Need support for detecting tokens from comments, help commands, etc.
141
+ fail "Unknown token detection strategy #{strategy}"
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,66 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class ScriptFactory < FactoryManager
4
+ include Omnitest::Core::Logger
5
+
6
+ TASK_PRIORITY = 5
7
+
8
+ attr_reader :priority, :psychic, :run_patterns
9
+
10
+ class << self
11
+ def register_script_factory
12
+ Omnitest::Psychic::ScriptFactoryManager.register_factory(self)
13
+ end
14
+
15
+ def run_patterns
16
+ @run_patterns ||= {}
17
+ end
18
+
19
+ def runs(ext, priority = 5)
20
+ run_patterns[ext] = priority
21
+ end
22
+
23
+ def priority_for_script(script)
24
+ script_path = Pathname(script)
25
+ run_patterns.each do | pattern, priority |
26
+ return priority if script_path.fnmatch(pattern, File::FNM_CASEFOLD)
27
+ end
28
+ nil
29
+ end
30
+ end
31
+
32
+ def initialize(psychic, opts)
33
+ @psychic = psychic
34
+ @opts = opts
35
+ @logger = psychic.logger
36
+ end
37
+
38
+ def active?
39
+ true
40
+ end
41
+
42
+ def cwd
43
+ psychic.cwd
44
+ end
45
+
46
+ def known_scripts
47
+ cwd_path = Pathname(cwd)
48
+ self.class.run_patterns.flat_map do | pattern, _priority |
49
+ Dir[cwd_path.join(pattern)]
50
+ end
51
+ end
52
+
53
+ def known_script?(script)
54
+ known_scripts.include? Pathname(script)
55
+ end
56
+
57
+ def priority_for_script(script)
58
+ self.class.priority_for_script(script)
59
+ end
60
+
61
+ def script(_script)
62
+ fail NotImplementedError, 'This should be implemented by subclasses'
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,24 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class ScriptFactoryManager < FactoryManager
4
+ def factories_for(script)
5
+ capable_factories = active_factories.select do | factory |
6
+ factory.priority_for_script(script)
7
+ end
8
+
9
+ capable_factories.sort_by do |factory|
10
+ factory.priority_for_script(script)
11
+ end
12
+ end
13
+
14
+ def priority_for(script)
15
+ active_factories.map do | factory |
16
+ priority = factory.priority_for_script(script) || 0
17
+ # FIXME: Need to change default log level to info before adding debug logging
18
+ # logger.debug("#{factory.class} priority for #{script.source_file}: #{priority}")
19
+ priority
20
+ end.max
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ module Omnitest
2
+ class Psychic
3
+ module ScriptRunner
4
+ def script_factory_manager
5
+ @script_factory_manager ||= ScriptFactoryManager.new(self, opts)
6
+ end
7
+
8
+ def known_scripts
9
+ @known_scripts ||= hints.scripts.map do | script_name, script_file |
10
+ Script.new(self, script_name, script_file, opts)
11
+ end
12
+ end
13
+
14
+ def script(script_name)
15
+ find_in_known_scripts(script_name) || find_in_basedir(script_name)
16
+ end
17
+
18
+ protected
19
+
20
+ def find_in_known_scripts(script_name)
21
+ known_scripts.find do |script|
22
+ script.name.downcase == script_name.downcase
23
+ end
24
+ end
25
+
26
+ def find_in_basedir(script_name) # rubocop:disable Metrics/AbcSize
27
+ file = FileFinder.find_file_by_alias(script_name, basedir) do | files |
28
+ candidates = files.group_by do | script_file |
29
+ # Chooses the file w/ the highest chance of being runnable
30
+ path = Omnitest::Core::FileSystem.relativize(script_file, cwd)
31
+ script = Script.new(self, script_name, path, opts)
32
+ script_factory_manager.priority_for(script) || 0
33
+ end
34
+ candidates.empty? ? files.first : candidates[candidates.keys.max].min_by(&:length)
35
+ end
36
+
37
+ return nil if file.nil?
38
+
39
+ Script.new(self, script_name, file, opts).tap do | script |
40
+ @known_scripts << script
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class Task < CommandTemplate
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class TaskFactoryManager < FactoryManager
4
+ def factories_for(task)
5
+ capable_factories = active_factories.select do | factory |
6
+ factory.priority_for_task(task)
7
+ end
8
+
9
+ capable_factories.sort_by do |factory|
10
+ factory.priority_for_task(task)
11
+ end
12
+ end
13
+
14
+ def known_tasks
15
+ active_factories.flat_map(&:known_tasks)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Omnitest
2
+ class Psychic
3
+ module TaskRunner
4
+ # Chooses an appropriate task for the task alias
5
+ # @param [String] task_alias an alias used to lookup a task
6
+ # @return [Task] the best match for the task alias
7
+ def task(task_alias)
8
+ task_alias = task_alias.to_sym
9
+ task_factory = task_factory_manager.factories_for(task_alias).last
10
+ fail TaskNotImplementedError, task_alias if task_factory.nil? || task_factory.priority == 0
11
+ command = task_factory.task(task_alias)
12
+ Task.new(self, command)
13
+ end
14
+
15
+ # Lists all known tasks. This will include tasks that have been
16
+ # manually alased in `psychic.yaml`, well-known tasks for detected
17
+ # tools, and possibly some dynamically detected tasks for tools that
18
+ # support task discovery.
19
+ # @return [Set<Task>] the set of known tasks
20
+ def known_tasks
21
+ task_factory_manager.known_tasks
22
+ end
23
+
24
+ # @api private
25
+ def task_factory_manager
26
+ @task_factory_manager ||= TaskFactoryManager.new(self, opts)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ autoload :Mustache, 'mustache'
2
+
3
+ module Omnitest
4
+ class Psychic
5
+ module Tokens
6
+ class RegexpTokenHandler
7
+ def initialize(template, token_pattern, token_replacement)
8
+ @template = template
9
+ @token_pattern = token_pattern
10
+ @token_replacement = token_replacement
11
+ end
12
+
13
+ def tokens
14
+ @template.scan(@token_pattern).flatten.uniq
15
+ end
16
+
17
+ def render(variables = {})
18
+ @template.gsub(@token_pattern) do
19
+ full_match = Regexp.last_match[0]
20
+ key = Regexp.last_match[1]
21
+ value = variables[key]
22
+ value = @token_replacement.gsub('\\1', value.to_s) unless @token_replacement.nil?
23
+ full_match.gsub(@token_pattern, value)
24
+ end
25
+ end
26
+ end
27
+
28
+ class MustacheTokenHandler
29
+ def initialize(template)
30
+ @template = Mustache::Template.new(template)
31
+ end
32
+
33
+ def tokens
34
+ @template.tags
35
+ end
36
+
37
+ def render(variables = {})
38
+ Mustache.render(@template, variables)
39
+ end
40
+ end
41
+
42
+ def self.replace_tokens(template, variables, token_regexp = nil, token_replacement = nil)
43
+ if token_regexp.nil?
44
+ MustacheTokenHandler.new(template).render(variables)
45
+ else
46
+ RegexpTokenHandler.new(template, token_regexp, token_replacement).render(variables)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,5 @@
1
+ module Omnitest
2
+ class Psychic
3
+ VERSION = '0.0.9'
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ module Omnitest
2
+ class Psychic
3
+ class Workflow
4
+ attr_reader :commands, :psychic
5
+
6
+ def initialize(psychic, name = 'workflow', options = {}, &block)
7
+ @psychic = psychic
8
+ @name = name
9
+ @options = options
10
+ @commands = []
11
+ instance_eval &block if block_given?
12
+ end
13
+
14
+ def task(name, *args)
15
+ @commands << psychic.task(name, *args)
16
+ end
17
+
18
+ def command
19
+ @commands.map(&:command).join("\n") + "\n"
20
+ end
21
+
22
+ def execute(_params = {}, shell_opts = {}, *extra_args)
23
+ @psychic.execute(command, shell_opts, *extra_args)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ module Omnitest
2
+ module Shell
3
+ autoload :ExecutionResult, 'omnitest/shell/execution_result'
4
+ autoload :ExecutionError, 'omnitest/shell/execution_result'
5
+ autoload :MixlibShellOutExecutor, 'omnitest/shell/mixlib_shellout_executor'
6
+ autoload :BuffShellOutExecutor, 'omnitest/shell/buff_shellout_executor'
7
+
8
+ AVAILABLE_OPTIONS = [
9
+ # All MixLib::ShellOut options - though we don't use most of these
10
+ :cwd, :domain, :password, :user, :group, :umask,
11
+ :timeout, :returns, :live_stream, :live_stdout,
12
+ :live_stderr, :input, :logger, :log_level, :log_tag, :env
13
+ ]
14
+
15
+ attr_writer :shell
16
+
17
+ def shell
18
+ @shell ||= RUBY_PLATFORM == 'java' ? BuffShellOutExecutor.new : MixlibShellOutExecutor.new
19
+ end
20
+
21
+ attr_writer :shell
22
+
23
+ def cli
24
+ @cli ||= Thor::Shell::Base.shell
25
+ end
26
+ end
27
+ end