;+
; This is the base class for managing a section in an index file. Sections are
; started with a line: '[section_name]'. This class manages all basic i/o functions
; for a section.
; See UML for all IO Classes
;
; @file_comments
; This is the base class for managing a section in an index file. Sections are
; started with a line: '[section_name]'. This class manages all basic i/o functions
; for a section.
; See UML for all IO Classes
;
; @field lines_incr number of lines that buffers grow by
; internally as necessary. Placed here for consistency only.
; @field max_line_width maximum number of chars allowed on each line
; @field num_lines the current number of lines in this section
; @field filename full pathname to the file where this section resides
; @field lun logical unit number for file
; @field section_marker name of section
; @field line_nums pointer to an array of indicies locating each line in file (line #s zero based)
; @field lines pointer to an array holding all lines in this section
; @field section_read boolean flag for wether section is in memory or not
; @field allow_append boolean flag for wether this section allows appends
; @field pad_width boolean flag determines wether lines are padded to max width
; @field debug boolean flag for wether this class is verbose or not
;
; @private_file
;-
PRO index_file_section__define
ifs = { INDEX_FILE_SECTION, $
lines_incr:0L, $
max_line_width:0L, $
num_lines:0L, $ ; does not include section marker
filename: string(replicate(32B,256)), $
lun:0L, $
section_marker: string(replicate(32B,6)), $ ; [section_marker]
line_nums:ptr_new(), $ ; line numbers zero-based
lines:ptr_new(), $ ; does not include section marker
section_read:0L, $
allow_append:0L, $
pad_width:0L, $
debug:0L $
}
END
;+
; Class Constructor
; @private
;-
FUNCTION INDEX_FILE_SECTION::init, lun, section, filename
compile_opt idl2, hidden
self.lun = lun
self.section_marker = section
if n_elements(filename) ne 0 then self.filename = filename
self.lines_incr = 100000L
self.max_line_width = 256L
self.line_nums = ptr_new(/allocate_heap)
self.lines = ptr_new(/allocate_heap)
self.debug = 0
return, 1
END
;+
; Class Destructor
; @private
;-
PRO INDEX_FILE_SECTION::cleanup
compile_opt idl2, hidden
if ptr_valid(self.line_nums) then ptr_free,self.line_nums
if ptr_valid(self.lines) then ptr_free,self.lines
END
;+
; Sets file name of file to be written to
; @param file_name {in}{type=string} name of index file
;-
PRO INDEX_FILE_SECTION::set_file_name, file_name
compile_opt idl2, hidden
self.filename = file_name
END
;+
; Sets object to pad lines written with spaces to their max width
;-
PRO INDEX_FILE_SECTION::pad_width_on
compile_opt idl2, hidden
self.pad_width = 1
END
;+
; Sets object to NOT pad lines written with spaces to their max width
;-
PRO INDEX_FILE_SECTION::pad_width_off
compile_opt idl2, hidden
self.pad_width = 0
END
;+
; Creates the section in the file.
;
; @param start_line_number {in}{optional}{type=long} what line to start section on in file
; @keyword lines {in}{optional}{type=string array} array of lines to be written in section upon creation (does not include section marker).
; @keyword append {in}{optional}{type=bool} exclusive with start_line_number: if set, lines get written at end of section
;
;-
PRO INDEX_FILE_SECTION::create, start_line_number, lines=lines, append=append
compile_opt idl2, hidden
if keyword_set(append) then begin
; we need to know how many lines currently in the file,
; HACK HACK HACK: for know determin this
;openu, self.lun, self.filename, /append
openu, self.lun, self.filename
tmp = ''
num_lines=0
while eof(self.lun) eq 0 do begin
readf, self.lun, tmp
num_lines = num_lines+1
endwhile
start_line_number = num_lines
endif else begin
; open file and position pointer for writing
openu, self.lun, self.filename
self->go_to_line, start_line_number
endelse
printf, self.lun, '['+self.section_marker+']'
if keyword_set(lines) then begin
; print them to the file
for i=0,n_elements(lines)-1 do begin
line = lines[i]
if self.pad_width then line = self->pad_line(line)
printf, self.lun, line
endfor
; get memory in sync with lines written
self.num_lines = n_elements(lines)
*self.lines = lines
; take into account the section marker written above when
; calculating the line numbers for each line
if n_elements(start_line_number) ne 0 then begin
*self.line_nums = indgen(self.num_lines)+start_line_number+1
endif else begin
*self.line_nums = indgen(self.num_lines)+1
endelse
endif
close, self.lun
self.section_read = 1
END
FUNCTION INDEX_FILE_SECTION::pad_line, line
compile_opt idl2, hidden
if strlen(line) lt self.max_line_width then begin
dif = self.max_line_width - strlen(line)
line = line + string(replicate(32B,dif))
endif
return, line
END
;+
; Retrieves array of lines in section
; @returns array of lines in section
;-
FUNCTION INDEX_FILE_SECTION::get_lines
compile_opt idl2, hidden
return, *self.lines
END
;+
; Retrieves the location of each section line in the file
; @returns integer array which is locatio of each section line in file
;-
FUNCTION INDEX_FILE_SECTION::get_line_nums
compile_opt idl2, hidden
return, *self.line_nums
END
;+
; Retrieves the number of lines in this section
; @returns long integer - number of lines in the section
;-
FUNCTION INDEX_FILE_SECTION::get_num_lines
compile_opt idl2, hidden
return, self.num_lines
END
;+
; Increments the number of lines this class believes are in the section.
; @private
;-
PRO INDEX_FILE_SECTION::increment_num_lines
compile_opt idl2, hidden
self.num_lines = self.num_lines + 1L
END
;+
; Reads the file, locates the section, and loads all lines and metainfo
; into objects memory
; @returns 0 - failure, 1 - success
;-
FUNCTION INDEX_FILE_SECTION::read_file
compile_opt idl2, hidden
result = 0
self.section_read = 0
openr, self.lun, self.filename
num_lines_read = 0L
line_nums = lonarr(self.lines_incr)
line_num = 0L
;lines = strarr(string(replicate(32B,256),self.max_rows)
lines = strarr(self.lines_incr)
current_section = 'None'
line = ''
done = 0
reading_section = 0
while (eof(self.lun) ne 1) and (done eq 0) do begin
readf, self.lun, line
first_char = strmid(line,0,1)
if (first_char eq '#') or (strlen(line) eq 0) then begin
; nothing to do with comments or blank lines
endif else begin
if (first_char eq '[') then begin
; begining new section
if reading_section then begin
; if we've been reading a section, then the start of a new
; one means that there's nothing more for us to read
done = 1
endif else begin
current_section = strmid(line,1,(strlen(line)-2))
if current_section eq self.section_marker then reading_section = 1
endelse
endif else begin
; we're in a section, is it the right one?
if reading_section then begin
; store this line and it's location in the file
if self.pad_width then line = strtrim(line, 2)
if num_lines_read ge n_elements(lines) then begin
; add in another increment to these arrays
lines = [lines,strarr(self.lines_incr)]
line_nums = [line_nums,lonarr(self.lines_incr)]
endif
lines[num_lines_read] = line
line_nums[num_lines_read]=line_num
num_lines_read = num_lines_read + 1L
if (num_lines_read lt 0) then begin
; must be too large for long integer if this happens
message,'number of rows in section exceeds maximum long integer, can not continue'
endif
endif ; if we're in our section
endelse ; if we're starting new section
endelse ; if anything but a comment line
line_num = line_num + 1L
endwhile
close, self.lun
; free unused portions of arrays
if (num_lines_read gt 0) then begin
lines = lines[0:(num_lines_read-1)]
line_nums = line_nums[0:(num_lines_read-1)]
endif
*self.line_nums = line_nums
*self.lines = lines
self.num_lines = n_elements(*self.lines)
if (num_lines_read eq 0) then begin
message, 'no lines found in section'
return, 0
endif
self.section_read = 1
return, 1
END
;+
; Advances file pointer to right before the line number parameter. To be used
; to write to a specific line in file. File must be opened beforehand and closed
; after call. This is something that idl should provide.
;
; @param line_number {in}{required}{type=long} line number to go to (0-based)
;
; @private
;-
PRO INDEX_FILE_SECTION::go_to_line, line_number
compile_opt idl2, hidden
line=''
next_line = 0L
while (next_line lt line_number) do begin
if (EOF(self.lun) ne 1) then begin
next_line = next_line+1L
readf, self.lun, line
endif else begin
; we hit the end of file before we got to our line_number
message, 'line number exceeds number of rows in file: '+string(line_number)
endelse
endwhile
END
;+
; Replaces a line already in the index with a new string.
;
; @param line_number {in}{required}{type=long} the file line number (first line,
; second line, etc. ) to replace.
; @param line {in}{required}{type=string} new line to place in section
; @param line_index {out}{optional}{type=long} index in array of lines for this section
; specified by line_number
;
;-
PRO INDEX_FILE_SECTION::set_line, line_number, line, line_index
compile_opt idl2, hidden
if n_elements(line_index) eq 0 then begin
line_index = where(line_number[0] eq *self.line_nums, cnt)
if line_index eq -1 then message, "line number is not in section: "+string(line_number)
endif
openu, self.lun, self.filename
self->go_to_line, line_number
; edit both the file and our memory to keep them 'nsync
(*self.lines)[line_index] = line
if self.pad_width then line = self->pad_line(line)
printf, self.lun, line
close, self.lun
END
;+
; Finds the file line number of a given string. If line not found, or
; line is found multiple times, error is raised.
;
; @param line {in}{required}{type=string} line that is searched for in section
; @param line_index {out}{optional}{type=long} the index at which this line
; is found in the array of section lines
;
; @returns the file line number where the param line is found
;-
FUNCTION INDEX_FILE_SECTION::get_line_location, line, line_index
compile_opt idl2, hidden
count = 0
ind = where(*self.lines eq line, count)
if count gt 1 then message, "line found more then once in section: "+line
if ind eq -1 then message, "line not found in read section: "+line
if n_elements(line_index) ne 0 then line_index = ind
return, (*self.line_nums)[ind]
END
;+
; Retrieves the index of the param line in the array of section lines: in other words,
; tells wether this is the first, second, etc. line in this section
;
; @param line {in}{required}{type=string} line to be searched for in array of section lines
;
; @returns the index of the param line in the array of section lines.
;-
FUNCTION INDEX_FILE_SECTION::get_line_index, line
compile_opt idl2, hidden
count = 0
ind = where(*self.lines eq line, count)
if ind eq -1 then message, "line not found in read section: ", line
if count lt 1 then message, "line found more then once in section: ", line
return, ind
END
;+
; Replaces a line in the section with a new one.
;
; @param old_line {in}{required}{type=string} identifies the string to replace.
; must be unique.
; @param new_line {in}{required}{type=string} the new line to replace the old one
;
; @uses get_line_location
;
;-
PRO INDEX_FILE_SECTION::rewrite_line, old_line, new_line
compile_opt idl2, hidden
; retrive the location of this line
line_index = -1
line_number = self->get_line_location(old_line, line_index)
; rewrite it!
self->set_line, line_number, new_line, line_index
END
;+
; Replaces the line passed in with a blank in the index file, and removes
; this line from the objects memory.
;
; @param line_str {in}{required}{type=string} line to be deleted. Must be unique.
;
; @uses get_line_index
; @uses rewrite_line
;
;-
PRO INDEX_FILE_SECTION::delete_line, line_str
compile_opt idl2, hidden
ind = self->get_line_index(line_str)
; replace this line in the index file with a blank
self->rewrite_line, line_str, string(replicate(32B,strlen(line_str)))
; now remove this from the array of lines
curr_size = self->get_num_lines()
new_lines = strarr(curr_size-1)
new_line_nums = lonarr(curr_size-1)
new_line_ind=0
for i=0L,(curr_size-1) do begin
if ind ne i then begin
new_lines[new_line_ind] = (*self.lines)[i]
new_line_nums[new_line_ind] = (*self.line_nums)[i]
new_line_ind = new_line_ind + 1L
endif
endfor
*self.lines = new_lines
*self.line_nums = new_line_nums
self.num_lines = n_elements(*self.lines)
END
;+
; Appends lines to end of section (if allowed). Keeps objects memory in sync with
; section
;
; @param lines {in}{required}{type=string array} lines to append to section
;
;-
PRO INDEX_FILE_SECTION::append_lines, lines
compile_opt idl2, hidden
if self.allow_append eq 0 then message, "this section does not allow appending lines"
openu, self.lun, self.filename, /append
starting_line_num = (*self.line_nums)[self.num_lines-1]
final_num_lines = self.num_lines+n_elements(lines)
if final_num_lines lt 0 then begin
message,'Number of rows in index file section exceeds largest long integer, can not continue'
endif
new_line_nums = lonarr(final_num_lines)
new_lines = strarr(final_num_lines)
for i=0,self.num_lines-1 do begin
new_line_nums[i] = (*self.line_nums)[i]
new_lines[i] = (*self.lines)[i]
endfor
; if sufficiently large, use a progress bar
if n_elements(lines) gt 1000 then progress_bar=1 else progress_bar=0
if progress_bar then begin
total_bar = '__________'
step_size = fix(n_elements(lines)/10)
step = 0
print, "Writing rows to index file:"
print, total_bar
endif
for i=0,n_elements(lines)-1 do begin
; write new lines to file
if self.pad_width then file_line=self->pad_line(lines[i]) else file_line=lines[i]
printf, self.lun, file_line
; keep this object in sync with the file
new_lines[self.num_lines+i] = lines[i]
new_line_nums[self.num_lines+i] = starting_line_num+i
; update the progress bar
if progress_bar then begin
if step eq step_size then begin
step = 0
print, format='("X",$)'
endif else begin
step += 1
endelse
endif
endfor
; terminate progress bar
if progress_bar then print, format='(/)'
*self.lines = new_lines
*self.line_nums = new_line_nums
self.num_lines = n_elements(*self.lines)
close, self.lun
END
;+
; Makes object verbose
;-
PRO INDEX_FILE_SECTION::set_debug_on
compile_opt idl2
self.debug = 1
END
;+
; Makes object quiet
;-
PRO INDEX_FILE_SECTION::set_debug_off
compile_opt idl2
self.debug = 0
END
;+
; Has the section been read?
; @returns 0 - no, 1 - yes
;-
FUNCTION INDEX_FILE_SECTION::is_section_read
compile_opt idl2
return, self.section_read
END