Initial import
This commit is contained in:
21
info.rkt
Normal file
21
info.rkt
Normal file
@@ -0,0 +1,21 @@
|
||||
#lang info
|
||||
|
||||
(define pkg-authors '(hnmdijkema))
|
||||
(define version "0.1.0")
|
||||
(define license 'Apache-2.0)
|
||||
(define collection "simple-ini")
|
||||
(define pkg-desc "A Simple .ini file reader/writer for racket")
|
||||
|
||||
(define scribblings
|
||||
'(
|
||||
("scribblings/ini.scrbl" () (library) "simple-ini")
|
||||
)
|
||||
)
|
||||
|
||||
(define deps
|
||||
'("base" "roos"))
|
||||
|
||||
(define build-deps
|
||||
'("racket-doc"
|
||||
"rackunit-lib"
|
||||
"scribble-lib"))
|
||||
146
main.rkt
Normal file
146
main.rkt
Normal file
@@ -0,0 +1,146 @@
|
||||
#lang racket/base
|
||||
|
||||
(require racket/string)
|
||||
(require racket/file)
|
||||
|
||||
(provide file->ini
|
||||
ini->file
|
||||
ini-get
|
||||
ini-set!
|
||||
make-ini
|
||||
)
|
||||
|
||||
(define (ini->file ini file)
|
||||
(let ((out (open-output-file file #:exists 'replace)))
|
||||
(let ((last-is-newline #f))
|
||||
(for-each (lambda (section)
|
||||
(let ((section-name (car section)))
|
||||
(if (eq? section-name 'nil)
|
||||
#t
|
||||
(begin
|
||||
(unless last-is-newline (newline out))
|
||||
(display "[" out)
|
||||
(display section-name out)
|
||||
(display "]" out)
|
||||
(newline out)))
|
||||
(let ((lines (cdr section)))
|
||||
(for-each (lambda (line)
|
||||
(if (eq? (car line) 'comment)
|
||||
(begin
|
||||
(set! last-is-newline #f)
|
||||
(display "; " out)
|
||||
(display (cadr line) out)
|
||||
(newline out))
|
||||
(if (eq? (car line) 'empty)
|
||||
(begin
|
||||
(newline out)
|
||||
(set! last-is-newline #t))
|
||||
(if (eq? (car line) 'keyval)
|
||||
(begin
|
||||
(set! last-is-newline #f)
|
||||
(display (cadr line) out)
|
||||
(display "=" out)
|
||||
(display (caddr line) out)
|
||||
(newline out))
|
||||
(error "Unknown line format")))))
|
||||
lines))))
|
||||
(mcdr ini)))
|
||||
(close-output-port out)))
|
||||
|
||||
(define (make-ini)
|
||||
(mcons 'ini (list)))
|
||||
|
||||
(define (interpret s)
|
||||
(let ((re-num #px"^([+]|[-])?([0-9]+([.]([0-9]+))?)$")
|
||||
(re-bool #px"^(#f|#t|true|false)$"))
|
||||
(let ((ss (string-downcase (string-trim s))))
|
||||
(let ((m-num (regexp-match re-num ss)))
|
||||
(if m-num
|
||||
(string->number (car m-num))
|
||||
(let ((m-b (regexp-match re-bool ss)))
|
||||
(if m-b
|
||||
(if (or (string=? ss "#t") (string=? ss "true"))
|
||||
#t
|
||||
#f)
|
||||
s)))))))
|
||||
|
||||
(define (file->ini file)
|
||||
(let* ((lines (file->lines file))
|
||||
(re-section #px"^\\[([a-zA-Z0-9_-]+)\\]$")
|
||||
(re-keyval #px"^([a-zA-Z0-9_-]+)[=](.*)$")
|
||||
(re-comment #px"^[;](.*)$"))
|
||||
(letrec ((f (lambda (sections section lines)
|
||||
(if (null? lines)
|
||||
(append sections (list section))
|
||||
(let* ((line (string-trim (car lines)))
|
||||
(empty (string=? line ""))
|
||||
(m-comment (regexp-match re-comment line))
|
||||
(m-keyval (regexp-match re-keyval line))
|
||||
(m-section (regexp-match re-section line)))
|
||||
(if empty
|
||||
(f sections (append section (list (list 'empty))) (cdr lines))
|
||||
(if m-comment
|
||||
(f sections (append section (list (list 'comment (cadr m-comment)))) (cdr lines))
|
||||
(if m-keyval
|
||||
(f sections (append section
|
||||
(list
|
||||
(list 'keyval (string->symbol
|
||||
(string-trim (string-downcase
|
||||
(cadr m-keyval))))
|
||||
(interpret (string-trim (caddr m-keyval)))))) (cdr lines))
|
||||
(if m-section
|
||||
(f (append sections (list section)) (list (string->symbol (string-trim (cadr m-section)))) (cdr lines))
|
||||
(error "Unknown INI line"))))))))))
|
||||
(mcons 'ini (f '() (list 'nil) lines)))))
|
||||
|
||||
(define (ini-get ini section key def-val)
|
||||
(letrec ((g (lambda (ini)
|
||||
(if (null? ini)
|
||||
def-val
|
||||
(if (eq? section (caar ini))
|
||||
(letrec ((f (lambda (l)
|
||||
(if (null? l)
|
||||
def-val
|
||||
(let ((entry (car l)))
|
||||
(if (eq? (car entry) 'keyval)
|
||||
(if (eq? (cadr entry) key)
|
||||
(caddr entry)
|
||||
(f (cdr l)))
|
||||
(f (cdr l))))))))
|
||||
(f (cdar ini)))
|
||||
(g (cdr ini)))))))
|
||||
(g (mcdr ini))))
|
||||
|
||||
(define (ini-set! ini section key val)
|
||||
(let ((found #f))
|
||||
(letrec ((for-sect (lambda (sect)
|
||||
(if (null? sect)
|
||||
(if found
|
||||
'()
|
||||
(begin
|
||||
(set! found #t)
|
||||
(list (list 'keyval key val))))
|
||||
(let ((entry (car sect)))
|
||||
(if (eq? (car entry) 'keyval)
|
||||
(if (eq? (cadr entry) key)
|
||||
(begin
|
||||
(set! found #t)
|
||||
(cons (list 'keyval key val) (for-sect (cdr sect))))
|
||||
(cons entry (for-sect (cdr sect))))
|
||||
(cons entry (for-sect (cdr sect)))))))))
|
||||
(letrec ((for-ini (lambda (ini)
|
||||
(if (null? ini)
|
||||
(if found
|
||||
'()
|
||||
(list (list section (list 'keyval key val))))
|
||||
(let* ((ini-section (car ini))
|
||||
(section-key (car ini-section)))
|
||||
(if (eq? section-key section)
|
||||
(cons (cons section (for-sect (cdr ini-section))) (for-ini (cdr ini)))
|
||||
(cons ini-section (for-ini (cdr ini)))))))))
|
||||
(let ((new-ini (for-ini (mcdr ini))))
|
||||
(set-mcdr! ini new-ini)
|
||||
ini)))))
|
||||
|
||||
|
||||
|
||||
58
roos.rkt
Normal file
58
roos.rkt
Normal file
@@ -0,0 +1,58 @@
|
||||
#lang racket/base
|
||||
|
||||
(require roos)
|
||||
(require "main.rkt")
|
||||
|
||||
(provide ini
|
||||
(all-from-out roos))
|
||||
|
||||
(define-syntax i-set!
|
||||
(syntax-rules ()
|
||||
((_ a b)
|
||||
(set! a b))))
|
||||
|
||||
(def-roos (ini . _file) this (supers)
|
||||
(file* (if (null? _file) #f (car _file)))
|
||||
(content (if (not (eq? file* #f))
|
||||
(if (file-exists? file*)
|
||||
(file->ini file*)
|
||||
(make-ini))
|
||||
(make-ini)))
|
||||
(fail #f)
|
||||
|
||||
((file) file*)
|
||||
((file! f) (i-set! file* f) (-> this reload))
|
||||
|
||||
((reload) (i-set! content
|
||||
(if (not (eq? file* #f))
|
||||
(if (file-exists? file*)
|
||||
(file->ini file*)
|
||||
(make-ini))
|
||||
(make-ini))))
|
||||
|
||||
((set! s k v)
|
||||
(begin
|
||||
(ini-set! content s k v)
|
||||
(if (eq? file* #f)
|
||||
(if fail
|
||||
(error "ini: No filename set, cannot write ini file after set!")
|
||||
#f)
|
||||
(ini->file content file*))
|
||||
this))
|
||||
|
||||
((get s k . def)
|
||||
(let ((def-val (if (null? def) '@@no-value@@ (car def))))
|
||||
(let ((r (ini-get content s k def-val)))
|
||||
(if fail
|
||||
(if (eq? r '@@no-value@@)
|
||||
(error (string-append
|
||||
"ini: No default value set and no value found for section '"
|
||||
(symbol->string s)
|
||||
"' and key '"
|
||||
(symbol->string k)
|
||||
"'"))
|
||||
r)
|
||||
(if (eq? r '@@no-value@@)
|
||||
#f
|
||||
r)))))
|
||||
)
|
||||
128
scribblings/ini.scrbl
Normal file
128
scribblings/ini.scrbl
Normal file
@@ -0,0 +1,128 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require
|
||||
scribble/example
|
||||
(for-label racket/base
|
||||
racket/string
|
||||
racket/file))
|
||||
|
||||
@;(define myeval
|
||||
; (make-base-eval '(require simple-ini)))
|
||||
|
||||
@title[#:tag "ini-parser"]{INI File Parser and Writer}
|
||||
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[simple-ini]{This module provides simple facilities for reading and writing INI-style configuration files, allowing interpretation of numeric and boolean values, and modification of the parsed structure.}
|
||||
|
||||
@section{Creating and Parsing INI Files}
|
||||
|
||||
@defproc[(make-ini) mc-pair?]{
|
||||
Creates a new empty INI structure as a mutable cons pair. This is the base structure for manipulating INI contents.
|
||||
}
|
||||
|
||||
@defproc[(file->ini [file path-string?]) mc-pair?]{
|
||||
Reads an INI file from disk and parses it into an internal mutable cons pair (mc-pair) structure.
|
||||
|
||||
The parser supports:
|
||||
|
||||
@itemlist[
|
||||
@item{Sections (e.g., @tt{[section-name]})}
|
||||
@item{Key-value pairs (e.g., @tt{key=value})}
|
||||
@item{Comments (lines starting with @tt{;})}
|
||||
@item{Empty lines}
|
||||
]
|
||||
|
||||
The keys are stored as symbols, and values are automatically interpreted as:
|
||||
@itemlist[
|
||||
@item{Numbers, if the value matches a number pattern}
|
||||
@item{Booleans, if the value is @tt{#t}, @tt{true}, @tt{#f}, or @tt{false} (case-insensitive)}
|
||||
@item{Otherwise, as strings}
|
||||
]
|
||||
}
|
||||
|
||||
@defproc[(ini->file [ini mc-pair?] [file path-string?]) void?]{
|
||||
Writes an INI structure (as produced by @racket[file->ini] or @racket[make-ini]) to the specified file.
|
||||
|
||||
The output preserves:
|
||||
@itemlist[
|
||||
@item{Section headers}
|
||||
@item{Key-value pairs}
|
||||
@item{Comments (prefixed with @tt{;})}
|
||||
@item{Empty lines}
|
||||
]
|
||||
}
|
||||
|
||||
@section{Accessing and Modifying Values}
|
||||
|
||||
@defproc[(ini-get [ini mc-pair?]
|
||||
[section symbol?]
|
||||
[key symbol?]
|
||||
[def-val any/c])
|
||||
any/c]{
|
||||
Retrieves the value associated with the given @racket[key] in the specified @racket[section] of the INI structure. If the key is not found, returns @racket[def-val].
|
||||
}
|
||||
|
||||
@defproc[(ini-set! [ini mc-pair?]
|
||||
[section symbol?]
|
||||
[key symbol?]
|
||||
[val any/c])
|
||||
mc-pair?]{
|
||||
Sets the value of @racket[key] in the specified @racket[section] of the INI structure. If the section or key does not exist, it is created. Returns the modified INI structure.
|
||||
}
|
||||
|
||||
@section{The @racket[ini] Roos Class}
|
||||
|
||||
@defmodule[simple-ini/roos]{Require this module for the OO implementation of this Simple INI implementation}
|
||||
|
||||
@racket[(def-roos ini . file)]{
|
||||
A Roos class that provides object-oriented access to INI files using the underlying @racket[file->ini] parser system. The class offers methods to load, query, and update INI files using familiar object-style interactions.
|
||||
|
||||
@defproc[
|
||||
(ini [file (or/c path-string? #f)] ...)
|
||||
]{
|
||||
Creates an @racket[ini] object. If a @racket[file] path is provided and the file exists, it is loaded immediately. Otherwise, an empty INI structure is created.
|
||||
|
||||
If no file is provided, the object operates in-memory only. Subsequent @racket[set!] operations will raise an error unless a file is later specified with @racket[(file!)].
|
||||
}
|
||||
|
||||
@defmethod[(file) (or/c path-string? #f)]{
|
||||
Returns the current filename associated with this INI object.
|
||||
}
|
||||
|
||||
@defmethod[(file! [f path-string?]) void?]{
|
||||
Sets the file to use as backing storage for the INI structure. Triggers a reload from disk.
|
||||
}
|
||||
|
||||
@defmethod[(reload) void?]{
|
||||
Reloads the INI content from disk, using the current file path.
|
||||
If the file does not exist, the content is reset to an empty INI structure.
|
||||
}
|
||||
|
||||
@defmethod[(set! [section symbol?] [key symbol?] [val any/c]) ini]{
|
||||
Sets the value in the INI structure for the given @racket[section] and @racket[key] to @racket[val].
|
||||
|
||||
If a file is associated with the object, the structure is saved to disk immediately.
|
||||
If no file is set and @racket[fail] is enabled, an error is raised.
|
||||
Returns the INI object itself.
|
||||
}
|
||||
|
||||
@defmethod[(get [section symbol?] [key symbol?] [def-val any/c] ...) any/c]{
|
||||
Retrieves the value associated with the given @racket[section] and @racket[key].
|
||||
|
||||
If not found:
|
||||
@itemlist[
|
||||
@item{Returns @racket[#f] if no default is given and @racket[fail] is disabled}
|
||||
@item{Returns @racket[def-val] if one is provided}
|
||||
@item{Raises an error if @racket[fail] is enabled and no default is given}
|
||||
]
|
||||
}
|
||||
|
||||
@bold{Internal state:}
|
||||
@itemlist[
|
||||
@item{@racket[file*] — the filename (or @racket[#f])}
|
||||
@item{@racket[content] — the mutable INI structure}
|
||||
@item{@racket[fail] — when enabled, raises errors instead of returning fallback values}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user