file_query_text 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.
file_query_text/cli.py CHANGED
@@ -4,7 +4,7 @@ import sys
4
4
  import argparse
5
5
  from pathlib import Path
6
6
  # Fix imports to work when installed as a package
7
- from file_query_text.main import parse_query, QueryVisitor, execute_query
7
+ from file_query_text.main import parse_query, QueryVisitor, execute_query, get_file_attributes
8
8
 
9
9
  def main():
10
10
  parser = argparse.ArgumentParser(description="SQL-like queries for your filesystem")
@@ -15,6 +15,16 @@ def main():
15
15
  action="store_true",
16
16
  help="Display content of the matching files"
17
17
  )
18
+ parser.add_argument(
19
+ "--debug", "-d",
20
+ action="store_true",
21
+ help="Debug mode: print all file attributes regardless of query"
22
+ )
23
+ parser.add_argument(
24
+ "--no-gitignore", "-g",
25
+ action="store_true",
26
+ help="Don't respect .gitignore files (show ignored files)"
27
+ )
18
28
  args = parser.parse_args()
19
29
 
20
30
  # Get current working directory for the query
@@ -40,7 +50,12 @@ def main():
40
50
  if parsed:
41
51
  visitor = QueryVisitor()
42
52
  visitor.visit(parsed)
43
- results = execute_query(visitor.select, visitor.from_dirs, visitor.where)
53
+ results = execute_query(
54
+ visitor.select,
55
+ visitor.from_dirs,
56
+ visitor.where,
57
+ respect_gitignore=not args.no_gitignore
58
+ )
44
59
 
45
60
  # Display results
46
61
  if not results:
@@ -48,8 +63,20 @@ def main():
48
63
  return
49
64
 
50
65
  print(f"Found {len(results)} matching files:")
66
+
67
+ # In debug mode, print headers
68
+ if args.debug:
69
+ print(f"{'EXTENSION':<15} {'NAME':<30} {'SIZE':<10} {'PATH'}")
70
+ print(f"{'-'*15} {'-'*30} {'-'*10} {'-'*30}")
71
+
51
72
  for file_path in results:
52
- print(file_path)
73
+ if args.debug:
74
+ # Print all attributes in debug mode
75
+ attrs = get_file_attributes(file_path)
76
+ print(f"{attrs['extension']:<15} {attrs['name']:<30} {attrs['size']:<10} {attrs['relative_path']}")
77
+ else:
78
+ # Standard output
79
+ print(file_path)
53
80
 
54
81
  # Optionally display file contents
55
82
  if args.show_content:
@@ -17,15 +17,16 @@ from pyparsing import (
17
17
  c_style_comment,
18
18
  nums,
19
19
  pyparsing_common,
20
+ CaselessKeyword,
20
21
  )
21
22
 
22
23
  # Define keywords
23
- SELECT = Suppress(Word("SELECT"))
24
- FROM = Suppress(Word("FROM"))
25
- WHERE = Suppress(Word("WHERE"))
26
- AND = Literal("AND")
27
- OR = Literal("OR")
28
- NOT = Literal("NOT")
24
+ SELECT = Suppress(CaselessKeyword("SELECT"))
25
+ FROM = Suppress(CaselessKeyword("FROM"))
26
+ WHERE = Suppress(CaselessKeyword("WHERE"))
27
+ AND = CaselessKeyword("AND")
28
+ OR = CaselessKeyword("OR")
29
+ NOT = CaselessKeyword("NOT")
29
30
 
30
31
  # Define identifiers and literals
31
32
  IDENTIFIER = Word(alphas + "_")
@@ -35,7 +36,7 @@ NUMERIC_LITERAL = pyparsing_common.integer
35
36
  DIRECTORY_LIST = Group(delimitedList(STRING_LITERAL))
36
37
 
37
38
  # Define comparison operators
38
- COMPARISON_OP = oneOf("== != < <= > >=")
39
+ COMPARISON_OP = oneOf("= == != <> < <= > >=")
39
40
  ATTRIBUTE = IDENTIFIER + Suppress("=") + STRING_LITERAL
40
41
 
