2019-07-15 12:14:27 -07:00
""" Functions that help us work with files and folders.
"""
2019-09-10 08:14:25 -04:00
import logging
2019-07-15 12:14:27 -07:00
import os
2021-02-07 21:02:51 +00:00
import argparse
2025-03-19 12:45:28 +11:00
from pathlib import Path , PureWindowsPath , PurePosixPath
2019-07-15 12:14:27 -07:00
2023-11-28 07:53:43 +11:00
from qmk . constants import MAX_KEYBOARD_SUBFOLDERS , QMK_FIRMWARE , QMK_USERSPACE , HAS_QMK_USERSPACE
2019-09-10 08:14:25 -04:00
from qmk . errors import NoSuchKeyboardError
2020-01-07 21:54:21 +01:00
2020-02-17 11:42:11 -08:00
def is_keyboard ( keyboard_name ) :
""" Returns True if `keyboard_name` is a keyboard we can compile.
"""
2024-03-13 00:28:08 +00:00
if not keyboard_name :
return False
2021-03-24 09:26:38 -07:00
2024-03-13 00:28:08 +00:00
# keyboard_name values of 'c:/something' or '/something' trigger append issues
# due to "If the argument is an absolute path, the previous path is ignored"
# however it should always be a folder located under qmk_firmware/keyboards
if Path ( keyboard_name ) . is_absolute ( ) :
return False
keyboard_path = QMK_FIRMWARE / ' keyboards ' / keyboard_name
rules_mk = keyboard_path / ' rules.mk '
2024-03-13 00:30:09 +00:00
keyboard_json = keyboard_path / ' keyboard.json '
2024-03-13 00:28:08 +00:00
2024-03-13 00:30:09 +00:00
return rules_mk . exists ( ) or keyboard_json . exists ( )
2020-02-17 11:42:11 -08:00
2023-11-01 22:37:05 +00:00
def under_qmk_firmware ( path = Path ( os . environ [ ' ORIG_CWD ' ] ) ) :
2020-02-17 11:42:11 -08:00
""" Returns a Path object representing the relative path under qmk_firmware, or None.
"""
try :
2023-11-01 22:37:05 +00:00
return path . relative_to ( QMK_FIRMWARE )
2020-02-17 11:42:11 -08:00
except ValueError :
return None
2023-11-28 07:53:43 +11:00
def under_qmk_userspace ( path = Path ( os . environ [ ' ORIG_CWD ' ] ) ) :
""" Returns a Path object representing the relative path under $QMK_USERSPACE, or None.
"""
try :
if HAS_QMK_USERSPACE :
return path . relative_to ( QMK_USERSPACE )
except ValueError :
pass
return None
def is_under_qmk_firmware ( path = Path ( os . environ [ ' ORIG_CWD ' ] ) ) :
""" Returns a boolean if the input path is a child under qmk_firmware.
"""
if path is None :
return False
try :
return Path ( os . path . commonpath ( [ Path ( path ) , QMK_FIRMWARE ] ) ) == QMK_FIRMWARE
except ValueError :
return False
def is_under_qmk_userspace ( path = Path ( os . environ [ ' ORIG_CWD ' ] ) ) :
""" Returns a boolean if the input path is a child under $QMK_USERSPACE.
"""
if path is None :
return False
try :
if HAS_QMK_USERSPACE :
return Path ( os . path . commonpath ( [ Path ( path ) , QMK_USERSPACE ] ) ) == QMK_USERSPACE
except ValueError :
return False
2020-11-07 09:56:08 -08:00
def keyboard ( keyboard_name ) :
""" Returns the path to a keyboard ' s directory relative to the qmk root.
"""
return Path ( ' keyboards ' ) / keyboard_name
2023-05-19 16:05:43 +10:00
def keymaps ( keyboard_name ) :
""" Returns all of the `keymaps/` directories for a given keyboard.
2019-07-15 12:14:27 -07:00
Args:
2020-02-17 11:42:11 -08:00
2020-11-07 09:56:08 -08:00
keyboard_name
2019-07-15 12:14:27 -07:00
The name of the keyboard. Example: clueboard/66/rev3
"""
2020-11-07 09:56:08 -08:00
keyboard_folder = keyboard ( keyboard_name )
2023-05-19 16:05:43 +10:00
found_dirs = [ ]
2020-02-17 11:42:11 -08:00
2023-11-28 07:53:43 +11:00
if HAS_QMK_USERSPACE :
this_keyboard_folder = Path ( QMK_USERSPACE ) / keyboard_folder
for _ in range ( MAX_KEYBOARD_SUBFOLDERS ) :
if ( this_keyboard_folder / ' keymaps ' ) . exists ( ) :
found_dirs . append ( ( this_keyboard_folder / ' keymaps ' ) . resolve ( ) )
this_keyboard_folder = this_keyboard_folder . parent
if this_keyboard_folder . resolve ( ) == QMK_USERSPACE . resolve ( ) :
break
# We don't have any relevant keymap directories in userspace, so we'll use the fully-qualified path instead.
if len ( found_dirs ) == 0 :
found_dirs . append ( ( QMK_USERSPACE / keyboard_folder / ' keymaps ' ) . resolve ( ) )
this_keyboard_folder = QMK_FIRMWARE / keyboard_folder
2022-02-08 19:03:30 +00:00
for _ in range ( MAX_KEYBOARD_SUBFOLDERS ) :
2023-11-28 07:53:43 +11:00
if ( this_keyboard_folder / ' keymaps ' ) . exists ( ) :
found_dirs . append ( ( this_keyboard_folder / ' keymaps ' ) . resolve ( ) )
2019-07-15 12:14:27 -07:00
2023-11-28 07:53:43 +11:00
this_keyboard_folder = this_keyboard_folder . parent
if this_keyboard_folder . resolve ( ) == QMK_FIRMWARE . resolve ( ) :
break
2019-07-15 12:14:27 -07:00
2023-05-19 16:05:43 +10:00
if len ( found_dirs ) > 0 :
return found_dirs
2020-02-17 11:42:11 -08:00
logging . error ( ' Could not find the keymaps directory! ' )
2020-11-07 09:56:08 -08:00
raise NoSuchKeyboardError ( ' Could not find keymaps directory for: %s ' % keyboard_name )
2019-07-15 12:14:27 -07:00
2023-05-19 16:05:43 +10:00
def keymap ( keyboard_name , keymap_name ) :
""" Locate the directory of a given keymap.
Args:
keyboard_name
The name of the keyboard. Example: clueboard/66/rev3
keymap_name
The name of the keymap. Example: default
"""
for keymap_dir in keymaps ( keyboard_name ) :
if ( keymap_dir / keymap_name ) . exists ( ) :
return ( keymap_dir / keymap_name ) . resolve ( )
2019-07-15 12:14:27 -07:00
def normpath ( path ) :
2020-02-17 11:42:11 -08:00
""" Returns a `pathlib.Path()` object for a given path.
2019-07-15 12:14:27 -07:00
2020-02-17 11:42:11 -08:00
This will use the path to a file as seen from the directory the script was called from. You should use this to normalize filenames supplied from the command line.
2019-07-15 12:14:27 -07:00
"""
2020-02-17 11:42:11 -08:00
path = Path ( path )
if path . is_absolute ( ) :
2020-03-13 15:47:04 -07:00
return path
2019-07-15 12:14:27 -07:00
2020-02-17 11:42:11 -08:00
return Path ( os . environ [ ' ORIG_CWD ' ] ) / path
2021-02-07 21:02:51 +00:00
2025-03-19 12:45:28 +11:00
def unix_style_path ( path ) :
""" Converts a Windows-style path with drive letter to a Unix path.
Path().as_posix() normally returns the path with drive letter and forward slashes, so is inappropriate for `Makefile` paths.
Passes through unadulterated if the path is not a Windows-style path.
Args:
path
The path to convert.
Returns:
The input path converted to Unix format.
"""
if isinstance ( path , PureWindowsPath ) :
p = list ( path . parts )
p [ 0 ] = f ' / { p [ 0 ] [ 0 ] . lower ( ) } ' # convert from `X:/` to `/x`
path = PurePosixPath ( * p )
return path
2021-02-07 21:02:51 +00:00
class FileType ( argparse . FileType ) :
2022-03-20 07:58:30 +11:00
def __init__ ( self , * args , * * kwargs ) :
2022-02-28 20:02:39 +00:00
# Use UTF8 by default for stdin
2022-03-20 07:58:30 +11:00
if ' encoding ' not in kwargs :
kwargs [ ' encoding ' ] = ' UTF-8 '
return super ( ) . __init__ ( * args , * * kwargs )
2022-02-28 20:02:39 +00:00
2021-02-07 21:02:51 +00:00
def __call__ ( self , string ) :
""" normalize and check exists
otherwise magic strings like ' - ' for stdin resolve to bad paths
"""
norm = normpath ( string )
2022-02-28 20:02:39 +00:00
return norm if norm . exists ( ) else super ( ) . __call__ ( string )