antimony 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDg0NTU1Yjg1YTRhZDY2MjY3YTNhNzc4MWFjMzM3MGFhZmZjOTljYQ==
5
+ data.tar.gz: !binary |-
6
+ YWJmZGJlYjVhMjViOTZkZTY2MTJiNmNmMzg5NDQ4NTUxNTc2Mzc3ZA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NmU0MjI5ZTY1MGVmYzZjM2Q3YTQ0ODE3MjkyNTRlZDhiNTUwNWQwN2RkMzlh
10
+ MTU1YTMxNWJkYzM5ODY1N2I2YTUxOTk4YjQyZjFlZDI5Zjk1YTZmMWE5ZDE2
11
+ ZTQ1NGEyMTViNGE2N2QzODQyYTM4ZTc4NTk2NDhlODk5ODg4MWE=
12
+ data.tar.gz: !binary |-
13
+ YjM4MDVmYTc1MWE0OTk5NWFlYTFmOWUwNjY4MTUzZGIxNjEwMDYxYmM3MzVl
14
+ YjkwYzJhMzJlNGNhMWIxNmM4MWYyNjAyNDQyMzU2YTg2ZDI3NmZlOGFiMzIx
15
+ N2E3ZDM0ODUzYmU4MGQ3ZTVmNzRiN2JiOTUwMTcwZjQzOGJjYmE=
data/bin/sb ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib_path = File.expand_path('../../lib'.freeze, __FILE__)
4
+ ($LOAD_PATH.unshift lib_path) unless $LOAD_PATH.include? lib_path
5
+
6
+ require 'thor'
7
+ require 'logging'
8
+ require 'antimony'
9
+
10
+ class AntimonyCLI < Thor
11
+ desc 'formula FILE [--inputs my_input:hello other_input:goodbye]', 'run the specified formula'
12
+ method_option :inputs, type: :hash
13
+ def formula(file)
14
+ inputs = options[:inputs] || {}
15
+ formula = Antimony::Formula.new(file, inputs)
16
+ formula.run
17
+ end
18
+
19
+ default_task :formula
20
+ end
21
+
22
+ AntimonyCLI.start(ARGV)
@@ -0,0 +1,88 @@
1
+ # encoding 'ASCII-8bit'
2
+ lib_path = File.expand_path('../antimony', __FILE__)
3
+ ($LOAD_PATH.unshift lib_path) unless $LOAD_PATH.include? lib_path
4
+
5
+ require 'bundler'
6
+ Bundler.require(:default)
7
+
8
+ # Misc
9
+ require 'net/telnet'
10
+ require 'logging'
11
+
12
+ # Gem
13
+ require "#{lib_path}/config.rb"
14
+ require "#{lib_path}/formula.rb"
15
+ require "#{lib_path}/session.rb"
16
+
17
+ module Antimony
18
+ class << self
19
+ attr_writer :configuration
20
+ end
21
+
22
+ def self.configuration
23
+ @configuration ||= Antimony::Config.new.tap { |config| config.show_output = false }
24
+ end
25
+
26
+ def self.configure
27
+ yield configuration if block_given?
28
+ end
29
+
30
+ # Constants
31
+ ANSI_REGEX = /(\e\[.{1,2};?.{0,2}m|\e\[.{1}J|\e\[.{1,2}A|\e\[.{1,2}B|\e\[.{1,2}C|\e\[.{1,2}D|\e\[K|\e\[.{1,2};.{1,2}H|\e\[.{3}|.)/.freeze # rubocop:disable Metrics/LineLength
32
+ SCREEN_SEPARATOR = '################################################################################'.freeze
33
+ LOG_SEPARATOR = '================================================================================'.freeze
34
+
35
+ SPACE = ' '.freeze
36
+ EMPTY = ''.freeze
37
+ RECEIVE_OPTS = { 'Match' => /.{20}/, 'Timeout' => 1 }.freeze
38
+
39
+ ENTER = "\n".freeze
40
+ TAB = "\t".freeze
41
+ ESCAPE = "\e".freeze
42
+
43
+ H = 'H'.freeze
44
+
45
+ SEMICOLON = ';'.freeze
46
+
47
+ LINE = /.{80}/.freeze
48
+
49
+ class Session
50
+ KEYBOARD_METHODS = {
51
+ f1: "\x1B\x31".freeze,
52
+ f2: "\x1B\x32".freeze,
53
+ f3: "\x1B\x33".freeze,
54
+ f4: "\x1B\x34".freeze,
55
+ f5: "\x1B\x35".freeze,
56
+ f6: "\x1B\x36".freeze,
57
+ f7: "\x1B\x37".freeze,
58
+ f8: "\x1B\x38".freeze,
59
+ f9: "\x1B\x39".freeze,
60
+ f10: "\x1B\x30".freeze,
61
+ f11: "\x1B\x2D".freeze,
62
+ f12: "\x1B\x3D".freeze,
63
+ f13: "\x1B\x21".freeze,
64
+ f14: "\x1B\x40".freeze,
65
+ f15: "\x1B\x23".freeze,
66
+ f16: "\x1B\x24".freeze,
67
+ f17: "\x1B\x25".freeze,
68
+ f18: "\x1B\x5E".freeze,
69
+ f19: "\x1B\x26".freeze,
70
+ f20: "\x1B\x2A".freeze,
71
+ f21: "\x1B\x28".freeze,
72
+ f22: "\x1B\x29".freeze,
73
+ f23: "\x1B\x5F".freeze,
74
+ f24: "\x1B\x2B".freeze,
75
+ enter: "\n".freeze,
76
+ tab: "\t".freeze,
77
+ backspace: "\177".freeze,
78
+ arrow_up: "\e[A".freeze,
79
+ arrow_down: "\e[B".freeze,
80
+ arrow_right: "\e[C".freeze,
81
+ arrow_left: "\e[D".freeze
82
+ }
83
+
84
+ KEYBOARD_METHODS.each do |key, val|
85
+ define_method(key) { |count = 1| send_keys val, count, false }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,5 @@
1
+ module Antimony
2
+ class Config
3
+ attr_accessor :show_output, :pretty
4
+ end
5
+ end
@@ -0,0 +1,62 @@
1
+ module Antimony
2
+ class Formula
3
+ LOG_PARENT_PATH = Dir.pwd + '/log'
4
+ LOG_PATH = "#{LOG_PARENT_PATH}/formulas"
5
+ FORMULAS_PATH = Dir.pwd + '/formulas'
6
+
7
+ attr_accessor :inputs, :outputs, :formula_log
8
+
9
+ def initialize(name, inputs = {})
10
+ ($LOAD_PATH.unshift FORMULAS_PATH) unless $LOAD_PATH.include? FORMULAS_PATH
11
+
12
+ @inputs = indifferent_hash(inputs)
13
+ @outputs = {}
14
+
15
+ init_log(name)
16
+
17
+ helper_path = "#{FORMULAS_PATH}/formula_helper.rb"
18
+
19
+ eval File.read(helper_path) if File.exist?(helper_path) # rubocop:disable Lint/Eval
20
+
21
+ @formula = File.read("#{FORMULAS_PATH}/#{name}.rb")
22
+ end
23
+
24
+ def run
25
+ eval(@formula) # rubocop:disable Lint/Eval
26
+ end
27
+
28
+ def session(host)
29
+ @connection = Antimony::Session.new(host)
30
+ yield if block_given?
31
+ @log.info @connection.session_log
32
+ @connection.close
33
+ @connection = nil
34
+ end
35
+
36
+ private
37
+
38
+ def init_log(name)
39
+ Dir.mkdir LOG_PARENT_PATH unless Dir.exist? LOG_PARENT_PATH
40
+ Dir.mkdir LOG_PATH unless Dir.exist? LOG_PATH
41
+ @log = Logging.logger[name]
42
+ layout = Logging.layouts.pattern(pattern: "%m\n")
43
+ @log.add_appenders(Logging.appenders.file("#{LOG_PATH}/#{name}.log", truncate: true,
44
+ layout: layout))
45
+ end
46
+
47
+ def method_missing(name, *args, &block)
48
+ fail 'No active connection!' unless @connection
49
+ @connection.send(name, *args, &block)
50
+ end
51
+
52
+ def indifferent_hash(hash)
53
+ {}.tap do |new_hash|
54
+ hash.each do |key, value|
55
+ value = indifferent_hash(value) if value.is_a?(Hash)
56
+ new_hash[key.to_sym] = value
57
+ new_hash[key.to_s] = value
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,120 @@
1
+ module Antimony
2
+ class Session
3
+ def initialize(host)
4
+ @cursor_position = 0
5
+ @screen_text = blank_screen
6
+ @session_log = []
7
+
8
+ @connection = Net::Telnet.new('Host' => host, 'Timeout' => 10)
9
+
10
+ update_screen
11
+ end
12
+
13
+ def close
14
+ @connection.close
15
+ end
16
+
17
+ def send_keys(keys, count = 1, text = true)
18
+ count.times { @connection.print keys }
19
+ update_screen
20
+ add_text(keys) if text
21
+ log_screen
22
+ print_screen if Antimony.configuration.show_output
23
+ end
24
+
25
+ def value_at(row, column, length)
26
+ start_index = ((row - 1) * 80) + (column - 1)
27
+ end_index = start_index + length - 1
28
+ @screen_text[start_index..end_index].join
29
+ end
30
+
31
+ def screen_text
32
+ @screen_text.join
33
+ end
34
+
35
+ def printable_screen_text
36
+ @screen_text.join.scan(LINE)
37
+ end
38
+
39
+ def print_screen
40
+ puts SCREEN_SEPARATOR
41
+ puts printable_screen_text
42
+ puts SCREEN_SEPARATOR
43
+ end
44
+
45
+ def session_log
46
+ txt = EMPTY.clone
47
+ @session_log.each_with_index do |entry, i|
48
+ txt += LOG_SEPARATOR + ENTER
49
+ txt += "#{i}: #{ENTER}"
50
+ txt += entry + ENTER
51
+ end
52
+ txt += LOG_SEPARATOR
53
+ txt
54
+ end
55
+
56
+ def log(text)
57
+ @session_log.push(text)
58
+ end
59
+
60
+ def log_screen
61
+ log(printable_screen_text.join("\n"))
62
+ end
63
+
64
+ private
65
+
66
+ def update_screen
67
+ @screen_data = receive_data
68
+ @screen_text = blank_screen if @screen_data.include?('[2J')
69
+ parse_ansi
70
+ end
71
+
72
+ def add_text(text)
73
+ text.chars.each do |char|
74
+ @screen_text[@cursor_position] = char
75
+ @cursor_position += 1
76
+ end
77
+ end
78
+
79
+ def receive_data
80
+ whole = []
81
+ whole.push(@chunk) while chunk
82
+ whole.join
83
+ end
84
+
85
+ def chunk
86
+ @chunk = @connection.waitfor RECEIVE_OPTS rescue nil
87
+ end
88
+
89
+ def cursor_position(esc)
90
+ esc_code = /\e\[/
91
+ pos = esc.gsub(esc_code, EMPTY).gsub(H, EMPTY).split(SEMICOLON)
92
+ row_index = pos[0].to_i - 1
93
+ col_index = pos[1].to_i - 1
94
+ (row_index * 80) + col_index
95
+ end
96
+
97
+ def parse_ansi
98
+ ansi = @screen_data.scan(ANSI_REGEX).map do |e|
99
+ {
100
+ value: e[0],
101
+ type: e[0].include?(ESCAPE) ? :esc : :chr
102
+ }
103
+ end
104
+
105
+ ansi.each do |e|
106
+ if (e[:type] == :esc) && (e[:value].end_with? H)
107
+ @cursor_position = cursor_position(e[:value])
108
+ elsif e[:type] == :chr
109
+ @screen_text[@cursor_position] = e[:value]
110
+ @cursor_position += 1
111
+ end
112
+ end
113
+ @screen_text
114
+ end
115
+
116
+ def blank_screen
117
+ (1..1920).to_a.map { |_i| SPACE }
118
+ end
119
+ end
120
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: antimony
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Terris
8
+ - Adrienne Hisbrook
9
+ - Lesley Dennison
10
+ - Ryan Rosenblum
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2015-05-15 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: logging
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: thor
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ~>
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ - !ruby/object:Gem::Dependency
45
+ name: rake
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ~>
49
+ - !ruby/object:Gem::Version
50
+ version: '10.3'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: '10.3'
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ version: '3.0'
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ version: '3.0'
72
+ - !ruby/object:Gem::Dependency
73
+ name: pry
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ~>
77
+ - !ruby/object:Gem::Version
78
+ version: 0.10.1
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.10.1
86
+ - !ruby/object:Gem::Dependency
87
+ name: rubocop
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ~>
91
+ - !ruby/object:Gem::Version
92
+ version: 0.31.0
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ~>
98
+ - !ruby/object:Gem::Version
99
+ version: 0.31.0
100
+ description: DSL for writing scripts to drive TN5250 green-screen applications
101
+ email: ''
102
+ executables:
103
+ - sb
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - bin/sb
108
+ - lib/antimony.rb
109
+ - lib/antimony/config.rb
110
+ - lib/antimony/formula.rb
111
+ - lib/antimony/session.rb
112
+ homepage: https://github.com/manheim/antimony
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 2.4.6
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: TN5250/AS400 automation
136
+ test_files: []
137
+ has_rdoc: