| 1 | # rcs.tcl -- |
|---|
| 2 | # |
|---|
| 3 | # Utilities for RCS related operations. |
|---|
| 4 | # |
|---|
| 5 | # Copyright (c) 2005 by Colin McCormack <coldstore@users.sourceforge.net> |
|---|
| 6 | # Copyright (c) 2005 by Andreas Kupries <andreas_kupries@users.sourceforge.net> |
|---|
| 7 | # |
|---|
| 8 | # See the file "license.terms" for information on usage and redistribution |
|---|
| 9 | # of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
|---|
| 10 | # |
|---|
| 11 | # RCS: @(#) $Id: rcs.tcl,v 1.4 2005/09/28 04:51:23 andreas_kupries Exp $ |
|---|
| 12 | |
|---|
| 13 | # ### ### ### ######### ######### ######### |
|---|
| 14 | ## Requisites. |
|---|
| 15 | |
|---|
| 16 | package require Tcl 8.4 |
|---|
| 17 | |
|---|
| 18 | # ### ### ### ######### ######### ######### |
|---|
| 19 | ## API Implementation |
|---|
| 20 | |
|---|
| 21 | namespace eval rcs {} |
|---|
| 22 | |
|---|
| 23 | # ::rcs::text2dict -- |
|---|
| 24 | # |
|---|
| 25 | # Convert a text into a dictionary. The dictionary is keyed by line |
|---|
| 26 | # numbers, and the value is the text of the corresponding line. The |
|---|
| 27 | # first line has index/number 1. |
|---|
| 28 | # |
|---|
| 29 | # Arguments |
|---|
| 30 | # - text The text to convert. |
|---|
| 31 | # |
|---|
| 32 | # Results |
|---|
| 33 | # A dictionary containing the text in split form. |
|---|
| 34 | # |
|---|
| 35 | # Side effects |
|---|
| 36 | # None |
|---|
| 37 | |
|---|
| 38 | proc ::rcs::text2dict {text} { |
|---|
| 39 | array set lines {} |
|---|
| 40 | set lnum 0 |
|---|
| 41 | foreach line [split $text \n] { |
|---|
| 42 | set lines([incr lnum]) $line |
|---|
| 43 | } |
|---|
| 44 | return [array get lines] |
|---|
| 45 | } |
|---|
| 46 | |
|---|
| 47 | # ::rcs::file2dict -- |
|---|
| 48 | # |
|---|
| 49 | # Convert a text stored in a file into a dictionary. The dictionary is |
|---|
| 50 | # keyed by line numbers, and the value is the text of the |
|---|
| 51 | # corresponding line. The first line has index/number 1. |
|---|
| 52 | # |
|---|
| 53 | # Arguments |
|---|
| 54 | # - file The path of the file containing the text to convert. |
|---|
| 55 | # |
|---|
| 56 | # Results |
|---|
| 57 | # A dictionary containing the text in split form. |
|---|
| 58 | # |
|---|
| 59 | # Side effects |
|---|
| 60 | # None |
|---|
| 61 | |
|---|
| 62 | proc ::rcs::file2dict {filename} { |
|---|
| 63 | set chan [open $filename r] |
|---|
| 64 | set text [read $chan] |
|---|
| 65 | close $chan |
|---|
| 66 | |
|---|
| 67 | return [text2dict $text] |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | # ::rcs::dict2text -- |
|---|
| 71 | # |
|---|
| 72 | # Converts a dictionary as created by the 2dict commands back into a |
|---|
| 73 | # text. The dictionary is keyed by line numbers, and the value is the |
|---|
| 74 | # text of the corresponding line. The first line has index/number 1. |
|---|
| 75 | # The dictionary may have gaps in the line numbers. |
|---|
| 76 | # |
|---|
| 77 | # Arguments |
|---|
| 78 | # - dict The dictionary to convert. |
|---|
| 79 | # |
|---|
| 80 | # Results |
|---|
| 81 | # The text stored in the dictionary. |
|---|
| 82 | # |
|---|
| 83 | # Side effects |
|---|
| 84 | # None |
|---|
| 85 | |
|---|
| 86 | proc ::rcs::dict2text {dict} { |
|---|
| 87 | array set lines $dict |
|---|
| 88 | set result {} |
|---|
| 89 | foreach lnum [lsort -integer [array names lines]] { |
|---|
| 90 | lappend result $lines($lnum) |
|---|
| 91 | } |
|---|
| 92 | return [join $result \n] |
|---|
| 93 | } |
|---|
| 94 | |
|---|
| 95 | # ::rcs::dict2file -- |
|---|
| 96 | # |
|---|
| 97 | # Converts a dictionary as created by the 2dict commands back into a |
|---|
| 98 | # text and stores it into the specified file. The dictionary is keyed |
|---|
| 99 | # by line numbers, and the value is the text of the corresponding |
|---|
| 100 | # line. The first line has index/number 1. The dictionary may have |
|---|
| 101 | # gaps in the line numbers. |
|---|
| 102 | # |
|---|
| 103 | # Arguments |
|---|
| 104 | # - filename The path to the file to store the reconstructed text into. |
|---|
| 105 | # - dict The dictionary to convert. |
|---|
| 106 | # |
|---|
| 107 | # Results |
|---|
| 108 | # None. |
|---|
| 109 | # |
|---|
| 110 | # Side effects |
|---|
| 111 | # None |
|---|
| 112 | |
|---|
| 113 | proc ::rcs::dict2file {filename dict} { |
|---|
| 114 | set chan [open $filename w] |
|---|
| 115 | puts -nonewline $chan [dict2text $dict] |
|---|
| 116 | close $chan |
|---|
| 117 | } |
|---|
| 118 | |
|---|
| 119 | # ::rcs::decodeRcsPatch -- |
|---|
| 120 | # |
|---|
| 121 | # Converts a text containing a RCS patch (diff -n format) into a list |
|---|
| 122 | # of patch commands. Each element of the list is a list containing the |
|---|
| 123 | # patch command and its arguments, in this order. |
|---|
| 124 | # |
|---|
| 125 | # The valid patch commands are 'a' and 'd'. 'a' has two arguments, the |
|---|
| 126 | # index of the line where to add the text, and the text itself. The |
|---|
| 127 | # 'd' command has two arguments as well, the index of the first line |
|---|
| 128 | # to delete, and the number of lines to delete. |
|---|
| 129 | # |
|---|
| 130 | # Arguments |
|---|
| 131 | # - patch The text in diff -n format, the patch to parse. |
|---|
| 132 | # |
|---|
| 133 | # Results |
|---|
| 134 | # A list containing the patch as sequence of commands. |
|---|
| 135 | # |
|---|
| 136 | # Side effects |
|---|
| 137 | # None |
|---|
| 138 | |
|---|
| 139 | proc ::rcs::decodeRcsPatch {patch} { |
|---|
| 140 | set patch [split $patch \n] |
|---|
| 141 | set plen [llength $patch] |
|---|
| 142 | set at 0 |
|---|
| 143 | set res {} |
|---|
| 144 | |
|---|
| 145 | while {$at < $plen} { |
|---|
| 146 | # I use an index into the list to avoid shifting the list |
|---|
| 147 | # elements down with each line processed. That is a lot of |
|---|
| 148 | # memcpy's. |
|---|
| 149 | |
|---|
| 150 | set cmd [string trim [lindex $patch $at]] |
|---|
| 151 | incr at |
|---|
| 152 | |
|---|
| 153 | switch -glob -- $cmd { |
|---|
| 154 | "" {} |
|---|
| 155 | a* { |
|---|
| 156 | foreach {start len} [split [string range $cmd 1 end]] break |
|---|
| 157 | |
|---|
| 158 | set to [expr {$at + $len - 1}] |
|---|
| 159 | lappend res [list \ |
|---|
| 160 | a \ |
|---|
| 161 | $start \ |
|---|
| 162 | [join [lrange $patch $at $to] \n]] |
|---|
| 163 | incr to |
|---|
| 164 | set at $to |
|---|
| 165 | } |
|---|
| 166 | d* { |
|---|
| 167 | foreach {start len} [split [string range $cmd 1 end]] break |
|---|
| 168 | lappend res [list d $start $len] |
|---|
| 169 | } |
|---|
| 170 | default { |
|---|
| 171 | return -code error "Unknown patch command: '$cmd'" |
|---|
| 172 | } |
|---|
| 173 | } |
|---|
| 174 | } |
|---|
| 175 | |
|---|
| 176 | return $res |
|---|
| 177 | } |
|---|
| 178 | |
|---|
| 179 | # ::rcs::encodeRcsPatch -- |
|---|
| 180 | # |
|---|
| 181 | # Converts a list of patch commands into a text containing the same |
|---|
| 182 | # command as a RCS patch (i.e. in diff -n format). See decodePatch for |
|---|
| 183 | # a description of the input format. |
|---|
| 184 | # |
|---|
| 185 | # Arguments |
|---|
| 186 | # - patch The patch as list of patch commands. |
|---|
| 187 | # |
|---|
| 188 | # Results |
|---|
| 189 | # A text containing the patch in diff -n format. |
|---|
| 190 | # |
|---|
| 191 | # Side effects |
|---|
| 192 | # None |
|---|
| 193 | |
|---|
| 194 | proc ::rcs::encodeRcsPatch {patch} { |
|---|
| 195 | set res {} |
|---|
| 196 | |
|---|
| 197 | foreach cmd $patch { |
|---|
| 198 | foreach {op a b} $cmd break |
|---|
| 199 | |
|---|
| 200 | switch -exact -- $op { |
|---|
| 201 | a { |
|---|
| 202 | # a = index of line where to add |
|---|
| 203 | # b = text to add |
|---|
| 204 | |
|---|
| 205 | set lines [llength [split $b \n]] |
|---|
| 206 | |
|---|
| 207 | lappend res "a$a $lines" |
|---|
| 208 | lappend res $b |
|---|
| 209 | } |
|---|
| 210 | d { |
|---|
| 211 | # a = index of first line to delete. |
|---|
| 212 | # b = #lines to delete. |
|---|
| 213 | |
|---|
| 214 | lappend res "d$a $b" |
|---|
| 215 | } |
|---|
| 216 | default { |
|---|
| 217 | return -code error "Unknown patch command: '$op'" |
|---|
| 218 | } |
|---|
| 219 | } |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | return [join $res \n]\n |
|---|
| 223 | } |
|---|
| 224 | |
|---|
| 225 | # ::rcs::applyRcsPatch -- |
|---|
| 226 | # |
|---|
| 227 | # Apply a patch in the format returned by decodeRcsPatch to a text in |
|---|
| 228 | # the format returned by the xx2dict commands. The result is |
|---|
| 229 | # dictionary containing the modified text. Use the dict2xx commands to |
|---|
| 230 | # convert this back into a regular text. |
|---|
| 231 | # |
|---|
| 232 | # Arguments |
|---|
| 233 | # - text The text (as dict) to patch |
|---|
| 234 | # - patch The patch (as cmd list) to apply. |
|---|
| 235 | # |
|---|
| 236 | # Results |
|---|
| 237 | # The modified text (as dict) |
|---|
| 238 | # |
|---|
| 239 | # Side effects |
|---|
| 240 | # None |
|---|
| 241 | |
|---|
| 242 | proc ::rcs::applyRcsPatch {text patch} { |
|---|
| 243 | array set lines $text |
|---|
| 244 | |
|---|
| 245 | foreach cmd $patch { |
|---|
| 246 | foreach {op a b} $cmd break |
|---|
| 247 | |
|---|
| 248 | switch -exact -- $op { |
|---|
| 249 | a { |
|---|
| 250 | # a = index of line where to add |
|---|
| 251 | # b = text to add |
|---|
| 252 | |
|---|
| 253 | if {[info exists lines($a)]} { |
|---|
| 254 | append lines($a) \n $b |
|---|
| 255 | } else { |
|---|
| 256 | set lines($a) $b |
|---|
| 257 | } |
|---|
| 258 | } |
|---|
| 259 | d { |
|---|
| 260 | # a = index of first line to delete. |
|---|
| 261 | # b = #lines to delete. |
|---|
| 262 | |
|---|
| 263 | while {$b > 0} { |
|---|
| 264 | unset lines($a) |
|---|
| 265 | incr a |
|---|
| 266 | incr b -1 |
|---|
| 267 | } |
|---|
| 268 | } |
|---|
| 269 | default { |
|---|
| 270 | return -code error "Unknown patch command: '$op'" |
|---|
| 271 | } |
|---|
| 272 | } |
|---|
| 273 | } |
|---|
| 274 | |
|---|
| 275 | return [array get lines] |
|---|
| 276 | } |
|---|
| 277 | |
|---|
| 278 | # ### ### ### ######### ######### ######### |
|---|
| 279 | ## Ready for use. |
|---|
| 280 | |
|---|
| 281 | package provide rcs 0.1 |
|---|