@@ -18,10 +18,13 @@ package config
1818import (
1919 "errors"
2020 "fmt"
21+ "log/slog"
2122 "os"
2223 "os/user"
24+ "path/filepath"
2325 "regexp"
2426 "strings"
27+ "time"
2528)
2629
2730const (
@@ -48,6 +51,16 @@ const (
4851 LibrarianDir = ".librarian"
4952 // ReleaseInitRequest is a JSON file that describes which library to release.
5053 ReleaseInitRequest = "release-init-request.json"
54+
55+ pipelineStateFile = "state.yaml"
56+ versionCmdName = "version"
57+ )
58+
59+ // are variables so it can be replaced during testing.
60+ var (
61+ now = time .Now
62+ tempDir = os .TempDir
63+ currentUser = user .Current
5164)
5265
5366var (
@@ -201,22 +214,26 @@ type Config struct {
201214 //
202215 // WorkRoot is used by all librarian commands.
203216 WorkRoot string
217+
218+ // commandName is the name of the command being executed.
219+ //
220+ // commandName is populated automatically after flag parsing. No user setup is
221+ // expected.
222+ commandName string
204223}
205224
206225// New returns a new Config populated with environment variables.
207- func New () * Config {
226+ func New (cmdName string ) * Config {
208227 return & Config {
228+ commandName : cmdName ,
209229 GitHubToken : os .Getenv ("LIBRARIAN_GITHUB_TOKEN" ),
210230 }
211231}
212232
213- // currentUser is a variable, so it can be replaced during testing.
214- var currentUser = user .Current
215-
216- // SetupUser performs late initialization of user-specific configuration,
233+ // setupUser performs late initialization of user-specific configuration,
217234// determining the current user. This is in a separate method as it
218235// can fail, and is called after flag parsing.
219- func (c * Config ) SetupUser () error {
236+ func (c * Config ) setupUser () error {
220237 user , err := currentUser ()
221238 if err != nil {
222239 return fmt .Errorf ("failed to get current user: %w" , err )
@@ -226,6 +243,55 @@ func (c *Config) SetupUser() error {
226243 return nil
227244}
228245
246+ func (c * Config ) createWorkRoot () error {
247+ if c .commandName == versionCmdName {
248+ return nil
249+ }
250+ if c .WorkRoot != "" {
251+ slog .Info ("Using specified working directory" , "dir" , c .WorkRoot )
252+ return nil
253+ }
254+ t := now ()
255+ path := filepath .Join (tempDir (), fmt .Sprintf ("librarian-%s" , formatTimestamp (t )))
256+
257+ _ , err := os .Stat (path )
258+ switch {
259+ case os .IsNotExist (err ):
260+ if err := os .Mkdir (path , 0755 ); err != nil {
261+ return fmt .Errorf ("unable to create temporary working directory '%s': %w" , path , err )
262+ }
263+ case err == nil :
264+ return fmt .Errorf ("temporary working directory already exists: %s" , path )
265+ default :
266+ return fmt .Errorf ("unable to check directory '%s': %w" , path , err )
267+ }
268+
269+ slog .Info ("Temporary working directory" , "dir" , path )
270+ c .WorkRoot = path
271+ return nil
272+ }
273+
274+ func (c * Config ) deriveRepo () error {
275+ if c .commandName == versionCmdName {
276+ return nil
277+ }
278+ if c .Repo != "" {
279+ slog .Debug ("repo value provided by user" , "repo" , c .Repo )
280+ return nil
281+ }
282+ wd , err := os .Getwd ()
283+ if err != nil {
284+ return fmt .Errorf ("getting working directory: %w" , err )
285+ }
286+ stateFile := filepath .Join (wd , LibrarianDir , pipelineStateFile )
287+ if _ , err := os .Stat (stateFile ); err != nil {
288+ return fmt .Errorf ("repo flag not specified and no state file found in current working directory: %w" , err )
289+ }
290+ slog .Info ("repo not specified, using current working directory as repo root" , "path" , wd )
291+ c .Repo = wd
292+ return nil
293+ }
294+
229295// IsValid ensures the values contained in a Config are valid.
230296func (c * Config ) IsValid () (bool , error ) {
231297 if c .Push && c .GitHubToken == "" {
@@ -247,9 +313,27 @@ func (c *Config) IsValid() (bool, error) {
247313 return false , err
248314 }
249315
316+ if c .commandName != versionCmdName && c .Repo == "" {
317+ return false , errors .New ("language repository not specified or detected" )
318+ }
319+
250320 return true , nil
251321}
252322
323+ // SetDefaults initializes values not set directly by the user.
324+ func (c * Config ) SetDefaults () error {
325+ if err := c .setupUser (); err != nil {
326+ return err
327+ }
328+ if err := c .createWorkRoot (); err != nil {
329+ return err
330+ }
331+ if err := c .deriveRepo (); err != nil {
332+ return err
333+ }
334+ return nil
335+ }
336+
253337func validateHostMount (hostMount , defaultValue string ) (bool , error ) {
254338 if hostMount == defaultValue {
255339 return true , nil
@@ -262,3 +346,8 @@ func validateHostMount(hostMount, defaultValue string) (bool, error) {
262346
263347 return true , nil
264348}
349+
350+ func formatTimestamp (t time.Time ) string {
351+ const yyyyMMddHHmmss = "20060102T150405Z" // Expected format by time library
352+ return t .Format (yyyyMMddHHmmss )
353+ }
0 commit comments