incodesert 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5ad6be7bdf06c9cba44bfad3b572459fd801874a
4
+ data.tar.gz: dc69f1f2234bdfb51e85d8468701c2f8fc70f0dc
5
+ SHA512:
6
+ metadata.gz: 687a44dcbfcca68c7a8749008fc64e749b25363f26e8d4fbd9c45b9e62c4b0e0438f636d74e1b2f146d371004629525d3bce626648dbece53be921d3cc9208bd
7
+ data.tar.gz: 11ca8ea225c4a98d84185373ffb70e1f80577694675c1695172f0a72d13341efc8f460462c41d4eb6baa295b765c5167cda95c58f7b5a71b00508c9555d900a7
data/.gemtest ADDED
File without changes
data/bin/incodesert ADDED
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require 'incodesert'
5
+
6
+ def usage
7
+ puts <<EOF
8
+ incodesert [flags] <source-file> <dest-file> [extractions-file]
9
+ incodesert inserts the code snippets found in the source file delimited
10
+ by special comment blocks and replaces matching comment blocks in the
11
+ destination file. If the optional extractions-file is supplied, that
12
+ file will contain the snippets of code removed from the destination
13
+ file (useful for later reverting the dest file).
14
+
15
+ Flag:
16
+ --help
17
+ Displays this message
18
+ --verbose
19
+ Provides diagnostics on stdout
20
+ --no-warn
21
+ By default, the replaced blocks in the destination will contain a
22
+ warning comment that the code there was auto-inserted by incodesert.
23
+ This flag suppresses that warning comment (most likely you would use
24
+ this when reverting from an extractions file).
25
+ --no-bak
26
+ By default, a backup of the destination file is produced with a .bak
27
+ suffix, just in case something goes horribly wrong. This flag
28
+ cleans up that file upon completion.
29
+ --classname <name>
30
+ By default, any occurance of the token __CLASSNAME__ in the source
31
+ file is replaced with the camel cased version of the destination
32
+ filename (sans path and suffix). If you want to override this
33
+ behavior with an explicit classname, supply it here.
34
+ EOF
35
+
36
+ abort
37
+ end
38
+
39
+ # Monkeypatch String to do camel casing.
40
+ class String
41
+ def camel_case
42
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
43
+ split('_').map{|e| e.capitalize}.join
44
+ end
45
+ end
46
+
47
+
48
+ verbose = false
49
+ no_warn = false
50
+ no_bak = false
51
+ classname = nil
52
+
53
+ if ARGV.length < 1
54
+ usage
55
+ end
56
+
57
+ while ARGV[0] =~ /^--/
58
+ arg = ARGV.shift
59
+ if arg == "--help"
60
+ usage
61
+ elsif arg == "--verbose"
62
+ verbose = true
63
+ elsif arg == "--no-warn"
64
+ no_warn = true
65
+ elsif arg == "--no-bak"
66
+ no_bak = true
67
+ elsif arg == "--classname"
68
+ classname = ARGV.shift
69
+ else
70
+ warn "Unrecognized flag: #{arg}"
71
+ usage
72
+ end
73
+ end
74
+
75
+ if ARGV.length < 2 and ARGV.length > 3
76
+ usage
77
+ end
78
+
79
+ source_name = ARGV.shift
80
+ dest_name = ARGV.shift
81
+ extractions_name = ARGV.shift if ARGV.any?
82
+ bak_name = dest_name + ".bak"
83
+
84
+ unless classname
85
+ # Deduce the classname represented by the dest file
86
+ # First strip off the path before the filename
87
+ classname = dest_name.sub(/\.\w+$/, "")
88
+ # Then strip off the suffix
89
+ classname = classname.sub(/^.*\//, "")
90
+
91
+ classname = classname.camel_case
92
+ end
93
+
94
+ source = File.open(source_name).read
95
+ # Protect against goofy carriage returns on non-unix systems
96
+ source.gsub!(/\r\n?/, "\n")
97
+
98
+ FileUtils.cp(dest_name, bak_name)
99
+ destination = File.open(bak_name).read
100
+ # Protect against goofy carriage returns on non-unix systems
101
+ destination.gsub!(/\r\n?/, "\n")
102
+
103
+ documents = Incodesert::Documents.new(source, destination)
104
+ documents.verbose = verbose
105
+ documents.no_warn = no_warn
106
+ documents.source_name = source_name
107
+
108
+ documents.replacements["CLASSNAME"] = classname
109
+
110
+ # Enough setup, let's light this candle!
111
+ documents.perform_insertions!
112
+
113
+ # Spew any warnings that came up (usually mismatched token names)
114
+ warn documents.warnings
115
+
116
+ File.open(dest_name, "w") do |file|
117
+ file.print documents.destination
118
+ end
119
+
120
+ if extractions_name
121
+ File.open(extractions_name, "w") do |file|
122
+ file.print documents.extractions
123
+ end
124
+ end
125
+
126
+ if no_bak
127
+ FileUtils.rm(bak_name)
128
+ end
129
+
130
+ # All done!
131
+
data/lib/incodesert.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Incodesert
2
+ VERSION = "0.1.0"
3
+ end
4
+
5
+ require 'incodesert/documents'
@@ -0,0 +1,216 @@
1
+ module Incodesert
2
+
3
+ # Documents holds all three documents involved in an incodesert insertion.
4
+ # Those three documents are:
5
+ # * The source: A String holding the blocks to be inserted delimited by special
6
+ # comments.
7
+ # * The destination: A String to receive the insertions that contains insertion
8
+ # points delimited by the same comments as appear in the source.
9
+ # * The extractions: A String of all the blocks that were removed in the destination
10
+ # in order to be replaced by the source. This can be useful to revert the changes.
11
+ #
12
+ # Documents also performs the insertion upon calling +perform_insertions!+
13
+ #
14
+ # Author:: Kirk Bowers (mailto:kirkbowers@yahoo.com)
15
+ # Copyright:: Copyright (c) 2015 Kirk Bowers
16
+ # License:: MIT License
17
+ class Documents
18
+
19
+ attr_accessor :source
20
+ attr_accessor :destination
21
+ attr_reader :extractions
22
+ attr_reader :warnings
23
+
24
+ attr_accessor :replacements
25
+
26
+ attr_accessor :verbose
27
+ attr_accessor :no_warn
28
+ attr_accessor :source_name
29
+
30
+ def initialize(source = "", destination = "")
31
+ @source = source
32
+ @destination = destination
33
+ @extractions = []
34
+ @warnings = []
35
+
36
+ @replacements = {}
37
+
38
+ @verbose = false
39
+ @no_warn = false
40
+ @source_name = nil
41
+ end
42
+
43
+ # Shadow global warn function
44
+ def warn(message)
45
+ @warnings.push message
46
+ end
47
+
48
+ def perform_insertions!
49
+ process_source
50
+
51
+ process_destination
52
+
53
+ @destination = @destination.join("\n")
54
+ @extractions = @extractions.join("\n")
55
+ # Add an extra newline to extractions so it doesn't end without one if non-empty
56
+ @extractions += "\n" if @extractions != ""
57
+
58
+ @warnings = @warnings.join("\n")
59
+ @warnings += "\n" if @warnings != ""
60
+ end
61
+
62
+ #-----------------------------------------------------------
63
+ private
64
+
65
+ def process_source
66
+ @blocks = {}
67
+ @current_block_name = ""
68
+
69
+ # The "-1" is an unintuitive way of saying don't trim off trailing newlines.
70
+ # We want to preserve the source exactly.
71
+ lines = @source.split("\n", -1)
72
+
73
+ lines.each do |line|
74
+ # If we match either a C-style or script style comment, exactly 3 < (in) chars,
75
+ # one or more spaces, then anything as a token (even including spaces)
76
+ # that opens a source block
77
+ if line =~ /^(\s*(\/{2}|#))\s+<{3}\s+(.+)/
78
+ open_source_block(line, $1, $3)
79
+ # Similarly, match a comment, exactly 3 > (out) chars, and a token,
80
+ # close the block
81
+ elsif line =~ /^\s*(\/{2}|#)\s+>{3}\s+(.+)/
82
+ close_source_block(line, $2)
83
+ # If we are in a current block, remember this line
84
+ elsif @current_block_name != ""
85
+ @current_block.push(line)
86
+ end
87
+ end
88
+ end
89
+
90
+ def open_source_block(line, comment, name)
91
+ name = name.rstrip
92
+ puts "Source: open block: #{name}" if @verbose
93
+ @current_block_name = name
94
+ @current_block = [line]
95
+ unless @no_warn
96
+ @current_block.push("#{comment}")
97
+ @current_block.push("#{comment} WARNING!!! This code auto-inserted by incodesert")
98
+ @current_block.push("#{comment} Do not edit this block!")
99
+ if @source_name
100
+ @current_block.push("#{comment} If you need to make changes, edit the source: #{@source_name}")
101
+ end
102
+ end
103
+ end
104
+
105
+ def close_source_block(line, name)
106
+ name = name.rstrip
107
+ puts "Source: close block: #{name}" if @verbose
108
+ if name == @current_block_name
109
+ @current_block.push(line)
110
+ @blocks[name] = @current_block
111
+ else
112
+ warn "In source: open and close blocks do not match!!"
113
+ warn "Opened with #{@current_block_name}"
114
+ warn "Closed with #{name}"
115
+ end
116
+
117
+ # Either way, we've attempted to close a block, so clear the current block name
118
+ # to signify we are not currently in a block at all.
119
+ @current_block_name = ""
120
+ end
121
+
122
+
123
+ def process_destination
124
+ @current_block_name = ""
125
+ @extractions = []
126
+
127
+ lines = @destination.split("\n", -1)
128
+
129
+ # We're going to rebuild the destination from scratch now
130
+ @destination = []
131
+
132
+ lines.each do |line|
133
+ if line =~ /^\s*(\/{2}|#)\s+<{3}\s+(.+)/
134
+ open_destination_block(line, $2)
135
+ elsif line =~ /^\s*(\/{2}|#)\s+>{3}\s+(.+)/
136
+ close_destination_block(line, $2)
137
+ elsif @current_block_name != ""
138
+ @current_block.push(line)
139
+ else
140
+ @destination.push(line)
141
+ end
142
+ end
143
+ end
144
+
145
+ def open_destination_block(line, name)
146
+ name = name.rstrip
147
+ puts "Destination: open block: #{name}" if @verbose
148
+ @current_block_name = name
149
+ @current_block = [line]
150
+ end
151
+
152
+ def close_destination_block(in_line, name)
153
+ name = name.rstrip
154
+ puts "Destination: close block: #{name}" if @verbose
155
+
156
+ @current_block.push(in_line)
157
+
158
+ if name == @current_block_name
159
+ lines_to_insert = @blocks[name]
160
+
161
+ if lines_to_insert.nil?
162
+ # There was no such block in the source
163
+ # We need to leave this block as is in the destination
164
+ @destination.concat(@current_block)
165
+ else
166
+ puts "Destination: matched block from source, replacing" if @verbose
167
+ lines_to_insert.each do |line|
168
+ result = ""
169
+ # This nasty bit of logic lets us scan through each line to insert and
170
+ # see if have any __ delimited tokens to possibly replace.
171
+ # Some languages, like ruby and python, do have variables with names that
172
+ # match this pattern. Our tokens here are all caps, so there's very little
173
+ # chance of a clash. Any __ surrounded thing we see but don't recognize
174
+ # should be left as is. Otherwise we replace.
175
+ while line.length > 0
176
+ match = /(__(\w+)__)/.match(line)
177
+ if match
178
+ replacement = @replacements[match[2]]
179
+ if replacement
180
+ result += match.pre_match + replacement
181
+ else
182
+ # There's probably a cleaner way to do this, but it works.
183
+ result += match.pre_match + match[1]
184
+ end
185
+ # Advance the part of the line we're looking at
186
+ line = match.post_match
187
+ else
188
+ # No match, consume all that's left
189
+ result += line
190
+ line = ""
191
+ end
192
+ end
193
+
194
+ @destination.push(result)
195
+ end
196
+
197
+ # Preserve the replaced block
198
+ @extractions.concat(@current_block)
199
+ end
200
+
201
+ else
202
+ # Pass the current block through unmodified
203
+ @destination.concat(@current_block)
204
+
205
+ warn "In Destination: open and close blocks do not match!!"
206
+ warn "Opened with #{@current_block_name}"
207
+ warn "Closed with #{name}"
208
+ end
209
+
210
+ # Either way, we've attempted to close a block, so clear the current block name
211
+ # to signify we are not currently in a block at all.
212
+ @current_block_name = ""
213
+ end
214
+
215
+ end
216
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: incodesert
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kirk Bowers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rdoc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hoe
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.13'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.13'
41
+ description: "code insert\n code in sert\n in code sert\n incodesert\n\n+incodesert+
42
+ (pronounced \"in code sert\", not \"inco desert\") \nis a utility for inserting
43
+ code snippets from one file into another.\nIt was originally intended to facilitate
44
+ autogenerating code and interlacing that\nautogened code with hand written code.
45
+ \ It is also useful for performing\nPre-rolled Blackbox Testing."
46
+ email:
47
+ - kirkbowers@yahoo.com
48
+ executables:
49
+ - incodesert
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - ".gemtest"
54
+ - bin/incodesert
55
+ - lib/incodesert.rb
56
+ - lib/incodesert/documents.rb
57
+ homepage: https://github.com/kirkbowers/incodesert
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options:
63
+ - "--main"
64
+ - README.txt
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.4.5
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: code insert code in sert in code sert incodesert +incodesert+ (pronounced
83
+ "in code sert", not "inco desert") is a utility for inserting code snippets from
84
+ one file into another
85
+ test_files: []