33from typing import Dict , Any , Optional , Literal , Union
44import logging
55from pathlib import Path
6- from pydantic import BaseModel , Field , model_validator , field_validator , ValidationError
6+ from pydantic import BaseModel , Field , model_validator , field_validator , ValidationError , ValidationInfo
77import yaml
88import toml
99import configparser
@@ -45,6 +45,25 @@ class ApplicationConfig(BaseModel):
4545 basedir : Optional [Path ] = Field (None , description = "Base directory for YAML files" )
4646 base_url : str = Field (default = "https://a11y-guidelines.freee.co.jp" , description = "Base URL for documentation" )
4747
48+ @model_validator (mode = 'after' )
49+ def resolve_credential_paths (self , info : ValidationInfo ) -> 'ApplicationConfig' :
50+ """Resolve credential and token paths relative to the config file."""
51+ # load_configurationからcontext経由で設定ファイルのパスを取得
52+ config_file_path = info .context .get ('config_file_path' ) if info .context else None
53+
54+ if config_file_path :
55+ config_dir = config_file_path .parent
56+
57+ # 1. credentials_path が相対パスの場合、設定ファイルのディレクトリからの相対パスとして解決
58+ if not self .credentials_path .is_absolute ():
59+ self .credentials_path = config_dir / self .credentials_path
60+
61+ # 2. token_path が相対パスの場合、同様に解決
62+ if not self .token_path .is_absolute ():
63+ self .token_path = config_dir / self .token_path
64+
65+ return self
66+
4867 def get_base_url (self , cmd_base_url : Optional [str ] = None ) -> str :
4968 """Get base URL for documentation, resolving from command line or config
5069
@@ -325,93 +344,81 @@ def get_config_loader(config_path: Path) -> ConfigLoader:
325344 return loaders [ext ]
326345
327346def find_config_file (config_path : Optional [Union [str , Path ]] = None ) -> Path :
328- """Find configuration file from specified path or default locations
329-
347+ """Find configuration file from specified path or default locations.
348+
349+ Search logic:
350+ 1. If -c option is used:
351+ a. If absolute path, use it directly.
352+ b. If relative path, search only in the current working directory.
353+ 2. If -c option is NOT used, search in order:
354+ a. In `${HOME}/.config/freee_a11y_gl/` for `yaml2sheet.{yaml,yml,ini,toml}`
355+ b. In the current working directory for `.yaml2sheet.{yaml,yml,ini,toml}`
356+
330357 Args:
331- config_path: Command-line specified configuration file path
332-
358+ config_path: Command-line specified configuration file path.
359+
333360 Returns:
334- Path: Absolute path to found configuration file
335-
361+ Path: Absolute path to the found configuration file.
362+
336363 Raises:
337- FileNotFoundError: If configuration file not found
364+ FileNotFoundError: If configuration file is not found.
338365 """
339- try :
340- # Convert to Path if string provided
341- config_path_obj = Path (config_path ) if config_path else None
342- except Exception as e :
343- raise ValueError (f"Invalid config path: { e } " )
344-
345- # If path specified with -c option
346- if config_path_obj :
347- if config_path_obj .is_absolute ():
348- # Use absolute path as is
349- if not config_path_obj .exists ():
350- raise FileNotFoundError (f"Specified configuration file not found: { config_path_obj } " )
351- return validate_readable_file (config_path_obj )
366+ # When -c option is specified, use the provided path directly
367+ if config_path :
368+ path_obj = Path (config_path )
369+
370+ # a. When the path is absolute
371+ if path_obj .is_absolute ():
372+ if not path_obj .exists ():
373+ raise FileNotFoundError (f"Specified configuration file not found: { path_obj } " )
374+ return validate_readable_file (path_obj )
375+
376+ # b. When the path is relative (only serach in current directory)
352377 else :
353- # For relative path, search in:
354- # 1. Current working directory
355- # 2. Script directory
356- search_dirs = [
357- Path .cwd (), # Current working directory first
358- Path (__file__ ).parent .absolute () # Script directory second
359- ]
360-
361- tried_paths = []
362- for dir_path in search_dirs :
363- try_path = dir_path / config_path_obj
364- tried_paths .append (try_path )
365- if try_path .exists ():
366- return validate_readable_file (try_path )
367-
368- raise FileNotFoundError (
369- "Specified configuration file not found. Searched in:\n " +
370- "\n " .join (f"- { p } " for p in tried_paths )
371- )
372-
373- # File types in order of precedence
374- config_types = ["yaml" , "yml" , "toml" , "ini" ]
375- search_dirs = [
376- Path .cwd (), # Current working directory first
377- Path (__file__ ).parent .absolute () # Script directory second
378+ search_path = Path .cwd () / path_obj
379+ if search_path .exists ():
380+ return validate_readable_file (search_path )
381+ else :
382+ raise FileNotFoundError (
383+ "Specified configuration file not found in the current directory.\n "
384+ f"- Searched for: { search_path } "
385+ )
386+
387+ # Default search logic when -c option is not used
388+ search_locations = [
389+ {
390+ "dir" : Path .home () / ".config" / "freee_a11y_gl" ,
391+ "basename" : "yaml2sheet"
392+ },
393+ {
394+ "dir" : Path .cwd (),
395+ "basename" : ".yaml2sheet"
396+ }
378397 ]
379398
380- # First check for config.* in current directory
381- for ext in config_types :
382- config_path = Path .cwd () / f"config.{ ext } "
383- if config_path .exists ():
384- logger .debug (f"Found config file in current directory: { config_path } " )
385- return validate_readable_file (config_path )
386-
387- # Then check script directory
388- script_dir = Path (__file__ ).parent .absolute ()
389- for ext in config_types :
390- config_path = script_dir / f"config.{ ext } "
391- if config_path .exists ():
392- logger .debug (f"Found config file in script directory: { config_path } " )
393- return validate_readable_file (config_path )
394-
395- # Build search paths list for error message, maintaining search order
396- search_paths = [
397- Path .cwd () / f"config.{ ext } "
398- for ext in config_types
399- ] + [
400- Path (__file__ ).parent .absolute () / f"config.{ ext } "
401- for ext in config_types
402- ]
399+ config_types = ["yaml" , "yml" , "ini" , "toml" ]
403400
401+ tried_paths = []
402+ for loc in search_locations :
403+ if not loc ["dir" ].is_dir ():
404+ continue
405+ for ext in config_types :
406+ path_to_check = loc ["dir" ] / f"{ loc ['basename' ]} .{ ext } "
407+ tried_paths .append (path_to_check )
408+ if path_to_check .exists ():
409+ logger .debug (f"Found config file: { path_to_check } " )
410+ return validate_readable_file (path_to_check )
411+
412+ # If no config file found, raise FileNotFoundError
404413 raise FileNotFoundError (
405414 "No configuration file found. Searched in order of precedence:\n " +
406- "\n " .join (f"- { p } " for p in search_paths ) +
407- "\n \n To create a config file, save as 'config.yaml' with the following content:\n " +
415+ "\n " .join (f"- { p } " for p in tried_paths ) +
416+ "\n \n To create a config file, save as "
417+ f"'{ Path .home () / '.config' / 'freee_a11y_gl' / 'yaml2sheet.yaml' } ' "
418+ "with the following content:\n " +
408419 "---\n " +
409- "credentials_path: credentials.json\n " +
410- "token_path: token.json\n " +
411420 "development_spreadsheet_id: YOUR_DEV_SPREADSHEET_ID\n " +
412- "production_spreadsheet_id: YOUR_PROD_SPREADSHEET_ID\n " +
413- "log_level: INFO\n " +
414- "base_url: https://a11y-guidelines.freee.co.jp\n "
421+ "production_spreadsheet_id: YOUR_PROD_SPREADSHEET_ID\n "
415422 )
416423
417424def load_configuration (config_path : Optional [Union [str , Path ]] = None ) -> ApplicationConfig :
@@ -437,7 +444,12 @@ def load_configuration(config_path: Optional[Union[str, Path]] = None) -> Applic
437444
438445 # Validate and return config
439446 try :
440- validated_config = ApplicationConfig .model_validate (config_data )
447+ validation_context = {"config_file_path" : actual_config_path }
448+ validated_config = ApplicationConfig .model_validate (
449+ config_data ,
450+ context = validation_context
451+ )
452+
441453 return validated_config
442454 except ValidationError as e :
443455 logger .error (f"Configuration validation error: { e } " )
@@ -451,13 +463,13 @@ def create_default_config(output_path: Optional[Union[str, Path]] = None) -> Pat
451463 """Create a default configuration file
452464
453465 Args:
454- output_path: Path to save configuration file (defaults to ./config .yaml)
466+ output_path: Path to save configuration file (defaults to ./yaml2sheet .yaml)
455467
456468 Returns:
457469 Path: Path to created configuration file
458470 """
459471 if output_path is None :
460- output_path = Path .cwd () / "config .yaml"
472+ output_path = Path .cwd () / "yaml2sheet .yaml"
461473 else :
462474 output_path = Path (output_path )
463475
0 commit comments