1+ /**Indepth analyzer */
2+ export async function indepth ( { login, data, imports} , { skipped} ) {
3+ //Check prerequisites
4+ if ( ! await imports . which ( "github-linguist" ) )
5+ throw new Error ( "Feature requires github-linguist" )
6+
7+ //Compute repositories stats from fetched repositories
8+ const results = { total :0 , stats :{ } }
9+ for ( const repository of data . user . repositories . nodes ) {
10+ //Skip repository if asked
11+ if ( ( skipped . includes ( repository . name . toLocaleLowerCase ( ) ) ) || ( skipped . includes ( `${ repository . owner . login } /${ repository . name } ` . toLocaleLowerCase ( ) ) ) ) {
12+ console . debug ( `metrics/compute/${ login } /plugins > languages > skipped repository ${ repository . owner . login } /${ repository . name } ` )
13+ continue
14+ }
15+
16+ //Repository handle
17+ const repo = `${ repository . owner . login } /${ repository . name } `
18+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > checking ${ repo } ` )
19+
20+ //Temporary directory
21+ const path = imports . paths . join ( imports . os . tmpdir ( ) , `${ data . user . databaseId } -${ repo . replace ( / [ ^ \w ] / g, "_" ) } ` )
22+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > cloning ${ repo } to temp dir ${ path } ` )
23+
24+ //Process
25+ try {
26+ //Git clone into temporary directory
27+ await imports . fs . rmdir ( path , { recursive :true } )
28+ await imports . fs . mkdir ( path , { recursive :true } )
29+ const git = await imports . git ( path )
30+ await git . clone ( `https://github.com/${ repo } ` , "." ) . status ( )
31+
32+ //Analyze repository
33+ await analyze ( arguments [ 0 ] , { results, path} )
34+ }
35+ catch {
36+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > an error occured while processing ${ repo } , skipping...` )
37+ }
38+ finally {
39+ //Cleaning
40+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > cleaning temp dir ${ path } ` )
41+ await imports . fs . rmdir ( path , { recursive :true } )
42+ }
43+ }
44+ return results
45+ }
46+
47+ /**Recent languages activity */
48+ export async function recent ( { login, data, imports, rest, account} , { skipped} ) {
49+ //Check prerequisites
50+ if ( ! await imports . which ( "github-linguist" ) )
51+ throw new Error ( "Feature requires github-linguist" )
52+
53+ //Get user recent activity
54+ console . debug ( `metrics/compute/${ login } /plugins > languages > querying api` )
55+ const commits = [ ] , days = 14 , pages = 3 , results = { total :0 , stats :{ } }
56+ try {
57+ for ( let page = 1 ; page <= pages ; page ++ ) {
58+ console . debug ( `metrics/compute/${ login } /plugins > languages > loading page ${ page } ` )
59+ commits . push ( ...( await rest . activity . listEventsForAuthenticatedUser ( { username :login , per_page :100 , page} ) ) . data
60+ . filter ( ( { type} ) => type === "PushEvent" )
61+ . filter ( ( { actor} ) => account === "organization" ? true : actor . login === login )
62+ . filter ( ( { repo :{ name :repo } } ) => ( ! skipped . includes ( repo . toLocaleLowerCase ( ) ) ) && ( ! skipped . includes ( repo . toLocaleLowerCase ( ) . split ( "/" ) . pop ( ) ) ) )
63+ . filter ( ( { created_at} ) => new Date ( created_at ) > new Date ( Date . now ( ) - days * 24 * 60 * 60 * 1000 ) )
64+ )
65+ }
66+ }
67+ catch {
68+ console . debug ( `metrics/compute/${ login } /plugins > languages > no more page to load` )
69+ }
70+ console . debug ( `metrics/compute/${ login } /plugins > languages > ${ commits . length } commits loaded` )
71+
72+ //Retrieve edited files and filter edited lines (those starting with +/-) from patches
73+ console . debug ( `metrics/compute/${ login } /plugins > languages > loading patches` )
74+ const patches = [
75+ ...await Promise . allSettled (
76+ commits
77+ . flatMap ( ( { payload} ) => payload . commits ) . map ( commit => commit . url )
78+ . map ( async commit => ( await rest . request ( commit ) ) . data . files ) ,
79+ ) ,
80+ ]
81+ . filter ( ( { status} ) => status === "fulfilled" )
82+ . map ( ( { value} ) => value )
83+ . flatMap ( files => files . map ( file => ( { name :imports . paths . basename ( file . filename ) , patch :file . patch ?? "" } ) ) )
84+ . map ( ( { name, patch} ) => ( { name, patch :patch . split ( "\n" ) . filter ( line => / ^ [ + ] / . test ( line ) ) . map ( line => line . substring ( 1 ) ) . join ( "\n" ) } ) )
85+
86+ //Temporary directory
87+ const path = imports . paths . join ( imports . os . tmpdir ( ) , `${ data . user . databaseId } ` )
88+ console . debug ( `metrics/compute/${ login } /plugins > languages > creating temp dir ${ path } with ${ patches . length } files` )
89+
90+ //Process
91+ try {
92+ //Save patches in temporary directory
93+ await imports . fs . rmdir ( path , { recursive :true } )
94+ await imports . fs . mkdir ( path , { recursive :true } )
95+ await Promise . all ( patches . map ( ( { name, patch} , i ) => imports . fs . writeFile ( imports . paths . join ( path , `${ i } ${ imports . paths . extname ( name ) } ` ) , patch ) ) )
96+
97+ //Create temporary git repository
98+ console . debug ( `metrics/compute/${ login } /plugins > languages > creating temp git repository` )
99+ const git = await imports . git ( path )
100+ await git . init ( ) . add ( "." ) . addConfig ( "user.name" , login ) . addConfig ( "user.email" , "<>" ) . commit ( "linguist" ) . status ( )
101+
102+ //Analyze repository
103+ await analyze ( arguments [ 0 ] , { results, path} )
104+ }
105+ catch {
106+ console . debug ( `metrics/compute/${ login } /plugins > languages > an error occured while processing recently used languages` )
107+ }
108+ finally {
109+ //Cleaning
110+ console . debug ( `metrics/compute/${ login } /plugins > languages > cleaning temp dir ${ path } ` )
111+ await imports . fs . rmdir ( path , { recursive :true } )
112+ }
113+
114+ console . log ( results )
115+ return results
116+ }
117+
118+ /**Analyze a single repository */
119+ async function analyze ( { login, imports} , { results, path} ) {
120+ //Spawn linguist process and map files to languages
121+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > running linguist` )
122+ const files = Object . fromEntries ( Object . entries ( JSON . parse ( await imports . run ( "github-linguist --json" , { cwd :path } , { log :false } ) ) ) . flatMap ( ( [ lang , files ] ) => files . map ( file => [ file , lang ] ) ) )
123+
124+ console . log ( files )
125+
126+ //Processing diff
127+ const per_page = 10
128+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > checking git log` )
129+ for ( let page = 0 ; ; page ++ ) {
130+ try {
131+ const stdout = await imports . run ( `git log --author="${ login } " --format="" --patch --max-count=${ per_page } --skip=${ page * per_page } ` , { cwd :path } , { log :false } )
132+ let file = null , lang = null
133+ if ( ! stdout . trim ( ) . length ) {
134+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > no more commits` )
135+ break
136+ }
137+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > processing commits ${ page * per_page } from ${ ( page + 1 ) * per_page } ` )
138+ for ( const line of stdout . split ( "\n" ) . map ( line => line . trim ( ) ) ) {
139+ //Ignore empty lines or unneeded lines
140+ if ( ( ! / ^ [ + ] / . test ( line ) ) || ( ! line . length ) )
141+ continue
142+ //File marker
143+ if ( / ^ [ + ] { 3 } \s b [ / ] (?< file > [ \s \S ] + ) $ / . test ( line ) ) {
144+ file = line . match ( / ^ [ + ] { 3 } \s b [ / ] (?< file > [ \s \S ] + ) $ / ) ?. groups ?. file ?? null
145+ lang = files [ file ] ?? null
146+ continue
147+ }
148+ //Ignore unkonwn languages
149+ if ( ! lang )
150+ continue
151+ //Added line marker
152+ if ( / ^ [ + ] \s (?< line > [ \s \S ] + ) $ / . test ( line ) ) {
153+ const size = Buffer . byteLength ( line . match ( / ^ [ + ] \s (?< line > [ \s \S ] + ) $ / ) ?. groups ?. line ?? "" , "utf-8" )
154+ results . stats [ lang ] = ( results . stats [ lang ] ?? 0 ) + size
155+ results . total += size
156+ }
157+ }
158+ }
159+ catch {
160+ console . debug ( `metrics/compute/${ login } /plugins > languages > indepth > an error occured on page ${ page } , skipping...` )
161+ }
162+ }
163+
164+ }
0 commit comments