Initial import

This commit is contained in:
2025-07-09 21:00:36 +02:00
parent 744a80ff3b
commit 0ffd793e17
4 changed files with 353 additions and 0 deletions

21
info.rkt Normal file
View 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
View 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
View 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
View 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}
]
}