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,92 @@
1
+ # Running Code Samples
2
+
3
+ Psychic can be used to run code samples as well as tasks. It's designed so that if two (or more) projects have a similar set of samples than the same psychic commands should find and run the samples, even if the commands required to run them are drastically different. Psychic does this by:
4
+ - Searching for sample code by name rather than path
5
+ - Finding a task runner that is capable of running hte sample
6
+ - Adjusting how to pass input to the code sample, if necessary
7
+
8
+ ## Showing Samples
9
+
10
+ You can check that psychic is finding the correct code sample before you attempt to run it. If you run:
11
+
12
+ ```
13
+ $ psychic show sample "upload file"
14
+ ```
15
+
16
+ This will show the information about the sample:
17
+
18
+ ```
19
+ Sample Name: upload file
20
+ Tokens:
21
+ - authUrl
22
+ - username
23
+ - apiKey
24
+ - region
25
+ - containerName
26
+ - localFilePath
27
+ - remoteObjectName
28
+ Source File: ObjectStore/upload-object.php
29
+ ```
30
+
31
+ If you pass the `--verbose` flag then Psychic will also display the code for the code sample, with syntax highlighting (if your terminal suports color).
32
+
33
+ The Tokens section shows what the tokens Psychic has detected that can be used for passing input to the code sample. See the documentation on [Tokens](tokens) for more details.
34
+
35
+ ### Automatic detection
36
+
37
+ The default behavior is for psychic to search for with file with a name that is similar to the name of the sample. It will basically do a case-insensitive search for a file with a name that contains "upload file", ignoring whitespace. It will then display info about the sample it found:
38
+
39
+ ```
40
+ $ bundle exec psychic show sample "upload file"
41
+ Sample Name: upload file
42
+ Tokens: (None)
43
+ Source File: storage/upload_file.rb
44
+ ```
45
+
46
+ ### Custom Map
47
+
48
+ You can tell Psychic exactly where to find a sample if it cannot be auto-detected. This is useful [omnitest](https://github.com/omnitest/omnitest) or any other situation where you want the same sample name to work across multiple projects even though the file names of the sample code have very different names.
49
+
50
+ You can do this by mapping sample names to files in your `psychic.yaml`. So you could add this:
51
+
52
+ ```yaml
53
+ samples:
54
+ upload file: ObjectStore/upload-object.php
55
+ ```
56
+
57
+ Now `psychic show sample "upload file"` will find the correct sample, even though it has "object" rather than "file" in the name.
58
+
59
+ ### Listing Samples
60
+
61
+ There is also a command for listing all known samples. See the [skeptic](https://github.com/omnitest/psychic-skeptic) project if you want to make ensure a project has a required set of samples.
62
+
63
+ ```
64
+ $ bundle exec psychic list samples
65
+ upload file ObjectStore/upload-object.php
66
+ change metadata ObjectStore/update-object-metadata.php
67
+ get file ObjectStore/get-object.php
68
+ create networked server Compute/create_server_with_network.php
69
+ create keypair Compute/create_new_keypair.php
70
+ create load balancer LoadBalancer/create-lb.php
71
+ secure load balancer LoadBalancer/blacklist-ip-range.php
72
+ setup ssl LoadBalancer/ssl-termination.php
73
+ delete load balancer LoadBalancer/delete-lb.php
74
+ ```
75
+
76
+ ### Running a sample
77
+
78
+ Once you've checked that Psychic is detecting the correct sample, you can easily tell Psychic to run it with hte `psychic sample` command.
79
+
80
+ If you want to see how psychic would run the sample first, you can use the `--print` flag:
81
+
82
+ ```
83
+ psychic sample "upload object" --print
84
+ ```
85
+
86
+ ### Dealing with Input
87
+
88
+ Psychic is able to run some code samples that require input. See the [Input documentation](input) for more details.
89
+
90
+ ### Testing code samples
91
+
92
+ You can use Psychic's companion project, [Skeptic](https://github.com/omnitest/psychic-skeptic) to test code samples.
@@ -0,0 +1,128 @@
1
+ # Psychic::Runner
2
+
3
+ Psychic runs anything.
4
+
5
+ ## What is Psychic
6
+
7
+ Psychic is a project to help developers that work on many different projects. It
8
+ provides a unified interface for running tasks so you don't need to remember a
9
+ bunch of project specific commands.
10
+
11
+ Psychic provides command aliases so that a command like `psychic bootstrap`
12
+ will invoke a similiar task in any project, but the actual command invoked will vary.
13
+ It might end up calling `bundle install`, `npm install` `./scripts/boostrap.sh` or some
14
+ other command.
15
+
16
+ Psychic provides a common set of alises for common tasks but also allows you to define
17
+ custom tasks across different projects, so you could have cross-project commands like
18
+ `metrics` or `documentation`.
19
+
20
+ ## Why?
21
+
22
+ Psychic exists to provide a common interface for tasks to build, test, and analyze projects while still allowing (or even encouraging) projects to use idiomatic patterns for their particular language. This makes it easy for new contributors to join a project because it follows the "principal of least astonishment", while also providing a consistent interface for tools to build, test or analyze any project.
23
+
24
+ The [bootstrap consistency pattern](http://wynnnetherland.com/linked/2013012801/bootstrapping-consistency) is an example of that. It aims to provide "a consistent user experience to get from zero to productive on any new project". Similarly, Travis-CI gives you a consistent interface to test any software project. The command `travis run` will always build and test a project.
25
+
26
+ Omnitest is an attempt to make a more generic framework for creating consistent user experiences across projects. That experienec can be "getting from zero to productive" like the bootstrap consistency pattern, "testing a change" like Travis-CI, or even something like "generating end of sprint reports" or "generating and previewing documentation".
27
+
28
+ In fact, the reason the project is called "psychic" is because it's supposed to seem like it's reading your mind. When you ask it to do something, like "bootstrap" a project, it should seem like it just magically picks the correct command to run. The project is actually more of a fraud than a clairvoyant - it just uses "[hot](http://en.wikipedia.org/wiki/Hot_reading)" and "[cold](http://en.wikipedia.org/wiki/Cold_reading)" reading tricks to pick the correct command.
29
+
30
+ ### Psychic vs scripts/*
31
+
32
+ ThoughtBot, GitHub and others use a [bootstrap consistency pattern](http://wynnnetherland.com/linked/2013012801/bootstrapping-consistency) to provide "a consistent user experience to get from zero to productive on any new project". The scripts used vary but common examples are:
33
+ - Bootstrapping via `bin/setup` or `script/bootstrap`
34
+ - Running tests via `script/test` and/or `script/cibuild`
35
+
36
+ Psychic's goals are similar but it's scope is broader, as explained above. Psychic will detect the scripts/* pattern and delegate task commands to it, so `psychic task bootstrap` will run `scripts/bootstrap.sh` if it exists. If you have both `bootstrap.sh` and `boostrap.ps1` (for Windows) psychic will choose the appropriate script for your platform.
37
+
38
+ The difference is that if you try to run a task for psychic that doesn't have a script Psychic will continue looking for other ways to run that task. For example, if you have a `scripts/bootstrap.sh` and `Rakefile` that defines `lint` task, then `psychic bootstrap` will run `scripts/bootstrap.sh`, while `psychic lint` will run `rake lint`.
39
+
40
+ ### Psychic vs Travis-Build
41
+
42
+ The goals of psychic are also similar to travis-build, and psychic will delegate supported tasks to travis-build if it is installed. Even if it isn't installed, Psychic aims to be as compatible as possible with travis-build, so a command like `psychic task install` behave a lot like `travis run install`.
43
+
44
+ Note that travis-build is not installed as a normal gem or Psychic would just depend on it. If you want psychic to delegate to travis-build you need to [install it as a CLI extension](https://github.com/travis-ci/travis-build#use-as-addon-for-cli).
45
+
46
+ #### Scope
47
+ Psychic's scope is broader, so there are things you could do with psychic that wouldn't make sense to run on Travis-CI. For example, you could setup a command like `psychic wip`, that would show your work in progress for any given project. The behavior would be project specific, but could include things like:
48
+ - Listing your local branches that haven't been merged
49
+ - Listing pull requests that are assigned to you
50
+ - Issues that are assigned to you
51
+ - Display `what_im_working_on.txt`
52
+
53
+ The command could be setup so it would display a WIP report for any project, even if some projects are getting info from your local git branches, other from GitHub, and others from issue trackers like JIRA, Launchpad, or Mingle. This command obviously doesn't make sense on Travis-CI, especially if it's only showing the work in progress for a current developer, but could be very useful if you want to quickly review your WIP across several projects. That's what the [omnitest](https://github.com/omnitest/omnitest) tool's [crosstasking](https://github.com/omnitest/omnitest#crosstasking-via-psychic) feature is designed to do.
54
+
55
+ #### Features
56
+
57
+ This is one of the reasons why psychic was created as a new project rather than as enhancements to travis-build. In general, Psychic aims to provide a simpler API that is less coupled with Travis. The major differences between Psychic and travis-build are:
58
+ - Psychic is distributed as a gem that can be used as a library or a standalone CLI. Travis-build is a
59
+ travis CLI extension that is not distributed as a gem.
60
+ - Psychic as a simple API for running tasks by alias. The travis-build API is tightly coupled with a travis-configuration object.
61
+ - Psychic just provides task aliases and inference. It does not provide environment setup, like fetching projects from version control or driving environment managers like rvm or virtualenv. Those actions should be done before invoking psychic.
62
+ - Psychic does not contain travis-specific features like integration with travis's caching system.
63
+
64
+ ## Psychic Commands
65
+
66
+ ### Tasks
67
+
68
+ Psychic supports both built-in and custom tasks. You should be able to use built-in tasks in virtually any project, even projects that weren't setup for psychic, because these tasks are mapped to common actions of commonly used tools.
69
+
70
+ #### Built-in Tasks
71
+
72
+ The `bootstrap` task is an example of a built-in task. If you run `psychic bootstrap` it will setup your project. It detects tools or patterns that are commonly used to setup projects and will choose an appropriate command for that tool. So it may run something like `bundle install`, `npm install`, or `scripts/bootstrap.sh`, depending on your project.
73
+
74
+ The built-in tasks are all mapped to top-level commands, so you can see them by running `psychic help`.
75
+
76
+ #### Custom Tasks
77
+
78
+ Psychic can also run custom tasks. You can list all known tasks, including custom tasks, with the command `psychic list tasks`. If you run with the `--verbose` flag it will show the command that would be run.
79
+
80
+ This list will include any custom tasks mapped to a command in `psychic.yaml`. Psychic will also try to detect known tasks from any tool that can print out a list of documented tasks, like Rake (via `rake --tasks` ), Grunt (via `grunt --help`) or NPM (via `npm run`). This is only partially supported, because not all tools support listing tasks, and even the ones that do rarely have an option for machine-readable output or an option like git's `--porcelain` (to "give the output in an easy-to-parse format for scripts", which will "remain stable across versions regardless of user configuration").
81
+
82
+ You can run a custom task via `psychic task <task_name>`. So if you have defined a `lint` task in a tool that supports autodetection, or defined the task in `psychic.yaml`, then you can run it with `psychic task lint`.
83
+
84
+ ```sh
85
+ bundle exec psychic task lint
86
+ I, [2015-01-15T13:01:09.752242 #59850] INFO -- : Executing bundle exec rubocop -D
87
+ I, [2015-01-15T13:01:10.595926 #59850] INFO -- : warning: parser/current is loading parser/ruby21, which recognizes
88
+ I, [2015-01-15T13:01:10.596019 #59850] INFO -- : warning: 2.1.5-compliant syntax, but you are running 2.1.4.
89
+ I, [2015-01-15T13:01:11.142419 #59850] INFO -- : Inspecting 2 files
90
+ I, [2015-01-15T13:01:11.142526 #59850] INFO -- : ..
91
+ I, [2015-01-15T13:01:11.142553 #59850] INFO -- :
92
+ I, [2015-01-15T13:01:11.142576 #59850] INFO -- : 2 files inspected, no offenses detected
93
+ ```
94
+
95
+ You can also pass additional arguments to the command after the end of options delimiter (`--`). This is useful for passing flags like `--debug`, `--verbose` or `--help`, though there is no guarantee that any of these flags will work (even `--help`) will work for a task.
96
+
97
+ ```sh
98
+ $ bundle exec psychic task lint -- --debug
99
+ I, [2015-01-15T13:06:11.456547 #60908] INFO -- : Executing bundle exec rubocop -D --debug
100
+ I, [2015-01-15T13:06:12.288424 #60908] INFO -- : warning: parser/current is loading parser/ruby21, which recognizes
101
+ I, [2015-01-15T13:06:12.289181 #60908] INFO -- : warning: 2.1.5-compliant syntax, but you are running 2.1.4.
102
+ I, [2015-01-15T13:06:12.689274 #60908] INFO -- : For /Users/Thoughtworker/repos/rackspace/polytrix/samples/sdks/ruby: configuration from /Users/Thoughtworker/repos/rackspace/polytrix/.rubocop.yml
103
+ I, [2015-01-15T13:06:12.689343 #60908] INFO -- : Inheriting configuration from /Users/Thoughtworker/repos/rackspace/polytrix/.rubocop_todo.yml
104
+ I, [2015-01-15T13:06:12.689372 #60908] INFO -- : Default configuration from /opt/boxen/rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/rubocop-0.28.0/config/default.yml
105
+ I, [2015-01-15T13:06:12.689396 #60908] INFO -- : Inheriting configuration from /opt/boxen/rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/rubocop-0.28.0/config/enabled.yml
106
+ I, [2015-01-15T13:06:12.689421 #60908] INFO -- : Inheriting configuration from /opt/boxen/rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/rubocop-0.28.0/config/disabled.yml
107
+ I, [2015-01-15T13:06:12.689447 #60908] INFO -- : Inspecting 2 files
108
+ I, [2015-01-15T13:06:12.689487 #60908] INFO -- : Scanning /Users/Thoughtworker/repos/rackspace/polytrix/samples/sdks/ruby/Gemfile
109
+ I, [2015-01-15T13:06:12.689514 #60908] INFO -- : .Scanning /Users/Thoughtworker/repos/rackspace/polytrix/samples/sdks/ruby/katas/hello_world.rb
110
+ I, [2015-01-15T13:06:12.689534 #60908] INFO -- : .
111
+ I, [2015-01-15T13:06:12.689553 #60908] INFO -- :
112
+ I, [2015-01-15T13:06:12.689574 #60908] INFO -- : 2 files inspected, no offenses detected
113
+ I, [2015-01-15T13:06:12.689595 #60908] INFO -- : Finished in 0.04216 seconds
114
+ ```
115
+
116
+ ### Code Samples
117
+
118
+ Psychic also has features to detect and run a code sample by alias, just like it runs tasks by an alias. This gets more complicated and might be split into a separate gem in the future, so we'll leave that for a separate doc.
119
+
120
+ ## Related projects
121
+
122
+ ### Skeptic
123
+
124
+ The [Skeptic](https://github.com/omnitest/skeptic) project is a companion to Psychic that tests the results code samples via Psychic. It captures and validates the output the exit code and output of the process, but can also capture additional data through "spies" like looking for HTTP calls it expects to see or files that should be created. So it let's you write assertions and reports on the behavior of code that's executed via Psychic.
125
+
126
+ ### Omnitest
127
+
128
+ The [omnitest](https://github.com/omnitest/omnitest) project is for running tasks or tests across multiple projects. It uses Psychic (and Skeptic) in order to run the task in teach project, and then consolidates all of the results and produces reports.
@@ -0,0 +1,84 @@
1
+ module Omnitest
2
+ module OutputHelper
3
+ class StringShell < Thor::Base.shell
4
+ attr_reader :io
5
+
6
+ def initialize(*args)
7
+ @io = StringIO.new
8
+ super
9
+ end
10
+
11
+ alias_method :stdout, :io
12
+ alias_method :stderr, :io
13
+
14
+ def string
15
+ @io.string
16
+ end
17
+
18
+ def can_display_colors?
19
+ # Still capture colors if they can eventually be displayed.
20
+ $stdout.tty?
21
+ end
22
+ end
23
+
24
+ def cli
25
+ @cli ||= Thor::Base.shell.new
26
+ end
27
+
28
+ def build_string
29
+ old_cli = @cli
30
+ new_cli = @cli = StringShell.new
31
+ yield
32
+ @cli = old_cli
33
+ new_cli.string
34
+ end
35
+
36
+ def reformat(string)
37
+ return if string.nil? || string.empty?
38
+
39
+ indent do
40
+ string.gsub(/^/, indent)
41
+ end
42
+ end
43
+
44
+ def indent
45
+ @indent_level ||= 0
46
+ if block_given?
47
+ @indent_level += 2
48
+ result = yield
49
+ @indent_level -= 2
50
+ result
51
+ else
52
+ ' ' * @indent_level
53
+ end
54
+ end
55
+
56
+ def say(msg)
57
+ cli.say msg if msg
58
+ end
59
+
60
+ def status(status, msg = nil, color = :cyan, colwidth = 50)
61
+ msg = yield if block_given?
62
+ cli.say(indent) if indent.length > 0
63
+ status = cli.set_color("#{status}:", color, true)
64
+ # The built-in say_status is right-aligned, we want left-aligned
65
+ cli.say format("%-#{colwidth}s %s", status, msg).rstrip
66
+ end
67
+
68
+ # TODO: Reporters for different formats
69
+ def print_table(*args)
70
+ # @reporter.print_table(*args)
71
+ cli.print_table(*args)
72
+ end
73
+
74
+ def colorize(string, *args)
75
+ return string unless @reporter.respond_to? :set_color
76
+ # @reporter.set_color(string, *args)
77
+ cli.set_color(string, *args)
78
+ end
79
+
80
+ def color_pad(string)
81
+ string + colorize('', :white)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,191 @@
1
+ require 'omnitest/core'
2
+ require 'omnitest/psychic/version'
3
+ require 'omnitest/psychic/error'
4
+ require 'omnitest/psychic/code2doc'
5
+ require 'yaml'
6
+
7
+ autoload :Thor, 'thor'
8
+
9
+ module Omnitest
10
+ autoload :Shell, 'omnitest/shell'
11
+ autoload :OutputHelper, 'omnitest/output_helper'
12
+
13
+ # The primary interface for using Psychic as an API.
14
+ #
15
+ # Detects scripts and tools that can run tasks in the instance's working directory,
16
+ # so that Psychic can act as a universal task/script selection and execution system.
17
+ class Psychic # rubocop:disable Metrics/ClassLength
18
+ autoload :Tokens, 'omnitest/psychic/tokens'
19
+ module Tokens
20
+ autoload :RegexpTokenHandler, 'omnitest/psychic/tokens'
21
+ autoload :MustacheTokenHandler, 'omnitest/psychic/tokens'
22
+ end
23
+ module Execution
24
+ autoload :DefaultStrategy, 'omnitest/psychic/execution/default_strategy'
25
+ autoload :TokenStrategy, 'omnitest/psychic/execution/token_strategy'
26
+ autoload :EnvStrategy, 'omnitest/psychic/execution/env_strategy'
27
+ autoload :FlagStrategy, 'omnitest/psychic/execution/flag_strategy'
28
+ end
29
+ autoload :Hints, 'omnitest/psychic/hints'
30
+ autoload :FileFinder, 'omnitest/psychic/file_finder'
31
+ autoload :FactoryManager, 'omnitest/psychic/factory_manager'
32
+ autoload :ScriptFactoryManager, 'omnitest/psychic/script_factory_manager'
33
+ autoload :ScriptFactoryManager, 'omnitest/psychic/script_factory_manager'
34
+ autoload :TaskFactoryManager, 'omnitest/psychic/task_factory_manager'
35
+ autoload :MagicTaskFactory, 'omnitest/psychic/magic_task_factory'
36
+ autoload :ScriptFactory, 'omnitest/psychic/script_factory'
37
+ autoload :CommandTemplate, 'omnitest/psychic/command_template'
38
+ autoload :Task, 'omnitest/psychic/task'
39
+ autoload :Script, 'omnitest/psychic/script'
40
+ autoload :Workflow, 'omnitest/psychic/workflow'
41
+ autoload :TaskRunner, 'omnitest/psychic/task_runner'
42
+ autoload :ScriptRunner, 'omnitest/psychic/script_runner'
43
+
44
+ FactoryManager.autoload_factories!
45
+
46
+ include Core::Logger
47
+ include Shell
48
+ include TaskRunner
49
+ include ScriptRunner
50
+
51
+ # @return [String] A name for logging and reporting.
52
+ # The default value is the name of the current working directory.
53
+ attr_reader :name
54
+ # @return [Dir] Current working directory for running commands.
55
+ attr_reader :cwd
56
+ alias_method :basedir, :cwd
57
+ # @return [Hash] Environment variables to use when executing commands.
58
+ # The default is to pass all environment variables to the command.
59
+ attr_reader :env
60
+ # @return [String] The Operating System to target. Autodetected if unset.
61
+ attr_reader :os
62
+ # @return [Hints] Psychic "hints" that are used to help Psychic locate tasks or scripts.
63
+ attr_reader :hints
64
+ # @return [Hash] Parameters to use as input for scripts.
65
+ attr_reader :parameters
66
+ # @return [Hash] Additional options
67
+ attr_reader :opts
68
+
69
+ DEFAULT_PARAMS_FILE = 'psychic-parameters.yaml'
70
+
71
+ # Creates a new Psychic instance that can be used to execute tasks and scripts.
72
+ # All options are
73
+ # @params [Hash] opts
74
+ # @option opts [Dir] :cwd sets the current working directory
75
+ # @option opts [Logger] :logger assigns a logger
76
+ # @option opts [Hash] :env sets environment variables
77
+ # @option opts [String] :name a name for logging and reporting
78
+ # @option opts [String] :os the target operating system
79
+ # @option opts [String] :interactive run psychic in interactive mode, where it will prompt for input
80
+ def initialize(opts = { cwd: Dir.pwd }) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
81
+ opts[:cwd] ||= Dir.pwd
82
+ # must be a string on windows...
83
+ @cwd = opts[:cwd] = Pathname(opts[:cwd]).to_s
84
+ @opts = opts
85
+ init_attr(:name) { File.basename cwd }
86
+ init_hints
87
+ init_attr(:logger) { new_logger }
88
+ init_attr(:env) { ENV.to_hash }
89
+ init_attr(:os) { RbConfig::CONFIG['host_os'] }
90
+ init_attrs :cli, :interactive, :parameter_mode, :restore_mode, :print
91
+ @shell_opts = select_shell_opts
92
+ @parameters = load_parameters(opts[:parameters])
93
+ end
94
+
95
+ # Executes a command using the options set on this Psychic instance.
96
+ # @param [String] command the command to execute
97
+ # @param [*args] *args additional arguments to join to the command
98
+ # @return [ExecutionResult] the result of running the command
99
+ # @raises [ExecutionError] if the command
100
+ #
101
+ # @example
102
+ # psychic.execute('echo', 'hello', 'world')
103
+ # #<Omnitest::Shell::ExecutionResult:0x007fdfe15208f0 @command="echo hello world",
104
+ # @exitstatus=0, @stderr="", @stdout="hello world\n">
105
+ #
106
+ # @example
107
+ # psychic.execute('foo')
108
+ # # Omnitest::Shell::ExecutionError: No such file or directory - foo
109
+ def execute(command, *args)
110
+ shell_opts = @shell_opts.dup
111
+ shell_opts.merge!(args.shift) if args.first.is_a? Hash
112
+ full_cmd = [command, *args].join(' ')
113
+ logger.banner("Executing: #{full_cmd}")
114
+ shell.execute(full_cmd, shell_opts)
115
+ end
116
+
117
+ def workflow(name = 'workflow', options = {}, &block)
118
+ Workflow.new(self, name, options, &block)
119
+ end
120
+
121
+ # Detects the Operating System family for the selected Operating System.
122
+ # @return [Symbol] The operating system family for {#os}.
123
+ def os_family
124
+ case os
125
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
126
+ :windows
127
+ when /darwin|mac os/
128
+ :macosx
129
+ when /linux/
130
+ :linux
131
+ when /solaris|bsd/
132
+ :unix
133
+ else
134
+ :unknown
135
+ end
136
+ end
137
+
138
+ # @return [Boolean] true if Psychic is in interactive mode and will prompt for decisions
139
+ def interactive?
140
+ @opts[:interactive]
141
+ end
142
+
143
+ private
144
+
145
+ def init_attr(var)
146
+ var_name = "@#{var}"
147
+ var_value = @opts[var]
148
+ var_value = yield if var_value.nil? && block_given?
149
+ instance_variable_set(var_name, var_value)
150
+ end
151
+
152
+ def init_attrs(*vars)
153
+ vars.each do | var |
154
+ init_attr var
155
+ end
156
+ end
157
+
158
+ def init_hints
159
+ hint_data = Omnitest::Core::Util.symbolized_hash(@opts[:hints] || load_hints || {})
160
+ @hints = Hints.new hint_data
161
+ @opts.merge! Omnitest::Core::Util.symbolized_hash(@hints.options)
162
+ end
163
+
164
+ def select_shell_opts
165
+ # Make sure to delete any option that isn't a MixLib::ShellOut option
166
+ @opts.select { |key, _| Omnitest::Shell::AVAILABLE_OPTIONS.include? key }
167
+ end
168
+
169
+ def load_hints
170
+ hints_file = Dir.glob("#{@cwd}/psychic.{yaml,yml}", File::FNM_CASEFOLD).first
171
+ YAML.load(File.read(hints_file)) unless hints_file.nil?
172
+ end
173
+
174
+ def load_parameters(parameters)
175
+ if parameters.nil? || parameters.is_a?(String)
176
+ load_parameters_file(parameters)
177
+ else
178
+ parameters
179
+ end
180
+ end
181
+
182
+ def load_parameters_file(file = nil)
183
+ if file.nil?
184
+ file ||= File.expand_path(DEFAULT_PARAMS_FILE, cwd)
185
+ return {} unless File.exist? file
186
+ end
187
+ # Just return it as a template, not as YAML
188
+ File.read(file)
189
+ end
190
+ end
191
+ end