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
16package require Tcl 8.4
17
18# ### ### ### ######### ######### #########
19## API Implementation
20
21namespace 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
38proc ::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
62proc ::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
86proc ::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
113proc ::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
139proc ::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
194proc ::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
242proc ::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
281package provide rcs 0.1
282