flyconf 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flyconf-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: flyconf
3
+ Version: 0.1.0
4
+ Summary: A parser for fc configuration files with support for complex structures, variables, and environment-aware configurations
5
+ Author-email: Your Name <your.email@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/yourusername/flyconf
8
+ Project-URL: Repository, https://github.com/yourusername/flyconf
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=7.0; extra == "dev"
13
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
14
+
15
+ # FlyConf Parser
16
+
17
+ FlyConf Parser 是一个用于解析fc配置文件格式的Python库。fc配置文件是一种具有特定语法的配置格式,支持复杂的数据结构、变量引用和环境感知配置。
18
+
19
+ ## 特性
20
+
21
+ - 词法分析器,支持识别fc配置文件中的各种标记
22
+ - 语法分析器,构建配置块的抽象语法树
23
+ - 数据模型,用于表示解析后的配置结构
24
+ - 字符串处理(原生多行字符串和单行字符串)
25
+ - 变量引用系统(支持外部变量、环境变量和配置内引用)
26
+ - 列表解析(简单列表和嵌套列表)
27
+ - 导入导出功能(支持JSON格式)
28
+ - 环境感知配置合并
29
+
30
+ ## 安装
31
+
32
+ ```bash
33
+ pip install flyconf
34
+ ```
35
+
36
+ ## 使用方法
37
+
38
+ ### 基本用法
39
+
40
+ ```python
41
+ from flyconf.parser import FCConfigParser
42
+
43
+ # 解析fc配置文件
44
+ config = FCConfigParser.parse_file("config.fc")
45
+
46
+ # 访问配置块
47
+ block = config.get_block("server")
48
+ print(block.data)
49
+
50
+ # 导出为JSON
51
+ import json
52
+ json_data = config.to_json()
53
+ ```
54
+
55
+ ### fc配置文件语法
56
+
57
+ fc配置文件具有以下语法结构:
58
+
59
+ ```
60
+ @block_name(meta_key>meta_value) data_key>data_value
61
+ ```
62
+
63
+ 示例:
64
+ ```fc
65
+ @mysql_default(type>conf.db)
66
+ dbtype>mysql
67
+ host>localhost
68
+ port>3306
69
+ database>oax
70
+ user>root
71
+ password>1234
72
+
73
+ @remember_me(type>conf.txt)
74
+ username>admin
75
+ password>1234
76
+
77
+ @test_list
78
+ users>[user1,user2,user3]
79
+ ```
80
+
81
+ ### 字符串
82
+
83
+ 使用 `^...^` 表示单行字符串,使用 `^^^...^^^` 表示多行字符串:
84
+
85
+ ```fc
86
+ @get_users(type>conf.sql)
87
+ dbtype>mysql
88
+ sql>^
89
+ SELECT id, username, email FROM users
90
+ ^
91
+ ```
92
+
93
+ ### 列表
94
+
95
+ 使用 `[...]` 表示列表:
96
+
97
+ ```fc
98
+ @test_list
99
+ users>[user1,user2,user3]
100
+ ```
101
+
102
+ ### 变量引用
103
+
104
+ 使用 `$(variable_name)` 表示变量引用:
105
+
106
+ ```fc
107
+ @server
108
+ path>$(config.path)
109
+ ```
110
+
111
+ ## API
112
+
113
+ ### FCConfigParser
114
+
115
+ - `FCConfigParser.parse_text(text)` - 从文本解析配置
116
+ - `FCConfigParser.parse_file(file_path)` - 从文件解析配置
117
+
118
+ ### FCConfig
119
+
120
+ - `config.get_block(name)` - 获取指定名称的块
121
+ - `config.add_block(block)` - 添加块
122
+ - `config.to_dict()` - 转换为字典
123
+ - `config.to_json()` - 转换为JSON字符串
124
+
125
+ ### FCBlock
126
+
127
+ - `block.name` - 块名称
128
+ - `block.meta` - 元数据字典
129
+ - `block.data` - 数据字典
@@ -0,0 +1,115 @@
1
+ # FlyConf Parser
2
+
3
+ FlyConf Parser 是一个用于解析fc配置文件格式的Python库。fc配置文件是一种具有特定语法的配置格式,支持复杂的数据结构、变量引用和环境感知配置。
4
+
5
+ ## 特性
6
+
7
+ - 词法分析器,支持识别fc配置文件中的各种标记
8
+ - 语法分析器,构建配置块的抽象语法树
9
+ - 数据模型,用于表示解析后的配置结构
10
+ - 字符串处理(原生多行字符串和单行字符串)
11
+ - 变量引用系统(支持外部变量、环境变量和配置内引用)
12
+ - 列表解析(简单列表和嵌套列表)
13
+ - 导入导出功能(支持JSON格式)
14
+ - 环境感知配置合并
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ pip install flyconf
20
+ ```
21
+
22
+ ## 使用方法
23
+
24
+ ### 基本用法
25
+
26
+ ```python
27
+ from flyconf.parser import FCConfigParser
28
+
29
+ # 解析fc配置文件
30
+ config = FCConfigParser.parse_file("config.fc")
31
+
32
+ # 访问配置块
33
+ block = config.get_block("server")
34
+ print(block.data)
35
+
36
+ # 导出为JSON
37
+ import json
38
+ json_data = config.to_json()
39
+ ```
40
+
41
+ ### fc配置文件语法
42
+
43
+ fc配置文件具有以下语法结构:
44
+
45
+ ```
46
+ @block_name(meta_key>meta_value) data_key>data_value
47
+ ```
48
+
49
+ 示例:
50
+ ```fc
51
+ @mysql_default(type>conf.db)
52
+ dbtype>mysql
53
+ host>localhost
54
+ port>3306
55
+ database>oax
56
+ user>root
57
+ password>1234
58
+
59
+ @remember_me(type>conf.txt)
60
+ username>admin
61
+ password>1234
62
+
63
+ @test_list
64
+ users>[user1,user2,user3]
65
+ ```
66
+
67
+ ### 字符串
68
+
69
+ 使用 `^...^` 表示单行字符串,使用 `^^^...^^^` 表示多行字符串:
70
+
71
+ ```fc
72
+ @get_users(type>conf.sql)
73
+ dbtype>mysql
74
+ sql>^
75
+ SELECT id, username, email FROM users
76
+ ^
77
+ ```
78
+
79
+ ### 列表
80
+
81
+ 使用 `[...]` 表示列表:
82
+
83
+ ```fc
84
+ @test_list
85
+ users>[user1,user2,user3]
86
+ ```
87
+
88
+ ### 变量引用
89
+
90
+ 使用 `$(variable_name)` 表示变量引用:
91
+
92
+ ```fc
93
+ @server
94
+ path>$(config.path)
95
+ ```
96
+
97
+ ## API
98
+
99
+ ### FCConfigParser
100
+
101
+ - `FCConfigParser.parse_text(text)` - 从文本解析配置
102
+ - `FCConfigParser.parse_file(file_path)` - 从文件解析配置
103
+
104
+ ### FCConfig
105
+
106
+ - `config.get_block(name)` - 获取指定名称的块
107
+ - `config.add_block(block)` - 添加块
108
+ - `config.to_dict()` - 转换为字典
109
+ - `config.to_json()` - 转换为JSON字符串
110
+
111
+ ### FCBlock
112
+
113
+ - `block.name` - 块名称
114
+ - `block.meta` - 元数据字典
115
+ - `block.data` - 数据字典
@@ -0,0 +1,13 @@
1
+ """
2
+ FlyConf Parser - A parser for fc configuration files.
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+ __author__ = "Your Name"
7
+ __email__ = "your.email@example.com"
8
+
9
+ from .parser import FCConfigParser
10
+ from .model import FCConfig, FCBlock
11
+ from .transformer import ConfigTransformer
12
+
13
+ __all__ = ["FCConfigParser", "FCConfig", "FCBlock", "ConfigTransformer"]
@@ -0,0 +1,8 @@
1
+ """
2
+ Main entry point for the flyconf module.
3
+ """
4
+
5
+ from . import main
6
+
7
+ if __name__ == "__main__":
8
+ main.main()
@@ -0,0 +1,245 @@
1
+ """
2
+ Lexer for fc configuration files.
3
+ """
4
+
5
+ from enum import Enum
6
+ from typing import List, Iterator, NamedTuple
7
+ import re
8
+
9
+
10
+ class TokenType(Enum):
11
+ """Token types for fc configuration files."""
12
+ AT = "@" # @
13
+ BLOCK_NAME = "BLOCK_NAME" # Block name identifier
14
+ LPAREN = "(" # (
15
+ RPAREN = ")" # )
16
+ LBRACKET = "[" # [
17
+ RBRACKET = "]" # ]
18
+ GT = ">" # >
19
+ CARET = "^" # ^
20
+ DOUBLE_CARET = "^^" # ^^
21
+ HASH = "#" # #
22
+ DOLLAR_PAREN = "$(" # $(
23
+ IDENTIFIER = "IDENTIFIER" # General identifier
24
+ STRING = "STRING" # String content
25
+ COMMA = "," # ,
26
+ EQUALS = "=" # =
27
+ NEWLINE = "NEWLINE" # \n
28
+ WHITESPACE = "WHITESPACE" # Spaces, tabs
29
+ EOF = "EOF" # End of file
30
+
31
+
32
+ class Token(NamedTuple):
33
+ """Represents a token with type, value, and position."""
34
+ type: TokenType
35
+ value: str
36
+ line: int
37
+ column: int
38
+
39
+
40
+ class LexerError(Exception):
41
+ """Exception raised for lexer errors."""
42
+ pass
43
+
44
+
45
+ class Lexer:
46
+ """Lexer for fc configuration files."""
47
+
48
+ def __init__(self, text: str):
49
+ self.text: str = text
50
+ self.pos: int = 0
51
+ self.line: int = 1
52
+ self.column: int = 1
53
+ self.tokens: List[Token] = []
54
+
55
+ def _current_char(self) -> str:
56
+ """Get the current character."""
57
+ if self.pos >= len(self.text):
58
+ return '\0'
59
+ return self.text[self.pos]
60
+
61
+ def _peek_char(self, offset: int = 1) -> str:
62
+ """Peek at the next character."""
63
+ if self.pos + offset >= len(self.text):
64
+ return '\0'
65
+ return self.text[self.pos + offset]
66
+
67
+ def _advance(self) -> None:
68
+ """Advance the position."""
69
+ if self._current_char() == '\n':
70
+ self.line += 1
71
+ self.column = 1
72
+ else:
73
+ self.column += 1
74
+ self.pos += 1
75
+
76
+ def _skip_whitespace(self) -> None:
77
+ """Skip whitespace characters."""
78
+ while self._current_char().isspace() and self._current_char() != '\0':
79
+ if self._current_char() == '\n':
80
+ token = Token(TokenType.NEWLINE, '\n', self.line, self.column)
81
+ self.tokens.append(token)
82
+ self._advance()
83
+
84
+ def _read_identifier(self) -> str:
85
+ """Read an identifier."""
86
+ start = self.pos
87
+ while (self._current_char().isalnum() or
88
+ self._current_char() in ['_', '-', '.']) and \
89
+ self._current_char() != '\0':
90
+ self._advance()
91
+ return self.text[start:self.pos]
92
+
93
+ def _read_string(self, delimiter: str) -> str:
94
+ """Read a string until the delimiter."""
95
+ start = self.pos
96
+ while self._current_char() != delimiter and self._current_char() != '\0':
97
+ # Handle escaped characters
98
+ if self._current_char() == '\\' and self._peek_char() != '\0':
99
+ self._advance()
100
+ self._advance()
101
+
102
+ if self._current_char() == '\0':
103
+ raise LexerError(f"Unterminated string at line {self.line}, column {self.column}")
104
+
105
+ value = self.text[start:self.pos]
106
+ # Skip the closing delimiter
107
+ self._advance()
108
+ return value
109
+
110
+ def _read_multiline_string(self, delimiter: str) -> str:
111
+ """Read a multiline string until the delimiter."""
112
+ start = self.pos
113
+ # Look for the closing delimiter (two consecutive ^)
114
+ while not (self._current_char() == delimiter and
115
+ self._peek_char() == delimiter) and \
116
+ self._current_char() != '\0':
117
+ # Handle escaped characters
118
+ if self._current_char() == '\\' and self._peek_char() != '\0':
119
+ self._advance()
120
+ self._advance()
121
+
122
+ if self._current_char() == '\0':
123
+ raise LexerError(f"Unterminated multiline string at line {self.line}, column {self.column}")
124
+
125
+ value = self.text[start:self.pos]
126
+ # Skip the closing delimiters
127
+ self._advance() # Skip first ^
128
+ self._advance() # Skip second ^
129
+ return value
130
+
131
+ def _read_raw_string(self, delimiter: str) -> str:
132
+ """Read a raw string until the delimiter."""
133
+ start = self.pos
134
+ # Look for the closing delimiter (three consecutive ^)
135
+ while not (self._current_char() == delimiter and
136
+ self._peek_char() == delimiter and
137
+ self._peek_char(2) == delimiter) and \
138
+ self._current_char() != '\0':
139
+ self._advance()
140
+
141
+ if self._current_char() == '\0':
142
+ raise LexerError(f"Unterminated raw string at line {self.line}, column {self.column}")
143
+
144
+ value = self.text[start:self.pos]
145
+ # Skip the closing delimiters
146
+ self._advance() # Skip first ^
147
+ self._advance() # Skip second ^
148
+ self._advance() # Skip third ^
149
+ return value
150
+
151
+ def _skip_comment(self) -> None:
152
+ """Skip a comment until end of line."""
153
+ while self._current_char() != '\n' and self._current_char() != '\0':
154
+ self._advance()
155
+
156
+ def tokenize(self) -> List[Token]:
157
+ """Tokenize the input text."""
158
+ while self._current_char() != '\0':
159
+ # Skip whitespace but capture newlines
160
+ self._skip_whitespace()
161
+
162
+ if self._current_char() == '\0':
163
+ break
164
+
165
+ # Handle comments
166
+ if self._current_char() == '#':
167
+ self._skip_comment()
168
+ continue
169
+
170
+ # Handle special tokens
171
+ char = self._current_char()
172
+ next_char = self._peek_char()
173
+ next_next_char = self._peek_char(2)
174
+
175
+ # Handle triple caret (raw string) (^^^)
176
+ if char == '^' and next_char == '^' and next_next_char == '^':
177
+ self._advance()
178
+ self._advance()
179
+ self._advance()
180
+ # Read raw string
181
+ string_value = self._read_raw_string('^')
182
+ string_token = Token(TokenType.STRING, string_value, self.line, self.column)
183
+ self.tokens.append(string_token)
184
+ continue
185
+
186
+ # Handle double caret (multiline string) (^^)
187
+ if char == '^' and next_char == '^' and next_next_char != '^':
188
+ self._advance()
189
+ self._advance()
190
+ # Read multiline string
191
+ string_value = self._read_multiline_string('^')
192
+ string_token = Token(TokenType.STRING, string_value, self.line, self.column)
193
+ self.tokens.append(string_token)
194
+ continue
195
+
196
+ # Handle single caret (^)
197
+ if char == '^':
198
+ self._advance()
199
+ # Read string
200
+ string_value = self._read_string('^')
201
+ string_token = Token(TokenType.STRING, string_value, self.line, self.column)
202
+ self.tokens.append(string_token)
203
+ continue
204
+
205
+ # Handle other special tokens
206
+ special_tokens = {
207
+ '@': TokenType.AT,
208
+ '(': TokenType.LPAREN,
209
+ ')': TokenType.RPAREN,
210
+ '[': TokenType.LBRACKET,
211
+ ']': TokenType.RBRACKET,
212
+ '>': TokenType.GT,
213
+ ',': TokenType.COMMA,
214
+ '=': TokenType.EQUALS,
215
+ '#': TokenType.HASH,
216
+ }
217
+
218
+ # Handle $(
219
+ if char == '$' and next_char == '(':
220
+ token = Token(TokenType.DOLLAR_PAREN, '$(', self.line, self.column)
221
+ self.tokens.append(token)
222
+ self._advance()
223
+ self._advance()
224
+ continue
225
+
226
+ # Handle other special tokens
227
+ if char in special_tokens:
228
+ token = Token(special_tokens[char], char, self.line, self.column)
229
+ self.tokens.append(token)
230
+ self._advance()
231
+ continue
232
+
233
+ # Handle identifiers
234
+ if char.isalnum() or char == '_':
235
+ value = self._read_identifier()
236
+ token = Token(TokenType.IDENTIFIER, value, self.line, self.column)
237
+ self.tokens.append(token)
238
+ continue
239
+
240
+ # Skip unknown characters
241
+ self._advance()
242
+
243
+ # Add EOF token
244
+ self.tokens.append(Token(TokenType.EOF, '', self.line, self.column))
245
+ return self.tokens
@@ -0,0 +1,93 @@
1
+ """
2
+ Data models for fc configuration files.
3
+ """
4
+
5
+ from typing import List, Dict, Any, Optional
6
+
7
+
8
+ class FCBlock:
9
+ """
10
+ Represents a block in fc configuration file.
11
+
12
+ A block consists of:
13
+ - name: Block identifier
14
+ - meta: Metadata dictionary
15
+ - data: Data dictionary containing key-value pairs
16
+ """
17
+
18
+ def __init__(self, name: str):
19
+ self.name: str = name
20
+ self.meta: Dict[str, Any] = {}
21
+ self.data: Dict[str, Any] = {}
22
+
23
+ def __repr__(self) -> str:
24
+ return f"FCBlock(name='{self.name}', meta={self.meta}, data={self.data})"
25
+
26
+
27
+ class FCConfig:
28
+ """
29
+ Represents a complete fc configuration.
30
+
31
+ Contains:
32
+ - blocks: List of FCBlock objects
33
+ - variables: Global variables dictionary
34
+ """
35
+
36
+ def __init__(self):
37
+ self.blocks: List[FCBlock] = []
38
+ self.variables: Dict[str, Any] = {}
39
+
40
+ def get_block(self, name: str) -> Optional[FCBlock]:
41
+ """Get a block by name."""
42
+ for block in self.blocks:
43
+ if block.name == name:
44
+ return block
45
+ return None
46
+
47
+ def add_block(self, block: FCBlock) -> None:
48
+ """Add a block to the configuration."""
49
+ self.blocks.append(block)
50
+
51
+ def to_dict(self) -> Dict[str, Any]:
52
+ """Convert the configuration to a dictionary."""
53
+ result = {
54
+ "variables": self.variables,
55
+ "blocks": {}
56
+ }
57
+
58
+ for block in self.blocks:
59
+ result["blocks"][block.name] = {
60
+ "meta": block.meta,
61
+ "data": block.data
62
+ }
63
+
64
+ return result
65
+
66
+ def to_json(self) -> str:
67
+ """Convert the configuration to JSON string."""
68
+ import json
69
+ return json.dumps(self.to_dict(), indent=2, ensure_ascii=False)
70
+
71
+ @classmethod
72
+ def from_dict(cls, data: Dict[str, Any]) -> "FCConfig":
73
+ """Create a configuration from a dictionary."""
74
+ config = cls()
75
+ config.variables = data.get("variables", {})
76
+
77
+ for block_name, block_data in data.get("blocks", {}).items():
78
+ block = FCBlock(block_name)
79
+ block.meta = block_data.get("meta", {})
80
+ block.data = block_data.get("data", {})
81
+ config.add_block(block)
82
+
83
+ return config
84
+
85
+ @classmethod
86
+ def from_json(cls, json_str: str) -> "FCConfig":
87
+ """Create a configuration from a JSON string."""
88
+ import json
89
+ data = json.loads(json_str)
90
+ return cls.from_dict(data)
91
+
92
+ def __repr__(self) -> str:
93
+ return f"FCConfig(blocks={len(self.blocks)}, variables={len(self.variables)})"