;+
; 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 section_marker name of section
; @field line_nums pointer to an array of indicies locating each line in file (line #s zero based)
; @field all_lines pointer to an array holding all lines in this
; section - not normally used except during testing. Derived classes
; should NOT rely on this.
; @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)), $
section_marker: string(replicate(32B,6)), $ ; [section_marker]
line_nums:ptr_new(), $ ; line numbers zero-based
all_lines:ptr_new(), $ ; only used during test
section_read:0L, $
allow_append:0L, $
pad_width:0L, $
debug:0L $
}
END
;+
; Class Constructor
; @private
;-
FUNCTION INDEX_FILE_SECTION::init, section, filename
compile_opt idl2, hidden
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.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.all_lines) then ptr_free,self.all_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, not_in_memory=not_in_memory
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 now determin this
;openu, lun, self.filename, /append
openu,lun, self.filename, /get_lun
tmp = ''
num_lines=0
while eof(lun) eq 0 do begin
readf, lun, tmp
num_lines = num_lines+1
endwhile
start_line_number = num_lines
endif else begin
; open file and position pointer for writing
openu, lun, self.filename, /get_lun
self->go_to_line, start_line_number, lun
endelse
inMemory = 1
if keyword_set(not_in_memory) then inMemory=0
printf, 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, lun, line
endfor
if inMemory then begin
; get memory in sync with lines written
self.num_lines = n_elements(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 = lindgen(self.num_lines)+start_line_number+1
endif else begin
*self.line_nums = lindgen(self.num_lines)+1
endelse
endif
endif
; this will also close this lun
free_lun, lun
self.section_read = 1
END
;+
; Adds extra blank characthers to end of given line, if this line is less
; then the max line width.
;
; @param line {in}{required}{type=string} line to pad
; @returns string with added blank chars to make it the max line width
;-
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
;+
; Tests the validity of the pointer to array of lines in section
; @returns 0 - not valid, 1 -valid
;-
FUNCTION INDEX_FILE_SECTION::has_valid_lines
compile_opt idl2, hidden
if not ptr_valid(self.line_nums) then begin
return, 0
endif else begin
if n_elements(*self.line_nums) eq 0 then begin
return, 0
endif else begin
return, 1
endelse
endelse
end
;+
; Tests the validity of the pointer to array of line numbers in section
; @returns 0 - not valid, 1 -valid
;-
FUNCTION INDEX_FILE_SECTION::has_valid_line_numbers
compile_opt idl2, hidden
if not ptr_valid(self.line_nums) then begin
return, 0
endif else begin
if n_elements(*self.line_nums) eq 0 then begin
return, 0
endif else begin
return, 1
endelse
endelse
end
;+
; Retrieves the location of each section line in the file
; @param {count}{optional}{type=long} number of line_nums
; @returns integer array which is location of each section line in file
;-
FUNCTION INDEX_FILE_SECTION::get_line_nums, count
compile_opt idl2, hidden
if ptr_valid(self.line_nums) then begin
count = n_elements(*self.line_nums)
if count gt 0 then begin
return, *self.line_nums
endif else begin
return, -1
endelse
endif else begin
count = 0
return, -1
endelse
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
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
;+
; Counts lines in the section, not including the section line itself
; @returns -1 on failure
;-
FUNCTION INDEX_FILE_SECTION::count_section_lines
compile_opt idl2, hidden
count = -1L
openr, lun, self.filename, /get_lun
done = 0
reading_section = 0
line = ""
while (eof(lun) ne 1) and (done eq 0) do begin
readf,lun,line
first_char = strmid(line,0,1)
if (first_char eq '#') or (strlen(line) eq 0) then begin
; ignore comments and blank lines
endif else begin
if (first_char eq '[') then begin
; section marker
if reading_section then begin
; must be done reading this section
done = 1
endif else begin
current_section = strmid(line,1,(strlen(line)-2))
if current_section eq self.section_marker then begin
reading_section = 1
count = 0L
endif
endelse
endif else begin
if reading_section then begin
count = count + 1L
if count lt 0 then begin
message,'Number of rows in section exceeds maximum long integer, can not continue'
endif
endif
endelse
endelse
endwhile
; free_lun also closes it
free_lun, lun
return, count
END
;+
; Retrieves array of lines in section; commented out lines are not
; included. This only works for testing and it should be replaced by
; the suitable version for any derived class as necessary.
; @param count {out}{optional}{type=long} number of lines returned
; @returns array of lines in section, -1 if no lines
;-
FUNCTION INDEX_FILE_SECTION::get_lines, count
compile_opt idl2, hidden
if ptr_valid(self.all_lines) then begin
count = n_elements(*self.all_lines)
if count eq 0 then begin
return, -1
endif else begin
return, *self.all_lines
endelse
endif else begin
count = 0
return, -1
endelse
END
;+
; Procedure to process each line. Invoked by
; index_file_section::read_file. This is a dummy version that must be
; replaced by any derived class wanting to make use of read_file.
; @param line {in}{required}{type=string} The line to handle.
; @param index {in}{required}{type=integer} The index number for this line
;-
PRO INDEX_FILE_SECTION::process_line, line, index
compile_opt idl2, hidden
if ptr_valid(self.all_lines) eq 0 then begin
nlines = self->count_section_lines()
self.all_lines = ptr_new(strarr(nlines))
endif
(*self.all_lines)[index] = line
END
;+
; Reads the file, locates the section, and loads all lines and metainfo
; into objects memory
; @keyword max_nrows {in}{optional}{type=long} Maximum number of rows
; to read.
; @returns 0 - failure, 1 - success
;-
FUNCTION INDEX_FILE_SECTION::read_file, max_nrows = max_nrows
compile_opt idl2, hidden
result = 0
self.section_read = 0
; reset pointers
if ptr_valid(self.line_nums) then ptr_free, self.line_nums
self.line_nums = ptr_new(/allocate_heap)
self.num_lines = 0
maxToRead = -1
if keyword_set(max_nrows) then maxToRead = max_nrows
openr, lun, self.filename, /get_lun
num_lines_read = 0L
line_nums = lonarr(self.lines_incr)
line_num = 0L
current_section = 'None'
line = ''
done = 0
reading_section = 0
while (eof(lun) ne 1) and (done eq 0) do begin
readf, lun, line
if self.debug then print, "read line from file: ", 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(line_nums) then begin
; add in another increment
line_nums = [line_nums,lonarr(self.lines_incr)]
endif
self->process_line, line, num_lines_read
line_nums[num_lines_read]=line_num
num_lines_read = num_lines_read + 1L
if maxToRead gt 0 and num_lines_read ge maxToRead then break
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
; free_lun also closes it
free_lun, lun
; free unused portions of arrays
if (num_lines_read gt 0) then begin
line_nums = line_nums[0:(num_lines_read-1)]
endif
if (num_lines_read eq 0) then begin
; message, 'no lines found in section'
return, 0
endif
*self.line_nums = line_nums
self.num_lines = n_elements(*self.line_nums)
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)
; @param lun {in}{required}{type=integer} Already opened file unit number
;
; @private
;-
PRO INDEX_FILE_SECTION::go_to_line, line_number, lun
compile_opt idl2, hidden
line=''
next_line = 0L
while (next_line lt line_number) do begin
if (EOF(lun) ne 1) then begin
next_line = next_line+1L
readf, 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
;+
; Retrurns the index associated with the given location in
; the file, the line number.
;
; @param location {in}{required}{type=long} the file line number
;
; @returns the index number associated with that location
;-
FUNCTION INDEX_FILE_SECTION::get_index_by_location, location
compile_opt idl2, hidden
ind = where(*self.line_nums eq location, count)
if count ne 1 then return, -1
return, ind
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 not self->has_valid_line_numbers() then return
if not self->has_valid_lines() then return
if n_elements(line_index) eq 0 then begin
line_index = where(line_number[0] eq *self.line_nums, cnt)
if cnt ne 1 then message, "line number is not uniquely found in section: "+string(line_number)
endif
openu, lun, self.filename, /get_lun
self->go_to_line, line_number, lun
if self.pad_width then line = self->pad_line(line)
printf, lun, line
; free_lun also closes it
free_lun, lun
END
;+
; Uses the line numbers in memory to return what the last
; line number is for the file
; @returns the last line number in memory for this file
;-
FUNCTION INDEX_FILE_SECTION::get_current_file_length
compile_opt idl2, hidden
if not self->has_valid_line_numbers() then return, -1
return, (*self.line_nums)[self.num_lines-1]
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"
if self.num_lines eq 0 then begin
; read the whole file to the number of lines
openr, lun, self.filename, /get_lun
num_lines = 0
line = ""
while (eof(lun) ne 1) do begin
readf, lun, line
num_lines += 1
endwhile
; free_lun also closes it
free_lun, lun
starting_line_num = num_lines
endif else begin
if not self->has_valid_lines() then message, "no valid lines in file to append to."
starting_line_num = self->get_current_file_length() + 1
endelse
openu, lun, self.filename, /append, /get_lun
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)
len = self.num_lines-1
if len ge 0 then begin
new_line_nums[0:len] = *self.line_nums
endif
; 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 = long(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, lun, file_line
; keep this object in sync with the file
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.line_nums = new_line_nums
self.num_lines = n_elements(*self.line_nums)
; free_lun also closes the file
free_lun, 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
;+
; Set this if the section can be appended to
;-
PRO INDEX_FILE_SECTION::set_allow_append_on
compile_opt idl2
self.allow_append = 1
END
;+
; Print out useful debugging information
;-
PRO INDEX_FILE_SECTION::show_state
compile_opt idl2
print,"INDEX_FILE_SECTION state"
print," filename : ", self.filename
print," num_lines : ", self.num_lines
if self->has_valid_lines() then begin
print," line_nums ; ", (*self.line_nums)
endif else begin
print," no valid line_nums"
endelse
print,"END INDEX_FILE_SECTION state"
END