41
42
  # Define basic condition with support for both string and numeric literals
file_query_text/main.py CHANGED
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import sys
3
3
  from file_query_text.grammar import query # Import the fixed grammar
4
+ import gitignore_parser
5
+ import os.path
4
6
 
5
7
 
6
8
  def parse_query(query_str):
@@ -29,14 +31,51 @@ class QueryVisitor:
29
31
  self.from_dirs = parsed_query.get("from_dirs", [])
30
32
  self.where = parsed_query.get("where", None)
31
33
 
32
- def execute_query(select, from_dirs, where_conditions):
34
+ def execute_query(select, from_dirs, where_conditions, respect_gitignore=True):
33
35
  matched_files = []
36
+
37
+ # Set up gitignore matching if requested
38
+ gitignore_matchers = {}
39
+
34
40
  for directory in from_dirs:
35
41
  if not os.path.exists(directory):
36
42
  continue
43
+
44
+ # Initialize gitignore matcher for this directory
45
+ if respect_gitignore:
46
+ # Find the repository root (where .git directory is)
47
+ repo_root = directory
48
+ while repo_root != os.path.dirname(repo_root): # Stop at filesystem root
49
+ if os.path.exists(os.path.join(repo_root, '.git')):
50
+ break
51
+ repo_root = os.path.dirname(repo_root)
52
+
53
+ # If we found a git repo, check for .gitignore
54
+ gitignore_path = os.path.join(repo_root, '.gitignore')
55
+ if os.path.exists(gitignore_path):
56
+ if repo_root not in gitignore_matchers:
57
+ try:
58
+ gitignore_matchers[repo_root] = gitignore_parser.parse_gitignore(gitignore_path)
59
+ except Exception as e:
60
+ print(f"Warning: Error parsing .gitignore at {gitignore_path}: {e}")
61
+
37
62
  for root, _, files in os.walk(directory):
38
63
  for filename in files:
39
64
  file_path = os.path.join(root, filename)
65
+
66
+ # Check if file is ignored by gitignore rules
67
+ if respect_gitignore:
68
+ # Find which repo this file belongs to
69
+ file_repo_root = None
70
+ for repo_root in gitignore_matchers:
71
+ if file_path.startswith(repo_root):
72
+ file_repo_root = repo_root
73
+ break
74
+
75
+ # Skip if file is ignored by gitignore
76
+ if file_repo_root and gitignore_matchers[file_repo_root](file_path):
77
+ continue
78
+
40
79
  if evaluate_conditions(file_path, where_conditions):
41
80
  matched_files.append(file_path)
42
81
  return matched_files
@@ -52,6 +91,8 @@ def evaluate_conditions(file_path, condition):
52
91
  return os.path.basename(file_path)
53
92
  if attr_name == "size":
54
93
  return os.path.getsize(file_path)
94
+ if attr_name == "path":
95
+ return file_path
55
96
  # Add more attributes as needed
56
97
  return None
57
98
 
@@ -69,8 +110,8 @@ def evaluate_conditions(file_path, condition):
69
110
  op = expr[1]
70
111
  val = expr[2].strip("'") if isinstance(expr[2], str) else expr[2] # Remove quotes if string
71
112
 
72
- if op == "==": return str(attr_val) == val
73
- if op == "!=": return str(attr_val) != val
113
+ if op == "=" or op == "==": return str(attr_val) == val
114
+ if op == "!=" or op == "<>": return str(attr_val) != val
74
115
  if op == "<": return attr_val is not None and int(attr_val) < int(val)
75
116
  if op == "<=": return attr_val is not None and int(attr_val) <= int(val)
76
117
  if op == ">": return attr_val is not None and int(attr_val) > int(val)
@@ -90,6 +131,26 @@ def evaluate_conditions(file_path, condition):
90
131
 
91
132
  return eval_expr(condition.asList())
92
133
 
