@@ -11,81 +11,126 @@ export type EnvironmentName = string;
11
11
/**
12
12
* Helpers for conda.
13
13
*/
14
- export class CondaHelper {
15
- /**
16
- * Return the string to display for the conda interpreter.
17
- */
18
- public getDisplayName ( condaInfo : CondaInfo = { } ) : string {
19
- // Samples.
20
- // "3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]".
21
- // "3.6.2 |Anaconda, Inc.| (default, Sep 21 2017, 18:29:43) \n[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]".
22
- const sysVersion = condaInfo [ 'sys.version' ] ;
23
- if ( ! sysVersion ) {
24
- return AnacondaDisplayName ;
25
- }
26
14
27
- // Take the second part of the sys.version.
28
- const sysVersionParts = sysVersion . split ( '|' , 2 ) ;
29
- if ( sysVersionParts . length === 2 ) {
30
- const displayName = sysVersionParts [ 1 ] . trim ( ) ;
31
- if ( this . isIdentifiableAsAnaconda ( displayName ) ) {
32
- return displayName ;
33
- } else {
34
- return `${ displayName } : ${ AnacondaDisplayName } ` ;
35
- }
15
+ /**
16
+ * Return the string to display for the conda interpreter.
17
+ */
18
+ export function getDisplayName ( condaInfo : CondaInfo = { } ) : string {
19
+ // Samples.
20
+ // "3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]".
21
+ // "3.6.2 |Anaconda, Inc.| (default, Sep 21 2017, 18:29:43) \n[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]".
22
+ const sysVersion = condaInfo [ 'sys.version' ] ;
23
+ if ( ! sysVersion ) {
24
+ return AnacondaDisplayName ;
25
+ }
26
+
27
+ // Take the second part of the sys.version.
28
+ const sysVersionParts = sysVersion . split ( '|' , 2 ) ;
29
+ if ( sysVersionParts . length === 2 ) {
30
+ const displayName = sysVersionParts [ 1 ] . trim ( ) ;
31
+ if ( isIdentifiableAsAnaconda ( displayName ) ) {
32
+ return displayName ;
36
33
} else {
37
- return AnacondaDisplayName ;
34
+ return ` ${ displayName } : ${ AnacondaDisplayName } ` ;
38
35
}
36
+ } else {
37
+ return AnacondaDisplayName ;
39
38
}
39
+ }
40
+
41
+ /**
42
+ * Parses output returned by the command `conda env list`.
43
+ * Sample output is as follows:
44
+ * # conda environments:
45
+ * #
46
+ * base * /Users/donjayamanne/anaconda3
47
+ * one /Users/donjayamanne/anaconda3/envs/one
48
+ * py27 /Users/donjayamanne/anaconda3/envs/py27
49
+ * py36 /Users/donjayamanne/anaconda3/envs/py36
50
+ * three /Users/donjayamanne/anaconda3/envs/three
51
+ * /Users/donjayamanne/anaconda3/envs/four
52
+ * /Users/donjayamanne/anaconda3/envs/five 5
53
+ * aaaa_bbbb_cccc_dddd_eeee_ffff_gggg /Users/donjayamanne/anaconda3/envs/aaaa_bbbb_cccc_dddd_eeee_ffff_gggg
54
+ * with*star /Users/donjayamanne/anaconda3/envs/with*star
55
+ * "/Users/donjayamanne/anaconda3/envs/seven "
56
+ */
57
+ export function parseCondaEnvFileContents (
58
+ condaEnvFileContents : string
59
+ ) : { name : string ; path : string ; isBase : boolean } [ ] | undefined {
60
+ // Don't trim the lines. `path` portion of the line can end with a space.
61
+ const lines = condaEnvFileContents . splitLines ( { trim : false } ) ;
62
+ const envs : { name : string ; path : string ; isBase : boolean } [ ] = [ ] ;
40
63
41
- /**
42
- * Parses output returned by the command `conda env list`.
43
- * Sample output is as follows:
44
- * # conda environments:
45
- * #
46
- * base * /Users/donjayamanne/anaconda3
47
- * one /Users/donjayamanne/anaconda3/envs/one
48
- * one two /Users/donjayamanne/anaconda3/envs/one two
49
- * py27 /Users/donjayamanne/anaconda3/envs/py27
50
- * py36 /Users/donjayamanne/anaconda3/envs/py36
51
- * three /Users/donjayamanne/anaconda3/envs/three
52
- * /Users/donjayamanne/anaconda3/envs/four
53
- * /Users/donjayamanne/anaconda3/envs/five 5
54
- * @param {string } condaEnvironmentList
55
- * @param {CondaInfo } condaInfo
56
- * @returns {{ name: string, path: string }[] | undefined }
57
- * @memberof CondaHelper
58
- */
59
- public parseCondaEnvironmentNames ( condaEnvironmentList : string ) : { name : string ; path : string } [ ] | undefined {
60
- const environments = condaEnvironmentList . splitLines ( { trim : false } ) ;
61
- const baseEnvironmentLine = environments . filter ( ( line ) => line . indexOf ( '*' ) > 0 ) ;
62
- if ( baseEnvironmentLine . length === 0 ) {
63
- return ;
64
+ lines . forEach ( ( line ) => {
65
+ const item = parseCondaEnvFileLine ( line ) ;
66
+ if ( item ) {
67
+ envs . push ( item ) ;
64
68
}
65
- const pathStartIndex = baseEnvironmentLine [ 0 ] . indexOf ( baseEnvironmentLine [ 0 ] . split ( '*' ) [ 1 ] . trim ( ) ) ;
66
- const envs : { name : string ; path : string } [ ] = [ ] ;
67
- environments . forEach ( ( line ) => {
68
- if ( line . length <= pathStartIndex ) {
69
- return ;
70
- }
71
- let name = line . substring ( 0 , pathStartIndex ) . trim ( ) ;
72
- if ( name . endsWith ( '*' ) ) {
73
- name = name . substring ( 0 , name . length - 1 ) . trim ( ) ;
74
- }
75
- const envPath = line . substring ( pathStartIndex ) . trim ( ) ;
76
- if ( envPath . length > 0 ) {
77
- envs . push ( { name, path : envPath } ) ;
78
- }
79
- } ) ;
69
+ } ) ;
80
70
81
- return envs ;
71
+ return envs . length > 0 ? envs : undefined ;
72
+ }
73
+
74
+ function parseCondaEnvFileLine ( line : string ) : { name : string ; path : string ; isBase : boolean } | undefined {
75
+ // Empty lines or lines starting with `#` are comments and can be ignored.
76
+ if ( line . length === 0 || line . startsWith ( '#' ) ) {
77
+ return undefined ;
82
78
}
83
79
84
- /**
85
- * Does the given string match a known Anaconda identifier.
86
- */
87
- private isIdentifiableAsAnaconda ( value : string ) {
88
- const valueToSearch = value . toLowerCase ( ) ;
89
- return AnacondaIdentifiers . some ( ( item ) => valueToSearch . indexOf ( item . toLowerCase ( ) ) !== - 1 ) ;
80
+ // If conda environment was created using `-p` then it may NOT have a name.
81
+ // Use empty string as default name for envs created using path only.
82
+ let name = '' ;
83
+ let remainder = line ;
84
+
85
+ // The `name` and `path` parts are separated by at least 5 spaces. We cannot
86
+ // use a single space here since it can be part of the name (see below for
87
+ // name spec). Another assumption here is that `name` does not start with
88
+ // 5*spaces or somewhere in the center. However, ` name` or `a b` is
89
+ // a valid name when using --clone. Highly unlikely that users will have this
90
+ // form as the environment name. lastIndexOf() can also be used but that assumes
91
+ // that `path` does NOT end with 5*spaces.
92
+ const spaceIndex = line . indexOf ( ' ' ) ;
93
+ if ( spaceIndex > 0 ) {
94
+ // Parsing `name`
95
+ // > `conda create -n <name>`
96
+ // conda environment `name` should NOT have following characters
97
+ // ('/', ' ', ':', '#'). So we can use the index of 5*space
98
+ // characters to extract the name.
99
+ //
100
+ // > `conda create --clone one -p "~/envs/one two"`
101
+ // this can generate a cloned env with name `one two`. This is
102
+ // only allowed for cloned environments. In both cases, the best
103
+ // separator is 5*spaces. It is highly unlikely that users will have
104
+ // 5*spaces in their environment name.
105
+ //
106
+ // Notes: When using clone if the path has a trailing space, it will
107
+ // not be preserved for the name. Trailing spaces in environment names
108
+ // are NOT allowed. But leading spaces are allowed. Currently there no
109
+ // special separator character between name and path, other than spaces.
110
+ // We will need a well known separator if this ever becomes a issue.
111
+ name = line . substring ( 0 , spaceIndex ) . trimRight ( ) ;
112
+ remainder = line . substring ( spaceIndex ) ;
90
113
}
114
+
115
+ // Detecting Base:
116
+ // Only `base` environment will have `*` between `name` and `path`. `name`
117
+ // or `path` can have `*` in them as well. So we have to look for `*` in
118
+ // between `name` and `path`. We already extracted the name, the next non-
119
+ // whitespace character should either be `*` or environment path.
120
+ remainder = remainder . trimLeft ( ) ;
121
+ const isBase = remainder . startsWith ( '*' ) ;
122
+
123
+ // Parsing `path`
124
+ // If `*` is the first then we can skip that character. Trim left again,
125
+ // don't do trim() or trimRight(), since paths can end with a space.
126
+ remainder = ( isBase ? remainder . substring ( 1 ) : remainder ) . trimLeft ( ) ;
127
+
128
+ return { name, path : remainder , isBase } ;
129
+ }
130
+ /**
131
+ * Does the given string match a known Anaconda identifier.
132
+ */
133
+ function isIdentifiableAsAnaconda ( value : string ) {
134
+ const valueToSearch = value . toLowerCase ( ) ;
135
+ return AnacondaIdentifiers . some ( ( item ) => valueToSearch . indexOf ( item . toLowerCase ( ) ) !== - 1 ) ;
91
136
}
0 commit comments