dryft 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dryft.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dryft (0.0.1)
5
+ nokogiri
6
+ sqlite3-ruby
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ nokogiri (1.4.4)
12
+ sqlite3 (1.3.3)
13
+ sqlite3-ruby (1.3.3)
14
+ sqlite3 (>= 1.3.3)
15
+
16
+ PLATFORMS
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ dryft!
21
+ nokogiri
22
+ sqlite3-ruby
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Chris Berkhout (http://chrisberkhout.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,112 @@
1
+ = DRYFT for WinAutomation
2
+
3
+ == Description
4
+
5
+ DRYFT (Don't Repeat Yourself Factoring Tool) lets you define procedures in
6
+ WinAutomation[http://www.winautomation.com/] that can be included in multiple
7
+ other places and easily maintained from their original definition locations
8
+ without introducing consistency issues.
9
+
10
+ == Installation
11
+
12
+ 1. Install Ruby 1.8.7 with
13
+ {RubyInstaller for Windows}[http://rubyinstaller.org].
14
+ 2. Run <tt>gem install dryft</tt>.
15
+ 3. Complete any additional setup for the <tt>sqlite3-ruby</tt> gem.
16
+
17
+ == Conventions for procedures
18
+
19
+ A procedure is a WinAutomation job that follows DRYFT conventions, and can
20
+ therefore have DRYFT manage its dependencies on other procedures.
21
+
22
+ === Creating a procedure
23
+
24
+ To create a procedure, create a job with a name enclosed in angle brackets: e.g.
25
+ <tt><your procedure name></tt>. Extra text may follow the closing bracket and
26
+ will not be considered part of the procedure name, however, the opening bracket
27
+ must be the first character of the job name.
28
+
29
+ DRYFT ignores any jobs that do not follow the procedure naming convention. Only
30
+ proper procedures can include other procedures.
31
+
32
+ === Defining the steps of a procedure
33
+
34
+ Inside the job, start the procedure definition with a comment step starting:
35
+ <tt><your procedure name></tt>. The procedure name must be at the start of the
36
+ comment, but may be followed by other text. This is the opening tag.
37
+
38
+ After that, add another comment step that has the procedure name with a
39
+ forward-slash following the opening angle bracket:
40
+ <tt></your procedure name></tt>. This is the closing tag.
41
+
42
+ Any steps you place between the opening and closing tags will be considered part
43
+ of the procedure definition and will be copied to wherever the procedure is used
44
+ in other procedures. Any steps before the opening tag or after the closing tag
45
+ will not be copyed.
46
+
47
+ This means that you can use steps outside of the definition to help you make the
48
+ procedure definition job run by itself. For example, you may have a large
49
+ procedure that dynamically sets up certain configuration variables before
50
+ calling a smaller procedure. In the job defining the smaller procedure you can
51
+ hard-code the configuration variables before the opening tag so that that job can
52
+ be run by itself.
53
+
54
+ Similarly, steps after the closing tag can be used to provide feedback that is
55
+ helpful when running the procedure definition job individually, but which
56
+ shouldn't be included when the procedure is used within a larger one.
57
+
58
+ === Using a procedure in other procedures
59
+
60
+ To use one procedure within another, copy the steps from the opening tag to the
61
+ closing tag in the source procedure, and paste them into the destination
62
+ procedure wherever you want them to run.
63
+
64
+ === Updating a procedure automatically
65
+
66
+ Now, if you make some changes to the procedure in its defining job, you can
67
+ run DRYFT to automatically copy those changes across to wherever that procedure
68
+ is used in others. Be aware that if you change the steps between a procedures
69
+ opening and closing tags in a job that uses it (rather than defines it), DRYFT
70
+ will overwrite those changes with the old steps from the original procedure
71
+ definition job.
72
+
73
+ A procedure can include multiple other procedures and they can be nested to any
74
+ depth. DRYFT resovles dependencies so that any given procedure will only ever be
75
+ updated after all of its dependencies have been updated. It will detect improper
76
+ use of opening and closing tags and generate appropriate error messages. For
77
+ example, it will report unbalanced tags, use of undefined procedures and
78
+ circular dependencies.
79
+
80
+ When using the WinAutomation console, it is necessary to press F5 to refresh
81
+ the job list after DRYFT performs updates. This is because updated jobs are
82
+ assigned new IDs as a way of forcing the WinAutomation to disregard its old copy
83
+ of the job code.
84
+
85
+ === In conclusion
86
+
87
+ Go ahead and define procedures as you please, maintain them from their original
88
+ definition jobs and run DRYFT (and refresh with F5) often to ensure your jobs
89
+ database is consistent.
90
+
91
+ == Usage
92
+
93
+ To run load and update the default WinAutomation jobs database:
94
+ dryft
95
+
96
+ To load and update a WinAutomation jobs database in a different location:
97
+ dryft \path\to\your\Jobs.dat
98
+
99
+ == Compatibility
100
+
101
+ DRYFT has been developed using WinAutomation version 3.1.4.628 (released
102
+ December 17, 2010). It may work on earlier versions and will probably work
103
+ on later 3.1.x versions.
104
+
105
+ == Copyright
106
+
107
+ DRYFT comes to you under the MIT license. See LICENSE.txt for details.
108
+
109
+ == Feedback
110
+
111
+ I would love to hear any feedback you have about DRYFT!
112
+ The best way to reach me is via email at gmail.com (chrisberkhout@).
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'dryft'
4
+
5
+ db_file = ARGV[0] || '\Documents and Settings\All Users\Application Data\Softomotive\WinAutomation\Jobs.dat'
6
+ DRYFT::Jobs.new(db_file).update_all
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dryft/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dryft"
7
+ s.version = Dryft::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Chris Berkhout"]
10
+ s.email = ["chrisberkhout@gmail.com"]
11
+ s.homepage = "http://github.com/chrisberkhout/dryft"
12
+ s.summary = %q{Don't Repeat Yoursef Factoring Tool for WinAutomation}
13
+ s.description = %q{Define WinAutomation procedures that can be included elsewhere without consistency issues.}
14
+
15
+ s.rubyforge_project = "dryft"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("sqlite3-ruby")
23
+ s.add_dependency("nokogiri")
24
+ end
@@ -0,0 +1,13 @@
1
+
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+
5
+ require 'sqlite3'
6
+ require 'nokogiri'
7
+
8
+ require 'ruby/object'
9
+ require 'nokogiri/node'
10
+ require 'nokogiri/node_set'
11
+
12
+ require 'dryft/job'
13
+ require 'dryft/jobs'
@@ -0,0 +1,126 @@
1
+ module DRYFT
2
+ class Job
3
+
4
+ attr_reader :id, :name, :proc, :def_acts, :deps
5
+
6
+ def initialize(db, info)
7
+ @db = db
8
+ @id = get_id(db, info)
9
+ load
10
+ end
11
+
12
+ def reload
13
+ load
14
+ end
15
+
16
+ def update_from_deps(jobs)
17
+ no_change = true
18
+ @deps.each do |dep|
19
+ src_job = jobs.by_proc(dep[:proc])
20
+ if dep[:acts].not_equivalent_to?( src_job.def_acts )
21
+ no_change = false
22
+ dep[:acts].after( src_job.def_acts.to_xml )
23
+ dep[:acts].unlink
24
+ puts "At #{@name}:#{dep[:start]}, updated procedure <#{dep[:proc]}> from its definition."
25
+ end
26
+ end
27
+ if no_change
28
+ puts "No change to '#{@name}'."
29
+ else
30
+ updated_code = @doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
31
+ @db.execute("UPDATE jobcode SET code = ? WHERE hex(id) = ?", [updated_code, @id])
32
+
33
+ # Switch to a new job ID, because there is no way to trigger a reload ov the original job ID's code.
34
+ new_id = @db.execute("SELECT HEX(RANDOMBLOB(LENGTH(Id))) FROM jobcode")[0][0]
35
+ @db.execute("UPDATE compilerproperties SET JobId = x'#{new_id}' WHERE hex(JobId) = ?", [@id])
36
+ @db.execute("UPDATE jobaddon SET JobId = x'#{new_id}' WHERE hex(JobId) = ?", [@id])
37
+ @db.execute("UPDATE jobaddonassembly SET JobId = x'#{new_id}' WHERE hex(JobId) = ?", [@id])
38
+ @db.execute("UPDATE jobattachment SET JobId = x'#{new_id}' WHERE hex(JobId) = ?", [@id])
39
+ @db.execute("UPDATE jobcode SET Id = x'#{new_id}' WHERE hex(Id) = ?", [@id])
40
+ @db.execute("UPDATE jobinfo SET Id = x'#{new_id}' WHERE hex(Id) = ?", [@id])
41
+ @db.execute("UPDATE jobproperties SET Id = x'#{new_id}' WHERE hex(Id) = ?", [@id])
42
+ @db.execute("UPDATE jobproperties2 SET Id = x'#{new_id}' WHERE hex(Id) = ?", [@id])
43
+ @db.execute("UPDATE triggers SET JobId = x'#{new_id}' WHERE hex(JobId) = ?", [@id])
44
+ @id = new_id
45
+
46
+ reload
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ def get_id(db, info)
53
+ case info.keys[0]
54
+ when :id
55
+ info[:id]
56
+ when :name
57
+ rows = db.execute("SELECT hex(id) FROM jobinfo WHERE name = ?", [info[:name]])
58
+ abort "ERROR: tried to load the job named '#{info[:name]}', but that name is not unique." if rows.length > 1
59
+ abort "ERROR: tried to load the job named '#{info[:name]}', but it was not found." if rows.length == 0
60
+ rows[0][0]
61
+ when :proc
62
+ rows = db.execute("SELECT hex(id) FROM jobinfo WHERE name LIKE ?", ["<#{info[:proc]}>%"])
63
+ abort "ERROR: tried to load the procedure named '#{info[:proc]}', but that name is not unique." if rows.length > 1
64
+ abort "ERROR: tried to load the procedure named '#{info[:proc]}', but it was not found." if rows.length == 0
65
+ rows[0][0]
66
+ end
67
+ end
68
+
69
+ def load
70
+ rows = @db.execute("SELECT hex(i.id), i.name, c.code FROM jobinfo i, jobcode c WHERE i.id = c.id AND hex(i.id) = ?", [@id])
71
+ abort "ERROR: tried to load the job with ID '#{@id}', but it was not found." if rows.length == 0
72
+ @id, @name, @code = rows[0]
73
+ if @name =~ /^\<([^\/\>].*?)\>.*/
74
+ @proc = $1
75
+ else
76
+ abort "ERROR: tried to initialise a job ('#{@name}') that is not a procedure."
77
+ end
78
+ parse
79
+ end
80
+
81
+ def parse
82
+ @actions = nil
83
+ @def_acts = nil
84
+ @deps = []
85
+ stack = []
86
+ def_start = nil
87
+ def_end = nil
88
+
89
+ @doc = Nokogiri::XML(@code)
90
+ @actions = @doc.xpath("//xmlns:ActionFlow[@Name='Main Flow']/*")
91
+ @actions.each_with_index do |a,i|
92
+
93
+ comment = a.xpath("./descendant-or-self::xmlns:CommentAction/xmlns:Comment").first
94
+ if comment && comment.inner_text =~ /^\<([^\/\>].*?)\>.*/ # opening tag
95
+
96
+ tag = $1
97
+ abort "ERROR: at '#{@name}:#{i+1}', attempt to define <#{@proc}> within <#{stack.last[:tag]}>." if tag == @proc && stack.length > 0
98
+ abort "ERROR: at '#{@name}:#{i+1}', reopening <#{tag}> here implies circular dependency." if tag.is_in? stack.map{ |e| e[:tag] }
99
+ stack.push({:tag => tag, :start => (i+1) })
100
+
101
+ elsif comment && comment.inner_text =~ /^\<\/(.*?)\>.*/ # closing tag
102
+
103
+ tag = $1
104
+ abort "ERROR: at '#{name}:#{i+1}', attempt to close <#{tag}> when nothing was open." if stack.length == 0
105
+ abort "ERROR: at '#{name}:#{i+1}', attempt to close <#{tag}> when close of <#{stack.last[:tag]}> was expected." if stack.last[:tag] != tag
106
+ if tag == @proc # end of definition of the procedure
107
+ def_start = stack.last[:start]
108
+ def_end = i+1
109
+ elsif stack.length == 1 || stack[-2][:tag] == @proc # end of definition of a direct dependency
110
+ @deps.push({ :proc => tag, :start => stack.last[:start], :end => i+1, :acts => @actions[(stack.last[:start]-1)..(i)] })
111
+ end
112
+ stack.pop
113
+
114
+ end
115
+
116
+ end # each action
117
+
118
+ abort "ERROR: in '#{@name}', there was no code to define the procedure <#{@proc}>." if def_start.nil?
119
+ abort "ERROR: in '#{@name}', no closing tag for the definition of <#{@proc}>." if def_end.nil?
120
+ abort "ERROR: in '#{@name}', the following tags were not closed: <#{stack.map{ |e| e[:tag] }.join('>, <')}>." if stack.length > 0
121
+
122
+ @def_acts = @actions[ (def_start-1) .. (def_end-1) ]
123
+ end # parse
124
+
125
+ end
126
+ end # module
@@ -0,0 +1,63 @@
1
+ module DRYFT
2
+ class Jobs
3
+
4
+ def initialize(db_file)
5
+ abort "ERROR: The specified WinAutomation jobs database does not exist: #{db_file}" if !File.exists?(db_file)
6
+ @job_list = get_jobs(SQLite3::Database.new db_file)
7
+ puts "Loaded the WinAutomation jobs database from: #{db_file}"
8
+ end
9
+
10
+ def by_id(id)
11
+ (@job_list.select{ |j| j.id == id }).first
12
+ end
13
+ def by_name(name)
14
+ (@job_list.select{ |j| j.name == name }).first
15
+ end
16
+ def by_proc(proc)
17
+ (@job_list.select{ |j| j.proc == proc }).first
18
+ end
19
+
20
+ def update_all
21
+ resolve_list(@job_list).each do |j|
22
+ j.update_from_deps(self)
23
+ end
24
+ puts "Updated all procedures.\n\n"
25
+ puts "Please PRESS F5 in the WinAutomation Console to refresh the jobs list!"
26
+ end
27
+
28
+ protected
29
+
30
+ def get_jobs(db)
31
+ jobs = []
32
+ rows = db.execute("SELECT hex(id) FROM jobinfo WHERE name LIKE '<%>%'")
33
+ rows.each do |r|
34
+ id = r[0]
35
+ jobs.push( Job.new(db, :id => id) )
36
+ end
37
+ jobs
38
+ end
39
+
40
+ def resolve_list(job_list)
41
+ resolved = []
42
+ job_list.each{ |j| resolved = resolve(j, resolved) }
43
+ resolved
44
+ end
45
+
46
+ def resolve(job, resolved = [], unresolved = [])
47
+ if job.not_in? resolved
48
+ unresolved << job
49
+ job.deps.each do |dep|
50
+ abort "ERROR: at '#{job.name}:#{dep[:start]}', the procedure <#{dep[:proc]}> is used but not defined." if by_proc(dep[:proc]).nil?
51
+ if by_proc(dep[:proc]).not_in? resolved
52
+ abort "ERROR: circular dependency detected: <#{job.proc}> -> <#{dep[:proc]}>." if by_proc(dep[:proc]).is_in? unresolved
53
+ resolved = resolve(by_proc(dep[:proc]), resolved, unresolved)
54
+ unresolved -= resolved
55
+ end
56
+ end
57
+ resolved << job
58
+ end
59
+ return resolved
60
+ end
61
+
62
+ end
63
+ end # module
@@ -0,0 +1,3 @@
1
+ module Dryft
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,32 @@
1
+ class Nokogiri::XML::Node
2
+
3
+ def equivalent_to?(other)
4
+
5
+ self_nodes = []
6
+ other_nodes = []
7
+
8
+ self.traverse { |n| self_nodes << n }
9
+ other.traverse { |n| other_nodes << n }
10
+
11
+ return false if self_nodes.length != other_nodes.length
12
+
13
+ 0.upto(self_nodes.length-1) do |i|
14
+
15
+ s = self_nodes[i] ; s_attribs = {} ; s.attributes.each_pair { |k,v| s_attribs[k] = v.value }
16
+ o = other_nodes[i] ; o_attribs = {} ; o.attributes.each_pair { |k,v| o_attribs[k] = v.value }
17
+
18
+ return false if s.name != o.name
19
+ return false if ((s.text? || s.cdata?) && s.text) != ((o.text? || o.cdata?) && o.text)
20
+ return false if s_attribs != o_attribs
21
+
22
+ end
23
+
24
+ return true
25
+
26
+ end
27
+
28
+ def not_equivalent_to?(other)
29
+ !equivalent_to?(other)
30
+ end
31
+
32
+ end
@@ -0,0 +1,13 @@
1
+ class Nokogiri::XML::NodeSet
2
+
3
+ def equivalent_to?(other)
4
+ return false if self.length != other.length
5
+ 0.upto(self.length-1) { |i| return false if self[i].not_equivalent_to?( other[i] ) }
6
+ return true
7
+ end
8
+
9
+ def not_equivalent_to?(other)
10
+ !equivalent_to?(other)
11
+ end
12
+
13
+ end
@@ -0,0 +1,11 @@
1
+ class Object
2
+
3
+ def is_in?(array)
4
+ array.include?(self)
5
+ end
6
+
7
+ def not_in?(array)
8
+ !array.include?(self)
9
+ end
10
+
11
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dryft
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Chris Berkhout
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-20 00:00:00 +11:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: sqlite3-ruby
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: nokogiri
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: Define WinAutomation procedures that can be included elsewhere without consistency issues.
50
+ email:
51
+ - chrisberkhout@gmail.com
52
+ executables:
53
+ - dryft
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - .gitignore
60
+ - Gemfile
61
+ - Gemfile.lock
62
+ - LICENSE.txt
63
+ - README.rdoc
64
+ - Rakefile
65
+ - bin/dryft
66
+ - dryft.gemspec
67
+ - lib/.DS_Store
68
+ - lib/dryft.rb
69
+ - lib/dryft/job.rb
70
+ - lib/dryft/jobs.rb
71
+ - lib/dryft/version.rb
72
+ - lib/nokogiri/node.rb
73
+ - lib/nokogiri/node_set.rb
74
+ - lib/ruby/.DS_Store
75
+ - lib/ruby/object.rb
76
+ has_rdoc: true
77
+ homepage: http://github.com/chrisberkhout/dryft
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options: []
82
+
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project: dryft
106
+ rubygems_version: 1.3.7
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Don't Repeat Yoursef Factoring Tool for WinAutomation
110
+ test_files: []
111
+