134
+ # Function to get all attributes for a file
135
+ def get_file_attributes(file_path):
136
+ """Get all available attributes for a file."""
137
+ cwd = os.getcwd()
138
+ try:
139
+ # Make path relative to current directory
140
+ rel_path = os.path.relpath(file_path, cwd)
141
+ except ValueError:
142
+ # Handle case when on different drives (Windows)
143
+ rel_path = file_path
144
+
145
+ attributes = {
146
+ "extension": os.path.splitext(file_path)[1][1:],
147
+ "name": os.path.basename(file_path),
148
+ "size": os.path.getsize(file_path),
149
+ "path": file_path,
150
+ "relative_path": rel_path,
151
+ }
152
+ return attributes
153
+
93
154
  # Example usage
94
155
  if __name__ == "__main__":
95
156
  # Get project root directory for demonstration
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: file_query_text
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: SQL-like interface for querying files in your filesystem
5
5
  Author-email: nik <42a11b@nikdav.is>
6
6
  License-Expression: MIT
@@ -90,6 +90,7 @@ SELECT * FROM 'directory_path' WHERE condition
90
90
  - `extension`: File extension (without the dot)
91
91
  - `name`: Filename with extension
92
92
  - `size`: File size in bytes
93
+ - `path`: Full file path
93
94
 
94
95
  ### Operators
95
96
 
@@ -107,4 +108,7 @@ fq "NOT name == 'main.py'"
107
108
 
108
109
  # Find all large image files
109
110
  fq "SELECT * FROM '.' WHERE (extension == 'jpg' OR extension == 'png') AND size > 500000"
111
+
112
+ # Find files with 'config' in their path
113
+ fq "path == '.*config.*'"
110
114
  ```
@@ -0,0 +1,9 @@
1
+ file_query_text/__init__.py,sha256=WXdvguZ706HG7MXS13Yb8e7VC5UFYyBzuExXbwBOTak,87
2
+ file_query_text/cli.py,sha256=z1e16emuXsgsJP298UKbezhtIVMx9NZQ6v7FNHAahoo,3306
3
+ file_query_text/grammar.py,sha256=XanSi9VCKuIQNlOMXRBZbeWxaJj2UhMJPDIYKgw4lEQ,1655
4
+ file_query_text/main.py,sha256=PVUQ6BZsvN2eEUhORjWr2aNh761evNieOaXTs_RDlB4,6377
5
+ file_query_text-0.1.2.dist-info/METADATA,sha256=AYCc9743EJ0bwqpBKy5qfhDX0uR_htYHFh5fQL6wylY,2440
6
+ file_query_text-0.1.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
7
+ file_query_text-0.1.2.dist-info/entry_points.txt,sha256=rNFYWzvcIsUZkGNsc_E_B5HyYRnqqdj_u8_IeQpw1wo,48
8
+ file_query_text-0.1.2.dist-info/top_level.txt,sha256=o1FzSvLa6kSV61b7RLHWRhEezc96m05YwIKqjuWUSxU,16
9
+ file_query_text-0.1.2.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- file_query_text/__init__.py,sha256=WXdvguZ706HG7MXS13Yb8e7VC5UFYyBzuExXbwBOTak,87
2
- file_query_text/cli.py,sha256=_0ahAdVoDCRBhAcIkcgKM6ZqA0YlqmAyF9i-s2GQRjk,2365
3
- file_query_text/grammar.py,sha256=lhw2pUq83IMga2cppA0r9RbebioPB7a65Az5f4Lheso,1572
4
- file_query_text/main.py,sha256=qMuY5YZ2TXrAUyfXaQfPkQOJZawpRE86NIre5Pz88Tk,4011
5
- file_query_text-0.1.1.dist-info/METADATA,sha256=0VTvghCuE7IIixRJdPVw1Y0Y5ijX8YerDMVpUlf_5UE,2347
6
- file_query_text-0.1.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
7
- file_query_text-0.1.1.dist-info/entry_points.txt,sha256=rNFYWzvcIsUZkGNsc_E_B5HyYRnqqdj_u8_IeQpw1wo,48
8
- file_query_text-0.1.1.dist-info/top_level.txt,sha256=o1FzSvLa6kSV61b7RLHWRhEezc96m05YwIKqjuWUSxU,16
9
- file_query_text-0.1.1.dist-info/RECORD,,